-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Improve performance by avoiding scalar mult during search phase of key.
- Loading branch information
Showing
8 changed files
with
334 additions
and
62 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
package ed25519 | ||
|
||
import ( | ||
"bytes" | ||
cryptorand "crypto/rand" | ||
"crypto/sha512" | ||
"errors" | ||
"fmt" | ||
"io" | ||
|
||
"github.com/oasisprotocol/curve25519-voi/curve" | ||
"github.com/oasisprotocol/curve25519-voi/curve/scalar" | ||
) | ||
|
||
const ( | ||
// PublicKeySize is the size, in bytes, of public keys as used in this package. | ||
PublicKeySize = 32 | ||
|
||
// PrivateKeySize is the size, in bytes, of private keys as used in this package. | ||
PrivateKeySize = 64 | ||
|
||
// SeedSize is the size, in bytes, of private key seeds. | ||
SeedSize = 32 | ||
) | ||
|
||
// PrivateKey is the type of Ed25519 private keys. | ||
type PrivateKey []byte | ||
|
||
// PublicKey is the type of Ed25519 public keys. | ||
type PublicKey []byte | ||
|
||
// KeyPair is a type with both Ed25519 keys. | ||
type KeyPair struct { | ||
// PublicKey is the public key of the Ed25519 key pair. | ||
PublicKey PublicKey | ||
|
||
// PrivateKey is the private key of the Ed25519 key pair. | ||
PrivateKey PrivateKey | ||
} | ||
|
||
// Validate performs sanity checks to ensure that the public and private keys match. | ||
func (kp *KeyPair) Validate() error { | ||
pk, err := getPublicKeyFromPrivateKey(kp.PrivateKey) | ||
if err != nil { | ||
return fmt.Errorf("could not compute public key from private key: %w", err) | ||
} | ||
|
||
if !bytes.Equal(kp.PublicKey, pk) { | ||
return errors.New("keys do not match") | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func GenerateKey(rand io.Reader) (*KeyPair, error) { | ||
if rand == nil { | ||
rand = cryptorand.Reader | ||
} | ||
|
||
seed := make([]byte, SeedSize) | ||
if _, err := io.ReadFull(rand, seed); err != nil { | ||
return nil, err | ||
} | ||
|
||
sk := make([]byte, PrivateKeySize) | ||
newKeyFromSeed(sk, seed) | ||
|
||
// Private key does not contain the public key in this implementation, so we | ||
// need to compute it instead. | ||
pk, err := getPublicKeyFromPrivateKey(sk) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return &KeyPair{ | ||
PublicKey: pk, | ||
PrivateKey: sk, | ||
}, nil | ||
} | ||
|
||
func newKeyFromSeed(sk, seed []byte) { | ||
if l := len(seed); l != SeedSize { | ||
panic(fmt.Sprintf("bad seed length: %d", l)) | ||
} | ||
|
||
digest := sha512.Sum512(seed) | ||
clampSecretKey(&digest) | ||
copy(sk, digest[:]) | ||
} | ||
|
||
func getPublicKeyFromPrivateKey(sk []byte) ([]byte, error) { | ||
if l := len(sk); l != PrivateKeySize { | ||
panic(fmt.Errorf("bad private key length: %d", len(sk))) | ||
} | ||
|
||
sc, err := scalar.NewFromBits(sk[:scalar.ScalarSize]) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
pk := curve.NewCompressedEdwardsY() | ||
pk.SetEdwardsPoint(curve.NewEdwardsPoint().MulBasepoint(curve.ED25519_BASEPOINT_TABLE, sc)) | ||
|
||
return pk[:], nil | ||
} | ||
|
||
func clampSecretKey(sk *[64]byte) { | ||
sk[0] &= 248 | ||
sk[31] &= 63 | ||
sk[31] |= 64 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package ed25519_test | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/innix/shrek/internal/ed25519" | ||
) | ||
|
||
func BenchmarkGenerateNewKey(b *testing.B) { | ||
for i := 0; i < b.N; i++ { | ||
_, err := ed25519.GenerateKey(nil) | ||
if err != nil { | ||
b.Fatal(err) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
package ed25519 | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"io" | ||
"math" | ||
|
||
"github.com/oasisprotocol/curve25519-voi/curve" | ||
"github.com/oasisprotocol/curve25519-voi/curve/scalar" | ||
) | ||
|
||
type keyIterator struct { | ||
kp *KeyPair | ||
eightPt *curve.EdwardsPoint | ||
|
||
pt *curve.EdwardsPoint | ||
sc *scalar.Scalar | ||
|
||
counter uint64 | ||
} | ||
|
||
// NewKeyIterator creates and initializes a new Ed25519 key iterator. | ||
// The iterator is NOT thread safe; you must create a separate iterator for | ||
// each worker instead of sharing a single instance. | ||
func NewKeyIterator(rand io.Reader) (*keyIterator, error) { | ||
eightPt := curve.NewEdwardsPoint() | ||
eightPt = eightPt.MulBasepoint(curve.ED25519_BASEPOINT_TABLE, scalar.NewFromUint64(8)) | ||
|
||
it := &keyIterator{ | ||
eightPt: eightPt, | ||
} | ||
if _, err := it.init(rand); err != nil { | ||
return nil, err | ||
} | ||
|
||
return it, nil | ||
} | ||
|
||
func (it *keyIterator) Next() bool { | ||
const maxCounter = math.MaxUint64 - 8 | ||
|
||
if it.counter > uint64(maxCounter) { | ||
return false | ||
} | ||
|
||
it.pt = it.pt.Add(it.pt, it.eightPt) | ||
it.counter += 8 | ||
|
||
return true | ||
} | ||
|
||
func (it *keyIterator) PublicKey() PublicKey { | ||
var pk curve.CompressedEdwardsY | ||
pk.SetEdwardsPoint(it.pt) | ||
|
||
return pk[:] | ||
} | ||
|
||
func (it *keyIterator) PrivateKey() (PrivateKey, error) { | ||
sc := scalar.New().Set(it.sc) | ||
|
||
if it.counter > 0 { | ||
if err := scalarAdd(sc, it.counter); err != nil { | ||
return nil, err | ||
} | ||
} | ||
|
||
sk := make([]byte, PrivateKeySize) | ||
if err := sc.ToBytes(sk[:scalar.ScalarSize]); err != nil { | ||
return nil, fmt.Errorf("could not pack scalar into byte array: %w", err) | ||
} | ||
copy(sk[scalar.ScalarSize:], it.kp.PrivateKey[scalar.ScalarSize:]) | ||
|
||
// Sanity check. | ||
if !((sk[0] & 248) == sk[0]) || !(((sk[31] & 63) | 64) == sk[31]) { | ||
return nil, errors.New("sanity check on private key failed") | ||
} | ||
|
||
return sk, nil | ||
} | ||
|
||
func (it *keyIterator) init(rand io.Reader) (*KeyPair, error) { | ||
kp, err := GenerateKey(rand) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// Parse private key. | ||
sk, err := scalar.NewFromBits(kp.PrivateKey[:scalar.ScalarSize]) | ||
if err != nil { | ||
return nil, fmt.Errorf("could not parse scalar from private key: %w", err) | ||
} | ||
|
||
// Parse public key. | ||
cpt, err := curve.NewCompressedEdwardsYFromBytes(kp.PublicKey) | ||
if err != nil { | ||
return nil, fmt.Errorf("could not parse point from public key: %w", err) | ||
} | ||
pk := curve.NewEdwardsPoint() | ||
if _, err := pk.SetCompressedY(cpt); err != nil { | ||
return nil, fmt.Errorf("could not decompress point from public key: %w", err) | ||
} | ||
|
||
// Cache data so it can be used later. | ||
it.kp = kp | ||
it.sc = sk | ||
it.pt = pk | ||
|
||
// Reset counter. | ||
it.counter = 0 | ||
|
||
return kp, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package ed25519_test | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/innix/shrek/internal/ed25519" | ||
) | ||
|
||
func BenchmarkKeyIterator_PublicKeyAndNext(b *testing.B) { | ||
it, err := ed25519.NewKeyIterator(nil) | ||
if err != nil { | ||
b.Fatalf("Could not create key iterator: %v", err) | ||
} | ||
|
||
for i := 0; i < b.N; i++ { | ||
_ = it.PublicKey() | ||
if err != nil { | ||
b.Fatal(err) | ||
} | ||
it.Next() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package ed25519 | ||
|
||
import ( | ||
"github.com/oasisprotocol/curve25519-voi/curve/scalar" | ||
) | ||
|
||
func scalarAdd(dst *scalar.Scalar, v uint64) error { | ||
var dstb [32]byte | ||
|
||
if err := dst.ToBytes(dstb[:]); err != nil { | ||
return err | ||
} | ||
|
||
scalarAddBytes(&dstb, v) | ||
|
||
if _, err := dst.SetBits(dstb[:]); err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func scalarAddBytes(dst *[32]byte, v uint64) { | ||
var carry uint32 | ||
|
||
for i := 0; i < 32; i++ { | ||
carry += uint32(dst[i]) + uint32(v&0xFF) | ||
dst[i] = byte(carry & 0xFF) | ||
carry >>= 8 | ||
|
||
v >>= 8 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.