Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 36 additions & 9 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,17 @@ jobs:
cargo check --features "crypto,subxt-native"
cargo test --features crypto-zk

- name: Build WASM package (web)
if: steps.check_tag.outputs.exists == 'false'
run: |
wasm-pack build --target web --out-dir pkg/web --release --no-opt --features crypto-zk

- name: Build WASM package (nodejs)
if: steps.check_tag.outputs.exists == 'false'
run: |
wasm-pack build --target nodejs --out-dir pkg --release --no-opt --features crypto-zk
wasm-pack build --target nodejs --out-dir pkg/node --release --no-opt --features crypto-zk

- name: Prepare npm package metadata
- name: Prepare universal npm package
if: steps.check_tag.outputs.exists == 'false'
run: |
node <<'NODE'
Expand All @@ -79,12 +84,13 @@ jobs:
if (!versionMatch) throw new Error('Version not found in Cargo.toml');
const version = versionMatch[1];

const pkgPath = path.join('pkg', 'package.json');
// Use package.json from one of the builds as base template
const pkgPath = path.join('pkg', 'web', 'package.json');
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));

pkg.name = '@orbinum/protocol-core';
pkg.version = version;
pkg.description = 'Core protocol primitives and WASM bindings for interacting with Orbinum';
pkg.description = 'Core protocol primitives and WASM bindings for interacting with Orbinum (Universal: Node + Web)';
pkg.license = 'Apache-2.0 OR GPL-3.0-or-later';
pkg.repository = {
type: 'git',
Expand All @@ -93,17 +99,38 @@ jobs:
pkg.homepage = 'https://github.com/orbinum/protocol-core';
pkg.publishConfig = { access: 'public' };
pkg.keywords = ['orbinum', 'protocol', 'wasm', 'substrate', 'zk'];

// Hybrid/Universal configuration
pkg.main = './node/orbinum_protocol_core.js';
pkg.module = './web/orbinum_protocol_core.js';
pkg.types = './web/orbinum_protocol_core.d.ts'; // Types are identical

pkg.files = ['web', 'node', 'README.md', 'LICENSE'];

pkg.exports = {
".": {
"browser": "./web/orbinum_protocol_core.js",
"node": "./node/orbinum_protocol_core.js",
"default": "./web/orbinum_protocol_core.js"
}
};

fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
// Write new package.json to root pkg dir
fs.writeFileSync(path.join('pkg', 'package.json'), JSON.stringify(pkg, null, 2) + '\n');
NODE

# Copy artifacts to root pkg structure
cp README.md pkg/README.md
# LICENSE usually copied by wasm-pack, but let's be sure to have it in root pkg if needed
# (wasm-pack puts it inside web/ and node/, good to have one at top level too?)
# For publishing, npm ignores root files not in 'files' array, but 'pkg' folder is what we publish.


- name: Create release asset
if: steps.check_tag.outputs.exists == 'false'
run: |
tar -czf orbinum-protocol-core-${{ steps.version.outputs.version }}-wasm-nodejs.tar.gz -C pkg .
ls -lh orbinum-protocol-core-${{ steps.version.outputs.version }}-wasm-nodejs.tar.gz
tar -czf orbinum-protocol-core-${{ steps.version.outputs.version }}-universal.tar.gz -C pkg .
ls -lh orbinum-protocol-core-${{ steps.version.outputs.version }}-universal.tar.gz

- name: Ensure CHANGELOG.md exists
if: steps.check_tag.outputs.exists == 'false'
Expand Down Expand Up @@ -169,14 +196,14 @@ jobs:
with:
tag_name: ${{ steps.version.outputs.tag }}
name: Release ${{ steps.version.outputs.version }}
files: orbinum-protocol-core-${{ steps.version.outputs.version }}-wasm-nodejs.tar.gz
files: orbinum-protocol-core-${{ steps.version.outputs.version }}-universal.tar.gz
generate_release_notes: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Clean local release artifact before cargo publish
if: steps.check_tag.outputs.exists == 'false'
run: rm -f orbinum-protocol-core-${{ steps.version.outputs.version }}-wasm-nodejs.tar.gz
run: rm -f orbinum-protocol-core-${{ steps.version.outputs.version }}-universal.tar.gz

- name: Publish crate to crates.io
if: steps.check_tag.outputs.exists == 'false'
Expand Down
25 changes: 25 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Changed

- `Cargo.toml`: added `path` dependency on local `orbinum-zk-core` to access `poseidon_hash_1`

### Added
- **Universal NPM Package**: Support for both Node.js (CommonJS/fs) and Web (ESM/fetch) environments in a single package.
- **Hybrid CI Build**: `release.yml` now generates both `pkg/node` and `pkg/web` artifacts.
- **Conditional Exports**: `package.json` configured with `exports` field to automatically select the correct build based on the environment (`browser` vs `node`).
- **Documentation**: Updated README with instructions for JS/TS (Universal), Rust Native, and WASM Runtimes.
- **Selective Disclosure Witness API** (`src/application/disclosure.rs`):
- `create_disclosure_witness()` — builds circuit inputs for `disclosure.circom` Groth16 proof
- `DisclosureWitness` struct with 4 public inputs + 7 private inputs as `[u8; 32]` field elements and disclosure flags
- `DisclosureError` enum (`InvalidMask`, `CryptoError`)
- Support for all 7 valid mask combinations (value, asset_id, owner hash)
- **`poseidon_hash_1()`** added to `ZkCryptoProvider` (single-input Poseidon hash for viewing key)
- **WASM binding** `Crypto::buildDisclosureInputs()` exported to JavaScript/TypeScript
- **18 new tests** (11 unit + 7 integration) in `src/application/disclosure.rs` — all pass
- **Criterion benchmark** `bench_build_disclosure_witness` — ~12.7 µs (release profile)
- **E2E test** `tests/disclosure.test.ts`: full encrypt → decrypt → witness → proof → verify flow (1034ms)
- **E2E test** `tests/shield-and-disclose.test.ts`: Alice shields, generates ZK disclosure proof, Bob verifies (379ms)

### Changed
- Refactored `release.yml` to support multi-target WASM compilation.
- Updated `Cargo.toml` version to `0.2.0`.

## [0.1.0] - 2026-02-16

### Added
Expand Down
21 changes: 14 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "orbinum-protocol-core"
version = "0.1.0"
version = "0.2.0"
edition = "2021"
license = "Apache-2.0 OR GPL-3.0-or-later"
description = "Core protocol primitives and WASM bindings for interacting with Orbinum"
Expand Down Expand Up @@ -29,11 +29,18 @@ std = [
"hex/std",
"orbinum-zk-core/std",
"orbinum-encrypted-memo/std",
"orbinum-encrypted-memo/encrypt",
]
# Crypto features split: ZK ops (WASM-compatible) vs Signing (native-only)
crypto-zk = [] # Poseidon, commitments, nullifiers - uses orbinum-zk-core (always available)
crypto-signing = ["libsecp256k1", "tiny-keccak"] # ECDSA signing for native builds
crypto = ["crypto-zk", "crypto-signing"] # Full crypto for native
crypto-zk = [
"orbinum-encrypted-memo/encrypt",
] # Poseidon, commitments, nullifiers - uses orbinum-zk-core (always available)
crypto-signing = [
"libsecp256k1",
"tiny-keccak",
"blake2",
] # ECDSA signing for native builds
crypto = ["crypto-zk", "crypto-signing"] # Full crypto for native
subxt-native = ["subxt", "subxt/native"]
subxt-web = ["subxt", "subxt/web"]

Expand All @@ -51,17 +58,17 @@ hex = { version = "0.4.3", default-features = false, features = [
] }

# ZK cryptographic primitives
orbinum-zk-core = { version = "0.4.0", default-features = false }
orbinum-zk-core = { version = "0.5.0", default-features = false }

# Encrypted memo primitive from Orbinum Node
orbinum-encrypted-memo = { version = "0.2.2", default-features = false, features = [
orbinum-encrypted-memo = { version = "0.3.0", default-features = false, features = [
"parity-scale-codec",
"encrypt",
] }

# Optional crypto deps
libsecp256k1 = { version = "0.7.1", optional = true }
tiny-keccak = { version = "2.0.2", optional = true, features = ["keccak"] }
blake2 = { version = "0.10", default-features = false, optional = true }

# Subxt - not required for basic transaction building
subxt = { version = "0.38.0", optional = true, default-features = false }
Expand Down
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.PHONY: all build test check clean wasm wasm-node examples doc help
.PHONY: all build test check clean wasm wasm-node wasm-all examples doc help

# Default target
all: check test
Expand Down Expand Up @@ -27,7 +27,7 @@ check:
clean:
@echo "🧹 Cleaning..."
@cargo clean
@rm -rf pkg pkg-node
@rm -rf pkg pkg-node target
@echo "✅ Clean complete"

# Build WASM for web (with ZK crypto, no signing)
Expand Down Expand Up @@ -68,7 +68,7 @@ help:
@echo " make test - Run all tests"
@echo " make check - Check code and run clippy"
@echo " make clean - Clean build artifacts"
@echo " make wasm - Build WASM for web"
@echo " make wasm - Build WASM for web (--target web, crypto-zk)
@echo " make wasm-node - Build WASM for Node.js"
@echo " make wasm-all - Build all WASM targets"
@echo " make fmt - Format code"
Expand Down
104 changes: 76 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,47 +2,94 @@

Core Rust/WASM protocol library for building and encoding Orbinum shielded-pool transactions.

`orbinum-protocol-core` provides a clean, feature-gated API for:
- unsigned call-data construction,
- signed extrinsic building,
- compliance/disclosure transaction flows,
- ZK helper operations (commitment, nullifier, Poseidon hashing),
- JavaScript/TypeScript consumption through WASM bindings.

## Status

- Version: `0.1.0` (initial release)
- Maturity: active development (MVP hardening phase)
- Runtime scope: Substrate/Frontier-compatible transaction payloads
**Universal Package**: Works seamlessly in **Node.js**, **Browsers**, **Rust Native**, and **WASM Runtimes** (Polkadot, Near, etc.).

## Features

- `std` (default)
- `crypto-zk`
- `crypto-signing`
- `crypto` (enables both `crypto-zk` and `crypto-signing`)
- `subxt-native`
- `subxt-web`
- **Universal WASM**: Single package supports `require('fs')` in Node and `fetch()` in Web.
- **Native Rust**: Full support for `no_std` environments and native binary compilation.
- **Type-safe**: Generated from runtime metadata using Subxt.
- **Zero-Knowledge Primitives**: Poseidon hashing, commitment generation, nullifier computation.
- **Transaction Building**: Create unsigned transactions for Note Transfer, Shielding, and Unshielding.
- **Clean Architecture**: Domain-driven design with clear separation of concerns.

## Installation

## Build
### JavaScript / TypeScript (npm)

```bash
cargo build --release --features crypto
npm install @orbinum/protocol-core
```

## WASM
### Rust (Cargo)

```bash
wasm-pack build --target web --out-dir pkg --release --features crypto-zk
wasm-pack build --target nodejs --out-dir pkg-node --release --features crypto-zk
```toml
[dependencies]
orbinum-protocol-core = "0.2.0"
```

## Usage

### Web / Browser (React, Next.js)

The npm package automatically uses `fetch` to load the WASM binary.

```typescript
import { Crypto, TransactionBuilder } from '@orbinum/protocol-core';

async function main() {
// Initialize WASM (downloads from CDN or local asset)
await Crypto.init();

// Create a Note Commitment
const commitment = Crypto.computeCommitment("100", 1, ownerPubkey, blinding);
console.log("Commitment:", commitment);
}
```

## Validate
### Node.js (Backend)

The npm package automatically uses `fs` to load the WASM binary.

```javascript
const { Crypto } = require('@orbinum/protocol-core');

async function main() {
// Initialize WASM (loads from disk)
await Crypto.init();

const hash = Crypto.poseidonHash2(left, right);
console.log("Hash:", hash);
}
```

### Rust (Native / WASM Runtime)

Ideal for CLI tools, Substrate pallets, or generic WASM actors.

```rust
use orbinum_protocol_core::{CryptoApi, TransactionBuilder};

fn main() {
// Native Rust code (no WASM initialization needed)
let commitment = CryptoApi::compute_commitment(
"100",
1,
&owner_pubkey,
&blinding
).unwrap();

println!("Commitment: {:?}", commitment);
}
```

## Build from Source

To build the universal package locally (requires `rust`, `wasm-pack`, and `node`):

```bash
make fmt
make test
make bench
# Build both targets (pkg/web and pkg/node)
make wasm-all
```

## Documentation
Expand All @@ -55,3 +102,4 @@ This README is intentionally brief. Full documentation is in `docs/`:
## License

Apache-2.0 OR GPL-3.0-or-later

36 changes: 34 additions & 2 deletions benches/transaction_api_bench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ fn bench_build_unshield_unsigned(c: &mut Criterion) {
black_box([3u8; 32]),
black_box(500u128),
black_box(1u32),
black_box([4u8; 20]),
black_box([4u8; 32]),
black_box([5u8; 32]),
black_box(vec![6u8; 192]),
black_box(1u32),
Expand Down Expand Up @@ -93,7 +93,7 @@ fn bench_serialize_signed_transaction(c: &mut Criterion) {
let tx = SignedTransaction::new(
vec![31u8; 128],
vec![32u8; 65],
Address::from_slice_unchecked(&[33u8; 20]),
Address::from_slice_unchecked(&[33u8; 32]),
42,
);

Expand Down Expand Up @@ -152,6 +152,37 @@ fn bench_crypto_api_poseidon_hash_2(c: &mut Criterion) {
});
}

/// Measures the time to create a disclosure witness.
///
/// This covers: 2× field element conversion + poseidon_hash_1 + mask evaluation.
/// The target is < 100ms per witness.
#[cfg(any(feature = "crypto-zk", feature = "crypto"))]
fn bench_build_disclosure_witness(c: &mut Criterion) {
use orbinum_encrypted_memo::{DisclosureMask, MemoData};
use orbinum_protocol_core::application::disclosure::create_disclosure_witness;

let memo = MemoData::new(100_000_000u64, [0x11u8; 32], [0x99u8; 32], 0u32);
let commitment = [0x42u8; 32];
let mask = DisclosureMask {
disclose_value: true,
disclose_owner: true,
disclose_asset_id: true,
disclose_blinding: false,
};

c.bench_function("disclosure.create_witness", |b| {
b.iter(|| {
let w = create_disclosure_witness(
black_box(&memo),
black_box(&commitment),
black_box(&mask),
)
.expect("benchmark witness should succeed");
black_box(w)
})
});
}

#[cfg(feature = "crypto")]
fn bench_sign_and_build_shield(c: &mut Criterion) {
const PRIVATE_KEY_HEX: &str =
Expand Down Expand Up @@ -184,6 +215,7 @@ criterion_group!(
bench_crypto_api_compute_commitment,
bench_crypto_api_compute_nullifier,
bench_crypto_api_poseidon_hash_2,
bench_build_disclosure_witness,
bench_sign_and_build_shield
);

Expand Down
Loading
Loading