diff --git a/Cargo.lock b/Cargo.lock index 843b979a..e0bc8ba9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -278,6 +278,7 @@ dependencies = [ "quickcheck", "quickcheck_macros", "rand 0.8.5", + "rand_chacha 0.3.1", "rsmt2", "rug", "serde", diff --git a/Cargo.toml b/Cargo.toml index 9e8f5c0f..af59eda6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ rug = { version = "1.11", features = ["serde"] } gmp-mpfr-sys = { version = "1.4", optional = true } lazy_static = { version = "1.4", optional = true } rand = "0.8" +rand_chacha = "0.3" rsmt2 = { version = "0.14", optional = true } ieee754 = { version = "0.2", optional = true} zokrates_parser = { path = "third_party/ZoKrates/zokrates_parser", optional = true } diff --git a/src/ir/term/fmt.rs b/src/ir/term/fmt.rs index 57691cd3..434137b4 100644 --- a/src/ir/term/fmt.rs +++ b/src/ir/term/fmt.rs @@ -185,6 +185,7 @@ impl DisplayIr for Op { Op::IntNaryOp(a) => write!(f, "{a}"), Op::IntBinPred(a) => write!(f, "{a}"), Op::UbvToPf(a) => write!(f, "(bv2pf {})", a.modulus()), + Op::PfChallenge(n, m) => write!(f, "(challenge {} {})", n, m.modulus()), Op::Select => write!(f, "select"), Op::Store => write!(f, "store"), Op::Tuple => write!(f, "tuple"), diff --git a/src/ir/term/mod.rs b/src/ir/term/mod.rs index 61d6fa32..025a28eb 100644 --- a/src/ir/term/mod.rs +++ b/src/ir/term/mod.rs @@ -125,6 +125,13 @@ pub enum Op { /// /// Takes the modulus. UbvToPf(FieldT), + /// A random value, sampled uniformly and independently of its arguments. + /// + /// Takes a name (if deterministically sampled, challenges of different names are sampled + /// differentely) and a field to sample from. + /// + /// In IR evaluation, we sample deterministically based on a hash of the name. + PfChallenge(String, FieldT), /// Integer n-ary operator IntNaryOp(IntNaryOp), @@ -272,6 +279,7 @@ impl Op { Op::FpToFp(_) => Some(1), Op::PfUnOp(_) => Some(1), Op::PfNaryOp(_) => None, + Op::PfChallenge(_, _) => None, Op::IntNaryOp(_) => None, Op::IntBinPred(_) => Some(2), Op::UbvToPf(_) => Some(1), @@ -1367,6 +1375,21 @@ pub fn eval_op(op: &Op, args: &[&Value], var_vals: &FxHashMap) -> ) }), Op::UbvToPf(fty) => Value::Field(fty.new_v(args[0].as_bv().uint())), + Op::PfChallenge(name, field) => { + use rand::SeedableRng; + use rand_chacha::ChaChaRng; + use std::hash::{Hash, Hasher}; + // hash the string + let mut hasher = fxhash::FxHasher::default(); + name.hash(&mut hasher); + let hash: u64 = hasher.finish(); + // seed ChaCha with the hash + let mut seed = [0u8; 32]; + seed[0..8].copy_from_slice(&hash.to_le_bytes()); + let mut rng = ChaChaRng::from_seed(seed); + // sample from ChaCha + Value::Field(field.random_v(&mut rng)) + } // tuple Op::Tuple => Value::Tuple(args.iter().map(|a| (*a).clone()).collect()), Op::Field(i) => { diff --git a/src/ir/term/text/mod.rs b/src/ir/term/text/mod.rs index c1fd2f40..79e0d0a7 100644 --- a/src/ir/term/text/mod.rs +++ b/src/ir/term/text/mod.rs @@ -288,6 +288,10 @@ impl<'src> IrInterp<'src> { [Leaf(Ident, b"ubv2fp"), a] => Ok(Op::UbvToFp(self.usize(a))), [Leaf(Ident, b"sbv2fp"), a] => Ok(Op::SbvToFp(self.usize(a))), [Leaf(Ident, b"fp2fp"), a] => Ok(Op::FpToFp(self.usize(a))), + [Leaf(Ident, b"challenge"), name, field] => Ok(Op::PfChallenge( + self.ident_string(name), + FieldT::from(self.int(field)), + )), [Leaf(Ident, b"bv2pf"), a] => Ok(Op::UbvToPf(FieldT::from(self.int(a)))), [Leaf(Ident, b"field"), a] => Ok(Op::Field(self.usize(a))), [Leaf(Ident, b"update"), a] => Ok(Op::Update(self.usize(a))), @@ -958,4 +962,12 @@ mod test { let c2 = parse_precompute(s.as_bytes()); assert_eq!(c, c2); } + + #[test] + fn challenge_roundtrip() { + let t = parse_term(b"(declare ((a bool) (b bool)) ((challenge hithere 17) a b))"); + let s = serialize_term(&t); + let t2 = parse_term(s.as_bytes()); + assert_eq!(t, t2); + } } diff --git a/src/ir/term/ty.rs b/src/ir/term/ty.rs index d9dd3a59..5ee68102 100644 --- a/src/ir/term/ty.rs +++ b/src/ir/term/ty.rs @@ -59,6 +59,7 @@ fn check_dependencies(t: &Term) -> Vec { Op::IntNaryOp(_) => Vec::new(), Op::IntBinPred(_) => Vec::new(), Op::UbvToPf(_) => Vec::new(), + Op::PfChallenge(_, _) => Vec::new(), Op::Select => vec![t.cs()[0].clone()], Op::Store => vec![t.cs()[0].clone()], Op::Tuple => t.cs().to_vec(), @@ -128,6 +129,7 @@ fn check_raw_step(t: &Term, tys: &TypeTable) -> Result { Op::IntNaryOp(_) => Ok(Sort::Int), Op::IntBinPred(_) => Ok(Sort::Bool), Op::UbvToPf(m) => Ok(Sort::Field(m.clone())), + Op::PfChallenge(_, m) => Ok(Sort::Field(m.clone())), Op::Select => array_or(get_ty(&t.cs()[0]), "select").map(|(_, v)| v.clone()), Op::Store => Ok(get_ty(&t.cs()[0]).clone()), Op::Tuple => Ok(Sort::Tuple(t.cs().iter().map(get_ty).cloned().collect())), @@ -332,6 +334,7 @@ pub fn rec_check_raw_helper(oper: &Op, a: &[&Sort]) -> Result bv_or(a, "ubv-to-pf").map(|_| Sort::Field(m.clone())), + (Op::PfChallenge(_, m), _) => Ok(Sort::Field(m.clone())), (Op::PfUnOp(_), &[a]) => pf_or(a, "pf unary op").map(|a| a.clone()), (Op::IntNaryOp(_), a) => { let ctx = "int nary op";