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