@smartledger/bsv 7.12.0 → 7.13.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 +72 -0
- package/README.md +38 -38
- package/bsv-didweb.min.js +6 -6
- package/bsv-gdaf.min.js +58 -58
- package/bsv-ltp.min.js +41 -41
- package/bsv-mnemonic.min.js +8 -8
- package/bsv-smartcontract.min.js +1 -1
- package/bsv.bundle.js +58 -58
- package/bsv.min.js +58 -58
- package/docs/AUDIT_SCOPE.md +18 -19
- 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 +10 -2
- package/lib/crypto/hash.browser.js +13 -5
- package/lib/crypto/hash.js +58 -2
- package/lib/crypto/random.js +72 -15
- package/lib/didweb/index.js +88 -9
- package/lib/encoding/base58.js +67 -3
- package/lib/mnemonic/mnemonic.js +19 -5
- package/lib/mnemonic/pbkdf2.js +35 -2
- package/lib/script/interpreter.js +21 -2
- package/lib/transaction/input/multisig.js +1 -1
- package/lib/transaction/input/multisigscripthash.js +1 -1
- package/lib/transaction/input/publickey.js +1 -1
- package/lib/transaction/input/publickeyhash.js +1 -1
- package/lib/transaction/signature.js +1 -1
- package/lib/util/inherits.js +37 -0
- package/package.json +4 -8
- package/test/crypto/backend_selection.js +126 -0
- package/test/crypto/random.js +73 -0
- package/test/didweb/relationships.js +144 -0
- package/test/transaction/unspentoutput.js +6 -1
- package/version.js +1 -1
package/test/crypto/random.js
CHANGED
|
@@ -28,5 +28,78 @@ describe('Random', function () {
|
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
})
|
|
31
|
+
|
|
32
|
+
// WebCrypto refuses more than 65,536 bytes per call. Before chunking, a request
|
|
33
|
+
// above that limit threw; a naive implementation that ignored the return size
|
|
34
|
+
// instead would hand back a zero-filled tail, which is far worse than throwing.
|
|
35
|
+
it('fills requests larger than the 65536-byte getRandomValues limit', function () {
|
|
36
|
+
var buf = Random.getRandomBuffer(70000)
|
|
37
|
+
buf.length.should.equal(70000)
|
|
38
|
+
// The tail comes from a second call — assert it is real entropy, not zero padding.
|
|
39
|
+
var tail = buf.subarray(65536)
|
|
40
|
+
tail.some(function (b) { return b !== 0 }).should.equal(true)
|
|
41
|
+
buf.subarray(0, 32).toString('hex').should.not.equal(tail.subarray(0, 32).toString('hex'))
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('handles the exact limit and one past it', function () {
|
|
45
|
+
Random.getRandomBuffer(65536).length.should.equal(65536)
|
|
46
|
+
Random.getRandomBuffer(65537).length.should.equal(65537)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
// The backend is chosen by feature detection. This used to branch on
|
|
50
|
+
// `process.browser`, which is undefined in React Native, Deno, Workers and Bun —
|
|
51
|
+
// all of which then fell through to require('crypto').randomBytes. Where a partial
|
|
52
|
+
// crypto shim supplies a weak randomBytes, that produced guessable keys silently.
|
|
53
|
+
it('does not depend on process.browser', function () {
|
|
54
|
+
var saved = process.browser
|
|
55
|
+
try {
|
|
56
|
+
process.browser = undefined
|
|
57
|
+
Random.getRandomBuffer(16).length.should.equal(16)
|
|
58
|
+
process.browser = true
|
|
59
|
+
Random.getRandomBuffer(16).length.should.equal(16)
|
|
60
|
+
} finally {
|
|
61
|
+
process.browser = saved
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('prefers globalThis.crypto.getRandomValues when present', function () {
|
|
66
|
+
var calls = 0
|
|
67
|
+
var real = Object.getOwnPropertyDescriptor(globalThis, 'crypto')
|
|
68
|
+
try {
|
|
69
|
+
Object.defineProperty(globalThis, 'crypto', {
|
|
70
|
+
configurable: true,
|
|
71
|
+
value: {
|
|
72
|
+
getRandomValues: function (arr) {
|
|
73
|
+
calls++
|
|
74
|
+
for (var i = 0; i < arr.length; i++) arr[i] = (i * 7 + 3) & 0xff
|
|
75
|
+
return arr
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
})
|
|
79
|
+
var buf = Random.getRandomBuffer(4)
|
|
80
|
+
calls.should.equal(1)
|
|
81
|
+
buf.toString('hex').should.equal('030a1118')
|
|
82
|
+
} finally {
|
|
83
|
+
if (real) Object.defineProperty(globalThis, 'crypto', real)
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
// The throw matters as much as the detection: a key library must never return bytes
|
|
88
|
+
// it cannot vouch for. Verified in a child process because it has to remove both
|
|
89
|
+
// backends, which cannot be undone safely in-process.
|
|
90
|
+
it('throws rather than returning bytes when no CSPRNG exists', function () {
|
|
91
|
+
var out = require('child_process').spawnSync(process.execPath, ['-e', [
|
|
92
|
+
'var Module = require("module"), orig = Module._load',
|
|
93
|
+
'delete globalThis.crypto',
|
|
94
|
+
'Module._load = function (r) {',
|
|
95
|
+
' if (r === "crypto") { var e = new Error("no crypto"); e.code = "MODULE_NOT_FOUND"; throw e }',
|
|
96
|
+
' return orig.apply(this, arguments)',
|
|
97
|
+
'}',
|
|
98
|
+
'var R = require(' + JSON.stringify(require.resolve('../../lib/crypto/random.js')) + ')',
|
|
99
|
+
'try { R.getRandomBuffer(32); console.log("RETURNED") }',
|
|
100
|
+
'catch (e) { console.log("THREW") }'
|
|
101
|
+
].join('\n')], { encoding: 'utf8' })
|
|
102
|
+
out.stdout.trim().should.equal('THREW')
|
|
103
|
+
})
|
|
31
104
|
})
|
|
32
105
|
})
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/* global describe, it */
|
|
4
|
+
|
|
5
|
+
// DID Core defines the verification relationships to be DISTINCT. `authentication`
|
|
6
|
+
// proves who you are right now; `assertionMethod` makes a claim that outlives the
|
|
7
|
+
// session. Keeping them apart is what stops a low-friction login signature from being
|
|
8
|
+
// redeemed as a signed statement.
|
|
9
|
+
//
|
|
10
|
+
// This module used to cross-list every key into both, and `rotateIssuerKey` rewrote both
|
|
11
|
+
// regardless of which one was being rotated — so replacing a compromised authentication
|
|
12
|
+
// key invalidated every statement the issuer had ever signed.
|
|
13
|
+
|
|
14
|
+
require('chai').should()
|
|
15
|
+
var didweb = require('../../lib/didweb')
|
|
16
|
+
|
|
17
|
+
var DOMAIN = 'example.com'
|
|
18
|
+
var DID = 'did:web:example.com'
|
|
19
|
+
var P256 = { kid: 'auth-1', jwk: { kty: 'EC', crv: 'P-256', x: 'a', y: 'b' } }
|
|
20
|
+
var K1 = { kid: 'sign-1', jwk: { kty: 'EC', crv: 'secp256k1', x: 'c', y: 'd' } }
|
|
21
|
+
|
|
22
|
+
function scoped (key, rels) {
|
|
23
|
+
return { kid: key.kid, jwk: key.jwk, relationships: rels }
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
describe('did:web verification relationships', function () {
|
|
27
|
+
describe('buildDidWebDocuments', function () {
|
|
28
|
+
// The compatibility guarantee: callers that never heard of `relationships` must get
|
|
29
|
+
// byte-identical documents, including key order.
|
|
30
|
+
it('keeps the previous output when no relationships are given', function () {
|
|
31
|
+
var doc = didweb.buildDidWebDocuments({ domain: DOMAIN, p256: P256, k1: K1 }).didDocument
|
|
32
|
+
doc.authentication.should.deep.equal([DID + '#auth-1', DID + '#sign-1'])
|
|
33
|
+
doc.assertionMethod.should.deep.equal([DID + '#auth-1', DID + '#sign-1'])
|
|
34
|
+
Object.keys(doc).should.deep.equal([
|
|
35
|
+
'@context', 'id', 'verificationMethod', 'authentication', 'assertionMethod'
|
|
36
|
+
])
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('scopes each key to the relationships it was given', function () {
|
|
40
|
+
var doc = didweb.buildDidWebDocuments({
|
|
41
|
+
domain: DOMAIN,
|
|
42
|
+
p256: scoped(P256, ['authentication']),
|
|
43
|
+
k1: scoped(K1, ['assertionMethod'])
|
|
44
|
+
}).didDocument
|
|
45
|
+
doc.authentication.should.deep.equal([DID + '#auth-1'])
|
|
46
|
+
doc.assertionMethod.should.deep.equal([DID + '#sign-1'])
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('supports the other three relationships and omits empty ones', function () {
|
|
50
|
+
var doc = didweb.buildDidWebDocuments({
|
|
51
|
+
domain: DOMAIN,
|
|
52
|
+
p256: scoped(P256, ['keyAgreement', 'capabilityInvocation', 'capabilityDelegation'])
|
|
53
|
+
}).didDocument
|
|
54
|
+
doc.keyAgreement.should.deep.equal([DID + '#auth-1'])
|
|
55
|
+
doc.capabilityInvocation.should.deep.equal([DID + '#auth-1'])
|
|
56
|
+
doc.capabilityDelegation.should.deep.equal([DID + '#auth-1'])
|
|
57
|
+
;(doc.authentication === undefined).should.equal(true)
|
|
58
|
+
;(doc.assertionMethod === undefined).should.equal(true)
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it('rejects an unknown relationship rather than silently dropping it', function () {
|
|
62
|
+
;(function () {
|
|
63
|
+
didweb.buildDidWebDocuments({ domain: DOMAIN, p256: scoped(P256, ['notARelationship']) })
|
|
64
|
+
}).should.throw(/unknown verification relationship/)
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('rejects an empty relationship list', function () {
|
|
68
|
+
;(function () {
|
|
69
|
+
didweb.buildDidWebDocuments({ domain: DOMAIN, p256: scoped(P256, []) })
|
|
70
|
+
}).should.throw(/non-empty array/)
|
|
71
|
+
})
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
describe('rotateIssuerKey', function () {
|
|
75
|
+
function currentDoc () {
|
|
76
|
+
return didweb.buildDidWebDocuments({
|
|
77
|
+
domain: DOMAIN,
|
|
78
|
+
p256: scoped(P256, ['authentication']),
|
|
79
|
+
k1: scoped(K1, ['assertionMethod'])
|
|
80
|
+
}).didDocument
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// The reported failure. Rotating a compromised auth key must not touch
|
|
84
|
+
// assertionMethod, or every seal the issuer ever made stops verifying.
|
|
85
|
+
it('leaves assertionMethod intact when only authentication is rotated', function () {
|
|
86
|
+
var before = currentDoc()
|
|
87
|
+
var after = didweb.rotateIssuerKey({
|
|
88
|
+
domain: DOMAIN,
|
|
89
|
+
newKey: { kid: 'auth-2', jwk: { kty: 'EC', crv: 'P-256', x: 'e', y: 'f' }, relationships: ['authentication'] },
|
|
90
|
+
currentDocument: before
|
|
91
|
+
}).didDocument
|
|
92
|
+
|
|
93
|
+
after.authentication.should.deep.equal([DID + '#auth-2'])
|
|
94
|
+
after.assertionMethod.should.deep.equal(before.assertionMethod)
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('still defines the key that the carried-over relationship points at', function () {
|
|
98
|
+
var after = didweb.rotateIssuerKey({
|
|
99
|
+
domain: DOMAIN,
|
|
100
|
+
newKey: { kid: 'auth-2', jwk: { kty: 'EC' }, relationships: ['authentication'] },
|
|
101
|
+
currentDocument: currentDoc()
|
|
102
|
+
}).didDocument
|
|
103
|
+
|
|
104
|
+
var defined = after.verificationMethod.map(function (vm) { return vm.id })
|
|
105
|
+
// Every referenced id resolves — no dangling references.
|
|
106
|
+
Object.keys(after).forEach(function (k) {
|
|
107
|
+
if (Array.isArray(after[k]) && k !== 'verificationMethod' && k !== '@context') {
|
|
108
|
+
after[k].forEach(function (id) { defined.indexOf(id).should.be.above(-1, k + ' references undefined ' + id) })
|
|
109
|
+
}
|
|
110
|
+
})
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
it('drops the rotated-out key, which nothing references any more', function () {
|
|
114
|
+
var after = didweb.rotateIssuerKey({
|
|
115
|
+
domain: DOMAIN,
|
|
116
|
+
newKey: { kid: 'auth-2', jwk: { kty: 'EC' }, relationships: ['authentication'] },
|
|
117
|
+
currentDocument: currentDoc()
|
|
118
|
+
}).didDocument
|
|
119
|
+
after.verificationMethod.some(function (vm) {
|
|
120
|
+
return vm.id === DID + '#auth-1'
|
|
121
|
+
}).should.equal(false)
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
it('keeps the previous behaviour with no currentDocument', function () {
|
|
125
|
+
var doc = didweb.rotateIssuerKey({
|
|
126
|
+
domain: DOMAIN, newKey: { kid: 'k9', jwk: { kty: 'EC' } }
|
|
127
|
+
}).didDocument
|
|
128
|
+
doc.authentication.should.deep.equal([DID + '#k9'])
|
|
129
|
+
doc.assertionMethod.should.deep.equal([DID + '#k9'])
|
|
130
|
+
doc.verificationMethod.length.should.equal(1)
|
|
131
|
+
Object.keys(doc).should.deep.equal([
|
|
132
|
+
'@context', 'id', 'verificationMethod', 'authentication', 'assertionMethod', 'rotationInfo'
|
|
133
|
+
])
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
it('still reports rotationInfo', function () {
|
|
137
|
+
var doc = didweb.rotateIssuerKey({
|
|
138
|
+
domain: DOMAIN, newKey: { kid: 'k9', jwk: { kty: 'EC' } }, keepOldForDays: 7
|
|
139
|
+
}).didDocument
|
|
140
|
+
doc.rotationInfo.gracePeriodDays.should.equal(7)
|
|
141
|
+
doc.rotationInfo.rotatedAt.should.be.a('string')
|
|
142
|
+
})
|
|
143
|
+
})
|
|
144
|
+
})
|
|
@@ -2,7 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
var chai = require('chai')
|
|
4
4
|
var expect = chai.expect
|
|
5
|
-
|
|
5
|
+
/* global structuredClone */
|
|
6
|
+
// structuredClone, not the former clone-deep dependency: the fixture below is plain
|
|
7
|
+
// JSON (strings and numbers), where the two are equivalent. The interpreter's stack
|
|
8
|
+
// clone is NOT — it holds Buffers, which structuredClone would return as Uint8Array.
|
|
9
|
+
// The global comment is for standard@12's eslint, which predates the API.
|
|
10
|
+
var cloneDeep = structuredClone
|
|
6
11
|
|
|
7
12
|
var bsv = require('../..')
|
|
8
13
|
var UnspentOutput = bsv.Transaction.UnspentOutput
|
package/version.js
CHANGED