@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
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
/* global describe, it */
|
|
4
|
-
|
|
5
|
-
// BRC-220 canonical encoding. The property that matters is not that these bytes are
|
|
6
|
-
// stable for us — it is that another implementation reading the spec produces the same
|
|
7
|
-
// ones. So the tests assert the byte layout the spec dictates, field by field, rather
|
|
8
|
-
// than only pinning our own output.
|
|
9
|
-
//
|
|
10
|
-
// The failure this module is most exposed to is a proofHash that is self-consistent and
|
|
11
|
-
// wrong: it verifies against itself, every local test passes, and nobody finds out until
|
|
12
|
-
// a third party tries to verify a certificate. Several tests below exist only to make
|
|
13
|
-
// that shape impossible.
|
|
14
|
-
|
|
15
|
-
require('chai').should()
|
|
16
|
-
var Encoding = require('../../lib/notaryhash/encoding')
|
|
17
|
-
var Hash = require('../../lib/crypto/hash')
|
|
18
|
-
|
|
19
|
-
var FIELDS = {
|
|
20
|
-
algorithm: 'ECDSA-secp256k1',
|
|
21
|
-
hashAlgorithm: 'SHA-256',
|
|
22
|
-
payloadHash: Buffer.alloc(32, 0x11),
|
|
23
|
-
publicKey: Buffer.alloc(33, 0x02),
|
|
24
|
-
signature: Buffer.alloc(71, 0x30),
|
|
25
|
-
createdAtUnix: 1786838400
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
describe('BRC-220 encoding', function () {
|
|
29
|
-
describe('lp()', function () {
|
|
30
|
-
it('is u32be(len) followed by the bytes', function () {
|
|
31
|
-
Encoding.lp(Buffer.from([0xaa, 0xbb])).toString('hex').should.equal('00000002aabb')
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
it('encodes an empty value as a bare zero length', function () {
|
|
35
|
-
Encoding.lp(Buffer.alloc(0)).toString('hex').should.equal('00000000')
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
it('measures BYTES, not characters, for text', function () {
|
|
39
|
-
// 'é' is one character and two UTF-8 bytes. Using .length here would prefix 1,
|
|
40
|
-
// and every proof containing a non-ASCII algorithm name would be unverifiable.
|
|
41
|
-
Encoding.lp('é').toString('hex').should.equal('00000002c3a9')
|
|
42
|
-
Encoding.lp('abc').toString('hex').should.equal('00000003616263')
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
// Length-prefixing exists to make field boundaries unambiguous. If it did not, these
|
|
46
|
-
// two field pairs would concatenate to identical bytes.
|
|
47
|
-
it('keeps ambiguous field splits distinct', function () {
|
|
48
|
-
var a = Buffer.concat([Encoding.lp('ab'), Encoding.lp('c')])
|
|
49
|
-
var b = Buffer.concat([Encoding.lp('a'), Encoding.lp('bc')])
|
|
50
|
-
a.toString('hex').should.not.equal(b.toString('hex'))
|
|
51
|
-
})
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
describe('u64be()', function () {
|
|
55
|
-
it('is 8 bytes, big-endian', function () {
|
|
56
|
-
Encoding.u64be(1).toString('hex').should.equal('0000000000000001')
|
|
57
|
-
Encoding.u64be(1786838400).toString('hex').should.equal('000000006a80fd80')
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
// Date.now() is milliseconds. A caller who forgets to divide gets a timestamp about
|
|
61
|
-
// a thousand years in the future that still encodes cleanly, so the guard is on the
|
|
62
|
-
// input rather than the output.
|
|
63
|
-
it('rejects a fractional value, which is what a millisecond mistake looks like', function () {
|
|
64
|
-
;(function () { Encoding.u64be(1786838400.5) }).should.throw(/whole seconds/)
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
it('rejects non-finite and negative values', function () {
|
|
68
|
-
;(function () { Encoding.u64be(NaN) }).should.throw(/finite/)
|
|
69
|
-
;(function () { Encoding.u64be(-1) }).should.throw(/negative/)
|
|
70
|
-
})
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
describe('toUnixSeconds()', function () {
|
|
74
|
-
it('converts an ISO 8601 string to whole seconds', function () {
|
|
75
|
-
Encoding.toUnixSeconds('2026-08-16T00:00:00.000Z').should.equal(1786838400)
|
|
76
|
-
})
|
|
77
|
-
|
|
78
|
-
// Certificates carry millisecond precision in createdAt; the canonical bytes do not.
|
|
79
|
-
// Two implementations must agree on the fractional part, and truncation is the only
|
|
80
|
-
// choice that never moves a timestamp forward.
|
|
81
|
-
it('truncates rather than rounds', function () {
|
|
82
|
-
Encoding.toUnixSeconds('2026-08-16T00:00:00.999Z').should.equal(1786838400)
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
it('rejects an unparseable date', function () {
|
|
86
|
-
;(function () { Encoding.toUnixSeconds('not-a-date') }).should.throw(/not a valid date/)
|
|
87
|
-
})
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
describe('canonicalBytes()', function () {
|
|
91
|
-
// The layout, asserted against a hand-built expectation rather than against the
|
|
92
|
-
// function's own output — otherwise this only proves the function is deterministic.
|
|
93
|
-
it('lays the fields out exactly as the spec specifies', function () {
|
|
94
|
-
var expected = Buffer.concat([
|
|
95
|
-
Encoding.lp('NotaryHash/1.0'),
|
|
96
|
-
Buffer.from([1]),
|
|
97
|
-
Encoding.lp('ECDSA-secp256k1'),
|
|
98
|
-
Encoding.lp('SHA-256'),
|
|
99
|
-
Encoding.lp(FIELDS.payloadHash),
|
|
100
|
-
Encoding.lp(FIELDS.publicKey),
|
|
101
|
-
Encoding.lp(FIELDS.signature),
|
|
102
|
-
Encoding.u64be(FIELDS.createdAtUnix)
|
|
103
|
-
])
|
|
104
|
-
Encoding.canonicalBytes(FIELDS).toString('hex').should.equal(expected.toString('hex'))
|
|
105
|
-
})
|
|
106
|
-
|
|
107
|
-
it('opens with the protocol prefix and the version byte', function () {
|
|
108
|
-
var bytes = Encoding.canonicalBytes(FIELDS)
|
|
109
|
-
bytes.slice(0, 4).readUInt32BE(0).should.equal(14) // len('NotaryHash/1.0')
|
|
110
|
-
bytes.slice(4, 18).toString('utf8').should.equal('NotaryHash/1.0')
|
|
111
|
-
bytes[18].should.equal(1) // u8(version)
|
|
112
|
-
})
|
|
113
|
-
|
|
114
|
-
it('ends with the timestamp', function () {
|
|
115
|
-
var bytes = Encoding.canonicalBytes(FIELDS)
|
|
116
|
-
bytes.slice(-8).toString('hex').should.equal(Encoding.u64be(FIELDS.createdAtUnix).toString('hex'))
|
|
117
|
-
})
|
|
118
|
-
|
|
119
|
-
// THE test for this module. GDAF once signed a serialization that silently omitted
|
|
120
|
-
// credentialSubject, so a forger could rewrite the claims without breaking the proof.
|
|
121
|
-
// Every field must move the hash, or it is not covered by it.
|
|
122
|
-
it('covers every field — changing any one changes the hash', function () {
|
|
123
|
-
var base = Encoding.proofHash(FIELDS).toString('hex')
|
|
124
|
-
var mutations = {
|
|
125
|
-
algorithm: 'ML-DSA-65',
|
|
126
|
-
hashAlgorithm: 'SHA-512',
|
|
127
|
-
payloadHash: Buffer.alloc(32, 0x12),
|
|
128
|
-
publicKey: Buffer.alloc(33, 0x03),
|
|
129
|
-
signature: Buffer.alloc(71, 0x31),
|
|
130
|
-
createdAtUnix: FIELDS.createdAtUnix + 1
|
|
131
|
-
}
|
|
132
|
-
Object.keys(mutations).forEach(function (field) {
|
|
133
|
-
var altered = Object.assign({}, FIELDS)
|
|
134
|
-
altered[field] = mutations[field]
|
|
135
|
-
Encoding.proofHash(altered).toString('hex')
|
|
136
|
-
.should.not.equal(base, field + ' does not affect proofHash, so it is not covered by it')
|
|
137
|
-
})
|
|
138
|
-
})
|
|
139
|
-
|
|
140
|
-
// Hex is what certificates carry, and hex is twice as long as the bytes it encodes.
|
|
141
|
-
// Accepting it here would produce a proof that verifies locally and nowhere else.
|
|
142
|
-
it('refuses a hex string where raw bytes are required', function () {
|
|
143
|
-
var withHex = Object.assign({}, FIELDS, { payloadHash: FIELDS.payloadHash.toString('hex') })
|
|
144
|
-
;(function () { Encoding.canonicalBytes(withHex) }).should.throw(/must be a Buffer/)
|
|
145
|
-
})
|
|
146
|
-
|
|
147
|
-
it('names the offending field and points at the hex trap', function () {
|
|
148
|
-
var withHex = Object.assign({}, FIELDS, { signature: FIELDS.signature.toString('hex') })
|
|
149
|
-
;(function () { Encoding.canonicalBytes(withHex) }).should.throw(/signature.*hex/)
|
|
150
|
-
})
|
|
151
|
-
|
|
152
|
-
it('requires the string fields to be present and non-empty', function () {
|
|
153
|
-
;(function () {
|
|
154
|
-
Encoding.canonicalBytes(Object.assign({}, FIELDS, { algorithm: '' }))
|
|
155
|
-
}).should.throw(/algorithm/)
|
|
156
|
-
;(function () {
|
|
157
|
-
Encoding.canonicalBytes(Object.assign({}, FIELDS, { hashAlgorithm: undefined }))
|
|
158
|
-
}).should.throw(/hashAlgorithm/)
|
|
159
|
-
})
|
|
160
|
-
|
|
161
|
-
it('handles a post-quantum-sized signature', function () {
|
|
162
|
-
// SLH-DSA signatures run to tens of kilobytes; the u32be length prefix must carry
|
|
163
|
-
// them without truncation.
|
|
164
|
-
var big = Object.assign({}, FIELDS, { signature: Buffer.alloc(49856, 0x7f) })
|
|
165
|
-
var bytes = Encoding.canonicalBytes(big)
|
|
166
|
-
var sigLenOffset = bytes.length - 8 - 49856 - 4
|
|
167
|
-
bytes.readUInt32BE(sigLenOffset).should.equal(49856)
|
|
168
|
-
})
|
|
169
|
-
})
|
|
170
|
-
|
|
171
|
-
describe('proofHash()', function () {
|
|
172
|
-
it('is SHA-256 of the canonical bytes', function () {
|
|
173
|
-
Encoding.proofHash(FIELDS).toString('hex')
|
|
174
|
-
.should.equal(Hash.sha256(Encoding.canonicalBytes(FIELDS)).toString('hex'))
|
|
175
|
-
})
|
|
176
|
-
|
|
177
|
-
it('is 32 bytes', function () {
|
|
178
|
-
Encoding.proofHash(FIELDS).length.should.equal(32)
|
|
179
|
-
})
|
|
180
|
-
|
|
181
|
-
it('is deterministic', function () {
|
|
182
|
-
Encoding.proofHash(FIELDS).toString('hex')
|
|
183
|
-
.should.equal(Encoding.proofHash(Object.assign({}, FIELDS)).toString('hex'))
|
|
184
|
-
})
|
|
185
|
-
})
|
|
186
|
-
})
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
/* global describe, it */
|
|
4
|
-
|
|
5
|
-
// BRC-220 signature interoperability.
|
|
6
|
-
//
|
|
7
|
-
// Every other test in this directory signs with `lib/crypto/ecdsa.js` and verifies
|
|
8
|
-
// through `lib/notaryhash/suites.js`, which also uses `lib/crypto/ecdsa.js`. That
|
|
9
|
-
// proves the module agrees with itself. It cannot prove the module agrees with the
|
|
10
|
-
// spec, and in 8.3.0 it did not: the suite set `endian: 'little'` before verifying,
|
|
11
|
-
// so it rejected any signature produced the way BRC-220 describes and accepted only
|
|
12
|
-
// signatures made with bsv's own byte-reversed convention. Twenty-odd passing tests
|
|
13
|
-
// said nothing about it, because they all signed the same wrong way.
|
|
14
|
-
//
|
|
15
|
-
// So this file verifies against `@noble/curves` instead — a separate implementation
|
|
16
|
-
// that shares no verification code with ours. It is the only test here that can fail
|
|
17
|
-
// when our code and our tests are wrong together.
|
|
18
|
-
//
|
|
19
|
-
// One trap worth naming, because it cost real time when this was diagnosed: noble v2
|
|
20
|
-
// PREHASHES by default. `secp256k1.sign(digest, key)` signs `sha256(digest)`, and
|
|
21
|
-
// `secp256k1.verify` prehashes to match, so noble looks self-consistent while
|
|
22
|
-
// disagreeing with everyone. BRC-220 signs the payloadHash itself, so every call
|
|
23
|
-
// below passes `{ prehash: false }`. Omit it and these tests compare the wrong things
|
|
24
|
-
// and "pass" for the wrong reason.
|
|
25
|
-
|
|
26
|
-
require('chai').should()
|
|
27
|
-
var bsv = require('../..')
|
|
28
|
-
var Suites = require('../../lib/notaryhash/suites')
|
|
29
|
-
var Hash = require('../../lib/crypto/hash')
|
|
30
|
-
var { secp256k1 } = require('@noble/curves/secp256k1.js')
|
|
31
|
-
|
|
32
|
-
var PAYLOAD_HASH = Hash.sha256(Buffer.from('the document nobody sees'))
|
|
33
|
-
|
|
34
|
-
// A fixed key, so a failure is reproducible rather than one-in-N flaky.
|
|
35
|
-
var SECRET = Buffer.from('c0ffee00c0ffee00c0ffee00c0ffee00c0ffee00c0ffee00c0ffee00c0ffee01', 'hex')
|
|
36
|
-
var PUBLIC_KEY = Buffer.from(secp256k1.getPublicKey(SECRET, true))
|
|
37
|
-
|
|
38
|
-
function rawSig (sig) {
|
|
39
|
-
return Buffer.concat([
|
|
40
|
-
sig.r.toArrayLike(Buffer, 'be', 32),
|
|
41
|
-
sig.s.toArrayLike(Buffer, 'be', 32)
|
|
42
|
-
])
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
describe('BRC-220 signature interop', function () {
|
|
46
|
-
it('agrees with @noble/curves on the public key for a given secret', function () {
|
|
47
|
-
// If this fails nothing below means anything — the two libraries would be
|
|
48
|
-
// signing under different keys.
|
|
49
|
-
var ours = bsv.PrivateKey.fromBuffer(SECRET).toPublicKey().toBuffer()
|
|
50
|
-
ours.toString('hex').should.equal(PUBLIC_KEY.toString('hex'))
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
it('ACCEPTS a signature made the way the spec describes', function () {
|
|
54
|
-
// This is the regression. Under 8.3.0 this assertion fails.
|
|
55
|
-
var conformant = Buffer.from(secp256k1.sign(PAYLOAD_HASH, SECRET, { prehash: false }))
|
|
56
|
-
Suites.verify('ECDSA-secp256k1', PAYLOAD_HASH, conformant, PUBLIC_KEY)
|
|
57
|
-
.should.equal(true, 'a conformant BRC-220 signature must verify')
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
it('emits signatures an independent implementation accepts', function () {
|
|
61
|
-
var key = bsv.PrivateKey.fromBuffer(SECRET)
|
|
62
|
-
var ecdsa = bsv.crypto.ECDSA().set({ hashbuf: PAYLOAD_HASH, privkey: key })
|
|
63
|
-
ecdsa.sign()
|
|
64
|
-
secp256k1.verify(rawSig(ecdsa.sig), PAYLOAD_HASH, PUBLIC_KEY, { prehash: false })
|
|
65
|
-
.should.equal(true, 'our signature must verify under @noble/curves')
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
it('REJECTS a signature over the byte-reversed digest', function () {
|
|
69
|
-
// The 8.3.0 convention. It must now fail: accepting both would mean two valid
|
|
70
|
-
// signatures exist for one signing act, and both are inside proofHash.
|
|
71
|
-
var key = bsv.PrivateKey.fromBuffer(SECRET)
|
|
72
|
-
var ecdsa = bsv.crypto.ECDSA().set({ hashbuf: PAYLOAD_HASH, endian: 'little', privkey: key })
|
|
73
|
-
ecdsa.sign()
|
|
74
|
-
Suites.verify('ECDSA-secp256k1', PAYLOAD_HASH, rawSig(ecdsa.sig), PUBLIC_KEY)
|
|
75
|
-
.should.equal(false, 'the pre-8.3.1 little-endian convention must not verify')
|
|
76
|
-
})
|
|
77
|
-
|
|
78
|
-
it('round-trips both ways over many payloads, not just one', function () {
|
|
79
|
-
// A single vector can pass by coincidence — the reversal of a digest whose bytes
|
|
80
|
-
// happen to be near-palindromic still differs, but a lucky scalar could mask a
|
|
81
|
-
// subtler bug. Twenty payloads with distinct digests makes that implausible.
|
|
82
|
-
var key = bsv.PrivateKey.fromBuffer(SECRET)
|
|
83
|
-
for (var i = 0; i < 20; i++) {
|
|
84
|
-
var h = Hash.sha256(Buffer.from('payload ' + i))
|
|
85
|
-
|
|
86
|
-
var theirs = Buffer.from(secp256k1.sign(h, SECRET, { prehash: false }))
|
|
87
|
-
Suites.verify('ECDSA-secp256k1', h, theirs, PUBLIC_KEY)
|
|
88
|
-
.should.equal(true, 'noble sig ' + i + ' rejected by our suite')
|
|
89
|
-
|
|
90
|
-
var ecdsa = bsv.crypto.ECDSA().set({ hashbuf: h, privkey: key })
|
|
91
|
-
ecdsa.sign()
|
|
92
|
-
secp256k1.verify(rawSig(ecdsa.sig), h, PUBLIC_KEY, { prehash: false })
|
|
93
|
-
.should.equal(true, 'our sig ' + i + ' rejected by noble')
|
|
94
|
-
}
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
it('still rejects a signature over a DIFFERENT payload', function () {
|
|
98
|
-
// Guards against the fix degenerating into "accept anything". A verifier that
|
|
99
|
-
// returns true unconditionally would pass every test above.
|
|
100
|
-
var other = Hash.sha256(Buffer.from('a different document'))
|
|
101
|
-
var sig = Buffer.from(secp256k1.sign(other, SECRET, { prehash: false }))
|
|
102
|
-
Suites.verify('ECDSA-secp256k1', PAYLOAD_HASH, sig, PUBLIC_KEY)
|
|
103
|
-
.should.equal(false)
|
|
104
|
-
})
|
|
105
|
-
|
|
106
|
-
it('still rejects a signature from a DIFFERENT key', function () {
|
|
107
|
-
var otherSecret = Buffer.from('a11ce00a11ce00a11ce00a11ce00a11ce00a11ce00a11ce00a11ce00a11ce001', 'hex')
|
|
108
|
-
var sig = Buffer.from(secp256k1.sign(PAYLOAD_HASH, otherSecret, { prehash: false }))
|
|
109
|
-
Suites.verify('ECDSA-secp256k1', PAYLOAD_HASH, sig, PUBLIC_KEY)
|
|
110
|
-
.should.equal(false)
|
|
111
|
-
})
|
|
112
|
-
})
|
|
@@ -1,181 +0,0 @@
|
|
|
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
|
-
})
|