3
2

wallet.rs 5.9 KB

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