diff --git a/Cargo.lock b/Cargo.lock index ebf280277..4e1e6389f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -731,7 +731,6 @@ checksum = "d69eab57e8d2663efa5c63135b2af4f396d66424f88954c21104125ab6b3e6bc" dependencies = [ "ark-ec 0.5.0", "ark-ff 0.5.0", - "ark-r1cs-std", "ark-std 0.5.0", ] @@ -1018,23 +1017,6 @@ dependencies = [ "rayon", ] -[[package]] -name = "ark-r1cs-std" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "941551ef1df4c7a401de7068758db6503598e6f01850bdb2cfdb614a1f9dbea1" -dependencies = [ - "ark-ec 0.5.0", - "ark-ff 0.5.0", - "ark-relations 0.5.1", - "ark-std 0.5.0", - "educe", - "num-bigint", - "num-integer", - "num-traits", - "tracing", -] - [[package]] name = "ark-relations" version = "0.4.0" @@ -1649,51 +1631,6 @@ dependencies = [ "half", ] -[[package]] -name = "circom-types" -version = "0.9.0" -source = "git+https://github.com/TaceoLabs/co-snarks.git#e636308efdf115149d53e05e70b157cfe5babb6c" -dependencies = [ - "ark-bn254 0.5.0", - "ark-ec 0.5.0", - "ark-ff 0.5.0", - "ark-groth16 0.5.0", - "ark-poly 0.5.0", - "ark-relations 0.5.1", - "ark-serialize 0.5.0", - "ark-std 0.5.0", - "byteorder", - "num-traits", - "rayon", - "serde", - "serde_json", - "thiserror 2.0.17", - "tracing", -] - -[[package]] -name = "circom-witness-rs" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4ffb423ecdfea516ad77c12a0a4b6e7a52b42656fe5b31df151ca2abc44545f" -dependencies = [ - "ark-bn254 0.5.0", - "ark-ff 0.5.0", - "ark-serialize 0.5.0", - "byteorder", - "cxx", - "cxx-build", - "eyre", - "hex", - "num-bigint", - "num-traits", - "postcard", - "rand 0.8.5", - "ruint", - "serde", - "serde_json", -] - [[package]] name = "clap" version = "4.5.37" @@ -1783,7 +1720,7 @@ version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -1940,20 +1877,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "cxx" -version = "1.0.180" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ecd70e33fb57b5fec1608d572bf8dc382f5385a19529056b32307a29ac329be" -dependencies = [ - "cc", - "cxxbridge-cmd", - "cxxbridge-flags", - "cxxbridge-macro", - "foldhash 0.2.0", - "link-cplusplus", -] - [[package]] name = "cxx-build" version = "1.0.158" @@ -1968,39 +1891,6 @@ dependencies = [ "syn 2.0.101", ] -[[package]] -name = "cxxbridge-cmd" -version = "1.0.180" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64320fd0856fdf2421f8404b67d41e91291cbcfa3d57457b390f0a2618ee9a68" -dependencies = [ - "clap", - "codespan-reporting", - "indexmap 2.9.0", - "proc-macro2", - "quote", - "syn 2.0.101", -] - -[[package]] -name = "cxxbridge-flags" -version = "1.0.180" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77e40964f209961217b972415a8e3a0c23299076a0b2dfe79fa8366b5e5c833e" - -[[package]] -name = "cxxbridge-macro" -version = "1.0.180" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51afdec15d8072d1b69f54f645edaf54250088a7e54c4fe493016781278136bd" -dependencies = [ - "indexmap 2.9.0", - "proc-macro2", - "quote", - "rustversion", - "syn 2.0.101", -] - [[package]] name = "darling" version = "0.20.11" @@ -2321,7 +2211,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2571,6 +2461,18 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" +[[package]] +name = "gloo-timers" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "goblin" version = "0.8.2" @@ -2582,22 +2484,6 @@ dependencies = [ "scroll", ] -[[package]] -name = "groth16" -version = "0.1.0" -source = "git+https://github.com/TaceoLabs/groth16.git#6529e47b7a9a792cf92425f2a9682ab4d4c433df" -dependencies = [ - "ark-ec 0.5.0", - "ark-ff 0.5.0", - "ark-groth16 0.5.0", - "ark-poly 0.5.0", - "ark-relations 0.5.1", - "eyre", - "num-traits", - "rayon", - "tracing", -] - [[package]] name = "group" version = "0.13.0" @@ -3235,7 +3121,6 @@ dependencies = [ "once_cell", "serdect", "sha2", - "signature", ] [[package]] @@ -3275,15 +3160,6 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" -[[package]] -name = "link-cplusplus" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f78c730aaa7d0b9336a299029ea49f9ee53b0ed06e9202e8cb7db9bae7b8c82" -dependencies = [ - "cc", -] - [[package]] name = "linux-raw-sys" version = "0.9.4" @@ -3592,30 +3468,6 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" -[[package]] -name = "oprf-client" -version = "0.1.0" -source = "git+https://github.com/worldcoin/world-id-protocol?rev=8041ac3#8041ac3403eaf3f751b0d0f1871f37b73d1b4207" -dependencies = [ - "ark-ec 0.5.0", - "eyre", - "futures", - "groth16", - "k256", - "oprf-core", - "oprf-types", - "oprf-world-types", - "oprf-zk 0.1.0 (git+https://github.com/worldcoin/world-id-protocol?rev=8041ac3)", - "rand 0.8.5", - "reqwest 0.12.22", - "serde_json", - "taceo-ark-babyjubjub", - "thiserror 1.0.69", - "tokio", - "tracing", - "uuid", -] - [[package]] name = "oprf-core" version = "0.1.0" @@ -3640,31 +3492,15 @@ dependencies = [ "zeroize", ] -[[package]] -name = "oprf-types" -version = "0.1.0" -source = "git+https://github.com/TaceoLabs/nullifier-oracle-service?rev=f71a91c#f71a91c84f3108eaa41dd2fa1bad5411c7323088" -dependencies = [ - "k256", - "oprf-core", - "oprf-zk 0.1.0 (git+https://github.com/TaceoLabs/nullifier-oracle-service?rev=f71a91c)", - "serde", - "taceo-ark-babyjubjub", - "taceo-ark-serde-compat", - "uuid", -] - [[package]] name = "oprf-world-types" version = "0.1.0" -source = "git+https://github.com/worldcoin/world-id-protocol?rev=8041ac3#8041ac3403eaf3f751b0d0f1871f37b73d1b4207" dependencies = [ "alloy", "ark-ec 0.5.0", "ark-ff 0.5.0", "k256", "oprf-core", - "oprf-zk 0.1.0 (git+https://github.com/worldcoin/world-id-protocol?rev=8041ac3)", "serde", "taceo-ark-babyjubjub", "taceo-ark-serde-compat", @@ -3672,55 +3508,6 @@ dependencies = [ "uuid", ] -[[package]] -name = "oprf-zk" -version = "0.1.0" -source = "git+https://github.com/worldcoin/world-id-protocol?rev=8041ac3#8041ac3403eaf3f751b0d0f1871f37b73d1b4207" -dependencies = [ - "ark-bn254 0.5.0", - "ark-ff 0.5.0", - "ark-groth16 0.5.0", - "circom-types", - "circom-witness-rs", - "eyre", - "groth16", - "hex", - "k256", - "rand 0.8.5", - "reqwest 0.12.22", - "ruint", - "serde", - "serde_json", - "taceo-ark-babyjubjub", - "taceo-ark-serde-compat", - "thiserror 1.0.69", - "tracing", -] - -[[package]] -name = "oprf-zk" -version = "0.1.0" -source = "git+https://github.com/TaceoLabs/nullifier-oracle-service?rev=f71a91c#f71a91c84f3108eaa41dd2fa1bad5411c7323088" -dependencies = [ - "ark-bn254 0.5.0", - "ark-ff 0.5.0", - "ark-groth16 0.5.0", - "circom-types", - "eyre", - "groth16", - "hex", - "k256", - "rand 0.8.5", - "reqwest 0.12.22", - "serde", - "serde_json", - "taceo-ark-babyjubjub", - "taceo-ark-serde-compat", - "thiserror 2.0.17", - "tracing", - "witness", -] - [[package]] name = "owo-colors" version = "4.2.0" @@ -4006,7 +3793,7 @@ dependencies = [ "once_cell", "socket2 0.5.9", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -4356,7 +4143,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -4810,6 +4597,17 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-wasm-bindgen" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + [[package]] name = "serde_core" version = "1.0.228" @@ -4940,15 +4738,6 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" -[[package]] -name = "signal-hook-registry" -version = "1.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" -dependencies = [ - "libc", -] - [[package]] name = "signature" version = "2.2.0" @@ -5249,7 +5038,7 @@ dependencies = [ "getrandom 0.3.2", "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -5407,7 +5196,6 @@ dependencies = [ "mio", "parking_lot", "pin-project-lite", - "signal-hook-registry", "slab", "socket2 0.6.0", "tokio-macros", @@ -5696,7 +5484,6 @@ checksum = "4dcd1d240101ba3b9d7532ae86d9cb64d9a7ff63e13a2b7b9e94a32a601d8233" dependencies = [ "anyhow", "camino", - "cargo_metadata", "clap", "uniffi_bindgen", "uniffi_build", @@ -5938,6 +5725,20 @@ dependencies = [ "world-id-core", ] +[[package]] +name = "walletkit-wasm" +version = "0.1.8" +dependencies = [ + "js-sys", + "serde", + "serde-wasm-bindgen", + "serde_json", + "walletkit-core", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "want" version = "0.3.1" @@ -6112,7 +5913,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -6365,32 +6166,9 @@ dependencies = [ "bitflags 2.9.0", ] -[[package]] -name = "witness" -version = "0.2.0" -source = "git+https://github.com/TaceoLabs/circom-witness-rs.git?rev=c8ce390#c8ce39065c158a7d58414212e3cc2ac671b1c088" -dependencies = [ - "ark-bn254 0.5.0", - "ark-ff 0.5.0", - "ark-serialize 0.5.0", - "byteorder", - "cxx", - "cxx-build", - "eyre", - "hex", - "num-bigint", - "num-traits", - "postcard", - "rand 0.8.5", - "ruint", - "serde", - "serde_json", -] - [[package]] name = "world-id-core" version = "0.1.0" -source = "git+https://github.com/worldcoin/world-id-protocol?rev=8041ac3#8041ac3403eaf3f751b0d0f1871f37b73d1b4207" dependencies = [ "alloy", "anyhow", @@ -6400,14 +6178,12 @@ dependencies = [ "ark-serialize 0.5.0", "chrono", "ciborium", - "circom-types", "eyre", - "groth16", + "getrandom 0.2.16", + "gloo-timers", "hex", - "oprf-client", - "oprf-types", + "k256", "oprf-world-types", - "oprf-zk 0.1.0 (git+https://github.com/worldcoin/world-id-protocol?rev=8041ac3)", "rand 0.8.5", "reqwest 0.12.22", "ruint", @@ -6429,7 +6205,6 @@ dependencies = [ [[package]] name = "world-id-primitives" version = "0.1.0" -source = "git+https://github.com/worldcoin/world-id-protocol?rev=8041ac3#8041ac3403eaf3f751b0d0f1871f37b73d1b4207" dependencies = [ "alloy-primitives", "ark-bn254 0.5.0", diff --git a/Cargo.toml b/Cargo.toml index 953d855f5..f0530a1ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["uniffi-bindgen","walletkit-core", "walletkit"] +members = ["uniffi-bindgen", "walletkit-core", "walletkit", "wasm"] resolver = "2" [workspace.package] @@ -8,8 +8,8 @@ license = "MIT" edition = "2021" authors = ["World Contributors"] readme = "./README.md" -homepage = "https://docs.world.org" # TODO: Update to specific WalletKit page -rust-version = "1.86" # MSRV +homepage = "https://docs.world.org" # TODO: Update to specific WalletKit page +rust-version = "1.86" # MSRV repository = "https://github.com/worldcoin/walletkit" exclude = ["tests/", "uniffi-bindgen/"] keywords = ["ZKP", "WorldID", "World", "Identity", "Semaphore"] @@ -17,11 +17,21 @@ categories = ["api-bindings", "cryptography::cryptocurrencies"] [workspace.dependencies] -alloy-core = { version = "1", default-features = false, features = ["sol-types"] } +alloy-core = { version = "1", default-features = false, features = [ + "sol-types", +] } alloy-primitives = { version = "1", default-features = false } walletkit-core = { version = "0.1.8", path = "walletkit-core", default-features = false } -uniffi = { version = "0.29", features = ["build", "tokio"] } -world-id-core = { git = "https://github.com/worldcoin/world-id-protocol", rev = "8041ac3", default-features = false, features = ["authenticator"] } +uniffi = { version = "0.29", default-features = false } +world-id-core = { path = "../world-id-protocol/crates/core", default-features = false, features = [ + "authenticator", +] } +wasm-bindgen = "0.2" +wasm-bindgen-futures = "0.4" +js-sys = "0.3" +web-sys = { version = "0.3", features = [] } +serde = { version = "1", features = ["derive"] } +serde_json = "1" [profile.release] opt-level = 'z' # Optimize for size. diff --git a/walletkit-core/Cargo.toml b/walletkit-core/Cargo.toml index afe51674d..372db5590 100644 --- a/walletkit-core/Cargo.toml +++ b/walletkit-core/Cargo.toml @@ -25,28 +25,48 @@ alloy-core = { workspace = true } alloy-primitives = { workspace = true } hex = "0.4" log = "0.4" -reqwest = { version = "0.12", default-features = false, features = [ - "json", - "brotli", - "rustls-tls", -] } ruint = { version = "1.17", default-features = false, features = [ "alloc", "ark-ff-04", ] } secrecy = "0.10" -semaphore-rs = { version = "0.5" } +# semaphore-rs uses mmap which doesn't compile on wasm; make it optional and gate on semaphore feature. +semaphore-rs = { version = "0.5", optional = true } serde = "1" serde_json = "1" strum = { version = "0.27", features = ["derive"] } subtle = "2" thiserror = "2" -tokio = { version = "1", features = ["sync"] } -uniffi = { workspace = true, features = ["build", "tokio"] } world-id-core = { workspace = true, optional = true } +[target.'cfg(target_arch = "wasm32")'.dependencies] +reqwest = { version = "0.12", default-features = false, features = ["json"] } +uniffi = { workspace = true, default-features = false } +tokio = { version = "1", default-features = false, features = ["sync"] } + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +reqwest = { version = "0.12", default-features = false, features = [ + "json", + "brotli", + "rustls-tls", +] } +uniffi = { workspace = true, features = ["build", "tokio"] } +tokio = { version = "1", features = [ + "sync", + "macros", + "rt", + "rt-multi-thread", + "time", +] } + [dev-dependencies] -alloy = { version = "1", default-features = false, features = ["getrandom", "json", "contract", "node-bindings", "signer-local"] } +alloy = { version = "1", default-features = false, features = [ + "getrandom", + "json", + "contract", + "node-bindings", + "signer-local", +] } chrono = "0.4.41" dotenvy = "0.15.7" mockito = "1.6" @@ -59,7 +79,8 @@ rand = "0.8" default = ["common-apps", "semaphore", "v4"] common-apps = [] http-tests = [] -semaphore = ["semaphore-rs/depth_30"] +# Semaphore proof generation pulls mmap-rs (not wasm-compatible); require explicit opt-in. +semaphore = ["dep:semaphore-rs", "semaphore-rs/depth_30"] v4 = ["world-id-core"] # Before conventions were introduced for external nullifiers with `app_id` & `action`, raw field elements were used. diff --git a/walletkit-core/src/authenticator.rs b/walletkit-core/src/authenticator.rs index 6ac83bae8..6d4712284 100644 --- a/walletkit-core/src/authenticator.rs +++ b/walletkit-core/src/authenticator.rs @@ -9,10 +9,11 @@ use crate::{ }; /// The Authenticator is the main component with which users interact with the World ID Protocol. -#[derive(Debug, uniffi::Object)] +#[derive(Debug)] +#[cfg_attr(not(target_arch = "wasm32"), derive(uniffi::Object))] pub struct Authenticator(CoreAuthenticator); -#[uniffi::export(async_runtime = "tokio")] +#[cfg_attr(not(target_arch = "wasm32"), uniffi::export(async_runtime = "tokio"))] impl Authenticator { /// Initializes a new Authenticator from a seed and with SDK defaults. /// diff --git a/walletkit-core/src/credential_type.rs b/walletkit-core/src/credential_type.rs index 556bcadf6..ec5096b58 100644 --- a/walletkit-core/src/credential_type.rs +++ b/walletkit-core/src/credential_type.rs @@ -20,8 +20,8 @@ use crate::Environment; Display, Serialize, Deserialize, - uniffi::Enum, )] +#[cfg_attr(not(target_arch = "wasm32"), derive(uniffi::Enum))] #[strum(serialize_all = "snake_case")] #[serde(rename_all = "snake_case")] pub enum CredentialType { diff --git a/walletkit-core/src/error.rs b/walletkit-core/src/error.rs index bd08db5c2..0c873d9b3 100644 --- a/walletkit-core/src/error.rs +++ b/walletkit-core/src/error.rs @@ -4,7 +4,8 @@ use thiserror::Error; use world_id_core::AuthenticatorError; /// Error outputs from `WalletKit` -#[derive(Debug, Error, uniffi::Error)] +#[derive(Debug, Error)] +#[cfg_attr(not(target_arch = "wasm32"), derive(uniffi::Error))] pub enum WalletKitError { /// Invalid input provided (e.g., incorrect length, format, etc.) #[error("invalid_input_{attribute}")] @@ -99,6 +100,7 @@ impl From for WalletKitError { } } +#[cfg(feature = "semaphore")] impl From for WalletKitError { fn from(error: semaphore_rs::protocol::ProofError) -> Self { Self::ProofGeneration { diff --git a/walletkit-core/src/lib.rs b/walletkit-core/src/lib.rs index 759a97d59..39c3e5390 100644 --- a/walletkit-core/src/lib.rs +++ b/walletkit-core/src/lib.rs @@ -26,7 +26,8 @@ use strum::EnumString; /// Each environment uses different sources of truth for the World ID credentials. /// /// More information on testing for the World ID Protocol can be found in: `https://docs.world.org/world-id/quick-start/testing` -#[derive(Debug, Clone, PartialEq, Eq, EnumString, uniffi::Enum)] +#[derive(Debug, Clone, PartialEq, Eq, EnumString)] +#[cfg_attr(not(target_arch = "wasm32"), derive(uniffi::Enum))] #[strum(serialize_all = "lowercase")] pub enum Environment { /// For testing purposes ONLY. @@ -58,13 +59,15 @@ pub use authenticator::Authenticator; pub(crate) mod defaults; //////////////////////////////////////////////////////////////////////////////// -// Legacy modules +// Legacy modules (require semaphore feature for proof generation) //////////////////////////////////////////////////////////////////////////////// /// Contains all components to interact and use a World ID +#[cfg(feature = "semaphore")] pub mod world_id; /// This module handles World ID proof generation +#[cfg(feature = "semaphore")] pub mod proof; /// This module exposes helper functions to interact with common apps & contracts related to the World ID Protocol. @@ -75,7 +78,10 @@ pub mod common_apps; // Private modules //////////////////////////////////////////////////////////////////////////////// +#[cfg(feature = "semaphore")] mod merkle_tree; +#[cfg(feature = "semaphore")] mod request; +#[cfg(not(target_arch = "wasm32"))] uniffi::setup_scaffolding!("walletkit_core"); diff --git a/walletkit-core/src/logger.rs b/walletkit-core/src/logger.rs index 45d3e1883..ab57d1a10 100644 --- a/walletkit-core/src/logger.rs +++ b/walletkit-core/src/logger.rs @@ -42,7 +42,7 @@ use std::sync::{Arc, OnceLock}; /// ```swift /// setupWalletKitLogger() // Call this only once!!! /// ``` -#[uniffi::export(with_foreign)] +#[cfg_attr(not(target_arch = "wasm32"), uniffi::export(with_foreign))] pub trait Logger: Sync + Send { /// Logs a message at the specified log level. /// @@ -56,7 +56,8 @@ pub trait Logger: Sync + Send { /// Enumeration of possible log levels. /// /// This enum represents the severity levels that can be used when logging messages. -#[derive(Debug, Clone, uniffi::Enum)] +#[derive(Debug, Clone)] +#[cfg_attr(not(target_arch = "wasm32"), derive(uniffi::Enum))] pub enum LogLevel { /// Designates very low priority, often extremely detailed messages. Trace, @@ -170,7 +171,7 @@ static LOGGER_INSTANCE: OnceLock> = OnceLock::new(); /// # Note /// /// If the logger has already been set, this function will print a message and do nothing. -#[uniffi::export] +#[cfg_attr(not(target_arch = "wasm32"), uniffi::export)] pub fn set_logger(logger: Arc) { match LOGGER_INSTANCE.set(logger) { Ok(()) => (), diff --git a/walletkit-core/src/u256.rs b/walletkit-core/src/u256.rs index 7293a91d9..4e419f538 100644 --- a/walletkit-core/src/u256.rs +++ b/walletkit-core/src/u256.rs @@ -13,10 +13,11 @@ use serde::{Deserialize, Serialize}; /// Particularly, when sending proof inputs/outputs as JSON on HTTP requests, the values SHOULD /// be represented as padded hex strings from Big Endian bytes. #[allow(clippy::module_name_repetitions)] -#[derive(Debug, PartialEq, Eq, Clone, Copy, uniffi::Object)] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(not(target_arch = "wasm32"), derive(uniffi::Object))] pub struct U256Wrapper(pub U256); -#[uniffi::export] +#[cfg_attr(not(target_arch = "wasm32"), uniffi::export)] impl U256Wrapper { /// Outputs a hex string representation of the `U256` value padded to 32 bytes (plus two bytes for the `0x` prefix). #[must_use] @@ -48,7 +49,7 @@ impl U256Wrapper { /// /// Logically this will only support values up to 64 bits. For larger values a different initialization should be used. #[must_use] - #[uniffi::constructor] + #[cfg_attr(not(target_arch = "wasm32"), uniffi::constructor)] pub fn from_u64(value: u64) -> Self { Self(U256::from(value)) } @@ -57,7 +58,7 @@ impl U256Wrapper { /// /// Logically this will only support values up to 32 bits. For larger values a different initialization should be used. #[must_use] - #[uniffi::constructor] + #[cfg_attr(not(target_arch = "wasm32"), uniffi::constructor)] pub fn from_u32(value: u32) -> Self { Self(U256::from(value)) } @@ -69,7 +70,7 @@ impl U256Wrapper { /// # Errors /// /// Will return an `Error::InvalidNumber` if the input is not a valid `U256` value. - #[uniffi::constructor] + #[cfg_attr(not(target_arch = "wasm32"), uniffi::constructor)] pub fn from_limbs(limbs: Vec) -> Result { let limbs = limbs .try_into() diff --git a/wasm/Cargo.toml b/wasm/Cargo.toml new file mode 100644 index 000000000..2ae2f229a --- /dev/null +++ b/wasm/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "walletkit-wasm" +version.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +authors.workspace = true +description = "WASM bindings for WalletKit" + +[package.metadata.wasm-pack.profile.release] +wasm-opt = false + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +# Disable semaphore to avoid mmap-rs (not wasm-compatible); only enable v4 authenticator. +walletkit-core = { path = "../walletkit-core", default-features = false, features = [ + "v4", +] } +wasm-bindgen = { workspace = true } +wasm-bindgen-futures = { workspace = true } +js-sys = { workspace = true } +web-sys = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +serde-wasm-bindgen = "0.6" diff --git a/wasm/README.md b/wasm/README.md new file mode 100644 index 000000000..504a07cda --- /dev/null +++ b/wasm/README.md @@ -0,0 +1,32 @@ +# WalletKit WASM bindings + +## Builds + +Run the helper scripts from the workspace root (they forward additional arguments to `wasm-pack`): + +```bash +./walletkit/wasm/scripts/build-web.sh # Builds for browsers +./walletkit/wasm/scripts/build-node.sh # Builds for Node.js (ESM + CJS) +``` + +Both scripts output artifacts to `walletkit/wasm/pkg/`. + +## Usage sketch + +```ts +import init, { Authenticator, Environment } from "./pkg/walletkit_wasm.js"; +import { readFile } from "node:fs/promises"; + +const wasm = await readFile("./pkg/walletkit_wasm_bg.wasm"); +await init(wasm); + +const seed = new Uint8Array(32); // placeholder seed +const authenticator = await Authenticator.initWithDefaults( + seed, + "https://rpc.example.com", + Environment.Staging() +); + +console.log(authenticator.onchainAddress()); +console.log(await authenticator.getPackedAccountIndexRemote()); // -> bigint +``` diff --git a/wasm/scripts/build-node.sh b/wasm/scripts/build-node.sh new file mode 100755 index 000000000..ee5e9aa45 --- /dev/null +++ b/wasm/scripts/build-node.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +CRATE_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" + +wasm-pack build "${CRATE_DIR}" --release --target nodejs "$@" diff --git a/wasm/scripts/build-web.sh b/wasm/scripts/build-web.sh new file mode 100755 index 000000000..3cbcfe8e4 --- /dev/null +++ b/wasm/scripts/build-web.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +CRATE_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" + +wasm-pack build "${CRATE_DIR}" --release --target web "$@" diff --git a/wasm/src/lib.rs b/wasm/src/lib.rs new file mode 100644 index 000000000..b274b58e1 --- /dev/null +++ b/wasm/src/lib.rs @@ -0,0 +1,228 @@ +//! WebAssembly bindings for `walletkit_core`. +//! +//! This crate exposes a minimal surface of the `Authenticator` API for usage in +//! browser and Node (CJS / ESM) environments through `wasm-bindgen`. + +#![deny(clippy::all, clippy::pedantic, clippy::nursery)] +#![allow(clippy::module_name_repetitions)] + +use std::sync::Arc; + +use js_sys::{Function, Promise, Reflect}; +use wasm_bindgen::prelude::*; +use wasm_bindgen::JsCast; +use wasm_bindgen_futures::future_to_promise; + +use walletkit_core::error::WalletKitError; +use walletkit_core::{ + Authenticator as CoreAuthenticator, Environment as CoreEnvironment, +}; +const ENV_STAGING: &str = "staging"; +const ENV_PRODUCTION: &str = "production"; + +#[wasm_bindgen] +pub struct Authenticator(Arc); + +#[wasm_bindgen] +#[allow(clippy::missing_const_for_fn)] +impl Authenticator { + /// Initializes a new authenticator using SDK defaults. + /// + /// # Errors + /// Returns a rejected promise when initialization fails or input parsing fails. + #[wasm_bindgen(js_name = initWithDefaults)] + pub fn init_with_defaults( + seed: Vec, + rpc_url: String, + environment: Environment, + ) -> Promise { + future_to_promise(async move { + let environment = environment.into_core(); + CoreAuthenticator::init_with_defaults(&seed, rpc_url, &environment) + .await + .map(|auth| Self(Arc::new(auth))) + .map(JsValue::from) + .map_err(|err| walletkit_error_to_jsvalue(&err)) + }) + } + + /// Initializes a new authenticator using a JSON config. + /// + /// # Errors + /// Returns a rejected promise when initialization fails or input parsing fails. + #[wasm_bindgen(js_name = init)] + pub fn init(seed: Vec, config_json: String) -> Promise { + future_to_promise(async move { + CoreAuthenticator::init(&seed, &config_json) + .await + .map(|auth| Self(Arc::new(auth))) + .map(JsValue::from) + .map_err(|err| walletkit_error_to_jsvalue(&err)) + }) + } + + /// Initializes (or creates) an authenticator using SDK defaults. + /// + /// # Errors + /// Returns a rejected promise when initialization fails or input parsing fails. + #[wasm_bindgen(js_name = initOrCreateBlockingWithDefaults)] + pub fn init_or_create_blocking_with_defaults( + seed: Vec, + rpc_url: String, + environment: Environment, + recovery_address: Option, + ) -> Promise { + future_to_promise(async move { + let environment = environment.into_core(); + CoreAuthenticator::init_or_create_blocking_with_defaults( + &seed, + rpc_url, + &environment, + recovery_address, + ) + .await + .map(|auth| Self(Arc::new(auth))) + .map(JsValue::from) + .map_err(|err| walletkit_error_to_jsvalue(&err)) + }) + } + + /// Initializes (or creates) an authenticator using a JSON config. + /// + /// # Errors + /// Returns a rejected promise when initialization fails or input parsing fails. + #[wasm_bindgen(js_name = initOrCreateBlocking)] + pub fn init_or_create_blocking( + seed: Vec, + config_json: String, + recovery_address: Option, + ) -> Promise { + future_to_promise(async move { + CoreAuthenticator::init_or_create_blocking( + &seed, + &config_json, + recovery_address, + ) + .await + .map(|auth| Self(Arc::new(auth))) + .map(JsValue::from) + .map_err(|err| walletkit_error_to_jsvalue(&err)) + }) + } + + /// Returns the packed account index for the holder's World ID. + /// + /// # Errors + /// Returns a stringified error if the value cannot be converted to a `BigInt`. + #[wasm_bindgen(js_name = accountId)] + pub fn account_id(&self) -> Result { + let hex = self.0.account_id().to_hex_string(); + big_int_from_hex(&hex) + } + + /// Returns the on-chain address as a checksum-encoded string. + #[wasm_bindgen(js_name = onchainAddress)] + #[must_use] + pub fn onchain_address(&self) -> String { + self.0.onchain_address() + } + + /// Retrieves the packed account index from the registry. + /// + /// # Errors + /// Returns a rejected promise if the remote call fails. + #[wasm_bindgen(js_name = getPackedAccountIndexRemote)] + pub fn get_packed_account_index_remote(&self) -> Promise { + let authenticator = Arc::clone(&self.0); + future_to_promise(async move { + match authenticator.get_packed_account_index_remote().await { + Ok(value) => big_int_from_hex(&value.to_hex_string()), + Err(err) => Err(walletkit_error_to_jsvalue(&err)), + } + }) + } +} + +#[wasm_bindgen] +pub struct Environment(CoreEnvironment); + +#[wasm_bindgen] +#[allow(clippy::missing_const_for_fn)] +impl Environment { + #[must_use] + #[wasm_bindgen(js_name = Staging)] + pub fn staging() -> Self { + Self(CoreEnvironment::Staging) + } + + #[must_use] + #[wasm_bindgen(js_name = Production)] + pub fn production() -> Self { + Self(CoreEnvironment::Production) + } + + #[must_use] + #[wasm_bindgen(js_name = toString)] + pub fn to_string_js(&self) -> String { + match self.0 { + CoreEnvironment::Staging => ENV_STAGING, + CoreEnvironment::Production => ENV_PRODUCTION, + } + .to_string() + } +} + +#[allow(clippy::missing_const_for_fn)] +impl Environment { + fn into_core(self) -> CoreEnvironment { + self.0 + } +} + +fn big_int_from_hex(hex: &str) -> Result { + let global = js_sys::global(); + let bigint_value = Reflect::get(&global, &JsValue::from_str("BigInt"))? + .dyn_into::()? + .call1(&JsValue::undefined(), &JsValue::from_str(hex))?; + Ok(bigint_value) +} + +fn walletkit_error_to_jsvalue(error: &WalletKitError) -> JsValue { + JsValue::from_str(&error.to_string()) +} + +#[wasm_bindgen(typescript_custom_section)] +const TYPESCRIPT_DEFS: &str = r#" +export class Authenticator { + static initWithDefaults( + seed: Uint8Array, + rpcUrl: string, + environment: Environment + ): Promise; + + static init(seed: Uint8Array, configJson: string): Promise; + + static initOrCreateBlockingWithDefaults( + seed: Uint8Array, + rpcUrl: string, + environment: Environment, + recoveryAddress?: string + ): Promise; + + static initOrCreateBlocking( + seed: Uint8Array, + configJson: string, + recoveryAddress?: string + ): Promise; + + accountId(): bigint; + onchainAddress(): string; + getPackedAccountIndexRemote(): Promise; +} + +export class Environment { + static Staging(): Environment; + static Production(): Environment; + toString(): "staging" | "production"; +} +"#; diff --git a/wasm/test-node.mjs b/wasm/test-node.mjs new file mode 100755 index 000000000..baedab729 --- /dev/null +++ b/wasm/test-node.mjs @@ -0,0 +1,48 @@ +#!/usr/bin/env node +/** + * Minimal smoke test for the WalletKit WASM bindings in a Node (ESM) runtime. + * + * Usage: + * node walletkit/wasm/test-node.mjs + * + * Ensure `wasm-pack` artifacts exist in `walletkit/wasm/pkg` first. + */ + +import { readFile } from "node:fs/promises"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const pkgDir = join(__dirname, "pkg"); + +// ESM: call the default export (init function) to load the WASM module. +import init, { Authenticator, Environment } from "./pkg/walletkit_wasm.js"; +const wasmBuffer = await readFile(join(pkgDir, "walletkit_wasm_bg.wasm")); +await init(wasmBuffer); +console.log("✓ WASM module loaded\n"); + +console.log("Environment constants:"); +console.log(" staging:", Environment.Staging().toString()); +console.log(" production:", Environment.Production().toString()); +console.log(""); + +const seed = new Uint8Array(32); + +console.log("Invoking initWithDefaults (expected to fail without a real RPC/Gateway)..."); +try { + await Authenticator.initWithDefaults( + seed, + "https://example.invalid.rpc", + Environment.Staging(), + ); + console.log("Unexpected success; provide a valid RPC endpoint to exercise getters."); +} catch (error) { + console.log("Received error (as string):", error.toString()); + console.log("✓ Error surfaced correctly\n"); +} + +console.log("You can now provide valid configuration details to fully exercise getters:"); +console.log(" - accountId(): bigint"); +console.log(" - onchainAddress(): string"); +console.log(" - getPackedAccountIndexRemote(): Promise"); +