@smartledger/bsv 6.2.1 → 7.0.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 +66 -0
- package/README.md +38 -38
- package/bsv-gdaf.min.js +17 -17
- package/bsv-ltp.min.js +17 -17
- package/bsv-smartcontract.min.js +18 -18
- package/bsv.bundle.js +17 -17
- package/bsv.d.ts +7 -6
- package/bsv.min.js +17 -17
- package/docs/MIGRATION_7.md +123 -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.mjs +116 -0
- package/lib/crypto/ecdsa.js +15 -14
- package/lib/gdaf/attestation-verifier.js +2 -7
- package/lib/gdaf/did-resolver.js +1 -3
- package/lib/ltp/obligation.js +1 -2
- package/lib/ltp/proof.js +1 -3
- package/lib/ltp/right.js +2 -3
- package/lib/smart_contract/preimage.js +48 -21
- package/package.json +18 -7
- package/test/build/esm_wrapper.js +66 -0
- package/test/build/exports_resolution.js +58 -0
- package/test/crypto/ecdsa.js +29 -4
- package/test/security/fail_closed_contracts.js +13 -9
- package/test/smart_contract/preimage.js +98 -0
- package/version.js +1 -1
|
@@ -40,6 +40,15 @@ function Preimage(preimage, options) {
|
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
// Structural minimum of a BIP-143 preimage, so a too-short buffer fails with an
|
|
44
|
+
// HONEST error instead of tripping decodeCompactSize into a misleading
|
|
45
|
+
// "8-byte CompactSize" throw. LEFT fixed (nVersion 4 + hashPrevouts 32 +
|
|
46
|
+
// hashSequence 32 + outpoint 36 = 104) + smallest scriptLen varint (1) + RIGHT
|
|
47
|
+
// fixed (amount 8 + nSequence 4 + hashOutputs 32 + nLocktime 4 + sighash 4 = 52).
|
|
48
|
+
Preimage.LEFT_FIXED_LENGTH = 4 + 32 + 32 + 36 // 104
|
|
49
|
+
Preimage.RIGHT_FIXED_LENGTH = 8 + 4 + 32 + 4 + 4 // 52
|
|
50
|
+
Preimage.MIN_PREIMAGE_LENGTH = Preimage.LEFT_FIXED_LENGTH + 1 + Preimage.RIGHT_FIXED_LENGTH // 157
|
|
51
|
+
|
|
43
52
|
/**
|
|
44
53
|
* Extract all preimage fields using bidirectional strategy
|
|
45
54
|
* @param {string} extractionStrategy - 'LEFT', 'RIGHT', or 'DYNAMIC'
|
|
@@ -141,13 +150,27 @@ Preimage.prototype.extractFields = function(fieldNames) {
|
|
|
141
150
|
* @returns {Object} Validation result
|
|
142
151
|
*/
|
|
143
152
|
Preimage.prototype.validate = function() {
|
|
153
|
+
var errors = []
|
|
154
|
+
var warnings = []
|
|
155
|
+
|
|
156
|
+
// Length first: a buffer too short to hold the fixed fields can't be
|
|
157
|
+
// field-extracted, so report it without tripping extraction. (For a
|
|
158
|
+
// non-deferred Preimage construction already threw the same error.)
|
|
159
|
+
if (this.preimage.length < Preimage.MIN_PREIMAGE_LENGTH) {
|
|
160
|
+
errors.push('Preimage too short: ' + this.preimage.length +
|
|
161
|
+
' bytes (BIP-143 preimage requires at least ' + Preimage.MIN_PREIMAGE_LENGTH + ')')
|
|
162
|
+
return {
|
|
163
|
+
valid: false,
|
|
164
|
+
errors: errors,
|
|
165
|
+
warnings: warnings,
|
|
166
|
+
sighashInfo: this.sighashInfo
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
144
170
|
if (!this.fields) {
|
|
145
171
|
this._extractFields()
|
|
146
172
|
}
|
|
147
173
|
|
|
148
|
-
var errors = []
|
|
149
|
-
var warnings = []
|
|
150
|
-
|
|
151
174
|
// Check required fields
|
|
152
175
|
var requiredFields = [
|
|
153
176
|
'version', 'hashPrevouts', 'hashSequence', 'outpoint',
|
|
@@ -165,11 +188,6 @@ Preimage.prototype.validate = function() {
|
|
|
165
188
|
warnings.push('Zero hashes detected - check SIGHASH flags: ' + this.sighashInfo.zeroFields.join(', '))
|
|
166
189
|
}
|
|
167
190
|
|
|
168
|
-
// Validate preimage length
|
|
169
|
-
if (this.preimage.length < 104) { // Minimum BIP-143 preimage size
|
|
170
|
-
errors.push('Preimage too short: ' + this.preimage.length + ' bytes (minimum 104)')
|
|
171
|
-
}
|
|
172
|
-
|
|
173
191
|
return {
|
|
174
192
|
valid: errors.length === 0,
|
|
175
193
|
errors: errors,
|
|
@@ -222,6 +240,13 @@ Preimage.prototype._extractFields = function() {
|
|
|
222
240
|
warnings: []
|
|
223
241
|
}
|
|
224
242
|
|
|
243
|
+
// Fail closed with an honest error before any slice/varint work runs on a
|
|
244
|
+
// buffer too small to hold the fixed BIP-143 fields.
|
|
245
|
+
if (this.preimage.length < Preimage.MIN_PREIMAGE_LENGTH) {
|
|
246
|
+
throw new Error('Preimage too short: ' + this.preimage.length +
|
|
247
|
+
' bytes (BIP-143 preimage requires at least ' + Preimage.MIN_PREIMAGE_LENGTH + ')')
|
|
248
|
+
}
|
|
249
|
+
|
|
225
250
|
try {
|
|
226
251
|
if (this.strategy === 'LEFT' || this.strategy === 'DYNAMIC') {
|
|
227
252
|
this._extractFromLeft()
|
|
@@ -735,6 +760,10 @@ Preimage.prototype._getExtractionStrategy = function(fieldName) {
|
|
|
735
760
|
* @returns {Object} Decoded value and next offset
|
|
736
761
|
*/
|
|
737
762
|
Preimage.decodeCompactSize = function(buffer, offset) {
|
|
763
|
+
if (offset >= buffer.length) {
|
|
764
|
+
throw new Error('CompactSize read past end of preimage buffer (offset ' +
|
|
765
|
+
offset + ', length ' + buffer.length + ')')
|
|
766
|
+
}
|
|
738
767
|
var firstByte = buffer[offset]
|
|
739
768
|
|
|
740
769
|
if (firstByte < 0xfd) {
|
|
@@ -742,24 +771,22 @@ Preimage.decodeCompactSize = function(buffer, offset) {
|
|
|
742
771
|
return { value: firstByte, nextOffset: offset + 1, bytes: 1 }
|
|
743
772
|
} else if (firstByte === 0xfd) {
|
|
744
773
|
// 2 byte encoding (253-65535)
|
|
774
|
+
if (offset + 3 > buffer.length) {
|
|
775
|
+
throw new Error('Truncated 2-byte CompactSize in preimage at offset ' + offset)
|
|
776
|
+
}
|
|
745
777
|
var value = buffer.readUInt16LE(offset + 1)
|
|
746
778
|
return { value: value, nextOffset: offset + 3, bytes: 3 }
|
|
747
779
|
} else if (firstByte === 0xfe) {
|
|
748
780
|
// 4 byte encoding (65536-4294967295) - rare for script lengths
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
} else {
|
|
752
|
-
// 8 byte encoding - extremely rare, try to handle gracefully
|
|
753
|
-
console.warn('Encountered 8-byte CompactSize - this is very unusual for script lengths')
|
|
754
|
-
// For our purposes, assume it's a reasonable script length and try to parse it
|
|
755
|
-
// This is a fallback for malformed or test data
|
|
756
|
-
try {
|
|
757
|
-
// Read as 4-byte for now since 8-byte values would be enormous for scripts
|
|
758
|
-
var value = buffer.readUInt32LE(offset + 1)
|
|
759
|
-
return { value: value, nextOffset: offset + 9, bytes: 9 } // Skip full 8 bytes + marker
|
|
760
|
-
} catch (e) {
|
|
761
|
-
throw new Error('Invalid 8-byte CompactSize encoding in preimage')
|
|
781
|
+
if (offset + 5 > buffer.length) {
|
|
782
|
+
throw new Error('Truncated 4-byte CompactSize in preimage at offset ' + offset)
|
|
762
783
|
}
|
|
784
|
+
var value4 = buffer.readUInt32LE(offset + 1)
|
|
785
|
+
return { value: value4, nextOffset: offset + 5, bytes: 5 }
|
|
786
|
+
} else {
|
|
787
|
+
// 0xff marks an 8-byte length (>4 GB) - impossible for a real script length,
|
|
788
|
+
// so this is malformed/misaligned data rather than a value to salvage.
|
|
789
|
+
throw new Error('Invalid CompactSize: 8-byte length prefix (0xff) is not a valid script length')
|
|
763
790
|
}
|
|
764
791
|
}
|
|
765
792
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@smartledger/bsv",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "7.0.0",
|
|
4
4
|
"description": "🚀 Complete Bitcoin SV development framework with legally-recognizable DID:web + W3C VC-JWT toolkit, Legal Token Protocol (LTP), Global Digital Attestation Framework (GDAF), StatusList2021 revocation, and 16 flexible loading options. Standards-based credentials with ES256/ES256K support, on-chain BSV anchoring, and comprehensive Bitcoin SV API. Perfect for legal tokens, verifiable credentials, DeFi, smart contracts, and secure Bitcoin applications.",
|
|
5
5
|
"author": "SmartLedger Technology <hello@smartledger.technology> (https://smartledger.technology)",
|
|
6
6
|
"homepage": "https://github.com/codenlighten/smartledger-bsv#readme",
|
|
@@ -9,6 +9,19 @@
|
|
|
9
9
|
},
|
|
10
10
|
"main": "index.js",
|
|
11
11
|
"types": "bsv.d.ts",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./bsv.d.ts",
|
|
15
|
+
"import": "./index.mjs",
|
|
16
|
+
"require": "./index.js",
|
|
17
|
+
"default": "./index.js"
|
|
18
|
+
},
|
|
19
|
+
"./package.json": "./package.json",
|
|
20
|
+
"./version": "./version.js",
|
|
21
|
+
"./lib/*.js": "./lib/*.js",
|
|
22
|
+
"./lib/*": "./lib/*.js",
|
|
23
|
+
"./*": "./*"
|
|
24
|
+
},
|
|
12
25
|
"engines": {
|
|
13
26
|
"node": ">=20.19.0"
|
|
14
27
|
},
|
|
@@ -51,7 +64,7 @@
|
|
|
51
64
|
"preimage:extract": "node examples/preimage/extract_preimage_bidirectional.js",
|
|
52
65
|
"prepublishOnly": "npm run build-all",
|
|
53
66
|
"sync-cdn": "node scripts/sync-cdn-urls.js",
|
|
54
|
-
"version": "node scripts/sync-cdn-urls.js && node scripts/sync-version.js && git add README.md docs version.js",
|
|
67
|
+
"version": "node scripts/sync-cdn-urls.js && node scripts/sync-version.js && node scripts/gen-esm-wrapper.js && git add README.md docs version.js index.mjs",
|
|
55
68
|
"test:browser:ci": "node tests/browser-smoke-runner.js"
|
|
56
69
|
},
|
|
57
70
|
"unpkg": "bsv.min.js",
|
|
@@ -59,6 +72,7 @@
|
|
|
59
72
|
"cdn": "bsv.min.js",
|
|
60
73
|
"files": [
|
|
61
74
|
"index.js",
|
|
75
|
+
"index.mjs",
|
|
62
76
|
"version.js",
|
|
63
77
|
"test/",
|
|
64
78
|
".mocharc.json",
|
|
@@ -196,12 +210,12 @@
|
|
|
196
210
|
},
|
|
197
211
|
"devDependencies": {
|
|
198
212
|
"assert": "^2.1.0",
|
|
199
|
-
"brfs": "2.0.1",
|
|
200
213
|
"browserify-zlib": "^0.2.0",
|
|
201
214
|
"buffer": "^6.0.3",
|
|
202
215
|
"chai": "4.2.0",
|
|
203
216
|
"crypto-browserify": "^3.12.1",
|
|
204
217
|
"esbuild": "^0.28.1",
|
|
218
|
+
"events": "^3.3.0",
|
|
205
219
|
"jose": "^5.10.0",
|
|
206
220
|
"mocha": "^11.7.6",
|
|
207
221
|
"nyc": "^18.0.0",
|
|
@@ -210,12 +224,9 @@
|
|
|
210
224
|
"sinon": "7.2.3",
|
|
211
225
|
"standard": "12.0.1",
|
|
212
226
|
"stream-browserify": "^3.0.0",
|
|
213
|
-
"terser-webpack-plugin": "^5.6.1",
|
|
214
227
|
"typescript": "^5.9.3",
|
|
215
228
|
"util": "^0.12.5",
|
|
216
|
-
"vm-browserify": "^1.1.2"
|
|
217
|
-
"webpack": "^5.107.2",
|
|
218
|
-
"webpack-cli": "^5.1.4"
|
|
229
|
+
"vm-browserify": "^1.1.2"
|
|
219
230
|
},
|
|
220
231
|
"license": "MIT",
|
|
221
232
|
"standard": {
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/* global describe, it */
|
|
4
|
+
|
|
5
|
+
// Guards index.mjs — the real ESM entry behind the package's `import` condition.
|
|
6
|
+
// Asserts (a) it hasn't drifted from the CJS surface, (b) native ESM default AND
|
|
7
|
+
// named imports actually work, and (c) deprecated getters (SmartUTXO) are NOT
|
|
8
|
+
// forced as named exports (they'd fire their warning on every import).
|
|
9
|
+
//
|
|
10
|
+
// ESM behaviour is exercised in a child process (via an --input-type=module
|
|
11
|
+
// script string) rather than a dynamic import() in this file, so the source
|
|
12
|
+
// stays parseable by the pinned standard@12 linter.
|
|
13
|
+
|
|
14
|
+
require('chai').should()
|
|
15
|
+
var fs = require('fs')
|
|
16
|
+
var path = require('path')
|
|
17
|
+
var execFileSync = require('child_process').execFileSync
|
|
18
|
+
var spawnSync = require('child_process').spawnSync
|
|
19
|
+
var gen = require('../../scripts/gen-esm-wrapper')
|
|
20
|
+
|
|
21
|
+
var ROOT = path.resolve(__dirname, '../..')
|
|
22
|
+
var MJS = path.join(ROOT, 'index.mjs')
|
|
23
|
+
|
|
24
|
+
function runEsm (body) {
|
|
25
|
+
return execFileSync(process.execPath, ['--input-type=module', '-e', body],
|
|
26
|
+
{ cwd: ROOT, stdio: 'pipe' })
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
describe('ESM wrapper (index.mjs)', function () {
|
|
30
|
+
it('is in sync with the CJS surface (no drift)', function () {
|
|
31
|
+
var onDisk = fs.readFileSync(MJS, 'utf8')
|
|
32
|
+
onDisk.should.equal(gen.generate())
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('exports every non-accessor top-level member as a named binding', function () {
|
|
36
|
+
var bsv = require('../..')
|
|
37
|
+
var names = gen.exportableNames(bsv)
|
|
38
|
+
names.length.should.be.above(100)
|
|
39
|
+
// SmartUTXO is an accessor (deprecated getter) and must be excluded.
|
|
40
|
+
names.should.not.include('SmartUTXO')
|
|
41
|
+
Object.getOwnPropertyDescriptor(bsv, 'SmartUTXO').get.should.be.a('function')
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('resolves real ESM default + named imports natively', function () {
|
|
45
|
+
runEsm(
|
|
46
|
+
'import bsv, { PrivateKey, crypto } from ' + JSON.stringify('./index.mjs') + ';' +
|
|
47
|
+
'process.exit((bsv && typeof PrivateKey === "function" && ' +
|
|
48
|
+
'PrivateKey === bsv.PrivateKey && typeof crypto.ECDSA === "function") ? 0 : 3)')
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('does not emit the SmartUTXO deprecation warning at import (getter excluded)', function () {
|
|
52
|
+
// Re-exporting the getter would trip its console.warn at import time; stderr
|
|
53
|
+
// must be empty for a bare `import './index.mjs'`.
|
|
54
|
+
var res = spawnSync(process.execPath,
|
|
55
|
+
['--input-type=module', '-e', 'import ' + JSON.stringify('./index.mjs') + ';'],
|
|
56
|
+
{ cwd: ROOT, encoding: 'utf8' })
|
|
57
|
+
res.status.should.equal(0)
|
|
58
|
+
res.stderr.should.equal('')
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it('keeps deprecated getters reachable via the default export only', function () {
|
|
62
|
+
runEsm(
|
|
63
|
+
'import bsv from ' + JSON.stringify('./index.mjs') + ';' +
|
|
64
|
+
'process.exit(("SmartUTXO" in bsv) ? 0 : 3)')
|
|
65
|
+
})
|
|
66
|
+
})
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/* global describe, it */
|
|
4
|
+
|
|
5
|
+
// Guards the 7.0 package.json `exports` map. Adding `exports` switches Node to
|
|
6
|
+
// strict subpath resolution, so this asserts the surface consumers actually use
|
|
7
|
+
// still resolves (via package self-reference, which Node enables once `exports`
|
|
8
|
+
// exists): the main entry, package.json, ./version, the shipped bundles, and
|
|
9
|
+
// lib/* deep imports in BOTH extension styles. Also smoke-tests the ESM `import`
|
|
10
|
+
// condition in a child process so the dual-ready "." entry can't silently rot.
|
|
11
|
+
|
|
12
|
+
require('chai').should()
|
|
13
|
+
var path = require('path')
|
|
14
|
+
var execFileSync = require('child_process').execFileSync
|
|
15
|
+
|
|
16
|
+
var N = '@smartledger/bsv'
|
|
17
|
+
var ROOT = path.resolve(__dirname, '../..')
|
|
18
|
+
|
|
19
|
+
describe('7.0 exports map resolution', function () {
|
|
20
|
+
it('resolves the main entry via self-reference', function () {
|
|
21
|
+
var bsv = require(N)
|
|
22
|
+
bsv.should.be.an('object')
|
|
23
|
+
bsv.PrivateKey.should.be.a('function')
|
|
24
|
+
bsv.crypto.ECDSA.should.be.a('function')
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('exposes package.json and ./version, and they agree', function () {
|
|
28
|
+
var pkg = require(N + '/package.json')
|
|
29
|
+
var version = require(N + '/version')
|
|
30
|
+
pkg.name.should.equal(N)
|
|
31
|
+
version.should.equal(pkg.version)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('resolves lib/* deep imports with and without the .js extension', function () {
|
|
35
|
+
var withExt = require(N + '/lib/crypto/ecdsa.js')
|
|
36
|
+
var noExt = require(N + '/lib/crypto/ecdsa')
|
|
37
|
+
withExt.should.be.a('function')
|
|
38
|
+
noExt.should.equal(withExt) // same module, one cache entry
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('serves the full browser bundle over a subpath (loaded in isolation)', function () {
|
|
42
|
+
// The bundle is a self-contained bsv; loading it in-process collides with the
|
|
43
|
+
// main require above ("multiple bsv instances"), so resolve+load it in a child.
|
|
44
|
+
var script = 'var m=require(' + JSON.stringify(N + '/bsv.min.js') + ');' +
|
|
45
|
+
'process.exit(m && m.PrivateKey ? 0 : 3)'
|
|
46
|
+
execFileSync(process.execPath, ['-e', script], { cwd: ROOT, stdio: 'pipe' })
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('routes the ESM `import` condition to index.mjs with default AND named exports', function () {
|
|
50
|
+
// Proves the dual-ESM entry end-to-end through the package specifier: the
|
|
51
|
+
// `import` condition resolves to index.mjs, which exposes real named bindings.
|
|
52
|
+
var script =
|
|
53
|
+
'import bsv, { PrivateKey, Transaction, crypto } from ' + JSON.stringify(N) + ';' +
|
|
54
|
+
'process.exit((bsv && bsv.PrivateKey && PrivateKey === bsv.PrivateKey && ' +
|
|
55
|
+
'typeof Transaction === "function" && typeof crypto.ECDSA === "function") ? 0 : 3)'
|
|
56
|
+
execFileSync(process.execPath, ['--input-type=module', '-e', script], { cwd: ROOT, stdio: 'pipe' })
|
|
57
|
+
})
|
|
58
|
+
})
|
package/test/crypto/ecdsa.js
CHANGED
|
@@ -176,7 +176,7 @@ describe('ECDSA', function () {
|
|
|
176
176
|
it('should create a valid signature', function () {
|
|
177
177
|
ecdsa.randomK()
|
|
178
178
|
ecdsa.sign()
|
|
179
|
-
ecdsa.verify().
|
|
179
|
+
ecdsa.verify().should.equal(true)
|
|
180
180
|
})
|
|
181
181
|
|
|
182
182
|
it('should should throw an error if hashbuf is not 32 bytes', function () {
|
|
@@ -253,11 +253,36 @@ describe('ECDSA', function () {
|
|
|
253
253
|
it('should verify a signature that was just signed', function () {
|
|
254
254
|
ecdsa.sig = Signature.fromString('3046022100e9915e6236695f093a4128ac2a956c' +
|
|
255
255
|
'40ed971531de2f4f41ba05fac7e2bd019c02210094e6a4a769cc7f2a8ab3db696c7cd8d56bcdbfff860a8c81de4bc6a798b90827')
|
|
256
|
-
ecdsa.verify().
|
|
256
|
+
ecdsa.verify().should.equal(true)
|
|
257
|
+
})
|
|
258
|
+
// 7.0: verify() returns a strict boolean, not the (always-truthy) instance.
|
|
259
|
+
// A forged signature must be REJECTED by `if (ecdsa.verify())`, which was the
|
|
260
|
+
// whole point of removing the trap.
|
|
261
|
+
it('returns a strict boolean, not the instance (7.0 trap closed)', function () {
|
|
262
|
+
ecdsa.signRandomK()
|
|
263
|
+
var result = ecdsa.verify()
|
|
264
|
+
result.should.be.a('boolean')
|
|
265
|
+
result.should.equal(true)
|
|
266
|
+
// Forge the signature: the return value itself must be falsy.
|
|
267
|
+
var forged = new ECDSA()
|
|
268
|
+
forged.hashbuf = ecdsa.hashbuf
|
|
269
|
+
forged.pubkey = ecdsa.pubkey
|
|
270
|
+
forged.sig = new Signature(ecdsa.sig.r.add(new BN(1)), ecdsa.sig.s)
|
|
271
|
+
forged.verify().should.equal(false)
|
|
272
|
+
;(!!forged.verify()).should.equal(false) // `if (forged.verify())` does NOT enter
|
|
273
|
+
})
|
|
274
|
+
it('verifyBool() remains a strict-boolean alias', function () {
|
|
275
|
+
ecdsa.signRandomK()
|
|
276
|
+
ecdsa.verifyBool().should.equal(true)
|
|
277
|
+
var forged = new ECDSA()
|
|
278
|
+
forged.hashbuf = ecdsa.hashbuf
|
|
279
|
+
forged.pubkey = ecdsa.pubkey
|
|
280
|
+
forged.sig = new Signature(ecdsa.sig.r.add(new BN(1)), ecdsa.sig.s)
|
|
281
|
+
forged.verifyBool().should.equal(false)
|
|
257
282
|
})
|
|
258
283
|
it('should verify this known good signature', function () {
|
|
259
284
|
ecdsa.signRandomK()
|
|
260
|
-
ecdsa.verify().
|
|
285
|
+
ecdsa.verify().should.equal(true)
|
|
261
286
|
})
|
|
262
287
|
it('should verify a valid signature, and unverify an invalid signature', function () {
|
|
263
288
|
var sig = ECDSA.sign(ecdsa.hashbuf, ecdsa.privkey)
|
|
@@ -295,7 +320,7 @@ describe('ECDSA', function () {
|
|
|
295
320
|
ecdsa2.k.toString().should.equal(ecdsa.k.toString())
|
|
296
321
|
ecdsa2.sig.toString().should.equal(ecdsa.sig.toString())
|
|
297
322
|
ecdsa2.sig.i.should.equal(ecdsa.sig.i)
|
|
298
|
-
ecdsa.verify().
|
|
323
|
+
ecdsa.verify().should.equal(true)
|
|
299
324
|
})
|
|
300
325
|
})
|
|
301
326
|
|
|
@@ -49,15 +49,19 @@ describe('security: fail-closed verification contracts', function () {
|
|
|
49
49
|
inst(wrongPub).verifyBool().should.equal(false)
|
|
50
50
|
})
|
|
51
51
|
|
|
52
|
-
//
|
|
53
|
-
//
|
|
54
|
-
//
|
|
55
|
-
it('instance .verify() returns
|
|
56
|
-
var
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
52
|
+
// 7.0: the trap is CLOSED. Instance .verify() now returns a STRICT boolean, so
|
|
53
|
+
// `if (ecdsa.verify())` correctly rejects a forgery. (Pre-7.0 it returned the
|
|
54
|
+
// truthy instance with the real result on .verified; that idiom is gone.)
|
|
55
|
+
it('instance .verify() returns a strict boolean and fails closed on a forgery', function () {
|
|
56
|
+
var good = new ECDSA(); good.hashbuf = hash; good.sig = goodSig; good.pubkey = rightPub
|
|
57
|
+
isStrictBool(good.verify()).should.equal(true)
|
|
58
|
+
good.verify().should.equal(true)
|
|
59
|
+
|
|
60
|
+
var forged = new ECDSA(); forged.hashbuf = hash; forged.sig = goodSig; forged.pubkey = wrongPub
|
|
61
|
+
isStrictBool(forged.verify()).should.equal(true)
|
|
62
|
+
forged.verify().should.equal(false) // forged → false, and `if (forged.verify())` does NOT enter
|
|
63
|
+
// The result is still mirrored on .verified as a side effect.
|
|
64
|
+
forged.verified.should.equal(false)
|
|
61
65
|
})
|
|
62
66
|
})
|
|
63
67
|
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/* global describe, it */
|
|
4
|
+
|
|
5
|
+
// Coverage for SmartContract.Preimage (previously untested public class).
|
|
6
|
+
// Focus: a too-short buffer must fail with an HONEST error (it used to trip
|
|
7
|
+
// decodeCompactSize into a misleading "8-byte CompactSize" throw plus console
|
|
8
|
+
// spam), while real BIP-143 preimages round-trip their fields.
|
|
9
|
+
|
|
10
|
+
require('chai').should()
|
|
11
|
+
var bsv = require('../..')
|
|
12
|
+
var Preimage = bsv.SmartContract.Preimage
|
|
13
|
+
|
|
14
|
+
describe('SmartContract.Preimage', function () {
|
|
15
|
+
describe('length guard (honest failure)', function () {
|
|
16
|
+
it('exposes the derived BIP-143 structural minimum (157)', function () {
|
|
17
|
+
Preimage.LEFT_FIXED_LENGTH.should.equal(104)
|
|
18
|
+
Preimage.RIGHT_FIXED_LENGTH.should.equal(52)
|
|
19
|
+
Preimage.MIN_PREIMAGE_LENGTH.should.equal(157)
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('throws a clear "too short" error at construction, not a CompactSize error', function () {
|
|
23
|
+
;[0, 50, 103, 156].forEach(function (n) {
|
|
24
|
+
(function () { return new Preimage(Buffer.alloc(n)) })
|
|
25
|
+
.should.throw(/Preimage too short/)
|
|
26
|
+
})
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('does NOT mention CompactSize when the real problem is length', function () {
|
|
30
|
+
var threw
|
|
31
|
+
try { threw = new Preimage(Buffer.alloc(50)) } catch (e) { threw = e.message }
|
|
32
|
+
threw.should.match(/Preimage too short/)
|
|
33
|
+
threw.should.not.match(/CompactSize/)
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('validate() reports the length gracefully for a deferred preimage (no throw)', function () {
|
|
37
|
+
var p = new Preimage(Buffer.alloc(50), { deferExtraction: true })
|
|
38
|
+
var res = p.validate()
|
|
39
|
+
res.valid.should.equal(false)
|
|
40
|
+
res.errors.join(' ').should.match(/Preimage too short: 50 bytes/)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('accepts a buffer exactly at the minimum length', function () {
|
|
44
|
+
// 157 bytes: 104 left + 1-byte scriptLen varint (0x00 => empty script) + 52 right.
|
|
45
|
+
var buf = Buffer.alloc(Preimage.MIN_PREIMAGE_LENGTH)
|
|
46
|
+
;(function () { return new Preimage(buf) }).should.not.throw()
|
|
47
|
+
})
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
describe('decodeCompactSize (honest failure)', function () {
|
|
51
|
+
it('throws when reading past the end of the buffer', function () {
|
|
52
|
+
(function () { Preimage.decodeCompactSize(Buffer.alloc(4), 4) })
|
|
53
|
+
.should.throw(/past end of preimage buffer/)
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('rejects the 8-byte (0xff) length prefix as invalid for a script length', function () {
|
|
57
|
+
var buf = Buffer.concat([Buffer.from([0xff]), Buffer.alloc(8)])
|
|
58
|
+
;(function () { Preimage.decodeCompactSize(buf, 0) })
|
|
59
|
+
.should.throw(/8-byte length prefix/)
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('rejects a truncated 2-byte CompactSize', function () {
|
|
63
|
+
(function () { Preimage.decodeCompactSize(Buffer.from([0xfd, 0x01]), 0) })
|
|
64
|
+
.should.throw(/Truncated 2-byte CompactSize/)
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('decodes a plain 1-byte length', function () {
|
|
68
|
+
var r = Preimage.decodeCompactSize(Buffer.from([0x19]), 0)
|
|
69
|
+
r.value.should.equal(0x19)
|
|
70
|
+
r.bytes.should.equal(1)
|
|
71
|
+
r.nextOffset.should.equal(1)
|
|
72
|
+
})
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
describe('real preimage round-trip', function () {
|
|
76
|
+
it('constructs, extracts fields, and validates a genuine preimage', function () {
|
|
77
|
+
var ex = Preimage.createExample(0x41) // SIGHASH_ALL | FORKID
|
|
78
|
+
ex.preimage.length.should.be.above(Preimage.MIN_PREIMAGE_LENGTH)
|
|
79
|
+
|
|
80
|
+
// Fixed-width fields come back at their BIP-143 sizes.
|
|
81
|
+
ex.getField('version').length.should.equal(4)
|
|
82
|
+
ex.getField('hashPrevouts').length.should.equal(32)
|
|
83
|
+
ex.getField('hashOutputs').length.should.equal(32)
|
|
84
|
+
ex.getField('sighash').length.should.equal(4)
|
|
85
|
+
|
|
86
|
+
var v = ex.validate()
|
|
87
|
+
v.valid.should.equal(true)
|
|
88
|
+
v.errors.length.should.equal(0)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('reports SIGHASH flag info for the extracted sighash', function () {
|
|
92
|
+
var ex = Preimage.createExample(0x41)
|
|
93
|
+
var info = ex.getSighashInfo()
|
|
94
|
+
info.flagName.should.match(/ALL/)
|
|
95
|
+
info.flagName.should.match(/FORKID/)
|
|
96
|
+
})
|
|
97
|
+
})
|
|
98
|
+
})
|
package/version.js
CHANGED