From 03a86c3e1c6dea564378422c2c6009edcf40c8bc Mon Sep 17 00:00:00 2001 From: Felipe Madero Date: Mon, 15 Sep 2025 12:48:31 -0300 Subject: [PATCH 1/9] Add CubeSigner keychain integration Integrate CubeSigner SDK for remote key signing. Key changes: - Add CubeSigner SDK dependency and keychain implementations - Extend keychain interface with SigningOption so as to provide tx context to cubesigner - Move EthKeychain interface to the same package of the Keychain interface - Enable forced hash signing with new wallet option --- go.mod | 26 +- go.sum | 52 ++- .../keychain/cubesigner/cubesigner_client.go | 17 + .../cubesigner/cubesigner_keychain.go | 234 ++++++++++ .../cubesigner/cubesigner_keychain_test.go | 229 ++++++++++ .../cubesignermock/cubesigner_client.go | 97 ++++ .../cubesigner/mocks_generate_test.go | 6 + utils/crypto/keychain/keychain.go | 150 +------ utils/crypto/keychain/ledger/ledger.go | 23 + .../crypto/keychain/ledger/ledger_keychain.go | 149 ++++++ .../keychain/ledger/ledger_keychain_test.go | 424 ++++++++++++++++++ .../keychain/ledger/ledgermock/ledger.go | 131 ++++++ .../keychain/ledger/mocks_generate_test.go | 6 + utils/crypto/keychain/signing_options.go | 27 ++ utils/crypto/ledger/ledger.go | 6 +- utils/crypto/secp256k1/secp256k1.go | 6 +- vms/avm/txs/txstest/builder.go | 4 +- vms/platformvm/txs/txstest/wallet.go | 3 +- vms/secp256k1fx/keychain.go | 3 +- wallet/chain/c/signer.go | 16 +- wallet/chain/p/signer/signer.go | 34 +- wallet/chain/p/signer/visitor.go | 55 +-- wallet/chain/p/signer/with_options.go | 36 ++ wallet/chain/p/wallet/wallet.go | 4 +- wallet/chain/x/signer/signer.go | 34 +- wallet/chain/x/signer/visitor.go | 35 +- wallet/chain/x/wallet.go | 2 +- wallet/subnet/primary/common/options.go | 15 +- wallet/subnet/primary/wallet.go | 10 +- 29 files changed, 1592 insertions(+), 242 deletions(-) create mode 100644 utils/crypto/keychain/cubesigner/cubesigner_client.go create mode 100644 utils/crypto/keychain/cubesigner/cubesigner_keychain.go create mode 100644 utils/crypto/keychain/cubesigner/cubesigner_keychain_test.go create mode 100644 utils/crypto/keychain/cubesigner/cubesignermock/cubesigner_client.go create mode 100644 utils/crypto/keychain/cubesigner/mocks_generate_test.go create mode 100644 utils/crypto/keychain/ledger/ledger.go create mode 100644 utils/crypto/keychain/ledger/ledger_keychain.go create mode 100644 utils/crypto/keychain/ledger/ledger_keychain_test.go create mode 100644 utils/crypto/keychain/ledger/ledgermock/ledger.go create mode 100644 utils/crypto/keychain/ledger/mocks_generate_test.go create mode 100644 utils/crypto/keychain/signing_options.go create mode 100644 wallet/chain/p/signer/with_options.go diff --git a/go.mod b/go.mod index f9dc38cc7b4f..a2877eb8bb4d 100644 --- a/go.mod +++ b/go.mod @@ -68,7 +68,7 @@ require ( golang.org/x/net v0.38.0 golang.org/x/sync v0.12.0 golang.org/x/term v0.30.0 - golang.org/x/time v0.3.0 + golang.org/x/time v0.5.0 golang.org/x/tools v0.29.0 gonum.org/v1/gonum v0.11.0 google.golang.org/genproto/googleapis/rpc v0.0.0-20240827150818-7e3bb234dfed @@ -82,6 +82,26 @@ require ( k8s.io/utils v0.0.0-20230726121419-3b25d923346b ) +require ( + github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect + github.com/aws/aws-sdk-go-v2 v1.36.2 // indirect + github.com/aws/aws-sdk-go-v2/config v1.29.7 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.17.60 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.29 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.33 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.33 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.14 // indirect + github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.34.19 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.24.16 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.15 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.33.15 // indirect + github.com/aws/smithy-go v1.22.2 // indirect + github.com/cubist-labs/cubesigner-go-sdk v0.0.16 + github.com/oapi-codegen/runtime v1.1.1 // indirect +) + require ( github.com/FactomProject/basen v0.0.0-20150613233007-fe3947df716e // indirect github.com/FactomProject/btcutilecc v0.0.0-20130527213604-d3a63a5752ec // indirect @@ -150,7 +170,7 @@ require ( github.com/magiconair/properties v1.8.6 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.17 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect github.com/mattn/go-shellwords v1.0.12 // indirect github.com/mitchellh/pointerstructure v1.2.0 // indirect @@ -163,7 +183,7 @@ require ( github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/pelletier/go-toml v1.9.5 // indirect - github.com/pelletier/go-toml/v2 v2.0.5 // indirect + github.com/pelletier/go-toml/v2 v2.0.9 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect diff --git a/go.sum b/go.sum index 7aa42bcfe858..1aa17033e7af 100644 --- a/go.sum +++ b/go.sum @@ -56,6 +56,7 @@ github.com/FactomProject/btcutilecc v0.0.0-20130527213604-d3a63a5752ec/go.mod h1 github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0= github.com/StephenButtolph/canoto v0.17.1 h1:WnN5czIHHALq7pwc+Z2F1sCsKJCDhxlq0zL0YK1etHc= github.com/StephenButtolph/canoto v0.17.1/go.mod h1:IcnAHC6nJUfQFVR9y60ko2ecUqqHHSB6UwI9NnBFZnE= @@ -67,6 +68,8 @@ github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/antithesishq/antithesis-sdk-go v0.3.8 h1:OvGoHxIcOXFJLyn9IJQ5DzByZ3YVAWNBc394ObzDRb8= github.com/antithesishq/antithesis-sdk-go v0.3.8/go.mod h1:IUpT2DPAKh6i/YhSbt6Gl3v2yvUZjmKncl7U91fup7E= +github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= +github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= @@ -80,11 +83,40 @@ github.com/ava-labs/libevm v1.13.14-0.3.0.rc.6 h1:tyM659nDOknwTeU4A0fUVsGNIU7k0v github.com/ava-labs/libevm v1.13.14-0.3.0.rc.6/go.mod h1:zP/DOcABRWargBmUWv1jXplyWNcfmBy9cxr0lw3LW3g= github.com/ava-labs/simplex v0.0.0-20250819180907-c9b5ae6aad19 h1:dtsx6DJw6wWkrMwDIcSUVJ9lXMeckgI5oZ7fJbAi23c= github.com/ava-labs/simplex v0.0.0-20250819180907-c9b5ae6aad19/go.mod h1:GVzumIo3zR23/qGRN2AdnVkIPHcKMq/D89EGWZfMGQ0= +github.com/aws/aws-sdk-go-v2 v1.36.2 h1:Ub6I4lq/71+tPb/atswvToaLGVMxKZvjYDVOWEExOcU= +github.com/aws/aws-sdk-go-v2 v1.36.2/go.mod h1:LLXuLpgzEbD766Z5ECcRmi8AzSwfZItDtmABVkRLGzg= +github.com/aws/aws-sdk-go-v2/config v1.29.7 h1:71nqi6gUbAUiEQkypHQcNVSFJVUFANpSeUNShiwWX2M= +github.com/aws/aws-sdk-go-v2/config v1.29.7/go.mod h1:yqJQ3nh2HWw/uxd56bicyvmDW4KSc+4wN6lL8pYjynU= +github.com/aws/aws-sdk-go-v2/credentials v1.17.60 h1:1dq+ELaT5ogfmqtV1eocq8SpOK1NRsuUfmhQtD/XAh4= +github.com/aws/aws-sdk-go-v2/credentials v1.17.60/go.mod h1:HDes+fn/xo9VeszXqjBVkxOo/aUy8Mc6QqKvZk32GlE= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.29 h1:JO8pydejFKmGcUNiiwt75dzLHRWthkwApIvPoyUtXEg= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.29/go.mod h1:adxZ9i9DRmB8zAT0pO0yGnsmu0geomp5a3uq5XpgOJ8= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.33 h1:knLyPMw3r3JsU8MFHWctE4/e2qWbPaxDYLlohPvnY8c= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.33/go.mod h1:EBp2HQ3f+XCB+5J+IoEbGhoV7CpJbnrsd4asNXmTL0A= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.33 h1:K0+Ne08zqti8J9jwENxZ5NoUyBnaFDTu3apwQJWrwwA= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.33/go.mod h1:K97stwwzaWzmqxO8yLGHhClbVW1tC6VT1pDLk1pGrq4= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 h1:eAh2A4b5IzM/lum78bZ590jy36+d/aFLgKF/4Vd1xPE= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3/go.mod h1:0yKJC/kb8sAnmlYa6Zs3QVYqaC8ug2AbnNChv5Ox3uA= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.14 h1:2scbY6//jy/s8+5vGrk7l1+UtHl0h9A4MjOO2k/TM2E= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.14/go.mod h1:bRpZPHZpSe5YRHmPfK3h1M7UBFCn2szHzyx0rw04zro= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.34.19 h1:O2xbipq7k1kTct69V7mFidwTagld9c/6iyK+3yo+QNg= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.34.19/go.mod h1:CxTOwBy2Qs8/+yV7fkz4eZB1RB5qeWaW9SvznvFLgRA= +github.com/aws/aws-sdk-go-v2/service/sso v1.24.16 h1:YV6xIKDJp6U7YB2bxfud9IENO1LRpGhe2Tv/OKtPrOQ= +github.com/aws/aws-sdk-go-v2/service/sso v1.24.16/go.mod h1:DvbmMKgtpA6OihFJK13gHMZOZrCHttz8wPHGKXqU+3o= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.15 h1:kMyK3aKotq1aTBsj1eS8ERJLjqYRRRcsmP33ozlCvlk= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.15/go.mod h1:5uPZU7vSNzb8Y0dm75xTikinegPYK3uJmIHQZFq5Aqo= +github.com/aws/aws-sdk-go-v2/service/sts v1.33.15 h1:ht1jVmeeo2anR7zDiYJLSnRYnO/9NILXXu42FP3rJg0= +github.com/aws/aws-sdk-go-v2/service/sts v1.33.15/go.mod h1:xWZ5cOiFe3czngChE4LhCBqUxNwgfwndEF7XlYP/yD8= +github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ= +github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bits-and-blooms/bitset v1.10.0 h1:ePXTeiPEazB5+opbv5fr8umg2R/1NlzgDsyepwsSr88= github.com/bits-and-blooms/bitset v1.10.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= github.com/btcsuite/btcd v0.23.0 h1:V2/ZgjfDFIygAX3ZapeigkVBoVUtOJKSwrhZdlpSvaA= @@ -162,6 +194,8 @@ github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233/go.mod h1:geZJ github.com/crate-crypto/go-kzg-4844 v1.0.0 h1:TsSgHwrkTKecKJ4kadtHi4b3xHW5dCFUDFnUp1TsawI= github.com/crate-crypto/go-kzg-4844 v1.0.0/go.mod h1:1kMhvPgI0Ky3yIa+9lFySEBUBXkYxeOi8ZF1sYioxhc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/cubist-labs/cubesigner-go-sdk v0.0.16 h1:X7kPk4BhVggw/XtKXPJcT1kgnV0v70nIdqn+wAnJgqU= +github.com/cubist-labs/cubesigner-go-sdk v0.0.16/go.mod h1:aSYPLNkIt10+QQiNI4ctE+wQ1IoJdUqlUlLCmwYXk2w= github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -417,6 +451,7 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= github.com/kataras/golog v0.0.10/go.mod h1:yJ8YKCmyL+nWjERB90Qwn+bdyBZsaQwU3bTVFgkFIp8= github.com/kataras/iris/v12 v12.1.8/go.mod h1:LMYy4VlP67TQ3Zgriz8RE2h2kMZV2SgMYbq3UhfoFmE= @@ -463,8 +498,8 @@ github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2y github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= @@ -510,6 +545,8 @@ github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96d github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/oapi-codegen/runtime v1.1.1 h1:EXLHh0DXIJnWhdRPN2w4MXAzFyE4CskzhNLUmtpMYro= +github.com/oapi-codegen/runtime v1.1.1/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -536,8 +573,8 @@ github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3I github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg= -github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= +github.com/pelletier/go-toml/v2 v2.0.9 h1:uH2qQXheeefCCkuBBSLi7jCiSmj3VRh2+Goq2N7Xxu0= +github.com/pelletier/go-toml/v2 v2.0.9/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= github.com/pires/go-proxyproto v0.6.2 h1:KAZ7UteSOt6urjme6ZldyFm4wDe/z0ZUP0Yv0Dos0d8= @@ -602,6 +639,7 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/spf13/viper v1.12.0 h1:CZ7eSOd3kZoaYDLbXnmzgQI5RlciuXBMA+18HwHRfZQ= github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI= +github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= github.com/status-im/keycard-go v0.2.0 h1:QDLFswOQu1r5jsycloeQh3bVU8n/NatHHaZobtDnDzA= github.com/status-im/keycard-go v0.2.0/go.mod h1:wlp8ZLbsmrF6g6WjugPAx+IzoLrkdf9+mHxBEeo3Hbg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -619,6 +657,7 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.3.0 h1:mjC+YW8QpAdXibNi+vNWgzmgBH4+5l5dCXv8cNysBLI= @@ -894,6 +933,7 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= @@ -918,8 +958,8 @@ golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/utils/crypto/keychain/cubesigner/cubesigner_client.go b/utils/crypto/keychain/cubesigner/cubesigner_client.go new file mode 100644 index 000000000000..b8c721d71698 --- /dev/null +++ b/utils/crypto/keychain/cubesigner/cubesigner_client.go @@ -0,0 +1,17 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package cubesigner + +import ( + "github.com/cubist-labs/cubesigner-go-sdk/client" + "github.com/cubist-labs/cubesigner-go-sdk/models" +) + +// CubeSignerClient defines the interface for CubeSigner client operations +// needed by the keychain implementation +type CubeSignerClient interface { + GetKeyInOrg(keyID string) (*models.KeyInfo, error) + BlobSign(keyID string, request models.BlobSignRequest, receipts ...*client.MfaReceipt) (*client.CubeSignerResponse[models.SignResponse], error) + AvaSerializedTxSign(chainAlias, materialID string, request models.AvaSerializedTxSignRequest, receipts ...*client.MfaReceipt) (*client.CubeSignerResponse[models.SignResponse], error) +} \ No newline at end of file diff --git a/utils/crypto/keychain/cubesigner/cubesigner_keychain.go b/utils/crypto/keychain/cubesigner/cubesigner_keychain.go new file mode 100644 index 000000000000..7cc9a09cd121 --- /dev/null +++ b/utils/crypto/keychain/cubesigner/cubesigner_keychain.go @@ -0,0 +1,234 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. +package cubesigner + +import ( + "encoding/base64" + "encoding/hex" + "fmt" + "strings" + + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/constants" + "github.com/ava-labs/avalanchego/utils/crypto/keychain" + avasecp256k1 "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" + "github.com/ava-labs/avalanchego/utils/formatting/address" + "github.com/ava-labs/avalanchego/utils/set" + "github.com/ava-labs/libevm/common" + + "github.com/cubist-labs/cubesigner-go-sdk/client" + "github.com/cubist-labs/cubesigner-go-sdk/models" + secp256k1 "github.com/decred/dcrd/dcrec/secp256k1/v4" + "golang.org/x/exp/maps" +) + +var ( + _ keychain.Keychain = (*CubesignerKeychain)(nil) + _ keychain.EthKeychain = (*CubesignerKeychain)(nil) + _ keychain.Signer = (*cubesignerSigner)(nil) + _ CubeSignerClient = (*client.ApiClient)(nil) +) + +// keyInfo holds both the public key and keyID for a cubesigner key +type keyInfo struct { + pubKey *avasecp256k1.PublicKey + keyID string +} + +// cubesignerKeychain is an abstraction of the underlying cubesigner connection, +// to be able to get a signer from a specific address +type CubesignerKeychain struct { + cubesignerClient CubeSignerClient + avaAddrToKeyInfo map[ids.ShortID]*keyInfo + ethAddrToKeyInfo map[common.Address]*keyInfo +} + +// processKey obtains and processes key information from cubesigner +func processKey( + cubesignerClient CubeSignerClient, + keyID string, +) (*avasecp256k1.PublicKey, error) { + // Validate key exists + keyInfo, err := cubesignerClient.GetKeyInOrg(keyID) + if err != nil { + return nil, fmt.Errorf("could not find server key %s: %w", keyID, err) + } + + // get public key + pubKeyHex := strings.TrimPrefix(keyInfo.PublicKey, "0x") + pubKeyBytes, err := hex.DecodeString(pubKeyHex) + if err != nil { + return nil, fmt.Errorf("failed to decode public key for server key %s: %w", keyID, err) + } + if len(pubKeyBytes) != 65 { + return nil, fmt.Errorf("invalid public key length for server key %s: expected 65 bytes, got %d", keyID, len(pubKeyBytes)) + } + if pubKeyBytes[0] != 0x04 { + return nil, fmt.Errorf("invalid public key format for server key %s: expected uncompressed format (0x04 prefix), got 0x%02x", keyID, pubKeyBytes[0]) + } + pubKey, err := secp256k1.ParsePubKey(pubKeyBytes) + if err != nil { + return nil, fmt.Errorf("invalid public key format for server key %s: %w", keyID, err) + } + avaPubKey, err := avasecp256k1.ToPublicKey(pubKey.SerializeCompressed()) + if err != nil { + return nil, fmt.Errorf("invalid public key format for server key %s: %w", keyID, err) + } + + return avaPubKey, nil +} + +// NewCubeSignerKeychain creates a new keychain abstraction over a cubesigner connection +func NewCubesignerKeychain( + cubesignerClient CubeSignerClient, + keyIDs []string, +) (*CubesignerKeychain, error) { + if len(keyIDs) == 0 { + return nil, fmt.Errorf("you need to provide at least one key to create a server keychain") + } + + avaAddrToKeyInfo := map[ids.ShortID]*keyInfo{} + ethAddrToKeyInfo := map[common.Address]*keyInfo{} + + for _, keyID := range keyIDs { + avaPubKey, err := processKey(cubesignerClient, keyID) + if err != nil { + return nil, err + } + + keyInf := &keyInfo{ + pubKey: avaPubKey, + keyID: keyID, + } + + avaAddrToKeyInfo[avaPubKey.Address()] = keyInf + ethAddrToKeyInfo[avaPubKey.EthAddress()] = keyInf + } + + return &CubesignerKeychain{ + cubesignerClient: cubesignerClient, + avaAddrToKeyInfo: avaAddrToKeyInfo, + ethAddrToKeyInfo: ethAddrToKeyInfo, + }, nil +} + +func (kc *CubesignerKeychain) Addresses() set.Set[ids.ShortID] { + return set.Of(maps.Keys(kc.avaAddrToKeyInfo)...) +} + +func (kc *CubesignerKeychain) Get(addr ids.ShortID) (keychain.Signer, bool) { + keyInf, found := kc.avaAddrToKeyInfo[addr] + if !found { + return nil, false + } + return &cubesignerSigner{ + cubesignerClient: kc.cubesignerClient, + pubKey: keyInf.pubKey, + keyID: keyInf.keyID, + }, true +} + +func (kc *CubesignerKeychain) EthAddresses() set.Set[common.Address] { + return set.Of(maps.Keys(kc.ethAddrToKeyInfo)...) +} + +func (kc *CubesignerKeychain) GetEth(addr common.Address) (keychain.Signer, bool) { + keyInf, found := kc.ethAddrToKeyInfo[addr] + if !found { + return nil, false + } + return &cubesignerSigner{ + cubesignerClient: kc.cubesignerClient, + pubKey: keyInf.pubKey, + keyID: keyInf.keyID, + }, true +} + +// cubesignerAvagoSigner is an abstraction of the underlying cubesigner connection, +// to be able sign for a specific address +type cubesignerSigner struct { + cubesignerClient CubeSignerClient + pubKey *avasecp256k1.PublicKey + keyID string +} + +// processSignatureResponse is a helper function that processes the common response +// pattern from CubeSigner signing operations +func processSignatureResponse(signatureHex string) ([]byte, error) { + signatureBytes, err := hex.DecodeString(strings.TrimPrefix(signatureHex, "0x")) + if err != nil { + return nil, fmt.Errorf("failed to decode server's signature: %w", err) + } + if len(signatureBytes) != avasecp256k1.SignatureLen { + return nil, fmt.Errorf("invalid server's signature length: expected %d bytes, got %d", avasecp256k1.SignatureLen, len(signatureBytes)) + } + return signatureBytes, nil +} + +// expects to receive a hash of the unsigned tx bytes +func (s *cubesignerSigner) SignHash(b []byte) ([]byte, error) { + response, err := s.cubesignerClient.BlobSign( + s.keyID, + models.BlobSignRequest{ + MessageBase64: base64.StdEncoding.EncodeToString(b), + }, + ) + if err != nil { + return nil, fmt.Errorf("server signing err: %w", err) + } + if response.ResponseData == nil { + return nil, fmt.Errorf("empty signature obtained from server") + } + return processSignatureResponse(response.ResponseData.Signature) +} + +// expects to receive the unsigned tx bytes +func (s *cubesignerSigner) Sign(b []byte, opts ...keychain.SigningOption) ([]byte, error) { + options := &keychain.SigningOptions{} + for _, opt := range opts { + opt(options) + } + + // Require chainAlias and network from options + if options.ChainAlias == "" { + return nil, fmt.Errorf("chainAlias must be specified in options for CubeSigner") + } + if options.ChainAlias != "P" && options.ChainAlias != "X" && options.ChainAlias != "C" { + return nil, fmt.Errorf("chainAlias must be 'P', 'X' or 'C' for CubeSigner, got %q", options.ChainAlias) + } + if options.NetworkID == 0 { + return nil, fmt.Errorf("network ID must be specified in options for CubeSigner") + } + + var materialID string + if options.ChainAlias == "C" { + materialID = s.pubKey.EthAddress().Hex() + } else { + hrp := constants.GetHRP(options.NetworkID) + addr := s.pubKey.Address() + var err error + materialID, err = address.FormatBech32(hrp, addr.Bytes()) + if err != nil { + return nil, fmt.Errorf("failed to format %s address %v as Bech32: %w", hrp, addr, err) + } + } + + response, err := s.cubesignerClient.AvaSerializedTxSign( + options.ChainAlias, + materialID, + models.AvaSerializedTxSignRequest{ + Tx: "0x" + hex.EncodeToString(b), + }, + ) + if err != nil { + return nil, fmt.Errorf("server signing err: %w", err) + } + if response.ResponseData == nil { + return nil, fmt.Errorf("empty signature obtained from server") + } + return processSignatureResponse(response.ResponseData.Signature) +} + +func (s *cubesignerSigner) Address() ids.ShortID { + return s.pubKey.Address() +} diff --git a/utils/crypto/keychain/cubesigner/cubesigner_keychain_test.go b/utils/crypto/keychain/cubesigner/cubesigner_keychain_test.go new file mode 100644 index 000000000000..0eea77ce8289 --- /dev/null +++ b/utils/crypto/keychain/cubesigner/cubesigner_keychain_test.go @@ -0,0 +1,229 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package cubesigner + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" + + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/crypto/keychain/cubesigner/cubesignermock" + "github.com/cubist-labs/cubesigner-go-sdk/client" + "github.com/cubist-labs/cubesigner-go-sdk/models" +) + +var errTest = errors.New("test") + +func TestNewCubesignerKeychain(t *testing.T) { + require := require.New(t) + ctrl := gomock.NewController(t) + + keyID := "test-key-id" + + // user provides no keys + mockClient := cubesignermock.NewCubeSignerClient(ctrl) + _, err := NewCubesignerKeychain(mockClient, []string{}) + require.ErrorContains(err, "you need to provide at least one key") + + // client returns error when getting key info + mockClient = cubesignermock.NewCubeSignerClient(ctrl) + mockClient.EXPECT().GetKeyInOrg(keyID).Return(nil, errTest).Times(1) + _, err = NewCubesignerKeychain(mockClient, []string{keyID}) + require.ErrorIs(err, errTest) + + // client returns unsupported key type + mockClient = cubesignermock.NewCubeSignerClient(ctrl) + keyInfo := &models.KeyInfo{ + KeyType: "UnsupportedType", + PublicKey: "0x04" + "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", + } + mockClient.EXPECT().GetKeyInOrg(keyID).Return(keyInfo, nil).Times(1) + _, err = NewCubesignerKeychain(mockClient, []string{keyID}) + require.ErrorContains(err, "keytype UnsupportedType of server key") + + // client returns invalid public key format + mockClient = cubesignermock.NewCubeSignerClient(ctrl) + keyInfo = &models.KeyInfo{ + KeyType: models.SecpAvaAddr, + PublicKey: "invalid-hex", + } + mockClient.EXPECT().GetKeyInOrg(keyID).Return(keyInfo, nil).Times(1) + _, err = NewCubesignerKeychain(mockClient, []string{keyID}) + require.ErrorContains(err, "failed to decode public key") + + // good path - Avalanche address + mockClient = cubesignermock.NewCubeSignerClient(ctrl) + keyInfo = &models.KeyInfo{ + KeyType: models.SecpAvaAddr, + PublicKey: "0x04" + "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8", + } + mockClient.EXPECT().GetKeyInOrg(keyID).Return(keyInfo, nil).Times(1) + kc, err := NewCubesignerKeychain(mockClient, []string{keyID}) + require.NoError(err) + require.NotNil(kc) + + // good path - Ethereum address + mockClient = cubesignermock.NewCubeSignerClient(ctrl) + keyInfo = &models.KeyInfo{ + KeyType: models.SecpEthAddr, + PublicKey: "0x04" + "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8", + } + mockClient.EXPECT().GetKeyInOrg(keyID).Return(keyInfo, nil).Times(1) + kc, err = NewCubesignerKeychain(mockClient, []string{keyID}) + require.NoError(err) + require.NotNil(kc) +} + +func TestCubesignerKeychain_Addresses(t *testing.T) { + require := require.New(t) + ctrl := gomock.NewController(t) + + keyID := "test-key-id" + mockClient := cubesignermock.NewCubeSignerClient(ctrl) + + keyInfo := &models.KeyInfo{ + KeyType: models.SecpAvaAddr, + PublicKey: "0x04" + "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8", + } + mockClient.EXPECT().GetKeyInOrg(keyID).Return(keyInfo, nil).Times(1) + + kc, err := NewCubesignerKeychain(mockClient, []string{keyID}) + require.NoError(err) + + addresses := kc.Addresses() + require.Len(addresses, 1) +} + +func TestCubesignerKeychain_Get(t *testing.T) { + require := require.New(t) + ctrl := gomock.NewController(t) + + keyID := "test-key-id" + mockClient := cubesignermock.NewCubeSignerClient(ctrl) + + keyInfo := &models.KeyInfo{ + KeyType: models.SecpAvaAddr, + PublicKey: "0x04" + "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8", + } + mockClient.EXPECT().GetKeyInOrg(keyID).Return(keyInfo, nil).Times(1) + + kc, err := NewCubesignerKeychain(mockClient, []string{keyID}) + require.NoError(err) + + addresses := kc.Addresses() + require.Len(addresses, 1) + + addr := addresses.List()[0] + signer, found := kc.Get(addr) + require.True(found) + require.NotNil(signer) + require.Equal(addr, signer.Address()) + + // test with non-existent address + randomAddr := ids.GenerateTestShortID() + signer, found = kc.Get(randomAddr) + require.False(found) + require.Nil(signer) +} + +func TestCubesignerKeychain_EthAddresses(t *testing.T) { + require := require.New(t) + ctrl := gomock.NewController(t) + + keyID := "test-key-id" + mockClient := cubesignermock.NewCubeSignerClient(ctrl) + + keyInfo := &models.KeyInfo{ + KeyType: models.SecpEthAddr, + PublicKey: "0x04" + "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8", + } + mockClient.EXPECT().GetKeyInOrg(keyID).Return(keyInfo, nil).Times(1) + + kc, err := NewCubesignerKeychain(mockClient, []string{keyID}) + require.NoError(err) + + ethAddresses := kc.EthAddresses() + require.Len(ethAddresses, 1) +} + +func TestCubesignerKeychain_GetEth(t *testing.T) { + require := require.New(t) + ctrl := gomock.NewController(t) + + keyID := "test-key-id" + mockClient := cubesignermock.NewCubeSignerClient(ctrl) + + keyInfo := &models.KeyInfo{ + KeyType: models.SecpEthAddr, + PublicKey: "0x04" + "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8", + } + mockClient.EXPECT().GetKeyInOrg(keyID).Return(keyInfo, nil).Times(1) + + kc, err := NewCubesignerKeychain(mockClient, []string{keyID}) + require.NoError(err) + + ethAddresses := kc.EthAddresses() + require.Len(ethAddresses, 1) + + ethAddr := ethAddresses.List()[0] + signer, found := kc.GetEth(ethAddr) + require.True(found) + require.NotNil(signer) + + // verify this is a C-chain signer + cubesignerSigner := signer.(*cubesignerSigner) + require.True(cubesignerSigner.cChainSigner) +} + +func TestCubesignerSigner_SignHash(t *testing.T) { + require := require.New(t) + ctrl := gomock.NewController(t) + + keyID := "test-key-id" + mockClient := cubesignermock.NewCubeSignerClient(ctrl) + + keyInfo := &models.KeyInfo{ + KeyType: models.SecpAvaAddr, + PublicKey: "0x04" + "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8", + } + mockClient.EXPECT().GetKeyInOrg(keyID).Return(keyInfo, nil).Times(1) + + kc, err := NewCubesignerKeychain(mockClient, []string{keyID}) + require.NoError(err) + + addresses := kc.Addresses() + addr := addresses.List()[0] + signer, found := kc.Get(addr) + require.True(found) + + hash := []byte("test-hash") + + // test successful signing + response := &client.CubeSignerResponse[models.SignResponse]{ + ResponseData: &models.SignResponse{ + Signature: "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef01", + }, + } + mockClient.EXPECT().BlobSign(keyID, gomock.Any()).Return(response, nil).Times(1) + + signature, err := signer.SignHash(hash) + require.NoError(err) + require.NotNil(signature) + + // test client error + mockClient.EXPECT().BlobSign(keyID, gomock.Any()).Return(nil, errTest).Times(1) + _, err = signer.SignHash(hash) + require.ErrorIs(err, errTest) + + // test empty response + emptyResponse := &client.CubeSignerResponse[models.SignResponse]{ + ResponseData: nil, + } + mockClient.EXPECT().BlobSign(keyID, gomock.Any()).Return(emptyResponse, nil).Times(1) + _, err = signer.SignHash(hash) + require.ErrorContains(err, "empty signature obtained from server") +} \ No newline at end of file diff --git a/utils/crypto/keychain/cubesigner/cubesignermock/cubesigner_client.go b/utils/crypto/keychain/cubesigner/cubesignermock/cubesigner_client.go new file mode 100644 index 000000000000..b2ce1b184945 --- /dev/null +++ b/utils/crypto/keychain/cubesigner/cubesignermock/cubesigner_client.go @@ -0,0 +1,97 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/ava-labs/avalanchego/utils/crypto/keychain/cubesigner (interfaces: CubeSignerClient) +// +// Generated by this command: +// +// mockgen -package=cubesignermock -destination=cubesignermock/cubesigner_client.go -mock_names=CubeSignerClient=CubeSignerClient . CubeSignerClient +// + +// Package cubesignermock is a generated GoMock package. +package cubesignermock + +import ( + reflect "reflect" + + client "github.com/cubist-labs/cubesigner-go-sdk/client" + models "github.com/cubist-labs/cubesigner-go-sdk/models" + gomock "go.uber.org/mock/gomock" +) + +// CubeSignerClient is a mock of CubeSignerClient interface. +type CubeSignerClient struct { + ctrl *gomock.Controller + recorder *CubeSignerClientMockRecorder + isgomock struct{} +} + +// CubeSignerClientMockRecorder is the mock recorder for CubeSignerClient. +type CubeSignerClientMockRecorder struct { + mock *CubeSignerClient +} + +// NewCubeSignerClient creates a new mock instance. +func NewCubeSignerClient(ctrl *gomock.Controller) *CubeSignerClient { + mock := &CubeSignerClient{ctrl: ctrl} + mock.recorder = &CubeSignerClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *CubeSignerClient) EXPECT() *CubeSignerClientMockRecorder { + return m.recorder +} + +// AvaSerializedTxSign mocks base method. +func (m *CubeSignerClient) AvaSerializedTxSign(chainAlias, materialID string, request models.AvaSerializedTxSignRequest, receipts ...*client.MfaReceipt) (*client.CubeSignerResponse[models.SignResponse], error) { + m.ctrl.T.Helper() + varargs := []any{chainAlias, materialID, request} + for _, a := range receipts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "AvaSerializedTxSign", varargs...) + ret0, _ := ret[0].(*client.CubeSignerResponse[models.SignResponse]) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// AvaSerializedTxSign indicates an expected call of AvaSerializedTxSign. +func (mr *CubeSignerClientMockRecorder) AvaSerializedTxSign(chainAlias, materialID, request any, receipts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{chainAlias, materialID, request}, receipts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AvaSerializedTxSign", reflect.TypeOf((*CubeSignerClient)(nil).AvaSerializedTxSign), varargs...) +} + +// BlobSign mocks base method. +func (m *CubeSignerClient) BlobSign(keyID string, request models.BlobSignRequest, receipts ...*client.MfaReceipt) (*client.CubeSignerResponse[models.SignResponse], error) { + m.ctrl.T.Helper() + varargs := []any{keyID, request} + for _, a := range receipts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "BlobSign", varargs...) + ret0, _ := ret[0].(*client.CubeSignerResponse[models.SignResponse]) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// BlobSign indicates an expected call of BlobSign. +func (mr *CubeSignerClientMockRecorder) BlobSign(keyID, request any, receipts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{keyID, request}, receipts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BlobSign", reflect.TypeOf((*CubeSignerClient)(nil).BlobSign), varargs...) +} + +// GetKeyInOrg mocks base method. +func (m *CubeSignerClient) GetKeyInOrg(keyID string) (*models.KeyInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetKeyInOrg", keyID) + ret0, _ := ret[0].(*models.KeyInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetKeyInOrg indicates an expected call of GetKeyInOrg. +func (mr *CubeSignerClientMockRecorder) GetKeyInOrg(keyID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetKeyInOrg", reflect.TypeOf((*CubeSignerClient)(nil).GetKeyInOrg), keyID) +} diff --git a/utils/crypto/keychain/cubesigner/mocks_generate_test.go b/utils/crypto/keychain/cubesigner/mocks_generate_test.go new file mode 100644 index 000000000000..5a5f526fc623 --- /dev/null +++ b/utils/crypto/keychain/cubesigner/mocks_generate_test.go @@ -0,0 +1,6 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package cubesigner + +//go:generate go run go.uber.org/mock/mockgen -package=${GOPACKAGE}mock -destination=${GOPACKAGE}mock/cubesigner_client.go -mock_names=CubeSignerClient=CubeSignerClient . CubeSignerClient diff --git a/utils/crypto/keychain/keychain.go b/utils/crypto/keychain/keychain.go index a27f8be24baf..8514c28ef3e3 100644 --- a/utils/crypto/keychain/keychain.go +++ b/utils/crypto/keychain/keychain.go @@ -1,31 +1,20 @@ -// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package keychain import ( - "errors" - "fmt" - + "github.com/ava-labs/libevm/common" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/set" ) -var ( - _ Keychain = (*ledgerKeychain)(nil) - _ Signer = (*ledgerSigner)(nil) - - ErrInvalidIndicesLength = errors.New("number of indices should be greater than 0") - ErrInvalidNumAddrsToDerive = errors.New("number of addresses to derive should be greater than 0") - ErrInvalidNumAddrsDerived = errors.New("incorrect number of ledger derived addresses") - ErrInvalidNumSignatures = errors.New("incorrect number of signatures") -) // Signer implements functions for a keychain to return its main address and -// to sign a hash +// to sign a hash or transaction type Signer interface { SignHash([]byte) ([]byte, error) - Sign([]byte) ([]byte, error) + Sign([]byte, ...SigningOption) ([]byte, error) Address() ids.ShortID } @@ -39,127 +28,12 @@ type Keychain interface { Addresses() set.Set[ids.ShortID] } -// ledgerKeychain is an abstraction of the underlying ledger hardware device, -// to be able to get a signer from a finite set of derived signers -type ledgerKeychain struct { - ledger Ledger - addrs set.Set[ids.ShortID] - addrToIdx map[ids.ShortID]uint32 -} - -// ledgerSigner is an abstraction of the underlying ledger hardware device, -// to be able sign for a specific address -type ledgerSigner struct { - ledger Ledger - idx uint32 - addr ids.ShortID -} - -// NewLedgerKeychain creates a new Ledger with [numToDerive] addresses. -func NewLedgerKeychain(l Ledger, numToDerive int) (Keychain, error) { - if numToDerive < 1 { - return nil, ErrInvalidNumAddrsToDerive - } - - indices := make([]uint32, numToDerive) - for i := range indices { - indices[i] = uint32(i) - } - - return NewLedgerKeychainFromIndices(l, indices) -} - -// NewLedgerKeychainFromIndices creates a new Ledger with addresses taken from the given [indices]. -func NewLedgerKeychainFromIndices(l Ledger, indices []uint32) (Keychain, error) { - if len(indices) == 0 { - return nil, ErrInvalidIndicesLength - } - - addrs, err := l.Addresses(indices) - if err != nil { - return nil, err - } - - if len(addrs) != len(indices) { - return nil, fmt.Errorf( - "%w. expected %d, got %d", - ErrInvalidNumAddrsDerived, - len(indices), - len(addrs), - ) - } - - addrsSet := set.Of(addrs...) - - addrToIdx := map[ids.ShortID]uint32{} - for i := range indices { - addrToIdx[addrs[i]] = indices[i] - } - - return &ledgerKeychain{ - ledger: l, - addrs: addrsSet, - addrToIdx: addrToIdx, - }, nil -} - -func (l *ledgerKeychain) Addresses() set.Set[ids.ShortID] { - return l.addrs -} - -func (l *ledgerKeychain) Get(addr ids.ShortID) (Signer, bool) { - idx, ok := l.addrToIdx[addr] - if !ok { - return nil, false - } - - return &ledgerSigner{ - ledger: l.ledger, - idx: idx, - addr: addr, - }, true -} - -// expects to receive a hash of the unsigned tx bytes -func (l *ledgerSigner) SignHash(b []byte) ([]byte, error) { - // Sign using the address with index l.idx on the ledger device. The number - // of returned signatures should be the same length as the provided indices. - sigs, err := l.ledger.SignHash(b, []uint32{l.idx}) - if err != nil { - return nil, err - } - - if sigsLen := len(sigs); sigsLen != 1 { - return nil, fmt.Errorf( - "%w. expected 1, got %d", - ErrInvalidNumSignatures, - sigsLen, - ) - } - - return sigs[0], nil -} - -// expects to receive the unsigned tx bytes -func (l *ledgerSigner) Sign(b []byte) ([]byte, error) { - // Sign using the address with index l.idx on the ledger device. The number - // of returned signatures should be the same length as the provided indices. - sigs, err := l.ledger.Sign(b, []uint32{l.idx}) - if err != nil { - return nil, err - } - - if sigsLen := len(sigs); sigsLen != 1 { - return nil, fmt.Errorf( - "%w. expected 1, got %d", - ErrInvalidNumSignatures, - sigsLen, - ) - } - - return sigs[0], nil -} - -func (l *ledgerSigner) Address() ids.ShortID { - return l.addr +// EthKeychain maintains a set of addresses together with their corresponding +// signers, +type EthKeychain interface { + // The returned Signer can provide a signature for [addr] + GetEth(addr common.Address) (Signer, bool) + // Returns the set of addresses for which the accessor keeps an associated + // signer + EthAddresses() set.Set[common.Address] } diff --git a/utils/crypto/keychain/ledger/ledger.go b/utils/crypto/keychain/ledger/ledger.go new file mode 100644 index 000000000000..d2b7edcf7f14 --- /dev/null +++ b/utils/crypto/keychain/ledger/ledger.go @@ -0,0 +1,23 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package ledger + +import ( + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/crypto/ledger" + "github.com/ava-labs/avalanchego/version" +) + +// Ledger interface for the ledger wrapper +type Ledger interface { + Version() (v *version.Semantic, err error) + Address(displayHRP string, addressIndex uint32) (ids.ShortID, error) + Addresses(addressIndices []uint32) ([]ids.ShortID, error) + SignHash(hash []byte, addressIndices []uint32) ([][]byte, error) + Sign(unsignedTxBytes []byte, addressIndices []uint32) ([][]byte, error) + Disconnect() error +} + +// Verify that the ledger implementation satisfies the interface +var _ Ledger = (*ledger.Ledger)(nil) diff --git a/utils/crypto/keychain/ledger/ledger_keychain.go b/utils/crypto/keychain/ledger/ledger_keychain.go new file mode 100644 index 000000000000..36206b44b16e --- /dev/null +++ b/utils/crypto/keychain/ledger/ledger_keychain.go @@ -0,0 +1,149 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package ledger + +import ( + "errors" + "fmt" + + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/crypto/keychain" + "github.com/ava-labs/avalanchego/utils/set" +) + +var ( + _ keychain.Keychain = (*ledgerKeychain)(nil) + _ keychain.Signer = (*ledgerSigner)(nil) + + ErrInvalidIndicesLength = errors.New("number of indices should be greater than 0") + ErrInvalidNumAddrsToDerive = errors.New("number of addresses to derive should be greater than 0") + ErrInvalidNumAddrsDerived = errors.New("incorrect number of ledger derived addresses") + ErrInvalidNumSignatures = errors.New("incorrect number of signatures") +) + +// ledgerKeychain is an abstraction of the underlying ledger hardware device, +// to be able to get a signer from a finite set of derived signers +type ledgerKeychain struct { + ledger Ledger + addrs set.Set[ids.ShortID] + addrToIdx map[ids.ShortID]uint32 +} + +// NewLedgerKeychain creates a new Ledger with [numToDerive] addresses. +func NewLedgerKeychain(l Ledger, numToDerive int) (keychain.Keychain, error) { + if numToDerive < 1 { + return nil, ErrInvalidNumAddrsToDerive + } + + indices := make([]uint32, numToDerive) + for i := range indices { + indices[i] = uint32(i) + } + + return NewLedgerKeychainFromIndices(l, indices) +} + +// NewLedgerKeychainFromIndices creates a new Ledger with addresses taken from the given [indices]. +func NewLedgerKeychainFromIndices(l Ledger, indices []uint32) (keychain.Keychain, error) { + if len(indices) == 0 { + return nil, ErrInvalidIndicesLength + } + + addrs, err := l.Addresses(indices) + if err != nil { + return nil, err + } + + if len(addrs) != len(indices) { + return nil, fmt.Errorf( + "%w. expected %d, got %d", + ErrInvalidNumAddrsDerived, + len(indices), + len(addrs), + ) + } + + addrsSet := set.Of(addrs...) + + addrToIdx := map[ids.ShortID]uint32{} + for i := range indices { + addrToIdx[addrs[i]] = indices[i] + } + + return &ledgerKeychain{ + ledger: l, + addrs: addrsSet, + addrToIdx: addrToIdx, + }, nil +} + +func (l *ledgerKeychain) Addresses() set.Set[ids.ShortID] { + return l.addrs +} + +func (l *ledgerKeychain) Get(addr ids.ShortID) (keychain.Signer, bool) { + idx, ok := l.addrToIdx[addr] + if !ok { + return nil, false + } + + return &ledgerSigner{ + ledger: l.ledger, + idx: idx, + addr: addr, + }, true +} + +// ledgerSigner is an abstraction of the underlying ledger hardware device, +// to be able sign for a specific address +type ledgerSigner struct { + ledger Ledger + idx uint32 + addr ids.ShortID +} + +// expects to receive a hash of the unsigned tx bytes +func (l *ledgerSigner) SignHash(b []byte) ([]byte, error) { + // Sign using the address with index l.idx on the ledger device. The number + // of returned signatures should be the same length as the provided indices. + sigs, err := l.ledger.SignHash(b, []uint32{l.idx}) + if err != nil { + return nil, err + } + + if sigsLen := len(sigs); sigsLen != 1 { + return nil, fmt.Errorf( + "%w. expected 1, got %d", + ErrInvalidNumSignatures, + sigsLen, + ) + } + + return sigs[0], nil +} + +// expects to receive the unsigned tx bytes +func (l *ledgerSigner) Sign(b []byte, opts ...keychain.SigningOption) ([]byte, error) { + // Ignore options - ledger signing doesn't need chain/network context + // Sign using the address with index l.idx on the ledger device. The number + // of returned signatures should be the same length as the provided indices. + sigs, err := l.ledger.Sign(b, []uint32{l.idx}) + if err != nil { + return nil, err + } + + if sigsLen := len(sigs); sigsLen != 1 { + return nil, fmt.Errorf( + "%w. expected 1, got %d", + ErrInvalidNumSignatures, + sigsLen, + ) + } + + return sigs[0], nil +} + +func (l *ledgerSigner) Address() ids.ShortID { + return l.addr +} diff --git a/utils/crypto/keychain/ledger/ledger_keychain_test.go b/utils/crypto/keychain/ledger/ledger_keychain_test.go new file mode 100644 index 000000000000..1dfdf4e04936 --- /dev/null +++ b/utils/crypto/keychain/ledger/ledger_keychain_test.go @@ -0,0 +1,424 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package ledger + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" + + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/crypto/keychain/ledger/ledgermock" +) + +var errTest = errors.New("test") + +func TestNewLedgerKeychain(t *testing.T) { + require := require.New(t) + ctrl := gomock.NewController(t) + + addr := ids.GenerateTestShortID() + + // user request invalid number of addresses to derive + ledger := ledgermock.NewLedger(ctrl) + _, err := NewLedgerKeychain(ledger, 0) + require.ErrorIs(err, ErrInvalidNumAddrsToDerive) + + // ledger does not return expected number of derived addresses + ledger = ledgermock.NewLedger(ctrl) + ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{}, nil).Times(1) + _, err = NewLedgerKeychain(ledger, 1) + require.ErrorIs(err, ErrInvalidNumAddrsDerived) + + // ledger return error when asked for derived addresses + ledger = ledgermock.NewLedger(ctrl) + ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{addr}, errTest).Times(1) + _, err = NewLedgerKeychain(ledger, 1) + require.ErrorIs(err, errTest) + + // good path + ledger = ledgermock.NewLedger(ctrl) + ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{addr}, nil).Times(1) + _, err = NewLedgerKeychain(ledger, 1) + require.NoError(err) +} + +func TestLedgerKeychain_Addresses(t *testing.T) { + require := require.New(t) + ctrl := gomock.NewController(t) + + addr1 := ids.GenerateTestShortID() + addr2 := ids.GenerateTestShortID() + addr3 := ids.GenerateTestShortID() + + // 1 addr + ledger := ledgermock.NewLedger(ctrl) + ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{addr1}, nil).Times(1) + kc, err := NewLedgerKeychain(ledger, 1) + require.NoError(err) + + addrs := kc.Addresses() + require.Len(addrs, 1) + require.True(addrs.Contains(addr1)) + + // multiple addresses + ledger = ledgermock.NewLedger(ctrl) + ledger.EXPECT().Addresses([]uint32{0, 1, 2}).Return([]ids.ShortID{addr1, addr2, addr3}, nil).Times(1) + kc, err = NewLedgerKeychain(ledger, 3) + require.NoError(err) + + addrs = kc.Addresses() + require.Len(addrs, 3) + require.Contains(addrs, addr1) + require.Contains(addrs, addr2) + require.Contains(addrs, addr3) +} + +func TestLedgerKeychain_Get(t *testing.T) { + require := require.New(t) + ctrl := gomock.NewController(t) + + addr1 := ids.GenerateTestShortID() + addr2 := ids.GenerateTestShortID() + addr3 := ids.GenerateTestShortID() + + // 1 addr + ledger := ledgermock.NewLedger(ctrl) + ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{addr1}, nil).Times(1) + kc, err := NewLedgerKeychain(ledger, 1) + require.NoError(err) + + _, b := kc.Get(ids.GenerateTestShortID()) + require.False(b) + + s, b := kc.Get(addr1) + require.Equal(s.Address(), addr1) + require.True(b) + + // multiple addresses + ledger = ledgermock.NewLedger(ctrl) + ledger.EXPECT().Addresses([]uint32{0, 1, 2}).Return([]ids.ShortID{addr1, addr2, addr3}, nil).Times(1) + kc, err = NewLedgerKeychain(ledger, 3) + require.NoError(err) + + _, b = kc.Get(ids.GenerateTestShortID()) + require.False(b) + + s, b = kc.Get(addr1) + require.True(b) + require.Equal(s.Address(), addr1) + + s, b = kc.Get(addr2) + require.True(b) + require.Equal(s.Address(), addr2) + + s, b = kc.Get(addr3) + require.True(b) + require.Equal(s.Address(), addr3) +} + +func TestLedgerSigner_SignHash(t *testing.T) { + require := require.New(t) + ctrl := gomock.NewController(t) + + addr1 := ids.GenerateTestShortID() + addr2 := ids.GenerateTestShortID() + addr3 := ids.GenerateTestShortID() + toSign := []byte{1, 2, 3, 4, 5} + expectedSignature1 := []byte{1, 1, 1} + expectedSignature2 := []byte{2, 2, 2} + expectedSignature3 := []byte{3, 3, 3} + + // ledger returns an incorrect number of signatures + ledger := ledgermock.NewLedger(ctrl) + ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{addr1}, nil).Times(1) + ledger.EXPECT().SignHash(toSign, []uint32{0}).Return([][]byte{}, nil).Times(1) + kc, err := NewLedgerKeychain(ledger, 1) + require.NoError(err) + + s, b := kc.Get(addr1) + require.True(b) + + _, err = s.SignHash(toSign) + require.ErrorIs(err, ErrInvalidNumSignatures) + + // ledger returns an error when asked for signature + ledger = ledgermock.NewLedger(ctrl) + ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{addr1}, nil).Times(1) + ledger.EXPECT().SignHash(toSign, []uint32{0}).Return([][]byte{expectedSignature1}, errTest).Times(1) + kc, err = NewLedgerKeychain(ledger, 1) + require.NoError(err) + + s, b = kc.Get(addr1) + require.True(b) + + _, err = s.SignHash(toSign) + require.ErrorIs(err, errTest) + + // good path 1 addr + ledger = ledgermock.NewLedger(ctrl) + ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{addr1}, nil).Times(1) + ledger.EXPECT().SignHash(toSign, []uint32{0}).Return([][]byte{expectedSignature1}, nil).Times(1) + kc, err = NewLedgerKeychain(ledger, 1) + require.NoError(err) + + s, b = kc.Get(addr1) + require.True(b) + + signature, err := s.SignHash(toSign) + require.NoError(err) + require.Equal(expectedSignature1, signature) + + // good path 3 addr + ledger = ledgermock.NewLedger(ctrl) + ledger.EXPECT().Addresses([]uint32{0, 1, 2}).Return([]ids.ShortID{addr1, addr2, addr3}, nil).Times(1) + ledger.EXPECT().SignHash(toSign, []uint32{0}).Return([][]byte{expectedSignature1}, nil).Times(1) + ledger.EXPECT().SignHash(toSign, []uint32{1}).Return([][]byte{expectedSignature2}, nil).Times(1) + ledger.EXPECT().SignHash(toSign, []uint32{2}).Return([][]byte{expectedSignature3}, nil).Times(1) + kc, err = NewLedgerKeychain(ledger, 3) + require.NoError(err) + + s, b = kc.Get(addr1) + require.True(b) + + signature, err = s.SignHash(toSign) + require.NoError(err) + require.Equal(expectedSignature1, signature) + + s, b = kc.Get(addr2) + require.True(b) + + signature, err = s.SignHash(toSign) + require.NoError(err) + require.Equal(expectedSignature2, signature) + + s, b = kc.Get(addr3) + require.True(b) + + signature, err = s.SignHash(toSign) + require.NoError(err) + require.Equal(expectedSignature3, signature) +} + +func TestNewLedgerKeychainFromIndices(t *testing.T) { + require := require.New(t) + ctrl := gomock.NewController(t) + + addr := ids.GenerateTestShortID() + _ = addr + + // user request invalid number of indices + ledger := ledgermock.NewLedger(ctrl) + _, err := NewLedgerKeychainFromIndices(ledger, []uint32{}) + require.ErrorIs(err, ErrInvalidIndicesLength) + + // ledger does not return expected number of derived addresses + ledger = ledgermock.NewLedger(ctrl) + ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{}, nil).Times(1) + _, err = NewLedgerKeychainFromIndices(ledger, []uint32{0}) + require.ErrorIs(err, ErrInvalidNumAddrsDerived) + + // ledger return error when asked for derived addresses + ledger = ledgermock.NewLedger(ctrl) + ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{addr}, errTest).Times(1) + _, err = NewLedgerKeychainFromIndices(ledger, []uint32{0}) + require.ErrorIs(err, errTest) + + // good path + ledger = ledgermock.NewLedger(ctrl) + ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{addr}, nil).Times(1) + _, err = NewLedgerKeychainFromIndices(ledger, []uint32{0}) + require.NoError(err) +} + +func TestLedgerKeychainFromIndices_Addresses(t *testing.T) { + require := require.New(t) + ctrl := gomock.NewController(t) + + addr1 := ids.GenerateTestShortID() + addr2 := ids.GenerateTestShortID() + addr3 := ids.GenerateTestShortID() + + // 1 addr + ledger := ledgermock.NewLedger(ctrl) + ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{addr1}, nil).Times(1) + kc, err := NewLedgerKeychainFromIndices(ledger, []uint32{0}) + require.NoError(err) + + addrs := kc.Addresses() + require.Len(addrs, 1) + require.True(addrs.Contains(addr1)) + + // first 3 addresses + ledger = ledgermock.NewLedger(ctrl) + ledger.EXPECT().Addresses([]uint32{0, 1, 2}).Return([]ids.ShortID{addr1, addr2, addr3}, nil).Times(1) + kc, err = NewLedgerKeychainFromIndices(ledger, []uint32{0, 1, 2}) + require.NoError(err) + + addrs = kc.Addresses() + require.Len(addrs, 3) + require.Contains(addrs, addr1) + require.Contains(addrs, addr2) + require.Contains(addrs, addr3) + + // some 3 addresses + indices := []uint32{3, 7, 1} + addresses := []ids.ShortID{addr1, addr2, addr3} + ledger = ledgermock.NewLedger(ctrl) + ledger.EXPECT().Addresses(indices).Return(addresses, nil).Times(1) + kc, err = NewLedgerKeychainFromIndices(ledger, indices) + require.NoError(err) + + addrs = kc.Addresses() + require.Len(addrs, len(indices)) + require.Contains(addrs, addr1) + require.Contains(addrs, addr2) + require.Contains(addrs, addr3) + + // repeated addresses + indices = []uint32{3, 7, 1, 3, 1, 7} + addresses = []ids.ShortID{addr1, addr2, addr3, addr1, addr2, addr3} + ledger = ledgermock.NewLedger(ctrl) + ledger.EXPECT().Addresses(indices).Return(addresses, nil).Times(1) + kc, err = NewLedgerKeychainFromIndices(ledger, indices) + require.NoError(err) + + addrs = kc.Addresses() + require.Len(addrs, 3) + require.Contains(addrs, addr1) + require.Contains(addrs, addr2) + require.Contains(addrs, addr3) +} + +func TestLedgerKeychainFromIndices_Get(t *testing.T) { + require := require.New(t) + ctrl := gomock.NewController(t) + + addr1 := ids.GenerateTestShortID() + addr2 := ids.GenerateTestShortID() + addr3 := ids.GenerateTestShortID() + + // 1 addr + ledger := ledgermock.NewLedger(ctrl) + ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{addr1}, nil).Times(1) + kc, err := NewLedgerKeychainFromIndices(ledger, []uint32{0}) + require.NoError(err) + + _, b := kc.Get(ids.GenerateTestShortID()) + require.False(b) + + s, b := kc.Get(addr1) + require.Equal(s.Address(), addr1) + require.True(b) + + // some 3 addresses + indices := []uint32{3, 7, 1} + addresses := []ids.ShortID{addr1, addr2, addr3} + ledger = ledgermock.NewLedger(ctrl) + ledger.EXPECT().Addresses(indices).Return(addresses, nil).Times(1) + kc, err = NewLedgerKeychainFromIndices(ledger, indices) + require.NoError(err) + + _, b = kc.Get(ids.GenerateTestShortID()) + require.False(b) + + s, b = kc.Get(addr1) + require.True(b) + require.Equal(s.Address(), addr1) + + s, b = kc.Get(addr2) + require.True(b) + require.Equal(s.Address(), addr2) + + s, b = kc.Get(addr3) + require.True(b) + require.Equal(s.Address(), addr3) +} + +func TestLedgerSignerFromIndices_SignHash(t *testing.T) { + require := require.New(t) + ctrl := gomock.NewController(t) + + addr1 := ids.GenerateTestShortID() + addr2 := ids.GenerateTestShortID() + addr3 := ids.GenerateTestShortID() + toSign := []byte{1, 2, 3, 4, 5} + expectedSignature1 := []byte{1, 1, 1} + expectedSignature2 := []byte{2, 2, 2} + expectedSignature3 := []byte{3, 3, 3} + + // ledger returns an incorrect number of signatures + ledger := ledgermock.NewLedger(ctrl) + ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{addr1}, nil).Times(1) + ledger.EXPECT().SignHash(toSign, []uint32{0}).Return([][]byte{}, nil).Times(1) + kc, err := NewLedgerKeychainFromIndices(ledger, []uint32{0}) + require.NoError(err) + + s, b := kc.Get(addr1) + require.True(b) + + _, err = s.SignHash(toSign) + require.ErrorIs(err, ErrInvalidNumSignatures) + + // ledger returns an error when asked for signature + ledger = ledgermock.NewLedger(ctrl) + ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{addr1}, nil).Times(1) + ledger.EXPECT().SignHash(toSign, []uint32{0}).Return([][]byte{expectedSignature1}, errTest).Times(1) + kc, err = NewLedgerKeychainFromIndices(ledger, []uint32{0}) + require.NoError(err) + + s, b = kc.Get(addr1) + require.True(b) + + _, err = s.SignHash(toSign) + require.ErrorIs(err, errTest) + + // good path 1 addr + ledger = ledgermock.NewLedger(ctrl) + ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{addr1}, nil).Times(1) + ledger.EXPECT().SignHash(toSign, []uint32{0}).Return([][]byte{expectedSignature1}, nil).Times(1) + kc, err = NewLedgerKeychainFromIndices(ledger, []uint32{0}) + require.NoError(err) + + s, b = kc.Get(addr1) + require.True(b) + + signature, err := s.SignHash(toSign) + require.NoError(err) + require.Equal(expectedSignature1, signature) + + // good path some 3 addresses + indices := []uint32{3, 7, 1} + addresses := []ids.ShortID{addr1, addr2, addr3} + ledger = ledgermock.NewLedger(ctrl) + ledger.EXPECT().Addresses(indices).Return(addresses, nil).Times(1) + ledger.EXPECT().SignHash(toSign, []uint32{indices[0]}).Return([][]byte{expectedSignature1}, nil).Times(1) + ledger.EXPECT().SignHash(toSign, []uint32{indices[1]}).Return([][]byte{expectedSignature2}, nil).Times(1) + ledger.EXPECT().SignHash(toSign, []uint32{indices[2]}).Return([][]byte{expectedSignature3}, nil).Times(1) + kc, err = NewLedgerKeychainFromIndices(ledger, indices) + require.NoError(err) + + s, b = kc.Get(addr1) + require.True(b) + + signature, err = s.SignHash(toSign) + require.NoError(err) + require.Equal(expectedSignature1, signature) + + s, b = kc.Get(addr2) + require.True(b) + + signature, err = s.SignHash(toSign) + require.NoError(err) + require.Equal(expectedSignature2, signature) + + s, b = kc.Get(addr3) + require.True(b) + + signature, err = s.SignHash(toSign) + require.NoError(err) + require.Equal(expectedSignature3, signature) +} diff --git a/utils/crypto/keychain/ledger/ledgermock/ledger.go b/utils/crypto/keychain/ledger/ledgermock/ledger.go new file mode 100644 index 000000000000..d55709767b58 --- /dev/null +++ b/utils/crypto/keychain/ledger/ledgermock/ledger.go @@ -0,0 +1,131 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/ava-labs/avalanchego/utils/crypto/keychain/ledger (interfaces: Ledger) +// +// Generated by this command: +// +// mockgen -package=ledgermock -destination=ledgermock/ledger.go -mock_names=Ledger=Ledger . Ledger +// + +// Package ledgermock is a generated GoMock package. +package ledgermock + +import ( + reflect "reflect" + + ids "github.com/ava-labs/avalanchego/ids" + version "github.com/ava-labs/avalanchego/version" + gomock "go.uber.org/mock/gomock" +) + +// Ledger is a mock of Ledger interface. +type Ledger struct { + ctrl *gomock.Controller + recorder *LedgerMockRecorder + isgomock struct{} +} + +// LedgerMockRecorder is the mock recorder for Ledger. +type LedgerMockRecorder struct { + mock *Ledger +} + +// NewLedger creates a new mock instance. +func NewLedger(ctrl *gomock.Controller) *Ledger { + mock := &Ledger{ctrl: ctrl} + mock.recorder = &LedgerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *Ledger) EXPECT() *LedgerMockRecorder { + return m.recorder +} + +// Address mocks base method. +func (m *Ledger) Address(displayHRP string, addressIndex uint32) (ids.ShortID, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Address", displayHRP, addressIndex) + ret0, _ := ret[0].(ids.ShortID) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Address indicates an expected call of Address. +func (mr *LedgerMockRecorder) Address(displayHRP, addressIndex any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Address", reflect.TypeOf((*Ledger)(nil).Address), displayHRP, addressIndex) +} + +// Addresses mocks base method. +func (m *Ledger) Addresses(addressIndices []uint32) ([]ids.ShortID, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Addresses", addressIndices) + ret0, _ := ret[0].([]ids.ShortID) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Addresses indicates an expected call of Addresses. +func (mr *LedgerMockRecorder) Addresses(addressIndices any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Addresses", reflect.TypeOf((*Ledger)(nil).Addresses), addressIndices) +} + +// Disconnect mocks base method. +func (m *Ledger) Disconnect() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Disconnect") + ret0, _ := ret[0].(error) + return ret0 +} + +// Disconnect indicates an expected call of Disconnect. +func (mr *LedgerMockRecorder) Disconnect() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Disconnect", reflect.TypeOf((*Ledger)(nil).Disconnect)) +} + +// Sign mocks base method. +func (m *Ledger) Sign(unsignedTxBytes []byte, addressIndices []uint32) ([][]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Sign", unsignedTxBytes, addressIndices) + ret0, _ := ret[0].([][]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Sign indicates an expected call of Sign. +func (mr *LedgerMockRecorder) Sign(unsignedTxBytes, addressIndices any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Sign", reflect.TypeOf((*Ledger)(nil).Sign), unsignedTxBytes, addressIndices) +} + +// SignHash mocks base method. +func (m *Ledger) SignHash(hash []byte, addressIndices []uint32) ([][]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SignHash", hash, addressIndices) + ret0, _ := ret[0].([][]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SignHash indicates an expected call of SignHash. +func (mr *LedgerMockRecorder) SignHash(hash, addressIndices any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SignHash", reflect.TypeOf((*Ledger)(nil).SignHash), hash, addressIndices) +} + +// Version mocks base method. +func (m *Ledger) Version() (*version.Semantic, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Version") + ret0, _ := ret[0].(*version.Semantic) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Version indicates an expected call of Version. +func (mr *LedgerMockRecorder) Version() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Version", reflect.TypeOf((*Ledger)(nil).Version)) +} diff --git a/utils/crypto/keychain/ledger/mocks_generate_test.go b/utils/crypto/keychain/ledger/mocks_generate_test.go new file mode 100644 index 000000000000..2e6e85b3e9d8 --- /dev/null +++ b/utils/crypto/keychain/ledger/mocks_generate_test.go @@ -0,0 +1,6 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package ledger + +//go:generate go run go.uber.org/mock/mockgen -package=${GOPACKAGE}mock -destination=${GOPACKAGE}mock/ledger.go -mock_names=Ledger=Ledger . Ledger diff --git a/utils/crypto/keychain/signing_options.go b/utils/crypto/keychain/signing_options.go new file mode 100644 index 000000000000..f4364fd8a19c --- /dev/null +++ b/utils/crypto/keychain/signing_options.go @@ -0,0 +1,27 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package keychain + +// SigningOptions contains context information for signing operations +type SigningOptions struct { + ChainAlias string + NetworkID uint32 +} + +// SigningOption is a function that modifies SigningOptions +type SigningOption func(*SigningOptions) + +// WithChainAlias sets the chain alias for signing context +func WithChainAlias(chainAlias string) SigningOption { + return func(opts *SigningOptions) { + opts.ChainAlias = chainAlias + } +} + +// WithNetworkID sets the network ID for signing context +func WithNetworkID(networkID uint32) SigningOption { + return func(opts *SigningOptions) { + opts.NetworkID = networkID + } +} \ No newline at end of file diff --git a/utils/crypto/ledger/ledger.go b/utils/crypto/ledger/ledger.go index 77f5290b4991..332ee6efdfee 100644 --- a/utils/crypto/ledger/ledger.go +++ b/utils/crypto/ledger/ledger.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package ledger @@ -7,7 +7,6 @@ import ( "fmt" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/utils/crypto/keychain" "github.com/ava-labs/avalanchego/utils/hashing" "github.com/ava-labs/avalanchego/version" @@ -21,7 +20,6 @@ const ( ledgerPathSize = 9 ) -var _ keychain.Ledger = (*Ledger)(nil) // Ledger is a wrapper around the low-level Ledger Device interface that // provides Avalanche-specific access. @@ -30,7 +28,7 @@ type Ledger struct { epk *bip32.Key } -func New() (keychain.Ledger, error) { +func New() (*Ledger, error) { device, err := ledger.FindLedgerAvalancheApp() return &Ledger{ device: device, diff --git a/utils/crypto/secp256k1/secp256k1.go b/utils/crypto/secp256k1/secp256k1.go index 41c3cdaa057a..d1d83214ba24 100644 --- a/utils/crypto/secp256k1/secp256k1.go +++ b/utils/crypto/secp256k1/secp256k1.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package secp256k1 @@ -13,6 +13,7 @@ import ( "github.com/decred/dcrd/dcrec/secp256k1/v4/ecdsa" "github.com/ava-labs/avalanchego/cache" + "github.com/ava-labs/avalanchego/utils/crypto/keychain" "github.com/ava-labs/avalanchego/cache/lru" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/cb58" @@ -212,7 +213,8 @@ func (k *PrivateKey) EthAddress() common.Address { return crypto.PubkeyToAddress(*(k.PublicKey().ToECDSA())) } -func (k *PrivateKey) Sign(msg []byte) ([]byte, error) { +func (k *PrivateKey) Sign(msg []byte, opts ...keychain.SigningOption) ([]byte, error) { + // Ignore options - secp256k1 signing doesn't need chain/network context return k.SignHash(hashing.ComputeHash256(msg)) } diff --git a/vms/avm/txs/txstest/builder.go b/vms/avm/txs/txstest/builder.go index beae84f2eb72..7ac5d57c2bad 100644 --- a/vms/avm/txs/txstest/builder.go +++ b/vms/avm/txs/txstest/builder.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package txstest @@ -225,7 +225,7 @@ func (b *Builder) builders(kc *secp256k1fx.Keychain) (builder.Builder, signer.Si addrs: addrs, } builder = builder.New(addrs, b.ctx, wa) - signer = signer.New(kc, wa) + signer = signer.New(kc, wa, b.ctx.NetworkID) ) return builder, signer } diff --git a/vms/platformvm/txs/txstest/wallet.go b/vms/platformvm/txs/txstest/wallet.go index 4a9e74b60aac..9253ac779c49 100644 --- a/vms/platformvm/txs/txstest/wallet.go +++ b/vms/platformvm/txs/txstest/wallet.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package txstest @@ -112,6 +112,7 @@ func NewWallet( signer.New( kc, backend, + ctx.NetworkID, ), ) } diff --git a/vms/secp256k1fx/keychain.go b/vms/secp256k1fx/keychain.go index 3d0813916f71..62ae1b1112db 100644 --- a/vms/secp256k1fx/keychain.go +++ b/vms/secp256k1fx/keychain.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package secp256k1fx @@ -22,6 +22,7 @@ var ( errCantSpend = errors.New("unable to spend this UTXO") _ keychain.Keychain = (*Keychain)(nil) + _ keychain.EthKeychain = (*Keychain)(nil) ) // Keychain is a collection of keys that can be used to spend outputs diff --git a/wallet/chain/c/signer.go b/wallet/chain/c/signer.go index ee0f7af14916..d7eab9a3477f 100644 --- a/wallet/chain/c/signer.go +++ b/wallet/chain/c/signer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package c @@ -9,14 +9,12 @@ import ( "fmt" "github.com/ava-labs/coreth/plugin/evm/atomic" - "github.com/ava-labs/libevm/common" "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/crypto/keychain" "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" "github.com/ava-labs/avalanchego/utils/hashing" - "github.com/ava-labs/avalanchego/utils/set" "github.com/ava-labs/avalanchego/vms/components/avax" "github.com/ava-labs/avalanchego/vms/components/verify" "github.com/ava-labs/avalanchego/vms/secp256k1fx" @@ -47,25 +45,17 @@ type Signer interface { SignAtomic(ctx context.Context, tx *atomic.Tx) error } -type EthKeychain interface { - // The returned Signer can provide a signature for [addr] - GetEth(addr common.Address) (keychain.Signer, bool) - // Returns the set of addresses for which the accessor keeps an associated - // signer - EthAddresses() set.Set[common.Address] -} - type SignerBackend interface { GetUTXO(ctx context.Context, chainID, utxoID ids.ID) (*avax.UTXO, error) } type txSigner struct { avaxKC keychain.Keychain - ethKC EthKeychain + ethKC keychain.EthKeychain backend SignerBackend } -func NewSigner(avaxKC keychain.Keychain, ethKC EthKeychain, backend SignerBackend) Signer { +func NewSigner(avaxKC keychain.Keychain, ethKC keychain.EthKeychain, backend SignerBackend) Signer { return &txSigner{ avaxKC: avaxKC, ethKC: ethKC, diff --git a/wallet/chain/p/signer/signer.go b/wallet/chain/p/signer/signer.go index 4d8a4e2f6635..22777a7ac019 100644 --- a/wallet/chain/p/signer/signer.go +++ b/wallet/chain/p/signer/signer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package signer @@ -9,6 +9,7 @@ import ( "github.com/ava-labs/avalanchego/vms/components/avax" "github.com/ava-labs/avalanchego/vms/platformvm/fx" "github.com/ava-labs/avalanchego/vms/platformvm/txs" + "github.com/ava-labs/avalanchego/wallet/subnet/primary/common" stdcontext "context" ) @@ -24,7 +25,7 @@ type Signer interface { // // If the signer doesn't have the ability to provide a required signature, // the signature slot will be skipped without reporting an error. - Sign(ctx stdcontext.Context, tx *txs.Tx) error + Sign(ctx stdcontext.Context, tx *txs.Tx, options ...common.Option) error } type Backend interface { @@ -33,23 +34,29 @@ type Backend interface { } type txSigner struct { - kc keychain.Keychain - backend Backend + kc keychain.Keychain + backend Backend + networkID uint32 } -func New(kc keychain.Keychain, backend Backend) Signer { +func New(kc keychain.Keychain, backend Backend, networkID uint32) Signer { return &txSigner{ - kc: kc, - backend: backend, + kc: kc, + backend: backend, + networkID: networkID, } } -func (s *txSigner) Sign(ctx stdcontext.Context, tx *txs.Tx) error { +func (s *txSigner) Sign(ctx stdcontext.Context, tx *txs.Tx, options ...common.Option) error { + ops := common.NewOptions(options) + return tx.Unsigned.Visit(&visitor{ - kc: s.kc, - backend: s.backend, - ctx: ctx, - tx: tx, + kc: s.kc, + backend: s.backend, + ctx: ctx, + tx: tx, + networkID: s.networkID, + forceSignHash: ops.ForceSignHash(), }) } @@ -57,7 +64,8 @@ func SignUnsigned( ctx stdcontext.Context, signer Signer, utx txs.UnsignedTx, + options ...common.Option, ) (*txs.Tx, error) { tx := &txs.Tx{Unsigned: utx} - return tx, signer.Sign(ctx, tx) + return tx, signer.Sign(ctx, tx, options...) } diff --git a/wallet/chain/p/signer/visitor.go b/wallet/chain/p/signer/visitor.go index d2162d08e207..9bbd2c9b35d5 100644 --- a/wallet/chain/p/signer/visitor.go +++ b/wallet/chain/p/signer/visitor.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package signer @@ -19,6 +19,7 @@ import ( "github.com/ava-labs/avalanchego/vms/platformvm/stakeable" "github.com/ava-labs/avalanchego/vms/platformvm/txs" "github.com/ava-labs/avalanchego/vms/secp256k1fx" + "github.com/ava-labs/avalanchego/wallet/chain/p/builder" ) var ( @@ -37,10 +38,12 @@ var ( // visitor handles signing transactions for the signer type visitor struct { - kc keychain.Keychain - backend Backend - ctx context.Context - tx *txs.Tx + kc keychain.Keychain + backend Backend + ctx context.Context + tx *txs.Tx + networkID uint32 + forceSignHash bool } func (*visitor) AdvanceTimeTx(*txs.AdvanceTimeTx) error { @@ -56,7 +59,7 @@ func (s *visitor) AddValidatorTx(tx *txs.AddValidatorTx) error { if err != nil { return err } - return sign(s.tx, false, txSigners) + return sign(s.tx, false || s.forceSignHash, txSigners, s.networkID) } func (s *visitor) AddSubnetValidatorTx(tx *txs.AddSubnetValidatorTx) error { @@ -69,7 +72,7 @@ func (s *visitor) AddSubnetValidatorTx(tx *txs.AddSubnetValidatorTx) error { return err } txSigners = append(txSigners, subnetAuthSigners) - return sign(s.tx, false, txSigners) + return sign(s.tx, false || s.forceSignHash, txSigners, s.networkID) } func (s *visitor) AddDelegatorTx(tx *txs.AddDelegatorTx) error { @@ -77,7 +80,7 @@ func (s *visitor) AddDelegatorTx(tx *txs.AddDelegatorTx) error { if err != nil { return err } - return sign(s.tx, false, txSigners) + return sign(s.tx, false || s.forceSignHash, txSigners, s.networkID) } func (s *visitor) CreateChainTx(tx *txs.CreateChainTx) error { @@ -90,7 +93,7 @@ func (s *visitor) CreateChainTx(tx *txs.CreateChainTx) error { return err } txSigners = append(txSigners, subnetAuthSigners) - return sign(s.tx, false, txSigners) + return sign(s.tx, false || s.forceSignHash, txSigners, s.networkID) } func (s *visitor) CreateSubnetTx(tx *txs.CreateSubnetTx) error { @@ -98,7 +101,7 @@ func (s *visitor) CreateSubnetTx(tx *txs.CreateSubnetTx) error { if err != nil { return err } - return sign(s.tx, false, txSigners) + return sign(s.tx, false || s.forceSignHash, txSigners, s.networkID) } func (s *visitor) ImportTx(tx *txs.ImportTx) error { @@ -111,7 +114,7 @@ func (s *visitor) ImportTx(tx *txs.ImportTx) error { return err } txSigners = append(txSigners, txImportSigners...) - return sign(s.tx, false, txSigners) + return sign(s.tx, false || s.forceSignHash, txSigners, s.networkID) } func (s *visitor) ExportTx(tx *txs.ExportTx) error { @@ -119,7 +122,7 @@ func (s *visitor) ExportTx(tx *txs.ExportTx) error { if err != nil { return err } - return sign(s.tx, false, txSigners) + return sign(s.tx, false || s.forceSignHash, txSigners, s.networkID) } func (s *visitor) RemoveSubnetValidatorTx(tx *txs.RemoveSubnetValidatorTx) error { @@ -132,7 +135,7 @@ func (s *visitor) RemoveSubnetValidatorTx(tx *txs.RemoveSubnetValidatorTx) error return err } txSigners = append(txSigners, subnetAuthSigners) - return sign(s.tx, true, txSigners) + return sign(s.tx, true, txSigners, s.networkID) } func (s *visitor) TransformSubnetTx(tx *txs.TransformSubnetTx) error { @@ -145,7 +148,7 @@ func (s *visitor) TransformSubnetTx(tx *txs.TransformSubnetTx) error { return err } txSigners = append(txSigners, subnetAuthSigners) - return sign(s.tx, true, txSigners) + return sign(s.tx, true, txSigners, s.networkID) } func (s *visitor) AddPermissionlessValidatorTx(tx *txs.AddPermissionlessValidatorTx) error { @@ -153,7 +156,7 @@ func (s *visitor) AddPermissionlessValidatorTx(tx *txs.AddPermissionlessValidato if err != nil { return err } - return sign(s.tx, true, txSigners) + return sign(s.tx, true, txSigners, s.networkID) } func (s *visitor) AddPermissionlessDelegatorTx(tx *txs.AddPermissionlessDelegatorTx) error { @@ -161,7 +164,7 @@ func (s *visitor) AddPermissionlessDelegatorTx(tx *txs.AddPermissionlessDelegato if err != nil { return err } - return sign(s.tx, true, txSigners) + return sign(s.tx, true, txSigners, s.networkID) } func (s *visitor) TransferSubnetOwnershipTx(tx *txs.TransferSubnetOwnershipTx) error { @@ -174,7 +177,7 @@ func (s *visitor) TransferSubnetOwnershipTx(tx *txs.TransferSubnetOwnershipTx) e return err } txSigners = append(txSigners, subnetAuthSigners) - return sign(s.tx, true, txSigners) + return sign(s.tx, true, txSigners, s.networkID) } func (s *visitor) BaseTx(tx *txs.BaseTx) error { @@ -182,7 +185,7 @@ func (s *visitor) BaseTx(tx *txs.BaseTx) error { if err != nil { return err } - return sign(s.tx, false, txSigners) + return sign(s.tx, false || s.forceSignHash, txSigners, s.networkID) } func (s *visitor) ConvertSubnetToL1Tx(tx *txs.ConvertSubnetToL1Tx) error { @@ -195,7 +198,7 @@ func (s *visitor) ConvertSubnetToL1Tx(tx *txs.ConvertSubnetToL1Tx) error { return err } txSigners = append(txSigners, subnetAuthSigners) - return sign(s.tx, true, txSigners) + return sign(s.tx, true, txSigners, s.networkID) } func (s *visitor) RegisterL1ValidatorTx(tx *txs.RegisterL1ValidatorTx) error { @@ -203,7 +206,7 @@ func (s *visitor) RegisterL1ValidatorTx(tx *txs.RegisterL1ValidatorTx) error { if err != nil { return err } - return sign(s.tx, true, txSigners) + return sign(s.tx, true, txSigners, s.networkID) } func (s *visitor) SetL1ValidatorWeightTx(tx *txs.SetL1ValidatorWeightTx) error { @@ -211,7 +214,7 @@ func (s *visitor) SetL1ValidatorWeightTx(tx *txs.SetL1ValidatorWeightTx) error { if err != nil { return err } - return sign(s.tx, true, txSigners) + return sign(s.tx, true, txSigners, s.networkID) } func (s *visitor) IncreaseL1ValidatorBalanceTx(tx *txs.IncreaseL1ValidatorBalanceTx) error { @@ -219,7 +222,7 @@ func (s *visitor) IncreaseL1ValidatorBalanceTx(tx *txs.IncreaseL1ValidatorBalanc if err != nil { return err } - return sign(s.tx, true, txSigners) + return sign(s.tx, true, txSigners, s.networkID) } func (s *visitor) DisableL1ValidatorTx(tx *txs.DisableL1ValidatorTx) error { @@ -232,7 +235,7 @@ func (s *visitor) DisableL1ValidatorTx(tx *txs.DisableL1ValidatorTx) error { return err } txSigners = append(txSigners, disableAuthSigners) - return sign(s.tx, true, txSigners) + return sign(s.tx, true, txSigners, s.networkID) } func (s *visitor) getSigners(sourceChainID ids.ID, ins []*avax.TransferableInput) ([][]keychain.Signer, error) { @@ -328,7 +331,7 @@ func (s *visitor) getAuthSigners(ownerID ids.ID, auth verify.Verifiable) ([]keyc } // TODO: remove [signHash] after the ledger supports signing all transactions. -func sign(tx *txs.Tx, signHash bool, txSigners [][]keychain.Signer) error { +func sign(tx *txs.Tx, signHash bool, txSigners [][]keychain.Signer, networkID uint32) error { unsignedBytes, err := txs.Codec.Marshal(txs.CodecVersion, &tx.Unsigned) if err != nil { return fmt.Errorf("couldn't marshal unsigned tx: %w", err) @@ -380,7 +383,9 @@ func sign(tx *txs.Tx, signHash bool, txSigners [][]keychain.Signer) error { if signHash { sig, err = signer.SignHash(unsignedHash) } else { - sig, err = signer.Sign(unsignedBytes) + sig, err = signer.Sign(unsignedBytes, + keychain.WithChainAlias(builder.Alias), + keychain.WithNetworkID(networkID)) } if err != nil { return fmt.Errorf("problem signing tx: %w", err) diff --git a/wallet/chain/p/signer/with_options.go b/wallet/chain/p/signer/with_options.go new file mode 100644 index 000000000000..60d82be8f25f --- /dev/null +++ b/wallet/chain/p/signer/with_options.go @@ -0,0 +1,36 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package signer + +import ( + stdcontext "context" + + "github.com/ava-labs/avalanchego/vms/platformvm/txs" + "github.com/ava-labs/avalanchego/wallet/subnet/primary/common" +) + +var _ Signer = (*withOptions)(nil) + +type withOptions struct { + signer Signer + options []common.Option +} + +// WithOptions returns a new signer that will use the given options by default. +// +// - [signer] is the signer that will be called to perform the underlying +// operations. +// - [options] will be provided to the signer in addition to the options +// provided in the method calls. +func WithOptions(signer Signer, options ...common.Option) Signer { + return &withOptions{ + signer: signer, + options: options, + } +} + +func (w *withOptions) Sign(ctx stdcontext.Context, tx *txs.Tx, options ...common.Option) error { + return w.signer.Sign(ctx, tx, common.UnionOptions(w.options, options)...) +} + diff --git a/wallet/chain/p/wallet/wallet.go b/wallet/chain/p/wallet/wallet.go index db04731025ac..e9128e3bf439 100644 --- a/wallet/chain/p/wallet/wallet.go +++ b/wallet/chain/p/wallet/wallet.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package wallet @@ -611,7 +611,7 @@ func (w *wallet) IssueUnsignedTx( ) (*txs.Tx, error) { ops := common.NewOptions(options) ctx := ops.Context() - tx, err := walletsigner.SignUnsigned(ctx, w.signer, utx) + tx, err := walletsigner.SignUnsigned(ctx, w.signer, utx, options...) if err != nil { return nil, err } diff --git a/wallet/chain/x/signer/signer.go b/wallet/chain/x/signer/signer.go index 5e22f523d5aa..82f4b519e440 100644 --- a/wallet/chain/x/signer/signer.go +++ b/wallet/chain/x/signer/signer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package signer @@ -10,6 +10,7 @@ import ( "github.com/ava-labs/avalanchego/utils/crypto/keychain" "github.com/ava-labs/avalanchego/vms/avm/txs" "github.com/ava-labs/avalanchego/vms/components/avax" + "github.com/ava-labs/avalanchego/wallet/subnet/primary/common" ) var _ Signer = (*signer)(nil) @@ -23,7 +24,7 @@ type Signer interface { // // If the signer doesn't have the ability to provide a required signature, // the signature slot will be skipped without reporting an error. - Sign(ctx context.Context, tx *txs.Tx) error + Sign(ctx context.Context, tx *txs.Tx, options ...common.Option) error } type Backend interface { @@ -31,23 +32,29 @@ type Backend interface { } type signer struct { - kc keychain.Keychain - backend Backend + kc keychain.Keychain + backend Backend + networkID uint32 } -func New(kc keychain.Keychain, backend Backend) Signer { +func New(kc keychain.Keychain, backend Backend, networkID uint32) Signer { return &signer{ - kc: kc, - backend: backend, + kc: kc, + backend: backend, + networkID: networkID, } } -func (s *signer) Sign(ctx context.Context, tx *txs.Tx) error { +func (s *signer) Sign(ctx context.Context, tx *txs.Tx, options ...common.Option) error { + ops := common.NewOptions(options) + return tx.Unsigned.Visit(&visitor{ - kc: s.kc, - backend: s.backend, - ctx: ctx, - tx: tx, + kc: s.kc, + backend: s.backend, + ctx: ctx, + tx: tx, + networkID: s.networkID, + forceSignHash: ops.ForceSignHash(), }) } @@ -55,7 +62,8 @@ func SignUnsigned( ctx context.Context, signer Signer, utx txs.UnsignedTx, + options ...common.Option, ) (*txs.Tx, error) { tx := &txs.Tx{Unsigned: utx} - return tx, signer.Sign(ctx, tx) + return tx, signer.Sign(ctx, tx, options...) } diff --git a/wallet/chain/x/signer/visitor.go b/wallet/chain/x/signer/visitor.go index e9f1ce64a7bc..4b3168bd66c1 100644 --- a/wallet/chain/x/signer/visitor.go +++ b/wallet/chain/x/signer/visitor.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package signer @@ -12,6 +12,7 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/crypto/keychain" "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" + "github.com/ava-labs/avalanchego/utils/hashing" "github.com/ava-labs/avalanchego/vms/avm/fxs" "github.com/ava-labs/avalanchego/vms/avm/txs" "github.com/ava-labs/avalanchego/vms/components/avax" @@ -37,10 +38,12 @@ var ( // visitor handles signing transactions for the signer type visitor struct { - kc keychain.Keychain - backend Backend - ctx context.Context - tx *txs.Tx + kc keychain.Keychain + backend Backend + ctx context.Context + tx *txs.Tx + networkID uint32 + forceSignHash bool } func (s *visitor) BaseTx(tx *txs.BaseTx) error { @@ -48,7 +51,7 @@ func (s *visitor) BaseTx(tx *txs.BaseTx) error { if err != nil { return err } - return sign(s.tx, txCreds, txSigners) + return sign(s.tx, txCreds, txSigners, s.networkID, s.forceSignHash) } func (s *visitor) CreateAssetTx(tx *txs.CreateAssetTx) error { @@ -56,7 +59,7 @@ func (s *visitor) CreateAssetTx(tx *txs.CreateAssetTx) error { if err != nil { return err } - return sign(s.tx, txCreds, txSigners) + return sign(s.tx, txCreds, txSigners, s.networkID, s.forceSignHash) } func (s *visitor) OperationTx(tx *txs.OperationTx) error { @@ -70,7 +73,7 @@ func (s *visitor) OperationTx(tx *txs.OperationTx) error { } txCreds = append(txCreds, txOpsCreds...) txSigners = append(txSigners, txOpsSigners...) - return sign(s.tx, txCreds, txSigners) + return sign(s.tx, txCreds, txSigners, s.networkID, s.forceSignHash) } func (s *visitor) ImportTx(tx *txs.ImportTx) error { @@ -84,7 +87,7 @@ func (s *visitor) ImportTx(tx *txs.ImportTx) error { } txCreds = append(txCreds, txImportCreds...) txSigners = append(txSigners, txImportSigners...) - return sign(s.tx, txCreds, txSigners) + return sign(s.tx, txCreds, txSigners, s.networkID, s.forceSignHash) } func (s *visitor) ExportTx(tx *txs.ExportTx) error { @@ -92,7 +95,7 @@ func (s *visitor) ExportTx(tx *txs.ExportTx) error { if err != nil { return err } - return sign(s.tx, txCreds, txSigners) + return sign(s.tx, txCreds, txSigners, s.networkID, s.forceSignHash) } func (s *visitor) getSigners(ctx context.Context, sourceChainID ids.ID, ins []*avax.TransferableInput) ([]verify.Verifiable, [][]keychain.Signer, error) { @@ -218,7 +221,7 @@ func (s *visitor) getOpsSigners(ctx context.Context, sourceChainID ids.ID, ops [ return txCreds, txSigners, nil } -func sign(tx *txs.Tx, creds []verify.Verifiable, txSigners [][]keychain.Signer) error { +func sign(tx *txs.Tx, creds []verify.Verifiable, txSigners [][]keychain.Signer, networkID uint32, signHash bool) error { codec := builder.Parser.Codec() unsignedBytes, err := codec.Marshal(txs.CodecVersion, &tx.Unsigned) if err != nil { @@ -282,7 +285,15 @@ func sign(tx *txs.Tx, creds []verify.Verifiable, txSigners [][]keychain.Signer) continue } - sig, err := signer.Sign(unsignedBytes) + var sig []byte + if signHash { + unsignedHash := hashing.ComputeHash256(unsignedBytes) + sig, err = signer.SignHash(unsignedHash) + } else { + sig, err = signer.Sign(unsignedBytes, + keychain.WithChainAlias(builder.Alias), + keychain.WithNetworkID(networkID)) + } if err != nil { return fmt.Errorf("problem signing tx: %w", err) } diff --git a/wallet/chain/x/wallet.go b/wallet/chain/x/wallet.go index b53d6a064f84..aa8e0115e762 100644 --- a/wallet/chain/x/wallet.go +++ b/wallet/chain/x/wallet.go @@ -281,7 +281,7 @@ func (w *wallet) IssueUnsignedTx( ) (*txs.Tx, error) { ops := common.NewOptions(options) ctx := ops.Context() - tx, err := signer.SignUnsigned(ctx, w.signer, utx) + tx, err := signer.SignUnsigned(ctx, w.signer, utx, options...) if err != nil { return nil, err } diff --git a/wallet/subnet/primary/common/options.go b/wallet/subnet/primary/common/options.go index dd23934f4db0..80386a079935 100644 --- a/wallet/subnet/primary/common/options.go +++ b/wallet/subnet/primary/common/options.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package common @@ -70,6 +70,8 @@ type Options struct { issuanceHandler func(IssuanceReceipt) confirmationHandler func(ConfirmationReceipt) + + forceSignHash bool } func NewOptions(ops []Option) *Options { @@ -161,6 +163,10 @@ func (o *Options) ConfirmationHandler() func(ConfirmationReceipt) { return o.confirmationHandler } +func (o *Options) ForceSignHash() bool { + return o.forceSignHash +} + func WithContext(ctx context.Context) Option { return func(o *Options) { o.ctx = ctx @@ -236,3 +242,10 @@ func WithConfirmationHandler(f func(ConfirmationReceipt)) Option { o.confirmationHandler = f } } + + +func WithForceSignHash() Option { + return func(o *Options) { + o.forceSignHash = true + } +} diff --git a/wallet/subnet/primary/wallet.go b/wallet/subnet/primary/wallet.go index 52951121c328..3fc905f9cceb 100644 --- a/wallet/subnet/primary/wallet.go +++ b/wallet/subnet/primary/wallet.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package primary @@ -82,7 +82,7 @@ func MakeWallet( ctx context.Context, uri string, avaxKeychain keychain.Keychain, - ethKeychain c.EthKeychain, + ethKeychain keychain.EthKeychain, config WalletConfig, ) (*Wallet, error) { avaxAddrs := avaxKeychain.Addresses() @@ -106,13 +106,13 @@ func MakeWallet( pBackend := pwallet.NewBackend(pUTXOs, owners) pClient := p.NewClient(avaxState.PClient, pBackend) pBuilder := pbuilder.New(avaxAddrs, avaxState.PCTX, pBackend) - pSigner := psigner.New(avaxKeychain, pBackend) + pSigner := psigner.New(avaxKeychain, pBackend, avaxState.PCTX.NetworkID) xChainID := avaxState.XCTX.BlockchainID xUTXOs := common.NewChainUTXOs(xChainID, avaxState.UTXOs) xBackend := x.NewBackend(avaxState.XCTX, xUTXOs) xBuilder := xbuilder.New(avaxAddrs, avaxState.XCTX, xBackend) - xSigner := xsigner.New(avaxKeychain, xBackend) + xSigner := xsigner.New(avaxKeychain, xBackend, avaxState.XCTX.NetworkID) cChainID := avaxState.CCTX.BlockchainID cUTXOs := common.NewChainUTXOs(cChainID, avaxState.UTXOs) @@ -157,6 +157,6 @@ func MakePWallet( pBackend := pwallet.NewBackend(pUTXOs, owners) pClient := p.NewClient(client, pBackend) pBuilder := pbuilder.New(addrs, context, pBackend) - pSigner := psigner.New(keychain, pBackend) + pSigner := psigner.New(keychain, pBackend, context.NetworkID) return pwallet.New(pClient, pBuilder, pSigner), nil } From 89fa84d3c411d1f9d0d8d99aa91f043d65375f2d Mon Sep 17 00:00:00 2001 From: Felipe Madero Date: Mon, 15 Sep 2025 13:07:11 -0300 Subject: [PATCH 2/9] =?UTF-8?q?Add=20key=20type=20validation=20to=20proces?= =?UTF-8?q?sKey=20function=20to=20ensure=20only=20supported=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=E2=94=82=20secp256k1=20key=20types=20are=20a?= =?UTF-8?q?ccepted.=20Fix=20test=20cases=20and=20remove=20obsolete=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=E2=94=82=20ledger=20keycha?= =?UTF-8?q?in=20files=20from=20main=20package.=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=E2=94=82=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=E2=94=82=20Key=20?= =?UTF-8?q?changes:=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=E2=94=82=20-=20Add=20validation?= =?UTF-8?q?=20for=20SecpAvaAddr,=20SecpAvaTestAddr,=20SecpEthAddr=20key=20?= =?UTF-8?q?types=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=E2=94=82=20-=20Fix=20TestC?= =?UTF-8?q?ubesignerKeychain=5FGetEth=20to=20test=20non-existent=20address?= =?UTF-8?q?=20handling=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=E2=94=82=20-=20Remove?= =?UTF-8?q?=20duplicate=20ledger=20keychain=20implementation=20files?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cubesigner/cubesigner_keychain.go | 8 + .../cubesigner/cubesigner_keychain_test.go | 9 +- utils/crypto/keychain/keychain_test.go | 424 ------------------ utils/crypto/keychain/keychainmock/ledger.go | 131 ------ utils/crypto/keychain/ledger.go | 19 - utils/crypto/keychain/mocks_generate_test.go | 6 - 6 files changed, 14 insertions(+), 583 deletions(-) delete mode 100644 utils/crypto/keychain/keychain_test.go delete mode 100644 utils/crypto/keychain/keychainmock/ledger.go delete mode 100644 utils/crypto/keychain/ledger.go delete mode 100644 utils/crypto/keychain/mocks_generate_test.go diff --git a/utils/crypto/keychain/cubesigner/cubesigner_keychain.go b/utils/crypto/keychain/cubesigner/cubesigner_keychain.go index 7cc9a09cd121..8abc1a35bc10 100644 --- a/utils/crypto/keychain/cubesigner/cubesigner_keychain.go +++ b/utils/crypto/keychain/cubesigner/cubesigner_keychain.go @@ -54,6 +54,14 @@ func processKey( return nil, fmt.Errorf("could not find server key %s: %w", keyID, err) } + // Validate key type + switch keyInfo.KeyType { + case models.SecpAvaAddr, models.SecpAvaTestAddr, models.SecpEthAddr: + // Supported key types + default: + return nil, fmt.Errorf("keytype %s of server key %s is not supported", keyInfo.KeyType, keyID) + } + // get public key pubKeyHex := strings.TrimPrefix(keyInfo.PublicKey, "0x") pubKeyBytes, err := hex.DecodeString(pubKeyHex) diff --git a/utils/crypto/keychain/cubesigner/cubesigner_keychain_test.go b/utils/crypto/keychain/cubesigner/cubesigner_keychain_test.go index 0eea77ce8289..60fe274d2616 100644 --- a/utils/crypto/keychain/cubesigner/cubesigner_keychain_test.go +++ b/utils/crypto/keychain/cubesigner/cubesigner_keychain_test.go @@ -7,6 +7,7 @@ import ( "errors" "testing" + "github.com/ava-labs/libevm/common" "github.com/stretchr/testify/require" "go.uber.org/mock/gomock" @@ -174,9 +175,11 @@ func TestCubesignerKeychain_GetEth(t *testing.T) { require.True(found) require.NotNil(signer) - // verify this is a C-chain signer - cubesignerSigner := signer.(*cubesignerSigner) - require.True(cubesignerSigner.cChainSigner) + // Test non-existent address + nonExistentAddr := common.HexToAddress("0x0000000000000000000000000000000000000000") + signer, found = kc.GetEth(nonExistentAddr) + require.False(found) + require.Nil(signer) } func TestCubesignerSigner_SignHash(t *testing.T) { diff --git a/utils/crypto/keychain/keychain_test.go b/utils/crypto/keychain/keychain_test.go deleted file mode 100644 index 66256583267e..000000000000 --- a/utils/crypto/keychain/keychain_test.go +++ /dev/null @@ -1,424 +0,0 @@ -// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package keychain - -import ( - "errors" - "testing" - - "github.com/stretchr/testify/require" - "go.uber.org/mock/gomock" - - "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/utils/crypto/keychain/keychainmock" -) - -var errTest = errors.New("test") - -func TestNewLedgerKeychain(t *testing.T) { - require := require.New(t) - ctrl := gomock.NewController(t) - - addr := ids.GenerateTestShortID() - - // user request invalid number of addresses to derive - ledger := keychainmock.NewLedger(ctrl) - _, err := NewLedgerKeychain(ledger, 0) - require.ErrorIs(err, ErrInvalidNumAddrsToDerive) - - // ledger does not return expected number of derived addresses - ledger = keychainmock.NewLedger(ctrl) - ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{}, nil).Times(1) - _, err = NewLedgerKeychain(ledger, 1) - require.ErrorIs(err, ErrInvalidNumAddrsDerived) - - // ledger return error when asked for derived addresses - ledger = keychainmock.NewLedger(ctrl) - ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{addr}, errTest).Times(1) - _, err = NewLedgerKeychain(ledger, 1) - require.ErrorIs(err, errTest) - - // good path - ledger = keychainmock.NewLedger(ctrl) - ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{addr}, nil).Times(1) - _, err = NewLedgerKeychain(ledger, 1) - require.NoError(err) -} - -func TestLedgerKeychain_Addresses(t *testing.T) { - require := require.New(t) - ctrl := gomock.NewController(t) - - addr1 := ids.GenerateTestShortID() - addr2 := ids.GenerateTestShortID() - addr3 := ids.GenerateTestShortID() - - // 1 addr - ledger := keychainmock.NewLedger(ctrl) - ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{addr1}, nil).Times(1) - kc, err := NewLedgerKeychain(ledger, 1) - require.NoError(err) - - addrs := kc.Addresses() - require.Len(addrs, 1) - require.True(addrs.Contains(addr1)) - - // multiple addresses - ledger = keychainmock.NewLedger(ctrl) - ledger.EXPECT().Addresses([]uint32{0, 1, 2}).Return([]ids.ShortID{addr1, addr2, addr3}, nil).Times(1) - kc, err = NewLedgerKeychain(ledger, 3) - require.NoError(err) - - addrs = kc.Addresses() - require.Len(addrs, 3) - require.Contains(addrs, addr1) - require.Contains(addrs, addr2) - require.Contains(addrs, addr3) -} - -func TestLedgerKeychain_Get(t *testing.T) { - require := require.New(t) - ctrl := gomock.NewController(t) - - addr1 := ids.GenerateTestShortID() - addr2 := ids.GenerateTestShortID() - addr3 := ids.GenerateTestShortID() - - // 1 addr - ledger := keychainmock.NewLedger(ctrl) - ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{addr1}, nil).Times(1) - kc, err := NewLedgerKeychain(ledger, 1) - require.NoError(err) - - _, b := kc.Get(ids.GenerateTestShortID()) - require.False(b) - - s, b := kc.Get(addr1) - require.Equal(s.Address(), addr1) - require.True(b) - - // multiple addresses - ledger = keychainmock.NewLedger(ctrl) - ledger.EXPECT().Addresses([]uint32{0, 1, 2}).Return([]ids.ShortID{addr1, addr2, addr3}, nil).Times(1) - kc, err = NewLedgerKeychain(ledger, 3) - require.NoError(err) - - _, b = kc.Get(ids.GenerateTestShortID()) - require.False(b) - - s, b = kc.Get(addr1) - require.True(b) - require.Equal(s.Address(), addr1) - - s, b = kc.Get(addr2) - require.True(b) - require.Equal(s.Address(), addr2) - - s, b = kc.Get(addr3) - require.True(b) - require.Equal(s.Address(), addr3) -} - -func TestLedgerSigner_SignHash(t *testing.T) { - require := require.New(t) - ctrl := gomock.NewController(t) - - addr1 := ids.GenerateTestShortID() - addr2 := ids.GenerateTestShortID() - addr3 := ids.GenerateTestShortID() - toSign := []byte{1, 2, 3, 4, 5} - expectedSignature1 := []byte{1, 1, 1} - expectedSignature2 := []byte{2, 2, 2} - expectedSignature3 := []byte{3, 3, 3} - - // ledger returns an incorrect number of signatures - ledger := keychainmock.NewLedger(ctrl) - ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{addr1}, nil).Times(1) - ledger.EXPECT().SignHash(toSign, []uint32{0}).Return([][]byte{}, nil).Times(1) - kc, err := NewLedgerKeychain(ledger, 1) - require.NoError(err) - - s, b := kc.Get(addr1) - require.True(b) - - _, err = s.SignHash(toSign) - require.ErrorIs(err, ErrInvalidNumSignatures) - - // ledger returns an error when asked for signature - ledger = keychainmock.NewLedger(ctrl) - ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{addr1}, nil).Times(1) - ledger.EXPECT().SignHash(toSign, []uint32{0}).Return([][]byte{expectedSignature1}, errTest).Times(1) - kc, err = NewLedgerKeychain(ledger, 1) - require.NoError(err) - - s, b = kc.Get(addr1) - require.True(b) - - _, err = s.SignHash(toSign) - require.ErrorIs(err, errTest) - - // good path 1 addr - ledger = keychainmock.NewLedger(ctrl) - ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{addr1}, nil).Times(1) - ledger.EXPECT().SignHash(toSign, []uint32{0}).Return([][]byte{expectedSignature1}, nil).Times(1) - kc, err = NewLedgerKeychain(ledger, 1) - require.NoError(err) - - s, b = kc.Get(addr1) - require.True(b) - - signature, err := s.SignHash(toSign) - require.NoError(err) - require.Equal(expectedSignature1, signature) - - // good path 3 addr - ledger = keychainmock.NewLedger(ctrl) - ledger.EXPECT().Addresses([]uint32{0, 1, 2}).Return([]ids.ShortID{addr1, addr2, addr3}, nil).Times(1) - ledger.EXPECT().SignHash(toSign, []uint32{0}).Return([][]byte{expectedSignature1}, nil).Times(1) - ledger.EXPECT().SignHash(toSign, []uint32{1}).Return([][]byte{expectedSignature2}, nil).Times(1) - ledger.EXPECT().SignHash(toSign, []uint32{2}).Return([][]byte{expectedSignature3}, nil).Times(1) - kc, err = NewLedgerKeychain(ledger, 3) - require.NoError(err) - - s, b = kc.Get(addr1) - require.True(b) - - signature, err = s.SignHash(toSign) - require.NoError(err) - require.Equal(expectedSignature1, signature) - - s, b = kc.Get(addr2) - require.True(b) - - signature, err = s.SignHash(toSign) - require.NoError(err) - require.Equal(expectedSignature2, signature) - - s, b = kc.Get(addr3) - require.True(b) - - signature, err = s.SignHash(toSign) - require.NoError(err) - require.Equal(expectedSignature3, signature) -} - -func TestNewLedgerKeychainFromIndices(t *testing.T) { - require := require.New(t) - ctrl := gomock.NewController(t) - - addr := ids.GenerateTestShortID() - _ = addr - - // user request invalid number of indices - ledger := keychainmock.NewLedger(ctrl) - _, err := NewLedgerKeychainFromIndices(ledger, []uint32{}) - require.ErrorIs(err, ErrInvalidIndicesLength) - - // ledger does not return expected number of derived addresses - ledger = keychainmock.NewLedger(ctrl) - ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{}, nil).Times(1) - _, err = NewLedgerKeychainFromIndices(ledger, []uint32{0}) - require.ErrorIs(err, ErrInvalidNumAddrsDerived) - - // ledger return error when asked for derived addresses - ledger = keychainmock.NewLedger(ctrl) - ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{addr}, errTest).Times(1) - _, err = NewLedgerKeychainFromIndices(ledger, []uint32{0}) - require.ErrorIs(err, errTest) - - // good path - ledger = keychainmock.NewLedger(ctrl) - ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{addr}, nil).Times(1) - _, err = NewLedgerKeychainFromIndices(ledger, []uint32{0}) - require.NoError(err) -} - -func TestLedgerKeychainFromIndices_Addresses(t *testing.T) { - require := require.New(t) - ctrl := gomock.NewController(t) - - addr1 := ids.GenerateTestShortID() - addr2 := ids.GenerateTestShortID() - addr3 := ids.GenerateTestShortID() - - // 1 addr - ledger := keychainmock.NewLedger(ctrl) - ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{addr1}, nil).Times(1) - kc, err := NewLedgerKeychainFromIndices(ledger, []uint32{0}) - require.NoError(err) - - addrs := kc.Addresses() - require.Len(addrs, 1) - require.True(addrs.Contains(addr1)) - - // first 3 addresses - ledger = keychainmock.NewLedger(ctrl) - ledger.EXPECT().Addresses([]uint32{0, 1, 2}).Return([]ids.ShortID{addr1, addr2, addr3}, nil).Times(1) - kc, err = NewLedgerKeychainFromIndices(ledger, []uint32{0, 1, 2}) - require.NoError(err) - - addrs = kc.Addresses() - require.Len(addrs, 3) - require.Contains(addrs, addr1) - require.Contains(addrs, addr2) - require.Contains(addrs, addr3) - - // some 3 addresses - indices := []uint32{3, 7, 1} - addresses := []ids.ShortID{addr1, addr2, addr3} - ledger = keychainmock.NewLedger(ctrl) - ledger.EXPECT().Addresses(indices).Return(addresses, nil).Times(1) - kc, err = NewLedgerKeychainFromIndices(ledger, indices) - require.NoError(err) - - addrs = kc.Addresses() - require.Len(addrs, len(indices)) - require.Contains(addrs, addr1) - require.Contains(addrs, addr2) - require.Contains(addrs, addr3) - - // repeated addresses - indices = []uint32{3, 7, 1, 3, 1, 7} - addresses = []ids.ShortID{addr1, addr2, addr3, addr1, addr2, addr3} - ledger = keychainmock.NewLedger(ctrl) - ledger.EXPECT().Addresses(indices).Return(addresses, nil).Times(1) - kc, err = NewLedgerKeychainFromIndices(ledger, indices) - require.NoError(err) - - addrs = kc.Addresses() - require.Len(addrs, 3) - require.Contains(addrs, addr1) - require.Contains(addrs, addr2) - require.Contains(addrs, addr3) -} - -func TestLedgerKeychainFromIndices_Get(t *testing.T) { - require := require.New(t) - ctrl := gomock.NewController(t) - - addr1 := ids.GenerateTestShortID() - addr2 := ids.GenerateTestShortID() - addr3 := ids.GenerateTestShortID() - - // 1 addr - ledger := keychainmock.NewLedger(ctrl) - ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{addr1}, nil).Times(1) - kc, err := NewLedgerKeychainFromIndices(ledger, []uint32{0}) - require.NoError(err) - - _, b := kc.Get(ids.GenerateTestShortID()) - require.False(b) - - s, b := kc.Get(addr1) - require.Equal(s.Address(), addr1) - require.True(b) - - // some 3 addresses - indices := []uint32{3, 7, 1} - addresses := []ids.ShortID{addr1, addr2, addr3} - ledger = keychainmock.NewLedger(ctrl) - ledger.EXPECT().Addresses(indices).Return(addresses, nil).Times(1) - kc, err = NewLedgerKeychainFromIndices(ledger, indices) - require.NoError(err) - - _, b = kc.Get(ids.GenerateTestShortID()) - require.False(b) - - s, b = kc.Get(addr1) - require.True(b) - require.Equal(s.Address(), addr1) - - s, b = kc.Get(addr2) - require.True(b) - require.Equal(s.Address(), addr2) - - s, b = kc.Get(addr3) - require.True(b) - require.Equal(s.Address(), addr3) -} - -func TestLedgerSignerFromIndices_SignHash(t *testing.T) { - require := require.New(t) - ctrl := gomock.NewController(t) - - addr1 := ids.GenerateTestShortID() - addr2 := ids.GenerateTestShortID() - addr3 := ids.GenerateTestShortID() - toSign := []byte{1, 2, 3, 4, 5} - expectedSignature1 := []byte{1, 1, 1} - expectedSignature2 := []byte{2, 2, 2} - expectedSignature3 := []byte{3, 3, 3} - - // ledger returns an incorrect number of signatures - ledger := keychainmock.NewLedger(ctrl) - ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{addr1}, nil).Times(1) - ledger.EXPECT().SignHash(toSign, []uint32{0}).Return([][]byte{}, nil).Times(1) - kc, err := NewLedgerKeychainFromIndices(ledger, []uint32{0}) - require.NoError(err) - - s, b := kc.Get(addr1) - require.True(b) - - _, err = s.SignHash(toSign) - require.ErrorIs(err, ErrInvalidNumSignatures) - - // ledger returns an error when asked for signature - ledger = keychainmock.NewLedger(ctrl) - ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{addr1}, nil).Times(1) - ledger.EXPECT().SignHash(toSign, []uint32{0}).Return([][]byte{expectedSignature1}, errTest).Times(1) - kc, err = NewLedgerKeychainFromIndices(ledger, []uint32{0}) - require.NoError(err) - - s, b = kc.Get(addr1) - require.True(b) - - _, err = s.SignHash(toSign) - require.ErrorIs(err, errTest) - - // good path 1 addr - ledger = keychainmock.NewLedger(ctrl) - ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{addr1}, nil).Times(1) - ledger.EXPECT().SignHash(toSign, []uint32{0}).Return([][]byte{expectedSignature1}, nil).Times(1) - kc, err = NewLedgerKeychainFromIndices(ledger, []uint32{0}) - require.NoError(err) - - s, b = kc.Get(addr1) - require.True(b) - - signature, err := s.SignHash(toSign) - require.NoError(err) - require.Equal(expectedSignature1, signature) - - // good path some 3 addresses - indices := []uint32{3, 7, 1} - addresses := []ids.ShortID{addr1, addr2, addr3} - ledger = keychainmock.NewLedger(ctrl) - ledger.EXPECT().Addresses(indices).Return(addresses, nil).Times(1) - ledger.EXPECT().SignHash(toSign, []uint32{indices[0]}).Return([][]byte{expectedSignature1}, nil).Times(1) - ledger.EXPECT().SignHash(toSign, []uint32{indices[1]}).Return([][]byte{expectedSignature2}, nil).Times(1) - ledger.EXPECT().SignHash(toSign, []uint32{indices[2]}).Return([][]byte{expectedSignature3}, nil).Times(1) - kc, err = NewLedgerKeychainFromIndices(ledger, indices) - require.NoError(err) - - s, b = kc.Get(addr1) - require.True(b) - - signature, err = s.SignHash(toSign) - require.NoError(err) - require.Equal(expectedSignature1, signature) - - s, b = kc.Get(addr2) - require.True(b) - - signature, err = s.SignHash(toSign) - require.NoError(err) - require.Equal(expectedSignature2, signature) - - s, b = kc.Get(addr3) - require.True(b) - - signature, err = s.SignHash(toSign) - require.NoError(err) - require.Equal(expectedSignature3, signature) -} diff --git a/utils/crypto/keychain/keychainmock/ledger.go b/utils/crypto/keychain/keychainmock/ledger.go deleted file mode 100644 index d7a22a93bc77..000000000000 --- a/utils/crypto/keychain/keychainmock/ledger.go +++ /dev/null @@ -1,131 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: github.com/ava-labs/avalanchego/utils/crypto/keychain (interfaces: Ledger) -// -// Generated by this command: -// -// mockgen -package=keychainmock -destination=keychainmock/ledger.go -mock_names=Ledger=Ledger . Ledger -// - -// Package keychainmock is a generated GoMock package. -package keychainmock - -import ( - reflect "reflect" - - ids "github.com/ava-labs/avalanchego/ids" - version "github.com/ava-labs/avalanchego/version" - gomock "go.uber.org/mock/gomock" -) - -// Ledger is a mock of Ledger interface. -type Ledger struct { - ctrl *gomock.Controller - recorder *LedgerMockRecorder - isgomock struct{} -} - -// LedgerMockRecorder is the mock recorder for Ledger. -type LedgerMockRecorder struct { - mock *Ledger -} - -// NewLedger creates a new mock instance. -func NewLedger(ctrl *gomock.Controller) *Ledger { - mock := &Ledger{ctrl: ctrl} - mock.recorder = &LedgerMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *Ledger) EXPECT() *LedgerMockRecorder { - return m.recorder -} - -// Address mocks base method. -func (m *Ledger) Address(displayHRP string, addressIndex uint32) (ids.ShortID, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Address", displayHRP, addressIndex) - ret0, _ := ret[0].(ids.ShortID) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// Address indicates an expected call of Address. -func (mr *LedgerMockRecorder) Address(displayHRP, addressIndex any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Address", reflect.TypeOf((*Ledger)(nil).Address), displayHRP, addressIndex) -} - -// Addresses mocks base method. -func (m *Ledger) Addresses(addressIndices []uint32) ([]ids.ShortID, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Addresses", addressIndices) - ret0, _ := ret[0].([]ids.ShortID) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// Addresses indicates an expected call of Addresses. -func (mr *LedgerMockRecorder) Addresses(addressIndices any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Addresses", reflect.TypeOf((*Ledger)(nil).Addresses), addressIndices) -} - -// Disconnect mocks base method. -func (m *Ledger) Disconnect() error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Disconnect") - ret0, _ := ret[0].(error) - return ret0 -} - -// Disconnect indicates an expected call of Disconnect. -func (mr *LedgerMockRecorder) Disconnect() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Disconnect", reflect.TypeOf((*Ledger)(nil).Disconnect)) -} - -// Sign mocks base method. -func (m *Ledger) Sign(unsignedTxBytes []byte, addressIndices []uint32) ([][]byte, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Sign", unsignedTxBytes, addressIndices) - ret0, _ := ret[0].([][]byte) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// Sign indicates an expected call of Sign. -func (mr *LedgerMockRecorder) Sign(unsignedTxBytes, addressIndices any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Sign", reflect.TypeOf((*Ledger)(nil).Sign), unsignedTxBytes, addressIndices) -} - -// SignHash mocks base method. -func (m *Ledger) SignHash(hash []byte, addressIndices []uint32) ([][]byte, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SignHash", hash, addressIndices) - ret0, _ := ret[0].([][]byte) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// SignHash indicates an expected call of SignHash. -func (mr *LedgerMockRecorder) SignHash(hash, addressIndices any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SignHash", reflect.TypeOf((*Ledger)(nil).SignHash), hash, addressIndices) -} - -// Version mocks base method. -func (m *Ledger) Version() (*version.Semantic, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Version") - ret0, _ := ret[0].(*version.Semantic) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// Version indicates an expected call of Version. -func (mr *LedgerMockRecorder) Version() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Version", reflect.TypeOf((*Ledger)(nil).Version)) -} diff --git a/utils/crypto/keychain/ledger.go b/utils/crypto/keychain/ledger.go deleted file mode 100644 index 5778ff304d7a..000000000000 --- a/utils/crypto/keychain/ledger.go +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package keychain - -import ( - "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/version" -) - -// Ledger interface for the ledger wrapper -type Ledger interface { - Version() (v *version.Semantic, err error) - Address(displayHRP string, addressIndex uint32) (ids.ShortID, error) - Addresses(addressIndices []uint32) ([]ids.ShortID, error) - SignHash(hash []byte, addressIndices []uint32) ([][]byte, error) - Sign(unsignedTxBytes []byte, addressIndices []uint32) ([][]byte, error) - Disconnect() error -} diff --git a/utils/crypto/keychain/mocks_generate_test.go b/utils/crypto/keychain/mocks_generate_test.go deleted file mode 100644 index 151f3c4650ec..000000000000 --- a/utils/crypto/keychain/mocks_generate_test.go +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package keychain - -//go:generate go run go.uber.org/mock/mockgen -package=${GOPACKAGE}mock -destination=${GOPACKAGE}mock/ledger.go -mock_names=Ledger=Ledger . Ledger From dde6d273623d68f4e1c15bd401fb5f57f3ec127f Mon Sep 17 00:00:00 2001 From: Felipe Madero Date: Mon, 15 Sep 2025 13:29:35 -0300 Subject: [PATCH 3/9] Update copyright headers to 2019-2025 Update copyright year range from 2019-2024 to 2019-2025 across all files modified in the CubeSigner keychain integration. --- utils/crypto/keychain/cubesigner/cubesigner_client.go | 2 +- utils/crypto/keychain/cubesigner/cubesigner_keychain.go | 2 +- utils/crypto/keychain/cubesigner/cubesigner_keychain_test.go | 2 +- utils/crypto/keychain/cubesigner/mocks_generate_test.go | 2 +- utils/crypto/keychain/keychain.go | 2 +- utils/crypto/keychain/ledger/ledger.go | 2 +- utils/crypto/keychain/ledger/ledger_keychain.go | 2 +- utils/crypto/keychain/ledger/ledger_keychain_test.go | 2 +- utils/crypto/keychain/ledger/mocks_generate_test.go | 2 +- utils/crypto/keychain/signing_options.go | 2 +- utils/crypto/ledger/ledger.go | 2 +- utils/crypto/secp256k1/secp256k1.go | 2 +- vms/avm/txs/txstest/builder.go | 2 +- vms/platformvm/txs/txstest/wallet.go | 2 +- vms/secp256k1fx/keychain.go | 2 +- wallet/chain/c/signer.go | 2 +- wallet/chain/p/signer/signer.go | 2 +- wallet/chain/p/signer/visitor.go | 2 +- wallet/chain/p/signer/with_options.go | 2 +- wallet/chain/p/wallet/wallet.go | 2 +- wallet/chain/x/signer/signer.go | 2 +- wallet/chain/x/signer/visitor.go | 2 +- wallet/subnet/primary/common/options.go | 2 +- wallet/subnet/primary/wallet.go | 2 +- 24 files changed, 24 insertions(+), 24 deletions(-) diff --git a/utils/crypto/keychain/cubesigner/cubesigner_client.go b/utils/crypto/keychain/cubesigner/cubesigner_client.go index b8c721d71698..a33a139013b4 100644 --- a/utils/crypto/keychain/cubesigner/cubesigner_client.go +++ b/utils/crypto/keychain/cubesigner/cubesigner_client.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package cubesigner diff --git a/utils/crypto/keychain/cubesigner/cubesigner_keychain.go b/utils/crypto/keychain/cubesigner/cubesigner_keychain.go index 8abc1a35bc10..266819f38524 100644 --- a/utils/crypto/keychain/cubesigner/cubesigner_keychain.go +++ b/utils/crypto/keychain/cubesigner/cubesigner_keychain.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package cubesigner diff --git a/utils/crypto/keychain/cubesigner/cubesigner_keychain_test.go b/utils/crypto/keychain/cubesigner/cubesigner_keychain_test.go index 60fe274d2616..78f674ce54d2 100644 --- a/utils/crypto/keychain/cubesigner/cubesigner_keychain_test.go +++ b/utils/crypto/keychain/cubesigner/cubesigner_keychain_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package cubesigner diff --git a/utils/crypto/keychain/cubesigner/mocks_generate_test.go b/utils/crypto/keychain/cubesigner/mocks_generate_test.go index 5a5f526fc623..dbe08ac881d7 100644 --- a/utils/crypto/keychain/cubesigner/mocks_generate_test.go +++ b/utils/crypto/keychain/cubesigner/mocks_generate_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package cubesigner diff --git a/utils/crypto/keychain/keychain.go b/utils/crypto/keychain/keychain.go index 8514c28ef3e3..b614bd37b500 100644 --- a/utils/crypto/keychain/keychain.go +++ b/utils/crypto/keychain/keychain.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package keychain diff --git a/utils/crypto/keychain/ledger/ledger.go b/utils/crypto/keychain/ledger/ledger.go index d2b7edcf7f14..f1d85bcef23e 100644 --- a/utils/crypto/keychain/ledger/ledger.go +++ b/utils/crypto/keychain/ledger/ledger.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package ledger diff --git a/utils/crypto/keychain/ledger/ledger_keychain.go b/utils/crypto/keychain/ledger/ledger_keychain.go index 36206b44b16e..dbc6529ddb4d 100644 --- a/utils/crypto/keychain/ledger/ledger_keychain.go +++ b/utils/crypto/keychain/ledger/ledger_keychain.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package ledger diff --git a/utils/crypto/keychain/ledger/ledger_keychain_test.go b/utils/crypto/keychain/ledger/ledger_keychain_test.go index 1dfdf4e04936..cc21be9f8fba 100644 --- a/utils/crypto/keychain/ledger/ledger_keychain_test.go +++ b/utils/crypto/keychain/ledger/ledger_keychain_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package ledger diff --git a/utils/crypto/keychain/ledger/mocks_generate_test.go b/utils/crypto/keychain/ledger/mocks_generate_test.go index 2e6e85b3e9d8..29093e1fff54 100644 --- a/utils/crypto/keychain/ledger/mocks_generate_test.go +++ b/utils/crypto/keychain/ledger/mocks_generate_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package ledger diff --git a/utils/crypto/keychain/signing_options.go b/utils/crypto/keychain/signing_options.go index f4364fd8a19c..1b640d650f06 100644 --- a/utils/crypto/keychain/signing_options.go +++ b/utils/crypto/keychain/signing_options.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package keychain diff --git a/utils/crypto/ledger/ledger.go b/utils/crypto/ledger/ledger.go index 332ee6efdfee..1cbf74a060ef 100644 --- a/utils/crypto/ledger/ledger.go +++ b/utils/crypto/ledger/ledger.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package ledger diff --git a/utils/crypto/secp256k1/secp256k1.go b/utils/crypto/secp256k1/secp256k1.go index d1d83214ba24..9e49b8842729 100644 --- a/utils/crypto/secp256k1/secp256k1.go +++ b/utils/crypto/secp256k1/secp256k1.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package secp256k1 diff --git a/vms/avm/txs/txstest/builder.go b/vms/avm/txs/txstest/builder.go index 7ac5d57c2bad..e6b2a45db786 100644 --- a/vms/avm/txs/txstest/builder.go +++ b/vms/avm/txs/txstest/builder.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package txstest diff --git a/vms/platformvm/txs/txstest/wallet.go b/vms/platformvm/txs/txstest/wallet.go index 9253ac779c49..74db93fb8671 100644 --- a/vms/platformvm/txs/txstest/wallet.go +++ b/vms/platformvm/txs/txstest/wallet.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package txstest diff --git a/vms/secp256k1fx/keychain.go b/vms/secp256k1fx/keychain.go index 62ae1b1112db..dac5a84aa689 100644 --- a/vms/secp256k1fx/keychain.go +++ b/vms/secp256k1fx/keychain.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package secp256k1fx diff --git a/wallet/chain/c/signer.go b/wallet/chain/c/signer.go index d7eab9a3477f..050efe45f3f5 100644 --- a/wallet/chain/c/signer.go +++ b/wallet/chain/c/signer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package c diff --git a/wallet/chain/p/signer/signer.go b/wallet/chain/p/signer/signer.go index 22777a7ac019..0841112f87e1 100644 --- a/wallet/chain/p/signer/signer.go +++ b/wallet/chain/p/signer/signer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package signer diff --git a/wallet/chain/p/signer/visitor.go b/wallet/chain/p/signer/visitor.go index 9bbd2c9b35d5..d1366c759e1e 100644 --- a/wallet/chain/p/signer/visitor.go +++ b/wallet/chain/p/signer/visitor.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package signer diff --git a/wallet/chain/p/signer/with_options.go b/wallet/chain/p/signer/with_options.go index 60d82be8f25f..8bdeb42853ba 100644 --- a/wallet/chain/p/signer/with_options.go +++ b/wallet/chain/p/signer/with_options.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package signer diff --git a/wallet/chain/p/wallet/wallet.go b/wallet/chain/p/wallet/wallet.go index e9128e3bf439..116508f6fb71 100644 --- a/wallet/chain/p/wallet/wallet.go +++ b/wallet/chain/p/wallet/wallet.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package wallet diff --git a/wallet/chain/x/signer/signer.go b/wallet/chain/x/signer/signer.go index 82f4b519e440..93bc103b0b6b 100644 --- a/wallet/chain/x/signer/signer.go +++ b/wallet/chain/x/signer/signer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package signer diff --git a/wallet/chain/x/signer/visitor.go b/wallet/chain/x/signer/visitor.go index 4b3168bd66c1..973103c1fe16 100644 --- a/wallet/chain/x/signer/visitor.go +++ b/wallet/chain/x/signer/visitor.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package signer diff --git a/wallet/subnet/primary/common/options.go b/wallet/subnet/primary/common/options.go index 80386a079935..d5a1dd7e1801 100644 --- a/wallet/subnet/primary/common/options.go +++ b/wallet/subnet/primary/common/options.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package common diff --git a/wallet/subnet/primary/wallet.go b/wallet/subnet/primary/wallet.go index 3fc905f9cceb..df4f335ce7db 100644 --- a/wallet/subnet/primary/wallet.go +++ b/wallet/subnet/primary/wallet.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package primary From 753a25df8184697cf642266f7b80e1e99fcf0b7e Mon Sep 17 00:00:00 2001 From: Felipe Madero Date: Mon, 15 Sep 2025 14:43:55 -0300 Subject: [PATCH 4/9] lint --- .../keychain/cubesigner/cubesigner_client.go | 2 +- .../cubesigner/cubesigner_keychain.go | 35 ++++++---- .../cubesigner/cubesigner_keychain_test.go | 69 +++++++++---------- utils/crypto/keychain/keychain.go | 2 +- .../crypto/keychain/ledger/ledger_keychain.go | 2 +- utils/crypto/keychain/signing_options.go | 2 +- utils/crypto/ledger/ledger.go | 1 - utils/crypto/secp256k1/secp256k1.go | 4 +- vms/secp256k1fx/keychain.go | 2 +- wallet/chain/p/signer/visitor.go | 16 ++--- wallet/chain/p/signer/with_options.go | 5 +- wallet/subnet/primary/common/options.go | 1 - 12 files changed, 70 insertions(+), 71 deletions(-) diff --git a/utils/crypto/keychain/cubesigner/cubesigner_client.go b/utils/crypto/keychain/cubesigner/cubesigner_client.go index a33a139013b4..3c14ab4b672d 100644 --- a/utils/crypto/keychain/cubesigner/cubesigner_client.go +++ b/utils/crypto/keychain/cubesigner/cubesigner_client.go @@ -14,4 +14,4 @@ type CubeSignerClient interface { GetKeyInOrg(keyID string) (*models.KeyInfo, error) BlobSign(keyID string, request models.BlobSignRequest, receipts ...*client.MfaReceipt) (*client.CubeSignerResponse[models.SignResponse], error) AvaSerializedTxSign(chainAlias, materialID string, request models.AvaSerializedTxSignRequest, receipts ...*client.MfaReceipt) (*client.CubeSignerResponse[models.SignResponse], error) -} \ No newline at end of file +} diff --git a/utils/crypto/keychain/cubesigner/cubesigner_keychain.go b/utils/crypto/keychain/cubesigner/cubesigner_keychain.go index 266819f38524..6d56cb834df8 100644 --- a/utils/crypto/keychain/cubesigner/cubesigner_keychain.go +++ b/utils/crypto/keychain/cubesigner/cubesigner_keychain.go @@ -5,21 +5,23 @@ package cubesigner import ( "encoding/base64" "encoding/hex" + "errors" "fmt" "strings" + "github.com/ava-labs/libevm/common" + "github.com/cubist-labs/cubesigner-go-sdk/client" + "github.com/cubist-labs/cubesigner-go-sdk/models" + "golang.org/x/exp/maps" + "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/utils/crypto/keychain" - avasecp256k1 "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" "github.com/ava-labs/avalanchego/utils/formatting/address" "github.com/ava-labs/avalanchego/utils/set" - "github.com/ava-labs/libevm/common" - "github.com/cubist-labs/cubesigner-go-sdk/client" - "github.com/cubist-labs/cubesigner-go-sdk/models" + avasecp256k1 "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" secp256k1 "github.com/decred/dcrd/dcrec/secp256k1/v4" - "golang.org/x/exp/maps" ) var ( @@ -27,6 +29,13 @@ var ( _ keychain.EthKeychain = (*CubesignerKeychain)(nil) _ keychain.Signer = (*cubesignerSigner)(nil) _ CubeSignerClient = (*client.ApiClient)(nil) + + ErrNoKeysProvided = errors.New("you need to provide at least one key to create a server keychain") + ErrEmptySignatureFromServer = errors.New("empty signature obtained from server") + ErrChainAliasMissing = errors.New("chainAlias must be specified in options for CubeSigner") + ErrNetworkIDMissing = errors.New("network ID must be specified in options for CubeSigner") + ErrUnsupportedKeyType = errors.New("unsupported key type") + ErrInvalidPublicKey = errors.New("invalid public key format") ) // keyInfo holds both the public key and keyID for a cubesigner key @@ -59,14 +68,14 @@ func processKey( case models.SecpAvaAddr, models.SecpAvaTestAddr, models.SecpEthAddr: // Supported key types default: - return nil, fmt.Errorf("keytype %s of server key %s is not supported", keyInfo.KeyType, keyID) + return nil, fmt.Errorf("keytype %s of server key %s: %w", keyInfo.KeyType, keyID, ErrUnsupportedKeyType) } // get public key pubKeyHex := strings.TrimPrefix(keyInfo.PublicKey, "0x") pubKeyBytes, err := hex.DecodeString(pubKeyHex) if err != nil { - return nil, fmt.Errorf("failed to decode public key for server key %s: %w", keyID, err) + return nil, fmt.Errorf("%w: failed to decode public key for server key %s: %w", ErrInvalidPublicKey, keyID, err) } if len(pubKeyBytes) != 65 { return nil, fmt.Errorf("invalid public key length for server key %s: expected 65 bytes, got %d", keyID, len(pubKeyBytes)) @@ -92,7 +101,7 @@ func NewCubesignerKeychain( keyIDs []string, ) (*CubesignerKeychain, error) { if len(keyIDs) == 0 { - return nil, fmt.Errorf("you need to provide at least one key to create a server keychain") + return nil, ErrNoKeysProvided } avaAddrToKeyInfo := map[ids.ShortID]*keyInfo{} @@ -185,7 +194,7 @@ func (s *cubesignerSigner) SignHash(b []byte) ([]byte, error) { return nil, fmt.Errorf("server signing err: %w", err) } if response.ResponseData == nil { - return nil, fmt.Errorf("empty signature obtained from server") + return nil, ErrEmptySignatureFromServer } return processSignatureResponse(response.ResponseData.Signature) } @@ -196,18 +205,16 @@ func (s *cubesignerSigner) Sign(b []byte, opts ...keychain.SigningOption) ([]byt for _, opt := range opts { opt(options) } - // Require chainAlias and network from options if options.ChainAlias == "" { - return nil, fmt.Errorf("chainAlias must be specified in options for CubeSigner") + return nil, ErrChainAliasMissing } if options.ChainAlias != "P" && options.ChainAlias != "X" && options.ChainAlias != "C" { return nil, fmt.Errorf("chainAlias must be 'P', 'X' or 'C' for CubeSigner, got %q", options.ChainAlias) } if options.NetworkID == 0 { - return nil, fmt.Errorf("network ID must be specified in options for CubeSigner") + return nil, ErrNetworkIDMissing } - var materialID string if options.ChainAlias == "C" { materialID = s.pubKey.EthAddress().Hex() @@ -232,7 +239,7 @@ func (s *cubesignerSigner) Sign(b []byte, opts ...keychain.SigningOption) ([]byt return nil, fmt.Errorf("server signing err: %w", err) } if response.ResponseData == nil { - return nil, fmt.Errorf("empty signature obtained from server") + return nil, ErrEmptySignatureFromServer } return processSignatureResponse(response.ResponseData.Signature) } diff --git a/utils/crypto/keychain/cubesigner/cubesigner_keychain_test.go b/utils/crypto/keychain/cubesigner/cubesigner_keychain_test.go index 78f674ce54d2..be3b7393f4b5 100644 --- a/utils/crypto/keychain/cubesigner/cubesigner_keychain_test.go +++ b/utils/crypto/keychain/cubesigner/cubesigner_keychain_test.go @@ -8,32 +8,32 @@ import ( "testing" "github.com/ava-labs/libevm/common" + "github.com/cubist-labs/cubesigner-go-sdk/client" + "github.com/cubist-labs/cubesigner-go-sdk/models" "github.com/stretchr/testify/require" "go.uber.org/mock/gomock" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/crypto/keychain/cubesigner/cubesignermock" - "github.com/cubist-labs/cubesigner-go-sdk/client" - "github.com/cubist-labs/cubesigner-go-sdk/models" ) var errTest = errors.New("test") +const testKeyID = "test-key-id" + func TestNewCubesignerKeychain(t *testing.T) { require := require.New(t) ctrl := gomock.NewController(t) - keyID := "test-key-id" - // user provides no keys mockClient := cubesignermock.NewCubeSignerClient(ctrl) _, err := NewCubesignerKeychain(mockClient, []string{}) - require.ErrorContains(err, "you need to provide at least one key") + require.ErrorIs(err, ErrNoKeysProvided) // client returns error when getting key info mockClient = cubesignermock.NewCubeSignerClient(ctrl) - mockClient.EXPECT().GetKeyInOrg(keyID).Return(nil, errTest).Times(1) - _, err = NewCubesignerKeychain(mockClient, []string{keyID}) + mockClient.EXPECT().GetKeyInOrg(testKeyID).Return(nil, errTest).Times(1) + _, err = NewCubesignerKeychain(mockClient, []string{testKeyID}) require.ErrorIs(err, errTest) // client returns unsupported key type @@ -42,9 +42,9 @@ func TestNewCubesignerKeychain(t *testing.T) { KeyType: "UnsupportedType", PublicKey: "0x04" + "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", } - mockClient.EXPECT().GetKeyInOrg(keyID).Return(keyInfo, nil).Times(1) - _, err = NewCubesignerKeychain(mockClient, []string{keyID}) - require.ErrorContains(err, "keytype UnsupportedType of server key") + mockClient.EXPECT().GetKeyInOrg(testKeyID).Return(keyInfo, nil).Times(1) + _, err = NewCubesignerKeychain(mockClient, []string{testKeyID}) + require.ErrorIs(err, ErrUnsupportedKeyType) // client returns invalid public key format mockClient = cubesignermock.NewCubeSignerClient(ctrl) @@ -52,9 +52,9 @@ func TestNewCubesignerKeychain(t *testing.T) { KeyType: models.SecpAvaAddr, PublicKey: "invalid-hex", } - mockClient.EXPECT().GetKeyInOrg(keyID).Return(keyInfo, nil).Times(1) - _, err = NewCubesignerKeychain(mockClient, []string{keyID}) - require.ErrorContains(err, "failed to decode public key") + mockClient.EXPECT().GetKeyInOrg(testKeyID).Return(keyInfo, nil).Times(1) + _, err = NewCubesignerKeychain(mockClient, []string{testKeyID}) + require.ErrorIs(err, ErrInvalidPublicKey) // good path - Avalanche address mockClient = cubesignermock.NewCubeSignerClient(ctrl) @@ -62,8 +62,8 @@ func TestNewCubesignerKeychain(t *testing.T) { KeyType: models.SecpAvaAddr, PublicKey: "0x04" + "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8", } - mockClient.EXPECT().GetKeyInOrg(keyID).Return(keyInfo, nil).Times(1) - kc, err := NewCubesignerKeychain(mockClient, []string{keyID}) + mockClient.EXPECT().GetKeyInOrg(testKeyID).Return(keyInfo, nil).Times(1) + kc, err := NewCubesignerKeychain(mockClient, []string{testKeyID}) require.NoError(err) require.NotNil(kc) @@ -73,8 +73,8 @@ func TestNewCubesignerKeychain(t *testing.T) { KeyType: models.SecpEthAddr, PublicKey: "0x04" + "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8", } - mockClient.EXPECT().GetKeyInOrg(keyID).Return(keyInfo, nil).Times(1) - kc, err = NewCubesignerKeychain(mockClient, []string{keyID}) + mockClient.EXPECT().GetKeyInOrg(testKeyID).Return(keyInfo, nil).Times(1) + kc, err = NewCubesignerKeychain(mockClient, []string{testKeyID}) require.NoError(err) require.NotNil(kc) } @@ -83,16 +83,15 @@ func TestCubesignerKeychain_Addresses(t *testing.T) { require := require.New(t) ctrl := gomock.NewController(t) - keyID := "test-key-id" mockClient := cubesignermock.NewCubeSignerClient(ctrl) keyInfo := &models.KeyInfo{ KeyType: models.SecpAvaAddr, PublicKey: "0x04" + "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8", } - mockClient.EXPECT().GetKeyInOrg(keyID).Return(keyInfo, nil).Times(1) + mockClient.EXPECT().GetKeyInOrg(testKeyID).Return(keyInfo, nil).Times(1) - kc, err := NewCubesignerKeychain(mockClient, []string{keyID}) + kc, err := NewCubesignerKeychain(mockClient, []string{testKeyID}) require.NoError(err) addresses := kc.Addresses() @@ -103,16 +102,15 @@ func TestCubesignerKeychain_Get(t *testing.T) { require := require.New(t) ctrl := gomock.NewController(t) - keyID := "test-key-id" mockClient := cubesignermock.NewCubeSignerClient(ctrl) keyInfo := &models.KeyInfo{ KeyType: models.SecpAvaAddr, PublicKey: "0x04" + "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8", } - mockClient.EXPECT().GetKeyInOrg(keyID).Return(keyInfo, nil).Times(1) + mockClient.EXPECT().GetKeyInOrg(testKeyID).Return(keyInfo, nil).Times(1) - kc, err := NewCubesignerKeychain(mockClient, []string{keyID}) + kc, err := NewCubesignerKeychain(mockClient, []string{testKeyID}) require.NoError(err) addresses := kc.Addresses() @@ -135,16 +133,15 @@ func TestCubesignerKeychain_EthAddresses(t *testing.T) { require := require.New(t) ctrl := gomock.NewController(t) - keyID := "test-key-id" mockClient := cubesignermock.NewCubeSignerClient(ctrl) keyInfo := &models.KeyInfo{ KeyType: models.SecpEthAddr, PublicKey: "0x04" + "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8", } - mockClient.EXPECT().GetKeyInOrg(keyID).Return(keyInfo, nil).Times(1) + mockClient.EXPECT().GetKeyInOrg(testKeyID).Return(keyInfo, nil).Times(1) - kc, err := NewCubesignerKeychain(mockClient, []string{keyID}) + kc, err := NewCubesignerKeychain(mockClient, []string{testKeyID}) require.NoError(err) ethAddresses := kc.EthAddresses() @@ -155,16 +152,15 @@ func TestCubesignerKeychain_GetEth(t *testing.T) { require := require.New(t) ctrl := gomock.NewController(t) - keyID := "test-key-id" mockClient := cubesignermock.NewCubeSignerClient(ctrl) keyInfo := &models.KeyInfo{ KeyType: models.SecpEthAddr, PublicKey: "0x04" + "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8", } - mockClient.EXPECT().GetKeyInOrg(keyID).Return(keyInfo, nil).Times(1) + mockClient.EXPECT().GetKeyInOrg(testKeyID).Return(keyInfo, nil).Times(1) - kc, err := NewCubesignerKeychain(mockClient, []string{keyID}) + kc, err := NewCubesignerKeychain(mockClient, []string{testKeyID}) require.NoError(err) ethAddresses := kc.EthAddresses() @@ -186,16 +182,15 @@ func TestCubesignerSigner_SignHash(t *testing.T) { require := require.New(t) ctrl := gomock.NewController(t) - keyID := "test-key-id" mockClient := cubesignermock.NewCubeSignerClient(ctrl) keyInfo := &models.KeyInfo{ KeyType: models.SecpAvaAddr, PublicKey: "0x04" + "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8", } - mockClient.EXPECT().GetKeyInOrg(keyID).Return(keyInfo, nil).Times(1) + mockClient.EXPECT().GetKeyInOrg(testKeyID).Return(keyInfo, nil).Times(1) - kc, err := NewCubesignerKeychain(mockClient, []string{keyID}) + kc, err := NewCubesignerKeychain(mockClient, []string{testKeyID}) require.NoError(err) addresses := kc.Addresses() @@ -211,14 +206,14 @@ func TestCubesignerSigner_SignHash(t *testing.T) { Signature: "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef01", }, } - mockClient.EXPECT().BlobSign(keyID, gomock.Any()).Return(response, nil).Times(1) + mockClient.EXPECT().BlobSign(testKeyID, gomock.Any()).Return(response, nil).Times(1) signature, err := signer.SignHash(hash) require.NoError(err) require.NotNil(signature) // test client error - mockClient.EXPECT().BlobSign(keyID, gomock.Any()).Return(nil, errTest).Times(1) + mockClient.EXPECT().BlobSign(testKeyID, gomock.Any()).Return(nil, errTest).Times(1) _, err = signer.SignHash(hash) require.ErrorIs(err, errTest) @@ -226,7 +221,7 @@ func TestCubesignerSigner_SignHash(t *testing.T) { emptyResponse := &client.CubeSignerResponse[models.SignResponse]{ ResponseData: nil, } - mockClient.EXPECT().BlobSign(keyID, gomock.Any()).Return(emptyResponse, nil).Times(1) + mockClient.EXPECT().BlobSign(testKeyID, gomock.Any()).Return(emptyResponse, nil).Times(1) _, err = signer.SignHash(hash) - require.ErrorContains(err, "empty signature obtained from server") -} \ No newline at end of file + require.ErrorIs(err, ErrEmptySignatureFromServer) +} diff --git a/utils/crypto/keychain/keychain.go b/utils/crypto/keychain/keychain.go index b614bd37b500..b36b070c20ed 100644 --- a/utils/crypto/keychain/keychain.go +++ b/utils/crypto/keychain/keychain.go @@ -5,11 +5,11 @@ package keychain import ( "github.com/ava-labs/libevm/common" + "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/set" ) - // Signer implements functions for a keychain to return its main address and // to sign a hash or transaction type Signer interface { diff --git a/utils/crypto/keychain/ledger/ledger_keychain.go b/utils/crypto/keychain/ledger/ledger_keychain.go index dbc6529ddb4d..e03ac9390091 100644 --- a/utils/crypto/keychain/ledger/ledger_keychain.go +++ b/utils/crypto/keychain/ledger/ledger_keychain.go @@ -124,7 +124,7 @@ func (l *ledgerSigner) SignHash(b []byte) ([]byte, error) { } // expects to receive the unsigned tx bytes -func (l *ledgerSigner) Sign(b []byte, opts ...keychain.SigningOption) ([]byte, error) { +func (l *ledgerSigner) Sign(b []byte, _ ...keychain.SigningOption) ([]byte, error) { // Ignore options - ledger signing doesn't need chain/network context // Sign using the address with index l.idx on the ledger device. The number // of returned signatures should be the same length as the provided indices. diff --git a/utils/crypto/keychain/signing_options.go b/utils/crypto/keychain/signing_options.go index 1b640d650f06..15086f6a9b6d 100644 --- a/utils/crypto/keychain/signing_options.go +++ b/utils/crypto/keychain/signing_options.go @@ -24,4 +24,4 @@ func WithNetworkID(networkID uint32) SigningOption { return func(opts *SigningOptions) { opts.NetworkID = networkID } -} \ No newline at end of file +} diff --git a/utils/crypto/ledger/ledger.go b/utils/crypto/ledger/ledger.go index 1cbf74a060ef..8e3014414152 100644 --- a/utils/crypto/ledger/ledger.go +++ b/utils/crypto/ledger/ledger.go @@ -20,7 +20,6 @@ const ( ledgerPathSize = 9 ) - // Ledger is a wrapper around the low-level Ledger Device interface that // provides Avalanche-specific access. type Ledger struct { diff --git a/utils/crypto/secp256k1/secp256k1.go b/utils/crypto/secp256k1/secp256k1.go index 9e49b8842729..7d9368531e76 100644 --- a/utils/crypto/secp256k1/secp256k1.go +++ b/utils/crypto/secp256k1/secp256k1.go @@ -13,10 +13,10 @@ import ( "github.com/decred/dcrd/dcrec/secp256k1/v4/ecdsa" "github.com/ava-labs/avalanchego/cache" - "github.com/ava-labs/avalanchego/utils/crypto/keychain" "github.com/ava-labs/avalanchego/cache/lru" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/cb58" + "github.com/ava-labs/avalanchego/utils/crypto/keychain" "github.com/ava-labs/avalanchego/utils/hashing" stdecdsa "crypto/ecdsa" @@ -213,7 +213,7 @@ func (k *PrivateKey) EthAddress() common.Address { return crypto.PubkeyToAddress(*(k.PublicKey().ToECDSA())) } -func (k *PrivateKey) Sign(msg []byte, opts ...keychain.SigningOption) ([]byte, error) { +func (k *PrivateKey) Sign(msg []byte, _ ...keychain.SigningOption) ([]byte, error) { // Ignore options - secp256k1 signing doesn't need chain/network context return k.SignHash(hashing.ComputeHash256(msg)) } diff --git a/vms/secp256k1fx/keychain.go b/vms/secp256k1fx/keychain.go index dac5a84aa689..3e57410bf3ee 100644 --- a/vms/secp256k1fx/keychain.go +++ b/vms/secp256k1fx/keychain.go @@ -21,7 +21,7 @@ import ( var ( errCantSpend = errors.New("unable to spend this UTXO") - _ keychain.Keychain = (*Keychain)(nil) + _ keychain.Keychain = (*Keychain)(nil) _ keychain.EthKeychain = (*Keychain)(nil) ) diff --git a/wallet/chain/p/signer/visitor.go b/wallet/chain/p/signer/visitor.go index d1366c759e1e..b919b3a22c5f 100644 --- a/wallet/chain/p/signer/visitor.go +++ b/wallet/chain/p/signer/visitor.go @@ -59,7 +59,7 @@ func (s *visitor) AddValidatorTx(tx *txs.AddValidatorTx) error { if err != nil { return err } - return sign(s.tx, false || s.forceSignHash, txSigners, s.networkID) + return sign(s.tx, s.forceSignHash, txSigners, s.networkID) } func (s *visitor) AddSubnetValidatorTx(tx *txs.AddSubnetValidatorTx) error { @@ -72,7 +72,7 @@ func (s *visitor) AddSubnetValidatorTx(tx *txs.AddSubnetValidatorTx) error { return err } txSigners = append(txSigners, subnetAuthSigners) - return sign(s.tx, false || s.forceSignHash, txSigners, s.networkID) + return sign(s.tx, s.forceSignHash, txSigners, s.networkID) } func (s *visitor) AddDelegatorTx(tx *txs.AddDelegatorTx) error { @@ -80,7 +80,7 @@ func (s *visitor) AddDelegatorTx(tx *txs.AddDelegatorTx) error { if err != nil { return err } - return sign(s.tx, false || s.forceSignHash, txSigners, s.networkID) + return sign(s.tx, s.forceSignHash, txSigners, s.networkID) } func (s *visitor) CreateChainTx(tx *txs.CreateChainTx) error { @@ -93,7 +93,7 @@ func (s *visitor) CreateChainTx(tx *txs.CreateChainTx) error { return err } txSigners = append(txSigners, subnetAuthSigners) - return sign(s.tx, false || s.forceSignHash, txSigners, s.networkID) + return sign(s.tx, s.forceSignHash, txSigners, s.networkID) } func (s *visitor) CreateSubnetTx(tx *txs.CreateSubnetTx) error { @@ -101,7 +101,7 @@ func (s *visitor) CreateSubnetTx(tx *txs.CreateSubnetTx) error { if err != nil { return err } - return sign(s.tx, false || s.forceSignHash, txSigners, s.networkID) + return sign(s.tx, s.forceSignHash, txSigners, s.networkID) } func (s *visitor) ImportTx(tx *txs.ImportTx) error { @@ -114,7 +114,7 @@ func (s *visitor) ImportTx(tx *txs.ImportTx) error { return err } txSigners = append(txSigners, txImportSigners...) - return sign(s.tx, false || s.forceSignHash, txSigners, s.networkID) + return sign(s.tx, s.forceSignHash, txSigners, s.networkID) } func (s *visitor) ExportTx(tx *txs.ExportTx) error { @@ -122,7 +122,7 @@ func (s *visitor) ExportTx(tx *txs.ExportTx) error { if err != nil { return err } - return sign(s.tx, false || s.forceSignHash, txSigners, s.networkID) + return sign(s.tx, s.forceSignHash, txSigners, s.networkID) } func (s *visitor) RemoveSubnetValidatorTx(tx *txs.RemoveSubnetValidatorTx) error { @@ -185,7 +185,7 @@ func (s *visitor) BaseTx(tx *txs.BaseTx) error { if err != nil { return err } - return sign(s.tx, false || s.forceSignHash, txSigners, s.networkID) + return sign(s.tx, s.forceSignHash, txSigners, s.networkID) } func (s *visitor) ConvertSubnetToL1Tx(tx *txs.ConvertSubnetToL1Tx) error { diff --git a/wallet/chain/p/signer/with_options.go b/wallet/chain/p/signer/with_options.go index 8bdeb42853ba..86f14ae30d12 100644 --- a/wallet/chain/p/signer/with_options.go +++ b/wallet/chain/p/signer/with_options.go @@ -4,7 +4,7 @@ package signer import ( - stdcontext "context" + "context" "github.com/ava-labs/avalanchego/vms/platformvm/txs" "github.com/ava-labs/avalanchego/wallet/subnet/primary/common" @@ -30,7 +30,6 @@ func WithOptions(signer Signer, options ...common.Option) Signer { } } -func (w *withOptions) Sign(ctx stdcontext.Context, tx *txs.Tx, options ...common.Option) error { +func (w *withOptions) Sign(ctx context.Context, tx *txs.Tx, options ...common.Option) error { return w.signer.Sign(ctx, tx, common.UnionOptions(w.options, options)...) } - diff --git a/wallet/subnet/primary/common/options.go b/wallet/subnet/primary/common/options.go index d5a1dd7e1801..287e7e6639b0 100644 --- a/wallet/subnet/primary/common/options.go +++ b/wallet/subnet/primary/common/options.go @@ -243,7 +243,6 @@ func WithConfirmationHandler(f func(ConfirmationReceipt)) Option { } } - func WithForceSignHash() Option { return func(o *Options) { o.forceSignHash = true From 3da2018b7a32ac6cf8bbc5bca74aeeaaab73c2f7 Mon Sep 17 00:00:00 2001 From: Felipe Madero Date: Mon, 15 Sep 2025 14:59:02 -0300 Subject: [PATCH 5/9] license lint --- utils/crypto/keychain/cubesigner/cubesigner_keychain.go | 1 + 1 file changed, 1 insertion(+) diff --git a/utils/crypto/keychain/cubesigner/cubesigner_keychain.go b/utils/crypto/keychain/cubesigner/cubesigner_keychain.go index 6d56cb834df8..1daa828c3f28 100644 --- a/utils/crypto/keychain/cubesigner/cubesigner_keychain.go +++ b/utils/crypto/keychain/cubesigner/cubesigner_keychain.go @@ -1,5 +1,6 @@ // Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. + package cubesigner import ( From 8c4723345be7433669bd75f813c1cd0aaa1c9ddb Mon Sep 17 00:00:00 2001 From: Felipe Madero Date: Sat, 20 Sep 2025 15:41:48 -0300 Subject: [PATCH 6/9] Address PR comments - Remove package name stuttering in CubeSigner and ledger packages - Renamed CubesignerKeychain to Keychain, ledgerKeychain to KeyChain - godoc comments to exported types and functions - Extract hardcoded constants (65, 0x04) with descriptive names - Update both packages to return concrete types (*KeyChain) for consistency --- .../keychain/cubesigner/cubesigner_client.go | 8 + .../cubesigner/cubesigner_keychain.go | 78 ++++++---- .../cubesigner/cubesigner_keychain_test.go | 139 ++++++++++++++++-- .../crypto/keychain/ledger/ledger_keychain.go | 22 +-- .../keychain/ledger/ledger_keychain_test.go | 56 +++---- 5 files changed, 220 insertions(+), 83 deletions(-) diff --git a/utils/crypto/keychain/cubesigner/cubesigner_client.go b/utils/crypto/keychain/cubesigner/cubesigner_client.go index 3c14ab4b672d..b40e9e9ea660 100644 --- a/utils/crypto/keychain/cubesigner/cubesigner_client.go +++ b/utils/crypto/keychain/cubesigner/cubesigner_client.go @@ -11,7 +11,15 @@ import ( // CubeSignerClient defines the interface for CubeSigner client operations // needed by the keychain implementation type CubeSignerClient interface { + // GetKeyInOrg retrieves key information for the given keyID from the CubeSigner organization. + // It returns the key metadata, including public key and key type. GetKeyInOrg(keyID string) (*models.KeyInfo, error) + + // BlobSign signs arbitrary data using the specified keyID. + // request contains the data to be signed. BlobSign(keyID string, request models.BlobSignRequest, receipts ...*client.MfaReceipt) (*client.CubeSignerResponse[models.SignResponse], error) + + // AvaSerializedTxSign signs Avalanche transactions using the specified chainAlias (P/X/C), and materialID (address). + // request contains the serialized transaction data to be signed. AvaSerializedTxSign(chainAlias, materialID string, request models.AvaSerializedTxSignRequest, receipts ...*client.MfaReceipt) (*client.CubeSignerResponse[models.SignResponse], error) } diff --git a/utils/crypto/keychain/cubesigner/cubesigner_keychain.go b/utils/crypto/keychain/cubesigner/cubesigner_keychain.go index 1daa828c3f28..a9b1e44e74e8 100644 --- a/utils/crypto/keychain/cubesigner/cubesigner_keychain.go +++ b/utils/crypto/keychain/cubesigner/cubesigner_keychain.go @@ -26,34 +26,46 @@ import ( ) var ( - _ keychain.Keychain = (*CubesignerKeychain)(nil) - _ keychain.EthKeychain = (*CubesignerKeychain)(nil) + _ keychain.Keychain = (*Keychain)(nil) + _ keychain.EthKeychain = (*Keychain)(nil) _ keychain.Signer = (*cubesignerSigner)(nil) _ CubeSignerClient = (*client.ApiClient)(nil) ErrNoKeysProvided = errors.New("you need to provide at least one key to create a server keychain") ErrEmptySignatureFromServer = errors.New("empty signature obtained from server") ErrChainAliasMissing = errors.New("chainAlias must be specified in options for CubeSigner") + ErrInvalidChainAlias = errors.New("chainAlias must be 'P', 'X' or 'C' for CubeSigner") ErrNetworkIDMissing = errors.New("network ID must be specified in options for CubeSigner") ErrUnsupportedKeyType = errors.New("unsupported key type") ErrInvalidPublicKey = errors.New("invalid public key format") ) -// keyInfo holds both the public key and keyID for a cubesigner key +const ( + // UncompressedPublicKeyLength is the expected length of an uncompressed secp256k1 public key in bytes. + // This includes the prefix byte (1 byte) plus the X and Y coordinates (32 bytes each). + UncompressedPublicKeyLength = 65 + // UncompressedPublicKeyPrefix is the prefix byte for uncompressed secp256k1 public keys. + // This byte indicates that the key is in uncompressed format. + UncompressedPublicKeyPrefix = 0x04 +) + +// keyInfo holds both the public key and keyID for a CubeSigner key. type keyInfo struct { - pubKey *avasecp256k1.PublicKey - keyID string + pubKey *avasecp256k1.PublicKey // The Avalanche public key derived from CubeSigner + keyID string // The CubeSigner key identifier } -// cubesignerKeychain is an abstraction of the underlying cubesigner connection, -// to be able to get a signer from a specific address -type CubesignerKeychain struct { - cubesignerClient CubeSignerClient - avaAddrToKeyInfo map[ids.ShortID]*keyInfo - ethAddrToKeyInfo map[common.Address]*keyInfo +// Keychain provides an abstraction over CubeSigner remote signing capabilities. +type Keychain struct { + cubesignerClient CubeSignerClient // Client for CubeSigner API operations + avaAddrToKeyInfo map[ids.ShortID]*keyInfo // Maps Avalanche addresses to key info + ethAddrToKeyInfo map[common.Address]*keyInfo // Maps Ethereum addresses to key info } -// processKey obtains and processes key information from cubesigner +// processKey obtains and processes key information from CubeSigner. +// It validates that the key exists in the CubeSigner organization, verifies +// that the key type is supported (secp256k1 for Avalanche/Ethereum), and +// converts the public key from hex format to an Avalanche public key. func processKey( cubesignerClient CubeSignerClient, keyID string, @@ -78,11 +90,11 @@ func processKey( if err != nil { return nil, fmt.Errorf("%w: failed to decode public key for server key %s: %w", ErrInvalidPublicKey, keyID, err) } - if len(pubKeyBytes) != 65 { - return nil, fmt.Errorf("invalid public key length for server key %s: expected 65 bytes, got %d", keyID, len(pubKeyBytes)) + if len(pubKeyBytes) != UncompressedPublicKeyLength { + return nil, fmt.Errorf("invalid public key length for server key %s: expected %d bytes, got %d", keyID, UncompressedPublicKeyLength, len(pubKeyBytes)) } - if pubKeyBytes[0] != 0x04 { - return nil, fmt.Errorf("invalid public key format for server key %s: expected uncompressed format (0x04 prefix), got 0x%02x", keyID, pubKeyBytes[0]) + if pubKeyBytes[0] != UncompressedPublicKeyPrefix { + return nil, fmt.Errorf("invalid public key format for server key %s: expected uncompressed format (0x%02x prefix), got 0x%02x", keyID, UncompressedPublicKeyPrefix, pubKeyBytes[0]) } pubKey, err := secp256k1.ParsePubKey(pubKeyBytes) if err != nil { @@ -96,11 +108,13 @@ func processKey( return avaPubKey, nil } -// NewCubeSignerKeychain creates a new keychain abstraction over a cubesigner connection -func NewCubesignerKeychain( +// NewKeychain creates a new keychain abstraction over a CubeSigner connection. +// It validates that all provided keyIDs exist in the CubeSigner organization and returns +// a keychain that can be used to sign transactions using those keys. +func NewKeychain( cubesignerClient CubeSignerClient, keyIDs []string, -) (*CubesignerKeychain, error) { +) (*Keychain, error) { if len(keyIDs) == 0 { return nil, ErrNoKeysProvided } @@ -123,18 +137,20 @@ func NewCubesignerKeychain( ethAddrToKeyInfo[avaPubKey.EthAddress()] = keyInf } - return &CubesignerKeychain{ + return &Keychain{ cubesignerClient: cubesignerClient, avaAddrToKeyInfo: avaAddrToKeyInfo, ethAddrToKeyInfo: ethAddrToKeyInfo, }, nil } -func (kc *CubesignerKeychain) Addresses() set.Set[ids.ShortID] { +// Addresses returns the set of Avalanche addresses that this keychain can sign for. +func (kc *Keychain) Addresses() set.Set[ids.ShortID] { return set.Of(maps.Keys(kc.avaAddrToKeyInfo)...) } -func (kc *CubesignerKeychain) Get(addr ids.ShortID) (keychain.Signer, bool) { +// Get returns a signer for the given Avalanche address, if it exists in this keychain. +func (kc *Keychain) Get(addr ids.ShortID) (keychain.Signer, bool) { keyInf, found := kc.avaAddrToKeyInfo[addr] if !found { return nil, false @@ -146,11 +162,13 @@ func (kc *CubesignerKeychain) Get(addr ids.ShortID) (keychain.Signer, bool) { }, true } -func (kc *CubesignerKeychain) EthAddresses() set.Set[common.Address] { +// EthAddresses returns the set of Ethereum addresses that this keychain can sign for. +func (kc *Keychain) EthAddresses() set.Set[common.Address] { return set.Of(maps.Keys(kc.ethAddrToKeyInfo)...) } -func (kc *CubesignerKeychain) GetEth(addr common.Address) (keychain.Signer, bool) { +// GetEth returns a signer for the given Ethereum address, if it exists in this keychain. +func (kc *Keychain) GetEth(addr common.Address) (keychain.Signer, bool) { keyInf, found := kc.ethAddrToKeyInfo[addr] if !found { return nil, false @@ -171,7 +189,7 @@ type cubesignerSigner struct { } // processSignatureResponse is a helper function that processes the common response -// pattern from CubeSigner signing operations +// pattern from CubeSigner signing operations. It decodes the hex signature and validates its length. func processSignatureResponse(signatureHex string) ([]byte, error) { signatureBytes, err := hex.DecodeString(strings.TrimPrefix(signatureHex, "0x")) if err != nil { @@ -183,7 +201,8 @@ func processSignatureResponse(signatureHex string) ([]byte, error) { return signatureBytes, nil } -// expects to receive a hash of the unsigned tx bytes +// SignHash signs the given hash using CubeSigner's BlobSign API. +// It expects to receive a hash of the unsigned transaction bytes. func (s *cubesignerSigner) SignHash(b []byte) ([]byte, error) { response, err := s.cubesignerClient.BlobSign( s.keyID, @@ -200,7 +219,9 @@ func (s *cubesignerSigner) SignHash(b []byte) ([]byte, error) { return processSignatureResponse(response.ResponseData.Signature) } -// expects to receive the unsigned tx bytes +// Sign signs the given payload according to the given signing options. +// It expects to receive the unsigned transaction bytes and requires ChainAlias and NetworkID +// to be specified in the signing options for CubeSigner's AvaSerializedTxSign API. func (s *cubesignerSigner) Sign(b []byte, opts ...keychain.SigningOption) ([]byte, error) { options := &keychain.SigningOptions{} for _, opt := range opts { @@ -211,7 +232,7 @@ func (s *cubesignerSigner) Sign(b []byte, opts ...keychain.SigningOption) ([]byt return nil, ErrChainAliasMissing } if options.ChainAlias != "P" && options.ChainAlias != "X" && options.ChainAlias != "C" { - return nil, fmt.Errorf("chainAlias must be 'P', 'X' or 'C' for CubeSigner, got %q", options.ChainAlias) + return nil, fmt.Errorf("%w, got %q", ErrInvalidChainAlias, options.ChainAlias) } if options.NetworkID == 0 { return nil, ErrNetworkIDMissing @@ -245,6 +266,7 @@ func (s *cubesignerSigner) Sign(b []byte, opts ...keychain.SigningOption) ([]byt return processSignatureResponse(response.ResponseData.Signature) } +// Address returns the Avalanche address associated with this signer. func (s *cubesignerSigner) Address() ids.ShortID { return s.pubKey.Address() } diff --git a/utils/crypto/keychain/cubesigner/cubesigner_keychain_test.go b/utils/crypto/keychain/cubesigner/cubesigner_keychain_test.go index be3b7393f4b5..5b21b60ccdb2 100644 --- a/utils/crypto/keychain/cubesigner/cubesigner_keychain_test.go +++ b/utils/crypto/keychain/cubesigner/cubesigner_keychain_test.go @@ -14,6 +14,7 @@ import ( "go.uber.org/mock/gomock" "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/crypto/keychain" "github.com/ava-labs/avalanchego/utils/crypto/keychain/cubesigner/cubesignermock" ) @@ -21,19 +22,19 @@ var errTest = errors.New("test") const testKeyID = "test-key-id" -func TestNewCubesignerKeychain(t *testing.T) { +func TestNewKeychain(t *testing.T) { require := require.New(t) ctrl := gomock.NewController(t) // user provides no keys mockClient := cubesignermock.NewCubeSignerClient(ctrl) - _, err := NewCubesignerKeychain(mockClient, []string{}) + _, err := NewKeychain(mockClient, []string{}) require.ErrorIs(err, ErrNoKeysProvided) // client returns error when getting key info mockClient = cubesignermock.NewCubeSignerClient(ctrl) mockClient.EXPECT().GetKeyInOrg(testKeyID).Return(nil, errTest).Times(1) - _, err = NewCubesignerKeychain(mockClient, []string{testKeyID}) + _, err = NewKeychain(mockClient, []string{testKeyID}) require.ErrorIs(err, errTest) // client returns unsupported key type @@ -43,7 +44,7 @@ func TestNewCubesignerKeychain(t *testing.T) { PublicKey: "0x04" + "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", } mockClient.EXPECT().GetKeyInOrg(testKeyID).Return(keyInfo, nil).Times(1) - _, err = NewCubesignerKeychain(mockClient, []string{testKeyID}) + _, err = NewKeychain(mockClient, []string{testKeyID}) require.ErrorIs(err, ErrUnsupportedKeyType) // client returns invalid public key format @@ -53,7 +54,7 @@ func TestNewCubesignerKeychain(t *testing.T) { PublicKey: "invalid-hex", } mockClient.EXPECT().GetKeyInOrg(testKeyID).Return(keyInfo, nil).Times(1) - _, err = NewCubesignerKeychain(mockClient, []string{testKeyID}) + _, err = NewKeychain(mockClient, []string{testKeyID}) require.ErrorIs(err, ErrInvalidPublicKey) // good path - Avalanche address @@ -63,7 +64,7 @@ func TestNewCubesignerKeychain(t *testing.T) { PublicKey: "0x04" + "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8", } mockClient.EXPECT().GetKeyInOrg(testKeyID).Return(keyInfo, nil).Times(1) - kc, err := NewCubesignerKeychain(mockClient, []string{testKeyID}) + kc, err := NewKeychain(mockClient, []string{testKeyID}) require.NoError(err) require.NotNil(kc) @@ -74,12 +75,12 @@ func TestNewCubesignerKeychain(t *testing.T) { PublicKey: "0x04" + "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8", } mockClient.EXPECT().GetKeyInOrg(testKeyID).Return(keyInfo, nil).Times(1) - kc, err = NewCubesignerKeychain(mockClient, []string{testKeyID}) + kc, err = NewKeychain(mockClient, []string{testKeyID}) require.NoError(err) require.NotNil(kc) } -func TestCubesignerKeychain_Addresses(t *testing.T) { +func TestKeychain_Addresses(t *testing.T) { require := require.New(t) ctrl := gomock.NewController(t) @@ -91,14 +92,14 @@ func TestCubesignerKeychain_Addresses(t *testing.T) { } mockClient.EXPECT().GetKeyInOrg(testKeyID).Return(keyInfo, nil).Times(1) - kc, err := NewCubesignerKeychain(mockClient, []string{testKeyID}) + kc, err := NewKeychain(mockClient, []string{testKeyID}) require.NoError(err) addresses := kc.Addresses() require.Len(addresses, 1) } -func TestCubesignerKeychain_Get(t *testing.T) { +func TestKeychain_Get(t *testing.T) { require := require.New(t) ctrl := gomock.NewController(t) @@ -110,7 +111,7 @@ func TestCubesignerKeychain_Get(t *testing.T) { } mockClient.EXPECT().GetKeyInOrg(testKeyID).Return(keyInfo, nil).Times(1) - kc, err := NewCubesignerKeychain(mockClient, []string{testKeyID}) + kc, err := NewKeychain(mockClient, []string{testKeyID}) require.NoError(err) addresses := kc.Addresses() @@ -129,7 +130,7 @@ func TestCubesignerKeychain_Get(t *testing.T) { require.Nil(signer) } -func TestCubesignerKeychain_EthAddresses(t *testing.T) { +func TestKeychain_EthAddresses(t *testing.T) { require := require.New(t) ctrl := gomock.NewController(t) @@ -141,14 +142,14 @@ func TestCubesignerKeychain_EthAddresses(t *testing.T) { } mockClient.EXPECT().GetKeyInOrg(testKeyID).Return(keyInfo, nil).Times(1) - kc, err := NewCubesignerKeychain(mockClient, []string{testKeyID}) + kc, err := NewKeychain(mockClient, []string{testKeyID}) require.NoError(err) ethAddresses := kc.EthAddresses() require.Len(ethAddresses, 1) } -func TestCubesignerKeychain_GetEth(t *testing.T) { +func TestKeychain_GetEth(t *testing.T) { require := require.New(t) ctrl := gomock.NewController(t) @@ -160,7 +161,7 @@ func TestCubesignerKeychain_GetEth(t *testing.T) { } mockClient.EXPECT().GetKeyInOrg(testKeyID).Return(keyInfo, nil).Times(1) - kc, err := NewCubesignerKeychain(mockClient, []string{testKeyID}) + kc, err := NewKeychain(mockClient, []string{testKeyID}) require.NoError(err) ethAddresses := kc.EthAddresses() @@ -190,7 +191,7 @@ func TestCubesignerSigner_SignHash(t *testing.T) { } mockClient.EXPECT().GetKeyInOrg(testKeyID).Return(keyInfo, nil).Times(1) - kc, err := NewCubesignerKeychain(mockClient, []string{testKeyID}) + kc, err := NewKeychain(mockClient, []string{testKeyID}) require.NoError(err) addresses := kc.Addresses() @@ -225,3 +226,109 @@ func TestCubesignerSigner_SignHash(t *testing.T) { _, err = signer.SignHash(hash) require.ErrorIs(err, ErrEmptySignatureFromServer) } + +func TestCubesignerSigner_Sign(t *testing.T) { + require := require.New(t) + ctrl := gomock.NewController(t) + + mockClient := cubesignermock.NewCubeSignerClient(ctrl) + + keyInfo := &models.KeyInfo{ + KeyType: models.SecpAvaAddr, + PublicKey: "0x04" + "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8", + } + mockClient.EXPECT().GetKeyInOrg(testKeyID).Return(keyInfo, nil).Times(1) + + kc, err := NewKeychain(mockClient, []string{testKeyID}) + require.NoError(err) + + addresses := kc.Addresses() + addr := addresses.List()[0] + signer, found := kc.Get(addr) + require.True(found) + + txBytes := []byte("test-transaction-bytes") + + // Test missing chain alias + _, err = signer.Sign(txBytes) + require.ErrorIs(err, ErrChainAliasMissing) + + // Test invalid chain alias + _, err = signer.Sign(txBytes, keychain.WithChainAlias("invalid")) + require.ErrorIs(err, ErrInvalidChainAlias) + + // Test missing network ID + _, err = signer.Sign(txBytes, keychain.WithChainAlias("P")) + require.ErrorIs(err, ErrNetworkIDMissing) + + // Test successful P-chain signing + response := &client.CubeSignerResponse[models.SignResponse]{ + ResponseData: &models.SignResponse{ + Signature: "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef01", + }, + } + mockClient.EXPECT().AvaSerializedTxSign( + "P", + gomock.Any(), // materialID (Bech32 address) + gomock.Any(), // request with hex-encoded tx + ).Return(response, nil).Times(1) + + signature, err := signer.Sign(txBytes, + keychain.WithChainAlias("P"), + keychain.WithNetworkID(1)) + require.NoError(err) + require.NotNil(signature) + + // Test successful X-chain signing + mockClient.EXPECT().AvaSerializedTxSign( + "X", + gomock.Any(), // materialID (Bech32 address) + gomock.Any(), // request with hex-encoded tx + ).Return(response, nil).Times(1) + + signature, err = signer.Sign(txBytes, + keychain.WithChainAlias("X"), + keychain.WithNetworkID(1)) + require.NoError(err) + require.NotNil(signature) + + // Test successful C-chain signing + mockClient.EXPECT().AvaSerializedTxSign( + "C", + gomock.Any(), // materialID (Eth address) + gomock.Any(), // request with hex-encoded tx + ).Return(response, nil).Times(1) + + signature, err = signer.Sign(txBytes, + keychain.WithChainAlias("C"), + keychain.WithNetworkID(1)) + require.NoError(err) + require.NotNil(signature) + + // Test client error + mockClient.EXPECT().AvaSerializedTxSign( + gomock.Any(), + gomock.Any(), + gomock.Any(), + ).Return(nil, errTest).Times(1) + + _, err = signer.Sign(txBytes, + keychain.WithChainAlias("P"), + keychain.WithNetworkID(1)) + require.ErrorIs(err, errTest) + + // Test empty response + emptyResponse := &client.CubeSignerResponse[models.SignResponse]{ + ResponseData: nil, + } + mockClient.EXPECT().AvaSerializedTxSign( + gomock.Any(), + gomock.Any(), + gomock.Any(), + ).Return(emptyResponse, nil).Times(1) + + _, err = signer.Sign(txBytes, + keychain.WithChainAlias("P"), + keychain.WithNetworkID(1)) + require.ErrorIs(err, ErrEmptySignatureFromServer) +} diff --git a/utils/crypto/keychain/ledger/ledger_keychain.go b/utils/crypto/keychain/ledger/ledger_keychain.go index e03ac9390091..cba99a67edb2 100644 --- a/utils/crypto/keychain/ledger/ledger_keychain.go +++ b/utils/crypto/keychain/ledger/ledger_keychain.go @@ -13,7 +13,7 @@ import ( ) var ( - _ keychain.Keychain = (*ledgerKeychain)(nil) + _ keychain.Keychain = (*KeyChain)(nil) _ keychain.Signer = (*ledgerSigner)(nil) ErrInvalidIndicesLength = errors.New("number of indices should be greater than 0") @@ -22,16 +22,16 @@ var ( ErrInvalidNumSignatures = errors.New("incorrect number of signatures") ) -// ledgerKeychain is an abstraction of the underlying ledger hardware device, +// KeyChain is an abstraction of the underlying ledger hardware device, // to be able to get a signer from a finite set of derived signers -type ledgerKeychain struct { +type KeyChain struct { ledger Ledger addrs set.Set[ids.ShortID] addrToIdx map[ids.ShortID]uint32 } -// NewLedgerKeychain creates a new Ledger with [numToDerive] addresses. -func NewLedgerKeychain(l Ledger, numToDerive int) (keychain.Keychain, error) { +// NewKeychain creates a new Ledger with [numToDerive] addresses. +func NewKeychain(l Ledger, numToDerive int) (*KeyChain, error) { if numToDerive < 1 { return nil, ErrInvalidNumAddrsToDerive } @@ -41,11 +41,11 @@ func NewLedgerKeychain(l Ledger, numToDerive int) (keychain.Keychain, error) { indices[i] = uint32(i) } - return NewLedgerKeychainFromIndices(l, indices) + return NewKeychainFromIndices(l, indices) } -// NewLedgerKeychainFromIndices creates a new Ledger with addresses taken from the given [indices]. -func NewLedgerKeychainFromIndices(l Ledger, indices []uint32) (keychain.Keychain, error) { +// NewKeychainFromIndices creates a new Ledger with addresses taken from the given [indices]. +func NewKeychainFromIndices(l Ledger, indices []uint32) (*KeyChain, error) { if len(indices) == 0 { return nil, ErrInvalidIndicesLength } @@ -71,18 +71,18 @@ func NewLedgerKeychainFromIndices(l Ledger, indices []uint32) (keychain.Keychain addrToIdx[addrs[i]] = indices[i] } - return &ledgerKeychain{ + return &KeyChain{ ledger: l, addrs: addrsSet, addrToIdx: addrToIdx, }, nil } -func (l *ledgerKeychain) Addresses() set.Set[ids.ShortID] { +func (l *KeyChain) Addresses() set.Set[ids.ShortID] { return l.addrs } -func (l *ledgerKeychain) Get(addr ids.ShortID) (keychain.Signer, bool) { +func (l *KeyChain) Get(addr ids.ShortID) (keychain.Signer, bool) { idx, ok := l.addrToIdx[addr] if !ok { return nil, false diff --git a/utils/crypto/keychain/ledger/ledger_keychain_test.go b/utils/crypto/keychain/ledger/ledger_keychain_test.go index cc21be9f8fba..58d5ed7d9333 100644 --- a/utils/crypto/keychain/ledger/ledger_keychain_test.go +++ b/utils/crypto/keychain/ledger/ledger_keychain_test.go @@ -16,7 +16,7 @@ import ( var errTest = errors.New("test") -func TestNewLedgerKeychain(t *testing.T) { +func TestNewKeychain(t *testing.T) { require := require.New(t) ctrl := gomock.NewController(t) @@ -24,25 +24,25 @@ func TestNewLedgerKeychain(t *testing.T) { // user request invalid number of addresses to derive ledger := ledgermock.NewLedger(ctrl) - _, err := NewLedgerKeychain(ledger, 0) + _, err := NewKeychain(ledger, 0) require.ErrorIs(err, ErrInvalidNumAddrsToDerive) // ledger does not return expected number of derived addresses ledger = ledgermock.NewLedger(ctrl) ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{}, nil).Times(1) - _, err = NewLedgerKeychain(ledger, 1) + _, err = NewKeychain(ledger, 1) require.ErrorIs(err, ErrInvalidNumAddrsDerived) // ledger return error when asked for derived addresses ledger = ledgermock.NewLedger(ctrl) ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{addr}, errTest).Times(1) - _, err = NewLedgerKeychain(ledger, 1) + _, err = NewKeychain(ledger, 1) require.ErrorIs(err, errTest) // good path ledger = ledgermock.NewLedger(ctrl) ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{addr}, nil).Times(1) - _, err = NewLedgerKeychain(ledger, 1) + _, err = NewKeychain(ledger, 1) require.NoError(err) } @@ -57,7 +57,7 @@ func TestLedgerKeychain_Addresses(t *testing.T) { // 1 addr ledger := ledgermock.NewLedger(ctrl) ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{addr1}, nil).Times(1) - kc, err := NewLedgerKeychain(ledger, 1) + kc, err := NewKeychain(ledger, 1) require.NoError(err) addrs := kc.Addresses() @@ -67,7 +67,7 @@ func TestLedgerKeychain_Addresses(t *testing.T) { // multiple addresses ledger = ledgermock.NewLedger(ctrl) ledger.EXPECT().Addresses([]uint32{0, 1, 2}).Return([]ids.ShortID{addr1, addr2, addr3}, nil).Times(1) - kc, err = NewLedgerKeychain(ledger, 3) + kc, err = NewKeychain(ledger, 3) require.NoError(err) addrs = kc.Addresses() @@ -88,7 +88,7 @@ func TestLedgerKeychain_Get(t *testing.T) { // 1 addr ledger := ledgermock.NewLedger(ctrl) ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{addr1}, nil).Times(1) - kc, err := NewLedgerKeychain(ledger, 1) + kc, err := NewKeychain(ledger, 1) require.NoError(err) _, b := kc.Get(ids.GenerateTestShortID()) @@ -101,7 +101,7 @@ func TestLedgerKeychain_Get(t *testing.T) { // multiple addresses ledger = ledgermock.NewLedger(ctrl) ledger.EXPECT().Addresses([]uint32{0, 1, 2}).Return([]ids.ShortID{addr1, addr2, addr3}, nil).Times(1) - kc, err = NewLedgerKeychain(ledger, 3) + kc, err = NewKeychain(ledger, 3) require.NoError(err) _, b = kc.Get(ids.GenerateTestShortID()) @@ -136,7 +136,7 @@ func TestLedgerSigner_SignHash(t *testing.T) { ledger := ledgermock.NewLedger(ctrl) ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{addr1}, nil).Times(1) ledger.EXPECT().SignHash(toSign, []uint32{0}).Return([][]byte{}, nil).Times(1) - kc, err := NewLedgerKeychain(ledger, 1) + kc, err := NewKeychain(ledger, 1) require.NoError(err) s, b := kc.Get(addr1) @@ -149,7 +149,7 @@ func TestLedgerSigner_SignHash(t *testing.T) { ledger = ledgermock.NewLedger(ctrl) ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{addr1}, nil).Times(1) ledger.EXPECT().SignHash(toSign, []uint32{0}).Return([][]byte{expectedSignature1}, errTest).Times(1) - kc, err = NewLedgerKeychain(ledger, 1) + kc, err = NewKeychain(ledger, 1) require.NoError(err) s, b = kc.Get(addr1) @@ -162,7 +162,7 @@ func TestLedgerSigner_SignHash(t *testing.T) { ledger = ledgermock.NewLedger(ctrl) ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{addr1}, nil).Times(1) ledger.EXPECT().SignHash(toSign, []uint32{0}).Return([][]byte{expectedSignature1}, nil).Times(1) - kc, err = NewLedgerKeychain(ledger, 1) + kc, err = NewKeychain(ledger, 1) require.NoError(err) s, b = kc.Get(addr1) @@ -178,7 +178,7 @@ func TestLedgerSigner_SignHash(t *testing.T) { ledger.EXPECT().SignHash(toSign, []uint32{0}).Return([][]byte{expectedSignature1}, nil).Times(1) ledger.EXPECT().SignHash(toSign, []uint32{1}).Return([][]byte{expectedSignature2}, nil).Times(1) ledger.EXPECT().SignHash(toSign, []uint32{2}).Return([][]byte{expectedSignature3}, nil).Times(1) - kc, err = NewLedgerKeychain(ledger, 3) + kc, err = NewKeychain(ledger, 3) require.NoError(err) s, b = kc.Get(addr1) @@ -203,7 +203,7 @@ func TestLedgerSigner_SignHash(t *testing.T) { require.Equal(expectedSignature3, signature) } -func TestNewLedgerKeychainFromIndices(t *testing.T) { +func TestNewKeychainFromIndices(t *testing.T) { require := require.New(t) ctrl := gomock.NewController(t) @@ -212,25 +212,25 @@ func TestNewLedgerKeychainFromIndices(t *testing.T) { // user request invalid number of indices ledger := ledgermock.NewLedger(ctrl) - _, err := NewLedgerKeychainFromIndices(ledger, []uint32{}) + _, err := NewKeychainFromIndices(ledger, []uint32{}) require.ErrorIs(err, ErrInvalidIndicesLength) // ledger does not return expected number of derived addresses ledger = ledgermock.NewLedger(ctrl) ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{}, nil).Times(1) - _, err = NewLedgerKeychainFromIndices(ledger, []uint32{0}) + _, err = NewKeychainFromIndices(ledger, []uint32{0}) require.ErrorIs(err, ErrInvalidNumAddrsDerived) // ledger return error when asked for derived addresses ledger = ledgermock.NewLedger(ctrl) ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{addr}, errTest).Times(1) - _, err = NewLedgerKeychainFromIndices(ledger, []uint32{0}) + _, err = NewKeychainFromIndices(ledger, []uint32{0}) require.ErrorIs(err, errTest) // good path ledger = ledgermock.NewLedger(ctrl) ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{addr}, nil).Times(1) - _, err = NewLedgerKeychainFromIndices(ledger, []uint32{0}) + _, err = NewKeychainFromIndices(ledger, []uint32{0}) require.NoError(err) } @@ -245,7 +245,7 @@ func TestLedgerKeychainFromIndices_Addresses(t *testing.T) { // 1 addr ledger := ledgermock.NewLedger(ctrl) ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{addr1}, nil).Times(1) - kc, err := NewLedgerKeychainFromIndices(ledger, []uint32{0}) + kc, err := NewKeychainFromIndices(ledger, []uint32{0}) require.NoError(err) addrs := kc.Addresses() @@ -255,7 +255,7 @@ func TestLedgerKeychainFromIndices_Addresses(t *testing.T) { // first 3 addresses ledger = ledgermock.NewLedger(ctrl) ledger.EXPECT().Addresses([]uint32{0, 1, 2}).Return([]ids.ShortID{addr1, addr2, addr3}, nil).Times(1) - kc, err = NewLedgerKeychainFromIndices(ledger, []uint32{0, 1, 2}) + kc, err = NewKeychainFromIndices(ledger, []uint32{0, 1, 2}) require.NoError(err) addrs = kc.Addresses() @@ -269,7 +269,7 @@ func TestLedgerKeychainFromIndices_Addresses(t *testing.T) { addresses := []ids.ShortID{addr1, addr2, addr3} ledger = ledgermock.NewLedger(ctrl) ledger.EXPECT().Addresses(indices).Return(addresses, nil).Times(1) - kc, err = NewLedgerKeychainFromIndices(ledger, indices) + kc, err = NewKeychainFromIndices(ledger, indices) require.NoError(err) addrs = kc.Addresses() @@ -283,7 +283,7 @@ func TestLedgerKeychainFromIndices_Addresses(t *testing.T) { addresses = []ids.ShortID{addr1, addr2, addr3, addr1, addr2, addr3} ledger = ledgermock.NewLedger(ctrl) ledger.EXPECT().Addresses(indices).Return(addresses, nil).Times(1) - kc, err = NewLedgerKeychainFromIndices(ledger, indices) + kc, err = NewKeychainFromIndices(ledger, indices) require.NoError(err) addrs = kc.Addresses() @@ -304,7 +304,7 @@ func TestLedgerKeychainFromIndices_Get(t *testing.T) { // 1 addr ledger := ledgermock.NewLedger(ctrl) ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{addr1}, nil).Times(1) - kc, err := NewLedgerKeychainFromIndices(ledger, []uint32{0}) + kc, err := NewKeychainFromIndices(ledger, []uint32{0}) require.NoError(err) _, b := kc.Get(ids.GenerateTestShortID()) @@ -319,7 +319,7 @@ func TestLedgerKeychainFromIndices_Get(t *testing.T) { addresses := []ids.ShortID{addr1, addr2, addr3} ledger = ledgermock.NewLedger(ctrl) ledger.EXPECT().Addresses(indices).Return(addresses, nil).Times(1) - kc, err = NewLedgerKeychainFromIndices(ledger, indices) + kc, err = NewKeychainFromIndices(ledger, indices) require.NoError(err) _, b = kc.Get(ids.GenerateTestShortID()) @@ -354,7 +354,7 @@ func TestLedgerSignerFromIndices_SignHash(t *testing.T) { ledger := ledgermock.NewLedger(ctrl) ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{addr1}, nil).Times(1) ledger.EXPECT().SignHash(toSign, []uint32{0}).Return([][]byte{}, nil).Times(1) - kc, err := NewLedgerKeychainFromIndices(ledger, []uint32{0}) + kc, err := NewKeychainFromIndices(ledger, []uint32{0}) require.NoError(err) s, b := kc.Get(addr1) @@ -367,7 +367,7 @@ func TestLedgerSignerFromIndices_SignHash(t *testing.T) { ledger = ledgermock.NewLedger(ctrl) ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{addr1}, nil).Times(1) ledger.EXPECT().SignHash(toSign, []uint32{0}).Return([][]byte{expectedSignature1}, errTest).Times(1) - kc, err = NewLedgerKeychainFromIndices(ledger, []uint32{0}) + kc, err = NewKeychainFromIndices(ledger, []uint32{0}) require.NoError(err) s, b = kc.Get(addr1) @@ -380,7 +380,7 @@ func TestLedgerSignerFromIndices_SignHash(t *testing.T) { ledger = ledgermock.NewLedger(ctrl) ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{addr1}, nil).Times(1) ledger.EXPECT().SignHash(toSign, []uint32{0}).Return([][]byte{expectedSignature1}, nil).Times(1) - kc, err = NewLedgerKeychainFromIndices(ledger, []uint32{0}) + kc, err = NewKeychainFromIndices(ledger, []uint32{0}) require.NoError(err) s, b = kc.Get(addr1) @@ -398,7 +398,7 @@ func TestLedgerSignerFromIndices_SignHash(t *testing.T) { ledger.EXPECT().SignHash(toSign, []uint32{indices[0]}).Return([][]byte{expectedSignature1}, nil).Times(1) ledger.EXPECT().SignHash(toSign, []uint32{indices[1]}).Return([][]byte{expectedSignature2}, nil).Times(1) ledger.EXPECT().SignHash(toSign, []uint32{indices[2]}).Return([][]byte{expectedSignature3}, nil).Times(1) - kc, err = NewLedgerKeychainFromIndices(ledger, indices) + kc, err = NewKeychainFromIndices(ledger, indices) require.NoError(err) s, b = kc.Get(addr1) From 158faf1d20e63f20e798a3792391b223b0b62ff2 Mon Sep 17 00:00:00 2001 From: Felipe Madero Date: Sat, 20 Sep 2025 15:52:44 -0300 Subject: [PATCH 7/9] Add godoc comments and unit tests for ledger Sign method - Add godoc comments to all public methods in ledger keychain - Add unit tests for ledger Sign() --- .../crypto/keychain/ledger/ledger_keychain.go | 9 +- .../keychain/ledger/ledger_keychain_test.go | 83 +++++++++++++++++++ 2 files changed, 90 insertions(+), 2 deletions(-) diff --git a/utils/crypto/keychain/ledger/ledger_keychain.go b/utils/crypto/keychain/ledger/ledger_keychain.go index cba99a67edb2..11f91f3007b7 100644 --- a/utils/crypto/keychain/ledger/ledger_keychain.go +++ b/utils/crypto/keychain/ledger/ledger_keychain.go @@ -78,10 +78,12 @@ func NewKeychainFromIndices(l Ledger, indices []uint32) (*KeyChain, error) { }, nil } +// Addresses returns the set of addresses that this keychain can sign for. func (l *KeyChain) Addresses() set.Set[ids.ShortID] { return l.addrs } +// Get returns a signer for the given address, if it exists in this keychain. func (l *KeyChain) Get(addr ids.ShortID) (keychain.Signer, bool) { idx, ok := l.addrToIdx[addr] if !ok { @@ -103,7 +105,8 @@ type ledgerSigner struct { addr ids.ShortID } -// expects to receive a hash of the unsigned tx bytes +// SignHash signs the provided hash using the Ledger device. +// It expects to receive a hash of the unsigned transaction bytes. func (l *ledgerSigner) SignHash(b []byte) ([]byte, error) { // Sign using the address with index l.idx on the ledger device. The number // of returned signatures should be the same length as the provided indices. @@ -123,7 +126,8 @@ func (l *ledgerSigner) SignHash(b []byte) ([]byte, error) { return sigs[0], nil } -// expects to receive the unsigned tx bytes +// Sign signs the provided unsigned transaction bytes using the Ledger device. +// It expects to receive the unsigned transaction bytes and returns the signature. func (l *ledgerSigner) Sign(b []byte, _ ...keychain.SigningOption) ([]byte, error) { // Ignore options - ledger signing doesn't need chain/network context // Sign using the address with index l.idx on the ledger device. The number @@ -144,6 +148,7 @@ func (l *ledgerSigner) Sign(b []byte, _ ...keychain.SigningOption) ([]byte, erro return sigs[0], nil } +// Address returns the address associated with this signer. func (l *ledgerSigner) Address() ids.ShortID { return l.addr } diff --git a/utils/crypto/keychain/ledger/ledger_keychain_test.go b/utils/crypto/keychain/ledger/ledger_keychain_test.go index 58d5ed7d9333..68762724a32d 100644 --- a/utils/crypto/keychain/ledger/ledger_keychain_test.go +++ b/utils/crypto/keychain/ledger/ledger_keychain_test.go @@ -422,3 +422,86 @@ func TestLedgerSignerFromIndices_SignHash(t *testing.T) { require.NoError(err) require.Equal(expectedSignature3, signature) } + +func TestLedgerSigner_Sign(t *testing.T) { + require := require.New(t) + ctrl := gomock.NewController(t) + + addr1 := ids.GenerateTestShortID() + addr2 := ids.GenerateTestShortID() + addr3 := ids.GenerateTestShortID() + toSign := []byte{1, 2, 3, 4, 5} + expectedSignature1 := []byte{1, 1, 1} + expectedSignature2 := []byte{2, 2, 2} + expectedSignature3 := []byte{3, 3, 3} + + // ledger returns an incorrect number of signatures + ledger := ledgermock.NewLedger(ctrl) + ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{addr1}, nil).Times(1) + ledger.EXPECT().Sign(toSign, []uint32{0}).Return([][]byte{}, nil).Times(1) + kc, err := NewKeychain(ledger, 1) + require.NoError(err) + + s, b := kc.Get(addr1) + require.True(b) + + _, err = s.Sign(toSign) + require.ErrorIs(err, ErrInvalidNumSignatures) + + // ledger returns an error when asked for signature + ledger = ledgermock.NewLedger(ctrl) + ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{addr1}, nil).Times(1) + ledger.EXPECT().Sign(toSign, []uint32{0}).Return([][]byte{expectedSignature1}, errTest).Times(1) + kc, err = NewKeychain(ledger, 1) + require.NoError(err) + + s, b = kc.Get(addr1) + require.True(b) + + _, err = s.Sign(toSign) + require.ErrorIs(err, errTest) + + // good path 1 addr + ledger = ledgermock.NewLedger(ctrl) + ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{addr1}, nil).Times(1) + ledger.EXPECT().Sign(toSign, []uint32{0}).Return([][]byte{expectedSignature1}, nil).Times(1) + kc, err = NewKeychain(ledger, 1) + require.NoError(err) + + s, b = kc.Get(addr1) + require.True(b) + + signature, err := s.Sign(toSign) + require.NoError(err) + require.Equal(expectedSignature1, signature) + + // good path 3 addr + ledger = ledgermock.NewLedger(ctrl) + ledger.EXPECT().Addresses([]uint32{0, 1, 2}).Return([]ids.ShortID{addr1, addr2, addr3}, nil).Times(1) + ledger.EXPECT().Sign(toSign, []uint32{0}).Return([][]byte{expectedSignature1}, nil).Times(1) + ledger.EXPECT().Sign(toSign, []uint32{1}).Return([][]byte{expectedSignature2}, nil).Times(1) + ledger.EXPECT().Sign(toSign, []uint32{2}).Return([][]byte{expectedSignature3}, nil).Times(1) + kc, err = NewKeychain(ledger, 3) + require.NoError(err) + + s, b = kc.Get(addr1) + require.True(b) + + signature, err = s.Sign(toSign) + require.NoError(err) + require.Equal(expectedSignature1, signature) + + s, b = kc.Get(addr2) + require.True(b) + + signature, err = s.Sign(toSign) + require.NoError(err) + require.Equal(expectedSignature2, signature) + + s, b = kc.Get(addr3) + require.True(b) + + signature, err = s.Sign(toSign) + require.NoError(err) + require.Equal(expectedSignature3, signature) +} From 2eb5ee363a959c825c97289e7ff323d2be155461 Mon Sep 17 00:00:00 2001 From: Felipe Madero Date: Thu, 25 Sep 2025 09:37:33 -0300 Subject: [PATCH 8/9] Fix P chain wallet signer integration - Update P chain wallet to properly use walletsigner.WithOptions --- wallet/chain/p/wallet/with_options.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/wallet/chain/p/wallet/with_options.go b/wallet/chain/p/wallet/with_options.go index f1a80e42de1f..960dee0c6870 100644 --- a/wallet/chain/p/wallet/with_options.go +++ b/wallet/chain/p/wallet/with_options.go @@ -43,7 +43,10 @@ func (w *withOptions) Builder() builder.Builder { } func (w *withOptions) Signer() walletsigner.Signer { - return w.wallet.Signer() + return walletsigner.WithOptions( + w.wallet.Signer(), + w.options..., + ) } func (w *withOptions) IssueBaseTx( From 0bb65bc3228c7a52c30aca6f712e1f99fb93172a Mon Sep 17 00:00:00 2001 From: Felipe Madero Date: Thu, 25 Sep 2025 12:51:39 -0300 Subject: [PATCH 9/9] Remove force sign hash wallet option to reduce PR scope - Remove forceSignHash functionality from wallet signers - Keep networkID support essential for CubeSigner keychain - Restore original ledger keychain implementation location - Use master's hardcoded signHash values instead of options - Remove P-chain signer with_options wrapper - Maintain EthKeychain interface in wallet/chain/c package - All tests and linting pass with reduced scope --- .../cubesigner/cubesigner_keychain.go | 9 +- utils/crypto/keychain/keychain.go | 144 ++++++++++++- ...dger_keychain_test.go => keychain_test.go} | 195 +++++------------- .../ledgermock => keychainmock}/ledger.go | 8 +- utils/crypto/keychain/{ledger => }/ledger.go | 6 +- .../crypto/keychain/ledger/ledger_keychain.go | 154 -------------- .../{ledger => }/mocks_generate_test.go | 2 +- utils/crypto/ledger/ledger.go | 5 +- vms/secp256k1fx/keychain.go | 3 +- wallet/chain/c/signer.go | 14 +- wallet/chain/p/signer/signer.go | 21 +- wallet/chain/p/signer/visitor.go | 27 ++- wallet/chain/p/signer/with_options.go | 35 ---- wallet/chain/p/wallet/wallet.go | 2 +- wallet/chain/p/wallet/with_options.go | 5 +- wallet/chain/x/signer/signer.go | 21 +- wallet/chain/x/signer/visitor.go | 36 ++-- wallet/chain/x/wallet.go | 2 +- wallet/subnet/primary/common/options.go | 12 -- wallet/subnet/primary/wallet.go | 2 +- 20 files changed, 266 insertions(+), 437 deletions(-) rename utils/crypto/keychain/{ledger/ledger_keychain_test.go => keychain_test.go} (68%) rename utils/crypto/keychain/{ledger/ledgermock => keychainmock}/ledger.go (95%) rename utils/crypto/keychain/{ledger => }/ledger.go (77%) delete mode 100644 utils/crypto/keychain/ledger/ledger_keychain.go rename utils/crypto/keychain/{ledger => }/mocks_generate_test.go (93%) delete mode 100644 wallet/chain/p/signer/with_options.go diff --git a/utils/crypto/keychain/cubesigner/cubesigner_keychain.go b/utils/crypto/keychain/cubesigner/cubesigner_keychain.go index a9b1e44e74e8..9bb08de3f8f9 100644 --- a/utils/crypto/keychain/cubesigner/cubesigner_keychain.go +++ b/utils/crypto/keychain/cubesigner/cubesigner_keychain.go @@ -20,16 +20,17 @@ import ( "github.com/ava-labs/avalanchego/utils/crypto/keychain" "github.com/ava-labs/avalanchego/utils/formatting/address" "github.com/ava-labs/avalanchego/utils/set" + "github.com/ava-labs/avalanchego/wallet/chain/c" avasecp256k1 "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" secp256k1 "github.com/decred/dcrd/dcrec/secp256k1/v4" ) var ( - _ keychain.Keychain = (*Keychain)(nil) - _ keychain.EthKeychain = (*Keychain)(nil) - _ keychain.Signer = (*cubesignerSigner)(nil) - _ CubeSignerClient = (*client.ApiClient)(nil) + _ keychain.Keychain = (*Keychain)(nil) + _ c.EthKeychain = (*Keychain)(nil) + _ keychain.Signer = (*cubesignerSigner)(nil) + _ CubeSignerClient = (*client.ApiClient)(nil) ErrNoKeysProvided = errors.New("you need to provide at least one key to create a server keychain") ErrEmptySignatureFromServer = errors.New("empty signature obtained from server") diff --git a/utils/crypto/keychain/keychain.go b/utils/crypto/keychain/keychain.go index b36b070c20ed..5223b33ef881 100644 --- a/utils/crypto/keychain/keychain.go +++ b/utils/crypto/keychain/keychain.go @@ -4,12 +4,23 @@ package keychain import ( - "github.com/ava-labs/libevm/common" + "errors" + "fmt" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/set" ) +var ( + _ Keychain = (*ledgerKeychain)(nil) + _ Signer = (*ledgerSigner)(nil) + + ErrInvalidIndicesLength = errors.New("number of indices should be greater than 0") + ErrInvalidNumAddrsToDerive = errors.New("number of addresses to derive should be greater than 0") + ErrInvalidNumAddrsDerived = errors.New("incorrect number of ledger derived addresses") + ErrInvalidNumSignatures = errors.New("incorrect number of signatures") +) + // Signer implements functions for a keychain to return its main address and // to sign a hash or transaction type Signer interface { @@ -28,12 +39,127 @@ type Keychain interface { Addresses() set.Set[ids.ShortID] } -// EthKeychain maintains a set of addresses together with their corresponding -// signers, -type EthKeychain interface { - // The returned Signer can provide a signature for [addr] - GetEth(addr common.Address) (Signer, bool) - // Returns the set of addresses for which the accessor keeps an associated - // signer - EthAddresses() set.Set[common.Address] +// ledgerKeychain is an abstraction of the underlying ledger hardware device, +// to be able to get a signer from a finite set of derived signers +type ledgerKeychain struct { + ledger Ledger + addrs set.Set[ids.ShortID] + addrToIdx map[ids.ShortID]uint32 +} + +// ledgerSigner is an abstraction of the underlying ledger hardware device, +// to be able sign for a specific address +type ledgerSigner struct { + ledger Ledger + idx uint32 + addr ids.ShortID +} + +// NewLedgerKeychain creates a new Ledger with [numToDerive] addresses. +func NewLedgerKeychain(l Ledger, numToDerive int) (Keychain, error) { + if numToDerive < 1 { + return nil, ErrInvalidNumAddrsToDerive + } + + indices := make([]uint32, numToDerive) + for i := range indices { + indices[i] = uint32(i) + } + + return NewLedgerKeychainFromIndices(l, indices) +} + +// NewLedgerKeychainFromIndices creates a new Ledger with addresses taken from the given [indices]. +func NewLedgerKeychainFromIndices(l Ledger, indices []uint32) (Keychain, error) { + if len(indices) == 0 { + return nil, ErrInvalidIndicesLength + } + + addrs, err := l.Addresses(indices) + if err != nil { + return nil, err + } + + if len(addrs) != len(indices) { + return nil, fmt.Errorf( + "%w. expected %d, got %d", + ErrInvalidNumAddrsDerived, + len(indices), + len(addrs), + ) + } + + addrsSet := set.Of(addrs...) + + addrToIdx := map[ids.ShortID]uint32{} + for i := range indices { + addrToIdx[addrs[i]] = indices[i] + } + + return &ledgerKeychain{ + ledger: l, + addrs: addrsSet, + addrToIdx: addrToIdx, + }, nil +} + +func (l *ledgerKeychain) Addresses() set.Set[ids.ShortID] { + return l.addrs +} + +func (l *ledgerKeychain) Get(addr ids.ShortID) (Signer, bool) { + idx, ok := l.addrToIdx[addr] + if !ok { + return nil, false + } + + return &ledgerSigner{ + ledger: l.ledger, + idx: idx, + addr: addr, + }, true +} + +// expects to receive a hash of the unsigned tx bytes +func (l *ledgerSigner) SignHash(b []byte) ([]byte, error) { + // Sign using the address with index l.idx on the ledger device. The number + // of returned signatures should be the same length as the provided indices. + sigs, err := l.ledger.SignHash(b, []uint32{l.idx}) + if err != nil { + return nil, err + } + + if sigsLen := len(sigs); sigsLen != 1 { + return nil, fmt.Errorf( + "%w. expected 1, got %d", + ErrInvalidNumSignatures, + sigsLen, + ) + } + + return sigs[0], nil +} + +// expects to receive the unsigned tx bytes +func (l *ledgerSigner) Sign(b []byte, _ ...SigningOption) ([]byte, error) { + // Sign using the address with index l.idx on the ledger device. The number + // of returned signatures should be the same length as the provided indices. + sigs, err := l.ledger.Sign(b, []uint32{l.idx}) + if err != nil { + return nil, err + } + + if sigsLen := len(sigs); sigsLen != 1 { + return nil, fmt.Errorf( + "%w. expected 1, got %d", + ErrInvalidNumSignatures, + sigsLen, + ) + } + + return sigs[0], nil +} + +func (l *ledgerSigner) Address() ids.ShortID { + return l.addr } diff --git a/utils/crypto/keychain/ledger/ledger_keychain_test.go b/utils/crypto/keychain/keychain_test.go similarity index 68% rename from utils/crypto/keychain/ledger/ledger_keychain_test.go rename to utils/crypto/keychain/keychain_test.go index 68762724a32d..66256583267e 100644 --- a/utils/crypto/keychain/ledger/ledger_keychain_test.go +++ b/utils/crypto/keychain/keychain_test.go @@ -1,7 +1,7 @@ // Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package ledger +package keychain import ( "errors" @@ -11,38 +11,38 @@ import ( "go.uber.org/mock/gomock" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/utils/crypto/keychain/ledger/ledgermock" + "github.com/ava-labs/avalanchego/utils/crypto/keychain/keychainmock" ) var errTest = errors.New("test") -func TestNewKeychain(t *testing.T) { +func TestNewLedgerKeychain(t *testing.T) { require := require.New(t) ctrl := gomock.NewController(t) addr := ids.GenerateTestShortID() // user request invalid number of addresses to derive - ledger := ledgermock.NewLedger(ctrl) - _, err := NewKeychain(ledger, 0) + ledger := keychainmock.NewLedger(ctrl) + _, err := NewLedgerKeychain(ledger, 0) require.ErrorIs(err, ErrInvalidNumAddrsToDerive) // ledger does not return expected number of derived addresses - ledger = ledgermock.NewLedger(ctrl) + ledger = keychainmock.NewLedger(ctrl) ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{}, nil).Times(1) - _, err = NewKeychain(ledger, 1) + _, err = NewLedgerKeychain(ledger, 1) require.ErrorIs(err, ErrInvalidNumAddrsDerived) // ledger return error when asked for derived addresses - ledger = ledgermock.NewLedger(ctrl) + ledger = keychainmock.NewLedger(ctrl) ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{addr}, errTest).Times(1) - _, err = NewKeychain(ledger, 1) + _, err = NewLedgerKeychain(ledger, 1) require.ErrorIs(err, errTest) // good path - ledger = ledgermock.NewLedger(ctrl) + ledger = keychainmock.NewLedger(ctrl) ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{addr}, nil).Times(1) - _, err = NewKeychain(ledger, 1) + _, err = NewLedgerKeychain(ledger, 1) require.NoError(err) } @@ -55,9 +55,9 @@ func TestLedgerKeychain_Addresses(t *testing.T) { addr3 := ids.GenerateTestShortID() // 1 addr - ledger := ledgermock.NewLedger(ctrl) + ledger := keychainmock.NewLedger(ctrl) ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{addr1}, nil).Times(1) - kc, err := NewKeychain(ledger, 1) + kc, err := NewLedgerKeychain(ledger, 1) require.NoError(err) addrs := kc.Addresses() @@ -65,9 +65,9 @@ func TestLedgerKeychain_Addresses(t *testing.T) { require.True(addrs.Contains(addr1)) // multiple addresses - ledger = ledgermock.NewLedger(ctrl) + ledger = keychainmock.NewLedger(ctrl) ledger.EXPECT().Addresses([]uint32{0, 1, 2}).Return([]ids.ShortID{addr1, addr2, addr3}, nil).Times(1) - kc, err = NewKeychain(ledger, 3) + kc, err = NewLedgerKeychain(ledger, 3) require.NoError(err) addrs = kc.Addresses() @@ -86,9 +86,9 @@ func TestLedgerKeychain_Get(t *testing.T) { addr3 := ids.GenerateTestShortID() // 1 addr - ledger := ledgermock.NewLedger(ctrl) + ledger := keychainmock.NewLedger(ctrl) ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{addr1}, nil).Times(1) - kc, err := NewKeychain(ledger, 1) + kc, err := NewLedgerKeychain(ledger, 1) require.NoError(err) _, b := kc.Get(ids.GenerateTestShortID()) @@ -99,9 +99,9 @@ func TestLedgerKeychain_Get(t *testing.T) { require.True(b) // multiple addresses - ledger = ledgermock.NewLedger(ctrl) + ledger = keychainmock.NewLedger(ctrl) ledger.EXPECT().Addresses([]uint32{0, 1, 2}).Return([]ids.ShortID{addr1, addr2, addr3}, nil).Times(1) - kc, err = NewKeychain(ledger, 3) + kc, err = NewLedgerKeychain(ledger, 3) require.NoError(err) _, b = kc.Get(ids.GenerateTestShortID()) @@ -133,10 +133,10 @@ func TestLedgerSigner_SignHash(t *testing.T) { expectedSignature3 := []byte{3, 3, 3} // ledger returns an incorrect number of signatures - ledger := ledgermock.NewLedger(ctrl) + ledger := keychainmock.NewLedger(ctrl) ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{addr1}, nil).Times(1) ledger.EXPECT().SignHash(toSign, []uint32{0}).Return([][]byte{}, nil).Times(1) - kc, err := NewKeychain(ledger, 1) + kc, err := NewLedgerKeychain(ledger, 1) require.NoError(err) s, b := kc.Get(addr1) @@ -146,10 +146,10 @@ func TestLedgerSigner_SignHash(t *testing.T) { require.ErrorIs(err, ErrInvalidNumSignatures) // ledger returns an error when asked for signature - ledger = ledgermock.NewLedger(ctrl) + ledger = keychainmock.NewLedger(ctrl) ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{addr1}, nil).Times(1) ledger.EXPECT().SignHash(toSign, []uint32{0}).Return([][]byte{expectedSignature1}, errTest).Times(1) - kc, err = NewKeychain(ledger, 1) + kc, err = NewLedgerKeychain(ledger, 1) require.NoError(err) s, b = kc.Get(addr1) @@ -159,10 +159,10 @@ func TestLedgerSigner_SignHash(t *testing.T) { require.ErrorIs(err, errTest) // good path 1 addr - ledger = ledgermock.NewLedger(ctrl) + ledger = keychainmock.NewLedger(ctrl) ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{addr1}, nil).Times(1) ledger.EXPECT().SignHash(toSign, []uint32{0}).Return([][]byte{expectedSignature1}, nil).Times(1) - kc, err = NewKeychain(ledger, 1) + kc, err = NewLedgerKeychain(ledger, 1) require.NoError(err) s, b = kc.Get(addr1) @@ -173,12 +173,12 @@ func TestLedgerSigner_SignHash(t *testing.T) { require.Equal(expectedSignature1, signature) // good path 3 addr - ledger = ledgermock.NewLedger(ctrl) + ledger = keychainmock.NewLedger(ctrl) ledger.EXPECT().Addresses([]uint32{0, 1, 2}).Return([]ids.ShortID{addr1, addr2, addr3}, nil).Times(1) ledger.EXPECT().SignHash(toSign, []uint32{0}).Return([][]byte{expectedSignature1}, nil).Times(1) ledger.EXPECT().SignHash(toSign, []uint32{1}).Return([][]byte{expectedSignature2}, nil).Times(1) ledger.EXPECT().SignHash(toSign, []uint32{2}).Return([][]byte{expectedSignature3}, nil).Times(1) - kc, err = NewKeychain(ledger, 3) + kc, err = NewLedgerKeychain(ledger, 3) require.NoError(err) s, b = kc.Get(addr1) @@ -203,7 +203,7 @@ func TestLedgerSigner_SignHash(t *testing.T) { require.Equal(expectedSignature3, signature) } -func TestNewKeychainFromIndices(t *testing.T) { +func TestNewLedgerKeychainFromIndices(t *testing.T) { require := require.New(t) ctrl := gomock.NewController(t) @@ -211,26 +211,26 @@ func TestNewKeychainFromIndices(t *testing.T) { _ = addr // user request invalid number of indices - ledger := ledgermock.NewLedger(ctrl) - _, err := NewKeychainFromIndices(ledger, []uint32{}) + ledger := keychainmock.NewLedger(ctrl) + _, err := NewLedgerKeychainFromIndices(ledger, []uint32{}) require.ErrorIs(err, ErrInvalidIndicesLength) // ledger does not return expected number of derived addresses - ledger = ledgermock.NewLedger(ctrl) + ledger = keychainmock.NewLedger(ctrl) ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{}, nil).Times(1) - _, err = NewKeychainFromIndices(ledger, []uint32{0}) + _, err = NewLedgerKeychainFromIndices(ledger, []uint32{0}) require.ErrorIs(err, ErrInvalidNumAddrsDerived) // ledger return error when asked for derived addresses - ledger = ledgermock.NewLedger(ctrl) + ledger = keychainmock.NewLedger(ctrl) ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{addr}, errTest).Times(1) - _, err = NewKeychainFromIndices(ledger, []uint32{0}) + _, err = NewLedgerKeychainFromIndices(ledger, []uint32{0}) require.ErrorIs(err, errTest) // good path - ledger = ledgermock.NewLedger(ctrl) + ledger = keychainmock.NewLedger(ctrl) ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{addr}, nil).Times(1) - _, err = NewKeychainFromIndices(ledger, []uint32{0}) + _, err = NewLedgerKeychainFromIndices(ledger, []uint32{0}) require.NoError(err) } @@ -243,9 +243,9 @@ func TestLedgerKeychainFromIndices_Addresses(t *testing.T) { addr3 := ids.GenerateTestShortID() // 1 addr - ledger := ledgermock.NewLedger(ctrl) + ledger := keychainmock.NewLedger(ctrl) ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{addr1}, nil).Times(1) - kc, err := NewKeychainFromIndices(ledger, []uint32{0}) + kc, err := NewLedgerKeychainFromIndices(ledger, []uint32{0}) require.NoError(err) addrs := kc.Addresses() @@ -253,9 +253,9 @@ func TestLedgerKeychainFromIndices_Addresses(t *testing.T) { require.True(addrs.Contains(addr1)) // first 3 addresses - ledger = ledgermock.NewLedger(ctrl) + ledger = keychainmock.NewLedger(ctrl) ledger.EXPECT().Addresses([]uint32{0, 1, 2}).Return([]ids.ShortID{addr1, addr2, addr3}, nil).Times(1) - kc, err = NewKeychainFromIndices(ledger, []uint32{0, 1, 2}) + kc, err = NewLedgerKeychainFromIndices(ledger, []uint32{0, 1, 2}) require.NoError(err) addrs = kc.Addresses() @@ -267,9 +267,9 @@ func TestLedgerKeychainFromIndices_Addresses(t *testing.T) { // some 3 addresses indices := []uint32{3, 7, 1} addresses := []ids.ShortID{addr1, addr2, addr3} - ledger = ledgermock.NewLedger(ctrl) + ledger = keychainmock.NewLedger(ctrl) ledger.EXPECT().Addresses(indices).Return(addresses, nil).Times(1) - kc, err = NewKeychainFromIndices(ledger, indices) + kc, err = NewLedgerKeychainFromIndices(ledger, indices) require.NoError(err) addrs = kc.Addresses() @@ -281,9 +281,9 @@ func TestLedgerKeychainFromIndices_Addresses(t *testing.T) { // repeated addresses indices = []uint32{3, 7, 1, 3, 1, 7} addresses = []ids.ShortID{addr1, addr2, addr3, addr1, addr2, addr3} - ledger = ledgermock.NewLedger(ctrl) + ledger = keychainmock.NewLedger(ctrl) ledger.EXPECT().Addresses(indices).Return(addresses, nil).Times(1) - kc, err = NewKeychainFromIndices(ledger, indices) + kc, err = NewLedgerKeychainFromIndices(ledger, indices) require.NoError(err) addrs = kc.Addresses() @@ -302,9 +302,9 @@ func TestLedgerKeychainFromIndices_Get(t *testing.T) { addr3 := ids.GenerateTestShortID() // 1 addr - ledger := ledgermock.NewLedger(ctrl) + ledger := keychainmock.NewLedger(ctrl) ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{addr1}, nil).Times(1) - kc, err := NewKeychainFromIndices(ledger, []uint32{0}) + kc, err := NewLedgerKeychainFromIndices(ledger, []uint32{0}) require.NoError(err) _, b := kc.Get(ids.GenerateTestShortID()) @@ -317,9 +317,9 @@ func TestLedgerKeychainFromIndices_Get(t *testing.T) { // some 3 addresses indices := []uint32{3, 7, 1} addresses := []ids.ShortID{addr1, addr2, addr3} - ledger = ledgermock.NewLedger(ctrl) + ledger = keychainmock.NewLedger(ctrl) ledger.EXPECT().Addresses(indices).Return(addresses, nil).Times(1) - kc, err = NewKeychainFromIndices(ledger, indices) + kc, err = NewLedgerKeychainFromIndices(ledger, indices) require.NoError(err) _, b = kc.Get(ids.GenerateTestShortID()) @@ -351,10 +351,10 @@ func TestLedgerSignerFromIndices_SignHash(t *testing.T) { expectedSignature3 := []byte{3, 3, 3} // ledger returns an incorrect number of signatures - ledger := ledgermock.NewLedger(ctrl) + ledger := keychainmock.NewLedger(ctrl) ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{addr1}, nil).Times(1) ledger.EXPECT().SignHash(toSign, []uint32{0}).Return([][]byte{}, nil).Times(1) - kc, err := NewKeychainFromIndices(ledger, []uint32{0}) + kc, err := NewLedgerKeychainFromIndices(ledger, []uint32{0}) require.NoError(err) s, b := kc.Get(addr1) @@ -364,10 +364,10 @@ func TestLedgerSignerFromIndices_SignHash(t *testing.T) { require.ErrorIs(err, ErrInvalidNumSignatures) // ledger returns an error when asked for signature - ledger = ledgermock.NewLedger(ctrl) + ledger = keychainmock.NewLedger(ctrl) ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{addr1}, nil).Times(1) ledger.EXPECT().SignHash(toSign, []uint32{0}).Return([][]byte{expectedSignature1}, errTest).Times(1) - kc, err = NewKeychainFromIndices(ledger, []uint32{0}) + kc, err = NewLedgerKeychainFromIndices(ledger, []uint32{0}) require.NoError(err) s, b = kc.Get(addr1) @@ -377,10 +377,10 @@ func TestLedgerSignerFromIndices_SignHash(t *testing.T) { require.ErrorIs(err, errTest) // good path 1 addr - ledger = ledgermock.NewLedger(ctrl) + ledger = keychainmock.NewLedger(ctrl) ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{addr1}, nil).Times(1) ledger.EXPECT().SignHash(toSign, []uint32{0}).Return([][]byte{expectedSignature1}, nil).Times(1) - kc, err = NewKeychainFromIndices(ledger, []uint32{0}) + kc, err = NewLedgerKeychainFromIndices(ledger, []uint32{0}) require.NoError(err) s, b = kc.Get(addr1) @@ -393,12 +393,12 @@ func TestLedgerSignerFromIndices_SignHash(t *testing.T) { // good path some 3 addresses indices := []uint32{3, 7, 1} addresses := []ids.ShortID{addr1, addr2, addr3} - ledger = ledgermock.NewLedger(ctrl) + ledger = keychainmock.NewLedger(ctrl) ledger.EXPECT().Addresses(indices).Return(addresses, nil).Times(1) ledger.EXPECT().SignHash(toSign, []uint32{indices[0]}).Return([][]byte{expectedSignature1}, nil).Times(1) ledger.EXPECT().SignHash(toSign, []uint32{indices[1]}).Return([][]byte{expectedSignature2}, nil).Times(1) ledger.EXPECT().SignHash(toSign, []uint32{indices[2]}).Return([][]byte{expectedSignature3}, nil).Times(1) - kc, err = NewKeychainFromIndices(ledger, indices) + kc, err = NewLedgerKeychainFromIndices(ledger, indices) require.NoError(err) s, b = kc.Get(addr1) @@ -422,86 +422,3 @@ func TestLedgerSignerFromIndices_SignHash(t *testing.T) { require.NoError(err) require.Equal(expectedSignature3, signature) } - -func TestLedgerSigner_Sign(t *testing.T) { - require := require.New(t) - ctrl := gomock.NewController(t) - - addr1 := ids.GenerateTestShortID() - addr2 := ids.GenerateTestShortID() - addr3 := ids.GenerateTestShortID() - toSign := []byte{1, 2, 3, 4, 5} - expectedSignature1 := []byte{1, 1, 1} - expectedSignature2 := []byte{2, 2, 2} - expectedSignature3 := []byte{3, 3, 3} - - // ledger returns an incorrect number of signatures - ledger := ledgermock.NewLedger(ctrl) - ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{addr1}, nil).Times(1) - ledger.EXPECT().Sign(toSign, []uint32{0}).Return([][]byte{}, nil).Times(1) - kc, err := NewKeychain(ledger, 1) - require.NoError(err) - - s, b := kc.Get(addr1) - require.True(b) - - _, err = s.Sign(toSign) - require.ErrorIs(err, ErrInvalidNumSignatures) - - // ledger returns an error when asked for signature - ledger = ledgermock.NewLedger(ctrl) - ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{addr1}, nil).Times(1) - ledger.EXPECT().Sign(toSign, []uint32{0}).Return([][]byte{expectedSignature1}, errTest).Times(1) - kc, err = NewKeychain(ledger, 1) - require.NoError(err) - - s, b = kc.Get(addr1) - require.True(b) - - _, err = s.Sign(toSign) - require.ErrorIs(err, errTest) - - // good path 1 addr - ledger = ledgermock.NewLedger(ctrl) - ledger.EXPECT().Addresses([]uint32{0}).Return([]ids.ShortID{addr1}, nil).Times(1) - ledger.EXPECT().Sign(toSign, []uint32{0}).Return([][]byte{expectedSignature1}, nil).Times(1) - kc, err = NewKeychain(ledger, 1) - require.NoError(err) - - s, b = kc.Get(addr1) - require.True(b) - - signature, err := s.Sign(toSign) - require.NoError(err) - require.Equal(expectedSignature1, signature) - - // good path 3 addr - ledger = ledgermock.NewLedger(ctrl) - ledger.EXPECT().Addresses([]uint32{0, 1, 2}).Return([]ids.ShortID{addr1, addr2, addr3}, nil).Times(1) - ledger.EXPECT().Sign(toSign, []uint32{0}).Return([][]byte{expectedSignature1}, nil).Times(1) - ledger.EXPECT().Sign(toSign, []uint32{1}).Return([][]byte{expectedSignature2}, nil).Times(1) - ledger.EXPECT().Sign(toSign, []uint32{2}).Return([][]byte{expectedSignature3}, nil).Times(1) - kc, err = NewKeychain(ledger, 3) - require.NoError(err) - - s, b = kc.Get(addr1) - require.True(b) - - signature, err = s.Sign(toSign) - require.NoError(err) - require.Equal(expectedSignature1, signature) - - s, b = kc.Get(addr2) - require.True(b) - - signature, err = s.Sign(toSign) - require.NoError(err) - require.Equal(expectedSignature2, signature) - - s, b = kc.Get(addr3) - require.True(b) - - signature, err = s.Sign(toSign) - require.NoError(err) - require.Equal(expectedSignature3, signature) -} diff --git a/utils/crypto/keychain/ledger/ledgermock/ledger.go b/utils/crypto/keychain/keychainmock/ledger.go similarity index 95% rename from utils/crypto/keychain/ledger/ledgermock/ledger.go rename to utils/crypto/keychain/keychainmock/ledger.go index d55709767b58..d7a22a93bc77 100644 --- a/utils/crypto/keychain/ledger/ledgermock/ledger.go +++ b/utils/crypto/keychain/keychainmock/ledger.go @@ -1,13 +1,13 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/ava-labs/avalanchego/utils/crypto/keychain/ledger (interfaces: Ledger) +// Source: github.com/ava-labs/avalanchego/utils/crypto/keychain (interfaces: Ledger) // // Generated by this command: // -// mockgen -package=ledgermock -destination=ledgermock/ledger.go -mock_names=Ledger=Ledger . Ledger +// mockgen -package=keychainmock -destination=keychainmock/ledger.go -mock_names=Ledger=Ledger . Ledger // -// Package ledgermock is a generated GoMock package. -package ledgermock +// Package keychainmock is a generated GoMock package. +package keychainmock import ( reflect "reflect" diff --git a/utils/crypto/keychain/ledger/ledger.go b/utils/crypto/keychain/ledger.go similarity index 77% rename from utils/crypto/keychain/ledger/ledger.go rename to utils/crypto/keychain/ledger.go index f1d85bcef23e..5778ff304d7a 100644 --- a/utils/crypto/keychain/ledger/ledger.go +++ b/utils/crypto/keychain/ledger.go @@ -1,11 +1,10 @@ // Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package ledger +package keychain import ( "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/utils/crypto/ledger" "github.com/ava-labs/avalanchego/version" ) @@ -18,6 +17,3 @@ type Ledger interface { Sign(unsignedTxBytes []byte, addressIndices []uint32) ([][]byte, error) Disconnect() error } - -// Verify that the ledger implementation satisfies the interface -var _ Ledger = (*ledger.Ledger)(nil) diff --git a/utils/crypto/keychain/ledger/ledger_keychain.go b/utils/crypto/keychain/ledger/ledger_keychain.go deleted file mode 100644 index 11f91f3007b7..000000000000 --- a/utils/crypto/keychain/ledger/ledger_keychain.go +++ /dev/null @@ -1,154 +0,0 @@ -// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package ledger - -import ( - "errors" - "fmt" - - "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/utils/crypto/keychain" - "github.com/ava-labs/avalanchego/utils/set" -) - -var ( - _ keychain.Keychain = (*KeyChain)(nil) - _ keychain.Signer = (*ledgerSigner)(nil) - - ErrInvalidIndicesLength = errors.New("number of indices should be greater than 0") - ErrInvalidNumAddrsToDerive = errors.New("number of addresses to derive should be greater than 0") - ErrInvalidNumAddrsDerived = errors.New("incorrect number of ledger derived addresses") - ErrInvalidNumSignatures = errors.New("incorrect number of signatures") -) - -// KeyChain is an abstraction of the underlying ledger hardware device, -// to be able to get a signer from a finite set of derived signers -type KeyChain struct { - ledger Ledger - addrs set.Set[ids.ShortID] - addrToIdx map[ids.ShortID]uint32 -} - -// NewKeychain creates a new Ledger with [numToDerive] addresses. -func NewKeychain(l Ledger, numToDerive int) (*KeyChain, error) { - if numToDerive < 1 { - return nil, ErrInvalidNumAddrsToDerive - } - - indices := make([]uint32, numToDerive) - for i := range indices { - indices[i] = uint32(i) - } - - return NewKeychainFromIndices(l, indices) -} - -// NewKeychainFromIndices creates a new Ledger with addresses taken from the given [indices]. -func NewKeychainFromIndices(l Ledger, indices []uint32) (*KeyChain, error) { - if len(indices) == 0 { - return nil, ErrInvalidIndicesLength - } - - addrs, err := l.Addresses(indices) - if err != nil { - return nil, err - } - - if len(addrs) != len(indices) { - return nil, fmt.Errorf( - "%w. expected %d, got %d", - ErrInvalidNumAddrsDerived, - len(indices), - len(addrs), - ) - } - - addrsSet := set.Of(addrs...) - - addrToIdx := map[ids.ShortID]uint32{} - for i := range indices { - addrToIdx[addrs[i]] = indices[i] - } - - return &KeyChain{ - ledger: l, - addrs: addrsSet, - addrToIdx: addrToIdx, - }, nil -} - -// Addresses returns the set of addresses that this keychain can sign for. -func (l *KeyChain) Addresses() set.Set[ids.ShortID] { - return l.addrs -} - -// Get returns a signer for the given address, if it exists in this keychain. -func (l *KeyChain) Get(addr ids.ShortID) (keychain.Signer, bool) { - idx, ok := l.addrToIdx[addr] - if !ok { - return nil, false - } - - return &ledgerSigner{ - ledger: l.ledger, - idx: idx, - addr: addr, - }, true -} - -// ledgerSigner is an abstraction of the underlying ledger hardware device, -// to be able sign for a specific address -type ledgerSigner struct { - ledger Ledger - idx uint32 - addr ids.ShortID -} - -// SignHash signs the provided hash using the Ledger device. -// It expects to receive a hash of the unsigned transaction bytes. -func (l *ledgerSigner) SignHash(b []byte) ([]byte, error) { - // Sign using the address with index l.idx on the ledger device. The number - // of returned signatures should be the same length as the provided indices. - sigs, err := l.ledger.SignHash(b, []uint32{l.idx}) - if err != nil { - return nil, err - } - - if sigsLen := len(sigs); sigsLen != 1 { - return nil, fmt.Errorf( - "%w. expected 1, got %d", - ErrInvalidNumSignatures, - sigsLen, - ) - } - - return sigs[0], nil -} - -// Sign signs the provided unsigned transaction bytes using the Ledger device. -// It expects to receive the unsigned transaction bytes and returns the signature. -func (l *ledgerSigner) Sign(b []byte, _ ...keychain.SigningOption) ([]byte, error) { - // Ignore options - ledger signing doesn't need chain/network context - // Sign using the address with index l.idx on the ledger device. The number - // of returned signatures should be the same length as the provided indices. - sigs, err := l.ledger.Sign(b, []uint32{l.idx}) - if err != nil { - return nil, err - } - - if sigsLen := len(sigs); sigsLen != 1 { - return nil, fmt.Errorf( - "%w. expected 1, got %d", - ErrInvalidNumSignatures, - sigsLen, - ) - } - - return sigs[0], nil -} - -// Address returns the address associated with this signer. -func (l *ledgerSigner) Address() ids.ShortID { - return l.addr -} diff --git a/utils/crypto/keychain/ledger/mocks_generate_test.go b/utils/crypto/keychain/mocks_generate_test.go similarity index 93% rename from utils/crypto/keychain/ledger/mocks_generate_test.go rename to utils/crypto/keychain/mocks_generate_test.go index 29093e1fff54..151f3c4650ec 100644 --- a/utils/crypto/keychain/ledger/mocks_generate_test.go +++ b/utils/crypto/keychain/mocks_generate_test.go @@ -1,6 +1,6 @@ // Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package ledger +package keychain //go:generate go run go.uber.org/mock/mockgen -package=${GOPACKAGE}mock -destination=${GOPACKAGE}mock/ledger.go -mock_names=Ledger=Ledger . Ledger diff --git a/utils/crypto/ledger/ledger.go b/utils/crypto/ledger/ledger.go index 8e3014414152..77f5290b4991 100644 --- a/utils/crypto/ledger/ledger.go +++ b/utils/crypto/ledger/ledger.go @@ -7,6 +7,7 @@ import ( "fmt" "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/crypto/keychain" "github.com/ava-labs/avalanchego/utils/hashing" "github.com/ava-labs/avalanchego/version" @@ -20,6 +21,8 @@ const ( ledgerPathSize = 9 ) +var _ keychain.Ledger = (*Ledger)(nil) + // Ledger is a wrapper around the low-level Ledger Device interface that // provides Avalanche-specific access. type Ledger struct { @@ -27,7 +30,7 @@ type Ledger struct { epk *bip32.Key } -func New() (*Ledger, error) { +func New() (keychain.Ledger, error) { device, err := ledger.FindLedgerAvalancheApp() return &Ledger{ device: device, diff --git a/vms/secp256k1fx/keychain.go b/vms/secp256k1fx/keychain.go index 3e57410bf3ee..3d0813916f71 100644 --- a/vms/secp256k1fx/keychain.go +++ b/vms/secp256k1fx/keychain.go @@ -21,8 +21,7 @@ import ( var ( errCantSpend = errors.New("unable to spend this UTXO") - _ keychain.Keychain = (*Keychain)(nil) - _ keychain.EthKeychain = (*Keychain)(nil) + _ keychain.Keychain = (*Keychain)(nil) ) // Keychain is a collection of keys that can be used to spend outputs diff --git a/wallet/chain/c/signer.go b/wallet/chain/c/signer.go index 050efe45f3f5..ee0f7af14916 100644 --- a/wallet/chain/c/signer.go +++ b/wallet/chain/c/signer.go @@ -9,12 +9,14 @@ import ( "fmt" "github.com/ava-labs/coreth/plugin/evm/atomic" + "github.com/ava-labs/libevm/common" "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/crypto/keychain" "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" "github.com/ava-labs/avalanchego/utils/hashing" + "github.com/ava-labs/avalanchego/utils/set" "github.com/ava-labs/avalanchego/vms/components/avax" "github.com/ava-labs/avalanchego/vms/components/verify" "github.com/ava-labs/avalanchego/vms/secp256k1fx" @@ -45,17 +47,25 @@ type Signer interface { SignAtomic(ctx context.Context, tx *atomic.Tx) error } +type EthKeychain interface { + // The returned Signer can provide a signature for [addr] + GetEth(addr common.Address) (keychain.Signer, bool) + // Returns the set of addresses for which the accessor keeps an associated + // signer + EthAddresses() set.Set[common.Address] +} + type SignerBackend interface { GetUTXO(ctx context.Context, chainID, utxoID ids.ID) (*avax.UTXO, error) } type txSigner struct { avaxKC keychain.Keychain - ethKC keychain.EthKeychain + ethKC EthKeychain backend SignerBackend } -func NewSigner(avaxKC keychain.Keychain, ethKC keychain.EthKeychain, backend SignerBackend) Signer { +func NewSigner(avaxKC keychain.Keychain, ethKC EthKeychain, backend SignerBackend) Signer { return &txSigner{ avaxKC: avaxKC, ethKC: ethKC, diff --git a/wallet/chain/p/signer/signer.go b/wallet/chain/p/signer/signer.go index 0841112f87e1..b00916ccd7e3 100644 --- a/wallet/chain/p/signer/signer.go +++ b/wallet/chain/p/signer/signer.go @@ -9,7 +9,6 @@ import ( "github.com/ava-labs/avalanchego/vms/components/avax" "github.com/ava-labs/avalanchego/vms/platformvm/fx" "github.com/ava-labs/avalanchego/vms/platformvm/txs" - "github.com/ava-labs/avalanchego/wallet/subnet/primary/common" stdcontext "context" ) @@ -25,7 +24,7 @@ type Signer interface { // // If the signer doesn't have the ability to provide a required signature, // the signature slot will be skipped without reporting an error. - Sign(ctx stdcontext.Context, tx *txs.Tx, options ...common.Option) error + Sign(ctx stdcontext.Context, tx *txs.Tx) error } type Backend interface { @@ -47,16 +46,13 @@ func New(kc keychain.Keychain, backend Backend, networkID uint32) Signer { } } -func (s *txSigner) Sign(ctx stdcontext.Context, tx *txs.Tx, options ...common.Option) error { - ops := common.NewOptions(options) - +func (s *txSigner) Sign(ctx stdcontext.Context, tx *txs.Tx) error { return tx.Unsigned.Visit(&visitor{ - kc: s.kc, - backend: s.backend, - ctx: ctx, - tx: tx, - networkID: s.networkID, - forceSignHash: ops.ForceSignHash(), + kc: s.kc, + backend: s.backend, + ctx: ctx, + tx: tx, + networkID: s.networkID, }) } @@ -64,8 +60,7 @@ func SignUnsigned( ctx stdcontext.Context, signer Signer, utx txs.UnsignedTx, - options ...common.Option, ) (*txs.Tx, error) { tx := &txs.Tx{Unsigned: utx} - return tx, signer.Sign(ctx, tx, options...) + return tx, signer.Sign(ctx, tx) } diff --git a/wallet/chain/p/signer/visitor.go b/wallet/chain/p/signer/visitor.go index b919b3a22c5f..a881249ac825 100644 --- a/wallet/chain/p/signer/visitor.go +++ b/wallet/chain/p/signer/visitor.go @@ -38,12 +38,11 @@ var ( // visitor handles signing transactions for the signer type visitor struct { - kc keychain.Keychain - backend Backend - ctx context.Context - tx *txs.Tx - networkID uint32 - forceSignHash bool + kc keychain.Keychain + backend Backend + ctx context.Context + tx *txs.Tx + networkID uint32 } func (*visitor) AdvanceTimeTx(*txs.AdvanceTimeTx) error { @@ -59,7 +58,7 @@ func (s *visitor) AddValidatorTx(tx *txs.AddValidatorTx) error { if err != nil { return err } - return sign(s.tx, s.forceSignHash, txSigners, s.networkID) + return sign(s.tx, false, txSigners, s.networkID) } func (s *visitor) AddSubnetValidatorTx(tx *txs.AddSubnetValidatorTx) error { @@ -72,7 +71,7 @@ func (s *visitor) AddSubnetValidatorTx(tx *txs.AddSubnetValidatorTx) error { return err } txSigners = append(txSigners, subnetAuthSigners) - return sign(s.tx, s.forceSignHash, txSigners, s.networkID) + return sign(s.tx, false, txSigners, s.networkID) } func (s *visitor) AddDelegatorTx(tx *txs.AddDelegatorTx) error { @@ -80,7 +79,7 @@ func (s *visitor) AddDelegatorTx(tx *txs.AddDelegatorTx) error { if err != nil { return err } - return sign(s.tx, s.forceSignHash, txSigners, s.networkID) + return sign(s.tx, false, txSigners, s.networkID) } func (s *visitor) CreateChainTx(tx *txs.CreateChainTx) error { @@ -93,7 +92,7 @@ func (s *visitor) CreateChainTx(tx *txs.CreateChainTx) error { return err } txSigners = append(txSigners, subnetAuthSigners) - return sign(s.tx, s.forceSignHash, txSigners, s.networkID) + return sign(s.tx, false, txSigners, s.networkID) } func (s *visitor) CreateSubnetTx(tx *txs.CreateSubnetTx) error { @@ -101,7 +100,7 @@ func (s *visitor) CreateSubnetTx(tx *txs.CreateSubnetTx) error { if err != nil { return err } - return sign(s.tx, s.forceSignHash, txSigners, s.networkID) + return sign(s.tx, false, txSigners, s.networkID) } func (s *visitor) ImportTx(tx *txs.ImportTx) error { @@ -114,7 +113,7 @@ func (s *visitor) ImportTx(tx *txs.ImportTx) error { return err } txSigners = append(txSigners, txImportSigners...) - return sign(s.tx, s.forceSignHash, txSigners, s.networkID) + return sign(s.tx, false, txSigners, s.networkID) } func (s *visitor) ExportTx(tx *txs.ExportTx) error { @@ -122,7 +121,7 @@ func (s *visitor) ExportTx(tx *txs.ExportTx) error { if err != nil { return err } - return sign(s.tx, s.forceSignHash, txSigners, s.networkID) + return sign(s.tx, false, txSigners, s.networkID) } func (s *visitor) RemoveSubnetValidatorTx(tx *txs.RemoveSubnetValidatorTx) error { @@ -185,7 +184,7 @@ func (s *visitor) BaseTx(tx *txs.BaseTx) error { if err != nil { return err } - return sign(s.tx, s.forceSignHash, txSigners, s.networkID) + return sign(s.tx, false, txSigners, s.networkID) } func (s *visitor) ConvertSubnetToL1Tx(tx *txs.ConvertSubnetToL1Tx) error { diff --git a/wallet/chain/p/signer/with_options.go b/wallet/chain/p/signer/with_options.go deleted file mode 100644 index 86f14ae30d12..000000000000 --- a/wallet/chain/p/signer/with_options.go +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package signer - -import ( - "context" - - "github.com/ava-labs/avalanchego/vms/platformvm/txs" - "github.com/ava-labs/avalanchego/wallet/subnet/primary/common" -) - -var _ Signer = (*withOptions)(nil) - -type withOptions struct { - signer Signer - options []common.Option -} - -// WithOptions returns a new signer that will use the given options by default. -// -// - [signer] is the signer that will be called to perform the underlying -// operations. -// - [options] will be provided to the signer in addition to the options -// provided in the method calls. -func WithOptions(signer Signer, options ...common.Option) Signer { - return &withOptions{ - signer: signer, - options: options, - } -} - -func (w *withOptions) Sign(ctx context.Context, tx *txs.Tx, options ...common.Option) error { - return w.signer.Sign(ctx, tx, common.UnionOptions(w.options, options)...) -} diff --git a/wallet/chain/p/wallet/wallet.go b/wallet/chain/p/wallet/wallet.go index 116508f6fb71..db04731025ac 100644 --- a/wallet/chain/p/wallet/wallet.go +++ b/wallet/chain/p/wallet/wallet.go @@ -611,7 +611,7 @@ func (w *wallet) IssueUnsignedTx( ) (*txs.Tx, error) { ops := common.NewOptions(options) ctx := ops.Context() - tx, err := walletsigner.SignUnsigned(ctx, w.signer, utx, options...) + tx, err := walletsigner.SignUnsigned(ctx, w.signer, utx) if err != nil { return nil, err } diff --git a/wallet/chain/p/wallet/with_options.go b/wallet/chain/p/wallet/with_options.go index 960dee0c6870..f1a80e42de1f 100644 --- a/wallet/chain/p/wallet/with_options.go +++ b/wallet/chain/p/wallet/with_options.go @@ -43,10 +43,7 @@ func (w *withOptions) Builder() builder.Builder { } func (w *withOptions) Signer() walletsigner.Signer { - return walletsigner.WithOptions( - w.wallet.Signer(), - w.options..., - ) + return w.wallet.Signer() } func (w *withOptions) IssueBaseTx( diff --git a/wallet/chain/x/signer/signer.go b/wallet/chain/x/signer/signer.go index 93bc103b0b6b..02b3ec9c6017 100644 --- a/wallet/chain/x/signer/signer.go +++ b/wallet/chain/x/signer/signer.go @@ -10,7 +10,6 @@ import ( "github.com/ava-labs/avalanchego/utils/crypto/keychain" "github.com/ava-labs/avalanchego/vms/avm/txs" "github.com/ava-labs/avalanchego/vms/components/avax" - "github.com/ava-labs/avalanchego/wallet/subnet/primary/common" ) var _ Signer = (*signer)(nil) @@ -24,7 +23,7 @@ type Signer interface { // // If the signer doesn't have the ability to provide a required signature, // the signature slot will be skipped without reporting an error. - Sign(ctx context.Context, tx *txs.Tx, options ...common.Option) error + Sign(ctx context.Context, tx *txs.Tx) error } type Backend interface { @@ -45,16 +44,13 @@ func New(kc keychain.Keychain, backend Backend, networkID uint32) Signer { } } -func (s *signer) Sign(ctx context.Context, tx *txs.Tx, options ...common.Option) error { - ops := common.NewOptions(options) - +func (s *signer) Sign(ctx context.Context, tx *txs.Tx) error { return tx.Unsigned.Visit(&visitor{ - kc: s.kc, - backend: s.backend, - ctx: ctx, - tx: tx, - networkID: s.networkID, - forceSignHash: ops.ForceSignHash(), + kc: s.kc, + backend: s.backend, + ctx: ctx, + tx: tx, + networkID: s.networkID, }) } @@ -62,8 +58,7 @@ func SignUnsigned( ctx context.Context, signer Signer, utx txs.UnsignedTx, - options ...common.Option, ) (*txs.Tx, error) { tx := &txs.Tx{Unsigned: utx} - return tx, signer.Sign(ctx, tx, options...) + return tx, signer.Sign(ctx, tx) } diff --git a/wallet/chain/x/signer/visitor.go b/wallet/chain/x/signer/visitor.go index 973103c1fe16..e1f2af8e8516 100644 --- a/wallet/chain/x/signer/visitor.go +++ b/wallet/chain/x/signer/visitor.go @@ -12,7 +12,6 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/crypto/keychain" "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" - "github.com/ava-labs/avalanchego/utils/hashing" "github.com/ava-labs/avalanchego/vms/avm/fxs" "github.com/ava-labs/avalanchego/vms/avm/txs" "github.com/ava-labs/avalanchego/vms/components/avax" @@ -38,12 +37,11 @@ var ( // visitor handles signing transactions for the signer type visitor struct { - kc keychain.Keychain - backend Backend - ctx context.Context - tx *txs.Tx - networkID uint32 - forceSignHash bool + kc keychain.Keychain + backend Backend + ctx context.Context + tx *txs.Tx + networkID uint32 } func (s *visitor) BaseTx(tx *txs.BaseTx) error { @@ -51,7 +49,7 @@ func (s *visitor) BaseTx(tx *txs.BaseTx) error { if err != nil { return err } - return sign(s.tx, txCreds, txSigners, s.networkID, s.forceSignHash) + return sign(s.tx, txCreds, txSigners, s.networkID) } func (s *visitor) CreateAssetTx(tx *txs.CreateAssetTx) error { @@ -59,7 +57,7 @@ func (s *visitor) CreateAssetTx(tx *txs.CreateAssetTx) error { if err != nil { return err } - return sign(s.tx, txCreds, txSigners, s.networkID, s.forceSignHash) + return sign(s.tx, txCreds, txSigners, s.networkID) } func (s *visitor) OperationTx(tx *txs.OperationTx) error { @@ -73,7 +71,7 @@ func (s *visitor) OperationTx(tx *txs.OperationTx) error { } txCreds = append(txCreds, txOpsCreds...) txSigners = append(txSigners, txOpsSigners...) - return sign(s.tx, txCreds, txSigners, s.networkID, s.forceSignHash) + return sign(s.tx, txCreds, txSigners, s.networkID) } func (s *visitor) ImportTx(tx *txs.ImportTx) error { @@ -87,7 +85,7 @@ func (s *visitor) ImportTx(tx *txs.ImportTx) error { } txCreds = append(txCreds, txImportCreds...) txSigners = append(txSigners, txImportSigners...) - return sign(s.tx, txCreds, txSigners, s.networkID, s.forceSignHash) + return sign(s.tx, txCreds, txSigners, s.networkID) } func (s *visitor) ExportTx(tx *txs.ExportTx) error { @@ -95,7 +93,7 @@ func (s *visitor) ExportTx(tx *txs.ExportTx) error { if err != nil { return err } - return sign(s.tx, txCreds, txSigners, s.networkID, s.forceSignHash) + return sign(s.tx, txCreds, txSigners, s.networkID) } func (s *visitor) getSigners(ctx context.Context, sourceChainID ids.ID, ins []*avax.TransferableInput) ([]verify.Verifiable, [][]keychain.Signer, error) { @@ -221,7 +219,7 @@ func (s *visitor) getOpsSigners(ctx context.Context, sourceChainID ids.ID, ops [ return txCreds, txSigners, nil } -func sign(tx *txs.Tx, creds []verify.Verifiable, txSigners [][]keychain.Signer, networkID uint32, signHash bool) error { +func sign(tx *txs.Tx, creds []verify.Verifiable, txSigners [][]keychain.Signer, networkID uint32) error { codec := builder.Parser.Codec() unsignedBytes, err := codec.Marshal(txs.CodecVersion, &tx.Unsigned) if err != nil { @@ -285,15 +283,9 @@ func sign(tx *txs.Tx, creds []verify.Verifiable, txSigners [][]keychain.Signer, continue } - var sig []byte - if signHash { - unsignedHash := hashing.ComputeHash256(unsignedBytes) - sig, err = signer.SignHash(unsignedHash) - } else { - sig, err = signer.Sign(unsignedBytes, - keychain.WithChainAlias(builder.Alias), - keychain.WithNetworkID(networkID)) - } + sig, err := signer.Sign(unsignedBytes, + keychain.WithChainAlias(builder.Alias), + keychain.WithNetworkID(networkID)) if err != nil { return fmt.Errorf("problem signing tx: %w", err) } diff --git a/wallet/chain/x/wallet.go b/wallet/chain/x/wallet.go index aa8e0115e762..b53d6a064f84 100644 --- a/wallet/chain/x/wallet.go +++ b/wallet/chain/x/wallet.go @@ -281,7 +281,7 @@ func (w *wallet) IssueUnsignedTx( ) (*txs.Tx, error) { ops := common.NewOptions(options) ctx := ops.Context() - tx, err := signer.SignUnsigned(ctx, w.signer, utx, options...) + tx, err := signer.SignUnsigned(ctx, w.signer, utx) if err != nil { return nil, err } diff --git a/wallet/subnet/primary/common/options.go b/wallet/subnet/primary/common/options.go index 287e7e6639b0..dd23934f4db0 100644 --- a/wallet/subnet/primary/common/options.go +++ b/wallet/subnet/primary/common/options.go @@ -70,8 +70,6 @@ type Options struct { issuanceHandler func(IssuanceReceipt) confirmationHandler func(ConfirmationReceipt) - - forceSignHash bool } func NewOptions(ops []Option) *Options { @@ -163,10 +161,6 @@ func (o *Options) ConfirmationHandler() func(ConfirmationReceipt) { return o.confirmationHandler } -func (o *Options) ForceSignHash() bool { - return o.forceSignHash -} - func WithContext(ctx context.Context) Option { return func(o *Options) { o.ctx = ctx @@ -242,9 +236,3 @@ func WithConfirmationHandler(f func(ConfirmationReceipt)) Option { o.confirmationHandler = f } } - -func WithForceSignHash() Option { - return func(o *Options) { - o.forceSignHash = true - } -} diff --git a/wallet/subnet/primary/wallet.go b/wallet/subnet/primary/wallet.go index df4f335ce7db..a51fbd5af879 100644 --- a/wallet/subnet/primary/wallet.go +++ b/wallet/subnet/primary/wallet.go @@ -82,7 +82,7 @@ func MakeWallet( ctx context.Context, uri string, avaxKeychain keychain.Keychain, - ethKeychain keychain.EthKeychain, + ethKeychain c.EthKeychain, config WalletConfig, ) (*Wallet, error) { avaxAddrs := avaxKeychain.Addresses()