fcomm/
lib.rs

1//! The `fcomm` CLI exposes an interface for creating and verifying Lurk proofs, and for manipulating functional commitments.
2
3use log::info;
4use std::collections::HashMap;
5use std::convert::TryFrom;
6use std::fs::File;
7use std::io::{self, BufReader, BufWriter};
8use std::path::Path;
9use std::str::FromStr;
10use std::sync::{Arc, Mutex};
11
12use ff::PrimeField;
13use hex::FromHex;
14use libipld::{
15    cbor::DagCborCodec,
16    json::DagJsonCodec,
17    multihash::{Code, MultihashDigest},
18    prelude::Codec,
19    serde::{from_ipld, to_ipld},
20    Cid, Ipld,
21};
22use lurk::{
23    circuit::ToInputs,
24    eval::{empty_sym_env, Evaluable, Evaluator, Status, IO},
25    field::LurkField,
26    proof::nova::{self, NovaProver, PublicParams},
27    proof::Prover,
28    scalar_store::ScalarStore,
29    store::{Pointer, Ptr, ScalarPtr, Store},
30    tag::ExprTag,
31    writer::Write,
32};
33use once_cell::sync::OnceCell;
34use pasta_curves::pallas;
35use rand::rngs::OsRng;
36use serde::de::DeserializeOwned;
37use serde::{Deserialize, Deserializer, Serialize, Serializer};
38
39pub mod error;
40mod file_map;
41
42use error::Error;
43use file_map::FileMap;
44
45pub const DEFAULT_REDUCTION_COUNT: ReductionCount = ReductionCount::Ten;
46pub static VERBOSE: OnceCell<bool> = OnceCell::new();
47
48pub type S1 = pallas::Scalar;
49
50mod base64 {
51    use serde::{Deserialize, Serialize};
52    use serde::{Deserializer, Serializer};
53
54    pub fn serialize<S: Serializer>(v: &Vec<u8>, s: S) -> Result<S::Ok, S::Error> {
55        let base64 = base64::encode(v);
56        String::serialize(&base64, s)
57    }
58
59    pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Vec<u8>, D::Error> {
60        let base64 = String::deserialize(d)?;
61        base64::decode(base64.as_bytes()).map_err(serde::de::Error::custom)
62    }
63}
64
65pub type NovaProofCache = FileMap<Cid, Proof<'static, S1>>;
66pub fn nova_proof_cache(reduction_count: usize) -> NovaProofCache {
67    FileMap::<Cid, Proof<S1>>::new(format!("nova_proofs.{}", reduction_count)).unwrap()
68}
69
70pub type CommittedExpressionMap = FileMap<Commitment<S1>, CommittedExpression<S1>>;
71pub fn committed_expression_store() -> CommittedExpressionMap {
72    FileMap::<Commitment<S1>, CommittedExpression<S1>>::new("committed_expressions").unwrap()
73}
74
75pub type PublicParamMemCache = Mutex<HashMap<usize, Arc<PublicParams<'static>>>>;
76fn public_param_mem_cache() -> &'static PublicParamMemCache {
77    static CACHE: OnceCell<PublicParamMemCache> = OnceCell::new();
78    CACHE.get_or_init(|| Mutex::new(HashMap::new()))
79}
80
81pub type PublicParamDiskCache = FileMap<String, PublicParams<'static>>;
82fn public_param_disk_cache() -> PublicParamDiskCache {
83    FileMap::new("public_params").unwrap()
84}
85
86pub fn public_params(rc: usize) -> Result<Arc<PublicParams<'static>>, Error> {
87    let mut mem_cache = public_param_mem_cache().lock().unwrap();
88    match mem_cache.get(&rc) {
89        Some(pp) => Ok(pp.clone()),
90        None => {
91            let disk_cache = public_param_disk_cache();
92            // TODO: Add versioning to cache key
93            let key = format!("public-params-rc-{rc}");
94            if let Some(pp) = disk_cache.get(&key) {
95                let pp = Arc::new(pp);
96                mem_cache.insert(rc, pp.clone());
97                Ok(pp)
98            } else {
99                let pp = Arc::new(nova::public_params(rc));
100                mem_cache.insert(rc, pp.clone());
101                disk_cache
102                    .set(key, &pp)
103                    .map_err(|e| Error::CacheError(format!("Disk write error: {e}")))?;
104                Ok(pp)
105            }
106        }
107    }
108}
109
110// Number of circuit reductions per step, equivalent to `chunk_frame_count`
111#[derive(Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)]
112pub enum ReductionCount {
113    One,
114    Five,
115    Ten,
116    OneHundred,
117}
118
119#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq)]
120pub struct Evaluation {
121    pub expr: String,
122    pub env: String,
123    pub cont: String,
124    pub expr_out: String,
125    pub env_out: String,
126    pub cont_out: String,
127    pub status: Status,
128    pub iterations: Option<usize>,
129}
130
131#[derive(Clone, Copy, Debug, Eq, PartialEq)]
132pub struct Commitment<F: LurkField> {
133    pub comm: F,
134}
135
136#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
137pub struct OpeningRequest<F: LurkField> {
138    pub commitment: Commitment<F>,
139    pub input: Expression,
140    pub chain: bool,
141}
142
143impl<F: LurkField> ToString for Commitment<F> {
144    fn to_string(&self) -> String {
145        let s = serde_json::to_string(&self).unwrap();
146        // Remove quotation marks. Yes, dumb hacks are happening.
147        s[1..s.len() - 1].to_string()
148    }
149}
150
151impl<F: LurkField> Serialize for Commitment<F> {
152    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
153    where
154        S: Serializer,
155    {
156        // Use be_bytes for consistency with PrimeField printed representation.
157        let be_bytes: Vec<u8> = self
158            .comm
159            .to_repr()
160            .as_ref()
161            .iter()
162            .rev()
163            .map(|x| x.to_owned())
164            .collect();
165
166        hex::serde::serialize(be_bytes, serializer)
167    }
168}
169
170impl<'de, F: LurkField> Deserialize<'de> for Commitment<F> {
171    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
172    where
173        D: Deserializer<'de>,
174    {
175        hex::serde::deserialize(deserializer)
176    }
177}
178
179impl<F: LurkField> FromHex for Commitment<F> {
180    type Error = hex::FromHexError;
181
182    fn from_hex<T>(s: T) -> Result<Self, <Self as FromHex>::Error>
183    where
184        T: AsRef<[u8]>,
185    {
186        let mut v = Vec::from_hex(s)?;
187        v.reverse();
188        let mut repr = <F as PrimeField>::Repr::default();
189        repr.as_mut()[..32].copy_from_slice(&v[..]);
190
191        Ok(Commitment {
192            comm: F::from_repr(repr).unwrap(),
193        })
194    }
195}
196
197#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
198pub struct Expression {
199    pub expr: LurkPtr,
200}
201
202#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
203pub struct Opening<F: LurkField> {
204    pub input: String,
205    pub output: String,
206    pub status: Status,
207    pub commitment: Commitment<F>,
208    pub new_commitment: Option<Commitment<F>>,
209}
210
211#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
212pub struct LurkScalarBytes {
213    #[serde(with = "base64")]
214    scalar_store: Vec<u8>,
215    #[serde(with = "base64")]
216    scalar_ptr: Vec<u8>,
217}
218
219#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
220pub struct LurkScalarIpld {
221    scalar_store: Ipld,
222    scalar_ptr: Ipld,
223}
224
225#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
226pub enum LurkPtr {
227    Source(String),
228    ScalarBytes(LurkScalarBytes),
229    Ipld(LurkScalarIpld),
230}
231
232#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
233pub struct CommittedExpression<F: LurkField + Serialize> {
234    pub expr: LurkPtr,
235    pub secret: Option<F>,
236    pub commitment: Option<Commitment<F>>,
237}
238
239#[derive(Debug, Serialize, Deserialize)]
240pub struct VerificationResult {
241    pub verified: bool,
242}
243
244#[derive(Serialize, Deserialize)]
245pub struct Proof<'a, F: LurkField> {
246    pub claim: Claim<F>,
247    pub proof: nova::Proof<'a>,
248    pub num_steps: usize,
249    pub reduction_count: ReductionCount,
250}
251
252#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
253pub enum Claim<F: LurkField> {
254    Evaluation(Evaluation),
255    Opening(Opening<F>),
256}
257
258// This is just a rough idea, mostly here so we can plumb it elsewhere. The idea is that a verifier can sign an
259// attestation that a given claim's proof was verified. It motivates the use of an online verifier for demo purposes.
260// Although real proofs should be fast to verify, they will still be large relative to a small (auditable) bundle like
261// this. Even if not entirely realistic, something with this general *shape* is likely to play a role in a recursive
262// system where the ability to aggregate proof verification more soundly is possible.
263#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
264pub struct Cert {
265    #[serde(serialize_with = "cid_string", deserialize_with = "string_cid")]
266    pub claim_cid: Cid,
267    #[serde(serialize_with = "cid_string", deserialize_with = "string_cid")]
268    pub proof_cid: Cid,
269    pub verified: bool,
270    pub verifier_id: String,
271    pub signature: String,
272}
273
274fn cid_string<S>(c: &Cid, s: S) -> Result<S::Ok, S::Error>
275where
276    S: Serializer,
277{
278    s.serialize_str(&c.to_string())
279}
280
281pub fn string_cid<'de, D>(d: D) -> Result<Cid, D::Error>
282where
283    D: Deserializer<'de>,
284{
285    use serde::de::Error;
286
287    let string = String::deserialize(d)?;
288
289    Cid::from_str(&string).map_err(|e| D::Error::custom(e.to_string()))
290}
291
292pub fn cid_from_string(s: &str) -> Result<Cid, libipld::cid::Error> {
293    Cid::from_str(s)
294}
295
296#[allow(dead_code)]
297impl<F: LurkField> Claim<F> {
298    pub fn is_evaluation(&self) -> bool {
299        self.evaluation().is_some()
300    }
301    pub fn is_opening(&self) -> bool {
302        self.opening().is_some()
303    }
304    pub fn evaluation(&self) -> Option<Evaluation> {
305        match self {
306            Self::Evaluation(e) => Some(e.clone()),
307            _ => None,
308        }
309    }
310    pub fn opening(&self) -> Option<Opening<F>> {
311        match self {
312            Self::Opening(o) => Some(o.clone()),
313            _ => None,
314        }
315    }
316}
317
318type E = Error;
319impl TryFrom<usize> for ReductionCount {
320    type Error = E;
321
322    fn try_from(count: usize) -> Result<Self, <Self as TryFrom<usize>>::Error> {
323        match count {
324            1 => Ok(ReductionCount::One),
325            5 => Ok(ReductionCount::Five),
326            10 => Ok(ReductionCount::Ten),
327            100 => Ok(ReductionCount::OneHundred),
328            c => Err(Error::UnsupportedReductionCount(c)),
329        }
330    }
331}
332impl ReductionCount {
333    pub fn count(&self) -> usize {
334        match self {
335            Self::One => 1,
336            Self::Five => 5,
337            Self::Ten => 10,
338            Self::OneHundred => 100,
339        }
340    }
341}
342
343pub trait Id
344where
345    Self: Sized,
346{
347    fn id(&self) -> String;
348    fn cid(&self) -> Cid;
349    fn has_id(&self, id: String) -> bool;
350}
351
352impl<T: Serialize> Id for T
353where
354    for<'de> T: Deserialize<'de>,
355{
356    fn cid(&self) -> Cid {
357        let ipld = to_ipld(self).unwrap();
358        let dag_json = DagJsonCodec.encode(&ipld).unwrap();
359
360        let digest = Code::Blake3_256.digest(&dag_json);
361        Cid::new_v1(0x55, digest)
362    }
363
364    fn id(&self) -> String {
365        self.cid().to_string()
366    }
367
368    fn has_id(&self, id: String) -> bool {
369        self.id() == id
370    }
371}
372
373pub trait FileStore
374where
375    Self: Sized,
376{
377    fn write_to_path<P: AsRef<Path>>(&self, path: P);
378    fn read_from_path<P: AsRef<Path>>(path: P) -> Result<Self, Error>;
379    fn read_from_stdin() -> Result<Self, Error>;
380}
381
382impl<T: Serialize> FileStore for T
383where
384    for<'de> T: Deserialize<'de>, // + Decode<DagJsonCodec>,
385{
386    fn write_to_path<P: AsRef<Path>>(&self, path: P) {
387        let file = File::create(path).expect("failed to create file");
388        let writer = BufWriter::new(&file);
389
390        serde_json::to_writer(writer, &self).expect("failed to write file");
391    }
392
393    fn read_from_path<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
394        let file = File::open(path)?;
395        let reader = BufReader::new(file);
396        Ok(serde_json::from_reader(reader)?)
397    }
398
399    fn read_from_stdin() -> Result<Self, Error> {
400        let reader = BufReader::new(io::stdin());
401        Ok(serde_json::from_reader(reader).expect("failed to read from stdin"))
402    }
403}
404
405impl Evaluation {
406    fn new<F: LurkField>(
407        s: &mut Store<F>,
408        input: IO<F>,
409        output: IO<F>,
410        iterations: Option<usize>, // This might be padded, so is not quite 'iterations' in the sense of number of actual reduction steps required
411                                   // to evaluate.
412    ) -> Self {
413        let status: Status = output.cont.into();
414        let terminal = status.is_terminal();
415
416        // For now, conservatively hide all outputs unless output is terminal. TODO: let evaluator configure this in a
417        // more fine-grained way, including no hiding.
418        // NOTE: If anything is hidden, a proof won't be possible.
419        macro_rules! maybe_hide {
420            ($x:expr) => {
421                if terminal {
422                    $x
423                } else {
424                    "".to_string()
425                }
426            };
427        }
428
429        let expr = input.expr.fmt_to_string(s);
430        let env = input.env.fmt_to_string(s);
431        let cont = input.cont.fmt_to_string(s);
432
433        let expr_out = maybe_hide!(output.expr.fmt_to_string(s));
434        let env_out = maybe_hide!(output.env.fmt_to_string(s));
435        let cont_out = maybe_hide!(output.cont.fmt_to_string(s));
436
437        Self {
438            expr,
439            env,
440            cont,
441            expr_out,
442            env_out,
443            cont_out,
444            status,
445            iterations,
446        }
447    }
448
449    pub fn eval<F: LurkField + Serialize>(
450        store: &mut Store<F>,
451        expr: Ptr<F>,
452        limit: usize,
453    ) -> Result<Self, Error> {
454        let env = empty_sym_env(store);
455        let mut evaluator = Evaluator::new(expr, env, store, limit);
456
457        let input = evaluator.initial();
458
459        let (output, iterations, _) = evaluator.eval().map_err(|_| Error::EvaluationFailure)?;
460
461        Ok(Self::new(store, input, output, Some(iterations)))
462    }
463}
464
465impl<F: LurkField + Serialize + DeserializeOwned> Commitment<F> {
466    pub fn from_comm(s: &mut Store<F>, ptr: &Ptr<F>) -> Self {
467        let digest = *s.hash_expr(ptr).expect("couldn't hash ptr").value();
468
469        assert_eq!(ExprTag::Comm, ptr.tag());
470
471        Commitment { comm: digest }
472    }
473
474    pub fn ptr(&self, s: &mut Store<F>) -> Ptr<F> {
475        s.intern_opaque_comm(self.comm)
476    }
477
478    pub fn from_ptr_with_hiding(s: &mut Store<F>, ptr: &Ptr<F>) -> (Self, F) {
479        let secret = F::random(OsRng);
480
481        let commitment = Self::from_ptr_and_secret(s, ptr, secret);
482
483        (commitment, secret)
484    }
485
486    pub fn from_ptr_and_secret(s: &mut Store<F>, ptr: &Ptr<F>, secret: F) -> Self {
487        let hidden = s.hide(secret, *ptr);
488
489        Self::from_comm(s, &hidden)
490    }
491
492    // Importantly, this ensures the function and secret are in the Store, s.
493    fn construct_with_fun_application(
494        s: &mut Store<F>,
495        function: CommittedExpression<F>,
496        input: Ptr<F>,
497        limit: usize,
498    ) -> Result<(Self, Ptr<F>), Error> {
499        let fun_ptr = function.expr_ptr(s, limit)?;
500        let secret = function.secret.expect("CommittedExpression secret missing");
501
502        let commitment = Self::from_ptr_and_secret(s, &fun_ptr, secret);
503
504        let open = s.sym("open");
505        let comm_ptr = s.hide(secret, fun_ptr);
506
507        // (open <commitment>)
508        let fun_expr = s.list(&[open, comm_ptr]);
509
510        // ((open <commitment>) input)
511        let expression = s.list(&[fun_expr, input]);
512
513        Ok((commitment, expression))
514    }
515
516    fn fun_application(&self, s: &mut Store<F>, input: Ptr<F>) -> Ptr<F> {
517        let open = s.sym("open");
518        let comm_ptr = self.ptr(s);
519
520        // (open <commitment>)
521        let fun_expr = s.list(&[open, comm_ptr]);
522
523        // ((open commitment) input)
524        s.list(&[fun_expr, input])
525    }
526}
527
528impl<F: LurkField + Serialize + DeserializeOwned> CommittedExpression<F> {
529    pub fn expr_ptr(&self, s: &mut Store<F>, limit: usize) -> Result<Ptr<F>, Error> {
530        let source_ptr = self.expr.ptr(s, limit);
531
532        Ok(source_ptr)
533    }
534}
535
536impl LurkPtr {
537    pub fn ptr<F: LurkField + Serialize + DeserializeOwned>(
538        &self,
539        s: &mut Store<F>,
540        limit: usize,
541    ) -> Ptr<F> {
542        match self {
543            LurkPtr::Source(source) => {
544                let ptr = s.read(source).expect("could not read source");
545                let (out, _) = evaluate(s, ptr, limit).unwrap();
546
547                out.expr
548            }
549            LurkPtr::ScalarBytes(lurk_scalar_bytes) => {
550                let scalar_store: Ipld = DagCborCodec
551                    .decode(&lurk_scalar_bytes.scalar_store)
552                    .expect("could not read opaque scalar store");
553                let scalar_ptr: Ipld = DagCborCodec
554                    .decode(&lurk_scalar_bytes.scalar_ptr)
555                    .expect("could not read opaque scalar ptr");
556
557                let lurk_ptr = LurkPtr::Ipld(LurkScalarIpld {
558                    scalar_store,
559                    scalar_ptr,
560                });
561
562                lurk_ptr.ptr(s, limit)
563            }
564            LurkPtr::Ipld(lurk_scalar_ipld) => {
565                // FIXME: put the scalar_store in a new field for the store.
566                let fun_scalar_store: ScalarStore<F> =
567                    from_ipld(lurk_scalar_ipld.scalar_store.clone()).unwrap();
568                let fun_scalar_ptr: ScalarPtr<F> =
569                    from_ipld(lurk_scalar_ipld.scalar_ptr.clone()).unwrap();
570                s.intern_scalar_ptr(fun_scalar_ptr, &fun_scalar_store)
571                    .expect("failed to intern scalar_ptr for fun")
572            }
573        }
574    }
575
576    pub fn from_ptr<F: LurkField + Serialize>(s: &mut Store<F>, ptr: &Ptr<F>) -> Self {
577        let (scalar_store, scalar_ptr) = ScalarStore::new_with_expr(s, ptr);
578        let scalar_ptr = scalar_ptr.unwrap();
579
580        let scalar_store_ipld = to_ipld(scalar_store).unwrap();
581        let new_fun_ipld = to_ipld(scalar_ptr).unwrap();
582
583        let scalar_store_bytes = DagCborCodec.encode(&scalar_store_ipld).unwrap();
584        let new_fun_bytes = DagCborCodec.encode(&new_fun_ipld).unwrap();
585
586        let again = from_ipld(new_fun_ipld).unwrap();
587        assert_eq!(&scalar_ptr, &again);
588
589        Self::ScalarBytes(LurkScalarBytes {
590            scalar_store: scalar_store_bytes,
591            scalar_ptr: new_fun_bytes,
592        })
593    }
594}
595
596impl Expression {
597    pub fn eval<F: LurkField + Serialize + DeserializeOwned>(
598        &self,
599        s: &mut Store<F>,
600        limit: usize,
601    ) -> Result<Ptr<F>, Error> {
602        let expr = self.expr.ptr(s, limit);
603        let (io, _iterations) = evaluate(s, expr, limit)?;
604
605        Ok(io.expr)
606    }
607}
608
609impl<'a> Opening<S1> {
610    #[allow(clippy::too_many_arguments)]
611    pub fn apply_and_prove(
612        s: &'a mut Store<S1>,
613        input: Ptr<S1>,
614        function: CommittedExpression<S1>,
615        limit: usize,
616        chain: bool,
617        only_use_cached_proofs: bool,
618        nova_prover: &'a NovaProver<S1>,
619        pp: &'a PublicParams,
620    ) -> Result<Proof<'a, S1>, Error> {
621        let claim = Self::apply(s, input, function, limit, chain)?;
622        Proof::prove_claim(s, &claim, limit, only_use_cached_proofs, nova_prover, pp)
623    }
624
625    pub fn open_and_prove(
626        s: &'a mut Store<S1>,
627        request: OpeningRequest<S1>,
628        limit: usize,
629        only_use_cached_proofs: bool,
630        nova_prover: &'a NovaProver<S1>,
631        pp: &'a PublicParams,
632    ) -> Result<Proof<'a, S1>, Error> {
633        let input = request.input.expr.ptr(s, limit);
634        let commitment = request.commitment;
635
636        let function_map = committed_expression_store();
637        let function = function_map
638            .get(&commitment)
639            .ok_or(Error::UnknownCommitment)?;
640
641        Self::apply_and_prove(
642            s,
643            input,
644            function,
645            limit,
646            request.chain,
647            only_use_cached_proofs,
648            nova_prover,
649            pp,
650        )
651    }
652
653    pub fn open(
654        s: &mut Store<S1>,
655        request: OpeningRequest<S1>,
656        limit: usize,
657        chain: bool,
658    ) -> Result<Claim<S1>, Error> {
659        let input = request.input.expr.ptr(s, limit);
660        let commitment = request.commitment;
661
662        let function_map = committed_expression_store();
663        let function = function_map
664            .get(&commitment)
665            .ok_or(Error::UnknownCommitment)?;
666
667        Self::apply(s, input, function, limit, chain)
668    }
669
670    fn _is_chained(&self) -> bool {
671        self.new_commitment.is_some()
672    }
673
674    fn public_output_expression(&self, s: &mut Store<S1>) -> Ptr<S1> {
675        let result = s.read(&self.output).expect("unreadable result");
676
677        if let Some(commitment) = self.new_commitment {
678            let c = commitment.ptr(s);
679
680            s.cons(result, c)
681        } else {
682            result
683        }
684    }
685
686    pub fn apply(
687        s: &mut Store<S1>,
688        input: Ptr<S1>,
689        function: CommittedExpression<S1>,
690        limit: usize,
691        chain: bool,
692    ) -> Result<Claim<S1>, Error> {
693        let (commitment, expression) =
694            Commitment::construct_with_fun_application(s, function, input, limit)?;
695        let (public_output, _iterations) = evaluate(s, expression, limit)?;
696
697        let (new_commitment, output_expr) = if chain {
698            let cons = public_output.expr;
699            let result_expr = s.car(&cons)?;
700            let new_comm = s.cdr(&cons)?;
701
702            let new_secret0 = s.secret(new_comm).expect("secret missing");
703            let new_secret = *s.get_expr_hash(&new_secret0).expect("hash missing").value();
704
705            let (_, new_fun) = s.open(new_comm).expect("opening missing");
706            let new_commitment = Commitment::from_comm(s, &new_comm);
707
708            s.hydrate_scalar_cache();
709
710            let expr = LurkPtr::from_ptr(s, &new_fun);
711
712            let new_function = CommittedExpression::<S1> {
713                expr,
714                secret: Some(new_secret),
715                commitment: Some(new_commitment),
716            };
717
718            let function_map = committed_expression_store();
719            function_map.set(new_commitment, &new_function)?;
720
721            (Some(new_commitment), result_expr)
722        } else {
723            (None, public_output.expr)
724        };
725
726        let input_string = input.fmt_to_string(s);
727        let status = public_output.status();
728        let output_string = if status.is_terminal() {
729            // Only actual output if result is terminal.
730            output_expr.fmt_to_string(s)
731        } else {
732            // We don't want to leak any internal information in the case of incomplete computations.
733            // Provers might want to expose results in the case of explicit errors.
734            // For now, don't -- but consider allowing it as an option.
735            "".to_string()
736        };
737
738        let claim = Claim::Opening(Opening {
739            commitment,
740            new_commitment,
741            input: input_string,
742            output: output_string,
743            status,
744        });
745
746        Ok(claim)
747    }
748}
749
750impl<'a> Proof<'a, S1> {
751    pub fn eval_and_prove(
752        s: &'a mut Store<S1>,
753        expr: Ptr<S1>,
754        limit: usize,
755        only_use_cached_proofs: bool,
756        nova_prover: &'a NovaProver<S1>,
757        pp: &'a PublicParams,
758    ) -> Result<Self, Error> {
759        let env = empty_sym_env(s);
760        let cont = s.intern_cont_outermost();
761        let input = IO { expr, env, cont };
762
763        let (public_output, _iterations) = evaluate(s, expr, limit)?;
764        let evaluation = Evaluation::new(s, input, public_output, None);
765        let claim = Claim::Evaluation(evaluation);
766
767        Self::prove_claim(s, &claim, limit, only_use_cached_proofs, nova_prover, pp)
768    }
769
770    pub fn prove_claim(
771        s: &'a mut Store<S1>,
772        claim: &Claim<S1>,
773        limit: usize,
774        only_use_cached_proofs: bool,
775        nova_prover: &'a NovaProver<S1>,
776        pp: &'a PublicParams,
777    ) -> Result<Self, Error> {
778        let reduction_count = nova_prover.reduction_count();
779
780        let proof_map = nova_proof_cache(reduction_count);
781        let function_map = committed_expression_store();
782
783        let cid = claim.cid();
784
785        if let Some(proof) = proof_map.get(&cid) {
786            return Ok(proof);
787        }
788
789        if only_use_cached_proofs {
790            // FIXME: Error handling.
791            panic!("no cached proof");
792        }
793
794        info!("Starting Proving");
795
796        let (expr, env) = match &claim {
797            Claim::Evaluation(e) => (
798                s.read(&e.expr).expect("bad expression"),
799                s.read(&e.env).expect("bad env"),
800            ),
801            Claim::Opening(o) => {
802                let commitment = o.commitment;
803
804                // In order to prove the opening, we need access to the original function.
805                let function = function_map
806                    .get(&commitment)
807                    .expect("function for commitment missing");
808
809                let input = s.read(&o.input).expect("bad expression");
810                let (c, expression) =
811                    Commitment::construct_with_fun_application(s, function, input, limit)?;
812
813                assert_eq!(commitment, c);
814                (expression, empty_sym_env(s))
815            }
816        };
817
818        let (proof, _public_input, _public_output, num_steps) = nova_prover
819            .evaluate_and_prove(pp, expr, env, s, limit)
820            .expect("Nova proof failed");
821
822        let proof = Self {
823            claim: claim.clone(),
824            proof,
825            num_steps,
826            reduction_count: ReductionCount::try_from(reduction_count)?,
827        };
828
829        match &claim {
830            Claim::Opening(o) => {
831                if o.status != Status::Terminal {
832                    return Err(Error::OpeningFailure("Claim status is not Terminal".into()));
833                };
834            }
835            Claim::Evaluation(e) => {
836                if e.status != Status::Terminal {
837                    return Err(Error::EvaluationFailure);
838                };
839            }
840        };
841
842        proof.verify(pp).expect("Nova verification failed");
843
844        proof_map.set(cid, &proof).unwrap();
845
846        Ok(proof)
847    }
848
849    pub fn verify(&self, pp: &PublicParams) -> Result<VerificationResult, Error> {
850        let (public_inputs, public_outputs) = self.io_vecs()?;
851
852        let claim_iterations_and_num_steps_are_consistent = if let Claim::Evaluation(Evaluation {
853            iterations: Some(iterations),
854            ..
855        }) = self.claim
856        {
857            // Currently, claims created by fcomm don't include the iteration count. If they do, then it should be
858            // possible to verify correctness. This may require making the iteration count explicit in the public
859            // output. That will allow maintaining iteration count without incrementing during frames added as
860            // padding; and it will also allow explicitly masking the count when desired for zero-knowledge.
861            // Meanwhile, since Nova currently requires the number of steps to be provided by the verifier, we have
862            // to provide it. For now, we should at least be able to calculate this value based on number of real
863            // iterations and number of frames per circuit. This is untested and mostly a placeholder to remind us
864            // that all of this will need to be handled in a more principled way eventually. (#282)
865
866            let num_steps = self.num_steps;
867
868            let chunk_frame_count = self.reduction_count.count();
869            let expected_steps =
870                (iterations / chunk_frame_count) + (iterations % chunk_frame_count != 0) as usize;
871
872            expected_steps == num_steps
873        } else {
874            true
875        };
876
877        let verified = claim_iterations_and_num_steps_are_consistent
878            && self
879                .proof
880                .verify(pp, self.num_steps, public_inputs, &public_outputs)
881                .expect("error verifying");
882
883        let result = VerificationResult::new(verified);
884
885        Ok(result)
886    }
887
888    pub fn evaluation_io(&self, s: &mut Store<S1>) -> Result<(IO<S1>, IO<S1>), Error> {
889        let evaluation = &self.claim.evaluation().expect("expected evaluation claim");
890
891        let input_io = {
892            let expr = s
893                .read(&evaluation.expr)
894                .map_err(|_| Error::VerificationError("failed to read expr".into()))?;
895
896            let env = s
897                .read(&evaluation.env)
898                .map_err(|_| Error::VerificationError("failed to read env".into()))?;
899
900            // FIXME: We ignore cont and assume Outermost, since we can't read a Cont.
901            let cont = s.intern_cont_outermost();
902
903            IO::<S1> { expr, env, cont }
904        };
905
906        let output_io = {
907            let expr = s
908                .read(&evaluation.expr_out)
909                .map_err(|_| Error::VerificationError("failed to read expr out".into()))?;
910
911            let env = s
912                .read(&evaluation.env_out)
913                .map_err(|_| Error::VerificationError("failed to read env out".into()))?;
914            let cont = evaluation
915                .status
916                .to_cont(s)
917                .ok_or_else(|| Error::VerificationError("continuation cannot be proved".into()))?;
918
919            IO::<S1> { expr, env, cont }
920        };
921
922        Ok((input_io, output_io))
923    }
924
925    pub fn opening_io(&self, s: &mut Store<S1>) -> Result<(IO<S1>, IO<S1>), Error> {
926        assert!(self.claim.is_opening());
927
928        let opening = self.claim.opening().expect("expected opening claim");
929        let output = opening.public_output_expression(s);
930        let input = s.read(&opening.input).expect("could not read input");
931
932        let expression = opening.commitment.fun_application(s, input);
933        let outermost = s.intern_cont_outermost();
934
935        let input_io = IO::<S1> {
936            expr: expression,
937            env: empty_sym_env(s),
938            cont: outermost,
939        };
940
941        let output_io = IO::<S1> {
942            expr: output,
943            env: empty_sym_env(s),
944            cont: s.intern_cont_terminal(),
945        };
946
947        Ok((input_io, output_io))
948    }
949
950    pub fn io(&self, s: &mut Store<S1>) -> Result<(IO<S1>, IO<S1>), Error> {
951        match self.claim {
952            Claim::Evaluation(_) => self.evaluation_io(s),
953            Claim::Opening(_) => self.opening_io(s),
954        }
955    }
956    fn io_vecs(&self) -> Result<(Vec<S1>, Vec<S1>), Error> {
957        let s = &mut Store::<S1>::default();
958
959        self.io(s).map(|(i, o)| (i.to_inputs(s), o.to_inputs(s)))
960    }
961}
962
963impl VerificationResult {
964    fn new(verified: bool) -> Self {
965        Self { verified }
966    }
967}
968
969pub fn evaluate<F: LurkField>(
970    store: &mut Store<F>,
971    expr: Ptr<F>,
972    limit: usize,
973) -> Result<(IO<F>, usize), Error> {
974    let env = empty_sym_env(store);
975    let mut evaluator = Evaluator::new(expr, env, store, limit);
976
977    let (io, iterations, _) = evaluator.eval().map_err(|_| Error::EvaluationFailure)?;
978
979    assert!(io.is_terminal());
980    Ok((io, iterations))
981}
982
983#[cfg(test)]
984mod test {
985    use super::*;
986
987    #[test]
988    fn test_cert_serialization() {
989        use serde_json::json;
990
991        let c = Commitment {
992            comm: S1::from(123),
993        };
994
995        let cid = c.cid();
996        let cert = Cert {
997            claim_cid: cid,
998            proof_cid: cid,
999            verified: true,
1000            verifier_id: "asdf".to_string(),
1001            signature: "fdsa".to_string(),
1002        };
1003        let json = json!(cert);
1004
1005        let string = json.to_string();
1006
1007        let cert_again: Cert = serde_json::from_str(&string).unwrap();
1008        assert_eq!(cert, cert_again);
1009    }
1010}