From 176bad6245b197cfda8c70b26e17e2aeb73dcefb Mon Sep 17 00:00:00 2001 From: Davie Li Date: Tue, 13 Aug 2024 15:29:57 +0200 Subject: [PATCH] [WIP] --- Cargo.toml | 34 ++++++-- crates/client-guest/Cargo.toml | 9 ++ crates/client-guest/src/main.rs | 56 +++++++++++++ crates/client-host/Cargo.toml | 10 +++ crates/client-host/build.rs | 5 ++ crates/client-host/rust-toolchain.toml | 3 + crates/client-host/src/main.rs | 7 ++ src/lib.rs | 32 ++++--- src/main.rs | 111 ++++++++++++++++++++++++- tests/test_cli.rs | 34 ++++++++ 10 files changed, 284 insertions(+), 17 deletions(-) create mode 100644 crates/client-guest/Cargo.toml create mode 100644 crates/client-guest/src/main.rs create mode 100644 crates/client-host/Cargo.toml create mode 100644 crates/client-host/build.rs create mode 100644 crates/client-host/rust-toolchain.toml create mode 100644 crates/client-host/src/main.rs create mode 100644 tests/test_cli.rs diff --git a/Cargo.toml b/Cargo.toml index 4767c35..5284940 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,13 +1,37 @@ [package] name = "mtcs-garage" +edition = { workspace = true } +version = { workspace = true } + +[workspace.package] version = "0.0.0" edition = "2021" +[workspace] +resolver = "2" +members = ["crates/*"] + +[workspace.dependencies] +ed25519-dalek-patched = { package = "ed25519-dalek", git = "https://github.com/sp1-patches/curve25519-dalek", branch = "patch-v4.1.1" } +sha2 = "0.10.8" +sp1-helper = { git = "https://github.com/succinctlabs/sp1.git" } +sp1-sdk = { git = "https://github.com/succinctlabs/sp1.git" } +sp1-zkvm = { git = "https://github.com/succinctlabs/sp1.git" } + +[dependencies] +clap = { version = "4.5.4", features = ["derive"] } +ed25519-dalek = "2.1.1" +hex = { version = "0.4.3", features = ["serde"] } +rand = "0.8.5" +serde = "1.0.198" +serde_json = "1.0.116" +sha2 = { workspace = true } + +[dev-dependencies] +assert_cmd = "2.0.14" +predicates = "3.1.0" +rstest = "0.19.0" + [[bin]] name = "credit5000" path = "src/main.rs" - -[dependencies] -ed25519-dalek = "2.1.1" -rand = "0.8.5" -sha2 = "0.10.8" diff --git a/crates/client-guest/Cargo.toml b/crates/client-guest/Cargo.toml new file mode 100644 index 0000000..580bf7b --- /dev/null +++ b/crates/client-guest/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "mtcs-client-guest" +version = { workspace = true } +edition = { workspace = true } + +[dependencies] +ed25519-dalek-patched = { workspace = true } +mtcs-garage = { path = "../../" } +sp1-zkvm = { workspace = true } diff --git a/crates/client-guest/src/main.rs b/crates/client-guest/src/main.rs new file mode 100644 index 0000000..d3fe4ca --- /dev/null +++ b/crates/client-guest/src/main.rs @@ -0,0 +1,56 @@ +//! Client side zkvm program. +#![no_main] +sp1_zkvm::entrypoint!(main); + +use ed25519_dalek_patched::{VerifyingKey, Signature, SecretKey} + +type InputLengh = u64; + +type Secret = [u8: 32]; + +struct InputHead { + salt: [u8: 32], + from: [u8: 32], + val: u32, + to: [u8: 32], + hash: [u8: 32], + signature: [u8: 64], + public_key: [u8: 32], + root: [u8: 32], +} + + +// Input Head length in bytes. +// Could be turned into Phantom attribute or somethin? +const HL = 260; + +pub fn main() { + let length = sp1_zkvm::io::read::(); + let input = sp1_zkvm::io::read::(); + let secret = sp1_zkvm::io::read::(); + let mut my_slice = [0_u8; 32]; + sp1_zkvm::io::read_slice(&mut my_slice); + let hashes = sp1_zkvm::io::read::>(); + + let pk = VerifyingKey::from_bytes(input.public_key).unwrap(); + assert!(pk.verify_strict( + &input.from + .iter() + .chain(input.val.to_le_bytes().iter()) + .chain(input.to.iter()) + .cloned() + .collect(), + Signature::from_bytes(&input.signature), + ).unwrap()); + + let mut h = Sha256::new(); + let chunks = hashes.chunks(32); + chunks.fold(chunks.next().unwrap(), |acc, e| { + h.update(); + assert!(*e == *h.finalize_reset()); + }); + assert!(input.root == *self.hashes.last().unwrap()); + + sp1_zkvm::io::commit_slice(&input.hash); + sp1_zkvm::io::commit_slice(&input.root); +} diff --git a/crates/client-host/Cargo.toml b/crates/client-host/Cargo.toml new file mode 100644 index 0000000..c2a4e5f --- /dev/null +++ b/crates/client-host/Cargo.toml @@ -0,0 +1,10 @@ +[package] +version = { workspace = true } +name = "client-host" +edition = { workspace = true } + +[dependencies] +sp1-sdk = { workspace = true } + +[build-dependencies] +sp1-helper = { workspace = true } diff --git a/crates/client-host/build.rs b/crates/client-host/build.rs new file mode 100644 index 0000000..c9ee7f9 --- /dev/null +++ b/crates/client-host/build.rs @@ -0,0 +1,5 @@ +use sp1_helper::build_program; + +fn main() { + build_program("../client-guest") +} diff --git a/crates/client-host/rust-toolchain.toml b/crates/client-host/rust-toolchain.toml new file mode 100644 index 0000000..46fc491 --- /dev/null +++ b/crates/client-host/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "nightly-2024-04-20" +components = ["llvm-tools", "rustc-dev"] diff --git a/crates/client-host/src/main.rs b/crates/client-host/src/main.rs new file mode 100644 index 0000000..a23863e --- /dev/null +++ b/crates/client-host/src/main.rs @@ -0,0 +1,7 @@ +//! A simple script to generate and verify the proof of a given program. + +use sp1_sdk::{ProverClient, SP1Stdin}; + +const ELF: &[u8] = include_bytes!("../../../elf/riscv32im-succinct-zkvm-elf"); + +fn main() { } diff --git a/src/lib.rs b/src/lib.rs index 1500bbc..2cbafdf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,11 @@ use std::error::Error; +use dalek::{ed25519::signature::SignerMut, SigningKey}; use ed25519_dalek as dalek; use ed25519_dalek::Verifier; use rand; use sha2::{digest::Digest, Sha256}; +use serde::{Deserialize, Serialize}; /// TODO: Use hashes instead of public addresses. /// TODO: Don't forget document endiannes. @@ -16,6 +18,7 @@ use sha2::{digest::Digest, Sha256}; /// much. pub type Address = [u8; 32]; +pub type Secret = [u8; 32]; #[derive(Copy, Clone, PartialEq, Debug)] pub struct Obligation { @@ -25,7 +28,7 @@ pub struct Obligation { } impl Obligation { - fn serialize(self) -> [u8; 68] { + pub fn serialize(self) -> [u8; 68] { let mut arr = [0; 68]; arr[..32].clone_from_slice(&self.from); arr[32..36].clone_from_slice(&self.value.to_le_bytes()); @@ -44,7 +47,7 @@ pub struct AuthCert { } impl AuthCert { - pub fn new( + pub fn maybe_from_signature( subject: &Obligation, signature: &Signature, pk: &Address, @@ -59,6 +62,12 @@ impl AuthCert { signature: *signature, }) } + pub fn from_secret(subject: &Obligation, secret: &Secret) -> Self { + Self { + subject: subject.clone(), + signature: SigningKey::from_bytes(secret).sign(&subject.serialize()).into(), + } + } } pub type PseudoAnonID = [u8; 32]; @@ -138,16 +147,17 @@ impl Cert { hash: h.finalize().into(), } } - pub fn zkvm_serialize(&self) -> Vec { + pub fn zkvm_serialize(&self) -> Vec { // , pk: Address // First 8 bytes are the length of the message, - // followed by 228 bytes of fields of defined length, + // followed by 260 bytes of fields of defined length, // the rest is membership proof. - (228 + 32 * self.membership.hashes.len()) + (260 + 32 * self.membership.hashes.len()) .to_le_bytes() // 8 bytes; NOTE: Machine specific! (from usize) .iter() .chain(self.salt.iter()) // 32 bytes .chain(self.subject.serialize().iter()) // 68 bytes .chain(self.hash.iter()) // 32 bytes + .chain(self.salt.iter()) // 32 bytes .chain(self.authenticity.signature.iter()) // 64 bytes .chain(self.membership.root.iter()) // 32 bytes .chain(self.membership.hashes.iter().flatten()) // tail @@ -224,15 +234,15 @@ mod tests { fn authenticity_cert_new() { let tob = rand_obligation(); let sig = tob.f.sk.sign(&tob.o.serialize()[..]).to_bytes(); - let cert = AuthCert::new(&tob.o, &sig, &tob.f.sk.verifying_key().to_bytes()); + let cert = AuthCert::maybe_from_signature(&tob.o, &sig, &tob.f.sk.verifying_key().to_bytes()); assert!(cert.is_ok()); assert_eq!(cert.as_ref().unwrap().subject, tob.o); assert_eq!(cert.as_ref().unwrap().signature, sig); // Why this sometime fails?? // assert!(AuthCert::new(&tob.o, &[0; 64], &[0; 32]).is_err()); - assert!(AuthCert::new(&tob.o, &[0; 64], &[1; 32]).is_err()); - assert!(AuthCert::new(&tob.o, &sig, &tob.o.from).is_err()); - assert!(AuthCert::new(&tob.o, &sig, &tob.o.to).is_err()); + assert!(AuthCert::maybe_from_signature(&tob.o, &[0; 64], &[1; 32]).is_err()); + assert!(AuthCert::maybe_from_signature(&tob.o, &sig, &tob.o.from).is_err()); + assert!(AuthCert::maybe_from_signature(&tob.o, &sig, &tob.o.to).is_err()); } #[test] @@ -268,7 +278,7 @@ mod tests { salt: rand::random(), hash: rand::random(), }; - assert!(c.zkvm_serialize().len() > 228); - assert!(c.zkvm_serialize().len() < 228 + (nodes * 32) * 2); + assert!(c.zkvm_serialize().len() > 260); + assert!(c.zkvm_serialize().len() < 260 + (nodes * 32) * 2); } } diff --git a/src/main.rs b/src/main.rs index f328e4d..62e737b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1 +1,110 @@ -fn main() {} +use clap::Parser; +// use sp1_sdk::{utils, ProverClient, SP1Stdin}; +use hex; +use mtcs_garage::{AuthCert, Cert, MembershipProof, Obligation}; + +// const ELF: &[u8] = include_bytes!("../elf/riscv32im-succinct-zkvm-elf"); + +#[derive(Parser, Debug)] +#[command(version, about, long_about = None)] +struct Args { + #[arg(short, long)] + secret: String, + + #[arg(short, long)] + from: String, + + #[arg(short, long)] + to: String, + + #[arg(short, long)] + value: u32, + + // #[arg(short, long)] + // cache: String, +} + +fn main() { + let args = Args::parse(); + let (from, to) = ( + hex::decode(args.from).unwrap().try_into().unwrap(), + hex::decode(args.to).unwrap().try_into().unwrap(), + ); + + let obligation = Obligation { from, to, value: args.value }; + let cert = Cert { + subject: obligation, + authenticity: AuthCert::from_secret( + &obligation, + &hex::decode(args.secret).unwrap().try_into().unwrap(), + ), + hash: [0; 32], + salt: [0; 32], + membership: MembershipProof::new(vec![from, to]) + }; + + // utils::setup_tracer(); + + // let mut stdin = SP1Stdin::new(); + // stdin.write(&cert.zkvm_serialize()); + // let client = ProverClient::new(); + // let (pk, vk) = client.prover.setup(ELF); + // let proof = client.prove(&pk, stdin); + // match proof { + // Ok(p) => println!("coool"), + // Err(_) => println!("not cool"), + // } +} + +// fn main() { + +// let nodes = rand::thread_rng().gen_range(0..100); +// let o = rand_obligation(); +// let mp = MembershipProof::new( +// std::iter::from_fn(|| Some(make_address())) +// .take(nodes) +// .collect(), +// ); +// let c = Cert { +// subject: o.o, +// authenticity: AuthCert { +// subject: o.o, +// signature: rand::random(), +// }, +// membership: mp, +// salt: rand::random(), +// hash: rand::random(), +// }; + + +// utils::setup_tracer(); +// let mut stdin = SP1Stdin::new(); + + +// stdin.write(&data_str); +// stdin.write(&key); +// stdin.write(&initial_account_state); +// stdin.write(&transactions); + +// let mut proof = SP1Prover::prove(JSON_ELF, stdin).expect("proving failed"); + +// // Read output. +// let val = proof.stdout.read::(); +// println!("Value of {} is {}", key, val); + +// let account_state = proof.stdout.read::(); +// println!( +// "Final account state: {}", +// serde_json::to_string(&account_state).unwrap() +// ); + +// // Verify proof. +// SP1Verifier::verify(JSON_ELF, &proof).expect("verification failed"); + +// // Save proof. +// proof +// .save("proof-with-io.json") +// .expect("saving proof failed"); + +// println!("successfully generated and verified proof for the program!") +// } diff --git a/tests/test_cli.rs b/tests/test_cli.rs new file mode 100644 index 0000000..dfed8b6 --- /dev/null +++ b/tests/test_cli.rs @@ -0,0 +1,34 @@ +use assert_cmd::prelude::*; +use predicates::prelude::*; +use rand; + +use std::process::Command; +use rstest::*; + +use mtcs_garage::*; +use hex; + + +#[fixture] +fn cmd() -> Command { + Command::cargo_bin("credit5000").unwrap() +} + +#[rstest] +fn cli_showes_help(mut cmd: Command) -> Result<(), Box> { + cmd.arg("--help"); + cmd.assert().stdout(predicate::str::contains("Usage: credit5000")); + Ok(()) +} + +#[rstest] +fn cli_stuff(mut cmd: Command) -> Result<(), Box> { + let o = cmd.args([ + "--secret", &hex::encode(rand::random::()), + "--from", &hex::encode(rand::random::
()), + "--to", &hex::encode(rand::random::
()), + "--value", "1000", + ]); + cmd.assert().stdout(predicate::str::contains("coool")); + Ok(()) +}