Skip to content

Commit 43e77b2

Browse files
committed
Fix a bug and update docs
Signed-off-by: lovesh <lovesh.bond@gmail.com>
1 parent 0d0360b commit 43e77b2

File tree

14 files changed

+190
-96
lines changed

14 files changed

+190
-96
lines changed

README.md

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -899,8 +899,8 @@ should be used while encryption, decryption, proving and verification (as shown
899899
For signers (issuers of credentials), it's important to encode attributes that need to be verifiably encoded using a reversible
900900
encoding as the decryption might happen much later than the proof verification and thus the decryptor should be able to independently
901901
recover the actual attributes. This situation is different from selective disclosure where the actual attributes are given to the
902-
verifier who can then encode the attributes before verifying the proof. One such pair of functions are `Signature.reversibleEncodeStringMessageForSigning`
903-
and `Signature.reversibleDecodeStringMessageForSigning` and you can see its use in the above-mentioned test.
902+
verifier who can then encode the attributes before verifying the proof. One such pair of functions are `Signature.reversibleEncodeStringForSigning`
903+
and `Signature.reversibleDecodeStringForSigning` and you can see its use in the above-mentioned test.
904904

905905
For creating the proof of knowledge of the BBS+ signature and verifiably encrypting an attribute, the prover creates the following 2 statements.
906906

@@ -1228,9 +1228,37 @@ For complete example, see [these tests](./tests/composite-proofs/bound-check.spe
12281228

12291229
### Working with messages as JS objects
12301230

1231-
The above interfaces have been found to be a bit difficult to work with when signing messages that are represented as JS objects.
1232-
[Here](./src/sign-verify-js-objs.ts) are some [utilities](./src/bbs-plus/encoder.ts) to make this task a bit easier. [The tests here](tests/composite-proofs/msg-js-obj) contain
1233-
plenty of examples.
1231+
The above interfaces have been found to be a bit difficult to work with when signing messages/credentials that are represented as JS objects like
1232+
1233+
```json
1234+
{
1235+
"fname": "John",
1236+
"lname": "Smith",
1237+
"sensitive": {
1238+
"secret": "my-secret-that-wont-tell-anyone",
1239+
"email": "john.smith@example.com",
1240+
"SSN": "123-456789-0",
1241+
"user-id": "user:123-xyz-#"
1242+
},
1243+
"location": {
1244+
"country": "USA",
1245+
"city": "New York"
1246+
},
1247+
"timeOfBirth": 1662010849619,
1248+
"physical": {
1249+
"height": 181.5,
1250+
"weight": 210,
1251+
"BMI": 23.25
1252+
},
1253+
"score": -13.5
1254+
}
1255+
```
1256+
1257+
[Here](./src/sign-verify-js-objs.ts) are some utilities to make this task a bit easier. The idea is to flatten the JSON, sort the keys alphabetically
1258+
to have a list with deterministic order and then use the [encoder](./src/bbs-plus/encoder.ts) to encode each value as a field element (a number between 0 and another large number).
1259+
The encoder can be configured to use different encoding functions for different keys to convert values from different types
1260+
like string, positive or negative integers or decimal numbers to field elements.
1261+
[The tests here](tests/composite-proofs/msg-js-obj) contain plenty of examples.
12341262

12351263

12361264
### Writing predicates in Circom

package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@docknetwork/crypto-wasm-ts",
3-
"version": "0.23.0",
3+
"version": "0.24.0",
44
"description": "Typescript abstractions over Dock's Rust crypto library's WASM wrapper",
55
"homepage": "https://github.com/docknetwork/crypto-wasm-ts",
66
"main": "lib/index.js",
@@ -21,9 +21,10 @@
2121
"lib": "lib"
2222
},
2323
"dependencies": {
24-
"@docknetwork/crypto-wasm": "0.14.0",
24+
"@docknetwork/crypto-wasm": "0.15.0",
25+
"@types/flat": "^5.0.2",
2526
"flat": "^5.0.2",
26-
"@types/flat": "^5.0.2"
27+
"lzutf8": "^0.6.3"
2728
},
2829
"devDependencies": {
2930
"@types/jest": "24.0.18",

src/accumulator/accumulator.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -861,10 +861,17 @@ export class UniversalAccumulator extends Accumulator {
861861
await this.ensureAbsenceOfBatch(nonMembers, state);
862862
const sk = this.getSecretKey(secretKey);
863863
const params_ = this.getParams(params);
864-
const members = await state.elements();
864+
865+
// store multiple `d`s for each non-member, outer index of array is the non-member index
866+
const dsForAll: Uint8Array[][] = new Array<Uint8Array[]>(nonMembers.length);
867+
for (let i = 0; i < nonMembers.length; i++) {
868+
dsForAll[i] = new Array<Uint8Array>();
869+
}
870+
865871
let currentBatch: Uint8Array[] = [];
866-
// store multiple `d`s for each non-member
867-
const dsForAll: Uint8Array[][] = new Array(nonMembers.length);
872+
873+
// Iterate over all members of the accumulator
874+
const members = await state.elements();
868875
for (const member of members) {
869876
currentBatch.push(member);
870877
if (currentBatch.length == batchSize) {
@@ -875,12 +882,13 @@ export class UniversalAccumulator extends Accumulator {
875882
currentBatch = [];
876883
}
877884
}
885+
878886
if (currentBatch.length > 0) {
879887
for (let i = 0; i < nonMembers.length; i++) {
880888
dsForAll[i].push(universalAccumulatorComputeD(nonMembers[i], currentBatch));
881889
}
882890
}
883-
const ds: Uint8Array[] = new Array(nonMembers.length);
891+
const ds: Uint8Array[] = new Array<Uint8Array>(nonMembers.length);
884892
for (let i = 0; i < nonMembers.length; i++) {
885893
// Combine `d`s corresponding to each non-member
886894
ds[i] = universalAccumulatorCombineMultipleD(dsForAll[i]);

src/bbs-plus/signature.ts

Lines changed: 37 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,14 @@ import {
1212
} from '@docknetwork/crypto-wasm';
1313
import { BBSPlusPublicKeyG2, BBSPlusSecretKey } from './keys';
1414
import { BytearrayWrapper } from '../bytearray-wrapper';
15+
import LZUTF8 from 'lzutf8';
1516

1617
export abstract class Signature extends BytearrayWrapper {
18+
// The field element size is 32 bytes so the maximum byte size of encoded message must be 32.
19+
static readonly maxEncodedLength = 32;
20+
static readonly textEncoder = new TextEncoder();
21+
static readonly textDecoder = new TextDecoder();
22+
1723
/**
1824
* This is an irreversible encoding as a hash function is used to convert a message of
1925
* arbitrary length to a fixed length encoding.
@@ -39,18 +45,18 @@ export abstract class Signature extends BytearrayWrapper {
3945
* decryption and thus the decryptor must be able to decrypt it independently. This is different from selective disclosure
4046
* where the verifier can check that the revealed message is same as the encoded one before even verifying the proof.
4147
* @param message - utf-8 string of at most 32 bytes
48+
* @param compress - whether to compress the text before encoding to bytes. Compression might not always help as things
49+
* like public keys, DIDs, UUIDs, etc. are designed to be random and thus won't be compressed
4250
*/
43-
static reversibleEncodeStringMessageForSigning(message: string): Uint8Array {
44-
const encoder = new TextEncoder();
45-
const bytes = encoder.encode(message);
46-
const maxLength = 32;
47-
if (bytes.length > maxLength) {
48-
throw new Error(`Expects a string with at most ${maxLength} bytes`);
51+
static reversibleEncodeStringForSigning(message: string, compress = false): Uint8Array {
52+
const bytes = compress ? LZUTF8.compress(message) : Signature.textEncoder.encode(message);
53+
if (bytes.length > Signature.maxEncodedLength) {
54+
throw new Error(`Expects a string with at most ${Signature.maxEncodedLength} bytes`);
4955
}
5056
// Create a little-endian representation
51-
const fieldElementBytes = new Uint8Array(maxLength);
57+
const fieldElementBytes = new Uint8Array(Signature.maxEncodedLength);
5258
fieldElementBytes.set(bytes);
53-
fieldElementBytes.set(new Uint8Array(maxLength - bytes.length), bytes.length);
59+
fieldElementBytes.set(new Uint8Array(Signature.maxEncodedLength - bytes.length), bytes.length);
5460
return fieldElementAsBytes(fieldElementBytes, true);
5561
}
5662

@@ -60,23 +66,33 @@ export abstract class Signature extends BytearrayWrapper {
6066
* occurrence of a null characters (UTF-16 code unit 0) so if the encoded (using `this.reversibleEncodeStringMessageForSigning`)
6167
* string also had a null then the decoded string will be different from it.
6268
* @param message
69+
* @param decompress - whether to decompress the bytes before converting to a string
6370
*/
64-
static reversibleDecodeStringMessageForSigning(message: Uint8Array): string {
65-
const maxLength = 32;
66-
if (message.length > maxLength) {
67-
throw new Error(`Expects a message with at most ${maxLength} bytes`);
71+
static reversibleDecodeStringForSigning(message: Uint8Array, decompress = false): string {
72+
if (message.length > Signature.maxEncodedLength) {
73+
throw new Error(`Expects a message with at most ${Signature.maxEncodedLength} bytes`);
6874
}
69-
const decoder = new TextDecoder();
70-
const decoded = decoder.decode(message);
71-
const chars: string[] = [];
72-
for (let i = 0; i < maxLength; i++) {
73-
// If a null character found then stop looking further
74-
if (decoded.charCodeAt(i) == 0) {
75-
break;
75+
if (decompress) {
76+
const strippedMsg = message.slice(0, message.indexOf(0));
77+
const str = LZUTF8.decompress(strippedMsg) as string;
78+
if (str.length > Signature.maxEncodedLength) {
79+
throw new Error(
80+
`Expects a message that can be decompressed to at most ${Signature.maxEncodedLength} bytes but decompressed size was ${str.length}`
81+
);
82+
}
83+
return str;
84+
} else {
85+
const decoded = Signature.textDecoder.decode(message);
86+
const chars: string[] = [];
87+
for (let i = 0; i < Signature.maxEncodedLength; i++) {
88+
// If a null character found then stop looking further
89+
if (decoded.charCodeAt(i) == 0) {
90+
break;
91+
}
92+
chars.push(decoded.charAt(i));
7693
}
77-
chars.push(decoded.charAt(i));
94+
return chars.join('');
7895
}
79-
return chars.join('');
8096
}
8197
}
8298

src/sign-verify-js-objs.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ export function encodeRevealedMsgs(
212212
encoder: Encoder
213213
): Map<number, Uint8Array> {
214214
const revealed = new Map<number, Uint8Array>();
215-
const names = Object.keys(flatten(msgStructure) as object).sort();
215+
const names = Object.keys(flatten(msgStructure)).sort();
216216
const flattenedRevealed = flatten(revealedMsgsRaw) as object;
217217
Object.entries(flattenedRevealed).forEach(([n, v]) => {
218218
const i = names.indexOf(n);
@@ -297,7 +297,7 @@ export function blindSignMessageObject(
297297
labelOrParams: Uint8Array | SignatureParamsG1,
298298
encoder: Encoder
299299
): BlindSignedMessages {
300-
const flattenedAllNames = Object.keys(flatten(msgStructure) as object).sort();
300+
const flattenedAllNames = Object.keys(flatten(msgStructure)).sort();
301301
const [flattenedUnblindedNames, encodedValues] = encoder.encodeMessageObject(knownMessages);
302302

303303
const knownMessagesEncoded = new Map<number, Uint8Array>();

tests/accumulator.spec.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -218,14 +218,35 @@ describe('Accumulators type', () => {
218218
const params = UniversalAccumulator.generateParams();
219219
const keypair = UniversalAccumulator.generateKeypair(params);
220220
const store = new InMemoryInitialElementsStore();
221-
const accumulator1 = await UniversalAccumulator.initialize(20, params, keypair.secretKey, store);
221+
const maxSize = 20;
222+
const accumulator1 = await UniversalAccumulator.initialize(maxSize, params, keypair.secretKey, store);
222223

223224
const fixed = UniversalAccumulator.fixedInitialElements();
224-
expect(store.store.size).toEqual(20 + fixed.length + 1);
225+
expect(store.store.size).toEqual(maxSize + fixed.length + 1);
225226
for (const i of fixed) {
226227
await expect(store.has(i)).resolves.toEqual(true);
227228
}
228229
const state1 = new InMemoryUniversalState();
229230
await runCommonTests(keypair, params, accumulator1, state1, store);
231+
232+
const nm1 = Accumulator.encodePositiveNumberAsAccumulatorMember(500);
233+
const nm1Wit = await accumulator1.nonMembershipWitness(nm1, state1, keypair.secretKey, params, store, 2);
234+
235+
let tempAccumulator = getAccum(accumulator1) as UniversalAccumulator;
236+
expect(tempAccumulator.verifyNonMembershipWitness(nm1, nm1Wit, keypair.publicKey, params)).toEqual(true);
237+
238+
const nm2 = Accumulator.encodePositiveNumberAsAccumulatorMember(501);
239+
const nm3 = Accumulator.encodePositiveNumberAsAccumulatorMember(502);
240+
241+
const [nm2Wit, nm3Wit] = await accumulator1.nonMembershipWitnessesForBatch(
242+
[nm2, nm3],
243+
state1,
244+
keypair.secretKey,
245+
params,
246+
store,
247+
3
248+
);
249+
expect(tempAccumulator.verifyNonMembershipWitness(nm2, nm2Wit, keypair.publicKey, params)).toEqual(true);
250+
expect(tempAccumulator.verifyNonMembershipWitness(nm3, nm3Wit, keypair.publicKey, params)).toEqual(true);
230251
});
231252
});

tests/bbs-plus.spec.ts

Lines changed: 41 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -222,46 +222,51 @@ describe('BBS+ signature', () => {
222222
});
223223

224224
it('should support reversible encoding', () => {
225-
const messages = [
226-
'John Jacob Smith Sr.',
227-
'San Francisco, California',
228-
'john.jacob.smith.1971@gmail.com',
229-
'+1 123-4567890009',
230-
'user-id:1234567890012134'
231-
];
232-
const count = messages.length;
233-
const encodedMessages = new Array<Uint8Array>(5);
234-
for (let i = 0; i < count; i++) {
235-
encodedMessages[i] = SignatureG1.reversibleEncodeStringMessageForSigning(messages[i]);
236-
const decoded = SignatureG1.reversibleDecodeStringMessageForSigning(encodedMessages[i]);
237-
expect(decoded).toEqual(messages[i]);
238-
}
239-
const params = SignatureParamsG1.generate(count);
240-
const keypair = KeypairG2.generate(params);
241-
const sig = SignatureG1.generate(encodedMessages, keypair.secretKey, params, false);
242-
expect(sig.verify(encodedMessages, keypair.publicKey, params, false).verified).toEqual(true);
225+
function check(compress: boolean) {
226+
const messages = [
227+
'John Jacob Smith Sr.',
228+
'San Francisco, California',
229+
'john.jacob.smith.1971@gmail.com',
230+
'+1 123-4567890009',
231+
'user-id:1234567890012134'
232+
];
233+
const count = messages.length;
234+
const encodedMessages = new Array<Uint8Array>(5);
235+
for (let i = 0; i < count; i++) {
236+
encodedMessages[i] = SignatureG1.reversibleEncodeStringForSigning(messages[i], compress);
237+
const decoded = SignatureG1.reversibleDecodeStringForSigning(encodedMessages[i], compress);
238+
expect(decoded).toEqual(messages[i]);
239+
}
240+
const params = SignatureParamsG1.generate(count);
241+
const keypair = KeypairG2.generate(params);
242+
const sig = SignatureG1.generate(encodedMessages, keypair.secretKey, params, false);
243+
expect(sig.verify(encodedMessages, keypair.publicKey, params, false).verified).toEqual(true);
244+
245+
// Reveal all messages! This is done for testing purposes only.
246+
let revealed: Set<number> = new Set();
247+
for (let i = 0; i < count; i++) {
248+
revealed.add(i);
249+
}
243250

244-
// Reveal all messages! This is done for testing purposes only.
245-
let revealed: Set<number> = new Set();
246-
for (let i = 0; i < count; i++) {
247-
revealed.add(i);
248-
}
251+
const [revealedMsgs] = getRevealedUnrevealed(encodedMessages, revealed);
252+
const protocol = PoKSigProtocol.initialize(encodedMessages, sig, params, false, undefined, revealed);
253+
const challengeContributionP = protocol.challengeContribution(params, true, revealedMsgs);
254+
const challengeProver = bytesToChallenge(challengeContributionP);
255+
const proof = protocol.generateProof(challengeProver);
249256

250-
const [revealedMsgs, unrevealedMsgs] = getRevealedUnrevealed(encodedMessages, revealed);
251-
const protocol = PoKSigProtocol.initialize(encodedMessages, sig, params, false, undefined, revealed);
252-
const challengeContributionP = protocol.challengeContribution(params, true, revealedMsgs);
253-
const challengeProver = bytesToChallenge(challengeContributionP);
254-
const proof = protocol.generateProof(challengeProver);
255-
256-
const challengeContributionV = proof.challengeContribution(params, true, revealedMsgs);
257-
const challengeVerifier = bytesToChallenge(challengeContributionV);
257+
const challengeContributionV = proof.challengeContribution(params, true, revealedMsgs);
258+
const challengeVerifier = bytesToChallenge(challengeContributionV);
258259

259-
expect(challengeProver).toEqual(challengeVerifier);
260+
expect(challengeProver).toEqual(challengeVerifier);
260261

261-
expect(proof.verify(challengeVerifier, keypair.publicKey, params, false, revealedMsgs).verified).toEqual(true);
262-
for (let i = 0; i < count; i++) {
263-
const decoded = SignatureG1.reversibleDecodeStringMessageForSigning(revealedMsgs.get(i) as Uint8Array);
264-
expect(decoded).toEqual(messages[i]);
262+
expect(proof.verify(challengeVerifier, keypair.publicKey, params, false, revealedMsgs).verified).toEqual(true);
263+
for (let i = 0; i < count; i++) {
264+
const decoded = SignatureG1.reversibleDecodeStringForSigning(revealedMsgs.get(i) as Uint8Array);
265+
expect(decoded).toEqual(messages[i]);
266+
}
265267
}
268+
269+
check(false);
270+
check(true);
266271
});
267272
});

tests/composite-proofs/msg-js-obj/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,11 @@ encoders.set('lessSensitive.department.location.geo.long', Encoder.decimalNumber
3131

3232
encoders.set('SSN', (v: unknown) => {
3333
// @ts-ignore
34-
return SignatureG1.reversibleEncodeStringMessageForSigning(v);
34+
return SignatureG1.reversibleEncodeStringForSigning(v);
3535
});
3636
encoders.set('sensitive.SSN', (v: unknown) => {
3737
// @ts-ignore
38-
return SignatureG1.reversibleEncodeStringMessageForSigning(v);
38+
return SignatureG1.reversibleEncodeStringForSigning(v);
3939
});
4040

4141
export const GlobalEncoder = new Encoder(encoders, defaultEncoder);

0 commit comments

Comments
 (0)