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 }