@smartledger/bsv 5.4.2 → 5.5.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.
Files changed (93) hide show
  1. package/.mocharc.json +5 -0
  2. package/CHANGELOG.md +66 -0
  3. package/README.md +28 -8
  4. package/bsv-covenant.min.js +8 -8
  5. package/bsv-gdaf.min.js +11 -11
  6. package/bsv-ltp.min.js +11 -11
  7. package/bsv-smartcontract.min.js +8 -8
  8. package/bsv.bundle.js +11 -11
  9. package/bsv.min.js +11 -11
  10. package/lib/smart_contract/authorizers.js +159 -0
  11. package/lib/smart_contract/covenant_helpers.js +3 -3
  12. package/lib/smart_contract/dsl.js +1 -1
  13. package/lib/smart_contract/index.js +9 -8
  14. package/lib/smart_contract/pels.js +1 -0
  15. package/lib/smart_contract/pushtx.js +51 -7
  16. package/lib/smart_contract/token.js +179 -35
  17. package/package.json +3 -1
  18. package/test/address.js +629 -0
  19. package/test/block/block.js +239 -0
  20. package/test/block/blockheader.js +270 -0
  21. package/test/block/merkleblock.js +207 -0
  22. package/test/cli/smoke.js +261 -0
  23. package/test/credentials-test.js +329 -0
  24. package/test/crypto/bn.js +168 -0
  25. package/test/crypto/ecdsa.js +328 -0
  26. package/test/crypto/elliptic-fixed.js +61 -0
  27. package/test/crypto/hash.browser.js +121 -0
  28. package/test/crypto/hash.js +122 -0
  29. package/test/crypto/point.js +193 -0
  30. package/test/crypto/random.js +32 -0
  31. package/test/crypto/security.js +127 -0
  32. package/test/crypto/shamir.js +169 -0
  33. package/test/crypto/signature.js +318 -0
  34. package/test/data/bip69.json +215 -0
  35. package/test/data/bitcoind/base58_keys_invalid.json +152 -0
  36. package/test/data/bitcoind/base58_keys_valid.json +452 -0
  37. package/test/data/bitcoind/blocks.json +27 -0
  38. package/test/data/bitcoind/script_tests.json +2244 -0
  39. package/test/data/bitcoind/sig_canonical.json +7 -0
  40. package/test/data/bitcoind/sig_noncanonical.json +22 -0
  41. package/test/data/bitcoind/tx_invalid.json +177 -0
  42. package/test/data/bitcoind/tx_valid.json +224 -0
  43. package/test/data/blk86756-testnet.dat +0 -0
  44. package/test/data/blk86756-testnet.js +12 -0
  45. package/test/data/blk86756-testnet.json +684 -0
  46. package/test/data/ecdsa.json +230 -0
  47. package/test/data/merkleblocks.js +486 -0
  48. package/test/data/messages.json +22 -0
  49. package/test/data/sighash.json +1004 -0
  50. package/test/data/tx_creation.json +85 -0
  51. package/test/ecies/bitcore-ecies.js +170 -0
  52. package/test/ecies/electrum-ecies.js +206 -0
  53. package/test/encoding/base58.js +131 -0
  54. package/test/encoding/base58check.js +136 -0
  55. package/test/encoding/bufferreader.js +328 -0
  56. package/test/encoding/bufferwriter.js +160 -0
  57. package/test/encoding/varint.js +104 -0
  58. package/test/gdaf/canonicalize.js +140 -0
  59. package/test/hdkeys.js +365 -0
  60. package/test/hdprivatekey.js +330 -0
  61. package/test/hdpublickey.js +293 -0
  62. package/test/index.js +16 -0
  63. package/test/ltp/right.js +198 -0
  64. package/test/message/message.js +190 -0
  65. package/test/mnemonic/data/fixtures.json +300 -0
  66. package/test/mnemonic/mnemonic.js +245 -0
  67. package/test/mnemonic/mocha.opts +1 -0
  68. package/test/mnemonic/pbkdf2.test.js +43 -0
  69. package/test/networks.js +162 -0
  70. package/test/opcode.js +161 -0
  71. package/test/privatekey.js +430 -0
  72. package/test/publickey.js +411 -0
  73. package/test/script/genesis_limits.js +80 -0
  74. package/test/script/interpreter.js +723 -0
  75. package/test/script/script.js +1256 -0
  76. package/test/script/string_ops.js +72 -0
  77. package/test/smart_contract/covenants.js +247 -0
  78. package/test/smart_contract/dsl_debugger.js +103 -0
  79. package/test/smart_contract/token_generalized.js +187 -0
  80. package/test/transaction/deserialize.js +33 -0
  81. package/test/transaction/input/input.js +92 -0
  82. package/test/transaction/input/multisig.js +174 -0
  83. package/test/transaction/input/multisigscripthash.js +111 -0
  84. package/test/transaction/input/publickey.js +68 -0
  85. package/test/transaction/input/publickeyhash.js +59 -0
  86. package/test/transaction/output.js +185 -0
  87. package/test/transaction/sighash.js +71 -0
  88. package/test/transaction/signature.js +127 -0
  89. package/test/transaction/transaction.js +1299 -0
  90. package/test/transaction/unspentoutput.js +92 -0
  91. package/test/util/js.js +76 -0
  92. package/test/util/preconditions.js +79 -0
  93. package/test/vcjwt/interop.js +126 -0
@@ -0,0 +1,261 @@
1
+ 'use strict'
2
+
3
+ // End-to-end smoke test for `bin/cli.js`.
4
+ //
5
+ // Exercises every subcommand advertised in the README quickstart:
6
+ // didweb init → vc issue → vc verify
7
+ // status create → status set → status check
8
+ // anchor hash → anchor build
9
+ //
10
+ // Each test runs the CLI in an isolated temp directory so the files it
11
+ // drops (.well-known/did.json, issuer-key-<kid>.json, etc.) don't pollute
12
+ // the repo. We assert on stdout/stderr/exit-code only — the CLI's primary
13
+ // output is stdout, status messages go to stderr.
14
+
15
+ var expect = require('chai').expect
16
+ var spawnSync = require('child_process').spawnSync
17
+ var fs = require('fs')
18
+ var os = require('os')
19
+ var path = require('path')
20
+
21
+ var REPO_ROOT = path.resolve(__dirname, '..', '..')
22
+ var CLI = path.join(REPO_ROOT, 'bin', 'cli.js')
23
+ var PKG_VERSION = require(path.join(REPO_ROOT, 'package.json')).version
24
+
25
+ function runCli(args, opts) {
26
+ opts = opts || {}
27
+ var result = spawnSync(process.execPath, [CLI].concat(args), {
28
+ cwd: opts.cwd || REPO_ROOT,
29
+ encoding: 'utf8',
30
+ timeout: 15000,
31
+ env: Object.assign({}, process.env, opts.env || {})
32
+ })
33
+ return {
34
+ stdout: result.stdout || '',
35
+ stderr: result.stderr || '',
36
+ code: result.status,
37
+ signal: result.signal
38
+ }
39
+ }
40
+
41
+ describe('CLI smoke', function () {
42
+ this.timeout(20000) // crypto + JWT signing can be slow on cold start
43
+
44
+ var tmpDir
45
+
46
+ beforeEach(function () {
47
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'bsv-cli-smoke-'))
48
+ })
49
+
50
+ afterEach(function () {
51
+ try {
52
+ fs.rmSync(tmpDir, { recursive: true, force: true })
53
+ } catch (e) { /* best-effort cleanup */ }
54
+ })
55
+
56
+ describe('meta', function () {
57
+ it('--version prints package.json version', function () {
58
+ var r = runCli(['--version'])
59
+ expect(r.code).to.equal(0)
60
+ expect(r.stdout.trim()).to.equal(PKG_VERSION)
61
+ })
62
+
63
+ it('-v is an alias for --version', function () {
64
+ var r = runCli(['-v'])
65
+ expect(r.code).to.equal(0)
66
+ expect(r.stdout.trim()).to.equal(PKG_VERSION)
67
+ })
68
+
69
+ it('no args prints the help banner with the current version', function () {
70
+ var r = runCli([])
71
+ expect(r.code).to.equal(0)
72
+ expect(r.stdout).to.include('SmartLedger BSV CLI v' + PKG_VERSION)
73
+ expect(r.stdout).to.include('didweb init')
74
+ expect(r.stdout).to.include('vc issue')
75
+ expect(r.stdout).to.include('status create')
76
+ expect(r.stdout).to.include('anchor hash')
77
+ })
78
+
79
+ it('--help prints the help banner', function () {
80
+ var r = runCli(['--help'])
81
+ expect(r.code).to.equal(0)
82
+ expect(r.stdout).to.include('Usage:')
83
+ })
84
+
85
+ it('unknown top-level command exits non-zero', function () {
86
+ var r = runCli(['nope'])
87
+ expect(r.code).to.not.equal(0)
88
+ expect(r.stderr).to.match(/Unknown command/i)
89
+ })
90
+ })
91
+
92
+ describe('didweb', function () {
93
+ it('init --domain creates .well-known files and an issuer key', function () {
94
+ var r = runCli(['didweb', 'init', '--domain', 'example.com', '--alg', 'ES256'], { cwd: tmpDir })
95
+ expect(r.code, r.stderr).to.equal(0)
96
+
97
+ var didPath = path.join(tmpDir, '.well-known', 'did.json')
98
+ var jwksPath = path.join(tmpDir, '.well-known', 'jwks.json')
99
+ expect(fs.existsSync(didPath), 'did.json missing').to.equal(true)
100
+ expect(fs.existsSync(jwksPath), 'jwks.json missing').to.equal(true)
101
+
102
+ var didDoc = JSON.parse(fs.readFileSync(didPath, 'utf8'))
103
+ expect(didDoc.id).to.equal('did:web:example.com')
104
+
105
+ var jwks = JSON.parse(fs.readFileSync(jwksPath, 'utf8'))
106
+ expect(jwks.keys).to.be.an('array').with.length.greaterThan(0)
107
+ expect(jwks.keys[0].kty).to.be.a('string')
108
+
109
+ // Issuer key file is named issuer-key-<kid>.json
110
+ var keyFiles = fs.readdirSync(tmpDir).filter(function (f) {
111
+ return f.startsWith('issuer-key-') && f.endsWith('.json')
112
+ })
113
+ expect(keyFiles, 'issuer key not written').to.have.length(1)
114
+
115
+ var keyData = JSON.parse(fs.readFileSync(path.join(tmpDir, keyFiles[0]), 'utf8'))
116
+ expect(keyData.did).to.equal('did:web:example.com')
117
+ expect(keyData.privateJwk).to.be.an('object')
118
+ expect(keyData.publicJwk).to.be.an('object')
119
+ expect(keyData.alg).to.equal('ES256')
120
+ })
121
+
122
+ it('init --alg ES256K produces a secp256k1 key', function () {
123
+ var r = runCli(['didweb', 'init', '--domain', 'example.org', '--alg', 'ES256K'], { cwd: tmpDir })
124
+ expect(r.code, r.stderr).to.equal(0)
125
+
126
+ var keyFiles = fs.readdirSync(tmpDir).filter(function (f) {
127
+ return f.startsWith('issuer-key-')
128
+ })
129
+ var keyData = JSON.parse(fs.readFileSync(path.join(tmpDir, keyFiles[0]), 'utf8'))
130
+ expect(keyData.alg).to.equal('ES256K')
131
+ })
132
+
133
+ it('init without --domain exits non-zero', function () {
134
+ var r = runCli(['didweb', 'init'], { cwd: tmpDir })
135
+ expect(r.code).to.not.equal(0)
136
+ expect(r.stderr).to.match(/--domain is required/)
137
+ })
138
+ })
139
+
140
+ describe('vc → status → anchor end-to-end', function () {
141
+ // One big sequenced test that uses didweb init's output to issue,
142
+ // verify, anchor, and revoke a credential — mirrors the README's
143
+ // quickstart exactly.
144
+ it('full credential lifecycle', function () {
145
+ // 1. Init a DID:web issuer
146
+ var initRes = runCli(['didweb', 'init', '--domain', 'example.com', '--alg', 'ES256'], { cwd: tmpDir })
147
+ expect(initRes.code, initRes.stderr).to.equal(0)
148
+
149
+ // The CLI writes the key file as issuer-key-<kid>.json but `vc issue`
150
+ // looks for `issuer-key.json` by default. Rename so we can use the
151
+ // default --key path.
152
+ var keyFile = fs.readdirSync(tmpDir).filter(function (f) {
153
+ return f.startsWith('issuer-key-')
154
+ })[0]
155
+ fs.renameSync(path.join(tmpDir, keyFile), path.join(tmpDir, 'issuer-key.json'))
156
+
157
+ // 2. Issue a credential
158
+ var issueRes = runCli([
159
+ 'vc', 'issue',
160
+ '--issuer', 'did:web:example.com',
161
+ '--subject', 'did:example:alice',
162
+ '--types', 'VerifiableCredential,AgeCredential',
163
+ '--claims', JSON.stringify({ ageOver: 18, country: 'US' })
164
+ ], { cwd: tmpDir })
165
+ expect(issueRes.code, issueRes.stderr).to.equal(0)
166
+
167
+ // JWT format: three base64url segments separated by `.`
168
+ var jwt = issueRes.stdout.trim()
169
+ expect(jwt.split('.')).to.have.length(3)
170
+
171
+ // 3. Verify the credential
172
+ fs.writeFileSync(path.join(tmpDir, 'cred.jwt'), jwt)
173
+ var verifyRes = runCli(['vc', 'verify', 'cred.jwt'], { cwd: tmpDir })
174
+ expect(verifyRes.code, verifyRes.stderr).to.equal(0)
175
+ expect(verifyRes.stderr).to.match(/VALID/)
176
+ // Verified payload echoed on stdout
177
+ var payload = JSON.parse(verifyRes.stdout)
178
+ expect(payload.iss).to.equal('did:web:example.com')
179
+
180
+ // 4. Anchor the credential hash
181
+ var anchorRes = runCli(['anchor', 'hash', 'cred.jwt'], { cwd: tmpDir })
182
+ expect(anchorRes.code, anchorRes.stderr).to.equal(0)
183
+ var hash = anchorRes.stdout.trim()
184
+ expect(hash).to.match(/^[0-9a-f]{64}$/)
185
+
186
+ // 5. Build an anchor payload around that hash
187
+ var buildRes = runCli([
188
+ 'anchor', 'build',
189
+ '--kind', 'VC_ANCHOR_SHA256',
190
+ '--hash', hash,
191
+ '--issuer', 'did:web:example.com'
192
+ ], { cwd: tmpDir })
193
+ expect(buildRes.code, buildRes.stderr).to.equal(0)
194
+ var payloadJson = JSON.parse(buildRes.stdout)
195
+ expect(payloadJson.hash).to.equal(hash)
196
+
197
+ // 6. Create a status list
198
+ var statusCreateRes = runCli([
199
+ 'status', 'create',
200
+ '--issuer', 'did:web:example.com'
201
+ ], { cwd: tmpDir })
202
+ expect(statusCreateRes.code, statusCreateRes.stderr).to.equal(0)
203
+ var listJwt = statusCreateRes.stdout.trim()
204
+ expect(listJwt.split('.')).to.have.length(3)
205
+
206
+ // 7. Revoke index 42
207
+ fs.writeFileSync(path.join(tmpDir, 'list.jwt'), listJwt)
208
+ var statusSetRes = runCli([
209
+ 'status', 'set',
210
+ '--list', 'list.jwt',
211
+ '--index', '42',
212
+ '--status', 'revoked'
213
+ ], { cwd: tmpDir })
214
+ expect(statusSetRes.code, statusSetRes.stderr).to.equal(0)
215
+ var updatedListJwt = statusSetRes.stdout.trim()
216
+ expect(updatedListJwt.split('.')).to.have.length(3)
217
+
218
+ // 8. Check the new status at index 42
219
+ fs.writeFileSync(path.join(tmpDir, 'list-updated.jwt'), updatedListJwt)
220
+ var statusCheckRes = runCli([
221
+ 'status', 'check',
222
+ '--list', 'list-updated.jwt',
223
+ '--index', '42'
224
+ ], { cwd: tmpDir })
225
+ expect(statusCheckRes.code, statusCheckRes.stderr).to.equal(0)
226
+ expect(statusCheckRes.stdout.trim()).to.equal('revoked')
227
+
228
+ // 9. A different index should still report valid
229
+ var statusCheckValidRes = runCli([
230
+ 'status', 'check',
231
+ '--list', 'list-updated.jwt',
232
+ '--index', '0'
233
+ ], { cwd: tmpDir })
234
+ expect(statusCheckValidRes.code, statusCheckValidRes.stderr).to.equal(0)
235
+ expect(statusCheckValidRes.stdout.trim()).to.equal('valid')
236
+ })
237
+ })
238
+
239
+ describe('error handling', function () {
240
+ it('vc issue without --issuer exits non-zero', function () {
241
+ var r = runCli(['vc', 'issue', '--subject', 'x', '--claims', '{}'], { cwd: tmpDir })
242
+ expect(r.code).to.not.equal(0)
243
+ expect(r.stderr).to.match(/--issuer/)
244
+ })
245
+
246
+ it('vc verify on a missing file exits non-zero', function () {
247
+ var r = runCli(['vc', 'verify', 'no-such-file.jwt'], { cwd: tmpDir })
248
+ expect(r.code).to.not.equal(0)
249
+ })
250
+
251
+ it('status set without --list exits non-zero', function () {
252
+ var r = runCli(['status', 'set', '--index', '1', '--status', 'revoked'], { cwd: tmpDir })
253
+ expect(r.code).to.not.equal(0)
254
+ })
255
+
256
+ it('anchor build without --kind exits non-zero', function () {
257
+ var r = runCli(['anchor', 'build', '--hash', 'x', '--issuer', 'y'], { cwd: tmpDir })
258
+ expect(r.code).to.not.equal(0)
259
+ })
260
+ })
261
+ })
@@ -0,0 +1,329 @@
1
+ /**
2
+ * Test Vectors for DID:web + VC-JWT + StatusList2021
3
+ * Comprehensive test suite for v3.4.0
4
+ */
5
+
6
+ 'use strict'
7
+
8
+ var didweb = require('../lib/didweb')
9
+ var vcjwt = require('../lib/vcjwt')
10
+ var statuslist = require('../lib/statuslist')
11
+ var anchor = require('../lib/anchor')
12
+ var assert = require('assert')
13
+
14
+ console.log('🧪 SmartLedger BSV v3.4.0 - Credential Test Suite\n')
15
+
16
+ // Test 1: DID:web Key Generation (ES256)
17
+ async function testES256KeyGeneration() {
18
+ console.log('1️⃣ Testing ES256 key generation...')
19
+
20
+ var keys = await didweb.generateIssuerKeys({ alg: 'ES256' })
21
+
22
+ assert(keys.privateJwk, 'Private JWK should exist')
23
+ assert(keys.publicJwk, 'Public JWK should exist')
24
+ assert.strictEqual(keys.alg, 'ES256', 'Algorithm should be ES256')
25
+ assert(keys.kid, 'Key ID should exist')
26
+ assert.strictEqual(keys.publicJwk.kty, 'EC', 'Key type should be EC')
27
+ assert.strictEqual(keys.publicJwk.crv, 'P-256', 'Curve should be P-256')
28
+
29
+ console.log(' ✅ ES256 key generation successful')
30
+ console.log(' ✅ Kid:', keys.kid)
31
+ return keys
32
+ }
33
+
34
+ // Test 2: DID:web Key Generation (ES256K)
35
+ async function testES256KKeyGeneration() {
36
+ console.log('\n2️⃣ Testing ES256K key generation...')
37
+
38
+ var keys = await didweb.generateIssuerKeys({ alg: 'ES256K' })
39
+
40
+ assert(keys.privateJwk, 'Private JWK should exist')
41
+ assert(keys.publicJwk, 'Public JWK should exist')
42
+ assert.strictEqual(keys.alg, 'ES256K', 'Algorithm should be ES256K')
43
+ assert(keys.kid, 'Key ID should exist')
44
+ assert.strictEqual(keys.publicJwk.kty, 'EC', 'Key type should be EC')
45
+ assert.strictEqual(keys.publicJwk.crv, 'secp256k1', 'Curve should be secp256k1')
46
+
47
+ console.log(' ✅ ES256K key generation successful')
48
+ console.log(' ✅ Kid:', keys.kid)
49
+ return keys
50
+ }
51
+
52
+ // Test 3: DID:web Document Building
53
+ async function testDIDDocumentBuilding(p256Keys) {
54
+ console.log('\n3️⃣ Testing DID:web document building...')
55
+
56
+ var docs = didweb.buildDidWebDocuments({
57
+ domain: 'smartledger.technology',
58
+ p256: { jwk: p256Keys.publicJwk, kid: p256Keys.kid },
59
+ controllerName: 'SmartLedger Test Issuer'
60
+ })
61
+
62
+ assert.strictEqual(docs.did, 'did:web:smartledger.technology', 'DID should be correctly formatted')
63
+ assert(docs.didDocument, 'DID Document should exist')
64
+ assert(docs.jwks, 'JWKS should exist')
65
+ assert(docs.didDocument.verificationMethod.length > 0, 'Should have verification methods')
66
+ assert(docs.jwks.keys.length > 0, 'JWKS should have keys')
67
+
68
+ console.log(' ✅ DID:', docs.did)
69
+ console.log(' ✅ Verification methods:', docs.didDocument.verificationMethod.length)
70
+ console.log(' ✅ JWKS keys:', docs.jwks.keys.length)
71
+
72
+ return docs
73
+ }
74
+
75
+ // Test 4: VC-JWT Issuance (ES256)
76
+ async function testVCIssuanceES256(issuerDid, keys) {
77
+ console.log('\n4️⃣ Testing VC-JWT issuance (ES256)...')
78
+
79
+ var result = await vcjwt.issueVcJwt({
80
+ issuerDid: issuerDid,
81
+ subjectId: 'did:example:holder123',
82
+ types: ['VerifiableCredential', 'AgeCredential'],
83
+ credentialSubject: {
84
+ ageOver: 18,
85
+ country: 'US'
86
+ },
87
+ privateJwk: keys.privateJwk,
88
+ alg: 'ES256',
89
+ kid: keys.kid
90
+ })
91
+
92
+ assert(result.jwt, 'JWT should be generated')
93
+ assert(result.jwt.split('.').length === 3, 'JWT should have 3 parts')
94
+
95
+ console.log(' ✅ JWT generated')
96
+ console.log(' ✅ JWT length:', result.jwt.length)
97
+
98
+ return result.jwt
99
+ }
100
+
101
+ // Test 5: VC-JWT Verification
102
+ async function testVCVerification(jwt, jwks) {
103
+ console.log('\n5️⃣ Testing VC-JWT verification...')
104
+
105
+ var didResolver = async function(did) {
106
+ return { jwks: jwks }
107
+ }
108
+
109
+ var result = await vcjwt.verifyVcJwt(jwt, {
110
+ didResolver: didResolver,
111
+ expectedIssuerDid: 'did:web:smartledger.technology'
112
+ })
113
+
114
+ assert.strictEqual(result.valid, true, 'JWT should be valid')
115
+ assert(result.payload, 'Payload should be present')
116
+ assert(result.header, 'Header should be present')
117
+
118
+ console.log(' ✅ JWT verification successful')
119
+ console.log(' ✅ Issuer:', result.payload.iss)
120
+ console.log(' ✅ Subject:', result.payload.sub)
121
+
122
+ return result
123
+ }
124
+
125
+ // Test 6: StatusList2021 Creation
126
+ async function testStatusListCreation(issuerDid, keys) {
127
+ console.log('\n6️⃣ Testing StatusList2021 creation...')
128
+
129
+ var result = await statuslist.createStatusList({
130
+ issuerDid: issuerDid,
131
+ privateJwk: keys.privateJwk
132
+ })
133
+
134
+ assert(result.listVcJwt, 'Status list JWT should be created')
135
+ assert(result.listId, 'List ID should be created')
136
+
137
+ console.log(' ✅ Status list created')
138
+ console.log(' ✅ List ID:', result.listId)
139
+
140
+ return result.listVcJwt
141
+ }
142
+
143
+ // Test 7: StatusList2021 Update (Revocation)
144
+ async function testStatusListRevocation(listJwt, keys) {
145
+ console.log('\n7️⃣ Testing credential revocation...')
146
+
147
+ var index = 42
148
+
149
+ // Check initial status
150
+ var initialStatus = statuslist.getCredentialStatusEntry({
151
+ listVcJwt: listJwt,
152
+ index: index
153
+ })
154
+
155
+ assert.strictEqual(initialStatus, 'valid', 'Initial status should be valid')
156
+ console.log(' ✅ Initial status at index', index + ':', initialStatus)
157
+
158
+ // Revoke credential
159
+ var result = await statuslist.updateStatusList({
160
+ listVcJwt: listJwt,
161
+ index: index,
162
+ status: 'revoked',
163
+ privateJwk: keys.privateJwk
164
+ })
165
+
166
+ assert(result.listVcJwt, 'Updated list should be returned')
167
+
168
+ // Check updated status
169
+ var updatedStatus = statuslist.getCredentialStatusEntry({
170
+ listVcJwt: result.listVcJwt,
171
+ index: index
172
+ })
173
+
174
+ assert.strictEqual(updatedStatus, 'revoked', 'Status should be revoked')
175
+ console.log(' ✅ Updated status at index', index + ':', updatedStatus)
176
+
177
+ return result.listVcJwt
178
+ }
179
+
180
+ // Test 8: Anchor Hash Generation
181
+ function testAnchorHashing() {
182
+ console.log('\n8️⃣ Testing anchor hash generation...')
183
+
184
+ var testData = 'Test Verifiable Credential Data'
185
+ var hash = anchor.sha256Hex(testData)
186
+
187
+ assert(hash, 'Hash should be generated')
188
+ assert.strictEqual(hash.length, 64, 'Hash should be 64 hex characters')
189
+ assert(/^[a-f0-9]{64}$/.test(hash), 'Hash should be valid hex')
190
+
191
+ console.log(' ✅ SHA-256 hash generated')
192
+ console.log(' ✅ Hash:', hash)
193
+
194
+ // Test hash reproducibility
195
+ var hash2 = anchor.sha256Hex(testData)
196
+ assert.strictEqual(hash, hash2, 'Hash should be reproducible')
197
+ console.log(' ✅ Hash reproducibility confirmed')
198
+
199
+ return hash
200
+ }
201
+
202
+ // Test 9: Anchor Payload Building
203
+ function testAnchorPayload(hash, issuerDid) {
204
+ console.log('\n9️⃣ Testing anchor payload building...')
205
+
206
+ var payload = anchor.buildAnchorPayload({
207
+ kind: 'VC_ANCHOR_SHA256',
208
+ hash: hash,
209
+ issuerDid: issuerDid,
210
+ issuedAt: new Date().toISOString()
211
+ })
212
+
213
+ assert(payload.json, 'Payload JSON should be generated')
214
+
215
+ var parsed = JSON.parse(payload.json)
216
+ assert.strictEqual(parsed.protocol, 'SmartLedger', 'Protocol should be SmartLedger')
217
+ assert.strictEqual(parsed.type, 'VC_ANCHOR_SHA256', 'Type should match')
218
+ assert.strictEqual(parsed.hash, hash, 'Hash should match')
219
+ assert.strictEqual(parsed.issuer, issuerDid, 'Issuer should match')
220
+
221
+ console.log(' ✅ Anchor payload created')
222
+ console.log(' ✅ Size:', payload.json.length, 'bytes')
223
+ console.log(' ✅ Protocol:', parsed.protocol)
224
+
225
+ return payload
226
+ }
227
+
228
+ // Test 10: End-to-end Workflow
229
+ async function testEndToEndWorkflow() {
230
+ console.log('\n🔟 Testing complete end-to-end workflow...')
231
+
232
+ // Generate keys
233
+ var keys = await didweb.generateIssuerKeys({ alg: 'ES256' })
234
+
235
+ // Build DID documents
236
+ var docs = didweb.buildDidWebDocuments({
237
+ domain: 'test.example.com',
238
+ p256: { jwk: keys.publicJwk, kid: keys.kid }
239
+ })
240
+
241
+ // Issue credential
242
+ var result = await vcjwt.issueVcJwt({
243
+ issuerDid: docs.did,
244
+ subjectId: 'did:example:alice',
245
+ types: ['VerifiableCredential', 'DriverLicense'],
246
+ credentialSubject: {
247
+ licenseNumber: 'DL123456',
248
+ class: 'C'
249
+ },
250
+ privateJwk: keys.privateJwk,
251
+ alg: 'ES256',
252
+ kid: keys.kid
253
+ })
254
+ var jwt = result.jwt
255
+
256
+ // Hash credential for anchoring
257
+ var hash = anchor.sha256Hex(jwt)
258
+
259
+ // Build anchor payload
260
+ var anchorPayload = anchor.buildAnchorPayload({
261
+ kind: 'VC_ANCHOR_SHA256',
262
+ hash: hash,
263
+ issuerDid: docs.did
264
+ })
265
+
266
+ // Verify credential
267
+ var didResolver = async function() { return { jwks: docs.jwks } }
268
+ var verification = await vcjwt.verifyVcJwt(jwt, { didResolver: didResolver })
269
+
270
+ assert.strictEqual(verification.valid, true, 'Credential should be valid')
271
+
272
+ // Create status list
273
+ var statusListResult = await statuslist.createStatusList({
274
+ issuerDid: docs.did,
275
+ privateJwk: keys.privateJwk
276
+ })
277
+
278
+ // Revoke credential
279
+ var updatedList = await statuslist.updateStatusList({
280
+ listVcJwt: statusListResult.listVcJwt,
281
+ index: 10,
282
+ status: 'revoked',
283
+ privateJwk: keys.privateJwk
284
+ })
285
+
286
+ // Verify credential is now revoked
287
+ var verification2 = await vcjwt.verifyVcJwt(jwt, { didResolver: didResolver })
288
+ var revokedStatus = statuslist.getCredentialStatusEntry({
289
+ listVcJwt: updatedList.listVcJwt,
290
+ index: 10
291
+ })
292
+
293
+ assert.strictEqual(verification2.valid, true, 'JWT signature still valid')
294
+ assert.strictEqual(revokedStatus, 'revoked', 'But credential is revoked')
295
+
296
+ console.log(' ✅ Complete workflow executed successfully')
297
+ console.log(' ✅ Issue → Anchor → Verify → Revoke → Verify')
298
+ }
299
+
300
+ // Run all tests
301
+ async function runTests() {
302
+ try {
303
+ var p256Keys = await testES256KeyGeneration()
304
+ var k1Keys = await testES256KKeyGeneration()
305
+ var docs = await testDIDDocumentBuilding(p256Keys)
306
+ var jwt = await testVCIssuanceES256(docs.did, p256Keys)
307
+ var verification = await testVCVerification(jwt, docs.jwks)
308
+ var statusListJwt = await testStatusListCreation(docs.did, p256Keys)
309
+ var revokedListJwt = await testStatusListRevocation(statusListJwt, p256Keys)
310
+ var hash = testAnchorHashing()
311
+ var anchorPayload = testAnchorPayload(hash, docs.did)
312
+ await testEndToEndWorkflow()
313
+
314
+ console.log('\n🎉 All tests passed successfully!')
315
+ console.log('✅ DID:web generation working')
316
+ console.log('✅ VC-JWT issuance and verification working')
317
+ console.log('✅ StatusList2021 revocation working')
318
+ console.log('✅ BSV anchor hashing working')
319
+ console.log('✅ ES256 and ES256K support confirmed')
320
+
321
+ } catch (error) {
322
+ console.error('\n❌ Test failed:', error.message)
323
+ console.error(error.stack)
324
+ process.exit(1)
325
+ }
326
+ }
327
+
328
+ // Run the test suite
329
+ runTests()