UNPKG

6.37 kBJavaScriptView Raw
1/**
2 * Transaction Verifier
3 * ====================
4 */
5'use strict'
6
7import { Bn } from './bn'
8import { Block } from './block'
9import { Interp } from './interp'
10import { Struct } from './struct'
11import { Tx } from './tx'
12import { Workers } from './workers'
13
14class TxVerifier extends Struct {
15 constructor (tx, txOutMap, errStr, interp) {
16 super({ tx, txOutMap, errStr, interp })
17 }
18
19 /**
20 * Verifies that the transaction is valid both by performing basic checks, such
21 * as ensuring that no two inputs are the same, as well as by verifying every
22 * script. The two checks are checkStr, which is analagous to bitcoind's
23 * CheckTransaction, and verifyStr, which runs the script interpreter.
24 *
25 * This does NOT check that any possible claimed fees are accurate; checking
26 * that the fees are accurate requires checking that the input transactions are
27 * valid, which is not performed by this test. That check is done with the
28 * normal verify function.
29 */
30 verify (flags = Interp.SCRIPT_ENABLE_SIGHASH_FORKID) {
31 return !this.checkStr() && !this.verifyStr(flags)
32 }
33
34 /*
35 * Returns true if the transaction was verified successfully (that is no
36 * error was found), and false otherwise. In case an error was found the
37 * error message can be accessed by calling this.getDebugString().
38 */
39 async asyncVerify (flags) {
40 const verifyStr = await this.asyncVerifyStr(flags)
41 return !this.checkStr() && !verifyStr
42 }
43
44 /**
45 * Convenience method to verify a transaction.
46 */
47 static verify (tx, txOutMap, flags) {
48 return new TxVerifier(tx, txOutMap).verify(flags)
49 }
50
51 static asyncVerify (tx, txOutMap, flags) {
52 return new TxVerifier(tx, txOutMap).asyncVerify(flags)
53 }
54
55 /**
56 * Check that a transaction passes basic sanity tests. If not, return a string
57 * describing the error. This function contains the same logic as
58 * CheckTransaction in bitcoin core.
59 */
60 checkStr () {
61 // Basic checks that don't depend on any context
62 if (this.tx.txIns.length === 0 || this.tx.txInsVi.toNumber() === 0) {
63 this.errStr = 'transaction txIns empty'
64 return this.errStr
65 }
66 if (this.tx.txOuts.length === 0 || this.tx.txOutsVi.toNumber() === 0) {
67 this.errStr = 'transaction txOuts empty'
68 return this.errStr
69 }
70
71 // Size limits
72 if (this.tx.toBuffer().length > Block.MAX_BLOCK_SIZE) {
73 this.errStr = 'transaction over the maximum block size'
74 return this.errStr
75 }
76
77 // Check for negative or overflow output values
78 let valueoutbn = new Bn(0)
79 for (let i = 0; i < this.tx.txOuts.length; i++) {
80 const txOut = this.tx.txOuts[i]
81 if (txOut.valueBn.lt(0)) {
82 this.errStr = 'transaction txOut ' + i + ' negative'
83 return this.errStr
84 }
85 if (txOut.valueBn.gt(Tx.MAX_MONEY)) {
86 this.errStr = 'transaction txOut ' + i + ' greater than MAX_MONEY'
87 return this.errStr
88 }
89 valueoutbn = valueoutbn.add(txOut.valueBn)
90 if (valueoutbn.gt(Tx.MAX_MONEY)) {
91 this.errStr =
92 'transaction txOut ' + i + ' total output greater than MAX_MONEY'
93 return this.errStr
94 }
95 }
96
97 // Check for duplicate inputs
98 const txInmap = {}
99 for (let i = 0; i < this.tx.txIns.length; i++) {
100 const txIn = this.tx.txIns[i]
101 const inputid = txIn.txHashBuf.toString('hex') + ':' + txIn.txOutNum
102 if (txInmap[inputid] !== undefined) {
103 this.errStr = 'transaction input ' + i + ' duplicate input'
104 return this.errStr
105 }
106 txInmap[inputid] = true
107 }
108
109 if (this.tx.isCoinbase()) {
110 const buf = this.tx.txIns[0].script.toBuffer()
111 if (buf.length < 2 || buf.length > 100) {
112 this.errStr = 'coinbase trasaction script size invalid'
113 return this.errStr
114 }
115 } else {
116 for (let i = 0; i < this.tx.txIns.length; i++) {
117 if (this.tx.txIns[i].hasNullInput()) {
118 this.errStr = 'transaction input ' + i + ' has null input'
119 return this.errStr
120 }
121 }
122 }
123 return false
124 }
125
126 /**
127 * verify the transaction inputs by running the script interpreter. Returns a
128 * string of the script interpreter is invalid, otherwise returns false.
129 */
130 verifyStr (flags) {
131 for (let i = 0; i < this.tx.txIns.length; i++) {
132 if (!this.verifyNIn(i, flags)) {
133 this.errStr = 'input ' + i + ' failed script verify'
134 return this.errStr
135 }
136 }
137 return false
138 }
139
140 async asyncVerifyStr (flags) {
141 for (let i = 0; i < this.tx.txIns.length; i++) {
142 const verifyNIn = await this.asyncVerifyNIn(i, flags)
143 if (!verifyNIn) {
144 this.errStr = 'input ' + i + ' failed script verify'
145 return this.errStr
146 }
147 }
148 return false
149 }
150
151 /**
152 * Verify a particular input by running the script interpreter. Returns true if
153 * the input is valid, false otherwise.
154 */
155 verifyNIn (nIn, flags) {
156 const txIn = this.tx.txIns[nIn]
157 const scriptSig = txIn.script
158 const txOut = this.txOutMap.get(txIn.txHashBuf, txIn.txOutNum)
159 if (!txOut) {
160 console.log('output ' + txIn.txOutNum + ' not found')
161 return false
162 }
163 const scriptPubKey = txOut.script
164 const valueBn = txOut.valueBn
165 this.interp = new Interp()
166 const verified = this.interp.verify(
167 scriptSig,
168 scriptPubKey,
169 this.tx,
170 nIn,
171 flags,
172 valueBn
173 )
174 return verified
175 }
176
177 async asyncVerifyNIn (nIn, flags) {
178 const txIn = this.tx.txIns[nIn]
179 const scriptSig = txIn.script
180 const txOut = this.txOutMap.get(txIn.txHashBuf, txIn.txOutNum)
181 if (!txOut) {
182 console.log('output ' + txIn.txOutNum + ' not found')
183 return false
184 }
185 const scriptPubKey = txOut.script
186 const valueBn = txOut.valueBn
187 this.interp = new Interp()
188 const workersResult = await Workers.asyncObjectMethod(
189 this.interp,
190 'verify',
191 [scriptSig, scriptPubKey, this.tx, nIn, flags, valueBn]
192 )
193 const verified = JSON.parse(workersResult.resbuf.toString())
194 return verified
195 }
196
197 getDebugObject () {
198 return {
199 errStr: this.errStr,
200 interpFailure: this.interp ? this.interp.getDebugObject() : undefined
201 }
202 }
203
204 getDebugString () {
205 return JSON.stringify(this.getDebugObject(), null, 2)
206 }
207}
208
209export { TxVerifier }