From 691b9439151fb6148528c19e905abcaa1dec708a Mon Sep 17 00:00:00 2001 From: nol4lej Date: Mon, 16 Feb 2026 01:42:08 -0300 Subject: [PATCH 01/16] feat: implement snarkjs proof compression and validation in WASM --- src/wasm/snarkjs_proof.rs | 194 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 194 insertions(+) create mode 100644 src/wasm/snarkjs_proof.rs diff --git a/src/wasm/snarkjs_proof.rs b/src/wasm/snarkjs_proof.rs new file mode 100644 index 0000000..2f960d4 --- /dev/null +++ b/src/wasm/snarkjs_proof.rs @@ -0,0 +1,194 @@ +use ark_bn254::{Bn254, Fq, Fq2, G1Affine, G2Affine}; +use ark_ff::PrimeField; +use ark_groth16::Proof as ArkProof; +use ark_serialize::CanonicalSerialize; +use num_bigint::BigUint; +use wasm_bindgen::prelude::*; + +#[derive(serde::Deserialize)] +struct SnarkjsProof { + pi_a: Vec, + pi_b: Vec>, + pi_c: Vec, +} + +fn parse_fq_decimal(value: &str) -> Result { + let bigint = BigUint::parse_bytes(value.as_bytes(), 10) + .ok_or_else(|| format!("Invalid decimal field element: {value}"))?; + + let bytes = bigint.to_bytes_le(); + Ok(Fq::from_le_bytes_mod_order(&bytes)) +} + +fn validate_snarkjs_proof_structure(parsed: &SnarkjsProof) -> Result<(), String> { + if parsed.pi_a.len() < 2 { + return Err( + "Invalid snarkjs proof structure: pi_a must contain at least 2 elements".to_string(), + ); + } + if parsed.pi_b.len() < 2 || parsed.pi_b[0].len() < 2 || parsed.pi_b[1].len() < 2 { + return Err("Invalid snarkjs proof structure: pi_b must be a 2x2 matrix".to_string()); + } + if parsed.pi_c.len() < 2 { + return Err( + "Invalid snarkjs proof structure: pi_c must contain at least 2 elements".to_string(), + ); + } + + Ok(()) +} + +fn snarkjs_to_ark_proof(parsed: &SnarkjsProof) -> Result, String> { + validate_snarkjs_proof_structure(parsed)?; + + let a = G1Affine::new( + parse_fq_decimal(&parsed.pi_a[0]).map_err(|e| format!("Invalid pi_a[0]: {e}"))?, + parse_fq_decimal(&parsed.pi_a[1]).map_err(|e| format!("Invalid pi_a[1]: {e}"))?, + ); + + let b = G2Affine::new( + Fq2::new( + parse_fq_decimal(&parsed.pi_b[0][0]).map_err(|e| format!("Invalid pi_b[0][0]: {e}"))?, + parse_fq_decimal(&parsed.pi_b[0][1]).map_err(|e| format!("Invalid pi_b[0][1]: {e}"))?, + ), + Fq2::new( + parse_fq_decimal(&parsed.pi_b[1][0]).map_err(|e| format!("Invalid pi_b[1][0]: {e}"))?, + parse_fq_decimal(&parsed.pi_b[1][1]).map_err(|e| format!("Invalid pi_b[1][1]: {e}"))?, + ), + ); + + let c = G1Affine::new( + parse_fq_decimal(&parsed.pi_c[0]).map_err(|e| format!("Invalid pi_c[0]: {e}"))?, + parse_fq_decimal(&parsed.pi_c[1]).map_err(|e| format!("Invalid pi_c[1]: {e}"))?, + ); + + Ok(ArkProof:: { a, b, c }) +} + +/// Compress a snarkjs Groth16 proof into arkworks canonical compressed bytes (128 bytes) +/// +/// # Arguments +/// * `proof_json` - JSON string of snarkjs proof with `pi_a`, `pi_b`, `pi_c` +/// +/// # Returns +/// Hex string `0x...` with 128 bytes (arkworks compressed proof) +#[wasm_bindgen] +pub fn compress_snarkjs_proof_wasm(proof_json: &str) -> Result { + let parsed: SnarkjsProof = serde_json::from_str(proof_json) + .map_err(|e| JsValue::from_str(&format!("Failed to parse snarkjs proof JSON: {e}")))?; + let proof = snarkjs_to_ark_proof(&parsed).map_err(|e| JsValue::from_str(&e))?; + + let mut compressed = Vec::new(); + proof + .serialize_compressed(&mut compressed) + .map_err(|e| JsValue::from_str(&format!("Failed to serialize compressed proof: {e}")))?; + + Ok(format!("0x{}", hex::encode(compressed))) +} + +#[cfg(all(test, not(target_arch = "wasm32")))] +mod tests { + use super::*; + use ark_ec::AffineRepr; + use ark_ff::BigInteger; + + fn fq_to_decimal_string(value: Fq) -> String { + value + .into_bigint() + .to_bytes_le() + .iter() + .rev() + .fold(BigUint::from(0u8), |acc, &byte| { + (acc << 8) + BigUint::from(byte) + }) + .to_str_radix(10) + } + + fn build_valid_snarkjs_proof_json() -> String { + let a = G1Affine::generator(); + let b = G2Affine::generator(); + let c = G1Affine::generator(); + + serde_json::json!({ + "pi_a": [ + fq_to_decimal_string(a.x), + fq_to_decimal_string(a.y) + ], + "pi_b": [ + [ + fq_to_decimal_string(b.x.c0), + fq_to_decimal_string(b.x.c1) + ], + [ + fq_to_decimal_string(b.y.c0), + fq_to_decimal_string(b.y.c1) + ] + ], + "pi_c": [ + fq_to_decimal_string(c.x), + fq_to_decimal_string(c.y) + ] + }) + .to_string() + } + + #[test] + fn test_parse_fq_decimal_invalid_input() { + let result = parse_fq_decimal("not-a-number"); + assert!(result.is_err()); + assert!(result + .unwrap_err() + .contains("Invalid decimal field element: not-a-number")); + } + + #[test] + fn test_validate_snarkjs_proof_structure_rejects_short_pi_a() { + let parsed = SnarkjsProof { + pi_a: vec!["1".to_string()], + pi_b: vec![ + vec!["1".to_string(), "2".to_string()], + vec!["3".to_string(), "4".to_string()], + ], + pi_c: vec!["1".to_string(), "2".to_string()], + }; + + let result = validate_snarkjs_proof_structure(&parsed); + assert!(result.is_err()); + assert!(result + .unwrap_err() + .contains("pi_a must contain at least 2 elements")); + } + + #[test] + fn test_compress_snarkjs_proof_matches_arkworks_serialization() { + let proof_json = build_valid_snarkjs_proof_json(); + + let compressed_hex = compress_snarkjs_proof_wasm(&proof_json).unwrap(); + assert!(compressed_hex.starts_with("0x")); + + let parsed: SnarkjsProof = serde_json::from_str(&proof_json).unwrap(); + let proof = snarkjs_to_ark_proof(&parsed).unwrap(); + let mut expected_bytes = Vec::new(); + proof.serialize_compressed(&mut expected_bytes).unwrap(); + + let expected_hex = format!("0x{}", hex::encode(expected_bytes)); + assert_eq!(compressed_hex, expected_hex); + } + + #[test] + fn test_snarkjs_to_ark_proof_rejects_invalid_structure() { + let invalid_proof_json = serde_json::json!({ + "pi_a": ["1"], + "pi_b": [["1", "2"]], + "pi_c": ["3", "4"] + }) + .to_string(); + + let parsed: SnarkjsProof = serde_json::from_str(&invalid_proof_json).unwrap(); + let result = snarkjs_to_ark_proof(&parsed); + assert!(result.is_err()); + assert!(result + .unwrap_err() + .contains("Invalid snarkjs proof structure")); + } +} From d8729034d887ca74c92d48aceb9dafa43292ded3 Mon Sep 17 00:00:00 2001 From: nol4lej Date: Mon, 16 Feb 2026 01:42:14 -0300 Subject: [PATCH 02/16] feat: refactor proof generation to use decimal format and update tests --- src/wasm.rs | 109 ++++++++-------------------------------------------- 1 file changed, 16 insertions(+), 93 deletions(-) diff --git a/src/wasm.rs b/src/wasm.rs index c38b566..97dd37a 100644 --- a/src/wasm.rs +++ b/src/wasm.rs @@ -10,7 +10,11 @@ use ark_std::rand::SeedableRng; use wasm_bindgen::prelude::*; use crate::circuit::WitnessCircuit; -use crate::utils::{decimal_to_field, hex_to_field}; +use crate::utils::decimal_to_field; + +mod snarkjs_proof; + +pub use snarkjs_proof::compress_snarkjs_proof_wasm; /// Initialize panic hook for better error messages in browser. /// Only call this when running in actual WASM environment, not tests. @@ -26,90 +30,6 @@ pub fn init_panic_hook() { // No-op: panic hook only needed in WASM } -/// Generate a Groth16 proof from witness (WASM interface) -/// -/// # Arguments -/// * `num_public_signals` - Number of public signals to extract from witness (e.g., 5 for unshield/transfer, 4 for disclosure) -/// * `witness_json` - JSON array of witness values as strings -/// * `proving_key_bytes` - Serialized proving key (arkworks format) -/// -/// # Returns -/// JSON string with format: `{"proof": "0x...", "publicSignals": ["...", "..."]}` -/// -/// # Example -/// ```ignore -/// // For a circuit with 5 public signals -/// let result = generate_proof_wasm(5, witness_json, proving_key_bytes)?; -/// ``` -#[wasm_bindgen] -pub fn generate_proof_wasm( - num_public_signals: usize, - witness_json: &str, - proving_key_bytes: &[u8], -) -> Result { - // Parse witness JSON - let witness_strings: Vec = serde_json::from_str(witness_json) - .map_err(|e| JsValue::from_str(&format!("Failed to parse witness JSON: {e}")))?; - - let witness: Vec = witness_strings - .iter() - .map(|s| hex_to_field(s)) - .collect::, _>>() - .map_err(|e| JsValue::from_str(&e))?; - - // Deserialize proving key - let proving_key = ProvingKey::::deserialize_compressed(proving_key_bytes) - .map_err(|e| JsValue::from_str(&format!("Failed to deserialize proving key: {e}")))?; - - // Generate proof - let circuit = WitnessCircuit { - witness: witness.clone(), - }; - let mut rng = StdRng::from_entropy(); - let proof = Groth16::::prove(&proving_key, circuit, &mut rng) - .map_err(|e| JsValue::from_str(&format!("Failed to generate proof: {e}")))?; - - // Serialize proof (compressed 128 bytes) - let mut proof_bytes = Vec::new(); - proof - .serialize_compressed(&mut proof_bytes) - .map_err(|e| JsValue::from_str(&format!("Failed to serialize proof: {e}")))?; - - let proof_hex = format!("0x{}", hex::encode(&proof_bytes)); - - // Extract public signals - // Validate bounds - if num_public_signals == 0 { - return Err(JsValue::from_str( - "num_public_signals must be greater than 0", - )); - } - if num_public_signals >= witness.len() { - return Err(JsValue::from_str(&format!( - "num_public_signals ({}) exceeds witness length ({})", - num_public_signals, - witness.len() - ))); - } - - let public_signals: Vec = witness[1..=num_public_signals] - .iter() - .map(|f| { - let bytes = f.into_bigint().to_bytes_le(); - format!("0x{}", hex::encode(&bytes)) - }) - .collect(); - - // Return JSON output - let output = serde_json::json!({ - "proof": proof_hex, - "publicSignals": public_signals, - }); - - serde_json::to_string(&output) - .map_err(|e| JsValue::from_str(&format!("Failed to serialize output: {e}"))) -} - /// Generate a Groth16 proof from witness in decimal format (snarkjs native) /// /// This function accepts witness in the native snarkjs format (decimal strings) @@ -184,7 +104,8 @@ pub fn generate_proof_from_decimal_wasm( let public_signals: Vec = witness[1..=num_public_signals] .iter() .map(|f| { - let bytes = f.into_bigint().to_bytes_le(); + let mut bytes = f.into_bigint().to_bytes_le(); + bytes.resize(32, 0u8); format!("0x{}", hex::encode(&bytes)) }) .collect(); @@ -203,7 +124,7 @@ pub fn generate_proof_from_decimal_wasm( // // Note: These tests use conditional compilation to avoid JsValue issues in native test runner. // They validate the underlying logic (JSON parsing, data conversion, validation) without -// calling generate_proof_wasm() directly. +// calling generate_proof_from_decimal_wasm() directly. // // For complete testing instructions, see: docs/testing.md @@ -261,15 +182,17 @@ mod tests { } #[test] - fn test_witness_hex_to_field_conversion() { + fn test_witness_decimal_to_field_conversion() { let witness_json = r#"[ - "0x0100000000000000000000000000000000000000000000000000000000000000", - "0x0500000000000000000000000000000000000000000000000000000000000000" + "1", + "5" ]"#; let witness_strings: Vec = serde_json::from_str(witness_json).unwrap(); - let witness_result: Result, String> = - witness_strings.iter().map(|s| hex_to_field(s)).collect(); + let witness_result: Result, String> = witness_strings + .iter() + .map(|s| decimal_to_field(s)) + .collect(); assert!(witness_result.is_ok()); let witness = witness_result.unwrap(); @@ -313,7 +236,7 @@ mod tests { .unwrap_or(&[]) .iter() .map(|f| { - let bytes = f.into_bigint().to_bytes_le(); + let bytes = f.into_bigint().to_bytes_be(); // BE to match runtime verifier format!("0x{}", hex::encode(&bytes)) }) .collect(); From 9464c18b51ef1fa4b6ab0a6c1d219eb29117aed7 Mon Sep 17 00:00:00 2001 From: nol4lej Date: Mon, 16 Feb 2026 01:42:19 -0300 Subject: [PATCH 03/16] feat: update WASM exports to include proof compression function --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 69d4b84..13f4479 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,4 +24,4 @@ pub use utils::{decimal_to_field, hex_to_field}; // Re-export WASM functions when feature is enabled #[cfg(feature = "wasm")] -pub use wasm::{generate_proof_from_decimal_wasm, generate_proof_wasm, init_panic_hook}; +pub use wasm::{compress_snarkjs_proof_wasm, generate_proof_from_decimal_wasm, init_panic_hook}; From a6fe0e115590939b6669570bb24a3527088bd267 Mon Sep 17 00:00:00 2001 From: nol4lej Date: Mon, 16 Feb 2026 01:42:31 -0300 Subject: [PATCH 04/16] feat: add README.md for Groth16 proof generator with usage examples and API documentation --- npm/README.md | 124 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 npm/README.md diff --git a/npm/README.md b/npm/README.md new file mode 100644 index 0000000..d6db5db --- /dev/null +++ b/npm/README.md @@ -0,0 +1,124 @@ +# @orbinum/groth16-proofs + +> High-performance Groth16 proof generator using arkworks for Orbinum privacy protocol + +[![npm version](https://img.shields.io/npm/v/@orbinum/groth16-proofs.svg)](https://www.npmjs.com/package/@orbinum/groth16-proofs) +[![License](https://img.shields.io/badge/license-Apache--2.0%20OR%20GPL--3.0-blue)](https://github.com/orbinum/groth16-proofs/blob/main/LICENSE-APACHE2) + +WebAssembly bindings for efficient **Groth16 zero-knowledge proof generation** using arkworks. + +## πŸš€ Installation + +```bash +npm install @orbinum/groth16-proofs +``` + +## πŸ“– Usage + +### Basic Example + +```typescript +import * as groth16 from '@orbinum/groth16-proofs'; + +// Initialize WASM module +await groth16.default(); +groth16.init_panic_hook(); + +// Generate proof from decimal witness (snarkjs native format) +const result = groth16.generate_proof_from_decimal_wasm( + numPublicSignals, + JSON.stringify(witnessArray), + provingKeyBytes +); + +const { proof, publicSignals } = JSON.parse(result); +``` + +### Node.js + +```typescript +import * as groth16 from '@orbinum/groth16-proofs'; +import { readFileSync } from 'fs'; +import { fileURLToPath } from 'url'; +import { dirname, join } from 'path'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +// Load WASM manually in Node.js +const wasmPath = join( + __dirname, + 'node_modules/@orbinum/groth16-proofs/groth16_proofs_bg.wasm' +); +const wasmBytes = readFileSync(wasmPath); + +await groth16.default({ module: wasmBytes }); +groth16.init_panic_hook(); +``` + +### Browser (with Bundlers) + +```typescript +import * as groth16 from '@orbinum/groth16-proofs'; + +// Automatic WASM loading +await groth16.default(); +groth16.init_panic_hook(); +``` + +## πŸ”§ API + +### `generate_proof_from_decimal_wasm(numPublicSignals, witnessJson, provingKeyBytes)` + +Generate Groth16 proof from decimal witness (snarkjs native format - **recommended**). + +**Parameters:** +- `numPublicSignals: number` - Number of public signals +- `witnessJson: string` - JSON stringified witness array (decimal strings) +- `provingKeyBytes: Uint8Array` - Proving key in arkworks format + +**Returns:** `string` - JSON with `{ proof: string, publicSignals: string[] }` + +### `compress_snarkjs_proof_wasm(proofJson)` + +Convert a snarkjs Groth16 proof JSON (`pi_a`, `pi_b`, `pi_c`) into arkworks +canonical compressed proof bytes. + +**Parameters:** +- `proofJson: string` - JSON stringified snarkjs proof + +**Returns:** `string` - Hex string (`0x...`) with 128-byte compressed Groth16 proof + +### `init_panic_hook()` + +Initialize panic hook for better error messages. + +### `default(input?)` + +Initialize WASM module. + +**Parameters:** +- `input?: { module: BufferSource }` - Optional WASM bytes (Node.js) + +## πŸ”— Related Packages + +- [@orbinum/proof-generator](https://www.npmjs.com/package/@orbinum/proof-generator) - High-level proof orchestrator +- [groth16-proofs](https://crates.io/crates/groth16-proofs) - Rust crate (native) + +## πŸ“š Documentation + +Full documentation: https://github.com/orbinum/groth16-proofs + +## πŸ“„ License + +Dual-licensed under Apache-2.0 OR GPL-3.0-or-later + +## πŸ”’ Security + +- Deterministic execution +- No network requests +- No local storage access +- Fully auditable (Rust source) + +## πŸ› Issues + +Report at: https://github.com/orbinum/groth16-proofs/issues From ffbf1f6555d42b14120917ef68d61f3875f97cc7 Mon Sep 17 00:00:00 2001 From: nol4lej Date: Mon, 16 Feb 2026 01:42:37 -0300 Subject: [PATCH 05/16] feat: add package.json.template for Groth16 proof generator configuration --- npm/package.json.template | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 npm/package.json.template diff --git a/npm/package.json.template b/npm/package.json.template new file mode 100644 index 0000000..9335393 --- /dev/null +++ b/npm/package.json.template @@ -0,0 +1,39 @@ +{ + "name": "@orbinum/groth16-proofs", + "version": "__VERSION__", + "description": "High-performance Groth16 proof generator using arkworks for Orbinum privacy protocol", + "type": "module", + "main": "groth16_proofs.js", + "types": "groth16_proofs.d.ts", + "files": [ + "groth16_proofs_bg.wasm", + "groth16_proofs.js", + "groth16_proofs.d.ts", + "LICENSE-APACHE2", + "LICENSE-GPL3", + "README.md" + ], + "keywords": [ + "zk-snark", + "groth16", + "proof-generation", + "arkworks", + "privacy" + ], + "author": "Orbinum", + "license": "Apache-2.0 OR GPL-3.0-or-later", + "repository": { + "type": "git", + "url": "git+https://github.com/orbinum/groth16-proofs.git" + }, + "homepage": "https://github.com/orbinum/groth16-proofs", + "publishConfig": { + "access": "public" + }, + "sideEffects": [ + "./snippets/*" + ], + "engines": { + "node": ">=18.0.0" + } +} From 80dbacbc1cc1965d6195720c61d197e0ac71fd83 Mon Sep 17 00:00:00 2001 From: nol4lej Date: Mon, 16 Feb 2026 01:42:41 -0300 Subject: [PATCH 06/16] feat: update witness formats documentation to clarify decimal format usage for WASM proof generation --- docs/witness-formats.md | 46 ++++++++++++----------------------------- 1 file changed, 13 insertions(+), 33 deletions(-) diff --git a/docs/witness-formats.md b/docs/witness-formats.md index fc6138d..0ae918f 100644 --- a/docs/witness-formats.md +++ b/docs/witness-formats.md @@ -4,10 +4,10 @@ This document explains the different witness formats supported by groth16-proofs ## Overview -**groth16-proofs** supports two witness formats: +**groth16-proofs** uses decimal witness format for WASM proof generation: 1. **Decimal Format** (Recommended) - Native snarkjs output -2. **Hex Little-Endian Format** (Legacy) - Custom format +2. **Hex Little-Endian Format** - Utility conversion format for low-level Rust helpers ## Format Comparison @@ -17,7 +17,7 @@ This document explains the different witness formats supported by groth16-proofs | **Example** | `"12345"` | `"0x3930000000...00"` | | **Conversion needed** | ❌ No | βœ… Yes | | **Function (Rust)** | `decimal_to_field()` | `hex_to_field()` | -| **Function (WASM)** | `generate_proof_from_decimal_wasm()` | `generate_proof_wasm()` | +| **Function (WASM)** | `generate_proof_from_decimal_wasm()` | N/A | | **Use case** | Modern integrations | Legacy code | ## 1. Decimal Format (Recommended βœ…) @@ -79,7 +79,7 @@ let witness: Vec = decimal_strings .collect::, _>>()?; ``` -## 2. Hex Little-Endian Format (Legacy) +## 2. Hex Little-Endian Format (Utility / Rust) ### What is it? @@ -127,22 +127,9 @@ Reverse bytes (LE): 0x3930000000...0000 ### How to use **WASM (JavaScript/TypeScript)**: -```typescript -import { generate_proof_wasm } from './wasm/groth16_proofs.js'; -// If you already have hex LE witness -const witnessHexLE = [ - "0x0100000000000000000000000000000000000000000000000000000000000000", - "0x3930000000000000000000000000000000000000000000000000000000000000", - // ... -]; - -const resultJson = generate_proof_wasm( - 5, - JSON.stringify(witnessHexLE), - provingKeyBytes -); -``` +Use `generate_proof_from_decimal_wasm()` with decimal witness exported by snarkjs. +Hex LE is not accepted by the WASM proof-generation API. **Rust**: ```rust @@ -203,9 +190,8 @@ function hexLEToDecimal(hexLE: string): string { ### Use Hex LE Format if: -- Working with existing code that uses it -- Interfacing with systems that expect hex -- Need to maintain compatibility with older versions +- You need low-level conversion utilities in Rust +- You are working with internal field-level tooling ## Technical Details @@ -269,21 +255,15 @@ async function generateProofForCircuit(inputs: any) { } ``` -### Legacy Hex LE Integration +### snarkjs Proof Interoperability ```typescript -import { generate_proof_wasm } from './groth16_proofs.js'; - -// If you have legacy code that produces hex LE -const witnessHexLE = loadLegacyWitness(); // ["0x...", "0x...", ...] +import { compress_snarkjs_proof_wasm } from './groth16_proofs.js'; -const resultJson = generate_proof_wasm( - 5, - JSON.stringify(witnessHexLE), - provingKeyBytes -); +const compressedProof = compress_snarkjs_proof_wasm(JSON.stringify(snarkjsProof)); +// "0x..." (128-byte arkworks canonical compressed proof) ``` ## Summary -**Recommendation**: Use **decimal format** for all new integrations. It's simpler, more efficient, and matches the native snarkjs output format. The hex LE format is supported for backward compatibility but requires unnecessary conversion overhead. +**Recommendation**: Use **decimal format** for all new integrations. It's simpler, more efficient, and matches the native snarkjs output format. The hex LE format remains useful for low-level Rust utilities, but not for WASM proof generation. From 102e59044f67bbb00a443033aceda3d7b43be4f4 Mon Sep 17 00:00:00 2001 From: nol4lej Date: Mon, 16 Feb 2026 01:42:46 -0300 Subject: [PATCH 07/16] feat: update usage documentation for proof generation and rename legacy function --- docs/usage.md | 150 ++++++++++++++------------------------------------ 1 file changed, 41 insertions(+), 109 deletions(-) diff --git a/docs/usage.md b/docs/usage.md index b5888f3..166b0d1 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -28,7 +28,7 @@ Hex-encoded 32-byte field elements: ```json ["0x0100...00", "0x3930...00", ...] ``` -Use `generate_proof_wasm()` or `hex_to_field()`. +Use `hex_to_field()`. ### Why Two Formats? @@ -77,7 +77,7 @@ pub fn generate_proof_from_witness( **Example**: ```rust -use orbinum_groth16_proofs::generate_proof_from_witness; +use groth16_proofs::generate_proof_from_witness; fn main() -> Result<(), Box> { let witness = vec![ @@ -107,7 +107,7 @@ pub fn decimal_to_field(decimal_str: &str) -> Result **Example**: ```rust -use orbinum_groth16_proofs::decimal_to_field; +use groth16_proofs::decimal_to_field; let field_element = decimal_to_field("12345")?; assert_eq!(field_element, Bn254Fr::from(12345u64)); @@ -127,7 +127,7 @@ pub fn hex_to_field(hex_str: &str) -> Result **Example**: ```rust -use orbinum_groth16_proofs::hex_to_field; +use groth16_proofs::hex_to_field; // Little-endian hex representation of 1 let field_element = hex_to_field("0x0100000000000000000000000000000000000000000000000000000000000000")?; @@ -198,108 +198,42 @@ async function generateProof(circuitInputs) { } ``` -### `generate_proof_wasm()` - Legacy +### `compress_snarkjs_proof_wasm()` - Interoperability -Generate proof from hex little-endian witness format. +Convert a proof generated by snarkjs (`pi_a`, `pi_b`, `pi_c`) into arkworks +canonical compressed bytes (same format expected by runtime integrations). **Signature**: ```typescript -function generate_proof_wasm( - numPublicSignals: number, // Number of public signals to extract (e.g., 5, 4, etc.) - witnessJson: string, // JSON array as string - provingKeyBytes: Uint8Array // Binary proving key -): string // JSON output +function compress_snarkjs_proof_wasm( + proofJson: string // snarkjs proof as JSON string +): string // 0x-prefixed compressed proof ``` **Parameters**: | Parameter | Type | Description | |-----------|------|-------------| -| `numPublicSignals` | number | Number of public signals to extract from witness | -| `witnessJson` | string | JSON string: `'["0x...", "0x...", ...]'` (hex LE) | -| `provingKeyBytes` | Uint8Array | Binary proving key (from `.ark` file) | - -**Returns**: JSON string -```json -{ - "proof": "0x...", // 128-byte compressed Groth16 proof - "publicSignals": ["0x...", "0x...", ...] // Public signals from witness -} -``` +| `proofJson` | string | JSON string with `pi_a`, `pi_b`, `pi_c` decimal coordinates | -**How to determine `numPublicSignals`**: +**Returns**: Hex string (`0x...`) of compressed Groth16 proof (128 bytes) -The number of public signals depends on your specific circuit implementation. Check your circuit definition to know how many signals are public. Common examples: -- Simple circuits: 1-5 signals -- Privacy protocols: 4-8 signals -- Complex applications: 10+ signals - -**Example (Browser)**: +**Example**: ```typescript -// Import from downloaded WASM module -import { generate_proof_wasm } from './wasm/groth16_proofs.js'; - -async function generateProof() { - // Prepare witness - const witness = [ - "0x0100000000000000000000000000000000000000000000000000000000000000", - // ... ~11,808 elements - ]; - - // Load proving key - const response = await fetch('proving_keys/my_circuit.ark'); - const provingKeyBytes = new Uint8Array(await response.arrayBuffer()); - - // Specify number of public signals for your circuit - const numPublicSignals = 5; // Adjust based on your circuit - - try { - const resultJson = generate_proof_wasm( - numPublicSignals, - JSON.stringify(witness), - provingKeyBytes - ); - - const { proof, publicSignals } = JSON.parse(resultJson); - - console.log('Proof:', proof); - console.log('Signals:', publicSignals); - - // Use proof... - return { proof, publicSignals }; - } catch (error) { - console.error('Generation failed:', error); - } -} -``` +import { compress_snarkjs_proof_wasm } from './wasm/groth16_proofs.js'; -**Example (Node.js)**: -```typescript -// Import from downloaded WASM module -import { generate_proof_wasm } from './wasm/groth16_proofs.js'; -import fs from 'fs'; -import path from 'path'; +const snarkjsProofJson = JSON.stringify({ + pi_a: ["...", "...", "1"], + pi_b: [["...", "..."], ["...", "..."], ["1", "0"]], + pi_c: ["...", "...", "1"], +}); -function generateProofFromFile() { - const witness = JSON.parse(fs.readFileSync('witness.json', 'utf-8')); - const provingKey = fs.readFileSync('circuits/my_circuit_pk.ark'); - - // Adjust based on your circuit's public signals - const numPublicSignals = 5; - - const result = generate_proof_wasm( - numPublicSignals, - JSON.stringify(witness), - new Uint8Array(provingKey) - ); - - const parsed = JSON.parse(result); - console.log('Generated proof:', parsed.proof); - - return parsed; -} +const compressedProof = compress_snarkjs_proof_wasm(snarkjsProofJson); +// compressedProof => "0x..." (128-byte arkworks canonical proof) ``` +> Note: Internally, this interoperability path is implemented in `src/wasm/snarkjs_proof.rs` and re-exported by `src/wasm.rs` and `src/lib.rs`. + ### `initPanicHook()` Initialize panic handling for better browser error messages. Usually called automatically. @@ -314,7 +248,7 @@ function initPanicHook(): void ### Rust Example ```rust -use orbinum_groth16_proofs::generate_proof_from_witness; +use groth16_proofs::generate_proof_from_witness; fn main() -> Result<(), Box> { // Prepare witness (hex-encoded field elements) @@ -335,13 +269,13 @@ fn main() -> Result<(), Box> { ### Browser Example ```typescript -import { generate_proof_wasm } from './wasm/groth16_proofs.js'; +import { generate_proof_from_decimal_wasm } from './wasm/groth16_proofs.js'; async function generateProof() { // Load witness data const witness = [ - "0x0100000000000000000000000000000000000000000000000000000000000000", - "0x0200000000000000000000000000000000000000000000000000000000000000", + "1", + "2", // ... more elements ]; @@ -353,7 +287,7 @@ async function generateProof() { const numPublicSignals = 5; // Depends on your circuit definition try { - const resultJson = generate_proof_wasm( + const resultJson = generate_proof_from_decimal_wasm( numPublicSignals, JSON.stringify(witness), provingKeyBytes @@ -374,7 +308,7 @@ async function generateProof() { ### Node.js Example ```typescript -import { generate_proof_wasm } from './wasm/groth16_proofs.js'; +import { generate_proof_from_decimal_wasm } from './wasm/groth16_proofs.js'; import fs from 'fs'; function generateProofFromFile(circuitName: string, witnessPath: string) { @@ -386,7 +320,7 @@ function generateProofFromFile(circuitName: string, witnessPath: string) { // You need to know how many public signals your circuit has const numPublicSignals = 5; - const result = generate_proof_wasm( + const result = generate_proof_from_decimal_wasm( numPublicSignals, JSON.stringify(witness), new Uint8Array(provingKey) @@ -401,19 +335,18 @@ function generateProofFromFile(circuitName: string, witnessPath: string) { ### Witness Format -Hex-encoded field elements (little-endian, each 256 bits): +Decimal field elements (snarkjs native output): ```json [ - "0x0100000000000000000000000000000000000000000000000000000000000000", - "0x0200000000000000000000000000000000000000000000000000000000000000", - "0x..." + "1", + "2", + "67890123456789012345678901234567890" ] ``` **Important**: -- Each element must be 66 characters (0x + 64 hex digits) -- Little-endian format +- Each element is a decimal string representing a BN254 field element - Total ~11,808 elements per proof ### Output Format @@ -451,7 +384,7 @@ match generate_proof_from_witness(&witness, "key.ark") { ```typescript try { const numPublicSignals = 5; // Adjust for your circuit - const result = generate_proof_wasm(numPublicSignals, witnessJson, keyBytes); + const result = generate_proof_from_decimal_wasm(numPublicSignals, witnessJson, keyBytes); const { proof, publicSignals } = JSON.parse(result); } catch (error) { console.error('Error:', error.message); @@ -505,7 +438,7 @@ const CIRCUIT_CONFIG = { async function generateProofCached(circuitName: string, witness: string[]) { const provingKey = await loadProvingKey(circuitName); const { publicSignals } = CIRCUIT_CONFIG[circuitName]; - return generate_proof_wasm(publicSignals, JSON.stringify(witness), provingKey); + return generate_proof_from_decimal_wasm(publicSignals, JSON.stringify(witness), provingKey); } ``` @@ -557,7 +490,7 @@ class ProofQueue { async process() { while (this.queue.length > 0) { const task = this.queue.shift(); - const result = generate_proof_wasm(numPublicSignals, witnessJson, provingKeyBytes); + const result = generate_proof_from_decimal_wasm(numPublicSignals, witnessJson, provingKeyBytes); // ... } } @@ -592,13 +525,12 @@ println!("Proof bytes: {}", hex::encode(&proof_bytes)); ```typescript // In TypeScript console.log('Witness JSON:', witnessJson); -const result = generate_proof_wasm(numPublicSignals, witnessJson, provingKeyBytes); -console.log('Result:', result); +const result = generate_proof_from_decimal_wasm(numPublicSignals, witnessJson, provingKeyBytes); console.log('Result:', result); ``` ## Next Steps - [Installation Guide](./installation.md) -- [Development](../DEVELOPMENT.md) -- [Contributing](../CONTRIBUTING.md) +- [Witness Formats](./witness-formats.md) +- [Release Process](./release.md) From bac3c08ff1bcc1272bdeadf1b4268c200bca1ee6 Mon Sep 17 00:00:00 2001 From: nol4lej Date: Mon, 16 Feb 2026 01:42:51 -0300 Subject: [PATCH 08/16] feat: remove outdated release process documentation --- docs/release.md | 372 ------------------------------------------------ 1 file changed, 372 deletions(-) delete mode 100644 docs/release.md diff --git a/docs/release.md b/docs/release.md deleted file mode 100644 index 77ca8d9..0000000 --- a/docs/release.md +++ /dev/null @@ -1,372 +0,0 @@ -# Release Process - -This document explains how releases are managed for `groth16-proofs`. - -## Overview - -Releases are **fully automated** using: -- **cargo-release**: Manages version bumping, CHANGELOG updates, and tagging -- **GitHub Actions**: CI/CD pipeline for building, testing, and publishing -- **CHANGELOG.md**: Automatically updated by cargo-release - -## How It Works - -### Automated CHANGELOG Updates - -`cargo-release` automatically updates the CHANGELOG: - -1. Moves `[Unreleased]` changes to a new version section -2. Adds the current date -3. Creates version comparison links -4. Commits the changes with message: `chore(release): {{version}}` -5. Creates a git tag - -**No manual CHANGELOG editing required!** Just keep adding changes under `[Unreleased]`. - -## Release Process - -### 1. Add Changes to CHANGELOG - -Keep [CHANGELOG.md](../CHANGELOG.md) updated with changes under `[Unreleased]`: - -```markdown -## [Unreleased] - -### Added -- New decimal witness format support -- `generate_proof_from_decimal_wasm()` function - -### Changed -- Updated documentation - -### Fixed -- Bug in bounds checking -``` - -### 2. Bump Version in Cargo.toml - -Update the version manually: - -```toml -[package] -name = "groth16-proofs" -version = "1.1.0" # ← Change this -``` - -### 3. Commit and Push - -```bash -git add Cargo.toml -git commit -m "chore: bump version to 1.1.0" -git push origin main -``` - -### 4. Automatic Pipeline Execution - -The GitHub Actions workflow will: - -1. βœ… Install cargo-release -2. βœ… Run `cargo release` to update CHANGELOG automatically -3. βœ… Create git tag (e.g., `v1.1.0`) -4. βœ… Push CHANGELOG commit and tag -5. βœ… Build WASM with wasm-pack -6. βœ… Create GitHub Release with WASM artifact -7. βœ… Publish to crates.io - -**The CHANGELOG is automatically updated from [Unreleased] to the new version!** - -## Manual Changelog Management (Legacy) - -If you prefer manual control, you can update CHANGELOG.md before step 2: - -### Structure - -```markdown -# Changelog - -## [Unreleased] -### Added -- New features - -### Fixed -- Bug fixes - -## [0.1.0] - 2026-02-12 -### Added -- Initial release -``` - -### Updating Before Release - -Before triggering a release, update `CHANGELOG.md`: - -1. Move items from `[Unreleased]` to a new version section -2. Add today's date -3. Add link references at bottom - -**Example**: - -```markdown -## [Unreleased] -### Added -- Feature coming soon - -## [0.2.0] - 2026-02-15 -### Added -- New proof generation improvements -- Performance optimizations - -### Fixed -- Iterator bounds checking in witness circuit -``` - -Then commit: -```bash -git add CHANGELOG.md -git commit -m "chore(changelog): Update for v0.2.0" -``` - -## Automated Release Trigger - -Push any commit with changes to `Cargo.toml` or `src/`: - -```bash -git add Cargo.toml src/ -git commit -m "feat: add new feature" -git push origin main -``` - -The GitHub Actions pipeline will: - -### 1. **CI Checks** (`.github/workflows/ci.yml`) -- βœ… Run cargo test -- βœ… Check formatting (cargo fmt) -- βœ… Run clippy lints - -### 2. **Release Job** (`.github/workflows/release.yml`) - -#### compile and publish to crates.io: - -```yaml -- Build WASM with wasm-pack -- Publish Rust crate to crates.io -- Run cargo-release to bump version -- Create git tag (v0.2.0) -``` - -#### Create GitHub Release: - -```yaml -- Package WASM as "orb-groth16-proof.tar.gz" -- Upload as release asset -- Create release with tag -``` - -## Configuration Files - -### `.cargo/release.toml` - -Controls release behavior: - -```toml -[release] -update-crates-io = true # Auto-publish to crates.io -consolidate-pushes = true # Single push for all changes -changelog-update = true # Update CHANGELOG.md -pre-release-commit-message = "chore(release): {{version}}" -tag-name = "v{{version}}" # Git tag format -pre-release-hook = [ - # Runs wasm-pack before release - { cmd = "wasm-pack", args = [...] } -] -allow-branch = ["main"] # Only release from main -``` - -### `.github/workflows/release.yml` - -Defines the release pipeline: - -- **Triggers on**: Changes to `Cargo.toml` or `src/**` on `main` branch -- **Perms required**: - - `contents: write` (GitHub release) - - `packages: write` (npm publish) - -Key steps: -1. Build WASM -2. Package as `orb-groth16-proof.tar.gz` -3. Publish to crates.io -4. Run cargo-release punch, `orb-groth16-proof.tar.gz` -5. Publish WASM to npm - -## Required Secrets - -Configure in GitHub `Settings > Secrets and variables > Actions`: - -| Secret | Source | Purpose | -|--------|--------|---------| -| `CARGO_REGISTRY_TOKEN` | https://crates.io/me | Publish to crates.io | -| `NODE_AUTH_TOKEN` | https://npmjs.com/settings/tokens | Publish to npm | - -## Versioning - -This project follows **Semantic Versioning**: - -- **MAJOR** (0.x.0): Breaking API changes -- **MINOR** (x.1.0): New features (backward compatible) -- **PATCH** (x.x.1): Bug fixes - -Example progression: -``` -0.1.0 β†’ 0.2.0 (added new circuits) -0.2.0 β†’ 0.2.1 (fixed bug) -0.2.1 β†’ 1.0.0 (stable API) -``` - -## What Gets Published - -### GitHub Release - -**Asset**: `orb-groth16-proof.tar.gz` -- Contains all WASM build artifacts -- `.wasm` binary -- `.js` wrapper -- `.d.ts` types -- `package.json` - -**Download**: -```bash -curl -L https://github.com/orbinum/groth16-proofs/releases/download/v0.2.0/orb-groth16-proof.tar.gz -o pkg.tar.gz -tar -xzf pkg.tar.gz -``` - -### crates.io - -Published as: `groth16-proofs` - -**Install**: -```toml -[dependencies] -groth16-proofs = "0.2" -``` - -### WASM - -Not published to NPM. Download precompiled binaries from GitHub Releases: - -**Download**: -```bash -curl -L https://github.com/orbinum/groth16-proofs/releases/download/v0.2.0/orb-groth16-proof.tar.gz -o wasm.tar.gz -tar -xzf wasm.tar.gz -C ./wasm -``` - -## Manual Release (Fallback) - -If you need to manually trigger a release: - -```bash -# Install cargo-release -cargo install cargo-release - -# Dry-run to see what would happen -cargo release --no-confirm - -# Execute release -cargo release --no-confirm --execute -``` - -This will: -1. Bump version patch (0.1.0 β†’ 0.1.1) -2. Update `Cargo.toml` -3. Create commit and tag -4. Push to crates.io -5. Create GitHub release (manual) -6. Push WASM to npm (manual) - -## Troubleshooting - -### "cargo-release failed" - -Check: -- `CARGO_REGISTRY_TOKEN` is valid and hasn't expired -- You have push access to main branch -- Version in `Cargo.toml` matches git tag - -### "npm publish failed" - -Verify: -- `NODE_AUTH_TOKEN` is set correctly -- Package version is unique (not already published) -- `pkg/package.json` exists and has correct version - -### "Changelog not updated" - -Manual fix: -1. Update `CHANGELOG.md` -2. Commit: `git commit -m "chore(Release): Add v0.2.0"` -3. Push: `git push origin main` - -## Example Release Workflow - -From start to finish: - -```bash -# 1. Make changes -git checkout -b feat/new-circuit -# ... edit src/ -git add src/ -git commit -m "feat: add transfer circuit support" - -# 2. Update changelog -vim CHANGELOG.md -# Move items from [Unreleased] to [0.2.0] -git add CHANGELOG.md -git commit -m "chore(changelog): Update for v0.2.0" - -# 3. Update version (optional, cargo-release does this) -# Edit Cargo.toml to bump version manually, or let cargo-release do it - -# 4. Push and trigger release -git push origin feat/new-circuit -# Create PR, get review -# Merge to main - -git push origin main -# GitHub Actions detects changes to src/ and Cargo.toml -# Automatically runs full release pipeline -# βœ… Tests pass -# βœ… WASM builds -# βœ… Published to crates.io -# βœ… GitHub release created with orb-groth16-proof.tar.gz -``` - -Then verify: -```bash -# Check on crates.io -curl https://crates.io/api/v1/crates/groth16-proofs/0.2.0 - -# Check GitHub -curl https://api.github.com/repos/orbinum/groth16-proofs/releases/latest -``` - -## FAQ - -**Q: How often should we release?** -A: Whenever there are meaningful changes. Not required to be frequent. - -**Q: Can we skip a version?** -A: No, cargo-release auto-increments. Always use semantic versioning. - -**Q: What if release fails midway?** -A: cargo-release is transactional. No partial states. Restart the workflow. - -**Q: Can I release from a branch other than main?** -A: No, cargo-release is configured to only work from main. - -**Q: Do I need to update CHANGELOG.md manually?** -A: Yes. cargo-release handles version bumping, you handle changelog organization. - -## See Also - -- [Development Guide](../DEVELOPMENT.md) -- [Contributing Guide](../CONTRIBUTING.md) -- [cargo-release docs](https://github.com/crate-ci/cargo-release) From 96f8a37f956e8c20c5d98a4c6a0778a0b6c7a08e Mon Sep 17 00:00:00 2001 From: nol4lej Date: Mon, 16 Feb 2026 01:42:56 -0300 Subject: [PATCH 09/16] feat: update installation guide for Groth16 proofs with version and WASM usage changes --- docs/installation.md | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/docs/installation.md b/docs/installation.md index 8982ffa..2333f03 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -16,23 +16,29 @@ Add to your `Cargo.toml`: ```toml [dependencies] -groth16-proofs = "0.1" +groth16-proofs = "2.0" ``` Then import in your code: ```rust -use orbinum_groth16_proofs::generate_proof_from_witness; +use groth16_proofs::generate_proof_from_witness; let proof = generate_proof_from_witness(&witness, "proving_key.ark")?; ``` ### As a WASM Module -**Note**: This library is not published to NPM. To use WASM, download the precompiled binaries from [GitHub Releases](https://github.com/orbinum/groth16-proofs/releases). +Install from npm: + +```bash +npm install @orbinum/groth16-proofs +``` + +You can also download precompiled binaries from [GitHub Releases](https://github.com/orbinum/groth16-proofs/releases). -1. Download `orb-groth16-proof.tar.gz` from the latest release -2. Extract to your project: +1. Download `orb-groth16-proof.tar.gz` from the latest release (optional) +2. Extract to your project (optional): ```bash tar -xzf orb-groth16-proof.tar.gz -C ./wasm ``` @@ -40,11 +46,11 @@ tar -xzf orb-groth16-proof.tar.gz -C ./wasm 3. Import in TypeScript/JavaScript: ```typescript -import { generate_proof_wasm } from './wasm/groth16_proofs.js'; +import { generate_proof_from_decimal_wasm } from './wasm/groth16_proofs.js'; // numPublicSignals depends on your circuit (check your circuit definition) const numPublicSignals = 5; -const result = generate_proof_wasm(numPublicSignals, witnessJson, provingKeyBytes); +const result = generate_proof_from_decimal_wasm(numPublicSignals, witnessJson, provingKeyBytes); ``` ### Development Installation @@ -140,8 +146,8 @@ let proof = generate_proof_from_witness(&witness, "key.ark")?; ```typescript // WASM -import { generate_proof_wasm } from './wasm/groth16_proofs.js'; -const result = generate_proof_wasm(numPublicSignals, witnessJson, keyBytes); +import { generate_proof_from_decimal_wasm } from './wasm/groth16_proofs.js'; +const result = generate_proof_from_decimal_wasm(numPublicSignals, witnessJson, keyBytes); ``` ## Configuration From 9465920aa1a18017b78238b83f6daafcbd27c0cb Mon Sep 17 00:00:00 2001 From: nol4lej Date: Mon, 16 Feb 2026 01:43:00 -0300 Subject: [PATCH 10/16] feat: enhance release workflow to include npm package configuration and publishing --- .github/workflows/release.yml | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8c0aaec..105d575 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,6 +6,7 @@ on: paths: - "Cargo.toml" - "src/**" + - "npm/**" - ".github/workflows/release.yml" permissions: @@ -80,6 +81,23 @@ jobs: if: steps.check_tag.outputs.exists == 'false' run: wasm-pack build --target web --out-dir ./pkg --release --features wasm + - name: Configure npm package.json + if: steps.check_tag.outputs.exists == 'false' + run: | + cd pkg + # Generate package.json from template (same approach as circuits) + node -e " + const fs = require('fs'); + const template = fs.readFileSync('../npm/package.json.template', 'utf8'); + const rendered = template.replace(/__VERSION__/g, '${{ steps.version.outputs.version }}'); + JSON.parse(rendered); + fs.writeFileSync('package.json', rendered); + " + # Copiar README especΓ­fico para npm + cp ../npm/README.md ./README.md + echo "πŸ“¦ Configured package.json:" + cat package.json + - name: Create WASM package artifact if: steps.check_tag.outputs.exists == 'false' run: | @@ -105,3 +123,18 @@ jobs: run: cargo publish --token ${{ secrets.CARGO_REGISTRY_TOKEN }} env: CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} + + - name: Setup Node.js for npm publish + if: steps.check_tag.outputs.exists == 'false' + uses: actions/setup-node@v4 + with: + node-version: '22' + registry-url: 'https://registry.npmjs.org' + + - name: Publish to npm + if: steps.check_tag.outputs.exists == 'false' + run: | + cd pkg + npm publish + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} From cf193aaab67a7345d8196f62f6b5aa322c555723 Mon Sep 17 00:00:00 2001 From: nol4lej Date: Mon, 16 Feb 2026 01:43:06 -0300 Subject: [PATCH 11/16] feat: remove pre-release hook for WASM build from release configuration --- .cargo/release.toml | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/.cargo/release.toml b/.cargo/release.toml index aa44c48..e5c0b97 100644 --- a/.cargo/release.toml +++ b/.cargo/release.toml @@ -12,18 +12,5 @@ tag-message = "Release {{version}}" tag-name = "v{{version}}" # IMPORTANT: Automatically update CHANGELOG.md changelog-update = true -# Build WASM before release (as a pre-release hook) -pre-release-hook = [ - { cmd = "wasm-pack", args = [ - "build", - "--target", - "web", - "--out-dir", - "./pkg", - "--release", - "--features", - "wasm", - ] }, -] # Only allow releases from main branch allow-branch = ["main"] From a67c6e20e4670b07c3c3fc9138c1bbebf43d3a0e Mon Sep 17 00:00:00 2001 From: nol4lej Date: Mon, 16 Feb 2026 01:43:11 -0300 Subject: [PATCH 12/16] feat: update README for WASM npm package integration and decimal-only API --- README.md | 57 +++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 39 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index f03eed0..8edd05b 100644 --- a/README.md +++ b/README.md @@ -2,47 +2,62 @@ > High-performance Groth16 proof generator using arkworks for Orbinum privacy protocol +[![npm version](https://img.shields.io/npm/v/@orbinum/groth16-proofs.svg)](https://www.npmjs.com/package/@orbinum/groth16-proofs) [![Crates.io](https://img.shields.io/crates/v/groth16-proofs.svg)](https://crates.io/crates/groth16-proofs) [![License](https://img.shields.io/badge/license-Apache--2.0%20OR%20GPL--3.0-blue)](./LICENSE-APACHE2) Efficient **Groth16 zero-knowledge proof generator** for Orbinum's privacy protocol. Compiles to both native Rust and WebAssembly for maximum flexibility. -## Quick Start +## πŸš€ Quick Start ### Install -**Rust**: +**npm/yarn/pnpm** (WASM for JavaScript/TypeScript): +```bash +npm install @orbinum/groth16-proofs +``` + +**Rust** (Native binary): ```toml [dependencies] -groth16-proofs = "0.1" +groth16-proofs = "2.0" ``` ### Use -**Rust**: -```rust -use orbinum_groth16_proofs::generate_proof_from_witness; - -let proof = generate_proof_from_witness(&witness, "proving_key.ark")?; -``` - -**JavaScript (Browser/Node.js)** - Direct from snarkjs: +**JavaScript/TypeScript**: ```typescript -import { generate_proof_from_decimal_wasm } from './wasm/groth16_proofs.js'; +import * as groth16 from '@orbinum/groth16-proofs'; import * as snarkjs from 'snarkjs'; +// Initialize WASM +await groth16.default(); +groth16.init_panic_hook(); + // Calculate witness with snarkjs const witnessArray = await snarkjs.wtns.exportJson('witness.wtns'); // Generate proof (no conversion needed!) -const result = generate_proof_from_decimal_wasm( +const result = groth16.generate_proof_from_decimal_wasm( 5, // number of public signals JSON.stringify(witnessArray), // direct from snarkjs provingKeyBytes ); + +const { proof, publicSignals } = JSON.parse(result); ``` -πŸ“– **Full guides**: See [Installation](./docs/installation.md) and [Usage](./docs/usage.md) +**Rust**: +```rust +use groth16_proofs::generate_proof_from_witness; + +let proof = generate_proof_from_witness(&witness, "proving_key.ark")?; +``` + +πŸ“– **Full guides**: +- [Installation](./docs/installation.md) +- [Usage](./docs/usage.md) +- [Release Process](./docs/release.md) ## What Is This? @@ -52,7 +67,11 @@ This crate generates **128-byte compressed Groth16 proofs** from witness data us - **Curves**: BN254 (Ethereum-compatible) - **Targets**: Native (Rust) + WebAssembly - **Circuits**: Unshield, Transfer, Disclosure -- **Formats**: Decimal (snarkjs native) or Hex little-endian +- **WASM Input Format**: Decimal witness (snarkjs native) + +For interoperability with snarkjs-generated proofs, the WASM API also exposes +`compress_snarkjs_proof_wasm()` to convert `pi_a/pi_b/pi_c` JSON into arkworks +canonical compressed proof bytes (`0x...`, 128 bytes). ## Architecture @@ -89,14 +108,15 @@ This crate generates **128-byte compressed Groth16 proofs** from witness data us | **proof.rs** | `src/` | Core Groth16 generation using arkworks | | **circuit.rs** | `src/` | Circuit wrapper implementing ConstraintSynthesizer | | **utils.rs** | `src/` | Format conversions (decimal ↔ hex ↔ field elements) | -| **wasm.rs** | `src/` | WASM FFI bindings with JSON I/O | +| **wasm.rs** | `src/` | WASM FFI bindings and public API re-exports | +| **wasm/snarkjs_proof.rs** | `src/wasm/` | snarkjs proof parsing/validation and compression | | **binary** | `src/bin/` | CLI tool for Node.js integration | ## Features βœ… **Multiple Targets**: Native + WASM βœ… **Fast**: 5-8 second proof generation -βœ… **Multiple Formats**: Decimal (snarkjs native) or Hex LE +βœ… **WASM Decimal-Only API**: Direct snarkjs witness input βœ… **Type-Safe**: Memory-safe cryptography βœ… **Well-Tested**: 21+ tests included βœ… **Automated Release**: CI/CD pipeline ready @@ -120,7 +140,7 @@ See [Makefile](./Makefile) for all available targets. - [**Installation Guide**](./docs/installation.md) - Setup for Rust and JavaScript - [**Usage Guide**](./docs/usage.md) - Complete API reference with examples -- [**Witness Formats**](./docs/witness-formats.md) - Decimal vs Hex LE formats explained +- [**Witness Formats**](./docs/witness-formats.md) - Decimal witness flow and format notes - [**Release Process**](./docs/release.md) - How releases are managed ## Performance @@ -140,6 +160,7 @@ See [Makefile](./Makefile) for all available targets. This crate is published to both registries: - **Rust**: [crates.io/crates/groth16-proofs](https://crates.io/crates/groth16-proofs) +- **npm**: [npmjs.com/package/@orbinum/groth16-proofs](https://www.npmjs.com/package/@orbinum/groth16-proofs) ## License From c8998e942b6b2c38063a5af8dd0ad2bf7263d02e Mon Sep 17 00:00:00 2001 From: nol4lej Date: Mon, 16 Feb 2026 01:43:14 -0300 Subject: [PATCH 13/16] feat: enhance WASM build process to configure npm package and include README --- Makefile | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 7dccb1d..d2116a3 100644 --- a/Makefile +++ b/Makefile @@ -66,13 +66,37 @@ build-wasm: ## Build WASM module (release) @echo "$(BLUE)Building WASM module...$(NC)" @command -v wasm-pack >/dev/null 2>&1 || { echo "$(YELLOW)Installing wasm-pack...$(NC)"; curl https://rustwasm.org/wasm-pack/installer/init.sh -sSf | sh; } wasm-pack build --target web --out-dir ./pkg --release --features wasm - @echo "$(GREEN)βœ“ WASM: ./pkg/orbinum_groth16_proofs.wasm$(NC)" + @echo "$(BLUE)Configuring npm package...$(NC)" + @cd pkg && node -e "\ + const fs = require('fs');\ + const cargoToml = fs.readFileSync('../Cargo.toml', 'utf8');\ + const versionMatch = cargoToml.match(/^version\s*=\s*\"(.+)\"/m);\ + if (!versionMatch) throw new Error('Could not extract version from Cargo.toml');\ + const template = fs.readFileSync('../npm/package.json.template', 'utf8');\ + const rendered = template.replace(/__VERSION__/g, versionMatch[1]);\ + JSON.parse(rendered);\ + fs.writeFileSync('package.json', rendered);\ + " + @cp npm/README.md pkg/README.md + @echo "$(GREEN)βœ“ WASM: ./pkg/@orbinum/groth16-proofs$(NC)" build-wasm-dev: ## Build WASM module (dev/unoptimized) @echo "$(BLUE)Building WASM module (dev)...$(NC)" @command -v wasm-pack >/dev/null 2>&1 || { echo "$(YELLOW)Installing wasm-pack...$(NC)"; curl https://rustwasm.org/wasm-pack/installer/init.sh -sSf | sh; } wasm-pack build --target web --out-dir ./pkg --dev --features wasm - @echo "$(GREEN)βœ“ WASM (dev): ./pkg/orbinum_groth16_proofs.wasm$(NC)" + @echo "$(BLUE)Configuring npm package...$(NC)" + @cd pkg && node -e "\ + const fs = require('fs');\ + const cargoToml = fs.readFileSync('../Cargo.toml', 'utf8');\ + const versionMatch = cargoToml.match(/^version\s*=\s*\"(.+)\"/m);\ + if (!versionMatch) throw new Error('Could not extract version from Cargo.toml');\ + const template = fs.readFileSync('../npm/package.json.template', 'utf8');\ + const rendered = template.replace(/__VERSION__/g, versionMatch[1]);\ + JSON.parse(rendered);\ + fs.writeFileSync('package.json', rendered);\ + " + @cp npm/README.md pkg/README.md + @echo "$(GREEN)βœ“ WASM (dev): ./pkg/@orbinum/groth16-proofs$(NC)" # Build everything build-all: build build-wasm ## Build both native and WASM From 9da25de39ee93f7a96b36a194dcd0e46bf21f333 Mon Sep 17 00:00:00 2001 From: nol4lej Date: Mon, 16 Feb 2026 01:43:17 -0300 Subject: [PATCH 14/16] feat: update CHANGELOG for decimal-only WASM proof generation and new API additions --- CHANGELOG.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b2e355..2f1c3fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,24 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Added +- `compress_snarkjs_proof_wasm()` WASM API for snarkjs proof (`pi_a`, `pi_b`, `pi_c`) to arkworks canonical compressed bytes conversion. +- Internal `src/wasm/snarkjs_proof.rs` module to separate snarkjs parsing/validation/compression responsibilities. +- `npm/package.json.template` as source of truth for npm package metadata (rendered with release version in CI/local builds). + +### Changed +- **BREAKING**: WASM proof generation is now decimal-only via `generate_proof_from_decimal_wasm()`. +- Documentation updated to reflect decimal-only WASM proof flow and snarkjs interoperability path. +- Release workflow now generates `pkg/package.json` from template (circuits-style), builds release asset (`orb-groth16-proof.tar.gz`), and publishes the rendered `pkg` package to npm. +- Release workflow trigger paths now include `npm/**` to ensure packaging metadata/template changes run through release automation. +- `Makefile` (`build-wasm`, `build-wasm-dev`) now renders `pkg/package.json` from template using `Cargo.toml` version for local parity with CI. +- `cargo-release` responsibility narrowed to version/changelog/tag preparation; WASM build is handled in CI release job to avoid duplicate builds. + +### Removed +- **BREAKING**: Removed legacy WASM API `generate_proof_wasm()` (hex little-endian witness input). + ## [1.0.0](https://github.com/orbinum/groth16-proofs/releases/tag/v1.0.0) - 2026-02-12 ### Added From 974e2bdbd192993288467e7e95374dc219a2d008 Mon Sep 17 00:00:00 2001 From: nol4lej Date: Mon, 16 Feb 2026 01:43:22 -0300 Subject: [PATCH 15/16] feat: update package version to 2.0.0 and add npm package metadata for WASM --- Cargo.toml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 1ace61d..4ace968 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "groth16-proofs" -version = "1.1.0" +version = "2.0.0" edition = "2021" license = "Apache-2.0 OR GPL-3.0-or-later" description = "High-performance Groth16 proof generator using arkworks for Orbinum privacy protocol" @@ -12,6 +12,10 @@ keywords = ["zk-snark", "groth16", "proof-generation", "arkworks", "privacy"] [lib] crate-type = ["cdylib", "rlib"] +# npm package metadata (consumed by wasm-pack) +[package.metadata.wasm-pack.profile.release] +wasm-opt = true + [[bin]] name = "generate-proof-from-witness" path = "src/bin/generate-proof-from-witness.rs" From c2a9b21b33d775d98996d4b2c5cae97beaf777eb Mon Sep 17 00:00:00 2001 From: nol4lej Date: Mon, 16 Feb 2026 01:46:46 -0300 Subject: [PATCH 16/16] feat: update byte order in proof generation to little-endian and ensure proper resizing --- src/wasm.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/wasm.rs b/src/wasm.rs index 97dd37a..814fe07 100644 --- a/src/wasm.rs +++ b/src/wasm.rs @@ -236,7 +236,8 @@ mod tests { .unwrap_or(&[]) .iter() .map(|f| { - let bytes = f.into_bigint().to_bytes_be(); // BE to match runtime verifier + let mut bytes = f.into_bigint().to_bytes_le(); + bytes.resize(32, 0u8); format!("0x{}", hex::encode(&bytes)) }) .collect();