Skip to content

Commit

Permalink
feat(wallet): Add sr25519-adr8 and sr25519-raw support
Browse files Browse the repository at this point in the history
  • Loading branch information
matevz committed Aug 8, 2023
1 parent a41c376 commit 80ee834
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 14 deletions.
54 changes: 46 additions & 8 deletions wallet/file/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ import (
"github.com/oasisprotocol/deoxysii"
"github.com/oasisprotocol/oasis-core/go/common/crypto/sakg"
coreSignature "github.com/oasisprotocol/oasis-core/go/common/crypto/signature"

"github.com/oasisprotocol/oasis-sdk/client-sdk/go/crypto/signature"
"github.com/oasisprotocol/oasis-sdk/client-sdk/go/crypto/signature/ed25519"
"github.com/oasisprotocol/oasis-sdk/client-sdk/go/crypto/signature/secp256k1"
"github.com/oasisprotocol/oasis-sdk/client-sdk/go/crypto/signature/sr25519"
"github.com/oasisprotocol/oasis-sdk/client-sdk/go/types"

"github.com/oasisprotocol/cli/config"
Expand All @@ -45,14 +45,14 @@ const (
// SupportedAlgorithmsForImport returns the algorithms supported by the given import kind.
func SupportedAlgorithmsForImport(kind *wallet.ImportKind) []string {
if kind == nil {
return []string{wallet.AlgorithmEd25519Adr8, wallet.AlgorithmEd25519Raw, wallet.AlgorithmSecp256k1Bip44, wallet.AlgorithmSecp256k1Raw}
return []string{wallet.AlgorithmEd25519Adr8, wallet.AlgorithmEd25519Raw, wallet.AlgorithmSecp256k1Bip44, wallet.AlgorithmSecp256k1Raw, wallet.AlgorithmSr25519Adr8, wallet.AlgorithmSr25519Raw}
}

switch *kind {
case wallet.ImportKindMnemonic:
return []string{wallet.AlgorithmEd25519Adr8, wallet.AlgorithmSecp256k1Bip44}
return []string{wallet.AlgorithmEd25519Adr8, wallet.AlgorithmSecp256k1Bip44, wallet.AlgorithmSr25519Adr8}
case wallet.ImportKindPrivateKey:
return []string{wallet.AlgorithmEd25519Raw, wallet.AlgorithmSecp256k1Raw}
return []string{wallet.AlgorithmEd25519Raw, wallet.AlgorithmSecp256k1Raw, wallet.AlgorithmSr25519Raw}
default:
return []string{}
}
Expand Down Expand Up @@ -192,7 +192,7 @@ func (af *fileAccountFactory) PrettyKind(rawCfg map[string]interface{}) string {
// In case of ADR8 or BIP44 show the keypair number.
var number string
switch cfg.Algorithm {
case wallet.AlgorithmEd25519Adr8, wallet.AlgorithmSecp256k1Bip44:
case wallet.AlgorithmEd25519Adr8, wallet.AlgorithmSecp256k1Bip44, wallet.AlgorithmSr25519Adr8:
number = fmt.Sprintf(":%d", cfg.Number)
}
return fmt.Sprintf("%s (%s%s)", Kind, cfg.Algorithm, number)
Expand Down Expand Up @@ -258,6 +258,8 @@ func (af *fileAccountFactory) DataPrompt(kind wallet.ImportKind, rawCfg map[stri
return &survey.Multiline{Message: "Private key (base64-encoded):"}
case wallet.AlgorithmSecp256k1Raw:
return &survey.Multiline{Message: "Private key (hex-encoded):"}
case wallet.AlgorithmSr25519Raw:
return &survey.Multiline{Message: "Private key (base64-encoded):"}
default:
return nil
}
Expand Down Expand Up @@ -288,6 +290,12 @@ func (af *fileAccountFactory) DataValidator(kind wallet.ImportKind, rawCfg map[s
if err != nil {
return fmt.Errorf("private key must be hex-encoded (without leading 0x): %w", err)
}
case wallet.AlgorithmSr25519Raw:
// Ensure the private key is base64 encoded.
_, err := base64.StdEncoding.DecodeString(ans.(string))
if err != nil {
return fmt.Errorf("private key must be base64-encoded: %w", err)
}
default:
return fmt.Errorf("unsupported algorithm for %s: %s", wallet.ImportKindPrivateKey, cfg.Algorithm)
}
Expand Down Expand Up @@ -421,13 +429,13 @@ func (af *fileAccountFactory) Import(name string, passphrase string, rawCfg map[
switch src.Kind {
case wallet.ImportKindMnemonic:
switch cfg.Algorithm {
case wallet.AlgorithmEd25519Adr8, wallet.AlgorithmSecp256k1Bip44:
case wallet.AlgorithmEd25519Adr8, wallet.AlgorithmSecp256k1Bip44, wallet.AlgorithmSr25519Adr8:
default:
return nil, fmt.Errorf("algorithm '%s' does not support import from mnemonic", cfg.Algorithm)
}
case wallet.ImportKindPrivateKey:
switch cfg.Algorithm {
case wallet.AlgorithmEd25519Raw, wallet.AlgorithmSecp256k1Raw:
case wallet.AlgorithmEd25519Raw, wallet.AlgorithmSecp256k1Raw, wallet.AlgorithmSr25519Raw:
default:
return nil, fmt.Errorf("algorithm '%s' does not support import from private key", cfg.Algorithm)
}
Expand Down Expand Up @@ -512,6 +520,34 @@ func newAccount(state *secretState, cfg *accountConfig) (wallet.Account, error)
return nil, fmt.Errorf("failed to initialize signer: %w", err)
}

return &fileAccount{
cfg: cfg,
state: state,
signer: signer,
}, nil
case wallet.AlgorithmSr25519Adr8:
// For Sr25519 use the ADR 0008 derivation scheme.
signer, err := Sr25519FromMnemonic(state.Data, cfg.Number)
if err != nil {
return nil, fmt.Errorf("failed to initialize signer: %w", err)
}

return &fileAccount{
cfg: cfg,
state: state,
signer: signer,
}, nil
case wallet.AlgorithmSr25519Raw:
// For Sr25519-Raw use the raw private key.
dataRaw, err := base64.StdEncoding.DecodeString(state.Data)
if err != nil {
return nil, err
}
signer, err := sr25519.NewSigner(dataRaw)
if err != nil {
return nil, err
}

return &fileAccount{
cfg: cfg,
state: state,
Expand Down Expand Up @@ -561,6 +597,8 @@ func (a *fileAccount) SignatureAddressSpec() types.SignatureAddressSpec {
return types.NewSignatureAddressSpecEd25519(a.Signer().Public().(ed25519.PublicKey))
case wallet.AlgorithmSecp256k1Bip44, wallet.AlgorithmSecp256k1Raw:
return types.NewSignatureAddressSpecSecp256k1Eth(a.Signer().Public().(secp256k1.PublicKey))
case wallet.AlgorithmSr25519Adr8, wallet.AlgorithmSr25519Raw:
return types.NewSignatureAddressSpecSr25519(a.Signer().Public().(sr25519.PublicKey))
default:
return types.SignatureAddressSpec{}
}
Expand All @@ -572,7 +610,7 @@ func (a *fileAccount) UnsafeExport() string {

func init() {
flags := flag.NewFlagSet("", flag.ContinueOnError)
flags.String(cfgAlgorithm, wallet.AlgorithmEd25519Adr8, fmt.Sprintf("Cryptographic algorithm to use for this account [%s, %s]", wallet.AlgorithmEd25519Adr8, wallet.AlgorithmSecp256k1Bip44))
flags.String(cfgAlgorithm, wallet.AlgorithmEd25519Adr8, fmt.Sprintf("Cryptographic algorithm to use for this account [%s, %s, %s]", wallet.AlgorithmEd25519Adr8, wallet.AlgorithmSecp256k1Bip44, wallet.AlgorithmSr25519Adr8))
flags.Uint32(cfgNumber, 0, "Key number to use in the key derivation scheme")

wallet.Register(&fileAccountFactory{
Expand Down
53 changes: 53 additions & 0 deletions wallet/file/sr25519.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package file

import (
"fmt"

"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/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"
)

// Sr25519FromMnemonic derives a signer using ADR-8 from given mnemonic.
func Sr25519FromMnemonic(mnemonic string, number uint32) (sdkSignature.Signer, error) {
if number > sakg.MaxAccountKeyNumber {
return nil, fmt.Errorf(
"sakg: invalid key number: %d (maximum: %d)",
number,
sakg.MaxAccountKeyNumber,
)
}

if !bip39.IsMnemonicValid(mnemonic) {
return nil, fmt.Errorf("sakg: invalid mnemonic")
}

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

signer, chainCode, err := slip10.NewMasterKey(seed)
if err != nil {
return nil, fmt.Errorf("sakg: error deriving master key: %w", err)
}

pathStr := fmt.Sprintf("%s/%d'", sakg.BIP32PathPrefix, number)
path, err := sakg.NewBIP32Path(pathStr)
if err != nil {
return nil, fmt.Errorf("sakg: error creating BIP-0032 path %s: %w", pathStr, err)
}

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

sr25519signer, err := sr25519.NewSigner(signer.(signature.UnsafeSigner).UnsafeBytes())
if err != nil {
return nil, err
}
return sr25519signer, nil
}
24 changes: 19 additions & 5 deletions wallet/ledger/ledger.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/oasisprotocol/oasis-sdk/client-sdk/go/crypto/signature"
"github.com/oasisprotocol/oasis-sdk/client-sdk/go/crypto/signature/ed25519"
"github.com/oasisprotocol/oasis-sdk/client-sdk/go/crypto/signature/secp256k1"
"github.com/oasisprotocol/oasis-sdk/client-sdk/go/crypto/signature/sr25519"
"github.com/oasisprotocol/oasis-sdk/client-sdk/go/types"

"github.com/oasisprotocol/cli/wallet"
Expand Down Expand Up @@ -216,6 +217,18 @@ func newAccount(cfg *accountConfig) (wallet.Account, error) {
return nil, err
}
pk = secp256k1pk
case wallet.AlgorithmSr25519Adr8:
path = getAdr0008Path(cfg.Number)
rawPk, err := dev.GetPublicKeySr25519(path, false)
if err != nil {
_ = dev.Close()
return nil, err
}
var sr25519pk sr25519.PublicKey
if err := sr25519pk.UnmarshalBinary(rawPk); err != nil {
return nil, err
}
pk = sr25519pk
default:
return nil, fmt.Errorf("unsupported algorithm %s", cfg.Algorithm)
}
Expand Down Expand Up @@ -253,8 +266,7 @@ func (a *ledgerAccount) Address() types.Address {
}

func (a *ledgerAccount) EthAddress() *ethCommon.Address {
switch a.cfg.Algorithm {
case wallet.AlgorithmSecp256k1Bip44, wallet.AlgorithmSecp256k1Raw:
if a.cfg.Algorithm == wallet.AlgorithmSecp256k1Bip44 {
h := sha3.NewLegacyKeccak256()
untaggedPk, _ := a.Signer().Public().(secp256k1.PublicKey).MarshalBinaryUncompressedUntagged()
h.Write(untaggedPk)
Expand All @@ -268,10 +280,12 @@ func (a *ledgerAccount) EthAddress() *ethCommon.Address {

func (a *ledgerAccount) SignatureAddressSpec() types.SignatureAddressSpec {
switch a.cfg.Algorithm {
case "", wallet.AlgorithmEd25519Legacy, wallet.AlgorithmEd25519Adr8, wallet.AlgorithmEd25519Raw:
case "", wallet.AlgorithmEd25519Legacy, wallet.AlgorithmEd25519Adr8:
return types.NewSignatureAddressSpecEd25519(a.Signer().Public().(ed25519.PublicKey))
case wallet.AlgorithmSecp256k1Bip44, wallet.AlgorithmSecp256k1Raw:
case wallet.AlgorithmSecp256k1Bip44:
return types.NewSignatureAddressSpecSecp256k1Eth(a.Signer().Public().(secp256k1.PublicKey))
case wallet.AlgorithmSr25519Adr8:
return types.NewSignatureAddressSpecSr25519(a.Signer().Public().(sr25519.PublicKey))
}
return types.SignatureAddressSpec{}
}
Expand All @@ -283,7 +297,7 @@ func (a *ledgerAccount) UnsafeExport() string {

func init() {
flags := flag.NewFlagSet("", flag.ContinueOnError)
flags.String(cfgAlgorithm, wallet.AlgorithmEd25519Legacy, fmt.Sprintf("Cryptographic algorithm to use for this account [%s, %s, %s]", wallet.AlgorithmEd25519Legacy, wallet.AlgorithmEd25519Adr8, wallet.AlgorithmSecp256k1Bip44))
flags.String(cfgAlgorithm, wallet.AlgorithmEd25519Legacy, fmt.Sprintf("Cryptographic algorithm to use for this account [%s, %s, %s, %s]", wallet.AlgorithmEd25519Legacy, wallet.AlgorithmEd25519Adr8, wallet.AlgorithmSecp256k1Bip44, wallet.AlgorithmSr25519Adr8))
flags.Uint32(cfgNumber, 0, "Key number to use in the derivation scheme")

wallet.Register(&ledgerAccountFactory{
Expand Down
2 changes: 2 additions & 0 deletions wallet/ledger/signer.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ func (ls *ledgerSigner) ContextSign(metadata signature.Context, message []byte)
return ls.dev.SignRtEd25519(ls.path, metadata, message)
case wallet.AlgorithmSecp256k1Bip44:
return ls.dev.SignRtSecp256k1(ls.path, metadata, message)
case wallet.AlgorithmSr25519Adr8:
return ls.dev.SignRtSr25519(ls.path, metadata, message)
}

return nil, fmt.Errorf("ledger: algorithm %s not supported", ls.algorithm)
Expand Down
6 changes: 5 additions & 1 deletion wallet/wallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (
var registeredFactories sync.Map

const (
// AlgorithmEd25519Adr8 is the Ed25519 algorithm using the ADR-8 derivation path (ROSE coin type).
// AlgorithmEd25519Adr8 is the Ed25519 algorithm using the ADR-8 derivation path.
AlgorithmEd25519Adr8 = "ed25519-adr8"
// AlgorithmEd25519Raw is the Ed25519 algorithm using raw private keys.
AlgorithmEd25519Raw = "ed25519-raw"
Expand All @@ -26,6 +26,10 @@ const (
AlgorithmSecp256k1Bip44 = "secp256k1-bip44"
// AlgorithmSecp256k1Raw is the Secp256k1 algorithm using raw private keys.
AlgorithmSecp256k1Raw = "secp256k1-raw"
// AlgorithmSr25519Adr8 is the Sr25519 algorithm using the ADR-8 derivation path.
AlgorithmSr25519Adr8 = "sr25519-adr8"
// AlgorithmSr25519Raw is the Sr25519 algorithm using raw private keys.
AlgorithmSr25519Raw = "sr25519-raw"
)

// Factory is a factory that supports accounts of a specific kind.
Expand Down

0 comments on commit 80ee834

Please sign in to comment.