wallet.rs 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. // We want Scalars to be lowercase letters, and Points and credentials
  2. // to be capital letters
  3. #![allow(non_snake_case)]
  4. use cmz::*;
  5. use curve25519_dalek::ristretto::RistrettoPoint as G;
  6. use group::Group;
  7. use rand_core::RngCore;
  8. use sha2::Sha512;
  9. CMZ! { Wallet: randid, balance }
  10. CMZ! { Item: serialno, price }
  11. CMZProtocol! { wallet_issue,
  12. ,
  13. W: Wallet { randid: J, balance: S },
  14. }
  15. CMZProtocol! { item_issue,
  16. ,
  17. I: Item { serialno: S, price: S },
  18. }
  19. CMZProtocol! { wallet_spend,
  20. [ W: Wallet { randid: R, balance: H },
  21. I: Item { serialno: H, price: H } ],
  22. N: Wallet { randid: J, balance: H },
  23. N.balance >= 0,
  24. W.balance = N.balance + I.price
  25. }
  26. CMZProtocol! { wallet_spend_with_fee<fee>,
  27. [ W: Wallet { randid: R, balance: H },
  28. I: Item { serialno: H, price: H } ],
  29. N: Wallet { randid: J, balance: H },
  30. N.balance >= 0,
  31. W.balance = N.balance + I.price + fee
  32. }
  33. // The issuer runs this on its own to create an Item credential for a
  34. // particular item (specified by a serial number) with a given price.
  35. fn issue_item(
  36. rng: &mut impl RngCore,
  37. serialno: u128,
  38. price: u128,
  39. privkey: &CMZPrivkey<G>,
  40. public: &CMZPubkey<G>,
  41. ) -> Result<Item, CMZError> {
  42. let (request, state) = item_issue::prepare(&mut *rng, Item::using_pubkey(public))?;
  43. let (reply, _) = item_issue::handle(
  44. &mut *rng,
  45. request,
  46. |I: &mut Item| {
  47. I.set_privkey(privkey);
  48. I.serialno = Some(serialno.into());
  49. I.price = Some(price.into());
  50. Ok(())
  51. },
  52. |I: &Item| Ok(()),
  53. )?;
  54. let res = state.finalize(reply);
  55. match res {
  56. Ok(c) => Ok(c),
  57. Err((err, _state)) => Err(err),
  58. }
  59. }
  60. // The issuer runs this on its own to create an initial wallet loaded
  61. // with funds, to sent to a client. The issuer will presumably charge
  62. // the client out of band for this loaded wallet.
  63. fn issue_wallet(
  64. rng: &mut impl RngCore,
  65. balance: u128,
  66. privkey: &CMZPrivkey<G>,
  67. public: &CMZPubkey<G>,
  68. ) -> Result<Wallet, CMZError> {
  69. let (request, state) = wallet_issue::prepare(&mut *rng, Wallet::using_pubkey(public))?;
  70. let (reply, wallet) = wallet_issue::handle(
  71. &mut *rng,
  72. request,
  73. |W: &mut Wallet| {
  74. W.set_privkey(privkey);
  75. W.balance = Some(balance.into());
  76. Ok(())
  77. },
  78. |I: &Wallet| Ok(()),
  79. )?;
  80. let res = state.finalize(reply);
  81. match res {
  82. Ok(c) => Ok(c),
  83. Err((err, _state)) => Err(err),
  84. }
  85. }
  86. #[test]
  87. fn test_wallet() -> Result<(), CMZError> {
  88. // Initialization
  89. let mut rng = rand::thread_rng();
  90. cmz_group_init(G::hash_from_bytes::<Sha512>(b"CMZ Generator A"));
  91. // Issuer: generate private and public keys for each type of
  92. // credential. (The client gets a copy of the public keys.)
  93. let (wallet_priv, wallet_pub) = Wallet::cmz_gen_keys(&mut rng);
  94. let (item_priv, item_pub) = Item::cmz_gen_keys(&mut rng);
  95. // The issuer makes some Item credentials for various items by just
  96. // executing the issuing protocol with itself
  97. let ebook_item = issue_item(&mut rng, 100, 2995, &item_priv, &item_pub)?;
  98. let album_item = issue_item(&mut rng, 200, 995, &item_priv, &item_pub)?;
  99. // The verify_MAC function is only usable for debugging, since the
  100. // client will not have the private key and the issuer will
  101. // typically not have the complete credential. But we'll use it
  102. // here to check that the credentials we get back are correct.
  103. ebook_item.verify_MAC(&item_priv).unwrap();
  104. album_item.verify_MAC(&item_priv).unwrap();
  105. // In exchange for out of band funds, the issuer generates a loaded
  106. // wallet and sends it to the client.
  107. let initial_wallet = issue_wallet(&mut rng, 10000, &wallet_priv, &wallet_pub)?;
  108. initial_wallet.verify_MAC(&wallet_priv).unwrap();
  109. // Buy an item (no fee version)
  110. // client actions
  111. let mut N = Wallet::using_pubkey(&wallet_pub);
  112. N.balance = Some(initial_wallet.balance.unwrap() - ebook_item.price.unwrap());
  113. let (request, state) = wallet_spend::prepare(&mut rng, &initial_wallet, &ebook_item, N)?;
  114. let reqbytes = request.as_bytes();
  115. // issuer actions
  116. let recvreq = wallet_spend::Request::try_from(&reqbytes[..]).unwrap();
  117. let (reply, (_W_issuer, _I_issuer, _N_issuer)) = wallet_spend::handle(
  118. &mut rng,
  119. recvreq,
  120. |W: &mut Wallet, I: &mut Item, N: &mut Wallet| {
  121. W.set_privkey(&wallet_priv);
  122. I.set_privkey(&item_priv);
  123. N.set_privkey(&wallet_priv);
  124. Ok(())
  125. },
  126. // This callback should test that W.randid has never been seen
  127. // before, but it doesn't currently do that.
  128. |W: &Wallet, I: &Item, N: &Wallet| Ok(()),
  129. )?;
  130. let replybytes = reply.as_bytes();
  131. // client actions
  132. let recvreply = wallet_spend::Reply::try_from(&replybytes[..]).unwrap();
  133. let W_issued = state.finalize(recvreply).unwrap();
  134. W_issued.verify_MAC(&wallet_priv).unwrap();
  135. // The version of the protocol parameterized by a fee. The client
  136. // and issue must agree on the params.
  137. let params = wallet_spend_with_fee::Params { fee: 5u128.into() };
  138. // client actions
  139. let mut N_fee = Wallet::using_pubkey(&wallet_pub);
  140. N_fee.balance = Some(W_issued.balance.unwrap() - album_item.price.unwrap() - params.fee);
  141. let (request_fee, state_fee) =
  142. wallet_spend_with_fee::prepare(&mut rng, &W_issued, &album_item, N_fee, &params)?;
  143. let reqbytes_fee = request_fee.as_bytes();
  144. // issuer actions
  145. let recvreq_fee = wallet_spend_with_fee::Request::try_from(&reqbytes_fee[..]).unwrap();
  146. let (reply_fee, (_W_fee_issuer, _I_fee_isser, _N_fee_issuer)) = wallet_spend_with_fee::handle(
  147. &mut rng,
  148. recvreq_fee,
  149. |W: &mut Wallet, I: &mut Item, N: &mut Wallet| {
  150. W.set_privkey(&wallet_priv);
  151. I.set_privkey(&item_priv);
  152. N.set_privkey(&wallet_priv);
  153. Ok(wallet_spend_with_fee::Params { fee: params.fee })
  154. },
  155. // This callback should test that W.randid has never been seen
  156. // before, but it doesn't currently do that.
  157. |W: &Wallet, I: &Item, N: &Wallet| Ok(()),
  158. )?;
  159. let replybytes_fee = reply_fee.as_bytes();
  160. // client actions
  161. let recvreply_fee = wallet_spend_with_fee::Reply::try_from(&replybytes_fee[..]).unwrap();
  162. let W_issued_fee = state_fee.finalize(recvreply_fee).unwrap();
  163. W_issued_fee.verify_MAC(&wallet_priv).unwrap();
  164. Ok(())
  165. }