Skip to content

Commit

Permalink
MC-1117 8-byte masked_value (#24)
Browse files Browse the repository at this point in the history
* Amount uses domain-separated hashes

* Amount.masked_value is now u64

* Updates proto for Amount

* Fixes blinding_mask, comments

* Fixes an import
  • Loading branch information
mfaulk authored Apr 21, 2020
1 parent 49076e3 commit acf85dd
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 55 deletions.
6 changes: 3 additions & 3 deletions consensus/api/proto/external.proto
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
17 changes: 5 additions & 12 deletions consensus/api/src/conversions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -590,17 +590,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
}
}
Expand All @@ -620,10 +616,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();
Expand Down
79 changes: 39 additions & 40 deletions transaction/core/src/amount.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,24 @@ 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 {
/// A Pedersen commitment `v*G + b*H` to a quantity `v` of MobileCoin, with blinding `b`,
#[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,
}
Expand All @@ -61,23 +67,27 @@ 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
};

Ok(Amount {
commitment,
masked_value,
masked_blinding: Blinding::from(masked_blinding),
masked_value: CurveScalar::from(masked_value),
})
}

Expand Down Expand Up @@ -107,64 +117,53 @@ 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();
Blinding::from(masked_blinding - mask)
}
}

/// 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! {

Expand Down Expand Up @@ -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::<u64>(),
other_masked_value in arbitrary_curve_scalar(),
other_masked_value in any::<u64>(),
blinding in arbitrary_blinding(),
shared_secret in arbitrary_ristretto_public())
{
Expand Down

0 comments on commit acf85dd

Please sign in to comment.