Skip to content

Commit

Permalink
feat(wallet): Add file-based sr25519-adr8 support
Browse files Browse the repository at this point in the history
  • Loading branch information
matevz committed Sep 26, 2023
1 parent da92a78 commit a4fa011
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 8 deletions.
82 changes: 74 additions & 8 deletions wallet/file/sr25519.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
package file

import (
"crypto/hmac"
"crypto/sha512"
"encoding/binary"
"fmt"

"github.com/tyler-smith/go-bip39"

"github.com/oasisprotocol/curve25519-voi/primitives/ed25519"
"github.com/oasisprotocol/curve25519-voi/primitives/sr25519"
"github.com/oasisprotocol/oasis-core/go/common/crypto/sakg"
"github.com/oasisprotocol/oasis-core/go/common/crypto/signature"
"github.com/oasisprotocol/oasis-core/go/common/crypto/signature/signers/memory"
"github.com/oasisprotocol/oasis-core/go/common/crypto/slip10"
sdkSignature "github.com/oasisprotocol/oasis-sdk/client-sdk/go/crypto/signature"
"github.com/oasisprotocol/oasis-sdk/client-sdk/go/crypto/signature/sr25519"
"github.com/tyler-smith/go-bip39"
sdkSr25519 "github.com/oasisprotocol/oasis-sdk/client-sdk/go/crypto/signature/sr25519"
)

// Sr25519FromMnemonic derives a signer using ADR-8 from given mnemonic.
Expand All @@ -27,7 +33,7 @@ func Sr25519FromMnemonic(mnemonic string, number uint32) (sdkSignature.Signer, e

seed := bip39.NewSeed(mnemonic, "")

signer, chainCode, err := slip10.NewMasterKey(seed)
_, chainCode, skBinary, err := newMasterKey(seed)
if err != nil {
return nil, fmt.Errorf("sakg: error deriving master key: %w", err)
}
Expand All @@ -38,16 +44,76 @@ func Sr25519FromMnemonic(mnemonic string, number uint32) (sdkSignature.Signer, e
return nil, fmt.Errorf("sakg: error creating BIP-0032 path %s: %w", pathStr, err)
}

var signer sdkSignature.Signer
for _, index := range path {
signer, chainCode, err = slip10.NewChildKey(signer, chainCode, index)
signer, chainCode, skBinary, err = newChildKey(skBinary, chainCode, index)
if err != nil {
return nil, fmt.Errorf("sakg: error deriving child key: %w", err)
}
}

sr25519signer, err := sr25519.NewSigner(signer.(signature.UnsafeSigner).UnsafeBytes())
return signer, nil
}

func newMasterKey(seed []byte) (sdkSignature.Signer, slip10.ChainCode, []byte, error) {
// Let S be a seed byte sequence of 128 to 512 bits in length.
if sLen := len(seed); sLen < slip10.SeedMinSize || sLen > slip10.SeedMaxSize {
return nil, slip10.ChainCode{}, nil, fmt.Errorf("slip10: invalid seed")
}

// 1. Calculate I = HMAC-SHA512(Key = Curve, Data = S)
mac := hmac.New(sha512.New, []byte("ed25519 seed"))
_, _ = mac.Write(seed)
I := mac.Sum(nil)

// 2. Split I into two 32-byte sequences, IL and IR.
// 3. Use parse256(IL) as master secret key, and IR as master chain code.
return splitDigest(I)
}

func newChildKey(kPar []byte, cPar slip10.ChainCode, index uint32) (sdkSignature.Signer, slip10.ChainCode, []byte, error) { //nolint: revive
if len(kPar) < memory.SeedSize {
return nil, slip10.ChainCode{}, nil, fmt.Errorf("slip10: invalid parent key")
}

// 1. Check whether i >= 2^31 (whether the child is a hardened key).
if index < 1<<31 {
// If not (normal child):
// If curve is ed25519: return failure.
return nil, slip10.ChainCode{}, nil, fmt.Errorf("slip10: non-hardened keys not supported")
}

// If so (hardened child):
// let I = HMAC-SHA512(Key = cpar, Data = 0x00 || ser256(kpar) || ser32(i)).
// (Note: The 0x00 pads the private key to make it 33 bytes long.)
var b [4]byte
mac := hmac.New(sha512.New, cPar[:])
_, _ = mac.Write(b[0:1]) // 0x00
_, _ = mac.Write(kPar[:memory.SeedSize]) // ser256(kPar)
binary.BigEndian.PutUint32(b[:], index) // Note: The spec neglects to define ser32.
_, _ = mac.Write(b[:]) // ser32(i)
I := mac.Sum(nil)

// 2. Split I into two 32-byte sequences, IL and IR.
// 3. The returned chain code ci is IR.
// 4. If curve is ed25519: The returned child key ki is parse256(IL).
return splitDigest(I)
}

func splitDigest(digest []byte) (sdkSignature.Signer, slip10.ChainCode, []byte, error) {
IL, IR := digest[:32], digest[32:]

var chainCode slip10.ChainCode

edSk := ed25519.NewKeyFromSeed(IL) // Needed for the SLIP10 scheme.
msk, err := sr25519.NewMiniSecretKeyFromBytes(IL)
if err != nil {
return nil, err
return nil, chainCode, nil, err
}
return sr25519signer, nil
sk := msk.ExpandUniform()

signer := sdkSr25519.NewSignerFromKeyPair(sk.KeyPair())
copy(chainCode[:], IR)

return signer, chainCode, edSk[:], nil
}
35 changes: 35 additions & 0 deletions wallet/file/sr25519_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package file

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestSr25519FromMnemonic(t *testing.T) {
mnemonics := []struct {
mnemonic string
num uint32
pubkey string
valid bool
}{
{mnemonic: "equip will roof matter pink blind book anxiety banner elbow sun young", num: 0, pubkey: "vP1R5MMzR/r9ricymYpPbBA9JvmlixdzlEYWEpx5ExY=", valid: true},
{mnemonic: "equip will roof matter pink blind book anxiety banner elbow sun young", num: 1, pubkey: "Ql96gaTGV7o0QzQbvPBSQfRT5K9PtBNLrxEh9kMV228=", valid: true},
{mnemonic: "equip will roof matter pink blind book anxiety banner elbow sun young", num: 2, pubkey: "qhbwbWt9k+3SW+68NxXq3knl2jokiGpc11DoKOrjTjo=", valid: true},
{mnemonic: "equip will roof matter pink blind book anxiety banner elbow sun young", num: 3, pubkey: "skCcMfU7D0olbxZtn8lLn0u41rJzxiCw7rcv3uVEw3Y=", valid: true},
{mnemonic: "actorr want explain gravity body drill bike update mask wool tell seven", pubkey: "", valid: false},
{mnemonic: "actor want explain gravity body drill bike update mask wool tell", pubkey: "", valid: false},
{mnemonic: "", pubkey: "", valid: false},
}

for _, m := range mnemonics {
if m.valid {
signer, err := Sr25519FromMnemonic(m.mnemonic, m.num)
require.NoError(t, err)
require.Equal(t, m.pubkey, signer.Public().String())
} else {
_, err := Sr25519FromMnemonic(m.mnemonic, 0)
require.Error(t, err)
}
}
}

0 comments on commit a4fa011

Please sign in to comment.