Skip to content
This repository has been archived by the owner on Jun 6, 2024. It is now read-only.

paychan: new crate #498

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ members = [
"blockchain",
"token",
"accounts",
"paychan",
"p2p",
"node",
]
Expand Down
3 changes: 3 additions & 0 deletions paychan/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/target
**/*.rs.bk
Cargo.lock
38 changes: 38 additions & 0 deletions paychan/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
[package]
name = "paychan"
version = "0.1.0"
authors = ["Oleg Andreev <[email protected]>"]
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"
33 changes: 23 additions & 10 deletions paychan/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)));
Expand All @@ -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<Item = Value>,
mut values: impl ExactSizeIterator<Item = Value>,
pred: Predicate,
) {
let n = values.len();
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand All @@ -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);
})],
Expand Down Expand Up @@ -186,17 +186,32 @@ 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
// message formatted for signtag instruction.
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]),
Expand All @@ -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");
}
175 changes: 175 additions & 0 deletions zkvm/src/dsl.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
//! Type-safe program builder

use crate::{Program, ProgramItem, String};

// What we want: ".dup::<N>()" 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<S0, S> {
/// 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<S0, S> ProgramState<S0, S> {
/// 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<T1: Clone>(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<T1: Clone, T2: Clone>(
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<T: StringArgument>(self, value: T) -> ProgramState<S0, (S, T)> {
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<T: ProgramArgument>(self, value: T) -> ProgramState<S0, (S, T)> {
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<S0, S, X> ProgramState<S0, (S, ProgramState<S, X>)> {
// ^ ^
// |_______________|
// |
// 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<S0, X> {
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<S0, S, T> ProgramState<S0, (S, T)> {
/// Implements `roll:0` instruction.
pub fn roll_0(self) -> ProgramState<S0, (S, T)> {
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<S0, ((S, T), T)>
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<S0, S, T1, T0> ProgramState<S0, ((S, T1), T0)> {
/// Implements `roll:1` instruction.
pub fn roll_1(self) -> ProgramState<S0, ((S, T0), T1)> {
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<S0, (((S, T1), T0), T1)>
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,
}
}
}
1 change: 1 addition & 0 deletions zkvm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ mod serialization;
mod constraints;
mod contract;
mod debug;
pub mod dsl;
pub mod encoding;
mod errors;
mod fees;
Expand Down