@smartledger/bsv 8.3.1 → 9.1.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 (169) hide show
  1. package/CHANGELOG.md +228 -0
  2. package/README.md +152 -1031
  3. package/STABILITY.md +134 -0
  4. package/bsv-gdaf.min.js +58 -53
  5. package/bsv-ltp.min.js +23 -18
  6. package/bsv-script-helper.min.js +1 -1
  7. package/bsv-smartcontract.min.js +21 -21
  8. package/bsv.bundle.js +58 -53
  9. package/bsv.d.ts +0 -4
  10. package/bsv.min.js +58 -53
  11. package/docs/BRC220_BATCH_LEAF_AMENDMENT.md +82 -7
  12. package/docs/MODULE_REFERENCE_COMPLETE.md +27 -27
  13. package/docs/README.md +2 -1
  14. package/docs/THREAT_MODEL.md +10 -2
  15. package/docs/advanced/UTXO_MANAGER_GUIDE.md +1 -1
  16. package/docs/getting-started/INSTALLATION.md +23 -23
  17. package/docs/getting-started/QUICK_START.md +7 -7
  18. package/docs/migration/FROM_BSV_1_5_6.md +5 -5
  19. package/docs/proposals/10.0.0-package-split.md +131 -0
  20. package/index.js +15 -26
  21. package/lib/block/merkleblock.js +19 -5
  22. package/lib/covenant/helpers.js +34 -35
  23. package/lib/covenant/pushtx.js +2 -2
  24. package/lib/custom-script-helper.js +0 -16
  25. package/lib/hdprivatekey.js +15 -0
  26. package/lib/ordinals/ordlock.js +1 -1
  27. package/lib/script/interpreter.js +109 -1
  28. package/lib/smart_contract/builder.js +14 -0
  29. package/lib/smart_contract/debugger.js +2 -2
  30. package/lib/smart_contract/dsl.js +2 -2
  31. package/lib/smart_contract/index.js +0 -3
  32. package/lib/smart_contract/locks.js +1 -41
  33. package/lib/smart_contract/pels.js +1 -1
  34. package/lib/smart_contract/token.js +1 -1
  35. package/lib/util/deprecate.js +159 -0
  36. package/package.json +19 -82
  37. package/tools/gen-brc220-batch-vector.js +205 -0
  38. package/version.js +1 -1
  39. package/.mocharc.json +0 -5
  40. package/test/address.js +0 -629
  41. package/test/block/block.js +0 -239
  42. package/test/block/blockheader.js +0 -270
  43. package/test/block/merkleblock.js +0 -207
  44. package/test/build/bundle_crypto_shim.js +0 -104
  45. package/test/build/bundle_externals.js +0 -125
  46. package/test/build/bundle_smoke.js +0 -55
  47. package/test/build/esbuild_main.js +0 -65
  48. package/test/build/esm_wrapper.js +0 -73
  49. package/test/build/exports_resolution.js +0 -84
  50. package/test/build/version_sync.js +0 -14
  51. package/test/cli/smoke.js +0 -261
  52. package/test/consensus/base58-vectors.js +0 -120
  53. package/test/consensus/sv-script-vectors.js +0 -81
  54. package/test/consensus/sv-sighash-vectors.js +0 -51
  55. package/test/consensus/sv-tx-vectors.js +0 -48
  56. package/test/credentials-test.js +0 -332
  57. package/test/crypto/backend_selection.js +0 -126
  58. package/test/crypto/bn.js +0 -168
  59. package/test/crypto/ecdsa.js +0 -426
  60. package/test/crypto/elliptic-fixed.js +0 -61
  61. package/test/crypto/hash.browser.js +0 -121
  62. package/test/crypto/hash.js +0 -122
  63. package/test/crypto/point.js +0 -193
  64. package/test/crypto/random.js +0 -105
  65. package/test/crypto/security.js +0 -176
  66. package/test/crypto/shamir.js +0 -177
  67. package/test/crypto/shamir_rngcap.js +0 -29
  68. package/test/crypto/signature.js +0 -399
  69. package/test/data/bip69.json +0 -215
  70. package/test/data/bitcoin-sv/README.md +0 -170
  71. package/test/data/bitcoin-sv/base58_encode_decode.json +0 -14
  72. package/test/data/bitcoin-sv/base58_keys_invalid.json +0 -152
  73. package/test/data/bitcoin-sv/base58_keys_valid.json +0 -452
  74. package/test/data/bitcoin-sv/script_tests.json +0 -2591
  75. package/test/data/bitcoin-sv/sighash.json +0 -1003
  76. package/test/data/bitcoin-sv/tx_invalid.json +0 -285
  77. package/test/data/bitcoin-sv/tx_valid.json +0 -367
  78. package/test/data/bitcoind/base58_keys_invalid.json +0 -152
  79. package/test/data/bitcoind/base58_keys_valid.json +0 -452
  80. package/test/data/bitcoind/blocks.json +0 -27
  81. package/test/data/bitcoind/script_tests.json +0 -2244
  82. package/test/data/bitcoind/sig_canonical.json +0 -7
  83. package/test/data/bitcoind/sig_noncanonical.json +0 -22
  84. package/test/data/bitcoind/tx_invalid.json +0 -177
  85. package/test/data/bitcoind/tx_valid.json +0 -224
  86. package/test/data/blk86756-testnet.dat +0 -0
  87. package/test/data/blk86756-testnet.js +0 -12
  88. package/test/data/blk86756-testnet.json +0 -684
  89. package/test/data/ecdsa.json +0 -230
  90. package/test/data/merkleblocks.js +0 -486
  91. package/test/data/messages.json +0 -22
  92. package/test/data/sighash.json +0 -1004
  93. package/test/data/tx_creation.json +0 -85
  94. package/test/didweb/relationships.js +0 -144
  95. package/test/ecies/bitcore-ecies.js +0 -178
  96. package/test/ecies/electrum-ecies.js +0 -206
  97. package/test/encoding/base58.js +0 -131
  98. package/test/encoding/base58check.js +0 -145
  99. package/test/encoding/bufferreader.js +0 -328
  100. package/test/encoding/bufferwriter.js +0 -160
  101. package/test/encoding/varint.js +0 -104
  102. package/test/gdaf/anchor_no_key_leak.js +0 -146
  103. package/test/gdaf/anchor_spv.js +0 -163
  104. package/test/gdaf/canonicalization.js +0 -106
  105. package/test/gdaf/canonicalize.js +0 -140
  106. package/test/gdaf/zk_prover.js +0 -204
  107. package/test/hdkeys.js +0 -365
  108. package/test/hdprivatekey.js +0 -339
  109. package/test/hdpublickey.js +0 -293
  110. package/test/index.js +0 -16
  111. package/test/ltp/ids.js +0 -74
  112. package/test/ltp/right.js +0 -198
  113. package/test/ltp/verify_failclosed.js +0 -134
  114. package/test/message/message.js +0 -190
  115. package/test/mnemonic/data/fixtures.json +0 -300
  116. package/test/mnemonic/mnemonic.js +0 -277
  117. package/test/mnemonic/mocha.opts +0 -1
  118. package/test/mnemonic/pbkdf2.test.js +0 -43
  119. package/test/networks.js +0 -207
  120. package/test/notaryhash/batch_leaf.js +0 -140
  121. package/test/notaryhash/certificate.js +0 -249
  122. package/test/notaryhash/encoding.js +0 -186
  123. package/test/notaryhash/interop.js +0 -112
  124. package/test/notaryhash/merkle.js +0 -181
  125. package/test/notaryhash/script.js +0 -270
  126. package/test/notaryhash/verify.js +0 -342
  127. package/test/opcode.js +0 -186
  128. package/test/ordinals/bsv20.js +0 -337
  129. package/test/ordinals/inscription.js +0 -319
  130. package/test/ordinals/ordlock.js +0 -613
  131. package/test/privatekey.js +0 -540
  132. package/test/publickey.js +0 -411
  133. package/test/regressions.js +0 -215
  134. package/test/script/chronicle.js +0 -543
  135. package/test/script/defaults.js +0 -160
  136. package/test/script/genesis_limits.js +0 -203
  137. package/test/script/interpreter.js +0 -776
  138. package/test/script/script.js +0 -1259
  139. package/test/script/string_ops.js +0 -88
  140. package/test/security/fail_closed_contracts.js +0 -104
  141. package/test/security/threat_model_coverage.js +0 -31
  142. package/test/smart_contract/covenants.js +0 -245
  143. package/test/smart_contract/dsl_debugger.js +0 -101
  144. package/test/smart_contract/extract_field.js +0 -61
  145. package/test/smart_contract/nonenforcing_guard.js +0 -44
  146. package/test/smart_contract/ordinal_transfer.js +0 -81
  147. package/test/smart_contract/preimage.js +0 -98
  148. package/test/smart_contract/sighash_marketplace.js +0 -108
  149. package/test/smart_contract/token_generalized.js +0 -185
  150. package/test/spv/headerchain.js +0 -86
  151. package/test/spv/merkleproof.js +0 -133
  152. package/test/statuslist/failclosed.js +0 -78
  153. package/test/transaction/deserialize.js +0 -33
  154. package/test/transaction/input/input.js +0 -92
  155. package/test/transaction/input/multisig.js +0 -174
  156. package/test/transaction/input/multisigscripthash.js +0 -111
  157. package/test/transaction/input/publickey.js +0 -68
  158. package/test/transaction/input/publickeyhash.js +0 -59
  159. package/test/transaction/output.js +0 -185
  160. package/test/transaction/sighash.js +0 -91
  161. package/test/transaction/signature.js +0 -127
  162. package/test/transaction/transaction.js +0 -1299
  163. package/test/transaction/unspentoutput.js +0 -97
  164. package/test/types/dts_drift.js +0 -124
  165. package/test/types/surface_honesty.js +0 -102
  166. package/test/util/id.js +0 -72
  167. package/test/util/js.js +0 -76
  168. package/test/util/preconditions.js +0 -79
  169. package/test/vcjwt/interop.js +0 -126
@@ -1,270 +0,0 @@
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
- })
@@ -1,342 +0,0 @@
1
- 'use strict'
2
-
3
- /* global describe, it, before */
4
-
5
- // BRC-220 verification: the three checks the spec requires, wired together.
6
- //
7
- // The plan's stance is that each check must have a test that defeats it IN ISOLATION —
8
- // a suite where only the conjunction is tested cannot tell you that check 3 was never
9
- // wired up, because checks 1 and 2 carry the verdict. Each of the three below is broken
10
- // on its own with the others left intact.
11
-
12
- require('chai').should()
13
- var bsv = require('../..')
14
- var NH = require('../../lib/notaryhash')
15
- var Encoding = require('../../lib/notaryhash/encoding')
16
- var Hash = require('../../lib/crypto/hash')
17
- var BN = require('../../lib/crypto/bn')
18
-
19
- // The Bitcoin genesis coinbase — one of the reference vectors the spec names, and the
20
- // only check here that is external to this implementation.
21
- var GENESIS_COINBASE_RAWTX =
22
- '01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff' +
23
- '4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72' +
24
- '206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff' +
25
- '0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f' +
26
- '61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000'
27
- var GENESIS_COINBASE_TXID = '4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b'
28
-
29
- function rawSig (sig) {
30
- return Buffer.concat([
31
- sig.r.toArrayLike(Buffer, 'be', 32),
32
- sig.s.toArrayLike(Buffer, 'be', 32)
33
- ])
34
- }
35
-
36
- describe('BRC-220 verification', function () {
37
- var key, publicKey, payloadHash, signature, certificate, header, rawTx
38
-
39
- before(function () {
40
- key = bsv.PrivateKey.fromRandom()
41
- publicKey = key.toPublicKey().toBuffer()
42
- payloadHash = Hash.sha256(Buffer.from('the document nobody sees'))
43
-
44
- // The signer signs the 32-byte payloadHash DIRECTLY — spec §Algorithms. No endian
45
- // option: the digest IS the scalar, big-endian. Until 8.3.1 this line passed
46
- // `endian: 'little'` while the comment above it said "directly", and the suite
47
- // reversed to match, so the two agreed with each other and with nothing else.
48
- var ecdsa = bsv.crypto.ECDSA().set({ hashbuf: payloadHash, privkey: key })
49
- ecdsa.sign()
50
- signature = rawSig(ecdsa.sig)
51
-
52
- var createdAt = '2026-08-16T00:00:00.000Z'
53
- var proofHash = Encoding.proofHash({
54
- algorithm: 'ECDSA-secp256k1',
55
- hashAlgorithm: 'SHA-256',
56
- payloadHash: payloadHash,
57
- publicKey: publicKey,
58
- signature: signature,
59
- createdAtUnix: Encoding.toUnixSeconds(createdAt)
60
- })
61
-
62
- // A real transaction carrying the record, so the anchor check has something to read.
63
- var tx = new bsv.Transaction()
64
- tx.addOutput(new bsv.Transaction.Output({
65
- script: NH.Script.build({
66
- mode: NH.MODE.FULL,
67
- algorithm: 'ECDSA-secp256k1',
68
- hashAlgorithm: 'SHA-256',
69
- payloadHash: payloadHash,
70
- proofHash: proofHash,
71
- publicKey: publicKey,
72
- signature: signature
73
- }),
74
- satoshis: 0
75
- }))
76
- rawTx = tx.toString()
77
- var txid = NH.txidFromRawTx(rawTx)
78
-
79
- // A single-transaction block: the Merkle root is the txid itself, so an empty path
80
- // folds to it. Proof of work is not required for these fixtures.
81
- header = new bsv.BlockHeader({
82
- version: 1,
83
- prevHash: Buffer.alloc(32),
84
- merkleRoot: Buffer.from(txid, 'hex').reverse(),
85
- time: 1786838400,
86
- bits: 0x1d00ffff,
87
- nonce: 0
88
- })
89
-
90
- certificate = NH.Certificate.attachSPV(
91
- NH.Certificate.build({
92
- mode: NH.MODE.FULL,
93
- algorithm: 'ECDSA-secp256k1',
94
- hashAlgorithm: 'SHA-256',
95
- payloadHash: payloadHash,
96
- publicKey: publicKey,
97
- signature: signature,
98
- createdAt: createdAt,
99
- anchor: { txid: txid, blockHeight: 800000 }
100
- }),
101
- { rawTx: rawTx, blockHash: header.id, blockHeight: 800000, merkleProof: { index: 0, nodes: [] } }
102
- )
103
- })
104
-
105
- var OPTS = function () { return { header: header, requirePow: false } }
106
-
107
- describe('the happy path', function () {
108
- it('passes all three checks', function () {
109
- var report = NH.verify(certificate, OPTS())
110
- report.signature.should.equal(true)
111
- report.proofIntegrity.should.equal(true)
112
- report.anchor.should.equal(true, JSON.stringify(report.errors))
113
- report.valid.should.equal(true)
114
- })
115
-
116
- it('isValid returns a strict boolean', function () {
117
- NH.isValid(certificate, OPTS()).should.equal(true)
118
- NH.isValid(certificate, OPTS()).should.be.a('boolean')
119
- })
120
- })
121
-
122
- // Each check defeated on its own, with the other two left working.
123
- describe('check 1 — signature, defeated in isolation', function () {
124
- it('fails when the signature is forged, while proof integrity still passes', function () {
125
- var forged = Object.assign({}, certificate, { signature: 'aa'.repeat(64) })
126
- var report = NH.verify(forged, OPTS())
127
- report.signature.should.equal(false)
128
- report.valid.should.equal(false)
129
- })
130
-
131
- it('fails when the certificate names a key that did not sign', function () {
132
- var other = bsv.PrivateKey.fromRandom().toPublicKey().toBuffer()
133
- var swapped = Object.assign({}, certificate, { publicKey: other.toString('hex') })
134
- NH.verify(swapped, OPTS()).signature.should.equal(false)
135
- })
136
-
137
- // An unregistered algorithm must FAIL, never fall through to a default. Falling
138
- // through to ECDSA for an ML-DSA certificate would be catastrophic.
139
- it('fails for an algorithm no suite is registered for', function () {
140
- var pq = Object.assign({}, certificate, { algorithm: 'ML-DSA-65' })
141
- NH.verifySignature(pq).should.equal(false)
142
- NH.Suites.verify('ML-DSA-65', Buffer.alloc(32), Buffer.alloc(64), Buffer.alloc(33))
143
- .should.equal(false)
144
- })
145
- })
146
-
147
- describe('check 2 — proof integrity, defeated in isolation', function () {
148
- // The signature still verifies here: payloadHash, signature and publicKey are
149
- // untouched. Only proofHash is a lie, and only check 2 can see it.
150
- it('fails when proofHash is replaced, while the signature still verifies', function () {
151
- var lying = Object.assign({}, certificate, { proofHash: '00'.repeat(32) })
152
- var report = NH.verify(lying, OPTS())
153
- report.signature.should.equal(true)
154
- report.proofIntegrity.should.equal(false)
155
- report.valid.should.equal(false)
156
- })
157
-
158
- it('fails when createdAt is moved after issuance', function () {
159
- var moved = Object.assign({}, certificate, { createdAt: '2026-08-17T00:00:00.000Z' })
160
- NH.verify(moved, OPTS()).proofIntegrity.should.equal(false)
161
- })
162
- })
163
-
164
- describe('check 3 — anchor, defeated in isolation', function () {
165
- // Signature and proof integrity both still pass; only the anchor is wrong.
166
- it('fails when rawTx does not hash to anchor.txid', function () {
167
- var wrongTxid = Object.assign({}, certificate, {
168
- anchor: { txid: 'ff'.repeat(32), blockHeight: 800000 }
169
- })
170
- var report = NH.verify(wrongTxid, OPTS())
171
- report.signature.should.equal(true)
172
- report.proofIntegrity.should.equal(true)
173
- report.anchor.should.equal(false)
174
- report.errors.should.include('rawTx does not hash to anchor.txid')
175
- })
176
-
177
- it('fails when the merkle proof does not fold to the header root', function () {
178
- var otherHeader = new bsv.BlockHeader({
179
- version: 1,
180
- prevHash: Buffer.alloc(32),
181
- merkleRoot: Buffer.alloc(32, 0x77),
182
- time: 1786838400,
183
- bits: 0x1d00ffff,
184
- nonce: 0
185
- })
186
- NH.verifyAnchorSPV(certificate, { header: otherHeader, requirePow: false })
187
- .valid.should.equal(false)
188
- })
189
-
190
- it('fails when the transaction carries no NotaryHash record', function () {
191
- var empty = new bsv.Transaction()
192
- empty.addOutput(new bsv.Transaction.Output({
193
- script: bsv.Script.buildDataOut('nothing to see'), satoshis: 0
194
- }))
195
- var swapped = Object.assign({}, certificate, {
196
- spv: Object.assign({}, certificate.spv, { rawTx: empty.toString() })
197
- })
198
- NH.verifyAnchorSPV(swapped, OPTS()).errors.join(' ').should.match(/no NotaryHash record|does not hash/)
199
- })
200
-
201
- // THE anchor check. The certificate is internally perfect and the transaction is
202
- // real; it simply is not the transaction this certificate describes.
203
- it('fails when the on-chain record describes a different payload', function () {
204
- var otherPayload = Hash.sha256(Buffer.from('a different document'))
205
- var tx = new bsv.Transaction()
206
- tx.addOutput(new bsv.Transaction.Output({
207
- script: NH.Script.build({
208
- mode: NH.MODE.FULL,
209
- algorithm: 'ECDSA-secp256k1',
210
- hashAlgorithm: 'SHA-256',
211
- payloadHash: otherPayload,
212
- proofHash: Buffer.alloc(32, 0x01),
213
- publicKey: Buffer.from(certificate.publicKey, 'hex'),
214
- signature: Buffer.from(certificate.signature, 'hex')
215
- }),
216
- satoshis: 0
217
- }))
218
- var raw = tx.toString()
219
- var txid = NH.txidFromRawTx(raw)
220
- var h = new bsv.BlockHeader({
221
- version: 1,
222
- prevHash: Buffer.alloc(32),
223
- merkleRoot: Buffer.from(txid, 'hex').reverse(),
224
- time: 1786838400,
225
- bits: 0x1d00ffff,
226
- nonce: 0
227
- })
228
- var mismatched = Object.assign({}, certificate, {
229
- anchor: { txid: txid, blockHeight: 800000 },
230
- spv: Object.assign({}, certificate.spv, { rawTx: raw })
231
- })
232
- var report = NH.verifyAnchorSPV(mismatched, { header: h, requirePow: false })
233
- report.valid.should.equal(false)
234
- report.errors.should.include('on-chain record does not match the certificate')
235
- })
236
-
237
- // Not supplying a header is not a pass. This is the trust the spec exists to remove.
238
- it('refuses to pass without a block header', function () {
239
- var report = NH.verifyAnchorSPV(certificate, {})
240
- report.valid.should.equal(false)
241
- report.errors.join(' ').should.match(/block header is required/)
242
- })
243
-
244
- it('reports a missing SPV envelope rather than ignoring the anchor', function () {
245
- var noSpv = Object.assign({}, certificate)
246
- delete noSpv.spv
247
- NH.verifyAnchorSPV(noSpv, OPTS()).errors.should.include('certificate has no SPV envelope')
248
- })
249
- })
250
-
251
- describe('reporting', function () {
252
- it('names which check failed rather than saying only "invalid"', function () {
253
- var forged = Object.assign({}, certificate, { signature: 'aa'.repeat(64) })
254
- NH.verify(forged, OPTS()).errors.should.include('signature does not verify')
255
- })
256
-
257
- it('reports shape problems before attempting the checks', function () {
258
- var report = NH.verify({ protocol: 'NotaryHash' }, OPTS())
259
- report.shape.length.should.be.above(0)
260
- report.signature.should.equal(false)
261
- report.valid.should.equal(false)
262
- })
263
-
264
- // skipAnchor is for offline triage and must never report a valid certificate.
265
- it('never reports valid when the anchor was skipped', function () {
266
- var report = NH.verify(certificate, { skipAnchor: true })
267
- report.signature.should.equal(true)
268
- report.proofIntegrity.should.equal(true)
269
- report.valid.should.equal(false)
270
- report.errors.join(' ').should.match(/NOT verified/)
271
- })
272
- })
273
-
274
- describe('txidFromRawTx', function () {
275
- // External check: the spec names this reference vector, and it is the one assertion
276
- // here that does not depend on our own construction being right.
277
- it('matches the Bitcoin genesis coinbase transaction', function () {
278
- NH.txidFromRawTx(GENESIS_COINBASE_RAWTX).should.equal(GENESIS_COINBASE_TXID)
279
- })
280
-
281
- it('accepts a Buffer as well as hex', function () {
282
- NH.txidFromRawTx(Buffer.from(GENESIS_COINBASE_RAWTX, 'hex')).should.equal(GENESIS_COINBASE_TXID)
283
- })
284
- })
285
-
286
- describe('suite registry', function () {
287
- it('registers a caller-supplied suite and uses it', function () {
288
- var called = false
289
- NH.registerSuite('TEST-SUITE', {
290
- verify: function () { called = true; return true }
291
- })
292
- NH.Suites.verify('TEST-SUITE', Buffer.alloc(32), Buffer.alloc(8), Buffer.alloc(8))
293
- .should.equal(true)
294
- called.should.equal(true)
295
- NH.Suites.unregister('TEST-SUITE')
296
- })
297
-
298
- // A suite returning a truthy object must not smuggle a pass through — that is the
299
- // defect class this codebase has fixed most often.
300
- it('coerces a truthy non-boolean suite result to false', function () {
301
- NH.registerSuite('TRUTHY-SUITE', { verify: function () { return { ok: true } } })
302
- NH.Suites.verify('TRUTHY-SUITE', Buffer.alloc(32), Buffer.alloc(8), Buffer.alloc(8))
303
- .should.equal(false)
304
- NH.Suites.unregister('TRUTHY-SUITE')
305
- })
306
-
307
- it('survives a throwing suite without reporting a pass', function () {
308
- NH.registerSuite('THROWS', { verify: function () { throw new Error('boom') } })
309
- NH.Suites.verify('THROWS', Buffer.alloc(32), Buffer.alloc(8), Buffer.alloc(8))
310
- .should.equal(false)
311
- NH.Suites.unregister('THROWS')
312
- })
313
-
314
- it('ships only ECDSA-secp256k1 by default', function () {
315
- NH.Suites.list().should.deep.equal(['ECDSA-secp256k1'])
316
- })
317
- })
318
-
319
- describe('ECDSA suite specifics', function () {
320
- it('requires low-S, rejecting the malleated form', function () {
321
- var sig = bsv.crypto.Signature.fromDER(
322
- new bsv.crypto.Signature(
323
- BN.fromBuffer(Buffer.from(certificate.signature, 'hex').slice(0, 32)),
324
- BN.fromBuffer(Buffer.from(certificate.signature, 'hex').slice(32))
325
- ).toDER()
326
- )
327
- var high = Buffer.concat([
328
- sig.r.toArrayLike(Buffer, 'be', 32),
329
- bsv.crypto.Point.getN().sub(sig.s).toArrayLike(Buffer, 'be', 32)
330
- ])
331
- NH.Suites.verify('ECDSA-secp256k1', payloadHash, high, publicKey).should.equal(false)
332
- })
333
-
334
- it('accepts DER for the legacy encoding', function () {
335
- var raw = Buffer.from(certificate.signature, 'hex')
336
- var der = new bsv.crypto.Signature(
337
- BN.fromBuffer(raw.slice(0, 32)), BN.fromBuffer(raw.slice(32))
338
- ).toDER()
339
- NH.Suites.verify('ECDSA-secp256k1', payloadHash, der, publicKey).should.equal(true)
340
- })
341
- })
342
- })