Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Some minor improvements in the documentation and code examples #39

Merged
merged 6 commits into from
Nov 28, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 31 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ the [WASM wrapper](https://github.com/docknetwork/crypto-wasm).
- [Terminology](#terminology)
- [Examples](#examples)
- [Selective disclosure](#selective-disclosure)
- [BBS signature over varying number of messages](#bbs-signature-over-varying-number-of-messages)
- [BBS signature over null-valued messages](#bbs-signatures-over-null-valued-messages)
lovesh marked this conversation as resolved.
Show resolved Hide resolved
- [Multiple BBS signatures](#multiple-bbs-signatures)
- [BBS signature together with accumulator membership](#bbs-signature-together-with-accumulator-membership)
- [Getting a blind signature](#getting-a-blind-signature)
Expand Down Expand Up @@ -471,39 +471,40 @@ const pk: BBSPublicKey;
// The signature
const sig: BBSSignature = ...;

// Prover prepares the attributes he wants to disclose, i.e. attribute index 2 and 4 (indexing is 0-based), and the ones he wants to hide.
// Prover prepares the attributes he wants to disclose,
// i.e. attribute index 2 and 4 (indexing is 0-based), and the ones he wants to hide.
const revealedMsgIndices: Set<number> = new Set();
revealedMsgIndices.add(2);
revealedMsgIndices.add(4);
revealedMsgIndices.add(3);
lovesh marked this conversation as resolved.
Show resolved Hide resolved

// revealedMsgs are the attributes disclosed to the verifier
const revealedMsgs: Map<number, Uint8Array> = new Map();
revealedMsgs.set(2, messages[2]);
revealedMsgs.set(3, messages[3]);
lovesh marked this conversation as resolved.
Show resolved Hide resolved

// unrevealedMsgs are the attributes hidden from the verifier
const unrevealedMsgs: Map<number, Uint8Array> = new Map();
unrevealedMsgs.set(0, messages[0]);
unrevealedMsgs.set(1, messages[1]);
unrevealedMsgs.set(3, messages[3]);
```

Since there is only 1 kind of proof, i.e. the knowledge of a BBS signature and the signed attributes, there would be only 1 `Statement`.

```ts
import { Statement, Statements } from '@docknetwork/crypto-wasm-ts'

// Create a BBS signature, true indicates that attributes/messages are arbitrary bytes and should be encoded first
// Create a BBS signature, true indicates that attributes/messages are arbitrary bytes and should be encoded first.
const statement1 = Statement.bbsSignatureProverConstantTime(paramsDeterministc, revealedMsgs, true);
const statements = new Statements();
statements.add(statement1);

// Optional context of the proof, this can specify the reason why the proof was created or date of the proof, or self-attested attributes (as JSON string), etc
// Optional context of the proof, this can specify the reason why the proof was created or date of the proof, or self-attested attributes (as JSON string), etc.
const context = stringToBytes('some context');
```

Once it has been established what needs to be proven, `ProofSpec` needs to be created which represents all the requirements.
Both the prover and verifier should independently construct this `ProofSpec`. Note that there are no `MetaStatements` as there are no
other conditions on the witnesses and thus its empty
other conditions on the witnesses and thus its empty.

```ts
import { ProofSpec, MetaStatements } from '@docknetwork/crypto-wasm-ts';
Expand Down Expand Up @@ -531,21 +532,34 @@ const nonce = stringToBytes('a unique nonce given by verifier');
const proof = CompositeProof.generate(proofSpec, witnesses, nonce);
```

Verifier can now verify this proof. Note that the verifier does not and must not receive `ProofSpec` from prover, it
needs to generate on its own.
Verifier can now verify this proof. Note that the verifier does not and must not receive `ProofSpec` from prover,
it needs to generate on its own.
Also, note the usage of `bbsSignatureVerifierConstantTime` instead.

```ts
console.assert(proof.verify(proofSpec, nonce).verified);
const statement1 = Statement.bbsSignatureVerifierConstantTime(sigParams, keyPair.publicKey, revealedMsgs, true);
const statements = new Statements();
statements.add(statement1);
const context = stringToBytes('some context');

const ms = new MetaStatements();
const verifierProofSpec = new ProofSpec(statements, ms, [], context);

console.assert(proof.verify(verifierProofSpec, nonce).verified);
```

##### BBS signatures over varying number of messages
##### BBS signatures over null-valued messages

The examples above assumed all messages have values in them. However, in some cases, one or more attributes will be null within the credential.
An example in which some of the messages correspond to attributes with null values (e.g. N/A) is a education qualification credential of a person. Someone with a highschool-level education will
have N/A for attributes like university name, major, etc.

The examples shown here have assumed that the number of messages for given signature params is fixed but that might not be always true.
An example is where some of the messages in the signature are null (like N/A) in certain signatures. Eg, when the messages are attributes
in a credential that specifies the educational qualifications and institutes of a person, someone with a high school level education will
have N/A for attributes like university name, major, etc. One way to deal with it is to decide some sentinel value like 0 for all the N/A
attributes and disclose those attributes while creating a proof. Other is to have certain attribute in the credential specify which attribute
indices that are N/A and always reveal this attribute. A complete example of the latter is shown in this [test](tests/composite-proofs/variable-number-of-messages.spec.ts).
One way to deal with this is to decide on some sentinel value like 0 or 'N/A' for all the null attributes
and disclose those values to the verifier.
Another is to have a certain attribute (e.g. first message) in the credential specify which attribute
indices are null and always reveal this attribute.
A complete example of the latter is shown in this
[test](tests/composite-proofs/variable-number-of-messages.spec.ts).

##### Multiple BBS signatures

Expand Down
15 changes: 8 additions & 7 deletions tests/composite-proofs/variable-number-of-messages.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ import { buildWitness, encodeMessageForSigningIfPS, Scheme } from '../scheme';
import { checkResult, getParamsAndKeys, proverStmt, signAndVerify, stringToBytes, verifierStmt } from '../utils';

describe(`Proving knowledge of 1 ${Scheme} signature where some of the attributes are null, i.e. not applicable`, () => {
it('works', async () => {
it('encodes messages with null values in a meaningful way', async () => {
// Load the WASM module
await initializeWasm();

// Messages to sign; the messages are attributes of a user like SSN (Social Security Number), name, email, etc. The attributes
// N/A don't apply to this user
// Messages to sign; the messages are attributes of a user like SSN (Social Security Number), name, email, etc.
// The attributes N/A don't apply to this user.
const messages: Uint8Array[] = [];
// Comma separated indices of N/A messages. An efficient way, especially in large number of messages, could be to use a bitvector
// where an unset bit would indicate N/A
Expand All @@ -35,15 +35,15 @@ describe(`Proving knowledge of 1 ${Scheme} signature where some of the attribute
const messageCount = messages.length;
const label = stringToBytes('My sig params in g1');

// Signers keys
// Signer's keys
const [params, sk, pk] = getParamsAndKeys(messageCount, label);

// Signer knows all the messages and signs
const [sig, result] = signAndVerify(messages, params, sk, pk, true);
checkResult(result);

// User reveals his name, high school year and city to verifier, i.e. indices 2, 4 and 8. He also needs to reveal first
// attribute (index 0) which indicates which attributes don't apply to him.
// User reveals his name, high school year, and city to verifier, i.e. indices 2, 4 and 8.
// He also needs to reveal first attribute (index 0) which indicates which attributes don't apply to him.
const revealedMsgIndices: Set<number> = new Set();
revealedMsgIndices.add(0);
revealedMsgIndices.add(2);
Expand All @@ -62,7 +62,7 @@ describe(`Proving knowledge of 1 ${Scheme} signature where some of the attribute
const statement1 = proverStmt(params, revealedMsgs, pk, true);
const statements = new Statements(statement1);

// Both the prover (user) and verifier should independently construct this `ProofSpec` but only for testing, i am reusing it.
// Prover constructing their ProofSpec
const proverProofSpec = new ProofSpec(statements, new MetaStatements());
expect(proverProofSpec.isValid()).toEqual(true);

Expand All @@ -73,6 +73,7 @@ describe(`Proving knowledge of 1 ${Scheme} signature where some of the attribute

const statement2 = verifierStmt(params, revealedMsgs, pk, true);
const verifierStatements = new Statements(statement2);
// Verifier constructing their own ProofSpec
const verifierProofSpec = new ProofSpec(verifierStatements, new MetaStatements(), []);
expect(verifierProofSpec.isValid()).toEqual(true);
checkResult(proof.verify(verifierProofSpec));
Expand Down
Loading