Skip to content

Commit

Permalink
Merge pull request #2374 from AleoHQ/committee-id
Browse files Browse the repository at this point in the history
[ZKS-02] Introduce Committee IDs
  • Loading branch information
howardwu authored Mar 2, 2024
2 parents 6922217 + fa3fbac commit cc68b0a
Show file tree
Hide file tree
Showing 13 changed files with 176 additions and 19 deletions.
23 changes: 23 additions & 0 deletions ledger/block/src/verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,29 @@ impl<N: Network> Block<N> {
Authority::Quorum(subdag) => subdag.timestamp(previous_committee_lookback),
};

// Check that the committee IDs are correct.
if let Authority::Quorum(subdag) = &self.authority {
// Check that the committee ID of the leader certificate is correct.
ensure!(
subdag.leader_certificate().committee_id() == current_committee_lookback.id(),
"Leader certificate has an incorrect committee ID"
);

// Check that all all certificates on each round have the same committee ID.
cfg_iter!(subdag).try_for_each(|(round, certificates)| {
// Check that every certificate for a given round shares the same committee ID.
let expected_committee_id = certificates
.first()
.map(|certificate| certificate.committee_id())
.ok_or(anyhow!("No certificates found for subdag round {round}"))?;
ensure!(
certificates.iter().skip(1).all(|certificate| certificate.committee_id() == expected_committee_id),
"Certificates on round {round} do not all have the same committee ID",
);
Ok(())
})?;
}

// Return success.
Ok((
expected_round,
Expand Down
8 changes: 8 additions & 0 deletions ledger/committee/src/bytes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ impl<N: Network> FromBytes for Committee<N> {
return Err(error("Invalid committee version"));
}

// Read the committee ID.
let id = Field::read_le(&mut reader)?;
// Read the starting round.
let starting_round = u64::read_le(&mut reader)?;
// Read the number of members.
Expand Down Expand Up @@ -51,6 +53,10 @@ impl<N: Network> FromBytes for Committee<N> {
let total_stake = u64::read_le(&mut reader)?;
// Construct the committee.
let committee = Self::new(starting_round, members).map_err(|e| error(e.to_string()))?;
// Ensure the committee ID matches.
if committee.id() != id {
return Err(error("Invalid committee ID during deserialization"));
}
// Ensure the total stake matches.
match committee.total_stake() == total_stake {
true => Ok(committee),
Expand All @@ -64,6 +70,8 @@ impl<N: Network> ToBytes for Committee<N> {
fn write_le<W: Write>(&self, mut writer: W) -> IoResult<()> {
// Write the version.
1u8.write_le(&mut writer)?;
// Write the committee ID.
self.id().write_le(&mut writer)?;
// Write the starting round.
self.starting_round.write_le(&mut writer)?;
// Write the number of members.
Expand Down
12 changes: 11 additions & 1 deletion ledger/committee/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
mod bytes;
mod serialize;
mod string;
mod to_id;

#[cfg(any(test, feature = "prop-tests"))]
pub mod prop_tests;
Expand All @@ -41,6 +42,8 @@ pub const MAX_DELEGATORS: u32 = 100_000u32;

#[derive(Clone, PartialEq, Eq)]
pub struct Committee<N: Network> {
/// The committee ID, defined as the hash of the starting round, members, and total stake.
id: Field<N>,
/// The starting round number for this committee.
starting_round: u64,
/// A map of `address` to `(stake, is_open)` state.
Expand Down Expand Up @@ -78,14 +81,21 @@ impl<N: Network> Committee<N> {
);
// Compute the total stake of the committee for this round.
let total_stake = Self::compute_total_stake(&members)?;
// Compute the committee ID.
let id = Self::compute_committee_id(starting_round, &members, total_stake)?;
#[cfg(feature = "metrics")]
metrics::gauge(metrics::committee::TOTAL_STAKE, total_stake as f64);
// Return the new committee.
Ok(Self { starting_round, members, total_stake })
Ok(Self { id, starting_round, members, total_stake })
}
}

impl<N: Network> Committee<N> {
/// Returns the committee ID.
pub const fn id(&self) -> Field<N> {
self.id
}

/// Returns the starting round number for this committee.
pub const fn starting_round(&self) -> u64 {
self.starting_round
Expand Down
8 changes: 7 additions & 1 deletion ledger/committee/src/serialize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ impl<N: Network> Serialize for Committee<N> {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
match serializer.is_human_readable() {
true => {
let mut certificate = serializer.serialize_struct("Committee", 3)?;
let mut certificate = serializer.serialize_struct("Committee", 4)?;
certificate.serialize_field("id", &self.id)?;
certificate.serialize_field("starting_round", &self.starting_round)?;
certificate.serialize_field("members", &self.members)?;
certificate.serialize_field("total_stake", &self.total_stake)?;
Expand All @@ -36,12 +37,17 @@ impl<'de, N: Network> Deserialize<'de> for Committee<N> {
match deserializer.is_human_readable() {
true => {
let mut value = serde_json::Value::deserialize(deserializer)?;
let id: Field<N> = DeserializeExt::take_from_value::<D>(&mut value, "id")?;
let total_stake: u64 = DeserializeExt::take_from_value::<D>(&mut value, "total_stake")?;
let committee = Self::new(
DeserializeExt::take_from_value::<D>(&mut value, "starting_round")?,
DeserializeExt::take_from_value::<D>(&mut value, "members")?,
)
.map_err(de::Error::custom)?;

if committee.id != id {
return Err(de::Error::custom("committee ID mismatch"));
}
match committee.total_stake == total_stake {
true => Ok(committee),
false => Err(de::Error::custom("total stake mismatch")),
Expand Down
50 changes: 50 additions & 0 deletions ledger/committee/src/to_id.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright (C) 2019-2023 Aleo Systems Inc.
// This file is part of the snarkVM library.

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
// http://www.apache.org/licenses/LICENSE-2.0

// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use super::*;

impl<N: Network> Committee<N> {
/// Returns the committee ID.
pub fn to_id(&self) -> Result<Field<N>> {
Self::compute_committee_id(self.starting_round, &self.members, self.total_stake)
}
}

impl<N: Network> Committee<N> {
/// Returns the commmitee ID.
pub fn compute_committee_id(
starting_round: u64,
members: &IndexMap<Address<N>, (u64, bool)>,
total_stake: u64,
) -> Result<Field<N>> {
let mut preimage = Vec::new();
// Insert the starting_round.
starting_round.write_le(&mut preimage)?;
// Write the number of members.
u16::try_from(members.len())?.write_le(&mut preimage)?;
// Write the members.
for (address, (stake, is_open)) in members {
// Write the address.
address.write_le(&mut preimage)?;
// Write the stake.
stake.write_le(&mut preimage)?;
// Write the is_open flag.
is_open.write_le(&mut preimage)?;
}
// Insert the total stake.
total_stake.write_le(&mut preimage)?;
// Hash the preimage.
N::hash_bhp1024(&preimage.to_bits_le())
}
}
15 changes: 10 additions & 5 deletions ledger/narwhal/batch-certificate/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,16 @@ impl<N: Network> BatchCertificate<N> {
self.batch_header().round()
}

/// Returns the timestamp of the batch header.
pub fn timestamp(&self) -> i64 {
self.batch_header().timestamp()
}

/// Returns the committee ID.
pub const fn committee_id(&self) -> Field<N> {
self.batch_header().committee_id()
}

/// Returns the transmission IDs.
pub const fn transmission_ids(&self) -> &IndexSet<TransmissionID<N>> {
self.batch_header().transmission_ids()
Expand All @@ -131,11 +141,6 @@ impl<N: Network> BatchCertificate<N> {
self.batch_header().previous_certificate_ids()
}

/// Returns the timestamp of the batch header.
pub fn timestamp(&self) -> i64 {
self.batch_header().timestamp()
}

/// Returns the signatures of the batch ID from the committee.
pub fn signatures(&self) -> Box<dyn '_ + ExactSizeIterator<Item = &Signature<N>>> {
Box::new(self.signatures.iter())
Expand Down
9 changes: 7 additions & 2 deletions ledger/narwhal/batch-header/src/bytes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ impl<N: Network> FromBytes for BatchHeader<N> {
let round = u64::read_le(&mut reader)?;
// Read the timestamp.
let timestamp = i64::read_le(&mut reader)?;
// Read the committee ID.
let committee_id = Field::read_le(&mut reader)?;

// Read the number of transmission IDs.
let num_transmission_ids = u32::read_le(&mut reader)?;
Expand Down Expand Up @@ -69,8 +71,9 @@ impl<N: Network> FromBytes for BatchHeader<N> {
let signature = Signature::read_le(&mut reader)?;

// Construct the batch.
let batch = Self::from(author, round, timestamp, transmission_ids, previous_certificate_ids, signature)
.map_err(error)?;
let batch =
Self::from(author, round, timestamp, committee_id, transmission_ids, previous_certificate_ids, signature)
.map_err(error)?;

// Return the batch.
match batch.batch_id == batch_id {
Expand All @@ -93,6 +96,8 @@ impl<N: Network> ToBytes for BatchHeader<N> {
self.round.write_le(&mut writer)?;
// Write the timestamp.
self.timestamp.write_le(&mut writer)?;
// Write the committee ID.
self.committee_id.write_le(&mut writer)?;
// Write the number of transmission IDs.
u32::try_from(self.transmission_ids.len()).map_err(|e| error(e.to_string()))?.write_le(&mut writer)?;
// Write the transmission IDs.
Expand Down
56 changes: 50 additions & 6 deletions ledger/narwhal/batch-header/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,16 @@ use narwhal_transmission_id::TransmissionID;
#[derive(Clone, PartialEq, Eq)]
pub struct BatchHeader<N: Network> {
/// The batch ID, defined as the hash of the author, round number, timestamp, transmission IDs,
/// previous batch certificate IDs, and last election certificate IDs.
/// committee ID, previous batch certificate IDs, and last election certificate IDs.
batch_id: Field<N>,
/// The author of the batch.
author: Address<N>,
/// The round number.
round: u64,
/// The timestamp.
timestamp: i64,
/// The committee ID.
committee_id: Field<N>,
/// The set of `transmission IDs`.
transmission_ids: IndexSet<TransmissionID<N>>,
/// The batch certificate IDs of the previous round.
Expand All @@ -66,6 +68,7 @@ impl<N: Network> BatchHeader<N> {
private_key: &PrivateKey<N>,
round: u64,
timestamp: i64,
committee_id: Field<N>,
transmission_ids: IndexSet<TransmissionID<N>>,
previous_certificate_ids: IndexSet<Field<N>>,
rng: &mut R,
Expand Down Expand Up @@ -95,18 +98,35 @@ impl<N: Network> BatchHeader<N> {
// Retrieve the address.
let author = Address::try_from(private_key)?;
// Compute the batch ID.
let batch_id = Self::compute_batch_id(author, round, timestamp, &transmission_ids, &previous_certificate_ids)?;
let batch_id = Self::compute_batch_id(
author,
round,
timestamp,
committee_id,
&transmission_ids,
&previous_certificate_ids,
)?;
// Sign the preimage.
let signature = private_key.sign(&[batch_id], rng)?;
// Return the batch header.
Ok(Self { author, batch_id, round, timestamp, transmission_ids, previous_certificate_ids, signature })
Ok(Self {
batch_id,
author,
round,
timestamp,
committee_id,
transmission_ids,
previous_certificate_ids,
signature,
})
}

/// Initializes a new batch header.
pub fn from(
author: Address<N>,
round: u64,
timestamp: i64,
committee_id: Field<N>,
transmission_ids: IndexSet<TransmissionID<N>>,
previous_certificate_ids: IndexSet<Field<N>>,
signature: Signature<N>,
Expand Down Expand Up @@ -134,13 +154,29 @@ impl<N: Network> BatchHeader<N> {
);

// Compute the batch ID.
let batch_id = Self::compute_batch_id(author, round, timestamp, &transmission_ids, &previous_certificate_ids)?;
let batch_id = Self::compute_batch_id(
author,
round,
timestamp,
committee_id,
&transmission_ids,
&previous_certificate_ids,
)?;
// Verify the signature.
if !signature.verify(&author, &[batch_id]) {
bail!("Invalid signature for the batch header");
}
// Return the batch header.
Ok(Self { author, batch_id, round, timestamp, transmission_ids, previous_certificate_ids, signature })
Ok(Self {
author,
batch_id,
round,
timestamp,
committee_id,
transmission_ids,
previous_certificate_ids,
signature,
})
}
}

Expand All @@ -165,6 +201,11 @@ impl<N: Network> BatchHeader<N> {
self.timestamp
}

/// Returns the committee ID.
pub const fn committee_id(&self) -> Field<N> {
self.committee_id
}

/// Returns the transmission IDs.
pub const fn transmission_ids(&self) -> &IndexSet<TransmissionID<N>> {
&self.transmission_ids
Expand Down Expand Up @@ -228,13 +269,16 @@ pub mod test_helpers {
) -> BatchHeader<CurrentNetwork> {
// Sample a private key.
let private_key = PrivateKey::new(rng).unwrap();
// Sample the committee ID.
let committee_id = Field::<CurrentNetwork>::rand(rng);
// Sample transmission IDs.
let transmission_ids =
narwhal_transmission_id::test_helpers::sample_transmission_ids(rng).into_iter().collect::<IndexSet<_>>();
// Checkpoint the timestamp for the batch.
let timestamp = OffsetDateTime::now_utc().unix_timestamp();
// Return the batch header.
BatchHeader::new(&private_key, round, timestamp, transmission_ids, previous_certificate_ids, rng).unwrap()
BatchHeader::new(&private_key, round, timestamp, committee_id, transmission_ids, previous_certificate_ids, rng)
.unwrap()
}

/// Returns a list of sample batch headers, sampled at random.
Expand Down
4 changes: 3 additions & 1 deletion ledger/narwhal/batch-header/src/serialize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,12 @@ impl<N: Network> Serialize for BatchHeader<N> {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
match serializer.is_human_readable() {
true => {
let mut header = serializer.serialize_struct("BatchHeader", 7)?;
let mut header = serializer.serialize_struct("BatchHeader", 8)?;
header.serialize_field("batch_id", &self.batch_id)?;
header.serialize_field("author", &self.author)?;
header.serialize_field("round", &self.round)?;
header.serialize_field("timestamp", &self.timestamp)?;
header.serialize_field("committee_id", &self.committee_id)?;
header.serialize_field("transmission_ids", &self.transmission_ids)?;
header.serialize_field("previous_certificate_ids", &self.previous_certificate_ids)?;
header.serialize_field("signature", &self.signature)?;
Expand All @@ -47,6 +48,7 @@ impl<'de, N: Network> Deserialize<'de> for BatchHeader<N> {
DeserializeExt::take_from_value::<D>(&mut header, "author")?,
DeserializeExt::take_from_value::<D>(&mut header, "round")?,
DeserializeExt::take_from_value::<D>(&mut header, "timestamp")?,
DeserializeExt::take_from_value::<D>(&mut header, "committee_id")?,
DeserializeExt::take_from_value::<D>(&mut header, "transmission_ids")?,
DeserializeExt::take_from_value::<D>(&mut header, "previous_certificate_ids")?,
DeserializeExt::take_from_value::<D>(&mut header, "signature")?,
Expand Down
Loading

0 comments on commit cc68b0a

Please sign in to comment.