Merkle tree for custom predicate batches (#471)

Resolve https://github.com/0xPARC/pod2/issues/466

Now batches are identified by the root of a merkle tree that contains all the predicates (using sequential indices as keys).  This means that the format to identify a custom predicate reference is still a hash + index, but the calculation of the hash is different.
The MainPod circuit now isn't limited by number of batches but instead number of custom predicates; and for each one we verify a merkle proof to verify the batch id.

I've removed a bunch of tests from lang that were testing splitting into multiple batches because there's no longer any need for that.  In a future PR we'll remove the code that handles batch splitting.

Each custom predicate needs 148.2 gates (which is very close to my estimate of 142.7 in https://github.com/0xPARC/pod2/issues/466#issuecomment-3823531286 where I actually made a mistake and considered 5 predicates per batch instead of 4 in the previous Params).
This commit is contained in:
Eduard S. 2026-02-04 11:12:32 +01:00 committed by GitHub
parent a7a30176a7
commit 641d8dabdd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 331 additions and 761 deletions

View file

@ -681,68 +681,6 @@ mod tests {
assert!(result.is_ok());
}
#[test]
fn test_multi_batch_packing() {
// Create more predicates than fit in a single batch
// With max_custom_batch_size = 4, 5 predicates should span 2 batches
let input = r#"
pred1(A) = AND(Equal(A["a"], 1))
pred2(B) = AND(Equal(B["b"], 2))
pred3(C) = AND(Equal(C["c"], 3))
pred4(D) = AND(Equal(D["d"], 4))
pred5(E) = AND(Equal(E["e"], 5))
"#;
let params = Params::default(); // max_custom_batch_size = 4
let result = parse_validate_and_lower(input, &params);
assert!(result.is_ok());
let lowered = result.unwrap();
let batches = lowered.batches.as_ref().expect("Expected batches");
// Should have 2 batches
assert_eq!(batches.batch_count(), 2);
assert_eq!(batches.total_predicate_count(), 5);
// First batch should have 4 predicates
assert_eq!(batches.batches()[0].predicates().len(), 4);
// Second batch should have 1 predicate
assert_eq!(batches.batches()[1].predicates().len(), 1);
}
#[test]
fn test_split_chains_span_batches() {
// Create predicates that will split, plus additional predicates
// to force the split chains across batch boundaries
let input = r#"
pred1(A) = AND(Equal(A["a"], 1))
pred2(B) = AND(Equal(B["b"], 2))
pred3(C) = AND(Equal(C["c"], 3))
large_pred(D) = AND(
Equal(D["a"], 1)
Equal(D["b"], 2)
Equal(D["c"], 3)
Equal(D["d"], 4)
Equal(D["e"], 5)
Equal(D["f"], 6)
)
"#;
let params = Params::default();
let result = parse_validate_and_lower(input, &params);
assert!(result.is_ok());
let lowered = result.unwrap();
let batches = lowered.batches.as_ref().expect("Expected batches");
// pred1, pred2, pred3 + large_pred split into 2 = 5 total predicates
// Should span 2 batches
assert_eq!(batches.total_predicate_count(), 5);
assert_eq!(batches.batch_count(), 2);
}
#[test]
fn test_intro_predicate_in_custom_predicate() {
use hex::ToHex;