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)))
}