diff --git a/wallet/chain/p/signer/signer.go b/wallet/chain/p/signer/signer.go index 4d8a4e2f6635..4cc11a556c32 100644 --- a/wallet/chain/p/signer/signer.go +++ b/wallet/chain/p/signer/signer.go @@ -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 { @@ -44,12 +45,15 @@ func New(kc keychain.Keychain, backend Backend) Signer { } } -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, + forceSignHash: ops.ForceSignHash(), }) } @@ -57,7 +61,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..c041350ddc94 100644 --- a/wallet/chain/p/signer/visitor.go +++ b/wallet/chain/p/signer/visitor.go @@ -37,10 +37,11 @@ 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 + forceSignHash bool } func (*visitor) AdvanceTimeTx(*txs.AdvanceTimeTx) error { @@ -56,7 +57,7 @@ func (s *visitor) AddValidatorTx(tx *txs.AddValidatorTx) error { if err != nil { return err } - return sign(s.tx, false, txSigners) + return sign(s.tx, s.forceSignHash, txSigners) } func (s *visitor) AddSubnetValidatorTx(tx *txs.AddSubnetValidatorTx) error { @@ -69,7 +70,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, s.forceSignHash, txSigners) } func (s *visitor) AddDelegatorTx(tx *txs.AddDelegatorTx) error { @@ -77,7 +78,7 @@ func (s *visitor) AddDelegatorTx(tx *txs.AddDelegatorTx) error { if err != nil { return err } - return sign(s.tx, false, txSigners) + return sign(s.tx, s.forceSignHash, txSigners) } func (s *visitor) CreateChainTx(tx *txs.CreateChainTx) error { @@ -90,7 +91,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, s.forceSignHash, txSigners) } func (s *visitor) CreateSubnetTx(tx *txs.CreateSubnetTx) error { @@ -98,7 +99,7 @@ func (s *visitor) CreateSubnetTx(tx *txs.CreateSubnetTx) error { if err != nil { return err } - return sign(s.tx, false, txSigners) + return sign(s.tx, s.forceSignHash, txSigners) } func (s *visitor) ImportTx(tx *txs.ImportTx) error { @@ -111,7 +112,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, s.forceSignHash, txSigners) } func (s *visitor) ExportTx(tx *txs.ExportTx) error { @@ -119,7 +120,7 @@ func (s *visitor) ExportTx(tx *txs.ExportTx) error { if err != nil { return err } - return sign(s.tx, false, txSigners) + return sign(s.tx, s.forceSignHash, txSigners) } func (s *visitor) RemoveSubnetValidatorTx(tx *txs.RemoveSubnetValidatorTx) error { @@ -182,7 +183,7 @@ func (s *visitor) BaseTx(tx *txs.BaseTx) error { if err != nil { return err } - return sign(s.tx, false, txSigners) + return sign(s.tx, s.forceSignHash, txSigners) } 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 new file mode 100644 index 000000000000..86f14ae30d12 --- /dev/null +++ b/wallet/chain/p/signer/with_options.go @@ -0,0 +1,35 @@ +// 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/with_options.go b/wallet/chain/p/wallet/with_options.go index f1a80e42de1f..73f560c3183d 100644 --- a/wallet/chain/p/wallet/with_options.go +++ b/wallet/chain/p/wallet/with_options.go @@ -43,7 +43,7 @@ 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( diff --git a/wallet/chain/x/signer/signer.go b/wallet/chain/x/signer/signer.go index 5e22f523d5aa..57abbd22556d 100644 --- a/wallet/chain/x/signer/signer.go +++ b/wallet/chain/x/signer/signer.go @@ -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 { @@ -42,12 +43,15 @@ func New(kc keychain.Keychain, backend Backend) Signer { } } -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, + forceSignHash: ops.ForceSignHash(), }) } @@ -55,7 +59,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..ff79235191ed 100644 --- a/wallet/chain/x/signer/visitor.go +++ b/wallet/chain/x/signer/visitor.go @@ -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,11 @@ 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 + forceSignHash bool } func (s *visitor) BaseTx(tx *txs.BaseTx) error { @@ -48,7 +50,7 @@ func (s *visitor) BaseTx(tx *txs.BaseTx) error { if err != nil { return err } - return sign(s.tx, txCreds, txSigners) + return sign(s.tx, s.forceSignHash, txCreds, txSigners) } func (s *visitor) CreateAssetTx(tx *txs.CreateAssetTx) error { @@ -56,7 +58,7 @@ func (s *visitor) CreateAssetTx(tx *txs.CreateAssetTx) error { if err != nil { return err } - return sign(s.tx, txCreds, txSigners) + return sign(s.tx, s.forceSignHash, txCreds, txSigners) } func (s *visitor) OperationTx(tx *txs.OperationTx) error { @@ -70,7 +72,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, s.forceSignHash, txCreds, txSigners) } func (s *visitor) ImportTx(tx *txs.ImportTx) error { @@ -84,7 +86,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, s.forceSignHash, txCreds, txSigners) } func (s *visitor) ExportTx(tx *txs.ExportTx) error { @@ -92,7 +94,7 @@ func (s *visitor) ExportTx(tx *txs.ExportTx) error { if err != nil { return err } - return sign(s.tx, txCreds, txSigners) + return sign(s.tx, s.forceSignHash, txCreds, txSigners) } func (s *visitor) getSigners(ctx context.Context, sourceChainID ids.ID, ins []*avax.TransferableInput) ([]verify.Verifiable, [][]keychain.Signer, error) { @@ -218,12 +220,13 @@ 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, signHash bool, creds []verify.Verifiable, txSigners [][]keychain.Signer) error { codec := builder.Parser.Codec() unsignedBytes, err := codec.Marshal(txs.CodecVersion, &tx.Unsigned) if err != nil { return fmt.Errorf("couldn't marshal unsigned tx: %w", err) } + unsignedHash := hashing.ComputeHash256(unsignedBytes) if expectedLen := len(txSigners); expectedLen != len(tx.Creds) { tx.Creds = make([]*fxs.FxCredential, expectedLen) @@ -282,7 +285,12 @@ func sign(tx *txs.Tx, creds []verify.Verifiable, txSigners [][]keychain.Signer) continue } - sig, err := signer.Sign(unsignedBytes) + var sig []byte + if signHash { + sig, err = signer.SignHash(unsignedHash) + } else { + sig, err = signer.Sign(unsignedBytes) + } if err != nil { return fmt.Errorf("problem signing tx: %w", err) } diff --git a/wallet/chain/x/signer/with_options.go b/wallet/chain/x/signer/with_options.go new file mode 100644 index 000000000000..4ef2c13b44ba --- /dev/null +++ b/wallet/chain/x/signer/with_options.go @@ -0,0 +1,35 @@ +// 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/avm/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/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/chain/x/wallet_with_options.go b/wallet/chain/x/wallet_with_options.go index 1b5aa0d3c317..0083bda6dc15 100644 --- a/wallet/chain/x/wallet_with_options.go +++ b/wallet/chain/x/wallet_with_options.go @@ -39,7 +39,7 @@ func (w *walletWithOptions) Builder() builder.Builder { } func (w *walletWithOptions) Signer() signer.Signer { - return w.wallet.Signer() + return signer.WithOptions(w.wallet.Signer(), w.options...) } func (w *walletWithOptions) IssueBaseTx( diff --git a/wallet/subnet/primary/common/options.go b/wallet/subnet/primary/common/options.go index dd23934f4db0..287e7e6639b0 100644 --- a/wallet/subnet/primary/common/options.go +++ b/wallet/subnet/primary/common/options.go @@ -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,9 @@ func WithConfirmationHandler(f func(ConfirmationReceipt)) Option { o.confirmationHandler = f } } + +func WithForceSignHash() Option { + return func(o *Options) { + o.forceSignHash = true + } +}