From 588bde243dfc6346d842a0c284aaed60931e6c1c Mon Sep 17 00:00:00 2001 From: Oleg Andreev Date: Tue, 30 Mar 2021 02:37:09 +0300 Subject: [PATCH 1/6] paychan: new crate --- Cargo.toml | 1 + paychan/.gitignore | 3 +++ paychan/Cargo.toml | 38 ++++++++++++++++++++++++++++++++++++++ paychan/src/lib.rs | 27 ++++++++++++++++++--------- 4 files changed, 60 insertions(+), 9 deletions(-) create mode 100644 paychan/.gitignore create mode 100644 paychan/Cargo.toml diff --git a/Cargo.toml b/Cargo.toml index 895dfb966..02859d030 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ members = [ "blockchain", "token", "accounts", + "paychan", "p2p", "node", ] diff --git a/paychan/.gitignore b/paychan/.gitignore new file mode 100644 index 000000000..693699042 --- /dev/null +++ b/paychan/.gitignore @@ -0,0 +1,3 @@ +/target +**/*.rs.bk +Cargo.lock diff --git a/paychan/Cargo.toml b/paychan/Cargo.toml new file mode 100644 index 000000000..e4b1e0be4 --- /dev/null +++ b/paychan/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "paychan" +version = "0.1.0" +authors = ["Oleg Andreev "] +edition = "2018" +readme = "README.md" +license = "Apache-2.0" +repository = "https://github.com/stellar/slingshot" +categories = ["cryptography", "blockchain"] +keywords = ["cryptography", "blockchain", "zero-knowledge", "bulletproofs"] +description = "A payment channel protocol for ZkVM" + +[dependencies] +thiserror = "1" +byteorder = "1" +merlin = "2" +rand = "0.7" +subtle = "2" +curve25519-dalek = { version = "3", features = ["serde"] } +serde = { version = "1.0", features=["derive"] } +subtle-encoding = "0.3" +hex = "^0.3" + +[dependencies.zkvm] +path = "../zkvm" + +[dependencies.starsig] +path = "../starsig" + +[dependencies.musig] +path = "../musig" + +[dependencies.readerwriter] +path = "../readerwriter" + +[dev-dependencies] +criterion = "0.2" +serde_json = "1.0" diff --git a/paychan/src/lib.rs b/paychan/src/lib.rs index ebc4c8527..194c0ebe0 100644 --- a/paychan/src/lib.rs +++ b/paychan/src/lib.rs @@ -10,12 +10,16 @@ fn blinded_value(v: ClearValue) -> Value { } } -// Creates program: ` commit commit borrow => -V +V` -fn borrow_value(p: &mut Program, v: Value) { + // Creates program: ` commit commit borrow => -V +V` + fn borrow_value(p: &mut Program, v: Value) { p.push(v.qty).commit().push(v.flv).commit().borrow(); } -fn output_value(p: &mut Program, value: Value, pred: Predicate) { +fn output_value( + p: &mut Program, + v: Value, + pred: Predicate, +) { // qty commit flv commit borrow => -v +v borrow_value(p, v); p.push(zkvm::String::Predicate(Box::new(pred))); @@ -25,7 +29,7 @@ fn output_value(p: &mut Program, value: Value, pred: Predicate) { // locks a list of values under a predicate, and leaves negative values on stack. fn output_multiple_values( p: &mut Program, - values: impl ExactSizeIterator, + mut values: impl ExactSizeIterator, pred: Predicate, ) { let n = values.len(); @@ -109,7 +113,7 @@ fn test() { // } let exit_predicate = Predicate::tree( PredicateTree::new( - Some(alice_bob_joined.clone()), + Some(Predicate::new(alice_bob_joined.clone())), vec![zkvm::Program::build(|p| { // stack: assets, seq, timeout, prog, tag // timeout is checked as a range proof @@ -146,7 +150,7 @@ fn test() { // } let initial_predicate = Predicate::tree( PredicateTree::new( - Some(alice_bob_joined.clone()), + Some(Predicate::new(alice_bob_joined.clone())), vec![zkvm::Program::build(|p| { // This is a simple adaptor contract that prepares a good format for applying // the pre-signed exit program. @@ -155,7 +159,7 @@ fn test() { u64::max_value(), )))) .push(zkvm::String::Opaque(Program::new().to_bytes())) - .push(zkvm::String::Opaque(channel_tag)) + .push(zkvm::String::Opaque(channel_tag.clone())) .push(zkvm::String::Predicate(Box::new(exit_predicate.clone()))) .contract(assets_count + 1 + 1 + 1 + 1); })], @@ -188,8 +192,8 @@ fn test() { let initial_exit = Program::build(|p| { // stack: assets, seq, timeout, prog, tag p.dup(3); // copy the seq from the stack - // TBD: check the sequence, update timeout and redistribution program - // TBD: tx.maxtime must be constrained close to mintime so we don't allow locking up resolution too far in the future. + // TBD: check the sequence, update timeout and redistribution program + // TBD: tx.maxtime must be constrained close to mintime so we don't allow locking up resolution too far in the future. }); // Produce a signature for the initial distribution @@ -197,6 +201,9 @@ fn test() { let mut t = Transcript::new(b"ZkVM.signtag"); t.append_message(b"tag", &channel_tag[..]); t.append_message(b"prog", &initial_exit.to_bytes()); + + // FIXME: this does not accurately emulate two-party interaction. + // See musig APIs for a proper MPC protocol where keys are not shared. let initial_exit_signature = Signature::sign( &mut t, Multikey::aggregated_signing_key(&vec![alice_prv, bob_prv]), @@ -205,6 +212,8 @@ fn test() { // Step 2: Alice and Bob co-sign a tx that locks up their funds in the initial contract. // In this example we just cook up a serialized contract that we'll be spending. + + // Step 3: Alice and Bob co-sign distribution program and exchange funds indefinitely // Case A: Alice and Bob co-sign exit from the channel From d9c41a7196fdaf8457b8a6e110177da0d5487ccf Mon Sep 17 00:00:00 2001 From: Oleg Andreev Date: Tue, 30 Mar 2021 02:40:01 +0300 Subject: [PATCH 2/6] fmt --- paychan/src/lib.rs | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/paychan/src/lib.rs b/paychan/src/lib.rs index 194c0ebe0..496128b4e 100644 --- a/paychan/src/lib.rs +++ b/paychan/src/lib.rs @@ -10,16 +10,20 @@ fn blinded_value(v: ClearValue) -> Value { } } - // Creates program: ` commit commit borrow => -V +V` - fn borrow_value(p: &mut Program, v: Value) { +// Creates program: ` commit commit borrow => -V +V` +fn borrow_value(p: &mut Program, v: Value) { p.push(v.qty).commit().push(v.flv).commit().borrow(); } +<<<<<<< HEAD fn output_value( p: &mut Program, v: Value, pred: Predicate, ) { +======= +fn output_value(p: &mut Program, value: Value, pred: Predicate) { +>>>>>>> 58643e7 (fmt) // qty commit flv commit borrow => -v +v borrow_value(p, v); p.push(zkvm::String::Predicate(Box::new(pred))); @@ -173,12 +177,20 @@ fn test() { output_multiple_values( p, alice_original_balances.into_iter(), +<<<<<<< HEAD Predicate::new(alice.clone()), +======= + Predicate::Key(alice.clone()), +>>>>>>> 58643e7 (fmt) ); output_multiple_values( p, bob_original_balances.into_iter(), +<<<<<<< HEAD Predicate::new(bob.clone()), +======= + Predicate::Key(bob.clone()), +>>>>>>> 58643e7 (fmt) ); }); @@ -192,8 +204,8 @@ fn test() { let initial_exit = Program::build(|p| { // stack: assets, seq, timeout, prog, tag p.dup(3); // copy the seq from the stack - // TBD: check the sequence, update timeout and redistribution program - // TBD: tx.maxtime must be constrained close to mintime so we don't allow locking up resolution too far in the future. + // TBD: check the sequence, update timeout and redistribution program + // TBD: tx.maxtime must be constrained close to mintime so we don't allow locking up resolution too far in the future. }); // Produce a signature for the initial distribution @@ -201,9 +213,12 @@ fn test() { let mut t = Transcript::new(b"ZkVM.signtag"); t.append_message(b"tag", &channel_tag[..]); t.append_message(b"prog", &initial_exit.to_bytes()); +<<<<<<< HEAD // FIXME: this does not accurately emulate two-party interaction. // See musig APIs for a proper MPC protocol where keys are not shared. +======= +>>>>>>> 58643e7 (fmt) let initial_exit_signature = Signature::sign( &mut t, Multikey::aggregated_signing_key(&vec![alice_prv, bob_prv]), @@ -212,10 +227,15 @@ fn test() { // Step 2: Alice and Bob co-sign a tx that locks up their funds in the initial contract. // In this example we just cook up a serialized contract that we'll be spending. +<<<<<<< HEAD // Step 3: Alice and Bob co-sign distribution program and exchange funds indefinitely +======= + // Step 3: Alice and Bob co-sign distribution program and exchange funds indefinitely + +>>>>>>> 58643e7 (fmt) // Case A: Alice and Bob co-sign exit from the channel // Case B: Alice closes channel with the latest version. From 91082530069598b012ba4933f8f460f500e68768 Mon Sep 17 00:00:00 2001 From: Oleg Andreev Date: Fri, 2 Apr 2021 12:32:11 +0300 Subject: [PATCH 3/6] cleanup rebase markers --- paychan/src/lib.rs | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/paychan/src/lib.rs b/paychan/src/lib.rs index 496128b4e..e642a0a9d 100644 --- a/paychan/src/lib.rs +++ b/paychan/src/lib.rs @@ -15,15 +15,11 @@ fn borrow_value(p: &mut Program, v: Value) { p.push(v.qty).commit().push(v.flv).commit().borrow(); } -<<<<<<< HEAD fn output_value( p: &mut Program, v: Value, pred: Predicate, ) { -======= -fn output_value(p: &mut Program, value: Value, pred: Predicate) { ->>>>>>> 58643e7 (fmt) // qty commit flv commit borrow => -v +v borrow_value(p, v); p.push(zkvm::String::Predicate(Box::new(pred))); @@ -177,20 +173,12 @@ fn test() { output_multiple_values( p, alice_original_balances.into_iter(), -<<<<<<< HEAD Predicate::new(alice.clone()), -======= - Predicate::Key(alice.clone()), ->>>>>>> 58643e7 (fmt) ); output_multiple_values( p, bob_original_balances.into_iter(), -<<<<<<< HEAD Predicate::new(bob.clone()), -======= - Predicate::Key(bob.clone()), ->>>>>>> 58643e7 (fmt) ); }); @@ -213,12 +201,8 @@ fn test() { let mut t = Transcript::new(b"ZkVM.signtag"); t.append_message(b"tag", &channel_tag[..]); t.append_message(b"prog", &initial_exit.to_bytes()); -<<<<<<< HEAD - // FIXME: this does not accurately emulate two-party interaction. // See musig APIs for a proper MPC protocol where keys are not shared. -======= ->>>>>>> 58643e7 (fmt) let initial_exit_signature = Signature::sign( &mut t, Multikey::aggregated_signing_key(&vec![alice_prv, bob_prv]), @@ -227,15 +211,9 @@ fn test() { // Step 2: Alice and Bob co-sign a tx that locks up their funds in the initial contract. // In this example we just cook up a serialized contract that we'll be spending. -<<<<<<< HEAD - - - // Step 3: Alice and Bob co-sign distribution program and exchange funds indefinitely -======= // Step 3: Alice and Bob co-sign distribution program and exchange funds indefinitely ->>>>>>> 58643e7 (fmt) // Case A: Alice and Bob co-sign exit from the channel // Case B: Alice closes channel with the latest version. From 8a83949a06844b27c84b0f90df656f73d933710a Mon Sep 17 00:00:00 2001 From: Oleg Andreev Date: Fri, 2 Apr 2021 17:19:21 +0300 Subject: [PATCH 4/6] cargo fmt and test fix for paychan --- paychan/src/lib.rs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/paychan/src/lib.rs b/paychan/src/lib.rs index e642a0a9d..204fb3633 100644 --- a/paychan/src/lib.rs +++ b/paychan/src/lib.rs @@ -15,11 +15,7 @@ fn borrow_value(p: &mut Program, v: Value) { p.push(v.qty).commit().push(v.flv).commit().borrow(); } -fn output_value( - p: &mut Program, - v: Value, - pred: Predicate, -) { +fn output_value(p: &mut Program, v: Value, pred: Predicate) { // qty commit flv commit borrow => -v +v borrow_value(p, v); p.push(zkvm::String::Predicate(Box::new(pred))); @@ -211,7 +207,6 @@ fn test() { // Step 2: Alice and Bob co-sign a tx that locks up their funds in the initial contract. // In this example we just cook up a serialized contract that we'll be spending. - // Step 3: Alice and Bob co-sign distribution program and exchange funds indefinitely // Case A: Alice and Bob co-sign exit from the channel @@ -221,6 +216,4 @@ fn test() { // Case C: Alice detects that channel was closed by Bob and updates her wallet state. // Case D: Alice detects that channel was closed by Bob with a stale update and sends out a newer version. - - assert_eq!("a", "b"); } From 26ae4e9c07dc19f5c9c4f6effd3b8c4056dca4bd Mon Sep 17 00:00:00 2001 From: Oleg Andreev Date: Sat, 3 Apr 2021 01:36:18 +0300 Subject: [PATCH 5/6] wip on dsl --- zkvm/src/dsl.rs | 199 ++++++++++++++++++++++++++++++++++++++++++++++++ zkvm/src/lib.rs | 1 + 2 files changed, 200 insertions(+) create mode 100644 zkvm/src/dsl.rs diff --git a/zkvm/src/dsl.rs b/zkvm/src/dsl.rs new file mode 100644 index 000000000..ad3a926d8 --- /dev/null +++ b/zkvm/src/dsl.rs @@ -0,0 +1,199 @@ +//! Type-safe program builder + +use crate::{Program, ProgramItem, String}; + +// What we want: ".dup::()" is available on a state that has N+1 items, expands state and records the bytecode. + +/// Value type that is encoded as String type on VM stack +pub trait StringArgument { + /// Encodes the abstract argument into a VM String + fn to_string(&self) -> String; +} + +/// Value type that can be copied in the VM +pub trait CopyableArgument { + /// Produces a copy of the argument + fn copy(&self) -> Self; +} + +/// Value type that is encoded as Program type on VM stack +pub trait ProgramArgument { + /// Encodes the abstract argument into a VM ProgramItem + fn to_program(&self) -> ProgramItem; +} + +/// Represents a state of the machine. +pub struct MachineState { + /// Value at the top of the stack + value: T, + + /// The rest of the stack + prev: S, +} + +/// Represents a state of the machine alongside with a program that produces it. +pub struct ProgramState { + /// Beginning state of the VM to which the program is applied + base_state: S0, + + /// Final state produced by the program + state: S, + + /// Program that brings the VM state from S0 to S. + program: Program, +} + +impl ProgramState { + /// Creates an empty program + pub fn new() -> ProgramState<(), ()> { + ProgramState { + base_state: (), + state: (), + program: Program::new(), + } + } + + /// Creates an empty program + pub fn new1(v1: T1) -> ProgramState, MachineState<(), T1>> { + ProgramState { + base_state: MachineState { + value: v1.clone(), + prev: (), + }, + state: MachineState { + value: v1, + prev: (), + }, + program: Program::new(), + } + } + + /// Pushes a string argument. + pub fn push(self, value: T) -> ProgramState> { + let mut program = self.program; + program.push(value.to_string()); + ProgramState { + base_state: self.base_state, + state: MachineState { + value, + prev: self.state, + }, + program, + } + } + + /// Pushes a program argument. + pub fn program(self, value: T) -> ProgramState> { + let mut program = self.program; + program.program(value.to_program()); + ProgramState { + base_state: self.base_state, + state: MachineState { + value, + prev: self.state, + }, + program, + } + } +} + +// For `eval` we need to have top item on the stack such that +// it's a ProgramState whose S0 is equal to the prev state. +impl ProgramState>> { + /// ^ ^ + /// |_______________| + /// | + /// We require program on stack to be bound + /// to the same state S as the stack it's on. + /// This guarantees that the number and types of arguments expected + /// by the inner program are the same as produced by the outer program + /// before the "push prog, eval" instructions are added. + + /// Executes a program that extends the current state S into state X + pub fn eval(self) -> ProgramState { + let mut program = self.program; + program.eval(); + let evaled_program = self.state.value; + ProgramState { + base_state: self.base_state, + state: evaled_program.state, // outer program's new state is the same as produced by inner program + program, + } + } +} + +// For `output` and `contract` instructions +// we want + +/// roll:0 is no-op - it simply puts the top item back on top +impl ProgramState> { + /// Implements `roll:0` instruction. + pub fn roll_0(self) -> ProgramState> { + let mut program = self.program; + program.roll(0); + ProgramState { + base_state: self.base_state, + state: MachineState { + value: self.state.value, + prev: self.state.prev, + }, + program, + } + } + + /// Implements `dup:0` instruction. + pub fn dup_0(self) -> ProgramState, T>> + where + T: CopyableArgument, + { + let mut program = self.program; + program.dup(0); + let value = self.state.value.copy(); + ProgramState { + base_state: self.base_state, + state: MachineState { + value, + prev: self.state, + }, + program, + } + } +} + +/// roll:1 is a swap of two top items. This means we need a state to have at least two items. +impl ProgramState, T0>> { + /// Implements `roll:1` instruction. + pub fn roll_1(self) -> ProgramState, T1>> { + let mut program = self.program; + program.roll(1); + ProgramState { + base_state: self.base_state, + state: MachineState { + value: self.state.prev.value, + prev: MachineState { + value: self.state.value, + prev: self.state.prev.prev, + }, + }, + program, + } + } + + /// Implements `dup:1` instruction. + pub fn dup_1(self) -> ProgramState, T0>, T1>> + where + T1: CopyableArgument, + { + let mut program = self.program; + program.dup(1); + let value = self.state.prev.value.copy(); + ProgramState { + base_state: self.base_state, + state: MachineState { + value, + prev: self.state, + }, + program, + } + } +} diff --git a/zkvm/src/lib.rs b/zkvm/src/lib.rs index 18a8bf77f..96c3fe341 100644 --- a/zkvm/src/lib.rs +++ b/zkvm/src/lib.rs @@ -17,6 +17,7 @@ mod serialization; mod constraints; mod contract; mod debug; +pub mod dsl; pub mod encoding; mod errors; mod fees; From af63dfa4299b769211a77885339374aec92b04be Mon Sep 17 00:00:00 2001 From: Oleg Andreev Date: Sat, 3 Apr 2021 02:02:42 +0300 Subject: [PATCH 6/6] wip --- paychan/src/lib.rs | 19 ++++++-- zkvm/src/dsl.rs | 116 ++++++++++++++++++--------------------------- 2 files changed, 62 insertions(+), 73 deletions(-) diff --git a/paychan/src/lib.rs b/paychan/src/lib.rs index 204fb3633..b6e8561f4 100644 --- a/paychan/src/lib.rs +++ b/paychan/src/lib.rs @@ -186,10 +186,23 @@ fn test() { // lock(self, P_exit) // } let initial_exit = Program::build(|p| { + // 4.. 3 2 1 0 // stack: assets, seq, timeout, prog, tag - p.dup(3); // copy the seq from the stack - // TBD: check the sequence, update timeout and redistribution program - // TBD: tx.maxtime must be constrained close to mintime so we don't allow locking up resolution too far in the future. + p.dup(3) // copy the seq from the stack + .neg() // -seq + .push(zkvm::String::Commitment(Box::new(Commitment::blinded(1)))) + .commit() + .add() // curr - seq + .range(); + + // TBD: update timeout + // TBD: update redistribution program + // TBD: tx.maxtime must be constrained close to mintime + // so we don't allow locking up funds too far in the future. + // timeout = tx.maxtime + T + // tx.maxtime < tx.mintime + Offset + // => tx.mintime + Offset - tx.maxtime > 0 + // => }); // Produce a signature for the initial distribution diff --git a/zkvm/src/dsl.rs b/zkvm/src/dsl.rs index ad3a926d8..a0c066896 100644 --- a/zkvm/src/dsl.rs +++ b/zkvm/src/dsl.rs @@ -22,15 +22,6 @@ pub trait ProgramArgument { fn to_program(&self) -> ProgramItem; } -/// Represents a state of the machine. -pub struct MachineState { - /// Value at the top of the stack - value: T, - - /// The rest of the stack - prev: S, -} - /// Represents a state of the machine alongside with a program that produces it. pub struct ProgramState { /// Beginning state of the VM to which the program is applied @@ -53,45 +44,45 @@ impl ProgramState { } } - /// Creates an empty program - pub fn new1(v1: T1) -> ProgramState, MachineState<(), T1>> { + /// Creates an empty program with one-value base state + pub fn new1(v1: T1) -> ProgramState<((), T1), ((), T1)> { ProgramState { - base_state: MachineState { - value: v1.clone(), - prev: (), - }, - state: MachineState { - value: v1, - prev: (), - }, + base_state: ((), v1.clone()), + state: ((), v1), + program: Program::new(), + } + } + + /// Creates an empty program with two-values base state + pub fn new2( + v1: T1, + v2: T2, + ) -> ProgramState<(((), T1), T2), (((), T1), T2)> { + ProgramState { + base_state: (((), v1.clone()), v2.clone()), + state: (((), v1), v2), program: Program::new(), } } /// Pushes a string argument. - pub fn push(self, value: T) -> ProgramState> { + pub fn push(self, value: T) -> ProgramState { let mut program = self.program; program.push(value.to_string()); ProgramState { base_state: self.base_state, - state: MachineState { - value, - prev: self.state, - }, + state: (self.state, value), program, } } /// Pushes a program argument. - pub fn program(self, value: T) -> ProgramState> { + pub fn program(self, value: T) -> ProgramState { let mut program = self.program; program.program(value.to_program()); ProgramState { base_state: self.base_state, - state: MachineState { - value, - prev: self.state, - }, + state: (self.state, value), program, } } @@ -99,25 +90,25 @@ impl ProgramState { // For `eval` we need to have top item on the stack such that // it's a ProgramState whose S0 is equal to the prev state. -impl ProgramState>> { - /// ^ ^ - /// |_______________| - /// | - /// We require program on stack to be bound - /// to the same state S as the stack it's on. - /// This guarantees that the number and types of arguments expected - /// by the inner program are the same as produced by the outer program - /// before the "push prog, eval" instructions are added. +impl ProgramState)> { + // ^ ^ + // |_______________| + // | + // We require program on stack to be bound + // to the same state S as the stack it's on. + // This guarantees that the number and types of arguments expected + // by the inner program are the same as produced by the outer program + // before the "push prog, eval" instructions are added. /// Executes a program that extends the current state S into state X pub fn eval(self) -> ProgramState { - let mut program = self.program; - program.eval(); - let evaled_program = self.state.value; + let mut outer_program = self.program; + outer_program.eval(); + let inner_program = self.state.1; ProgramState { base_state: self.base_state, - state: evaled_program.state, // outer program's new state is the same as produced by inner program - program, + state: inner_program.state, + program: outer_program, } } } @@ -126,73 +117,58 @@ impl ProgramState>> { // we want /// roll:0 is no-op - it simply puts the top item back on top -impl ProgramState> { +impl ProgramState { /// Implements `roll:0` instruction. - pub fn roll_0(self) -> ProgramState> { + pub fn roll_0(self) -> ProgramState { let mut program = self.program; program.roll(0); ProgramState { base_state: self.base_state, - state: MachineState { - value: self.state.value, - prev: self.state.prev, - }, + state: self.state, program, } } /// Implements `dup:0` instruction. - pub fn dup_0(self) -> ProgramState, T>> + pub fn dup_0(self) -> ProgramState where T: CopyableArgument, { let mut program = self.program; program.dup(0); - let value = self.state.value.copy(); + let value = self.state.1.copy(); ProgramState { base_state: self.base_state, - state: MachineState { - value, - prev: self.state, - }, + state: (self.state, value), program, } } } /// roll:1 is a swap of two top items. This means we need a state to have at least two items. -impl ProgramState, T0>> { +impl ProgramState { /// Implements `roll:1` instruction. - pub fn roll_1(self) -> ProgramState, T1>> { + pub fn roll_1(self) -> ProgramState { let mut program = self.program; program.roll(1); ProgramState { base_state: self.base_state, - state: MachineState { - value: self.state.prev.value, - prev: MachineState { - value: self.state.value, - prev: self.state.prev.prev, - }, - }, + state: ((self.state.0 .0, self.state.1), self.state.0 .1), program, } } /// Implements `dup:1` instruction. - pub fn dup_1(self) -> ProgramState, T0>, T1>> + pub fn dup_1(self) -> ProgramState where T1: CopyableArgument, { let mut program = self.program; program.dup(1); - let value = self.state.prev.value.copy(); + let value = self.state.0 .1.copy(); ProgramState { base_state: self.base_state, - state: MachineState { - value, - prev: self.state, - }, + state: (self.state, value), program, } }