diff --git a/Cargo.toml b/Cargo.toml index 895dfb96..02859d03 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 00000000..69369904 --- /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 00000000..e4b1e0be --- /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 ebc4c852..b6e8561f 100644 --- a/paychan/src/lib.rs +++ b/paychan/src/lib.rs @@ -15,7 +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, 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 +25,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 +109,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 +146,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 +155,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); })], @@ -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 @@ -197,6 +210,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()); + // 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]), @@ -214,6 +229,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"); } diff --git a/zkvm/src/dsl.rs b/zkvm/src/dsl.rs new file mode 100644 index 00000000..a0c06689 --- /dev/null +++ b/zkvm/src/dsl.rs @@ -0,0 +1,175 @@ +//! 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 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 with one-value base state + pub fn new1(v1: T1) -> ProgramState<((), T1), ((), T1)> { + ProgramState { + 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 { + let mut program = self.program; + program.push(value.to_string()); + ProgramState { + base_state: self.base_state, + state: (self.state, value), + 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: (self.state, value), + 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 outer_program = self.program; + outer_program.eval(); + let inner_program = self.state.1; + ProgramState { + base_state: self.base_state, + state: inner_program.state, + program: outer_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: self.state, + program, + } + } + + /// Implements `dup:0` instruction. + pub fn dup_0(self) -> ProgramState + where + T: CopyableArgument, + { + let mut program = self.program; + program.dup(0); + let value = self.state.1.copy(); + ProgramState { + base_state: self.base_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 { + /// Implements `roll:1` instruction. + pub fn roll_1(self) -> ProgramState { + let mut program = self.program; + program.roll(1); + ProgramState { + base_state: self.base_state, + state: ((self.state.0 .0, self.state.1), self.state.0 .1), + program, + } + } + + /// Implements `dup:1` instruction. + pub fn dup_1(self) -> ProgramState + where + T1: CopyableArgument, + { + let mut program = self.program; + program.dup(1); + let value = self.state.0 .1.copy(); + ProgramState { + base_state: self.base_state, + state: (self.state, value), + program, + } + } +} diff --git a/zkvm/src/lib.rs b/zkvm/src/lib.rs index 18a8bf77..96c3fe34 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;