@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.
- package/CHANGELOG.md +228 -0
- package/README.md +152 -1031
- package/STABILITY.md +134 -0
- package/bsv-gdaf.min.js +58 -53
- package/bsv-ltp.min.js +23 -18
- package/bsv-script-helper.min.js +1 -1
- package/bsv-smartcontract.min.js +21 -21
- package/bsv.bundle.js +58 -53
- package/bsv.d.ts +0 -4
- package/bsv.min.js +58 -53
- package/docs/BRC220_BATCH_LEAF_AMENDMENT.md +82 -7
- package/docs/MODULE_REFERENCE_COMPLETE.md +27 -27
- package/docs/README.md +2 -1
- package/docs/THREAT_MODEL.md +10 -2
- package/docs/advanced/UTXO_MANAGER_GUIDE.md +1 -1
- package/docs/getting-started/INSTALLATION.md +23 -23
- package/docs/getting-started/QUICK_START.md +7 -7
- package/docs/migration/FROM_BSV_1_5_6.md +5 -5
- package/docs/proposals/10.0.0-package-split.md +131 -0
- package/index.js +15 -26
- package/lib/block/merkleblock.js +19 -5
- package/lib/covenant/helpers.js +34 -35
- package/lib/covenant/pushtx.js +2 -2
- package/lib/custom-script-helper.js +0 -16
- package/lib/hdprivatekey.js +15 -0
- package/lib/ordinals/ordlock.js +1 -1
- package/lib/script/interpreter.js +109 -1
- package/lib/smart_contract/builder.js +14 -0
- package/lib/smart_contract/debugger.js +2 -2
- package/lib/smart_contract/dsl.js +2 -2
- package/lib/smart_contract/index.js +0 -3
- package/lib/smart_contract/locks.js +1 -41
- package/lib/smart_contract/pels.js +1 -1
- package/lib/smart_contract/token.js +1 -1
- package/lib/util/deprecate.js +159 -0
- package/package.json +19 -82
- package/tools/gen-brc220-batch-vector.js +205 -0
- package/version.js +1 -1
- package/.mocharc.json +0 -5
- package/test/address.js +0 -629
- package/test/block/block.js +0 -239
- package/test/block/blockheader.js +0 -270
- package/test/block/merkleblock.js +0 -207
- package/test/build/bundle_crypto_shim.js +0 -104
- package/test/build/bundle_externals.js +0 -125
- package/test/build/bundle_smoke.js +0 -55
- package/test/build/esbuild_main.js +0 -65
- package/test/build/esm_wrapper.js +0 -73
- package/test/build/exports_resolution.js +0 -84
- package/test/build/version_sync.js +0 -14
- package/test/cli/smoke.js +0 -261
- package/test/consensus/base58-vectors.js +0 -120
- package/test/consensus/sv-script-vectors.js +0 -81
- package/test/consensus/sv-sighash-vectors.js +0 -51
- package/test/consensus/sv-tx-vectors.js +0 -48
- package/test/credentials-test.js +0 -332
- package/test/crypto/backend_selection.js +0 -126
- package/test/crypto/bn.js +0 -168
- package/test/crypto/ecdsa.js +0 -426
- package/test/crypto/elliptic-fixed.js +0 -61
- package/test/crypto/hash.browser.js +0 -121
- package/test/crypto/hash.js +0 -122
- package/test/crypto/point.js +0 -193
- package/test/crypto/random.js +0 -105
- package/test/crypto/security.js +0 -176
- package/test/crypto/shamir.js +0 -177
- package/test/crypto/shamir_rngcap.js +0 -29
- package/test/crypto/signature.js +0 -399
- package/test/data/bip69.json +0 -215
- package/test/data/bitcoin-sv/README.md +0 -170
- package/test/data/bitcoin-sv/base58_encode_decode.json +0 -14
- package/test/data/bitcoin-sv/base58_keys_invalid.json +0 -152
- package/test/data/bitcoin-sv/base58_keys_valid.json +0 -452
- package/test/data/bitcoin-sv/script_tests.json +0 -2591
- package/test/data/bitcoin-sv/sighash.json +0 -1003
- package/test/data/bitcoin-sv/tx_invalid.json +0 -285
- package/test/data/bitcoin-sv/tx_valid.json +0 -367
- package/test/data/bitcoind/base58_keys_invalid.json +0 -152
- package/test/data/bitcoind/base58_keys_valid.json +0 -452
- package/test/data/bitcoind/blocks.json +0 -27
- package/test/data/bitcoind/script_tests.json +0 -2244
- package/test/data/bitcoind/sig_canonical.json +0 -7
- package/test/data/bitcoind/sig_noncanonical.json +0 -22
- package/test/data/bitcoind/tx_invalid.json +0 -177
- package/test/data/bitcoind/tx_valid.json +0 -224
- package/test/data/blk86756-testnet.dat +0 -0
- package/test/data/blk86756-testnet.js +0 -12
- package/test/data/blk86756-testnet.json +0 -684
- package/test/data/ecdsa.json +0 -230
- package/test/data/merkleblocks.js +0 -486
- package/test/data/messages.json +0 -22
- package/test/data/sighash.json +0 -1004
- package/test/data/tx_creation.json +0 -85
- package/test/didweb/relationships.js +0 -144
- package/test/ecies/bitcore-ecies.js +0 -178
- package/test/ecies/electrum-ecies.js +0 -206
- package/test/encoding/base58.js +0 -131
- package/test/encoding/base58check.js +0 -145
- package/test/encoding/bufferreader.js +0 -328
- package/test/encoding/bufferwriter.js +0 -160
- package/test/encoding/varint.js +0 -104
- package/test/gdaf/anchor_no_key_leak.js +0 -146
- package/test/gdaf/anchor_spv.js +0 -163
- package/test/gdaf/canonicalization.js +0 -106
- package/test/gdaf/canonicalize.js +0 -140
- package/test/gdaf/zk_prover.js +0 -204
- package/test/hdkeys.js +0 -365
- package/test/hdprivatekey.js +0 -339
- package/test/hdpublickey.js +0 -293
- package/test/index.js +0 -16
- package/test/ltp/ids.js +0 -74
- package/test/ltp/right.js +0 -198
- package/test/ltp/verify_failclosed.js +0 -134
- package/test/message/message.js +0 -190
- package/test/mnemonic/data/fixtures.json +0 -300
- package/test/mnemonic/mnemonic.js +0 -277
- package/test/mnemonic/mocha.opts +0 -1
- package/test/mnemonic/pbkdf2.test.js +0 -43
- package/test/networks.js +0 -207
- package/test/notaryhash/batch_leaf.js +0 -140
- package/test/notaryhash/certificate.js +0 -249
- package/test/notaryhash/encoding.js +0 -186
- package/test/notaryhash/interop.js +0 -112
- package/test/notaryhash/merkle.js +0 -181
- package/test/notaryhash/script.js +0 -270
- package/test/notaryhash/verify.js +0 -342
- package/test/opcode.js +0 -186
- package/test/ordinals/bsv20.js +0 -337
- package/test/ordinals/inscription.js +0 -319
- package/test/ordinals/ordlock.js +0 -613
- package/test/privatekey.js +0 -540
- package/test/publickey.js +0 -411
- package/test/regressions.js +0 -215
- package/test/script/chronicle.js +0 -543
- package/test/script/defaults.js +0 -160
- package/test/script/genesis_limits.js +0 -203
- package/test/script/interpreter.js +0 -776
- package/test/script/script.js +0 -1259
- package/test/script/string_ops.js +0 -88
- package/test/security/fail_closed_contracts.js +0 -104
- package/test/security/threat_model_coverage.js +0 -31
- package/test/smart_contract/covenants.js +0 -245
- package/test/smart_contract/dsl_debugger.js +0 -101
- package/test/smart_contract/extract_field.js +0 -61
- package/test/smart_contract/nonenforcing_guard.js +0 -44
- package/test/smart_contract/ordinal_transfer.js +0 -81
- package/test/smart_contract/preimage.js +0 -98
- package/test/smart_contract/sighash_marketplace.js +0 -108
- package/test/smart_contract/token_generalized.js +0 -185
- package/test/spv/headerchain.js +0 -86
- package/test/spv/merkleproof.js +0 -133
- package/test/statuslist/failclosed.js +0 -78
- package/test/transaction/deserialize.js +0 -33
- package/test/transaction/input/input.js +0 -92
- package/test/transaction/input/multisig.js +0 -174
- package/test/transaction/input/multisigscripthash.js +0 -111
- package/test/transaction/input/publickey.js +0 -68
- package/test/transaction/input/publickeyhash.js +0 -59
- package/test/transaction/output.js +0 -185
- package/test/transaction/sighash.js +0 -91
- package/test/transaction/signature.js +0 -127
- package/test/transaction/transaction.js +0 -1299
- package/test/transaction/unspentoutput.js +0 -97
- package/test/types/dts_drift.js +0 -124
- package/test/types/surface_honesty.js +0 -102
- package/test/util/id.js +0 -72
- package/test/util/js.js +0 -76
- package/test/util/preconditions.js +0 -79
- package/test/vcjwt/interop.js +0 -126
package/test/networks.js
DELETED
|
@@ -1,207 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
var expect = require('chai').expect
|
|
4
|
-
var should = require('chai').should()
|
|
5
|
-
var bsv = require('..')
|
|
6
|
-
var networks = bsv.Networks
|
|
7
|
-
|
|
8
|
-
describe('Networks', function () {
|
|
9
|
-
var customnet
|
|
10
|
-
|
|
11
|
-
it('should contain all Networks', function () {
|
|
12
|
-
should.exist(networks.livenet)
|
|
13
|
-
should.exist(networks.testnet)
|
|
14
|
-
should.exist(networks.stn)
|
|
15
|
-
should.exist(networks.defaultNetwork)
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
it('should be able to define a custom Network', function () {
|
|
19
|
-
var custom = {
|
|
20
|
-
name: 'customnet',
|
|
21
|
-
alias: 'mynet',
|
|
22
|
-
pubkeyhash: 0x10,
|
|
23
|
-
privatekey: 0x90,
|
|
24
|
-
scripthash: 0x08,
|
|
25
|
-
xpubkey: 0x0278b20e,
|
|
26
|
-
xprivkey: 0x0278ade4,
|
|
27
|
-
networkMagic: 0xe7beb4d4,
|
|
28
|
-
port: 20001,
|
|
29
|
-
dnsSeeds: [
|
|
30
|
-
'localhost',
|
|
31
|
-
'mynet.localhost'
|
|
32
|
-
]
|
|
33
|
-
}
|
|
34
|
-
networks.add(custom)
|
|
35
|
-
customnet = networks.get('customnet')
|
|
36
|
-
for (var key in custom) {
|
|
37
|
-
if (key !== 'networkMagic') {
|
|
38
|
-
customnet[key].should.equal(custom[key])
|
|
39
|
-
} else {
|
|
40
|
-
var expected = Buffer.from('e7beb4d4', 'hex')
|
|
41
|
-
customnet[key].should.deep.equal(expected)
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
it('should have network magic for testnet', function () {
|
|
47
|
-
var testnet = networks.get('testnet')
|
|
48
|
-
Buffer.isBuffer(testnet.networkMagic).should.equal(true)
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
it('should have network magic for stn', function () {
|
|
52
|
-
var stn = networks.get('stn')
|
|
53
|
-
Buffer.isBuffer(stn.networkMagic).should.equal(true)
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
it('can remove a custom network', function () {
|
|
57
|
-
networks.remove(customnet)
|
|
58
|
-
var net = networks.get('customnet')
|
|
59
|
-
should.equal(net, undefined)
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
it('should not set a network map for an undefined value', function () {
|
|
63
|
-
var custom = {
|
|
64
|
-
name: 'somenet',
|
|
65
|
-
pubkeyhash: 0x13,
|
|
66
|
-
privatekey: 0x93,
|
|
67
|
-
scripthash: 0x11,
|
|
68
|
-
xpubkey: 0x0278b20f,
|
|
69
|
-
xprivkey: 0x0278ade5,
|
|
70
|
-
networkMagic: 0xe7beb4d5,
|
|
71
|
-
port: 20008,
|
|
72
|
-
dnsSeeds: [
|
|
73
|
-
'somenet.localhost'
|
|
74
|
-
]
|
|
75
|
-
}
|
|
76
|
-
networks.add(custom)
|
|
77
|
-
var network = networks.get(undefined)
|
|
78
|
-
should.not.exist(network)
|
|
79
|
-
var somenet = networks.get('somenet')
|
|
80
|
-
should.exist(somenet)
|
|
81
|
-
somenet.name.should.equal('somenet')
|
|
82
|
-
networks.remove(somenet)
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
var constants = ['name', 'alias', 'pubkeyhash', 'scripthash', 'xpubkey', 'xprivkey']
|
|
86
|
-
|
|
87
|
-
constants.forEach(function (key) {
|
|
88
|
-
it('should have constant ' + key + ' for livenet, testnet and stn', function () {
|
|
89
|
-
networks.testnet.hasOwnProperty(key).should.equal(true)
|
|
90
|
-
networks.livenet.hasOwnProperty(key).should.equal(true)
|
|
91
|
-
networks.stn.hasOwnProperty(key).should.equal(true)
|
|
92
|
-
})
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
it('tests only for the specified key', function () {
|
|
96
|
-
expect(networks.get(0x6f, 'pubkeyhash')).to.equal(networks.testnet)
|
|
97
|
-
expect(networks.get(0x6f, 'privatekey')).to.equal(undefined)
|
|
98
|
-
})
|
|
99
|
-
|
|
100
|
-
it('can test for multiple keys', function () {
|
|
101
|
-
expect(networks.get(0x6f, ['pubkeyhash', 'scripthash'])).to.equal(networks.testnet)
|
|
102
|
-
expect(networks.get(0xc4, ['pubkeyhash', 'scripthash'])).to.equal(networks.testnet)
|
|
103
|
-
expect(networks.get(0x6f, ['privatekey', 'port'])).to.equal(undefined)
|
|
104
|
-
})
|
|
105
|
-
|
|
106
|
-
it('should have regtest network', function () {
|
|
107
|
-
expect(networks.get('regtest').name).to.equal('regtest')
|
|
108
|
-
})
|
|
109
|
-
|
|
110
|
-
it('should have testnet network', function () {
|
|
111
|
-
expect(networks.get('testnet').name).to.equal('testnet')
|
|
112
|
-
})
|
|
113
|
-
|
|
114
|
-
it('should have stn network', function () {
|
|
115
|
-
expect(networks.get('stn').name).to.equal('stn')
|
|
116
|
-
})
|
|
117
|
-
|
|
118
|
-
it('should have livenet network', function () {
|
|
119
|
-
expect(networks.get('livenet').name).to.equal('livenet')
|
|
120
|
-
})
|
|
121
|
-
|
|
122
|
-
it('should have bchtest cashAddrPrefix', function () {
|
|
123
|
-
expect(networks.get('testnet').cashAddrPrefix).to.equal('bchtest')
|
|
124
|
-
})
|
|
125
|
-
|
|
126
|
-
it('should have bchreg cashAddrPrefix', function () {
|
|
127
|
-
expect(networks.get('regtest').cashAddrPrefix).to.equal('bchreg')
|
|
128
|
-
})
|
|
129
|
-
|
|
130
|
-
it('should have bchreg cashAddrPrefix after enableRegtest is called', function () {
|
|
131
|
-
var network = networks.get('testnet')
|
|
132
|
-
networks.enableRegtest()
|
|
133
|
-
expect(network.cashAddrPrefix).to.equal('bchreg')
|
|
134
|
-
})
|
|
135
|
-
|
|
136
|
-
it('should have bchtest cashAddrPrefix after disableRegtest is called', function () {
|
|
137
|
-
var network = networks.get('testnet')
|
|
138
|
-
networks.disableRegtest()
|
|
139
|
-
expect(network.cashAddrPrefix).to.equal('bchtest')
|
|
140
|
-
})
|
|
141
|
-
it('should have bsvstn cashAddrPrefix after enableStn is called', function () {
|
|
142
|
-
var network = networks.get('testnet')
|
|
143
|
-
networks.enableStn()
|
|
144
|
-
expect(network.cashAddrPrefix).to.equal('bsvstn')
|
|
145
|
-
})
|
|
146
|
-
|
|
147
|
-
it('should have bchtest cashAddrPrefix after disableStn is called', function () {
|
|
148
|
-
var network = networks.get('testnet')
|
|
149
|
-
networks.disableStn()
|
|
150
|
-
expect(network.cashAddrPrefix).to.equal('bchtest')
|
|
151
|
-
})
|
|
152
|
-
|
|
153
|
-
it('converts to string using the "name" property', function () {
|
|
154
|
-
networks.livenet.toString().should.equal('livenet')
|
|
155
|
-
})
|
|
156
|
-
|
|
157
|
-
it('network object should be immutable', function () {
|
|
158
|
-
expect(networks.testnet.name).to.equal('testnet')
|
|
159
|
-
var fn = function () { networks.testnet.name = 'livenet' }
|
|
160
|
-
expect(fn).to.throw(TypeError)
|
|
161
|
-
})
|
|
162
|
-
// The `cashAddrPrefixArray` getter has three branches; only the STN one reached for
|
|
163
|
-
// the helper on the STN data object instead of the module-level function, so reading
|
|
164
|
-
// it with STN enabled threw `STN.cashAddrPrefixToArray is not a function`. The tests
|
|
165
|
-
// above cover `cashAddrPrefix` (the string) but never the array getter, which is why
|
|
166
|
-
// a live TypeError survived a 4,597-test suite.
|
|
167
|
-
describe('cashAddrPrefixArray across the regtest/stn toggles', function () {
|
|
168
|
-
// enableStn/enableRegtest mutate the shared testnet object, so without a reset the
|
|
169
|
-
// flags leak into every later test in the run.
|
|
170
|
-
afterEach(function () {
|
|
171
|
-
networks.disableStn()
|
|
172
|
-
networks.disableRegtest()
|
|
173
|
-
})
|
|
174
|
-
|
|
175
|
-
function assertPrefixArray (arr) {
|
|
176
|
-
Array.isArray(arr).should.equal(true)
|
|
177
|
-
arr.length.should.be.above(0)
|
|
178
|
-
arr.forEach(function (n) { n.should.be.a('number') })
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
it('works with stn enabled', function () {
|
|
182
|
-
networks.enableStn()
|
|
183
|
-
assertPrefixArray(networks.testnet.cashAddrPrefixArray)
|
|
184
|
-
})
|
|
185
|
-
|
|
186
|
-
it('works with regtest enabled', function () {
|
|
187
|
-
networks.enableRegtest()
|
|
188
|
-
assertPrefixArray(networks.testnet.cashAddrPrefixArray)
|
|
189
|
-
})
|
|
190
|
-
|
|
191
|
-
it('works with neither enabled', function () {
|
|
192
|
-
assertPrefixArray(networks.testnet.cashAddrPrefixArray)
|
|
193
|
-
})
|
|
194
|
-
|
|
195
|
-
it('returns a different prefix array per mode', function () {
|
|
196
|
-
var plain = networks.testnet.cashAddrPrefixArray.join(',')
|
|
197
|
-
networks.enableStn()
|
|
198
|
-
var stn = networks.testnet.cashAddrPrefixArray.join(',')
|
|
199
|
-
networks.disableStn()
|
|
200
|
-
networks.enableRegtest()
|
|
201
|
-
var regtest = networks.testnet.cashAddrPrefixArray.join(',')
|
|
202
|
-
stn.should.not.equal(plain)
|
|
203
|
-
regtest.should.not.equal(plain)
|
|
204
|
-
stn.should.not.equal(regtest)
|
|
205
|
-
})
|
|
206
|
-
})
|
|
207
|
-
})
|
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
/* global describe, it */
|
|
4
|
-
|
|
5
|
-
// BRC-220 batch mode: what is in a Merkle leaf.
|
|
6
|
-
//
|
|
7
|
-
// The spec writes `leaf = SHA256(0x00 ‖ d)` and never binds `d`. That is RFC 6962's own
|
|
8
|
-
// generic notation for the construction, quoted to identify WHICH tree — domain
|
|
9
|
-
// separation, the power-of-two split, no duplicated last leaf — not to define the leaf's
|
|
10
|
-
// contents. `canonicalBytes` appears once in the whole document, in the proofHash
|
|
11
|
-
// definition, and nowhere in the batch text.
|
|
12
|
-
//
|
|
13
|
-
// This library reads `d` as proofHash. The alternative reading, d = canonicalBytes, is
|
|
14
|
-
// equally sound cryptographically and produces a DIFFERENT root — so the two do not
|
|
15
|
-
// interoperate, and the failure is silent: the fold just does not reach the root.
|
|
16
|
-
//
|
|
17
|
-
// These tests pin the choice, and pin the divergence too. A comment saying "we use
|
|
18
|
-
// proofHash" would not fail if someone changed it; this does.
|
|
19
|
-
//
|
|
20
|
-
// Reasoning and proposed spec text: docs/BRC220_BATCH_LEAF_AMENDMENT.md
|
|
21
|
-
|
|
22
|
-
require('chai').should()
|
|
23
|
-
var NotaryHash = require('../../lib/notaryhash')
|
|
24
|
-
var Merkle = require('../../lib/notaryhash/merkle')
|
|
25
|
-
var Hash = require('../../lib/crypto/hash')
|
|
26
|
-
var NotaryScript = require('../../lib/notaryhash/script')
|
|
27
|
-
|
|
28
|
-
// Four fixed proofHash values, standing in for a four-proof batch.
|
|
29
|
-
var PROOF_HASHES = [
|
|
30
|
-
'00'.repeat(31) + '01',
|
|
31
|
-
'00'.repeat(31) + '02',
|
|
32
|
-
'ab'.repeat(32),
|
|
33
|
-
'ff'.repeat(32)
|
|
34
|
-
].map(function (h) { return Buffer.from(h, 'hex') })
|
|
35
|
-
|
|
36
|
-
function batchCertificate (index, leaves) {
|
|
37
|
-
return {
|
|
38
|
-
mode: NotaryScript.MODE.BATCH,
|
|
39
|
-
proofHash: leaves[index].toString('hex'),
|
|
40
|
-
merkle: {
|
|
41
|
-
root: Merkle.root(leaves).toString('hex'),
|
|
42
|
-
leafIndex: index,
|
|
43
|
-
leafCount: leaves.length,
|
|
44
|
-
path: Merkle.path(leaves, index).map(function (n) { return n.toString('hex') })
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
describe('BRC-220 batch leaf', function () {
|
|
50
|
-
describe('the leaf datum is proofHash', function () {
|
|
51
|
-
it('a leaf is SHA-256(0x00 || proofHash)', function () {
|
|
52
|
-
var proofHash = PROOF_HASHES[0]
|
|
53
|
-
Merkle.hashLeaf(proofHash).toString('hex').should.equal(
|
|
54
|
-
Hash.sha256(Buffer.concat([Buffer.from([0x00]), proofHash])).toString('hex')
|
|
55
|
-
)
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
// Stated the long way round, because this is the identity the spec leaves open.
|
|
59
|
-
it('is therefore SHA-256(0x00 || SHA-256(canonicalBytes))', function () {
|
|
60
|
-
var canonicalBytes = Buffer.from('any canonical proof bytes at all')
|
|
61
|
-
var proofHash = Hash.sha256(canonicalBytes)
|
|
62
|
-
Merkle.hashLeaf(proofHash).toString('hex').should.equal(
|
|
63
|
-
Hash.sha256(Buffer.concat([
|
|
64
|
-
Buffer.from([0x00]), Hash.sha256(canonicalBytes)
|
|
65
|
-
])).toString('hex')
|
|
66
|
-
)
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
it('verifies inclusion for every leaf of a four-proof batch', function () {
|
|
70
|
-
for (var i = 0; i < PROOF_HASHES.length; i++) {
|
|
71
|
-
var report = NotaryHash.verifyBatchInclusion(batchCertificate(i, PROOF_HASHES))
|
|
72
|
-
report.valid.should.equal(true, 'leaf ' + i + ' should verify')
|
|
73
|
-
report.errors.should.deep.equal([])
|
|
74
|
-
}
|
|
75
|
-
})
|
|
76
|
-
})
|
|
77
|
-
|
|
78
|
-
describe('the two readings of `d` are not interchangeable', function () {
|
|
79
|
-
var canonicalBytesSet = [
|
|
80
|
-
Buffer.from('proof one canonical bytes'),
|
|
81
|
-
Buffer.from('proof two canonical bytes'),
|
|
82
|
-
Buffer.from('proof three canonical bytes'),
|
|
83
|
-
Buffer.from('proof four canonical bytes')
|
|
84
|
-
]
|
|
85
|
-
var proofHashes = canonicalBytesSet.map(function (b) { return Hash.sha256(b) })
|
|
86
|
-
|
|
87
|
-
it('produces a different root under each reading', function () {
|
|
88
|
-
Merkle.root(proofHashes).toString('hex')
|
|
89
|
-
.should.not.equal(Merkle.root(canonicalBytesSet).toString('hex'))
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
// The interoperability failure, made concrete. This is what a batch built by an
|
|
93
|
-
// implementation using the other reading would look like to this verifier.
|
|
94
|
-
it('REJECTS a certificate whose tree was built over canonicalBytes', function () {
|
|
95
|
-
var certificate = {
|
|
96
|
-
mode: NotaryScript.MODE.BATCH,
|
|
97
|
-
proofHash: proofHashes[1].toString('hex'),
|
|
98
|
-
merkle: {
|
|
99
|
-
// Root and path from the canonicalBytes tree — the other reading.
|
|
100
|
-
root: Merkle.root(canonicalBytesSet).toString('hex'),
|
|
101
|
-
leafIndex: 1,
|
|
102
|
-
leafCount: 4,
|
|
103
|
-
path: Merkle.path(canonicalBytesSet, 1).map(function (n) { return n.toString('hex') })
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
var report = NotaryHash.verifyBatchInclusion(certificate)
|
|
107
|
-
report.valid.should.equal(false)
|
|
108
|
-
report.errors.should.deep.equal(['merkle inclusion proof does not fold to the batch root'])
|
|
109
|
-
})
|
|
110
|
-
|
|
111
|
-
// Both are sound; neither is being called wrong. The point is only that a choice had
|
|
112
|
-
// to be made and that it is checkable.
|
|
113
|
-
it('both readings are domain-separated and second-preimage resistant', function () {
|
|
114
|
-
var leafBytes = Buffer.alloc(64, 7)
|
|
115
|
-
Merkle.hashLeaf(leafBytes).toString('hex')
|
|
116
|
-
.should.not.equal(Merkle.hashNode(Buffer.alloc(32, 7), Buffer.alloc(32, 7)).toString('hex'))
|
|
117
|
-
})
|
|
118
|
-
})
|
|
119
|
-
|
|
120
|
-
describe('the verdict is a strict report, not a truthy object', function () {
|
|
121
|
-
it('reports valid:false rather than throwing on a malformed merkle object', function () {
|
|
122
|
-
var report = NotaryHash.verifyBatchInclusion({
|
|
123
|
-
mode: NotaryScript.MODE.BATCH,
|
|
124
|
-
proofHash: PROOF_HASHES[0].toString('hex'),
|
|
125
|
-
merkle: { root: 'not-hex', leafIndex: 0, leafCount: 1, path: [] }
|
|
126
|
-
})
|
|
127
|
-
report.valid.should.equal(false)
|
|
128
|
-
report.errors.length.should.be.above(0)
|
|
129
|
-
})
|
|
130
|
-
|
|
131
|
-
it('rejects a non-batch certificate by mode', function () {
|
|
132
|
-
var report = NotaryHash.verifyBatchInclusion({
|
|
133
|
-
mode: NotaryScript.MODE.FULL,
|
|
134
|
-
proofHash: PROOF_HASHES[0].toString('hex')
|
|
135
|
-
})
|
|
136
|
-
report.valid.should.equal(false)
|
|
137
|
-
report.errors.should.deep.equal(['certificate is not in batch mode'])
|
|
138
|
-
})
|
|
139
|
-
})
|
|
140
|
-
})
|
|
@@ -1,249 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
/* global describe, it */
|
|
4
|
-
|
|
5
|
-
// BRC-220 certificate. This is validity check 2 of the three the spec requires — proof
|
|
6
|
-
// integrity — plus the object a verifier is actually handed.
|
|
7
|
-
//
|
|
8
|
-
// The tests below concentrate on three things the spec makes explicit and that would fail
|
|
9
|
-
// silently if got wrong: the certificate carries FULL blobs even when the chain carries
|
|
10
|
-
// digests; the SPV envelope never disturbs proofHash; and proofHash is over the binary
|
|
11
|
-
// proof bytes, NOT over the certificate JSON.
|
|
12
|
-
|
|
13
|
-
require('chai').should()
|
|
14
|
-
var Certificate = require('../../lib/notaryhash/certificate')
|
|
15
|
-
var Encoding = require('../../lib/notaryhash/encoding')
|
|
16
|
-
var NS = require('../../lib/notaryhash/script')
|
|
17
|
-
var Hash = require('../../lib/crypto/hash')
|
|
18
|
-
var JCS = require('../../lib/util/jcs')
|
|
19
|
-
|
|
20
|
-
var PARAMS = {
|
|
21
|
-
mode: NS.MODE.FULL,
|
|
22
|
-
algorithm: 'ECDSA-secp256k1',
|
|
23
|
-
hashAlgorithm: 'SHA-256',
|
|
24
|
-
payloadHash: Buffer.alloc(32, 0x11),
|
|
25
|
-
publicKey: Buffer.alloc(33, 0x02),
|
|
26
|
-
signature: Buffer.alloc(64, 0x30),
|
|
27
|
-
createdAt: '2026-08-16T00:00:00.000Z',
|
|
28
|
-
anchor: { txid: 'ab'.repeat(32), blockHeight: 800000 }
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
var SPV = {
|
|
32
|
-
rawTx: 'deadbeef',
|
|
33
|
-
blockHash: 'cc'.repeat(32),
|
|
34
|
-
blockHeight: 800000,
|
|
35
|
-
merkleProof: { index: 0, nodes: ['aa'.repeat(32), '*'] }
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
describe('BRC-220 certificate', function () {
|
|
39
|
-
describe('build', function () {
|
|
40
|
-
it('carries every field the spec requires', function () {
|
|
41
|
-
var cert = Certificate.build(PARAMS)
|
|
42
|
-
Certificate.REQUIRED_FIELDS.forEach(function (field) {
|
|
43
|
-
;(cert[field] === undefined).should.equal(false, 'missing ' + field)
|
|
44
|
-
})
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
it('defaults encoding to raw', function () {
|
|
48
|
-
Certificate.build(PARAMS).encoding.should.equal('raw')
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
it('accepts der for Bitcoin-native signers', function () {
|
|
52
|
-
Certificate.build(Object.assign({}, PARAMS, { encoding: 'der' })).encoding.should.equal('der')
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
it('rejects an unknown encoding rather than passing it through', function () {
|
|
56
|
-
;(function () {
|
|
57
|
-
Certificate.build(Object.assign({}, PARAMS, { encoding: 'base64' }))
|
|
58
|
-
}).should.throw(/encoding must be/)
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
it('hex-encodes the byte fields', function () {
|
|
62
|
-
var cert = Certificate.build(PARAMS)
|
|
63
|
-
cert.payloadHash.should.equal(PARAMS.payloadHash.toString('hex'))
|
|
64
|
-
cert.publicKey.should.equal(PARAMS.publicKey.toString('hex'))
|
|
65
|
-
cert.signature.should.equal(PARAMS.signature.toString('hex'))
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
// proofHash is computed, never accepted. A caller cannot hand in a value that
|
|
69
|
-
// disagrees with the fields beside it — which is precisely the artefact this protocol
|
|
70
|
-
// exists to make impossible.
|
|
71
|
-
it('computes proofHash rather than accepting one', function () {
|
|
72
|
-
var cert = Certificate.build(Object.assign({}, PARAMS, { proofHash: 'ff'.repeat(32) }))
|
|
73
|
-
cert.proofHash.should.not.equal('ff'.repeat(32))
|
|
74
|
-
Certificate.proofHashMatches(cert).should.equal(true)
|
|
75
|
-
})
|
|
76
|
-
|
|
77
|
-
it('requires a merkle proof for batch mode', function () {
|
|
78
|
-
;(function () {
|
|
79
|
-
Certificate.build(Object.assign({}, PARAMS, { mode: NS.MODE.BATCH }))
|
|
80
|
-
}).should.throw(/merkle/)
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
it('carries the merkle proof when batched', function () {
|
|
84
|
-
var cert = Certificate.build(Object.assign({}, PARAMS, {
|
|
85
|
-
mode: NS.MODE.BATCH,
|
|
86
|
-
merkle: { root: 'aa'.repeat(32), leafIndex: 2, leafCount: 8, path: [] }
|
|
87
|
-
}))
|
|
88
|
-
cert.merkle.leafIndex.should.equal(2)
|
|
89
|
-
cert.merkle.leafCount.should.equal(8)
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
// Hybrid puts only digests on chain; the certificate keeps the originals. That
|
|
93
|
-
// asymmetry is the whole point of the mode.
|
|
94
|
-
it('keeps the FULL key and signature in hybrid mode', function () {
|
|
95
|
-
var cert = Certificate.build(Object.assign({}, PARAMS, { mode: NS.MODE.HYBRID }))
|
|
96
|
-
cert.publicKey.should.equal(PARAMS.publicKey.toString('hex'))
|
|
97
|
-
cert.signature.should.equal(PARAMS.signature.toString('hex'))
|
|
98
|
-
// What goes on chain is the digest of each — different values entirely.
|
|
99
|
-
var onChain = NS.parse(NS.build(Object.assign({}, PARAMS, { mode: NS.MODE.HYBRID, proofHash: Buffer.alloc(32) })))
|
|
100
|
-
onChain.publicKeyHash.toString('hex').should.equal(Hash.sha256(PARAMS.publicKey).toString('hex'))
|
|
101
|
-
onChain.publicKeyHash.toString('hex').should.not.equal(cert.publicKey)
|
|
102
|
-
})
|
|
103
|
-
})
|
|
104
|
-
|
|
105
|
-
describe('proof integrity (validity check 2)', function () {
|
|
106
|
-
it('matches for a well-formed certificate', function () {
|
|
107
|
-
Certificate.proofHashMatches(Certificate.build(PARAMS)).should.equal(true)
|
|
108
|
-
})
|
|
109
|
-
|
|
110
|
-
// Each field is inside the canonical proof bytes, so tampering with any of them must
|
|
111
|
-
// be detected. This is the certificate-level counterpart of the encoding module's
|
|
112
|
-
// coverage test.
|
|
113
|
-
it('detects tampering with any covered field', function () {
|
|
114
|
-
var cert = Certificate.build(PARAMS)
|
|
115
|
-
var tampers = {
|
|
116
|
-
algorithm: 'ML-DSA-65',
|
|
117
|
-
hashAlgorithm: 'SHA-512',
|
|
118
|
-
payloadHash: 'ff'.repeat(32),
|
|
119
|
-
publicKey: '03' + '11'.repeat(32),
|
|
120
|
-
signature: 'aa'.repeat(64),
|
|
121
|
-
createdAt: '2026-08-17T00:00:00.000Z'
|
|
122
|
-
}
|
|
123
|
-
Object.keys(tampers).forEach(function (field) {
|
|
124
|
-
var bad = Object.assign({}, cert)
|
|
125
|
-
bad[field] = tampers[field]
|
|
126
|
-
Certificate.proofHashMatches(bad)
|
|
127
|
-
.should.equal(false, 'tampering with ' + field + ' was not detected')
|
|
128
|
-
})
|
|
129
|
-
})
|
|
130
|
-
|
|
131
|
-
// Fields NOT in the canonical proof bytes must not affect it — otherwise the SPV
|
|
132
|
-
// envelope could not be additive.
|
|
133
|
-
it('is unaffected by fields outside the canonical bytes', function () {
|
|
134
|
-
var cert = Certificate.build(PARAMS)
|
|
135
|
-
var moved = Object.assign({}, cert, {
|
|
136
|
-
anchor: { txid: 'ff'.repeat(32), blockHeight: 999999 },
|
|
137
|
-
mode: NS.MODE.HYBRID
|
|
138
|
-
})
|
|
139
|
-
Certificate.proofHashMatches(moved).should.equal(true)
|
|
140
|
-
})
|
|
141
|
-
|
|
142
|
-
it('returns a strict boolean, never a truthy object', function () {
|
|
143
|
-
Certificate.proofHashMatches(Certificate.build(PARAMS)).should.be.a('boolean')
|
|
144
|
-
Certificate.proofHashMatches(null).should.equal(false)
|
|
145
|
-
Certificate.proofHashMatches({}).should.equal(false)
|
|
146
|
-
Certificate.proofHashMatches({ proofHash: 'nonsense' }).should.equal(false)
|
|
147
|
-
})
|
|
148
|
-
|
|
149
|
-
// The hex fields decode to bytes before hashing. Hashing the strings would be twice
|
|
150
|
-
// as many bytes and produce something that still looks like a hash.
|
|
151
|
-
it('hashes the decoded bytes, not the hex strings', function () {
|
|
152
|
-
var cert = Certificate.build(PARAMS)
|
|
153
|
-
var overHex = Hash.sha256(Encoding.canonicalBytes({
|
|
154
|
-
algorithm: cert.algorithm,
|
|
155
|
-
hashAlgorithm: cert.hashAlgorithm,
|
|
156
|
-
payloadHash: Buffer.from(cert.payloadHash, 'utf8'), // the STRING, wrongly
|
|
157
|
-
publicKey: Buffer.from(cert.publicKey, 'hex'),
|
|
158
|
-
signature: Buffer.from(cert.signature, 'hex'),
|
|
159
|
-
createdAtUnix: Encoding.toUnixSeconds(cert.createdAt)
|
|
160
|
-
})).toString('hex')
|
|
161
|
-
overHex.should.not.equal(cert.proofHash)
|
|
162
|
-
})
|
|
163
|
-
})
|
|
164
|
-
|
|
165
|
-
describe('SPV envelope', function () {
|
|
166
|
-
// The spec's explicit guarantee. If it ever broke, every previously issued
|
|
167
|
-
// certificate would become unverifiable.
|
|
168
|
-
it('never changes proofHash', function () {
|
|
169
|
-
var cert = Certificate.build(PARAMS)
|
|
170
|
-
var withSPV = Certificate.attachSPV(cert, SPV)
|
|
171
|
-
withSPV.proofHash.should.equal(cert.proofHash)
|
|
172
|
-
Certificate.proofHashMatches(withSPV).should.equal(true)
|
|
173
|
-
})
|
|
174
|
-
|
|
175
|
-
it('does not mutate the certificate it was given', function () {
|
|
176
|
-
var cert = Certificate.build(PARAMS)
|
|
177
|
-
Certificate.attachSPV(cert, SPV)
|
|
178
|
-
;(cert.spv === undefined).should.equal(true)
|
|
179
|
-
})
|
|
180
|
-
|
|
181
|
-
it('defaults format to TSC', function () {
|
|
182
|
-
Certificate.attachSPV(Certificate.build(PARAMS), SPV).spv.format.should.equal('TSC')
|
|
183
|
-
})
|
|
184
|
-
|
|
185
|
-
it('keeps an explicit format, for BUMP or BEEF', function () {
|
|
186
|
-
var withSPV = Certificate.attachSPV(Certificate.build(PARAMS),
|
|
187
|
-
Object.assign({}, SPV, { format: 'BUMP' }))
|
|
188
|
-
withSPV.spv.format.should.equal('BUMP')
|
|
189
|
-
})
|
|
190
|
-
})
|
|
191
|
-
|
|
192
|
-
describe('validateShape', function () {
|
|
193
|
-
it('passes a well-formed certificate', function () {
|
|
194
|
-
Certificate.validateShape(Certificate.build(PARAMS)).should.deep.equal([])
|
|
195
|
-
})
|
|
196
|
-
|
|
197
|
-
it('names every missing required field', function () {
|
|
198
|
-
var problems = Certificate.validateShape({})
|
|
199
|
-
problems.length.should.equal(Certificate.REQUIRED_FIELDS.length)
|
|
200
|
-
problems[0].should.match(/missing required field/)
|
|
201
|
-
})
|
|
202
|
-
|
|
203
|
-
it('rejects a foreign protocol or version', function () {
|
|
204
|
-
var cert = Certificate.build(PARAMS)
|
|
205
|
-
Certificate.validateShape(Object.assign({}, cert, { protocol: 'Other' }))
|
|
206
|
-
.should.include('protocol must be "NotaryHash"')
|
|
207
|
-
Certificate.validateShape(Object.assign({}, cert, { version: 2 }))
|
|
208
|
-
.should.include('unsupported version: 2')
|
|
209
|
-
})
|
|
210
|
-
|
|
211
|
-
it('rejects a hash field of the wrong length or that is not hex', function () {
|
|
212
|
-
var cert = Certificate.build(PARAMS)
|
|
213
|
-
Certificate.validateShape(Object.assign({}, cert, { payloadHash: 'ab' }))
|
|
214
|
-
.should.include('payloadHash must be 32 bytes (64 hex chars)')
|
|
215
|
-
Certificate.validateShape(Object.assign({}, cert, { proofHash: 'zz'.repeat(32) }))
|
|
216
|
-
.should.include('proofHash must be a hex string')
|
|
217
|
-
})
|
|
218
|
-
|
|
219
|
-
// Shape is not verification, and a test says so — otherwise it gets used as one.
|
|
220
|
-
it('says nothing about whether the proofHash is correct', function () {
|
|
221
|
-
var cert = Certificate.build(PARAMS)
|
|
222
|
-
var lying = Object.assign({}, cert, { proofHash: '00'.repeat(32) })
|
|
223
|
-
Certificate.validateShape(lying).should.deep.equal([])
|
|
224
|
-
Certificate.proofHashMatches(lying).should.equal(false)
|
|
225
|
-
})
|
|
226
|
-
})
|
|
227
|
-
|
|
228
|
-
describe('canonicalize', function () {
|
|
229
|
-
it('is RFC 8785 over the certificate', function () {
|
|
230
|
-
var cert = Certificate.build(PARAMS)
|
|
231
|
-
Certificate.canonicalize(cert).should.equal(JCS.stringify(cert))
|
|
232
|
-
})
|
|
233
|
-
|
|
234
|
-
it('sorts keys, so transport order does not matter', function () {
|
|
235
|
-
var cert = Certificate.build(PARAMS)
|
|
236
|
-
var reordered = {}
|
|
237
|
-
Object.keys(cert).reverse().forEach(function (k) { reordered[k] = cert[k] })
|
|
238
|
-
Certificate.canonicalize(reordered).should.equal(Certificate.canonicalize(cert))
|
|
239
|
-
})
|
|
240
|
-
|
|
241
|
-
// proofHash is over the length-prefixed BINARY proof bytes, not over this JSON.
|
|
242
|
-
// Confusing the two produces a value that looks like a proofHash and is not one.
|
|
243
|
-
it('is not what proofHash is computed over', function () {
|
|
244
|
-
var cert = Certificate.build(PARAMS)
|
|
245
|
-
Hash.sha256(Buffer.from(Certificate.canonicalize(cert), 'utf8')).toString('hex')
|
|
246
|
-
.should.not.equal(cert.proofHash)
|
|
247
|
-
})
|
|
248
|
-
})
|
|
249
|
-
})
|