monero_wallet/
lib.rs

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
28/// Structures and functionality for working with transactions' extra fields.
29pub 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
41/// Structs and functionality for sending transactions.
42pub mod send;
43
44#[cfg(test)]
45mod tests;
46
47#[derive(Clone, PartialEq, Eq, Zeroize)]
48struct SharedKeyDerivations {
49  // Hs("view_tag" || 8Ra || o)
50  view_tag: u8,
51  // Hs(uniqueness || 8Ra || o) where uniqueness may be empty
52  shared_key: Scalar,
53}
54
55impl SharedKeyDerivations {
56  // https://gist.github.com/kayabaNerve/8066c13f1fe1573286ba7a2fd79f6100
57  fn uniqueness(inputs: &[Input]) -> [u8; 32] {
58    let mut u = b"uniqueness".to_vec();
59    for input in inputs {
60      match input {
61        // If Gen, this should be the only input, making this loop somewhat pointless
62        // This works and even if there were somehow multiple inputs, it'd be a false negative
63        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    // 8Ra
79    let mut output_derivation = Zeroizing::new(
80      Zeroizing::new(Zeroizing::new(ecdh.mul_by_cofactor()).compress().to_bytes()).to_vec(),
81    );
82
83    // || o
84    {
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    // uniqueness ||
93    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  // H(8Ra || 0x8d)
106  #[allow(clippy::needless_pass_by_value)]
107  fn payment_id_xor(ecdh: Zeroizing<EdwardsPoint>) -> [u8; 8] {
108    // 8Ra
109    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        // d2b from rctTypes.cpp
149        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}