1#![cfg_attr(docsrs, feature(doc_auto_cfg))]
2#![doc = include_str!("../README.md")]
3#![deny(missing_docs)]
4#![cfg_attr(not(feature = "std"), no_std)]
5
6use std_shims::vec::Vec;
7
8use zeroize::{Zeroize, Zeroizing};
9
10use curve25519_dalek::{Scalar, EdwardsPoint};
11
12use monero_oxide::{
13 io::write_varint,
14 primitives::{Commitment, keccak256, keccak256_to_scalar},
15 ringct::EncryptedAmount,
16 transaction::Input,
17};
18
19pub use monero_oxide::*;
20
21pub use monero_rpc as rpc;
22
23pub use monero_address as address;
24
25mod view_pair;
26pub use view_pair::{ViewPairError, ViewPair, GuaranteedViewPair};
27
28pub mod extra;
30pub(crate) use extra::{PaymentId, Extra};
31
32pub(crate) mod output;
33pub use output::WalletOutput;
34
35mod scan;
36pub use scan::{Timelocked, ScanError, Scanner, GuaranteedScanner};
37
38mod decoys;
39pub use decoys::OutputWithDecoys;
40
41pub mod send;
43
44#[cfg(test)]
45mod tests;
46
47#[derive(Clone, PartialEq, Eq, Zeroize)]
48struct SharedKeyDerivations {
49 view_tag: u8,
51 shared_key: Scalar,
53}
54
55impl SharedKeyDerivations {
56 fn uniqueness(inputs: &[Input]) -> [u8; 32] {
58 let mut u = b"uniqueness".to_vec();
59 for input in inputs {
60 match input {
61 Input::Gen(height) => {
64 write_varint(height, &mut u).expect("write failed but <Vec as io::Write> doesn't fail");
65 }
66 Input::ToKey { key_image, .. } => u.extend(key_image.compress().to_bytes()),
67 }
68 }
69 keccak256(u)
70 }
71
72 #[allow(clippy::needless_pass_by_value)]
73 fn output_derivations(
74 uniqueness: Option<[u8; 32]>,
75 ecdh: Zeroizing<EdwardsPoint>,
76 o: usize,
77 ) -> Zeroizing<SharedKeyDerivations> {
78 let mut output_derivation = Zeroizing::new(
80 Zeroizing::new(Zeroizing::new(ecdh.mul_by_cofactor()).compress().to_bytes()).to_vec(),
81 );
82
83 {
85 let output_derivation: &mut Vec<u8> = output_derivation.as_mut();
86 write_varint(&o, output_derivation)
87 .expect("write failed but <Vec as io::Write> doesn't fail");
88 }
89
90 let view_tag = keccak256([b"view_tag".as_ref(), &output_derivation].concat())[0];
91
92 let output_derivation = if let Some(uniqueness) = uniqueness {
94 Zeroizing::new([uniqueness.as_ref(), &output_derivation].concat())
95 } else {
96 output_derivation
97 };
98
99 Zeroizing::new(SharedKeyDerivations {
100 view_tag,
101 shared_key: keccak256_to_scalar(&output_derivation),
102 })
103 }
104
105 #[allow(clippy::needless_pass_by_value)]
107 fn payment_id_xor(ecdh: Zeroizing<EdwardsPoint>) -> [u8; 8] {
108 let output_derivation = Zeroizing::new(
110 Zeroizing::new(Zeroizing::new(ecdh.mul_by_cofactor()).compress().to_bytes()).to_vec(),
111 );
112
113 let mut payment_id_xor = [0; 8];
114 payment_id_xor
115 .copy_from_slice(&keccak256([output_derivation.as_ref(), [0x8d].as_ref()].concat())[.. 8]);
116 payment_id_xor
117 }
118
119 fn commitment_mask(&self) -> Scalar {
120 let mut mask = b"commitment_mask".to_vec();
121 mask.extend(self.shared_key.as_bytes());
122 let res = keccak256_to_scalar(&mask);
123 mask.zeroize();
124 res
125 }
126
127 fn compact_amount_encryption(&self, amount: u64) -> [u8; 8] {
128 let mut amount_mask = Zeroizing::new(b"amount".to_vec());
129 amount_mask.extend(self.shared_key.to_bytes());
130 let mut amount_mask = keccak256(&amount_mask);
131
132 let mut amount_mask_8 = [0; 8];
133 amount_mask_8.copy_from_slice(&amount_mask[.. 8]);
134 amount_mask.zeroize();
135
136 (amount ^ u64::from_le_bytes(amount_mask_8)).to_le_bytes()
137 }
138
139 fn decrypt(&self, enc_amount: &EncryptedAmount) -> Commitment {
140 match enc_amount {
141 EncryptedAmount::Original { mask, amount } => {
142 let mask_shared_sec_scalar = keccak256_to_scalar(self.shared_key.as_bytes());
143 let amount_shared_sec_scalar = keccak256_to_scalar(mask_shared_sec_scalar.as_bytes());
144
145 let mask = Scalar::from_bytes_mod_order(*mask) - mask_shared_sec_scalar;
146 let amount_scalar = Scalar::from_bytes_mod_order(*amount) - amount_shared_sec_scalar;
147
148 let amount = u64::from_le_bytes(
150 amount_scalar.to_bytes()[.. 8]
151 .try_into()
152 .expect("32-byte array couldn't have an 8-byte slice taken"),
153 );
154
155 Commitment::new(mask, amount)
156 }
157 EncryptedAmount::Compact { amount } => Commitment::new(
158 self.commitment_mask(),
159 u64::from_le_bytes(self.compact_amount_encryption(u64::from_le_bytes(*amount))),
160 ),
161 }
162 }
163}