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.
This commit is contained in:
Piotr Dyraga committed 2018-07-26 08:53:23 +02:00
1 parent f5a107a36f
commit e0b836a4bc
2 files changed
+244 -78

No files matched your search

+146 -78
View File
@@ -5,9 +5,14 @@
package paillier package paillier
import ( import (
"context"
"crypto/rand"
"errors" "errors"
"fmt"
"io" "io"
"math/big" "math/big"
"sync"
"time"
) )
// smallPrimes is a list of small, prime numbers that allows us to rapidly // smallPrimes is a list of small, prime numbers that allows us to rapidly
@@ -25,102 +30,165 @@ var smallPrimes = []uint8{
// operations. // operations.
var smallPrimesProduct = new(big.Int).SetUint64(16294579238595022365) var smallPrimesProduct = new(big.Int).SetUint64(16294579238595022365)
// Prime returns a number, p, of the given size, such that p is prime type safePrime struct {
// with high probability. p *big.Int
// Prime will return error for any error returned by rand.Read or if bits < 2. q *big.Int
func SafePrimes(rand io.Reader, bits int) (p *big.Int, q *big.Int, err error) { }
qbits := bits - 1
if qbits < 2 { func GenerateSafePrime(
err = errors.New("crypto/rand: prime size must be at least 2-bit") bitLen int,
return 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 { if b == 0 {
b = 8 b = 8
} }
bytes := make([]byte, (qbits+7)/8) bytes := make([]byte, (qBitLen+7)/8)
p = new(big.Int) p := new(big.Int)
q = new(big.Int) q := new(big.Int)
bigMod := new(big.Int) bigMod := new(big.Int)
//NextRand: go func() {
for { for {
_, err = io.ReadFull(rand, bytes) select {
if err != nil { case <-ctx.Done():
return nil, nil, err 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. // Clear bits in the first byte to make sure the candidate has a size <= bits.
bytes[0] &= uint8(int(1<<b) - 1) bytes[0] &= uint8(int(1<<b) - 1)
// Don't let the value be too small, i.e, set the most significant two bits. // Don't let the value be too small, i.e, set the most significant two bits.
// Setting the top two bits, rather than just the top bit, // Setting the top two bits, rather than just the top bit,
// means that when two of these values are multiplied together, // means that when two of these values are multiplied together,
// the result isn't ever one bit short. // the result isn't ever one bit short.
if b >= 2 { if b >= 2 {
bytes[0] |= 3 << (b - 2) bytes[0] |= 3 << (b - 2)
} else { } else {
// Here b==1, because b cannot be zero. // Here b==1, because b cannot be zero.
bytes[0] |= 1 bytes[0] |= 1
if len(bytes) > 1 { if len(bytes) > 1 {
bytes[1] |= 0x80 bytes[1] |= 0x80
} }
} }
// Make the value odd since an even number this large certainly isn't prime. // Make the value odd since an even number this large certainly isn't prime.
bytes[len(bytes)-1] |= 1 bytes[len(bytes)-1] |= 1
q.SetBytes(bytes) q.SetBytes(bytes)
p.Mul(q, big.NewInt(2)) p.Mul(q, big.NewInt(2))
p.Add(p, big.NewInt(1)) p.Add(p, big.NewInt(1))
// Calculate the value mod the product of smallPrimes. If it's // 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. // a multiple of any of these primes we add two until it isn't.
// The probability of overflowing is minimal and can be ignored // The probability of overflowing is minimal and can be ignored
// because we still perform Miller-Rabin tests on the result. // because we still perform Miller-Rabin tests on the result.
bigMod.Mod(q, smallPrimesProduct) bigMod.Mod(q, smallPrimesProduct)
mod := bigMod.Uint64() mod := bigMod.Uint64()
NextDelta: NextDelta:
for delta := uint64(0); delta < 1<<20; delta += 2 { for delta := uint64(0); delta < 1<<20; delta += 2 {
m := mod + delta m := mod + delta
for _, prime := range smallPrimes { for _, prime := range smallPrimes {
if m%uint64(prime) == 0 && (qbits > 6 || m != uint64(prime)) { if m%uint64(prime) == 0 && (qBitLen > 6 || m != uint64(prime)) {
continue NextDelta 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 { func isPocklingtonCriterionSatisfied(p *big.Int) bool {
+98
View File
@@ -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,
)
}
}
})
}
}