@smartledger/bsv 9.11.2 → 9.12.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.
@@ -16,10 +16,132 @@
16
16
  * working hash" — Bitcoin's odd-node rule, per the TSC Merkle Proof standard.
17
17
  */
18
18
  var BlockHeader = require('../block/blockheader')
19
+ var BN = require('../crypto/bn')
19
20
  var Hash = require('../crypto/hash')
20
21
 
21
22
  var HEX32 = /^[0-9a-fA-F]{64}$/
22
23
 
24
+ // 2^256, for turning a target into the work it represents.
25
+ var TWO_256 = BlockHeader.Constants.LARGEST_HASH
26
+
27
+ /**
28
+ * The easiest target a header may declare and still be believed: difficulty 1,
29
+ * `0x1d00ffff`, which is the proof-of-work limit of both mainnet and testnet. Regtest
30
+ * headers declare `0x207fffff` and are only checked when a caller asks for that limit.
31
+ */
32
+ var POW_LIMIT_BITS = 0x1d00ffff
33
+
34
+ /**
35
+ * A compact target (`bits`) as a BN, or null if the encoding is one no header may use.
36
+ *
37
+ * Transcribed from Bitcoin's arith_uint256::SetCompact and CheckProofOfWork: a negative
38
+ * target, a zero target and an overflowing target are all refused. `getTargetDifficulty`
39
+ * on BlockHeader implements none of those rules and shifts the wrong way for a size
40
+ * below 4, so it is not used here.
41
+ *
42
+ * @param {Number} bits
43
+ * @returns {BN|null}
44
+ */
45
+ function targetFromBits (bits) {
46
+ var size = bits >>> 24
47
+ var word = bits & 0x007fffff
48
+ var target
49
+ // SetCompact shifts BEFORE its zero, negative and overflow tests, so a small size whose
50
+ // shifted word is zero gives a target of zero and is refused. Testing the raw word
51
+ // instead returned a target of 0, and 0 made workFromTarget report 2^256 of work.
52
+ if (size <= 3) {
53
+ word = word >>> (8 * (3 - size))
54
+ target = new BN(word)
55
+ } else {
56
+ target = new BN(word).shln(8 * (size - 3))
57
+ }
58
+ if (word === 0) return null // zero: no hash can be at or below it
59
+ if ((bits & 0x00800000) !== 0) return null // negative
60
+ if ((size > 34) || (word > 0xff && size > 33) || (word > 0xffff && size > 32)) return null
61
+ return target
62
+ }
63
+
64
+ /** The work a target represents: 2^256 / (target + 1). Difficulty 1 is about 4.295e9. */
65
+ function workFromTarget (target) {
66
+ return TWO_256.div(target.add(new BN(1)))
67
+ }
68
+
69
+ function targetLimit (powLimit) {
70
+ if (powLimit === undefined || powLimit === null) powLimit = POW_LIMIT_BITS
71
+ // A BN is taken as the target itself. The node's limit is a uint256 (2^224 - 1 on
72
+ // mainnet), and for compact bits the two agree, because 0x1d00ffff is the largest
73
+ // compact target at or below it; a BN limit can sit anywhere between them.
74
+ if (BN.isBN(powLimit)) return powLimit
75
+ var bits
76
+ if (typeof powLimit === 'string') {
77
+ // parseInt stops at the first character it dislikes: '1d00ffzz' would silently
78
+ // become 0x1d00ff, a different limit.
79
+ if (!/^(0x)?[0-9a-fA-F]{1,8}$/.test(powLimit)) {
80
+ throw new Error('powLimit must be compact bits as hex, a uint32, or a BN target, not ' +
81
+ JSON.stringify(powLimit))
82
+ }
83
+ bits = parseInt(powLimit.replace(/^0x/, ''), 16)
84
+ } else if (typeof powLimit === 'number' && Number.isInteger(powLimit) &&
85
+ powLimit >= 0 && powLimit <= 0xffffffff) {
86
+ bits = powLimit
87
+ } else {
88
+ throw new Error('powLimit must be compact bits as hex, a uint32, or a BN target, not ' +
89
+ JSON.stringify(powLimit))
90
+ }
91
+ var target = targetFromBits(bits)
92
+ if (!target) throw new Error('powLimit is not a usable compact target: ' + powLimit)
93
+ return target
94
+ }
95
+
96
+ /**
97
+ * `minWork` as a BN, refusing anything bn.js would read loosely. It parses `1e21` as
98
+ * 23521, `'4.3e9'` as 4 and `'abc'` as 1122, so a floor set from a Number near the
99
+ * chain's work would silently become a few thousand — a floor nothing fails.
100
+ */
101
+ function minWorkBN (minWork) {
102
+ if (BN.isBN(minWork)) return minWork
103
+ if (typeof minWork === 'number') {
104
+ if (!Number.isSafeInteger(minWork) || minWork < 0) {
105
+ throw new Error('minWork as a number must be a non-negative safe integer; for larger ' +
106
+ 'floors pass a decimal string or a BN, not ' + minWork)
107
+ }
108
+ return new BN(String(minWork), 10)
109
+ }
110
+ if (typeof minWork === 'string' && /^[0-9]+$/.test(minWork)) return new BN(minWork, 10)
111
+ throw new Error('minWork must be a non-negative integer, a decimal string or a BN, not ' +
112
+ JSON.stringify(minWork))
113
+ }
114
+
115
+ /**
116
+ * Exactly 80 bytes of header, and a BlockHeader parsed from those same bytes.
117
+ *
118
+ * Every check reads this one snapshot, so a caller cannot pass an object whose stated
119
+ * merkle root differs from the bytes its hash is taken over. An object that merely
120
+ * states a root is refused outright: believing it is the trust the protocol removes.
121
+ */
122
+ function headerSnapshot (header) {
123
+ var buf
124
+ if (Buffer.isBuffer(header)) buf = header
125
+ else if (typeof header === 'string') {
126
+ // Buffer.from stops at the first non-hex character, so 160 good characters followed
127
+ // by junk would arrive as a clean 80 bytes.
128
+ if (!/^[0-9a-fA-F]{160}$/.test(header)) {
129
+ throw new Error('a block header as hex must be exactly 160 hex characters')
130
+ }
131
+ buf = Buffer.from(header, 'hex')
132
+ } else if (header instanceof BlockHeader) buf = header.toBuffer()
133
+ else if (header && typeof header.toBuffer === 'function') buf = header.toBuffer()
134
+ else {
135
+ throw new Error('a block header must be 80 bytes, as hex, a Buffer or a BlockHeader; ' +
136
+ 'an object stating a merkleRoot is not a header and cannot be checked')
137
+ }
138
+ if (!Buffer.isBuffer(buf) || buf.length !== 80) {
139
+ throw new Error('a block header must be exactly 80 bytes, not ' +
140
+ (Buffer.isBuffer(buf) ? buf.length : typeof buf))
141
+ }
142
+ return BlockHeader.fromBuffer(buf)
143
+ }
144
+
23
145
  function rev (buf) { return Buffer.from(buf).reverse() }
24
146
  function toInternal (hex) { return rev(Buffer.from(hex, 'hex')) } // display -> internal LE
25
147
  function toDisplay (buf) { return rev(buf).toString('hex') } // internal LE -> display
@@ -67,41 +189,71 @@ function verifyMerkleProof (proof) {
67
189
  }
68
190
 
69
191
  /**
70
- * Verify a transaction is included in a block: branch -> root, root ==
71
- * header.merkleRoot, and (unless disabled) the header meets its PoW target.
192
+ * Verify a transaction is included in a block: branch -> root, root == the root in the
193
+ * header's own bytes, and (unless disabled) the header's proof of work.
194
+ *
195
+ * A header's work is only as good as the target it declares, and a header declares its
196
+ * own. `hash <= target` alone therefore proves nothing: with `bits` of 0x2100ffff nearly
197
+ * every hash passes (65535 in 65536), so a forged header costs one attempt. The declared
198
+ * target is capped at
199
+ * `powLimit` — difficulty 1 by default, the limit of mainnet and testnet — and
200
+ * `minWork` can demand more.
72
201
  *
73
- * NOTE: this proves inclusion in the SUPPLIED header. Confirming that header is on
74
- * the honest chain (height / confirmations) requires a trusted header chain, which
75
- * the caller supplies out of band.
202
+ * NOTE: this proves inclusion in the SUPPLIED header. Even a genuine, fully worked
203
+ * header can belong to an orphaned block; only a chain source knows which block is the
204
+ * chain's at a height. That check is the caller's, out of band.
76
205
  *
77
- * @param {object} params { txid, index, nodes, header, requirePow=true }
78
- * header: a bsv.BlockHeader, an 80-byte Buffer, or 80-byte hex.
79
- * @returns {{ valid:boolean, rootMatches:boolean, powValid:boolean, merkleRoot:string, blockHash:string }}
206
+ * @param {object} params { txid, index, nodes, header, requirePow=true, powLimit, minWork }
207
+ * header: a bsv.BlockHeader, an 80-byte Buffer, or 80-byte hex.
208
+ * powLimit: the easiest target a header may declare, as compact bits (number or hex
209
+ * string) or a BN target. Default 0x1d00ffff. Regtest needs 0x207fffff.
210
+ * minWork: minimum work the header must represent (number, decimal string or BN);
211
+ * 2^256 / (target + 1). Difficulty 1 is about 4.295e9.
212
+ * With `requirePow: false` the work checks are not performed at all: `targetAllowed` and
213
+ * `workSufficient` report true because nothing objected, and `powLimit`/`minWork` are not
214
+ * even read.
215
+ *
216
+ * @returns {{ valid:boolean, rootMatches:boolean, powValid:boolean, targetAllowed:boolean,
217
+ * workSufficient:boolean, work:string, merkleRoot:string, blockHash:string }}
80
218
  */
81
219
  function verifyTxInclusion (params) {
82
- var header = params.header
83
- if (Buffer.isBuffer(header) || typeof header === 'string') {
84
- header = BlockHeader.fromBuffer(Buffer.isBuffer(header) ? header : Buffer.from(header, 'hex'))
85
- }
86
- if (!header || !header.merkleRoot) throw new Error('a valid block header is required')
220
+ var header = headerSnapshot(params.header)
87
221
 
88
- var headerRoot = toDisplay(header.merkleRoot) // header stores the root internal-LE
222
+ var headerRoot = toDisplay(header.merkleRoot) // the root as the header's own bytes give it
89
223
  var computed = merkleRootFromBranch(params.txid, params.index, params.nodes)
90
224
  var rootMatches = computed.toLowerCase() === headerRoot.toLowerCase()
91
225
 
92
226
  var requirePow = params.requirePow !== false
93
- var powValid = header.validProofOfWork()
227
+ var target = targetFromBits(header.bits)
228
+ var powValid = target !== null && new BN(header.id, 'hex').cmp(target) <= 0
229
+ var work = target === null ? new BN(0) : workFromTarget(target)
230
+ // Both are policy the caller supplies, so neither is read — nor rejected as malformed —
231
+ // when the work checks are off.
232
+ var targetAllowed = target !== null &&
233
+ (!requirePow || target.cmp(targetLimit(params.powLimit)) <= 0)
234
+ var workSufficient = !requirePow || params.minWork === undefined || params.minWork === null ||
235
+ work.cmp(minWorkBN(params.minWork)) >= 0
94
236
 
95
237
  return {
96
- valid: rootMatches && (!requirePow || powValid),
238
+ valid: rootMatches && (!requirePow || (powValid && targetAllowed && workSufficient)),
97
239
  rootMatches: rootMatches,
98
240
  powValid: powValid,
241
+ targetAllowed: targetAllowed,
242
+ workSufficient: workSufficient,
243
+ work: work.toString(10),
99
244
  merkleRoot: computed,
100
245
  blockHash: header.id
101
246
  }
102
247
  }
103
248
 
104
249
  module.exports = {
250
+ POW_LIMIT_BITS: POW_LIMIT_BITS,
251
+ // Internal to lib/spv: headerchain applies the same cap. Not re-exported by lib/spv,
252
+ // because the public surface is deliberately hard to grow (see test/api_surface.js).
253
+ targetFromBits: targetFromBits,
254
+ targetLimit: targetLimit,
255
+ workFromTarget: workFromTarget,
256
+ headerSnapshot: headerSnapshot,
105
257
  merkleRootFromBranch: merkleRootFromBranch,
106
258
  verifyMerkleProof: verifyMerkleProof,
107
259
  verifyTxInclusion: verifyTxInclusion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smartledger/bsv",
3
- "version": "9.11.2",
3
+ "version": "9.12.0",
4
4
  "description": "Bitcoin SV library with an interpreter-verified script engine: OP_PUSH_TX covenants, BIP-143 preimage tooling, and consensus flags that match what miners actually enforce. Also ships DID:web / W3C VC-JWT credentials and the Legal Token Protocol.",
5
5
  "author": "SmartLedger Technology <hello@smartledger.technology> (https://smartledger.technology)",
6
6
  "homepage": "https://github.com/codenlighten/smartledger-bsv#readme",
package/version.js CHANGED
@@ -1,3 +1,3 @@
1
1
  'use strict'
2
2
  // GENERATED by scripts/sync-version.js on `npm version` — do not edit.
3
- module.exports = '9.11.2'
3
+ module.exports = '9.12.0'