This crate is centred around the concept of _credentials_. A credential contains: - A number of _attributes_ (each of a type called `Scalar`) - A _Message Authentication Code (MAC)_, which is two values of type `Point` Credentials are held by _clients_, and are both issued and validated by an _issuer_. With CMZ credentials (the kind used in this crate), the issuer is the only entity that can check whether a given credential is valid. (Checking the credential requires the same secret key as is used to create the credential.) Your application can have multiple different kinds of credentials, each with its own set of attributes. All of the credentials in your application should use the _same_ `Scalar` and `Point` types. You get these from a mathematical _group_, which must satisfy the trait [group::prime::PrimeGroup](https://docs.rs/group/0.13.0/group/prime/trait.PrimeGroup.html). A typical such group would be [curve25519\_dalek::ristretto::RistrettoPoint](https://docs.rs/curve25519-dalek/4.1.3/curve25519_dalek/ristretto/struct.RistrettoPoint.html). To declare a credential type, use the `CMZ!` macro at the top level of your crate or module (outside of any function): ```rust CMZ! { Lox : id, bucket, trust_level, level_since, invites_remaining, blockages } ``` This declares a _credential type_ called `Lox` using the mathematical group `RistrettoPoint`. The credential has six attributes, with the names `id`, `bucket`, etc. If you omit the ``, a default of `` will be assumed, so you will need to have a group called `G` in scope. For example: `use curve25519_dalek::ristretto::RistrettoPoint as G;` Note that this macro declares a _type_ for a credential. Your application may have any number (zero or more) actual credentials of this type. The attribute fields of this credential are of type `Option`. The field values could be `None` if, for example, a credential is incomplete (in the process of being issued, and the attributes are not fully filled in yet), or if an attribute is being hidden from the issuer (in which case the issuer will see a credential with some of the fields being `None`). ## CMZ Protocols A _protocol_ is executed by a client and the issuer, and involves: - Proving possession of ("showing") zero or more credentials, which may be of the same or different credential types - Requesting zero or more new credentials to be issued, which may be of the same or different credential types Importantly, when a client shows a credential and/or requests for a new credential to be issued, the attributes of those credentials _are not necessarily revealed to the issuer_. The protocol defines which attributes are revealed, and which are hidden. (There are also a few more options, described below.) For the attributes that are hidden, the client can nonetheless prove that certain facts about them are true, using a _zero-knowledge proof_ (which will be automatically created and checked by the modules generated by this crate). ### Example Suppose we have a credential type called `Wallet`, with two attributes `randid` (a random id number for the wallet) and `balance` (the amount of funds in the wallet). We also have a second credential type called `Item`, representing items that can be purchased, with two attributes `serialno` (the serial number of the item), and `price` (the price of the item): ```rust CMZ! { Wallet: randid, balance } CMZ! { Item: serialno, price } ``` Now we want to implement a zero-knowledge protocol by which a client who holds a wallet with a given balance can buy an item and be issued a new wallet with the remaining balance. The balance, however, is _not_ revealed to the issuer. To avoid double-spending (using an old wallet with a larger balance after having spent some of that balance already), the random id of the wallet will be revealed in each transaction, and the issuer will reject attempts to use the same random id two or more times. The new wallet will be created with a fresh random id that is also unknown to the issuer, so that the issuer cannot track clients from transaction to transaction. Items for purchase are represented by credentials that anyone can download from the issuer's website. The primary way to create a protocol is with the `muCMZProtocol!` macro. ```rust muCMZProtocol! { wallet_spend, [ W: Wallet { randid: R, balance: H }, I: Item { serialno: H, price: H } ], N: Wallet { randid: J, balance: H }, (0..=100000000).contains(N.balance), W.balance = N.balance + I.price } ``` The parameters to the macro call are: - an identifier for the protocol - a list of zero or more specifications for credentials that will be shown - a list of zero or more specifications for credentials that will be issued - zero or more statements relating the attributes in the credentials Each credential specification list can be: - empty - a single credential specification - a square-bracketed list of credential specifications Each credential specification is: - an identifier for the credential - a type for the credential, previously defined with the `CMZ!` macro - a braced list of the attributes of the credential (as defined in the `CMZ!` macro), annotated with the attribute specification An attribute specification for a credential to be shown is one of: - H (hide) - R (reveal) - I (implicit) An attribute specification for a credential to be issued is one of: - H (hide) - R (reveal) - I (implicit) - S (set by issuer) - J (joint creation) For the attributes: - "hide" means that the attribute is not revealed to the issuer (but the statements may still prove things about them). - "reveal" means that the attribute is revealed to the issuer. - "implicit" means that some other part of the overall system means that both the client and the issuer already know what the value of this attribute should be, and so it doesn't need to be sent in the CMZ protocol (saving some space). - "set by issuer", for an attribute in a credential to be issued, means that the issuer will choose the value of this attribute, and send it back to the client with the issued credential. - "joint creation" means that both the client and the issuer will contribute a random component to this attribute; the resulting attribute will be the sum of those components. The issuer will have no information about the resulting attribute value, and the client will not be able to predict the resulting attribute value before receiving the newly issued credential. So in the example, we are creating a protocol called `wallet_spend`, where the client needs to already have two credentials (their current Wallet W and the credential I for the item they wish to purchase). The client will receive back a new Wallet credential N. (Outside of this protocol, the issuer would likely send the item being purchased to the client, perhaps using Private Information Retrieval, or something like that, since the item's serial number and price are hidden from the issuer in this example protocol.) This macro invocation creates a _module_ called `wallet_spend` that contains definitions of three structs and two functions. The general flow is: - The client calls the `prepare` function, passing it the two credentials to be shown, as well as a partially constructed credential to be issued - The `prepare` function will output a `Request` struct, and a `ClientState` struct. - The client will send the `Request` struct to the issuer. (The struct has serialization and deserialization methods.) - The issuer will call the `handle` function, which, if everything checks out, will output the two shown credentials and the newly issued credential, with only the attributes visible to the issuer filled in. It will also output a `Reply` struct. - The issuer will send the `Reply` struct to the client. (Again it has serialization and deserialization methods.) - The client will pass the `Reply` struct to the `finalize` method of the `ClientState` struct it held on to. If everything goes well, the `finalize` method will output the completed newly issued credential. ### API The generated `wallet_spend::prepare` function (run by the client) has the following signature: ```rust pub fn prepare( rng: &mut impl RngCore, session_id: &[u8], W: &Wallet, I: &Item, N: Wallet, ) -> Result<(Request, ClientState), CMZError> ``` The `session_id` parameter is a session identifier. It can be any sequence of bytes, but the value passed here to `prepare` and below to `handle` must be the same. You should treat the `Request` and `ClientState` structs as opaque, but they are currently not, and have `Debug` implemented, so if you wanted, you could look inside with `println!("{:#?}", request)` or similar. You can serialize and deserialize a `Request` struct with `request.as_bytes()` and `wallet_spend::Request::try_from(bytes)`, or using `serde` (`Serialize` and `Deserialize` are implemented for `Request`.) The generated `wallet_spend::handle`` function (run by the issuer) has the following signature: ```rust pub fn handle( rng: &mut impl RngCore, session_id: &[u8], request: Request, fill_creds: F, authorize: A, ) -> Result<(Reply, (Wallet, Item, Wallet)), CMZError> where F: FnOnce(&mut Wallet, &mut Item, &mut Wallet) -> Result<(),CMZError>, A: FnOnce(&Wallet, &Item, &Wallet) -> Result<(),CMZError> ``` Note that `handle` _consumes_ the `Request`. The `handle` function takes two callbacks: `fill_creds` and `authorize`. The `handle` function will read the request, and use it to fill in the revealed attributes from the shown and issued credentials (in this case, just `W.randid`). The hidden attributes from the credentials will be set to `None`, as will the implicit, set by issuer, and joint creation attributes. It is the job of the `fill_creds` callback to: - Set the values of the implicit, set by issuer, and the issuer contribution to joint creation attributes (if any) for each shown and issued credential - Set the private keys for each credential The `handle` function will then check that the credentials shown by the client are all valid, and that the statements given in the `muCMZProtocol!` macro call are all true. If not, it will return with an `Err`. If so, `handle` will call the `authorize` callback, which can do any final application-specific checks on the credentials (and any other state it can access in its closure). If `authorize` returns `Err`, so will `handle`. If `authorize` returns `Ok`, then `handle` will issue the credentials to be issued (in this case, the new Wallet credential). It will return a `Reply` struct and copies of the shown and issued credentials (but the attributes not visible to the issuer will still be `None` of course). The `Reply` struct can be serialized and deserialized in the same way as the `Request` struct, so that it can be sent back to the client. The client will then pass that deserialized `Reply` struct into the `finalize` method of the `ClientState` struct that was output by `prepare`, above. The `finalize` method has the following signature: ```rust pub fn finalize( self, reply: Reply, ) -> Result ``` Note that `finalize` _consumes_ both the `Reply` and also `self`. In the event of an error (such as a malicious reply impersonating the issuer?), `self` is returned so you can possibly try again. In the event of success, the newly issued credentials are returned as a tuple, or if as in this case, there's just one, as a single element. ### Parameterized Protocols A protocol can optionally be declared as having _parameters_, which are public Scalar or Point constants that will be filled in at runtime. You declare parameters by changing the first line of the `muCMZProtocol!` macro invocation from, for example: ```rust muCMZProtocol! { proto_name, ``` to: ```rust muCMZProtocol! { proto_name, ``` then you can use `param1` and `param2` wherever you could have used a literal Scalar constant in the statements in the statement list, and `param3` wherever you could have used a public Point (the `@` indicates the parameter is a Point; the default is that the parameter is a Scalar). For example: ```rust muCMZProtocol! { wallet_spend, [ W: Wallet { randid: R, balance: H }, I: Item { serialno: H, price: H } ], N: Wallet { randid: J, balance: H }, (0..=100000000).contains(N.balance), W.balance = N.balance + I.price + fee } ``` If you declare parameters in your protocol, the API changes as follows: - There is a `struct Params` declared in the generated module, containing a `Scalar` field for each named parameter. - The `prepare` function takes an additional `&Params` argument at the end, which the client must fill in before calling `prepare`. - The `fill_creds` callback returns `Result` instead of `Result<(),CMZError>`, and it is the job of that callback to supply a filled-in `Params` struct, possibly based on other values it receives from the client in the attributes of the credentials.