smartledger-bsv 3.2.1 → 3.3.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 +147 -0
- package/README.md +289 -55
- package/architecture_demo.js +247 -0
- package/bsv-covenant.min.js +10 -0
- package/bsv-gdaf.min.js +37 -0
- package/bsv-ltp.min.js +37 -0
- package/bsv-script-helper.min.js +10 -0
- package/bsv-security.min.js +31 -0
- package/bsv-shamir.min.js +12 -0
- package/bsv-smartcontract.min.js +37 -0
- package/bsv.bundle.js +9 -9
- package/bsv.min.js +3 -3
- package/build/bsv-covenant.min.js +10 -0
- package/build/bsv-script-helper.min.js +10 -0
- package/build/bsv-security.min.js +31 -0
- package/build/bsv-smartcontract.min.js +39 -0
- package/build/bsv.bundle.js +39 -0
- package/build/bsv.min.js +39 -0
- package/build/webpack.bundle.config.js +22 -0
- package/build/webpack.config.js +18 -0
- package/build/webpack.covenant.config.js +27 -0
- package/build/webpack.gdaf.config.js +54 -0
- package/build/webpack.ltp.config.js +17 -0
- package/build/webpack.script-helper.config.js +27 -0
- package/build/webpack.security.config.js +23 -0
- package/build/webpack.smartcontract.config.js +25 -0
- package/build/webpack.subproject.config.js +6 -0
- package/bundle-entry.js +341 -0
- package/complete_ltp_demo.js +511 -0
- package/covenant-entry.js +44 -0
- package/docs/pushtx-key-insights.md +106 -0
- package/gdaf-entry.js +54 -0
- package/index.js +272 -5
- package/lib/crypto/shamir.js +360 -0
- package/lib/gdaf/attestation-signer.js +461 -0
- package/lib/gdaf/attestation-verifier.js +600 -0
- package/lib/gdaf/did-resolver.js +382 -0
- package/lib/gdaf/index.js +471 -0
- package/lib/gdaf/schema-validator.js +682 -0
- package/lib/gdaf/smartledger-anchor.js +486 -0
- package/lib/gdaf/zk-prover.js +507 -0
- package/lib/ltp/anchor.js +438 -0
- package/lib/ltp/claim.js +1026 -0
- package/lib/ltp/index.js +470 -0
- package/lib/ltp/obligation.js +945 -0
- package/lib/ltp/proof.js +828 -0
- package/lib/ltp/registry.js +702 -0
- package/lib/ltp/right.js +765 -0
- package/lib/smart_contract/API_REFERENCE.md +1 -1
- package/lib/smart_contract/EXAMPLES.md +2 -2
- package/lib/smart_contract/QUICK_START.md +2 -2
- package/lib/smart_contract/README.md +1 -1
- package/lib/smart_contract/index.js +4 -0
- package/ltp-entry.js +92 -0
- package/package.json +91 -20
- package/script-helper-entry.js +49 -0
- package/security-entry.js +70 -0
- package/shamir-entry.js +173 -0
- package/shamir_demo.js +121 -0
- package/simple_demo.js +204 -0
- package/smartcontract-entry.js +133 -0
- package/test_shamir.js +221 -0
- package/test_standalone_shamir.html +83 -0
- package/tests/bundle-completeness-test.html +131 -0
- package/tests/bundle-demo.html +476 -0
- package/tests/smartcontract-test.html +239 -0
- package/tests/standalone-modules-test.html +260 -0
- package/tests/test.html +612 -0
- package/tests/unpkg-demo.html +194 -0
- package/docs/nchain.md +0 -958
|
@@ -0,0 +1,600 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
var bsv = require('../../')
|
|
4
|
+
var DIDResolver = require('./did-resolver')
|
|
5
|
+
var AttestationSigner = require('./attestation-signer')
|
|
6
|
+
var PublicKey = bsv.PublicKey
|
|
7
|
+
var Hash = bsv.crypto.Hash
|
|
8
|
+
var ECDSA = bsv.crypto.ECDSA
|
|
9
|
+
var Signature = bsv.crypto.Signature
|
|
10
|
+
var $ = bsv.util.preconditions
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* AttestationVerifier
|
|
14
|
+
*
|
|
15
|
+
* Verifies W3C Verifiable Credentials and Presentations created by
|
|
16
|
+
* AttestationSigner. Provides comprehensive validation including:
|
|
17
|
+
* - Signature verification
|
|
18
|
+
* - DID resolution and validation
|
|
19
|
+
* - Schema compliance checking
|
|
20
|
+
* - Temporal validity (expiration, issuance dates)
|
|
21
|
+
* - Trust chain validation
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* AttestationVerifier constructor
|
|
26
|
+
* @param {Object} options - Configuration options
|
|
27
|
+
*/
|
|
28
|
+
function AttestationVerifier(options) {
|
|
29
|
+
if (!(this instanceof AttestationVerifier)) {
|
|
30
|
+
return new AttestationVerifier(options)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
this.options = options || {}
|
|
34
|
+
this.trustedIssuers = this.options.trustedIssuers || []
|
|
35
|
+
|
|
36
|
+
return this
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Verify a Verifiable Credential
|
|
41
|
+
* @param {Object} credential - Credential to verify
|
|
42
|
+
* @param {Object} options - Verification options
|
|
43
|
+
* @returns {Promise<Object>} Verification result
|
|
44
|
+
*/
|
|
45
|
+
AttestationVerifier.verifyCredential = async function(credential, options) {
|
|
46
|
+
options = options || {}
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
$.checkArgument(credential && typeof credential === 'object', 'Invalid credential')
|
|
50
|
+
|
|
51
|
+
var result = {
|
|
52
|
+
valid: false,
|
|
53
|
+
errors: [],
|
|
54
|
+
warnings: [],
|
|
55
|
+
checks: {
|
|
56
|
+
structure: false,
|
|
57
|
+
signature: false,
|
|
58
|
+
issuer: false,
|
|
59
|
+
temporal: false,
|
|
60
|
+
schema: false
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// 1. Structure validation
|
|
65
|
+
var structureCheck = AttestationVerifier._validateStructure(credential)
|
|
66
|
+
result.checks.structure = structureCheck.valid
|
|
67
|
+
if (!structureCheck.valid) {
|
|
68
|
+
result.errors = result.errors.concat(structureCheck.errors)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 2. Signature verification
|
|
72
|
+
var signatureCheck = await AttestationVerifier._verifySignature(credential)
|
|
73
|
+
result.checks.signature = signatureCheck.valid
|
|
74
|
+
if (!signatureCheck.valid) {
|
|
75
|
+
result.errors = result.errors.concat(signatureCheck.errors)
|
|
76
|
+
} else {
|
|
77
|
+
result.issuerDID = signatureCheck.issuerDID
|
|
78
|
+
result.verificationMethod = signatureCheck.verificationMethod
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// 3. Issuer validation
|
|
82
|
+
var issuerCheck = await AttestationVerifier._validateIssuer(credential, options)
|
|
83
|
+
result.checks.issuer = issuerCheck.valid
|
|
84
|
+
if (!issuerCheck.valid) {
|
|
85
|
+
result.errors = result.errors.concat(issuerCheck.errors)
|
|
86
|
+
}
|
|
87
|
+
if (issuerCheck.warnings) {
|
|
88
|
+
result.warnings = result.warnings.concat(issuerCheck.warnings)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// 4. Temporal validation
|
|
92
|
+
var temporalCheck = AttestationVerifier._validateTemporal(credential)
|
|
93
|
+
result.checks.temporal = temporalCheck.valid
|
|
94
|
+
if (!temporalCheck.valid) {
|
|
95
|
+
result.errors = result.errors.concat(temporalCheck.errors)
|
|
96
|
+
}
|
|
97
|
+
if (temporalCheck.warnings) {
|
|
98
|
+
result.warnings = result.warnings.concat(temporalCheck.warnings)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// 5. Schema validation (if schema provided)
|
|
102
|
+
if (options.schema) {
|
|
103
|
+
var schemaCheck = AttestationVerifier._validateSchema(credential, options.schema)
|
|
104
|
+
result.checks.schema = schemaCheck.valid
|
|
105
|
+
if (!schemaCheck.valid) {
|
|
106
|
+
result.errors = result.errors.concat(schemaCheck.errors)
|
|
107
|
+
}
|
|
108
|
+
} else {
|
|
109
|
+
result.checks.schema = true // Skip if no schema provided
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Overall validity
|
|
113
|
+
result.valid = Object.values(result.checks).every(check => check === true)
|
|
114
|
+
|
|
115
|
+
return result
|
|
116
|
+
|
|
117
|
+
} catch (error) {
|
|
118
|
+
return {
|
|
119
|
+
valid: false,
|
|
120
|
+
errors: ['Verification failed: ' + error.message],
|
|
121
|
+
warnings: [],
|
|
122
|
+
checks: {
|
|
123
|
+
structure: false,
|
|
124
|
+
signature: false,
|
|
125
|
+
issuer: false,
|
|
126
|
+
temporal: false,
|
|
127
|
+
schema: false
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Verify a Verifiable Presentation
|
|
135
|
+
* @param {Object} presentation - Presentation to verify
|
|
136
|
+
* @param {Object} options - Verification options
|
|
137
|
+
* @returns {Promise<Object>} Verification result
|
|
138
|
+
*/
|
|
139
|
+
AttestationVerifier.verifyPresentation = async function(presentation, options) {
|
|
140
|
+
options = options || {}
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
$.checkArgument(presentation && typeof presentation === 'object', 'Invalid presentation')
|
|
144
|
+
|
|
145
|
+
var result = {
|
|
146
|
+
valid: false,
|
|
147
|
+
errors: [],
|
|
148
|
+
warnings: [],
|
|
149
|
+
credentialResults: [],
|
|
150
|
+
presentationValid: false
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Verify presentation structure
|
|
154
|
+
if (!presentation.type || !presentation.type.includes('VerifiablePresentation')) {
|
|
155
|
+
result.errors.push('Invalid presentation type')
|
|
156
|
+
return result
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (!presentation.holder) {
|
|
160
|
+
result.errors.push('Missing presentation holder')
|
|
161
|
+
return result
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Verify presentation signature
|
|
165
|
+
var presentationCheck = await AttestationVerifier._verifyPresentationSignature(presentation)
|
|
166
|
+
result.presentationValid = presentationCheck.valid
|
|
167
|
+
if (!presentationCheck.valid) {
|
|
168
|
+
result.errors = result.errors.concat(presentationCheck.errors)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Verify each credential in the presentation
|
|
172
|
+
if (presentation.verifiableCredential && Array.isArray(presentation.verifiableCredential)) {
|
|
173
|
+
for (var i = 0; i < presentation.verifiableCredential.length; i++) {
|
|
174
|
+
var credential = presentation.verifiableCredential[i]
|
|
175
|
+
var credentialResult = await AttestationVerifier.verifyCredential(credential, options)
|
|
176
|
+
result.credentialResults.push({
|
|
177
|
+
index: i,
|
|
178
|
+
result: credentialResult
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
if (!credentialResult.valid) {
|
|
182
|
+
result.errors.push('Credential ' + i + ' is invalid')
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Overall validity
|
|
188
|
+
var allCredentialsValid = result.credentialResults.every(cr => cr.result.valid)
|
|
189
|
+
result.valid = result.presentationValid && allCredentialsValid
|
|
190
|
+
|
|
191
|
+
return result
|
|
192
|
+
|
|
193
|
+
} catch (error) {
|
|
194
|
+
return {
|
|
195
|
+
valid: false,
|
|
196
|
+
errors: ['Presentation verification failed: ' + error.message],
|
|
197
|
+
warnings: [],
|
|
198
|
+
credentialResults: [],
|
|
199
|
+
presentationValid: false
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Validate credential structure
|
|
206
|
+
* @private
|
|
207
|
+
*/
|
|
208
|
+
AttestationVerifier._validateStructure = function(credential) {
|
|
209
|
+
var errors = []
|
|
210
|
+
|
|
211
|
+
// Required fields
|
|
212
|
+
if (!credential['@context']) {
|
|
213
|
+
errors.push('Missing @context')
|
|
214
|
+
} else if (!Array.isArray(credential['@context'])) {
|
|
215
|
+
errors.push('@context must be an array')
|
|
216
|
+
} else if (!credential['@context'].includes('https://www.w3.org/2018/credentials/v1')) {
|
|
217
|
+
errors.push('Missing required W3C credentials context')
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (!credential.type) {
|
|
221
|
+
errors.push('Missing type')
|
|
222
|
+
} else if (!Array.isArray(credential.type)) {
|
|
223
|
+
errors.push('type must be an array')
|
|
224
|
+
} else if (!credential.type.includes('VerifiableCredential')) {
|
|
225
|
+
errors.push('Must include VerifiableCredential type')
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (!credential.issuer) {
|
|
229
|
+
errors.push('Missing issuer')
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (!credential.issuanceDate) {
|
|
233
|
+
errors.push('Missing issuanceDate')
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (!credential.credentialSubject) {
|
|
237
|
+
errors.push('Missing credentialSubject')
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (!credential.proof) {
|
|
241
|
+
errors.push('Missing proof')
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return {
|
|
245
|
+
valid: errors.length === 0,
|
|
246
|
+
errors: errors
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Verify credential signature
|
|
252
|
+
* @private
|
|
253
|
+
*/
|
|
254
|
+
AttestationVerifier._verifySignature = async function(credential) {
|
|
255
|
+
try {
|
|
256
|
+
var proof = credential.proof
|
|
257
|
+
if (!proof) {
|
|
258
|
+
return { valid: false, errors: ['Missing proof'] }
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (proof.type !== 'EcdsaSecp256k1Signature2019') {
|
|
262
|
+
return { valid: false, errors: ['Unsupported proof type: ' + proof.type] }
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (!proof.verificationMethod) {
|
|
266
|
+
return { valid: false, errors: ['Missing verification method'] }
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (!proof.jws) {
|
|
270
|
+
return { valid: false, errors: ['Missing JWS signature'] }
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Extract DID from verification method
|
|
274
|
+
var verificationMethod = proof.verificationMethod
|
|
275
|
+
var did = verificationMethod.split('#')[0]
|
|
276
|
+
|
|
277
|
+
// Get public key from DID
|
|
278
|
+
var publicKey = DIDResolver.getPublicKey(did)
|
|
279
|
+
|
|
280
|
+
// Recreate credential without proof for verification
|
|
281
|
+
var credentialCopy = JSON.parse(JSON.stringify(credential))
|
|
282
|
+
delete credentialCopy.proof
|
|
283
|
+
delete credentialCopy.rootHash
|
|
284
|
+
|
|
285
|
+
// Create hash
|
|
286
|
+
var credentialHash = AttestationSigner._hashCredential(credentialCopy)
|
|
287
|
+
|
|
288
|
+
// Verify signature
|
|
289
|
+
var signature = AttestationVerifier._parseJWSSignature(proof.jws)
|
|
290
|
+
|
|
291
|
+
var ecdsa = new ECDSA()
|
|
292
|
+
ecdsa.hashbuf = credentialHash
|
|
293
|
+
ecdsa.pubkey = publicKey
|
|
294
|
+
ecdsa.sig = signature
|
|
295
|
+
|
|
296
|
+
var valid = ecdsa.verify()
|
|
297
|
+
|
|
298
|
+
if (valid) {
|
|
299
|
+
return {
|
|
300
|
+
valid: true,
|
|
301
|
+
issuerDID: did,
|
|
302
|
+
verificationMethod: verificationMethod
|
|
303
|
+
}
|
|
304
|
+
} else {
|
|
305
|
+
return { valid: false, errors: ['Signature verification failed'] }
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
} catch (error) {
|
|
309
|
+
return { valid: false, errors: ['Signature verification error: ' + error.message] }
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Parse JWS signature
|
|
315
|
+
* @private
|
|
316
|
+
*/
|
|
317
|
+
AttestationVerifier._parseJWSSignature = function(jws) {
|
|
318
|
+
var parts = jws.split('.')
|
|
319
|
+
if (parts.length !== 3) {
|
|
320
|
+
throw new Error('Invalid JWS format')
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
var signatureB64 = parts[2]
|
|
324
|
+
var signatureBuffer = Buffer.from(signatureB64, 'base64url')
|
|
325
|
+
|
|
326
|
+
return Signature.fromDER(signatureBuffer)
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Verify presentation signature
|
|
331
|
+
* @private
|
|
332
|
+
*/
|
|
333
|
+
AttestationVerifier._verifyPresentationSignature = async function(presentation) {
|
|
334
|
+
try {
|
|
335
|
+
var proof = presentation.proof
|
|
336
|
+
if (!proof) {
|
|
337
|
+
return { valid: false, errors: ['Missing presentation proof'] }
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Extract holder DID
|
|
341
|
+
var holderDID = presentation.holder
|
|
342
|
+
var publicKey = DIDResolver.getPublicKey(holderDID)
|
|
343
|
+
|
|
344
|
+
// Recreate presentation without proof
|
|
345
|
+
var presentationCopy = JSON.parse(JSON.stringify(presentation))
|
|
346
|
+
delete presentationCopy.proof
|
|
347
|
+
|
|
348
|
+
// Create hash
|
|
349
|
+
var presentationHash = AttestationSigner._hashCredential(presentationCopy)
|
|
350
|
+
|
|
351
|
+
// Verify signature
|
|
352
|
+
var signature = AttestationVerifier._parseJWSSignature(proof.jws)
|
|
353
|
+
|
|
354
|
+
var ecdsa = new ECDSA()
|
|
355
|
+
ecdsa.hashbuf = presentationHash
|
|
356
|
+
ecdsa.pubkey = publicKey
|
|
357
|
+
ecdsa.sig = signature
|
|
358
|
+
|
|
359
|
+
var valid = ecdsa.verify()
|
|
360
|
+
|
|
361
|
+
return {
|
|
362
|
+
valid: valid,
|
|
363
|
+
errors: valid ? [] : ['Presentation signature verification failed']
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
} catch (error) {
|
|
367
|
+
return { valid: false, errors: ['Presentation signature error: ' + error.message] }
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Validate issuer
|
|
373
|
+
* @private
|
|
374
|
+
*/
|
|
375
|
+
AttestationVerifier._validateIssuer = async function(credential, options) {
|
|
376
|
+
var errors = []
|
|
377
|
+
var warnings = []
|
|
378
|
+
|
|
379
|
+
try {
|
|
380
|
+
var issuer = credential.issuer
|
|
381
|
+
|
|
382
|
+
// Validate DID format
|
|
383
|
+
if (!DIDResolver.isValidDID(issuer)) {
|
|
384
|
+
errors.push('Invalid issuer DID format')
|
|
385
|
+
return { valid: false, errors: errors }
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Resolve DID document
|
|
389
|
+
var resolution = await DIDResolver.resolve(issuer)
|
|
390
|
+
if (!resolution.didDocument) {
|
|
391
|
+
errors.push('Unable to resolve issuer DID')
|
|
392
|
+
return { valid: false, errors: errors }
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Check if issuer is in trusted list
|
|
396
|
+
if (options.trustedIssuers && Array.isArray(options.trustedIssuers)) {
|
|
397
|
+
if (!options.trustedIssuers.includes(issuer)) {
|
|
398
|
+
warnings.push('Issuer not in trusted list')
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
return {
|
|
403
|
+
valid: errors.length === 0,
|
|
404
|
+
errors: errors,
|
|
405
|
+
warnings: warnings
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
} catch (error) {
|
|
409
|
+
return {
|
|
410
|
+
valid: false,
|
|
411
|
+
errors: ['Issuer validation error: ' + error.message],
|
|
412
|
+
warnings: warnings
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Validate temporal constraints
|
|
419
|
+
* @private
|
|
420
|
+
*/
|
|
421
|
+
AttestationVerifier._validateTemporal = function(credential) {
|
|
422
|
+
var errors = []
|
|
423
|
+
var warnings = []
|
|
424
|
+
var now = new Date()
|
|
425
|
+
|
|
426
|
+
// Validate issuance date
|
|
427
|
+
if (credential.issuanceDate) {
|
|
428
|
+
var issuanceDate = new Date(credential.issuanceDate)
|
|
429
|
+
if (isNaN(issuanceDate.getTime())) {
|
|
430
|
+
errors.push('Invalid issuanceDate format')
|
|
431
|
+
} else if (issuanceDate > now) {
|
|
432
|
+
errors.push('Credential issued in the future')
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Validate expiration date
|
|
437
|
+
if (credential.expirationDate) {
|
|
438
|
+
var expirationDate = new Date(credential.expirationDate)
|
|
439
|
+
if (isNaN(expirationDate.getTime())) {
|
|
440
|
+
errors.push('Invalid expirationDate format')
|
|
441
|
+
} else if (expirationDate <= now) {
|
|
442
|
+
errors.push('Credential has expired')
|
|
443
|
+
} else if (expirationDate <= new Date(now.getTime() + 24 * 60 * 60 * 1000)) {
|
|
444
|
+
warnings.push('Credential expires within 24 hours')
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
return {
|
|
449
|
+
valid: errors.length === 0,
|
|
450
|
+
errors: errors,
|
|
451
|
+
warnings: warnings
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Validate schema compliance
|
|
457
|
+
* @private
|
|
458
|
+
*/
|
|
459
|
+
AttestationVerifier._validateSchema = function(credential, schema) {
|
|
460
|
+
var errors = []
|
|
461
|
+
|
|
462
|
+
// Basic schema validation (can be extended with JSON Schema validator)
|
|
463
|
+
if (schema.requiredFields) {
|
|
464
|
+
schema.requiredFields.forEach(function(field) {
|
|
465
|
+
if (!AttestationVerifier._getNestedProperty(credential, field)) {
|
|
466
|
+
errors.push('Missing required field: ' + field)
|
|
467
|
+
}
|
|
468
|
+
})
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
if (schema.requiredTypes) {
|
|
472
|
+
var hasRequiredType = schema.requiredTypes.some(function(type) {
|
|
473
|
+
return credential.type && credential.type.includes(type)
|
|
474
|
+
})
|
|
475
|
+
|
|
476
|
+
if (!hasRequiredType) {
|
|
477
|
+
errors.push('Missing required credential type')
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
return {
|
|
482
|
+
valid: errors.length === 0,
|
|
483
|
+
errors: errors
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Get nested property from object
|
|
489
|
+
* @private
|
|
490
|
+
*/
|
|
491
|
+
AttestationVerifier._getNestedProperty = function(obj, path) {
|
|
492
|
+
return path.split('.').reduce(function(current, prop) {
|
|
493
|
+
return current && current[prop]
|
|
494
|
+
}, obj)
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Verify credential hash integrity
|
|
499
|
+
* @param {Object} credential - Credential to verify
|
|
500
|
+
* @returns {Boolean} True if hash matches
|
|
501
|
+
*/
|
|
502
|
+
AttestationVerifier.verifyCredentialHash = function(credential) {
|
|
503
|
+
try {
|
|
504
|
+
if (!credential.rootHash) {
|
|
505
|
+
return false
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
var credentialCopy = JSON.parse(JSON.stringify(credential))
|
|
509
|
+
delete credentialCopy.proof
|
|
510
|
+
delete credentialCopy.rootHash
|
|
511
|
+
|
|
512
|
+
var computedHash = AttestationSigner._hashCredential(credentialCopy)
|
|
513
|
+
var storedHash = Buffer.from(credential.rootHash, 'hex')
|
|
514
|
+
|
|
515
|
+
return Buffer.compare(computedHash, storedHash) === 0
|
|
516
|
+
} catch (error) {
|
|
517
|
+
return false
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* Extract claims from credential
|
|
523
|
+
* @param {Object} credential - Credential
|
|
524
|
+
* @returns {Object} Extracted claims
|
|
525
|
+
*/
|
|
526
|
+
AttestationVerifier.extractClaims = function(credential) {
|
|
527
|
+
try {
|
|
528
|
+
var claims = {
|
|
529
|
+
issuer: credential.issuer,
|
|
530
|
+
subject: credential.credentialSubject.id || 'unknown',
|
|
531
|
+
types: credential.type || [],
|
|
532
|
+
issuanceDate: credential.issuanceDate,
|
|
533
|
+
expirationDate: credential.expirationDate,
|
|
534
|
+
claims: {}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// Extract subject claims
|
|
538
|
+
Object.keys(credential.credentialSubject).forEach(function(key) {
|
|
539
|
+
if (key !== 'id') {
|
|
540
|
+
claims.claims[key] = credential.credentialSubject[key]
|
|
541
|
+
}
|
|
542
|
+
})
|
|
543
|
+
|
|
544
|
+
return claims
|
|
545
|
+
} catch (error) {
|
|
546
|
+
return null
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* Create verification report
|
|
552
|
+
* @param {Object} verificationResult - Result from verifyCredential
|
|
553
|
+
* @returns {String} Human-readable report
|
|
554
|
+
*/
|
|
555
|
+
AttestationVerifier.createReport = function(verificationResult) {
|
|
556
|
+
var report = []
|
|
557
|
+
|
|
558
|
+
report.push('=== Credential Verification Report ===')
|
|
559
|
+
report.push('Overall Status: ' + (verificationResult.valid ? 'VALID' : 'INVALID'))
|
|
560
|
+
report.push('')
|
|
561
|
+
|
|
562
|
+
// Checks
|
|
563
|
+
report.push('Verification Checks:')
|
|
564
|
+
Object.keys(verificationResult.checks).forEach(function(check) {
|
|
565
|
+
var status = verificationResult.checks[check] ? '✓' : '✗'
|
|
566
|
+
report.push(' ' + status + ' ' + check.charAt(0).toUpperCase() + check.slice(1))
|
|
567
|
+
})
|
|
568
|
+
report.push('')
|
|
569
|
+
|
|
570
|
+
// Errors
|
|
571
|
+
if (verificationResult.errors.length > 0) {
|
|
572
|
+
report.push('Errors:')
|
|
573
|
+
verificationResult.errors.forEach(function(error) {
|
|
574
|
+
report.push(' • ' + error)
|
|
575
|
+
})
|
|
576
|
+
report.push('')
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// Warnings
|
|
580
|
+
if (verificationResult.warnings.length > 0) {
|
|
581
|
+
report.push('Warnings:')
|
|
582
|
+
verificationResult.warnings.forEach(function(warning) {
|
|
583
|
+
report.push(' • ' + warning)
|
|
584
|
+
})
|
|
585
|
+
report.push('')
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// Issuer info
|
|
589
|
+
if (verificationResult.issuerDID) {
|
|
590
|
+
report.push('Issuer: ' + verificationResult.issuerDID)
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
if (verificationResult.verificationMethod) {
|
|
594
|
+
report.push('Verification Method: ' + verificationResult.verificationMethod)
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
return report.join('\n')
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
module.exports = AttestationVerifier
|