@smartledger/bsv 4.6.0 → 5.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 +75 -0
- package/README.md +38 -38
- package/bsv-covenant.min.js +8 -5
- package/bsv-ecies.min.js +1 -1
- package/bsv-gdaf.min.js +9 -6
- package/bsv-ltp.min.js +9 -6
- package/bsv-shamir.min.js +5 -2
- package/bsv-smartcontract.min.js +9 -6
- package/bsv-statuslist.min.js +1 -1
- package/bsv-vcjwt.min.js +3 -3
- package/bsv.bundle.js +13 -10
- package/bsv.min.js +13 -10
- package/build/webpack.bundle.config.js +3 -2
- package/build/webpack.config.js +3 -1
- package/build/webpack.security.config.js +2 -1
- 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 +1 -1
- package/lib/crypto/elliptic-fixed.js +30 -37
- package/lib/crypto/shamir.js +239 -238
- package/lib/ecies/bitcore-ecies.js +16 -5
- package/lib/vcjwt/index.js +83 -22
- package/package.json +3 -9
package/lib/vcjwt/index.js
CHANGED
|
@@ -4,12 +4,28 @@
|
|
|
4
4
|
* VC-JWT Module
|
|
5
5
|
* W3C Verifiable Credentials using JWT/JWS
|
|
6
6
|
* Supports ES256 (P-256) and ES256K (secp256k1)
|
|
7
|
+
*
|
|
8
|
+
* Signatures are emitted and verified as raw r||s (IEEE P1363) per the JOSE
|
|
9
|
+
* specs (RFC 7515/7518; RFC 8812 for ES256K). This is what every standards
|
|
10
|
+
* compliant JWT/JWS library (jose, jsonwebtoken, ...) expects. Tokens issued
|
|
11
|
+
* by versions <= 4.6.0 used Node's default DER encoding and are NOT JOSE
|
|
12
|
+
* compatible; pass `{ allowLegacyDER: true }` to verifyVcJwt to accept them
|
|
13
|
+
* during migration.
|
|
7
14
|
*/
|
|
8
15
|
|
|
9
16
|
var crypto = require('crypto')
|
|
10
17
|
|
|
18
|
+
// Supported JWS algorithms mapped to their expected JWK curve.
|
|
19
|
+
// (RFC 7518 §3.1 for ES256/P-256; RFC 8812 for ES256K/secp256k1.)
|
|
20
|
+
var ALG_TO_CRV = {
|
|
21
|
+
ES256: 'P-256',
|
|
22
|
+
ES256K: 'secp256k1'
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
var DEFAULT_ALLOWED_ALGS = ['ES256', 'ES256K']
|
|
26
|
+
|
|
11
27
|
// Base64URL encoding
|
|
12
|
-
function base64UrlEncode(buffer) {
|
|
28
|
+
function base64UrlEncode (buffer) {
|
|
13
29
|
return buffer.toString('base64')
|
|
14
30
|
.replace(/\+/g, '-')
|
|
15
31
|
.replace(/\//g, '_')
|
|
@@ -17,7 +33,7 @@ function base64UrlEncode(buffer) {
|
|
|
17
33
|
}
|
|
18
34
|
|
|
19
35
|
// Base64URL decoding
|
|
20
|
-
function base64UrlDecode(str) {
|
|
36
|
+
function base64UrlDecode (str) {
|
|
21
37
|
str = str.replace(/-/g, '+').replace(/_/g, '/')
|
|
22
38
|
while (str.length % 4) {
|
|
23
39
|
str += '='
|
|
@@ -26,12 +42,19 @@ function base64UrlDecode(str) {
|
|
|
26
42
|
}
|
|
27
43
|
|
|
28
44
|
// Issue a Verifiable Credential as JWT
|
|
29
|
-
async function issueVcJwt(params) {
|
|
45
|
+
async function issueVcJwt (params) {
|
|
30
46
|
if (!params.issuerDid || !params.subjectId || !params.credentialSubject || !params.privateJwk) {
|
|
31
47
|
throw new Error('issuerDid, subjectId, credentialSubject, and privateJwk are required')
|
|
32
48
|
}
|
|
33
49
|
|
|
34
50
|
var alg = params.alg || 'ES256'
|
|
51
|
+
if (!ALG_TO_CRV[alg]) {
|
|
52
|
+
throw new Error('Unsupported algorithm: ' + alg)
|
|
53
|
+
}
|
|
54
|
+
if (params.privateJwk.crv && params.privateJwk.crv !== ALG_TO_CRV[alg]) {
|
|
55
|
+
throw new Error('Key curve ' + params.privateJwk.crv + ' does not match alg ' + alg)
|
|
56
|
+
}
|
|
57
|
+
|
|
35
58
|
var kid = params.kid || params.privateJwk.kid
|
|
36
59
|
var types = params.types || ['VerifiableCredential']
|
|
37
60
|
var expSeconds = params.expSeconds || (365 * 24 * 60 * 60) // 1 year default
|
|
@@ -73,20 +96,20 @@ async function issueVcJwt(params) {
|
|
|
73
96
|
var payloadB64 = base64UrlEncode(Buffer.from(JSON.stringify(jwtPayload)))
|
|
74
97
|
var signingInput = headerB64 + '.' + payloadB64
|
|
75
98
|
|
|
76
|
-
// Sign with private key
|
|
99
|
+
// Sign with private key.
|
|
77
100
|
var privateKey = crypto.createPrivateKey({
|
|
78
101
|
key: params.privateJwk,
|
|
79
102
|
format: 'jwk'
|
|
80
103
|
})
|
|
81
104
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
105
|
+
// JWS (RFC 7515/7518) requires ECDSA signatures as the raw r||s concatenation
|
|
106
|
+
// (IEEE P1363), NOT the DER encoding Node emits by default. Without
|
|
107
|
+
// dsaEncoding:'ieee-p1363' the token fails verification in every standards
|
|
108
|
+
// compliant JOSE library.
|
|
109
|
+
var signature = crypto.sign('sha256', Buffer.from(signingInput), {
|
|
110
|
+
key: privateKey,
|
|
111
|
+
dsaEncoding: 'ieee-p1363'
|
|
112
|
+
})
|
|
90
113
|
|
|
91
114
|
var signatureB64 = base64UrlEncode(signature)
|
|
92
115
|
var jwt = signingInput + '.' + signatureB64
|
|
@@ -95,11 +118,14 @@ async function issueVcJwt(params) {
|
|
|
95
118
|
}
|
|
96
119
|
|
|
97
120
|
// Verify a VC-JWT
|
|
98
|
-
async function verifyVcJwt(jwt, opts) {
|
|
121
|
+
async function verifyVcJwt (jwt, opts) {
|
|
99
122
|
opts = opts || {}
|
|
100
123
|
|
|
101
124
|
try {
|
|
102
125
|
// Parse JWT
|
|
126
|
+
if (typeof jwt !== 'string') {
|
|
127
|
+
return { valid: false, error: 'Invalid JWT format' }
|
|
128
|
+
}
|
|
103
129
|
var parts = jwt.split('.')
|
|
104
130
|
if (parts.length !== 3) {
|
|
105
131
|
return { valid: false, error: 'Invalid JWT format' }
|
|
@@ -113,14 +139,27 @@ async function verifyVcJwt(jwt, opts) {
|
|
|
113
139
|
var payload = JSON.parse(base64UrlDecode(payloadB64).toString())
|
|
114
140
|
var signature = base64UrlDecode(signatureB64)
|
|
115
141
|
|
|
142
|
+
// Pin the algorithm: never trust header.alg blindly. Reject anything
|
|
143
|
+
// outside the supported set (or outside opts.allowedAlgs when provided).
|
|
144
|
+
// This is the primary defense against JWT algorithm-substitution attacks.
|
|
145
|
+
var allowedAlgs = opts.allowedAlgs || DEFAULT_ALLOWED_ALGS
|
|
146
|
+
if (!header.alg || allowedAlgs.indexOf(header.alg) === -1 || !ALG_TO_CRV[header.alg]) {
|
|
147
|
+
return { valid: false, error: 'Algorithm not allowed: ' + header.alg, header: header, payload: payload }
|
|
148
|
+
}
|
|
149
|
+
|
|
116
150
|
// Check expiration
|
|
117
151
|
var now = Math.floor(Date.now() / 1000)
|
|
118
152
|
var clockTolerance = opts.clockToleranceSec || 60
|
|
119
|
-
|
|
153
|
+
|
|
120
154
|
if (payload.exp && payload.exp < (now - clockTolerance)) {
|
|
121
155
|
return { valid: false, error: 'JWT expired', header: header, payload: payload }
|
|
122
156
|
}
|
|
123
157
|
|
|
158
|
+
// Reject not-yet-valid tokens (nbf), if present.
|
|
159
|
+
if (payload.nbf && payload.nbf > (now + clockTolerance)) {
|
|
160
|
+
return { valid: false, error: 'JWT not yet valid', header: header, payload: payload }
|
|
161
|
+
}
|
|
162
|
+
|
|
124
163
|
// Check issuer if expected
|
|
125
164
|
if (opts.expectedIssuerDid && payload.iss !== opts.expectedIssuerDid) {
|
|
126
165
|
return { valid: false, error: 'Unexpected issuer', header: header, payload: payload }
|
|
@@ -133,22 +172,28 @@ async function verifyVcJwt(jwt, opts) {
|
|
|
133
172
|
if (!resolved || !resolved.jwks || !resolved.jwks.keys) {
|
|
134
173
|
return { valid: false, error: 'Failed to resolve issuer DID', header: header, payload: payload }
|
|
135
174
|
}
|
|
136
|
-
|
|
175
|
+
|
|
137
176
|
// Find matching key by kid
|
|
138
|
-
var matchingKey = resolved.jwks.keys.find(function(k) {
|
|
177
|
+
var matchingKey = resolved.jwks.keys.find(function (k) {
|
|
139
178
|
return k.kid === header.kid
|
|
140
179
|
})
|
|
141
|
-
|
|
180
|
+
|
|
142
181
|
if (!matchingKey) {
|
|
143
182
|
return { valid: false, error: 'Key not found in JWKS', header: header, payload: payload }
|
|
144
183
|
}
|
|
145
|
-
|
|
184
|
+
|
|
146
185
|
publicKey = matchingKey
|
|
147
186
|
} else {
|
|
148
187
|
// Without resolver, verification cannot proceed
|
|
149
188
|
return { valid: false, error: 'DID resolver required for verification', header: header, payload: payload }
|
|
150
189
|
}
|
|
151
190
|
|
|
191
|
+
// Ensure the resolving key's curve matches the pinned algorithm, so an
|
|
192
|
+
// ES256K signature can't be checked against a P-256 key or vice versa.
|
|
193
|
+
if (publicKey.crv && publicKey.crv !== ALG_TO_CRV[header.alg]) {
|
|
194
|
+
return { valid: false, error: 'Key curve does not match algorithm', header: header, payload: payload }
|
|
195
|
+
}
|
|
196
|
+
|
|
152
197
|
// Verify signature
|
|
153
198
|
var signingInput = headerB64 + '.' + payloadB64
|
|
154
199
|
var pubKey = crypto.createPublicKey({
|
|
@@ -156,13 +201,29 @@ async function verifyVcJwt(jwt, opts) {
|
|
|
156
201
|
format: 'jwk'
|
|
157
202
|
})
|
|
158
203
|
|
|
204
|
+
// Signature is raw r||s (IEEE P1363) per JWS — decode it as such.
|
|
159
205
|
var isValid = crypto.verify(
|
|
160
206
|
'sha256',
|
|
161
207
|
Buffer.from(signingInput),
|
|
162
|
-
pubKey,
|
|
208
|
+
{ key: pubKey, dsaEncoding: 'ieee-p1363' },
|
|
163
209
|
signature
|
|
164
210
|
)
|
|
165
211
|
|
|
212
|
+
// Migration path: tokens issued by <= 4.6.0 carry a DER-encoded signature.
|
|
213
|
+
// Only accepted when the caller explicitly opts in.
|
|
214
|
+
if (!isValid && opts.allowLegacyDER) {
|
|
215
|
+
try {
|
|
216
|
+
isValid = crypto.verify(
|
|
217
|
+
'sha256',
|
|
218
|
+
Buffer.from(signingInput),
|
|
219
|
+
{ key: pubKey, dsaEncoding: 'der' },
|
|
220
|
+
signature
|
|
221
|
+
)
|
|
222
|
+
} catch (e) {
|
|
223
|
+
isValid = false
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
166
227
|
if (!isValid) {
|
|
167
228
|
return { valid: false, error: 'Invalid signature', header: header, payload: payload }
|
|
168
229
|
}
|
|
@@ -172,7 +233,6 @@ async function verifyVcJwt(jwt, opts) {
|
|
|
172
233
|
header: header,
|
|
173
234
|
payload: payload
|
|
174
235
|
}
|
|
175
|
-
|
|
176
236
|
} catch (error) {
|
|
177
237
|
return {
|
|
178
238
|
valid: false,
|
|
@@ -185,5 +245,6 @@ module.exports = {
|
|
|
185
245
|
issueVcJwt: issueVcJwt,
|
|
186
246
|
verifyVcJwt: verifyVcJwt,
|
|
187
247
|
base64UrlEncode: base64UrlEncode,
|
|
188
|
-
base64UrlDecode: base64UrlDecode
|
|
189
|
-
|
|
248
|
+
base64UrlDecode: base64UrlDecode,
|
|
249
|
+
ALG_TO_CRV: ALG_TO_CRV
|
|
250
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@smartledger/bsv",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.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",
|
|
@@ -146,14 +146,7 @@
|
|
|
146
146
|
"pels",
|
|
147
147
|
"bip143",
|
|
148
148
|
"preimage",
|
|
149
|
-
"shamir-secret-sharing",
|
|
150
|
-
"threshold-cryptography",
|
|
151
149
|
"secret-splitting",
|
|
152
|
-
"global-digital-attestation",
|
|
153
|
-
"gdaf",
|
|
154
|
-
"verifiable-credentials",
|
|
155
|
-
"w3c-credentials",
|
|
156
|
-
"decentralized-identity",
|
|
157
150
|
"did-resolution",
|
|
158
151
|
"zero-knowledge-proofs",
|
|
159
152
|
"blockchain-anchoring",
|
|
@@ -198,7 +191,6 @@
|
|
|
198
191
|
"request": "browser-request"
|
|
199
192
|
},
|
|
200
193
|
"dependencies": {
|
|
201
|
-
"@smartledger/bsv": "^4.4.0",
|
|
202
194
|
"aes-js": "^3.1.2",
|
|
203
195
|
"bn.js": "=4.11.9",
|
|
204
196
|
"bs58": "=4.0.1",
|
|
@@ -206,11 +198,13 @@
|
|
|
206
198
|
"elliptic": "6.6.1",
|
|
207
199
|
"hash.js": "^1.1.7",
|
|
208
200
|
"inherits": "2.0.3",
|
|
201
|
+
"secrets.js-grempe": "^2.0.0",
|
|
209
202
|
"unorm": "1.4.1"
|
|
210
203
|
},
|
|
211
204
|
"devDependencies": {
|
|
212
205
|
"brfs": "2.0.1",
|
|
213
206
|
"chai": "4.2.0",
|
|
207
|
+
"jose": "^5.10.0",
|
|
214
208
|
"mocha": "^8.4.0",
|
|
215
209
|
"nyc": "^14.1.1",
|
|
216
210
|
"sinon": "7.2.3",
|