123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179 |
- // 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 }
- 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(())
- },
- // 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();
- // 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.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();
- Ok(())
- }
|