Skip to content

Commit 1c4329b

Browse files
committed
Error handling and support for reversible encoding
Signed-off-by: lovesh <lovesh.bond@gmail.com>
1 parent 98b7532 commit 1c4329b

File tree

9 files changed

+172
-32
lines changed

9 files changed

+172
-32
lines changed

src/accumulator/accumulator.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ import {
4141
universalAccumulatorFixedInitialElements
4242
} from '@docknetwork/crypto-wasm';
4343
import { MembershipWitness, NonMembershipWitness } from './accumulatorWitness';
44-
import { getUint8ArraysFromObject } from '../util';
44+
import { getUint8ArraysFromObject, isNumberBiggerThanNBits } from '../util';
4545
import { IAccumulatorState, IUniversalAccumulatorState } from './IAccumulatorState';
4646
import { IInitialElementsStore } from './IInitialElementsStore';
4747

@@ -81,7 +81,8 @@ export abstract class Accumulator {
8181

8282
/**
8383
* To add arbitrary bytes like byte representation of UUID or some other user id or something else as an accumulator
84-
* member, encode it first using this.
84+
* member, encode it first using this. This is an irreversible encoding as a hash function is used to convert a message
85+
* of arbitrary length to a fixed length encoding.
8586
* @param bytes
8687
*/
8788
static encodeBytesAsAccumulatorMember(bytes: Uint8Array): Uint8Array {
@@ -90,12 +91,10 @@ export abstract class Accumulator {
9091

9192
/**
9293
* To add a positive number as an accumulator member, encode it first using this.
94+
* Encodes a positive integer of at most 4 bytes
9395
* @param num - should be a positive integer
9496
*/
9597
static encodePositiveNumberAsAccumulatorMember(num: number): Uint8Array {
96-
if (!Number.isInteger(num) || num < 0) {
97-
throw new Error(`Need a positive integer to encode but found ${num} `);
98-
}
9998
return generateFieldElementFromNumber(num);
10099
}
101100

src/bbs-plus/signature.ts

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { SignatureParamsG1 } from './params';
2-
import { generateFieldElementFromNumber, VerifyResult } from '@docknetwork/crypto-wasm';
2+
import { fieldElementAsBytes, generateFieldElementFromNumber, VerifyResult } from '@docknetwork/crypto-wasm';
33
import {
44
bbsBlindSignG1,
55
bbsEncodeMessageForSigning,
@@ -8,6 +8,7 @@ import {
88
bbsVerifyG1,
99
generateRandomFieldElement
1010
} from '@docknetwork/crypto-wasm';
11+
import { isNumberBiggerThanNBits } from '../util';
1112

1213
export abstract class Signature {
1314
value: Uint8Array;
@@ -16,16 +17,66 @@ export abstract class Signature {
1617
this.value = value;
1718
}
1819

20+
/**
21+
* This is an irreversible encoding as a hash function is used to convert a message of
22+
* arbitrary length to a fixed length encoding.
23+
* @param message
24+
*/
1925
static encodeMessageForSigning(message: Uint8Array): Uint8Array {
2026
return bbsEncodeMessageForSigning(message);
2127
}
2228

23-
static encodePositiveNumberMessage(num: number): Uint8Array {
24-
if (!Number.isInteger(num) || num < 0) {
25-
throw new Error(`Need a positive integer to encode but found ${num} `);
26-
}
29+
/**
30+
* Encodes a positive integer of at most 4 bytes
31+
* @param num
32+
*/
33+
static encodePositiveNumberForSigning(num: number): Uint8Array {
2734
return generateFieldElementFromNumber(num);
2835
}
36+
37+
/**
38+
* Encode the given string to bytes and create a field element by considering the bytes in little-endian format.
39+
* Note that this will add trailing 0s to the bytes to make the size 32 bytes
40+
* @param message - utf-8 string of at most 32 bytes
41+
*/
42+
static reversibleEncodeStringMessageForSigning(message: string): Uint8Array {
43+
const encoder = new TextEncoder();
44+
const bytes = encoder.encode(message);
45+
const maxLength = 32;
46+
if (bytes.length > maxLength) {
47+
throw new Error(`Expects a string with at most ${maxLength} bytes`);
48+
}
49+
// Create a little-endian representation
50+
const fieldElementBytes = new Uint8Array(maxLength);
51+
fieldElementBytes.set(bytes);
52+
fieldElementBytes.set(new Uint8Array(maxLength - bytes.length), bytes.length);
53+
return fieldElementAsBytes(fieldElementBytes);
54+
}
55+
56+
/**
57+
* Decode the given representation. This should **only** be used when the encoding was done
58+
* using `this.reversibleEncodeStringMessageForSigning`. Also, this function trims any characters from the first
59+
* occurrence of a null characters (UTF-16 code unit 0) so if the encoded (using `this.reversibleEncodeStringMessageForSigning`)
60+
* string also had a null then the decoded string will be different from it.
61+
* @param message
62+
*/
63+
static reversibleDecodeStringMessageForSigning(message: Uint8Array): string {
64+
const maxLength = 32;
65+
if (message.length > maxLength) {
66+
throw new Error(`Expects a message with at most ${maxLength} bytes`);
67+
}
68+
const decoder = new TextDecoder();
69+
const decoded = decoder.decode(message);
70+
const chars = [];
71+
for (let i = 0; i < maxLength; i++) {
72+
// If a null character found then stop looking further
73+
if (decoded.charCodeAt(i) == 0) {
74+
break;
75+
}
76+
chars.push(decoded.charAt(i));
77+
}
78+
return chars.join('');
79+
}
2980
}
3081

3182
export class SignatureG1 extends Signature {

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ export * from './saver';
66
export * from './legosnark';
77
export * from './bound-check';
88
export * from './ICompressed';
9+
export * from './bytearray-wrapper';

src/saver/util.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
export function getChunkBitSize(chunkBitSize?: number): number {
22
if (chunkBitSize === undefined) {
3-
return 8;
3+
return 16;
44
}
5-
if (chunkBitSize !== 4 && chunkBitSize !== 8) {
6-
throw new Error(`Chunk bit size of ${chunkBitSize} is not acceptable. Only 4 and 8 allowed`);
5+
if (chunkBitSize !== 4 && chunkBitSize !== 8 && chunkBitSize !== 16) {
6+
throw new Error(`Chunk bit size of ${chunkBitSize} is not acceptable. Only 4, 8 and 16 allowed`);
77
}
88
return chunkBitSize;
99
}

src/util.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
* @param json
44
* @returns
55
*/
6-
import exp from 'constants';
76
import { generateFieldElementFromBytes } from '@docknetwork/crypto-wasm';
87

98
export function jsonObjToUint8Array(json: string): Uint8Array {
@@ -35,3 +34,8 @@ export function getUint8ArraysFromObject(obj: Record<string, any>, keys: string[
3534
export function bytesToChallenge(bytes: Uint8Array): Uint8Array {
3635
return generateFieldElementFromBytes(bytes);
3736
}
37+
38+
export function isNumberBiggerThanNBits(num: number, bits: number): boolean {
39+
// Following can be done using bit shifts, but they only work for small number of shifts. Checked in Chrome and FF
40+
return num.toString(2).length > bits;
41+
}

tests/bbs-plus.spec.ts

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
SignatureG1,
99
SignatureParamsG1
1010
} from '../src';
11-
import { stringToBytes } from './utils';
11+
import { getRevealedUnrevealed, stringToBytes } from './utils';
1212

1313
function getMessages(count: number): Uint8Array[] {
1414
const messages: Uint8Array[] = [];
@@ -219,4 +219,48 @@ describe('BBS+ signature', () => {
219219
const sig2 = SignatureG1.generate(messages5, sk, params5, true);
220220
expect(sig2.verify(messages5, pk, params5, true).verified).toEqual(true);
221221
});
222+
223+
it('should support reversible encoding', () => {
224+
const messages = [
225+
'John Jacob Smith Sr.',
226+
'San Francisco, California',
227+
'john.jacob.smith.1971@gmail.com',
228+
'+1 123-4567890009',
229+
'user-id:1234567890012134'
230+
];
231+
const count = messages.length;
232+
const encodedMessages = new Array<Uint8Array>(5);
233+
for (let i = 0; i < count; i++) {
234+
encodedMessages[i] = SignatureG1.reversibleEncodeStringMessageForSigning(messages[i]);
235+
const decoded = SignatureG1.reversibleDecodeStringMessageForSigning(encodedMessages[i]);
236+
expect(decoded).toEqual(messages[i]);
237+
}
238+
const params = SignatureParamsG1.generate(count);
239+
const keypair = KeypairG2.generate(params);
240+
const sig = SignatureG1.generate(encodedMessages, keypair.secretKey, params, false);
241+
expect(sig.verify(encodedMessages, keypair.publicKey, params, false).verified).toEqual(true);
242+
243+
// Reveal all messages! This is done for testing purposes only.
244+
let revealed: Set<number> = new Set();
245+
for (let i = 0; i < count; i++) {
246+
revealed.add(i);
247+
}
248+
249+
const [revealedMsgs, unrevealedMsgs] = getRevealedUnrevealed(encodedMessages, revealed);
250+
const protocol = PoKSigProtocol.initialize(encodedMessages, sig, params, false, undefined, revealed);
251+
const challengeContributionP = protocol.challengeContribution(params, true, revealedMsgs);
252+
const challengeProver = bytesToChallenge(challengeContributionP);
253+
const proof = protocol.generateProof(challengeProver);
254+
255+
const challengeContributionV = proof.challengeContribution(params, true, revealedMsgs);
256+
const challengeVerifier = bytesToChallenge(challengeContributionV);
257+
258+
expect(challengeProver).toEqual(challengeVerifier);
259+
260+
expect(proof.verify(challengeVerifier, keypair.publicKey, params, false, revealedMsgs).verified).toEqual(true);
261+
for (let i = 0; i < count; i++) {
262+
const decoded = SignatureG1.reversibleDecodeStringMessageForSigning(revealedMsgs.get(i) as Uint8Array);
263+
expect(decoded).toEqual(messages[i]);
264+
}
265+
});
222266
});

tests/composite-proofs/saver.spec.ts

Lines changed: 46 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ import {
1010
SaverEncryptionGens,
1111
SaverEncryptionGensUncompressed,
1212
SaverEncryptionKeyUncompressed,
13-
SaverProvingKeyUncompressed, SaverSecretKey,
13+
SaverProvingKeyUncompressed,
14+
SaverSecretKey,
1415
SaverVerifyingKeyUncompressed,
1516
SetupParam,
1617
SignatureG1,
@@ -24,9 +25,9 @@ import {
2425
import { getRevealedUnrevealed, stringToBytes } from '../utils';
2526

2627
describe('Verifiable encryption of signed messages', () => {
27-
const messageCount = 5;
28-
const chunkBitSize = 8;
28+
const chunkBitSize = 16;
2929
const encMsgIdx = 1;
30+
let messageCount;
3031

3132
let snarkProvingKey: SaverProvingKeyUncompressed,
3233
snarkVerifyingKey: SaverVerifyingKeyUncompressed,
@@ -41,6 +42,7 @@ describe('Verifiable encryption of signed messages', () => {
4142
sigParams2: SignatureParamsG1,
4243
sigSk2: Uint8Array,
4344
sigPk2: Uint8Array;
45+
let messages1AsStrings: string[], messages2AsStrings: string[];
4446
let messages1: Uint8Array[], messages2: Uint8Array[], sig1: SignatureG1, sig2: SignatureG1;
4547

4648
beforeAll(async () => {
@@ -50,15 +52,51 @@ describe('Verifiable encryption of signed messages', () => {
5052
it('do decryptor setup', () => {
5153
const gens = SaverEncryptionGens.generate();
5254
const [snarkPk, sk, ek, dk] = SaverDecryptor.setup(gens, chunkBitSize);
55+
console.log(snarkPk.value.length, ek.value.length, gens.value.length);
5356
saverEncGens = gens.decompress();
5457
snarkProvingKey = snarkPk.decompress();
5558
snarkVerifyingKey = snarkPk.getVerifyingKeyUncompressed();
5659
saverSk = sk;
5760
saverEk = ek.decompress();
5861
saverDk = dk.decompress();
62+
console.log(
63+
snarkProvingKey.value.length,
64+
snarkVerifyingKey.value.length,
65+
saverEk.value.length,
66+
saverDk.value.length,
67+
saverEncGens.value.length
68+
);
5969
}, 300000);
6070

6171
it('do signers setup', () => {
72+
// Setup the messages, its important to use a reversible encoding for the messages used in verifiable encryption as
73+
// the decryptor should be able to decrypt the message without the holder's help.
74+
75+
messages1AsStrings = [
76+
'John Jacob Smith Sr.',
77+
'San Francisco, California',
78+
'john.jacob.smith.1971@gmail.com',
79+
'+1 123-4567890009',
80+
'user-id:1234567890012134'
81+
];
82+
83+
messages2AsStrings = [
84+
'Alice Jr. from Wonderland',
85+
'Wonderland',
86+
'alice.wonderland.1980@gmail.com',
87+
'+1 456-7891230991',
88+
'user-id:9876543210987654'
89+
];
90+
91+
messageCount = messages1AsStrings.length;
92+
93+
messages1 = [];
94+
messages2 = [];
95+
for (let i = 0; i < messageCount; i++) {
96+
messages1.push(SignatureG1.reversibleEncodeStringMessageForSigning(messages1AsStrings[i]));
97+
messages2.push(SignatureG1.reversibleEncodeStringMessageForSigning(messages2AsStrings[i]));
98+
}
99+
62100
sigParams1 = SignatureParamsG1.generate(messageCount);
63101
const sigKeypair1 = KeypairG2.generate(sigParams1);
64102
sigSk1 = sigKeypair1.secretKey;
@@ -69,13 +107,6 @@ describe('Verifiable encryption of signed messages', () => {
69107
sigSk2 = sigKeypair2.secretKey;
70108
sigPk2 = sigKeypair2.publicKey;
71109

72-
messages1 = [];
73-
messages2 = [];
74-
for (let i = 0; i < messageCount; i++) {
75-
messages1.push(generateRandomFieldElement());
76-
messages2.push(generateRandomFieldElement());
77-
}
78-
79110
sig1 = SignatureG1.generate(messages1, sigSk1, sigParams1, false);
80111
sig2 = SignatureG1.generate(messages2, sigSk2, sigParams2, false);
81112
expect(sig1.verify(messages1, sigPk1, sigParams1, false).verified).toEqual(true);
@@ -95,6 +126,7 @@ describe('Verifiable encryption of signed messages', () => {
95126
sigParams: SignatureParamsG1,
96127
sigPk: Uint8Array,
97128
messages: Uint8Array[],
129+
messagesAsStrings: string[],
98130
sig: SignatureG1,
99131
label: string
100132
) {
@@ -133,14 +165,16 @@ describe('Verifiable encryption of signed messages', () => {
133165
expect(proof.verifyWithDeconstructedProofSpec(verifierStatements, metaStatements).verified).toEqual(true);
134166

135167
decryptAndVerify(proof, 1, messages[encMsgIdx]);
168+
const decoded = SignatureG1.reversibleDecodeStringMessageForSigning(messages[encMsgIdx]);
169+
expect(decoded).toEqual(messagesAsStrings[encMsgIdx]);
136170
}
137171

138172
it('prove knowledge of verifiable encryption of 1 message from 1st signature', () => {
139-
proveAndVerifySingle(sigParams1, sigPk1, messages1, sig1, 'public test label 1');
173+
proveAndVerifySingle(sigParams1, sigPk1, messages1, messages1AsStrings, sig1, 'public test label 1');
140174
}, 20000);
141175

142176
it('prove knowledge of verifiable encryption of 1 message from 2nd signature', () => {
143-
proveAndVerifySingle(sigParams2, sigPk2, messages2, sig2, 'public test label 2');
177+
proveAndVerifySingle(sigParams2, sigPk2, messages2, messages2AsStrings, sig2, 'public test label 2');
144178
}, 20000);
145179

146180
it('prove knowledge of verifiable encryption of 1 message from both signatures', () => {

tests/saver.spec.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
SaverEncryptionKeyUncompressed,
1313
SaverProvingKey,
1414
SaverProvingKeyUncompressed,
15+
SaverSecretKey,
1516
SaverVerifyingKey,
1617
SaverVerifyingKeyUncompressed
1718
} from '../src';
@@ -59,22 +60,23 @@ describe('SAVER setup', () => {
5960
});
6061

6162
it('do setup for decryptor', () => {
62-
expect(getChunkBitSize()).toEqual(8);
63+
expect(getChunkBitSize()).toEqual(16);
6364
expect(getChunkBitSize(4)).toEqual(4);
6465
expect(getChunkBitSize(8)).toEqual(8);
66+
expect(getChunkBitSize(16)).toEqual(16);
6567
expect(() => getChunkBitSize(2)).toThrow();
6668
expect(() => getChunkBitSize(3)).toThrow();
6769
expect(() => getChunkBitSize(10)).toThrow();
68-
expect(() => getChunkBitSize(16)).toThrow();
70+
expect(() => getChunkBitSize(17)).toThrow();
6971

7072
expect(() => SaverDecryptor.setup(encGens, 2)).toThrow();
7173
expect(() => SaverDecryptor.setup(encGens, 3)).toThrow();
7274
expect(() => SaverDecryptor.setup(encGens, 10)).toThrow();
73-
expect(() => SaverDecryptor.setup(encGens, 16)).toThrow();
75+
expect(() => SaverDecryptor.setup(encGens, 17)).toThrow();
7476

75-
const [snarkPk, sk, ek, dk] = SaverDecryptor.setup(encGens, 8);
77+
const [snarkPk, sk, ek, dk] = SaverDecryptor.setup(encGens, 16);
7678
expect(snarkPk instanceof SaverProvingKey).toBe(true);
77-
expect(sk instanceof Uint8Array).toBe(true);
79+
expect(sk instanceof SaverSecretKey).toBe(true);
7880
expect(ek instanceof SaverEncryptionKey).toBe(true);
7981
expect(dk instanceof SaverDecryptionKey).toBe(true);
8082

tests/utils.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ export function areUint8ArraysEqual(arr1: Uint8Array, arr2: Uint8Array): boolean
1818
return true;
1919
}
2020

21+
/**
22+
* Given messages and indices to reveal, returns 2 maps, one for revealed messages and one for unrevealed
23+
* @param messages
24+
* @param revealedIndices
25+
*/
2126
export function getRevealedUnrevealed(
2227
messages: Uint8Array[],
2328
revealedIndices: Set<number>

0 commit comments

Comments
 (0)