smartledger-bsv 3.3.5 → 3.4.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/README.md +147 -20
- package/anchor-entry.js +1 -0
- package/bin/cli.js +349 -0
- package/bsv-smartcontract.min.js +9 -9
- package/bsv.min.js +8 -8
- package/build/webpack.anchor.config.js +21 -0
- package/build/webpack.didweb.config.js +21 -0
- package/build/webpack.statuslist.config.js +22 -0
- package/build/webpack.vcjwt.config.js +21 -0
- package/demos/browser-test.html +1 -1
- package/didweb-entry.js +1 -0
- package/docs/technical/roadmap.md +3 -3
- package/index.js +28 -0
- package/lib/anchor/index.js +102 -0
- package/lib/didweb/index.js +177 -0
- package/lib/statuslist/index.js +164 -0
- package/lib/vcjwt/index.js +189 -0
- package/package.json +11 -3
- package/statuslist-entry.js +1 -0
- package/vcjwt-entry.js +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
var path = require('path')
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
target: 'web',
|
|
5
|
+
mode: 'production',
|
|
6
|
+
entry: './anchor-entry.js',
|
|
7
|
+
output: {
|
|
8
|
+
path: path.resolve(__dirname, '..'),
|
|
9
|
+
filename: 'bsv-anchor.min.js',
|
|
10
|
+
library: 'bsvAnchor',
|
|
11
|
+
libraryTarget: 'umd'
|
|
12
|
+
},
|
|
13
|
+
node: {
|
|
14
|
+
crypto: true,
|
|
15
|
+
stream: true,
|
|
16
|
+
buffer: true
|
|
17
|
+
},
|
|
18
|
+
node: {
|
|
19
|
+
Buffer: true
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
var path = require('path')
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
target: 'web',
|
|
5
|
+
mode: 'production',
|
|
6
|
+
entry: './didweb-entry.js',
|
|
7
|
+
output: {
|
|
8
|
+
path: path.resolve(__dirname, '..'),
|
|
9
|
+
filename: 'bsv-didweb.min.js',
|
|
10
|
+
library: 'bsvDidWeb',
|
|
11
|
+
libraryTarget: 'umd'
|
|
12
|
+
},
|
|
13
|
+
node: {
|
|
14
|
+
crypto: true,
|
|
15
|
+
stream: true,
|
|
16
|
+
buffer: true
|
|
17
|
+
},
|
|
18
|
+
node: {
|
|
19
|
+
Buffer: true
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
var path = require('path')
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
target: 'web',
|
|
5
|
+
mode: 'production',
|
|
6
|
+
entry: './statuslist-entry.js',
|
|
7
|
+
output: {
|
|
8
|
+
path: path.resolve(__dirname, '..'),
|
|
9
|
+
filename: 'bsv-statuslist.min.js',
|
|
10
|
+
library: 'bsvStatusList',
|
|
11
|
+
libraryTarget: 'umd'
|
|
12
|
+
},
|
|
13
|
+
node: {
|
|
14
|
+
crypto: true,
|
|
15
|
+
stream: true,
|
|
16
|
+
buffer: true,
|
|
17
|
+
zlib: true
|
|
18
|
+
},
|
|
19
|
+
node: {
|
|
20
|
+
Buffer: true
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
var path = require('path')
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
target: 'web',
|
|
5
|
+
mode: 'production',
|
|
6
|
+
entry: './vcjwt-entry.js',
|
|
7
|
+
output: {
|
|
8
|
+
path: path.resolve(__dirname, '..'),
|
|
9
|
+
filename: 'bsv-vcjwt.min.js',
|
|
10
|
+
library: 'bsvVcJwt',
|
|
11
|
+
libraryTarget: 'umd'
|
|
12
|
+
},
|
|
13
|
+
node: {
|
|
14
|
+
crypto: true,
|
|
15
|
+
stream: true,
|
|
16
|
+
buffer: true
|
|
17
|
+
},
|
|
18
|
+
node: {
|
|
19
|
+
Buffer: true
|
|
20
|
+
}
|
|
21
|
+
}
|
package/demos/browser-test.html
CHANGED
package/didweb-entry.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = require('./lib/didweb')
|
|
@@ -606,11 +606,11 @@ Provides hardened Bitcoin SV primitives + SmartLedger security modules.
|
|
|
606
606
|
|
|
607
607
|
- **GitHub Repository** - https://github.com/codenlighten/smartledger-bsv🔒 *“Verified cryptographic fabric + transaction interpreter.”*
|
|
608
608
|
|
|
609
|
-
- **Developer Documentation** - https://docs.smartledger.
|
|
609
|
+
- **Developer Documentation** - https://docs.smartledger.technology
|
|
610
610
|
|
|
611
611
|
- **Community Discord** - https://discord.gg/smartledger---
|
|
612
612
|
|
|
613
|
-
- **Standards Working Group** - https://standards.smartledger.
|
|
613
|
+
- **Standards Working Group** - https://standards.smartledger.technology
|
|
614
614
|
|
|
615
615
|
## 2️⃣ Identity Layer: GDAF (Global Digital Attestation Framework)
|
|
616
616
|
|
|
@@ -903,7 +903,7 @@ SmartLedger-BSV/
|
|
|
903
903
|
|
|
904
904
|
### 🔹 **Minified CDN Build**
|
|
905
905
|
|
|
906
|
-
`https://cdn.smartledger.
|
|
906
|
+
`https://cdn.smartledger.technology/smartledger-bsv.min.js`
|
|
907
907
|
Provides browser-side access to the same hardened primitives used in Node:
|
|
908
908
|
|
|
909
909
|
```js
|
package/index.js
CHANGED
|
@@ -136,6 +136,34 @@ if (typeof window === 'undefined' && typeof require === 'function') {
|
|
|
136
136
|
// Global Digital Attestation Framework (GDAF)
|
|
137
137
|
bsv.GDAF = require('./lib/gdaf')
|
|
138
138
|
|
|
139
|
+
// DID:web Module (W3C standards-based DIDs)
|
|
140
|
+
try {
|
|
141
|
+
bsv.DIDWeb = require('./lib/didweb')
|
|
142
|
+
} catch (e) {
|
|
143
|
+
// DIDWeb module not available - use standalone bsv-didweb.min.js
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// VC-JWT Module (W3C Verifiable Credentials)
|
|
147
|
+
try {
|
|
148
|
+
bsv.VcJwt = require('./lib/vcjwt')
|
|
149
|
+
} catch (e) {
|
|
150
|
+
// VcJwt module not available - use standalone bsv-vcjwt.min.js
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// StatusList2021 Module (Credential revocation)
|
|
154
|
+
try {
|
|
155
|
+
bsv.StatusList = require('./lib/statuslist')
|
|
156
|
+
} catch (e) {
|
|
157
|
+
// StatusList module not available - use standalone bsv-statuslist.min.js
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Anchor Module (BSV hash anchoring)
|
|
161
|
+
try {
|
|
162
|
+
bsv.Anchor = require('./lib/anchor')
|
|
163
|
+
} catch (e) {
|
|
164
|
+
// Anchor module not available - use standalone bsv-anchor.min.js
|
|
165
|
+
}
|
|
166
|
+
|
|
139
167
|
// GDAF Direct Access Methods (for easier developer experience)
|
|
140
168
|
bsv.createDID = function(publicKey) {
|
|
141
169
|
var gdaf = new bsv.GDAF()
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* BSV Anchor Module
|
|
5
|
+
* Hash anchoring helpers for on-chain evidence (no PII)
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
var crypto = require('crypto')
|
|
9
|
+
|
|
10
|
+
// SHA-256 hex hash
|
|
11
|
+
function sha256Hex(data) {
|
|
12
|
+
var buffer
|
|
13
|
+
if (typeof data === 'string') {
|
|
14
|
+
buffer = Buffer.from(data, 'utf8')
|
|
15
|
+
} else if (Buffer.isBuffer(data)) {
|
|
16
|
+
buffer = data
|
|
17
|
+
} else if (data instanceof Uint8Array) {
|
|
18
|
+
buffer = Buffer.from(data)
|
|
19
|
+
} else {
|
|
20
|
+
throw new Error('Data must be string, Buffer, or Uint8Array')
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return crypto.createHash('sha256').update(buffer).digest('hex')
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Build anchor payload for OP_RETURN
|
|
27
|
+
function buildAnchorPayload(params) {
|
|
28
|
+
if (!params.kind || !params.hash || !params.issuerDid) {
|
|
29
|
+
throw new Error('kind, hash, and issuerDid are required')
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
var validKinds = ['VC_ANCHOR_SHA256', 'STATUSLIST_SHA256', 'PRESENTATION_SHA256']
|
|
33
|
+
if (validKinds.indexOf(params.kind) === -1) {
|
|
34
|
+
throw new Error('Invalid kind. Must be one of: ' + validKinds.join(', '))
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Validate hash format (64 hex characters)
|
|
38
|
+
if (!/^[a-fA-F0-9]{64}$/.test(params.hash)) {
|
|
39
|
+
throw new Error('Invalid hash format. Must be 64 hex characters')
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
var payload = {
|
|
43
|
+
protocol: 'SmartLedger',
|
|
44
|
+
version: '1.0',
|
|
45
|
+
type: params.kind,
|
|
46
|
+
hash: params.hash,
|
|
47
|
+
issuer: params.issuerDid,
|
|
48
|
+
timestamp: params.issuedAt || new Date().toISOString()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
json: JSON.stringify(payload)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Verify anchor hash against original data
|
|
57
|
+
function verifyAnchorHash(originalData, anchorHash) {
|
|
58
|
+
var computed = sha256Hex(originalData)
|
|
59
|
+
return computed === anchorHash
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Extract anchor info from OP_RETURN data
|
|
63
|
+
function parseAnchorPayload(opReturnData) {
|
|
64
|
+
try {
|
|
65
|
+
var parsed = JSON.parse(opReturnData)
|
|
66
|
+
|
|
67
|
+
if (parsed.protocol !== 'SmartLedger') {
|
|
68
|
+
return { valid: false, error: 'Invalid protocol' }
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
var validTypes = ['VC_ANCHOR_SHA256', 'STATUSLIST_SHA256', 'PRESENTATION_SHA256']
|
|
72
|
+
if (validTypes.indexOf(parsed.type) === -1) {
|
|
73
|
+
return { valid: false, error: 'Invalid anchor type' }
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (!/^[a-fA-F0-9]{64}$/.test(parsed.hash)) {
|
|
77
|
+
return { valid: false, error: 'Invalid hash format' }
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
valid: true,
|
|
82
|
+
protocol: parsed.protocol,
|
|
83
|
+
version: parsed.version,
|
|
84
|
+
type: parsed.type,
|
|
85
|
+
hash: parsed.hash,
|
|
86
|
+
issuer: parsed.issuer,
|
|
87
|
+
timestamp: parsed.timestamp
|
|
88
|
+
}
|
|
89
|
+
} catch (error) {
|
|
90
|
+
return {
|
|
91
|
+
valid: false,
|
|
92
|
+
error: 'Failed to parse anchor payload: ' + error.message
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
module.exports = {
|
|
98
|
+
sha256Hex: sha256Hex,
|
|
99
|
+
buildAnchorPayload: buildAnchorPayload,
|
|
100
|
+
verifyAnchorHash: verifyAnchorHash,
|
|
101
|
+
parseAnchorPayload: parseAnchorPayload
|
|
102
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* DID:web Module
|
|
5
|
+
* Legally-recognizable DID (did:web) generation and management
|
|
6
|
+
* Supports ES256 (P-256) and ES256K (secp256k1) keys
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
var crypto = require('crypto')
|
|
10
|
+
|
|
11
|
+
// Generate issuer keys (ES256 or ES256K)
|
|
12
|
+
async function generateIssuerKeys(opts) {
|
|
13
|
+
opts = opts || {}
|
|
14
|
+
var alg = opts.alg || 'ES256'
|
|
15
|
+
var kid = opts.kid || 'key-' + Date.now()
|
|
16
|
+
|
|
17
|
+
if (alg !== 'ES256' && alg !== 'ES256K') {
|
|
18
|
+
throw new Error('Invalid algorithm. Must be ES256 or ES256K')
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
var keyPair
|
|
22
|
+
if (alg === 'ES256') {
|
|
23
|
+
// P-256 (NIST curve)
|
|
24
|
+
keyPair = crypto.generateKeyPairSync('ec', {
|
|
25
|
+
namedCurve: 'P-256',
|
|
26
|
+
publicKeyEncoding: { type: 'spki', format: 'pem' },
|
|
27
|
+
privateKeyEncoding: { type: 'pkcs8', format: 'pem' }
|
|
28
|
+
})
|
|
29
|
+
} else {
|
|
30
|
+
// secp256k1
|
|
31
|
+
keyPair = crypto.generateKeyPairSync('ec', {
|
|
32
|
+
namedCurve: 'secp256k1',
|
|
33
|
+
publicKeyEncoding: { type: 'spki', format: 'pem' },
|
|
34
|
+
privateKeyEncoding: { type: 'pkcs8', format: 'pem' }
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Convert to JWK format
|
|
39
|
+
var publicJwk = crypto.createPublicKey(keyPair.publicKey).export({ format: 'jwk' })
|
|
40
|
+
var privateJwk = crypto.createPrivateKey(keyPair.privateKey).export({ format: 'jwk' })
|
|
41
|
+
|
|
42
|
+
// Add required JWK fields
|
|
43
|
+
publicJwk.kid = kid
|
|
44
|
+
publicJwk.alg = alg
|
|
45
|
+
publicJwk.use = 'sig'
|
|
46
|
+
publicJwk.kty = 'EC'
|
|
47
|
+
|
|
48
|
+
privateJwk.kid = kid
|
|
49
|
+
privateJwk.alg = alg
|
|
50
|
+
privateJwk.use = 'sig'
|
|
51
|
+
privateJwk.kty = 'EC'
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
privateJwk: privateJwk,
|
|
55
|
+
publicJwk: publicJwk,
|
|
56
|
+
kid: kid,
|
|
57
|
+
alg: alg
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Build did:web documents (did.json and jwks.json)
|
|
62
|
+
function buildDidWebDocuments(params) {
|
|
63
|
+
if (!params.domain) {
|
|
64
|
+
throw new Error('domain is required')
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
var domain = params.domain
|
|
68
|
+
var did = 'did:web:' + domain.replace(/:/g, '%3A')
|
|
69
|
+
|
|
70
|
+
var verificationMethods = []
|
|
71
|
+
var publicKeys = []
|
|
72
|
+
|
|
73
|
+
// Add P-256 key if provided
|
|
74
|
+
if (params.p256) {
|
|
75
|
+
var p256Method = {
|
|
76
|
+
id: did + '#' + params.p256.kid,
|
|
77
|
+
type: 'JsonWebKey2020',
|
|
78
|
+
controller: did,
|
|
79
|
+
publicKeyJwk: params.p256.jwk
|
|
80
|
+
}
|
|
81
|
+
verificationMethods.push(p256Method)
|
|
82
|
+
publicKeys.push(params.p256.jwk)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Add secp256k1 key if provided
|
|
86
|
+
if (params.k1) {
|
|
87
|
+
var k1Method = {
|
|
88
|
+
id: did + '#' + params.k1.kid,
|
|
89
|
+
type: 'JsonWebKey2020',
|
|
90
|
+
controller: did,
|
|
91
|
+
publicKeyJwk: params.k1.jwk
|
|
92
|
+
}
|
|
93
|
+
verificationMethods.push(k1Method)
|
|
94
|
+
publicKeys.push(params.k1.jwk)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (verificationMethods.length === 0) {
|
|
98
|
+
throw new Error('At least one key (p256 or k1) must be provided')
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Build DID Document
|
|
102
|
+
var didDocument = {
|
|
103
|
+
'@context': [
|
|
104
|
+
'https://www.w3.org/ns/did/v1',
|
|
105
|
+
'https://w3id.org/security/suites/jws-2020/v1'
|
|
106
|
+
],
|
|
107
|
+
id: did,
|
|
108
|
+
verificationMethod: verificationMethods,
|
|
109
|
+
authentication: verificationMethods.map(function(vm) { return vm.id }),
|
|
110
|
+
assertionMethod: verificationMethods.map(function(vm) { return vm.id })
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (params.controllerName) {
|
|
114
|
+
didDocument.controller = params.controllerName
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Build JWKS
|
|
118
|
+
var jwks = {
|
|
119
|
+
keys: publicKeys
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
did: did,
|
|
124
|
+
didDocument: didDocument,
|
|
125
|
+
jwks: jwks
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Rotate issuer key
|
|
130
|
+
function rotateIssuerKey(params) {
|
|
131
|
+
if (!params.domain || !params.newKey) {
|
|
132
|
+
throw new Error('domain and newKey are required')
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
var domain = params.domain
|
|
136
|
+
var did = 'did:web:' + domain.replace(/:/g, '%3A')
|
|
137
|
+
var keepOldForDays = params.keepOldForDays || 30
|
|
138
|
+
|
|
139
|
+
// Create verification method for new key
|
|
140
|
+
var newMethod = {
|
|
141
|
+
id: did + '#' + params.newKey.kid,
|
|
142
|
+
type: 'JsonWebKey2020',
|
|
143
|
+
controller: did,
|
|
144
|
+
publicKeyJwk: params.newKey.jwk
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Build updated DID Document with new key as primary
|
|
148
|
+
var didDocument = {
|
|
149
|
+
'@context': [
|
|
150
|
+
'https://www.w3.org/ns/did/v1',
|
|
151
|
+
'https://w3id.org/security/suites/jws-2020/v1'
|
|
152
|
+
],
|
|
153
|
+
id: did,
|
|
154
|
+
verificationMethod: [newMethod],
|
|
155
|
+
authentication: [newMethod.id],
|
|
156
|
+
assertionMethod: [newMethod.id],
|
|
157
|
+
rotationInfo: {
|
|
158
|
+
rotatedAt: new Date().toISOString(),
|
|
159
|
+
gracePeriodDays: keepOldForDays
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
var jwks = {
|
|
164
|
+
keys: [params.newKey.jwk]
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
didDocument: didDocument,
|
|
169
|
+
jwks: jwks
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
module.exports = {
|
|
174
|
+
generateIssuerKeys: generateIssuerKeys,
|
|
175
|
+
buildDidWebDocuments: buildDidWebDocuments,
|
|
176
|
+
rotateIssuerKey: rotateIssuerKey
|
|
177
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* StatusList2021 Module
|
|
5
|
+
* W3C StatusList2021 for credential revocation and suspension
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
var vcjwt = require('../vcjwt')
|
|
9
|
+
var crypto = require('crypto')
|
|
10
|
+
|
|
11
|
+
// Create a new status list
|
|
12
|
+
async function createStatusList(params) {
|
|
13
|
+
if (!params.issuerDid || !params.privateJwk) {
|
|
14
|
+
throw new Error('issuerDid and privateJwk are required')
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
var listId = params.listId || params.issuerDid + '/status/' + Date.now()
|
|
18
|
+
|
|
19
|
+
// Create a bitstring for 100,000 credentials (default size)
|
|
20
|
+
var listSize = params.listSize || 100000
|
|
21
|
+
var byteSize = Math.ceil(listSize / 8)
|
|
22
|
+
var bitstringBuffer = Buffer.alloc(byteSize, 0)
|
|
23
|
+
|
|
24
|
+
// Compress with gzip
|
|
25
|
+
var zlib = require('zlib')
|
|
26
|
+
var compressed = zlib.gzipSync(bitstringBuffer)
|
|
27
|
+
var encodedCompressed = compressed.toString('base64')
|
|
28
|
+
|
|
29
|
+
// Create StatusList2021 credential
|
|
30
|
+
var statusListCredential = {
|
|
31
|
+
'@context': [
|
|
32
|
+
'https://www.w3.org/2018/credentials/v1',
|
|
33
|
+
'https://w3id.org/vc/status-list/2021/v1'
|
|
34
|
+
],
|
|
35
|
+
type: ['VerifiableCredential', 'StatusList2021Credential'],
|
|
36
|
+
issuer: params.issuerDid,
|
|
37
|
+
issuanceDate: new Date().toISOString(),
|
|
38
|
+
credentialSubject: {
|
|
39
|
+
id: listId,
|
|
40
|
+
type: 'StatusList2021',
|
|
41
|
+
statusPurpose: 'revocation',
|
|
42
|
+
encodedList: encodedCompressed
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Issue as JWT
|
|
47
|
+
var result = await vcjwt.issueVcJwt({
|
|
48
|
+
issuerDid: params.issuerDid,
|
|
49
|
+
subjectId: listId,
|
|
50
|
+
types: ['VerifiableCredential', 'StatusList2021Credential'],
|
|
51
|
+
credentialSubject: statusListCredential.credentialSubject,
|
|
52
|
+
privateJwk: params.privateJwk,
|
|
53
|
+
alg: params.privateJwk.alg || 'ES256'
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
listVcJwt: result.jwt,
|
|
58
|
+
listId: listId
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Update status list (revoke/suspend/activate)
|
|
63
|
+
async function updateStatusList(params) {
|
|
64
|
+
if (!params.listVcJwt || params.index === undefined || !params.status || !params.privateJwk) {
|
|
65
|
+
throw new Error('listVcJwt, index, status, and privateJwk are required')
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Decode the existing status list JWT
|
|
69
|
+
var parts = params.listVcJwt.split('.')
|
|
70
|
+
if (parts.length !== 3) {
|
|
71
|
+
throw new Error('Invalid JWT format')
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
var payload = JSON.parse(vcjwt.base64UrlDecode(parts[1]).toString())
|
|
75
|
+
var encodedList = payload.vc.credentialSubject.encodedList
|
|
76
|
+
|
|
77
|
+
// Decompress
|
|
78
|
+
var zlib = require('zlib')
|
|
79
|
+
var compressed = Buffer.from(encodedList, 'base64')
|
|
80
|
+
var bitstring = zlib.gunzipSync(compressed)
|
|
81
|
+
|
|
82
|
+
// Update the bit at the given index
|
|
83
|
+
var byteIndex = Math.floor(params.index / 8)
|
|
84
|
+
var bitIndex = params.index % 8
|
|
85
|
+
|
|
86
|
+
if (byteIndex >= bitstring.length) {
|
|
87
|
+
throw new Error('Index out of range')
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// StatusList2021 uses 2 bits per credential for 4 states
|
|
91
|
+
// For simplicity, we'll use single bit: 0=valid, 1=revoked/suspended
|
|
92
|
+
var statusBit = (params.status === 'revoked' || params.status === 'suspended') ? 1 : 0
|
|
93
|
+
|
|
94
|
+
if (statusBit === 1) {
|
|
95
|
+
bitstring[byteIndex] |= (1 << bitIndex)
|
|
96
|
+
} else {
|
|
97
|
+
bitstring[byteIndex] &= ~(1 << bitIndex)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Recompress
|
|
101
|
+
var recompressed = zlib.gzipSync(bitstring)
|
|
102
|
+
var newEncodedList = recompressed.toString('base64')
|
|
103
|
+
|
|
104
|
+
// Create updated credential
|
|
105
|
+
var updatedCredentialSubject = {
|
|
106
|
+
id: payload.vc.credentialSubject.id,
|
|
107
|
+
type: 'StatusList2021',
|
|
108
|
+
statusPurpose: 'revocation',
|
|
109
|
+
encodedList: newEncodedList
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Re-issue as JWT
|
|
113
|
+
var result = await vcjwt.issueVcJwt({
|
|
114
|
+
issuerDid: payload.iss,
|
|
115
|
+
subjectId: payload.vc.credentialSubject.id,
|
|
116
|
+
types: ['VerifiableCredential', 'StatusList2021Credential'],
|
|
117
|
+
credentialSubject: updatedCredentialSubject,
|
|
118
|
+
privateJwk: params.privateJwk,
|
|
119
|
+
alg: params.privateJwk.alg || 'ES256'
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
listVcJwt: result.jwt
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Get credential status entry
|
|
128
|
+
function getCredentialStatusEntry(params) {
|
|
129
|
+
if (!params.listVcJwt || params.index === undefined) {
|
|
130
|
+
throw new Error('listVcJwt and index are required')
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Decode the status list JWT
|
|
134
|
+
var parts = params.listVcJwt.split('.')
|
|
135
|
+
if (parts.length !== 3) {
|
|
136
|
+
throw new Error('Invalid JWT format')
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
var payload = JSON.parse(vcjwt.base64UrlDecode(parts[1]).toString())
|
|
140
|
+
var encodedList = payload.vc.credentialSubject.encodedList
|
|
141
|
+
|
|
142
|
+
// Decompress
|
|
143
|
+
var zlib = require('zlib')
|
|
144
|
+
var compressed = Buffer.from(encodedList, 'base64')
|
|
145
|
+
var bitstring = zlib.gunzipSync(compressed)
|
|
146
|
+
|
|
147
|
+
// Check the bit at the given index
|
|
148
|
+
var byteIndex = Math.floor(params.index / 8)
|
|
149
|
+
var bitIndex = params.index % 8
|
|
150
|
+
|
|
151
|
+
if (byteIndex >= bitstring.length) {
|
|
152
|
+
throw new Error('Index out of range')
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
var bit = (bitstring[byteIndex] >> bitIndex) & 1
|
|
156
|
+
|
|
157
|
+
return bit === 1 ? 'revoked' : 'valid'
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
module.exports = {
|
|
161
|
+
createStatusList: createStatusList,
|
|
162
|
+
updateStatusList: updateStatusList,
|
|
163
|
+
getCredentialStatusEntry: getCredentialStatusEntry
|
|
164
|
+
}
|