diff --git a/primitives/sr25519/keys.go b/primitives/sr25519/keys.go index f128eae..a37cbd5 100644 --- a/primitives/sr25519/keys.go +++ b/primitives/sr25519/keys.go @@ -252,6 +252,34 @@ func NewSecretKeyFromBytes(b []byte) (*SecretKey, error) { return &sk, nil } +// NewSecretKeyFromEd25519Bytes constructs a SecretKey from the byte representation +// of an expanded Ed25519 key. +func NewSecretKeyFromEd25519Bytes(b []byte) (*SecretKey, error) { + if len(b) != SecretKeySize { + return nil, fmt.Errorf("sr25519: invalid expanded ed25519 key length") + } + + // Check the scalar is valid, s == clamp(s) + s := b[:SecretKeyScalarSize] + if s[0] != (s[0]&0b1111_1000) || (s[31] != ((s[31] & 0b0011_1111) | 0b0100_0000)) { + return nil, fmt.Errorf("sr25519: ed25519 scalar invalid") + } + + scalar, err := scalarDivideByCofactor(b[:SecretKeyScalarSize]) + if err != nil { + return nil, err + } + + var nonce [SecretKeyNonceSize]byte + copy(nonce[:], b[SecretKeyScalarSize:]) + + sk := &SecretKey{ + key: scalar, + nonce: nonce, + } + return sk, nil +} + // GenerateSecretKey generates a SecretKey using entropy from rng. // If rng is nil, crypto/rand.Reader will be used. func GenerateSecretKey(rng io.Reader) (*SecretKey, error) { diff --git a/primitives/sr25519/keys_test.go b/primitives/sr25519/keys_test.go index 4374047..a440a38 100644 --- a/primitives/sr25519/keys_test.go +++ b/primitives/sr25519/keys_test.go @@ -34,6 +34,7 @@ package sr25519 import ( "bytes" "crypto/rand" + "encoding/hex" "testing" "github.com/oasisprotocol/curve25519-voi/curve/scalar" @@ -197,6 +198,36 @@ func TestSecretKey(t *testing.T) { t.Fatalf("sk == sk2") } }) + t.Run("Ed25519Bytes", func(t *testing.T) { + ed25519Bytes, _ := hex.DecodeString("28b0ae221c6bb06856b287f60d7ea0d98552ea5a16db16956849aa371db3eb51fd190cce74df356432b410bd64682309d6dedb27c76845daf388557cbac3ca34") + sk, err := NewSecretKeyFromEd25519Bytes(ed25519Bytes) + if err != nil { + t.Fatalf("NewSecretKeyFromEd25519Bytes: %v", err) + } + + pkBytes, err := sk.PublicKey().MarshalBinary() + if err != nil { + t.Fatalf("sk.MarshalBinary: %v", err) + } + expectedPkBytes, _ := hex.DecodeString("46ebddef8cd9bb167dc30878d7113b7e168e6f0646beffd77d69d39bad76b47a") + if !bytes.Equal(pkBytes, expectedPkBytes) { + t.Fatalf("public key bytes differ: %v != %v", hex.EncodeToString(pkBytes), hex.EncodeToString(expectedPkBytes)) + } + + skBytes := make([]byte, scalar.ScalarSize) + if sk.key.ToBytes(skBytes) != nil { + t.Fatalf("sk.ToBytes: %v", err) + } + expectedSkBytes, _ := hex.DecodeString("05d65584630d16cd4af6d0bec10f34bb504a5dcb62dba2122d49f5a663763d0a") + if !bytes.Equal(skBytes, expectedSkBytes) { + t.Fatalf("secret key bytes differ (%v != %v)", hex.EncodeToString(skBytes), hex.EncodeToString(expectedSkBytes)) + } + + expectedNonce, _ := hex.DecodeString("fd190cce74df356432b410bd64682309d6dedb27c76845daf388557cbac3ca34") + if !bytes.Equal(sk.nonce[:], expectedNonce) { + t.Fatalf("nonce bytes differ: %v != %v", hex.EncodeToString(sk.nonce[:]), hex.EncodeToString(expectedNonce)) + } + }) } func TestPublicKey(t *testing.T) {