@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.
- package/.mocharc.json +5 -0
- package/CHANGELOG.md +66 -0
- package/README.md +28 -8
- package/bsv-covenant.min.js +8 -8
- package/bsv-gdaf.min.js +11 -11
- package/bsv-ltp.min.js +11 -11
- package/bsv-smartcontract.min.js +8 -8
- package/bsv.bundle.js +11 -11
- package/bsv.min.js +11 -11
- package/lib/smart_contract/authorizers.js +159 -0
- package/lib/smart_contract/covenant_helpers.js +3 -3
- package/lib/smart_contract/dsl.js +1 -1
- package/lib/smart_contract/index.js +9 -8
- package/lib/smart_contract/pels.js +1 -0
- package/lib/smart_contract/pushtx.js +51 -7
- package/lib/smart_contract/token.js +179 -35
- package/package.json +3 -1
- package/test/address.js +629 -0
- package/test/block/block.js +239 -0
- package/test/block/blockheader.js +270 -0
- package/test/block/merkleblock.js +207 -0
- package/test/cli/smoke.js +261 -0
- package/test/credentials-test.js +329 -0
- package/test/crypto/bn.js +168 -0
- package/test/crypto/ecdsa.js +328 -0
- package/test/crypto/elliptic-fixed.js +61 -0
- package/test/crypto/hash.browser.js +121 -0
- package/test/crypto/hash.js +122 -0
- package/test/crypto/point.js +193 -0
- package/test/crypto/random.js +32 -0
- package/test/crypto/security.js +127 -0
- package/test/crypto/shamir.js +169 -0
- package/test/crypto/signature.js +318 -0
- package/test/data/bip69.json +215 -0
- package/test/data/bitcoind/base58_keys_invalid.json +152 -0
- package/test/data/bitcoind/base58_keys_valid.json +452 -0
- package/test/data/bitcoind/blocks.json +27 -0
- package/test/data/bitcoind/script_tests.json +2244 -0
- package/test/data/bitcoind/sig_canonical.json +7 -0
- package/test/data/bitcoind/sig_noncanonical.json +22 -0
- package/test/data/bitcoind/tx_invalid.json +177 -0
- package/test/data/bitcoind/tx_valid.json +224 -0
- package/test/data/blk86756-testnet.dat +0 -0
- package/test/data/blk86756-testnet.js +12 -0
- package/test/data/blk86756-testnet.json +684 -0
- package/test/data/ecdsa.json +230 -0
- package/test/data/merkleblocks.js +486 -0
- package/test/data/messages.json +22 -0
- package/test/data/sighash.json +1004 -0
- package/test/data/tx_creation.json +85 -0
- package/test/ecies/bitcore-ecies.js +170 -0
- package/test/ecies/electrum-ecies.js +206 -0
- package/test/encoding/base58.js +131 -0
- package/test/encoding/base58check.js +136 -0
- package/test/encoding/bufferreader.js +328 -0
- package/test/encoding/bufferwriter.js +160 -0
- package/test/encoding/varint.js +104 -0
- package/test/gdaf/canonicalize.js +140 -0
- package/test/hdkeys.js +365 -0
- package/test/hdprivatekey.js +330 -0
- package/test/hdpublickey.js +293 -0
- package/test/index.js +16 -0
- package/test/ltp/right.js +198 -0
- package/test/message/message.js +190 -0
- package/test/mnemonic/data/fixtures.json +300 -0
- package/test/mnemonic/mnemonic.js +245 -0
- package/test/mnemonic/mocha.opts +1 -0
- package/test/mnemonic/pbkdf2.test.js +43 -0
- package/test/networks.js +162 -0
- package/test/opcode.js +161 -0
- package/test/privatekey.js +430 -0
- package/test/publickey.js +411 -0
- package/test/script/genesis_limits.js +80 -0
- package/test/script/interpreter.js +723 -0
- package/test/script/script.js +1256 -0
- package/test/script/string_ops.js +72 -0
- package/test/smart_contract/covenants.js +247 -0
- package/test/smart_contract/dsl_debugger.js +103 -0
- package/test/smart_contract/token_generalized.js +187 -0
- package/test/transaction/deserialize.js +33 -0
- package/test/transaction/input/input.js +92 -0
- package/test/transaction/input/multisig.js +174 -0
- package/test/transaction/input/multisigscripthash.js +111 -0
- package/test/transaction/input/publickey.js +68 -0
- package/test/transaction/input/publickeyhash.js +59 -0
- package/test/transaction/output.js +185 -0
- package/test/transaction/sighash.js +71 -0
- package/test/transaction/signature.js +127 -0
- package/test/transaction/transaction.js +1299 -0
- package/test/transaction/unspentoutput.js +92 -0
- package/test/util/js.js +76 -0
- package/test/util/preconditions.js +79 -0
- 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()
|