github.com/ava-labs/avalanchego@v1.11.11/vms/secp256k1fx/fx.go (about) 1 // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. 2 // See the file LICENSE for licensing terms. 3 4 package secp256k1fx 5 6 import ( 7 "errors" 8 "fmt" 9 10 "github.com/ava-labs/avalanchego/cache" 11 "github.com/ava-labs/avalanchego/ids" 12 "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" 13 "github.com/ava-labs/avalanchego/utils/hashing" 14 "github.com/ava-labs/avalanchego/vms/components/verify" 15 ) 16 17 const ( 18 defaultCacheSize = 256 19 ) 20 21 var ( 22 ErrWrongVMType = errors.New("wrong vm type") 23 ErrWrongTxType = errors.New("wrong tx type") 24 ErrWrongOpType = errors.New("wrong operation type") 25 ErrWrongUTXOType = errors.New("wrong utxo type") 26 ErrWrongInputType = errors.New("wrong input type") 27 ErrWrongCredentialType = errors.New("wrong credential type") 28 ErrWrongOwnerType = errors.New("wrong owner type") 29 ErrMismatchedAmounts = errors.New("utxo amount and input amount are not equal") 30 ErrWrongNumberOfUTXOs = errors.New("wrong number of utxos for the operation") 31 ErrWrongMintCreated = errors.New("wrong mint output created from the operation") 32 ErrTimelocked = errors.New("output is time locked") 33 ErrTooManySigners = errors.New("input has more signers than expected") 34 ErrTooFewSigners = errors.New("input has less signers than expected") 35 ErrInputOutputIndexOutOfBounds = errors.New("input referenced a nonexistent address in the output") 36 ErrInputCredentialSignersMismatch = errors.New("input expected a different number of signers than provided in the credential") 37 ErrWrongSig = errors.New("wrong signature") 38 ) 39 40 // Fx describes the secp256k1 feature extension 41 type Fx struct { 42 secp256k1.RecoverCache 43 44 VM VM 45 bootstrapped bool 46 } 47 48 func (fx *Fx) Initialize(vmIntf interface{}) error { 49 if err := fx.InitializeVM(vmIntf); err != nil { 50 return err 51 } 52 53 log := fx.VM.Logger() 54 log.Debug("initializing secp256k1 fx") 55 56 fx.RecoverCache = secp256k1.RecoverCache{ 57 LRU: cache.LRU[ids.ID, *secp256k1.PublicKey]{ 58 Size: defaultCacheSize, 59 }, 60 } 61 c := fx.VM.CodecRegistry() 62 return errors.Join( 63 c.RegisterType(&TransferInput{}), 64 c.RegisterType(&MintOutput{}), 65 c.RegisterType(&TransferOutput{}), 66 c.RegisterType(&MintOperation{}), 67 c.RegisterType(&Credential{}), 68 ) 69 } 70 71 func (fx *Fx) InitializeVM(vmIntf interface{}) error { 72 vm, ok := vmIntf.(VM) 73 if !ok { 74 return ErrWrongVMType 75 } 76 fx.VM = vm 77 return nil 78 } 79 80 func (*Fx) Bootstrapping() error { 81 return nil 82 } 83 84 func (fx *Fx) Bootstrapped() error { 85 fx.bootstrapped = true 86 return nil 87 } 88 89 // VerifyPermission returns nil iff [credIntf] proves that [controlGroup] assents to [txIntf] 90 func (fx *Fx) VerifyPermission(txIntf, inIntf, credIntf, ownerIntf interface{}) error { 91 tx, ok := txIntf.(UnsignedTx) 92 if !ok { 93 return ErrWrongTxType 94 } 95 in, ok := inIntf.(*Input) 96 if !ok { 97 return ErrWrongInputType 98 } 99 cred, ok := credIntf.(*Credential) 100 if !ok { 101 return ErrWrongCredentialType 102 } 103 owner, ok := ownerIntf.(*OutputOwners) 104 if !ok { 105 return ErrWrongOwnerType 106 } 107 if err := verify.All(in, cred, owner); err != nil { 108 return err 109 } 110 return fx.VerifyCredentials(tx, in, cred, owner) 111 } 112 113 func (fx *Fx) VerifyOperation(txIntf, opIntf, credIntf interface{}, utxosIntf []interface{}) error { 114 tx, ok := txIntf.(UnsignedTx) 115 if !ok { 116 return ErrWrongTxType 117 } 118 op, ok := opIntf.(*MintOperation) 119 if !ok { 120 return ErrWrongOpType 121 } 122 cred, ok := credIntf.(*Credential) 123 if !ok { 124 return ErrWrongCredentialType 125 } 126 if len(utxosIntf) != 1 { 127 return ErrWrongNumberOfUTXOs 128 } 129 out, ok := utxosIntf[0].(*MintOutput) 130 if !ok { 131 return ErrWrongUTXOType 132 } 133 return fx.verifyOperation(tx, op, cred, out) 134 } 135 136 func (fx *Fx) verifyOperation(tx UnsignedTx, op *MintOperation, cred *Credential, utxo *MintOutput) error { 137 if err := verify.All(op, cred, utxo); err != nil { 138 return err 139 } 140 if !utxo.Equals(&op.MintOutput.OutputOwners) { 141 return ErrWrongMintCreated 142 } 143 return fx.VerifyCredentials(tx, &op.MintInput, cred, &utxo.OutputOwners) 144 } 145 146 func (fx *Fx) VerifyTransfer(txIntf, inIntf, credIntf, utxoIntf interface{}) error { 147 tx, ok := txIntf.(UnsignedTx) 148 if !ok { 149 return ErrWrongTxType 150 } 151 in, ok := inIntf.(*TransferInput) 152 if !ok { 153 return ErrWrongInputType 154 } 155 cred, ok := credIntf.(*Credential) 156 if !ok { 157 return ErrWrongCredentialType 158 } 159 out, ok := utxoIntf.(*TransferOutput) 160 if !ok { 161 return ErrWrongUTXOType 162 } 163 return fx.VerifySpend(tx, in, cred, out) 164 } 165 166 // VerifySpend ensures that the utxo can be sent to any address 167 func (fx *Fx) VerifySpend(utx UnsignedTx, in *TransferInput, cred *Credential, utxo *TransferOutput) error { 168 if err := verify.All(utxo, in, cred); err != nil { 169 return err 170 } else if utxo.Amt != in.Amt { 171 return fmt.Errorf("%w: %d != %d", ErrMismatchedAmounts, utxo.Amt, in.Amt) 172 } 173 174 return fx.VerifyCredentials(utx, &in.Input, cred, &utxo.OutputOwners) 175 } 176 177 // VerifyCredentials ensures that the output can be spent by the input with the 178 // credential. A nil return values means the output can be spent. 179 func (fx *Fx) VerifyCredentials(utx UnsignedTx, in *Input, cred *Credential, out *OutputOwners) error { 180 numSigs := len(in.SigIndices) 181 switch { 182 case out.Locktime > fx.VM.Clock().Unix(): 183 return ErrTimelocked 184 case out.Threshold < uint32(numSigs): 185 return ErrTooManySigners 186 case out.Threshold > uint32(numSigs): 187 return ErrTooFewSigners 188 case numSigs != len(cred.Sigs): 189 return ErrInputCredentialSignersMismatch 190 case !fx.bootstrapped: // disable signature verification during bootstrapping 191 return nil 192 } 193 194 txHash := hashing.ComputeHash256(utx.Bytes()) 195 for i, index := range in.SigIndices { 196 // Make sure the input references an address that exists 197 if index >= uint32(len(out.Addrs)) { 198 return ErrInputOutputIndexOutOfBounds 199 } 200 // Make sure each signature in the signature list is from an owner of 201 // the output being consumed 202 sig := cred.Sigs[i] 203 pk, err := fx.RecoverPublicKeyFromHash(txHash, sig[:]) 204 if err != nil { 205 return err 206 } 207 if expectedAddress := out.Addrs[index]; expectedAddress != pk.Address() { 208 return fmt.Errorf("%w: expected signature from %s but got from %s", 209 ErrWrongSig, 210 expectedAddress, 211 pk.Address()) 212 } 213 } 214 215 return nil 216 } 217 218 // CreateOutput creates a new output with the provided control group worth 219 // the specified amount 220 func (*Fx) CreateOutput(amount uint64, ownerIntf interface{}) (interface{}, error) { 221 owner, ok := ownerIntf.(*OutputOwners) 222 if !ok { 223 return nil, ErrWrongOwnerType 224 } 225 if err := owner.Verify(); err != nil { 226 return nil, err 227 } 228 return &TransferOutput{ 229 Amt: amount, 230 OutputOwners: *owner, 231 }, nil 232 }