Improved performance of the safe prime generator #9
No files matched your search
+85
-17
@@ -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 {
|
||||||
|
|||||||
@@ -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