Browse Source

µCMZ issuing protocol complete, except for ZKPs

Ian Goldberg 5 months ago
parent
commit
a1f97113dd
2 changed files with 309 additions and 196 deletions
  1. 116 22
      cmzcred_derive/src/lib.rs
  2. 193 174
      tests/wallet.rs

+ 116 - 22
cmzcred_derive/src/lib.rs

@@ -142,6 +142,9 @@ fn impl_cmzcred_derive(ast: &syn::DeriveInput, group_ident: &Ident) -> TokenStre
                     return Err(());
                 }
                 let mut coeff = privkey.x0;
+                if privkey.muCMZ {
+                    coeff += privkey.xr;
+                }
                 for field in Self::attrs().iter() {
                     let attr_val = self.attr(field).ok_or(())?;
                     coeff += attr_val * privkey.x[Self::attr_num(field)];
@@ -578,10 +581,20 @@ fn protocol_macro(
         // The (revealed part of) the MAC
         let P_cred = format_ident!("P_iss_cred_{}", iss_cred.id);
         let Q_cred = format_ident!("Q_iss_cred_{}", iss_cred.id);
+
+        // Only for CMZ14, not µCMZ:
+
         // The encrypted form of the hidden part of the MAC
-        // EQ_cred is only used for CMZ, not muCMZ
         let EQ_cred = format_ident!("EQ_iss_cred_{}", iss_cred.id);
 
+        // Only for µCMZ, not CMZ14:
+
+        // The Pedersen commitment to only the Hide and Joint attributes
+        let C_cred = format_ident!("C_iss_cred_{}", iss_cred.id);
+        // The completed Pedersen commitment to the attributes
+        // (including all kinds of attributes)
+        let K_cred = format_ident!("K_iss_cred_{}", iss_cred.id);
+
         let iss_cred_type = &iss_cred.cred_type;
 
         // String version of the credential name
@@ -705,6 +718,17 @@ fn protocol_macro(
                 }
             }
 
+            if use_muCMZ && (spec == IssueSpec::Hide || spec == IssueSpec::Joint) {
+                /* For each Hide and Joint attribute (for µCMZ): add
+                   attr*X_attr to C.
+                */
+                prepare_code = quote! {
+                    #prepare_code
+                    #C_cred += #scoped_attr *
+                        #pubkey_cred.X[#iss_cred_type::attr_num(#attr_str)];
+                };
+            }
+
             /* For each Reveal attribute: include attr in Request (client will
                pass the value into prepare).  Also store it in the
                ClientState.
@@ -758,8 +782,8 @@ fn protocol_macro(
             */
             if spec == IssueSpec::Set {
                 reply_fields.push_scalar(&scoped_attr.to_string());
-                handle_code_post_auth = quote! {
-                    #handle_code_post_auth
+                handle_code_post_fill = quote! {
+                    #handle_code_post_fill
                     let #scoped_attr =
                     #iss_cred_id.#attr.ok_or(CMZError::SetAttrMissing(#cred_str,
                     #attr_str))?;
@@ -770,20 +794,36 @@ fn protocol_macro(
                 }
             }
 
-            /* For each Reveal, Implicit, Set, or Joint attribute, add
-               attr*x_attr*P to Q in handle.
-            */
             if spec == IssueSpec::Reveal
                 || spec == IssueSpec::Implicit
                 || spec == IssueSpec::Set
                 || spec == IssueSpec::Joint
             {
-                handle_code_post_auth = quote! {
-                    #handle_code_post_auth
-                    #Q_cred += (#scoped_attr *
-                        #iss_cred_id.privkey.x[#iss_cred_type::attr_num(#attr_str)])
-                        * #P_cred;
-                };
+                if use_muCMZ {
+                    /* For each Reveal, Implicit, Set, or Joint attribute, add
+                       attr*X_attr to K in handle and finalize.
+                    */
+                    handle_code_post_fill = quote! {
+                        #handle_code_post_fill
+                        #K_cred += (#scoped_attr *
+                            #iss_cred_id.pubkey.X[#iss_cred_type::attr_num(#attr_str)]);
+                    };
+                    finalize_code = quote! {
+                        #finalize_code
+                        #K_cred += (#iss_cred_id.#attr.unwrap() *
+                            #iss_cred_id.pubkey.X[#iss_cred_type::attr_num(#attr_str)]);
+                    };
+                } else {
+                    /* For each Reveal, Implicit, Set, or Joint attribute, add
+                       attr*x_attr*P to Q in handle.
+                    */
+                    handle_code_post_auth = quote! {
+                        #handle_code_post_auth
+                        #Q_cred += (#scoped_attr *
+                            #iss_cred_id.privkey.x[#iss_cred_type::attr_num(#attr_str)])
+                            * #P_cred;
+                    };
+                }
             }
         }
 
@@ -848,17 +888,71 @@ fn protocol_macro(
             /* For all Hide and Joint attributes of a single credential to be
                issued (for µCMZ): The client chooses a random s, computes C =
                (\sum_{hide,joint} attr*X_attr) + s*A, where X_attr is the
-               public key for that attribute.  Include s in the ClientState, C
-               in the Request, and the attributes, s, and C in the CliProof.
-               Hide attributes will be passed into prepare on the client side;
-               Joint attributes (client contribution) will be generated randomly
-               by prepare on the client side.  On the issuer side, handle will
-               pick a random b, compute P = b*A, R = b*(x_0*A + C) +
-               \sum_{implicit,reveal,set,joint} x_attr*attr*P.  Include P
-               and R in Reply, and x_0, b, P, R, C in IssProof.  For each
-               implicit,reveal,set,joint attribute, include x_attr and P_attr =
-               attr*P in IssProof.  The client will compute Q = R - s*P.
+               public key for that attribute.  Include s and C in the
+               ClientState, C in the Request, and the attributes, s, and
+               C in the CliProof.  Hide attributes will be passed into
+               prepare on the client side; Joint attributes (client
+               contribution) will be generated randomly by prepare on
+               the client side.  On the issuer side, handle will pick a
+               random b, compute P = b*A, K = C + X_r +
+               \sum_{implicit,reveal,set,joint} attr*X_attr, R =
+               b*(x_0*A + K).  Include P and R in Reply, and x_0, b, P,
+               R, K in IssProof.  For each implicit,reveal,set,joint
+               attribute, include x_attr and P_attr = attr*P in
+               IssProof.  The client will compute K as above, and Q = R
+               - s*P.
             */
+            let R_cred = format_ident!("R_iss_cred_{}", iss_cred.id);
+            let s_cred = format_ident!("s_iss_cred_{}", iss_cred.id);
+            reply_fields.push_point(&P_cred.to_string());
+            reply_fields.push_point(&R_cred.to_string());
+            if cred_hide_joint {
+                clientstate_fields.push_scalar(&s_cred.to_string());
+                clientstate_fields.push_point(&C_cred.to_string());
+                request_fields.push_point(&C_cred.to_string());
+                prepare_code = quote! {
+                    let #s_cred = <Scalar as ff::Field>::random(&mut *rng);
+                    let mut #C_cred = bp.mulA(&#s_cred);
+                    #prepare_code
+                };
+                handle_code_post_fill = quote! {
+                    let mut #K_cred = request.#C_cred + #iss_cred_id.pubkey.Xr.unwrap();
+                    #handle_code_post_fill
+                };
+                finalize_code = quote! {
+                    let mut #K_cred = self.#C_cred + self.#pubkey_cred.Xr.unwrap();
+                    #finalize_code
+                };
+            } else {
+                handle_code_post_fill = quote! {
+                    let mut #K_cred = #iss_cred_id.pubkey.Xr.unwrap();
+                    #handle_code_post_fill
+                };
+                finalize_code = quote! {
+                    let mut #K_cred = self.#pubkey_cred.Xr.unwrap();
+                    #finalize_code
+                };
+            }
+            handle_code_post_auth = quote! {
+                #handle_code_post_auth
+                let #b_cred = <Scalar as ff::Field>::random(&mut *rng);
+                let #P_cred = bp.mulA(&#b_cred);
+                let #R_cred = #b_cred * (bp.mulA(&#iss_cred_id.privkey.x0) + #K_cred);
+            };
+            let finalize_Q_code = if cred_hide_joint {
+                quote! {
+                    #iss_cred_id.MAC.Q = reply.#R_cred - self.#s_cred * reply.#P_cred;
+                }
+            } else {
+                quote! {
+                    #iss_cred_id.MAC.Q = reply.#R_cred;
+                }
+            };
+            finalize_code = quote! {
+                #finalize_code
+                #iss_cred_id.MAC.P = reply.#P_cred;
+                #finalize_Q_code
+            };
         }
 
         any_hide_joint |= cred_hide_joint;

+ 193 - 174
tests/wallet.rs

@@ -11,182 +11,201 @@ 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 },
-}
+// Define two versions of each protocol and test: one for CMZ14 and one for µCMZ
+
+macro_rules! protos_def {
+    ($proto_macro:tt, $wallet_issue:ident, $item_issue:ident,
+    $wallet_spend:ident, $wallet_spend_with_fee:ident,
+    $gen_keys:ident, $test_wallet: ident) => {
+        $proto_macro! { $wallet_issue,
+          ,
+          W: Wallet { randid: J, balance: S },
+        }
+
+        $proto_macro! { $item_issue,
+          ,
+          I: Item { serialno: S, price: S },
+        }
+
+        $proto_macro! { $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
+        }
+
+        $proto_macro! { $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
+        }
+
+        #[test]
+        fn $test_wallet() -> Result<(), CMZError> {
+            // 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_issue::handle(
+                    &mut *rng,
+                    request,
+                    |W: &mut Wallet| {
+                        W.set_privkey(privkey);
+                        W.balance = Some(balance.into());
+                        Ok(())
+                    },
+                    |_W: &Wallet| Ok(()),
+                )?;
+                let res = state.finalize(reply);
+                match res {
+                    Ok(c) => Ok(c),
+                    Err((err, _state)) => Err(err),
+                }
+            }
+
+            // 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::$gen_keys(&mut rng);
+            let (item_priv, item_pub) = Item::$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)?;
+
+            // The verify_MAC function is only usable for debugging, since the
+            // client will not have the private key and the issuer will
+            // typically not have the complete credential.  But we'll use it
+            // here to check that the credentials we get back are correct.
+            ebook_item.verify_MAC(&item_priv).unwrap();
+            album_item.verify_MAC(&item_priv).unwrap();
+
+            // 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)?;
+
+            initial_wallet.verify_MAC(&wallet_priv).unwrap();
+
+            // 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(())
+                },
+                // This callback should test that W.randid has never been seen
+                // before, but it doesn't currently do that.
+                |_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();
+
+            W_issued.verify_MAC(&wallet_priv).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_fee.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 })
+                    },
+                    // This callback should test that W.randid has never been seen
+                    // before, but it doesn't currently do that.
+                    |_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();
+
+            W_issued_fee.verify_MAC(&wallet_priv).unwrap();
 
-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),
-    }
-}
+protos_def! {CMZProtocol, cmz14_wallet_issue, cmz14_item_issue, cmz14_wallet_spend,
+cmz14_wallet_spend_with_fee, cmz_gen_keys, test_cmz14_wallet}
 
-#[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)?;
-
-    // The verify_MAC function is only usable for debugging, since the
-    // client will not have the private key and the issuer will
-    // typically not have the complete credential.  But we'll use it
-    // here to check that the credentials we get back are correct.
-    ebook_item.verify_MAC(&item_priv).unwrap();
-    album_item.verify_MAC(&item_priv).unwrap();
-
-    // 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)?;
-
-    initial_wallet.verify_MAC(&wallet_priv).unwrap();
-
-    // 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(())
-        },
-        // This callback should test that W.randid has never been seen
-        // before, but it doesn't currently do that.
-        |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();
-
-    W_issued.verify_MAC(&wallet_priv).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_fee.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 })
-        },
-        // This callback should test that W.randid has never been seen
-        // before, but it doesn't currently do that.
-        |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();
-
-    W_issued_fee.verify_MAC(&wallet_priv).unwrap();
-
-    Ok(())
-}
+protos_def! {muCMZProtocol, mu_wallet_issue, mu_item_issue,
+mu_wallet_spend, mu_wallet_spend_with_fee, mucmz_gen_keys, test_mucmz_wallet}