diff --git a/consensus/api/proto/external.proto b/consensus/api/proto/external.proto index 6e26a36a7c..66f70e97bc 100644 --- a/consensus/api/proto/external.proto +++ b/consensus/api/proto/external.proto @@ -75,10 +75,10 @@ message Amount { // A Pedersen commitment `v*G + s*H` CurvePoint commitment = 1; - // `masked_value = value + SHA3-512_scalar(shared_secret || n)` - CurveScalar masked_value = 2; + // `masked_value = value XOR_8 Blake2B("value_mask" || shared_secret)` + uint64 masked_value = 2; - // `masked_blinding = value + SHA3-512_scalar(SHA3-512_scalar(shared_secret || n)) + // `masked_blinding = blinding + Blake2B("bliding_mask" || shared_secret)) CurveScalar masked_blinding = 3; } diff --git a/consensus/api/src/conversions.rs b/consensus/api/src/conversions.rs index 1e1c80b49c..8c886dffca 100644 --- a/consensus/api/src/conversions.rs +++ b/consensus/api/src/conversions.rs @@ -616,17 +616,13 @@ impl TryFrom<&external::CurvePoint> for CompressedCommitment { impl From<&Amount> for external::Amount { fn from(source: &Amount) -> Self { - let mut amount = external::Amount::new(); - let commitment_bytes = source.commitment.to_bytes().to_vec(); - amount.mut_commitment().set_data(commitment_bytes); - - let masked_value_bytes = source.masked_value.as_bytes().to_vec(); - amount.mut_masked_value().set_data(masked_value_bytes); - let masked_blinding_bytes = source.masked_blinding.as_bytes().to_vec(); - amount.mut_masked_blinding().set_data(masked_blinding_bytes); + let mut amount = external::Amount::new(); + amount.mut_commitment().set_data(commitment_bytes); + amount.set_masked_value(source.masked_value); + amount.mut_masked_blinding().set_data(masked_blinding_bytes); amount } } @@ -646,10 +642,7 @@ impl TryFrom<&external::Amount> for Amount { Ok(CurveScalar::from_bytes_mod_order(curve_bytes)) }; - let masked_value: CurveScalar = { - let bytes = source.get_masked_value().get_data(); - vec_to_curve_scalar(bytes)? - }; + let masked_value = source.get_masked_value(); let masked_blinding: Blinding = { let bytes = source.get_masked_blinding().get_data(); diff --git a/transaction/core/src/amount.rs b/transaction/core/src/amount.rs index a691105743..718030a4ec 100644 --- a/transaction/core/src/amount.rs +++ b/transaction/core/src/amount.rs @@ -28,6 +28,12 @@ pub enum AmountError { InconsistentCommitment, } +/// Value mask hash function domain separator. +const VALUE_MASK: &str = "amount_value_mask"; + +/// Blinding mask hash function domain separator. +const BLINDING_MASK: &str = "amount_blinding_mask"; + /// A commitment to an amount of MobileCoin, denominated in picoMOB. #[derive(Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Message, Digestible)] pub struct Amount { @@ -35,11 +41,11 @@ pub struct Amount { #[prost(message, required, tag = "1")] pub commitment: CompressedCommitment, - /// `masked_value = value + Blake2B(shared_secret)` - #[prost(message, required, tag = "2")] - pub masked_value: CurveScalar, + /// `masked_value = value XOR_8 Blake2B(value_mask | shared_secret)` + #[prost(uint64, required, tag = "2")] + pub masked_value: u64, - /// `masked_blinding = blinding + Blake2B(Blake2B(shared_secret)) + /// `masked_blinding = blinding + Blake2B(blinding_mask | shared_secret)) #[prost(message, required, tag = "3")] pub masked_blinding: Blinding, } @@ -61,14 +67,18 @@ impl Amount { // Pedersen commitment `v*G + b*H`. let commitment = CompressedCommitment::new(value, blinding.into()); - // `v + Blake2B(shared_secret)` - let masked_value: Scalar = { - let value: Scalar = Scalar::from(value); - let mask = get_value_mask(&shared_secret); - value + mask + // The value is XORed with the first 8 bytes of the mask. + // `v XOR_8 Blake2B(value_mask | shared_secret)` + let masked_value: u64 = { + let mask: u64 = { + let mut temp = [0u8; 8]; + temp.copy_from_slice(&get_value_mask(&shared_secret).as_bytes()[0..8]); + u64::from_le_bytes(temp) + }; + value ^ mask }; - // `s + Blake2B(Blake2B(shared_secret))` + // `b + Blake2B("blinding_mask" | shared_secret)` let masked_blinding: Scalar = { let mask = get_blinding_mask(&shared_secret); blinding.as_ref() + mask @@ -76,8 +86,8 @@ impl Amount { Ok(Amount { commitment, + masked_value, masked_blinding: Blinding::from(masked_blinding), - masked_value: CurveScalar::from(masked_value), }) } @@ -107,19 +117,15 @@ impl Amount { /// Reveals `masked_value`. fn unmask_value(&self, shared_secret: &RistrettoPublic) -> u64 { - let mask = get_value_mask(shared_secret); - let masked_value: Scalar = self.masked_value.into(); - let value_as_scalar = masked_value - mask; - // TODO: better way to do this? - // We might want to give an error if scalar.as_bytes() is larger than u64 - let mut temp = [0u8; 8]; - temp.copy_from_slice(&value_as_scalar.as_bytes()[0..8]); - // Note: Dalek documents that scalar.as_bytes() returns in little-endian - // https://doc.dalek.rs/curve25519_dalek/scalar/struct.Scalar.html#method.as_bytes - u64::from_le_bytes(temp) + let mask: u64 = { + let mut temp = [0u8; 8]; + temp.copy_from_slice(&get_value_mask(&shared_secret).as_bytes()[0..8]); + u64::from_le_bytes(temp) + }; + self.masked_value ^ mask } - /// Reveals masked_blinding. + /// Reveals `masked_blinding`. fn unmask_blinding(&self, shared_secret: &RistrettoPublic) -> Blinding { let mask = get_blinding_mask(shared_secret); let masked_blinding: Scalar = self.masked_blinding.into(); @@ -127,44 +133,37 @@ impl Amount { } } -/// Computes `Blake2B(shared_secret)` +/// Computes `Blake2B(value_mask | shared_secret)`. /// /// # Arguments /// * `shared_secret` - The shared secret, e.g. `rB`. fn get_value_mask(shared_secret: &RistrettoPublic) -> Scalar { - get_mask(&shared_secret) + let mut hasher = Blake2b::new(); + hasher.input(&VALUE_MASK); + hasher.input(&shared_secret.to_bytes()); + Scalar::from_hash(hasher) } -/// Computes `Blake2B(Blake2B(shared_secret)`. +/// Computes `Blake2B(blinding_mask | shared_secret)`. /// /// # Arguments /// * `shared_secret` - The shared secret, e.g. `rB`. fn get_blinding_mask(shared_secret: &RistrettoPublic) -> Scalar { - let inner_mask = get_mask(shared_secret); - - let mut hasher = Blake2b::new(); - hasher.input(&inner_mask.to_bytes()); - - Scalar::from_hash(hasher) -} - -/// Computes `Blake2B(shared_secret)`. -fn get_mask(shared_secret: &RistrettoPublic) -> Scalar { let mut hasher = Blake2b::new(); + hasher.input(&BLINDING_MASK); hasher.input(&shared_secret.to_bytes()); Scalar::from_hash(hasher) } #[cfg(test)] -mod tests { - use crate::proptest_fixtures::*; - use proptest::prelude::*; - +mod amount_tests { use crate::{ amount::{Amount, AmountError}, + proptest_fixtures::*, ring_signature::{Scalar, GENERATORS}, CompressedCommitment, }; + use proptest::prelude::*; proptest! { @@ -236,7 +235,7 @@ mod tests { /// get_value should return InconsistentCommitment if the masked value is incorrect. fn test_get_value_incorrect_masked_value( value in any::(), - other_masked_value in arbitrary_curve_scalar(), + other_masked_value in any::(), blinding in arbitrary_blinding(), shared_secret in arbitrary_ristretto_public()) {