Improved performance of the safe prime generator #9
No files matched your search
+86
-18
@@ -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,33 +30,87 @@ 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
|
||||
|
||||
if qbits < 2 {
|
||||
err = errors.New("crypto/rand: prime size must be at least 2-bit")
|
||||
return
|
||||
type safePrime struct {
|
||||
p *big.Int
|
||||
q *big.Int
|
||||
}
|
||||
|
||||
b := uint(qbits % 8)
|
||||
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")
|
||||
}
|
||||
|
||||
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:
|
||||
go func() {
|
||||
for {
|
||||
_, err = io.ReadFull(rand, bytes)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
_, err := io.ReadFull(rand, bytes)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
errChan <- err
|
||||
return
|
||||
}
|
||||
|
||||
// Clear bits in the first byte to make sure the candidate has a size <= bits.
|
||||
@@ -88,7 +147,7 @@ func SafePrimes(rand io.Reader, bits int) (p *big.Int, q *big.Int, err error) {
|
||||
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)) {
|
||||
if m%uint64(prime) == 0 && (qBitLen > 6 || m != uint64(prime)) {
|
||||
continue NextDelta
|
||||
}
|
||||
}
|
||||
@@ -117,11 +176,20 @@ func SafePrimes(rand io.Reader, bits int) (p *big.Int, q *big.Int, err error) {
|
||||
// here.
|
||||
if q.ProbablyPrime(20) &&
|
||||
isPocklingtonCriterionSatisfied(p) &&
|
||||
q.BitLen() == qbits {
|
||||
q.BitLen() == qBitLen {
|
||||
|
||||
mutex.Lock()
|
||||
if ctx.Err() == nil {
|
||||
primeChan <- safePrime{p, q}
|
||||
}
|
||||
mutex.Unlock()
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func isPocklingtonCriterionSatisfied(p *big.Int) bool {
|
||||
return new(big.Int).Exp(
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user