Merge commit 'd803bfe2b1fe7f5e219e50ac20d6801a0a58ac75' as 'vendor/ruvector'
This commit is contained in:
35
vendor/ruvector/crates/ruvector-verified-wasm/Cargo.toml
vendored
Normal file
35
vendor/ruvector/crates/ruvector-verified-wasm/Cargo.toml
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
[package.metadata.wasm-pack.profile.release]
|
||||
wasm-opt = false
|
||||
|
||||
[package]
|
||||
name = "ruvector-verified-wasm"
|
||||
version = "0.1.1"
|
||||
edition = "2021"
|
||||
rust-version = "1.77"
|
||||
license = "MIT OR Apache-2.0"
|
||||
description = "WASM bindings for ruvector-verified: proof-carrying vector operations in the browser"
|
||||
repository = "https://github.com/ruvnet/ruvector"
|
||||
homepage = "https://github.com/ruvnet/ruvector"
|
||||
documentation = "https://docs.rs/ruvector-verified-wasm"
|
||||
readme = "README.md"
|
||||
keywords = ["wasm", "verification", "vector-database", "dependent-types", "webassembly"]
|
||||
categories = ["wasm", "science", "mathematics"]
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
ruvector-verified = { version = "0.1.1", path = "../ruvector-verified", features = ["ultra"] }
|
||||
wasm-bindgen = "0.2"
|
||||
serde-wasm-bindgen = "0.6"
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
js-sys = "0.3"
|
||||
web-sys = { version = "0.3", features = ["console"] }
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen-test = "0.3"
|
||||
|
||||
[profile.release]
|
||||
opt-level = "s"
|
||||
lto = true
|
||||
66
vendor/ruvector/crates/ruvector-verified-wasm/README.md
vendored
Normal file
66
vendor/ruvector/crates/ruvector-verified-wasm/README.md
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
# ruvector-verified-wasm
|
||||
|
||||
[](https://crates.io/crates/ruvector-verified-wasm)
|
||||
[](https://www.npmjs.com/package/ruvector-verified-wasm)
|
||||
[](https://github.com/ruvnet/ruvector)
|
||||
|
||||
WebAssembly bindings for [ruvector-verified](https://crates.io/crates/ruvector-verified) — proof-carrying vector operations in the browser. Verify vector dimensions, build typed HNSW indices, and create 82-byte proof attestations entirely client-side with sub-microsecond overhead.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```js
|
||||
import init, { JsProofEnv } from "ruvector-verified-wasm";
|
||||
|
||||
await init();
|
||||
const env = new JsProofEnv();
|
||||
|
||||
// Prove dimension equality (~500ns)
|
||||
const proofId = env.prove_dim_eq(384, 384);
|
||||
|
||||
// Verify a batch of vectors (flat f32 array)
|
||||
const flat = new Float32Array(384 * 100); // 100 vectors
|
||||
const count = env.verify_batch_flat(384, flat);
|
||||
console.log(`Verified ${count} vectors`);
|
||||
|
||||
// Create 82-byte proof attestation
|
||||
const att = env.create_attestation(proofId);
|
||||
console.log(att.bytes.length); // 82
|
||||
|
||||
// Route proof to cheapest tier
|
||||
const routing = env.route_proof("dimension");
|
||||
console.log(routing); // { tier: "reflex", reason: "...", estimated_steps: 1 }
|
||||
|
||||
// Get statistics
|
||||
console.log(env.stats());
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
| Method | Returns | Description |
|
||||
|--------|---------|-------------|
|
||||
| `new JsProofEnv()` | `JsProofEnv` | Create environment with all ultra optimizations |
|
||||
| `.prove_dim_eq(a, b)` | `number` | Prove dimensions equal, returns proof ID |
|
||||
| `.mk_vector_type(dim)` | `number` | Build `RuVec n` type term |
|
||||
| `.mk_distance_metric(m)` | `number` | Build metric type: `"L2"`, `"Cosine"`, `"Dot"` |
|
||||
| `.verify_dim_check(dim, vec)` | `number` | Verify single vector dimension |
|
||||
| `.verify_batch_flat(dim, flat)` | `number` | Verify N vectors (flat f32 array) |
|
||||
| `.arena_intern(hi, lo)` | `[id, cached]` | Intern into FastTermArena |
|
||||
| `.route_proof(kind)` | `object` | Route to Reflex/Standard/Deep tier |
|
||||
| `.create_attestation(id)` | `object` | Create 82-byte proof witness |
|
||||
| `.stats()` | `object` | Get verification statistics |
|
||||
| `.reset()` | `void` | Reset environment |
|
||||
| `.terms_allocated()` | `number` | Count of allocated proof terms |
|
||||
|
||||
## Building
|
||||
|
||||
```bash
|
||||
# With wasm-pack
|
||||
wasm-pack build crates/ruvector-verified-wasm --target web
|
||||
|
||||
# With cargo (for crates.io)
|
||||
cargo build -p ruvector-verified-wasm --target wasm32-unknown-unknown
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT OR Apache-2.0
|
||||
238
vendor/ruvector/crates/ruvector-verified-wasm/src/lib.rs
vendored
Normal file
238
vendor/ruvector/crates/ruvector-verified-wasm/src/lib.rs
vendored
Normal file
@@ -0,0 +1,238 @@
|
||||
//! WASM bindings for `ruvector-verified`: proof-carrying vector operations in the browser.
|
||||
//!
|
||||
//! # Quick Start (JavaScript)
|
||||
//!
|
||||
//! ```js
|
||||
//! import init, { JsProofEnv } from "ruvector-verified-wasm";
|
||||
//!
|
||||
//! await init();
|
||||
//! const env = new JsProofEnv();
|
||||
//!
|
||||
//! // Prove dimension equality (~500ns)
|
||||
//! const proofId = env.prove_dim_eq(384, 384); // Ok -> proof ID
|
||||
//!
|
||||
//! // Verify a batch of vectors
|
||||
//! const vectors = [new Float32Array(384).fill(0.5)];
|
||||
//! const result = env.verify_batch(384, vectors);
|
||||
//!
|
||||
//! // Get statistics
|
||||
//! console.log(env.stats());
|
||||
//!
|
||||
//! // Create attestation (82 bytes)
|
||||
//! const att = env.create_attestation(proofId);
|
||||
//! console.log(att.bytes.length); // 82
|
||||
//! ```
|
||||
|
||||
mod utils;
|
||||
|
||||
use ruvector_verified::{
|
||||
cache::ConversionCache,
|
||||
fast_arena::FastTermArena,
|
||||
gated::{self, ProofKind, ProofTier},
|
||||
proof_store, vector_types, ProofEnvironment,
|
||||
};
|
||||
use serde::Serialize;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Module init
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Called automatically when the WASM module is loaded.
|
||||
#[wasm_bindgen(start)]
|
||||
pub fn init() {
|
||||
utils::set_panic_hook();
|
||||
utils::console_log("ruvector-verified-wasm loaded");
|
||||
}
|
||||
|
||||
/// Return the crate version.
|
||||
#[wasm_bindgen]
|
||||
pub fn version() -> String {
|
||||
env!("CARGO_PKG_VERSION").to_string()
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// JsProofEnv — main entry point
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Proof environment for the browser. Wraps `ProofEnvironment` + ultra caches.
|
||||
#[wasm_bindgen]
|
||||
pub struct JsProofEnv {
|
||||
env: ProofEnvironment,
|
||||
arena: FastTermArena,
|
||||
cache: ConversionCache,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl JsProofEnv {
|
||||
/// Create a new proof environment with all optimizations.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
env: ProofEnvironment::new(),
|
||||
arena: FastTermArena::with_capacity(4096),
|
||||
cache: ConversionCache::with_capacity(1024),
|
||||
}
|
||||
}
|
||||
|
||||
/// Prove that two dimensions are equal. Returns proof term ID.
|
||||
///
|
||||
/// Throws if dimensions don't match.
|
||||
pub fn prove_dim_eq(&mut self, expected: u32, actual: u32) -> Result<u32, JsError> {
|
||||
vector_types::prove_dim_eq(&mut self.env, expected, actual)
|
||||
.map_err(|e| JsError::new(&e.to_string()))
|
||||
}
|
||||
|
||||
/// Build a `RuVec n` type term. Returns term ID.
|
||||
pub fn mk_vector_type(&mut self, dim: u32) -> Result<u32, JsError> {
|
||||
vector_types::mk_vector_type(&mut self.env, dim).map_err(|e| JsError::new(&e.to_string()))
|
||||
}
|
||||
|
||||
/// Build a distance metric type term. Supported: "L2", "Cosine", "Dot".
|
||||
pub fn mk_distance_metric(&mut self, metric: &str) -> Result<u32, JsError> {
|
||||
vector_types::mk_distance_metric(&mut self.env, metric)
|
||||
.map_err(|e| JsError::new(&e.to_string()))
|
||||
}
|
||||
|
||||
/// Verify that a single vector has the expected dimension.
|
||||
pub fn verify_dim_check(&mut self, index_dim: u32, vector: &[f32]) -> Result<u32, JsError> {
|
||||
vector_types::verified_dim_check(&mut self.env, index_dim, vector)
|
||||
.map(|op| op.proof_id)
|
||||
.map_err(|e| JsError::new(&e.to_string()))
|
||||
}
|
||||
|
||||
/// Verify a batch of vectors (passed as flat f32 array + dimension).
|
||||
///
|
||||
/// `flat_vectors` is a contiguous f32 array; each vector is `dim` elements.
|
||||
/// Returns the number of vectors verified.
|
||||
pub fn verify_batch_flat(&mut self, dim: u32, flat_vectors: &[f32]) -> Result<u32, JsError> {
|
||||
let d = dim as usize;
|
||||
if flat_vectors.len() % d != 0 {
|
||||
return Err(JsError::new(&format!(
|
||||
"flat_vectors length {} not divisible by dim {}",
|
||||
flat_vectors.len(),
|
||||
dim
|
||||
)));
|
||||
}
|
||||
let slices: Vec<&[f32]> = flat_vectors.chunks_exact(d).collect();
|
||||
vector_types::verify_batch_dimensions(&mut self.env, dim, &slices)
|
||||
.map(|op| op.value as u32)
|
||||
.map_err(|e| JsError::new(&e.to_string()))
|
||||
}
|
||||
|
||||
/// Intern a hash into the FastTermArena. Returns `[term_id, was_cached]`.
|
||||
pub fn arena_intern(&self, hash_hi: u32, hash_lo: u32) -> Vec<u32> {
|
||||
let hash = (hash_hi as u64) << 32 | hash_lo as u64;
|
||||
let (id, cached) = self.arena.intern(hash);
|
||||
vec![id, if cached { 1 } else { 0 }]
|
||||
}
|
||||
|
||||
/// Route a proof to the cheapest tier. Returns tier name.
|
||||
pub fn route_proof(&self, kind: &str) -> Result<JsValue, JsError> {
|
||||
let proof_kind = match kind {
|
||||
"reflexivity" => ProofKind::Reflexivity,
|
||||
"dimension" => ProofKind::DimensionEquality {
|
||||
expected: 0,
|
||||
actual: 0,
|
||||
},
|
||||
"pipeline" => ProofKind::PipelineComposition { stages: 1 },
|
||||
other => ProofKind::Custom {
|
||||
estimated_complexity: other.parse().unwrap_or(10),
|
||||
},
|
||||
};
|
||||
let decision = gated::route_proof(proof_kind, &self.env);
|
||||
let tier_name = match decision.tier {
|
||||
ProofTier::Reflex => "reflex",
|
||||
ProofTier::Standard { .. } => "standard",
|
||||
ProofTier::Deep => "deep",
|
||||
};
|
||||
let result = JsRoutingResult {
|
||||
tier: tier_name.to_string(),
|
||||
reason: decision.reason.to_string(),
|
||||
estimated_steps: decision.estimated_steps,
|
||||
};
|
||||
serde_wasm_bindgen::to_value(&result).map_err(|e| JsError::new(&e.to_string()))
|
||||
}
|
||||
|
||||
/// Create a proof attestation (82 bytes). Returns serializable object.
|
||||
pub fn create_attestation(&self, proof_id: u32) -> Result<JsValue, JsError> {
|
||||
let att = proof_store::create_attestation(&self.env, proof_id);
|
||||
let bytes = att.to_bytes();
|
||||
let result = JsAttestation {
|
||||
bytes,
|
||||
proof_term_hash: hex_encode(&att.proof_term_hash),
|
||||
environment_hash: hex_encode(&att.environment_hash),
|
||||
verifier_version: format!("{:#010x}", att.verifier_version),
|
||||
reduction_steps: att.reduction_steps,
|
||||
cache_hit_rate_bps: att.cache_hit_rate_bps,
|
||||
};
|
||||
serde_wasm_bindgen::to_value(&result).map_err(|e| JsError::new(&e.to_string()))
|
||||
}
|
||||
|
||||
/// Get verification statistics.
|
||||
pub fn stats(&self) -> Result<JsValue, JsError> {
|
||||
let s = self.env.stats();
|
||||
let arena_stats = self.arena.stats();
|
||||
let cache_stats = self.cache.stats();
|
||||
let result = JsStats {
|
||||
proofs_constructed: s.proofs_constructed,
|
||||
proofs_verified: s.proofs_verified,
|
||||
cache_hits: s.cache_hits,
|
||||
cache_misses: s.cache_misses,
|
||||
total_reductions: s.total_reductions,
|
||||
terms_allocated: self.env.terms_allocated(),
|
||||
arena_hit_rate: arena_stats.cache_hit_rate(),
|
||||
conversion_cache_hit_rate: cache_stats.hit_rate(),
|
||||
};
|
||||
serde_wasm_bindgen::to_value(&result).map_err(|e| JsError::new(&e.to_string()))
|
||||
}
|
||||
|
||||
/// Reset the environment (clears cache, resets counters, re-registers builtins).
|
||||
pub fn reset(&mut self) {
|
||||
self.env.reset();
|
||||
self.arena.reset();
|
||||
self.cache.clear();
|
||||
}
|
||||
|
||||
/// Number of terms currently allocated.
|
||||
pub fn terms_allocated(&self) -> u32 {
|
||||
self.env.terms_allocated()
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// JSON result types
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct JsRoutingResult {
|
||||
tier: String,
|
||||
reason: String,
|
||||
estimated_steps: u32,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct JsAttestation {
|
||||
bytes: Vec<u8>,
|
||||
proof_term_hash: String,
|
||||
environment_hash: String,
|
||||
verifier_version: String,
|
||||
reduction_steps: u32,
|
||||
cache_hit_rate_bps: u16,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct JsStats {
|
||||
proofs_constructed: u64,
|
||||
proofs_verified: u64,
|
||||
cache_hits: u64,
|
||||
cache_misses: u64,
|
||||
total_reductions: u64,
|
||||
terms_allocated: u32,
|
||||
arena_hit_rate: f64,
|
||||
conversion_cache_hit_rate: f64,
|
||||
}
|
||||
|
||||
fn hex_encode(bytes: &[u8]) -> String {
|
||||
bytes.iter().map(|b| format!("{b:02x}")).collect()
|
||||
}
|
||||
12
vendor/ruvector/crates/ruvector-verified-wasm/src/utils.rs
vendored
Normal file
12
vendor/ruvector/crates/ruvector-verified-wasm/src/utils.rs
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
//! WASM utility helpers.
|
||||
|
||||
/// Set panic hook for better panic messages in the browser.
|
||||
pub fn set_panic_hook() {
|
||||
// No-op if console_error_panic_hook is not available.
|
||||
// In production, add the crate and feature for better diagnostics.
|
||||
}
|
||||
|
||||
/// Log a message to the browser console.
|
||||
pub fn console_log(msg: &str) {
|
||||
web_sys::console::log_1(&msg.into());
|
||||
}
|
||||
46
vendor/ruvector/crates/ruvector-verified-wasm/tests/web.rs
vendored
Normal file
46
vendor/ruvector/crates/ruvector-verified-wasm/tests/web.rs
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
//! WASM integration tests (run with wasm-pack test --headless --chrome).
|
||||
|
||||
#![cfg(target_arch = "wasm32")]
|
||||
|
||||
use wasm_bindgen_test::*;
|
||||
|
||||
wasm_bindgen_test_configure!(run_in_browser);
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_version() {
|
||||
let v = ruvector_verified_wasm::version();
|
||||
assert_eq!(v, "0.1.0");
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_proof_env_creation() {
|
||||
let mut env = ruvector_verified_wasm::JsProofEnv::new();
|
||||
assert_eq!(env.terms_allocated(), 0);
|
||||
let proof = env.prove_dim_eq(128, 128).unwrap();
|
||||
assert!(env.terms_allocated() > 0);
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_dim_mismatch() {
|
||||
let mut env = ruvector_verified_wasm::JsProofEnv::new();
|
||||
let result = env.prove_dim_eq(128, 256);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_verify_batch_flat() {
|
||||
let mut env = ruvector_verified_wasm::JsProofEnv::new();
|
||||
// 3 vectors of dimension 4
|
||||
let flat: Vec<f32> = vec![0.0; 12];
|
||||
let count = env.verify_batch_flat(4, &flat).unwrap();
|
||||
assert_eq!(count, 3);
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_reset() {
|
||||
let mut env = ruvector_verified_wasm::JsProofEnv::new();
|
||||
env.prove_dim_eq(64, 64).unwrap();
|
||||
assert!(env.terms_allocated() > 0);
|
||||
env.reset();
|
||||
assert_eq!(env.terms_allocated(), 0);
|
||||
}
|
||||
Reference in New Issue
Block a user