{"id":49507535,"url":"https://github.com/cedoor/squid","last_synced_at":"2026-05-01T17:36:36.773Z","repository":{"id":350644147,"uuid":"1204955509","full_name":"cedoor/squid","owner":"cedoor","description":"A ergonomic Rust wrapper for Poulpy + Browser and Node bindings.","archived":false,"fork":false,"pushed_at":"2026-04-28T13:38:02.000Z","size":114,"stargazers_count":2,"open_issues_count":12,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-28T15:29:16.801Z","etag":null,"topics":["fhe","poulpy"],"latest_commit_sha":null,"homepage":"https://squid.cedoor.dev/","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cedoor.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-08T13:49:55.000Z","updated_at":"2026-04-28T13:38:16.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/cedoor/squid","commit_stats":null,"previous_names":["cedoor/squid"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/cedoor/squid","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cedoor%2Fsquid","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cedoor%2Fsquid/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cedoor%2Fsquid/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cedoor%2Fsquid/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cedoor","download_url":"https://codeload.github.com/cedoor/squid/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cedoor%2Fsquid/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32507091,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-30T13:12:12.517Z","status":"online","status_checked_at":"2026-05-01T02:00:05.856Z","response_time":64,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["fhe","poulpy"],"created_at":"2026-05-01T17:36:36.155Z","updated_at":"2026-05-01T17:36:36.762Z","avatar_url":"https://github.com/cedoor.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 🦑 Squid\n\n**An ergonomic Rust wrapper ([`squid`](crates/squid)) for [Poulpy](https://github.com/poulpy-fhe/poulpy) and the [`squid-js`](packages/squid-js) library for browser and Node (WebAssembly + napi-rs), making Fully Homomorphic Encryption accessible without sacrificing control.**\n\n[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) [![CI](https://github.com/cedoor/squid/actions/workflows/ci.yml/badge.svg)](https://github.com/cedoor/squid/actions) [![Live demo](https://img.shields.io/badge/demo-squid.cedoor.dev-blueviolet)](https://squid.cedoor.dev/) ![Status](https://img.shields.io/badge/status-early%20development-orange)\n\nPoulpy is a low-level, modular toolkit exposing the full machinery of lattice-based homomorphic encryption. That power comes with sharp edges: manual scratch arenas, explicit lifecycle transitions, trait-heavy APIs. [`squid`](crates/squid) wraps Poulpy with a smaller, opinionated surface so you can write FHE programs without managing every byte of workspace memory or tracking which representation a ciphertext currently lives in.\n\nFor JavaScript and TypeScript, **[`squid-js`](packages/squid-js)** exposes the same stack: **`squid-js/client`** runs keygen, encrypt, and decrypt in the browser over WebAssembly (typically inside a dedicated worker so crypto stays off the UI thread), while **`squid-js/server`** runs homomorphic evaluation in Node via a native addon (napi-rs). In the usual setup the secret key stays on the client; the server only receives the evaluation key and ciphertexts.\n\n**Current scope:** [`squid`](crates/squid) wraps Poulpy's `bin_fhe::bdd_arithmetic` layer: gate-level FHE on encrypted unsigned integers (`u8`, `u16`, `u32`). This is the only fully exposed end-to-end capability in `poulpy-schemes` today. The API will expand as Poulpy adds more scheme-level implementations.\n\n## Monorepo structure\n\n| Path                                     | Description                                                                                                             |\n| ---------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |\n| [`crates/squid`](crates/squid)           | Rust library — ergonomic Poulpy wrapper (this is the main crate)                                                        |\n| [`crates/squid-wasm`](crates/squid-wasm) | WebAssembly bindings via `wasm-bindgen` (browser)                                                                       |\n| [`crates/squid-napi`](crates/squid-napi) | Node.js native bindings via `napi-rs` (server)                                                                          |\n| [`packages/squid-js`](packages/squid-js) | npm package — browser client + Node evaluator                                                                           |\n| [`demo`](demo)                           | Next.js demo: keygen in browser, homomorphic eval on server — **[live at squid.cedoor.dev](https://squid.cedoor.dev/)** |\n| [`tests`](tests)                         | Playwright end-to-end tests for the demo                                                                                |\n\nThe Cargo workspace ties the three Rust crates together. The pnpm workspace ties [`squid-js`](packages/squid-js), the demo, and the E2E tests together.\n\n## Usage (Rust)\n\n### Quick start\n\n```rust\nuse squid::{Context, Params};\n\nfn main() {\n    // Demo preset — not a vetted security level (see Params::unsecure docs)\n    let mut ctx = Context::new(Params::unsecure());\n\n    // Generate keys (secret key + evaluation key)\n    let (sk, ek) = ctx.keygen();\n\n    // Encrypt two 32-bit integers\n    let a = ctx.encrypt::\u003cu32\u003e(255, \u0026sk);\n    let b = ctx.encrypt::\u003cu32\u003e(30, \u0026sk);\n\n    // Homomorphic addition: computes (a + b) under encryption\n    let c = ctx.add(\u0026a, \u0026b, \u0026ek);\n\n    // Decrypt the result\n    let result: u32 = ctx.decrypt(\u0026c, \u0026sk);\n    assert_eq!(result, 255_u32.wrapping_add(30));\n    println!(\"255 + 30 = {result}\");\n}\n```\n\n### Preset name (config / CLI)\n\nBuilt-in bundles can be selected by string: `Params::by_name(\"unsecure\")` and `Params::by_name(\"test\")` each return `Some(Params)`, and any other input returns `None`. Prefer `Params::unsecure()` / `Params::test()` when the choice is fixed in code.\n\n### Serialize / deserialize an evaluation key\n\nThe evaluation key is public material needed for every homomorphic op. Persist\nit once and reload it on the server that runs the circuits. The blob is\nversioned and tied to the [`Params`](crates/squid/src/context.rs) used at keygen — loading\nunder different parameters returns an `io::Error`.\n\n```rust\nuse squid::{Context, EvaluationKey, Params};\n\nlet mut ctx = Context::new(Params::unsecure());\nlet (_sk, ek) = ctx.keygen();\n\n// Serialize to a versioned little-endian blob.\nlet blob: Vec\u003cu8\u003e = ctx.serialize_evaluation_key(\u0026ek).unwrap();\nstd::fs::write(\"ek.bin\", \u0026blob).unwrap();\n\n// Reload later, under the same Params, into a fresh Context.\nlet mut ctx = Context::new(Params::unsecure());\nlet bytes = std::fs::read(\"ek.bin\").unwrap();\nlet ek: EvaluationKey = ctx.deserialize_evaluation_key(\u0026bytes).unwrap();\n```\n\nSecret keys do not expose binary I/O — persist\n[`KeygenSeeds`](#deterministic-key-generation-from-seeds) instead.\n\n### Serialize / deserialize a ciphertext\n\nCiphertexts are the wire format for sending encrypted values between parties.\nThe blob records the plaintext bit width and GLWE layout, so mismatched\nparameters or a wrong `T` are rejected before any ciphertext bytes are read.\n\n```rust\nuse squid::{Ciphertext, Context, Params};\n\nlet mut ctx = Context::new(Params::unsecure());\nlet (sk, _ek) = ctx.keygen();\n\nlet ct = ctx.encrypt::\u003cu32\u003e(42, \u0026sk);\nlet blob: Vec\u003cu8\u003e = ctx.serialize_ciphertext(\u0026ct).unwrap();\n\n// Reload with the same T and the same Params.\nlet ct: Ciphertext\u003cu32\u003e = ctx.deserialize_ciphertext(\u0026blob).unwrap();\nassert_eq!(ctx.decrypt::\u003cu32\u003e(\u0026ct, \u0026sk), 42);\n```\n\n### Deterministic key generation from seeds\n\nPoulpy does not expose a stable wire format for secret keys. To reproduce the\nsame `(SecretKey, EvaluationKey)` pair across runs or machines, persist the\nthree 32-byte ChaCha8 seeds returned by `keygen_with_seeds` and rebuild with\n`keygen_from_seeds`. Same `Params`, same backend, same keys.\n\n```rust\nuse squid::{Context, KeygenSeeds, Params};\n\nlet mut ctx = Context::new(Params::unsecure());\n\n// OS-random seeds (kept so we can replay keygen).\nlet (sk, ek, seeds) = ctx.keygen_with_seeds();\n\n// Persist the seeds at the app level — `KeygenSeeds` redacts its Debug output.\nlet KeygenSeeds { lattice, bdd_mask, bdd_noise } = seeds;\n// std::fs::write(\"seeds.bin\", [lattice, bdd_mask, bdd_noise].concat()).unwrap();\n\n// Later: regenerate the same keys deterministically.\nlet mut ctx = Context::new(Params::unsecure());\nlet (sk2, ek2) = ctx.keygen_from_seeds(seeds);\n\n// If you only need encrypt/decrypt (no homomorphic ops), the lattice seed alone\n// is enough to rebuild the SecretKey — no EvaluationKey produced.\nlet sk_only = ctx.secret_key_from_lattice_seed(seeds.lattice);\n```\n\nTreat the seeds as secret: anyone holding them can reconstruct `sk`.\n\n## Operations\n\nAll operations currently require `T = u32` (the only width with compiled BDD circuits in Poulpy). Encrypt and decrypt work for `u8`, `u16`, and `u32`.\n\n| Method               | Description            |\n| -------------------- | ---------------------- |\n| `ctx.add(a, b, ek)`  | Wrapping addition      |\n| `ctx.sub(a, b, ek)`  | Wrapping subtraction   |\n| `ctx.and(a, b, ek)`  | Bitwise AND            |\n| `ctx.or(a, b, ek)`   | Bitwise OR             |\n| `ctx.xor(a, b, ek)`  | Bitwise XOR            |\n| `ctx.sll(a, b, ek)`  | Logical left shift     |\n| `ctx.srl(a, b, ek)`  | Logical right shift    |\n| `ctx.sra(a, b, ek)`  | Arithmetic right shift |\n| `ctx.slt(a, b, ek)`  | Signed less-than       |\n| `ctx.sltu(a, b, ek)` | Unsigned less-than     |\n\nIntegration tests in [`crates/squid/tests/bdd_ops.rs`](crates/squid/tests/bdd_ops.rs) exercise every method in the table above on `u32` with `Params::test()` and deterministic seeds; run them with `cargo test -p squid`.\n\n## Backends\n\n| Feature       | Backend    | Notes                           |\n| ------------- | ---------- | ------------------------------- |\n| _(default)_   | `FFT64Ref` | Portable                        |\n| `backend-avx` | `FFT64Avx` | x86-64, AVX2+FMA (~3–5× vs ref) |\n\n```sh\nRUSTFLAGS=\"-C target-cpu=native\" cargo build --release --features backend-avx\n```\n\nThe public API is identical regardless of which backend is selected.\n\n## Roadmap\n\n### Milestone 1 — Working Foundation: [#1](https://github.com/cedoor/squid/milestone/1)\n\n- [x] Write README with installation, quick start example: [#2](https://github.com/cedoor/squid/issues/2)\n- [x] Set up GitHub Actions (cargo test, cargo clippy, cargo fmt check): [#3](https://github.com/cedoor/squid/issues/3)\n- [x] Release first alpha version: [#5](https://github.com/cedoor/squid/issues/5)\n- [x] Add at least one runnable example in examples/: [#7](https://github.com/cedoor/squid/issues/7)\n- [x] Add tests for all existing ops: [#4](https://github.com/cedoor/squid/issues/4)\n- [ ] Add rustdoc comments to all public items: [#6](https://github.com/cedoor/squid/issues/6)\n- [x] Faster tests via fixtures or deterministic keygen: [#19](https://github.com/cedoor/squid/issues/19)\n- [ ] Refactor `context.rs`: [#20](https://github.com/cedoor/squid/issues/20)\n\n### Milestone 2 — Full bin_fhe Coverage: [#2](https://github.com/cedoor/squid/milestone/2)\n\n- [ ] Wrap Poulpy's blind selection / retrieval primitives: [#8](https://github.com/cedoor/squid/issues/8)\n- [x] Multi-threaded evaluation: [#9](https://github.com/cedoor/squid/issues/9)\n- [ ] Sub-word operations: [#10](https://github.com/cedoor/squid/issues/10)\n- [ ] Identity / noise refresh: [#11](https://github.com/cedoor/squid/issues/11)\n- [ ] NTT backend: [#12](https://github.com/cedoor/squid/issues/12)\n- [x] Key serialization: [#13](https://github.com/cedoor/squid/issues/13)\n- [x] Revert `encrypt` workaround once upstream poulpy bug is fixed: [#24](https://github.com/cedoor/squid/issues/24)\n\n### Milestone 3 — Developer Experience \u0026 Optimization: [#3](https://github.com/cedoor/squid/milestone/3)\n\n- [x] WASM crate: [#14](https://github.com/cedoor/squid/issues/14)\n- [ ] Params validation with clear error messages: [#15](https://github.com/cedoor/squid/issues/15)\n- [ ] Realistic examples: [#16](https://github.com/cedoor/squid/issues/16)\n- [ ] Benchmarks: [#17](https://github.com/cedoor/squid/issues/17)\n- [ ] Vetted Params presets: [#18](https://github.com/cedoor/squid/issues/18)\n- [x] Add CHANGELOG file: [#26](https://github.com/cedoor/squid/issues/26)\n- [x] [#22](https://github.com/cedoor/squid/issues/22) — closed: `Context` no longer keeps a persistent max-sized arena; scratch is allocated per operation from Poulpy's `*_tmp_bytes` (supersedes the issue's \"split keygen vs runtime\" split).\n\n## Design goals\n\n- **Hide scratch management.** Callers never allocate or thread scratch buffers.\n- **Hide lifecycle transitions.** The Standard → Prepared → BDD-eval pipeline is handled internally; users see one coherent `Ciphertext\u003cT\u003e` type.\n- **Explicitly non-production defaults.** `Params::unsecure()` matches Poulpy's `bdd_arithmetic` example for demos; treat it as unaudited unless you analyse parameters yourself.\n- **No magic.** Every abstraction is traceable to the underlying Poulpy call. No hidden global state; scratch is sized with Poulpy's `*_tmp_bytes` at each operation.\n- **Safe defaults.** Every user-facing choice has a default that works without configuration. Alternatives are documented with their trade-offs and the conditions under which they should be preferred.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcedoor%2Fsquid","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcedoor%2Fsquid","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcedoor%2Fsquid/lists"}