|
|
@@ -3,13 +3,33 @@
|
|
|
//! If that crate gets its own macro interface, it can use this module
|
|
|
//! directly.
|
|
|
|
|
|
-use super::combiners::StatementTree;
|
|
|
+use super::combiners::{PrivScalarMap, StatementTree};
|
|
|
use super::types::{expr_type_tokens_id_closure, AExprType, VarDict};
|
|
|
use proc_macro2::TokenStream;
|
|
|
use quote::{format_ident, quote, ToTokens};
|
|
|
use std::collections::HashSet;
|
|
|
+use syn::visit::Visit;
|
|
|
use syn::{Expr, Ident};
|
|
|
|
|
|
+/// Test whether an expression is term-free: it involves no private
|
|
|
+/// `Scalar`s, so its truth can be evaluated directly from the public
|
|
|
+/// instance rather than being proven in zero knowledge. How such an
|
|
|
+/// equation is lowered depends on where it sits; see
|
|
|
+/// [`CodeGen::linear_relation_codegen`].
|
|
|
+fn expr_is_term_free(vars: &VarDict, expr: &Expr) -> bool {
|
|
|
+ let mut has_priv = false;
|
|
|
+ let mut psmap = PrivScalarMap {
|
|
|
+ vars,
|
|
|
+ closure: &mut |_| {
|
|
|
+ has_priv = true;
|
|
|
+ Ok(())
|
|
|
+ },
|
|
|
+ result: Ok(()),
|
|
|
+ };
|
|
|
+ psmap.visit_expr(expr);
|
|
|
+ !has_priv
|
|
|
+}
|
|
|
+
|
|
|
/// Names and types of fields that might end up in a generated struct
|
|
|
#[derive(Clone)]
|
|
|
pub enum StructField {
|
|
|
@@ -201,14 +221,127 @@ impl<'a> CodeGen<'a> {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /// Generate code that pushes constant-shape claim branches for a
|
|
|
+ /// term-free equation `left = right` onto the relation vector
|
|
|
+ /// `out_var` (or, with `witness` set, the matching
|
|
|
+ /// `ComposedWitness::Claim` entries onto the witness vector). The
|
|
|
+ /// claim `left - right == identity` is evaluated by both parties
|
|
|
+ /// from the instance accessed through `acc`; a vector equation
|
|
|
+ /// contributes one claim per component. The emission is identical
|
|
|
+ /// whether the claim is true or false, so the proof shape never
|
|
|
+ /// depends on the public values.
|
|
|
+ fn claim_push_tokens(
|
|
|
+ &self,
|
|
|
+ expr: &Expr,
|
|
|
+ acc: &Ident,
|
|
|
+ out_var: &Ident,
|
|
|
+ witness: bool,
|
|
|
+ ) -> TokenStream {
|
|
|
+ let Expr::Assign(syn::ExprAssign { left, right, .. }) = expr else {
|
|
|
+ let expr_str = quote! { #expr }.to_string();
|
|
|
+ panic!("Unrecognized expression: {expr_str}");
|
|
|
+ };
|
|
|
+ let mut closure = |id: &Ident, _: AExprType| Ok(quote! {#acc.#id});
|
|
|
+ let (left_type, left_tokens) =
|
|
|
+ expr_type_tokens_id_closure(self.vars, left, &mut closure, false).unwrap();
|
|
|
+ let AExprType::Point {
|
|
|
+ is_pub: true,
|
|
|
+ is_vec: left_is_vec,
|
|
|
+ } = left_type
|
|
|
+ else {
|
|
|
+ let expr_str = quote! { #expr }.to_string();
|
|
|
+ panic!("Left side of = does not evaluate to a public point: {expr_str}");
|
|
|
+ };
|
|
|
+ let Ok((right_type, right_tokens)) =
|
|
|
+ expr_type_tokens_id_closure(self.vars, right, &mut closure, false)
|
|
|
+ else {
|
|
|
+ let expr_str = quote! { #expr }.to_string();
|
|
|
+ panic!("Right side of = is not a valid arithmetic expression: {expr_str}");
|
|
|
+ };
|
|
|
+ let AExprType::Point {
|
|
|
+ is_vec: right_is_vec,
|
|
|
+ ..
|
|
|
+ } = right_type
|
|
|
+ else {
|
|
|
+ let expr_str = quote! { #expr }.to_string();
|
|
|
+ panic!("Right side of = does not evaluate to a Point: {expr_str}");
|
|
|
+ };
|
|
|
+ if left_is_vec != right_is_vec {
|
|
|
+ let expr_str = quote! { #expr }.to_string();
|
|
|
+ panic!("Only one side of = is a vector expression: {expr_str}");
|
|
|
+ }
|
|
|
+ let one = quote! { Scalar::from_u128(1u128) };
|
|
|
+ match (witness, left_is_vec) {
|
|
|
+ (false, false) => quote! {
|
|
|
+ #out_var.push(ComposedInstance::claim([
|
|
|
+ (#one, #left_tokens),
|
|
|
+ (#one.neg(), #right_tokens),
|
|
|
+ ])?);
|
|
|
+ },
|
|
|
+ (false, true) => {
|
|
|
+ let lvar = format_ident!("{}cl", self.unique_prefix);
|
|
|
+ let rvar = format_ident!("{}cr", self.unique_prefix);
|
|
|
+ let lelem = format_ident!("{}cle", self.unique_prefix);
|
|
|
+ let relem = format_ident!("{}cre", self.unique_prefix);
|
|
|
+ quote! {
|
|
|
+ {
|
|
|
+ let #lvar = &(#left_tokens);
|
|
|
+ let #rvar = &(#right_tokens);
|
|
|
+ if #lvar.len() != #rvar.len() {
|
|
|
+ return Err(InvalidInstance::new(
|
|
|
+ "the two sides of a public equality have different lengths",
|
|
|
+ ));
|
|
|
+ }
|
|
|
+ for (#lelem, #relem) in #lvar.iter().zip(#rvar.iter()) {
|
|
|
+ #out_var.push(ComposedInstance::claim([
|
|
|
+ (#one, *#lelem),
|
|
|
+ (#one.neg(), *#relem),
|
|
|
+ ])?);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ (true, false) => quote! {
|
|
|
+ #out_var.push(ComposedWitness::Claim);
|
|
|
+ },
|
|
|
+ (true, true) => quote! {
|
|
|
+ for _ in 0..(#left_tokens).len() {
|
|
|
+ #out_var.push(ComposedWitness::Claim);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
/// Generate the code for the `protocol` and `protocol_witness`
|
|
|
/// functions that create the `ComposedInstance` and `ComposedWitness`
|
|
|
/// structs, respectively, given a slice of [`Expr`]s that will be
|
|
|
/// bundled into a single `LinearRelation`. The `protocol` code
|
|
|
/// must evaluate to a `Result<ComposedInstance>` and the `protocol_witness`
|
|
|
/// code must evaluate to a `Result<ComposedWitness>`.
|
|
|
- fn linear_relation_codegen(&self, exprs: &[&Expr]) -> (TokenStream, TokenStream) {
|
|
|
+ ///
|
|
|
+ /// With `disjunctive` set (the bundle sits under an `Or` or a
|
|
|
+ /// threshold), term-free equations become native claim branches
|
|
|
+ /// conjoined with the relation: both parties evaluate their truth
|
|
|
+ /// from the public instance, and the emission (hence the proof
|
|
|
+ /// shape) is the same whether they are true or false, so a branch
|
|
|
+ /// whose claim is false stays simulatable. Otherwise they stay in
|
|
|
+ /// the `LinearRelation` and `compile()` evaluates them: a true one
|
|
|
+ /// is stripped, a false one invalidates the instance. That keeps
|
|
|
+ /// the claim leaf, which is outside the specification's wire
|
|
|
+ /// format, off statements that never disjoin.
|
|
|
+ fn linear_relation_codegen(
|
|
|
+ &self,
|
|
|
+ exprs: &[&Expr],
|
|
|
+ disjunctive: bool,
|
|
|
+ ) -> (TokenStream, TokenStream) {
|
|
|
let instance_var = format_ident!("{}instance", self.unique_prefix);
|
|
|
+ let (claim_exprs, exprs): (Vec<&Expr>, Vec<&Expr>) = if disjunctive {
|
|
|
+ exprs
|
|
|
+ .iter()
|
|
|
+ .partition(|expr| expr_is_term_free(self.vars, expr))
|
|
|
+ } else {
|
|
|
+ (Vec::new(), exprs.to_vec())
|
|
|
+ };
|
|
|
let lr_var = format_ident!("{}lr", self.unique_prefix);
|
|
|
let mut allocated_vars: HashSet<Ident> = HashSet::new();
|
|
|
let mut param_vec_code = quote! {};
|
|
|
@@ -442,9 +575,46 @@ impl<'a> CodeGen<'a> {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- (
|
|
|
+ if claim_exprs.is_empty() {
|
|
|
+ return (
|
|
|
+ quote! {
|
|
|
+ {
|
|
|
+ let mut #lr_var = LinearRelation::<Point>::new();
|
|
|
+ #param_vec_code
|
|
|
+ #scalar_allocs
|
|
|
+ #element_allocs
|
|
|
+ #eq_code
|
|
|
+ #element_assigns
|
|
|
+
|
|
|
+ ComposedInstance::try_from(#lr_var)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ quote! {
|
|
|
+ {
|
|
|
+ #witness_vec_code
|
|
|
+ let mut witnessvec = Vec::new();
|
|
|
+ #witness_code
|
|
|
+ ProverResult::Ok(ComposedWitness::Simple(witnessvec))
|
|
|
+ }
|
|
|
+ },
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ let rels_var = format_ident!("{}rels", self.unique_prefix);
|
|
|
+ let wits_var = format_ident!("{}wits", self.unique_prefix);
|
|
|
+ let claims_proto: Vec<TokenStream> = claim_exprs
|
|
|
+ .iter()
|
|
|
+ .map(|expr| self.claim_push_tokens(expr, &instance_var, &rels_var, false))
|
|
|
+ .collect();
|
|
|
+ let claims_witness: Vec<TokenStream> = claim_exprs
|
|
|
+ .iter()
|
|
|
+ .map(|expr| self.claim_push_tokens(expr, &format_ident!("instance"), &wits_var, true))
|
|
|
+ .collect();
|
|
|
+ let core_push_proto = if exprs.is_empty() {
|
|
|
+ quote! {}
|
|
|
+ } else {
|
|
|
quote! {
|
|
|
- {
|
|
|
+ #rels_var.push({
|
|
|
let mut #lr_var = LinearRelation::<Point>::new();
|
|
|
#param_vec_code
|
|
|
#scalar_allocs
|
|
|
@@ -452,15 +622,45 @@ impl<'a> CodeGen<'a> {
|
|
|
#eq_code
|
|
|
#element_assigns
|
|
|
|
|
|
- ComposedInstance::try_from(#lr_var)
|
|
|
- }
|
|
|
- },
|
|
|
+ ComposedInstance::try_from(#lr_var)?
|
|
|
+ });
|
|
|
+ }
|
|
|
+ };
|
|
|
+ let core_push_witness = if exprs.is_empty() {
|
|
|
+ quote! {}
|
|
|
+ } else {
|
|
|
quote! {
|
|
|
- {
|
|
|
+ #wits_var.push({
|
|
|
#witness_vec_code
|
|
|
let mut witnessvec = Vec::new();
|
|
|
#witness_code
|
|
|
- ProverResult::Ok(ComposedWitness::Simple(witnessvec))
|
|
|
+ ComposedWitness::Simple(witnessvec)
|
|
|
+ });
|
|
|
+ }
|
|
|
+ };
|
|
|
+ (
|
|
|
+ quote! {
|
|
|
+ {
|
|
|
+ let mut #rels_var: Vec<ComposedInstance<Point>> = Vec::new();
|
|
|
+ #(#claims_proto)*
|
|
|
+ #core_push_proto
|
|
|
+ if #rels_var.len() == 1 {
|
|
|
+ InstanceResult::Ok(#rels_var.pop().unwrap())
|
|
|
+ } else {
|
|
|
+ ComposedInstance::and(#rels_var)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ quote! {
|
|
|
+ {
|
|
|
+ let mut #wits_var: Vec<ComposedWitness<Point>> = Vec::new();
|
|
|
+ #(#claims_witness)*
|
|
|
+ #core_push_witness
|
|
|
+ if #wits_var.len() == 1 {
|
|
|
+ ProverResult::Ok(#wits_var.pop().unwrap())
|
|
|
+ } else {
|
|
|
+ ProverResult::Ok(ComposedWitness::and(#wits_var))
|
|
|
+ }
|
|
|
}
|
|
|
},
|
|
|
)
|
|
|
@@ -474,7 +674,17 @@ impl<'a> CodeGen<'a> {
|
|
|
/// The `protocol` code must evaluate to a `Result<Protocol>` and
|
|
|
/// the `protocol_witness` code must evaluate to a
|
|
|
/// `Result<ComposedWitness>`.
|
|
|
- fn proto_witness_codegen(&self, statement: &StatementTree) -> (TokenStream, TokenStream) {
|
|
|
+ ///
|
|
|
+ /// `disjunctive` records whether this subtree lies under an `Or` or
|
|
|
+ /// a threshold, and so whether term-free equations need claim
|
|
|
+ /// leaves ([`Self::linear_relation_codegen`]). It is set on
|
|
|
+ /// descent into a disjunction and never cleared: an `And` nested in
|
|
|
+ /// an `Or` is still a branch that may have to be simulated.
|
|
|
+ fn proto_witness_codegen(
|
|
|
+ &self,
|
|
|
+ statement: &StatementTree,
|
|
|
+ disjunctive: bool,
|
|
|
+ ) -> (TokenStream, TokenStream) {
|
|
|
match statement {
|
|
|
// The StatementTree has no statements (it's just the single
|
|
|
// leaf "true")
|
|
|
@@ -489,7 +699,7 @@ impl<'a> CodeGen<'a> {
|
|
|
// The StatementTree is a single statement. Generate a
|
|
|
// single LinearRelation from it.
|
|
|
StatementTree::Leaf(leafexpr) => {
|
|
|
- self.linear_relation_codegen(std::slice::from_ref(&leafexpr))
|
|
|
+ self.linear_relation_codegen(std::slice::from_ref(&leafexpr), disjunctive)
|
|
|
}
|
|
|
// The StatementTree is an And. Separate out the leaf
|
|
|
// statements, and generate a single LinearRelation from
|
|
|
@@ -504,14 +714,15 @@ impl<'a> CodeGen<'a> {
|
|
|
_ => others.push(st),
|
|
|
}
|
|
|
}
|
|
|
- let (proto_code, witness_code) = self.linear_relation_codegen(&leaves);
|
|
|
+ let (proto_code, witness_code) =
|
|
|
+ self.linear_relation_codegen(&leaves, disjunctive);
|
|
|
if others.is_empty() {
|
|
|
(proto_code, witness_code)
|
|
|
} else {
|
|
|
let (others_proto, others_witness): (Vec<TokenStream>, Vec<TokenStream>) =
|
|
|
others
|
|
|
.iter()
|
|
|
- .map(|st| self.proto_witness_codegen(st))
|
|
|
+ .map(|st| self.proto_witness_codegen(st, disjunctive))
|
|
|
.unzip();
|
|
|
(
|
|
|
quote! {
|
|
|
@@ -532,7 +743,7 @@ impl<'a> CodeGen<'a> {
|
|
|
StatementTree::Or(stvec) => {
|
|
|
let (proto, witness): (Vec<TokenStream>, Vec<TokenStream>) = stvec
|
|
|
.iter()
|
|
|
- .map(|st| self.proto_witness_codegen(st))
|
|
|
+ .map(|st| self.proto_witness_codegen(st, true))
|
|
|
.unzip();
|
|
|
(
|
|
|
quote! {
|
|
|
@@ -550,7 +761,7 @@ impl<'a> CodeGen<'a> {
|
|
|
StatementTree::Thresh(thresh, stvec) => {
|
|
|
let (proto, witness): (Vec<TokenStream>, Vec<TokenStream>) = stvec
|
|
|
.iter()
|
|
|
- .map(|st| self.proto_witness_codegen(st))
|
|
|
+ .map(|st| self.proto_witness_codegen(st, true))
|
|
|
.unzip();
|
|
|
(
|
|
|
quote! {
|
|
|
@@ -656,7 +867,7 @@ impl<'a> CodeGen<'a> {
|
|
|
quote! {}
|
|
|
};
|
|
|
|
|
|
- let (protocol_code, witness_code) = self.proto_witness_codegen(self.statements);
|
|
|
+ let (protocol_code, witness_code) = self.proto_witness_codegen(self.statements, false);
|
|
|
|
|
|
// Generate the function that creates the sigma-proofs Protocol
|
|
|
let protocol_func = {
|