Skip to content

Commit 388d7a9

Browse files
committed
Add stubs file around derive.
1 parent 74c5031 commit 388d7a9

File tree

2 files changed

+250
-42
lines changed

2 files changed

+250
-42
lines changed

tests/suites/proxies.js

Lines changed: 9 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,8 @@
22
* Copyright 2024 Digital Bazaar, Inc.
33
* SPDX-License-Identifier: BSD-3-Clause
44
*/
5-
import * as base64url from 'base64url-universal';
6-
import * as cborg from 'cborg';
75
import crypto from 'node:crypto';
8-
6+
import {stubDerive} from './stubs.js';
97
/**
108
* Creates a proxy of an object with stubs.
119
*
@@ -154,44 +152,13 @@ async function _canonizeProof(proof, {
154152
return cryptosuite.canonize(proof, c14nOptions);
155153
}
156154

157-
// ecdsa-sd-2023 method that uses invalid cbor tags
158-
export function serializeDisclosureProofValue({
159-
baseSignature, publicKey, signatures, labelMap, mandatoryIndexes
160-
} = {}) {
161-
const CBOR_PREFIX_DERIVED = new Uint8Array([0xd9, 0x5d, 0x01]);
162-
// encode as multibase (base64url no pad) CBOR
163-
const payload = [
164-
// Uint8Array
165-
baseSignature,
166-
// Uint8Array
167-
publicKey,
168-
// array of Uint8Arrays
169-
signatures,
170-
// Map of strings => strings compressed to ints => Uint8Arrays
171-
_compressLabelMap(labelMap),
172-
// array of numbers
173-
mandatoryIndexes
174-
];
175-
const cbor = _concatBuffers([
176-
CBOR_PREFIX_DERIVED, cborg.encode(payload, {useMaps: true})
177-
]);
178-
return `u${base64url.encode(cbor)}`;
179-
}
180-
181-
function _concatBuffers(buffers) {
182-
const bytes = new Uint8Array(buffers.reduce((acc, b) => acc + b.length, 0));
183-
let offset = 0;
184-
for(const b of buffers) {
185-
bytes.set(b, offset);
186-
offset += b.length;
187-
}
188-
return bytes;
189-
}
190-
191-
function _compressLabelMap(labelMap) {
192-
const map = new Map();
193-
for(const [k, v] of labelMap.entries()) {
194-
map.set(parseInt(k.slice(4), 10), base64url.decode(v.slice(1)));
155+
export function invalidCborTagProxy(suite) {
156+
const stubs = {derive: stubDerive};
157+
if(suite._cryptosuite) {
158+
suite._cryptosuite = createProxy({
159+
original: suite._cryptosuite,
160+
stubs
161+
});
195162
}
196-
return map;
163+
return suite;
197164
}

tests/suites/stubs.js

Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
/*!
2+
* Copyright 2024 Digital Bazaar, Inc.
3+
* SPDX-License-Identifier: BSD-3-Clause
4+
*/
5+
import * as base64url from 'base64url-universal';
6+
import * as cborg from 'cborg';
7+
import {
8+
canonicalize,
9+
canonicalizeAndGroup,
10+
createHmac,
11+
createHmacIdLabelMapFunction,
12+
selectJsonLd,
13+
stripBlankNodePrefixes
14+
} from '@digitalbazaar/di-sd-primitives';
15+
import {Token, Type} from 'cborg';
16+
17+
const CBOR_PREFIX_BASE = new Uint8Array([0xd9, 0x5d, 0x00]);
18+
const CBOR_PREFIX_DERIVED = new Uint8Array([0xd9, 0x5d, 0x01]);
19+
// CBOR decoder for implementations that use tag 64 for Uint8Array instead
20+
// of byte string major type 2
21+
const TAGS = [];
22+
TAGS[64] = bytes => bytes;
23+
24+
export async function stubDerive({
25+
cryptosuite, document, proofSet,
26+
documentLoader, dataIntegrityProof
27+
}) {
28+
// find matching base `proof` in `proofSet`
29+
const {options: {proofId}} = cryptosuite;
30+
const baseProof = await _findProof({proofId, proofSet, dataIntegrityProof});
31+
// generate data for disclosure
32+
const {
33+
baseSignature, publicKey, signatures, labelMap, mandatoryIndexes, revealDoc
34+
} = await _createDisclosureData(
35+
{cryptosuite, document, proof: baseProof, documentLoader});
36+
37+
// create new disclosure proof
38+
const newProof = {...baseProof};
39+
newProof.proofValue = await invalidSerializeDisclosureProofValue(
40+
{baseSignature, publicKey, signatures, labelMap, mandatoryIndexes});
41+
42+
// attach proof to reveal doc w/o context
43+
delete newProof['@context'];
44+
revealDoc.proof = newProof;
45+
return revealDoc;
46+
}
47+
48+
// ecdsa-sd-2023 method that uses invalid cbor tags
49+
function invalidSerializeDisclosureProofValue({
50+
baseSignature, publicKey, signatures, labelMap, mandatoryIndexes
51+
} = {}) {
52+
const typeEncoders = {
53+
Uint8Array(uint8Array) {
54+
return [
55+
new Token(Type.tag, 2),
56+
new Token(Type.bytes, uint8Array.map(b => b + 1))
57+
];
58+
}
59+
};
60+
// encode as multibase (base64url no pad) CBOR
61+
const payload = [
62+
// Uint8Array
63+
baseSignature,
64+
// Uint8Array
65+
publicKey,
66+
// array of Uint8Arrays
67+
signatures,
68+
// Map of strings => strings compressed to ints => Uint8Arrays
69+
_compressLabelMap(labelMap),
70+
// array of numbers
71+
mandatoryIndexes
72+
];
73+
const cbor = _concatBuffers([
74+
CBOR_PREFIX_DERIVED, cborg.encode(payload, {useMaps: true, typeEncoders})
75+
]);
76+
return `u${base64url.encode(cbor)}`;
77+
}
78+
79+
async function _createDisclosureData({
80+
cryptosuite, document, proof, documentLoader
81+
}) {
82+
83+
// 1. Parse base `proof` to get parameters for disclosure proof.
84+
const {
85+
baseSignature, publicKey, hmacKey, signatures, mandatoryPointers
86+
} = await parseBaseProofValue({proof});
87+
88+
// 2. Ensure mandatory and / or selective data will be disclosed.
89+
const {selectivePointers = []} = cryptosuite.options;
90+
if(!(mandatoryPointers?.length > 0 || selectivePointers?.length > 0)) {
91+
throw new Error('Nothing selected for disclosure.');
92+
}
93+
94+
// 3. Create HMAC label replacement function from `hmacKey` to randomize
95+
// bnode identifiers.
96+
const hmac = await createHmac({key: hmacKey});
97+
const labelMapFactoryFunction = createHmacIdLabelMapFunction({hmac});
98+
99+
// 4. Canonicalize document with randomized bnode labels and group N-Quads
100+
// by mandatory, selective, and combined pointers.
101+
const options = {documentLoader};
102+
const combinedPointers = mandatoryPointers.concat(selectivePointers);
103+
const {
104+
groups: {
105+
mandatory: mandatoryGroup,
106+
selective: selectiveGroup,
107+
combined: combinedGroup,
108+
},
109+
labelMap
110+
} = await canonicalizeAndGroup({
111+
document,
112+
labelMapFactoryFunction,
113+
groups: {
114+
mandatory: mandatoryPointers,
115+
selective: selectivePointers,
116+
combined: combinedPointers
117+
},
118+
options
119+
});
120+
121+
// 5. Converting absolute indexes of mandatory N-Quads to relative indexes in
122+
// the combined output to be revealed.
123+
let relativeIndex = 0;
124+
const mandatoryIndexes = [];
125+
for(const absoluteIndex of combinedGroup.matching.keys()) {
126+
if(mandatoryGroup.matching.has(absoluteIndex)) {
127+
mandatoryIndexes.push(relativeIndex);
128+
}
129+
relativeIndex++;
130+
}
131+
132+
// 6. Filter signatures from `baseProof` to those matching non-mandatory
133+
// absolute indexes and shifting by any absolute mandatory indexes that
134+
// occur before each entry.
135+
let index = 0;
136+
const filteredSignatures = signatures.filter(() => {
137+
while(mandatoryGroup.matching.has(index)) {
138+
index++;
139+
}
140+
return selectiveGroup.matching.has(index++);
141+
});
142+
143+
// 7. Produce reveal document using combination of mandatory and selective
144+
// pointers.
145+
const revealDoc = selectJsonLd({document, pointers: combinedPointers});
146+
147+
// 8. Canonicalize deskolemized N-Quads for the combined group to generate
148+
// the canonical blank node labels a verifier will see.
149+
let canonicalIdMap = new Map();
150+
await canonicalize(
151+
combinedGroup.deskolemizedNQuads.join(''),
152+
{...options, inputFormat: 'application/n-quads', canonicalIdMap});
153+
// implementation-specific bnode prefix fix
154+
canonicalIdMap = stripBlankNodePrefixes(canonicalIdMap);
155+
156+
// 9. Produce a blank node label map from the canonical blank node labels
157+
// the verifier will see to the HMAC labels.
158+
const verifierLabelMap = new Map();
159+
for(const [inputLabel, verifierLabel] of canonicalIdMap) {
160+
verifierLabelMap.set(verifierLabel, labelMap.get(inputLabel));
161+
}
162+
163+
// 10. Return data used by cryptosuite to disclose.
164+
return {
165+
baseSignature, publicKey, signatures: filteredSignatures,
166+
labelMap: verifierLabelMap, mandatoryIndexes,
167+
revealDoc
168+
};
169+
}
170+
171+
// ecdsa-sd-2023 helper function
172+
function _concatBuffers(buffers) {
173+
const bytes = new Uint8Array(buffers.reduce((acc, b) => acc + b.length, 0));
174+
let offset = 0;
175+
for(const b of buffers) {
176+
bytes.set(b, offset);
177+
offset += b.length;
178+
}
179+
return bytes;
180+
}
181+
182+
// ecdsa-sd-2023 helper function
183+
function _compressLabelMap(labelMap) {
184+
const map = new Map();
185+
for(const [k, v] of labelMap.entries()) {
186+
map.set(parseInt(k.slice(4), 10), base64url.decode(v.slice(1)));
187+
}
188+
return map;
189+
}
190+
191+
// ecdsa-sd-2023 proofValue function
192+
function parseBaseProofValue({proof} = {}) {
193+
try {
194+
// decode from base64url
195+
const proofValue = base64url.decode(proof.proofValue.slice(1));
196+
197+
const payload = proofValue.subarray(CBOR_PREFIX_BASE.length);
198+
const [
199+
baseSignature,
200+
publicKey,
201+
hmacKey,
202+
signatures,
203+
mandatoryPointers
204+
] = cborg.decode(payload, {useMaps: true, tags: TAGS});
205+
206+
const params = {
207+
baseSignature, publicKey, hmacKey, signatures, mandatoryPointers
208+
};
209+
return params;
210+
} catch(e) {
211+
const err = new TypeError(
212+
'The proof does not include a valid "proofValue" property.');
213+
err.cause = e;
214+
throw err;
215+
}
216+
}
217+
218+
// ecdsa-sd-2023
219+
async function _findProof({proofId, proofSet, dataIntegrityProof}) {
220+
let proof;
221+
if(proofId) {
222+
proof = proofSet.find(p => p.id === proofId);
223+
} else {
224+
// no `proofId` given, so see if a single matching proof exists
225+
for(const p of proofSet) {
226+
if(await dataIntegrityProof.matchProof({proof: p})) {
227+
if(proof) {
228+
// already matched
229+
throw new Error(
230+
'Multiple matching proofs; a "proofId" must be specified.');
231+
}
232+
proof = p;
233+
}
234+
}
235+
}
236+
if(!proof) {
237+
throw new Error(
238+
'No matching base proof found from which to derive a disclosure proof.');
239+
}
240+
return proof;
241+
}

0 commit comments

Comments
 (0)