123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211 |
- // 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 group::Group;
- use rand_core::RngCore;
- use sha2::Sha512;
- CMZ! { Wallet: randid, balance }
- CMZ! { Item: serialno, price }
- // 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, ¶ms)?;
- 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! {CMZ14Protocol, cmz14_wallet_issue, cmz14_item_issue, cmz14_wallet_spend,
- cmz14_wallet_spend_with_fee, cmz_gen_keys, test_cmz14_wallet}
- protos_def! {muCMZProtocol, mu_wallet_issue, mu_item_issue,
- mu_wallet_spend, mu_wallet_spend_with_fee, mucmz_gen_keys, test_mucmz_wallet}
|