From e2b00865cab47955181a1ac99b785037d46bb807 Mon Sep 17 00:00:00 2001 From: Jaguar0625 Date: Mon, 29 Jul 2024 15:59:33 -0400 Subject: [PATCH] add support for Ed25519 signature scheme with custom (Keccak512) hash function this is required for NEM support --- asserter/construction.go | 4 +- go.mod | 16 ++-- go.sum | 22 ++--- keys/keys.go | 38 ++++++++ keys/keys_test.go | 26 +++++- keys/signer_edwards25519_keccak.go | 108 ++++++++++++++++++++++ keys/signer_edwards25519_keccak_test.go | 115 ++++++++++++++++++++++++ types/curve_type.go | 25 +++--- types/signature_type.go | 22 +++-- 9 files changed, 336 insertions(+), 40 deletions(-) create mode 100644 keys/signer_edwards25519_keccak.go create mode 100644 keys/signer_edwards25519_keccak_test.go diff --git a/asserter/construction.go b/asserter/construction.go index 96982a50..03e6a230 100644 --- a/asserter/construction.go +++ b/asserter/construction.go @@ -247,7 +247,7 @@ func CurveType( curve types.CurveType, ) error { switch curve { - case types.Secp256k1, types.Secp256r1, types.Edwards25519, types.Tweedle, types.Pallas: + case types.Secp256k1, types.Secp256r1, types.Edwards25519, types.Edwards25519_Keccak, types.Tweedle, types.Pallas: return nil default: return ErrCurveTypeNotSupported @@ -356,7 +356,7 @@ func SignatureType( signature types.SignatureType, ) error { switch signature { - case types.Ecdsa, types.EcdsaRecovery, types.Ed25519, types.Schnorr1, types.SchnorrPoseidon: + case types.Ecdsa, types.EcdsaRecovery, types.Ed25519, types.Ed25519_Keccak, types.Schnorr1, types.SchnorrPoseidon: return nil default: return ErrSignatureTypeNotSupported diff --git a/go.mod b/go.mod index cb73cdfe..ce59d5df 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,10 @@ module github.com/coinbase/rosetta-sdk-go -go 1.18 +go 1.22.5 require ( github.com/DataDog/zstd v1.5.2 + github.com/NemProject/nem/gocrypto v0.0.1 github.com/Zilliqa/gozilliqa-sdk v1.2.1-0.20201201074141-dd0ecada1be6 github.com/btcsuite/btcd v0.22.1 github.com/cenkalti/backoff v2.2.1+incompatible @@ -24,7 +25,7 @@ require ( ) require ( - filippo.io/edwards25519 v1.0.0-rc.1 // indirect + filippo.io/edwards25519 v1.1.0 // indirect github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce // indirect github.com/bwesterb/go-ristretto v1.2.0 // indirect github.com/cespare/xxhash v1.1.0 // indirect @@ -45,9 +46,14 @@ require ( github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.0 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect - golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect - golang.org/x/net v0.0.0-20220607020251-c690dde0001d // indirect - golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect + golang.org/x/crypto v0.25.0 // indirect + golang.org/x/net v0.21.0 // indirect + golang.org/x/sys v0.22.0 // indirect google.golang.org/protobuf v1.26.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) + +replace github.com/coinbase/rosetta-sdk-go/types v1.0.0 => ./types + +// temporary until gocrypto is published +replace github.com/NemProject/nem/gocrypto v0.0.1 => ../../NemProject/nem/gocrypto diff --git a/go.sum b/go.sum index e6ae8c97..ebb359be 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -filippo.io/edwards25519 v1.0.0-rc.1 h1:m0VOOB23frXZvAOK44usCgLWvtsxIoMCTBGJZlpmGfU= -filippo.io/edwards25519 v1.0.0-rc.1/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns= +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/DataDog/zstd v1.5.2 h1:vUG4lAyuPCXO0TLbXvPv7EB7cNK1QV/luu55UHLrrn8= github.com/DataDog/zstd v1.5.2/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= @@ -14,6 +14,7 @@ github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13P github.com/btcsuite/btcd v0.22.1 h1:CnwP9LM/M9xuRrGSCGeMVs9iv09uMqwsVX7EeIpgV2c= github.com/btcsuite/btcd v0.22.1/go.mod h1:wqgTSL29+50LRkmOVknEdmt8ZojIzhuWvgu/iptuN7Y= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= github.com/btcsuite/btcutil v0.0.0-20190207003914-4c204d697803/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= @@ -32,8 +33,6 @@ github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/coinbase/kryptology v1.8.0 h1:Aoq4gdTsJhSU3lNWsD5BWmFSz2pE0GlmrljaOxepdYY= github.com/coinbase/kryptology v1.8.0/go.mod h1:RYXOAPdzOGUe3qlSFkMGn58i3xUA8hmxYHksuq+8ciI= -github.com/coinbase/rosetta-sdk-go/types v1.0.0 h1:jpVIwLcPoOeCR6o1tU+Xv7r5bMONNbHU7MuEHboiFuA= -github.com/coinbase/rosetta-sdk-go/types v1.0.0/go.mod h1:eq7W2TMRH22GTW0N0beDnN931DW0/WOI1R2sdHNHG4c= github.com/consensys/bavard v0.1.8-0.20210915155054-088da2f7f54a/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= github.com/consensys/gnark-crypto v0.5.3 h1:4xLFGZR3NWEH2zy+YzvzHicpToQR8FXFbfLNvpGB+rE= github.com/consensys/gnark-crypto v0.5.3/go.mod h1:hOdPlWQV1gDLp7faZVeg8Y0iEPFaOUnCc4XeCCk96p0= @@ -87,6 +86,7 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= github.com/lucasjones/reggen v0.0.0-20180717132126-cdb49ff09d77 h1:6xiz3+ZczT3M4+I+JLpcPGG1bQKm8067HktB17EDWEE= @@ -155,14 +155,14 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= +golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220607020251-c690dde0001d h1:4SFsTMi4UahlKoloni7L4eYzhFRifURQLw+yv0QDCx8= -golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= @@ -177,20 +177,22 @@ golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df h1:5Pf6pFKu98ODmgnpvkJ3kFUOQGGLIzLIkbzUHp47618= +golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/keys/keys.go b/keys/keys.go index c14fdc02..b6fcdaa6 100644 --- a/keys/keys.go +++ b/keys/keys.go @@ -23,11 +23,15 @@ import ( "fmt" "math/big" + "golang.org/x/crypto/sha3" + "github.com/btcsuite/btcd/btcec" "github.com/coinbase/kryptology/pkg/signatures/schnorr/mina" "github.com/coinbase/rosetta-sdk-go/asserter" "github.com/coinbase/rosetta-sdk-go/types" + + nemcrypto "github.com/NemProject/nem/gocrypto" ) // PrivKeyBytesLen are 32-bytes for all supported curvetypes @@ -89,6 +93,19 @@ func ImportPrivateKey(privKeyHex string, curve types.CurveType) (*KeyPair, error CurveType: curve, } + keyPair = &KeyPair{ + PublicKey: pubKey, + PrivateKey: rawPrivKey.Seed(), + } + case types.Edwards25519_Keccak: + hasher := sha3.NewLegacyKeccak512() + rawPrivKey := nemcrypto.NewKeyFromSeed(privKey, hasher) + + pubKey := &types.PublicKey{ + Bytes: rawPrivKey.Public().(nemcrypto.PublicKey), + CurveType: curve, + } + keyPair = &KeyPair{ PublicKey: pubKey, PrivateKey: rawPrivKey.Seed(), @@ -188,6 +205,25 @@ func GenerateKeypair(curve types.CurveType) (*KeyPair, error) { CurveType: curve, } + keyPair = &KeyPair{ + PublicKey: pubKey, + PrivateKey: rawPrivKey.Seed(), + } + case types.Edwards25519_Keccak: + hasher := sha3.NewLegacyKeccak512() + rawPubKey, rawPrivKey, err := nemcrypto.GenerateKey(nil, hasher) + if err != nil { + return nil, fmt.Errorf( + "failed to generate key pair for edwards25519 curve type: %w", + err, + ) + } + + pubKey := &types.PublicKey{ + Bytes: rawPubKey, + CurveType: curve, + } + keyPair = &KeyPair{ PublicKey: pubKey, PrivateKey: rawPrivKey.Seed(), @@ -265,6 +301,8 @@ func (k *KeyPair) Signer() (Signer, error) { return &SignerSecp256k1{k}, nil case types.Edwards25519: return &SignerEdwards25519{k}, nil + case types.Edwards25519_Keccak: + return &SignerEdwards25519Keccak{k}, nil case types.Secp256r1: return &SignerSecp256r1{k}, nil case types.Pallas: diff --git a/keys/keys_test.go b/keys/keys_test.go index a7985f90..03077663 100644 --- a/keys/keys_test.go +++ b/keys/keys_test.go @@ -74,6 +74,15 @@ func TestGenerateKeypairEdwards25519(t *testing.T) { assert.Len(t, keypair.PrivateKey, PrivKeyBytesLen) } +func TestGenerateKeypairEdwards25519Keccak(t *testing.T) { + curve := types.Edwards25519_Keccak + keypair, err := GenerateKeypair(curve) + + assert.NoError(t, err) + assert.Equal(t, keypair.PublicKey.CurveType, curve) + assert.Len(t, keypair.PrivateKey, PrivKeyBytesLen) +} + func TestGenerateKeypairPallas(t *testing.T) { curve := types.Pallas keypair, err := GenerateKeypair(curve) @@ -129,6 +138,11 @@ func TestImportPrivateKey(t *testing.T) { types.Edwards25519, nil, }, + "simple ed25519 (keccak)": { + "aeb121b4c545f0f850e1480492508c65a250e9965b0d90176fab4d7506398ebb", + types.Edwards25519_Keccak, + nil, + }, "simple Secp256k1": { "0b188af56b25d007fbc4bbf2176cd2a54d876ce4774bb5df38b7c83349405b7a", types.Secp256k1, @@ -139,17 +153,23 @@ func TestImportPrivateKey(t *testing.T) { types.Pallas, nil, }, - "short ed25519": {"asd", types.Secp256k1, ErrPrivKeyUndecodable}, + "short ed25519": {"asd", types.Edwards25519, ErrPrivKeyUndecodable}, + "short ed25519 (keccak)": {"asd", types.Edwards25519_Keccak, ErrPrivKeyUndecodable}, "short Secp256k1": {"asd", types.Edwards25519, ErrPrivKeyUndecodable}, "short pallas": {"asd", types.Pallas, ErrPrivKeyUndecodable}, "long ed25519": { "aeb121b4c545f0f850e1480492508c65a250e9965b0d90176fab4d7506398ebbaeb121b4c545f0f850e1480492508c65a250e9965b0d90176fab4d7506398ebb", // nolint:lll - types.Secp256k1, + types.Edwards25519, + ErrPrivKeyLengthInvalid, + }, + "long ed25519 (keccak)": { + "aeb121b4c545f0f850e1480492508c65a250e9965b0d90176fab4d7506398ebbaeb121b4c545f0f850e1480492508c65a250e9965b0d90176fab4d7506398ebb", // nolint:lll + types.Edwards25519_Keccak, ErrPrivKeyLengthInvalid, }, "long Secp256k1": { "0b188af56b25d007fbc4bbf2176cd2a54d876ce4774bb5df38b7c83349405b7a0b188af56b25d007fbc4bbf2176cd2a54d876ce4774bb5df38b7c83349405b7a", // nolint:lll - types.Edwards25519, + types.Secp256k1, ErrPrivKeyLengthInvalid, }, "long Pallas": { diff --git a/keys/signer_edwards25519_keccak.go b/keys/signer_edwards25519_keccak.go new file mode 100644 index 00000000..b507d060 --- /dev/null +++ b/keys/signer_edwards25519_keccak.go @@ -0,0 +1,108 @@ +// Copyright 2024 Coinbase, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package keys + +import ( + "fmt" + + "golang.org/x/crypto/sha3" + + "github.com/coinbase/rosetta-sdk-go/asserter" + "github.com/coinbase/rosetta-sdk-go/types" + + nemcrypto "github.com/NemProject/nem/gocrypto" +) + +// SignerEdwards25519Keccak is initialized from a keypair +type SignerEdwards25519Keccak struct { + KeyPair *KeyPair +} + +var _ Signer = (*SignerEdwards25519Keccak)(nil) + +// PublicKey returns the PublicKey of the signer +func (s *SignerEdwards25519Keccak) PublicKey() *types.PublicKey { + return s.KeyPair.PublicKey +} + +// Sign arbitrary payloads using a KeyPair +func (s *SignerEdwards25519Keccak) Sign( + payload *types.SigningPayload, + sigType types.SignatureType, +) (*types.Signature, error) { + err := s.KeyPair.IsValid() + if err != nil { + return nil, fmt.Errorf("key pair is invalid: %w", err) + } + + if !(payload.SignatureType == types.Ed25519_Keccak || payload.SignatureType == "") { + return nil, fmt.Errorf( + "expected signing payload signature type %v but got %v: %w", + types.Ed25519_Keccak, + payload.SignatureType, + ErrSignUnsupportedPayloadSignatureType, + ) + } + + if sigType != types.Ed25519_Keccak { + return nil, fmt.Errorf( + "expected signature type %v but got %v: %w", + types.Ed25519_Keccak, + sigType, + ErrSignUnsupportedSignatureType, + ) + } + + hasher := sha3.NewLegacyKeccak512() + privKeyBytes := s.KeyPair.PrivateKey + privKey := nemcrypto.NewKeyFromSeed(privKeyBytes, hasher) + sig := nemcrypto.Sign(privKey, payload.Bytes, hasher) + + return &types.Signature{ + SigningPayload: payload, + PublicKey: s.KeyPair.PublicKey, + SignatureType: payload.SignatureType, + Bytes: sig, + }, nil +} + +// Verify verifies a Signature, by checking the validity of a Signature, +// the SigningPayload, and the PublicKey of the Signature. +func (s *SignerEdwards25519Keccak) Verify(signature *types.Signature) error { + if signature.SignatureType != types.Ed25519_Keccak { + return fmt.Errorf( + "expected signing payload signature type %v but got %v: %w", + types.Ed25519_Keccak, + signature.SignatureType, + ErrVerifyUnsupportedPayloadSignatureType, + ) + } + + pubKey := signature.PublicKey.Bytes + message := signature.SigningPayload.Bytes + sig := signature.Bytes + err := asserter.Signatures([]*types.Signature{signature}) + if err != nil { + return fmt.Errorf("signature is invalid: %w", err) + } + + hasher := sha3.NewLegacyKeccak512() + verify := nemcrypto.Verify(pubKey, message, sig, hasher) + if !verify { + return ErrVerifyFailed + } + + return nil +} diff --git a/keys/signer_edwards25519_keccak_test.go b/keys/signer_edwards25519_keccak_test.go new file mode 100644 index 00000000..939a3139 --- /dev/null +++ b/keys/signer_edwards25519_keccak_test.go @@ -0,0 +1,115 @@ +// Copyright 2024 Coinbase, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package keys + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/coinbase/rosetta-sdk-go/types" +) + +var signerEdwards25519Keccak Signer + +func init() { + keypair, _ := GenerateKeypair(types.Edwards25519_Keccak) + signerEdwards25519Keccak, _ = keypair.Signer() +} + +func TestSignEdwards25519_Keccak(t *testing.T) { + type payloadTest struct { + payload *types.SigningPayload + err bool + errMsg error + } + + var payloadTests = []payloadTest{ + {mockPayload(make([]byte, 32), types.Ed25519_Keccak), false, nil}, + {mockPayload(make([]byte, 32), ""), false, nil}, + {mockPayload(make([]byte, 33), types.Ecdsa), true, ErrSignUnsupportedPayloadSignatureType}, + { + mockPayload(make([]byte, 34), types.EcdsaRecovery), + true, + ErrSignUnsupportedPayloadSignatureType, + }, + } + + for _, test := range payloadTests { + signature, err := signerEdwards25519Keccak.Sign(test.payload, types.Ed25519_Keccak) + + if !test.err { + assert.NoError(t, err) + assert.Len(t, signature.Bytes, 64) + assert.Equal(t, signerEdwards25519Keccak.PublicKey(), signature.PublicKey) + } else { + assert.Contains(t, err.Error(), test.errMsg.Error()) + } + } +} + +func TestVerifyEdwards25519_Keccak(t *testing.T) { + type signatureTest struct { + signature *types.Signature + errMsg error + } + + simpleBytes := make([]byte, 32) + copy(simpleBytes, "hello") + + payload := &types.SigningPayload{ + AccountIdentifier: &types.AccountIdentifier{Address: "test"}, + Bytes: simpleBytes, + SignatureType: types.Ed25519_Keccak, + } + testSignature, err := signerEdwards25519Keccak.Sign(payload, types.Ed25519_Keccak) + assert.NoError(t, err) + + var signatureTests = []signatureTest{ + {mockSignature( + types.Ecdsa, + signerEdwards25519Keccak.PublicKey(), + simpleBytes, + simpleBytes), ErrVerifyUnsupportedPayloadSignatureType}, + {mockSignature( + types.EcdsaRecovery, + signerEdwards25519Keccak.PublicKey(), + simpleBytes, + simpleBytes), ErrVerifyUnsupportedPayloadSignatureType}, + {mockSignature( + types.Ed25519_Keccak, + signerEdwards25519Keccak.PublicKey(), + func() []byte { + b := make([]byte, 40) + copy(b, "hello") + + return b + }(), + testSignature.Bytes), ErrVerifyFailed}, + } + + for _, test := range signatureTests { + err := signerEdwards25519Keccak.Verify(test.signature) + assert.Contains(t, err.Error(), test.errMsg.Error()) + } + + goodSignature := mockSignature( + types.Ed25519_Keccak, + signerEdwards25519Keccak.PublicKey(), + simpleBytes, + testSignature.Bytes, + ) + assert.Equal(t, nil, signerEdwards25519Keccak.Verify(goodSignature)) +} diff --git a/types/curve_type.go b/types/curve_type.go index 10682ec8..6c0e184e 100644 --- a/types/curve_type.go +++ b/types/curve_type.go @@ -16,20 +16,23 @@ package types -// CurveType CurveType is the type of cryptographic curve associated with a PublicKey. * secp256k1: -// SEC compressed - `33 bytes` (https://secg.org/sec1-v2.pdf#subsubsection.2.3.3) * secp256r1: SEC -// compressed - `33 bytes` (https://secg.org/sec1-v2.pdf#subsubsection.2.3.3) * edwards25519: `y -// (255-bits) || x-sign-bit (1-bit)` - `32 bytes` (https://ed25519.cr.yp.to/ed25519-20110926.pdf) * -// tweedle: 1st pk : Fq.t (32 bytes) || 2nd pk : Fq.t (32 bytes) -// (https://github.com/CodaProtocol/coda/blob/develop/rfcs/0038-rosetta-construction-api.md#marshal-keys) +// CurveType CurveType is the type of cryptographic curve associated with a PublicKey. +// * secp256k1: SEC compressed - `33 bytes` (https://secg.org/sec1-v2.pdf#subsubsection.2.3.3) +// * secp256r1: SEC compressed - `33 bytes` (https://secg.org/sec1-v2.pdf#subsubsection.2.3.3) +// * edwards25519: `y (255-bits) || x-sign-bit (1-bit)` - `32 bytes` (https://ed25519.cr.yp.to/ed25519-20110926.pdf) +// * edwards25519_keccak: `y (255-bits) || x-sign-bit (1-bit)` - `32 bytes` using keccak as hash function +// (https://ed25519.cr.yp.to/ed25519-20110926.pdf) +// * tweedle: 1st pk : Fq.t (32 bytes) || 2nd pk : Fq.t (32 bytes) +// (https://github.com/CodaProtocol/coda/blob/develop/rfcs/0038-rosetta-construction-api.md#marshal-keys) // * pallas: `x (255 bits) || y-parity-bit (1-bit) - 32 bytes` (https://github.com/zcash/pasta) type CurveType string // List of CurveType const ( - Secp256k1 CurveType = "secp256k1" - Secp256r1 CurveType = "secp256r1" - Edwards25519 CurveType = "edwards25519" - Tweedle CurveType = "tweedle" - Pallas CurveType = "pallas" + Secp256k1 CurveType = "secp256k1" + Secp256r1 CurveType = "secp256r1" + Edwards25519 CurveType = "edwards25519" + Edwards25519_Keccak CurveType = "edwards25519_keccak" + Tweedle CurveType = "tweedle" + Pallas CurveType = "pallas" ) diff --git a/types/signature_type.go b/types/signature_type.go index 228e4c7b..df3b0933 100644 --- a/types/signature_type.go +++ b/types/signature_type.go @@ -16,15 +16,18 @@ package types -// SignatureType SignatureType is the type of a cryptographic signature. * ecdsa: `r (32-bytes) || s -// (32-bytes)` - `64 bytes` * ecdsa_recovery: `r (32-bytes) || s (32-bytes) || v (1-byte)` - `65 -// bytes` * ed25519: `R (32-byte) || s (32-bytes)` - `64 bytes` * schnorr_1: `r (32-bytes) || s -// (32-bytes)` - `64 bytes` (schnorr signature implemented by Zilliqa where both `r` and `s` are -// scalars encoded as `32-bytes` values, most significant byte first.) * schnorr_poseidon: `r -// (32-bytes) || s (32-bytes)` where s = Hash(1st pk || 2nd pk || r) - `64 bytes` (schnorr -// signature w/ Poseidon hash function implemented by O(1) Labs where both `r` and `s` are scalars -// encoded as `32-bytes` values, least significant byte first. -// https://github.com/CodaProtocol/signer-reference/blob/master/schnorr.ml ) +// SignatureType SignatureType is the type of a cryptographic signature. +// * ecdsa: `r (32-bytes) || s (32-bytes)` - `64 bytes` +// * ecdsa_recovery: `r (32-bytes) || s (32-bytes) || v (1-byte)` - `65 bytes` +// * ed25519: `R (32-byte) || s (32-bytes)` - `64 bytes` +// * ed25519_keccak: `R (32-byte) || s (32-bytes)` - `64 bytes` using keccak as hash function +// * schnorr_1: `r (32-bytes) || s (32-bytes)` - `64 bytes` +// (schnorr signature implemented by Zilliqa where both `r` and `s` are +// scalars encoded as `32-bytes` values, most significant byte first.) +// * schnorr_poseidon: `r (32-bytes) || s (32-bytes)` where s = Hash(1st pk || 2nd pk || r) - `64 bytes` +// (schnorr signature w/ Poseidon hash function implemented by O(1) Labs where both `r` and `s` are scalars +// encoded as `32-bytes` values, least significant byte first. +// https://github.com/CodaProtocol/signer-reference/blob/master/schnorr.ml ) type SignatureType string // List of SignatureType @@ -32,6 +35,7 @@ const ( Ecdsa SignatureType = "ecdsa" EcdsaRecovery SignatureType = "ecdsa_recovery" Ed25519 SignatureType = "ed25519" + Ed25519_Keccak SignatureType = "ed25519_keccak" Schnorr1 SignatureType = "schnorr_1" SchnorrPoseidon SignatureType = "schnorr_poseidon" )