Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ZKS-05] Caller Is Not Fixed Throughout Function Execution #2076

Merged
merged 13 commits into from
Jan 27, 2024
13 changes: 13 additions & 0 deletions circuit/program/src/request/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@ pub struct Request<A: Aleo> {
tvk: Field<A>,
/// The transition commitment.
tcm: Field<A>,
/// The signer commitment.
scm: Field<A>,
}

#[cfg(console)]
Expand All @@ -147,6 +149,9 @@ impl<A: Aleo> Inject for Request<A> {
// Inject the transition commitment `tcm` as `Mode::Public`.
let tcm = Field::new(Mode::Public, *request.tcm());

// Inject the signer commitment `scm` as `Mode::Public`.
let scm = Field::new(Mode::Public, *request.scm());

// Inject the inputs.
let inputs = match request
.input_ids()
Expand Down Expand Up @@ -218,6 +223,7 @@ impl<A: Aleo> Inject for Request<A> {
sk_tag: Field::new(mode, *request.sk_tag()),
tvk: Field::new(mode, *request.tvk()),
tcm,
scm,
}
}
}
Expand Down Expand Up @@ -272,6 +278,11 @@ impl<A: Aleo> Request<A> {
pub const fn tcm(&self) -> &Field<A> {
&self.tcm
}

/// Returns the signer commitment.
pub const fn scm(&self) -> &Field<A> {
&self.scm
}
}

#[cfg(console)]
Expand All @@ -290,6 +301,7 @@ impl<A: Aleo> Eject for Request<A> {
self.sk_tag.eject_mode(),
self.tvk.eject_mode(),
self.tcm.eject_mode(),
self.scm.eject_mode(),
])
}

Expand All @@ -306,6 +318,7 @@ impl<A: Aleo> Eject for Request<A> {
self.sk_tag.eject_value(),
self.tvk.eject_value(),
self.tcm.eject_value(),
self.scm.eject_value(),
))
}
}
24 changes: 18 additions & 6 deletions circuit/program/src/request/verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,15 @@ use super::*;

impl<A: Aleo> Request<A> {
/// Returns `true` if the input IDs are derived correctly, the input records all belong to the signer,
/// and the signature is valid. tpk is passed separately so it can have a Mode different from Self.
/// and the signature is valid.
///
/// Verifies (challenge == challenge') && (address == address') && (serial_numbers == serial_numbers') where:
/// challenge' := HashToScalar(r * G, pk_sig, pr_sig, signer, \[tvk, tcm, function ID, input IDs\])
pub fn verify(
&self,
input_types: &[console::ValueType<A::Network>],
tpk: &Group<A>,
root_tvk: Option<Field<A>>,
is_root: Boolean<A>,
) -> Boolean<A> {
// Compute the function ID.
Expand Down Expand Up @@ -59,15 +60,21 @@ impl<A: Aleo> Request<A> {
None => A::halt("Missing input elements in request verification"),
}

// Verify the transition public key and commitment are well-formed.
let root_tvk = root_tvk.unwrap_or(Field::<A>::new(Mode::Private, self.tvk.eject_value()));

// Verify the transition public key and commitments are well-formed.
let tpk_checks = {
// Compute the transition commitment as `Hash(tvk)`.
let tcm = A::hash_psd2(&[self.tvk.clone()]);
// Compute the signer commitment as `Hash(signer || root_tvk)`.
let scm = A::hash_psd2(&[self.signer.to_field(), root_tvk]);

// Ensure the transition public key matches with the saved one from the signature.
tpk.is_equal(&self.to_tpk())
// Ensure the computed transition commitment matches.
& tcm.is_equal(&self.tcm)
// Ensure the computed signer commitment matches.
& scm.is_equal(&self.scm)
};

// Verify the signature.
Expand Down Expand Up @@ -356,6 +363,9 @@ mod tests {
console::ValueType::from_str("token.aleo/token.record").unwrap(),
];

// Sample 'root_tvk'.
let root_tvk = None;

// Sample 'is_root'.
let is_root = true;

Expand All @@ -366,6 +376,7 @@ mod tests {
function_name,
inputs.iter(),
&input_types,
root_tvk,
is_root,
rng,
)?;
Expand All @@ -377,7 +388,8 @@ mod tests {
let is_root = Boolean::new(mode, is_root);

Circuit::scope(format!("Request {i}"), || {
let candidate = request.verify(&input_types, &tpk, is_root);
let root_tvk = None;
let candidate = request.verify(&input_types, &tpk, root_tvk, is_root);
assert!(candidate.eject_value());
match mode.is_constant() {
true => assert_scope!(<=num_constants, <=num_public, <=num_private, <=num_constraints),
Expand Down Expand Up @@ -411,16 +423,16 @@ mod tests {
// Note: This is correct. At this (high) level of a program, we override the default mode in the `Record` case,
// based on the user-defined visibility in the record type. Thus, we have nonzero private and constraint values.
// These bounds are determined experimentally.
check_verify(Mode::Constant, 42629, 0, 17494, 17518)
check_verify(Mode::Constant, 43000, 0, 18000, 18000)
}

#[test]
fn test_sign_and_verify_public() -> Result<()> {
check_verify(Mode::Public, 40130, 0, 26401, 26429)
check_verify(Mode::Public, 40131, 0, 26675, 26702)
}

#[test]
fn test_sign_and_verify_private() -> Result<()> {
check_verify(Mode::Private, 40130, 0, 26401, 26429)
check_verify(Mode::Private, 40131, 0, 26675, 26702)
}
}
22 changes: 19 additions & 3 deletions console/program/src/request/bytes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,22 @@ impl<N: Network> FromBytes for Request<N> {
let tvk = FromBytes::read_le(&mut reader)?;
// Read the transition commitment.
let tcm = FromBytes::read_le(&mut reader)?;

Ok(Self::from((signer, network_id, program_id, function_name, input_ids, inputs, signature, sk_tag, tvk, tcm)))
// Read the signer commitment.
let scm = FromBytes::read_le(&mut reader)?;

Ok(Self::from((
signer,
network_id,
program_id,
function_name,
input_ids,
inputs,
signature,
sk_tag,
tvk,
tcm,
scm,
)))
}
}

Expand Down Expand Up @@ -93,7 +107,9 @@ impl<N: Network> ToBytes for Request<N> {
// Write the transition view key.
self.tvk.write_le(&mut writer)?;
// Write the transition commitment.
self.tcm.write_le(&mut writer)
self.tcm.write_le(&mut writer)?;
// Write the signer commitment.
self.scm.write_le(&mut writer)
}
}

Expand Down
18 changes: 15 additions & 3 deletions console/program/src/request/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ pub struct Request<N: Network> {
tvk: Field<N>,
/// The transition commitment.
tcm: Field<N>,
/// The signer commitment.
scm: Field<N>,
}

impl<N: Network>
Expand All @@ -62,11 +64,12 @@ impl<N: Network>
Field<N>,
Field<N>,
Field<N>,
Field<N>,
)> for Request<N>
{
/// Note: See `Request::sign` to create the request. This method is used to eject from a circuit.
fn from(
(signer, network_id, program_id, function_name, input_ids, inputs, signature, sk_tag, tvk, tcm): (
(signer, network_id, program_id, function_name, input_ids, inputs, signature, sk_tag, tvk, tcm, scm): (
Address<N>,
U16<N>,
ProgramID<N>,
Expand All @@ -77,13 +80,14 @@ impl<N: Network>
Field<N>,
Field<N>,
Field<N>,
Field<N>,
),
) -> Self {
// Ensure the network ID is correct.
if *network_id != N::ID {
N::halt(format!("Invalid network ID. Expected {}, found {}", N::ID, *network_id))
} else {
Self { signer, network_id, program_id, function_name, input_ids, inputs, signature, sk_tag, tvk, tcm }
Self { signer, network_id, program_id, function_name, input_ids, inputs, signature, sk_tag, tvk, tcm, scm }
}
}
}
Expand Down Expand Up @@ -150,6 +154,11 @@ impl<N: Network> Request<N> {
pub const fn tcm(&self) -> &Field<N> {
&self.tcm
}

/// Returns the signer commitment `scm`.
pub const fn scm(&self) -> &Field<N> {
&self.scm
}
}

#[cfg(test)]
Expand Down Expand Up @@ -196,9 +205,12 @@ mod test_helpers {
ValueType::from_str("token.aleo/token.record").unwrap(),
];

// Sample root_tvk.
let root_tvk = None;

// Compute the signed request.
let request =
Request::sign(&private_key, program_id, function_name, inputs.into_iter(), &input_types, is_root, rng).unwrap();
Request::sign(&private_key, program_id, function_name, inputs.into_iter(), &input_types, root_tvk, is_root, rng).unwrap();
assert!(request.verify(&input_types, is_root));
request
})
Expand Down
5 changes: 4 additions & 1 deletion console/program/src/request/serialize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ impl<N: Network> Serialize for Request<N> {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
match serializer.is_human_readable() {
true => {
let mut transition = serializer.serialize_struct("Request", 10)?;
let mut transition = serializer.serialize_struct("Request", 11)?;
transition.serialize_field("signer", &self.signer)?;
transition.serialize_field("network", &self.network_id)?;
transition.serialize_field("program", &self.program_id)?;
Expand All @@ -32,6 +32,7 @@ impl<N: Network> Serialize for Request<N> {
transition.serialize_field("sk_tag", &self.sk_tag)?;
transition.serialize_field("tvk", &self.tvk)?;
transition.serialize_field("tcm", &self.tcm)?;
transition.serialize_field("scm", &self.scm)?;
transition.end()
}
false => ToBytesSerializer::serialize_with_size_encoding(self, serializer),
Expand Down Expand Up @@ -68,6 +69,8 @@ impl<'de, N: Network> Deserialize<'de> for Request<N> {
DeserializeExt::take_from_value::<D>(&mut request, "tvk")?,
// Retrieve the `tcm`.
DeserializeExt::take_from_value::<D>(&mut request, "tcm")?,
// Retrieve the `scm`.
DeserializeExt::take_from_value::<D>(&mut request, "scm")?,
)))
}
false => FromBytesDeserializer::<Self>::deserialize_with_size_encoding(deserializer, "request"),
Expand Down
5 changes: 5 additions & 0 deletions console/program/src/request/sign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ impl<N: Network> Request<N> {
function_name: Identifier<N>,
inputs: impl ExactSizeIterator<Item = impl TryInto<Value<N>>>,
input_types: &[ValueType<N>],
root_tvk: Option<Field<N>>,
is_root: bool,
rng: &mut R,
) -> Result<Self> {
Expand Down Expand Up @@ -64,6 +65,9 @@ impl<N: Network> Request<N> {
let tvk = (*signer * r).to_x_coordinate();
// Compute the transition commitment `tcm` as `Hash(tvk)`.
let tcm = N::hash_psd2(&[tvk])?;
// Compute the signer commitment `scm` as `Hash(signer || root_tvk)`.
let root_tvk = root_tvk.unwrap_or(tvk);
let scm = N::hash_psd2(&[signer.deref().to_x_coordinate(), root_tvk])?;
// Compute 'is_root' as a field element.
let is_root = if is_root { Field::<N>::one() } else { Field::<N>::zero() };

Expand Down Expand Up @@ -234,6 +238,7 @@ impl<N: Network> Request<N> {
sk_tag,
tvk,
tcm,
scm,
})
}
}
17 changes: 14 additions & 3 deletions console/program/src/request/verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -252,12 +252,23 @@ mod tests {
ValueType::from_str("token.aleo/token.record").unwrap(),
];

// Sample 'root_tvk'.
let root_tvk = None;
// Sample 'is_root'.
let is_root = Uniform::rand(rng);

// Compute the signed request.
let request =
Request::sign(&private_key, program_id, function_name, inputs.into_iter(), &input_types, is_root, rng)
.unwrap();
let request = Request::sign(
&private_key,
program_id,
function_name,
inputs.into_iter(),
&input_types,
root_tvk,
is_root,
rng,
)
.unwrap();
assert!(request.verify(&input_types, is_root));
}
}
Expand Down
8 changes: 6 additions & 2 deletions ledger/block/src/transition/bytes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,12 @@ impl<N: Network> FromBytes for Transition<N> {
let tpk = FromBytes::read_le(&mut reader)?;
// Read the transition commitment.
let tcm = FromBytes::read_le(&mut reader)?;
// Read the signer commitment.
let scm = FromBytes::read_le(&mut reader)?;

// Construct the candidate transition.
let transition =
Self::new(program_id, function_name, inputs, outputs, tpk, tcm).map_err(|e| error(e.to_string()))?;
Self::new(program_id, function_name, inputs, outputs, tpk, tcm, scm).map_err(|e| error(e.to_string()))?;
// Ensure the transition ID matches the expected ID.
match transition_id == *transition.id() {
true => Ok(transition),
Expand Down Expand Up @@ -91,7 +93,9 @@ impl<N: Network> ToBytes for Transition<N> {
// Write the transition public key.
self.tpk.write_le(&mut writer)?;
// Write the transition commitment.
self.tcm.write_le(&mut writer)
self.tcm.write_le(&mut writer)?;
// Write the signer commitment.
self.scm.write_le(&mut writer)
}
}

Expand Down
14 changes: 12 additions & 2 deletions ledger/block/src/transition/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ pub struct Transition<N: Network> {
tpk: Group<N>,
/// The transition commitment.
tcm: Field<N>,
/// The transition signer commitment.
scm: Field<N>,
}

impl<N: Network> Transition<N> {
Expand All @@ -74,12 +76,13 @@ impl<N: Network> Transition<N> {
outputs: Vec<Output<N>>,
tpk: Group<N>,
tcm: Field<N>,
scm: Field<N>,
) -> Result<Self> {
// Compute the transition ID.
let function_tree = Self::function_tree(&inputs, &outputs)?;
let id = N::hash_bhp512(&(*function_tree.root(), tcm).to_bits_le())?;
// Return the transition.
Ok(Self { id: id.into(), program_id, function_name, inputs, outputs, tpk, tcm })
Ok(Self { id: id.into(), program_id, function_name, inputs, outputs, tpk, tcm, scm })
}

/// Initializes a new transition from a request and response.
Expand Down Expand Up @@ -255,8 +258,10 @@ impl<N: Network> Transition<N> {
let tpk = request.to_tpk();
// Retrieve the `tcm`.
let tcm = *request.tcm();
// Retrieve the `scm`.
let scm = *request.scm();
// Return the transition.
Self::new(program_id, function_name, inputs, outputs, tpk, tcm)
Self::new(program_id, function_name, inputs, outputs, tpk, tcm, scm)
}
}

Expand Down Expand Up @@ -295,6 +300,11 @@ impl<N: Network> Transition<N> {
pub const fn tcm(&self) -> &Field<N> {
&self.tcm
}

/// Returns the signer commitment.
pub const fn scm(&self) -> &Field<N> {
&self.scm
}
}

impl<N: Network> Transition<N> {
Expand Down
Loading