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

     1  // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package signer
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  	"fmt"
    10  
    11  	"github.com/ava-labs/avalanchego/database"
    12  	"github.com/ava-labs/avalanchego/ids"
    13  	"github.com/ava-labs/avalanchego/utils/crypto/keychain"
    14  	"github.com/ava-labs/avalanchego/utils/crypto/secp256k1"
    15  	"github.com/ava-labs/avalanchego/vms/avm/fxs"
    16  	"github.com/ava-labs/avalanchego/vms/avm/txs"
    17  	"github.com/ava-labs/avalanchego/vms/components/avax"
    18  	"github.com/ava-labs/avalanchego/vms/components/verify"
    19  	"github.com/ava-labs/avalanchego/vms/nftfx"
    20  	"github.com/ava-labs/avalanchego/vms/propertyfx"
    21  	"github.com/ava-labs/avalanchego/vms/secp256k1fx"
    22  	"github.com/ava-labs/avalanchego/wallet/chain/x/builder"
    23  )
    24  
    25  var (
    26  	_ txs.Visitor = (*visitor)(nil)
    27  
    28  	ErrUnknownInputType      = errors.New("unknown input type")
    29  	ErrUnknownOpType         = errors.New("unknown operation type")
    30  	ErrInvalidNumUTXOsInOp   = errors.New("invalid number of UTXOs in operation")
    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  // visitor handles signing transactions for the signer
    39  type visitor struct {
    40  	kc      keychain.Keychain
    41  	backend Backend
    42  	ctx     context.Context
    43  	tx      *txs.Tx
    44  }
    45  
    46  func (s *visitor) BaseTx(tx *txs.BaseTx) error {
    47  	txCreds, txSigners, err := s.getSigners(s.ctx, tx.BlockchainID, tx.Ins)
    48  	if err != nil {
    49  		return err
    50  	}
    51  	return sign(s.tx, txCreds, txSigners)
    52  }
    53  
    54  func (s *visitor) CreateAssetTx(tx *txs.CreateAssetTx) error {
    55  	txCreds, txSigners, err := s.getSigners(s.ctx, tx.BlockchainID, tx.Ins)
    56  	if err != nil {
    57  		return err
    58  	}
    59  	return sign(s.tx, txCreds, txSigners)
    60  }
    61  
    62  func (s *visitor) OperationTx(tx *txs.OperationTx) error {
    63  	txCreds, txSigners, err := s.getSigners(s.ctx, tx.BlockchainID, tx.Ins)
    64  	if err != nil {
    65  		return err
    66  	}
    67  	txOpsCreds, txOpsSigners, err := s.getOpsSigners(s.ctx, tx.BlockchainID, tx.Ops)
    68  	if err != nil {
    69  		return err
    70  	}
    71  	txCreds = append(txCreds, txOpsCreds...)
    72  	txSigners = append(txSigners, txOpsSigners...)
    73  	return sign(s.tx, txCreds, txSigners)
    74  }
    75  
    76  func (s *visitor) ImportTx(tx *txs.ImportTx) error {
    77  	txCreds, txSigners, err := s.getSigners(s.ctx, tx.BlockchainID, tx.Ins)
    78  	if err != nil {
    79  		return err
    80  	}
    81  	txImportCreds, txImportSigners, err := s.getSigners(s.ctx, tx.SourceChain, tx.ImportedIns)
    82  	if err != nil {
    83  		return err
    84  	}
    85  	txCreds = append(txCreds, txImportCreds...)
    86  	txSigners = append(txSigners, txImportSigners...)
    87  	return sign(s.tx, txCreds, txSigners)
    88  }
    89  
    90  func (s *visitor) ExportTx(tx *txs.ExportTx) error {
    91  	txCreds, txSigners, err := s.getSigners(s.ctx, tx.BlockchainID, tx.Ins)
    92  	if err != nil {
    93  		return err
    94  	}
    95  	return sign(s.tx, txCreds, txSigners)
    96  }
    97  
    98  func (s *visitor) getSigners(ctx context.Context, sourceChainID ids.ID, ins []*avax.TransferableInput) ([]verify.Verifiable, [][]keychain.Signer, error) {
    99  	txCreds := make([]verify.Verifiable, len(ins))
   100  	txSigners := make([][]keychain.Signer, len(ins))
   101  	for credIndex, transferInput := range ins {
   102  		txCreds[credIndex] = &secp256k1fx.Credential{}
   103  		input, ok := transferInput.In.(*secp256k1fx.TransferInput)
   104  		if !ok {
   105  			return nil, nil, ErrUnknownInputType
   106  		}
   107  
   108  		inputSigners := make([]keychain.Signer, len(input.SigIndices))
   109  		txSigners[credIndex] = inputSigners
   110  
   111  		utxoID := transferInput.InputID()
   112  		utxo, err := s.backend.GetUTXO(ctx, sourceChainID, utxoID)
   113  		if err == database.ErrNotFound {
   114  			// If we don't have access to the UTXO, then we can't sign this
   115  			// transaction. However, we can attempt to partially sign it.
   116  			continue
   117  		}
   118  		if err != nil {
   119  			return nil, nil, err
   120  		}
   121  
   122  		out, ok := utxo.Out.(*secp256k1fx.TransferOutput)
   123  		if !ok {
   124  			return nil, nil, ErrUnknownOutputType
   125  		}
   126  
   127  		for sigIndex, addrIndex := range input.SigIndices {
   128  			if addrIndex >= uint32(len(out.Addrs)) {
   129  				return nil, nil, ErrInvalidUTXOSigIndex
   130  			}
   131  
   132  			addr := out.Addrs[addrIndex]
   133  			key, ok := s.kc.Get(addr)
   134  			if !ok {
   135  				// If we don't have access to the key, then we can't sign this
   136  				// transaction. However, we can attempt to partially sign it.
   137  				continue
   138  			}
   139  			inputSigners[sigIndex] = key
   140  		}
   141  	}
   142  	return txCreds, txSigners, nil
   143  }
   144  
   145  func (s *visitor) getOpsSigners(ctx context.Context, sourceChainID ids.ID, ops []*txs.Operation) ([]verify.Verifiable, [][]keychain.Signer, error) {
   146  	txCreds := make([]verify.Verifiable, len(ops))
   147  	txSigners := make([][]keychain.Signer, len(ops))
   148  	for credIndex, op := range ops {
   149  		var input *secp256k1fx.Input
   150  		switch op := op.Op.(type) {
   151  		case *secp256k1fx.MintOperation:
   152  			txCreds[credIndex] = &secp256k1fx.Credential{}
   153  			input = &op.MintInput
   154  		case *nftfx.MintOperation:
   155  			txCreds[credIndex] = &nftfx.Credential{}
   156  			input = &op.MintInput
   157  		case *nftfx.TransferOperation:
   158  			txCreds[credIndex] = &nftfx.Credential{}
   159  			input = &op.Input
   160  		case *propertyfx.MintOperation:
   161  			txCreds[credIndex] = &propertyfx.Credential{}
   162  			input = &op.MintInput
   163  		case *propertyfx.BurnOperation:
   164  			txCreds[credIndex] = &propertyfx.Credential{}
   165  			input = &op.Input
   166  		default:
   167  			return nil, nil, ErrUnknownOpType
   168  		}
   169  
   170  		inputSigners := make([]keychain.Signer, len(input.SigIndices))
   171  		txSigners[credIndex] = inputSigners
   172  
   173  		if len(op.UTXOIDs) != 1 {
   174  			return nil, nil, ErrInvalidNumUTXOsInOp
   175  		}
   176  		utxoID := op.UTXOIDs[0].InputID()
   177  		utxo, err := s.backend.GetUTXO(ctx, sourceChainID, utxoID)
   178  		if err == database.ErrNotFound {
   179  			// If we don't have access to the UTXO, then we can't sign this
   180  			// transaction. However, we can attempt to partially sign it.
   181  			continue
   182  		}
   183  		if err != nil {
   184  			return nil, nil, err
   185  		}
   186  
   187  		var addrs []ids.ShortID
   188  		switch out := utxo.Out.(type) {
   189  		case *secp256k1fx.MintOutput:
   190  			addrs = out.Addrs
   191  		case *nftfx.MintOutput:
   192  			addrs = out.Addrs
   193  		case *nftfx.TransferOutput:
   194  			addrs = out.Addrs
   195  		case *propertyfx.MintOutput:
   196  			addrs = out.Addrs
   197  		case *propertyfx.OwnedOutput:
   198  			addrs = out.Addrs
   199  		default:
   200  			return nil, nil, ErrUnknownOutputType
   201  		}
   202  
   203  		for sigIndex, addrIndex := range input.SigIndices {
   204  			if addrIndex >= uint32(len(addrs)) {
   205  				return nil, nil, ErrInvalidUTXOSigIndex
   206  			}
   207  
   208  			addr := addrs[addrIndex]
   209  			key, ok := s.kc.Get(addr)
   210  			if !ok {
   211  				// If we don't have access to the key, then we can't sign this
   212  				// transaction. However, we can attempt to partially sign it.
   213  				continue
   214  			}
   215  			inputSigners[sigIndex] = key
   216  		}
   217  	}
   218  	return txCreds, txSigners, nil
   219  }
   220  
   221  func sign(tx *txs.Tx, creds []verify.Verifiable, txSigners [][]keychain.Signer) error {
   222  	codec := builder.Parser.Codec()
   223  	unsignedBytes, err := codec.Marshal(txs.CodecVersion, &tx.Unsigned)
   224  	if err != nil {
   225  		return fmt.Errorf("couldn't marshal unsigned tx: %w", err)
   226  	}
   227  
   228  	if expectedLen := len(txSigners); expectedLen != len(tx.Creds) {
   229  		tx.Creds = make([]*fxs.FxCredential, expectedLen)
   230  	}
   231  
   232  	sigCache := make(map[ids.ShortID][secp256k1.SignatureLen]byte)
   233  	for credIndex, inputSigners := range txSigners {
   234  		fxCred := tx.Creds[credIndex]
   235  		if fxCred == nil {
   236  			fxCred = &fxs.FxCredential{}
   237  			tx.Creds[credIndex] = fxCred
   238  		}
   239  		credIntf := fxCred.Credential
   240  		if credIntf == nil {
   241  			credIntf = creds[credIndex]
   242  			fxCred.Credential = credIntf
   243  		}
   244  
   245  		var cred *secp256k1fx.Credential
   246  		switch credImpl := credIntf.(type) {
   247  		case *secp256k1fx.Credential:
   248  			fxCred.FxID = secp256k1fx.ID
   249  			cred = credImpl
   250  		case *nftfx.Credential:
   251  			fxCred.FxID = nftfx.ID
   252  			cred = &credImpl.Credential
   253  		case *propertyfx.Credential:
   254  			fxCred.FxID = propertyfx.ID
   255  			cred = &credImpl.Credential
   256  		default:
   257  			return ErrUnknownCredentialType
   258  		}
   259  
   260  		if expectedLen := len(inputSigners); expectedLen != len(cred.Sigs) {
   261  			cred.Sigs = make([][secp256k1.SignatureLen]byte, expectedLen)
   262  		}
   263  
   264  		for sigIndex, signer := range inputSigners {
   265  			if signer == nil {
   266  				// If we don't have access to the key, then we can't sign this
   267  				// transaction. However, we can attempt to partially sign it.
   268  				continue
   269  			}
   270  			addr := signer.Address()
   271  			if sig := cred.Sigs[sigIndex]; sig != emptySig {
   272  				// If this signature has already been populated, we can just
   273  				// copy the needed signature for the future.
   274  				sigCache[addr] = sig
   275  				continue
   276  			}
   277  
   278  			if sig, exists := sigCache[addr]; exists {
   279  				// If this key has already produced a signature, we can just
   280  				// copy the previous signature.
   281  				cred.Sigs[sigIndex] = sig
   282  				continue
   283  			}
   284  
   285  			sig, err := signer.Sign(unsignedBytes)
   286  			if err != nil {
   287  				return fmt.Errorf("problem signing tx: %w", err)
   288  			}
   289  			copy(cred.Sigs[sigIndex][:], sig)
   290  			sigCache[addr] = cred.Sigs[sigIndex]
   291  		}
   292  	}
   293  
   294  	signedBytes, err := codec.Marshal(txs.CodecVersion, tx)
   295  	if err != nil {
   296  		return fmt.Errorf("couldn't marshal tx: %w", err)
   297  	}
   298  	tx.SetBytes(unsignedBytes, signedBytes)
   299  	return nil
   300  }