@smartledger/bsv 8.1.0 → 8.3.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.
Files changed (47) hide show
  1. package/CHANGELOG.md +166 -0
  2. package/README.md +38 -38
  3. package/bsv-ecies.min.js +1 -1
  4. package/bsv-gdaf.min.js +62 -62
  5. package/bsv-ltp.min.js +48 -48
  6. package/bsv-smartcontract.min.js +1 -1
  7. package/bsv.bundle.js +62 -62
  8. package/bsv.min.js +62 -62
  9. package/docs/AUDIT_SCOPE.md +8 -8
  10. package/docs/BRC220_ENCODING_AMENDMENT.md +100 -0
  11. package/docs/BRC220_PLAN.md +224 -0
  12. package/docs/MODULE_REFERENCE_COMPLETE.md +27 -27
  13. package/docs/advanced/UTXO_MANAGER_GUIDE.md +1 -1
  14. package/docs/getting-started/INSTALLATION.md +23 -23
  15. package/docs/getting-started/QUICK_START.md +7 -7
  16. package/docs/migration/FROM_BSV_1_5_6.md +5 -5
  17. package/index.js +5 -0
  18. package/index.mjs +1 -0
  19. package/lib/gdaf/attestation-signer.js +54 -2
  20. package/lib/gdaf/attestation-verifier.js +41 -25
  21. package/lib/gdaf/zk-prover.js +143 -46
  22. package/lib/notaryhash/certificate.js +282 -0
  23. package/lib/notaryhash/encoding.js +150 -0
  24. package/lib/notaryhash/index.js +334 -0
  25. package/lib/notaryhash/merkle.js +209 -0
  26. package/lib/notaryhash/script.js +261 -0
  27. package/lib/notaryhash/suites.js +156 -0
  28. package/lib/util/jcs.js +75 -0
  29. package/package.json +9 -6
  30. package/test/gdaf/canonicalization.js +106 -0
  31. package/test/gdaf/zk_prover.js +204 -0
  32. package/test/notaryhash/certificate.js +249 -0
  33. package/test/notaryhash/encoding.js +186 -0
  34. package/test/notaryhash/merkle.js +181 -0
  35. package/test/notaryhash/script.js +270 -0
  36. package/test/notaryhash/verify.js +339 -0
  37. package/tools/minimal_reproduction.js +119 -0
  38. package/tools/opcode_map.js +342 -0
  39. package/tools/server.js +97 -0
  40. package/tools/simple_real_tx.js +136 -0
  41. package/tools/sv-sighash-harness.js +123 -0
  42. package/tools/sv-sighash-report.js +71 -0
  43. package/tools/sv-tx-harness.js +172 -0
  44. package/tools/sv-tx-report.js +43 -0
  45. package/tools/sv-vector-harness.js +304 -0
  46. package/tools/sv-vector-report.js +88 -0
  47. package/version.js +1 -1
@@ -0,0 +1,181 @@
1
+ 'use strict'
2
+
3
+ /* global describe, it */
4
+
5
+ // RFC 6962 Merkle trees, for BRC-220 batch mode.
6
+ //
7
+ // The first version of foldPath here descended from the root by splitting at the largest
8
+ // power of two, mirroring how root() builds the tree. It round-tripped perfectly for
9
+ // powers of two and failed for 21 of the 45 leaves in trees of size 1..9 — which is
10
+ // exactly why the published vectors below matter more than any round-trip test. A tree
11
+ // that agrees with itself is not evidence.
12
+
13
+ require('chai').should()
14
+ var Merkle = require('../../lib/notaryhash/merkle')
15
+ var Hash = require('../../lib/crypto/hash')
16
+
17
+ // The canonical RFC 6962 / Certificate Transparency inputs.
18
+ var D = ['', '00', '10', '2021', '3031', '40414243', '5051525354555657',
19
+ '606162636465666768696a6b6c6d6e6f'].map(function (h) { return Buffer.from(h, 'hex') })
20
+
21
+ describe('RFC 6962 Merkle tree', function () {
22
+ describe('published vectors', function () {
23
+ // External validation. Everything else in this file could be internally consistent
24
+ // and wrong; these numbers come from outside this codebase.
25
+ var EXPECTED = {
26
+ 0: 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855',
27
+ 1: '6e340b9cffb37a989ca544e6bb780a2c78901d3fb33738768511a30617afa01d',
28
+ 2: 'fac54203e7cc696cf0dfcb42c92a1d9dbaf70ad9e621f4bd8d98662f00e3c125',
29
+ 4: 'd37ee418976dd95753c1c73862b9398fa2a2cf9b4ff0fdfe8b30cd95209614b7',
30
+ 7: 'ddb89be403809e325750d3d263cd78929c2942b7942a34b77e122c9594a74c8c',
31
+ 8: '5dc9da79a70659a9ad559cb701ded9a2ab9d823aad2f4960cfe370eff4604328'
32
+ }
33
+
34
+ Object.keys(EXPECTED).forEach(function (n) {
35
+ it('MTH of ' + n + ' leaves matches the published root', function () {
36
+ Merkle.root(D.slice(0, Number(n))).toString('hex').should.equal(EXPECTED[n])
37
+ })
38
+ })
39
+ })
40
+
41
+ describe('construction', function () {
42
+ it('hashes the empty tree as SHA-256 of the empty string, not zeroes', function () {
43
+ Merkle.root([]).toString('hex').should.equal(Hash.sha256(Buffer.alloc(0)).toString('hex'))
44
+ Merkle.root([]).toString('hex').should.not.equal('00'.repeat(32))
45
+ })
46
+
47
+ it('domain-separates leaves from internal nodes', function () {
48
+ Merkle.hashLeaf(Buffer.from('ab', 'hex')).toString('hex')
49
+ .should.equal(Hash.sha256(Buffer.from('00ab', 'hex')).toString('hex'))
50
+ Merkle.hashNode(Buffer.alloc(32, 1), Buffer.alloc(32, 2)).toString('hex')
51
+ .should.equal(Hash.sha256(Buffer.concat([
52
+ Buffer.from([0x01]), Buffer.alloc(32, 1), Buffer.alloc(32, 2)
53
+ ])).toString('hex'))
54
+ })
55
+
56
+ // Without the 0x00/0x01 prefixes an internal node could be reinterpreted as a leaf,
57
+ // which is the second-preimage attack Bitcoin's tree is open to.
58
+ it('gives a leaf and a node over the same bytes different hashes', function () {
59
+ var payload = Buffer.alloc(64, 7)
60
+ Merkle.hashLeaf(payload).toString('hex')
61
+ .should.not.equal(Merkle.hashNode(Buffer.alloc(32, 7), Buffer.alloc(32, 7)).toString('hex'))
62
+ })
63
+
64
+ // The split point, not the midpoint. For n=5 the RFC splits 4/1.
65
+ it('splits at the largest power of two below n', function () {
66
+ var expected = { 2: 1, 3: 2, 4: 2, 5: 4, 6: 4, 7: 4, 8: 4, 9: 8, 16: 8, 17: 16 }
67
+ Object.keys(expected).forEach(function (n) {
68
+ Merkle.largestPowerOfTwoBelow(Number(n)).should.equal(expected[n])
69
+ })
70
+ })
71
+ })
72
+
73
+ // The property the plan named as the one that would ship silently: a Bitcoin-style
74
+ // tree over the same leaves must produce a DIFFERENT root.
75
+ describe('is not the Bitcoin tree', function () {
76
+ function bitcoinStyleRoot (leaves) {
77
+ var level = leaves.map(function (l) { return Hash.sha256sha256(l) })
78
+ while (level.length > 1) {
79
+ var next = []
80
+ for (var i = 0; i < level.length; i += 2) {
81
+ var left = level[i]
82
+ var right = i + 1 < level.length ? level[i + 1] : left // duplicated
83
+ next.push(Hash.sha256sha256(Buffer.concat([left, right])))
84
+ }
85
+ level = next
86
+ }
87
+ return level[0]
88
+ }
89
+
90
+ it('produces a different root over the same leaves', function () {
91
+ var leaves = D.slice(0, 4)
92
+ Merkle.root(leaves).toString('hex')
93
+ .should.not.equal(bitcoinStyleRoot(leaves).toString('hex'))
94
+ })
95
+
96
+ it('differs for an odd leaf count, where Bitcoin duplicates and RFC 6962 does not', function () {
97
+ var leaves = D.slice(0, 3)
98
+ Merkle.root(leaves).toString('hex')
99
+ .should.not.equal(bitcoinStyleRoot(leaves).toString('hex'))
100
+ })
101
+ })
102
+
103
+ describe('inclusion proofs', function () {
104
+ it('round-trips every leaf for every tree size 1..33', function () {
105
+ var failures = []
106
+ for (var n = 1; n <= 33; n++) {
107
+ var leaves = []
108
+ for (var i = 0; i < n; i++) leaves.push(Buffer.from([i & 0xff, n & 0xff]))
109
+ var root = Merkle.root(leaves)
110
+ for (var j = 0; j < n; j++) {
111
+ if (!Merkle.verifyInclusion(leaves[j], j, n, Merkle.path(leaves, j), root)) {
112
+ failures.push('n=' + n + ' i=' + j)
113
+ }
114
+ }
115
+ }
116
+ failures.should.deep.equal([])
117
+ })
118
+
119
+ it('rejects a proof for the wrong leaf', function () {
120
+ var leaves = D.slice(0, 8)
121
+ var root = Merkle.root(leaves)
122
+ Merkle.verifyInclusion(D[1], 0, 8, Merkle.path(leaves, 0), root).should.equal(false)
123
+ })
124
+
125
+ it('rejects a proof at the wrong index', function () {
126
+ var leaves = D.slice(0, 8)
127
+ var root = Merkle.root(leaves)
128
+ Merkle.verifyInclusion(leaves[3], 4, 8, Merkle.path(leaves, 3), root).should.equal(false)
129
+ })
130
+
131
+ // leafCount changes the tree shape — but only for SOME indices. On an 8-leaf tree
132
+ // claimed as 7, indices 0..5 fold identically and only 6 and 7 differ, because the
133
+ // left region of the tree is the same shape either way.
134
+ //
135
+ // That is a property worth knowing rather than a defect: it means an inclusion proof
136
+ // alone cannot be trusted to detect a wrong leafCount, which is why
137
+ // NotaryHash.recordMatchesCertificate compares the certificate's count against the
138
+ // authoritative u32be in the on-chain record instead of assuming the fold implies it.
139
+ it('rejects a wrong leafCount where the shape differs', function () {
140
+ var leaves = D.slice(0, 8)
141
+ var root = Merkle.root(leaves)
142
+ Merkle.verifyInclusion(leaves[6], 6, 7, Merkle.path(leaves, 6), root).should.equal(false)
143
+ Merkle.verifyInclusion(leaves[7], 7, 7, Merkle.path(leaves, 7), root).should.equal(false)
144
+ })
145
+
146
+ it('does NOT catch a wrong leafCount where the shape is the same', function () {
147
+ var leaves = D.slice(0, 8)
148
+ var root = Merkle.root(leaves)
149
+ // Documented, not desired. The on-chain leafCount is what closes this.
150
+ Merkle.verifyInclusion(leaves[3], 3, 7, Merkle.path(leaves, 3), root).should.equal(true)
151
+ })
152
+
153
+ it('rejects a truncated or padded path', function () {
154
+ var leaves = D.slice(0, 8)
155
+ var root = Merkle.root(leaves)
156
+ var path = Merkle.path(leaves, 3)
157
+ Merkle.verifyInclusion(leaves[3], 3, 8, path.slice(0, -1), root).should.equal(false)
158
+ Merkle.verifyInclusion(leaves[3], 3, 8, path.concat([Buffer.alloc(32)]), root).should.equal(false)
159
+ })
160
+
161
+ it('rejects a tampered sibling', function () {
162
+ var leaves = D.slice(0, 8)
163
+ var root = Merkle.root(leaves)
164
+ var path = Merkle.path(leaves, 3)
165
+ path[0] = Buffer.alloc(32, 0xff)
166
+ Merkle.verifyInclusion(leaves[3], 3, 8, path, root).should.equal(false)
167
+ })
168
+
169
+ it('returns a strict boolean and never throws on malformed input', function () {
170
+ Merkle.verifyInclusion(D[0], 0, 1, [], Merkle.root([D[0]])).should.be.a('boolean')
171
+ Merkle.verifyInclusion(null, 0, 1, [], Buffer.alloc(32)).should.equal(false)
172
+ Merkle.verifyInclusion(D[0], 5, 1, [], Buffer.alloc(32)).should.equal(false)
173
+ Merkle.verifyInclusion(D[0], 0, 1, [], 'not-a-buffer').should.equal(false)
174
+ })
175
+
176
+ it('gives an empty path for a single-leaf tree', function () {
177
+ Merkle.path([D[0]], 0).should.deep.equal([])
178
+ Merkle.verifyInclusion(D[0], 0, 1, [], Merkle.hashLeaf(D[0])).should.equal(true)
179
+ })
180
+ })
181
+ })
@@ -0,0 +1,270 @@
1
+ 'use strict'
2
+
3
+ /* global describe, it */
4
+
5
+ // BRC-220 on-chain record. As with the encoding module, the property that matters is that
6
+ // another implementation reading the spec produces and accepts the same bytes — so the
7
+ // tests assert the push layout the spec dictates rather than only round-tripping our own
8
+ // output, which would pass even if every field were in the wrong place.
9
+
10
+ require('chai').should()
11
+ var bsv = require('../..')
12
+ var NS = require('../../lib/notaryhash/script')
13
+ var Hash = require('../../lib/crypto/hash')
14
+ var Opcode = require('../../lib/opcode')
15
+
16
+ var FULL = {
17
+ mode: NS.MODE.FULL,
18
+ algorithm: 'ECDSA-secp256k1',
19
+ hashAlgorithm: 'SHA-256',
20
+ payloadHash: Buffer.alloc(32, 0x11),
21
+ proofHash: Buffer.alloc(32, 0x22),
22
+ publicKey: Buffer.alloc(33, 0x02),
23
+ signature: Buffer.alloc(64, 0x30)
24
+ }
25
+
26
+ // The spec counts PUSH indices, which start after OP_FALSE OP_RETURN. Script chunks
27
+ // include those two opcodes, so push N is chunk N+2.
28
+ function pushes (script) {
29
+ return script.chunks.slice(2)
30
+ }
31
+
32
+ describe('BRC-220 on-chain record', function () {
33
+ describe('push layout', function () {
34
+ it('is OP_FALSE OP_RETURN followed by the pushes', function () {
35
+ var s = NS.build(FULL)
36
+ s.chunks[0].opcodenum.should.equal(Opcode.OP_FALSE)
37
+ s.chunks[1].opcodenum.should.equal(Opcode.OP_RETURN)
38
+ })
39
+
40
+ it('puts the prefix at push index 0, as 10 ASCII bytes', function () {
41
+ var p = pushes(NS.build(FULL))
42
+ p[0].buf.toString('ascii').should.equal('NOTARYHASH')
43
+ p[0].buf.length.should.equal(10)
44
+ p[0].buf.toString('hex').should.equal('4e4f5441525948415348')
45
+ })
46
+
47
+ // The spec discriminates on "the mode/kind byte at push index 2". If this drifts,
48
+ // every other implementation reads the wrong field.
49
+ it('puts the version at push index 1 and the mode at push index 2', function () {
50
+ var p = pushes(NS.build(FULL))
51
+ p[1].buf.length.should.equal(1)
52
+ p[1].buf[0].should.equal(1)
53
+ p[2].buf.length.should.equal(1)
54
+ p[2].buf[0].should.equal(NS.MODE.FULL)
55
+ })
56
+
57
+ it('orders full-mode fields as the spec lists them', function () {
58
+ var p = pushes(NS.build(FULL))
59
+ p.length.should.equal(9)
60
+ p[3].buf.toString('utf8').should.equal('ECDSA-secp256k1')
61
+ p[4].buf.toString('utf8').should.equal('SHA-256')
62
+ p[5].buf.toString('hex').should.equal(FULL.payloadHash.toString('hex'))
63
+ p[6].buf.toString('hex').should.equal(FULL.proofHash.toString('hex'))
64
+ p[7].buf.toString('hex').should.equal(FULL.publicKey.toString('hex'))
65
+ p[8].buf.toString('hex').should.equal(FULL.signature.toString('hex'))
66
+ })
67
+
68
+ it('lays batch mode out as root then u32be count', function () {
69
+ var p = pushes(NS.build({ mode: NS.MODE.BATCH, merkleRoot: Buffer.alloc(32, 0xaa), leafCount: 12345 }))
70
+ p.length.should.equal(5)
71
+ p[2].buf[0].should.equal(NS.MODE.BATCH)
72
+ p[3].buf.length.should.equal(32)
73
+ p[4].buf.length.should.equal(4)
74
+ p[4].buf.readUInt32BE(0).should.equal(12345)
75
+ })
76
+
77
+ // The canonical proof bytes use lp() because they are one flat string; here the push
78
+ // opcode already carries the length. Applying lp() as well would double-encode.
79
+ it('does not length-prefix inside the pushes', function () {
80
+ var p = pushes(NS.build(FULL))
81
+ // A doubly-encoded payloadHash would be 4 + 32 bytes.
82
+ p[5].buf.length.should.equal(32)
83
+ p[3].buf.length.should.equal('ECDSA-secp256k1'.length)
84
+ })
85
+ })
86
+
87
+ describe('hybrid mode', function () {
88
+ it('puts SHA-256 of the key and signature on chain', function () {
89
+ var p = pushes(NS.build(Object.assign({}, FULL, { mode: NS.MODE.HYBRID })))
90
+ p[7].buf.toString('hex').should.equal(Hash.sha256(FULL.publicKey).toString('hex'))
91
+ p[8].buf.toString('hex').should.equal(Hash.sha256(FULL.signature).toString('hex'))
92
+ p[7].buf.length.should.equal(32)
93
+ p[8].buf.length.should.equal(32)
94
+ })
95
+
96
+ // The caller always supplies the FULL key and signature; hashing is this module's job.
97
+ // Accepting a pre-hashed blob would produce a record that looks right and cannot be
98
+ // reconciled with the certificate, and nothing downstream would notice.
99
+ it('hashes internally rather than trusting a pre-hashed input', function () {
100
+ var preHashed = Object.assign({}, FULL, {
101
+ mode: NS.MODE.HYBRID,
102
+ publicKey: Hash.sha256(FULL.publicKey)
103
+ })
104
+ var p = pushes(NS.build(preHashed))
105
+ // Hashed again, so it does NOT equal the value that was passed in.
106
+ p[7].buf.toString('hex').should.not.equal(Hash.sha256(FULL.publicKey).toString('hex'))
107
+ })
108
+
109
+ it('parses to hashes, never to raw blobs', function () {
110
+ var parsed = NS.parse(NS.build(Object.assign({}, FULL, { mode: NS.MODE.HYBRID })))
111
+ parsed.publicKeyHash.toString('hex').should.equal(Hash.sha256(FULL.publicKey).toString('hex'))
112
+ ;(parsed.publicKey === undefined).should.equal(true)
113
+ ;(parsed.signature === undefined).should.equal(true)
114
+ })
115
+ })
116
+
117
+ describe('round trip', function () {
118
+ it('preserves every full-mode field', function () {
119
+ var p = NS.parse(NS.build(FULL))
120
+ p.mode.should.equal(NS.MODE.FULL)
121
+ p.version.should.equal(1)
122
+ p.algorithm.should.equal(FULL.algorithm)
123
+ p.hashAlgorithm.should.equal(FULL.hashAlgorithm)
124
+ p.payloadHash.toString('hex').should.equal(FULL.payloadHash.toString('hex'))
125
+ p.proofHash.toString('hex').should.equal(FULL.proofHash.toString('hex'))
126
+ p.publicKey.toString('hex').should.equal(FULL.publicKey.toString('hex'))
127
+ p.signature.toString('hex').should.equal(FULL.signature.toString('hex'))
128
+ })
129
+
130
+ it('preserves batch fields', function () {
131
+ var p = NS.parse(NS.build({ mode: NS.MODE.BATCH, merkleRoot: Buffer.alloc(32, 0xbb), leafCount: 1 }))
132
+ p.merkleRoot.toString('hex').should.equal(Buffer.alloc(32, 0xbb).toString('hex'))
133
+ p.leafCount.should.equal(1)
134
+ })
135
+
136
+ it('round-trips through hex', function () {
137
+ var hex = NS.build(FULL).toHex()
138
+ NS.parse(hex).algorithm.should.equal(FULL.algorithm)
139
+ })
140
+
141
+ it('carries a post-quantum-sized signature in full mode', function () {
142
+ var big = Object.assign({}, FULL, { signature: Buffer.alloc(7856, 0x5a) })
143
+ NS.parse(NS.build(big)).signature.length.should.equal(7856)
144
+ })
145
+ })
146
+
147
+ describe('parser rejects', function () {
148
+ function corrupt (fn) {
149
+ var s = NS.build(FULL)
150
+ fn(s)
151
+ return s
152
+ }
153
+
154
+ // Long enough to pass the length check, so the OP_FALSE check is what rejects it.
155
+ it('a record that does not open with OP_FALSE', function () {
156
+ var s = corrupt(function (s) { s.chunks[0] = { opcodenum: Opcode.OP_1 } })
157
+ ;(function () { NS.parse(s) }).should.throw(/OP_FALSE/)
158
+ })
159
+
160
+ it('a record with no OP_RETURN', function () {
161
+ var s = corrupt(function (s) { s.chunks[1] = { opcodenum: Opcode.OP_1 } })
162
+ ;(function () { NS.parse(s) }).should.throw(/OP_RETURN/)
163
+ })
164
+
165
+ it('a foreign protocol prefix', function () {
166
+ var s = corrupt(function (s) { s.chunks[2].buf = Buffer.from('SOMETHINGELSE', 'ascii') })
167
+ ;(function () { NS.parse(s) }).should.throw(/prefix/)
168
+ })
169
+
170
+ it('an unsupported version', function () {
171
+ var s = corrupt(function (s) { s.chunks[3].buf = Buffer.from([9]) })
172
+ ;(function () { NS.parse(s) }).should.throw(/version: 9/)
173
+ })
174
+
175
+ it('an unknown mode', function () {
176
+ var s = corrupt(function (s) { s.chunks[4].buf = Buffer.from([7]) })
177
+ ;(function () { NS.parse(s) }).should.throw(/unknown NotaryHash mode: 7/)
178
+ })
179
+
180
+ it('the wrong number of pushes', function () {
181
+ var s = corrupt(function (s) { s.chunks.pop() })
182
+ ;(function () { NS.parse(s) }).should.throw(/exactly 11 pushes/)
183
+ })
184
+
185
+ it('a payloadHash that is not 32 bytes', function () {
186
+ var s = corrupt(function (s) { s.chunks[7].buf = Buffer.alloc(31, 1) })
187
+ ;(function () { NS.parse(s) }).should.throw(/payloadHash must be exactly 32/)
188
+ })
189
+
190
+ it('a leafCount that is not 4 bytes', function () {
191
+ var s = NS.build({ mode: NS.MODE.BATCH, merkleRoot: Buffer.alloc(32, 1), leafCount: 5 })
192
+ s.chunks[6].buf = Buffer.alloc(3, 0)
193
+ ;(function () { NS.parse(s) }).should.throw(/leafCount must be 4 bytes/)
194
+ })
195
+
196
+ it('too few pushes to be a record at all', function () {
197
+ ;(function () { NS.parse(new bsv.Script().add(Opcode.OP_FALSE).add(Opcode.OP_RETURN)) })
198
+ .should.throw(/too few pushes/)
199
+ })
200
+ })
201
+
202
+ describe('builder rejects', function () {
203
+ it('an unknown mode', function () {
204
+ ;(function () { NS.build(Object.assign({}, FULL, { mode: 5 })) }).should.throw(/mode must be/)
205
+ })
206
+
207
+ it('hex where raw bytes belong', function () {
208
+ ;(function () {
209
+ NS.build(Object.assign({}, FULL, { payloadHash: FULL.payloadHash.toString('hex') }))
210
+ }).should.throw(/must be a Buffer/)
211
+ })
212
+
213
+ it('a payloadHash of the wrong length', function () {
214
+ ;(function () {
215
+ NS.build(Object.assign({}, FULL, { payloadHash: Buffer.alloc(31) }))
216
+ }).should.throw(/exactly 32 bytes/)
217
+ })
218
+
219
+ it('a non-integer leafCount', function () {
220
+ ;(function () {
221
+ NS.build({ mode: NS.MODE.BATCH, merkleRoot: Buffer.alloc(32), leafCount: 1.5 })
222
+ }).should.throw(/leafCount/)
223
+ })
224
+ })
225
+
226
+ // A builder that minimally encodes its pushes represents u8(1) as OP_1. Those records
227
+ // are conformant, and rejecting them would be an interop failure rather than a safety
228
+ // measure — the mode byte is a routing hint, and every field that matters is checked
229
+ // on its own terms.
230
+ describe('interop with minimally-encoded pushes', function () {
231
+ it('reads OP_1 / OP_0 as the version and mode bytes', function () {
232
+ var s = NS.build(FULL)
233
+ s.chunks[3] = { opcodenum: Opcode.OP_1 }
234
+ s.chunks[4] = { opcodenum: Opcode.OP_0 }
235
+ var p = NS.parse(s)
236
+ p.version.should.equal(1)
237
+ p.mode.should.equal(NS.MODE.FULL)
238
+ })
239
+
240
+ it('reads OP_2 as batch mode', function () {
241
+ var s = NS.build({ mode: NS.MODE.BATCH, merkleRoot: Buffer.alloc(32, 3), leafCount: 2 })
242
+ s.chunks[4] = { opcodenum: Opcode.OP_2 }
243
+ NS.parse(s).mode.should.equal(NS.MODE.BATCH)
244
+ })
245
+ })
246
+
247
+ describe('isNotaryHash', function () {
248
+ it('accepts a record of each mode', function () {
249
+ NS.isNotaryHash(NS.build(FULL)).should.equal(true)
250
+ NS.isNotaryHash(NS.build(Object.assign({}, FULL, { mode: NS.MODE.HYBRID }))).should.equal(true)
251
+ NS.isNotaryHash(NS.build({ mode: NS.MODE.BATCH, merkleRoot: Buffer.alloc(32), leafCount: 0 }))
252
+ .should.equal(true)
253
+ })
254
+
255
+ it('rejects an unrelated output without throwing', function () {
256
+ NS.isNotaryHash(bsv.Script.buildPublicKeyHashOut(bsv.PrivateKey.fromRandom().toAddress()))
257
+ .should.equal(false)
258
+ NS.isNotaryHash('not-hex-at-all').should.equal(false)
259
+ })
260
+
261
+ // It is a scanning filter, not a validator. Saying so in a test stops it being used
262
+ // as one.
263
+ it('is only a filter — a truncated record still passes it but fails parse()', function () {
264
+ var s = NS.build(FULL)
265
+ s.chunks = s.chunks.slice(0, 6)
266
+ NS.isNotaryHash(s).should.equal(true)
267
+ ;(function () { NS.parse(s) }).should.throw()
268
+ })
269
+ })
270
+ })