github.com/fozzysec/SiaPrime@v0.0.0-20190612043147-66c8e8d11fe3/types/transactions.go (about) 1 package types 2 3 // transaction.go defines the transaction type and all of the sub-fields of the 4 // transaction, as well as providing helper functions for working with 5 // transactions. The various IDs are designed such that, in a legal blockchain, 6 // it is cryptographically unlikely that any two objects would share an id. 7 8 import ( 9 "errors" 10 11 "SiaPrime/build" 12 "SiaPrime/crypto" 13 "SiaPrime/encoding" 14 ) 15 16 const ( 17 // SpecifierLen is the length in bytes of a Specifier. 18 SpecifierLen = 16 19 20 // UnlockHashChecksumSize is the size of the checksum used to verify 21 // human-readable addresses. It is not a crypytographically secure 22 // checksum, it's merely intended to prevent typos. 6 is chosen because it 23 // brings the total size of the address to 38 bytes, leaving 2 bytes for 24 // potential version additions in the future. 25 UnlockHashChecksumSize = 6 26 ) 27 28 // These Specifiers are used internally when calculating a type's ID. See 29 // Specifier for more details. 30 var ( 31 ErrTransactionIDWrongLen = errors.New("input has wrong length to be an encoded transaction id") 32 33 SpecifierClaimOutput = Specifier{'c', 'l', 'a', 'i', 'm', ' ', 'o', 'u', 't', 'p', 'u', 't'} 34 SpecifierFileContract = Specifier{'f', 'i', 'l', 'e', ' ', 'c', 'o', 'n', 't', 'r', 'a', 'c', 't'} 35 SpecifierFileContractRevision = Specifier{'f', 'i', 'l', 'e', ' ', 'c', 'o', 'n', 't', 'r', 'a', 'c', 't', ' ', 'r', 'e'} 36 SpecifierMinerFee = Specifier{'m', 'i', 'n', 'e', 'r', ' ', 'f', 'e', 'e'} 37 SpecifierMinerPayout = Specifier{'m', 'i', 'n', 'e', 'r', ' ', 'p', 'a', 'y', 'o', 'u', 't'} 38 SpecifierSiacoinInput = Specifier{'s', 'i', 'a', 'c', 'o', 'i', 'n', ' ', 'i', 'n', 'p', 'u', 't'} 39 SpecifierSiacoinOutput = Specifier{'s', 'i', 'a', 'c', 'o', 'i', 'n', ' ', 'o', 'u', 't', 'p', 'u', 't'} 40 SpecifierSiafundInput = Specifier{'s', 'i', 'a', 'f', 'u', 'n', 'd', ' ', 'i', 'n', 'p', 'u', 't'} 41 SpecifierSiafundOutput = Specifier{'s', 'i', 'a', 'f', 'u', 'n', 'd', ' ', 'o', 'u', 't', 'p', 'u', 't'} 42 SpecifierStorageProof = Specifier{'s', 't', 'o', 'r', 'a', 'g', 'e', ' ', 'p', 'r', 'o', 'o', 'f'} 43 SpecifierStorageProofOutput = Specifier{'s', 't', 'o', 'r', 'a', 'g', 'e', ' ', 'p', 'r', 'o', 'o', 'f'} 44 ) 45 46 type ( 47 // A Specifier is a fixed-length byte-array that serves two purposes. In 48 // the wire protocol, they are used to identify a particular encoding 49 // algorithm, signature algorithm, etc. This allows nodes to communicate on 50 // their own terms; for example, to reduce bandwidth costs, a node might 51 // only accept compressed messages. 52 // 53 // Internally, Specifiers are used to guarantee unique IDs. Various 54 // consensus types have an associated ID, calculated by hashing the data 55 // contained in the type. By prepending the data with Specifier, we can 56 // guarantee that distinct types will never produce the same hash. 57 Specifier [SpecifierLen]byte 58 59 // IDs are used to refer to a type without revealing its contents. They 60 // are constructed by hashing specific fields of the type, along with a 61 // Specifier. While all of these types are hashes, defining type aliases 62 // gives us type safety and makes the code more readable. 63 64 // TransactionID uniquely identifies a transaction 65 TransactionID crypto.Hash 66 // SiacoinOutputID uniquely identifies a siacoin output 67 SiacoinOutputID crypto.Hash 68 // SiafundOutputID uniquely identifies a siafund output 69 SiafundOutputID crypto.Hash 70 // FileContractID uniquely identifies a file contract 71 FileContractID crypto.Hash 72 // OutputID uniquely identifies an output 73 OutputID crypto.Hash 74 75 // A Transaction is an atomic component of a block. Transactions can contain 76 // inputs and outputs, file contracts, storage proofs, and even arbitrary 77 // data. They can also contain signatures to prove that a given party has 78 // approved the transaction, or at least a particular subset of it. 79 // 80 // Transactions can depend on other previous transactions in the same block, 81 // but transactions cannot spend outputs that they create or otherwise be 82 // self-dependent. 83 Transaction struct { 84 SiacoinInputs []SiacoinInput `json:"siacoininputs"` 85 SiacoinOutputs []SiacoinOutput `json:"siacoinoutputs"` 86 FileContracts []FileContract `json:"filecontracts"` 87 FileContractRevisions []FileContractRevision `json:"filecontractrevisions"` 88 StorageProofs []StorageProof `json:"storageproofs"` 89 SiafundInputs []SiafundInput `json:"siafundinputs"` 90 SiafundOutputs []SiafundOutput `json:"siafundoutputs"` 91 MinerFees []Currency `json:"minerfees"` 92 ArbitraryData [][]byte `json:"arbitrarydata"` 93 TransactionSignatures []TransactionSignature `json:"transactionsignatures"` 94 } 95 96 // A SiacoinInput consumes a SiacoinOutput and adds the siacoins to the set of 97 // siacoins that can be spent in the transaction. The ParentID points to the 98 // output that is getting consumed, and the UnlockConditions contain the rules 99 // for spending the output. The UnlockConditions must match the UnlockHash of 100 // the output. 101 SiacoinInput struct { 102 ParentID SiacoinOutputID `json:"parentid"` 103 UnlockConditions UnlockConditions `json:"unlockconditions"` 104 } 105 106 // A SiacoinOutput holds a volume of siacoins. Outputs must be spent 107 // atomically; that is, they must all be spent in the same transaction. The 108 // UnlockHash is the hash of the UnlockConditions that must be fulfilled 109 // in order to spend the output. 110 SiacoinOutput struct { 111 Value Currency `json:"value"` 112 UnlockHash UnlockHash `json:"unlockhash"` 113 } 114 115 // A SiafundInput consumes a SiafundOutput and adds the siafunds to the set of 116 // siafunds that can be spent in the transaction. The ParentID points to the 117 // output that is getting consumed, and the UnlockConditions contain the rules 118 // for spending the output. The UnlockConditions must match the UnlockHash of 119 // the output. 120 SiafundInput struct { 121 ParentID SiafundOutputID `json:"parentid"` 122 UnlockConditions UnlockConditions `json:"unlockconditions"` 123 ClaimUnlockHash UnlockHash `json:"claimunlockhash"` 124 } 125 126 // A SiafundOutput holds a volume of siafunds. Outputs must be spent 127 // atomically; that is, they must all be spent in the same transaction. The 128 // UnlockHash is the hash of a set of UnlockConditions that must be fulfilled 129 // in order to spend the output. 130 // 131 // When the SiafundOutput is spent, a SiacoinOutput is created, where: 132 // 133 // SiacoinOutput.Value := (SiafundPool - ClaimStart) / 10,000 * Value 134 // SiacoinOutput.UnlockHash := SiafundInput.ClaimUnlockHash 135 // 136 // When a SiafundOutput is put into a transaction, the ClaimStart must always 137 // equal zero. While the transaction is being processed, the ClaimStart is set 138 // to the value of the SiafundPool. 139 SiafundOutput struct { 140 Value Currency `json:"value"` 141 UnlockHash UnlockHash `json:"unlockhash"` 142 ClaimStart Currency `json:"claimstart"` 143 } 144 145 // An UnlockHash is a specially constructed hash of the UnlockConditions type. 146 // "Locked" values can be unlocked by providing the UnlockConditions that hash 147 // to a given UnlockHash. See UnlockConditions.UnlockHash for details on how the 148 // UnlockHash is constructed. 149 UnlockHash crypto.Hash 150 ) 151 152 // ID returns the id of a transaction, which is taken by marshalling all of the 153 // fields except for the signatures and taking the hash of the result. 154 func (t Transaction) ID() TransactionID { 155 // Get the transaction id by hashing all data minus the signatures. 156 var txid TransactionID 157 h := crypto.NewHash() 158 t.MarshalSiaNoSignatures(h) 159 h.Sum(txid[:0]) 160 161 // Sanity check in debug builds to make sure that the ids are going to be 162 // the same. 163 if build.DEBUG { 164 verify := TransactionID(crypto.HashAll( 165 t.SiacoinInputs, 166 t.SiacoinOutputs, 167 t.FileContracts, 168 t.FileContractRevisions, 169 t.StorageProofs, 170 t.SiafundInputs, 171 t.SiafundOutputs, 172 t.MinerFees, 173 t.ArbitraryData, 174 )) 175 176 if verify != txid { 177 panic("TransactionID is not marshalling correctly") 178 } 179 } 180 181 return txid 182 } 183 184 // SiacoinOutputID returns the ID of a siacoin output at the given index, 185 // which is calculated by hashing the concatenation of the SiacoinOutput 186 // Specifier, all of the fields in the transaction (except the signatures), 187 // and output index. 188 func (t Transaction) SiacoinOutputID(i uint64) SiacoinOutputID { 189 // Create the id. 190 var id SiacoinOutputID 191 h := crypto.NewHash() 192 h.Write(SpecifierSiacoinOutput[:]) 193 t.MarshalSiaNoSignatures(h) // Encode non-signature fields into hash. 194 encoding.WriteUint64(h, i) // Writes index of this output. 195 h.Sum(id[:0]) 196 197 // Sanity check - verify that the optimized code is always returning the 198 // same ids as the unoptimized code. 199 if build.DEBUG { 200 verificationID := SiacoinOutputID(crypto.HashAll( 201 SpecifierSiacoinOutput, 202 t.SiacoinInputs, 203 t.SiacoinOutputs, 204 t.FileContracts, 205 t.FileContractRevisions, 206 t.StorageProofs, 207 t.SiafundInputs, 208 t.SiafundOutputs, 209 t.MinerFees, 210 t.ArbitraryData, 211 i, 212 )) 213 if id != verificationID { 214 panic("SiacoinOutputID is not marshalling correctly") 215 } 216 } 217 218 return id 219 } 220 221 // FileContractID returns the ID of a file contract at the given index, which 222 // is calculated by hashing the concatenation of the FileContract Specifier, 223 // all of the fields in the transaction (except the signatures), and the 224 // contract index. 225 func (t Transaction) FileContractID(i uint64) FileContractID { 226 var id FileContractID 227 h := crypto.NewHash() 228 h.Write(SpecifierFileContract[:]) 229 t.MarshalSiaNoSignatures(h) // Encode non-signature fields into hash. 230 encoding.WriteUint64(h, i) // Writes index of this output. 231 h.Sum(id[:0]) 232 233 // Sanity check - verify that the optimized code is always returning the 234 // same ids as the unoptimized code. 235 if build.DEBUG { 236 verificationID := FileContractID(crypto.HashAll( 237 SpecifierFileContract, 238 t.SiacoinInputs, 239 t.SiacoinOutputs, 240 t.FileContracts, 241 t.FileContractRevisions, 242 t.StorageProofs, 243 t.SiafundInputs, 244 t.SiafundOutputs, 245 t.MinerFees, 246 t.ArbitraryData, 247 i, 248 )) 249 if id != verificationID { 250 panic("FileContractID is not marshalling correctly") 251 } 252 } 253 254 return id 255 } 256 257 // SiafundOutputID returns the ID of a SiafundOutput at the given index, which 258 // is calculated by hashing the concatenation of the SiafundOutput Specifier, 259 // all of the fields in the transaction (except the signatures), and output 260 // index. 261 func (t Transaction) SiafundOutputID(i uint64) SiafundOutputID { 262 var id SiafundOutputID 263 h := crypto.NewHash() 264 h.Write(SpecifierSiafundOutput[:]) 265 t.MarshalSiaNoSignatures(h) // Encode non-signature fields into hash. 266 encoding.WriteUint64(h, i) // Writes index of this output. 267 h.Sum(id[:0]) 268 269 // Sanity check - verify that the optimized code is always returning the 270 // same ids as the unoptimized code. 271 if build.DEBUG { 272 verificationID := SiafundOutputID(crypto.HashAll( 273 SpecifierSiafundOutput, 274 t.SiacoinInputs, 275 t.SiacoinOutputs, 276 t.FileContracts, 277 t.FileContractRevisions, 278 t.StorageProofs, 279 t.SiafundInputs, 280 t.SiafundOutputs, 281 t.MinerFees, 282 t.ArbitraryData, 283 i, 284 )) 285 if id != verificationID { 286 panic("SiafundOutputID is not marshalling correctly") 287 } 288 } 289 return id 290 } 291 292 // SiacoinOutputSum returns the sum of all the siacoin outputs in the 293 // transaction, which must match the sum of all the siacoin inputs. Siacoin 294 // outputs created by storage proofs and siafund outputs are not considered, as 295 // they were considered when the contract responsible for funding them was 296 // created. 297 func (t Transaction) SiacoinOutputSum() (sum Currency) { 298 // Add the siacoin outputs. 299 for _, sco := range t.SiacoinOutputs { 300 sum = sum.Add(sco.Value) 301 } 302 303 // Add the file contract payouts. 304 for _, fc := range t.FileContracts { 305 sum = sum.Add(fc.Payout) 306 } 307 308 // Add the miner fees. 309 for _, fee := range t.MinerFees { 310 sum = sum.Add(fee) 311 } 312 313 return 314 } 315 316 // SiaClaimOutputID returns the ID of the SiacoinOutput that is created when 317 // the siafund output is spent. The ID is the hash the SiafundOutputID. 318 func (id SiafundOutputID) SiaClaimOutputID() SiacoinOutputID { 319 return SiacoinOutputID(crypto.HashObject(id)) 320 }