diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..485dee6 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.idea diff --git a/0000-zmix/README.md b/0000-zmix/README.md new file mode 100644 index 0000000..464ec42 --- /dev/null +++ b/0000-zmix/README.md @@ -0,0 +1,401 @@ +- Feature Name: (mal000002, zmix) +- Start Date: 05-Feb-2019 +- RFC PR: (leave this empty) +- Ursa Issue: (leave this empty) +- Version: 1 + +# Summary +[summary]: #summary + +Zmix is a library for expressing, constructing, and verifying non-interactive +zero-knowledge proofs (ZKPs). A broad class of zero-knowledge proofs is +supported. Schnorr-type proofs are used for many parts and to glue pieces +together, while individual parts of a proof may use other zero-knowledge +techniques. There is a PoC [here](https://github.com/lovesh/ursa/blob/zmix/libzmix/src/zkl/sample_proof.rs) but this doc still needs alignment with the PoC. + +# Motivation +[motivation]: #motivation + +Zero-knowledge proofs form a cryptographic building block with many +applications. Within hyperledger, Indy relies heavily on ZKPs to construct +anonymous credentials. Fabric uses ZKPs to authenticate transactions when +using the identity mixer membership service provider. Implementing ZKPs +correctly however is challenging. Zmix aims to offer a secure +implementation of ZKPs which can be consumed through a simple API, making +it easier for other projects to use ZKPs. + +# Guide-level explanation +[guide-level-explanation]: #guide-level-explanation + +## Introduction + +A zero-knowledge proof enables a prover to convince a verifier of the truth +of some statement about certain values, without revealing those values. For +example, a prover may know that values `A` and `B` have an equal discrete +logarithm. That is, for some `x`, `A = g^x` and `B = h^x`. It could +convince a verifier that this is true by revealing `x`. If the prover would +rather not disclose `x`, it could instead construct a zero-knowledge proof +of this statement, and send the resulting proof to the verifier. The +verifier can now be sure that `log_g(A) = log_h(B)`, without knowing the +discrete log `x`. + +In the example above, there are three components: + +- What do we want to prove? That there exists an `x` such that `A = g^x` +and `B = h^x`. + +- How do we know this is true? Because we know `x`. + +- How can we prove it? With a zero-knowledge proof that the prover produces +and the verifier verifies. + +In zmix, we have data types corresponding to these three components: + +- `ProofSpec`: specifies what we want to prove + +- `Witness`: contains the secrets that satisfy the `ProofSpec`. + +- `Proof`: the zero-knowledge proof. + +The library provides two features: construction and verification of +zero-knowledge proofs. + +To construct a proof, zmix requires a `ProofSpec s` and a `Witness w` +(which is a valid witness for `p`), and will output a `Proof p`. +To verify a proof, zmix requires a `ProofSpec s` and a `Proof p`, and will +output a boolean indicating whether the proof was valid (true) or invalid +(false). +Typically a prover will be requested by a verifier to present a proof through +an artifact (Aries calls such an artifact a proof-request). The prover parses that +artifact to create a ProofSpec which is then used (with a witness) to +create a proof. The verifier will use the same process to create a ProofSpec. + + +# Reference-level explanation +[reference-level-explanation]: #reference-level-explanation + +Conceptually, the zmix library will offer the functions: + +* `generate_proof(s: ProofSpec, w: Witness) -> Result` +* `verify_proof(p: Proof, s: ProofSpec) -> Result<(), Error>` + +where the + +1. proof specification `ProofSpec` contains a list of _statements_ `Statement`, where each statement represents a _part_ of the overall relation being proved +1. witness `Witness` contains the witnesses for the statements. Each `Statement` can have an associated `StatementWitness`. Witness for some statements might be derived from witnesses of other statements. +1. proof `Proof` contains the proofs for the statements. Each `Statement` can have an associated `StatementProof` however proofs for statements might be combined in a single `StatementProof`. Bulletproofs is an example where several proofs for statements like range proof, set membership, merkle tree membership etc are combined in a single proof. + +``` +pub struct ProofSpec { + statements: Vec, + ... +} + +pub struct Witness { + pub statement_witnesses: HashMap, +} + +pub struct Proof { + // Keeping statement_proofs a vector and not a map of statement index -> Proof since several statements can have a single proof, like in case of bulletproofs + pub statement_proofs: Vec, +} +``` +The library will offer and support various types of statements, such as signatures and commitments. +To combine and connect all the statements (i.e., the proof's parts) contained in the proof specification, that is, glue all parts together into a single zero-knowledge proof, the library will use _Schnorr proofs_. + +For generating a Schnorr proof that encompasses multiple statements, for each statement one requires a _hash contribution_ on the one hand, and a _proof contribution_ based on an aggregation of the hash contributions on the other hand. +In particular, the library will implement a proof orchestrator that conceptually generates Schnorr proofs as follows: + +![Generation flow](zmix_proof_generation.png) + +For verifying such a Schnorr proof, one again requires (re-computed) hash contributions for each statement. +In particular, the library's proof orchestrator will conceptually verify a Schnorr proof as follows: + +![Verification flow](zmix_proof_verification.png) + +## Proof Modules + +In accordance with the sequence diagrams shown previously, to (re-)generate the hash contributions and the proof contributions, the library will implement the following _proof module_ interface for each supported statement type: + +``` +pub trait ProofModule { + fn get_hash_contribution( + &mut self, + witness: StatementWitness + ) -> Result, ZkLangError>; + fn get_proof_contribution( + &mut self, + challenge: &FieldElement, + ) -> Result; + fn verify_proof_contribution( + &self, + challenge: &FieldElement, + proof: StatementProof, + ) -> Result; +} +``` + +## Referencing secrets by index + +We previously described that the proof specification contains the number of secrets (called messages) involved in the proof as `message_count`, and a list of statements. +When generating and verifying a proof according to a proof spec, the number of secrets and the various statements are related in the following way: +assuming an ordered list of size `message_count` where each element represents some particular (unknown) secret involved in the proof, the statements can reference elements (that is, secrets) of that list by index. +If different statements reference the same index, they effectively reference the same secret. + +To illustrate this, consider a statement type for signatures. +In general, signatures contain at least + +1. a public key under which the signature can be verified, and +1. an *ordered* list of values that are either hidden (that is, secret) or +revealed (that is, public). + +For example, the library will offer a statement type for Boneh Boyen +Shacham (BBS) signatures as follows: + +``` +pub enum Statement { + PoKSignatureBBS(PoKSignatureBBS), + ... +} + +.... +pub struct PoKSignatureBBS { + pk: BBSVerkey, + // Messages being revealed. + revealed_messages: HashMap, +} +``` + +While each revealed value in the signature statement simply contains +the actual value, hidden values are qualified with an _index_. +In accordance with the list metaphor described above, this index must be smaller or equal to the overall number of secrets as specified in the field `message_count` in the proof specification. + +### Referencing the same secret from different statements + +The index-based approach allows for referring to the *same* secret in the +overall zero-knowledge proof from *different* statements. For example, +another important statement type are commitments, such as the following +Pederson commitment: +``` +pub enum Statement { + ... + PedersenCommitment { + message_index: usize, + commitment: Vec, + ... + }, +``` +Consider a proof specification with a `message_count` of 2 and the +following two statements: +1. a `SignatureBBS` over three values where the first is hidden referring +to index 0, the second is hidden referring to index 1, and the third is the +revealed value 42, and +1. a `PedersenCommitment` referring (via `message_index`) to index 0. + +The fact that both the signature and the commitment refer to index 0 means +that they both refer to the same secret in the overall list of secrets (of +size `message_count`) involved in the zero-knowledge proof. + +### Expressing equality of secrets + +The case when *different signature statements* reference the *same* index is special: this means that the underlying secret values in the signatures are *equal*. + +## Supported statement types + +Zmix plans to offer the following proof modules for the following statement types: + +- Signatures + - Boneh Boyen Shacham + - Pointcheval Sanders +- Pedersen commitments +- Bulletproof intervals +- Bulletproof set membership inclusive and exclusive +- zk-SNARK set memberships +- Verifiable encryption +- Linkable indistinguisable tags (see ia.cr/2011/658) to realize scope pseudonyms + +## Structures + +### Statements + +```rust +pub enum Statement { + /// Boneh Boyen Shacham Signature + SignatureBBS { + pk: Vec, + messages: Vec, + }, + /// Pointcheval Sanders Signature + SignaturePS { + pk: Vec, + messages: Vec, + }, + PedersenCommitment { + message_index: usize, + commitment: Vec, + params: PedersenCommitmentParams, + }, + IntervalBulletproof { + message_index: usize, + min: Vec, + max: Vec, + params: Vec, + }, + /// Camenisch Shoup Encryption + EncryptionCS { + message_index: usize, + pk: Vec, + ciphertext: Vec, + }, + /// As defined by Bernhard et al., Anonymous attestation with user-controlled linkability (ia.cr/2011/658) + LinkableIndistinguishableTagBLS { + message_index: usize, + tag: Vec, + params: Vec, + }, +} + +pub enum HiddenOrRevealedValue { + HiddenValueIndex(usize), + RevealedValue(Vec), +} + +pub struct PedersenCommitmentParams(pub Vec); +``` + +### Proof Specification + +The proof spec follows this data model +```rust +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +pub struct ProofSpec { + #[serde(rename = "messageCount")] + pub message_count: usize, + pub statements: Vec, + pub params: ProofSpecParams, +} + +pub enum ProofSpecParams { + BN254, + BLS12_381, + Ed25519, +} +``` + +For example, a proof spec could be represented in JSON as +```json +{ + "messageCount": 2, + "statements": [ + { + "type": "SignatureBBS", + "data": { + "pk": [ + 112, + 117, + 98, + 108, + 105, + 99, + 107, + 101, + 121 + ], + "messages": [ + { + "hiddenValueIndex": 0 + }, + { + "revealedValue": [ + 118, + 97, + 108, + 117, + 101 + ] + } + ] + } + } + ], + "params": "BLS12_381" +} + +``` + +### Witness +The witness follows this data model + +```rust +/// A witness w is valid w.r.t. a proof specification s if +/// * w contains one value for each of the (hidden) messages in s, so w.messages.len() == s.message_count +/// * w contains one statement witness for each statement in s, so w.statement_witnesses.len() == s.statements.len() +/// * TODO: add further requirements +pub struct Witness { + messages: Vec>, + statement_witnesses: Vec, +} + +pub enum StatementWitness { + SignatureBBS(SignatureBBSWitness), + SignaturePS(SignaturePSWitness), + EncryptionCS(EncryptionCSWitness), + PedersenCommitment(PedersenCommitmentWitness), + IntervalBulletproof, // no witness data needed + SetMembershipBulletProofMerkle(MerklePathWitness), + SetMembershipEccAccumulator(AccumulatorWitness), +} + +pub struct SignatureBBSWitness { + a: PointG1, + e: FieldOrderElement, + s: FieldOrderElement, +} +``` + +### Proof +The proof follows this data model +```rust +/// A proof p is valid w.r.t. a proof specification s if +/// * p contains one message s-value for each of the (hidden) messages in s, so p.message_s_values.len() == s.message_count +/// * p contains one statement proof for each statement in s, so p.statement_proofs.len() == s.statements.len() +/// * the type of p.statement_proofs[i] corresponds to the type of s.statements[i] +/// * TODO: add further requirements +pub struct Proof { + // proof contains a single “challenge” value + challenge_hash: Vec, + message_s_values: Vec>, + statement_proofs: Vec, +} + +pub enum StatementProof { + SignatureBBS(SignatureBBSProof), + SignaturePS(SignaturePSProof), + EncryptionCS(EncryptionCSProof), + PedersenCommitment(PedersenCommitmentProof), + IntervalBulletproof(IntervalBulletproofProof), +} + +pub struct SignatureBBSProof { + a_prime: Vec, + a_bar: Vec, + b_prime: Vec, + s_r2: Vec, + s_r3: Vec, + s_s_prime: Vec, + s_e: Vec, +} + +pub struct PedersenCommitmentProof { + opening_s_val: Vec, +} +``` + +# Changelog +[changelog]: #changelog + +- [19 Aug 2019] - v1.2 - Update with example data models + +- [15 Aug 2019] - v1.1 - Update following recent discussions. + +- [5 Feb 2019] - v1 - Initial version. diff --git a/0000-zmix/proof-spec.json b/0000-zmix/proof-spec.json new file mode 100644 index 0000000..930c247 --- /dev/null +++ b/0000-zmix/proof-spec.json @@ -0,0 +1,272 @@ +{ + "id": "https://github.com/hyperledger-labs/zmix/docs/zklang_spec.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ZKLang Proof Specification", + "type": "object", + "required": ["clauses"], + "properties": { + "clauses": { + "type": "array", + "minItems": 1, + "items": { + "anyOf": [ + { "$ref": "#/definitions/commitmentClause" }, + { "$ref": "#/definitions/credentialClause" }, + { "$ref": "#/definitions/intervalClause" }, + { "$ref": "#/definitions/setClause" }, + { "$ref": "#/definitions/verifiableEncryptionClause" }, + { "$ref": "#/definitions/scopeCommitmentClause" } + ] + } + } + }, + "definitions": { + "attrs": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { "type": "integer" } + }, + "pk": { + "type": "string" + }, + "commitmentClause": { + "type": "object", + "required": ["type", "clauseData"], + "properties": { + "type": { + "type": "string", + "enum": ["commitment"] + }, + "clauseData": { + "$ref": "#/definitions/commitment" + } + } + }, + "credentialClause": { + "type": "object", + "required": ["type", "clauseData"], + "properties": { + "type": { + "type": "string", + "enum": ["credential"] + }, + "clauseData": { + "$ref": "#/definitions/credential" + } + } + }, + "intervalClause": { + "type": "object", + "required": ["type", "clauseData"], + "properties": { + "type": { + "type": "string", + "enum": ["interval"] + }, + "clauseData": { + "$ref": "#/definitions/interval" + } + } + }, + "setClause": { + "type": "object", + "required": ["type", "clauseData"], + "properties": { + "type": { + "type": "string", + "enum": ["set"] + }, + "clauseData": { + "$ref": "#/definitions/set" + } + } + }, + "verifiable_encClause": { + "type": "object", + "required": ["type", "clauseData"], + "properties": { + "type": { + "type": "string", + "enum": ["verifiable_enc"] + }, + "clauseData": { + "$ref": "#/definitions/verifiable_enc" + } + } + }, + "scopeCommitmentClause": { + "type": "object", + "required": ["type", "clauseData"], + "properties": { + "type": { + "type": "string", + "enum": ["scope_commitment"] + }, + "clauseData": { + "$ref": "#/definitions/scope_commitment" + } + } + }, + "commitment": { + "type": "object", + "allOf": [ + { + "required": ["attrs"] + }, + { + "oneOf": [ + { + "required": ["generators", "modulus"], + "not": { "required": ["curve"] } + }, + { + "required": ["curve"], + "not": { "required": ["generators", "modulus"]} + } + ] + } + ], + "properties": { + "attrs": { + "$ref": "#/definitions/attrs" + }, + "generators": { + "type": "array", + "minItems": 1, + "items": { + "type": "string" + } + }, + "modulus": { + "type": "string" + }, + "curve": { + "type": "string", + "enum": ["x25519", "p256r1", "p384r1", "p512r1", "p256k1", "bls381", "bn254"] + } + } + }, + "credential": { + "type": "object", + "required": ["attrs", "pk"], + "properties": { + "attrs": { + "$ref": "#/definitions/attrs" + }, + "pk": { + "$ref": "#/definitions/pk" + } + } + }, + "interval": { + "type": "object", + "allOf": [ + { + "required": ["attrs"] + }, + { + "oneOf": [ + { + "required": ["pk", "min", "max"], + "not": { "required": ["sigs"] } + }, + { + "required": ["sigs"], + "not": { "required": ["pk", "min", "max"] } + } + ] + } + ], + "properties": { + "attrs": { + "$ref": "#/definitions/attrs" + }, + "pk": { + "$ref": "#/definitions/pk" + }, + "min": { + "type": "number" + }, + "max": { + "type": "number" + }, + "sigs": { + "type": "array", + "minItems": 1, + "items": { + "type": "string" + } + } + } + }, + "set": { + "type": "object", + "allOf": [ + { + "required": ["attrs", "pk", "type"] + }, + { + "oneOf": [ + { + "required": ["value"], + "not": { "required": ["circuit"] } + }, + { + "required": ["circuit"], + "not": { "required": ["value"] } + } + ] + } + ], + "properties": { + "attrs": { + "$ref": "#/definitions/attrs" + }, + "pk": { + "$ref": "#/definitions/pk" + }, + "value": { + "type": "integer" + }, + "circuit": { + "type": "string" + } + } + }, + "verifiable_enc": { + "type": "object", + "required": ["attrs", "pk", "crypto_val", "label"], + "properties": { + "attrs": { + "$ref": "#/definitions/attrs" + }, + "pk": { + "$ref": "#/definitions/pk" + }, + "crypto_val": { + "type": "string" + }, + "label": { + "type": "label", + "description": "Decryption policy" + } + } + }, + "scope_commitment": { + "type": "object", + "required": ["attrs", "crypto_cal", "scope_label"], + "properties": { + "attrs": { + "$ref": "#/definitions/attrs" + }, + "crypto_val": { + "type": "string" + }, + "scope_label": { + "type": "string" + } + } + } + } +} diff --git a/0000-zmix/zmix_flow.png b/0000-zmix/zmix_flow.png new file mode 100644 index 0000000..6580dfc Binary files /dev/null and b/0000-zmix/zmix_flow.png differ diff --git a/0000-zmix/zmix_flow.puml b/0000-zmix/zmix_flow.puml new file mode 100644 index 0000000..7924a73 --- /dev/null +++ b/0000-zmix/zmix_flow.puml @@ -0,0 +1,38 @@ +@startuml + +actor Alice as a +participant "Libzmix" as z +database "Public\nCrypto\nData" as p +actor Bob as b + +note over a, b +PR, PO, PF are outside the scope of zmix + +end note +b -> a: PR = proof request +note right +Bob can store this somewhere public +or generate each time +end note +opt +a --> b: PO = proof offer +end +a -> a: PF = proof fulfillment +note right +Application helps Alice decide +end note +a -> p: PD = public data +a -> a: Proof Spec = PD + PR + PF +== ZMix == + +a -> z: Proof Spec + Witness +z -> a: Proof + +a -> b: PF, Proof +b -> p: PD = public data +b -> b: Proof Spec = PD + PR + PF +b -> z: PD + Proof +z -> b: accept or reject + + +@enduml diff --git a/0000-zmix/zmix_proof_generation.png b/0000-zmix/zmix_proof_generation.png new file mode 100644 index 0000000..4a000b7 Binary files /dev/null and b/0000-zmix/zmix_proof_generation.png differ diff --git a/0000-zmix/zmix_proof_generation.puml b/0000-zmix/zmix_proof_generation.puml new file mode 100644 index 0000000..70fcce4 --- /dev/null +++ b/0000-zmix/zmix_proof_generation.puml @@ -0,0 +1,26 @@ +@startuml +participant "zmix Proof Orchestrator" as po +participant "Proof Module 1" as pm1 +participant "..." as pm2 +participant "Proof Module N" as pmn + +-> po: generate_proof(ProofSpec, Witness) + +po -> pm1: get_hash_contribution(...) +po <- pm1: hc1 +po <-> pm2: ... +po -> pmn: get_hash_contribution(...) +po <- pmn: hcN + +po -> po: Compute common challenge as\nhash of individual contributions.\nExample: c = hash(hc1 || ... || hcN) + +po -> pm1: get_proof_contribution(c, ...) +po <- pm1: pc1 +po <-> pm2: ... +po -> pmn: get_proof_contribution(c, ...) +po <- pmn: pcN + +po -> po: Compose proof contributions into a singe proof + +<- po: Proof +@enduml diff --git a/0000-zmix/zmix_proof_verification.png b/0000-zmix/zmix_proof_verification.png new file mode 100644 index 0000000..cd93fdb Binary files /dev/null and b/0000-zmix/zmix_proof_verification.png differ diff --git a/0000-zmix/zmix_proof_verification.puml b/0000-zmix/zmix_proof_verification.puml new file mode 100644 index 0000000..ee1c9c0 --- /dev/null +++ b/0000-zmix/zmix_proof_verification.puml @@ -0,0 +1,26 @@ +@startuml +participant "zmix Proof Orchestrator" as po +participant "Proof Module 1" as pm1 +participant "..." as pm2 +participant "Proof Module N" as pmn + +-> po: verify_proof(Proof, ProofSpec) + +note over po +Proof contains a challenge hash c +end note + +po -> pm1: recompute_hash_contribution(c, ...) +po <- pm1: rhc1 +po <-> pm2: ... +po -> pmn: recompute_hash_contribution(...) +po <- pmn: rhcN + +po -> po: Recompute common challenge as\nhash of individual contributions.\nExample: c' = hash(rhc1 || ... || rhcN) + +note over po +Proof is valid w.r.t. ProofSpec if c == c' +end note + +<- po: true/false +@enduml