From 6b45481077467418bf2109e55e84be2015fc9563 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Ber=C4=8Di=C4=8D?= Date: Mon, 7 Aug 2023 01:54:02 +0200 Subject: [PATCH] primitives/sr25519: Enable generating secret keys from ed25519 keys --- primitives/sr25519/keys.go | 28 +++++++++++++++++ primitives/sr25519/keys_test.go | 55 +++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+) diff --git a/primitives/sr25519/keys.go b/primitives/sr25519/keys.go index f128eae..3215adb 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 clamped. + s := b[:SecretKeyScalarSize] + if (s[0]&0b0000_0111) != 0 || (s[31]&0b1100_0000) != 0b0100_0000 { + return nil, fmt.Errorf("sr25519: ed25519 scalar invalid") + } + + scalar, err := scalarDivideByCofactor(s) + 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..2a7cf67 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,60 @@ func TestSecretKey(t *testing.T) { t.Fatalf("sk == sk2") } }) + t.Run("Ed25519Bytes", func(t *testing.T) { + var err error + + // Test some incorrect key byte slices. + for _, d := range []struct { + bytes string + message string + }{ + { + bytes: "28b0ae221c6bb06856b287f60d7ea0d98552ea5a16db16956849aa371db3eb51fd190cce74df356432b410bd64682309d6dedb27c76845daf388557cbac3ca34aa", + message: "NewSecretKeyFromEd25519Bytes: using too long key must fail", + }, + { + bytes: "28b0ae221c6bb06856b287f60d7ea0d98552ea5a16db16956849aa371db3eb51fd190cce74df356432b410bd64682309d6dedb27c76845daf388557cbac3ca", + message: "NewSecretKeyFromEd25519Bytes: using too short key must fail", + }, + { + bytes: "28b0ae221c6bb06856b287f60d7ea0d98552ea5a16db16956849aa371db3ebd1fd190cce74df356432b410bd64682309d6dedb27c76845daf388557cbac3ca34", + message: "NewSecretKeyFromEd25519Bytes: scalar with fixed top bit must fail", + }, + { + bytes: "2ab0ae221c6bb06856b287f60d7ea0d98552ea5a16db16956849aa371db3eb51fd190cce74df356432b410bd64682309d6dedb27c76845daf388557cbac3ca34", + message: "NewSecretKeyFromEd25519Bytes: scalar not divisible by 8 must fail", + }, + } { + decoded := testhelpers.MustUnhex(t, d.bytes) + _, err = NewSecretKeyFromEd25519Bytes(decoded) + if err == nil { + t.Errorf(d.message) + } + } + + // Valid. For the test bytes, see the schnorrkel Rust crate and + // its SecretKey::from_ed25519_bytes method documentation. + ed25519Bytes := testhelpers.MustUnhex(t, "28b0ae221c6bb06856b287f60d7ea0d98552ea5a16db16956849aa371db3eb51fd190cce74df356432b410bd64682309d6dedb27c76845daf388557cbac3ca34") + sk, err := NewSecretKeyFromEd25519Bytes(ed25519Bytes) + if err != nil { + t.Fatalf("NewSecretKeyFromEd25519Bytes: %v", err) + } + + skBytes := make([]byte, scalar.ScalarSize) + if sk.key.ToBytes(skBytes) != nil { + t.Fatalf("sk.ToBytes: %v", err) + } + expectedSkBytes := testhelpers.MustUnhex(t, "05d65584630d16cd4af6d0bec10f34bb504a5dcb62dba2122d49f5a663763d0a") + if !bytes.Equal(skBytes, expectedSkBytes) { + t.Fatalf("secret key bytes differ (%v != %v)", hex.EncodeToString(skBytes), hex.EncodeToString(expectedSkBytes)) + } + + expectedNonce := testhelpers.MustUnhex(t, "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) {