New Native Operation PublicKeyOf (#355)

* wrote some initial code

* added way to input private key into circuit

* TypedValue::SecretKey hashed as 10 32-bit limbs

* Check PublicKeyOf in Frontend and Middleware

* Diff review

* PR review

* Finish utest

* Fix bounds check

* added giving secret key witness to circuit

* Test & doc improvements

* added private key comparison to circuit and added test cases

* cargo fmt

* Add frontend tests for PublicKeyOf

* Add public_key_of and hash_of to op! macro

* Add ownership check to ticket example

* Group order checking in tests

* More negative test cases at circuit level

* Cleanups after self review

* clippy fixes

* Fixes after merge.  Temporarily remove plonky2 commit hash

* Add a nullifier to the ticket test example

* Test PublicKeyOf with a real prover (not mock)

* plonky-u32 dependency

* feat: optimize operation checks

Skip the circuits that verify operation checks other than None, Copy or
NewEntry for the public statements.  This works because public
statements are created by copying private statements, so we never use
the other operation checks in those slots.

---------

Co-authored-by: Andrew Twyman <artwyman@gmail.com>
Co-authored-by: Eduard S. <eduardsanou@posteo.net>
This commit is contained in:
brian6l 2025-07-28 15:53:01 -07:00 committed by GitHub
parent 9f8335756c
commit 5b04b2a360
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 958 additions and 76 deletions

View file

@ -1,6 +1,7 @@
use std::{array, iter, sync::Arc};
use itertools::{izip, zip_eq, Itertools};
use num::{BigUint, One};
use plonky2::{
field::types::Field,
hash::{
@ -14,6 +15,7 @@ use plonky2::{
},
plonk::config::AlgebraicHasher,
};
use plonky2_u32::gadgets::multiple_comparison::list_le_circuit;
use serde::{Deserialize, Serialize};
use crate::{
@ -31,9 +33,15 @@ use crate::{
},
emptypod::{cache_get_standard_empty_pod_circuit_data, EmptyPod},
error::Result,
mainpod::{self, pad_statement},
primitives::merkletree::{
verify_merkle_proof_circuit, MerkleClaimAndProof, MerkleClaimAndProofTarget,
mainpod::{self, pad_statement, OperationArg},
primitives::{
ec::{
bits::{BigUInt320Target, CircuitBuilderBits},
curve::{CircuitBuilderElliptic, Point, WitnessWriteCurve, GROUP_ORDER},
},
merkletree::{
verify_merkle_proof_circuit, MerkleClaimAndProof, MerkleClaimAndProofTarget,
},
},
recursion::{InnerCircuit, VerifiedProofTarget},
signedpod::SignedPod,
@ -41,11 +49,10 @@ use crate::{
measure_gates_begin, measure_gates_end,
middleware::{
AnchoredKey, CustomPredicate, CustomPredicateBatch, CustomPredicateRef, NativeOperation,
NativePredicate, Params, PodType, PredicatePrefix, Statement, StatementArg, ToFields,
Value, ValueRef, F, HASH_SIZE, KEY_TYPE, SELF, VALUE_SIZE,
NativePredicate, OperationType, Params, PodType, PredicatePrefix, Statement, StatementArg,
ToFields, TypedValue, Value, ValueRef, F, HASH_SIZE, KEY_TYPE, SELF, VALUE_SIZE,
},
};
//
// MainPod verification
//
@ -137,6 +144,47 @@ impl StatementCache {
}
}
/// Specialized implementation of `verify_operation_circuit` for operations that generate public
/// statement. This only allows operations to be None, NewEntry or Copy and accounts for the fact
/// that public statements in the current implementation are always generated by copying private
/// statements (or NewEntry for the `KEY_TYPE` public entry).
fn verify_operation_public_statement_circuit(
params: &Params,
builder: &mut CircuitBuilder,
st: &StatementTarget,
op: &OperationTarget,
prev_statements: &[StatementTarget],
input_statements_offset: usize,
) -> Result<()> {
let measure = measure_gates_begin!(builder, "OpVerify");
// Verify that the operation `op` correctly generates the statement `st`. The operation
// can reference any of the `prev_statements`.
// TODO: Clean this up.
let measure_resolve_op_args = measure_gates_begin!(builder, "ResolveOpArgs");
let cache = StatementCache::new(params, builder, op, st, prev_statements);
measure_gates_end!(builder, measure_resolve_op_args);
let op_checks = vec![
verify_none_circuit(params, builder, st, &op.op_type),
verify_new_entry_circuit(
params,
builder,
st,
&op.op_type,
prev_statements,
input_statements_offset,
),
verify_copy_circuit(builder, st, &op.op_type, &cache.op_args),
];
let ok = builder.any(op_checks);
builder.assert_one(ok.target);
measure_gates_end!(builder, measure);
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn verify_operation_circuit(
params: &Params,
@ -146,6 +194,7 @@ fn verify_operation_circuit(
prev_statements: &[StatementTarget],
input_statements_offset: usize,
merkle_claims: &[MerkleClaimTarget],
secret_key: &BigUInt320Target,
custom_predicate_verification_table: &[HashOutTarget],
) -> Result<()> {
let measure = measure_gates_begin!(builder, "OpVerify");
@ -218,6 +267,7 @@ fn verify_operation_circuit(
verify_transitive_eq_circuit(params, builder, st, &op.op_type, &cache.op_args),
verify_lt_to_neq_circuit(params, builder, st, &op.op_type, &cache.op_args),
verify_hash_of_circuit(params, builder, st, &op.op_type, &cache),
verify_public_key_of_circuit(params, builder, st, &op.op_type, secret_key, &cache),
verify_sum_of_circuit(params, builder, st, &op.op_type, &cache),
verify_product_of_circuit(params, builder, st, &op.op_type, &cache),
verify_max_of_circuit(params, builder, st, &op.op_type, &cache),
@ -539,6 +589,69 @@ fn verify_hash_of_circuit(
ok
}
fn verify_public_key_of_circuit(
params: &Params,
builder: &mut CircuitBuilder,
st: &StatementTarget,
op_type: &OperationTypeTarget,
secret_key: &BigUInt320Target,
cache: &StatementCache,
) -> BoolTarget {
let measure = measure_gates_begin!(builder, "OpPublicKeyOf");
let op_code_ok = op_type.has_native(builder, NativeOperation::PublicKeyOf);
let (arg_types_ok, [arg1_value, arg2_value]) = cache.first_n_args_as_values();
// inputting public_key, secret_key
let public_key_hash = arg1_value.elements;
let secret_key_hash = arg2_value.elements;
let secret_key_hash_v =
builder.hash_n_to_hash_no_pad::<PoseidonHash>(secret_key.limbs.to_vec());
let skey_hash_ok = builder.is_equal_slice(&secret_key_hash, &secret_key_hash_v.elements);
let invgenerator = builder.constant_point(Point::generator().inverse());
let secret_key_bits = secret_key.bits;
let group_orderm1 = &*GROUP_ORDER - BigUint::one();
let group_orderm1target = builder.constant_biguint320(&group_orderm1);
let compare_ok = list_le_circuit(
builder,
secret_key.limbs.to_vec(),
group_orderm1target.limbs.to_vec(),
32,
);
// public_key = g^-secret key
let public_key = builder.multiply_point(&secret_key_bits, &invgenerator);
let public_key_hash_v = builder.hash_n_to_hash_no_pad::<PoseidonHash>(
public_key
.x
.components
.into_iter()
.chain(public_key.u.components)
.collect(),
);
let pkey_hash_ok = builder.is_equal_slice(&public_key_hash, &public_key_hash_v.elements);
let arg1_expected = cache.equations[0].lhs.clone();
let arg2_expected = cache.equations[1].lhs.clone();
let expected_statement = StatementTarget::new_native(
builder,
params,
NativePredicate::PublicKeyOf,
&[arg1_expected, arg2_expected],
);
let st_ok = builder.is_equal_flattenable(st, &expected_statement);
let ok = builder.all([
op_code_ok,
arg_types_ok,
pkey_hash_ok,
skey_hash_ok,
compare_ok,
st_ok,
]);
measure_gates_end!(builder, measure);
ok
}
fn verify_sum_of_circuit(
params: &Params,
builder: &mut CircuitBuilder,
@ -1239,8 +1352,8 @@ fn verify_main_pod_circuit(
for statement in &main_pod.input_statements {
statements.push(statement.clone());
}
let pub_statements = &main_pod.input_statements
[main_pod.input_statements.len() - params.max_public_statements..];
let public_statements_offset = main_pod.input_statements.len() - params.max_public_statements;
let pub_statements = &main_pod.input_statements[public_statements_offset..];
// Verify Merkle claim/proof targets
let merkle_claims = main_pod
@ -1288,16 +1401,28 @@ fn verify_main_pod_circuit(
// 5. Verify input statements
for (i, (st, op)) in izip!(&main_pod.input_statements, &main_pod.operations).enumerate() {
let prev_statements = &statements[..input_statements_offset + i];
verify_operation_circuit(
params,
builder,
st,
op,
prev_statements,
input_statements_offset,
&merkle_claims,
&custom_predicate_verification_table,
)?;
if i < public_statements_offset {
verify_operation_circuit(
params,
builder,
st,
op,
prev_statements,
input_statements_offset,
&merkle_claims,
&main_pod.secret_keys[i],
&custom_predicate_verification_table,
)?;
} else {
verify_operation_public_statement_circuit(
params,
builder,
st,
op,
prev_statements,
input_statements_offset,
)?;
}
}
measure_gates_end!(builder, measure);
@ -1315,6 +1440,7 @@ pub struct MainPodVerifyTarget {
input_statements: Vec<StatementTarget>,
operations: Vec<OperationTarget>,
merkle_proofs: Vec<MerkleClaimAndProofTarget>,
secret_keys: Vec<BigUInt320Target>,
custom_predicate_batches: Vec<CustomPredicateBatchTarget>,
custom_predicate_verifications: Vec<CustomPredicateVerifyEntryTarget>,
}
@ -1348,6 +1474,9 @@ impl MainPodVerifyTarget {
MerkleClaimAndProofTarget::new_virtual(params.max_depth_mt_containers, builder)
})
.collect(),
secret_keys: (0..params.max_statements)
.map(|_| builder.add_virtual_biguint320_target())
.collect(),
custom_predicate_batches: (0..params.max_custom_predicate_batches)
.map(|_| builder.add_virtual_custom_predicate_batch(params))
.collect(),
@ -1484,6 +1613,41 @@ impl InnerCircuit for MainPodVerifyTarget {
for (i, (st, op)) in zip_eq(&input.statements, &input.operations).enumerate() {
self.input_statements[i].set_targets(pw, &self.params, st)?;
self.operations[i].set_targets(pw, &self.params, op)?;
if matches!(
op.op_type(),
OperationType::Native(NativeOperation::PublicKeyOf)
) {
if let StatementArg::Literal(value) = &st.1[1] {
if let TypedValue::SecretKey(sk) = value.typed() {
pw.set_biguint320_target(&self.secret_keys[i], &sk.0)?;
} else {
panic!("SecretKey literal of incorrect type!")
}
} else if let OperationArg::Index(ind) = op.1[1] {
// TODO: This adjustment only works if the secret key came
// from a statement in the current POD, which is the most
// common case. A more general solution needs to be able
// index across the virtual array of statements from all
// input PODs, similar to what's done in
// plonky2::mainpod::layout_statements.
let adjusted_index = ind
- (1 + self.params.max_input_signed_pods
* self.params.max_signed_pod_values
+ self.params.max_input_recursive_pods
* self.params.max_public_statements);
if let StatementArg::Literal(value) = &input.statements[adjusted_index].1[1] {
if let TypedValue::SecretKey(sk) = value.typed() {
pw.set_biguint320_target(&self.secret_keys[i], &sk.0)?;
} else {
panic!("SecretKey literal of incorrect type!")
}
}
} else {
panic!("SecretKey arg not found!")
}
} else {
pw.set_biguint320_target(&self.secret_keys[i], &BigUint::ZERO)?;
}
}
assert!(input.merkle_proofs.len() <= self.params.max_merkle_proofs_containers);
@ -1556,7 +1720,10 @@ mod tests {
basetypes::C,
circuits::common::tests::I64_TEST_PAIRS,
mainpod::{calculate_id, OperationArg, OperationAux},
primitives::merkletree::{MerkleClaimAndProof, MerkleTree},
primitives::{
ec::schnorr::SecretKey,
merkletree::{MerkleClaimAndProof, MerkleTree},
},
},
frontend::{self, literal, CustomPredicateBatchBuilder, StatementTmplBuilder},
middleware::{
@ -1570,6 +1737,7 @@ mod tests {
op: mainpod::Operation,
prev_statements: Vec<mainpod::Statement>,
merkle_proofs: Vec<MerkleClaimAndProof>,
secret_key: &SecretKey,
) -> Result<()> {
let params = Params {
max_custom_predicate_batches: 0,
@ -1601,6 +1769,7 @@ mod tests {
.into_iter()
.map(|pf| pf.into())
.collect();
let secret_key_target = builder.constant_biguint320(&secret_key.0);
let custom_predicate_verification_table = vec![];
verify_operation_circuit(
@ -1611,6 +1780,7 @@ mod tests {
&prev_statements_target,
0,
&merkle_claims_target,
&secret_key_target,
&custom_predicate_verification_table,
)?;
@ -1771,7 +1941,13 @@ mod tests {
.into_iter()
.for_each(|(op, st)| {
let check = std::panic::catch_unwind(|| {
operation_verify(st, op, prev_statements.to_vec(), vec![])
operation_verify(
st,
op,
prev_statements.to_vec(),
vec![],
&SecretKey(BigUint::ZERO),
)
});
match check {
Err(e) => {
@ -1836,7 +2012,14 @@ mod tests {
]
.into_iter()
.for_each(|(op, st)| {
assert!(operation_verify(st, op, prev_statements.to_vec(), vec![]).is_err())
assert!(operation_verify(
st,
op,
prev_statements.to_vec(),
vec![],
&SecretKey(BigUint::ZERO)
)
.is_err())
});
}
@ -1849,7 +2032,7 @@ mod tests {
OperationAux::None,
);
let prev_statements = vec![Statement::None.into()];
operation_verify(st, op, prev_statements, vec![])
operation_verify(st, op, prev_statements, vec![], &SecretKey(BigUint::ZERO))
}
#[test]
@ -1867,7 +2050,7 @@ mod tests {
vec![],
OperationAux::None,
);
operation_verify(st1, op, prev_statements, vec![])
operation_verify(st1, op, prev_statements, vec![], &SecretKey(BigUint::ZERO))
}
#[test]
@ -1879,7 +2062,7 @@ mod tests {
OperationAux::None,
);
let prev_statements = vec![Statement::None.into()];
operation_verify(st, op, prev_statements, vec![])
operation_verify(st, op, prev_statements, vec![], &SecretKey(BigUint::ZERO))
}
#[test]
@ -1902,7 +2085,7 @@ mod tests {
OperationAux::None,
);
let prev_statements = vec![st1, st2];
operation_verify(st, op, prev_statements, vec![])
operation_verify(st, op, prev_statements, vec![], &SecretKey(BigUint::ZERO))
}
#[test]
@ -1925,7 +2108,7 @@ mod tests {
OperationAux::None,
);
let prev_statements = vec![st1, st2];
operation_verify(st, op, prev_statements, vec![])
operation_verify(st, op, prev_statements, vec![], &SecretKey(BigUint::ZERO))
}
#[test]
@ -1948,7 +2131,7 @@ mod tests {
OperationAux::None,
);
let prev_statements = vec![st1, st2.clone()];
operation_verify(st, op, prev_statements, vec![])?;
operation_verify(st, op, prev_statements, vec![], &SecretKey(BigUint::ZERO))?;
// Also check negative < negative
let st3: mainpod::Statement = Statement::equal(
@ -1972,7 +2155,7 @@ mod tests {
OperationAux::None,
);
let prev_statements = vec![st3.clone(), st4];
operation_verify(st, op, prev_statements, vec![])?;
operation_verify(st, op, prev_statements, vec![], &SecretKey(BigUint::ZERO))?;
// Also check negative < positive
let st: mainpod::Statement = Statement::lt(
@ -1986,7 +2169,7 @@ mod tests {
OperationAux::None,
);
let prev_statements = vec![st3, st2];
operation_verify(st, op, prev_statements, vec![])
operation_verify(st, op, prev_statements, vec![], &SecretKey(BigUint::ZERO))
}
#[test]
@ -2009,7 +2192,7 @@ mod tests {
OperationAux::None,
);
let prev_statements = vec![st1, st2.clone()];
operation_verify(st, op, prev_statements, vec![])?;
operation_verify(st, op, prev_statements, vec![], &SecretKey(BigUint::ZERO))?;
// Also check negative <= negative
let st3: mainpod::Statement = Statement::equal(
@ -2033,7 +2216,7 @@ mod tests {
OperationAux::None,
);
let prev_statements = vec![st3.clone(), st4];
operation_verify(st, op, prev_statements, vec![])?;
operation_verify(st, op, prev_statements, vec![], &SecretKey(BigUint::ZERO))?;
// Also check negative <= positive
let st: mainpod::Statement = Statement::lt_eq(
@ -2047,7 +2230,13 @@ mod tests {
OperationAux::None,
);
let prev_statements = vec![st3, st2];
operation_verify(st, op, prev_statements.clone(), vec![])?;
operation_verify(
st,
op,
prev_statements.clone(),
vec![],
&SecretKey(BigUint::ZERO),
)?;
// Also check equality, both positive and negative.
let st: mainpod::Statement = Statement::lt_eq(
@ -2060,7 +2249,13 @@ mod tests {
vec![OperationArg::Index(0), OperationArg::Index(0)],
OperationAux::None,
);
operation_verify(st, op, prev_statements.clone(), vec![])?;
operation_verify(
st,
op,
prev_statements.clone(),
vec![],
&SecretKey(BigUint::ZERO),
)?;
let st: mainpod::Statement = Statement::lt_eq(
AnchoredKey::from((PodId(RawValue::from(88).into()), "hello")),
AnchoredKey::from((PodId(RawValue::from(88).into()), "hello")),
@ -2071,7 +2266,7 @@ mod tests {
vec![OperationArg::Index(1), OperationArg::Index(1)],
OperationAux::None,
);
operation_verify(st, op, prev_statements, vec![])
operation_verify(st, op, prev_statements, vec![], &SecretKey(BigUint::ZERO))
}
#[test]
@ -2120,7 +2315,7 @@ mod tests {
OperationAux::None,
);
let prev_statements = vec![st1, st2, st3];
operation_verify(st, op, prev_statements, vec![])
operation_verify(st, op, prev_statements, vec![], &SecretKey(BigUint::ZERO))
}
#[test]
@ -2166,7 +2361,7 @@ mod tests {
OperationAux::None,
);
let prev_statements = vec![st1, st2, st3];
operation_verify(st, op, prev_statements, vec![])
operation_verify(st, op, prev_statements, vec![], &SecretKey(BigUint::ZERO))
})
}
@ -2213,7 +2408,7 @@ mod tests {
OperationAux::None,
);
let prev_statements = vec![st1, st2, st3];
operation_verify(st, op, prev_statements, vec![])
operation_verify(st, op, prev_statements, vec![], &SecretKey(BigUint::ZERO))
})
}
@ -2255,7 +2450,7 @@ mod tests {
OperationAux::None,
);
let prev_statements = vec![st1, st2, st3];
operation_verify(st, op, prev_statements, vec![])
operation_verify(st, op, prev_statements, vec![], &SecretKey(BigUint::ZERO))
})
}
@ -2300,7 +2495,13 @@ mod tests {
let prev_statements = [st1, st2, st3];
let check = std::panic::catch_unwind(|| {
operation_verify(st, op, prev_statements.to_vec(), vec![])
operation_verify(
st,
op,
prev_statements.to_vec(),
vec![],
&SecretKey(BigUint::ZERO),
)
});
match check {
Err(e) => {
@ -2333,7 +2534,7 @@ mod tests {
OperationAux::None,
);
let prev_statements = vec![st1];
operation_verify(st, op, prev_statements, vec![])
operation_verify(st, op, prev_statements, vec![], &SecretKey(BigUint::ZERO))
}
#[test]
@ -2359,7 +2560,7 @@ mod tests {
OperationAux::None,
);
let prev_statements = vec![st1, st2];
operation_verify(st, op, prev_statements, vec![])
operation_verify(st, op, prev_statements, vec![], &SecretKey(BigUint::ZERO))
}
#[test]
@ -2399,7 +2600,13 @@ mod tests {
no_key_pf,
)];
let prev_statements = vec![root_st, key_st];
operation_verify(st, op, prev_statements, merkle_proofs)
operation_verify(
st,
op,
prev_statements,
merkle_proofs,
&SecretKey(BigUint::ZERO),
)
}
#[test]
@ -2446,7 +2653,138 @@ mod tests {
key_pf,
)];
let prev_statements = vec![root_st, key_st, value_st];
operation_verify(st, op, prev_statements, merkle_proofs)
operation_verify(
st,
op,
prev_statements,
merkle_proofs,
&SecretKey(BigUint::ZERO),
)
}
#[test]
fn test_operation_verify_publickeyof() -> Result<()> {
[
&SecretKey(BigUint::one()),
&SecretKey::new_rand(),
&SecretKey(&*GROUP_ORDER - BigUint::one()),
]
.into_iter()
.try_for_each(|secret_key| {
let public_key = secret_key.public_key();
let public_key_value = Value::from(TypedValue::from(public_key));
let secret_key_value = Value::from(TypedValue::from(secret_key.clone()));
let public_key_ak = AnchoredKey::from((PodId(RawValue::from(88).into()), "public key"));
let secret_key_ak = AnchoredKey::from((PodId(RawValue::from(70).into()), "secret key"));
let public_key_st: mainpod::Statement =
Statement::equal(public_key_ak.clone(), public_key_value.clone()).into();
let secret_key_st: mainpod::Statement =
Statement::equal(secret_key_ak.clone(), secret_key_value.clone()).into();
let st: mainpod::Statement =
Statement::public_key_of(public_key_ak, secret_key_ak).into();
let op = mainpod::Operation(
OperationType::Native(NativeOperation::PublicKeyOf),
vec![OperationArg::Index(0), OperationArg::Index(1)],
OperationAux::None,
);
let prev_statements = vec![public_key_st, secret_key_st];
operation_verify(st, op, prev_statements, vec![], secret_key)
})
}
#[test]
fn test_operation_verify_publickeyof_failure_wrong_key() {
let secret_key = SecretKey(BigUint::one());
let public_key = SecretKey(BigUint::ZERO).public_key();
let public_key_value = Value::from(TypedValue::from(public_key));
let secret_key_value = Value::from(TypedValue::from(secret_key.clone()));
let public_key_ak = AnchoredKey::from((PodId(RawValue::from(88).into()), "public key"));
let secret_key_ak = AnchoredKey::from((PodId(RawValue::from(70).into()), "secret key"));
let public_key_st: mainpod::Statement =
Statement::equal(public_key_ak.clone(), public_key_value.clone()).into();
let secret_key_st: mainpod::Statement =
Statement::equal(secret_key_ak.clone(), secret_key_value.clone()).into();
let st: mainpod::Statement = Statement::public_key_of(public_key_ak, secret_key_ak).into();
let op = mainpod::Operation(
OperationType::Native(NativeOperation::PublicKeyOf),
vec![OperationArg::Index(0), OperationArg::Index(1)],
OperationAux::None,
);
let prev_statements = vec![public_key_st, secret_key_st];
assert!(operation_verify(st, op, prev_statements, vec![], &secret_key).is_err())
}
#[test]
fn test_operation_verify_publickeyof_failure_pk_type() {
let secret_key = SecretKey(BigUint::one());
let public_key = 123i64;
let public_key_value = Value::from(TypedValue::from(public_key));
let secret_key_value = Value::from(TypedValue::from(secret_key.clone()));
let public_key_ak = AnchoredKey::from((PodId(RawValue::from(88).into()), "public key"));
let secret_key_ak = AnchoredKey::from((PodId(RawValue::from(70).into()), "secret key"));
let public_key_st: mainpod::Statement =
Statement::equal(public_key_ak.clone(), public_key_value.clone()).into();
let secret_key_st: mainpod::Statement =
Statement::equal(secret_key_ak.clone(), secret_key_value.clone()).into();
let st: mainpod::Statement = Statement::public_key_of(public_key_ak, secret_key_ak).into();
let op = mainpod::Operation(
OperationType::Native(NativeOperation::PublicKeyOf),
vec![OperationArg::Index(0), OperationArg::Index(1)],
OperationAux::None,
);
let prev_statements = vec![public_key_st, secret_key_st];
assert!(operation_verify(st, op, prev_statements, vec![], &secret_key).is_err())
}
#[test]
fn test_operation_verify_publickeyof_failure_sk_type() {
let secret_key = 123i64;
let public_key = SecretKey(BigUint::from(123u32)).public_key();
let public_key_value = Value::from(TypedValue::from(public_key));
let secret_key_value = Value::from(TypedValue::from(secret_key));
let public_key_ak = AnchoredKey::from((PodId(RawValue::from(88).into()), "public key"));
let secret_key_ak = AnchoredKey::from((PodId(RawValue::from(70).into()), "secret key"));
let public_key_st: mainpod::Statement =
Statement::equal(public_key_ak.clone(), public_key_value.clone()).into();
let secret_key_st: mainpod::Statement =
Statement::equal(secret_key_ak.clone(), secret_key_value.clone()).into();
let st: mainpod::Statement = Statement::public_key_of(public_key_ak, secret_key_ak).into();
let op = mainpod::Operation(
OperationType::Native(NativeOperation::PublicKeyOf),
vec![OperationArg::Index(0), OperationArg::Index(1)],
OperationAux::None,
);
let prev_statements = vec![public_key_st, secret_key_st];
assert!(operation_verify(
st,
op,
prev_statements,
vec![],
&SecretKey(BigUint::from(123u32))
)
.is_err())
}
#[test]
fn test_operation_verify_publickeyof_failure_sk_size() {
let secret_key = SecretKey(&*GROUP_ORDER - BigUint::ZERO);
let public_key = secret_key.public_key();
let public_key_value = Value::from(TypedValue::from(public_key));
let secret_key_value = Value::from(TypedValue::from(secret_key.clone()));
let public_key_ak = AnchoredKey::from((PodId(RawValue::from(88).into()), "public key"));
let secret_key_ak = AnchoredKey::from((PodId(RawValue::from(70).into()), "secret key"));
let public_key_st: mainpod::Statement =
Statement::equal(public_key_ak.clone(), public_key_value.clone()).into();
let secret_key_st: mainpod::Statement =
Statement::equal(secret_key_ak.clone(), secret_key_value.clone()).into();
let st: mainpod::Statement = Statement::public_key_of(public_key_ak, secret_key_ak).into();
let op = mainpod::Operation(
OperationType::Native(NativeOperation::PublicKeyOf),
vec![OperationArg::Index(0), OperationArg::Index(1)],
OperationAux::None,
);
let prev_statements = vec![public_key_st, secret_key_st];
assert!(operation_verify(st, op, prev_statements, vec![], &secret_key).is_err())
}
fn helper_statement_arg_from_template(

View file

@ -739,7 +739,10 @@ pub mod tests {
primitives::ec::schnorr::SecretKey,
signedpod::Signer,
},
examples::{attest_eth_friend, zu_kyc_pod_builder, zu_kyc_sign_pod_builders, EthDosHelper},
examples::{
attest_eth_friend, tickets_pod_full_flow, zu_kyc_pod_builder, zu_kyc_sign_pod_builders,
EthDosHelper,
},
frontend::{
self, literal, CustomPredicateBatchBuilder, MainPodBuilder, StatementTmplBuilder as STB,
},
@ -789,6 +792,19 @@ pub mod tests {
Ok(pod.verify()?)
}
#[test]
fn test_main_tickets() -> frontend::Result<()> {
let params = Params::default();
let ticket_builder = tickets_pod_full_flow(&params, &DEFAULT_VD_SET)?;
let prover = Prover {};
let kyc_pod = ticket_builder.prove(&prover, &params)?;
crate::measure_gates_print!();
let pod = (kyc_pod.pod as Box<dyn Any>).downcast::<MainPod>().unwrap();
Ok(pod.verify()?)
}
#[test]
fn test_mini_0() {
let params = middleware::Params {

View file

@ -70,6 +70,10 @@ impl TryFrom<Statement> for middleware::Statement {
(NP::MaxOf, &[a1, a2, a3]) => {
S::MaxOf(a1.try_into()?, a2.try_into()?, a3.try_into()?)
}
(NP::HashOf, &[a1, a2, a3]) => {
S::HashOf(a1.try_into()?, a2.try_into()?, a3.try_into()?)
}
(NP::PublicKeyOf, &[a1, a2]) => S::PublicKeyOf(a1.try_into()?, a2.try_into()?),
_ => Err(Error::custom(format!(
"Ill-formed statement expression {:?}",
s

View file

@ -430,8 +430,7 @@ pub mod tests {
great_boy_pod_full_flow, tickets_pod_full_flow, zu_kyc_pod_builder,
zu_kyc_sign_pod_builders, MOCK_VD_SET,
},
frontend,
middleware::{self},
frontend, middleware,
};
#[test]
@ -486,7 +485,7 @@ pub mod tests {
#[test]
fn test_mock_main_tickets() -> frontend::Result<()> {
let params = middleware::Params::default();
let tickets_builder = tickets_pod_full_flow()?;
let tickets_builder = tickets_pod_full_flow(&params, &MOCK_VD_SET)?;
let prover = MockProver {};
let proof_pod = tickets_builder.prove(&prover, &params)?;
let pod = (proof_pod.pod as Box<dyn Any>)

View file

@ -552,6 +552,7 @@ pub trait CircuitBuilderElliptic {
fn add_point(&mut self, p1: &PointTarget, p2: &PointTarget) -> PointTarget;
fn double_point(&mut self, p: &PointTarget) -> PointTarget;
fn multiply_point(&mut self, p1_scalar: &[BoolTarget; 320], p1: &PointTarget) -> PointTarget;
fn linear_combination_points(
&mut self,
p1_scalar: &[BoolTarget; 320],
@ -654,6 +655,17 @@ impl CircuitBuilderElliptic for CircuitBuilder<GoldilocksField, 2> {
*/
}
fn multiply_point(&mut self, p1_scalar: &[BoolTarget; 320], p1: &PointTarget) -> PointTarget {
let zero = self.identity_point();
let mut ans = zero.clone();
for i in (0..320).rev() {
ans = self.double_point(&ans);
let maybe_p1 = self.if_point(p1_scalar[i], p1, &zero);
ans = self.add_point(&ans, &maybe_p1);
}
ans
}
fn linear_combination_points(
&mut self,
p1_scalar: &[BoolTarget; 320],

View file

@ -1,4 +1,4 @@
use std::array;
use std::{array, fmt, str::FromStr};
use num::BigUint;
use num_bigint::RandBigInt;
@ -6,7 +6,7 @@ use plonky2::{
field::{
extension::FieldExtension,
goldilocks_field::GoldilocksField,
types::{Field, PrimeField},
types::{Field, PrimeField, PrimeField64},
},
hash::{
hash_types::HashOutTarget,
@ -173,6 +173,94 @@ impl SecretKey {
let s = (nonce + &self.0 * &e) % &*GROUP_ORDER;
Signature { s, e }
}
pub fn as_bytes(&self) -> Vec<u8> {
let bytes = self.0.to_bytes_le();
assert!(bytes.len() <= 40);
bytes
.into_iter()
.chain(std::iter::repeat(0u8))
.take(40)
.collect()
}
pub fn from_bytes(sk_bytes: &[u8]) -> Result<Self, Error> {
if sk_bytes.len() != 40 {
return Err(Error::custom(
"Invalid byte encoding of Schnorr secret key.".to_string(),
));
}
let big_uint = BigUint::from_bytes_le(sk_bytes);
if big_uint >= *GROUP_ORDER {
return Err(Error::custom(
"Invalid Schnorr secret key - not less than group order.".to_string(),
));
}
Ok(Self(big_uint))
}
pub fn to_limbs(&self) -> [GoldilocksField; 10] {
assert!(self.0.bits() <= 320);
let digits = self.0.to_u32_digits();
array::from_fn(|i| {
let d = digits.get(i).copied().unwrap_or(0);
GoldilocksField::from_canonical_u32(d)
})
}
pub fn from_limbs(limbs: [GoldilocksField; 10]) -> Result<Self, Error> {
let mut limb_vec = vec![];
for gl in limbs.iter() {
let g64 = gl.to_canonical_u64();
if g64 >= (1 << 32) {
return Err(Error::custom(
"Invalid limb value in Schnorr secret key.".to_string(),
));
}
limb_vec.push(g64 as u32);
}
Ok(Self(BigUint::from_slice(&limb_vec)))
}
}
impl Serialize for SecretKey {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let sk_b64 = serialize_bytes(&self.as_bytes());
serializer.serialize_str(&sk_b64)
}
}
impl<'de> Deserialize<'de> for SecretKey {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let sk_b64 = String::deserialize(deserializer)?;
let sk_bytes = deserialize_bytes(&sk_b64).map_err(serde::de::Error::custom)?;
SecretKey::from_bytes(&sk_bytes).map_err(serde::de::Error::custom)
}
}
impl fmt::Display for SecretKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", serialize_bytes(self.as_bytes().as_slice()))
}
}
impl FromStr for SecretKey {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let sk_bytes = deserialize_bytes(s)?;
SecretKey::from_bytes(&sk_bytes)
}
}
impl SignatureTarget {
@ -218,9 +306,14 @@ fn hash_array_circuit(
#[cfg(test)]
mod test {
use itertools::Itertools;
use num::BigUint;
use num_bigint::RandBigInt;
use plonky2::{
field::{goldilocks_field::GoldilocksField, types::Sample},
field::{
goldilocks_field::GoldilocksField,
types::{Field, Sample},
},
iop::{
target::Target,
witness::{PartialWitness, WitnessWrite},
@ -253,6 +346,29 @@ mod test {
(public_key, msg, sig)
}
#[test]
fn test_key_pairs() -> Result<(), anyhow::Error> {
let fixed_sk = SecretKey(BigUint::from(0x1234567890abcdefu64));
let fixed_sk_expected_str = "782rkHhWNBIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==";
assert_eq!(fixed_sk.to_string(), fixed_sk_expected_str);
let parsed_sk = fixed_sk_expected_str.parse::<SecretKey>()?;
assert_eq!(fixed_sk, parsed_sk);
let fixed_pk = fixed_sk.public_key();
let fixed_pk_expected_str = "8sctoHc2aduKbYtZwo7Z3v7YugSuimLUMAhE95V18CWU55tWMvZxEqA";
assert_eq!(fixed_pk.to_string(), fixed_pk_expected_str);
let parsed_pk = fixed_pk_expected_str.parse::<Point>()?;
assert_eq!(fixed_pk, parsed_pk);
let rand_sk = SecretKey::new_rand();
assert_ne!(fixed_sk, rand_sk);
assert_eq!(rand_sk, rand_sk.to_string().parse::<SecretKey>()?);
let rand_pk = rand_sk.public_key();
assert_ne!(fixed_pk, rand_pk);
Ok(())
}
#[test]
fn test_verify_signature() {
let (public_key, msg, sig) = gen_signed_message();
@ -352,4 +468,67 @@ mod test {
data.verify(proof)?;
Ok(())
}
#[test]
fn test_secret_key_serialization() -> Result<(), anyhow::Error> {
let sk_123 = SecretKey(BigUint::from(123u32));
let str_123 = serde_json::to_string(&sk_123).unwrap();
let deser_123 = serde_json::from_str(&str_123).unwrap();
assert_eq!(sk_123, deser_123);
let sk_rand = SecretKey::new_rand();
assert_ne!(sk_123, sk_rand);
let str_rand = serde_json::to_string(&sk_rand).unwrap();
let deser_rand = serde_json::from_str(&str_rand).unwrap();
assert_eq!(sk_rand, deser_rand);
let sk_max = SecretKey(&*GROUP_ORDER - 1u32);
assert_ne!(sk_123, sk_max);
let str_max = serde_json::to_string(&sk_max).unwrap();
let deser_max = serde_json::from_str(&str_max).unwrap();
assert_eq!(sk_max, deser_max);
// Value is too big for group but fits within 320 bits. Thus it
// survives the assert in as_bytes(), but gets caught during deserialization.
let sk_toobig = SecretKey(GROUP_ORDER.clone());
assert_ne!(sk_toobig, sk_123);
let str_toobig = serde_json::to_string(&sk_toobig).unwrap();
assert!(serde_json::from_str::<SecretKey>(&str_toobig).is_err());
Ok(())
}
#[test]
fn test_secret_key_limbs() -> Result<(), anyhow::Error> {
let sk_0 = SecretKey(BigUint::from(0u32));
let limbs_0 = sk_0.to_limbs();
assert_eq!(limbs_0, [GoldilocksField::from_canonical_u32(0); 10]);
let rsk_0 = SecretKey::from_limbs(limbs_0).unwrap();
assert_eq!(sk_0, rsk_0);
let sk_n: SecretKey = SecretKey(BigUint::from_slice((0..10).collect_vec().as_slice()));
let limbs_n = sk_n.to_limbs();
assert_eq!(
limbs_n.as_slice(),
(0..10)
.map(GoldilocksField::from_canonical_u32)
.collect_vec()
.as_slice()
);
let rsk_n = SecretKey::from_limbs(limbs_n).unwrap();
assert_eq!(sk_n, rsk_n);
assert!(SecretKey::from_limbs(
(0..9)
.chain([9u64 << 32])
.map(GoldilocksField::from_canonical_u64)
.collect_vec()
.as_slice()
.try_into()
.unwrap(),
)
.is_err());
Ok(())
}
}

View file

@ -3,6 +3,7 @@ pub mod custom;
use std::{collections::HashSet, sync::LazyLock};
use custom::eth_dos_batch;
use num::BigUint;
pub static MOCK_VD_SET: LazyLock<VDSet> = LazyLock::new(|| VDSet::new(6, &[]).unwrap());
@ -10,8 +11,8 @@ use crate::{
backends::plonky2::{primitives::ec::schnorr::SecretKey, signedpod::Signer},
frontend::{MainPod, MainPodBuilder, Result, SignedPod, SignedPodBuilder},
middleware::{
containers::Set, CustomPredicateRef, Params, PodSigner, PodType, Predicate, Statement,
StatementArg, TypedValue, VDSet, Value, KEY_SIGNER, KEY_TYPE,
containers::Set, hash_values, CustomPredicateRef, Params, PodSigner, PodType, Predicate,
Statement, StatementArg, TypedValue, VDSet, Value, KEY_SIGNER, KEY_TYPE,
},
op,
};
@ -393,13 +394,17 @@ pub fn great_boy_pod_full_flow() -> Result<(Params, MainPodBuilder)> {
// Tickets
pub const TICKET_OWNER_SECRET_KEY: SecretKey = SecretKey(BigUint::ZERO);
pub fn tickets_sign_pod_builder(params: &Params) -> SignedPodBuilder {
// Create a signed pod with all atomic types (string, int, bool)
let mut builder = SignedPodBuilder::new(params);
builder.insert("eventId", 123);
builder.insert("productId", 456);
builder.insert("attendeeName", "John Doe");
// Removed temporarily to make the example fit in 8 entries.
//builder.insert("attendeeName", "John Doe");
builder.insert("attendeeEmail", "john.doe@example.com");
builder.insert("attendeePublicKey", TICKET_OWNER_SECRET_KEY.public_key());
builder.insert("isConsumed", true);
builder.insert("isRevoked", false);
builder
@ -425,17 +430,31 @@ pub fn tickets_pod_builder(
blacklisted_email_set_value,
(signed_pod, "attendeeEmail")
))?;
// This isn't the most fool-proof way to prove ownership (it requires
// verifier to check pod ID on an anchored key to confirm statement wasn't
// copied), but it's the simplest.
let st_sk = builder.priv_literal(TICKET_OWNER_SECRET_KEY)?;
builder.pub_op(op!(
public_key_of,
(signed_pod, "attendeePublicKey"),
st_sk.clone()
))?;
// Nullifier calculation is public, but based on the private sk.
let external_nullifier = "external nullifier";
let nullifier = hash_values(&[TICKET_OWNER_SECRET_KEY.into(), external_nullifier.into()]);
builder.pub_op(op!(hash_of, nullifier, st_sk, external_nullifier))?;
Ok(builder)
}
pub fn tickets_pod_full_flow() -> Result<MainPodBuilder> {
let params = Params::default();
let vd_set = &*MOCK_VD_SET;
let builder = tickets_sign_pod_builder(&params);
pub fn tickets_pod_full_flow(params: &Params, vd_set: &VDSet) -> Result<MainPodBuilder> {
let builder = tickets_sign_pod_builder(params);
let signed_pod = builder.sign(&Signer(SecretKey(1u32.into()))).unwrap();
tickets_pod_builder(
&params,
params,
vd_set,
&signed_pod,
123,

View file

@ -436,6 +436,19 @@ impl MainPodBuilder {
// TODO: validate proof
Statement::NotContains(r1, r2)
}
(PublicKeyOf, &[a1, a2]) => {
let (r1, v1) = a1
.value_and_ref()
.ok_or_else(|| arg_error("public-key-from-entries"))?;
let (r2, v2) = a2
.value_and_ref()
.ok_or_else(|| arg_error("public-key-from-entries"))?;
if middleware::Operation::check_public_key(v1, v2)? {
Statement::PublicKeyOf(r1, r2)
} else {
return Err(arg_error("public-key-from-entries"));
}
}
(t, _) => {
if t.is_syntactic_sugar() {
return Err(Error::custom(format!(
@ -824,12 +837,20 @@ pub mod build_utils {
(array_contains, $array:expr, $index:expr, $value:expr) => { $crate::frontend::Operation(
$crate::middleware::OperationType::Native($crate::middleware::NativeOperation::ArrayContainsFromEntries),
$crate::op_args!($array, $index, $value), $crate::middleware::OperationAux::None) };
(hash_of, $hash:expr, $val1:expr, $val2:expr) => { $crate::frontend::Operation(
$crate::middleware::OperationType::Native($crate::middleware::NativeOperation::HashOf),
$crate::op_args!($hash, $val1, $val2), $crate::middleware::OperationAux::None) };
(public_key_of, $pk:expr, $sk:expr) => { $crate::frontend::Operation(
$crate::middleware::OperationType::Native($crate::middleware::NativeOperation::PublicKeyOf),
$crate::op_args!($pk, $sk), $crate::middleware::OperationAux::None) };
}
}
#[cfg(test)]
pub mod tests {
use num::BigUint;
use super::*;
use crate::{
backends::plonky2::{
@ -957,7 +978,7 @@ pub mod tests {
#[test]
fn test_front_tickets() -> Result<()> {
let builder = tickets_pod_full_flow()?;
let builder = tickets_pod_full_flow(&Params::default(), &MOCK_VD_SET)?;
println!("{}", builder);
Ok(())
@ -1086,6 +1107,132 @@ pub mod tests {
Ok(())
}
#[test]
fn test_public_key_of() -> Result<()> {
let params = Params::default();
let vd_set = &*MOCK_VD_SET;
let sk = SecretKey::new_rand();
let pk = sk.public_key();
// Signed POD contains public key as owner
let mut builder = SignedPodBuilder::new(&params);
builder.insert("owner", Value::from(pk));
builder.insert("other_data", Value::from(123));
let signer = Signer(SecretKey(1u32.into()));
let signed_pod = builder.sign(&signer).unwrap();
// Main POD proves ownership of the owner's secret key.
let mut builder = MainPodBuilder::new(&params, vd_set);
builder.add_signed_pod(&signed_pod);
let st0 = signed_pod.get_statement("owner").unwrap();
let st1 = builder
.priv_op(op!(new_entry, "known_secret", Value::from(sk)))
.unwrap();
builder
.pub_op(Operation(
// OperationType
OperationType::Native(NativeOperation::PublicKeyOf),
// Vec<OperationArg>
vec![OperationArg::Statement(st0), OperationArg::Statement(st1)],
OperationAux::None,
))
.unwrap();
// Prove Main POD to check.
let main_prover = MockProver {};
let main_pod = builder.prove(&main_prover, &params).unwrap();
println!("{}", main_pod);
Ok(())
}
#[test]
fn test_public_key_of_wrong_key() -> Result<()> {
let params = Params::default();
let vd_set = &*MOCK_VD_SET;
let sk = SecretKey::new_rand();
let pk = sk.public_key();
// Signed POD contains public key as owner
let mut builder = SignedPodBuilder::new(&params);
builder.insert("owner", Value::from(pk));
builder.insert("other_data", Value::from(123));
let signer = Signer(SecretKey(1u32.into()));
let signed_pod = builder.sign(&signer).unwrap();
// Try to build with the wrong secret key. The pre-proving checks
// will catch this.
let mut builder = MainPodBuilder::new(&params, vd_set);
builder.add_signed_pod(&signed_pod);
let st0 = signed_pod.get_statement("owner").unwrap();
let st1 = builder
.priv_op(op!(
new_entry,
"known_secret",
Value::from(SecretKey(BigUint::from(123u32)))
))
.unwrap();
assert!(builder
.pub_op(Operation(
// OperationType
OperationType::Native(NativeOperation::PublicKeyOf),
// Vec<OperationArg>
vec![OperationArg::Statement(st0), OperationArg::Statement(st1)],
OperationAux::None,
))
.is_err());
Ok(())
}
#[test]
fn test_public_key_of_wrong_type() -> Result<()> {
let params = Params::default();
let vd_set = &*MOCK_VD_SET;
let sk = SecretKey::new_rand();
let pk = sk.public_key();
// Try to build with wrong type in 1st arg
let mut builder = MainPodBuilder::new(&params, vd_set);
let st_pk = builder.literal(false, Value::from(pk)).unwrap();
let st_int1 = builder.literal(false, Value::from(123)).unwrap();
assert!(builder
.pub_op(Operation(
// OperationType
OperationType::Native(NativeOperation::PublicKeyOf),
// Vec<OperationArg>
vec![
OperationArg::Statement(st_pk),
OperationArg::Statement(st_int1),
],
OperationAux::None,
))
.is_err());
// Try to build with wrong type in 2nd arg
builder = MainPodBuilder::new(&params, vd_set);
let st_sk = builder.literal(false, Value::from(pk)).unwrap();
let st_int2 = builder.literal(false, Value::from(123)).unwrap();
assert!(builder
.pub_op(Operation(
// OperationType
OperationType::Native(NativeOperation::PublicKeyOf),
// Vec<OperationArg>
vec![
OperationArg::Statement(st_int2),
OperationArg::Statement(st_sk),
],
OperationAux::None,
))
.is_err());
Ok(())
}
#[should_panic]
#[test]
fn test_reject_duplicate_new_entry() {

View file

@ -39,6 +39,7 @@ pub fn native_predicate_from_string(s: &str) -> Option<NativePredicate> {
"ProductOf" => Some(NativePredicate::ProductOf),
"MaxOf" => Some(NativePredicate::MaxOf),
"HashOf" => Some(NativePredicate::HashOf),
"PublicKeyOf" => Some(NativePredicate::PublicKeyOf),
"DictContains" => Some(NativePredicate::DictContains),
"DictNotContains" => Some(NativePredicate::DictNotContains),
"ArrayContains" => Some(NativePredicate::ArrayContains),
@ -369,7 +370,8 @@ fn validate_and_build_statement_template(
| NativePredicate::SetContains
| NativePredicate::DictNotContains
| NativePredicate::SetNotContains
| NativePredicate::NotContains => 2,
| NativePredicate::NotContains
| NativePredicate::PublicKeyOf => 2,
NativePredicate::Contains
| NativePredicate::ArrayContains
| NativePredicate::DictContains

View file

@ -34,7 +34,8 @@ use serialization::*;
pub use statement::*;
use crate::backends::plonky2::primitives::{
ec::curve::Point as PublicKey, merkletree::MerkleProof,
ec::{curve::Point as PublicKey, schnorr::SecretKey},
merkletree::MerkleProof,
};
pub const SELF: PodId = PodId(SELF_ID_HASH);
@ -62,8 +63,10 @@ pub enum TypedValue {
),
// Uses the serialization for middleware::Value:
Raw(RawValue),
// Public key variant
// Schnorr public key variant (EC point)
PublicKey(PublicKey),
// Schnorr secret key variant (scalar)
SecretKey(SecretKey),
PodId(PodId),
// UNTAGGED TYPES:
#[serde(untagged)]
@ -114,6 +117,12 @@ impl From<PublicKey> for TypedValue {
}
}
impl From<SecretKey> for TypedValue {
fn from(sk: SecretKey) -> Self {
TypedValue::SecretKey(sk)
}
}
impl From<PodId> for TypedValue {
fn from(id: PodId) -> Self {
TypedValue::PodId(id)
@ -188,6 +197,28 @@ impl TryFrom<&TypedValue> for PodId {
}
}
impl TryFrom<&TypedValue> for PublicKey {
type Error = Error;
fn try_from(v: &TypedValue) -> std::result::Result<Self, Self::Error> {
if let TypedValue::PublicKey(pk) = v {
Ok(*pk)
} else {
Err(Error::custom("Value not a public key".to_string()))
}
}
}
impl TryFrom<&TypedValue> for SecretKey {
type Error = Error;
fn try_from(v: &TypedValue) -> std::result::Result<Self, Self::Error> {
if let TypedValue::SecretKey(sk) = v {
Ok(sk.clone())
} else {
Err(Error::custom("Value not a secret key".to_string()))
}
}
}
impl fmt::Display for TypedValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
@ -233,6 +264,7 @@ impl fmt::Display for TypedValue {
write!(f, "]")
}
TypedValue::PublicKey(p) => write!(f, "PublicKey({})", p),
TypedValue::SecretKey(p) => write!(f, "SecretKey({})", p),
TypedValue::PodId(p) => {
write!(f, "0x{}", p.0.encode_hex::<String>())
}
@ -254,6 +286,7 @@ impl From<&TypedValue> for RawValue {
TypedValue::Array(a) => RawValue::from(a.commitment()),
TypedValue::Raw(v) => *v,
TypedValue::PublicKey(p) => RawValue::from(hash_fields(&p.as_fields())),
TypedValue::SecretKey(sk) => RawValue::from(hash_fields(&sk.to_limbs())),
TypedValue::PodId(id) => RawValue::from(id.0),
}
}
@ -321,6 +354,19 @@ impl JsonSchema for TypedValue {
..Default::default()
};
let secret_key_schema = schemars::schema::SchemaObject {
instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::Object))),
object: Some(Box::new(schemars::schema::ObjectValidation {
// SecretKey is serialized as a string
properties: [("SecretKey".to_string(), gen.subschema_for::<String>())]
.into_iter()
.collect(),
required: ["SecretKey".to_string()].into_iter().collect(),
..Default::default()
})),
..Default::default()
};
// This is the part that Schemars can't generate automatically:
let untagged_array_schema = gen.subschema_for::<Array>();
let untagged_set_schema = gen.subschema_for::<Set>();
@ -334,6 +380,7 @@ impl JsonSchema for TypedValue {
Schema::Object(int_schema),
Schema::Object(raw_schema),
Schema::Object(public_key_schema),
Schema::Object(secret_key_schema),
untagged_array_schema,
untagged_dictionary_schema,
untagged_string_schema,

View file

@ -5,7 +5,13 @@ use plonky2::field::types::Field;
use serde::{Deserialize, Serialize};
use crate::{
backends::plonky2::primitives::merkletree::{MerkleProof, MerkleTree},
backends::plonky2::primitives::{
ec::{
curve::{Point as PublicKey, GROUP_ORDER},
schnorr::SecretKey,
},
merkletree::{MerkleProof, MerkleTree},
},
middleware::{
hash_values, AnchoredKey, CustomPredicate, CustomPredicateRef, Error, NativePredicate,
Params, Predicate, Result, Statement, StatementArg, StatementTmplArg, ToFields, Value,
@ -73,6 +79,7 @@ pub enum NativeOperation {
ProductOf = 12,
MaxOf = 13,
HashOf = 14,
PublicKeyOf = 15,
// Syntactic sugar operations. These operations are not supported by the backend. The
// frontend compiler is responsible of translating these operations into the operations above.
@ -130,6 +137,9 @@ impl OperationType {
NativeOperation::ProductOf => Some(Predicate::Native(NativePredicate::ProductOf)),
NativeOperation::MaxOf => Some(Predicate::Native(NativePredicate::MaxOf)),
NativeOperation::HashOf => Some(Predicate::Native(NativePredicate::HashOf)),
NativeOperation::PublicKeyOf => {
Some(Predicate::Native(NativePredicate::PublicKeyOf))
}
no => unreachable!("Unexpected syntactic sugar op {:?}", no),
},
OperationType::Custom(cpr) => Some(Predicate::Custom(cpr.clone())),
@ -164,6 +174,7 @@ pub enum Operation {
ProductOf(Statement, Statement, Statement),
MaxOf(Statement, Statement, Statement),
HashOf(Statement, Statement, Statement),
PublicKeyOf(Statement, Statement),
Custom(CustomPredicateRef, Vec<Statement>),
}
@ -203,6 +214,7 @@ impl Operation {
Self::ProductOf(_, _, _) => OT::Native(ProductOf),
Self::MaxOf(_, _, _) => OT::Native(MaxOf),
Self::HashOf(_, _, _) => OT::Native(HashOf),
Self::PublicKeyOf(_, _) => OT::Native(PublicKeyOf),
Self::Custom(cpr, _) => OT::Custom(cpr.clone()),
}
}
@ -224,6 +236,7 @@ impl Operation {
Self::ProductOf(s1, s2, s3) => vec![s1, s2, s3],
Self::MaxOf(s1, s2, s3) => vec![s1, s2, s3],
Self::HashOf(s1, s2, s3) => vec![s1, s2, s3],
Self::PublicKeyOf(s1, s2) => vec![s1, s2],
Self::Custom(_, args) => args,
}
}
@ -276,6 +289,7 @@ impl Operation {
(NO::HashOf, &[s1, s2, s3], OA::None) => {
Self::HashOf(s1.clone(), s2.clone(), s3.clone())
}
(NO::PublicKeyOf, &[s1, s2], OA::None) => Self::PublicKeyOf(s1.clone(), s2.clone()),
_ => Err(Error::custom(format!(
"Ill-formed operation {:?} with {} arguments {:?} and aux {:?}.",
op_code,
@ -310,6 +324,12 @@ impl Operation {
Ok(i1 == f(i2, i3))
}
pub(crate) fn check_public_key(v1: &Value, v2: &Value) -> Result<bool> {
let pk: PublicKey = v1.typed().try_into()?;
let sk: SecretKey = v2.typed().try_into()?;
Ok(sk.0 < *GROUP_ORDER && pk == sk.public_key())
}
/// Checks the given operation against a statement.
pub fn check(&self, params: &Params, output_statement: &Statement) -> Result<bool> {
use Statement::*;
@ -369,6 +389,9 @@ impl Operation {
(Self::HashOf(s1, s2, s3), HashOf(v4, v5, v6)) => {
val(v4, s1)? == hash_op(val(v5, s2)?, val(v6, s3)?)
}
(Self::PublicKeyOf(s1, s2), PublicKeyOf(v3, v4)) => {
Self::check_public_key(&val(v3, s1)?, &val(v4, s2)?)?
}
(Self::Custom(CustomPredicateRef { batch, index }, args), Custom(cpr, s_args))
if batch == &cpr.batch && index == &cpr.index =>
{
@ -564,8 +587,13 @@ pub(crate) fn value_from_op(input_st: &Statement, output_ref: &ValueRef) -> Opti
mod tests {
use std::collections::HashMap;
use num::BigUint;
use crate::{
backends::plonky2::primitives::merkletree::MerkleTree,
backends::plonky2::primitives::{
ec::{curve::GROUP_ORDER, schnorr::SecretKey},
merkletree::MerkleTree,
},
middleware::{
hash_value, AnchoredKey, Error, Key, Operation, Params, PodId, Result, Statement,
},
@ -635,4 +663,85 @@ mod tests {
})
})
}
#[test]
fn check_public_key_of_op() -> Result<()> {
let fixed_sk = SecretKey(BigUint::from(0x1234567890abcdefu64));
let fixed_pk = fixed_sk.public_key();
let rand_sk = SecretKey::new_rand();
let rand_pk = rand_sk.public_key();
let small_sk = SecretKey(BigUint::from(0x1u32));
let small_pk = small_sk.public_key();
let too_large_sk = SecretKey(small_sk.0.clone() + GROUP_ORDER.clone());
assert_eq!(small_pk, too_large_sk.public_key());
let test_cases = [
// Valid pairs
(fixed_pk, fixed_sk.clone(), true),
(rand_pk, rand_sk.clone(), true),
// Mismatched pairs
(fixed_pk, rand_sk.clone(), false),
(rand_pk, fixed_sk.clone(), false),
// Above group order
(small_pk, small_sk.clone(), true),
(small_pk, too_large_sk.clone(), false),
];
let params = Params::default();
let pod_id = PodId::default();
let pk_ak = AnchoredKey::new(pod_id, Key::new("pubkey".into()));
let sk_ak = AnchoredKey::new(pod_id, Key::new("secret".into()));
test_cases.iter().try_for_each(|(pk, sk, expect_good)| {
// Form op args
let pk_s = Statement::Equal(pk_ak.clone().into(), (*pk).into());
let sk_s = Statement::Equal(sk_ak.clone().into(), sk.clone().into());
// Form op
let op = Operation::PublicKeyOf(pk_s.clone(), sk_s.clone());
// Form output statement
let st = Statement::PublicKeyOf(pk_ak.clone().into(), sk_ak.clone().into());
// Check
op.check(&params, &st).map(|is_good| {
assert_eq!(
is_good, *expect_good,
"PublicKeyOf({}, {}) => {}",
pk, sk, is_good
);
})
})
}
#[test]
fn check_public_key_of_op_arg_types() -> Result<()> {
let fixed_sk = SecretKey(BigUint::from(0x1234567890abcdefu64));
let fixed_pk = fixed_sk.public_key();
let params = Params::default();
let pod_id = PodId::default();
let pk_ak = AnchoredKey::new(pod_id, Key::new("pubkey".into()));
let sk_ak = AnchoredKey::new(pod_id, Key::new("secret".into()));
// Form op args
let pk_s = Statement::Equal(pk_ak.clone().into(), fixed_pk.into());
let sk_s = Statement::Equal(sk_ak.clone().into(), fixed_sk.clone().into());
// Bad op and statement with bad first args
let op = Operation::PublicKeyOf(pk_s.clone(), pk_s.clone());
let st = Statement::PublicKeyOf(pk_ak.clone().into(), pk_ak.clone().into());
// Check
assert!(op.check(&params, &st).is_err());
// Bad op and statement with bad second args
let op = Operation::PublicKeyOf(sk_s.clone(), sk_s.clone());
let st = Statement::PublicKeyOf(sk_ak.clone().into(), sk_ak.clone().into());
// Check
assert!(op.check(&params, &st).is_err());
Ok(())
}
}

View file

@ -35,6 +35,7 @@ pub enum NativePredicate {
ProductOf = 9,
MaxOf = 10,
HashOf = 11,
PublicKeyOf = 12,
// Syntactic sugar predicates. These predicates are not supported by the backend. The
// frontend compiler is responsible of translating these predicates into the predicates above.
@ -64,6 +65,7 @@ impl Display for NativePredicate {
NativePredicate::ProductOf => "ProductOf",
NativePredicate::MaxOf => "MaxOf",
NativePredicate::HashOf => "HashOf",
NativePredicate::PublicKeyOf => "PublicKeyOf",
NativePredicate::DictContains => "DictContains",
NativePredicate::DictNotContains => "DictNotContains",
NativePredicate::ArrayContains => "ArrayContains",
@ -177,6 +179,7 @@ pub enum Statement {
ProductOf(ValueRef, ValueRef, ValueRef),
MaxOf(ValueRef, ValueRef, ValueRef),
HashOf(ValueRef, ValueRef, ValueRef),
PublicKeyOf(ValueRef, ValueRef),
Custom(CustomPredicateRef, Vec<Value>),
}
@ -211,6 +214,7 @@ impl Statement {
statement_constructor!(product_of, ProductOf, 3);
statement_constructor!(max_of, MaxOf, 3);
statement_constructor!(hash_of, HashOf, 3);
statement_constructor!(public_key_of, PublicKeyOf, 2);
pub fn predicate(&self) -> Predicate {
use Predicate::*;
match self {
@ -225,6 +229,7 @@ impl Statement {
Self::ProductOf(_, _, _) => Native(NativePredicate::ProductOf),
Self::MaxOf(_, _, _) => Native(NativePredicate::MaxOf),
Self::HashOf(_, _, _) => Native(NativePredicate::HashOf),
Self::PublicKeyOf(_, _) => Native(NativePredicate::PublicKeyOf),
Self::Custom(cpr, _) => Custom(cpr.clone()),
}
}
@ -242,6 +247,7 @@ impl Statement {
Self::ProductOf(ak1, ak2, ak3) => vec![ak1.into(), ak2.into(), ak3.into()],
Self::MaxOf(ak1, ak2, ak3) => vec![ak1.into(), ak2.into(), ak3.into()],
Self::HashOf(ak1, ak2, ak3) => vec![ak1.into(), ak2.into(), ak3.into()],
Self::PublicKeyOf(ak1, ak2) => vec![ak1.into(), ak2.into()],
Self::Custom(_, args) => Vec::from_iter(args.into_iter().map(Literal)),
}
}
@ -286,7 +292,9 @@ impl Statement {
(Native(NativePredicate::HashOf), &[a1, a2, a3]) => {
Self::HashOf(a1.try_into()?, a2.try_into()?, a3.try_into()?)
}
(Native(NativePredicate::PublicKeyOf), &[a1, a2]) => {
Self::PublicKeyOf(a1.try_into()?, a2.try_into()?)
}
(Native(np), _) => {
return Err(Error::custom(format!("Predicate {:?} is syntax sugar", np)))
}