github.com/ava-labs/avalanchego@v1.11.11/wallet/chain/c/signer.go (about)

     1  // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package c
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  	"fmt"
    10  
    11  	"github.com/ava-labs/coreth/plugin/evm"
    12  	"github.com/ethereum/go-ethereum/common"
    13  
    14  	"github.com/ava-labs/avalanchego/database"
    15  	"github.com/ava-labs/avalanchego/ids"
    16  	"github.com/ava-labs/avalanchego/utils/crypto/keychain"
    17  	"github.com/ava-labs/avalanchego/utils/crypto/secp256k1"
    18  	"github.com/ava-labs/avalanchego/utils/hashing"
    19  	"github.com/ava-labs/avalanchego/utils/set"
    20  	"github.com/ava-labs/avalanchego/vms/components/avax"
    21  	"github.com/ava-labs/avalanchego/vms/components/verify"
    22  	"github.com/ava-labs/avalanchego/vms/secp256k1fx"
    23  )
    24  
    25  const version = 0
    26  
    27  var (
    28  	_ Signer = (*txSigner)(nil)
    29  
    30  	errUnknownInputType      = errors.New("unknown input type")
    31  	errUnknownCredentialType = errors.New("unknown credential type")
    32  	errUnknownOutputType     = errors.New("unknown output type")
    33  	errInvalidUTXOSigIndex   = errors.New("invalid UTXO signature index")
    34  
    35  	emptySig [secp256k1.SignatureLen]byte
    36  )
    37  
    38  type Signer interface {
    39  	// SignAtomic adds as many missing signatures as possible to the provided
    40  	// transaction.
    41  	//
    42  	// If there are already some signatures on the transaction, those signatures
    43  	// will not be removed.
    44  	//
    45  	// If the signer doesn't have the ability to provide a required signature,
    46  	// the signature slot will be skipped without reporting an error.
    47  	SignAtomic(ctx context.Context, tx *evm.Tx) error
    48  }
    49  
    50  type EthKeychain interface {
    51  	// The returned Signer can provide a signature for [addr]
    52  	GetEth(addr common.Address) (keychain.Signer, bool)
    53  	// Returns the set of addresses for which the accessor keeps an associated
    54  	// signer
    55  	EthAddresses() set.Set[common.Address]
    56  }
    57  
    58  type SignerBackend interface {
    59  	GetUTXO(ctx context.Context, chainID, utxoID ids.ID) (*avax.UTXO, error)
    60  }
    61  
    62  type txSigner struct {
    63  	avaxKC  keychain.Keychain
    64  	ethKC   EthKeychain
    65  	backend SignerBackend
    66  }
    67  
    68  func NewSigner(avaxKC keychain.Keychain, ethKC EthKeychain, backend SignerBackend) Signer {
    69  	return &txSigner{
    70  		avaxKC:  avaxKC,
    71  		ethKC:   ethKC,
    72  		backend: backend,
    73  	}
    74  }
    75  
    76  func (s *txSigner) SignAtomic(ctx context.Context, tx *evm.Tx) error {
    77  	switch utx := tx.UnsignedAtomicTx.(type) {
    78  	case *evm.UnsignedImportTx:
    79  		signers, err := s.getImportSigners(ctx, utx.SourceChain, utx.ImportedInputs)
    80  		if err != nil {
    81  			return err
    82  		}
    83  		return sign(tx, true, signers)
    84  	case *evm.UnsignedExportTx:
    85  		signers := s.getExportSigners(utx.Ins)
    86  		return sign(tx, true, signers)
    87  	default:
    88  		return fmt.Errorf("%w: %T", errUnknownTxType, tx)
    89  	}
    90  }
    91  
    92  func (s *txSigner) getImportSigners(ctx context.Context, sourceChainID ids.ID, ins []*avax.TransferableInput) ([][]keychain.Signer, error) {
    93  	txSigners := make([][]keychain.Signer, len(ins))
    94  	for credIndex, transferInput := range ins {
    95  		input, ok := transferInput.In.(*secp256k1fx.TransferInput)
    96  		if !ok {
    97  			return nil, errUnknownInputType
    98  		}
    99  
   100  		inputSigners := make([]keychain.Signer, len(input.SigIndices))
   101  		txSigners[credIndex] = inputSigners
   102  
   103  		utxoID := transferInput.InputID()
   104  		utxo, err := s.backend.GetUTXO(ctx, sourceChainID, utxoID)
   105  		if err == database.ErrNotFound {
   106  			// If we don't have access to the UTXO, then we can't sign this
   107  			// transaction. However, we can attempt to partially sign it.
   108  			continue
   109  		}
   110  		if err != nil {
   111  			return nil, err
   112  		}
   113  
   114  		out, ok := utxo.Out.(*secp256k1fx.TransferOutput)
   115  		if !ok {
   116  			return nil, errUnknownOutputType
   117  		}
   118  
   119  		for sigIndex, addrIndex := range input.SigIndices {
   120  			if addrIndex >= uint32(len(out.Addrs)) {
   121  				return nil, errInvalidUTXOSigIndex
   122  			}
   123  
   124  			addr := out.Addrs[addrIndex]
   125  			key, ok := s.avaxKC.Get(addr)
   126  			if !ok {
   127  				// If we don't have access to the key, then we can't sign this
   128  				// transaction. However, we can attempt to partially sign it.
   129  				continue
   130  			}
   131  			inputSigners[sigIndex] = key
   132  		}
   133  	}
   134  	return txSigners, nil
   135  }
   136  
   137  func (s *txSigner) getExportSigners(ins []evm.EVMInput) [][]keychain.Signer {
   138  	txSigners := make([][]keychain.Signer, len(ins))
   139  	for credIndex, input := range ins {
   140  		inputSigners := make([]keychain.Signer, 1)
   141  		txSigners[credIndex] = inputSigners
   142  
   143  		key, ok := s.ethKC.GetEth(input.Address)
   144  		if !ok {
   145  			// If we don't have access to the key, then we can't sign this
   146  			// transaction. However, we can attempt to partially sign it.
   147  			continue
   148  		}
   149  		inputSigners[0] = key
   150  	}
   151  	return txSigners
   152  }
   153  
   154  func SignUnsignedAtomic(ctx context.Context, signer Signer, utx evm.UnsignedAtomicTx) (*evm.Tx, error) {
   155  	tx := &evm.Tx{UnsignedAtomicTx: utx}
   156  	return tx, signer.SignAtomic(ctx, tx)
   157  }
   158  
   159  // TODO: remove [signHash] after the ledger supports signing all transactions.
   160  func sign(tx *evm.Tx, signHash bool, txSigners [][]keychain.Signer) error {
   161  	unsignedBytes, err := evm.Codec.Marshal(version, &tx.UnsignedAtomicTx)
   162  	if err != nil {
   163  		return fmt.Errorf("couldn't marshal unsigned tx: %w", err)
   164  	}
   165  	unsignedHash := hashing.ComputeHash256(unsignedBytes)
   166  
   167  	if expectedLen := len(txSigners); expectedLen != len(tx.Creds) {
   168  		tx.Creds = make([]verify.Verifiable, expectedLen)
   169  	}
   170  
   171  	sigCache := make(map[ids.ShortID][secp256k1.SignatureLen]byte)
   172  	for credIndex, inputSigners := range txSigners {
   173  		credIntf := tx.Creds[credIndex]
   174  		if credIntf == nil {
   175  			credIntf = &secp256k1fx.Credential{}
   176  			tx.Creds[credIndex] = credIntf
   177  		}
   178  
   179  		cred, ok := credIntf.(*secp256k1fx.Credential)
   180  		if !ok {
   181  			return errUnknownCredentialType
   182  		}
   183  		if expectedLen := len(inputSigners); expectedLen != len(cred.Sigs) {
   184  			cred.Sigs = make([][secp256k1.SignatureLen]byte, expectedLen)
   185  		}
   186  
   187  		for sigIndex, signer := range inputSigners {
   188  			if signer == nil {
   189  				// If we don't have access to the key, then we can't sign this
   190  				// transaction. However, we can attempt to partially sign it.
   191  				continue
   192  			}
   193  			addr := signer.Address()
   194  			if sig := cred.Sigs[sigIndex]; sig != emptySig {
   195  				// If this signature has already been populated, we can just
   196  				// copy the needed signature for the future.
   197  				sigCache[addr] = sig
   198  				continue
   199  			}
   200  
   201  			if sig, exists := sigCache[addr]; exists {
   202  				// If this key has already produced a signature, we can just
   203  				// copy the previous signature.
   204  				cred.Sigs[sigIndex] = sig
   205  				continue
   206  			}
   207  
   208  			var sig []byte
   209  			if signHash {
   210  				sig, err = signer.SignHash(unsignedHash)
   211  			} else {
   212  				sig, err = signer.Sign(unsignedBytes)
   213  			}
   214  			if err != nil {
   215  				return fmt.Errorf("problem signing tx: %w", err)
   216  			}
   217  			copy(cred.Sigs[sigIndex][:], sig)
   218  			sigCache[addr] = cred.Sigs[sigIndex]
   219  		}
   220  	}
   221  
   222  	signedBytes, err := evm.Codec.Marshal(version, tx)
   223  	if err != nil {
   224  		return fmt.Errorf("couldn't marshal tx: %w", err)
   225  	}
   226  	tx.Initialize(unsignedBytes, signedBytes)
   227  	return nil
   228  }