diff --git a/Cargo.lock b/Cargo.lock index 4d9e708a0..7e97f0064 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1553,6 +1553,7 @@ dependencies = [ "criterion", "ff", "fpe", + "getrandom", "getset", "group", "halo2_gadgets", @@ -1579,6 +1580,7 @@ dependencies = [ "subtle", "tracing", "visibility", + "wasm-bindgen", "zcash_note_encryption", "zcash_spec", "zip32", diff --git a/Cargo.toml b/Cargo.toml index 36cb32705..eeb20c20e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,6 +48,7 @@ incrementalmerkletree = "0.8.1" zcash_spec = "0.2.1" zip32 = { version = "0.2.0", default-features = false } visibility = "0.1.1" +wasm-bindgen = { version = "0.2", optional = true } # Circuit halo2_gadgets = { git = "https://github.com/QED-it/halo2", branch = "zsa1", optional = true } @@ -79,17 +80,22 @@ inferno = { version = "0.11", default-features = false, features = ["multithread #clap = "=4.2.0" #Pinned: Used by inferno. Later version requires Rust 1.70 pprof = { version = "0.11", features = ["criterion", "flamegraph"] } +[target.'cfg(target_arch = "wasm32")'.dependencies] +getrandom = { version = "0.2", default-features = false, features = ["js"] } + [lib] +crate-type = ["cdylib", "rlib"] bench = false [features] -default = ["circuit", "multicore", "std"] +default = ["circuit", "std"] # multicore is opt-in; wasm builds disable it to avoid atomics issues std = ["core2/std", "group/wnaf-memuse", "reddsa/std"] circuit = ["dep:halo2_gadgets", "dep:halo2_proofs", "std"] unstable-frost = [] multicore = ["halo2_proofs?/multicore"] dev-graph = ["halo2_proofs?/dev-graph", "image", "plotters"] test-dependencies = ["proptest", "rand/std"] +wasm = ["wasm-bindgen"] [[bench]] name = "note_decryption" diff --git a/pkg/package.json b/pkg/package.json new file mode 100644 index 000000000..19e1023ae --- /dev/null +++ b/pkg/package.json @@ -0,0 +1,38 @@ +{ + "name": "orchard", + "type": "module", + "collaborators": [ + "Sean Bowe ", + "Jack Grigg ", + "Daira-Emma Hopwood ", + "Ying Tong Lai", + "Kris Nuttycombe " + ], + "description": "The Orchard shielded transaction protocol", + "version": "0.11.0", + "license": "MIT OR Apache-2.0", + "repository": { + "type": "git", + "url": "https://github.com/zcash/orchard" + }, + "files": [ + "orchard_bg.wasm", + "orchard.js", + "orchard_bg.js", + "orchard.d.ts", + "LICENSE-APACHE", + "LICENSE-MIT" + ], + "main": "orchard.js", + "types": "orchard.d.ts", + "sideEffects": [ + "./orchard.js", + "./snippets/*" + ], + "keywords": [ + "zcash" + ], + "dependencies": { + "bip39": "^3.0.4" + } +} \ No newline at end of file diff --git a/pkg/test-derive.js b/pkg/test-derive.js new file mode 100644 index 000000000..c099e33f7 --- /dev/null +++ b/pkg/test-derive.js @@ -0,0 +1,65 @@ +#!/usr/bin/env node +/* + * Minimal Node.js test for the full Orchard key/address derivation pipeline using a BIP39 mnemonic. + * + * Steps: + * 1) derive_spending_key(seed: Uint8Array, coinType: number, account: number) -> spendingKey + * 2) derive_full_viewing_key(spendingKey: Uint8Array) -> fullViewingKey + * 3) derive_address(fullViewingKey: Uint8Array, diversifierIndex: number) -> rawAddress + * + * Prerequisites: + * npm install bip39 + * node --experimental-wasm-modules test-derive.js + */ +import { readFile } from 'fs/promises'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import * as orchard from './orchard_bg.js'; +import bip39 from 'bip39'; + +// In ES modules, compute __dirname relative to this file +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +async function main() { + // Load and instantiate the WebAssembly module manually + // Resolve the .wasm path relative to this script's directory + const wasmPath = path.resolve(__dirname, 'orchard_bg.wasm'); + const wasmBytes = await readFile(wasmPath); + // Provide the two JS functions the .wasm expects as imports + const importObject = { + './orchard_bg.js': { + __wbindgen_string_new: orchard.__wbindgen_string_new, + __wbindgen_object_drop_ref: orchard.__wbindgen_object_drop_ref, + }, + }; + const { instance } = await WebAssembly.instantiate(wasmBytes, importObject); + orchard.__wbg_set_wasm(instance.exports); + + // Example BIP39 mnemonic (12 words) + const mnemonic = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about'; + // Derive a 64-byte seed from the mnemonic + const seedBuffer = await bip39.mnemonicToSeed(mnemonic); + const seed = new Uint8Array(seedBuffer); + + const coinType = 1; + const account = 0; + + // Derive the Orchard spending key + const spendingKey = orchard.derive_spending_key(seed, coinType, account); + console.log('Spending key (hex):', Buffer.from(spendingKey).toString('hex')); + + // Derive the full viewing key from the spending key + const fullViewingKey = orchard.derive_full_viewing_key(spendingKey); + console.log('Full viewing key (hex):', Buffer.from(fullViewingKey).toString('hex')); + + // Derive a raw Orchard payment address (using diversifier index = 0) + const diversifierIndex = 0; + const rawAddress = orchard.derive_address(fullViewingKey, diversifierIndex); + console.log('Raw address (hex):', Buffer.from(rawAddress).toString('hex')); +} + +main().catch(err => { + console.error(err); + process.exit(1); +}); \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 1a15de563..1703e0c36 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -100,3 +100,11 @@ impl Proof { Proof(bytes) } } +#[cfg(test)] +mod key_test; + +// WASM-bindgen: derive Orchard address from a spending key +#[cfg(feature = "wasm")] +mod wasm_bindings; +#[cfg(feature = "wasm")] +pub use wasm_bindings::derive_orchard_address;