From e0b836a4bc5c07e52e83e913cc8665976a5f1b67 Mon Sep 17 00:00:00 2001 From: Piotr Dyraga Date: Thu, 26 Jul 2018 08:53:23 +0200 Subject: [PATCH] Generate safe prime concurrently How fast we generate a prime number is mostly a matter of luck. It varies from miliseconds to seconds and depends how lucky we are with drawing bytes. With today's multicore processors, we can execute the process on multiple cores concurrently, accept the first valid result and cancel the rest of work. This way, with the same algorithm, we get the result faster. --- safe_prime_generator.go | 224 +++++++++++++++++++++++------------ safe_prime_generator_test.go | 98 +++++++++++++++ 2 files changed, 244 insertions(+), 78 deletions(-) create mode 100644 safe_prime_generator_test.go diff --git a/safe_prime_generator.go b/safe_prime_generator.go index 4266472..8d9e2fa 100644 --- a/safe_prime_generator.go +++ b/safe_prime_generator.go @@ -5,9 +5,14 @@ package paillier import ( + "context" + "crypto/rand" "errors" + "fmt" "io" "math/big" + "sync" + "time" ) // smallPrimes is a list of small, prime numbers that allows us to rapidly @@ -25,102 +30,165 @@ var smallPrimes = []uint8{ // operations. var smallPrimesProduct = new(big.Int).SetUint64(16294579238595022365) -// Prime returns a number, p, of the given size, such that p is prime -// with high probability. -// Prime will return error for any error returned by rand.Read or if bits < 2. -func SafePrimes(rand io.Reader, bits int) (p *big.Int, q *big.Int, err error) { - qbits := bits - 1 +type safePrime struct { + p *big.Int + q *big.Int +} - if qbits < 2 { - err = errors.New("crypto/rand: prime size must be at least 2-bit") - return +func GenerateSafePrime( + bitLen int, + concurrencyLevel int, + timeout time.Duration, +) (*big.Int, *big.Int, error) { + if bitLen < 6 { + return nil, nil, errors.New("safe prime size must be at least 6 bits") } - b := uint(qbits % 8) + primeChan := make(chan safePrime, 1) + errChan := make(chan error, 1) + + defer close(primeChan) + defer close(errChan) + + mutex := &sync.Mutex{} + + ctx, cancel := context.WithCancel(context.Background()) + + for i := 0; i < concurrencyLevel; i++ { + runGenPrimeRoutine(ctx, primeChan, errChan, mutex, rand.Reader, bitLen) + } + + go func() { + time.Sleep(timeout) + mutex.Lock() + cancel() + mutex.Unlock() + }() + + select { + case result := <-primeChan: + mutex.Lock() + cancel() + mutex.Unlock() + return result.p, result.q, nil + case err := <-errChan: + mutex.Lock() + cancel() + mutex.Unlock() + return nil, nil, err + case <-ctx.Done(): + return nil, nil, fmt.Errorf("generator timed out after %v", timeout) + } +} + +func runGenPrimeRoutine( + ctx context.Context, + primeChan chan safePrime, + errChan chan error, + mutex *sync.Mutex, + rand io.Reader, + pBitLen int, +) { + qBitLen := pBitLen - 1 + b := uint(qBitLen % 8) if b == 0 { b = 8 } - bytes := make([]byte, (qbits+7)/8) - p = new(big.Int) - q = new(big.Int) + bytes := make([]byte, (qBitLen+7)/8) + p := new(big.Int) + q := new(big.Int) bigMod := new(big.Int) - //NextRand: - for { - _, err = io.ReadFull(rand, bytes) - if err != nil { - return nil, nil, err - } + go func() { + for { + select { + case <-ctx.Done(): + return + default: + _, err := io.ReadFull(rand, bytes) + if err != nil { + errChan <- err + return + } - // Clear bits in the first byte to make sure the candidate has a size <= bits. - bytes[0] &= uint8(int(1<= 2 { - bytes[0] |= 3 << (b - 2) - } else { - // Here b==1, because b cannot be zero. - bytes[0] |= 1 - if len(bytes) > 1 { - bytes[1] |= 0x80 - } - } - // Make the value odd since an even number this large certainly isn't prime. - bytes[len(bytes)-1] |= 1 + // Clear bits in the first byte to make sure the candidate has a size <= bits. + bytes[0] &= uint8(int(1<= 2 { + bytes[0] |= 3 << (b - 2) + } else { + // Here b==1, because b cannot be zero. + bytes[0] |= 1 + if len(bytes) > 1 { + bytes[1] |= 0x80 + } + } + // Make the value odd since an even number this large certainly isn't prime. + bytes[len(bytes)-1] |= 1 - q.SetBytes(bytes) + q.SetBytes(bytes) - p.Mul(q, big.NewInt(2)) - p.Add(p, big.NewInt(1)) + p.Mul(q, big.NewInt(2)) + p.Add(p, big.NewInt(1)) - // Calculate the value mod the product of smallPrimes. If it's - // a multiple of any of these primes we add two until it isn't. - // The probability of overflowing is minimal and can be ignored - // because we still perform Miller-Rabin tests on the result. - bigMod.Mod(q, smallPrimesProduct) - mod := bigMod.Uint64() + // Calculate the value mod the product of smallPrimes. If it's + // a multiple of any of these primes we add two until it isn't. + // The probability of overflowing is minimal and can be ignored + // because we still perform Miller-Rabin tests on the result. + bigMod.Mod(q, smallPrimesProduct) + mod := bigMod.Uint64() - NextDelta: - for delta := uint64(0); delta < 1<<20; delta += 2 { - m := mod + delta - for _, prime := range smallPrimes { - if m%uint64(prime) == 0 && (qbits > 6 || m != uint64(prime)) { - continue NextDelta + NextDelta: + for delta := uint64(0); delta < 1<<20; delta += 2 { + m := mod + delta + for _, prime := range smallPrimes { + if m%uint64(prime) == 0 && (qBitLen > 6 || m != uint64(prime)) { + continue NextDelta + } + } + + if delta > 0 { + bigMod.SetUint64(delta) + q.Add(q, bigMod) + } + + qMod3 := new(big.Int).Mod(q, big.NewInt(3)) + if qMod3.Cmp(big.NewInt(1)) == 0 { + continue NextDelta + } + + p.Mul(q, big.NewInt(2)) + p.Add(p, big.NewInt(1)) + if !isPrimeCandidate(p) { + continue NextDelta + } + + break + } + + // There is a tiny possibility that, by adding delta, we caused + // the number to be one bit too long. Thus we check BitLen + // here. + if q.ProbablyPrime(20) && + isPocklingtonCriterionSatisfied(p) && + q.BitLen() == qBitLen { + + mutex.Lock() + if ctx.Err() == nil { + primeChan <- safePrime{p, q} + } + mutex.Unlock() + + return } } - - if delta > 0 { - bigMod.SetUint64(delta) - q.Add(q, bigMod) - } - - qMod3 := new(big.Int).Mod(q, big.NewInt(3)) - if qMod3.Cmp(big.NewInt(1)) == 0 { - continue NextDelta - } - - p.Mul(q, big.NewInt(2)) - p.Add(p, big.NewInt(1)) - if !isPrimeCandidate(p) { - continue NextDelta - } - - break } - - // There is a tiny possibility that, by adding delta, we caused - // the number to be one bit too long. Thus we check BitLen - // here. - if q.ProbablyPrime(20) && - isPocklingtonCriterionSatisfied(p) && - q.BitLen() == qbits { - return - } - } + }() } func isPocklingtonCriterionSatisfied(p *big.Int) bool { diff --git a/safe_prime_generator_test.go b/safe_prime_generator_test.go new file mode 100644 index 0000000..a0d1ba9 --- /dev/null +++ b/safe_prime_generator_test.go @@ -0,0 +1,98 @@ +package paillier + +import ( + "errors" + "fmt" + "math/big" + "reflect" + "testing" + "time" +) + +func TestAsyncGenerator(t *testing.T) { + concurrencyLevel := 4 + + var tests = map[string]struct { + bitLen int + timeout time.Duration + expectedError error + }{ + "primes successfully generated": { + bitLen: 512, + timeout: 60 * time.Second, + expectedError: nil, + }, + "generator timed out": { + bitLen: 8192, + timeout: 1 * time.Second, + expectedError: errors.New("generator timed out after 1s"), + }, + "bit length is 5": { + bitLen: 5, + timeout: 1 * time.Second, + expectedError: errors.New("safe prime size must be at least 6 bits"), + }, + "bit length is 6": { + bitLen: 6, + timeout: 60 * time.Second, + expectedError: nil, + }, + } + + for testName, test := range tests { + t.Run(testName, func(t *testing.T) { + p, q, err := GenerateSafePrime( + test.bitLen, + concurrencyLevel, + test.timeout, + ) + + if test.expectedError != nil { + if !reflect.DeepEqual(test.expectedError, err) { + t.Fatalf( + "Unexpected error\nActual: %v\nExpected: %v", + err, + test.expectedError, + ) + } + } else { + if err != nil { + t.Fatal(err) + } + + if !p.ProbablyPrime(20) { + t.Errorf("p is not prime; p = %v", p) + } + + if !q.ProbablyPrime(20) { + t.Errorf("q is not prime; q = %v", q) + } + + // p = 2q + 1 ? + expectedP := new(big.Int) + expectedP.Mul(big.NewInt(2), q) + expectedP.Add(expectedP, big.NewInt(1)) + + if expectedP.Cmp(p) != 0 { + t.Errorf("2q + 1 != p") + } + + if len(p.Bytes()) != test.bitLen { + fmt.Errorf( + "Unexpected p bit length\nActual: %v\nExpected: %v", + len(p.Bytes()), + test.bitLen, + ) + } + + if len(q.Bytes()) != test.bitLen-1 { + fmt.Errorf( + "Unexpected q bit length\nActual: %v\nExpected: %v", + len(q.Bytes()), + test.bitLen-1, + ) + } + } + }) + } +}