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 }