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, + ) + } + } + }) + } +}