Improved performance of the safe prime generator #9

Merged
pdyraga merged 17 commits from master into master 2018-08-02 06:47:13 -04:00
2 changed files with 183 additions and 17 deletions
Showing only changes of commit e0b836a4bc - Show all commits

No files matched your search

+85 -17
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,33 +30,87 @@ 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 {
case <-ctx.Done():
return
default:
_, err := io.ReadFull(rand, bytes)
if err != nil { 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. // 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 { 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
} }
} }
@@ -117,10 +176,19 @@ func SafePrimes(rand io.Reader, bits int) (p *big.Int, q *big.Int, err error) {
// here. // here.
if q.ProbablyPrime(20) && if q.ProbablyPrime(20) &&
isPocklingtonCriterionSatisfied(p) && isPocklingtonCriterionSatisfied(p) &&
q.BitLen() == qbits { q.BitLen() == qBitLen {
mutex.Lock()
if ctx.Err() == nil {
primeChan <- safePrime{p, q}
}
mutex.Unlock()
return 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,
)
}
}
})
}
}