From 7a0b62c219e9217c7793ed01209ce2c2eb2aea95 Mon Sep 17 00:00:00 2001 From: Mathias Hall-Andersen Date: Wed, 25 Sep 2024 19:15:41 +0200 Subject: [PATCH 1/7] spec: work on security definitions --- spec/rfc.md | 61 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 59 insertions(+), 2 deletions(-) diff --git a/spec/rfc.md b/spec/rfc.md index 3fd76b2..9485843 100644 --- a/spec/rfc.md +++ b/spec/rfc.md @@ -6,6 +6,63 @@ This proposal introduces an extension to the wallet provider API for Mina wallet Additionally, this proposal should enable users to collaborate with a 3rd party such as a KYC provider to generate a proof that includes data not available locally. +## Assumptions + +We assume that the Kimchi (the underlying proof) is a Zero-Knowledge Argument of Knowledge: + +- **Zero-Knowledge:** Kimchi is (perfect) Zero-Knowledge. +- **Proof-of-Knowledge:** Kimchi is extractable (in the random oracle model). + +We do not assume simulation extractability ("non-malleability") of Kimchi, +although Kimchi is likely to satisfy this stronger property. \ +Our construction does not assume non-malleability of the zkSNARK and +hence security is retained even if the zkSNARK was replaced with a malleable proof system (such as Groth16). + +## Formal Security Goals + +We prove that the construction detailed in this RFC satisfy the following security properties: + +> **Privacy: Simulation.** +> +> **Informally:** The adversary should not be able to +> +> Formally, we define this as the ability to simulate a presentation +> A + +> **Unforgeability: Simulation Extractability.** +> +> **Informally:** The adversary should not be able to present a credential to the verifier without having a valid credential. +> +> Formally, we define this as the ability to extract a witness (e.g. a valid signature by the issuer) from a presentation: +> +> Adv() + +This condition holds even when *the adversary has access to an oracle* that can generate valid presentation proofs for arbitrary statements. + +This serves to formalize that the adversary is unable to create valid presentations even if it has access to other valid presentations, +or is able to execute a "man-in-the-middle" attack on the credential presentation: +forwarding a clients proof as if it was its own. + +## Further Security Considerations + +The following consideration is formally covered by unforgeability, but should be considered by applications interacting with this RFC: + +**Context Binding:** It is rarely intresting to present a credential without binding the presentation to some additional data.\ +For instance, the presentation of a credential may allow spending from an account, +in such a scenario the presentation should be bound to the transaction: the recipient, the amount, etc. to prevent the transaction from being mauled. +This specification details how to bind *arbitrary data* to the presentation, +it is beyond the scope of this specification to detail how such data should be derived: +applications are expected to ensure that context bound to the presentation is sufficient to meet application-specific security requirements. + +**Hardware Wallets:** Every credential has an `owner` field, a Mina public key, and the credential can only be used by signing with the corresponding private key. \ +This enables hardware-wallet support for credentials: +the hardware wallet the context in which the credential is used, but does not need to compute the costly proof. +In the case of a compromised prover (browser), the attacker learns the attributes of the credential, +but is unable to use the credential to sign transactions or otherwise impersonate the credential owner. + +**Compromised Credentials:** We use the flexibility afforded by the Kimchi proof +system to reduce the practical impact of a compromised credential. + ## Motivation The motivation behind extending the Mina wallet provider API stems from the evolving needs of the Mina ecosystem's zkApp landscape. Currently, there exists no standard for Mina wallets to interact with zkApps and attest to known private data. This limitation hinders the full potential of Mina's composable privacy feature, which is vital for user autonomy and data security. @@ -221,7 +278,7 @@ Every credential program receives an `OperationNode`, an operation node is an ob ```json { "operation": "and", - "inputs": [ + "inputs": [ { "operation": "greaterThan", "firstInput": 18, @@ -229,7 +286,7 @@ Every credential program receives an `OperationNode`, an operation node is an ob }, { "operation": "lessThan", - "firstInput": 51, + "firstInput": 51, "secondInput": "credential.maxAge", }, ... From 1187469de12ea8ce0438213c4c6fad552f559979 Mon Sep 17 00:00:00 2001 From: Mathias Hall-Andersen Date: Sun, 13 Oct 2024 18:51:33 +0200 Subject: [PATCH 2/7] spec: progress on presentation proof --- spec/design.md | 153 +++++++++++++++++++++++++++++++++++++++++++ spec/rfc.md | 28 ++++++++ spec/spec.md | 173 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 354 insertions(+) create mode 100644 spec/design.md create mode 100644 spec/spec.md diff --git a/spec/design.md b/spec/design.md new file mode 100644 index 0000000..60dfa43 --- /dev/null +++ b/spec/design.md @@ -0,0 +1,153 @@ +# The Design of Mina Credentials + +This document is a high-level overview of the Mina Credentials system, +its relation to other systems, its (informal) design goals and rationale for the design choices made in the system. +The goal of this project is to design a system sufficiently flexible to encompass any "anonymous credential"-style application, +while unifying the user interface, API and design with the aim of providing a clear specification with minimal footgun potential. + +This is achieved by using the recursive proofs of Mina extensively: +seperating the "creation" of the credential from the "presentation" of the credential: +besides unifying the presentation proof, it also allows doing most of the expensive operations (e.g. verifying an RSA signature using SHA256 and parsing a JSON object) once during the creation of the credential. + +At the highest possible level of abstraction, Mina credentials +are a set of "attributes" (e.g. name, age, SSN) attested to by a "issuer". +The issuer is an opaque entity (a hash) and may identify e.g. the root of a Merkle tree, +the root authorities of a PKI, the hash of Google's OAuth public key, etc. +By exploiting the recursive proofs of Mina, this diverse set of issuers / applications +can be brought into a standard form: a SNARK on a hash of the attributes. +All these credentials can also be stored/verified/used in the same way. +This provides a plug-and-play system: +allowing developers to create new credential types and application logics seperately, combining them in a safe, modular way. + +# Related Works & Systems + +## [zkLogin](https://docs.sui.io/concepts/cryptography/zklogin) + +zkLogin + +## [zkPassport](https://zkpassport.id/) + +zkPassport is based + +## [World ID and Semaphore](https://worldcoin.org/blog/worldcoin/intro-zero-knowledge-proofs-semaphore-application-world-id) + + +The goal of Mina credentials is broader than these systems and must enable +the implementation of these systems within the Mina ecosystem. + +# Design Rational + +The cryptography team considered two options for the presentation proof. + +1. Credentials are "bearer tokens": simply knowing the credential is sufficient to present it. +2. Credentials are associate attributes/capabilities to public keys. + +To explain our choice, let us first explore the two options in more detail. + +## Option 1: "Bearer Tokens" + +In this scenario, the simpler of the two, knowing the credential is equivalent to owning it. +Let us make that more concrete. For instance, the credential might simply be a signature over a set of attributes: + +```javascript +cred = { + 'attributes': { + 'name': 'Alice', + 'age': 25, + 'ssn': '123-45-6789' + }, + 'signature': '---signature---' + 'issuer': '---issuer public key---' +} +``` + +Knowing this signed object is equivalent to "being" Alice. +To "present" the credential, to e.g. show that Alice is over 18, Alice creates a proof of the following relation: + +```javascript +// verify the issuer signature +cred.signature.verify(cred.issuer, cred.attributes); + +// check the issuer +assert(cred.issuer == issuer); + +// verify the claim +assert(cred.attributes['age'] >= 18); +``` + +Using the context of the presentation as an public input to the proof: +the context is a domain-specific hash of the context in which the credential is presented, +incorporating the entity to which the credential is presented, a nonce to avoid replay attacks and additional information as needed. +We describe this in more detail in the formal specification. + +## Option 2: "Associated Attributes" + +In this scenario, the credential is associates a set of attributes with a public key. +Knowning the corresponding signing key allows one to present the credential. +To make this more concrete, the credential might look like this: + +```javascript +cred = { + 'owner': '---owner public key---', + 'attributes': { + 'name': 'Alice', + 'age': 25, + 'ssn': '123-45-6789' + }, + 'signature': '---signature---' + 'issuer': '---issuer---' +} +``` + +To "present" the credential, Alice creates a proof of the following relation: + +```javascript +// verify the issuer signature +cred.signature.verify(cred.issuer, cred.owner, cred.attributes); + +// verify the owner signature +ownerSignature.verify(cred.owner, [cred.attributes, cred.issuer, context]); + +// check the issuer +assert(cred.issuer == issuer); + +// verify the claim +assert(cred.attributes['age'] >= 18); +``` + +Again, using the context as an public input to the proof: but now the context is also signed by the owner +and the signature is verified in-circuit. + +## Justification + +We deemed that the additional complexity of the second option, +an in-circuit verification of a native signature, +is outweighed by the following benefits: + +- Allow use of existing infrastructure for key management. + Including hardware enclaves and the ability to authortize presentations efficiently using MPC: + authortization requires the parties to threshold sign using Schnorr. + An untrusted party can be asked to compute the actual proof at the cost of privacy. + +- Out-sourcing the computation of the presentation proof is possible at the cost of privacy: + the user must reveal the credential and the context to the prover, but the prover cannot impersonate the user or change the intented action. + This is useful in scenarios where the prover is a resource-constrained device. + +- A compromise of the credential object itself does not allow impersonation. + +- Easy integration with the existing Nullifier system within Mina: every credential comes with a public key + and nullifiers can be computed / exposed against this public key to allow linkability when desired. + +- From a theorectical/robustness perspective, a small benefit is that we can assume weaker properties of the proof system: + the first scheme requires [(weak) simulation extractability](https://eprint.iacr.org/2020/1306.pdf) since the "context". + +We obtain a design in which the SNARK serves only to hide the + +We show that if even if the SNARK prover is malicious, the credential remains unforgeable: +this is useful in applications such as zkLogin, where for practicality reasons, the proving is outsourced to a third party in practice. +We want to allow the Mina ecosystem this option should it be relevant to particular applications. + +We note that for some applications, most notably zkPassport, secure outsourcing is inherently not possible when *creating the credential*: +since knowledge of the witness (the digitial signature on the ePassport) identifies the owner. +In such applications we must rely on the security of the SNARK prover during the creation of the credential, +however, the presentation proof remains secure (unforgeable) even if the SNARK prover is malicious. diff --git a/spec/rfc.md b/spec/rfc.md index 9485843..f2ef067 100644 --- a/spec/rfc.md +++ b/spec/rfc.md @@ -63,6 +63,34 @@ but is unable to use the credential to sign transactions or otherwise impersonat **Compromised Credentials:** We use the flexibility afforded by the Kimchi proof system to reduce the practical impact of a compromised credential. + +## Protocols + +### Credential Types + +At a high level, a credential is a collection of *attributes* along a proof and corresponding verification procedure: +checking the validity of the credential. + +#### Native Credentials + +A native credential is simply a native Mina signature on the set of attributes. +This type of credential is supported to allow Mina-native application to have very efficient credentials. + +#### Recursive Credentials + +A recursive credential is Kimchi proof taking the set of attributes as public input. +This type of credential is supported to accomodate any possible credential +and the ability to integrate existing credential systems (such as ePassport) into the Mina ecosystem +in a modular way. + +- ECDSA signatures over foreign fields on JSON document (e.g. JSON Web Tokens), +- RSA signatures. +- Merkle tree inclusion/exclusion proofs. + + +### Issuance + + ## Motivation The motivation behind extending the Mina wallet provider API stems from the evolving needs of the Mina ecosystem's zkApp landscape. Currently, there exists no standard for Mina wallets to interact with zkApps and attest to known private data. This limitation hinders the full potential of Mina's composable privacy feature, which is vital for user autonomy and data security. diff --git a/spec/spec.md b/spec/spec.md new file mode 100644 index 0000000..86220d7 --- /dev/null +++ b/spec/spec.md @@ -0,0 +1,173 @@ +# Technical Specification for Mina Credentials + +This document is a low-level technical specification for the Mina Credentials system. +It is intended as document for the accompanying codebase and implementators. +It does not include security proofs or motivations for the design choices, +see the RFC for such discussions. + +# Protocol: Presenting Credentials + +The application logic only interacts with the `Credential` struct: + +```javascript +Attributes { + owner: PublicKey, // the owners public key + attrs: Attributes, // hidden attributes (e.g. age, name, SSN) +} +``` + +```javascript +Credential { + owner: PublicKey, // the owners public key + attributes: Attributes, // hidden attributes (e.g. age, name, SSN) +} +``` + +```javascript +PublicInput { + context: Field, // context : specified later + claims: Claims // application specific public inputs. +} +``` + +This means that the application logic does not need to know about the underlying cryptographic primitives +and is pluggable between the simple and recursive credentials. +The public input for both credential types is: + +The issuing authority is either: + +- A hash of a public key for simple credentials. +- A hash of a sequence of public inputs and verification keys for recursive credentials: +binding the credential to a specific verication logic (e.g. a circuit implementing RSA verification) and input (e.g. hash of Google's RSA public key). + +## Circuit: Present Simple Credential + +A standardized circuit for presenting simple credentials. + +The circuit verifies two signatures: one from the issuer and one from the owner. + +```javascript +// the private inputs for the circuit +PrivateInput { + credential: Credential, + issuerPk: PublicKey, + issuerSignature: Signature, + ownerSignature: Signature, +} + +// hash the credential +let credHash = Poseidon.hashPacked(Credential, credential); + +// verify the credential issuer signature +issuerSignature.verify(issuerPk, credHash); + +// convert issuerPK to opaque field element +let issuer = Poseidon.hashWithPrefix( + "mina-cred:v0:simple", // sep. the domain of "simple" and "recursive" issuers + issuerPk +); + +// verify the credential owners signature +ownerSignature.verify(owner, [credHash, issuer, context]); + +// verify application specific constraints using the standard API +applicationConstraints( + credential, // hidden attributes/owner + issuer, // potentially hidden issuer + claims, // application specific public input +) +``` + +## Circuit: Present Recursive Credential + +A standardized circuit for presenting recursive credentials. + +The circuit verifies a proof "from" the issuing authority and a signature from the owner. + +```javascript +// the private inputs for the circuit +PrivateInput { + vk: VerificationKey, + credIdent: Field, + credProof: Proof, + credential: Credential, + ownerSignature: Signature, +} + +// hash the credential +let credHash = Poseidon.hashPacked(Credential, credential); + +// verify the credential proof +credProof.publicInput.assertEquals([credHash, credIdent]); +credProof.verify(vk).assertEqual(true); + +// the issuer is identified by the recursive relation and public input +let issuer = Poseidon.hashWithPrefix( + "mina-cred:v0:recursive", // sep. the domain of "simple" and "recursive" issuers + [vk.hash, credIdent] // identifies the issuing authority / validation logic +); + +// verify the credential owners signature +ownerSignature.verify(owner, [credHash, issuer, context]); + +// verify application specific constraints using the standard API +applicationConstraints( + credential, // hidden attributes/owner + issuer, // potentially hidden issuer + claims, // application specific public input +) +``` + +# Context Identifiers + +The verifier computes the context (out-of-circuit) as: + +```javascript +context = Poseidon.hashWithPrefix( + "mina-cred:v0:context", // for versioning + [ + vk.hash, // the verification key hash (of the presentation proof) + claims, // the public input (the set of "claims" being presented) + nonce, // a random nonce + verifier, // a URI for the verifiers identifier (see below) + action, // the "action" being performed (e.g. login, transaction hash etc.) + ] +) +``` + +The nonce MUST be a uniformly random value generated by the prover. + +### Web Application + +[Uniform Resource Identifier](https://datatracker.ietf.org/doc/html/rfc3986) + +```javascript +let verifier = Keccak256.hash("https://example.com/verify"); +``` + +The scheme MUST be `https`. + +# Discussion + +Discuss the following with Gregor: + +1. Should the `issuer` be a struct instead? (e.g. `Issuer { pk: PublicKey, signature: Signature }`) +1. What is the standard way to provide domain-specific for signautures in the Mina ecosystem? should we do: +``` +m = Poseidon.hashWithPrefix("mina-cred:v1:", [credHash, issuer, context]); + +signature.verifySignedHashV2(message, m); +``` +1. Discuss [Nullifiers](https://github.com/o1-labs/o1js/issues/756) in the context of Mina Credentials. + +# Example: Merkle-Tree Credential + +# Example: RSA Credential + +# Example: PKI Credential + +# Bearer Credentials to Mina Credentials + +In some applications, e.g. zkPassport (ICAO), knowledge of the signed object constitutes the credential. +To reduce the window of attack (e.g. avoid storing passport scans) and bring the credential into the Mina Credentials system, +a public key must be bound to the knowledge of this signed object. From aa53706b5011b6139e1d767352edc3a7741f8621 Mon Sep 17 00:00:00 2001 From: Mathias Hall-Andersen Date: Sun, 13 Oct 2024 19:16:13 +0200 Subject: [PATCH 3/7] spec: minor changes to comment --- spec/design.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/spec/design.md b/spec/design.md index 60dfa43..3e69788 100644 --- a/spec/design.md +++ b/spec/design.md @@ -146,8 +146,3 @@ We obtain a design in which the SNARK serves only to hide the We show that if even if the SNARK prover is malicious, the credential remains unforgeable: this is useful in applications such as zkLogin, where for practicality reasons, the proving is outsourced to a third party in practice. We want to allow the Mina ecosystem this option should it be relevant to particular applications. - -We note that for some applications, most notably zkPassport, secure outsourcing is inherently not possible when *creating the credential*: -since knowledge of the witness (the digitial signature on the ePassport) identifies the owner. -In such applications we must rely on the security of the SNARK prover during the creation of the credential, -however, the presentation proof remains secure (unforgeable) even if the SNARK prover is malicious. From ddfe90a7f8b8f8972e590eb55a502a5977ad722d Mon Sep 17 00:00:00 2001 From: Mathias Hall-Andersen Date: Wed, 16 Oct 2024 12:37:24 +0200 Subject: [PATCH 4/7] spec: notes from meeting - Add metadata - Draft section on formats --- spec/design.md | 11 ++---- spec/spec.md | 100 ++++++++++++++++++++++++++++++------------------- 2 files changed, 65 insertions(+), 46 deletions(-) diff --git a/spec/design.md b/spec/design.md index 3e69788..ea24fff 100644 --- a/spec/design.md +++ b/spec/design.md @@ -127,11 +127,12 @@ is outweighed by the following benefits: - Allow use of existing infrastructure for key management. Including hardware enclaves and the ability to authortize presentations efficiently using MPC: authortization requires the parties to threshold sign using Schnorr. - An untrusted party can be asked to compute the actual proof at the cost of privacy. -- Out-sourcing the computation of the presentation proof is possible at the cost of privacy: +- Outsourcing the computation of the proofs is possible at the cost of privacy: the user must reveal the credential and the context to the prover, but the prover cannot impersonate the user or change the intented action. - This is useful in scenarios where the prover is a resource-constrained device. + This is useful in scenarios where the prover is a resource-constrained device, + this is useful in applications such as zkLogin, where for practicality reasons, the proving is outsourced to a third party in practice. + We want to allow the Mina ecosystem this option should it be relevant to particular applications. - A compromise of the credential object itself does not allow impersonation. @@ -142,7 +143,3 @@ is outweighed by the following benefits: the first scheme requires [(weak) simulation extractability](https://eprint.iacr.org/2020/1306.pdf) since the "context". We obtain a design in which the SNARK serves only to hide the - -We show that if even if the SNARK prover is malicious, the credential remains unforgeable: -this is useful in applications such as zkLogin, where for practicality reasons, the proving is outsourced to a third party in practice. -We want to allow the Mina ecosystem this option should it be relevant to particular applications. diff --git a/spec/spec.md b/spec/spec.md index 86220d7..a7a3fba 100644 --- a/spec/spec.md +++ b/spec/spec.md @@ -5,24 +5,58 @@ It is intended as document for the accompanying codebase and implementators. It does not include security proofs or motivations for the design choices, see the RFC for such discussions. -# Protocol: Presenting Credentials +# Metadata -The application logic only interacts with the `Credential` struct: +Metadata SHOULD NOT be constrained during the creation of a credential. +Metadata MUST NOT be used to determine the validity of a credential or its issuer. +Metadata MUST only be used to present information about the credential in a human-readable way +inside wallets and other applications for easy identification and selection. + +# Formats + +## Mina Credential + +< HOW A MINA CREDENTIAL IS FORMATTED / STORED > ```javascript -Attributes { - owner: PublicKey, // the owners public key - attrs: Attributes, // hidden attributes (e.g. age, name, SSN) +credential = { + owner: PublicKey, // the owners public key + metahash: Field, // hash of arbitrary metadata + attributes: Attributes, // struct of hidden attributes (e.g. age, name, SSN) } ``` +## Mina Credential Presentation + ```javascript -Credential { - owner: PublicKey, // the owners public key - attributes: Attributes, // hidden attributes (e.g. age, name, SSN) +presentation = { + proof: Proof, + claims: Claims, } ``` +The presentation MUST NOT contain the "context" field, which MUST be recomputed by the verifier. + +## Mina Credential Metadata + +< HOW IS METADATA FORMATTED > + + +```javascript +metadata = Keccak256.hash( + +``` + +# Protocols + +## Presentations + +Presentation proofs MUST not be reused. +Presentation proofs MUST be generated for each presentation. + + +### Public Inputs + ```javascript PublicInput { context: Field, // context : specified later @@ -30,17 +64,7 @@ PublicInput { } ``` -This means that the application logic does not need to know about the underlying cryptographic primitives -and is pluggable between the simple and recursive credentials. -The public input for both credential types is: - -The issuing authority is either: - -- A hash of a public key for simple credentials. -- A hash of a sequence of public inputs and verification keys for recursive credentials: -binding the credential to a specific verication logic (e.g. a circuit implementing RSA verification) and input (e.g. hash of Google's RSA public key). - -## Circuit: Present Simple Credential +### Circuit: Present Simple Credential A standardized circuit for presenting simple credentials. @@ -78,7 +102,7 @@ applicationConstraints( ) ``` -## Circuit: Present Recursive Credential +### Circuit: Present Recursive Credential A standardized circuit for presenting recursive credentials. @@ -118,7 +142,7 @@ applicationConstraints( ) ``` -# Context Identifiers +# Context Binding The verifier computes the context (out-of-circuit) as: @@ -126,23 +150,33 @@ The verifier computes the context (out-of-circuit) as: context = Poseidon.hashWithPrefix( "mina-cred:v0:context", // for versioning [ - vk.hash, // the verification key hash (of the presentation proof) - claims, // the public input (the set of "claims" being presented) - nonce, // a random nonce - verifier, // a URI for the verifiers identifier (see below) - action, // the "action" being performed (e.g. login, transaction hash etc.) + // the verification key hash (of the presentation circuit) + presentationCircuitVK.hash, + claims, // the public input (the set of "claims" being presented) + nonce, // a random nonce + verifierIdentity, // a URI for the verifiers identifier (see below) + action, // the "action" being performed (e.g. login, transaction hash etc.) ] ) ``` The nonce MUST be a uniformly random value generated by the prover. -### Web Application +## ZK App + +verifier = Mina address + +action = Method with arguments (note one of the args is the presentation proof). + + +## Web Application [Uniform Resource Identifier](https://datatracker.ietf.org/doc/html/rfc3986) ```javascript let verifier = Keccak256.hash("https://example.com/verify"); + +let action = Keccak256.hash(HTTP_REQUEST); ``` The scheme MUST be `https`. @@ -159,15 +193,3 @@ m = Poseidon.hashWithPrefix("mina-cred:v1:", [credHash, issuer, context]); signature.verifySignedHashV2(message, m); ``` 1. Discuss [Nullifiers](https://github.com/o1-labs/o1js/issues/756) in the context of Mina Credentials. - -# Example: Merkle-Tree Credential - -# Example: RSA Credential - -# Example: PKI Credential - -# Bearer Credentials to Mina Credentials - -In some applications, e.g. zkPassport (ICAO), knowledge of the signed object constitutes the credential. -To reduce the window of attack (e.g. avoid storing passport scans) and bring the credential into the Mina Credentials system, -a public key must be bound to the knowledge of this signed object. From e593c03856b388840f305537eae4e7bcef9748ce Mon Sep 17 00:00:00 2001 From: Mathias Hall-Andersen Date: Wed, 16 Oct 2024 17:11:18 +0200 Subject: [PATCH 5/7] spec: encoding formats --- spec/spec.md | 77 ++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 53 insertions(+), 24 deletions(-) diff --git a/spec/spec.md b/spec/spec.md index a7a3fba..0e4ed2c 100644 --- a/spec/spec.md +++ b/spec/spec.md @@ -16,20 +16,39 @@ inside wallets and other applications for easy identification and selection. ## Mina Credential -< HOW A MINA CREDENTIAL IS FORMATTED / STORED > - ```javascript -credential = { +type credential = { owner: PublicKey, // the owners public key - metahash: Field, // hash of arbitrary metadata + metaHash: Field, // hash of arbitrary metadata attributes: Attributes, // struct of hidden attributes (e.g. age, name, SSN) } ``` +```javascript +type Witness = + | { type: "simple", + issuer: PublicKey, + issuerSignature: Signature, + } + | { type: "recursive", + vk: VerificationKey, + credIdent: Field, + credProof: Proof, + } +``` + +```javascript +type storedCredential = { + witness: Witness, + metadata: Metadata, + credential: Credential, +} +``` + ## Mina Credential Presentation ```javascript -presentation = { +type presentation = { proof: Proof, claims: Claims, } @@ -39,12 +58,36 @@ The presentation MUST NOT contain the "context" field, which MUST be recomputed ## Mina Credential Metadata -< HOW IS METADATA FORMATTED > +Metadata is a general key-value map. We standardize a few fields for interoperability across wallets: +so that e.g. wallet can display an issuer name and icon for any compatible credential. +Issuers may add their own fields as needed. +Standardized fields are: +- `credName`: The name of the credential. +- `issuerName`: The name of the issuer. +- `description`: A human-readable description of the credential. +- `icon`: A byte array representing an icon for the credential. + +Any standardized fields MAY be omitted, wallets MUST handle the absence of any field gracefully, e.g. with a default icon. +Wallets MUST NOT make trust decisions based on metadata, in particular, +wallets MUST NOT verify the issuer based on the `issuerName` field. +Wallets MAY ignore ANY metadata field. ```javascript -metadata = Keccak256.hash( +type metadata = { + credName: String, + issuerName: String, + description: String, + icon: Bytes, // jpg, png, webp, etc. + ... +} +``` + +The `metaHash` field of the credential is the hash of the metadata. +The `metaHash` fiueld MUST be computed using `Keccak256` over the metadata. +```javascript +metaHash = Keccak256.hash(metadata) ``` # Protocols @@ -54,11 +97,10 @@ metadata = Keccak256.hash( Presentation proofs MUST not be reused. Presentation proofs MUST be generated for each presentation. - ### Public Inputs ```javascript -PublicInput { +type PublicInput = { context: Field, // context : specified later claims: Claims // application specific public inputs. } @@ -72,7 +114,7 @@ The circuit verifies two signatures: one from the issuer and one from the owner. ```javascript // the private inputs for the circuit -PrivateInput { +type PrivateInput = { credential: Credential, issuerPk: PublicKey, issuerSignature: Signature, @@ -110,7 +152,7 @@ The circuit verifies a proof "from" the issuing authority and a signature from t ```javascript // the private inputs for the circuit -PrivateInput { +type PrivateInput = { vk: VerificationKey, credIdent: Field, credProof: Proof, @@ -180,16 +222,3 @@ let action = Keccak256.hash(HTTP_REQUEST); ``` The scheme MUST be `https`. - -# Discussion - -Discuss the following with Gregor: - -1. Should the `issuer` be a struct instead? (e.g. `Issuer { pk: PublicKey, signature: Signature }`) -1. What is the standard way to provide domain-specific for signautures in the Mina ecosystem? should we do: -``` -m = Poseidon.hashWithPrefix("mina-cred:v1:", [credHash, issuer, context]); - -signature.verifySignedHashV2(message, m); -``` -1. Discuss [Nullifiers](https://github.com/o1-labs/o1js/issues/756) in the context of Mina Credentials. From d6d23b91f6813545638ed2fead51f4a5c8b0f96f Mon Sep 17 00:00:00 2001 From: Mathias Hall-Andersen Date: Wed, 16 Oct 2024 17:29:34 +0200 Subject: [PATCH 6/7] spec: more consistent naming --- spec/spec.md | 48 +++++++++++++++++++++++++++++++----------------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/spec/spec.md b/spec/spec.md index 0e4ed2c..2488211 100644 --- a/spec/spec.md +++ b/spec/spec.md @@ -16,14 +16,18 @@ inside wallets and other applications for easy identification and selection. ## Mina Credential +A credential is a set of attributes and an owner: + ```javascript -type credential = { +type Credential = { owner: PublicKey, // the owners public key metaHash: Field, // hash of arbitrary metadata attributes: Attributes, // struct of hidden attributes (e.g. age, name, SSN) } ``` +Is is stored along with metadata and the version of the credential: + ```javascript type Witness = | { type: "simple", @@ -38,47 +42,55 @@ type Witness = ``` ```javascript -type storedCredential = { +type StoredCredential = { + version: "v0", witness: Witness, metadata: Metadata, credential: Credential, } ``` +Wallets MUST import/export credentials in this format, but MAY store them in any format internally. +Wallets MUST validate the credential before importing it, we describe the validation procedure in this document. +Note: validating a credential does not require access to the owner's private key. + ## Mina Credential Presentation +The presentation proof is encoded as follows: + ```javascript -type presentation = { +type Presentation = { + version: "v0", proof: Proof, claims: Claims, } ``` -The presentation MUST NOT contain the "context" field, which MUST be recomputed by the verifier. - ## Mina Credential Metadata Metadata is a general key-value map. We standardize a few fields for interoperability across wallets: so that e.g. wallet can display an issuer name and icon for any compatible credential. -Issuers may add their own fields as needed. +Issuers may add their own fields as needed, such custom fields MUST NOT use the `mina` prefix. + Standardized fields are: -- `credName`: The name of the credential. -- `issuerName`: The name of the issuer. -- `description`: A human-readable description of the credential. -- `icon`: A byte array representing an icon for the credential. +- `minaCredName`: The name of the credential: utf-8 encoded string. +- `minaIssuerName`: The name of the issuer: utf-8 encoded string. +- `minaDescription`: A human-readable description of the credential: utf-8 encoded string. +- `minaIcon`: A byte array representing an icon for the credential. -Any standardized fields MAY be omitted, wallets MUST handle the absence of any field gracefully, e.g. with a default icon. +Any fields (inlcuding the standardized ones) MAY be omitted, +wallets MUST handle the absence of any field gracefully, e.g. with a default icon. Wallets MUST NOT make trust decisions based on metadata, in particular, -wallets MUST NOT verify the issuer based on the `issuerName` field. +wallets MUST NOT verify the issuer based on the `minaIssuerName` field. Wallets MAY ignore ANY metadata field. ```javascript -type metadata = { - credName: String, - issuerName: String, - description: String, - icon: Bytes, // jpg, png, webp, etc. +type Metadata = { + minaCredName: String, + minaIssuerName: String, + minaDescription: String, + minaIcon: Uint8Array, // svg, jpg, png, webp, etc. ... } ``` @@ -96,6 +108,8 @@ metaHash = Keccak256.hash(metadata) Presentation proofs MUST not be reused. Presentation proofs MUST be generated for each presentation. +The presentation MUST NOT contain the "context" field, which MUST be recomputed by the verifier. +The presentation MUST NOT include the `metadata` of the credential. ### Public Inputs From 239ec94d57ee26ca1b4d76d57c246eb8d9a9b8af Mon Sep 17 00:00:00 2001 From: Mathias Hall-Andersen Date: Mon, 21 Oct 2024 11:30:16 +0200 Subject: [PATCH 7/7] spec: guidelines for context binding --- spec/spec.md | 78 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 58 insertions(+), 20 deletions(-) diff --git a/spec/spec.md b/spec/spec.md index 2488211..c90b92c 100644 --- a/spec/spec.md +++ b/spec/spec.md @@ -11,6 +11,8 @@ Metadata SHOULD NOT be constrained during the creation of a credential. Metadata MUST NOT be used to determine the validity of a credential or its issuer. Metadata MUST only be used to present information about the credential in a human-readable way inside wallets and other applications for easy identification and selection. +Metadata MUST NOT be used to make trust decisions. +Metadata MUST NOT be presented to the verifier during the presentation of a credential. # Formats @@ -19,6 +21,10 @@ inside wallets and other applications for easy identification and selection. A credential is a set of attributes and an owner: ```javascript +type Attributes = { + [key: string]: Any, // any o1js type +} + type Credential = { owner: PublicKey, // the owners public key metaHash: Field, // hash of arbitrary metadata @@ -35,7 +41,7 @@ type Witness = issuerSignature: Signature, } | { type: "recursive", - vk: VerificationKey, + credVK: VerificationKey, credIdent: Field, credProof: Proof, } @@ -106,13 +112,15 @@ metaHash = Keccak256.hash(metadata) ## Presentations -Presentation proofs MUST not be reused. -Presentation proofs MUST be generated for each presentation. -The presentation MUST NOT contain the "context" field, which MUST be recomputed by the verifier. -The presentation MUST NOT include the `metadata` of the credential. +- The presentation proofs MUST NOT be reused. +- The presentation proofs MUST be generated for each presentation. +- The presentation MUST NOT contain the "context" field, which MUST be recomputed by the verifier. +- The presentation MUST NOT include the `metadata` of the credential. ### Public Inputs +The public inputs for the presentations circuits (simple and recursive) are: + ```javascript type PublicInput = { context: Field, // context : specified later @@ -148,7 +156,10 @@ let issuer = Poseidon.hashWithPrefix( ); // verify the credential owners signature -ownerSignature.verify(owner, [credHash, issuer, context]); +ownerSignature.verify( + credential.owner, + [context, issuer, credHash] +); // verify application specific constraints using the standard API applicationConstraints( @@ -167,7 +178,7 @@ The circuit verifies a proof "from" the issuing authority and a signature from t ```javascript // the private inputs for the circuit type PrivateInput = { - vk: VerificationKey, + credVK: VerificationKey, credIdent: Field, credProof: Proof, credential: Credential, @@ -179,7 +190,7 @@ let credHash = Poseidon.hashPacked(Credential, credential); // verify the credential proof credProof.publicInput.assertEquals([credHash, credIdent]); -credProof.verify(vk).assertEqual(true); +credProof.verify(credVK); // the issuer is identified by the recursive relation and public input let issuer = Poseidon.hashWithPrefix( @@ -188,7 +199,10 @@ let issuer = Poseidon.hashWithPrefix( ); // verify the credential owners signature -ownerSignature.verify(owner, [credHash, issuer, context]); +ownerSignature.verify( + credential.owner, + [context, issuer, credHash] +); // verify application specific constraints using the standard API applicationConstraints( @@ -206,33 +220,57 @@ The verifier computes the context (out-of-circuit) as: context = Poseidon.hashWithPrefix( "mina-cred:v0:context", // for versioning [ - // the verification key hash (of the presentation circuit) - presentationCircuitVK.hash, - claims, // the public input (the set of "claims" being presented) - nonce, // a random nonce - verifierIdentity, // a URI for the verifiers identifier (see below) - action, // the "action" being performed (e.g. login, transaction hash etc.) + type, // seperates different types of verifiers + presentationCircuitVK.hash, // binds the presentation to the relation + nonce, // a random nonce to prevent replay attacks + verifierIdentity, // verifiers identifier + action, // the "action" being performed (e.g. login, transaction hash etc.) + claims, // the public input (the set of "claims" being presented) ] ) ``` -The nonce MUST be a uniformly random value generated by the prover. +The nonce MUST be generated as follows: + +```javascript +let nonce = Poseidon.hashWithPrefix( + "mina-cred:v0:nonce", + [serverNonce, clientNonce] +) +``` + +- The `clientNonce` MUST be a uniformly random field element generated by the client. +- The `clientNonce` MUST never be reused. +- The `serverNonce` MAY be zero in applications where storing the set of expended nonces indefinitely is not a concern. + +Usual applications of `serverNonce` is to seperate the nonce space into "epochs" to prevent storage of all nonces indefinitely: +for instance, a timestamp may be used and validity requires the timestamp to be recent. +Allowing the server to only store nonces for a limited time. + +## zkApp -## ZK App +```javascript +let type = Keccak256.hash("zk-app") -verifier = Mina address +let verifierIdentity = "Mina Address of the ZK App" -action = Method with arguments (note one of the args is the presentation proof). +let action = Poseidon.hash([METHOD_ID, ARG1, ARG2, ...]) +``` +The ZK app MUST check the validity of the presentation proof and the claims. ## Web Application [Uniform Resource Identifier](https://datatracker.ietf.org/doc/html/rfc3986) ```javascript -let verifier = Keccak256.hash("https://example.com/verify"); +let type = Keccak256.hash("https"); + +let verifierIdentity = Keccak256.hash("example.com"); let action = Keccak256.hash(HTTP_REQUEST); ``` The scheme MUST be `https`. + +Keccak is used to improve efficiency when the HTTP request is long: such as uploading a file.