@smartledger/bsv 6.2.2 → 7.0.1
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 +52 -0
- package/README.md +38 -38
- package/bsv-gdaf.min.js +27 -27
- package/bsv-ltp.min.js +27 -27
- package/bsv-mnemonic.min.js +8 -8
- package/bsv-smartcontract.min.js +1 -1
- package/bsv.bundle.js +27 -27
- package/bsv.d.ts +9 -7
- package/bsv.min.js +27 -27
- 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/mnemonic/mnemonic.js +32 -2
- package/package.json +16 -2
- 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/mnemonic/mnemonic.js +32 -0
- package/test/security/fail_closed_contracts.js +13 -9
- package/version.js +1 -1
package/lib/mnemonic/mnemonic.js
CHANGED
|
@@ -89,8 +89,38 @@ var Mnemonic = function (data, wordlist) {
|
|
|
89
89
|
})
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
|
|
93
|
-
|
|
92
|
+
/**
|
|
93
|
+
* Generate a random Mnemonic with the given wordlist and entropy.
|
|
94
|
+
*
|
|
95
|
+
* A number in either argument position is the entropy in bits (128, 160, 192,
|
|
96
|
+
* 224 or 256 — higher = more words); an array is the wordlist. So every form
|
|
97
|
+
* below works AND honours the requested strength:
|
|
98
|
+
* fromRandom() -> ENGLISH, 128-bit (12 words)
|
|
99
|
+
* fromRandom(wordlist) -> wordlist, 128-bit
|
|
100
|
+
* fromRandom(wordlist, 256) -> wordlist, 256-bit (24 words)
|
|
101
|
+
* fromRandom(256) -> ENGLISH, 256-bit
|
|
102
|
+
* fromRandom(256, wordlist) -> wordlist, 256-bit
|
|
103
|
+
*
|
|
104
|
+
* Prior to 7.0.1 the second argument was silently dropped, so the documented
|
|
105
|
+
* `fromRandom(wordlist, 256)` form returned a weaker 12-word (128-bit) phrase
|
|
106
|
+
* with no error — a security foot-gun this fix closes. Invalid entropy still
|
|
107
|
+
* throws (must be a multiple of 32 and >= 128).
|
|
108
|
+
*
|
|
109
|
+
* @param {Array<string>|number} [wordlist] - wordlist, or entropy bits
|
|
110
|
+
* @param {number|Array<string>} [ent] - entropy bits, or wordlist
|
|
111
|
+
* @returns {Mnemonic}
|
|
112
|
+
*/
|
|
113
|
+
Mnemonic.fromRandom = function (wordlist, ent) {
|
|
114
|
+
// A number in the first position is the entropy; normalise so `ent` holds it
|
|
115
|
+
// and `wordlist` holds the array (either argument order is accepted).
|
|
116
|
+
if (_.isNumber(wordlist)) {
|
|
117
|
+
var swapped = ent
|
|
118
|
+
ent = wordlist
|
|
119
|
+
wordlist = swapped
|
|
120
|
+
}
|
|
121
|
+
wordlist = wordlist || Mnemonic.Words.ENGLISH
|
|
122
|
+
ent = ent || 128
|
|
123
|
+
return new Mnemonic(ent, wordlist)
|
|
94
124
|
}
|
|
95
125
|
|
|
96
126
|
Mnemonic.fromString = function (mnemonic, wordlist = Mnemonic.Words.ENGLISH) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@smartledger/bsv",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "7.0.1",
|
|
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",
|
|
@@ -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
|
|
|
@@ -27,6 +27,38 @@ describe('Mnemonic', function () {
|
|
|
27
27
|
Mnemonic.Words.SPANISH.includes(mnemonic3.toString().split(' ')[1]).should.equal(true)
|
|
28
28
|
Mnemonic.Words.SPANISH.includes(mnemonic3.toString().split(' ')[2]).should.equal(true)
|
|
29
29
|
})
|
|
30
|
+
|
|
31
|
+
// Regression: the second argument used to be silently dropped, so the
|
|
32
|
+
// documented fromRandom(wordlist, 256) form returned a WEAK 12-word phrase.
|
|
33
|
+
function wordCount (m) { return m.toString().split(' ').length }
|
|
34
|
+
|
|
35
|
+
it('defaults to a 12-word (128-bit) phrase', function () {
|
|
36
|
+
wordCount(Mnemonic.fromRandom()).should.equal(12)
|
|
37
|
+
wordCount(Mnemonic.fromRandom(Mnemonic.Words.ENGLISH)).should.equal(12)
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('honours entropy passed as the second argument (wordlist, ent)', function () {
|
|
41
|
+
// The previously-broken documented form: must now yield 24 words.
|
|
42
|
+
wordCount(Mnemonic.fromRandom(Mnemonic.Words.ENGLISH, 256)).should.equal(24)
|
|
43
|
+
wordCount(Mnemonic.fromRandom(Mnemonic.Words.ENGLISH, 192)).should.equal(18)
|
|
44
|
+
wordCount(Mnemonic.fromRandom(Mnemonic.Words.ENGLISH, 160)).should.equal(15)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('honours entropy passed as the first argument (ent) or (ent, wordlist)', function () {
|
|
48
|
+
wordCount(Mnemonic.fromRandom(256)).should.equal(24)
|
|
49
|
+
var es = Mnemonic.fromRandom(256, Mnemonic.Words.SPANISH)
|
|
50
|
+
wordCount(es).should.equal(24)
|
|
51
|
+
Mnemonic.Words.SPANISH.includes(es.toString().split(' ')[0]).should.equal(true)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('throws on invalid entropy instead of silently degrading', function () {
|
|
55
|
+
;(function () { Mnemonic.fromRandom(Mnemonic.Words.ENGLISH, 200) }).should.throw(/ENT/)
|
|
56
|
+
;(function () { Mnemonic.fromRandom(64) }).should.throw(/ENT/)
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('reports a meaningful arity (2), not 0', function () {
|
|
60
|
+
Mnemonic.fromRandom.length.should.equal(2)
|
|
61
|
+
})
|
|
30
62
|
})
|
|
31
63
|
|
|
32
64
|
describe('# Mnemonic', function () {
|
|
@@ -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
|
|
package/version.js
CHANGED