diff --git a/demo/bank.lurk b/demo/bank.lurk new file mode 100644 index 0000000000..8d7a020a1e --- /dev/null +++ b/demo/bank.lurk @@ -0,0 +1,242 @@ +;; Let's build a functional bank. + +;; We'll start by defining a tiny database of people and their balances. + +!(def people '((:first-name "Alonzo" :last-name "Church" :balance 123 :id 0) + (:first-name "Alan" :last-name "Turing" :balance 456 :id 1) + (:first-name "Satoshi" :last-name "Nakamoto" :balance 9000 :id 2))) + +;; We need a way to look up keys in the database records, so we define a getter. + +!(defrec get (lambda (key plist) + (if plist + (if (eq key (car plist)) + (car (cdr plist)) + (get key (cdr (cdr plist)))) + nil))) + +;; Let's test it by getting the last name of the first person. + +(get :last-name (car people)) + +;; We also need some functional helpers. Map applies a function to each element of a list. + +!(defrec map (lambda (f list) + (if list + (cons (f (car list)) + (map f (cdr list))) + ()))) + +;; Filter removes elements of a list that don't satisfy a predicate function. + +!(defrec filter (lambda (pred list) + (if list + (if (pred (car list)) + (cons (car list) (filter pred (cdr list))) + (filter pred (cdr list))) + ()))) + +;; Let's write a predicate that is true when an entry's balance is at least a specified amount. + +!(def balance-at-least? (lambda (x) + (lambda (entry) + (>= (get :balance entry) x)))) + +;; Putting it together, let's query the database for the first name of people with a balance of at least 200. + +(map (get :first-name) (filter (balance-at-least? 200) people)) + +;; And let's get everyone's balance. + +(map (get :balance) people) + +;; We can define a function to sum a list of values recursively. + +!(defrec sum (lambda (vals) + (if vals + (+ (car vals) (sum (cdr vals))) + 0))) + +;; Apply this to the balances, and we can calculate the total funds in a database. + +!(def total-funds (lambda (db) (sum (map (get :balance) db)))) + +;; Let's snapshot the initial funds. + +!(def initial-total-funds (emit (total-funds people))) + +;; We can check a database to see if funds were conserved by comparing with the inital total. + +!(def funds-are-conserved? (lambda (db) (= initial-total-funds (total-funds db)))) + +;; Here's a setter. + +!(def set (lambda (key value plist) + (letrec ((aux (lambda (acc plist) + (if plist + (if (eq key (car plist)) + (aux (cons key (cons value acc)) + (cdr (cdr plist))) + (aux (cons (car plist) + (cons (car (cdr plist)) acc)) + (cdr (cdr plist)))) + acc)))) + (aux () plist)))) + +;; We can use it to change a balance. + +(set :balance 666 (car people)) + +;; More useful is an update function that modifes a field based on its current value. + +!(def update (lambda (key update-fn plist) + (letrec ((aux (lambda (acc plist) + (if plist + (if (eq key (car plist)) + (aux (cons key (cons (update-fn (car (cdr plist))) acc)) + (cdr (cdr plist))) + (aux (cons (car plist) + (cons (car (cdr plist)) acc)) + (cdr (cdr plist)))) + acc)))) + (aux () plist)))) + +;; For example, we can double Church's balance. + +(update :balance (lambda (x) (* x 2)) (car people)) + +;; And, here's a function that updates only the rows that satisfy a predicate. + +!(def update-where (lambda (predicate key update-fn db) + (letrec ((aux (lambda (db) + (if db + (if (predicate (car db)) + (cons (update key update-fn (car db)) + (aux (cdr db))) + (cons (car db) + (aux (cdr db)))) + nil)))) + (aux db)))) + +;; Let's write a predicate for selecting rows by ID. + +!(def has-id? (lambda (id x) (eq id (get :id x)))) + +;; That lets us change the first letter of the first name of the person with ID 2. + +(update-where (has-id? 2) :first-name (lambda (x) (strcons 'Z' (cdr x))) people) + +;; Finally, let's put it all together and write a function to send balances from one account to another. + +;; We select the from account by filtering on id, + +;; Check that its balance is at least the transfer amount, + +;; Then update both the sender and receiver's balance by the amount. + +;; If the sender doesn't have enough funds, we display an insufficient funds message, and return the database unchanged. + +!(def send (lambda (amount from-id to-id db) + (let ((from (car (filter (has-id? from-id) db)))) + (if (balance-at-least? amount from) + (let ((debited (update-where (has-id? from-id) :balance (lambda (x) (- x amount)) db)) + (credited (update-where (has-id? to-id) :balance (lambda (x) (+ x amount)) debited))) + credited) + (begin (emit "INSUFFICIENT FUNDS") db))))) + +;; In token of this new function, we'll call our database of people a ledger. + +!(def ledger people) + +;; We can send 200 from account 1 to account 0. + +!(def ledger1 (send 200 1 0 ledger)) + +ledger1 + +;; And assert that funds were conserved. (Nothing was created or destroyed.) + +!(assert (funds-are-conserved? ledger1)) + +;; Or, using the original ledger, we can try sending 200 from account 0 to 1. + +!(def ledger2 (send 200 0 1 ledger)) + +ledger2 + +;; Notice that this transaction fails due to insufficient funds. However, + +!(assert (funds-are-conserved? ledger2)) + +;; funds are still conserved + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Functional Commitment to a Database Value + +;; Let's define a function that takes a database and returns a transfer function. + +;; Transfer function takes an amount, a source id, and a destination id, then attempts to send the funds. + +!(def fn<-db (lambda (db) + (lambda (transfer) + (let ((amount (car transfer)) + (rest (cdr transfer)) + (from-id (car rest)) + (rest (cdr rest)) + (to-id (car rest))) + (send (emit amount) (emit from-id) (emit to-id) (emit db)))))) + +;; Now let's create a transfer function for our ledger, and commit to it. + +!(commit (fn<-db ledger)) + +;; Now we can open the committed ledger transfer function on a transaction. + +!(call 0x014a94be6a32b6c74be26071f627aff0a06ef3caade04d335958b8a3bb818925 '(1 0 2)) + +;; And the record reflects that Church sent one unit to Satoshi. + +;; Let's prove it. + +!(prove) + +;; We can verify the proof.. + +!(verify "Nova_Pallas_10_3ec93e16c42eb6822e1597d915c0a1f284fea322297786eb506bd814e29e33d3") + +;; Unfortunately, this functional commitment doesn't let us maintain state. +;; Let's turn our single-transaction function into a chained function. + +!(def chain<-db (lambda (db secret) + (letrec ((foo (lambda (state msg) + (let ((new-state ((fn<-db state) msg))) + (cons new-state (hide secret (foo new-state))))))) + (foo db)))) + +;; We'll call this on our ledger, and protect write-access with a secret value (999). + +!(commit (chain<-db ledger 999)) + +;; Now we can transfer one unit from Church to Satoshi like before. + +!(chain 0x0757a47095c97d0a58a8ff66a2146949ee9d60d3f0a82887c203edf1748be711 '(1 0 2)) + +!(prove) + +!(verify "Nova_Pallas_10_118481b08f97e5ea84499dd305d5c2b86089d5d5e5253e6c345119d55020bde3") + +;; Then we can transfer 5 more, proceeding from the new head of the chain. + +!(chain 0x0d0b562063718f3623c1749268da4dd74ed33142edc411f4e74d7a84e64f3f30 '(5 0 2)) + +!(prove) + +!(verify "Nova_Pallas_10_045d2fef5862777cf50a9dc8df50650f1aebfcbfb8135adfea57fb8f45389af4") + +;; And once more, this time we'll transfer 20 from Turing to Church. + +!(chain 0x3e232dc8d44c85697fa4205d0d945014d34b980af498aed9194216c27c489e05 '(20 1 0)) + +!(prove) + +!(verify "Nova_Pallas_10_25c76318cda48570568b9bb766ee9a906790c4dc94c96b1d6549e96c3bff8aa7") diff --git a/demo/chained-functional-commitment.lurk b/demo/chained-functional-commitment.lurk new file mode 100644 index 0000000000..e6f65c2f28 --- /dev/null +++ b/demo/chained-functional-commitment.lurk @@ -0,0 +1,57 @@ +;; First, we define a stateful function that adds its input to an internal counter, initialized to 0. + +;; The function returns a new counter value and a commitment to a replacement function wrapping the new counter. + +!(commit (letrec ((add (lambda (counter x) + (let ((counter (+ counter x))) + (cons counter (commit (add counter))))))) + (add 0))) + +;; We chain a next commitment by applying the committed function to a value of 9. + +!(chain 0x06042852d90bf409974d1ee3bc153c0f48ea5512c9b4f697561df9ad7b5abbe0 9) + +;; The new counter value is 9, and the function returns a new functional commitment. + +;; This new commitment is now the head of the chain. + +;; Next, we ccreate a proof of this transtion. + +!(prove) + +;; We can verify the proof. + +!(verify "Nova_Pallas_10_241f5c936d5c11e9c99b52017354738f9024c084fdfe49da9ad4a39fb2fe6619") + +;; Now let's chain another call to the new head, adding 12 to the counter. + +!(chain 0x251ccd3ecf6ae912eeb6558733b04b50e0b0c374a2dd1b797fcca84b0d9a8794 12) + +;; Now the counter is 21, and we have a new head commitment. + +;; Prove it. + +!(prove) + +;; And verify. + +!(verify "Nova_Pallas_10_369621a97ad8521369e49e773e0a531b0162d2f193877fbdfb308d3b2a23eea2") + +;; One more time, we'll add 14 to the head commitment's internal state. + +!(chain 0x281605259696cd2529c00806465c9726d81df4ccd2c3500312c991c1fd572592 14) + +;; 21 + 14 = 35, as expected. + +;; Prove. + +!(prove) + +;; Verify. + +!(verify "Nova_Pallas_10_057ebe2b592d6d82c1badc86493f6118a70c81aebb3b53a054d9b2517ad118f2") + +;; Repeat indefinitely. + +;; At every step, we have proof that the head commitment was correctly derived from the previous and some input. + diff --git a/demo/functional-commitment.lurk b/demo/functional-commitment.lurk new file mode 100644 index 0000000000..6bd8e809a1 --- /dev/null +++ b/demo/functional-commitment.lurk @@ -0,0 +1,29 @@ +;; Let's define a function: f(x) = 3x^2 + 9x + 2 + +!(def f (lambda (x) (+ (* 3 (* x x)) (+ (* 9 x) 2)))) + +!(assert-eq (f 5) 122) + +;; We can create a cryptographic commitment to f. + +!(commit f) + +;; We open the functional commitment on input 5: Evaluate f(5). + +!(call 0x178453ec28175e52c42a6467520df4a1322dd03e06abb3dfc829425ac590e48c 5) + +;; We can prove the functional-commitment opening. + +!(prove) + +;; We can inspect the input/output expressions of the proof. + +!(inspect "Nova_Pallas_10_02058c301605abc546248c543d450e07172690e7fb6be0fa27be6d5a898817e0") + +;; Or the full proof claim + +!(inspect-full "Nova_Pallas_10_02058c301605abc546248c543d450e07172690e7fb6be0fa27be6d5a898817e0") + +;; Finally, and most importantly, we can verify the proof. + +!(verify "Nova_Pallas_10_02058c301605abc546248c543d450e07172690e7fb6be0fa27be6d5a898817e0") diff --git a/demo/simple.lurk b/demo/simple.lurk new file mode 100644 index 0000000000..b0990eb5ba --- /dev/null +++ b/demo/simple.lurk @@ -0,0 +1,17 @@ +123 + +(+ 1 1) + +!(def square (lambda (x) (* x x))) + +(square 8) + +!(def make-adder + (lambda (n) + (lambda (x) + (+ x n)))) + +!(def five-plus + (make-adder 5)) + +(five-plus 3) diff --git a/demo/vdf.lurk b/demo/vdf.lurk new file mode 100644 index 0000000000..4978dfac02 --- /dev/null +++ b/demo/vdf.lurk @@ -0,0 +1,79 @@ +;; Hat tip to JP Aumasson. +!(defrec fastexp (lambda (b e) + (if (= e 0) 1 + (if (< (/ e 2) 0) ; is e odd? + (* b (fastexp (* b b) (/ (- e 1) 2))) + (fastexp (* b b) (/ e 2)))))) + +(fastexp 2 5) + +;; (4p - 3) / 5 +!(def r 23158417847463239084714197001737581570690445185553317903743794198714690358477) + +!(def fifth-root (lambda (n) (fastexp n r))) + +!(def fifth (lambda (n) (fastexp n 5))) + +(fifth-root 42) + +(fifth 0x2e6606ca7e8983f71964677e06cd8fd13ee0d46bf3c3e52d3af1b80df06f730b) + +!(def round (lambda (state) + (let ((x (car state)) + (y (car (cdr state))) + (i (car (cdr (cdr state))))) + (cons (fifth-root (+ x y)) + (cons (+ x i) + (cons (+ i 1) nil)))))) + +!(def inverse-round (lambda (state) + (let ((x (car state)) + (y (car (cdr state))) + (i (car (cdr (cdr state)))) + (new-i (- i 1)) + (new-x (- y new-i)) + (new-y (- (fifth x) new-x))) + (cons new-x (cons new-y (cons new-i nil)))))) + +!(defrec minroot (lambda (state rounds) + (if (= rounds 0) + state + (minroot (round state) (- rounds 1))))) + +!(defrec minroot-inverse (lambda (state rounds) + (if (= rounds 0) + state + (minroot-inverse (inverse-round state) (- rounds 1))))) + +(minroot '(123 456 1) 10) + +(minroot-inverse '(0x27ec1d892ff1b85d98dd8e61509c0ce63b6954da8a743ee54b1f405cde722eb1 0x0da555f3ff604e853948466204d773c4c34d8cf38cea55351c9c97593613fb3b 11) 10) + +!(prove) + +!(verify "Nova_Pallas_10_20c429019e3eade18d0182febe27b987af8da1ab15cf55b0794296deb0435929") + +!(def timelock-encrypt (lambda (secret-key plaintext rounds) + (let ((ciphertext (+ secret-key plaintext)) + (timelocked-key-state (minroot-inverse (cons secret-key '(0 1)) rounds))) + (cons timelocked-key-state ciphertext)))) + +!(def timelock-decrypt (lambda (timelocked-key-state ciphertext rounds) + (let ((secret-key (car (minroot timelocked-key-state rounds))) + (plaintext (- ciphertext secret-key))) + plaintext))) + +; (timelock-encrypt (num (commit )) 10000) + +; [2370068 iterations] => ((0x2b7a3b8ddd37f5729671b40f14ea588eb74e0474516503cae76114c80c3e68b3 0x39766ed0c1d5a61b0a0b5146585f01ea78bac01860ce0f8653bb098d42efcce3 0x40000000000000000000000000000000224698fc0994a8dd8c46eb20ffffd8f2) . 0x0fbc16c244caeec63f5e0316c9b36ad5eba0b1c10f7ecf5d681a911e9dfa74d0) + +(timelock-decrypt ;; timelocked key state + '(0x2b7a3b8ddd37f5729671b40f14ea588eb74e0474516503cae76114c80c3e68b3 + 0x39766ed0c1d5a61b0a0b5146585f01ea78bac01860ce0f8653bb098d42efcce3 + 0x40000000000000000000000000000000224698fc0994a8dd8c46eb20ffffd8f2) + ;; ciphertext + 0x0fbc16c244caeec63f5e0316c9b36ad5eba0b1c10f7ecf5d681a911e9dfa74d0 + ;; rounds + 10000) + +;; [97420052 iterations] => diff --git a/src/cli/lurk_proof.rs b/src/cli/lurk_proof.rs index f1e085b6da..b20c427fcf 100644 --- a/src/cli/lurk_proof.rs +++ b/src/cli/lurk_proof.rs @@ -1,4 +1,4 @@ -use std::sync::Arc; +use std::{collections::HashMap, sync::Arc}; use ::nova::{ supernova::NonUniformCircuit, @@ -12,7 +12,7 @@ use crate::{ coprocessor::Coprocessor, eval::lang::{Coproc, Lang}, field::LurkField, - lem::pointers::ZPtr, + lem::{pointers::ZPtr, store::Store}, proof::{ nova::{self, CurveCycleEquipped, G1, G2}, supernova::C2, @@ -22,12 +22,13 @@ use crate::{ instance::{Instance, Kind}, public_params, }, + state::{initial_lurk_state, State}, }; use super::{ field_data::{dump, load, HasFieldModulus}, paths::{proof_meta_path, proof_path}, - zstore::ZStore, + zstore::{populate_store, ZStore}, }; /// Carries extra information to help with visualization, experiments etc. @@ -95,6 +96,53 @@ impl LurkProofMeta { } } +impl LurkProofMeta { + pub(crate) fn inspect_proof( + proof_key: &str, + store_state: Option<(&Store, &State)>, + full: bool, + ) -> Result<()> { + let proof_meta: LurkProofMeta = load(&proof_meta_path(proof_key))?; + let do_inspect = |store: &Store, state: &State| { + let mut cache = HashMap::default(); + let z_store = &proof_meta.z_store; + let expr = populate_store(store, &proof_meta.expr, z_store, &mut cache)?; + let expr_out = populate_store(store, &proof_meta.expr_out, z_store, &mut cache)?; + if full { + let env = populate_store(store, &proof_meta.env, z_store, &mut cache)?; + let cont = populate_store(store, &proof_meta.cont, z_store, &mut cache)?; + let env_out = populate_store(store, &proof_meta.env_out, z_store, &mut cache)?; + let cont_out = populate_store(store, &proof_meta.cont_out, z_store, &mut cache)?; + println!( + "Input:\n Expr: {}\n Env: {}\n Cont: {}", + expr.fmt_to_string(store, state), + env.fmt_to_string(store, state), + cont.fmt_to_string(store, state), + ); + println!( + "Output:\n Expr: {}\n Env: {}\n Cont: {}", + expr_out.fmt_to_string(store, state), + env_out.fmt_to_string(store, state), + cont_out.fmt_to_string(store, state), + ); + } else { + println!( + "Input:\n {}\nOutput:\n {}", + expr.fmt_to_string(store, state), + expr_out.fmt_to_string(store, state) + ); + } + println!("Iterations: {}", proof_meta.iterations); + Ok(()) + }; + if let Some((store, state)) = store_state { + do_inspect(store, state) + } else { + do_inspect(&Store::default(), initial_lurk_state()) + } + } +} + impl<'a, F: CurveCycleEquipped + Serialize, M: MultiFrameTrait<'a, F, Coproc>> LurkProof<'a, F, Coproc, M> where diff --git a/src/cli/mod.rs b/src/cli/mod.rs index a5ab017b28..0725673e7c 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -53,6 +53,8 @@ enum Command { Repl(ReplArgs), /// Verifies a Lurk proof Verify(VerifyArgs), + /// Inspects a Lurk proof + Inspect(InspectArgs), /// Instantiates a new circom gadget to interface with bellpepper. /// /// See `lurk circom --help` for more details @@ -415,9 +417,9 @@ impl LoadCli { #[derive(Args, Debug)] struct VerifyArgs { - /// ID of the proof to be verified + /// Key of the proof to be verified #[clap(value_parser)] - proof_id: String, + proof_key: String, /// Path to public parameters directory #[clap(long, value_parser)] @@ -432,6 +434,21 @@ struct VerifyArgs { config: Option, } +#[derive(Args, Debug)] +struct InspectArgs { + /// Key of the proof to be inspected + #[clap(value_parser)] + proof_key: String, + + /// Flag to show the entire proof meta-data + #[arg(long)] + full: bool, + + /// Path to proofs directory + #[clap(long, value_parser)] + proofs_dir: Option, +} + /// To setup a new circom gadget ``, place your circom files in a designated folder and /// create a file called `.circom`. `/.circom` is the input file /// for the `circom` binary; in this file you must declare your circom main component. @@ -578,9 +595,23 @@ impl Cli { cli_config(verify_args.config.as_ref(), Some(&cli_settings)); LurkProof::<_, _, MultiFrame<'_, _, Coproc>>::verify_proof( - &verify_args.proof_id, - )?; - Ok(()) + &verify_args.proof_key, + ) + } + #[allow(unused_variables)] + Command::Inspect(inspect_args) => { + use crate::cli::lurk_proof::LurkProofMeta; + let mut cli_settings = HashMap::new(); + if let Some(dir) = inspect_args.proofs_dir { + cli_settings.insert("proofs_dir", dir.to_string()); + } + cli_config(None, Some(&cli_settings)); + + LurkProofMeta::::inspect_proof( + &inspect_args.proof_key, + None, + inspect_args.full, + ) } Command::Circom(circom_args) => { use crate::cli::circom::create_circom_gadget; @@ -593,8 +624,7 @@ impl Cli { } cli_config(circom_args.config.as_ref(), Some(&cli_settings)); - create_circom_gadget(&circom_args.circom_folder, &circom_args.name)?; - Ok(()) + create_circom_gadget(&circom_args.circom_folder, &circom_args.name) } Command::PublicParams(public_params_args) => { let mut cli_settings = HashMap::new(); @@ -604,9 +634,8 @@ impl Cli { cli_config(public_params_args.config.as_ref(), Some(&cli_settings)); - create_lurk_dirs().unwrap(); - public_params_args.run()?; - Ok(()) + create_lurk_dirs()?; + public_params_args.run() } } } diff --git a/src/cli/repl.rs b/src/cli/repl.rs index 5cb1e6cbff..08383fa586 100644 --- a/src/cli/repl.rs +++ b/src/cli/repl.rs @@ -17,7 +17,7 @@ use crate::{ eval::lang::{Coproc, Lang}, field::LurkField, lem::{ - eval::{evaluate, evaluate_simple}, + eval::{evaluate_simple_with_env, evaluate_with_env}, interpreter::Frame, multiframe::MultiFrame, pointers::Ptr, @@ -63,6 +63,13 @@ struct Evaluation { iterations: usize, } +impl Evaluation { + #[inline] + fn get_result(&self) -> Option<&Ptr> { + self.frames.last().and_then(|frame| frame.output.first()) + } +} + #[allow(dead_code)] pub struct Repl { store: Store, @@ -93,6 +100,10 @@ fn pad(a: usize, m: usize) -> usize { } impl Repl { + fn get_evaluation(&self) -> &Option> { + &self.evaluation + } + fn peek1(&self, cmd: &str, args: &Ptr) -> Result> { let (first, rest) = self.store.car_cdr(args)?; if !rest.is_nil() { @@ -295,10 +306,7 @@ impl Repl { let commitment = Commitment::new(Some(secret), payload, &self.store); let hash_str = &commitment.hash.hex_digits(); commitment.persist()?; - println!( - "Data: {}\nHash: 0x{hash_str}", - payload.fmt_to_string(&self.store, &self.state.borrow()) - ); + println!("Hash: 0x{hash_str}"); Ok(()) } @@ -337,8 +345,13 @@ impl Repl { } fn eval_expr(&mut self, expr_ptr: Ptr) -> Result<(Vec>, usize, Vec>)> { - let (ptrs, iterations, emitted) = - evaluate_simple::>(None, expr_ptr, &self.store, self.limit)?; + let (ptrs, iterations, emitted) = evaluate_simple_with_env::>( + None, + expr_ptr, + self.env, + &self.store, + self.limit, + )?; match ptrs[2].tag() { Tag::Cont(ContTag::Terminal) => Ok((ptrs, iterations, emitted)), t => { @@ -356,8 +369,13 @@ impl Repl { &mut self, expr_ptr: Ptr, ) -> Result<(Vec>, usize, Vec>)> { - let (ptrs, iterations, emitted) = - evaluate_simple::>(None, expr_ptr, &self.store, self.limit)?; + let (ptrs, iterations, emitted) = evaluate_simple_with_env::>( + None, + expr_ptr, + self.env, + &self.store, + self.limit, + )?; if matches!( ptrs[2].tag(), Tag::Cont(ContTag::Terminal) | Tag::Cont(ContTag::Error) @@ -373,13 +391,12 @@ impl Repl { fn eval_expr_and_memoize(&mut self, expr_ptr: Ptr) -> Result<(Vec>, usize)> { let (frames, iterations) = - evaluate::>(None, expr_ptr, &self.store, self.limit)?; + evaluate_with_env::>(None, expr_ptr, self.env, &self.store, self.limit)?; let output = frames[frames.len() - 1].output.clone(); self.evaluation = Some(Evaluation { frames, iterations }); Ok((output, iterations)) } - // #[allow(dead_code)] fn get_comm_hash(&mut self, cmd: &str, args: &Ptr) -> Result { let first = self.peek1(cmd, args)?; let num = self.store.intern_lurk_symbol("num"); @@ -393,7 +410,7 @@ impl Repl { Ok(hash) } - fn handle_non_meta(&mut self, expr_ptr: Ptr) -> Result<()> { + pub fn handle_non_meta(&mut self, expr_ptr: Ptr) -> Result<()> { self.eval_expr_and_memoize(expr_ptr) .map(|(output, iterations)| { let iterations_display = Self::pretty_iterations_display(iterations); diff --git a/src/cli/repl/meta_cmd.rs b/src/cli/repl/meta_cmd.rs index 920e4e97ad..e49d97a78a 100644 --- a/src/cli/repl/meta_cmd.rs +++ b/src/cli/repl/meta_cmd.rs @@ -6,7 +6,7 @@ use serde::de::DeserializeOwned; use std::process; use crate::{ - cli::lurk_proof::LurkProof, + cli::lurk_proof::{LurkProof, LurkProofMeta}, eval::lang::Coproc, field::LurkField, lem::{multiframe::MultiFrame, pointers::Ptr, Tag}, @@ -31,20 +31,19 @@ type F = pasta_curves::pallas::Scalar; // TODO: generalize this impl MetaCmd { const LOAD: MetaCmd = MetaCmd { name: "load", - summary: "Load lurk expressions from a file path.", + summary: "Load lurk expressions from a file.", format: "!(load )", description: &[], - example: &["Load lurk expressions from a file path."], + example: &["!(load \"my_file.lurk\")"], run: |repl, cmd, args| { let first = repl.peek1(cmd, args)?; match repl.store.fetch_string(&first) { Some(path) => { let joined = repl.pwd_path.join(Utf8Path::new(&path)); - repl.load_file(&joined, false)? + repl.load_file(&joined, false) } _ => bail!("Argument of `load` must be a string."), } - Ok(()) }, }; } @@ -236,8 +235,7 @@ impl MetaCmd { run: |repl, cmd, args| { let first = repl.peek1(cmd, args)?; let (first_io, ..) = repl.eval_expr(first)?; - repl.hide(ff::Field::ZERO, first_io[0])?; - Ok(()) + repl.hide(F::NON_HIDING_COMMITMENT_SECRET, first_io[0]) } }; } @@ -285,8 +283,7 @@ impl MetaCmd { ], run: |repl, cmd, args| { let hash = repl.get_comm_hash(cmd, args)?; - repl.fetch(&hash, false)?; - Ok(()) + repl.fetch(&hash, false) }, }; } @@ -303,8 +300,7 @@ impl MetaCmd { ], run: |repl, cmd, args| { let hash = repl.get_comm_hash(cmd, args)?; - repl.fetch(&hash, true)?; - Ok(()) + repl.fetch(&hash, true) }, }; } @@ -359,8 +355,7 @@ impl MetaCmd { if !args.is_nil() { repl.eval_expr_and_memoize(repl.peek1(cmd, args)?)?; } - repl.prove_last_frames()?; - Ok(()) + repl.prove_last_frames() } }; } @@ -374,15 +369,10 @@ where ::CK2: Sync + Send, { const VERIFY: MetaCmd = MetaCmd { - name: - "verify", - summary: - "Verify a proof", - format: - "!(verify )", - description: &[ - "Verify proof id and print the result.", - ], + name: "verify", + summary: "Verify a proof", + format: "!(verify )", + description: &["Verify proof key and print the result."], example: &[ "!(prove '(1 2 3))", "!(verify \"Nova_Pallas_10_166fafef9d86d1ddd29e7b62fa5e4fb2d7f4d885baf28e23187860d0720f74ca\")", @@ -393,8 +383,7 @@ where let proof_id = repl.get_string(&first)?; LurkProof::<_, _, MultiFrame<'_, _, Coproc>>::verify_proof( &proof_id, - )?; - Ok(()) + ) } }; } @@ -474,20 +463,19 @@ impl MetaCmd { Tag::Expr(ExprTag::Str) => { let name = repl.get_string(&first)?; let package_name = repl.state.borrow_mut().intern(name); - repl.state.borrow_mut().set_current_package(package_name)?; + repl.state.borrow_mut().set_current_package(package_name) } Tag::Expr(ExprTag::Sym) => { let package_name = repl.get_symbol(&first)?; repl.state .borrow_mut() - .set_current_package(package_name.into())?; + .set_current_package(package_name.into()) } _ => bail!( "Expected string or symbol. Got {}", first.fmt_to_string(&repl.store, &repl.state.borrow()) ), } - Ok(()) }, }; } @@ -496,7 +484,7 @@ impl MetaCmd { const HELP: MetaCmd = MetaCmd { name: "help", summary: "Print help message.", - format: "!(help [|])", + format: "!(help )", description: &[ "Without arguments it prints a summary of all available commands.", "Otherwise the full help for the command in the first argument is printed.", @@ -548,7 +536,124 @@ impl MetaCmd { } impl MetaCmd { - const CMDS: [MetaCmd; 19] = [ + fn call(repl: &mut Repl, cmd: &str, args: &Ptr) -> Result<()> { + let (hash_ptr, args) = repl.store.car_cdr(args)?; + let hash_expr = match hash_ptr.tag() { + Tag::Expr(ExprTag::Cons) => hash_ptr, + _ => repl.store.list(vec![hash_ptr]), + }; + let hash = repl.get_comm_hash(cmd, &hash_expr)?; + if repl.store.open(hash).is_none() { + repl.fetch(&hash, false)?; + } + let open = repl.store.intern_lurk_symbol("open"); + let open_expr = repl.store.list(vec![open, Ptr::num(hash)]); + let (args_vec, _) = repl + .store + .fetch_list(&args) + .expect("data must have been interned"); + let mut expr_vec = Vec::with_capacity(args_vec.len() + 1); + expr_vec.push(open_expr); + expr_vec.extend(args_vec); + repl.handle_non_meta(repl.store.list(expr_vec)) + } + + const CALL: MetaCmd = MetaCmd { + name: "call", + summary: "Open a functional commitment then apply arguments to it", + format: "!(call )", + description: &[], + example: &[ + "(commit (lambda (x) x))", + "!(call 0x39a14e7823d7af7275e83f0cb74f80ca4217c6c6930761b0bbd6879b123dbbc2 0)", + ], + run: Self::call, + }; +} + +impl MetaCmd { + const CHAIN: MetaCmd = MetaCmd { + name: "chain", + summary: "Chain a functional commitment by applying the provided arguments to it", + format: "!(chain )", + description: &[ + "Chain a functional commitment by applying the provided arguments to it.", + "The chained function must return a pair whose first component is the actual result", + " and the second is a commitment to the next function", + ], + example: &[ + "!(commit (letrec ((add (lambda (counter x) + (let ((counter (+ counter x))) + (cons counter (commit (add counter))))))) + (add 0)))", + "!(chain 0x06042852d90bf409974d1ee3bc153c0f48ea5512c9b4f697561df9ad7b5abbe0 1)", + ], + run: |repl: &mut Repl, cmd: &str, args: &Ptr| { + Self::call(repl, cmd, args)?; + let ev = repl + .get_evaluation() + .as_ref() + .expect("evaluation must have been set"); + let result = ev + .get_result() + .expect("evaluation result must have been set"); + let (_, comm) = repl.store.car_cdr(result)?; + let Ptr::Atom(Tag::Expr(ExprTag::Comm), hash) = comm else { + bail!("Second component of a chain must be a commitment") + }; + let (_, fun) = repl + .store + .open(hash) + .expect("data must have been committed"); + repl.hide(F::NON_HIDING_COMMITMENT_SECRET, *fun) + }, + }; +} + +impl MetaCmd { + fn inspect(repl: &mut Repl, cmd: &str, args: &Ptr, full: bool) -> Result<()> { + let first = repl.peek1(cmd, args)?; + let proof_id = repl.get_string(&first)?; + LurkProofMeta::::inspect_proof( + &proof_id, + Some((&repl.store, &repl.state.borrow())), + full, + ) + } + + const INSPECT: MetaCmd = MetaCmd { + name: "inspect", + summary: "Print part of a proof claim", + format: "!(inspect )", + description: &[], + example: &[ + "!(prove '(1 2 3))", + "!(inspect \"Nova_Pallas_10_166fafef9d86d1ddd29e7b62fa5e4fb2d7f4d885baf28e23187860d0720f74ca\")", + ], + run: |repl, cmd, args| { + Self::inspect(repl, cmd, args, false) + } + }; +} + +impl MetaCmd { + const INSPECT_FULL: MetaCmd = MetaCmd { + name: "inspect-full", + summary: "Print a proof claim", + format: "!(inspect-full )", + description: &[], + example: &[ + "!(prove '(1 2 3))", + "!(inspect-full \"Nova_Pallas_10_166fafef9d86d1ddd29e7b62fa5e4fb2d7f4d885baf28e23187860d0720f74ca\")", + ], + run: |repl, cmd, args| { + Self::inspect(repl, cmd, args, true) + } + }; +} + +impl MetaCmd { + const CMDS: [MetaCmd; 23] = [ MetaCmd::LOAD, MetaCmd::DEF, MetaCmd::DEFREC, @@ -568,6 +673,10 @@ impl MetaCmd { MetaCmd::IMPORT, MetaCmd::IN_PACKAGE, MetaCmd::HELP, + MetaCmd::CALL, + MetaCmd::CHAIN, + MetaCmd::INSPECT, + MetaCmd::INSPECT_FULL, ]; pub(super) fn cmds() -> std::collections::HashMap<&'static str, MetaCmd> { diff --git a/src/lem/eval.rs b/src/lem/eval.rs index c7651152e4..00ff74f303 100644 --- a/src/lem/eval.rs +++ b/src/lem/eval.rs @@ -183,6 +183,18 @@ pub fn evaluate_with_env_and_cont>( } } +#[inline] +pub fn evaluate_with_env>( + func_lang: Option<(&Func, &Lang)>, + expr: Ptr, + env: Ptr, + store: &Store, + limit: usize, +) -> Result<(Vec>, usize)> { + evaluate_with_env_and_cont(func_lang, expr, env, store.cont_outermost(), store, limit) +} + +#[inline] pub fn evaluate>( func_lang: Option<(&Func, &Lang)>, expr: Ptr, @@ -199,13 +211,14 @@ pub fn evaluate>( ) } -pub fn evaluate_simple>( +pub fn evaluate_simple_with_env>( func_lang: Option<(&Func, &Lang)>, expr: Ptr, + env: Ptr, store: &Store, limit: usize, ) -> Result<(Vec>, usize, Vec>)> { - let input = vec![expr, store.intern_nil(), store.cont_outermost()]; + let input = vec![expr, env, store.cont_outermost()]; match func_lang { None => { let lang: Lang = Lang::new(); @@ -218,6 +231,16 @@ pub fn evaluate_simple>( } } +#[inline] +pub fn evaluate_simple>( + func_lang: Option<(&Func, &Lang)>, + expr: Ptr, + store: &Store, + limit: usize, +) -> Result<(Vec>, usize, Vec>)> { + evaluate_simple_with_env(func_lang, expr, store.intern_nil(), store, limit) +} + /// Creates a LEM `Func` corresponding to Lurk's step function from a `Lang`. /// The `ivc` flag tells whether the generated step function is used for IVC or /// NIVC. In the IVC case, the step function is capable of reducing calls to the diff --git a/src/lem/store.rs b/src/lem/store.rs index 52c6feb363..9513ebc6d3 100644 --- a/src/lem/store.rs +++ b/src/lem/store.rs @@ -470,7 +470,7 @@ impl Store { /// Fetches a cons list that was interned. If the list is improper, the second /// element of the returned pair will carry the improper terminating value - fn fetch_list(&self, ptr: &Ptr) -> Option<(Vec>, Option>)> { + pub fn fetch_list(&self, ptr: &Ptr) -> Option<(Vec>, Option>)> { match ptr { Ptr::Tuple2(Tag::Expr(Nil), _) => Some((vec![], None)), Ptr::Tuple2(Tag::Expr(Cons), mut idx) => { diff --git a/src/state.rs b/src/state.rs index 6e7e7fcf00..f7d64c4d3b 100644 --- a/src/state.rs +++ b/src/state.rs @@ -254,7 +254,7 @@ const LURK_PACKAGE_SYMBOLS_NAMES: [&str; 36] = [ "_", ]; -const META_PACKAGE_SYMBOLS_NAMES: [&str; 19] = [ +const META_PACKAGE_SYMBOLS_NAMES: [&str; 23] = [ "def", "defrec", "load", @@ -274,6 +274,10 @@ const META_PACKAGE_SYMBOLS_NAMES: [&str; 19] = [ "import", "in-package", "help", + "call", + "chain", + "inspect", + "inspect-full", ]; #[cfg(test)] diff --git a/tests/lurk-lib-tests.rs b/tests/lurk-lib-tests.rs index a3cdde9b44..39acac8ffa 100644 --- a/tests/lurk-lib-tests.rs +++ b/tests/lurk-lib-tests.rs @@ -43,6 +43,6 @@ fn lurk_cli_tests() { let joined_new = Utf8PathBuf::from_path_buf(joined.clone()).unwrap(); repl::>, _, Coproc>(Some(joined), Lang::new()).unwrap(); - let _ = repl_new.load_file(&joined_new, false); + repl_new.load_file(&joined_new, false).unwrap(); } }