Explorar o código

CMZ (not muCMZ) issuing should now work, except for there's no ZKPs

Also add a new tests/wallet.rs test file
Ian Goldberg hai 5 meses
pai
achega
28eadc73eb
Modificáronse 2 ficheiros con 199 adicións e 8 borrados
  1. 23 8
      cmzcred_derive/src/lib.rs
  2. 176 0
      tests/wallet.rs

+ 23 - 8
cmzcred_derive/src/lib.rs

@@ -603,7 +603,8 @@ fn protocol_macro(
 
             if spec == IssueSpec::Hide {
                 /* For each Hide attribute, the attribute (passed in the
-                   prepare) goes in the ClientState.
+                   prepare) goes in the ClientState, and from there to
+                   to the generated credential in finalize.
                 */
                 clientstate_fields.push_scalar(&scoped_attr.to_string());
                 prepare_code = quote! {
@@ -612,6 +613,10 @@ fn protocol_macro(
                     #iss_cred_id.#attr.ok_or(CMZError::HideAttrMissing(#cred_str,
                     #attr_str))?;
                 };
+                finalize_code = quote! {
+                    #finalize_code
+                    #iss_cred_id.#attr = Some(self.#scoped_attr);
+                }
             }
 
             if spec == IssueSpec::Joint {
@@ -667,10 +672,12 @@ fn protocol_macro(
             }
 
             /* For each Reveal attribute: include attr in Request (client will
-               pass the value into prepare).
+               pass the value into prepare).  Also store it in the
+               ClientState.
             */
             if spec == IssueSpec::Reveal {
                 request_fields.push_scalar(&scoped_attr.to_string());
+                clientstate_fields.push_scalar(&scoped_attr.to_string());
                 prepare_code = quote! {
                     #prepare_code
                     let #scoped_attr =
@@ -681,13 +688,18 @@ fn protocol_macro(
                     #handle_code_pre_fill
                     let #scoped_attr = request.#scoped_attr;
                 };
+                finalize_code = quote! {
+                    #finalize_code
+                    #iss_cred_id.#attr = Some(self.#scoped_attr);
+                };
             }
 
-            /* For each Implicit attribute: does not appear (will be filled in
-               by fill_creds on the issuer side and passed into prepare on the
-               client side)
+            /* For each Implicit attribute: store it in ClientState
+               (will be passed into prepare) on the client side, and
+               will be filled in by fill_creds on the issuer side.
             */
             if spec == IssueSpec::Implicit {
+                clientstate_fields.push_scalar(&scoped_attr.to_string());
                 prepare_code = quote! {
                     #prepare_code
                     let #scoped_attr =
@@ -699,7 +711,11 @@ fn protocol_macro(
                     let #scoped_attr =
                     #iss_cred_id.#attr.ok_or(CMZError::ImplicitAttrIssMissing(#cred_str,
                     #attr_str))?;
-                }
+                };
+                finalize_code = quote! {
+                    #finalize_code
+                    #iss_cred_id.#attr = Some(self.#scoped_attr);
+                };
             }
 
             /* For each Set attribute: the issuer's value will be set
@@ -715,8 +731,7 @@ fn protocol_macro(
                 };
                 finalize_code = quote! {
                     #finalize_code
-                    let #scoped_attr = reply.#scoped_attr;
-                    #iss_cred_id.#attr = Some(#scoped_attr);
+                    #iss_cred_id.#attr = Some(reply.#scoped_attr);
                 }
             }
 

+ 176 - 0
tests/wallet.rs

@@ -0,0 +1,176 @@
+// We want Scalars to be lowercase letters, and Points and credentials
+// to be capital letters
+#![allow(non_snake_case)]
+
+use cmz::*;
+use curve25519_dalek::ristretto::RistrettoPoint as G;
+use ff::PrimeField;
+use group::Group;
+use rand_core::RngCore;
+use sha2::Sha512;
+
+CMZ! { Wallet: randid, balance }
+CMZ! { Item: serialno, price }
+
+CMZProtocol! { wallet_issue,
+  ,
+  W: Wallet { randid: J, balance: S },
+}
+
+CMZProtocol! { item_issue,
+  ,
+  I: Item { serialno: S, price: S },
+}
+
+CMZProtocol! { wallet_spend,
+  [ W: Wallet { randid: R, balance: H },
+    I: Item { serialno: H, price: H } ],
+  N: Wallet { randid: J, balance: H },
+  N.balance >= 0,
+  W.balance = N.balance + I.price
+}
+
+CMZProtocol! { wallet_spend_with_fee<fee>,
+  [ W: Wallet { randid: R, balance: H },
+    I: Item { serialno: H, price: H } ],
+  N: Wallet { randid: J, balance: H },
+  N.balance >= 0,
+  W.balance = N.balance + I.price + fee
+}
+
+// The issuer runs this on its own to create an Item credential for a
+// particular item (specified by a serial number) with a given price.
+fn issue_item(
+    rng: &mut impl RngCore,
+    serialno: u128,
+    price: u128,
+    privkey: &CMZPrivkey<G>,
+    public: &CMZPubkey<G>,
+) -> Result<Item, CMZError> {
+    let (request, state) = item_issue::prepare(&mut *rng, Item::using_pubkey(public))?;
+    let (reply, _) = item_issue::handle(
+        &mut *rng,
+        request,
+        |I: &mut Item| {
+            I.set_privkey(privkey);
+            I.serialno = Some(serialno.into());
+            I.price = Some(price.into());
+            Ok(())
+        },
+        |I: &Item| Ok(()),
+    )?;
+    let res = state.finalize(reply);
+    match res {
+        Ok(c) => Ok(c),
+        Err((err, _state)) => Err(err),
+    }
+}
+
+// The issuer runs this on its own to create an initial wallet loaded
+// with funds, to sent to a client.  The issuer will presumably charge
+// the client out of band for this loaded wallet.
+fn issue_wallet(
+    rng: &mut impl RngCore,
+    balance: u128,
+    privkey: &CMZPrivkey<G>,
+    public: &CMZPubkey<G>,
+) -> Result<Wallet, CMZError> {
+    let (request, state) = wallet_issue::prepare(&mut *rng, Wallet::using_pubkey(public))?;
+    let (reply, wallet) = wallet_issue::handle(
+        &mut *rng,
+        request,
+        |W: &mut Wallet| {
+            W.set_privkey(privkey);
+            W.balance = Some(balance.into());
+            Ok(())
+        },
+        |I: &Wallet| Ok(()),
+    )?;
+    let res = state.finalize(reply);
+    match res {
+        Ok(c) => Ok(c),
+        Err((err, _state)) => Err(err),
+    }
+}
+
+#[test]
+fn test_wallet() -> Result<(), CMZError> {
+    // Initialization
+    let mut rng = rand::thread_rng();
+    cmz_group_init(G::hash_from_bytes::<Sha512>(b"CMZ Generator A"));
+
+    // Issuer: generate private and public keys for each type of
+    // credential.  (The client gets a copy of the public keys.)
+    let (wallet_priv, wallet_pub) = Wallet::cmz_gen_keys(&mut rng);
+    let (item_priv, item_pub) = Item::cmz_gen_keys(&mut rng);
+
+    // The issuer makes some Item credentials for various items by just
+    // executing the issuing protocol with itself
+    let ebook_item = issue_item(&mut rng, 100, 2995, &item_priv, &item_pub)?;
+    let album_item = issue_item(&mut rng, 200, 995, &item_priv, &item_pub)?;
+
+    // In exchange for out of band funds, the issuer generates a loaded
+    // wallet and sends it to the client.
+    let initial_wallet = issue_wallet(&mut rng, 10000, &wallet_priv, &wallet_pub)?;
+
+    // Buy an item (no fee version)
+
+    // client actions
+    let mut N = Wallet::using_pubkey(&wallet_pub);
+    N.balance = Some(initial_wallet.balance.unwrap() - ebook_item.price.unwrap());
+    let (request, state) = wallet_spend::prepare(&mut rng, &initial_wallet, &ebook_item, N)?;
+    let reqbytes = request.as_bytes();
+
+    // issuer actions
+    let recvreq = wallet_spend::Request::try_from(&reqbytes[..]).unwrap();
+    let (reply, (_W_issuer, _I_issuer, _N_issuer)) = wallet_spend::handle(
+        &mut rng,
+        recvreq,
+        |W: &mut Wallet, I: &mut Item, N: &mut Wallet| {
+            W.set_privkey(&wallet_priv);
+            I.set_privkey(&item_priv);
+            N.set_privkey(&wallet_priv);
+            Ok(())
+        },
+        |W: &Wallet, I: &Item, N: &Wallet| Ok(()),
+    )?;
+    let replybytes = reply.as_bytes();
+
+    // client actions
+    let recvreply = wallet_spend::Reply::try_from(&replybytes[..]).unwrap();
+    let W_issued = state.finalize(recvreply).unwrap();
+
+    // The version of the protocol parameterized by a fee.  The client
+    // and issue must agree on the params.
+    let params = wallet_spend_with_fee::Params { fee: 5u128.into() };
+
+    // client actions
+    let mut N_fee = Wallet::using_pubkey(&wallet_pub);
+
+    N_fee.balance = Some(W_issued.balance.unwrap() - album_item.price.unwrap() - params.fee);
+
+    let (request_fee, state_fee) =
+        wallet_spend_with_fee::prepare(&mut rng, &W_issued, &album_item, N_fee, &params)?;
+    let reqbytes_fee = request.as_bytes();
+
+    // issuer actions
+    let recvreq_fee = wallet_spend_with_fee::Request::try_from(&reqbytes_fee[..]).unwrap();
+    let (reply_fee, (_W_fee_issuer, _I_fee_isser, _N_fee_issuer)) = wallet_spend_with_fee::handle(
+        &mut rng,
+        recvreq_fee,
+        |W: &mut Wallet, I: &mut Item, N: &mut Wallet| {
+            W.set_privkey(&wallet_priv);
+            I.set_privkey(&item_priv);
+            N.set_privkey(&wallet_priv);
+            Ok(wallet_spend_with_fee::Params { fee: params.fee })
+        },
+        |W: &Wallet, I: &Item, N: &Wallet| Ok(()),
+    )?;
+    let replybytes_fee = reply_fee.as_bytes();
+
+    // client actions
+    let recvreply_fee = wallet_spend_with_fee::Reply::try_from(&replybytes_fee[..]).unwrap();
+    let W_issued_fee = state_fee.finalize(recvreply_fee).unwrap();
+
+    Ok(())
+}