diff --git a/Cargo.toml b/Cargo.toml index d8a4626..0adc75d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ lazy_static = "1.5.0" thiserror = { version = "2.0.12" } # enabled by features: plonky2 = { git = "https://github.com/0xPARC/plonky2.git", rev = "3defd60532c8693cf5e9d2e6a8412c77ca58760f", optional = true } +plonky2_u32 = { git = "https://github.com/ax0/plonky2-u32", rev = "7a38240693455d6182210c2efecba99cbf871a6f" } serde = "1.0.219" serde_json = "1.0.140" base64 = "0.22.1" diff --git a/book/src/operations.md b/book/src/operations.md index 78149ce..91fa62a 100644 --- a/book/src/operations.md +++ b/book/src/operations.md @@ -22,6 +22,7 @@ The following table summarises the natively-supported operations: | 12 | `ProductOf` | `s1`, `s2`, `s3` | `s1 = ValueOf(ak1, value1)`, `s2 = ValueOf(ak2, value2)`, `s3 = ValueOf(ak3, value3)`, `value1 = value2 * value3` | `ProductOf(ak1, ak2, ak3)` | | 13 | `MaxOf` | `s1`, `s2`, `s3` | `s1 = ValueOf(ak1, value1)`, `s2 = ValueOf(ak2, value2)`, `s3 = ValueOf(ak3, value3)`, `value1 = max(value2, value3)` | `MaxOf(ak1, ak2, ak3)` | | 14 | `HashOf` | `s1`, `s2`, `s3` | `s1 = ValueOf(ak1, value1)`, `s2 = ValueOf(ak2, value2)`, `s3 = ValueOf(ak3, value3)`, `value1 = hash(value2, value3)`| `HashOf(ak1, ak2, ak3)` | +| 15 | `PublicKeyOf` | `s1`, `s2` | `s1 = ValueOf(ak1, value1)`, `s2 = ValueOf(ak2, value2)`, `value1 = derive_public_key(value2)` | `PublicKeyOf(ak1, ak2)` |

diff --git a/book/src/statements.md b/book/src/statements.md index 4bdef06..e9003ef 100644 --- a/book/src/statements.md +++ b/book/src/statements.md @@ -26,17 +26,17 @@ The following table summarises the natively-supported statements, where we write |------|---------------|---------------------|-------------------------------------------------------------------| | 0 | `None` | | no statement, always true (useful for padding) | | 1 | `False` | | always false (useful for padding disjunctions) | -| 2 | `ValueOf` | `ak`, `value` | `value_of(ak) = value` | -| 3 | `Equal` | `ak1`, `ak2` | `value_of(ak1) = value_of(ak2)` | -| 4 | `NotEqual` | `ak1`, `ak2` | `value_of(ak1) != value_of(ak2)` | -| 5 | `LtEq` | `ak1`, `ak2` | `value_of(ak1) <= value_of(ak2)` | -| 6 | `Lt` | `ak1`, `ak2` | `value_of(ak1) < value_of(ak2)` | -| 7 | `Contains` | `ak1`, `ak2` | `(key_of(ak2), value_of(ak2)) ∈ value_of(ak1)` (Merkle inclusion) | -| 8 | `NotContains` | `ak1`, `ak2` | `(key_of(ak2), value_of(ak2)) ∉ value_of(ak1)` (Merkle exclusion) | -| 9 | `SumOf` | `ak1`, `ak2`, `ak3` | `value_of(ak1) = value_of(ak2) + value_of(ak3)` | -| 10 | `ProductOf` | `ak1`, `ak2`, `ak3` | `value_of(ak1) = value_of(ak2) * value_of(ak3)` | -| 11 | `MaxOf` | `ak1`, `ak2`, `ak3` | `value_of(ak1) = max(value_of(ak2), value_of(ak3))` | -| 12 | `HashOf` | `ak1`, `ak2`, `ak3` | `value_of(ak1) = hash(value_of(ak2), value_of(ak3))` | +| 2 | `Equal` | `ak1`, `ak2` | `value_of(ak1) = value_of(ak2)` | +| 3 | `NotEqual` | `ak1`, `ak2` | `value_of(ak1) != value_of(ak2)` | +| 4 | `LtEq` | `ak1`, `ak2` | `value_of(ak1) <= value_of(ak2)` | +| 5 | `Lt` | `ak1`, `ak2` | `value_of(ak1) < value_of(ak2)` | +| 6 | `Contains` | `ak1`, `ak2` | `(key_of(ak2), value_of(ak2)) ∈ value_of(ak1)` (Merkle inclusion) | +| 7 | `NotContains` | `ak1`, `ak2` | `(key_of(ak2), value_of(ak2)) ∉ value_of(ak1)` (Merkle exclusion) | +| 8 | `SumOf` | `ak1`, `ak2`, `ak3` | `value_of(ak1) = value_of(ak2) + value_of(ak3)` | +| 9 | `ProductOf` | `ak1`, `ak2`, `ak3` | `value_of(ak1) = value_of(ak2) * value_of(ak3)` | +| 10 | `MaxOf` | `ak1`, `ak2`, `ak3` | `value_of(ak1) = max(value_of(ak2), value_of(ak3))` | +| 11 | `HashOf` | `ak1`, `ak2`, `ak3` | `value_of(ak1) = hash(value_of(ak2), value_of(ak3))` | +| 12 | `PublicKeyOf` | `ak1`, `ak2` | `value_of(ak1) = derive_public_key(value_of(ak2))` | ### Frontend statements diff --git a/src/backends/plonky2/circuits/mainpod.rs b/src/backends/plonky2/circuits/mainpod.rs index d896356..332eecd 100644 --- a/src/backends/plonky2/circuits/mainpod.rs +++ b/src/backends/plonky2/circuits/mainpod.rs @@ -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::(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::( + 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, operations: Vec, merkle_proofs: Vec, + secret_keys: Vec, custom_predicate_batches: Vec, custom_predicate_verifications: Vec, } @@ -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, merkle_proofs: Vec, + 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( diff --git a/src/backends/plonky2/mainpod/mod.rs b/src/backends/plonky2/mainpod/mod.rs index 4ea7280..8a07319 100644 --- a/src/backends/plonky2/mainpod/mod.rs +++ b/src/backends/plonky2/mainpod/mod.rs @@ -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(¶ms, &DEFAULT_VD_SET)?; + let prover = Prover {}; + let kyc_pod = ticket_builder.prove(&prover, ¶ms)?; + crate::measure_gates_print!(); + let pod = (kyc_pod.pod as Box).downcast::().unwrap(); + + Ok(pod.verify()?) + } + #[test] fn test_mini_0() { let params = middleware::Params { diff --git a/src/backends/plonky2/mainpod/statement.rs b/src/backends/plonky2/mainpod/statement.rs index 54b4faa..2505485 100644 --- a/src/backends/plonky2/mainpod/statement.rs +++ b/src/backends/plonky2/mainpod/statement.rs @@ -70,6 +70,10 @@ impl TryFrom 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 diff --git a/src/backends/plonky2/mock/mainpod.rs b/src/backends/plonky2/mock/mainpod.rs index cedb8b0..f385030 100644 --- a/src/backends/plonky2/mock/mainpod.rs +++ b/src/backends/plonky2/mock/mainpod.rs @@ -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(¶ms, &MOCK_VD_SET)?; let prover = MockProver {}; let proof_pod = tickets_builder.prove(&prover, ¶ms)?; let pod = (proof_pod.pod as Box) diff --git a/src/backends/plonky2/primitives/ec/curve.rs b/src/backends/plonky2/primitives/ec/curve.rs index caf82c0..d9e7ab5 100644 --- a/src/backends/plonky2/primitives/ec/curve.rs +++ b/src/backends/plonky2/primitives/ec/curve.rs @@ -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 { */ } + 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], diff --git a/src/backends/plonky2/primitives/ec/schnorr.rs b/src/backends/plonky2/primitives/ec/schnorr.rs index 8d2c83d..6de3bff 100644 --- a/src/backends/plonky2/primitives/ec/schnorr.rs +++ b/src/backends/plonky2/primitives/ec/schnorr.rs @@ -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 { + 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 { + 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 { + 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(&self, serializer: S) -> Result + where + S: Serializer, + { + let sk_b64 = serialize_bytes(&self.as_bytes()); + serializer.serialize_str(&sk_b64) + } +} + +impl<'de> Deserialize<'de> for SecretKey { + fn deserialize(deserializer: D) -> Result + 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 { + 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::()?; + 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::()?; + 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::()?); + 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::(&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(()) + } } diff --git a/src/examples/mod.rs b/src/examples/mod.rs index a0e4c06..04d73a7 100644 --- a/src/examples/mod.rs +++ b/src/examples/mod.rs @@ -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 = 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 { - let params = Params::default(); - let vd_set = &*MOCK_VD_SET; - let builder = tickets_sign_pod_builder(¶ms); +pub fn tickets_pod_full_flow(params: &Params, vd_set: &VDSet) -> Result { + let builder = tickets_sign_pod_builder(params); let signed_pod = builder.sign(&Signer(SecretKey(1u32.into()))).unwrap(); tickets_pod_builder( - ¶ms, + params, vd_set, &signed_pod, 123, diff --git a/src/frontend/mod.rs b/src/frontend/mod.rs index cbe5655..3993630 100644 --- a/src/frontend/mod.rs +++ b/src/frontend/mod.rs @@ -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(¶ms); + 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(¶ms, 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 + 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, ¶ms).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(¶ms); + 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(¶ms, 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 + 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(¶ms, 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 + 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(¶ms, 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 + vec![ + OperationArg::Statement(st_int2), + OperationArg::Statement(st_sk), + ], + OperationAux::None, + )) + .is_err()); + + Ok(()) + } + #[should_panic] #[test] fn test_reject_duplicate_new_entry() { diff --git a/src/lang/processor.rs b/src/lang/processor.rs index 7024273..fb08b33 100644 --- a/src/lang/processor.rs +++ b/src/lang/processor.rs @@ -39,6 +39,7 @@ pub fn native_predicate_from_string(s: &str) -> Option { "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 diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs index 21f7145..a3bb566 100644 --- a/src/middleware/mod.rs +++ b/src/middleware/mod.rs @@ -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 for TypedValue { } } +impl From for TypedValue { + fn from(sk: SecretKey) -> Self { + TypedValue::SecretKey(sk) + } +} + impl From 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 { + 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 { + 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::()) } @@ -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::())] + .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::(); let untagged_set_schema = gen.subschema_for::(); @@ -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, diff --git a/src/middleware/operation.rs b/src/middleware/operation.rs index eb59896..333b998 100644 --- a/src/middleware/operation.rs +++ b/src/middleware/operation.rs @@ -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), } @@ -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 { + 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 { 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(¶ms, &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(¶ms, &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(¶ms, &st).is_err()); + + Ok(()) + } } diff --git a/src/middleware/statement.rs b/src/middleware/statement.rs index 2daca16..3cbbdb8 100644 --- a/src/middleware/statement.rs +++ b/src/middleware/statement.rs @@ -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), } @@ -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))) }