wallet.rs 8.6 KB

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