Skip to content

Commit

Permalink
initial
Browse files Browse the repository at this point in the history
  • Loading branch information
pdtfh committed Nov 21, 2024
1 parent 970fbb5 commit 7841dd9
Show file tree
Hide file tree
Showing 10 changed files with 5,876 additions and 330 deletions.
6,003 changes: 5,679 additions & 324 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"ignorePaths": ["Cargo.toml"],
"words": [
"aarch",
"ruint",
"Uniffi",
"WalletKit",
"Worldcoin",
Expand Down
3 changes: 3 additions & 0 deletions walletkit-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,7 @@ crate-type = ["lib", "staticlib", "cdylib"]
name = "walletkit_core"

[dependencies]
ruint = { version = "1.12.3", default-features = false, features = ["alloc"] }
semaphore = { git = "https://github.com/worldcoin/semaphore-rs", rev = "accb14b", features = ["depth_30"] }
thiserror = "2.0.3"
uniffi = { workspace = true, features = ["build"] }
10 changes: 10 additions & 0 deletions walletkit-core/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
use thiserror::Error;

#[derive(Debug, Error, uniffi::Error)]
#[uniffi(flat_error)]
pub enum Error {
#[error("fetching_inclusion_proof_failed")]
FetchingInclusionProofFailed,
#[error("invalid_hex_string")]
U256ParsingError(#[from] ruint::ParseError),
}
28 changes: 28 additions & 0 deletions walletkit-core/src/field.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#[derive(uniffi::Object, Debug, PartialEq, Eq, Clone)]
/// A field element in the Semaphore protocol.
pub struct Field(pub semaphore::Field);

impl Field {
#[must_use]
pub fn to_hex_string(&self) -> String {
format!("{:#04x}", self.0)
}
}

impl From<&Field> for semaphore::Field {
fn from(val: &Field) -> Self {
val.0
}
}

impl From<Field> for semaphore::Field {
fn from(val: Field) -> Self {
val.0
}
}

impl std::fmt::Display for Field {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_hex_string())
}
}
49 changes: 49 additions & 0 deletions walletkit-core/src/identity.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
use ruint::aliases::U256;
use semaphore::protocol::generate_nullifier_hash;

use crate::proof::Context;

#[derive(Clone, PartialEq, Eq, Debug, uniffi::Object)]
pub struct Identity(pub semaphore::identity::Identity);

impl From<Identity> for semaphore::identity::Identity {
fn from(identity: Identity) -> Self {
identity.0
}
}

impl Identity {
#[must_use]
pub fn new(secret: &[u8]) -> Self {
let mut secret_key = secret.to_vec();
let identity = semaphore::identity::Identity::from_secret(&mut secret_key, None);
Self(identity)
}

/// Generates a nullifier hash for a particular context (i.e. app + action) and the identity.
/// The nullifier hash is a unique pseudo-random number for the particular identity and context.
/// More information can be found [here](https://docs.world.org/world-id/concepts#vocabulary)
///
/// [Protocol Reference](https://docs.semaphore.pse.dev/V2/technical-reference/circuits#nullifier-hash).
///
/// # Result
/// Outputs a Field element as a `U256` value
#[must_use]
pub fn generate_nullifier_hash(&self, context: &Context) -> U256 {
generate_nullifier_hash(&self.0, context.external_nullifier)
}
}

#[cfg(test)]
mod tests {
use crate::utils::HexNumber;

use super::*;
#[test]
fn test() {
let identity = Identity::new(b"not_a_real_secret");
let context = Context::new(b"app_id", b"action");
let nullifier_hash = identity.generate_nullifier_hash(&context);
println!("{}", nullifier_hash.to_hex_string());
}
}
9 changes: 7 additions & 2 deletions walletkit-core/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
#![deny(clippy::all, clippy::pedantic, clippy::nursery, clippy::cargo)]
#![deny(clippy::all, clippy::pedantic, clippy::nursery)]

pub mod math;
pub mod error;
pub mod field;
pub mod identity;
pub mod proof;
mod utils;
pub use utils::*;

uniffi::setup_scaffolding!("walletkit_core");
4 changes: 0 additions & 4 deletions walletkit-core/src/math.rs

This file was deleted.

15 changes: 15 additions & 0 deletions walletkit-core/src/proof.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
use ruint::aliases::U256;
use semaphore::hash_to_field;

pub struct Context {
pub external_nullifier: U256,
}

impl Context {
#[must_use]
pub fn new(app_id: &[u8], action: &[u8]) -> Self {
let external_nullifier = hash_to_field(app_id);
// TODO: handle action properly
Self { external_nullifier }
}
}
84 changes: 84 additions & 0 deletions walletkit-core/src/utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
use ruint::aliases::U256;

use crate::error::Error;

/// A trait for types that can be represented as a hex string.
///
/// This trait is implemented for `U256` and can be used to convert between hex strings and numbers.
/// Most inputs and outputs from the zero-knowledge proofs are `U256` values.
/// While using `U256` directly is convenient and recommended when working with the proofs, particularly in Rust,
/// it is not a user-friendly type for interactions or communications in other languages / systems.
///
/// Particularly, when sending proof inputs/outputs as JSON on HTTP requests, the values SHOULD be represented as hex strings.
pub trait HexNumber {
/// Convert the value to a hex string.
fn to_hex_string(&self) -> String;
/// Convert a hex string to a `U256`.
///
/// # Errors
///
/// Returns an error if the hex string is invalid as a `U256`.
fn try_from_hex_string(hex: &str) -> Result<Self, Error>
where
Self: std::marker::Sized;
}

impl HexNumber for U256 {
fn to_hex_string(&self) -> String {
format!("{self:#066x}")
}

fn try_from_hex_string(hex: &str) -> Result<Self, Error> {
Self::from_str_radix(hex, 16).map_err(Error::U256ParsingError)
}
}

#[cfg(test)]
mod tests {
use super::*;
use ruint::uint;

#[test]
fn test_to_hex_string_for_u256() {
assert_eq!(
U256::from(1).to_hex_string(),
"0x0000000000000000000000000000000000000000000000000000000000000001"
);
assert_eq!(
U256::from(42).to_hex_string(),
"0x000000000000000000000000000000000000000000000000000000000000002a"
);

assert_eq!(
uint!(999999_U256).to_hex_string(),
"0x00000000000000000000000000000000000000000000000000000000000f423f"
);

assert_eq!(
uint!(80084422859880547211683076133703299733277748156566366325829078699459944778998_U256).to_hex_string(),
"0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6"
);

assert_eq!(
uint!(0x036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db0_U256).to_hex_string(),
"0x036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db0"
);
}

#[test]
fn test_try_from_hex_string() {
// Test valid hex strings
assert_eq!(U256::try_from_hex_string("0x2a").unwrap(), U256::from(42));
assert_eq!(U256::try_from_hex_string("2a").unwrap(), U256::from(42));

// Test larger numbers
assert_eq!(U256::try_from_hex_string("0xf423f").unwrap(), U256::from(999999));

// Test zero
assert_eq!(U256::try_from_hex_string("0x0").unwrap(), U256::from(0));

// Test invalid hex strings
assert!(U256::try_from_hex_string("0xg").is_err());
assert!(U256::try_from_hex_string("not hex").is_err());
}
}

0 comments on commit 7841dd9

Please sign in to comment.