Skip to content
This repository has been archived by the owner on Aug 30, 2024. It is now read-only.

feat: simplify hash_validator_leaf #11

Merged
merged 3 commits into from
Aug 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 15 additions & 7 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions src/inputs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ pub struct Validator {
pub message: Vec<u8>,
pub message_byte_length: u32,
pub voting_power: u64,
pub validator_byte_length: u32,
pub validator_byte_length: usize,
pub enabled: bool,
pub signed: bool,
}
Expand Down Expand Up @@ -106,7 +106,7 @@ pub fn generate_step_inputs(block: usize) -> CelestiaBlockProof {
message: signed_vote.sign_bytes(),
message_byte_length: signed_vote.sign_bytes().len() as u32,
voting_power: validator.power(),
validator_byte_length: val_bytes.len() as u32,
validator_byte_length: val_bytes.len(),
enabled: true,
signed: true,
});
Expand Down
11 changes: 6 additions & 5 deletions src/step.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use plonky2x::ecc::ed25519::gadgets::curve::CircuitBuilderCurve;
use plonky2x::ecc::ed25519::gadgets::eddsa::{EDDSAPublicKeyTarget, EDDSASignatureTarget};
use plonky2x::num::nonnative::nonnative::CircuitBuilderNonNative;
use plonky2x::num::u32::gadgets::arithmetic_u32::{CircuitBuilderU32, U32Target};
use plonky2::iop::target::Target;

use crate::signature::TendermintSignature;
use crate::utils::{
Expand All @@ -35,7 +36,7 @@ pub struct ValidatorTarget<C: Curve> {
message: ValidatorMessageTarget,
message_byte_length: U32Target,
voting_power: I64Target,
validator_byte_length: U32Target,
validator_byte_length: Target,
enabled: BoolTarget,
signed: BoolTarget,
}
Expand Down Expand Up @@ -92,7 +93,7 @@ impl<F: RichField + Extendable<D>, const D: usize> TendermintStep<F, D> for Circ
let one = self.one();
// Verify each of the validators marshal correctly
// Assumes the validators are sorted in the correct order
let byte_lengths: Vec<U32Target> =
let byte_lengths: Vec<Target> =
validators.iter().map(|v| v.validator_byte_length).collect();
let marshalled_validators: Vec<MarshalledValidatorTarget> = validators
.iter()
Expand Down Expand Up @@ -248,7 +249,7 @@ where
builder.add_virtual_u32_target(),
builder.add_virtual_u32_target(),
]);
let validator_byte_length = builder.add_virtual_u32_target();
let validator_byte_length = builder.add_virtual_target();
let enabled = builder.add_virtual_bool_target_safe();
let signed = builder.add_virtual_bool_target_safe();

Expand Down Expand Up @@ -528,9 +529,9 @@ pub(crate) mod tests {
);

// Set length targets
pw.set_u32_target(
pw.set_target(
celestia_proof_target.validators[i].validator_byte_length,
validator.validator_byte_length as u32,
F::from_canonical_usize(validator.validator_byte_length as usize),
);
pw.set_u32_target(
celestia_proof_target.validators[i].message_byte_length,
Expand Down
140 changes: 36 additions & 104 deletions src/validator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,21 @@
//! The `pubkey` is encoded as the raw list of bytes used in the public key. The `varint` is
//! encoded using protobuf's default integer encoding, which consist of 7 bit payloads. You can
//! read more about them here: https://protobuf.dev/programming-guides/encoding/#varints.
use curta::plonky2::field::CubicParameters;
use plonky2::field::extension::Extendable;
use plonky2::iop::target::BoolTarget;
use plonky2::{hash::hash_types::RichField, plonk::circuit_builder::CircuitBuilder};
use plonky2x::ecc::ed25519::curve::curve_types::Curve;
use plonky2x::ecc::ed25519::curve::ed25519::Ed25519;
use plonky2x::ecc::ed25519::gadgets::curve::{AffinePointTarget, CircuitBuilderCurve};
use plonky2x::hash::sha::sha256::sha256;
use plonky2x::num::u32::gadgets::arithmetic_u32::{CircuitBuilderU32, U32Target};
use plonky2x::hash::sha::sha256::{sha256, sha256_variable_length_single_chunk};
use plonky2x::num::u32::gadgets::arithmetic_u32::CircuitBuilderU32;
use plonky2::iop::target::Target;

use crate::utils::{
EncTendermintHashTarget, I64Target, MarshalledValidatorTarget, TendermintHashTarget,
HASH_SIZE_BITS, NUM_POSSIBLE_VALIDATOR_BYTE_LENGTHS, PROTOBUF_HASH_SIZE_BITS,
VALIDATOR_BYTE_LENGTH_MAX, VALIDATOR_BYTE_LENGTH_MIN, VALIDATOR_SET_SIZE_MAX,
VOTING_POWER_BITS_LENGTH_MAX, VOTING_POWER_BYTES_LENGTH_MAX,
HASH_SIZE_BITS, PROTOBUF_HASH_SIZE_BITS,
VALIDATOR_BYTE_LENGTH_MAX, VALIDATOR_SET_SIZE_MAX,
VOTING_POWER_BITS_LENGTH_MAX, VOTING_POWER_BYTES_LENGTH_MAX, VALIDATOR_BIT_LENGTH_MAX,
};

pub trait TendermintMarshaller<F: RichField + Extendable<D>, const D: usize> {
Expand Down Expand Up @@ -57,14 +57,14 @@ pub trait TendermintMarshaller<F: RichField + Extendable<D>, const D: usize> {
fn hash_validator_leaf(
&mut self,
validator: &MarshalledValidatorTarget,
validator_byte_length: &U32Target,
validator_byte_length: Target,
) -> TendermintHashTarget;

/// Hashes multiple validators to get their leaves according to the Tendermint spec using hash_validator_leaf.
fn hash_validator_leaves(
&mut self,
validators: &Vec<MarshalledValidatorTarget>,
validator_byte_lengths: &Vec<U32Target>,
validator_byte_lengths: &Vec<Target>,
) -> Vec<TendermintHashTarget>;

/// Hashes two nodes to get the inner node according to the Tendermint spec. (0x01 || left || right)
Expand All @@ -88,7 +88,7 @@ pub trait TendermintMarshaller<F: RichField + Extendable<D>, const D: usize> {
fn hash_validator_set(
&mut self,
validators: &Vec<MarshalledValidatorTarget>,
validator_byte_length: &Vec<U32Target>,
validator_byte_lengths: &Vec<Target>,
validator_enabled: &Vec<BoolTarget>,
) -> TendermintHashTarget;
}
Expand Down Expand Up @@ -296,85 +296,30 @@ impl<F: RichField + Extendable<D>, const D: usize> TendermintMarshaller<F, D>
fn hash_validator_leaf(
&mut self,
validator: &MarshalledValidatorTarget,
validator_byte_length: &U32Target,
validator_byte_length: Target,
) -> TendermintHashTarget {
// Range check the validator byte length is between [VALIDATOR_BYTE_LENGTH_MIN, VALIDATOR_BYTE_LENGTH_MAX]
let min_validator_bytes_length =
self.constant(F::from_canonical_usize(VALIDATOR_BYTE_LENGTH_MIN));
let max_validator_bytes_length =
self.constant(F::from_canonical_usize(VALIDATOR_BYTE_LENGTH_MAX));

// len - min
let diff_with_min_length = self.sub(validator_byte_length.0, min_validator_bytes_length);

// max - len
let diff_with_max_length = self.sub(max_validator_bytes_length, validator_byte_length.0);

// Check that diff_with_min_len and diff_with_max_len are both small (if outside of range, one would be a large element of F).
self.range_check(diff_with_min_length, 4);
self.range_check(diff_with_max_length, 4);

// Note: Because the byte length of each validator is variable, need to hash the validator bytes for each potential byte length.
let mut validator_bytes_hashes =
[[self._false(); HASH_SIZE_BITS]; NUM_POSSIBLE_VALIDATOR_BYTE_LENGTHS];
for j in 0..NUM_POSSIBLE_VALIDATOR_BYTE_LENGTHS {
// Calculate the length of the message for the leaf hash.
// 0x00 || validatorBytes
let bits_length = 8 + (VALIDATOR_BYTE_LENGTH_MIN + j) * 8;

// Calculate the message for the leaf hash.
let mut validator_bits = vec![self._false(); bits_length];

// 0x00
for k in 0..8 {
validator_bits[k] = self._false();
}

// validatorBytes
for k in 8..bits_length {
validator_bits[k] = validator.0[k - 8];
}

// Load the output of the hash.
let hash = sha256(self, &validator_bits);
for k in 0..HASH_SIZE_BITS {
validator_bytes_hashes[j][k] = hash[k];
}
}
let validator_byte_length_min_constant =
self.constant(F::from_canonical_u32(VALIDATOR_BYTE_LENGTH_MIN as u32));
let one = self.one();
let eight = self.constant(F::from_canonical_usize(8));

// Calculate the index of the validator's bytes length in the range [0, NUM_POSSIBLE_VALIDATOR_BYTE_LENGTHS).
let length_index = self.sub(validator_byte_length.0, validator_byte_length_min_constant);
// Add one to account for the 0x00 byte.
let enc_validator_byte_length = self.add(one, validator_byte_length);
// Multiply by 8 to get the bit length.
let enc_validator_bit_length = self.mul(enc_validator_byte_length, eight);

// Create a bitmap, with a selector bit set to 1 if the current index corresponds to the index of the validator's bytes length.
let mut validator_byte_hash_selector = [self._false(); NUM_POSSIBLE_VALIDATOR_BYTE_LENGTHS];
for j in 0..NUM_POSSIBLE_VALIDATOR_BYTE_LENGTHS {
let byte_length_index = self.constant(F::from_canonical_u32(j as u32));
validator_byte_hash_selector[j] = self.is_equal(length_index, byte_length_index);
}

let mut ret_validator_leaf_hash = [self._false(); HASH_SIZE_BITS];
for j in 0..NUM_POSSIBLE_VALIDATOR_BYTE_LENGTHS {
for k in 0..HASH_SIZE_BITS {
// Select the correct byte hash for the validator's byte length.
// Copy the bits from the correct byte hash into the return hash if the selector bit for that byte length is set to 1.
// In all other cases, keep the existing bits in the return hash, yielding desired behavior.
ret_validator_leaf_hash[k] = BoolTarget::new_unsafe(self.select(
validator_byte_hash_selector[j],
validator_bytes_hashes[j][k].target,
ret_validator_leaf_hash[k].target,
));
}
// Encode leaf 0x00 || validator_bits
let mut enc_validator_bits = [self._false(); VALIDATOR_BIT_LENGTH_MAX + 8];
for i in 0..VALIDATOR_BIT_LENGTH_MAX {
enc_validator_bits[i + 8] = validator.0[i];
}
let hash = sha256_variable_length_single_chunk(self, &enc_validator_bits, enc_validator_bit_length);

TendermintHashTarget(ret_validator_leaf_hash)
TendermintHashTarget(hash.try_into().unwrap())
}

fn hash_validator_leaves(
&mut self,
validators: &Vec<MarshalledValidatorTarget>,
validator_byte_lengths: &Vec<U32Target>,
validator_byte_lengths: &Vec<Target>,
) -> Vec<TendermintHashTarget> {
let num_validators = self.constant(F::from_canonical_usize(validators.len()));
let num_validator_byte_lengths =
Expand All @@ -397,7 +342,7 @@ impl<F: RichField + Extendable<D>, const D: usize> TendermintMarshaller<F, D>
[TendermintHashTarget([self._false(); HASH_SIZE_BITS]); VALIDATOR_SET_SIZE_MAX];
for i in 0..VALIDATOR_SET_SIZE_MAX {
validators_leaf_hashes[i] =
self.hash_validator_leaf(&validators[i], &validator_byte_lengths[i]);
self.hash_validator_leaf(&validators[i], validator_byte_lengths[i]);
}

validators_leaf_hashes.to_vec()
Expand Down Expand Up @@ -484,7 +429,7 @@ impl<F: RichField + Extendable<D>, const D: usize> TendermintMarshaller<F, D>
fn hash_validator_set(
&mut self,
validators: &Vec<MarshalledValidatorTarget>,
validator_byte_lengths: &Vec<U32Target>,
validator_byte_lengths: &Vec<Target>,
validator_enabled: &Vec<BoolTarget>,
) -> TendermintHashTarget {
let num_validators = self.constant(F::from_canonical_usize(validators.len()));
Expand Down Expand Up @@ -529,9 +474,6 @@ impl<F: RichField + Extendable<D>, const D: usize> TendermintMarshaller<F, D>
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use curta::math::goldilocks::cubic::GoldilocksCubicParameters;
use num::BigUint;
use plonky2::field::goldilocks_field::GoldilocksField;
use plonky2::field::types::Field;
use plonky2::iop::target::BoolTarget;
use plonky2::{
Expand All @@ -555,13 +497,10 @@ pub(crate) mod tests {
use plonky2x::num::u32::gadgets::arithmetic_u32::U32Target;

use crate::{
signature::TendermintSignature,
utils::{f_bits_to_bytes, to_be_bits},
validator::{I64Target, TendermintMarshaller},
};

use super::VALIDATOR_BYTE_LENGTH_MIN;

type C = PoseidonGoldilocksConfig;
type F = <C as GenericConfig<D>>::F;
type Curve = Ed25519;
Expand All @@ -573,19 +512,13 @@ pub(crate) mod tests {
validators: &Vec<String>,
) -> (
Vec<MarshalledValidatorTarget>,
Vec<U32Target>,
Vec<Target>,
Vec<BoolTarget>,
) {
let mut validator_byte_length: Vec<U32Target> =
vec![
U32Target(builder.constant(F::from_canonical_usize(VALIDATOR_BYTE_LENGTH_MIN)));
VALIDATOR_SET_SIZE_MAX
];
let mut validator_byte_length: Vec<Target> = Vec::new();

let mut validator_enabled: Vec<BoolTarget> = vec![builder._false(); VALIDATOR_SET_SIZE_MAX];

let mut validator_bits: Vec<Vec<bool>> = (0..256).map(|_| Vec::<bool>::new()).collect();

let mut validators_target: Vec<MarshalledValidatorTarget> =
vec![
MarshalledValidatorTarget([builder._false(); VALIDATOR_BIT_LENGTH_MAX]);
Expand All @@ -595,18 +528,17 @@ pub(crate) mod tests {
// Convert the hex strings to bytes.
for i in 0..validators.len() {
let val_byte_length = validators[i].len() / 2;
validator_bits[i] = to_be_bits(hex::decode(&validators[i]).unwrap());
for j in 0..(val_byte_length * 8) {
if validator_bits[i][j] {
validators_target[i].0[j] = builder._true();
} else {
validators_target[i].0[j] = builder._false();
}
let validator_bits = to_be_bits(hex::decode(&validators[i]).unwrap().to_vec());
for j in 0..validator_bits.len() {
validators_target[i].0[j] = builder.constant_bool(validator_bits[j]);
}
validator_byte_length[i] =
U32Target(builder.constant(F::from_canonical_usize(val_byte_length)));
validator_byte_length.push(builder.constant(F::from_canonical_usize(val_byte_length)));
validator_enabled[i] = builder._true();
}

for _ in validators.len()..VALIDATOR_SET_SIZE_MAX {
validator_byte_length.push(builder.constant(F::from_canonical_usize(0)));
}
return (validators_target, validator_byte_length, validator_enabled);
}

Expand Down Expand Up @@ -765,7 +697,7 @@ pub(crate) mod tests {
let (validators_target, validator_byte_length, _) =
generate_inputs(&mut builder, &validators);

let result = builder.hash_validator_leaf(&validators_target[0], &validator_byte_length[0]);
let result = builder.hash_validator_leaf(&validators_target[0], validator_byte_length[0]);

// Set the target bits to the expected digest bits.
for i in 0..HASH_SIZE_BITS {
Expand Down