@smartledger/bsv 8.1.0 → 8.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,123 @@
1
+ 'use strict'
2
+
3
+ /**
4
+ * Shared harness for the SV Node transaction digest vectors in
5
+ * test/data/bitcoin-sv/sighash.json.
6
+ *
7
+ * Used by tools/sv-sighash-report.js (which reports) and
8
+ * test/consensus/sv-sighash-vectors.js (which ratchets), so the progress
9
+ * report and the regression gate cannot disagree about what passes.
10
+ *
11
+ * Row layout, from sighash_tests.cpp in the node:
12
+ *
13
+ * [ raw_transaction, script, input_index, hashType,
14
+ * signature_hash (regular), signature_hash (no forkid) ]
15
+ *
16
+ * Two columns pin the routing in SignatureHash() rather than one branch of it.
17
+ * The node takes BIP143 only when forkid is enabled AND requested AND the
18
+ * signature has not asked for the original algorithm:
19
+ *
20
+ * if(enabledSighashForkid && sigHashType.hasForkId() && !sigHashType.hasChronicle())
21
+ * return SignatureHashBIP143(...);
22
+ * return SignatureHashOriginal(...);
23
+ *
24
+ * Note where the Chronicle bit is NOT consulted: the node does not ask whether
25
+ * Chronicle is enabled before honouring it. That gating lives in
26
+ * CheckSignatureEncoding, which rejects a signature carrying the bit outside
27
+ * Chronicle as SCRIPT_ERR_ILLEGAL_CHRONICLE — so such a signature never
28
+ * reaches a digest at all. For a row whose hash type sets 0x20 both columns
29
+ * are therefore identical, the original algorithm being taken either way, and
30
+ * that is what makes carrying both columns worth the trouble.
31
+ */
32
+
33
+ const bsv = require('..')
34
+ const Script = bsv.Script
35
+ const BN = bsv.crypto.BN
36
+ const Transaction = bsv.Transaction
37
+ const Signature = bsv.crypto.Signature
38
+ const Interpreter = bsv.Script.Interpreter
39
+ const sighash = Transaction.Sighash
40
+
41
+ const rawVectors = require('../test/data/bitcoin-sv/sighash.json')
42
+
43
+ const FORKID = Interpreter.SCRIPT_ENABLE_SIGHASH_FORKID
44
+ const zeroBN = BN.Zero
45
+
46
+ /** The first row of the corpus names the columns rather than carrying data. */
47
+ function rows () {
48
+ return rawVectors.slice(1).filter(v => Array.isArray(v) && v.length >= 6)
49
+ }
50
+
51
+ /**
52
+ * Which digest the node produces for this hash type, under each flag setting.
53
+ * Named rather than inferred, so a routing regression reports as one.
54
+ */
55
+ function algorithm (nhashtype, forkidEnabled) {
56
+ const t = nhashtype >>> 0
57
+ const bip143 = forkidEnabled &&
58
+ (t & Signature.SIGHASH_FORKID) !== 0 &&
59
+ (t & Signature.SIGHASH_CHRONICLE) === 0
60
+ return bip143 ? 'BIP143' : 'OTDA'
61
+ }
62
+
63
+ /**
64
+ * Run every vector. Each result is
65
+ *
66
+ * { index, hashType, chronicleBit, algorithm, passed, reason }
67
+ *
68
+ * where `algorithm` is the one the node's routing selects with forkid enabled.
69
+ */
70
+ function runAll () {
71
+ return rows().map(function (vector, i) {
72
+ const nhashtype = vector[3]
73
+ const chronicleBit = ((nhashtype >>> 0) & Signature.SIGHASH_CHRONICLE) !== 0
74
+
75
+ const base = {
76
+ index: i + 1,
77
+ hashType: nhashtype,
78
+ chronicleBit,
79
+ algorithm: algorithm(nhashtype, true)
80
+ }
81
+
82
+ // A fresh Script per call: the original algorithm removes code separators
83
+ // in place, so sharing one would let the first call change what the second
84
+ // is given.
85
+ function digest (flags) {
86
+ const tx = new Transaction(Buffer.from(vector[0], 'hex'))
87
+ return sighash.sighash(tx, nhashtype, vector[2],
88
+ Script(Buffer.from(vector[1], 'hex')), zeroBN, flags).toString('hex')
89
+ }
90
+
91
+ let regular, noForkId
92
+ try {
93
+ regular = digest(FORKID)
94
+ noForkId = digest(0)
95
+ } catch (e) {
96
+ return Object.assign(base, {
97
+ passed: false,
98
+ reason: 'threw: ' + String(e.message).slice(0, 70)
99
+ })
100
+ }
101
+
102
+ if (regular !== vector[4]) {
103
+ return Object.assign(base, {
104
+ passed: false,
105
+ reason: 'regular column: ' + regular + ' != ' + vector[4]
106
+ })
107
+ }
108
+ if (noForkId !== vector[5]) {
109
+ return Object.assign(base, {
110
+ passed: false,
111
+ reason: 'no-forkid column: ' + noForkId + ' != ' + vector[5]
112
+ })
113
+ }
114
+ return Object.assign(base, { passed: true, reason: null })
115
+ })
116
+ }
117
+
118
+ /** Rows whose two columns must agree, because the Chronicle bit is set. */
119
+ function chronicleRows () {
120
+ return rows().filter(v => ((v[3] >>> 0) & Signature.SIGHASH_CHRONICLE) !== 0)
121
+ }
122
+
123
+ module.exports = { runAll, rows, chronicleRows, algorithm }
@@ -0,0 +1,71 @@
1
+ 'use strict'
2
+
3
+ /**
4
+ * How far this library's transaction digest is from the reference node.
5
+ *
6
+ * npm run vectors:sv-sighash
7
+ * npm run vectors:sv-sighash -- --verbose
8
+ *
9
+ * There is no accept/reject direction to report here as there is for the
10
+ * script and transaction vectors: a digest either matches or it does not, and
11
+ * a wrong one is equally bad either way — it makes valid signatures unverifiable
12
+ * and, worse, signs something other than what the caller was shown.
13
+ *
14
+ * The breakdown by algorithm is the useful reading, because the failure this
15
+ * corpus is best at catching is a routing mistake rather than a hashing one.
16
+ */
17
+
18
+ const harness = require('./sv-sighash-harness')
19
+
20
+ const verbose = process.argv.indexOf('--verbose') !== -1
21
+ const results = harness.runAll()
22
+
23
+ const failing = results.filter(r => !r.passed)
24
+ const passing = results.length - failing.length
25
+ const chronicle = results.filter(r => r.chronicleBit)
26
+ const bip143 = results.filter(r => r.algorithm === 'BIP143')
27
+ const otda = results.filter(r => r.algorithm === 'OTDA')
28
+
29
+ function line (label, subset) {
30
+ const bad = subset.filter(r => !r.passed).length
31
+ console.log(' ' + label.padEnd(34),
32
+ String(subset.length - bad).padStart(4) + '/' + String(subset.length).padEnd(5),
33
+ bad ? ' <- ' + bad + ' failing' : '')
34
+ }
35
+
36
+ console.log()
37
+ console.log('=== SV Node v1.2.0 transaction digest vectors ===')
38
+ console.log()
39
+ console.log('vectors run :', results.length, '(each checked on both columns)')
40
+ console.log('passing :', passing,
41
+ ' (' + (100 * passing / results.length).toFixed(1) + '%)')
42
+ console.log('failing :', failing.length)
43
+ console.log()
44
+ console.log('--- by routing ---')
45
+ line('BIP-143 digest', bip143)
46
+ line('original digest (OTDA)', otda)
47
+ line(' of which set SIGHASH_CHRONICLE', chronicle)
48
+
49
+ if (failing.length) {
50
+ const byReason = {}
51
+ failing.forEach(function (r) {
52
+ const key = String(r.reason).replace(/:.*/, '')
53
+ byReason[key] = (byReason[key] || 0) + 1
54
+ })
55
+ console.log()
56
+ console.log('--- failure reasons ---')
57
+ Object.keys(byReason).sort((a, b) => byReason[b] - byReason[a])
58
+ .forEach(k => console.log(String(byReason[k]).padStart(5), k))
59
+
60
+ console.log()
61
+ console.log('--- failing rows ---')
62
+ failing.slice(0, verbose ? failing.length : 10).forEach(function (r) {
63
+ console.log(' #' + String(r.index).padStart(4) +
64
+ ' hashType 0x' + (r.hashType >>> 0).toString(16).padStart(2, '0') +
65
+ ' ' + r.algorithm.padEnd(7) + r.reason)
66
+ })
67
+ if (!verbose && failing.length > 10) {
68
+ console.log(' ... and ' + (failing.length - 10) + ' more; pass --verbose')
69
+ }
70
+ }
71
+ console.log()
@@ -0,0 +1,172 @@
1
+ 'use strict'
2
+
3
+ /**
4
+ * Harness for the SV Node transaction vectors in test/data/bitcoin-sv.
5
+ *
6
+ * These reach a level the script vectors do not. A script vector evaluates one
7
+ * unlocking script against one locking script; these deserialise a whole
8
+ * transaction, resolve each input against the outputs it claims to spend, and
9
+ * require every one of them to verify — across multiple inputs, with locktimes
10
+ * and sequence numbers in play, and with the transaction's own structural
11
+ * checks applied.
12
+ *
13
+ * Row layout, from the node's src/test/transaction_tests.cpp:
14
+ *
15
+ * [ [[prevout hash, prevout n, scriptPubKey, amount?], ...],
16
+ * serialized transaction,
17
+ * flags ]
18
+ *
19
+ * A row in tx_valid.json must verify; one in tx_invalid.json must not. A
20
+ * prevout index of -1 is the node casting to an unsigned int, so it means
21
+ * 0xffffffff. The amount is present only where a row needs one.
22
+ */
23
+
24
+ const bsv = require('..')
25
+ const Transaction = bsv.Transaction
26
+ const Interpreter = bsv.Script.Interpreter
27
+ const BN = bsv.crypto.BN
28
+
29
+ const harness = require('./sv-vector-harness')
30
+
31
+ const validVectors = require('../test/data/bitcoin-sv/tx_valid.json')
32
+ const invalidVectors = require('../test/data/bitcoin-sv/tx_invalid.json')
33
+
34
+ /** A stable id for a row, so a list of known failures survives reordering. */
35
+ function vectorId (raw) {
36
+ return harness.vectorId(raw)
37
+ }
38
+
39
+ /**
40
+ * Resolve each input against the output it spends and verify it.
41
+ *
42
+ * Returns { ok, reason }. `ok` is whether the transaction verifies overall,
43
+ * which requires the structural checks to pass and every non-null input to
44
+ * verify.
45
+ */
46
+ function evaluate (row) {
47
+ const prevouts = {}
48
+ row[0].forEach(function (p) {
49
+ // -1 is the node's cast of an unsigned index.
50
+ const n = p[1] === -1 ? 0xffffffff : p[1]
51
+ prevouts[p[0] + ':' + n] = {
52
+ script: harness.fromBitcoindString(p[2]),
53
+ satoshis: p.length > 3 ? p[3] : 0
54
+ }
55
+ })
56
+
57
+ const tx = new Transaction(row[1])
58
+ const cls = harness.classifyFlags(row[2])
59
+
60
+ // The node runs these vectors at ProtocolEra::PreGenesis explicitly —
61
+ // transaction_tests.cpp says so — which is why one tx_valid row legitimately
62
+ // carries a P2SH output, an output Genesis stopped anyone creating. This
63
+ // library's verify() has no era and applies the pre-Genesis rules, which is
64
+ // what these vectors want.
65
+ let structural
66
+ try {
67
+ structural = tx.verify()
68
+ } catch (e) {
69
+ return { ok: false, reason: 'tx.verify threw: ' + String(e.message).slice(0, 60) }
70
+ }
71
+ if (structural !== true) {
72
+ return { ok: false, reason: 'tx.verify: ' + structural }
73
+ }
74
+
75
+ for (let i = 0; i < tx.inputs.length; i++) {
76
+ const input = tx.inputs[i]
77
+ if (input.isNull()) {
78
+ continue
79
+ }
80
+ const key = input.prevTxId.toString('hex') + ':' + input.outputIndex
81
+ const prevout = prevouts[key]
82
+ if (prevout === undefined) {
83
+ return { ok: false, reason: 'no prevout given for input ' + i }
84
+ }
85
+ const interp = new Interpreter()
86
+ const verified = interp.verify(
87
+ input.script, prevout.script, tx, i, cls.flags, new BN(prevout.satoshis)
88
+ )
89
+ if (!verified) {
90
+ return { ok: false, reason: 'input ' + i + ': ' + interp.errstr }
91
+ }
92
+ }
93
+
94
+ return { ok: true, reason: null }
95
+ }
96
+
97
+ /**
98
+ * Run both files. Each result is
99
+ *
100
+ * { id, expected, ok, passed, reason, direction, comment }
101
+ *
102
+ * `direction` is 'accept' where we accepted a transaction the node rejects —
103
+ * the dangerous way round — and 'reject' for the converse.
104
+ */
105
+ function runAll () {
106
+ const results = []
107
+
108
+ const run = function (vectors, expected) {
109
+ let comment = ''
110
+ vectors.forEach(function (raw) {
111
+ if (!Array.isArray(raw)) return
112
+ if (raw.length === 1) {
113
+ // A single-element row is a comment describing the rows below it.
114
+ comment = String(raw[0])
115
+ return
116
+ }
117
+
118
+ let got = null
119
+ let thrown = null
120
+ try {
121
+ got = evaluate(raw)
122
+ } catch (e) {
123
+ thrown = e
124
+ }
125
+
126
+ let reason = null
127
+ let direction = null
128
+ const ok = thrown === null && got.ok
129
+
130
+ if (thrown !== null) {
131
+ reason = 'threw: ' + String(thrown.message).slice(0, 70)
132
+ direction = expected ? 'reject' : null
133
+ if (!expected) {
134
+ // Throwing on a transaction the node also rejects is the right
135
+ // outcome reached untidily, not a consensus failure.
136
+ reason = null
137
+ }
138
+ } else if (ok !== expected) {
139
+ reason = expected
140
+ ? 'expected valid, rejected with ' + got.reason
141
+ : 'expected invalid, but ACCEPTED'
142
+ direction = expected ? 'reject' : 'accept'
143
+ }
144
+
145
+ results.push({
146
+ id: vectorId(raw),
147
+ expected,
148
+ ok,
149
+ passed: reason === null,
150
+ reason,
151
+ direction,
152
+ comment
153
+ })
154
+ })
155
+ }
156
+
157
+ run(validVectors, true)
158
+ run(invalidVectors, false)
159
+ return results
160
+ }
161
+
162
+ /** A one-line description of a row, for failure output. */
163
+ function describe (result) {
164
+ return (result.expected ? 'tx_valid' : 'tx_invalid') +
165
+ (result.comment ? ' // ' + result.comment.slice(0, 70) : '')
166
+ }
167
+
168
+ module.exports = {
169
+ runAll,
170
+ evaluate,
171
+ describe
172
+ }
@@ -0,0 +1,43 @@
1
+ 'use strict'
2
+
3
+ /**
4
+ * How far this interpreter is from the node at the transaction level.
5
+ *
6
+ * npm run vectors:sv-tx
7
+ *
8
+ * A script vector evaluates one unlocking script against one locking script.
9
+ * These deserialise a whole transaction, resolve each input against the output
10
+ * it claims to spend, and require every one to verify — with locktimes,
11
+ * sequence numbers and multiple inputs in play. That reaches rules no single
12
+ * script can.
13
+ */
14
+
15
+ const harness = require('./sv-tx-harness')
16
+
17
+ const results = harness.runAll()
18
+ const failing = results.filter(r => !r.passed)
19
+ const accepts = failing.filter(r => r.direction === 'accept')
20
+ const rejects = failing.filter(r => r.direction === 'reject')
21
+
22
+ console.log()
23
+ console.log('=== SV Node v1.2.0 transaction vectors ===')
24
+ console.log()
25
+ console.log('vectors run :', results.length,
26
+ '(' + results.filter(r => r.expected).length + ' must verify, ' +
27
+ results.filter(r => !r.expected).length + ' must not)')
28
+ console.log('passing :', results.length - failing.length,
29
+ ' (' + (100 * (results.length - failing.length) / results.length).toFixed(1) + '%)')
30
+ console.log()
31
+ console.log(' false accepts (we accept, node rejects) :', accepts.length,
32
+ accepts.length ? ' <- the direction that can cost money' : '')
33
+ console.log(' false rejects (we reject, node accepts) :', rejects.length)
34
+
35
+ if (failing.length) {
36
+ console.log()
37
+ console.log('--- failing ---')
38
+ failing.forEach(function (r) {
39
+ console.log(' ' + r.id + ' ' + r.reason)
40
+ console.log(' ' + harness.describe(r))
41
+ })
42
+ }
43
+ console.log()