@smartledger/bsv 8.1.0 → 8.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +166 -0
- package/README.md +38 -38
- package/bsv-ecies.min.js +1 -1
- package/bsv-gdaf.min.js +62 -62
- package/bsv-ltp.min.js +48 -48
- package/bsv-smartcontract.min.js +1 -1
- package/bsv.bundle.js +62 -62
- package/bsv.min.js +62 -62
- package/docs/AUDIT_SCOPE.md +8 -8
- package/docs/BRC220_ENCODING_AMENDMENT.md +100 -0
- package/docs/BRC220_PLAN.md +224 -0
- package/docs/MODULE_REFERENCE_COMPLETE.md +27 -27
- 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/index.js +5 -0
- package/index.mjs +1 -0
- package/lib/gdaf/attestation-signer.js +54 -2
- package/lib/gdaf/attestation-verifier.js +41 -25
- package/lib/gdaf/zk-prover.js +143 -46
- package/lib/notaryhash/certificate.js +282 -0
- package/lib/notaryhash/encoding.js +150 -0
- package/lib/notaryhash/index.js +334 -0
- package/lib/notaryhash/merkle.js +209 -0
- package/lib/notaryhash/script.js +261 -0
- package/lib/notaryhash/suites.js +156 -0
- package/lib/util/jcs.js +75 -0
- package/package.json +9 -6
- package/test/gdaf/canonicalization.js +106 -0
- package/test/gdaf/zk_prover.js +204 -0
- package/test/notaryhash/certificate.js +249 -0
- package/test/notaryhash/encoding.js +186 -0
- package/test/notaryhash/merkle.js +181 -0
- package/test/notaryhash/script.js +270 -0
- package/test/notaryhash/verify.js +339 -0
- package/tools/minimal_reproduction.js +119 -0
- package/tools/opcode_map.js +342 -0
- package/tools/server.js +97 -0
- package/tools/simple_real_tx.js +136 -0
- package/tools/sv-sighash-harness.js +123 -0
- package/tools/sv-sighash-report.js +71 -0
- package/tools/sv-tx-harness.js +172 -0
- package/tools/sv-tx-report.js +43 -0
- package/tools/sv-vector-harness.js +304 -0
- package/tools/sv-vector-report.js +88 -0
- package/version.js +1 -1
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
var Transaction = require('../transaction')
|
|
4
|
+
var Hash = require('../crypto/hash')
|
|
5
|
+
var SPV = require('../spv')
|
|
6
|
+
var Encoding = require('./encoding')
|
|
7
|
+
var NotaryScript = require('./script')
|
|
8
|
+
var Certificate = require('./certificate')
|
|
9
|
+
var Suites = require('./suites')
|
|
10
|
+
var Merkle = require('./merkle')
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* BRC-220 NotaryHash.
|
|
14
|
+
*
|
|
15
|
+
* A certificate is valid if and only if ALL THREE of the spec's checks hold:
|
|
16
|
+
*
|
|
17
|
+
* 1. Signature — verify(algorithm, payloadHash, signature, publicKey) (offline)
|
|
18
|
+
* 2. Proof integrity — recomputed proofHash equals certificate.proofHash (offline)
|
|
19
|
+
* 3. Anchor — SPV against a block header, or a direct chain lookup
|
|
20
|
+
*
|
|
21
|
+
* `verify()` runs all three and reports each separately, because "invalid" without
|
|
22
|
+
* saying which check failed is unactionable — a bad signature and an unmined transaction
|
|
23
|
+
* are different problems with different fixes.
|
|
24
|
+
*
|
|
25
|
+
* NOTE this library never fetches a block header. The spec is explicit that the verifier
|
|
26
|
+
* "trusts only a block header, obtained from any source it chooses", and choosing that
|
|
27
|
+
* source is the caller's decision, not ours: a single provider is a single point of
|
|
28
|
+
* trust, and quietly picking one on the caller's behalf would hide exactly the trust
|
|
29
|
+
* assumption the protocol exists to remove. The caller supplies the header.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
var NotaryHash = {}
|
|
33
|
+
|
|
34
|
+
NotaryHash.Encoding = Encoding
|
|
35
|
+
NotaryHash.Script = NotaryScript
|
|
36
|
+
NotaryHash.Certificate = Certificate
|
|
37
|
+
NotaryHash.Suites = Suites
|
|
38
|
+
NotaryHash.Merkle = Merkle
|
|
39
|
+
NotaryHash.MODE = NotaryScript.MODE
|
|
40
|
+
|
|
41
|
+
/** Register a signature suite. See lib/notaryhash/suites.js for why PQ is not built in. */
|
|
42
|
+
NotaryHash.registerSuite = function (algorithm, suite) {
|
|
43
|
+
return Suites.register(algorithm, suite)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Check 1 — the signature, offline.
|
|
48
|
+
*
|
|
49
|
+
* @param {Object} certificate
|
|
50
|
+
* @returns {Boolean}
|
|
51
|
+
*/
|
|
52
|
+
NotaryHash.verifySignature = function (certificate) {
|
|
53
|
+
try {
|
|
54
|
+
if (!certificate || typeof certificate !== 'object') return false
|
|
55
|
+
return Suites.verify(
|
|
56
|
+
certificate.algorithm,
|
|
57
|
+
Buffer.from(certificate.payloadHash, 'hex'),
|
|
58
|
+
Buffer.from(certificate.signature, 'hex'),
|
|
59
|
+
Buffer.from(certificate.publicKey, 'hex')
|
|
60
|
+
)
|
|
61
|
+
} catch (e) {
|
|
62
|
+
return false
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Compute a txid from a raw transaction, the way the spec states it:
|
|
68
|
+
* `txid = reverse(SHA256(SHA256(rawTx)))`.
|
|
69
|
+
*
|
|
70
|
+
* This is what makes provider-supplied data self-checking — a raw transaction is only
|
|
71
|
+
* accepted if it hashes to the txid already held, so a provider cannot substitute
|
|
72
|
+
* different bytes.
|
|
73
|
+
*
|
|
74
|
+
* @param {Buffer|String} rawTx
|
|
75
|
+
* @returns {String} txid, big-endian hex as displayed
|
|
76
|
+
*/
|
|
77
|
+
NotaryHash.txidFromRawTx = function (rawTx) {
|
|
78
|
+
var buf = Buffer.isBuffer(rawTx) ? rawTx : Buffer.from(rawTx, 'hex')
|
|
79
|
+
return Buffer.from(Hash.sha256sha256(buf)).reverse().toString('hex')
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Find and parse the NotaryHash record in a raw transaction.
|
|
84
|
+
*
|
|
85
|
+
* @param {Buffer|String} rawTx
|
|
86
|
+
* @returns {Object|null} the parsed record, or null if there is none
|
|
87
|
+
*/
|
|
88
|
+
NotaryHash.recordFromRawTx = function (rawTx) {
|
|
89
|
+
try {
|
|
90
|
+
var tx = new Transaction(Buffer.isBuffer(rawTx) ? rawTx.toString('hex') : rawTx)
|
|
91
|
+
for (var i = 0; i < tx.outputs.length; i++) {
|
|
92
|
+
var script = tx.outputs[i].script
|
|
93
|
+
if (NotaryScript.isNotaryHash(script)) {
|
|
94
|
+
return NotaryScript.parse(script)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return null
|
|
98
|
+
} catch (e) {
|
|
99
|
+
return null
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Does an on-chain record agree with the certificate that claims it?
|
|
105
|
+
*
|
|
106
|
+
* Compares only the fields the record actually carries. In hybrid mode the chain holds
|
|
107
|
+
* SHA-256 of the key and signature, so those are compared as digests of the
|
|
108
|
+
* certificate's full blobs — which is the whole point of the mode, and the step that
|
|
109
|
+
* would otherwise let a hybrid certificate reference a record for a different key.
|
|
110
|
+
*
|
|
111
|
+
* @param {Object} record - from NotaryScript.parse
|
|
112
|
+
* @param {Object} certificate
|
|
113
|
+
* @returns {Boolean}
|
|
114
|
+
*/
|
|
115
|
+
NotaryHash.recordMatchesCertificate = function (record, certificate) {
|
|
116
|
+
try {
|
|
117
|
+
if (!record || !certificate) return false
|
|
118
|
+
if (record.mode !== certificate.mode) return false
|
|
119
|
+
|
|
120
|
+
if (record.mode === NotaryScript.MODE.BATCH) {
|
|
121
|
+
if (!certificate.merkle) return false
|
|
122
|
+
if (record.merkleRoot.toString('hex') !== String(certificate.merkle.root).toLowerCase()) {
|
|
123
|
+
return false
|
|
124
|
+
}
|
|
125
|
+
// leafCount is compared because the inclusion proof CANNOT be relied on to catch a
|
|
126
|
+
// wrong one. RFC 6962 derives the tree shape from the count, but for most indices
|
|
127
|
+
// the fold is identical across neighbouring counts — measured on an 8-leaf tree,
|
|
128
|
+
// only indices 6 and 7 fold differently when the count is claimed as 7. The
|
|
129
|
+
// on-chain u32be is the authoritative value, so it is checked here rather than
|
|
130
|
+
// assumed to be implied.
|
|
131
|
+
return record.leafCount === certificate.merkle.leafCount
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (record.algorithm !== certificate.algorithm) return false
|
|
135
|
+
if (record.hashAlgorithm !== certificate.hashAlgorithm) return false
|
|
136
|
+
if (record.payloadHash.toString('hex') !== String(certificate.payloadHash).toLowerCase()) return false
|
|
137
|
+
if (record.proofHash.toString('hex') !== String(certificate.proofHash).toLowerCase()) return false
|
|
138
|
+
|
|
139
|
+
var certPub = Buffer.from(certificate.publicKey, 'hex')
|
|
140
|
+
var certSig = Buffer.from(certificate.signature, 'hex')
|
|
141
|
+
|
|
142
|
+
if (record.mode === NotaryScript.MODE.HYBRID) {
|
|
143
|
+
return record.publicKeyHash.equals(Hash.sha256(certPub)) &&
|
|
144
|
+
record.signatureHash.equals(Hash.sha256(certSig))
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return record.publicKey.equals(certPub) && record.signature.equals(certSig)
|
|
148
|
+
} catch (e) {
|
|
149
|
+
return false
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Check 3 — the anchor, via SPV.
|
|
155
|
+
*
|
|
156
|
+
* The caller supplies the block HEADER, obtained however it chose. Not a bare Merkle
|
|
157
|
+
* root: a header carries the proof of work, so `lib/spv` can confirm the root belongs to
|
|
158
|
+
* a block that cost something to produce rather than to a root someone asserted. Without
|
|
159
|
+
* a header this returns false — a certificate whose anchor has not been checked against
|
|
160
|
+
* one has not satisfied check 3, and reporting otherwise would restore the exact trust
|
|
161
|
+
* the spec removes.
|
|
162
|
+
*
|
|
163
|
+
* @param {Object} certificate - must carry an `spv` envelope
|
|
164
|
+
* @param {Object} opts
|
|
165
|
+
* @param {String|Buffer|BlockHeader} opts.header - independently obtained
|
|
166
|
+
* @param {Boolean} [opts.requirePow=true] - pass false only for test fixtures
|
|
167
|
+
* @returns {Object} { valid, errors }
|
|
168
|
+
*/
|
|
169
|
+
NotaryHash.verifyAnchorSPV = function (certificate, opts) {
|
|
170
|
+
var errors = []
|
|
171
|
+
opts = opts || {}
|
|
172
|
+
|
|
173
|
+
try {
|
|
174
|
+
var spv = certificate && certificate.spv
|
|
175
|
+
if (!spv) {
|
|
176
|
+
return { valid: false, errors: ['certificate has no SPV envelope'] }
|
|
177
|
+
}
|
|
178
|
+
if (!opts.header) {
|
|
179
|
+
return {
|
|
180
|
+
valid: false,
|
|
181
|
+
errors: ['a block header is required: the verifier must obtain one itself ' +
|
|
182
|
+
'rather than trust the certificate or its issuer for the anchor']
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
var txid = NotaryHash.txidFromRawTx(spv.rawTx)
|
|
187
|
+
if (txid !== String(certificate.anchor.txid).toLowerCase()) {
|
|
188
|
+
errors.push('rawTx does not hash to anchor.txid')
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
var record = NotaryHash.recordFromRawTx(spv.rawTx)
|
|
192
|
+
if (!record) {
|
|
193
|
+
errors.push('no NotaryHash record found in rawTx')
|
|
194
|
+
} else if (!NotaryHash.recordMatchesCertificate(record, certificate)) {
|
|
195
|
+
errors.push('on-chain record does not match the certificate')
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
var proof = spv.merkleProof || {}
|
|
199
|
+
var inclusion = SPV.verifyTxInclusion({
|
|
200
|
+
header: opts.header,
|
|
201
|
+
txid: txid,
|
|
202
|
+
index: proof.index,
|
|
203
|
+
nodes: proof.nodes,
|
|
204
|
+
requirePow: opts.requirePow !== false
|
|
205
|
+
})
|
|
206
|
+
if (inclusion.valid !== true) {
|
|
207
|
+
errors.push(inclusion.rootMatches === false
|
|
208
|
+
? 'merkle proof does not fold to the header\'s root'
|
|
209
|
+
: 'block header failed proof-of-work validation')
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return { valid: errors.length === 0, errors: errors }
|
|
213
|
+
} catch (e) {
|
|
214
|
+
return { valid: false, errors: ['anchor verification error: ' + e.message] }
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Batch inclusion — the extra step a batched certificate needs.
|
|
220
|
+
*
|
|
221
|
+
* The on-chain record for a batch carries only a root and a leaf count, so the
|
|
222
|
+
* certificate's own `merkle` proof is what ties it to that root. Folding is RFC 6962,
|
|
223
|
+
* NOT the Bitcoin tree in lib/spv — see lib/notaryhash/merkle.js for why the difference
|
|
224
|
+
* matters and why reusing the other one would be silently wrong.
|
|
225
|
+
*
|
|
226
|
+
* ASSUMPTION, flagged rather than buried: the leaf data is the certificate's proofHash.
|
|
227
|
+
* The spec says a batch "anchors many proofs" under one root and that the certificate's
|
|
228
|
+
* merkle proof must fold to it, but does not state what a leaf contains. proofHash is the
|
|
229
|
+
* only per-proof 32-byte value that identifies the proof, so it is the reading taken
|
|
230
|
+
* here — and it needs confirming against the reference implementation before this is
|
|
231
|
+
* relied on. See docs/BRC220_PLAN.md.
|
|
232
|
+
*
|
|
233
|
+
* @param {Object} certificate
|
|
234
|
+
* @returns {Object} { valid, errors }
|
|
235
|
+
*/
|
|
236
|
+
NotaryHash.verifyBatchInclusion = function (certificate) {
|
|
237
|
+
try {
|
|
238
|
+
if (!certificate || certificate.mode !== NotaryScript.MODE.BATCH) {
|
|
239
|
+
return { valid: false, errors: ['certificate is not in batch mode'] }
|
|
240
|
+
}
|
|
241
|
+
var m = certificate.merkle
|
|
242
|
+
if (!m) return { valid: false, errors: ['batch certificate has no merkle proof'] }
|
|
243
|
+
|
|
244
|
+
var leafData = Buffer.from(certificate.proofHash, 'hex')
|
|
245
|
+
var path = (m.path || []).map(function (node) {
|
|
246
|
+
return Buffer.isBuffer(node) ? node : Buffer.from(node, 'hex')
|
|
247
|
+
})
|
|
248
|
+
var root = Buffer.isBuffer(m.root) ? m.root : Buffer.from(String(m.root), 'hex')
|
|
249
|
+
|
|
250
|
+
var included = Merkle.verifyInclusion(leafData, m.leafIndex, m.leafCount, path, root)
|
|
251
|
+
return included
|
|
252
|
+
? { valid: true, errors: [] }
|
|
253
|
+
: { valid: false, errors: ['merkle inclusion proof does not fold to the batch root'] }
|
|
254
|
+
} catch (e) {
|
|
255
|
+
return { valid: false, errors: ['batch inclusion error: ' + e.message] }
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Verify a certificate: all three checks.
|
|
261
|
+
*
|
|
262
|
+
* Returns a REPORT, not a boolean — `valid` is the verdict and the per-check fields say
|
|
263
|
+
* why. Callers must read `.valid`; the object itself is always truthy, and this module
|
|
264
|
+
* deliberately does not hand back something that could be mistaken for a pass. That
|
|
265
|
+
* distinction has bitten this codebase repeatedly, so `isValid()` below exists for the
|
|
266
|
+
* `if (...)` case.
|
|
267
|
+
*
|
|
268
|
+
* @param {Object} certificate
|
|
269
|
+
* @param {Object} [opts]
|
|
270
|
+
* @param {String|Buffer} [opts.header] - an independently obtained block header
|
|
271
|
+
* @param {Boolean} [opts.skipAnchor] - check 1 and 2 only; the result is NOT a valid
|
|
272
|
+
* certificate, and `valid` will be false. For offline triage.
|
|
273
|
+
* @returns {Object} { valid, signature, proofIntegrity, anchor, shape, errors }
|
|
274
|
+
*/
|
|
275
|
+
NotaryHash.verify = function (certificate, opts) {
|
|
276
|
+
opts = opts || {}
|
|
277
|
+
|
|
278
|
+
var report = {
|
|
279
|
+
valid: false,
|
|
280
|
+
shape: [],
|
|
281
|
+
signature: false,
|
|
282
|
+
proofIntegrity: false,
|
|
283
|
+
anchor: false,
|
|
284
|
+
errors: []
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
report.shape = Certificate.validateShape(certificate)
|
|
288
|
+
if (report.shape.length) {
|
|
289
|
+
report.errors = report.shape.slice()
|
|
290
|
+
return report
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
report.signature = NotaryHash.verifySignature(certificate)
|
|
294
|
+
if (!report.signature) report.errors.push('signature does not verify')
|
|
295
|
+
|
|
296
|
+
report.proofIntegrity = Certificate.proofHashMatches(certificate)
|
|
297
|
+
if (!report.proofIntegrity) report.errors.push('proofHash does not match the certificate fields')
|
|
298
|
+
|
|
299
|
+
if (opts.skipAnchor) {
|
|
300
|
+
report.errors.push('anchor not checked (skipAnchor): this certificate is NOT verified')
|
|
301
|
+
return report
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
var anchor = NotaryHash.verifyAnchorSPV(certificate, opts)
|
|
305
|
+
report.anchor = anchor.valid
|
|
306
|
+
anchor.errors.forEach(function (e) { report.errors.push(e) })
|
|
307
|
+
|
|
308
|
+
// A batched certificate has a fourth thing to prove: that this proof is actually one
|
|
309
|
+
// of the ones the on-chain root commits to. Without it, any certificate could point at
|
|
310
|
+
// any batch anchor and the anchor check alone would not notice.
|
|
311
|
+
report.batchInclusion = true
|
|
312
|
+
if (certificate.mode === NotaryScript.MODE.BATCH) {
|
|
313
|
+
var batch = NotaryHash.verifyBatchInclusion(certificate)
|
|
314
|
+
report.batchInclusion = batch.valid
|
|
315
|
+
batch.errors.forEach(function (e) { report.errors.push(e) })
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
report.valid = report.signature && report.proofIntegrity && report.anchor &&
|
|
319
|
+
report.batchInclusion
|
|
320
|
+
return report
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Strict boolean verdict, for `if (...)`.
|
|
325
|
+
*
|
|
326
|
+
* @param {Object} certificate
|
|
327
|
+
* @param {Object} [opts]
|
|
328
|
+
* @returns {Boolean}
|
|
329
|
+
*/
|
|
330
|
+
NotaryHash.isValid = function (certificate, opts) {
|
|
331
|
+
return NotaryHash.verify(certificate, opts).valid === true
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
module.exports = NotaryHash
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
var Hash = require('../crypto/hash')
|
|
4
|
+
var $ = require('../util/preconditions')
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* RFC 6962 Merkle trees, for BRC-220 batch mode.
|
|
8
|
+
*
|
|
9
|
+
* THIS IS NOT THE BITCOIN MERKLE TREE, and it is not the one in lib/gdaf/zk-prover.js
|
|
10
|
+
* either. Three trees now live in this repository and all three differ:
|
|
11
|
+
*
|
|
12
|
+
* lib/spv/merkleproof.js leaf = txid node = sha256d(l‖r) odd leaf DUPLICATED
|
|
13
|
+
* lib/gdaf/zk-prover.js leaf = salted hash node = sha256(l‖r) odd leaf duplicated
|
|
14
|
+
* here (RFC 6962) leaf = sha256(0x00‖d) node = sha256(0x01‖l‖r) NEVER duplicated
|
|
15
|
+
*
|
|
16
|
+
* The domain separation and the no-duplication rule are what make RFC 6962 resistant to
|
|
17
|
+
* the second-preimage attack Bitcoin's tree is famously open to: without the 0x00/0x01
|
|
18
|
+
* prefixes, an internal node can be reinterpreted as a leaf.
|
|
19
|
+
*
|
|
20
|
+
* Reusing either of the other two here would produce a root that no other BRC-220
|
|
21
|
+
* implementation computes, and the failure would surface only when somebody else tried
|
|
22
|
+
* to verify a batch certificate. That is why this file exists rather than an import.
|
|
23
|
+
*
|
|
24
|
+
* One more trap: RFC 6962 splits at the LARGEST POWER OF TWO STRICTLY LESS THAN n, not at
|
|
25
|
+
* the midpoint. For n = 5 that is 4/1, where a midpoint split gives 2/3 and a different
|
|
26
|
+
* root. Every non-power-of-two tree depends on getting this right.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
var Merkle = {}
|
|
30
|
+
|
|
31
|
+
/** Prefix bytes, per RFC 6962 §2.1. */
|
|
32
|
+
Merkle.LEAF_PREFIX = 0x00
|
|
33
|
+
Merkle.NODE_PREFIX = 0x01
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Hash a leaf: `SHA-256(0x00 || d)`.
|
|
37
|
+
*
|
|
38
|
+
* @param {Buffer} d
|
|
39
|
+
* @returns {Buffer} 32 bytes
|
|
40
|
+
*/
|
|
41
|
+
Merkle.hashLeaf = function (d) {
|
|
42
|
+
$.checkArgument(Buffer.isBuffer(d), 'leaf data must be a Buffer')
|
|
43
|
+
return Hash.sha256(Buffer.concat([Buffer.from([Merkle.LEAF_PREFIX]), d]))
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Hash an internal node: `SHA-256(0x01 || left || right)`.
|
|
48
|
+
*
|
|
49
|
+
* @param {Buffer} left
|
|
50
|
+
* @param {Buffer} right
|
|
51
|
+
* @returns {Buffer} 32 bytes
|
|
52
|
+
*/
|
|
53
|
+
Merkle.hashNode = function (left, right) {
|
|
54
|
+
return Hash.sha256(Buffer.concat([Buffer.from([Merkle.NODE_PREFIX]), left, right]))
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The largest power of two strictly less than n.
|
|
59
|
+
*
|
|
60
|
+
* RFC 6962 splits here rather than at the midpoint, which is what makes its trees
|
|
61
|
+
* left-complete. For n = 5 this is 4, so the split is 4/1 — a midpoint split would give
|
|
62
|
+
* 2/3 and a root no conformant implementation would agree with.
|
|
63
|
+
*
|
|
64
|
+
* @param {Number} n - at least 2
|
|
65
|
+
* @returns {Number}
|
|
66
|
+
*/
|
|
67
|
+
Merkle.largestPowerOfTwoBelow = function (n) {
|
|
68
|
+
$.checkArgument(Number.isInteger(n) && n >= 2, 'n must be an integer >= 2')
|
|
69
|
+
var k = 1
|
|
70
|
+
while (k * 2 < n) k *= 2
|
|
71
|
+
return k
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* The Merkle Tree Hash of a list of leaf data, per RFC 6962 §2.1.
|
|
76
|
+
*
|
|
77
|
+
* MTH({}) = SHA-256() — the hash of the empty string
|
|
78
|
+
* MTH({d}) = SHA-256(0x00 || d)
|
|
79
|
+
* MTH(D[n]) = SHA-256(0x01 || MTH(D[0:k]) || MTH(D[k:n]))
|
|
80
|
+
*
|
|
81
|
+
* @param {Array<Buffer>} leaves - the leaf DATA, not pre-hashed
|
|
82
|
+
* @returns {Buffer} 32 bytes
|
|
83
|
+
*/
|
|
84
|
+
Merkle.root = function (leaves) {
|
|
85
|
+
$.checkArgument(Array.isArray(leaves), 'leaves must be an array')
|
|
86
|
+
|
|
87
|
+
// The empty tree is the hash of the empty string, not a zero buffer. Stated explicitly
|
|
88
|
+
// because "no leaves" is easy to answer with 32 zero bytes and be wrong.
|
|
89
|
+
if (leaves.length === 0) return Hash.sha256(Buffer.alloc(0))
|
|
90
|
+
if (leaves.length === 1) return Merkle.hashLeaf(leaves[0])
|
|
91
|
+
|
|
92
|
+
var k = Merkle.largestPowerOfTwoBelow(leaves.length)
|
|
93
|
+
return Merkle.hashNode(
|
|
94
|
+
Merkle.root(leaves.slice(0, k)),
|
|
95
|
+
Merkle.root(leaves.slice(k))
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* The audit path for the leaf at `index`, per RFC 6962 §2.1.1.
|
|
101
|
+
*
|
|
102
|
+
* Returns sibling hashes ordered from the leaf upward. Unlike the Bitcoin proofs in
|
|
103
|
+
* lib/spv, there is no direction flag: RFC 6962 recovers left/right from the index and
|
|
104
|
+
* the tree size during folding, so the path is bare hashes and `verify` needs both
|
|
105
|
+
* numbers.
|
|
106
|
+
*
|
|
107
|
+
* @param {Array<Buffer>} leaves
|
|
108
|
+
* @param {Number} index
|
|
109
|
+
* @returns {Array<Buffer>}
|
|
110
|
+
*/
|
|
111
|
+
Merkle.path = function (leaves, index) {
|
|
112
|
+
$.checkArgument(Array.isArray(leaves) && leaves.length > 0, 'leaves must be a non-empty array')
|
|
113
|
+
$.checkArgument(Number.isInteger(index) && index >= 0 && index < leaves.length,
|
|
114
|
+
'index must be within the leaves')
|
|
115
|
+
|
|
116
|
+
if (leaves.length === 1) return []
|
|
117
|
+
|
|
118
|
+
var k = Merkle.largestPowerOfTwoBelow(leaves.length)
|
|
119
|
+
|
|
120
|
+
if (index < k) {
|
|
121
|
+
return Merkle.path(leaves.slice(0, k), index)
|
|
122
|
+
.concat([Merkle.root(leaves.slice(k))])
|
|
123
|
+
}
|
|
124
|
+
return Merkle.path(leaves.slice(k), index - k)
|
|
125
|
+
.concat([Merkle.root(leaves.slice(0, k))])
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Fold an audit path back to a root, per RFC 6962 §2.1.2.
|
|
130
|
+
*
|
|
131
|
+
* `leafCount` is required and is not decoration: the tree's shape at each level comes
|
|
132
|
+
* from it, and RFC 6962 folding cannot be done from the path alone. Bitcoin proofs carry
|
|
133
|
+
* a direction bit per node instead, which is why lib/spv's shape does not transfer here.
|
|
134
|
+
*
|
|
135
|
+
* @param {Buffer} leafData - the leaf DATA, hashed here
|
|
136
|
+
* @param {Number} index
|
|
137
|
+
* @param {Number} leafCount
|
|
138
|
+
* @param {Array<Buffer>} path
|
|
139
|
+
* @returns {Buffer} the computed root
|
|
140
|
+
*/
|
|
141
|
+
Merkle.foldPath = function (leafData, index, leafCount, path) {
|
|
142
|
+
$.checkArgument(Buffer.isBuffer(leafData), 'leafData must be a Buffer')
|
|
143
|
+
$.checkArgument(Number.isInteger(index) && index >= 0, 'index must be a non-negative integer')
|
|
144
|
+
$.checkArgument(Number.isInteger(leafCount) && leafCount > 0, 'leafCount must be a positive integer')
|
|
145
|
+
$.checkArgument(index < leafCount, 'index must be less than leafCount')
|
|
146
|
+
$.checkArgument(Array.isArray(path), 'path must be an array')
|
|
147
|
+
|
|
148
|
+
// RFC 6962 §2.1.2, transcribed rather than reinvented. The first attempt at this
|
|
149
|
+
// descended from the root by repeatedly splitting at the largest power of two, which
|
|
150
|
+
// is how root() builds the tree — but the audit path is ordered LEAF-UPWARD, so
|
|
151
|
+
// top-down consumption pairs each sibling at the wrong level. It round-tripped for
|
|
152
|
+
// powers of two and failed for 21 of the 45 leaves in trees of size 1..9.
|
|
153
|
+
//
|
|
154
|
+
// The RFC tracks two counters instead: fn, the node's index within its level, and sn,
|
|
155
|
+
// the index of the last node in that level. A node folds as a RIGHT child when fn is
|
|
156
|
+
// odd or when it is the last node in the level — which is what encodes "the rightmost
|
|
157
|
+
// leaf is never duplicated" without a direction flag in the path.
|
|
158
|
+
var r = Merkle.hashLeaf(leafData)
|
|
159
|
+
var fn = index
|
|
160
|
+
var sn = leafCount - 1
|
|
161
|
+
|
|
162
|
+
for (var i = 0; i < path.length; i++) {
|
|
163
|
+
if (sn === 0) {
|
|
164
|
+
throw new Error('audit path is too long for a tree of ' + leafCount + ' leaves')
|
|
165
|
+
}
|
|
166
|
+
if ((fn & 1) === 1 || fn === sn) {
|
|
167
|
+
r = Merkle.hashNode(path[i], r)
|
|
168
|
+
while ((fn & 1) === 0 && fn !== 0) {
|
|
169
|
+
fn >>= 1
|
|
170
|
+
sn >>= 1
|
|
171
|
+
}
|
|
172
|
+
} else {
|
|
173
|
+
r = Merkle.hashNode(r, path[i])
|
|
174
|
+
}
|
|
175
|
+
fn >>= 1
|
|
176
|
+
sn >>= 1
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (sn !== 0) {
|
|
180
|
+
throw new Error('audit path is too short for a tree of ' + leafCount + ' leaves')
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return r
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Does a leaf belong to the tree with this root?
|
|
188
|
+
*
|
|
189
|
+
* Strict boolean, and false on any malformed input rather than throwing — a caller
|
|
190
|
+
* writing `if (verifyInclusion(...))` must not receive a truthy object, which is the
|
|
191
|
+
* defect class this codebase has fixed most often.
|
|
192
|
+
*
|
|
193
|
+
* @param {Buffer} leafData
|
|
194
|
+
* @param {Number} index
|
|
195
|
+
* @param {Number} leafCount
|
|
196
|
+
* @param {Array<Buffer>} path
|
|
197
|
+
* @param {Buffer} expectedRoot
|
|
198
|
+
* @returns {Boolean}
|
|
199
|
+
*/
|
|
200
|
+
Merkle.verifyInclusion = function (leafData, index, leafCount, path, expectedRoot) {
|
|
201
|
+
try {
|
|
202
|
+
if (!Buffer.isBuffer(expectedRoot)) return false
|
|
203
|
+
return Merkle.foldPath(leafData, index, leafCount, path).equals(expectedRoot)
|
|
204
|
+
} catch (e) {
|
|
205
|
+
return false
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
module.exports = Merkle
|