diff --git a/zkvm/docs/zkvm-spec.md b/zkvm/docs/zkvm-spec.md index 7ecaa24ad..e10335855 100644 --- a/zkvm/docs/zkvm-spec.md +++ b/zkvm/docs/zkvm-spec.md @@ -842,7 +842,7 @@ Then, the VM executes the current program till completion: 1. Each instruction is read at the current program offset, including its immediate data (if any). 2. Program offset is advanced immediately after reading the instruction to the next instruction. 3. The instruction is executed per [specification below](#instructions). If the instruction fails, VM exits early with an error result. -4. If VM encounters [`call`](#call), [`signid`](#signid) or [`signtag`](#signtag) instruction, the new program with offset zero is set as the current program. The next iteration of the vm will start from the beginning of the new program. +4. If VM encounters [`eval`](#eval), [`call`](#call), [`signid`](#signid) or [`signtag`](#signtag) instruction, the new program with offset zero is set as the current program. The next iteration of the vm will start from the beginning of the new program. 5. If the offset is less than the current program’s length, a new instruction is read (go back to step 1). 6. Otherwise (reached the end of the current program): 1. If the program stack is not empty, pop top item from the program stack and set it to the current program. Go to step 5. @@ -965,10 +965,11 @@ Code | Instruction | Stack diagram | 0x1b | [`output:k`](#output) | _items... pred_ → ø | Modifies [tx log](#transaction-log) 0x1c | [`contract:k`](#contract) | _items... pred_ → _contract_ | 0x1d | [`log`](#log) | _data_ → ø | Modifies [tx log](#transaction-log) -0x1e | [`call`](#call) |_contract(P) proof prog_ → _results..._ | [Defers point operations](#deferred-point-operations) -0x1f | [`signtx`](#signtx) | _contract_ → _results..._ | Modifies [deferred verification keys](#transaction-signature) -0x20 | [`signid`](#signid) |_contract prog sig_ → _results..._ | [Defers point operations](#deferred-point-operations) -0x21 | [`signtag`](#signtag) |_contract prog sig_ → _results..._ | [Defers point operations](#deferred-point-operations) +0x1e | [`eval`](#eval) | _prog_ → _results..._ | +0x1f | [`call`](#call) |_contract(P) proof prog_ → _results..._ | [Defers point operations](#deferred-point-operations) +0x20 | [`signtx`](#signtx) | _contract_ → _results..._ | Modifies [deferred verification keys](#transaction-signature) +0x21 | [`signid`](#signid) |_contract prog sig_ → _results..._ | [Defers point operations](#deferred-point-operations) +0x22 | [`signtag`](#signtag) |_contract prog sig_ → _results..._ | [Defers point operations](#deferred-point-operations) — | [`ext`](#ext) | ø → ø | Fails if [extension flag](#vm-state) is not set. @@ -1409,6 +1410,16 @@ _data_ **log** → ø Fails if `data` is not a [string](#string-type). +#### eval + +_prog_ **eval** → _results..._ + +1. Pops [program](#program-type) `prog`. +2. Set the `prog` as current. + +Fails if `prog` is not a [program](#program-type). + + #### call _contract(P) proof prog_ **call** → _results..._ diff --git a/zkvm/src/debug.rs b/zkvm/src/debug.rs index febc2105d..5c0d2a38f 100644 --- a/zkvm/src/debug.rs +++ b/zkvm/src/debug.rs @@ -126,6 +126,7 @@ impl Instruction { Instruction::Output(k) => write!(f, "output:{}", k), Instruction::Contract(k) => write!(f, "contract:{}", k), Instruction::Log => write!(f, "log"), + Instruction::Eval => write!(f, "eval"), Instruction::Call => write!(f, "call"), Instruction::Signtx => write!(f, "signtx"), Instruction::Signid => write!(f, "signid"), diff --git a/zkvm/src/ops.rs b/zkvm/src/ops.rs index 1558ac655..c0b5022a2 100644 --- a/zkvm/src/ops.rs +++ b/zkvm/src/ops.rs @@ -387,6 +387,14 @@ pub enum Instruction { /// Fails if `data` is not a _string_. Log, + /// _prog_ **eval** → _results..._ + /// + /// 1. Pops _program_ `prog`. + /// 2. Set the `prog` as current. + /// + /// Fails if `prog` is not a _program_. + Eval, + /// _contract(P) proof prog_ **call** → _results..._ /// /// 1. Pops _program_ `prog`, the _call proof_ `proof`, and a _contract_ `contract`. @@ -561,17 +569,19 @@ pub enum Opcode { Contract = 0x1c, /// A code for [Instruction::Log] Log = 0x1d, + /// A code for [Instruction::Eval] + Eval = 0x1e, /// A code for [Instruction::Call] - Call = 0x1e, + Call = 0x1f, /// A code for [Instruction::Signtx] - Signtx = 0x1f, + Signtx = 0x20, /// A code for [Instruction::Signid] - Signid = 0x20, + Signid = 0x21, /// A code for [Instruction::Signtag] Signtag = MAX_OPCODE, } -const MAX_OPCODE: u8 = 0x21; +const MAX_OPCODE: u8 = 0x22; impl Opcode { /// Converts the opcode to `u8`. @@ -650,6 +660,7 @@ impl Encodable for Instruction { w.write_u32(b"k", *k as u32)?; } Instruction::Log => write(Opcode::Log)?, + Instruction::Eval => write(Opcode::Eval)?, Instruction::Call => write(Opcode::Call)?, Instruction::Signtx => write(Opcode::Signtx)?, Instruction::Signid => write(Opcode::Signid)?, @@ -749,6 +760,7 @@ impl Instruction { Ok(Instruction::Contract(k)) } Opcode::Log => Ok(Instruction::Log), + Opcode::Eval => Ok(Instruction::Eval), Opcode::Call => Ok(Instruction::Call), Opcode::Signtx => Ok(Instruction::Signtx), Opcode::Signid => Ok(Instruction::Signid), diff --git a/zkvm/src/program.rs b/zkvm/src/program.rs index 88a93eb92..f8a2f55d2 100644 --- a/zkvm/src/program.rs +++ b/zkvm/src/program.rs @@ -187,8 +187,8 @@ impl Program { def_op!(contract, Contract, usize, "contract:k"); def_op!(log, Log, "log"); + def_op!(eval, Eval, "eval"); def_op!(call, Call, "call"); - def_op!(signtx, Signtx, "signtx"); def_op!(signid, Signid, "signid"); def_op!(signtag, Signtag, "signtag"); diff --git a/zkvm/src/vm.rs b/zkvm/src/vm.rs index 1546f321f..de2603438 100644 --- a/zkvm/src/vm.rs +++ b/zkvm/src/vm.rs @@ -177,6 +177,7 @@ where Instruction::Output(k) => self.output(k)?, Instruction::Contract(k) => self.contract(k)?, Instruction::Log => self.log()?, + Instruction::Eval => self.eval()?, Instruction::Call => self.call()?, Instruction::Signtx => self.signtx()?, Instruction::Signid => self.signid()?, @@ -572,6 +573,15 @@ where Ok(()) } + fn eval(&mut self) -> Result<(), VMError> { + // Pop program + let program_item = self.pop_item()?.to_program()?; + + // Replace current program with new program + self.continue_with_program(program_item)?; + Ok(()) + } + fn call(&mut self) -> Result<(), VMError> { // Pop program, call proof, and contract let program_item = self.pop_item()?.to_program()?; diff --git a/zkvm/tests/zkvm.rs b/zkvm/tests/zkvm.rs index 6f6c424c5..8a2127228 100644 --- a/zkvm/tests/zkvm.rs +++ b/zkvm/tests/zkvm.rs @@ -572,6 +572,36 @@ fn constraints_cannot_be_copied() { ); } +#[test] +fn eval_test() { + let (pred, prv) = generate_predicate(); + let prog = Program::build(|p| { + p.push(String::from(Scalar::from(20u64))); + p.scalar(); + p.program(Program::build(|p2| { + p2.push(String::from(Scalar::from(1u64))); + p2.scalar(); + p2.program(Program::build(|p3| { + p3.add(); + })); + p2.eval(); + })); + p.eval(); + p.push(String::from(Scalar::from(21u64))); + p.scalar(); + p.eq(); + p.verify(); + + // to make program finish we need to spend a dummy input + p.input_helper(0, Scalar::zero(), pred.clone()); + p.output_helper(pred); + }); + + dbg!(&prog); + + build_and_verify(prog, &vec![prv]).expect("should succeed"); +} + #[test] fn borrow_output() { //inputs 10 units, borrows 5 units, outputs two (5 units)