diff --git a/coins/monero/src/ringct/bulletproofs/mod.rs b/coins/monero/src/ringct/bulletproofs/mod.rs index 2d62553d..9c3d571e 100644 --- a/coins/monero/src/ringct/bulletproofs/mod.rs +++ b/coins/monero/src/ringct/bulletproofs/mod.rs @@ -189,7 +189,7 @@ impl Bulletproof { self.write_core(w, |points, w| write_vec(write_point, points, w)) } - /// Serialize the Bulletproof(+) to a Vec. + /// Serialize the Bulletproof(+) to a `Vec`. pub fn serialize(&self) -> Vec { let mut serialized = vec![]; self.write(&mut serialized).unwrap(); diff --git a/coins/monero/src/wallet/mod.rs b/coins/monero/src/wallet/mod.rs index 4125daa1..7ccf27a5 100644 --- a/coins/monero/src/wallet/mod.rs +++ b/coins/monero/src/wallet/mod.rs @@ -10,7 +10,7 @@ use curve25519_dalek::{ }; use crate::{ - hash, hash_to_scalar, serialize::write_varint, ringct::EncryptedAmount, transaction::Input, + hash, hash_to_scalar, serialize::write_varint, Commitment, ringct::EncryptedAmount, transaction::Input, }; pub mod extra; @@ -94,32 +94,38 @@ pub(crate) fn commitment_mask(shared_key: Scalar) -> Scalar { hash_to_scalar(&mask) } -pub(crate) fn amount_encryption(amount: u64, key: Scalar) -> [u8; 8] { +pub(crate) fn compact_amount_encryption(amount: u64, key: Scalar) -> [u8; 8] { let mut amount_mask = b"amount".to_vec(); amount_mask.extend(key.to_bytes()); (amount ^ u64::from_le_bytes(hash(&amount_mask)[.. 8].try_into().unwrap())).to_le_bytes() } -// TODO: Move this under EncryptedAmount? -fn amount_decryption(amount: &EncryptedAmount, key: Scalar) -> (Scalar, u64) { - match amount { - EncryptedAmount::Original { mask, amount } => { - let mask_shared_sec = hash(key.as_bytes()); - let mask = - Scalar::from_bytes_mod_order(*mask) - Scalar::from_bytes_mod_order(mask_shared_sec); +impl EncryptedAmount { + /// Decrypt an EncryptedAmount into the Commitment it encrypts. + /// + /// The caller must verify the decrypted Commitment matches with the actual Commitment used + /// within in the Monero protocol. + pub fn decrypt(&self, key: Scalar) -> Commitment { + match self { + // TODO: Add a test vector for this + EncryptedAmount::Original { mask, amount } => { + let mask_shared_sec = hash(key.as_bytes()); + let mask = + Scalar::from_bytes_mod_order(*mask) - Scalar::from_bytes_mod_order(mask_shared_sec); - let amount_shared_sec = hash(&mask_shared_sec); - let amount_scalar = - Scalar::from_bytes_mod_order(*amount) - Scalar::from_bytes_mod_order(amount_shared_sec); - // d2b from rctTypes.cpp - let amount = u64::from_le_bytes(amount_scalar.to_bytes()[0 .. 8].try_into().unwrap()); + let amount_shared_sec = hash(&mask_shared_sec); + let amount_scalar = + Scalar::from_bytes_mod_order(*amount) - Scalar::from_bytes_mod_order(amount_shared_sec); + // d2b from rctTypes.cpp + let amount = u64::from_le_bytes(amount_scalar.to_bytes()[0 .. 8].try_into().unwrap()); - (mask, amount) + Commitment::new(mask, amount) + } + EncryptedAmount::Compact { amount } => Commitment::new( + commitment_mask(key), + u64::from_le_bytes(compact_amount_encryption(u64::from_le_bytes(*amount), key)), + ), } - EncryptedAmount::Compact { amount } => ( - commitment_mask(key), - u64::from_le_bytes(amount_encryption(u64::from_le_bytes(*amount), key)), - ), } } diff --git a/coins/monero/src/wallet/scan.rs b/coins/monero/src/wallet/scan.rs index 45bae04d..f9a33d47 100644 --- a/coins/monero/src/wallet/scan.rs +++ b/coins/monero/src/wallet/scan.rs @@ -17,9 +17,7 @@ use crate::{ transaction::{Input, Timelock, Transaction}, block::Block, rpc::{RpcError, RpcConnection, Rpc}, - wallet::{ - PaymentId, Extra, address::SubaddressIndex, Scanner, uniqueness, shared_key, amount_decryption, - }, + wallet::{PaymentId, Extra, address::SubaddressIndex, Scanner, uniqueness, shared_key}, }; /// An absolute output ID, defined as its transaction hash and output index. @@ -427,15 +425,13 @@ impl Scanner { commitment.amount = amount; // Regular transaction } else { - let (mask, amount) = match tx.rct_signatures.base.encrypted_amounts.get(o) { - Some(amount) => amount_decryption(amount, shared_key), + commitment = match tx.rct_signatures.base.encrypted_amounts.get(o) { + Some(amount) => amount.decrypt(shared_key), // This should never happen, yet it may be possible with miner transactions? // Using get just decreases the possibility of a panic and lets us move on in that case None => break, }; - // Rebuild the commitment to verify it - commitment = Commitment::new(mask, amount); // If this is a malicious commitment, move to the next output // Any other R value will calculate to a different spend key and are therefore ignorable if Some(&commitment.calculate()) != tx.rct_signatures.base.commitments.get(o) { diff --git a/coins/monero/src/wallet/send/mod.rs b/coins/monero/src/wallet/send/mod.rs index e230725e..565c101b 100644 --- a/coins/monero/src/wallet/send/mod.rs +++ b/coins/monero/src/wallet/send/mod.rs @@ -39,7 +39,7 @@ use crate::{ wallet::{ address::{Network, AddressSpec, MoneroAddress}, ViewPair, SpendableOutput, Decoys, PaymentId, ExtraField, Extra, key_image_sort, uniqueness, - shared_key, commitment_mask, amount_encryption, + shared_key, commitment_mask, compact_amount_encryption, extra::{ARBITRARY_DATA_MARKER, MAX_ARBITRARY_DATA_SIZE}, }, }; @@ -92,7 +92,7 @@ impl SendOutput { view_tag, dest: ((&shared_key * ED25519_BASEPOINT_TABLE) + output.0.spend), commitment: Commitment::new(commitment_mask(shared_key), output.1), - amount: amount_encryption(output.1, shared_key), + amount: compact_amount_encryption(output.1, shared_key), }, payment_id, ) @@ -936,7 +936,7 @@ impl Eventuality { return false; } - // TODO: Remove this when the following for loop is updated + // TODO: Remove this if/when the following for loop is updated to support older TXs assert!( rct_type.compact_encrypted_amounts(), "created an Eventuality for a very old RctType we don't support proving for"