github.com/MetalBlockchain/metalgo@v1.11.9/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/MetalBlockchain/metalgo/cache"
    11  	"github.com/MetalBlockchain/metalgo/ids"
    12  	"github.com/MetalBlockchain/metalgo/utils/crypto/secp256k1"
    13  	"github.com/MetalBlockchain/metalgo/utils/hashing"
    14  	"github.com/MetalBlockchain/metalgo/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  }