github.com/MetalBlockchain/metalgo@v1.11.9/vms/platformvm/txs/tx.go (about) 1 // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. 2 // See the file LICENSE for licensing terms. 3 4 package txs 5 6 import ( 7 "errors" 8 "fmt" 9 10 "github.com/MetalBlockchain/metalgo/codec" 11 "github.com/MetalBlockchain/metalgo/ids" 12 "github.com/MetalBlockchain/metalgo/network/p2p/gossip" 13 "github.com/MetalBlockchain/metalgo/snow" 14 "github.com/MetalBlockchain/metalgo/utils/crypto/secp256k1" 15 "github.com/MetalBlockchain/metalgo/utils/hashing" 16 "github.com/MetalBlockchain/metalgo/utils/set" 17 "github.com/MetalBlockchain/metalgo/vms/components/avax" 18 "github.com/MetalBlockchain/metalgo/vms/components/verify" 19 "github.com/MetalBlockchain/metalgo/vms/secp256k1fx" 20 ) 21 22 var ( 23 _ gossip.Gossipable = (*Tx)(nil) 24 25 ErrNilSignedTx = errors.New("nil signed tx is not valid") 26 27 errSignedTxNotInitialized = errors.New("signed tx was never initialized and is not valid") 28 ) 29 30 // Tx is a signed transaction 31 type Tx struct { 32 // The body of this transaction 33 Unsigned UnsignedTx `serialize:"true" json:"unsignedTx"` 34 35 // The credentials of this transaction 36 Creds []verify.Verifiable `serialize:"true" json:"credentials"` 37 38 TxID ids.ID `json:"id"` 39 bytes []byte 40 } 41 42 func NewSigned( 43 unsigned UnsignedTx, 44 c codec.Manager, 45 signers [][]*secp256k1.PrivateKey, 46 ) (*Tx, error) { 47 res := &Tx{Unsigned: unsigned} 48 return res, res.Sign(c, signers) 49 } 50 51 func (tx *Tx) Initialize(c codec.Manager) error { 52 signedBytes, err := c.Marshal(CodecVersion, tx) 53 if err != nil { 54 return fmt.Errorf("couldn't marshal ProposalTx: %w", err) 55 } 56 57 unsignedBytesLen, err := c.Size(CodecVersion, &tx.Unsigned) 58 if err != nil { 59 return fmt.Errorf("couldn't calculate UnsignedTx marshal length: %w", err) 60 } 61 62 unsignedBytes := signedBytes[:unsignedBytesLen] 63 tx.SetBytes(unsignedBytes, signedBytes) 64 return nil 65 } 66 67 func (tx *Tx) SetBytes(unsignedBytes, signedBytes []byte) { 68 tx.Unsigned.SetBytes(unsignedBytes) 69 tx.bytes = signedBytes 70 tx.TxID = hashing.ComputeHash256Array(signedBytes) 71 } 72 73 // Parse signed tx starting from its byte representation. 74 // Note: We explicitly pass the codec in Parse since we may need to parse 75 // P-Chain genesis txs whose length exceed the max length of txs.Codec. 76 func Parse(c codec.Manager, signedBytes []byte) (*Tx, error) { 77 tx := &Tx{} 78 if _, err := c.Unmarshal(signedBytes, tx); err != nil { 79 return nil, fmt.Errorf("couldn't parse tx: %w", err) 80 } 81 82 unsignedBytesLen, err := c.Size(CodecVersion, &tx.Unsigned) 83 if err != nil { 84 return nil, fmt.Errorf("couldn't calculate UnsignedTx marshal length: %w", err) 85 } 86 87 unsignedBytes := signedBytes[:unsignedBytesLen] 88 tx.SetBytes(unsignedBytes, signedBytes) 89 return tx, nil 90 } 91 92 func (tx *Tx) Bytes() []byte { 93 return tx.bytes 94 } 95 96 func (tx *Tx) Size() int { 97 return len(tx.bytes) 98 } 99 100 func (tx *Tx) ID() ids.ID { 101 return tx.TxID 102 } 103 104 func (tx *Tx) GossipID() ids.ID { 105 return tx.TxID 106 } 107 108 // UTXOs returns the UTXOs transaction is producing. 109 func (tx *Tx) UTXOs() []*avax.UTXO { 110 outs := tx.Unsigned.Outputs() 111 utxos := make([]*avax.UTXO, len(outs)) 112 for i, out := range outs { 113 utxos[i] = &avax.UTXO{ 114 UTXOID: avax.UTXOID{ 115 TxID: tx.TxID, 116 OutputIndex: uint32(i), 117 }, 118 Asset: avax.Asset{ID: out.AssetID()}, 119 Out: out.Out, 120 } 121 } 122 return utxos 123 } 124 125 // InputIDs returns the set of inputs this transaction consumes 126 func (tx *Tx) InputIDs() set.Set[ids.ID] { 127 return tx.Unsigned.InputIDs() 128 } 129 130 func (tx *Tx) SyntacticVerify(ctx *snow.Context) error { 131 switch { 132 case tx == nil: 133 return ErrNilSignedTx 134 case tx.TxID == ids.Empty: 135 return errSignedTxNotInitialized 136 default: 137 return tx.Unsigned.SyntacticVerify(ctx) 138 } 139 } 140 141 // Sign this transaction with the provided signers 142 // Note: We explicitly pass the codec in Sign since we may need to sign P-Chain 143 // genesis txs whose length exceed the max length of txs.Codec. 144 func (tx *Tx) Sign(c codec.Manager, signers [][]*secp256k1.PrivateKey) error { 145 unsignedBytes, err := c.Marshal(CodecVersion, &tx.Unsigned) 146 if err != nil { 147 return fmt.Errorf("couldn't marshal UnsignedTx: %w", err) 148 } 149 150 // Attach credentials 151 hash := hashing.ComputeHash256(unsignedBytes) 152 for _, keys := range signers { 153 cred := &secp256k1fx.Credential{ 154 Sigs: make([][secp256k1.SignatureLen]byte, len(keys)), 155 } 156 for i, key := range keys { 157 sig, err := key.SignHash(hash) // Sign hash 158 if err != nil { 159 return fmt.Errorf("problem generating credential: %w", err) 160 } 161 copy(cred.Sigs[i][:], sig) 162 } 163 tx.Creds = append(tx.Creds, cred) // Attach credential 164 } 165 166 signedBytes, err := c.Marshal(CodecVersion, tx) 167 if err != nil { 168 return fmt.Errorf("couldn't marshal ProposalTx: %w", err) 169 } 170 tx.SetBytes(unsignedBytes, signedBytes) 171 return nil 172 }