Skip to content

Commit

Permalink
CLI for crafting PBH transactions (#26)
Browse files Browse the repository at this point in the history
* wip cli

* Readme + docs

* clippy
  • Loading branch information
Dzejkop authored Oct 9, 2024
1 parent 0a03864 commit 8a0a214
Show file tree
Hide file tree
Showing 10 changed files with 275 additions and 125 deletions.
197 changes: 99 additions & 98 deletions world-chain-builder/Cargo.lock

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions world-chain-builder/crates/toolkit/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@ bytes = "1.7.2"
clap = { version = "4", features = ["derive", "env"] }
eyre = { version = "0.6", package = "color-eyre" }
hex = "0.4.3"
reqwest = "0.12"
serde = { version = "1", features = ["derive"] }
serde_json = "1.0"
tokio = { version = "1", features = ["full"] }
dotenvy = "0.15.7"
chrono = "0.4"
rand = "0.8.5"
51 changes: 51 additions & 0 deletions world-chain-builder/crates/toolkit/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Creating human verified txs

## 0. Info
In order to create a PBH transaction we'll need 2 bits of secret information:
1. A wallet private key
2. An identity secret

For the wallet we'll use the default test mnemonic used by both Anvil and Reth `test test test test test test test test test test test junk`. We can find the private key with the following command

```bash
> cast wallet private-key --mnemonic "test test test test test test test test test test test junk" --mnemonic-index 0
0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
```

The identity secret is a little weird since we never actually access it directly. Instead we use a seed to deterministicly generate an identity. For the purpose of testing we can use `11ff11` as our testing seed (the associated identity commitment is included in the staging sequencer).

## 1. Craft a transaction with `cast mktx`
```bash
# Sends 1ETH from 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 to 0x8603937E3F761958187207aeab2714ECA1C1D031
> cast mktx --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 0x8603937E3F761958187207aeab2714ECA1C1D031 --value 1ether
```

This should return
```
0x02f870827a6980018477359401825209948603937e3f761958187207aeab2714eca1c1d031880de0b6b3a764000080c001a00ff6b2d8e020715137f19cbecf3b162576de99ce1b96b7158dafe2962e7d4c9ea0429a0a04cf25ba60444c0275f05bd83c20198be1612b6c08e30243d5feae71e4
```

which we'll use in the next step

## 2. Prove the transaction for PBH
```bash
> export IDENTITY=11ff11
> export INCLUSION_PROOF_URL=https://signup-orb-ethereum.stage-crypto.worldcoin.dev/inclusionProof
> ./x prove -t 0x02f870827a6980018477359401825209948603937e3f761958187207aeab2714eca1c1d031880de0b6b3a764000080c001a00ff6b2d8e020715137f19cbecf3b162576de99ce1b96b7158dafe2962e7d4c9ea0429a0a04cf25ba60444c0275f05bd83c20198be1612b6c08e30243d5feae71e4 -N 3
```

This should return
```
02f870827a6980018477359401825209948603937e3f761958187207aeab2714eca1c1d031880de0b6b3a764000080c001a00ff6b2d8e020715137f19cbecf3b162576de99ce1b96b7158dafe2962e7d4c9ea0429a0a04cf25ba60444c0275f05bd83c20198be1612b6c08e30243d5feae71e4f901918b76312d3130323032342d339fba535b5fad84465fa11734ec0b5c932eef8abe2be0bc41b6dacf142efd2e36a02e1d5d3e2d601502f61491450ff8306522fd51153f0530c964f41f137d3fc62c9f2b7a5d363c79e99a8b2beca564b151cc7ec6f91c8bc2a105286deb68954ac7a001df1992cc8c17d0e2b2c2763b49f0fae836a2e967a24c8fa602ce3c17b7dbf4b901000885d05bc5474812458e3a0c8221cd1a1d206a3d14b88803549f08d80b3c6e1e25382e980a49388418261b5c928ce66bd33ee02f41e89e1d072d01c1e151830b0b6022659ce65dde6fc3f21110619c47e31174998b08e25534357a317ad1b7870e7a7044de43bf4a54f7b38a798a03c4404fc4ec8a543f7459555d9084e3f9c62ff7fe8b898e5529538b4d4c4a5cc7d51ad625bb68db07938f0a403968480ed808257ebbb03be708586a764a1eff0e024938440b7401ffba4982776fcfac932503907056ee0171baa1bc02d184afb154270122368a45fc51c66a9ed4c6692ade298500aa348decf3d713ce22e59acc886e43d6064c1c652bbcde38cf893e0392
```

which is a concatenation of both the raw transaction and the PBH proof.

We can now take this payload and publish it

## 3. Publish the payload
Assuming you're env vars are pointing to an instance of the world chain builder, we can simply run:
```bash
> cast publish 02f870827a6980018477359401825209948603937e3f761958187207aeab2714eca1c1d031880de0b6b3a764000080c001a00ff6b2d8e020715137f19cbecf3b162576de99ce1b96b7158dafe2962e7d4c9ea0429a0a04cf25ba60444c0275f05bd83c20198be1612b6c08e30243d5feae71e4f901918b76312d3130323032342d339fba535b5fad84465fa11734ec0b5c932eef8abe2be0bc41b6dacf142efd2e36a02e1d5d3e2d601502f61491450ff8306522fd51153f0530c964f41f137d3fc62c9f2b7a5d363c79e99a8b2beca564b151cc7ec6f91c8bc2a105286deb68954ac7a001df1992cc8c17d0e2b2c2763b49f0fae836a2e967a24c8fa602ce3c17b7dbf4b901000885d05bc5474812458e3a0c8221cd1a1d206a3d14b88803549f08d80b3c6e1e25382e980a49388418261b5c928ce66bd33ee02f41e89e1d072d01c1e151830b0b6022659ce65dde6fc3f21110619c47e31174998b08e25534357a317ad1b7870e7a7044de43bf4a54f7b38a798a03c4404fc4ec8a543f7459555d9084e3f9c62ff7fe8b898e5529538b4d4c4a5cc7d51ad625bb68db07938f0a403968480ed808257ebbb03be708586a764a1eff0e024938440b7401ffba4982776fcfac932503907056ee0171baa1bc02d184afb154270122368a45fc51c66a9ed4c6692ade298500aa348decf3d713ce22e59acc886e43d6064c1c652bbcde38cf893e0392
```

13 changes: 11 additions & 2 deletions world-chain-builder/crates/toolkit/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,26 @@ pub mod identity_source;
pub mod inclusion_proof_source;
mod utils;

/// A CLI utility for proving raw Ethereum transactions
#[derive(Debug, Clone, Parser)]
#[clap(version, about)]
pub struct Opt {
#[clap(subcommand)]
pub cmd: Cmd,
}

#[derive(Debug, Clone, Parser)]
pub enum Cmd {
/// Proves a transaction and returns a hex encoded payload ready to be sent to a World Chain Builder
///
/// Note that it's necessary to provide the identity and inclusion proof
/// and there exist multiple ways to provide them
///
/// For the identity in testing the simplest way is to use a predefined identity secret via `-I 11ff11` flag or `export IDENTITY=11ff11` env var
///
/// For the inclusion proof you can fetch it dynamically from the (staging) sequencer API via `--inclusion-proof-url https://signup-orb-ethereum.stage-crypto.worldcoin.dev/inclusionProof`
/// or `export INCLUSION_PROOF_URL=https://signup-orb-ethereum.stage-crypto.worldcoin.dev/inclusionProof` env var
Prove(ProveArgs),

Send(SendArgs),
}

#[derive(Debug, Clone, Parser)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub struct IdentitySource {
#[clap(
short = 'I',
long,
env,
conflicts_with = "identity_file",
required_unless_present = "identity_file",
value_parser = bytes_mut_parse_hex
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use clap::Args;
use semaphore::poseidon_tree::Proof;

use super::utils::parse_from_json;
use crate::InclusionProof;

#[derive(Debug, Clone, Args)]
pub struct InclusionProofSource {
Expand All @@ -13,32 +14,55 @@ pub struct InclusionProofSource {
long,
value_parser = parse_from_json::<Proof>,
conflicts_with = "inclusion_proof_file",
required_unless_present = "inclusion_proof_file"
conflicts_with = "inclusion_proof_url",
required_unless_present = "inclusion_proof_file",
required_unless_present = "inclusion_proof_url"
)]
pub inclusion_proof: Option<Proof>,
pub inclusion_proof: Option<InclusionProof>,

#[clap(
long,
conflicts_with = "inclusion_proof",
required_unless_present = "inclusion_proof"
conflicts_with = "inclusion_proof_url",
required_unless_present = "inclusion_proof",
required_unless_present = "inclusion_proof_url"
)]
pub inclusion_proof_file: Option<PathBuf>,

// TODO: Add fetching from signup-sequencer/world-tree
// TODO: Add fetching from smart contract via RPC
/// Endpoint to fetch the inclusion proof from
///
/// i.e. https://world-tree.crypto.worldcoin.dev/inclusionProof
#[clap(
long,
env,
conflicts_with = "inclusion_proof",
conflicts_with = "inclusion_proof_file",
required_unless_present = "inclusion_proof",
required_unless_present = "inclusion_proof_file"
)]
pub inclusion_proof_url: Option<String>,
}

impl InclusionProofSource {
pub fn load(&self) -> Proof {
if let Some(inclusion_proof) = self.inclusion_proof.clone() {
return inclusion_proof;
pub fn into_variant(self) -> InclusionProofSourceVariant {
if let Some(inclusion_proof) = self.inclusion_proof {
return InclusionProofSourceVariant::Proof(inclusion_proof);
}

if let Some(inclusion_proof_file) = self.inclusion_proof_file {
return InclusionProofSourceVariant::File(inclusion_proof_file);
}

if let Some(inclusion_proof_file) = &self.inclusion_proof_file {
let inclusion_proof = std::fs::read(inclusion_proof_file).unwrap();
return serde_json::from_slice(&inclusion_proof).unwrap();
if let Some(inclusion_proof_url) = self.inclusion_proof_url {
return InclusionProofSourceVariant::Url(inclusion_proof_url);
}

unreachable!()
}
}

pub enum InclusionProofSourceVariant {
Proof(InclusionProof),
File(PathBuf),
Url(String),
}
6 changes: 2 additions & 4 deletions world-chain-builder/crates/toolkit/src/cli/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,10 @@ pub fn bytes_mut_parse_hex(s: &str) -> eyre::Result<BytesMut> {
}

pub fn bytes_parse_hex(s: &str) -> eyre::Result<Bytes> {
Ok(Bytes::from(
hex::decode(s.trim_start_matches("0x"))?,
))
Ok(Bytes::from(hex::decode(s.trim_start_matches("0x"))?))
}

pub fn parse_from_json<'a, T>(s: &'a str) -> eyre::Result<T>
pub fn parse_from_json<T>(s: &str) -> eyre::Result<T>
where
T: DeserializeOwned,
{
Expand Down
76 changes: 70 additions & 6 deletions world-chain-builder/crates/toolkit/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,29 +1,49 @@
use alloy_consensus::TxEnvelope;
use alloy_rlp::Decodable;
use clap::Parser;
use cli::inclusion_proof_source::InclusionProofSourceVariant;
use cli::{Cmd, Opt};
use semaphore::hash_to_field;
use semaphore::identity::Identity;
use semaphore::poseidon_tree::Proof;
use semaphore::{hash_to_field, Field};
use serde::{Deserialize, Serialize};
use world_chain_builder::date_marker::DateMarker;
use world_chain_builder::external_nullifier::ExternalNullifier;
use world_chain_builder::pbh::semaphore::SemaphoreProof;

mod cli;

#[derive(Debug, Clone, Serialize, Deserialize)]
struct InclusionProof {
root: Field,
proof: Proof,
}

#[tokio::main]
async fn main() -> eyre::Result<()> {
dotenvy::dotenv().ok();

let args = Opt::parse();
println!("{:#?}", args);

match args.cmd {
Cmd::Prove(prove_args) => {
let tx: TxEnvelope = TxEnvelope::decode(&mut prove_args.tx.as_ref())?;
let raw_tx_bytes = prove_args.tx;
let tx: TxEnvelope = TxEnvelope::decode(&mut raw_tx_bytes.as_ref())?;

let tx_hash = tx.tx_hash();
let signal_hash = hash_to_field(tx_hash.as_ref());

let identity = prove_args.identity_source.load();
let merkle_proof = prove_args.inclusion_proof_source.load();

let inclusion_proof_proof_src =
prove_args.inclusion_proof_source.clone().into_variant();
let inclusion_proof = match inclusion_proof_proof_src {
InclusionProofSourceVariant::Proof(proof) => proof,
InclusionProofSourceVariant::File(file) => load_inclusion_proof_file(file)?,
InclusionProofSourceVariant::Url(url) => {
fetch_inclusion_proof(&url, &identity).await?
}
};

let date = prove_args
.custom_date
Expand All @@ -36,13 +56,57 @@ async fn main() -> eyre::Result<()> {

let semaphore_proof = semaphore::protocol::generate_proof(
&identity,
&merkle_proof,
&inclusion_proof.proof,
external_nullifier_hash,
signal_hash,
)?;

let nullifier_hash =
semaphore::protocol::generate_nullifier_hash(&identity, external_nullifier_hash);

let proof = SemaphoreProof {
external_nullifier: external_nullifier.to_string(),
external_nullifier_hash,
nullifier_hash,
signal_hash,
root: inclusion_proof.root,
proof: world_chain_builder::pbh::semaphore::Proof(semaphore_proof),
};

let encoded = alloy_rlp::encode(proof);

let concatenated_bytes = [raw_tx_bytes.as_ref(), encoded.as_slice()].concat();

let encoded_hex = hex::encode(concatenated_bytes);

println!("{}", encoded_hex);
}
_ => unimplemented!(),
}

Ok(())
}

fn load_inclusion_proof_file(path: impl AsRef<std::path::Path>) -> eyre::Result<InclusionProof> {
let file = std::fs::File::open(path)?;
let proof = serde_json::from_reader(file)?;

Ok(proof)
}

async fn fetch_inclusion_proof(url: &str, identity: &Identity) -> eyre::Result<InclusionProof> {
let client = reqwest::Client::new();

let commitment = identity.commitment();
let response = client
.post(url)
.json(&serde_json::json! {{
"identityCommitment": commitment,
}})
.send()
.await?
.error_for_status()?;

let proof: InclusionProof = response.json().await?;

Ok(proof)
}
4 changes: 2 additions & 2 deletions world-chain-builder/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
pub mod date_marker;
#[cfg(test)]
pub mod e2e_tests;
pub mod external_nullifier;
pub mod node;
pub mod payload;
pub mod pbh;
pub mod pool;
pub mod primitives;
pub mod rpc;
pub mod external_nullifier;
pub mod date_marker;
4 changes: 2 additions & 2 deletions world-chain-builder/x
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/bin/bash

if [ -n "$RELEASE" ]; then
cargo run --release --package toolkit "$@"
cargo run --release --package toolkit -- "$@"
else
cargo run --package toolkit "$@"
cargo run --package toolkit -- "$@"
fi

0 comments on commit 8a0a214

Please sign in to comment.