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 184 additions and 18 deletions
Showing only changes of commit e0b836a4bc - Show all commits

No files matched your search

+86 -18
View File
@@ -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(
+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,
)
}
}
})
}
}