diff --git a/coins/monero/src/transaction.rs b/coins/monero/src/transaction.rs index 53219b3e..174a83d8 100644 --- a/coins/monero/src/transaction.rs +++ b/coins/monero/src/transaction.rs @@ -62,7 +62,7 @@ impl Input { pub struct Output { pub amount: u64, pub key: EdwardsPoint, - pub tag: Option, + pub view_tag: Option, } impl Output { @@ -72,31 +72,34 @@ impl Output { pub fn serialize(&self, w: &mut W) -> std::io::Result<()> { write_varint(&self.amount, w)?; - w.write_all(&[2 + (if self.tag.is_some() { 1 } else { 0 })])?; + w.write_all(&[2 + (if self.view_tag.is_some() { 1 } else { 0 })])?; write_point(&self.key, w)?; - if let Some(tag) = self.tag { - w.write_all(&[tag])?; + if let Some(view_tag) = self.view_tag { + w.write_all(&[view_tag])?; } Ok(()) } pub fn deserialize(r: &mut R) -> std::io::Result { let amount = read_varint(r)?; - let mut tag = [0]; - r.read_exact(&mut tag)?; - if (tag[0] != 2) && (tag[0] != 3) { - Err(std::io::Error::new( + let mut output_type = [0]; + r.read_exact(&mut output_type)?; + let view_tag = match output_type[0] { + 2 => false, + 3 => true, + _ => Err(std::io::Error::new( std::io::ErrorKind::Other, "Tried to deserialize unknown/unused output type", - ))?; - } + ))?, + }; Ok(Output { amount, key: read_point(r)?, - tag: if tag[0] == 3 { - r.read_exact(&mut tag)?; - Some(tag[0]) + view_tag: if view_tag { + let mut view_tag = [0]; + r.read_exact(&mut view_tag)?; + Some(view_tag[0]) } else { None }, diff --git a/coins/monero/src/wallet/mod.rs b/coins/monero/src/wallet/mod.rs index 5822163d..e60979cc 100644 --- a/coins/monero/src/wallet/mod.rs +++ b/coins/monero/src/wallet/mod.rs @@ -35,22 +35,29 @@ pub(crate) fn uniqueness(inputs: &[Input]) -> [u8; 32] { hash(&u) } -// Hs(8Ra || o) with https://github.com/monero-project/research-lab/issues/103 as an option +// Hs("view_tag" || 8Ra || o) and Hs(8Ra || o) with uniqueness inclusion as an option #[allow(non_snake_case)] pub(crate) fn shared_key( uniqueness: Option<[u8; 32]>, s: Scalar, P: &EdwardsPoint, o: usize, -) -> Scalar { - // uniqueness - let mut shared = uniqueness.map_or(vec![], |uniqueness| uniqueness.to_vec()); - // || 8Ra - shared.extend((s * P).mul_by_cofactor().compress().to_bytes().to_vec()); +) -> (u8, Scalar) { + // 8Ra + let mut output_derivation = (s * P).mul_by_cofactor().compress().to_bytes().to_vec(); // || o - write_varint(&o.try_into().unwrap(), &mut shared).unwrap(); - // Hs() - hash_to_scalar(&shared) + write_varint(&o.try_into().unwrap(), &mut output_derivation).unwrap(); + + let view_tag = hash(&[b"view_tag".as_ref(), &output_derivation].concat())[0]; + + // uniqueness || + let shared_key = if let Some(uniqueness) = uniqueness { + [uniqueness.as_ref(), &output_derivation].concat().to_vec() + } else { + output_derivation + }; + + (view_tag, hash_to_scalar(&shared_key)) } pub(crate) fn amount_encryption(amount: u64, key: Scalar) -> [u8; 8] { diff --git a/coins/monero/src/wallet/scan.rs b/coins/monero/src/wallet/scan.rs index 92ac3e3a..021c5c6f 100644 --- a/coins/monero/src/wallet/scan.rs +++ b/coins/monero/src/wallet/scan.rs @@ -101,12 +101,19 @@ impl Transaction { for (o, output) in self.prefix.outputs.iter().enumerate() { // TODO: This may be replaceable by pubkeys[o] for pubkey in &pubkeys { - let key_offset = shared_key( + let (view_tag, key_offset) = shared_key( Some(uniqueness(&self.prefix.inputs)).filter(|_| guaranteed), view.view, pubkey, o, ); + + if let Some(actual_view_tag) = output.view_tag { + if actual_view_tag != view_tag { + continue; + } + } + // P - shared == spend if (output.key - (&key_offset * &ED25519_BASEPOINT_TABLE)) != view.spend { continue; diff --git a/coins/monero/src/wallet/send/mod.rs b/coins/monero/src/wallet/send/mod.rs index b90a13e1..f89f777d 100644 --- a/coins/monero/src/wallet/send/mod.rs +++ b/coins/monero/src/wallet/send/mod.rs @@ -38,6 +38,7 @@ pub use multisig::TransactionMachine; #[derive(Clone, PartialEq, Eq, Debug)] struct SendOutput { R: EdwardsPoint, + view_tag: u8, dest: EdwardsPoint, commitment: Commitment, amount: [u8; 8], @@ -51,7 +52,7 @@ impl SendOutput { o: usize, ) -> SendOutput { let r = random_scalar(rng); - let shared_key = + let (view_tag, shared_key) = shared_key(Some(unique).filter(|_| output.0.meta.guaranteed), r, &output.0.view, o); let spend = output.0.spend; @@ -63,6 +64,7 @@ impl SendOutput { } AddressType::Subaddress => r * spend, }, + view_tag, dest: ((&shared_key * &ED25519_BASEPOINT_TABLE) + spend), commitment: Commitment::new(commitment_mask(shared_key), output.1), amount: amount_encryption(output.1, shared_key), @@ -297,7 +299,7 @@ impl SignableTransaction { tx_outputs.push(Output { amount: 0, key: self.outputs[o].dest, - tag: Some(0).filter(|_| matches!(self.protocol, Protocol::v16)), + view_tag: Some(self.outputs[o].view_tag).filter(|_| matches!(self.protocol, Protocol::v16)), }); ecdh_info.push(self.outputs[o].amount); }