Implement HashOf statement and op (#217)

This commit is contained in:
Ahmad Afuni 2025-05-06 19:14:53 +10:00 committed by GitHub
parent 53ade6ea26
commit 8cc090c5e0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 139 additions and 6 deletions

View file

@ -7,7 +7,10 @@ use plonky2::{
extension::Extendable,
types::{Field, PrimeField64},
},
hash::hash_types::{HashOutTarget, RichField, NUM_HASH_OUT_ELTS},
hash::{
hash_types::{HashOutTarget, RichField, NUM_HASH_OUT_ELTS},
poseidon::PoseidonHash,
},
iop::{
target::{BoolTarget, Target},
witness::{PartialWitness, WitnessWrite},
@ -320,6 +323,9 @@ pub trait CircuitBuilderPod<F: RichField + Extendable<D>, const D: usize> {
/// and `y` each consist of two `u32` limbs.
fn assert_i64_less_if(&mut self, b: BoolTarget, x: ValueTarget, y: ValueTarget);
/// Creates value target that is a hash of two given values.
fn hash_values(&mut self, x: ValueTarget, y: ValueTarget) -> ValueTarget;
// Convenience methods for accessing and connecting elements of
// (vectors of) flattenables.
fn vec_ref<T: Flattenable>(&mut self, ts: &[T], i: Target) -> T;
@ -455,6 +461,14 @@ impl CircuitBuilderPod<F, D> for CircuitBuilder<F, D> {
assert_limb_lt(self, lhs, rhs);
}
fn hash_values(&mut self, x: ValueTarget, y: ValueTarget) -> ValueTarget {
ValueTarget::from_slice(
&self
.hash_n_to_hash_no_pad::<PoseidonHash>([x.elements, y.elements].concat())
.elements,
)
}
fn vec_ref<T: Flattenable>(&mut self, ts: &[T], i: Target) -> T {
// TODO: Revisit this when we need more than 64 statements.
let vector_ref = |builder: &mut CircuitBuilder<F, D>, v: &[Target], i| {

View file

@ -108,6 +108,7 @@ impl OperationVerifyGadget {
self.eval_copy(builder, st, op, &resolved_op_args)?,
self.eval_eq_from_entries(builder, st, op, &resolved_op_args),
self.eval_lt_lteq_from_entries(builder, st, op, &resolved_op_args),
self.eval_hash_of(builder, st, op, &resolved_op_args),
]
},
// Skip these if there are no resolved Merkle claims
@ -275,6 +276,40 @@ impl OperationVerifyGadget {
builder.all([op_st_code_ok, arg_types_ok, st_args_ok])
}
fn eval_hash_of(
&self,
builder: &mut CircuitBuilder<F, D>,
st: &StatementTarget,
op: &OperationTarget,
resolved_op_args: &[StatementTarget],
) -> BoolTarget {
let op_code_ok = op.has_native_type(builder, NativeOperation::HashOf);
let arg_types_ok = self.first_n_args_are_valueofs(builder, 3, resolved_op_args);
let arg1_value = resolved_op_args[0].args[1].as_value();
let arg2_value = resolved_op_args[1].args[1].as_value();
let arg3_value = resolved_op_args[2].args[1].as_value();
let expected_hash_value = builder.hash_values(arg2_value, arg3_value);
let hash_value_ok =
builder.is_equal_slice(&arg1_value.elements, &expected_hash_value.elements);
let arg1_key = resolved_op_args[0].args[0].clone();
let arg2_key = resolved_op_args[1].args[0].clone();
let arg3_key = resolved_op_args[2].args[0].clone();
let expected_statement = StatementTarget::new_native(
builder,
&self.params,
NativePredicate::HashOf,
&[arg1_key, arg2_key, arg3_key],
);
let st_ok = builder.is_equal_flattenable(st, &expected_statement);
builder.all([op_code_ok, arg_types_ok, hash_value_ok, st_ok])
}
fn eval_none(
&self,
builder: &mut CircuitBuilder<F, D>,
@ -533,7 +568,7 @@ mod tests {
mainpod::{OperationArg, OperationAux},
primitives::merkletree::{MerkleClaimAndProof, MerkleTree},
},
middleware::{Hash, OperationType, PodId, RawValue},
middleware::{hash_values, Hash, OperationType, PodId, RawValue},
};
fn operation_verify(
@ -939,6 +974,53 @@ mod tests {
);
operation_verify(st, op, prev_statements, merkle_proofs.clone())?;
// HashOf
let input_values = [
Value::from(RawValue([
GoldilocksField(1),
GoldilocksField(2),
GoldilocksField(3),
GoldilocksField(4),
])),
Value::from(512),
];
let v1 = hash_values(&input_values);
let [v2, v3] = input_values;
let st1: mainpod::Statement = Statement::ValueOf(
AnchoredKey::from((PodId(RawValue::from(88).into()), "hola")),
v1.into(),
)
.into();
let st2: mainpod::Statement = Statement::ValueOf(
AnchoredKey::from((PodId(RawValue::from(128).into()), "mundo")),
v2,
)
.into();
let st3: mainpod::Statement = Statement::ValueOf(
AnchoredKey::from((PodId(RawValue::from(256).into()), "!")),
v3,
)
.into();
let st: mainpod::Statement = Statement::HashOf(
AnchoredKey::from((PodId(RawValue::from(88).into()), "hola")),
AnchoredKey::from((PodId(RawValue::from(128).into()), "mundo")),
AnchoredKey::from((PodId(RawValue::from(256).into()), "!")),
)
.into();
let op = mainpod::Operation(
OperationType::Native(NativeOperation::HashOf),
vec![
OperationArg::Index(0),
OperationArg::Index(1),
OperationArg::Index(2),
],
OperationAux::None,
);
let prev_statements = vec![st1, st2, st3];
operation_verify(st, op, prev_statements, merkle_proofs.clone())?;
// NotContainsFromEntries
let kvs = [
(1.into(), 55.into()),