@swimmingkiim/trust-sdk 0.1.32 → 0.2.2
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/dist/credentials/vc-handler.service.d.ts +20 -6
- package/dist/credentials/vc-handler.service.d.ts.map +1 -1
- package/dist/credentials/vc-handler.service.js +58 -34
- package/dist/credentials/vc-handler.service.js.map +1 -1
- package/dist/identity/did-manager.d.ts +28 -3
- package/dist/identity/did-manager.d.ts.map +1 -1
- package/dist/identity/did-manager.js +105 -16
- package/dist/identity/did-manager.js.map +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -17
- package/dist/index.js.map +1 -1
- package/dist/resolver.d.ts +13 -0
- package/dist/resolver.d.ts.map +1 -0
- package/dist/resolver.js +21 -0
- package/dist/resolver.js.map +1 -0
- package/package.json +7 -19
- package/src/credentials/vc-handler.service.ts +65 -38
- package/src/identity/did-manager.ts +131 -17
- package/src/index.ts +5 -3
- package/src/resolver.ts +26 -0
- package/test/identity.security.test.ts +44 -54
- package/test/identity.test.ts +59 -47
- package/test/stateless.test.ts +133 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/src/agent.ts +0 -99
|
@@ -1,25 +1,139 @@
|
|
|
1
|
-
import
|
|
1
|
+
import crypto from 'crypto'
|
|
2
|
+
import { createResolver } from '../resolver'
|
|
3
|
+
import { Resolver } from 'did-resolver'
|
|
4
|
+
|
|
5
|
+
export interface EphemeralIdentity {
|
|
6
|
+
did: string
|
|
7
|
+
keyPair: {
|
|
8
|
+
publicKey: Uint8Array
|
|
9
|
+
secretKey: Uint8Array
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Ed25519 multicodec prefix (0xed01)
|
|
14
|
+
const ED25519_MULTICODEC_PREFIX = new Uint8Array([0xed, 0x01])
|
|
15
|
+
|
|
16
|
+
// Base58btc alphabet (Bitcoin alphabet)
|
|
17
|
+
const BASE58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Encodes a Uint8Array to base58btc string.
|
|
21
|
+
* Pure implementation with no external dependencies.
|
|
22
|
+
*/
|
|
23
|
+
function base58btcEncode(bytes: Uint8Array): string {
|
|
24
|
+
// Count leading zeros
|
|
25
|
+
let leadingZeros = 0
|
|
26
|
+
for (let i = 0; i < bytes.length && bytes[i] === 0; i++) {
|
|
27
|
+
leadingZeros++
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Convert to BigInt for base conversion
|
|
31
|
+
let num = BigInt('0x' + Buffer.from(bytes).toString('hex'))
|
|
32
|
+
const chars: string[] = []
|
|
33
|
+
|
|
34
|
+
while (num > 0n) {
|
|
35
|
+
const remainder = Number(num % 58n)
|
|
36
|
+
chars.unshift(BASE58_ALPHABET[remainder])
|
|
37
|
+
num = num / 58n
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Add '1' for each leading zero byte
|
|
41
|
+
for (let i = 0; i < leadingZeros; i++) {
|
|
42
|
+
chars.unshift('1')
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return chars.join('')
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Encodes an Ed25519 public key as a did:key identifier.
|
|
50
|
+
* Format: did:key:z + base58btc(multicodec_prefix + public_key)
|
|
51
|
+
*/
|
|
52
|
+
function publicKeyToDidKey(publicKey: Uint8Array): string {
|
|
53
|
+
const multicodecKey = new Uint8Array(ED25519_MULTICODEC_PREFIX.length + publicKey.length)
|
|
54
|
+
multicodecKey.set(ED25519_MULTICODEC_PREFIX)
|
|
55
|
+
multicodecKey.set(publicKey, ED25519_MULTICODEC_PREFIX.length)
|
|
56
|
+
return `did:key:z${base58btcEncode(multicodecKey)}`
|
|
57
|
+
}
|
|
2
58
|
|
|
3
59
|
export class IdentityManager {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
})
|
|
9
|
-
return identifier
|
|
60
|
+
private resolver: Resolver
|
|
61
|
+
|
|
62
|
+
constructor(resolver?: Resolver) {
|
|
63
|
+
this.resolver = resolver ?? createResolver()
|
|
10
64
|
}
|
|
11
65
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
})
|
|
18
|
-
|
|
66
|
+
/**
|
|
67
|
+
* Creates an ephemeral (in-memory) did:key identity.
|
|
68
|
+
* Uses Node.js built-in crypto — no external dependencies, no database.
|
|
69
|
+
*/
|
|
70
|
+
async createEphemeralDID(): Promise<EphemeralIdentity> {
|
|
71
|
+
const { publicKey, privateKey } = crypto.generateKeyPairSync('ed25519')
|
|
72
|
+
|
|
73
|
+
// Export raw key bytes from DER format
|
|
74
|
+
const pubDer = publicKey.export({ type: 'spki', format: 'der' })
|
|
75
|
+
const privDer = privateKey.export({ type: 'pkcs8', format: 'der' })
|
|
76
|
+
|
|
77
|
+
// Ed25519 SPKI DER: 12-byte header + 32-byte key
|
|
78
|
+
const rawPublicKey = new Uint8Array(pubDer.subarray(pubDer.length - 32))
|
|
79
|
+
// Ed25519 PKCS8 DER: 16-byte header + 32-byte key
|
|
80
|
+
const rawSecretKey = new Uint8Array(privDer.subarray(privDer.length - 32))
|
|
81
|
+
|
|
82
|
+
const did = publicKeyToDidKey(rawPublicKey)
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
did,
|
|
86
|
+
keyPair: { publicKey: rawPublicKey, secretKey: rawSecretKey },
|
|
87
|
+
}
|
|
19
88
|
}
|
|
20
89
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
90
|
+
/**
|
|
91
|
+
* Resolves a DID to its DID Document.
|
|
92
|
+
* Stateless: uses blockchain (did:ethr) or derivation (did:key).
|
|
93
|
+
*/
|
|
94
|
+
async resolveDID(did: string): Promise<any> {
|
|
95
|
+
return await this.resolver.resolve(did)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Imports an existing Ed25519 secret key and derives the DID.
|
|
100
|
+
* Use this when you already have a fixed key (e.g., from did_keys.json).
|
|
101
|
+
*
|
|
102
|
+
* @param secretKey - 32-byte Ed25519 secret key as hex string or Uint8Array
|
|
103
|
+
* @returns EphemeralIdentity with the derived DID and key pair
|
|
104
|
+
*/
|
|
105
|
+
async fromSecretKey(secretKey: string | Uint8Array): Promise<EphemeralIdentity> {
|
|
106
|
+
const rawSecretKey = typeof secretKey === 'string'
|
|
107
|
+
? new Uint8Array(Buffer.from(secretKey.replace(/^0x/, ''), 'hex'))
|
|
108
|
+
: secretKey
|
|
109
|
+
|
|
110
|
+
if (rawSecretKey.length !== 32) {
|
|
111
|
+
throw new Error(`Invalid Ed25519 secret key length: expected 32 bytes, got ${rawSecretKey.length}`)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Reconstruct Node.js KeyObject from raw bytes
|
|
115
|
+
// Ed25519 PKCS8 DER = fixed 16-byte header + 32-byte raw key
|
|
116
|
+
const pkcs8Header = Buffer.from([
|
|
117
|
+
0x30, 0x2e, 0x02, 0x01, 0x00, 0x30, 0x05, 0x06,
|
|
118
|
+
0x03, 0x2b, 0x65, 0x70, 0x04, 0x22, 0x04, 0x20,
|
|
119
|
+
])
|
|
120
|
+
const pkcs8Der = Buffer.concat([pkcs8Header, Buffer.from(rawSecretKey)])
|
|
121
|
+
const privateKeyObject = crypto.createPrivateKey({
|
|
122
|
+
key: pkcs8Der,
|
|
123
|
+
format: 'der',
|
|
124
|
+
type: 'pkcs8',
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
// Derive public key
|
|
128
|
+
const publicKeyObject = crypto.createPublicKey(privateKeyObject)
|
|
129
|
+
const pubDer = publicKeyObject.export({ type: 'spki', format: 'der' })
|
|
130
|
+
const rawPublicKey = new Uint8Array(pubDer.subarray(pubDer.length - 32))
|
|
131
|
+
|
|
132
|
+
const did = publicKeyToDidKey(rawPublicKey)
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
did,
|
|
136
|
+
keyPair: { publicKey: rawPublicKey, secretKey: rawSecretKey },
|
|
137
|
+
}
|
|
24
138
|
}
|
|
25
139
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
-
export
|
|
2
|
-
export
|
|
3
|
-
export
|
|
1
|
+
export { IdentityManager } from './identity/did-manager'
|
|
2
|
+
export type { EphemeralIdentity } from './identity/did-manager'
|
|
3
|
+
export { VCHandler } from './credentials/vc-handler.service'
|
|
4
|
+
export { createResolver } from './resolver'
|
|
5
|
+
export type { ResolverOptions } from './resolver'
|
package/src/resolver.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Resolver } from 'did-resolver'
|
|
2
|
+
import { getResolver as getKeyResolver } from 'key-did-resolver'
|
|
3
|
+
import { getResolver as getEthrResolver } from 'ethr-did-resolver'
|
|
4
|
+
|
|
5
|
+
export interface ResolverOptions {
|
|
6
|
+
/** Ethereum network name for did:ethr (default: 'base') */
|
|
7
|
+
ethNetwork?: string
|
|
8
|
+
/** RPC URL for did:ethr resolution */
|
|
9
|
+
rpcUrl?: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Creates a stateless DID Resolver.
|
|
14
|
+
* No database, no state — only blockchain/key-based resolution.
|
|
15
|
+
*/
|
|
16
|
+
export function createResolver(options?: ResolverOptions): Resolver {
|
|
17
|
+
const ethNetwork = options?.ethNetwork || 'base'
|
|
18
|
+
const rpcUrl = options?.rpcUrl || 'https://mainnet.base.org'
|
|
19
|
+
|
|
20
|
+
return new Resolver({
|
|
21
|
+
...getKeyResolver(),
|
|
22
|
+
...getEthrResolver({
|
|
23
|
+
networks: [{ name: ethNetwork, rpcUrl }],
|
|
24
|
+
}),
|
|
25
|
+
})
|
|
26
|
+
}
|
|
@@ -1,68 +1,58 @@
|
|
|
1
|
-
import { describe, it, expect
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { IdentityManager } from '../src/identity/did-manager'
|
|
2
3
|
import { VCHandler } from '../src/credentials/vc-handler.service'
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
verifyCredential: vi.fn().mockImplementation(async ({ credential }) => {
|
|
8
|
-
// Check for expiration FIRST
|
|
9
|
-
if (credential.expirationDate === '1999-01-01T00:00:00Z') {
|
|
10
|
-
return { verified: false, error: 'Credential expired' }
|
|
11
|
-
}
|
|
12
|
-
// Then check for signature
|
|
13
|
-
if (credential.proof && credential.proof.jwt === 'valid_signature') {
|
|
14
|
-
return { verified: true }
|
|
15
|
-
}
|
|
16
|
-
return { verified: false, error: 'Invalid signature' }
|
|
17
|
-
}),
|
|
18
|
-
createVerifiableCredential: vi.fn().mockResolvedValue({
|
|
19
|
-
proof: { jwt: 'valid_signature' },
|
|
20
|
-
credentialSubject: { id: 'did:example:123', name: 'Test' },
|
|
21
|
-
issuanceDate: new Date().toISOString()
|
|
22
|
-
})
|
|
23
|
-
}
|
|
24
|
-
return {
|
|
25
|
-
agent: mockAgent,
|
|
26
|
-
// Ensure initAgent returns the object containing verify/create methods
|
|
27
|
-
initAgent: vi.fn().mockResolvedValue(mockAgent)
|
|
28
|
-
}
|
|
29
|
-
})
|
|
30
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Security Tests (No Mocks — Real JWT Verification)
|
|
7
|
+
*/
|
|
31
8
|
describe('a2trust: Security Tests', () => {
|
|
9
|
+
let idManager: IdentityManager
|
|
32
10
|
let vcHandler: VCHandler
|
|
33
11
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
console.error('DEBUG: Security Test mockAgent keys:', Object.keys(mockAgent))
|
|
37
|
-
vcHandler = new VCHandler(mockAgent as any)
|
|
38
|
-
})
|
|
12
|
+
idManager = new IdentityManager()
|
|
13
|
+
vcHandler = new VCHandler()
|
|
39
14
|
|
|
40
|
-
it('
|
|
41
|
-
|
|
42
|
-
const validVC = await vcHandler.createCredential('issuer', 'subject', {})
|
|
15
|
+
it('should reject a tampered JWT', async () => {
|
|
16
|
+
const identity = await idManager.createEphemeralDID()
|
|
43
17
|
|
|
44
|
-
|
|
45
|
-
|
|
18
|
+
const validJwt = await vcHandler.createCredential(
|
|
19
|
+
identity.did,
|
|
20
|
+
identity.did,
|
|
21
|
+
{ walletAddress: '0x123' },
|
|
22
|
+
identity.keyPair
|
|
23
|
+
)
|
|
46
24
|
|
|
47
|
-
//
|
|
48
|
-
|
|
49
|
-
const
|
|
50
|
-
|
|
25
|
+
// Tamper with the JWT payload
|
|
26
|
+
const parts = validJwt.split('.')
|
|
27
|
+
const payload = JSON.parse(Buffer.from(parts[1], 'base64').toString())
|
|
28
|
+
payload.vc.credentialSubject.walletAddress = '0xHACKED'
|
|
29
|
+
parts[1] = Buffer.from(JSON.stringify(payload)).toString('base64url')
|
|
30
|
+
const tamperedJwt = parts.join('.')
|
|
51
31
|
|
|
52
|
-
|
|
53
|
-
expect(result
|
|
32
|
+
const result = await vcHandler.verifyCredential(tamperedJwt)
|
|
33
|
+
expect(result).toBe(false)
|
|
54
34
|
})
|
|
55
35
|
|
|
56
|
-
it('
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
proof: { jwt: 'valid_signature' },
|
|
61
|
-
expirationDate: '1999-01-01T00:00:00Z'
|
|
62
|
-
}
|
|
36
|
+
it('should reject a completely invalid JWT string', async () => {
|
|
37
|
+
const result = await vcHandler.verifyCredential('not.a.valid.jwt')
|
|
38
|
+
expect(result).toBe(false)
|
|
39
|
+
})
|
|
63
40
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
41
|
+
it('should reject a JWT signed by unknown issuer', async () => {
|
|
42
|
+
// Create a VC signed by identity A
|
|
43
|
+
const identityA = await idManager.createEphemeralDID()
|
|
44
|
+
const identityB = await idManager.createEphemeralDID()
|
|
45
|
+
|
|
46
|
+
// Sign with A's key but claim issuer is B
|
|
47
|
+
// This should fail because B's DID document won't match A's key
|
|
48
|
+
const vcJwt = await vcHandler.createCredential(
|
|
49
|
+
identityB.did, // claim to be B
|
|
50
|
+
identityB.did,
|
|
51
|
+
{ walletAddress: '0x123' },
|
|
52
|
+
identityA.keyPair // but sign with A's key
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
const result = await vcHandler.verifyCredential(vcJwt)
|
|
56
|
+
expect(result).toBe(false)
|
|
67
57
|
})
|
|
68
58
|
})
|
package/test/identity.test.ts
CHANGED
|
@@ -1,69 +1,81 @@
|
|
|
1
|
-
import { describe, it, expect
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
2
|
import { IdentityManager } from '../src/identity/did-manager'
|
|
3
3
|
import { VCHandler } from '../src/credentials/vc-handler.service'
|
|
4
|
-
import { agent } from '../src/agent'
|
|
5
|
-
|
|
6
|
-
// Mock the agent module to avoid loading Veramo dependencies which cause syntax errors in Node 22
|
|
7
|
-
vi.mock('../src/agent', () => {
|
|
8
|
-
const mockAgent = {
|
|
9
|
-
didManagerCreate: vi.fn().mockResolvedValue({ did: 'did:key:mocked123' }),
|
|
10
|
-
createVerifiableCredential: vi.fn().mockResolvedValue({
|
|
11
|
-
proof: { jwt: 'mock_jwt' },
|
|
12
|
-
credentialSubject: { id: 'did:key:mocked123', name: 'Test Agent' }
|
|
13
|
-
}),
|
|
14
|
-
verifyCredential: vi.fn().mockResolvedValue({ verified: true })
|
|
15
|
-
}
|
|
16
|
-
return {
|
|
17
|
-
agent: mockAgent,
|
|
18
|
-
initAgent: vi.fn().mockResolvedValue(mockAgent)
|
|
19
|
-
}
|
|
20
|
-
})
|
|
21
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Identity & Credentials Tests (No Mocks)
|
|
7
|
+
*
|
|
8
|
+
* These test REAL in-memory behavior — no vi.mock, no DB.
|
|
9
|
+
*/
|
|
22
10
|
describe('a2trust: Identity & Credentials', () => {
|
|
23
11
|
let idManager: IdentityManager
|
|
24
12
|
let vcHandler: VCHandler
|
|
25
|
-
let issuerDid: string
|
|
26
|
-
let subjectDid: string
|
|
27
|
-
|
|
28
|
-
beforeAll(async () => {
|
|
29
|
-
// Since we mocked the module, imports of ../src/agent return the mock
|
|
30
|
-
const { agent: mockAgent } = await import('../src/agent')
|
|
31
13
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
vcHandler = new VCHandler(mockAgent as any)
|
|
35
|
-
})
|
|
14
|
+
idManager = new IdentityManager()
|
|
15
|
+
vcHandler = new VCHandler()
|
|
36
16
|
|
|
37
17
|
it('should create an ephemeral did:key', async () => {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
expect(did).
|
|
41
|
-
expect(did.did).toMatch(/^did:key:/)
|
|
42
|
-
issuerDid = did.did
|
|
18
|
+
const result = await idManager.createEphemeralDID()
|
|
19
|
+
expect(result).toBeDefined()
|
|
20
|
+
expect(result.did).toMatch(/^did:key:/)
|
|
43
21
|
})
|
|
44
22
|
|
|
45
|
-
it('should
|
|
46
|
-
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
expect(
|
|
23
|
+
it('should resolve a created DID', async () => {
|
|
24
|
+
const created = await idManager.createEphemeralDID()
|
|
25
|
+
const resolved = await idManager.resolveDID(created.did)
|
|
26
|
+
|
|
27
|
+
expect(resolved).toBeDefined()
|
|
28
|
+
expect(resolved.didDocument).toBeDefined()
|
|
29
|
+
expect(resolved.didDocument?.id).toBe(created.did)
|
|
50
30
|
})
|
|
51
31
|
|
|
52
|
-
it('should issue a Verifiable Credential', async () => {
|
|
32
|
+
it('should issue a Verifiable Credential as JWT', async () => {
|
|
33
|
+
const identity = await idManager.createEphemeralDID()
|
|
53
34
|
const claims = { name: 'Test Agent', role: 'Tester' }
|
|
54
|
-
|
|
35
|
+
|
|
36
|
+
const vc = await vcHandler.createCredential(
|
|
37
|
+
identity.did,
|
|
38
|
+
identity.did,
|
|
39
|
+
claims,
|
|
40
|
+
identity.keyPair
|
|
41
|
+
)
|
|
55
42
|
|
|
56
43
|
expect(vc).toBeDefined()
|
|
57
|
-
expect(vc
|
|
58
|
-
|
|
44
|
+
expect(typeof vc).toBe('string')
|
|
45
|
+
|
|
46
|
+
// Should be a valid JWT (3 parts separated by dots)
|
|
47
|
+
const parts = vc.split('.')
|
|
48
|
+
expect(parts.length).toBe(3)
|
|
59
49
|
})
|
|
60
50
|
|
|
61
|
-
it('should verify a Verifiable Credential', async () => {
|
|
51
|
+
it('should verify a valid Verifiable Credential', async () => {
|
|
52
|
+
const identity = await idManager.createEphemeralDID()
|
|
62
53
|
const claims = { name: 'Verified Agent' }
|
|
63
|
-
const vc = await vcHandler.createCredential(issuerDid, subjectDid, claims)
|
|
64
54
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
55
|
+
const vcJwt = await vcHandler.createCredential(
|
|
56
|
+
identity.did,
|
|
57
|
+
identity.did,
|
|
58
|
+
claims,
|
|
59
|
+
identity.keyPair
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
const result = await vcHandler.verifyCredential(vcJwt)
|
|
63
|
+
expect(result).toBe(true)
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it('createCredential should include claims in the VC payload', async () => {
|
|
67
|
+
const identity = await idManager.createEphemeralDID()
|
|
68
|
+
const claims = { walletAddress: '0xABCDEF1234567890' }
|
|
69
|
+
|
|
70
|
+
const vcJwt = await vcHandler.createCredential(
|
|
71
|
+
identity.did,
|
|
72
|
+
identity.did,
|
|
73
|
+
claims,
|
|
74
|
+
identity.keyPair
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
// Decode the JWT payload
|
|
78
|
+
const payload = JSON.parse(Buffer.from(vcJwt.split('.')[1], 'base64').toString())
|
|
79
|
+
expect(payload.vc.credentialSubject.walletAddress).toBe('0xABCDEF1234567890')
|
|
68
80
|
})
|
|
69
81
|
})
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Stateless Property Tests
|
|
5
|
+
*
|
|
6
|
+
* These tests verify that trust-sdk operates without ANY database dependency.
|
|
7
|
+
* No environment variables, no DB connections, no external state.
|
|
8
|
+
*/
|
|
9
|
+
describe('trust-sdk: Stateless Properties', () => {
|
|
10
|
+
it('should create an IdentityManager without any env vars or DB', async () => {
|
|
11
|
+
// Must import without AGENT_SECRET_KEY or DB env vars set
|
|
12
|
+
const { IdentityManager } = await import('../src/identity/did-manager')
|
|
13
|
+
const idManager = new IdentityManager()
|
|
14
|
+
expect(idManager).toBeDefined()
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
it('should create a VCHandler without any env vars or DB', async () => {
|
|
18
|
+
const { VCHandler } = await import('../src/credentials/vc-handler.service')
|
|
19
|
+
const vcHandler = new VCHandler()
|
|
20
|
+
expect(vcHandler).toBeDefined()
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('should create a DID without any database', async () => {
|
|
24
|
+
const { IdentityManager } = await import('../src/identity/did-manager')
|
|
25
|
+
const idManager = new IdentityManager()
|
|
26
|
+
|
|
27
|
+
const result = await idManager.createEphemeralDID()
|
|
28
|
+
expect(result).toBeDefined()
|
|
29
|
+
expect(result.did).toMatch(/^did:key:/)
|
|
30
|
+
// Must return a key pair (or at least the DID string)
|
|
31
|
+
expect(typeof result.did).toBe('string')
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('should resolve a did:key without any database', async () => {
|
|
35
|
+
const { IdentityManager } = await import('../src/identity/did-manager')
|
|
36
|
+
const idManager = new IdentityManager()
|
|
37
|
+
|
|
38
|
+
const created = await idManager.createEphemeralDID()
|
|
39
|
+
const resolved = await idManager.resolveDID(created.did)
|
|
40
|
+
|
|
41
|
+
expect(resolved).toBeDefined()
|
|
42
|
+
expect(resolved.didDocument).toBeDefined()
|
|
43
|
+
expect(resolved.didDocument?.id).toBe(created.did)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('should issue and verify a VC E2E without any database', async () => {
|
|
47
|
+
const { IdentityManager } = await import('../src/identity/did-manager')
|
|
48
|
+
const { VCHandler } = await import('../src/credentials/vc-handler.service')
|
|
49
|
+
|
|
50
|
+
const idManager = new IdentityManager()
|
|
51
|
+
const vcHandler = new VCHandler()
|
|
52
|
+
|
|
53
|
+
// Create a DID
|
|
54
|
+
const identity = await idManager.createEphemeralDID()
|
|
55
|
+
|
|
56
|
+
// Issue a self-signed VC
|
|
57
|
+
const vc = await vcHandler.createCredential(
|
|
58
|
+
identity.did,
|
|
59
|
+
identity.did,
|
|
60
|
+
{ walletAddress: '0x1234567890abcdef' },
|
|
61
|
+
identity.keyPair // pass the signing key
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
expect(vc).toBeDefined()
|
|
65
|
+
expect(typeof vc).toBe('string') // JWT string
|
|
66
|
+
|
|
67
|
+
// Verify the VC
|
|
68
|
+
const isValid = await vcHandler.verifyCredential(vc)
|
|
69
|
+
expect(isValid).toBe(true)
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('should export createResolver from the SDK', async () => {
|
|
73
|
+
const sdk = await import('../src/index')
|
|
74
|
+
expect(sdk.createResolver).toBeDefined()
|
|
75
|
+
expect(typeof sdk.createResolver).toBe('function')
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
it('should NOT export agent or initAgent', async () => {
|
|
79
|
+
const sdk = await import('../src/index')
|
|
80
|
+
expect((sdk as any).agent).toBeUndefined()
|
|
81
|
+
expect((sdk as any).initAgent).toBeUndefined()
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('should import an existing secret key via fromSecretKey (hex string)', async () => {
|
|
85
|
+
const { IdentityManager } = await import('../src/identity/did-manager')
|
|
86
|
+
const idManager = new IdentityManager()
|
|
87
|
+
|
|
88
|
+
// Generate a key first, then re-import it
|
|
89
|
+
const original = await idManager.createEphemeralDID()
|
|
90
|
+
const hexKey = Buffer.from(original.keyPair.secretKey).toString('hex')
|
|
91
|
+
|
|
92
|
+
const imported = await idManager.fromSecretKey(hexKey)
|
|
93
|
+
expect(imported.did).toBe(original.did) // same key = same DID
|
|
94
|
+
expect(imported.keyPair.publicKey).toEqual(original.keyPair.publicKey)
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('should import a 0x-prefixed hex secret key', async () => {
|
|
98
|
+
const { IdentityManager } = await import('../src/identity/did-manager')
|
|
99
|
+
const idManager = new IdentityManager()
|
|
100
|
+
|
|
101
|
+
const original = await idManager.createEphemeralDID()
|
|
102
|
+
const hexKey = '0x' + Buffer.from(original.keyPair.secretKey).toString('hex')
|
|
103
|
+
|
|
104
|
+
const imported = await idManager.fromSecretKey(hexKey)
|
|
105
|
+
expect(imported.did).toBe(original.did)
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
it('should issue and verify a VC using imported key', async () => {
|
|
109
|
+
const { IdentityManager } = await import('../src/identity/did-manager')
|
|
110
|
+
const { VCHandler } = await import('../src/credentials/vc-handler.service')
|
|
111
|
+
|
|
112
|
+
const idManager = new IdentityManager()
|
|
113
|
+
const vcHandler = new VCHandler()
|
|
114
|
+
|
|
115
|
+
// Simulate a bot with a fixed key
|
|
116
|
+
const original = await idManager.createEphemeralDID()
|
|
117
|
+
const hexKey = Buffer.from(original.keyPair.secretKey).toString('hex')
|
|
118
|
+
|
|
119
|
+
// Import the key (as a bot would)
|
|
120
|
+
const identity = await idManager.fromSecretKey(hexKey)
|
|
121
|
+
|
|
122
|
+
// Create and verify VC
|
|
123
|
+
const vc = await vcHandler.createCredential(
|
|
124
|
+
identity.did,
|
|
125
|
+
identity.did,
|
|
126
|
+
{ walletAddress: '0xBotWallet123' },
|
|
127
|
+
identity.keyPair
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
const isValid = await vcHandler.verifyCredential(vc)
|
|
131
|
+
expect(isValid).toBe(true)
|
|
132
|
+
})
|
|
133
|
+
})
|