Skip to content

Commit c02f6e2

Browse files
authored
DCKA-5494 normalize cheqd DID documents from universal resolver so BBS+ keys are verifiable (#538)
The universal resolver fallback returns cheqd offchain (BBS+) keys as JSON-stringified blobs in assertionMethod instead of proper objects in verificationMethod, leaving publicKeyBase58 unreachable for BBS+ verification. Normalize resolved documents before caching to lift these inline keys into verificationMethod, matching the on-chain resolver behaviour.
1 parent 0864440 commit c02f6e2

3 files changed

Lines changed: 132 additions & 3 deletions

File tree

integration-tests/did-resolution.test.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {blockchainService} from '@docknetwork/wallet-sdk-wasm/src/services/blockchain';
2+
import {BLOCKCHAIN_NETWORKS} from '@docknetwork/wallet-sdk-wasm/src/modules/network-manager';
23
import {
34
getWallet,
45
getDIDProvider,
@@ -47,3 +48,57 @@ describe('DID resolution', () => {
4748
expect(cachedEntry.id).toEqual('did:cheqd:testnet:c0890f1c-c7bb-4ea6-be7a-8c31404743b7');
4849
});
4950
});
51+
52+
describe('BBS+ assertion keys in resolved DID document', () => {
53+
const did = 'did:cheqd:testnet:c0890f1c-c7bb-4ea6-be7a-8c31404743b7';
54+
const bbsKeyId = `${did}#keys-2`;
55+
56+
afterAll(async () => {
57+
await blockchainService.disconnect();
58+
});
59+
60+
// BBS+ key must be a proper verificationMethod object (so jsonld.frame can
61+
// read publicKeyBase58), and assertionMethod must hold only string refs.
62+
function expectBBSKeyReachable(doc) {
63+
const bbsMethod = (doc.verificationMethod ?? []).find(
64+
method => method?.id === bbsKeyId,
65+
);
66+
67+
expect(bbsMethod).toBeDefined();
68+
expect(bbsMethod.type).toEqual('Bls12381BBSVerificationKeyDock2023');
69+
expect(typeof bbsMethod.publicKeyBase58).toEqual('string');
70+
71+
for (const entry of doc.assertionMethod ?? []) {
72+
expect(typeof entry).toEqual('string');
73+
expect(() => JSON.parse(entry)).toThrow();
74+
}
75+
}
76+
77+
it('should expose BBS+ assertion keys via the fallback (universal) resolver', async () => {
78+
// No blockchain: the router falls back to the universal resolver.
79+
const fallbackResolver = blockchainService.createDIDResolver(false);
80+
81+
await fallbackResolver.clearCache(did);
82+
83+
const doc = await fallbackResolver.resolve(did);
84+
85+
expectBBSKeyReachable(doc);
86+
});
87+
88+
it('should expose BBS+ assertion keys via the on-chain (blockchain) resolver', async () => {
89+
// Control: the on-chain path normalises BBS+ keys, so it should pass for
90+
// the same DID. Connect inline (not beforeAll) to avoid a stray teardown
91+
// from another describe block disconnecting between setup and resolve.
92+
await blockchainService.init({
93+
cheqdApiUrl: BLOCKCHAIN_NETWORKS.testnet.cheqdApiUrl,
94+
networkId: 'testnet',
95+
});
96+
97+
const onChainResolver = blockchainService.createDIDResolver(true);
98+
await onChainResolver.clearCache(did);
99+
100+
const doc = await onChainResolver.resolve(did);
101+
102+
expectBBSKeyReachable(doc);
103+
}, 30000);
104+
});

packages/wasm/src/services/blockchain/cached-did-resolver.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { storageService } from '../storage';
2+
import { normalizeDIDDocument } from './normalize-did-document';
23

34
interface CacheEntry {
45
value: any;
@@ -76,7 +77,7 @@ export class CachedDIDResolver {
7677
}
7778

7879
console.log('Cache miss, resolving:', did);
79-
const result = await this.router.resolve(did);
80+
const result = normalizeDIDDocument(await this.router.resolve(did));
8081

8182
await this.setCacheEntry(result.id, {
8283
value: result,
@@ -90,8 +91,8 @@ export class CachedDIDResolver {
9091
private async refreshInBackground(did: string): Promise<void> {
9192
try {
9293
console.log('Refreshing cache in background for:', did);
93-
const result = await this.router.resolve(did);
94-
94+
const result = normalizeDIDDocument(await this.router.resolve(did));
95+
9596
await this.setCacheEntry(did, {
9697
value: result,
9798
id: did,
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// The universal resolver returns cheqd offchain (BBS+) keys as stringified
2+
// blobs in the relationship arrays instead of objects in verificationMethod,
3+
// leaving publicKeyBase58 unreachable. Lift them out, matching the on-chain
4+
// resolver's CheqdDIDDocument.toDIDDocument behaviour.
5+
6+
const RELATIONSHIP_PROPS = [
7+
'assertionMethod',
8+
'authentication',
9+
'capabilityInvocation',
10+
'capabilityDelegation',
11+
'keyAgreement',
12+
] as const;
13+
14+
function parseInlineVerificationMethod(entry: string): any | null {
15+
let parsed: unknown = entry;
16+
17+
// cheqd offchain keys are double-JSON-stringified, so unwrap up to twice.
18+
for (let i = 0; i < 2 && typeof parsed === 'string'; i++) {
19+
try {
20+
parsed = JSON.parse(parsed);
21+
} catch {
22+
return null;
23+
}
24+
}
25+
26+
if (parsed && typeof parsed === 'object' && typeof (parsed as any).id === 'string') {
27+
return parsed;
28+
}
29+
30+
return null;
31+
}
32+
33+
export function normalizeDIDDocument(doc: any): any {
34+
if (!doc || typeof doc !== 'object') {
35+
return doc;
36+
}
37+
38+
const liftedMethods: any[] = [];
39+
40+
for (const prop of RELATIONSHIP_PROPS) {
41+
if (!Array.isArray(doc[prop])) {
42+
continue;
43+
}
44+
45+
doc[prop] = doc[prop].map((entry: any) => {
46+
if (typeof entry !== 'string') {
47+
return entry;
48+
}
49+
50+
const method = parseInlineVerificationMethod(entry);
51+
if (!method) {
52+
return entry;
53+
}
54+
55+
liftedMethods.push(method);
56+
return method.id;
57+
});
58+
}
59+
60+
if (liftedMethods.length) {
61+
const existing = Array.isArray(doc.verificationMethod)
62+
? doc.verificationMethod
63+
: [];
64+
const existingIds = new Set(existing.map((method: any) => method?.id));
65+
const newMethods = liftedMethods.filter(
66+
method => !existingIds.has(method.id),
67+
);
68+
69+
doc.verificationMethod = [...existing, ...newMethods];
70+
}
71+
72+
return doc;
73+
}

0 commit comments

Comments
 (0)