diff --git a/README.md b/README.md index 86f64012..baed99e8 100644 --- a/README.md +++ b/README.md @@ -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) - [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) @@ -462,7 +462,9 @@ to reveal his last name and city, but not any other attribute while proving that ```ts // The attributes, [SSN, first name, last name, email, city] -const messages: Uint8Array[] = [...]; +const messages: Uint8Array[] = [ + "230-95-4628", "Harry", "Potter", "harry@potter.com", "Little Whinging" +].map(element => stringToBytes(element)); // Public values const params: BBSSignatureParams; @@ -471,7 +473,8 @@ 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 = new Set(); revealedMsgIndices.add(2); revealedMsgIndices.add(4); @@ -479,12 +482,13 @@ revealedMsgIndices.add(4); // revealedMsgs are the attributes disclosed to the verifier const revealedMsgs: Map = new Map(); revealedMsgs.set(2, messages[2]); +revealedMsgs.set(4, messages[4]); // unrevealedMsgs are the attributes hidden from the verifier const unrevealedMsgs: Map = new Map(); unrevealedMsgs.set(0, messages[0]); unrevealedMsgs.set(1, messages[1]); -unrevealedMsgs.set(3, messages[3]); +unrevealedMsgs.set(1, 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`. @@ -492,18 +496,18 @@ Since there is only 1 kind of proof, i.e. the knowledge of a BBS signature and t ```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'; @@ -531,21 +535,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 diff --git a/tests/composite-proofs/variable-number-of-messages.spec.ts b/tests/composite-proofs/variable-number-of-messages.spec.ts index 3590d50c..79f052b3 100644 --- a/tests/composite-proofs/variable-number-of-messages.spec.ts +++ b/tests/composite-proofs/variable-number-of-messages.spec.ts @@ -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 @@ -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 = new Set(); revealedMsgIndices.add(0); revealedMsgIndices.add(2); @@ -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); @@ -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));