Skip to content

Conversation

felipemadero
Copy link
Contributor

Why this should be merged

Currently, the wallet layer needs to know when to use SignHash vs Sign for different transaction types, creating tight coupling between wallet logic and signing implementation details. This PR moves that decision-making into the ledger keychain itself, simplifying the wallet interface and centralizing signing logic where it belongs.

Additionally, this PR incorporates the refactoring changes from #4339 (ledger-keychain-move) to resolve circular dependency issues between the keychain and secp256k1fx packages.

How this works

Ledger Signing Improvements

The ledger keychain's Sign method now internally determines when to use SignHash based on transaction type:

  • For TransferSubnetOwnershipTx only: Uses SignHash (required by Ledger hardware)
  • For all other P/X/C-Chain transactions: Uses the regular Sign method

Transactions that moved from SignHash → Sign

After comprehensive testing with Ledger hardware, these transactions were verified to work with full-bytes signing and no longer require hash signing:

P-Chain:

  • RemoveSubnetValidatorTx
  • TransformSubnetTx
  • AddPermissionlessValidatorTx
  • AddPermissionlessDelegatorTx
  • ConvertSubnetToL1Tx
  • RegisterL1ValidatorTx
  • SetL1ValidatorWeightTx
  • IncreaseL1ValidatorBalanceTx
  • DisableL1ValidatorTx

C-Chain:

  • ImportTx
  • ExportTx

Architecture Changes

  • Wallet layer no longer needs conditional logic to choose between signing methods - it always calls Sign
  • Ledger-specific code moved to utils/crypto/keychain/ledger package to fix circular dependencies

How this was tested

  • Unit tests for all transaction types
  • CLI flows to deploy an L1 with Ledger hardware
  • Comprehensive Ledger hardware testing for all P/X/C-Chain transaction types to verify which require SignHash vs Sign
  • Example programs for subnet/chain creation and P<->C transfers

Need to be documented in RELEASES.md?

Move SignHash decision logic from wallet layer into ledger keychain implementation. The ledger keychain now determines when to use SignHash (for TransferSubnetOwnershipTx) versus regular Sign based on transaction type, simplifying the wallet interface and centralizing signing logic.
@Copilot Copilot AI review requested due to automatic review settings October 3, 2025 15:27
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR refactors transaction signing logic to move signing method decisions from the wallet layer into the ledger keychain. Previously, wallet code needed to know when to use SignHash vs Sign for different transaction types, creating tight coupling. The refactor centralizes this logic in the ledger keychain where it belongs.

  • Moved most P-Chain transactions from hash signing to full-bytes signing after hardware testing
  • Centralized signing method selection in ledger keychain with only TransferSubnetOwnershipTx still requiring hash signing
  • Introduced signing options infrastructure to pass chain/network context to signers

Reviewed Changes

Copilot reviewed 24 out of 25 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
wallet/subnet/primary/wallet.go Updated signer constructors to include networkID parameter
wallet/chain/x/signer/ Added networkID parameter and signing options to X-chain signing
wallet/chain/p/signer/ Removed signHash parameter, simplified to always use Sign method with options
wallet/chain/c/signer.go Removed signHash parameter from C-chain atomic transaction signing
utils/crypto/secp256k1/secp256k1.go Updated Sign method to accept signing options
utils/crypto/keychain/ Moved ledger-specific code to separate package and added signing options
utils/crypto/keychain/ledger/ New package containing ledger keychain implementation with signing logic
utils/crypto/keychain/cubesigner/ New CubeSigner keychain implementation

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment on lines +168 to +179
// shouldUseSignHash determines if the transaction requires hash signing on the Ledger device.
// Currently, TransferSubnetOwnershipTx requires hash signing.
func shouldUseSignHash(unsignedTxBytes []byte) (bool, error) {
var unsignedTx txs.UnsignedTx
_, err := txs.Codec.Unmarshal(unsignedTxBytes, &unsignedTx)
if err != nil {
return false, err
}

_, isTransferSubnetOwnership := unsignedTx.(*txs.TransferSubnetOwnershipTx)
return isTransferSubnetOwnership, nil
}
Copy link
Preview

Copilot AI Oct 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function name and comment describe checking if hash signing should be used, but the implementation only checks for TransferSubnetOwnershipTx. The comment should be more specific about this being the only transaction type requiring hash signing, or the function could be renamed to be more specific like isTransferSubnetOwnershipTx.

Copilot uses AI. Check for mistakes.

if options.ChainAlias != "P" && options.ChainAlias != "X" && options.ChainAlias != "C" {
return nil, fmt.Errorf("%w, got %q", ErrInvalidChainAlias, options.ChainAlias)
}
if options.ChainAlias != "C" && options.NetworkID == 0 {
Copy link
Preview

Copilot AI Oct 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The condition options.NetworkID == 0 may incorrectly reject valid network ID 0. If 0 is a valid network ID, this should check if the network ID was explicitly set rather than comparing against 0.

Suggested change
if options.ChainAlias != "C" && options.NetworkID == 0 {
// The NetworkIDSet field should be set to true by the signing option when NetworkID is explicitly set.
if options.ChainAlias != "C" && !options.NetworkIDSet {

Copilot uses AI. Check for mistakes.

Comment on lines 47 to 50
UncompressedPublicKeyLength = 65
// UncompressedPublicKeyPrefix is the prefix byte for uncompressed secp256k1 public keys.
// This byte indicates that the key is in uncompressed format.
UncompressedPublicKeyPrefix = 0x04
Copy link
Preview

Copilot AI Oct 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These constants are duplicated knowledge that likely exists elsewhere in the codebase. Consider importing these constants from a shared crypto package instead of redefining them.

Suggested change
UncompressedPublicKeyLength = 65
// UncompressedPublicKeyPrefix is the prefix byte for uncompressed secp256k1 public keys.
// This byte indicates that the key is in uncompressed format.
UncompressedPublicKeyPrefix = 0x04
UncompressedPublicKeyLength = avasecp256k1.PublicKeyBytes
// UncompressedPublicKeyPrefix is the prefix byte for uncompressed secp256k1 public keys.
// This byte indicates that the key is in uncompressed format.
UncompressedPublicKeyPrefix = avasecp256k1.PublicKeyPrefix

Copilot uses AI. Check for mistakes.

@felipemadero felipemadero changed the title refactor: move most ledger transactions from hash signing to full-bytes signingmove hash signing decision out of wallet into ledger keychain refactor: update ledger's hash signing requirements, and move hash signing decision down to keychain implementations Oct 3, 2025
@felipemadero felipemadero changed the base branch from master to cubesigner-keychain-on-master October 3, 2025 15:30
@felipemadero felipemadero marked this pull request as draft October 3, 2025 16:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: No status
Development

Successfully merging this pull request may close these issues.

1 participant