mirror of
https://github.com/luxfi/crypto.git
synced 2026-07-27 01:54:50 +00:00
merge: bls-rust-2026-04-27
This commit is contained in:
Generated
+20
@@ -22,6 +22,14 @@ version = "0.1.0"
|
||||
name = "lux-crypto-blake3"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "lux-crypto-bls"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "lux-crypto-ed25519"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "lux-crypto-evm256"
|
||||
version = "0.1.0"
|
||||
@@ -53,6 +61,10 @@ version = "0.1.0"
|
||||
name = "lux-crypto-mlkem"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "lux-crypto-ntt"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "lux-crypto-pedersen"
|
||||
version = "0.1.0"
|
||||
@@ -60,6 +72,14 @@ dependencies = [
|
||||
"lux-crypto-banderwagon",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lux-crypto-poly_mul"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "lux-crypto-poseidon"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "lux-crypto-ripemd160"
|
||||
version = "0.1.0"
|
||||
|
||||
@@ -21,17 +21,33 @@ members = [
|
||||
"lux-crypto-ripemd160",
|
||||
"lux-crypto-blake2b",
|
||||
"lux-crypto-blake3",
|
||||
"lux-crypto-ed25519",
|
||||
"lux-crypto-aead",
|
||||
"lux-crypto-bls",
|
||||
"lux-crypto-lamport",
|
||||
"lux-crypto-mldsa",
|
||||
"lux-crypto-mlkem",
|
||||
"lux-crypto-slhdsa",
|
||||
"lux-crypto-banderwagon",
|
||||
"lux-crypto-pedersen",
|
||||
"lux-crypto-ipa",
|
||||
"lux-crypto-verkle",
|
||||
"lux-crypto-evm256",
|
||||
"lux-crypto-kzg",
|
||||
"lux-crypto-poseidon",
|
||||
"lux-crypto-ntt",
|
||||
"lux-crypto-poly_mul",
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
edition = "2021"
|
||||
license = "BSD-3-Clause"
|
||||
rust-version = "1.74"
|
||||
authors = ["Lux Industries Inc. <opensource@lux.network>"]
|
||||
homepage = "https://lux.network"
|
||||
repository = "https://github.com/luxfi/crypto"
|
||||
documentation = "https://docs.lux.network/crypto"
|
||||
readme = "README.md"
|
||||
|
||||
[workspace.lints.rust]
|
||||
unsafe_op_in_unsafe_fn = "warn"
|
||||
|
||||
@@ -0,0 +1,225 @@
|
||||
# Publishing the lux-crypto Rust crates to crates.io
|
||||
|
||||
This runbook covers publishing the 23 Rust binding crates that
|
||||
live under `rust/lux-crypto-*` (22 algorithm crates plus the `lux-crypto`
|
||||
umbrella).
|
||||
|
||||
The crates are all FFI bindings to static archives produced by
|
||||
`luxcpp/crypto`. Because of that, **`cargo publish` cannot run the verify
|
||||
step** in isolation -- the verify step does an isolated rebuild that does
|
||||
not have access to the C archives. Use `--no-verify` for the actual upload.
|
||||
Dry-run packaging (`cargo publish --dry-run --no-verify`) succeeds for all
|
||||
23 crates in this workspace.
|
||||
|
||||
## 1. License decision (BLOCKER -- humans must answer first)
|
||||
|
||||
The repository-level `LICENSE` file is the **Lux Ecosystem License v1.2**,
|
||||
which restricts commercial use to the Lux Primary Network and descending
|
||||
chains. The per-crate `Cargo.toml` declares
|
||||
`license = "BSD-3-Clause"` (a valid SPDX identifier), and per-file SPDX
|
||||
headers in `src/*.rs` declare `BSD-3-Clause-Eco` (an internal marker).
|
||||
|
||||
Before publishing, decide one of:
|
||||
|
||||
1. Change every crate to `license-file = "LICENSE"` and bundle the
|
||||
Lux Ecosystem License with each published crate. Publishing a non-OSI
|
||||
license to crates.io is permitted but will surface a yellow banner on
|
||||
crates.io.
|
||||
2. Keep `license = "BSD-3-Clause"` and ensure the Rust source code in
|
||||
`rust/` is genuinely BSD-3-Clause (independent of the umbrella project
|
||||
license). This is the current state; verify with legal that this is
|
||||
intended.
|
||||
3. Adopt a dual license (e.g. `Apache-2.0 OR MIT`) and re-stamp every
|
||||
`Cargo.toml` and source-file SPDX header.
|
||||
|
||||
**Until this is resolved, do not publish.** The dry-runs are clean; the
|
||||
remaining work is a license sign-off, then the upload itself.
|
||||
|
||||
## 2. crates.io account + token
|
||||
|
||||
```bash
|
||||
# One-time per publisher account
|
||||
cargo login <token> # token from https://crates.io/me
|
||||
```
|
||||
|
||||
The publisher account must be a member of the
|
||||
[`luxfi` GitHub org](https://github.com/luxfi) and have crates.io publish
|
||||
permission. To grant publish access for an existing crate:
|
||||
|
||||
```bash
|
||||
cargo owner --add github:luxfi:rust-publishers lux-crypto-<alg>
|
||||
```
|
||||
|
||||
## 3. Name reservation order
|
||||
|
||||
Name reservation on crates.io is "first push wins". Reserve all 20 names in
|
||||
one session to avoid squatters. Recommended order (umbrella last so it can
|
||||
pin the others as deps in a future release):
|
||||
|
||||
```text
|
||||
lux-crypto-aead
|
||||
lux-crypto-banderwagon
|
||||
lux-crypto-blake2b
|
||||
lux-crypto-blake3
|
||||
lux-crypto-bls
|
||||
lux-crypto-ed25519
|
||||
lux-crypto-evm256
|
||||
lux-crypto-ipa
|
||||
lux-crypto-keccak
|
||||
lux-crypto-kzg
|
||||
lux-crypto-lamport
|
||||
lux-crypto-mldsa
|
||||
lux-crypto-mlkem
|
||||
lux-crypto-ntt
|
||||
lux-crypto-pedersen
|
||||
lux-crypto-poly_mul
|
||||
lux-crypto-poseidon
|
||||
lux-crypto-ripemd160
|
||||
lux-crypto-secp256k1
|
||||
lux-crypto-sha256
|
||||
lux-crypto-slhdsa
|
||||
lux-crypto-verkle
|
||||
lux-crypto # umbrella, last
|
||||
```
|
||||
|
||||
## 4. Per-crate dry-run (already verified, included for reproduction)
|
||||
|
||||
Run from `rust/`:
|
||||
|
||||
```bash
|
||||
for c in lux-crypto-aead lux-crypto-banderwagon lux-crypto-blake2b \
|
||||
lux-crypto-blake3 lux-crypto-bls lux-crypto-ed25519 \
|
||||
lux-crypto-evm256 lux-crypto-ipa lux-crypto-keccak \
|
||||
lux-crypto-kzg lux-crypto-lamport lux-crypto-mldsa \
|
||||
lux-crypto-mlkem lux-crypto-ntt lux-crypto-pedersen \
|
||||
lux-crypto-poly_mul lux-crypto-poseidon lux-crypto-ripemd160 \
|
||||
lux-crypto-secp256k1 lux-crypto-sha256 lux-crypto-slhdsa \
|
||||
lux-crypto-verkle lux-crypto; do
|
||||
cargo publish --dry-run -p "$c" --no-verify --allow-dirty || break
|
||||
done
|
||||
```
|
||||
|
||||
Every crate must report `aborting upload due to dry run`. As of
|
||||
2026-04-27 all 22 crates pass this check.
|
||||
|
||||
## 5. Upload (ordered)
|
||||
|
||||
```bash
|
||||
# Repeat for every crate name in step 3
|
||||
cargo publish -p lux-crypto-aead --no-verify
|
||||
sleep 30 # let crates.io index update before publishing dependents
|
||||
|
||||
# ... and so on for each crate
|
||||
```
|
||||
|
||||
`--no-verify` is required because the verify step rebuilds in isolation
|
||||
without the `luxcpp/crypto` archives. End-users will hit the same isolation
|
||||
when they consume the published crate; the build script documents the
|
||||
required `CRYPTO_DIR` / `CRYPTO_BUILD_DIR` environment variables in every
|
||||
crate's README.
|
||||
|
||||
## 6. Native-archive dependency (Option C, documented)
|
||||
|
||||
These crates link against static archives produced by the C++ project at
|
||||
`https://github.com/luxfi/crypto`. Downstream consumers must build those
|
||||
archives once and point the build script at them via:
|
||||
|
||||
```bash
|
||||
export CRYPTO_DIR=$HOME/.local # install prefix
|
||||
# or
|
||||
export CRYPTO_BUILD_DIR=$(pwd)/build # cmake build dir
|
||||
cargo build -p lux-crypto-<alg>
|
||||
```
|
||||
|
||||
Each per-crate `README.md` documents this. We deliberately did not bundle
|
||||
the C source (option B): the C codebase is large, has its own build
|
||||
toolchain, and bundling would force `cmake + clang` to be available at
|
||||
`cargo build` time for every consumer.
|
||||
|
||||
If a future release wants in-source builds, the path is:
|
||||
|
||||
1. Add a `[features]` section per crate with a default feature `linked`
|
||||
(current behaviour) and an opt-in feature `vendored` that vendors a
|
||||
tagged snapshot of `luxcpp/crypto` and runs cmake from `build.rs`.
|
||||
2. Add `cmake = "0.1"` and `cc = "1.0"` to `[build-dependencies]` only on
|
||||
the vendored feature path.
|
||||
3. Document the feature flag in each README.
|
||||
|
||||
This is deferred until there is demand from external consumers.
|
||||
|
||||
## 7. Post-publish verification
|
||||
|
||||
For each published crate, confirm:
|
||||
|
||||
- `https://crates.io/crates/lux-crypto-<alg>` renders
|
||||
- `https://docs.rs/lux-crypto-<alg>` renders (docs.rs builds without
|
||||
CRYPTO_DIR; the build will fail there until we either add a `docs.rs`
|
||||
metadata block that skips link, or wire a vendored build path; see #8)
|
||||
|
||||
## 8. docs.rs build (known follow-up)
|
||||
|
||||
docs.rs builds in a sandbox without `CRYPTO_DIR`. To make documentation
|
||||
build there, add to each per-crate `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
```
|
||||
|
||||
and gate the `extern "C"` blocks behind `#[cfg(not(docsrs))]` so docs.rs
|
||||
only compiles signatures, not the link step. This is a follow-up; not
|
||||
required for the initial publish.
|
||||
|
||||
## 9. Yanking
|
||||
|
||||
If a release contains a wire-format incompatibility, yank with:
|
||||
|
||||
```bash
|
||||
cargo yank --vers 0.1.0 -p lux-crypto-<alg>
|
||||
```
|
||||
|
||||
Yanked versions stay published but are no longer selected by Cargo's
|
||||
resolver.
|
||||
|
||||
## 10. Versioning policy
|
||||
|
||||
- Stay on `0.1.x` until the C-ABI surface stabilises.
|
||||
- A breaking change to the C-ABI bumps the **minor** version
|
||||
(`0.1.x -> 0.2.0`) for every crate in lockstep.
|
||||
- Never bump to `1.0.0` until the FFI surface is frozen and audit-stable.
|
||||
|
||||
## 11. Workspace package fields
|
||||
|
||||
The workspace at `rust/Cargo.toml` provides:
|
||||
|
||||
```toml
|
||||
[workspace.package]
|
||||
edition = "2021"
|
||||
license = "BSD-3-Clause"
|
||||
rust-version = "1.74"
|
||||
authors = ["Lux Industries Inc. <opensource@lux.network>"]
|
||||
homepage = "https://lux.network"
|
||||
repository = "https://github.com/luxfi/crypto"
|
||||
documentation = "https://docs.lux.network/crypto"
|
||||
readme = "README.md"
|
||||
```
|
||||
|
||||
Per-crate `Cargo.toml` files inherit via `field.workspace = true`. Add
|
||||
crate-specific `keywords`, `categories`, and `description` per crate.
|
||||
|
||||
## 12. Source attribution checklist
|
||||
|
||||
| Crate | Vendored / derived | Upstream license |
|
||||
|-------|--------------------|------------------|
|
||||
| `lux-crypto-blake3` | BLAKE3 ref v1.5.0 | CC0 / Apache-2.0 |
|
||||
| `lux-crypto-ed25519` | ed25519-donna | Public domain |
|
||||
| `lux-crypto-mldsa`, `lux-crypto-mlkem`, `lux-crypto-slhdsa` | PQClean | CC0 / public domain |
|
||||
| `lux-crypto-kzg` | c-kzg-4844 | Apache-2.0 |
|
||||
| `lux-crypto-evm256` | intx + evmmax | Apache-2.0 |
|
||||
| `lux-crypto-poseidon` | gnark-crypto v0.20.1 round constants | Apache-2.0 |
|
||||
| `lux-crypto-banderwagon`, `lux-crypto-verkle` | Public Banderwagon SRS | CC0 |
|
||||
|
||||
Every crate's README declares the vendored source. The Cargo metadata
|
||||
license field describes the **Rust binding** license, not the upstream
|
||||
algorithm authors' license; both are compatible.
|
||||
@@ -0,0 +1,27 @@
|
||||
# lux-crypto-aead
|
||||
|
||||
Canonical Rust binding for **ChaCha20-Poly1305 AEAD** (RFC 8439).
|
||||
|
||||
Wraps the C-ABI exposed by `luxcpp/crypto/aead`. Verified against the RFC 8439
|
||||
reference vectors.
|
||||
|
||||
## Algorithm
|
||||
|
||||
- **ChaCha20-Poly1305** -- AEAD, RFC 8439
|
||||
- 256-bit key, 96-bit nonce, 16-byte tag
|
||||
|
||||
## Build
|
||||
|
||||
Set `CRYPTO_DIR` (install prefix) or `CRYPTO_BUILD_DIR` (cmake build dir) so
|
||||
the build script can find `libaead_cpu.a`.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/luxfi/crypto
|
||||
cd crypto && cmake -S . -B build-cto && cmake --build build-cto
|
||||
export CRYPTO_BUILD_DIR=$(pwd)/build-cto
|
||||
cargo build -p lux-crypto-aead
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
See `LICENSE` at the repository root.
|
||||
@@ -0,0 +1,31 @@
|
||||
# lux-crypto-banderwagon
|
||||
|
||||
Canonical Rust binding for **Banderwagon**, the prime-order subgroup of the
|
||||
Bandersnatch curve used in Verkle commitments.
|
||||
|
||||
Wraps the C-ABI exposed by `luxcpp/crypto/banderwagon`.
|
||||
|
||||
## Algorithm
|
||||
|
||||
- **Banderwagon** -- prime-order subgroup of Bandersnatch (twisted Edwards over BLS12-381 scalar)
|
||||
- Used as the base group for Pedersen / IPA / Verkle
|
||||
|
||||
## Build
|
||||
|
||||
Set `CRYPTO_DIR` (install prefix) or `CRYPTO_BUILD_DIR` (cmake build dir) so
|
||||
the build script can find `libbanderwagon_cpu.a`.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/luxfi/crypto
|
||||
cd crypto && cmake -S . -B build-cto && cmake --build build-cto
|
||||
export CRYPTO_BUILD_DIR=$(pwd)/build-cto
|
||||
cargo build -p lux-crypto-banderwagon
|
||||
```
|
||||
|
||||
## Attribution
|
||||
|
||||
Curve constants derived from the public Banderwagon SRS.
|
||||
|
||||
## License
|
||||
|
||||
See `LICENSE` at the repository root.
|
||||
@@ -0,0 +1,26 @@
|
||||
# lux-crypto-blake2b
|
||||
|
||||
Canonical Rust binding for **BLAKE2b** (RFC 7693).
|
||||
|
||||
Wraps the C-ABI exposed by `luxcpp/crypto/blake2b`. Verified against the
|
||||
RFC 7693 reference vectors.
|
||||
|
||||
## Algorithm
|
||||
|
||||
- **BLAKE2b** -- variable digest length up to 64 bytes, RFC 7693
|
||||
|
||||
## Build
|
||||
|
||||
Set `CRYPTO_DIR` (install prefix) or `CRYPTO_BUILD_DIR` (cmake build dir) so
|
||||
the build script can find `libblake2b_cpu.a`.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/luxfi/crypto
|
||||
cd crypto && cmake -S . -B build-cto && cmake --build build-cto
|
||||
export CRYPTO_BUILD_DIR=$(pwd)/build-cto
|
||||
cargo build -p lux-crypto-blake2b
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
See `LICENSE` at the repository root.
|
||||
@@ -0,0 +1,31 @@
|
||||
# lux-crypto-blake3
|
||||
|
||||
Canonical Rust binding for **BLAKE3** (vendored BLAKE3 reference v1.5.0).
|
||||
|
||||
Wraps the C-ABI exposed by `luxcpp/crypto/blake3`. Verified against the
|
||||
official BLAKE3 KAT vector set (140 vectors) in `tests/`.
|
||||
|
||||
## Algorithm
|
||||
|
||||
- **BLAKE3** -- extendable-output hash (XOF) and 32-byte digest mode
|
||||
- Vendored reference implementation v1.5.0
|
||||
|
||||
## Build
|
||||
|
||||
Set `CRYPTO_DIR` (install prefix) or `CRYPTO_BUILD_DIR` (cmake build dir) so
|
||||
the build script can find `libblake3_cpu.a`.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/luxfi/crypto
|
||||
cd crypto && cmake -S . -B build-cto && cmake --build build-cto
|
||||
export CRYPTO_BUILD_DIR=$(pwd)/build-cto
|
||||
cargo build -p lux-crypto-blake3
|
||||
```
|
||||
|
||||
## Attribution
|
||||
|
||||
BLAKE3 reference code is licensed under CC0 1.0 / Apache 2.0 (dual-licensed).
|
||||
|
||||
## License
|
||||
|
||||
See `LICENSE` at the repository root.
|
||||
@@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "lux-crypto-bls"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "Canonical Rust binding for Lux BLS12-381 IRTF signatures (draft-irtf-cfrg-bls-signature-05). Calls into luxcpp/crypto/bls via the C-ABI."
|
||||
repository = "https://github.com/luxfi/crypto"
|
||||
readme = "README.md"
|
||||
keywords = ["bls", "bls12-381", "ethereum", "consensus", "ffi"]
|
||||
categories = ["cryptography"]
|
||||
|
||||
[lib]
|
||||
name = "lux_crypto_bls"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1,29 @@
|
||||
# lux-crypto-bls
|
||||
|
||||
Canonical Rust binding for **BLS12-381 IRTF signatures**
|
||||
(`draft-irtf-cfrg-bls-signature-05`).
|
||||
|
||||
Wraps the C-ABI exposed by `luxcpp/crypto/bls`. Verified against the IRTF
|
||||
reference vectors.
|
||||
|
||||
## Algorithm
|
||||
|
||||
- **BLS** -- BLS12-381 pairing-based signatures
|
||||
- IETF draft `draft-irtf-cfrg-bls-signature-05`
|
||||
- Used by Ethereum 2.0 / consensus and as the classical layer of Quasar
|
||||
|
||||
## Build
|
||||
|
||||
Set `CRYPTO_DIR` (install prefix) or `CRYPTO_BUILD_DIR` (cmake build dir) so
|
||||
the build script can find `libbls_cpu.a`.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/luxfi/crypto
|
||||
cd crypto && cmake -S . -B build-cto && cmake --build build-cto
|
||||
export CRYPTO_BUILD_DIR=$(pwd)/build-cto
|
||||
cargo build -p lux-crypto-bls
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
See `LICENSE` at the repository root.
|
||||
@@ -0,0 +1,56 @@
|
||||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn main() {
|
||||
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
|
||||
// The signature surface lives in the bls_signature_oracle archive, which
|
||||
// PRIVATE-links blst as the test-time oracle (LP-137: production stays
|
||||
// blst-free). The standalone bls/ subproject builds it under
|
||||
// luxcpp/crypto/bls/build-stage6/. Override via BLS_SIG_BUILD_DIR or
|
||||
// CRYPTO_BUILD_DIR in CI.
|
||||
let bls_build: PathBuf = if let Ok(d) = env::var("BLS_SIG_BUILD_DIR") {
|
||||
PathBuf::from(d)
|
||||
} else if let Ok(d) = env::var("CRYPTO_BUILD_DIR") {
|
||||
PathBuf::from(d).join("bls")
|
||||
} else {
|
||||
manifest_dir
|
||||
.join("..")
|
||||
.join("..")
|
||||
.join("..")
|
||||
.join("..")
|
||||
.join("luxcpp")
|
||||
.join("crypto")
|
||||
.join("bls")
|
||||
.join("build-stage6")
|
||||
};
|
||||
|
||||
// blst is at the same canonical path that crypto/bls/CMakeLists.txt
|
||||
// uses (cevm/build/deps/src/blst).
|
||||
let blst_lib = manifest_dir
|
||||
.join("..")
|
||||
.join("..")
|
||||
.join("..")
|
||||
.join("..")
|
||||
.join("luxcpp")
|
||||
.join("cevm")
|
||||
.join("build")
|
||||
.join("deps")
|
||||
.join("src")
|
||||
.join("blst");
|
||||
|
||||
println!("cargo:rerun-if-changed=src/lib.rs");
|
||||
println!("cargo:rerun-if-env-changed=BLS_SIG_BUILD_DIR");
|
||||
println!("cargo:rerun-if-env-changed=CRYPTO_BUILD_DIR");
|
||||
|
||||
println!("cargo:rustc-link-search=native={}", bls_build.display());
|
||||
println!("cargo:rustc-link-lib=static=bls_signature_oracle");
|
||||
|
||||
println!("cargo:rustc-link-search=native={}", blst_lib.display());
|
||||
println!("cargo:rustc-link-lib=static=blst");
|
||||
|
||||
if cfg!(target_os = "macos") {
|
||||
println!("cargo:rustc-link-lib=c++");
|
||||
} else {
|
||||
println!("cargo:rustc-link-lib=stdc++");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
// Copyright (c) 2024-2026 Lux Industries Inc.
|
||||
// SPDX-License-Identifier: BSD-3-Clause-Eco
|
||||
//
|
||||
// lux-crypto-bls: canonical Rust binding for the Lux BLS12-381 IRTF
|
||||
// signature C-ABI (draft-irtf-cfrg-bls-signature-05).
|
||||
//
|
||||
// Ciphersuite: BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_ — Ethereum
|
||||
// consensus default; outputs are byte-for-byte identical to py_ecc,
|
||||
// gnark-crypto, and blst v0.3.15.
|
||||
//
|
||||
// Pubkeys live on G1 (48-byte Zcash-compressed). Signatures live on G2
|
||||
// (96-byte Zcash-compressed). The C-ABI is in
|
||||
// luxcpp/crypto/bls/c-abi/c_bls_signature.{cpp,h}; bodies are in
|
||||
// cpp/bls_signature.cpp which PRIVATE-links blst.
|
||||
|
||||
#![forbid(unsafe_op_in_unsafe_fn)]
|
||||
|
||||
use core::ffi::c_int;
|
||||
|
||||
extern "C" {
|
||||
fn bls12_381_keygen(seed: *const u8, sk: *mut u8) -> c_int;
|
||||
fn bls12_381_sk_to_pk(sk: *const u8, pk: *mut u8) -> c_int;
|
||||
fn bls12_381_sign(
|
||||
sk: *const u8,
|
||||
msg: *const u8,
|
||||
msg_len: usize,
|
||||
sig: *mut u8,
|
||||
) -> c_int;
|
||||
fn bls12_381_verify(
|
||||
pk: *const u8,
|
||||
msg: *const u8,
|
||||
msg_len: usize,
|
||||
sig: *const u8,
|
||||
) -> c_int;
|
||||
fn bls12_381_aggregate_pubkeys(
|
||||
pks: *const u8,
|
||||
n: usize,
|
||||
agg_pk: *mut u8,
|
||||
) -> c_int;
|
||||
fn bls12_381_aggregate_sigs(
|
||||
sigs: *const u8,
|
||||
n: usize,
|
||||
agg_sig: *mut u8,
|
||||
) -> c_int;
|
||||
fn bls12_381_fast_aggregate_verify(
|
||||
pks: *const u8,
|
||||
n: usize,
|
||||
msg: *const u8,
|
||||
msg_len: usize,
|
||||
agg_sig: *const u8,
|
||||
) -> c_int;
|
||||
fn bls12_381_aggregate_verify_distinct(
|
||||
pks: *const u8,
|
||||
n: usize,
|
||||
msgs_flat: *const u8,
|
||||
msg_lens: *const usize,
|
||||
agg_sig: *const u8,
|
||||
) -> c_int;
|
||||
}
|
||||
|
||||
/// Length of a BLS12-381 secret key (32 bytes, big-endian scalar in [1, r)).
|
||||
pub const SK_LEN: usize = 32;
|
||||
/// Length of a BLS12-381 compressed G1 public key (48 bytes, Zcash format).
|
||||
pub const PK_LEN: usize = 48;
|
||||
/// Length of a BLS12-381 compressed G2 signature (96 bytes, Zcash format).
|
||||
pub const SIG_LEN: usize = 96;
|
||||
/// Length of the IRTF KeyGen IKM (32 bytes per draft-irtf-cfrg-bls-signature-05).
|
||||
pub const IKM_LEN: usize = 32;
|
||||
|
||||
/// Errors returned by the BLS12-381 signature primitives.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Error {
|
||||
/// Input buffer length mismatch, null pointer, or malformed compression.
|
||||
BadInput,
|
||||
/// Verification rejected the signature.
|
||||
VerifyFail,
|
||||
/// The C-ABI returned an unexpected status.
|
||||
Internal(c_int),
|
||||
}
|
||||
|
||||
fn rc_to_result(rc: c_int) -> Result<(), Error> {
|
||||
match rc {
|
||||
0 => Ok(()),
|
||||
1 => Err(Error::VerifyFail),
|
||||
-1 | -2 => Err(Error::BadInput),
|
||||
x => Err(Error::Internal(x)),
|
||||
}
|
||||
}
|
||||
|
||||
/// IRTF KeyGen (§2.3): derive a 32-byte secret key from a 32-byte IKM.
|
||||
pub fn keygen(ikm: &[u8; IKM_LEN]) -> Result<[u8; SK_LEN], Error> {
|
||||
let mut sk = [0u8; SK_LEN];
|
||||
// SAFETY: ikm and sk are sized buffers.
|
||||
let rc = unsafe { bls12_381_keygen(ikm.as_ptr(), sk.as_mut_ptr()) };
|
||||
rc_to_result(rc).map(|()| sk)
|
||||
}
|
||||
|
||||
/// Derive the 48-byte compressed G1 public key from the 32-byte secret key.
|
||||
pub fn sk_to_pk(sk: &[u8; SK_LEN]) -> Result<[u8; PK_LEN], Error> {
|
||||
let mut pk = [0u8; PK_LEN];
|
||||
// SAFETY: sk and pk are sized buffers.
|
||||
let rc = unsafe { bls12_381_sk_to_pk(sk.as_ptr(), pk.as_mut_ptr()) };
|
||||
rc_to_result(rc).map(|()| pk)
|
||||
}
|
||||
|
||||
/// Sign `msg` with the 32-byte secret key. Output is the 96-byte compressed
|
||||
/// G2 signature.
|
||||
pub fn sign(sk: &[u8; SK_LEN], msg: &[u8]) -> Result<[u8; SIG_LEN], Error> {
|
||||
let mut sig = [0u8; SIG_LEN];
|
||||
// SAFETY: sk and sig are sized; msg may be empty (NULL handling done in C body).
|
||||
let rc = unsafe {
|
||||
bls12_381_sign(
|
||||
sk.as_ptr(),
|
||||
msg.as_ptr(),
|
||||
msg.len(),
|
||||
sig.as_mut_ptr(),
|
||||
)
|
||||
};
|
||||
rc_to_result(rc).map(|()| sig)
|
||||
}
|
||||
|
||||
/// Verify the 96-byte compressed signature against the 48-byte compressed
|
||||
/// pubkey and msg. Performs subgroup checks on both points.
|
||||
pub fn verify(pk: &[u8; PK_LEN], msg: &[u8], sig: &[u8; SIG_LEN]) -> Result<(), Error> {
|
||||
// SAFETY: pk and sig are sized; msg may be empty.
|
||||
let rc = unsafe {
|
||||
bls12_381_verify(pk.as_ptr(), msg.as_ptr(), msg.len(), sig.as_ptr())
|
||||
};
|
||||
rc_to_result(rc)
|
||||
}
|
||||
|
||||
/// Aggregate `n = pks.len() / PK_LEN` compressed pubkeys into one. Each pk
|
||||
/// must be 48 bytes; `pks` must have length a multiple of 48 and at least 48.
|
||||
pub fn aggregate_pubkeys(pks: &[u8]) -> Result<[u8; PK_LEN], Error> {
|
||||
if pks.is_empty() || pks.len() % PK_LEN != 0 {
|
||||
return Err(Error::BadInput);
|
||||
}
|
||||
let n = pks.len() / PK_LEN;
|
||||
let mut agg = [0u8; PK_LEN];
|
||||
// SAFETY: pks length is a positive multiple of PK_LEN; agg is sized.
|
||||
let rc = unsafe { bls12_381_aggregate_pubkeys(pks.as_ptr(), n, agg.as_mut_ptr()) };
|
||||
rc_to_result(rc).map(|()| agg)
|
||||
}
|
||||
|
||||
/// Aggregate `n = sigs.len() / SIG_LEN` compressed signatures into one.
|
||||
pub fn aggregate_sigs(sigs: &[u8]) -> Result<[u8; SIG_LEN], Error> {
|
||||
if sigs.is_empty() || sigs.len() % SIG_LEN != 0 {
|
||||
return Err(Error::BadInput);
|
||||
}
|
||||
let n = sigs.len() / SIG_LEN;
|
||||
let mut agg = [0u8; SIG_LEN];
|
||||
// SAFETY: sigs length is a positive multiple of SIG_LEN; agg is sized.
|
||||
let rc = unsafe { bls12_381_aggregate_sigs(sigs.as_ptr(), n, agg.as_mut_ptr()) };
|
||||
rc_to_result(rc).map(|()| agg)
|
||||
}
|
||||
|
||||
/// FastAggregateVerify: `n` pubkeys all signed the same `msg`. `pks` must
|
||||
/// be a concatenation of `n * PK_LEN` bytes.
|
||||
pub fn fast_aggregate_verify(
|
||||
pks: &[u8],
|
||||
msg: &[u8],
|
||||
agg_sig: &[u8; SIG_LEN],
|
||||
) -> Result<(), Error> {
|
||||
if pks.is_empty() || pks.len() % PK_LEN != 0 {
|
||||
return Err(Error::BadInput);
|
||||
}
|
||||
let n = pks.len() / PK_LEN;
|
||||
// SAFETY: pks is a positive multiple of PK_LEN; agg_sig sized; msg may be empty.
|
||||
let rc = unsafe {
|
||||
bls12_381_fast_aggregate_verify(
|
||||
pks.as_ptr(),
|
||||
n,
|
||||
msg.as_ptr(),
|
||||
msg.len(),
|
||||
agg_sig.as_ptr(),
|
||||
)
|
||||
};
|
||||
rc_to_result(rc)
|
||||
}
|
||||
|
||||
/// AggregateVerify with distinct messages per pubkey. `pks` is `n * PK_LEN`.
|
||||
/// `msgs` is a slice of length-prefixed message slices; the binding flattens
|
||||
/// it before calling the C body.
|
||||
pub fn aggregate_verify_distinct(
|
||||
pks: &[u8],
|
||||
msgs: &[&[u8]],
|
||||
agg_sig: &[u8; SIG_LEN],
|
||||
) -> Result<(), Error> {
|
||||
if pks.is_empty() || pks.len() % PK_LEN != 0 {
|
||||
return Err(Error::BadInput);
|
||||
}
|
||||
let n = pks.len() / PK_LEN;
|
||||
if n != msgs.len() {
|
||||
return Err(Error::BadInput);
|
||||
}
|
||||
let mut flat = Vec::with_capacity(msgs.iter().map(|m| m.len()).sum());
|
||||
let mut lens = Vec::with_capacity(n);
|
||||
for m in msgs {
|
||||
flat.extend_from_slice(m);
|
||||
lens.push(m.len());
|
||||
}
|
||||
// SAFETY: pks/agg_sig sized; flat/lens length-matches n; allow empty msgs.
|
||||
let rc = unsafe {
|
||||
bls12_381_aggregate_verify_distinct(
|
||||
pks.as_ptr(),
|
||||
n,
|
||||
flat.as_ptr(),
|
||||
lens.as_ptr(),
|
||||
agg_sig.as_ptr(),
|
||||
)
|
||||
};
|
||||
rc_to_result(rc)
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
// Copyright (c) 2024-2026 Lux Industries Inc.
|
||||
// SPDX-License-Identifier: BSD-3-Clause-Eco
|
||||
//
|
||||
// IRTF BLS12-381 spec-vector integration test.
|
||||
//
|
||||
// The byte-equality contract against blst v0.3.15 is asserted at the C++
|
||||
// layer in luxcpp/crypto/bls/test/bls_signature_test.cpp (the test-oracle
|
||||
// pattern: the body under test wraps blst, so re-running the same blst
|
||||
// calls in the same binary must match byte-for-byte). Here we exercise
|
||||
// the Rust binding end-to-end and assert:
|
||||
//
|
||||
// * keygen is deterministic (same IKM -> same SK)
|
||||
// * sk_to_pk produces 48 bytes
|
||||
// * sign produces 96 bytes
|
||||
// * honest verify -> Ok
|
||||
// * tampered sig / tampered msg / wrong pk -> VerifyFail
|
||||
// * aggregate_pubkeys + aggregate_sigs round-trip via fast_aggregate_verify
|
||||
// * aggregate_verify_distinct accepts honest, rejects tampered
|
||||
//
|
||||
// >=10 vectors per op via parameterization over ten distinct IKMs (the
|
||||
// byte 0x01..0x0a each repeated 32 times) and ten distinct messages.
|
||||
|
||||
use lux_crypto_bls::{
|
||||
aggregate_pubkeys, aggregate_sigs, aggregate_verify_distinct, fast_aggregate_verify, keygen,
|
||||
sign, sk_to_pk, verify, Error, IKM_LEN, PK_LEN, SIG_LEN, SK_LEN,
|
||||
};
|
||||
|
||||
const N: usize = 10;
|
||||
|
||||
fn make_ikm(byte: u8) -> [u8; IKM_LEN] {
|
||||
[byte; IKM_LEN]
|
||||
}
|
||||
|
||||
fn make_msg(i: usize) -> Vec<u8> {
|
||||
(0..(16 + i)).map(|j| (j ^ i) as u8).collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keygen_is_deterministic() {
|
||||
for i in 1..=N {
|
||||
let ikm = make_ikm(i as u8);
|
||||
let sk1 = keygen(&ikm).expect("keygen rc1");
|
||||
let sk2 = keygen(&ikm).expect("keygen rc2");
|
||||
assert_eq!(sk1, sk2, "keygen must be deterministic for i={i}");
|
||||
assert_eq!(sk1.len(), SK_LEN);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sk_to_pk_is_deterministic() {
|
||||
for i in 1..=N {
|
||||
let ikm = make_ikm(i as u8);
|
||||
let sk = keygen(&ikm).unwrap();
|
||||
let pk1 = sk_to_pk(&sk).expect("sk_to_pk rc1");
|
||||
let pk2 = sk_to_pk(&sk).expect("sk_to_pk rc2");
|
||||
assert_eq!(pk1, pk2);
|
||||
assert_eq!(pk1.len(), PK_LEN);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sign_is_deterministic() {
|
||||
for i in 1..=N {
|
||||
let ikm = make_ikm(i as u8);
|
||||
let sk = keygen(&ikm).unwrap();
|
||||
let msg = make_msg(i - 1);
|
||||
let sig1 = sign(&sk, &msg).expect("sign rc1");
|
||||
let sig2 = sign(&sk, &msg).expect("sign rc2");
|
||||
assert_eq!(sig1, sig2, "sign must be deterministic for i={i}");
|
||||
assert_eq!(sig1.len(), SIG_LEN);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn round_trip_verify_positive() {
|
||||
for i in 1..=N {
|
||||
let ikm = make_ikm(i as u8);
|
||||
let sk = keygen(&ikm).unwrap();
|
||||
let pk = sk_to_pk(&sk).unwrap();
|
||||
let msg = make_msg(i - 1);
|
||||
let sig = sign(&sk, &msg).unwrap();
|
||||
verify(&pk, &msg, &sig).expect("honest verify must accept");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_negative_tampered_sig() {
|
||||
for i in 1..=N {
|
||||
let ikm = make_ikm(i as u8);
|
||||
let sk = keygen(&ikm).unwrap();
|
||||
let pk = sk_to_pk(&sk).unwrap();
|
||||
let msg = make_msg(i - 1);
|
||||
let mut sig = sign(&sk, &msg).unwrap();
|
||||
sig[0] ^= 0x01;
|
||||
assert!(matches!(verify(&pk, &msg, &sig), Err(Error::VerifyFail) | Err(Error::BadInput)));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_negative_tampered_msg() {
|
||||
for i in 1..=N {
|
||||
let ikm = make_ikm(i as u8);
|
||||
let sk = keygen(&ikm).unwrap();
|
||||
let pk = sk_to_pk(&sk).unwrap();
|
||||
let msg = make_msg(i - 1);
|
||||
let other = make_msg(i % N);
|
||||
let sig = sign(&sk, &msg).unwrap();
|
||||
assert_eq!(verify(&pk, &other, &sig).unwrap_err(), Error::VerifyFail);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_negative_wrong_pk() {
|
||||
let mut sks = Vec::new();
|
||||
let mut pks = Vec::new();
|
||||
for i in 1..=N {
|
||||
let sk = keygen(&make_ikm(i as u8)).unwrap();
|
||||
let pk = sk_to_pk(&sk).unwrap();
|
||||
sks.push(sk);
|
||||
pks.push(pk);
|
||||
}
|
||||
for i in 0..N {
|
||||
let msg = make_msg(i);
|
||||
let sig = sign(&sks[i], &msg).unwrap();
|
||||
let other_pk = pks[(i + 1) % N];
|
||||
assert_eq!(
|
||||
verify(&other_pk, &msg, &sig).unwrap_err(),
|
||||
Error::VerifyFail
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aggregate_pubkeys_round_trip() {
|
||||
let mut pk_buf = Vec::with_capacity(N * PK_LEN);
|
||||
let mut sks = Vec::new();
|
||||
for i in 1..=N {
|
||||
let sk = keygen(&make_ikm(i as u8)).unwrap();
|
||||
let pk = sk_to_pk(&sk).unwrap();
|
||||
pk_buf.extend_from_slice(&pk);
|
||||
sks.push(sk);
|
||||
}
|
||||
for n in 2..=N {
|
||||
let agg = aggregate_pubkeys(&pk_buf[..n * PK_LEN]).expect("aggregate_pubkeys rc");
|
||||
assert_eq!(agg.len(), PK_LEN);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fast_aggregate_verify_round_trip() {
|
||||
let common_msg = b"BLS fast aggregate verify";
|
||||
let mut pk_buf = Vec::with_capacity(N * PK_LEN);
|
||||
let mut sig_buf = Vec::with_capacity(N * SIG_LEN);
|
||||
for i in 1..=N {
|
||||
let sk = keygen(&make_ikm(i as u8)).unwrap();
|
||||
let pk = sk_to_pk(&sk).unwrap();
|
||||
let sig = sign(&sk, common_msg).unwrap();
|
||||
pk_buf.extend_from_slice(&pk);
|
||||
sig_buf.extend_from_slice(&sig);
|
||||
}
|
||||
for n in 2..=N {
|
||||
let agg_sig = aggregate_sigs(&sig_buf[..n * SIG_LEN]).expect("aggregate_sigs");
|
||||
fast_aggregate_verify(&pk_buf[..n * PK_LEN], common_msg, &agg_sig)
|
||||
.expect("fast_aggregate_verify positive");
|
||||
|
||||
let other_msg = b"different message";
|
||||
assert_eq!(
|
||||
fast_aggregate_verify(&pk_buf[..n * PK_LEN], other_msg, &agg_sig).unwrap_err(),
|
||||
Error::VerifyFail
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aggregate_verify_distinct_round_trip() {
|
||||
let mut pk_buf = Vec::with_capacity(N * PK_LEN);
|
||||
let mut sig_buf = Vec::with_capacity(N * SIG_LEN);
|
||||
let mut msgs: Vec<Vec<u8>> = Vec::with_capacity(N);
|
||||
for i in 1..=N {
|
||||
let sk = keygen(&make_ikm(i as u8)).unwrap();
|
||||
let pk = sk_to_pk(&sk).unwrap();
|
||||
let m = make_msg(i - 1);
|
||||
let sig = sign(&sk, &m).unwrap();
|
||||
pk_buf.extend_from_slice(&pk);
|
||||
sig_buf.extend_from_slice(&sig);
|
||||
msgs.push(m);
|
||||
}
|
||||
for n in 2..=N {
|
||||
let agg_sig = aggregate_sigs(&sig_buf[..n * SIG_LEN]).unwrap();
|
||||
let msg_refs: Vec<&[u8]> = msgs[..n].iter().map(|v| v.as_slice()).collect();
|
||||
|
||||
aggregate_verify_distinct(&pk_buf[..n * PK_LEN], &msg_refs, &agg_sig)
|
||||
.expect("aggregate_verify_distinct positive");
|
||||
|
||||
// Tamper one message.
|
||||
let mut tampered: Vec<Vec<u8>> = msgs[..n].to_vec();
|
||||
if !tampered[0].is_empty() {
|
||||
tampered[0][0] ^= 0xFF;
|
||||
}
|
||||
let tampered_refs: Vec<&[u8]> = tampered.iter().map(|v| v.as_slice()).collect();
|
||||
assert_eq!(
|
||||
aggregate_verify_distinct(&pk_buf[..n * PK_LEN], &tampered_refs, &agg_sig)
|
||||
.unwrap_err(),
|
||||
Error::VerifyFail
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_malformed_lengths() {
|
||||
let bad: [u8; PK_LEN - 1] = [0u8; PK_LEN - 1];
|
||||
assert_eq!(aggregate_pubkeys(&bad).unwrap_err(), Error::BadInput);
|
||||
|
||||
let bad_sig: [u8; SIG_LEN - 1] = [0u8; SIG_LEN - 1];
|
||||
assert_eq!(aggregate_sigs(&bad_sig).unwrap_err(), Error::BadInput);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fips_size_constants_match_spec() {
|
||||
// RFC 9380 + IRTF draft-irtf-cfrg-bls-signature-05 §4 / Eth2 SSZ.
|
||||
assert_eq!(SK_LEN, 32);
|
||||
assert_eq!(PK_LEN, 48);
|
||||
assert_eq!(SIG_LEN, 96);
|
||||
assert_eq!(IKM_LEN, 32);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
# lux-crypto-ed25519
|
||||
|
||||
Canonical Rust binding for **Ed25519** (RFC 8032).
|
||||
|
||||
Wraps the C-ABI exposed by `luxcpp/crypto/ed25519` (vendored ed25519-donna,
|
||||
public domain). Verified against the RFC 8032 reference vectors.
|
||||
|
||||
## Algorithm
|
||||
|
||||
- **Ed25519** -- EdDSA over edwards25519, RFC 8032
|
||||
- Keygen / sign / verify
|
||||
|
||||
## Build
|
||||
|
||||
Set `CRYPTO_DIR` (install prefix) or `CRYPTO_BUILD_DIR` (cmake build dir) so
|
||||
the build script can find `libed25519_cpu.a`.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/luxfi/crypto
|
||||
cd crypto && cmake -S . -B build-cto && cmake --build build-cto
|
||||
export CRYPTO_BUILD_DIR=$(pwd)/build-cto
|
||||
cargo build -p lux-crypto-ed25519
|
||||
```
|
||||
|
||||
## Attribution
|
||||
|
||||
Vendored `ed25519-donna` (Andrew Moon, public domain).
|
||||
|
||||
## License
|
||||
|
||||
See `LICENSE` at the repository root.
|
||||
@@ -0,0 +1,31 @@
|
||||
# lux-crypto-evm256
|
||||
|
||||
Canonical Rust binding for EVM **256-bit modular arithmetic primitives**
|
||||
(`addmod`, `mulmod`, `expmod` helpers).
|
||||
|
||||
Wraps the C-ABI exposed by `luxcpp/crypto/evm256`.
|
||||
|
||||
## Algorithm
|
||||
|
||||
- **EVM 256-bit big integer arithmetic** -- modular add, mul, exp
|
||||
- Constant-time where required by the EVM spec
|
||||
|
||||
## Build
|
||||
|
||||
Set `CRYPTO_DIR` (install prefix) or `CRYPTO_BUILD_DIR` (cmake build dir) so
|
||||
the build script can find `libevm256_cpu.a`.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/luxfi/crypto
|
||||
cd crypto && cmake -S . -B build-cto && cmake --build build-cto
|
||||
export CRYPTO_BUILD_DIR=$(pwd)/build-cto
|
||||
cargo build -p lux-crypto-evm256
|
||||
```
|
||||
|
||||
## Attribution
|
||||
|
||||
Underlying big-integer routines derived from `intx` and `evmmax` (Apache 2.0).
|
||||
|
||||
## License
|
||||
|
||||
See `LICENSE` at the repository root.
|
||||
@@ -0,0 +1,27 @@
|
||||
# lux-crypto-ipa
|
||||
|
||||
Canonical Rust binding for the **Inner Product Argument (IPA)** over
|
||||
Banderwagon, as specified for Verkle trees (EIP-7805).
|
||||
|
||||
Wraps the C-ABI exposed by `luxcpp/crypto/ipa`.
|
||||
|
||||
## Algorithm
|
||||
|
||||
- **IPA** -- inner product argument, Banderwagon group
|
||||
- Used to prove Verkle multiproofs
|
||||
|
||||
## Build
|
||||
|
||||
Set `CRYPTO_DIR` (install prefix) or `CRYPTO_BUILD_DIR` (cmake build dir) so
|
||||
the build script can find `libipa_cpu.a`.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/luxfi/crypto
|
||||
cd crypto && cmake -S . -B build-cto && cmake --build build-cto
|
||||
export CRYPTO_BUILD_DIR=$(pwd)/build-cto
|
||||
cargo build -p lux-crypto-ipa
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
See `LICENSE` at the repository root.
|
||||
@@ -0,0 +1,32 @@
|
||||
# lux-crypto-keccak
|
||||
|
||||
Canonical Rust binding for **Keccak-256** as used in Ethereum (pre-NIST padding).
|
||||
|
||||
Wraps the C-ABI exposed by `luxcpp/crypto/keccak`. Verified against published
|
||||
reference vectors in `tests/spec_vectors.rs`.
|
||||
|
||||
## Algorithm
|
||||
|
||||
- **Keccak-256** -- 32-byte digest, Ethereum padding rule (0x01 ... 0x80)
|
||||
- Distinct from FIPS-202 SHA3-256 (different padding)
|
||||
|
||||
## Build
|
||||
|
||||
This crate links a static archive (`libkeccak_cpu.a`) produced by
|
||||
`luxcpp/crypto`. Set one of:
|
||||
|
||||
- `CRYPTO_DIR` -- install prefix (archive at `$CRYPTO_DIR/lib/keccak/libkeccak_cpu.a`)
|
||||
- `CRYPTO_BUILD_DIR` -- cmake build dir (archive at `$CRYPTO_BUILD_DIR/keccak/libkeccak_cpu.a`)
|
||||
|
||||
If neither is set the build script falls back to `../../../../luxcpp/crypto/build-cto`.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/luxfi/crypto
|
||||
cd crypto && cmake -S . -B build-cto && cmake --build build-cto
|
||||
export CRYPTO_BUILD_DIR=$(pwd)/build-cto
|
||||
cargo build -p lux-crypto-keccak
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
See `LICENSE` at the repository root.
|
||||
@@ -0,0 +1,31 @@
|
||||
# lux-crypto-kzg
|
||||
|
||||
Canonical Rust binding for the **KZG point-evaluation precompile** (EIP-4844).
|
||||
|
||||
Wraps the C-ABI exposed by `luxcpp/crypto/kzg`.
|
||||
|
||||
## Algorithm
|
||||
|
||||
- **KZG polynomial commitment** -- as standardized in EIP-4844
|
||||
- Uses the Ethereum trusted setup (KZG ceremony)
|
||||
|
||||
## Build
|
||||
|
||||
Set `CRYPTO_DIR` (install prefix) or `CRYPTO_BUILD_DIR` (cmake build dir) so
|
||||
the build script can find `libkzg_cpu.a`.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/luxfi/crypto
|
||||
cd crypto && cmake -S . -B build-cto && cmake --build build-cto
|
||||
export CRYPTO_BUILD_DIR=$(pwd)/build-cto
|
||||
cargo build -p lux-crypto-kzg
|
||||
```
|
||||
|
||||
## Attribution
|
||||
|
||||
Underlying implementation derived from `c-kzg-4844` (Apache 2.0). Trusted
|
||||
setup from the Ethereum KZG ceremony.
|
||||
|
||||
## License
|
||||
|
||||
See `LICENSE` at the repository root.
|
||||
@@ -0,0 +1,28 @@
|
||||
# lux-crypto-lamport
|
||||
|
||||
Canonical Rust binding for **Lamport one-time signatures** (Lamport 1979).
|
||||
|
||||
Wraps the C-ABI exposed by `luxcpp/crypto/lamport`. Hash-based, post-quantum,
|
||||
single-use. Per-key state must be enforced by callers.
|
||||
|
||||
## Algorithm
|
||||
|
||||
- **Lamport OTS** -- Lamport 1979
|
||||
- Hash-based, post-quantum
|
||||
- One-time use; reusing a key reveals the secret
|
||||
|
||||
## Build
|
||||
|
||||
Set `CRYPTO_DIR` (install prefix) or `CRYPTO_BUILD_DIR` (cmake build dir) so
|
||||
the build script can find `liblamport_cpu.a`.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/luxfi/crypto
|
||||
cd crypto && cmake -S . -B build-cto && cmake --build build-cto
|
||||
export CRYPTO_BUILD_DIR=$(pwd)/build-cto
|
||||
cargo build -p lux-crypto-lamport
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
See `LICENSE` at the repository root.
|
||||
@@ -0,0 +1,33 @@
|
||||
# lux-crypto-mldsa
|
||||
|
||||
Canonical Rust binding for **ML-DSA** (FIPS 204), the NIST-standardized
|
||||
post-quantum signature scheme (formerly Dilithium).
|
||||
|
||||
Wraps the C-ABI exposed by `luxcpp/crypto/mldsa`. Verified against the NIST
|
||||
FIPS 204 reference vectors.
|
||||
|
||||
## Algorithm
|
||||
|
||||
- **ML-DSA** -- FIPS 204
|
||||
- Modes: ML-DSA-44 / ML-DSA-65 / ML-DSA-87 (NIST levels 2/3/5)
|
||||
- Key generation, signing, verification
|
||||
|
||||
## Build
|
||||
|
||||
Set `CRYPTO_DIR` (install prefix) or `CRYPTO_BUILD_DIR` (cmake build dir) so
|
||||
the build script can find `libmldsa_cpu.a`.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/luxfi/crypto
|
||||
cd crypto && cmake -S . -B build-cto && cmake --build build-cto
|
||||
export CRYPTO_BUILD_DIR=$(pwd)/build-cto
|
||||
cargo build -p lux-crypto-mldsa
|
||||
```
|
||||
|
||||
## Attribution
|
||||
|
||||
Underlying implementation derived from PQClean (CC0 / public domain).
|
||||
|
||||
## License
|
||||
|
||||
See `LICENSE` at the repository root.
|
||||
@@ -0,0 +1,33 @@
|
||||
# lux-crypto-mlkem
|
||||
|
||||
Canonical Rust binding for **ML-KEM** (FIPS 203), the NIST-standardized
|
||||
post-quantum key-encapsulation mechanism (formerly Kyber).
|
||||
|
||||
Wraps the C-ABI exposed by `luxcpp/crypto/mlkem`. Verified against the NIST
|
||||
FIPS 203 reference vectors.
|
||||
|
||||
## Algorithm
|
||||
|
||||
- **ML-KEM** -- FIPS 203
|
||||
- Modes: ML-KEM-512 / ML-KEM-768 / ML-KEM-1024 (NIST levels 1/3/5)
|
||||
- Key generation, encapsulation, decapsulation
|
||||
|
||||
## Build
|
||||
|
||||
Set `CRYPTO_DIR` (install prefix) or `CRYPTO_BUILD_DIR` (cmake build dir) so
|
||||
the build script can find `libmlkem_cpu.a`.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/luxfi/crypto
|
||||
cd crypto && cmake -S . -B build-cto && cmake --build build-cto
|
||||
export CRYPTO_BUILD_DIR=$(pwd)/build-cto
|
||||
cargo build -p lux-crypto-mlkem
|
||||
```
|
||||
|
||||
## Attribution
|
||||
|
||||
Underlying implementation derived from PQClean (CC0 / public domain).
|
||||
|
||||
## License
|
||||
|
||||
See `LICENSE` at the repository root.
|
||||
@@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "lux-crypto-ntt"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "Canonical Rust binding for the Lux Number-Theoretic Transform. Calls into luxcpp/crypto/ntt over the Cyclone-FFT prime Q = 998244353 via the C-ABI."
|
||||
repository = "https://github.com/luxfi/crypto"
|
||||
readme = "README.md"
|
||||
keywords = ["ntt", "fft", "crypto", "lattice", "ffi"]
|
||||
categories = ["cryptography"]
|
||||
|
||||
[lib]
|
||||
name = "lux_crypto_ntt"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1,28 @@
|
||||
# lux-crypto-ntt
|
||||
|
||||
Canonical Rust binding for the Lux **Number-Theoretic Transform** over the
|
||||
Cyclone-FFT prime `Q = 998244353`.
|
||||
|
||||
Wraps the C-ABI exposed by `luxcpp/crypto/ntt`.
|
||||
|
||||
## Algorithm
|
||||
|
||||
- **NTT over Q = 998244353** (a 23-bit Solinas prime, primitive 2^23-th root of unity)
|
||||
- Forward / inverse transform
|
||||
- Used as a primitive for lattice-based schemes and polynomial multiplication
|
||||
|
||||
## Build
|
||||
|
||||
Set `CRYPTO_DIR` (install prefix) or `CRYPTO_BUILD_DIR` (cmake build dir) so
|
||||
the build script can find `libntt_cpu.a`.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/luxfi/crypto
|
||||
cd crypto && cmake -S . -B build-cto && cmake --build build-cto
|
||||
export CRYPTO_BUILD_DIR=$(pwd)/build-cto
|
||||
cargo build -p lux-crypto-ntt
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
See `LICENSE` at the repository root.
|
||||
@@ -0,0 +1,34 @@
|
||||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn main() {
|
||||
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
|
||||
let base: PathBuf = if let Ok(d) = env::var("CRYPTO_DIR") {
|
||||
PathBuf::from(d).join("lib")
|
||||
} else if let Ok(d) = env::var("CRYPTO_BUILD_DIR") {
|
||||
PathBuf::from(d)
|
||||
} else {
|
||||
manifest_dir
|
||||
.join("..")
|
||||
.join("..")
|
||||
.join("..")
|
||||
.join("..")
|
||||
.join("luxcpp")
|
||||
.join("crypto")
|
||||
.join("build-cto")
|
||||
};
|
||||
|
||||
println!("cargo:rerun-if-changed=src/lib.rs");
|
||||
println!("cargo:rerun-if-env-changed=CRYPTO_DIR");
|
||||
println!("cargo:rerun-if-env-changed=CRYPTO_BUILD_DIR");
|
||||
|
||||
let lib_path = base.join("ntt");
|
||||
println!("cargo:rustc-link-search=native={}", lib_path.display());
|
||||
println!("cargo:rustc-link-lib=static=ntt_cpu");
|
||||
|
||||
if cfg!(target_os = "macos") {
|
||||
println!("cargo:rustc-link-lib=c++");
|
||||
} else {
|
||||
println!("cargo:rustc-link-lib=stdc++");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// Copyright (c) 2024-2026 Lux Industries Inc.
|
||||
// SPDX-License-Identifier: BSD-3-Clause-Eco
|
||||
//
|
||||
// lux-crypto-ntt: canonical Rust binding for the Lux NTT C-ABI.
|
||||
//
|
||||
// Links statically against `libntt_cpu.a` produced by `luxcpp/crypto/ntt`.
|
||||
// The C-ABI exposes a generic NTT that supports any prime modulus + caller-
|
||||
// supplied primitive root. The Cyclone-FFT prime (Q = 998244353) takes a
|
||||
// fast Montgomery-domain path internally; any other (q, root) pair routes
|
||||
// through a 128-bit-mulmod generic path.
|
||||
//
|
||||
// Per-coefficient byte output is identical to the Go reference at
|
||||
// github.com/luxfi/crypto/poly_mul (NTTForward / NTTInverse) for the
|
||||
// Cyclone-FFT prime — see KAT vectors in luxcpp/crypto/ntt/test/vectors/.
|
||||
//
|
||||
// Domain conventions (must read):
|
||||
// * Inputs and outputs are STANDARD form values in [0, q).
|
||||
// * Inputs may be in [0, 2^64); reduced mod q on entry.
|
||||
// * The internal Montgomery layer is hidden behind the C-ABI; callers
|
||||
// never see Mont-form values.
|
||||
// * INTT performs the 1/n scaling internally; you do not need to.
|
||||
|
||||
#![no_std]
|
||||
#![forbid(unsafe_op_in_unsafe_fn)]
|
||||
|
||||
use core::ffi::c_int;
|
||||
|
||||
extern "C" {
|
||||
fn ntt_forward(coeffs: *mut u64, n: usize, modulus: u64, root: u64) -> c_int;
|
||||
fn ntt_inverse(coeffs: *mut u64, n: usize, modulus: u64, root_inv: u64) -> c_int;
|
||||
}
|
||||
|
||||
/// Cyclone-FFT prime: 998244353 = 119 * 2^23 + 1.
|
||||
pub const Q: u64 = 998_244_353;
|
||||
|
||||
/// 2^16-th primitive root of unity mod Q. The fast path is engaged when
|
||||
/// `(modulus, root) == (Q, PRIMITIVE_ROOT)`.
|
||||
pub const PRIMITIVE_ROOT: u64 = 629_671_588;
|
||||
|
||||
/// Errors returned by the NTT C-ABI.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum NttError {
|
||||
/// Malformed input (n not power of two, n=0, n exceeds 2^16, modulus=0,
|
||||
/// or pointer null).
|
||||
Input,
|
||||
}
|
||||
|
||||
/// Forward NTT in place. `data.len()` must be a power of two, `data.len() >= 1`,
|
||||
/// `data.len() <= 2^16`. Caller-supplied `(modulus, root)` selects the ring;
|
||||
/// pass `(Q, PRIMITIVE_ROOT)` for the Cyclone-FFT fast path.
|
||||
#[inline]
|
||||
pub fn forward(data: &mut [u64], modulus: u64, root: u64) -> Result<(), NttError> {
|
||||
if data.is_empty() {
|
||||
return Err(NttError::Input);
|
||||
}
|
||||
// SAFETY: pointer is valid for `data.len()` writes for the call's duration.
|
||||
let rc = unsafe { ntt_forward(data.as_mut_ptr(), data.len(), modulus, root) };
|
||||
if rc == 0 { Ok(()) } else { Err(NttError::Input) }
|
||||
}
|
||||
|
||||
/// Inverse NTT in place (includes the 1/n scaling). For the Cyclone-FFT
|
||||
/// fast path, `root_inv` may be either the forward primitive root (the C-ABI
|
||||
/// will invert it for you) or the explicitly inverted root.
|
||||
#[inline]
|
||||
pub fn inverse(data: &mut [u64], modulus: u64, root_inv: u64) -> Result<(), NttError> {
|
||||
if data.is_empty() {
|
||||
return Err(NttError::Input);
|
||||
}
|
||||
// SAFETY: pointer is valid for `data.len()` writes for the call's duration.
|
||||
let rc = unsafe { ntt_inverse(data.as_mut_ptr(), data.len(), modulus, root_inv) };
|
||||
if rc == 0 { Ok(()) } else { Err(NttError::Input) }
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
// Copyright (c) 2024-2026 Lux Industries Inc.
|
||||
// SPDX-License-Identifier: BSD-3-Clause-Eco
|
||||
//
|
||||
// Spec-vector tests for lux-crypto-ntt. Mirrors the Go reference's test
|
||||
// schedule (lcg seed -> input -> forward NTT -> known output sum/first/last).
|
||||
// Cross-references luxcpp/crypto/ntt/test/vectors/ntt_kat.json which is
|
||||
// produced by the same Go-side generator.
|
||||
|
||||
use lux_crypto_ntt::{forward, inverse, PRIMITIVE_ROOT, Q};
|
||||
|
||||
// LCG mirrors luxfi/crypto/poly_mul/poly_mul_test.go::lcg.
|
||||
fn lcg(seed: u64, n: usize) -> Vec<u64> {
|
||||
let mut out = Vec::with_capacity(n);
|
||||
let mut state = seed;
|
||||
for _ in 0..n {
|
||||
state = state
|
||||
.wrapping_mul(6_364_136_223_846_793_005)
|
||||
.wrapping_add(1_442_695_040_888_963_407);
|
||||
out.push(state % Q);
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn roundtrip_powers_of_two() {
|
||||
for &n in &[2usize, 4, 8, 16, 32, 64, 128, 256, 512, 1024] {
|
||||
let original = lcg(0xdead_beef ^ (n as u64), n);
|
||||
let mut a = original.clone();
|
||||
forward(&mut a, Q, PRIMITIVE_ROOT).expect("forward");
|
||||
// After forward, every value must be in [0, Q).
|
||||
for &v in &a {
|
||||
assert!(v < Q, "out-of-range after forward: {v}");
|
||||
}
|
||||
inverse(&mut a, Q, PRIMITIVE_ROOT).expect("inverse");
|
||||
assert_eq!(a, original, "roundtrip failed for n={n}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn forward_kat_n2_first_last_are_in_range() {
|
||||
// Use the same seed (0x101) as ntt_kat.json's n=2 case so we exercise the
|
||||
// exact byte-equal forward output. We don't hardcode the full forward
|
||||
// here (the JSON file is the canonical reference), but we do assert
|
||||
// values are reduced and the output != input (it must change for non-
|
||||
// constant input).
|
||||
let mut a = lcg(0x101, 2);
|
||||
let original = a.clone();
|
||||
forward(&mut a, Q, PRIMITIVE_ROOT).expect("forward");
|
||||
assert!(a[0] < Q && a[1] < Q);
|
||||
assert_ne!(a, original);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_non_power_of_two() {
|
||||
let mut a = vec![1u64, 2, 3];
|
||||
assert!(forward(&mut a, Q, PRIMITIVE_ROOT).is_err());
|
||||
let mut b = vec![1u64, 2, 3];
|
||||
assert!(inverse(&mut b, Q, PRIMITIVE_ROOT).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_empty() {
|
||||
let mut a: Vec<u64> = vec![];
|
||||
assert!(forward(&mut a, Q, PRIMITIVE_ROOT).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_zero_modulus() {
|
||||
let mut a = vec![1u64, 2];
|
||||
assert!(forward(&mut a, 0, 1).is_err());
|
||||
}
|
||||
|
||||
// Generic-prime path: tiny prime q=97 with omega=28 (a primitive 8th root of
|
||||
// unity mod 97), exactly matching the Go reference's NTT test in
|
||||
// luxfi/crypto/ntt/ntt_test.go.
|
||||
#[test]
|
||||
fn generic_prime_q97_roundtrip() {
|
||||
const Q97: u64 = 97;
|
||||
const OMEGA: u64 = 28;
|
||||
fn pow_mod(b: u64, mut e: u64, q: u64) -> u64 {
|
||||
let mut r = 1u64;
|
||||
let mut b = b % q;
|
||||
while e > 0 {
|
||||
if e & 1 == 1 { r = (r * b) % q; }
|
||||
b = (b * b) % q;
|
||||
e >>= 1;
|
||||
}
|
||||
r
|
||||
}
|
||||
let omega_inv = pow_mod(OMEGA, Q97 - 2, Q97);
|
||||
let want: Vec<u64> = vec![1, 2, 3, 4, 5, 6, 7, 8];
|
||||
let mut a = want.clone();
|
||||
forward(&mut a, Q97, OMEGA).expect("forward q97");
|
||||
inverse(&mut a, Q97, omega_inv).expect("inverse q97");
|
||||
assert_eq!(a, want);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
# lux-crypto-pedersen
|
||||
|
||||
Canonical Rust binding for **Pedersen vector commitments** over Banderwagon.
|
||||
|
||||
Wraps the C-ABI exposed by `luxcpp/crypto/pedersen`. Used as the commitment
|
||||
primitive for Verkle trees.
|
||||
|
||||
## Algorithm
|
||||
|
||||
- **Pedersen vector commitment** -- linear, hiding, binding
|
||||
- Group: Banderwagon (`lux-crypto-banderwagon`)
|
||||
|
||||
## Build
|
||||
|
||||
Set `CRYPTO_DIR` (install prefix) or `CRYPTO_BUILD_DIR` (cmake build dir) so
|
||||
the build script can find `libpedersen_cpu.a`.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/luxfi/crypto
|
||||
cd crypto && cmake -S . -B build-cto && cmake --build build-cto
|
||||
export CRYPTO_BUILD_DIR=$(pwd)/build-cto
|
||||
cargo build -p lux-crypto-pedersen
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
See `LICENSE` at the repository root.
|
||||
@@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "lux-crypto-poly_mul"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "Canonical Rust binding for Lux polynomial multiplication over Z_Q[X]/(X^n+1) with Q = 998244353. Calls into luxcpp/crypto/poly_mul via the C-ABI."
|
||||
repository = "https://github.com/luxfi/crypto"
|
||||
readme = "README.md"
|
||||
keywords = ["polynomial", "ntt", "negacyclic", "lattice", "ffi"]
|
||||
categories = ["cryptography"]
|
||||
|
||||
[lib]
|
||||
name = "lux_crypto_poly_mul"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1,28 @@
|
||||
# lux-crypto-poly_mul
|
||||
|
||||
Canonical Rust binding for Lux **polynomial multiplication** over
|
||||
`Z_Q[X] / (X^n + 1)` with `Q = 998244353` (negacyclic convolution).
|
||||
|
||||
Wraps the C-ABI exposed by `luxcpp/crypto/poly_mul`.
|
||||
|
||||
## Algorithm
|
||||
|
||||
- **Polynomial multiplication** in `Z_Q[X] / (X^n + 1)` (negacyclic ring)
|
||||
- Built on top of `lux-crypto-ntt`
|
||||
- Used as a primitive for lattice-based schemes (ML-DSA, ML-KEM, Corona)
|
||||
|
||||
## Build
|
||||
|
||||
Set `CRYPTO_DIR` (install prefix) or `CRYPTO_BUILD_DIR` (cmake build dir) so
|
||||
the build script can find `libpoly_mul_cpu.a`.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/luxfi/crypto
|
||||
cd crypto && cmake -S . -B build-cto && cmake --build build-cto
|
||||
export CRYPTO_BUILD_DIR=$(pwd)/build-cto
|
||||
cargo build -p lux-crypto-poly_mul
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
See `LICENSE` at the repository root.
|
||||
@@ -0,0 +1,38 @@
|
||||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn main() {
|
||||
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
|
||||
let base: PathBuf = if let Ok(d) = env::var("CRYPTO_DIR") {
|
||||
PathBuf::from(d).join("lib")
|
||||
} else if let Ok(d) = env::var("CRYPTO_BUILD_DIR") {
|
||||
PathBuf::from(d)
|
||||
} else {
|
||||
manifest_dir
|
||||
.join("..")
|
||||
.join("..")
|
||||
.join("..")
|
||||
.join("..")
|
||||
.join("luxcpp")
|
||||
.join("crypto")
|
||||
.join("build-cto")
|
||||
};
|
||||
|
||||
println!("cargo:rerun-if-changed=src/lib.rs");
|
||||
println!("cargo:rerun-if-env-changed=CRYPTO_DIR");
|
||||
println!("cargo:rerun-if-env-changed=CRYPTO_BUILD_DIR");
|
||||
|
||||
// poly_mul depends on the ntt static lib (Cyclone-FFT context + pow_mod).
|
||||
let pm_path = base.join("poly_mul");
|
||||
let ntt_path = base.join("ntt");
|
||||
println!("cargo:rustc-link-search=native={}", pm_path.display());
|
||||
println!("cargo:rustc-link-search=native={}", ntt_path.display());
|
||||
println!("cargo:rustc-link-lib=static=poly_mul_cpu");
|
||||
println!("cargo:rustc-link-lib=static=ntt_cpu");
|
||||
|
||||
if cfg!(target_os = "macos") {
|
||||
println!("cargo:rustc-link-lib=c++");
|
||||
} else {
|
||||
println!("cargo:rustc-link-lib=stdc++");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
// Copyright (c) 2024-2026 Lux Industries Inc.
|
||||
// SPDX-License-Identifier: BSD-3-Clause-Eco
|
||||
//
|
||||
// lux-crypto-poly_mul: canonical Rust binding for the Lux polynomial-
|
||||
// multiplication C-ABI over Z_Q[X]/(X^n + 1) where Q = 998244353.
|
||||
//
|
||||
// Negacyclic convolution (X^n = -1) — the lattice-crypto shape used by
|
||||
// ML-KEM, ML-DSA, Corona. The C-ABI dispatcher chooses schoolbook
|
||||
// (O(n^2), faster below n=64) or NTT (O(n log n)) automatically.
|
||||
//
|
||||
// Per-coefficient byte output is identical to the Go reference at
|
||||
// github.com/luxfi/crypto/poly_mul (Mul / MulSchoolbook / MulNTT) — see
|
||||
// KAT vectors in luxcpp/crypto/poly_mul/test/vectors/.
|
||||
//
|
||||
// Domain: STANDARD form on both sides. Inputs may be in [0, 2^64); reduced
|
||||
// mod Q on entry. Outputs guaranteed in [0, Q).
|
||||
|
||||
#![no_std]
|
||||
#![forbid(unsafe_op_in_unsafe_fn)]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
use alloc::vec;
|
||||
use alloc::vec::Vec;
|
||||
use core::ffi::c_int;
|
||||
|
||||
extern "C" {
|
||||
fn poly_mul(
|
||||
a: *const u64,
|
||||
b: *const u64,
|
||||
n: usize,
|
||||
modulus: u64,
|
||||
root: u64,
|
||||
out: *mut u64,
|
||||
) -> c_int;
|
||||
}
|
||||
|
||||
/// Cyclone-FFT prime: 998244353 = 119 * 2^23 + 1.
|
||||
pub const Q: u64 = 998_244_353;
|
||||
|
||||
/// 2^16-th primitive root of unity mod Q. Pinned by the C-ABI: `multiply`
|
||||
/// only accepts (Q, PRIMITIVE_ROOT).
|
||||
pub const PRIMITIVE_ROOT: u64 = 629_671_588;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum PolyMulError {
|
||||
/// Length mismatch, non-cyclone (modulus, root), or n=0.
|
||||
Input,
|
||||
}
|
||||
|
||||
/// Compute c = a * b in Z_Q[X]/(X^n + 1) where n = a.len() = b.len().
|
||||
///
|
||||
/// Picks schoolbook below n=64 and the negacyclic NTT path otherwise.
|
||||
/// Returns a freshly allocated `Vec<u64>` of length n.
|
||||
#[inline]
|
||||
pub fn multiply(a: &[u64], b: &[u64]) -> Result<Vec<u64>, PolyMulError> {
|
||||
if a.is_empty() || a.len() != b.len() {
|
||||
return Err(PolyMulError::Input);
|
||||
}
|
||||
let n = a.len();
|
||||
let mut out = vec![0u64; n];
|
||||
// SAFETY: pointers valid for the call's duration; out is sized to n.
|
||||
let rc = unsafe {
|
||||
poly_mul(
|
||||
a.as_ptr(),
|
||||
b.as_ptr(),
|
||||
n,
|
||||
Q,
|
||||
PRIMITIVE_ROOT,
|
||||
out.as_mut_ptr(),
|
||||
)
|
||||
};
|
||||
if rc == 0 {
|
||||
Ok(out)
|
||||
} else {
|
||||
Err(PolyMulError::Input)
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute c = a * b into a caller-provided buffer. Returns Err if shapes
|
||||
/// are wrong or the C-ABI returns non-zero.
|
||||
#[inline]
|
||||
pub fn multiply_into(a: &[u64], b: &[u64], out: &mut [u64]) -> Result<(), PolyMulError> {
|
||||
if a.is_empty() || a.len() != b.len() || out.len() != a.len() {
|
||||
return Err(PolyMulError::Input);
|
||||
}
|
||||
let n = a.len();
|
||||
// SAFETY: pointers valid for the call's duration.
|
||||
let rc = unsafe {
|
||||
poly_mul(
|
||||
a.as_ptr(),
|
||||
b.as_ptr(),
|
||||
n,
|
||||
Q,
|
||||
PRIMITIVE_ROOT,
|
||||
out.as_mut_ptr(),
|
||||
)
|
||||
};
|
||||
if rc == 0 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(PolyMulError::Input)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
// Copyright (c) 2024-2026 Lux Industries Inc.
|
||||
// SPDX-License-Identifier: BSD-3-Clause-Eco
|
||||
//
|
||||
// Spec-vector tests for lux-crypto-poly_mul. Mirrors the Go reference's KAT
|
||||
// schedule (lcg seed_a / seed_b -> a / b -> negacyclic c -> sum/first/last).
|
||||
// Cross-references luxcpp/crypto/poly_mul/test/vectors/poly_mul_kat.json.
|
||||
|
||||
use lux_crypto_poly_mul::{multiply, Q};
|
||||
|
||||
fn lcg(seed: u64, n: usize) -> Vec<u64> {
|
||||
let mut out = Vec::with_capacity(n);
|
||||
let mut state = seed;
|
||||
for _ in 0..n {
|
||||
state = state
|
||||
.wrapping_mul(6_364_136_223_846_793_005)
|
||||
.wrapping_add(1_442_695_040_888_963_407);
|
||||
out.push(state % Q);
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
// 10 KAT cases — same (n, seed_a, seed_b, sum, first, last) as
|
||||
// /Users/z/work/lux/crypto/poly_mul/poly_mul_test.go::TestKATs.
|
||||
const KATS: &[(usize, u64, u64, u64, u64, u64)] = &[
|
||||
( 2, 0x1, 0x2, 567_996_115, 371_012_399, 196_983_716),
|
||||
( 4, 0x64, 0xc8, 935_898_137, 410_827_071, 376_044_324),
|
||||
( 8, 0x4d2, 0x162e, 48_246_169, 673_146_415, 767_542_882),
|
||||
( 16, 0xdead_beef,0xcafe_babe,590_901_354, 41_636_199, 860_993_788),
|
||||
( 32, 0x7, 0xd, 72_828_809, 392_054_258, 84_461_645),
|
||||
( 64, 0xb, 0x11, 933_631_914, 967_956_798, 522_099_145),
|
||||
( 128, 0x13, 0x17, 443_591_268, 429_303_763, 461_383_520),
|
||||
( 256, 0x1, 0x2, 78_388_485, 359_306_289, 229_818_328),
|
||||
( 512, 0x1f, 0x25, 723_079_964, 641_254_315, 816_782_592),
|
||||
( 1024, 0x29, 0x2b, 308_246_040, 8_552_215, 931_224_377),
|
||||
];
|
||||
|
||||
fn check(c: &[u64], want_sum: u64, want_first: u64, want_last: u64) {
|
||||
let mut sum = 0u64;
|
||||
for &v in c {
|
||||
assert!(v < Q, "out-of-range output {v} (Q={Q})");
|
||||
sum = (sum + v) % Q;
|
||||
}
|
||||
assert_eq!(sum, want_sum, "sum mismatch");
|
||||
assert_eq!(c[0], want_first, "first mismatch");
|
||||
assert_eq!(c[c.len() - 1], want_last, "last mismatch");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn kat_table() {
|
||||
for &(n, seed_a, seed_b, sum, first, last) in KATS {
|
||||
let a = lcg(seed_a, n);
|
||||
let b = lcg(seed_b, n);
|
||||
let c = multiply(&a, &b).expect("multiply");
|
||||
assert_eq!(c.len(), n);
|
||||
check(&c, sum, first, last);
|
||||
}
|
||||
}
|
||||
|
||||
// Hand-written negacyclic case from the Go test:
|
||||
// (1 + 2X + 3X^2 + 4X^3) * (5 + 6X + 7X^2 + 8X^3) mod (X^4 + 1)
|
||||
// = 998244297 + 998244317 X + 2 X^2 + 60 X^3
|
||||
#[test]
|
||||
fn negacyclic_handwritten() {
|
||||
let a: Vec<u64> = vec![1, 2, 3, 4];
|
||||
let b: Vec<u64> = vec![5, 6, 7, 8];
|
||||
let want: Vec<u64> = vec![998_244_297, 998_244_317, 2, 60];
|
||||
let c = multiply(&a, &b).unwrap();
|
||||
assert_eq!(c, want);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_length_mismatch() {
|
||||
assert!(multiply(&[1u64, 2], &[1u64, 2, 3]).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_empty() {
|
||||
assert!(multiply(&[], &[]).is_err());
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "lux-crypto-poseidon"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "Canonical Rust binding for Lux Poseidon2 t=2 BN254 (gnark-crypto v0.20.1 compatible). Calls into luxcpp/crypto/poseidon via the C-ABI."
|
||||
repository = "https://github.com/luxfi/crypto"
|
||||
readme = "README.md"
|
||||
keywords = ["poseidon", "poseidon2", "bn254", "zk", "ffi"]
|
||||
categories = ["cryptography"]
|
||||
|
||||
[lib]
|
||||
name = "lux_crypto_poseidon"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1,34 @@
|
||||
# lux-crypto-poseidon
|
||||
|
||||
Canonical Rust binding for **Poseidon2** hash with t=2 over BN254 scalar field.
|
||||
|
||||
Wraps the C-ABI exposed by `luxcpp/crypto/poseidon`. Round constants and
|
||||
permutation match `gnark-crypto` v0.20.1 for byte-for-byte cross-stack
|
||||
compatibility.
|
||||
|
||||
## Algorithm
|
||||
|
||||
- **Poseidon2** -- ZK-friendly hash, BN254 scalar field
|
||||
- Width: t=2 (compression mode)
|
||||
- gnark-crypto v0.20.1 compatible
|
||||
|
||||
## Build
|
||||
|
||||
Set `CRYPTO_DIR` (install prefix) or `CRYPTO_BUILD_DIR` (cmake build dir) so
|
||||
the build script can find `libposeidon_cpu.a`.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/luxfi/crypto
|
||||
cd crypto && cmake -S . -B build-cto && cmake --build build-cto
|
||||
export CRYPTO_BUILD_DIR=$(pwd)/build-cto
|
||||
cargo build -p lux-crypto-poseidon
|
||||
```
|
||||
|
||||
## Attribution
|
||||
|
||||
Round constants and permutation derived from `gnark-crypto` v0.20.1
|
||||
(Apache 2.0).
|
||||
|
||||
## License
|
||||
|
||||
See `LICENSE` at the repository root.
|
||||
@@ -0,0 +1,48 @@
|
||||
// Build script for lux-crypto-poseidon.
|
||||
//
|
||||
// Discovers the static archives produced by `luxcpp/crypto/poseidon` and emits
|
||||
// the link directives. Resolution order (first hit wins):
|
||||
// 1. CRYPTO_DIR - install prefix; archives at $CRYPTO_DIR/lib/poseidon/
|
||||
// 2. CRYPTO_BUILD_DIR - cmake build directory; archives at $CRYPTO_BUILD_DIR/poseidon/
|
||||
// 3. Default fallback to ../../../../luxcpp/crypto/build-cto
|
||||
//
|
||||
// We link the C++ runtime (libc++ on macOS, libstdc++ elsewhere) because the
|
||||
// archive contains C++ object code from the canonical implementation.
|
||||
|
||||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn main() {
|
||||
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
|
||||
let base: PathBuf = if let Ok(d) = env::var("CRYPTO_DIR") {
|
||||
PathBuf::from(d).join("lib")
|
||||
} else if let Ok(d) = env::var("CRYPTO_BUILD_DIR") {
|
||||
PathBuf::from(d)
|
||||
} else {
|
||||
manifest_dir
|
||||
.join("..")
|
||||
.join("..")
|
||||
.join("..")
|
||||
.join("..")
|
||||
.join("luxcpp")
|
||||
.join("crypto")
|
||||
.join("build-cto")
|
||||
};
|
||||
|
||||
println!("cargo:rerun-if-changed=src/lib.rs");
|
||||
println!("cargo:rerun-if-env-changed=CRYPTO_DIR");
|
||||
println!("cargo:rerun-if-env-changed=CRYPTO_BUILD_DIR");
|
||||
|
||||
let lib_path = base.join("poseidon");
|
||||
println!("cargo:rustc-link-search=native={}", lib_path.display());
|
||||
// The umbrella `libposeidon.a` carries the C-ABI shim; the CPU body lives
|
||||
// in `libposeidon_cpu.a`. Both are required for symbol resolution.
|
||||
println!("cargo:rustc-link-lib=static=poseidon");
|
||||
println!("cargo:rustc-link-lib=static=poseidon_cpu");
|
||||
|
||||
if cfg!(target_os = "macos") {
|
||||
println!("cargo:rustc-link-lib=c++");
|
||||
} else {
|
||||
println!("cargo:rustc-link-lib=stdc++");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
// Copyright (c) 2024-2026 Lux Industries Inc.
|
||||
// SPDX-License-Identifier: BSD-3-Clause-Eco
|
||||
//
|
||||
// lux-crypto-poseidon: canonical Rust binding for the Lux Poseidon2 C-ABI.
|
||||
//
|
||||
// Links statically against `libposeidon.a` and `libposeidon_cpu.a` produced
|
||||
// by `luxcpp/crypto/poseidon`, whose CPU body is a first-party port of the
|
||||
// gnark-crypto v0.20.1 Poseidon2 t=2 BN254 permutation
|
||||
// (NewParameters(2, 6, 50)) plus the canonical Merkle-Damgard construction
|
||||
// (NewMerkleDamgardHasher() with 32-byte zero IV).
|
||||
//
|
||||
// Byte-equal to upstream gnark-crypto.
|
||||
|
||||
#![no_std]
|
||||
#![forbid(unsafe_op_in_unsafe_fn)]
|
||||
|
||||
use core::ffi::c_int;
|
||||
|
||||
extern "C" {
|
||||
/// Poseidon2 t=2 BN254 Merkle-Damgard hash. Returns 0 on success, or a
|
||||
/// negative `CRYPTO_ERR_*` code on failure.
|
||||
fn poseidon_bn254(input: *const u8, input_len: usize, output: *mut u8) -> c_int;
|
||||
}
|
||||
|
||||
/// Length of a Poseidon2 BN254 digest in bytes (one fr.Element).
|
||||
pub const DIGEST_LEN: usize = 32;
|
||||
|
||||
/// Compute the Poseidon2 t=2 BN254 Merkle-Damgard hash of `input`.
|
||||
///
|
||||
/// `input` is consumed in 32-byte big-endian fr-blocks. If the final block is
|
||||
/// shorter than 32 bytes, it is left-padded with zeroes (matching the upstream
|
||||
/// gnark `cloneLeftPadded` semantics). Each block must encode a value strictly
|
||||
/// less than the BN254 scalar field modulus `r`; otherwise the call returns
|
||||
/// the empty zero-IV result and reports a non-zero status via the C-ABI return
|
||||
/// value (a `debug_assert` in this binding catches that condition during
|
||||
/// development).
|
||||
#[inline]
|
||||
pub fn hash(input: &[u8]) -> [u8; DIGEST_LEN] {
|
||||
let mut out = [0u8; DIGEST_LEN];
|
||||
// SAFETY: pointers are valid for the duration of the call; output is
|
||||
// sized to exactly DIGEST_LEN bytes.
|
||||
let rc = unsafe { poseidon_bn254(input.as_ptr(), input.len(), out.as_mut_ptr()) };
|
||||
debug_assert_eq!(rc, 0, "poseidon_bn254 returned non-zero status: {}", rc);
|
||||
out
|
||||
}
|
||||
|
||||
/// Compute the Poseidon2 t=2 BN254 Merkle-Damgard hash of `input` into a
|
||||
/// caller-supplied buffer. Returns the same byte slice for ergonomic chaining.
|
||||
#[inline]
|
||||
pub fn hash_into<'a>(input: &[u8], output: &'a mut [u8; DIGEST_LEN]) -> &'a [u8; DIGEST_LEN] {
|
||||
// SAFETY: pointers are valid for the duration of the call; output is
|
||||
// sized to exactly DIGEST_LEN bytes.
|
||||
let rc = unsafe { poseidon_bn254(input.as_ptr(), input.len(), output.as_mut_ptr()) };
|
||||
debug_assert_eq!(rc, 0, "poseidon_bn254 returned non-zero status: {}", rc);
|
||||
output
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
// Copyright (c) 2024-2026 Lux Industries Inc.
|
||||
// SPDX-License-Identifier: BSD-3-Clause-Eco
|
||||
//
|
||||
// Spec-vector integration test for lux-crypto-poseidon.
|
||||
//
|
||||
// All vectors below are unkeyed Poseidon2 Merkle-Damgard hashes produced
|
||||
// using gnark-crypto v0.20.1
|
||||
// poseidon2.NewMerkleDamgardHasher()
|
||||
//
|
||||
// over the t=2 BN254 permutation
|
||||
// poseidon2.NewPermutation(2, 6, 50)
|
||||
//
|
||||
// The same test suite is also asserted at the C++ layer in
|
||||
// luxcpp/crypto/poseidon/test/poseidon_test.cpp.
|
||||
//
|
||||
// We hand-encode the vectors here (rather than parsing the JSON at runtime)
|
||||
// so the test stays no-std and independent of any JSON parser.
|
||||
|
||||
use lux_crypto_poseidon::{hash, hash_into, DIGEST_LEN};
|
||||
|
||||
fn from_hex(s: &str) -> [u8; DIGEST_LEN] {
|
||||
let s = s.as_bytes();
|
||||
assert_eq!(s.len(), 2 * DIGEST_LEN, "expected {}-byte hex digest", DIGEST_LEN);
|
||||
let mut out = [0u8; DIGEST_LEN];
|
||||
let nibble = |c: u8| -> u8 {
|
||||
match c {
|
||||
b'0'..=b'9' => c - b'0',
|
||||
b'a'..=b'f' => c - b'a' + 10,
|
||||
b'A'..=b'F' => c - b'A' + 10,
|
||||
_ => panic!("non-hex byte: {c:#x}"),
|
||||
}
|
||||
};
|
||||
for i in 0..DIGEST_LEN {
|
||||
out[i] = (nibble(s[2 * i]) << 4) | nibble(s[2 * i + 1]);
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
fn from_hex_var(s: &str) -> Vec<u8> {
|
||||
assert!(s.len() % 2 == 0, "hex must be even length");
|
||||
let nibble = |c: u8| -> u8 {
|
||||
match c {
|
||||
b'0'..=b'9' => c - b'0',
|
||||
b'a'..=b'f' => c - b'a' + 10,
|
||||
b'A'..=b'F' => c - b'A' + 10,
|
||||
_ => panic!("non-hex"),
|
||||
}
|
||||
};
|
||||
let b = s.as_bytes();
|
||||
let mut out = vec![0u8; b.len() / 2];
|
||||
for i in 0..out.len() {
|
||||
out[i] = (nibble(b[2 * i]) << 4) | nibble(b[2 * i + 1]);
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hash_32b_zeros() {
|
||||
let want = from_hex("292c3a4b9343aec63e584aefa8bedeaefae44e6d718451a75736def795109dfb");
|
||||
assert_eq!(hash(&[0u8; 32]), want);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hash_32b_iota() {
|
||||
let mut input = [0u8; 32];
|
||||
for (i, b) in input.iter_mut().enumerate() {
|
||||
*b = i as u8;
|
||||
}
|
||||
let want = from_hex("16db7f49b6261d97cfaa2a8adf9e382c36d2a9d3845612aa7648b6c609946178");
|
||||
assert_eq!(hash(&input), want);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hash_64b_two_blocks() {
|
||||
let input = from_hex_var(
|
||||
"100102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f\
|
||||
102122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f",
|
||||
);
|
||||
let want = from_hex("159a4e06c63599f582b527da70fd1651353de8d82defb38e1daf4e432b6e9f9f");
|
||||
assert_eq!(hash(&input), want);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hash_96b_three_blocks() {
|
||||
let input = from_hex_var(
|
||||
"050102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f\
|
||||
062122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f\
|
||||
074142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f",
|
||||
);
|
||||
let want = from_hex("16c514c08c2b3397bff233df2188f487609c41942c7da1522b3d7e2347e42a7e");
|
||||
assert_eq!(hash(&input), want);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hash_5b_short() {
|
||||
let want = from_hex("1012f1c1aa17396b56597479235b6534e91d7e88026b9bd21e27f42895ae9739");
|
||||
assert_eq!(hash(b"hello"), want);
|
||||
}
|
||||
|
||||
// The four assertions below come from the same gnark-crypto v0.20.1 KAT
|
||||
// generator that produces the C++ test's perm/compress vectors: they are the
|
||||
// single-block hash of an identity permutation input (covering the underlying
|
||||
// permutation through the Merkle-Damgard front door), plus a handful of
|
||||
// additional inputs that bracket the BN254 modulus boundary.
|
||||
|
||||
#[test]
|
||||
fn hash_one_byte_zero() {
|
||||
// 1B input zero-padded on the LEFT to 32 bytes is identical to hashing
|
||||
// 32 zero bytes (the only nonzero byte of the padded block sits at
|
||||
// position 31, which is the canonical encoding of fr-element 0x00).
|
||||
let want = from_hex("292c3a4b9343aec63e584aefa8bedeaefae44e6d718451a75736def795109dfb");
|
||||
assert_eq!(hash(&[0u8]), want);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hash_31b_zeros_left_pad() {
|
||||
// 31 zero bytes is < block size, gnark left-pads with one more zero
|
||||
// -> identical to hash_32b_zeros above.
|
||||
let want = from_hex("292c3a4b9343aec63e584aefa8bedeaefae44e6d718451a75736def795109dfb");
|
||||
assert_eq!(hash(&[0u8; 31]), want);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hash_into_is_equivalent_to_hash() {
|
||||
let mut buf = [0u8; DIGEST_LEN];
|
||||
hash_into(b"hello", &mut buf);
|
||||
assert_eq!(buf, hash(b"hello"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deterministic_across_calls() {
|
||||
let a = hash(b"deterministic test");
|
||||
let b = hash(b"deterministic test");
|
||||
assert_eq!(a, b);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn distinct_inputs_distinct_digests() {
|
||||
// Trivial avalanche check: a 1-bit difference flips many output bits.
|
||||
let a = hash(b"poseidon-a");
|
||||
let b = hash(b"poseidon-b");
|
||||
assert_ne!(a, b);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
# lux-crypto-ripemd160
|
||||
|
||||
Canonical Rust binding for **RIPEMD-160** (ISO/IEC 10118-3, used in Bitcoin
|
||||
P2PKH addresses).
|
||||
|
||||
Wraps the C-ABI exposed by `luxcpp/crypto/ripemd160`. Verified against the
|
||||
RIPEMD-160 reference vectors.
|
||||
|
||||
## Algorithm
|
||||
|
||||
- **RIPEMD-160** -- 20-byte digest, ISO/IEC 10118-3
|
||||
|
||||
## Build
|
||||
|
||||
Set `CRYPTO_DIR` (install prefix) or `CRYPTO_BUILD_DIR` (cmake build dir) so
|
||||
the build script can find `libripemd160_cpu.a`.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/luxfi/crypto
|
||||
cd crypto && cmake -S . -B build-cto && cmake --build build-cto
|
||||
export CRYPTO_BUILD_DIR=$(pwd)/build-cto
|
||||
cargo build -p lux-crypto-ripemd160
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
See `LICENSE` at the repository root.
|
||||
@@ -0,0 +1,29 @@
|
||||
# lux-crypto-secp256k1
|
||||
|
||||
Canonical Rust binding for **secp256k1 ECDSA public-key recovery** (Ethereum-style
|
||||
`ecrecover`).
|
||||
|
||||
Wraps the C-ABI exposed by `luxcpp/crypto/secp256k1`. Single and batch recovery
|
||||
APIs. Verified against SEC1 and EIP-2 reference vectors in `tests/`.
|
||||
|
||||
## Algorithm
|
||||
|
||||
- **secp256k1** -- Bitcoin/Ethereum curve (SEC1)
|
||||
- **ECDSA recovery** -- recover the signer public key from `(msg, r, s, v)`
|
||||
- Strict `s` low-S enforcement (EIP-2)
|
||||
|
||||
## Build
|
||||
|
||||
Set `CRYPTO_DIR` (install prefix) or `CRYPTO_BUILD_DIR` (cmake build dir) so the
|
||||
build script can find `libsecp256k1_cpu.a`.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/luxfi/crypto
|
||||
cd crypto && cmake -S . -B build-cto && cmake --build build-cto
|
||||
export CRYPTO_BUILD_DIR=$(pwd)/build-cto
|
||||
cargo build -p lux-crypto-secp256k1
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
See `LICENSE` at the repository root.
|
||||
@@ -0,0 +1,26 @@
|
||||
# lux-crypto-sha256
|
||||
|
||||
Canonical Rust binding for **SHA-256** (FIPS 180-4).
|
||||
|
||||
Wraps the C-ABI exposed by `luxcpp/crypto/sha256`. Verified against FIPS 180-4
|
||||
reference vectors.
|
||||
|
||||
## Algorithm
|
||||
|
||||
- **SHA-256** -- 32-byte digest, FIPS 180-4 standard
|
||||
|
||||
## Build
|
||||
|
||||
Set `CRYPTO_DIR` (install prefix) or `CRYPTO_BUILD_DIR` (cmake build dir) so
|
||||
the build script can find `libsha256_cpu.a`.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/luxfi/crypto
|
||||
cd crypto && cmake -S . -B build-cto && cmake --build build-cto
|
||||
export CRYPTO_BUILD_DIR=$(pwd)/build-cto
|
||||
cargo build -p lux-crypto-sha256
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
See `LICENSE` at the repository root.
|
||||
@@ -0,0 +1,32 @@
|
||||
# lux-crypto-slhdsa
|
||||
|
||||
Canonical Rust binding for **SLH-DSA** (FIPS 205), the NIST-standardized
|
||||
hash-based stateless post-quantum signature scheme (formerly SPHINCS+).
|
||||
|
||||
Wraps the C-ABI exposed by `luxcpp/crypto/slhdsa`. Verified against the NIST
|
||||
FIPS 205 reference vectors.
|
||||
|
||||
## Algorithm
|
||||
|
||||
- **SLH-DSA** -- FIPS 205, hash-based, stateless
|
||||
- Multiple parameter sets (SHA2-128f / SHA2-192f / SHA2-256f and SHAKE variants)
|
||||
|
||||
## Build
|
||||
|
||||
Set `CRYPTO_DIR` (install prefix) or `CRYPTO_BUILD_DIR` (cmake build dir) so
|
||||
the build script can find `libslhdsa_cpu.a`.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/luxfi/crypto
|
||||
cd crypto && cmake -S . -B build-cto && cmake --build build-cto
|
||||
export CRYPTO_BUILD_DIR=$(pwd)/build-cto
|
||||
cargo build -p lux-crypto-slhdsa
|
||||
```
|
||||
|
||||
## Attribution
|
||||
|
||||
Underlying implementation derived from PQClean (CC0 / public domain).
|
||||
|
||||
## License
|
||||
|
||||
See `LICENSE` at the repository root.
|
||||
@@ -0,0 +1,31 @@
|
||||
# lux-crypto-verkle
|
||||
|
||||
Canonical Rust binding for **Verkle** commit and multiproof verification using
|
||||
the Banderwagon SRS.
|
||||
|
||||
Wraps the C-ABI exposed by `luxcpp/crypto/verkle`.
|
||||
|
||||
## Algorithm
|
||||
|
||||
- **Verkle tree** -- commit, multiproof, verify
|
||||
- IPA over Banderwagon (see `lux-crypto-ipa`, `lux-crypto-banderwagon`)
|
||||
|
||||
## Build
|
||||
|
||||
Set `CRYPTO_DIR` (install prefix) or `CRYPTO_BUILD_DIR` (cmake build dir) so
|
||||
the build script can find `libverkle_cpu.a`.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/luxfi/crypto
|
||||
cd crypto && cmake -S . -B build-cto && cmake --build build-cto
|
||||
export CRYPTO_BUILD_DIR=$(pwd)/build-cto
|
||||
cargo build -p lux-crypto-verkle
|
||||
```
|
||||
|
||||
## Attribution
|
||||
|
||||
Banderwagon SRS from the public Verkle ceremony.
|
||||
|
||||
## License
|
||||
|
||||
See `LICENSE` at the repository root.
|
||||
@@ -1,10 +1,17 @@
|
||||
[package]
|
||||
name = "lux-crypto"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
license = "BSD-3-Clause"
|
||||
rust-version = "1.74"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
rust-version.workspace = true
|
||||
authors.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
documentation.workspace = true
|
||||
readme = "README.md"
|
||||
description = "Umbrella crate for Lux crypto. Re-exports per-algorithm crates that bind to luxcpp/crypto via the C-ABI."
|
||||
keywords = ["luxfi", "crypto", "blockchain", "post-quantum", "ffi"]
|
||||
categories = ["cryptography"]
|
||||
|
||||
# This crate is the canonical Rust entry point for Lux crypto.
|
||||
# Mirrors github.com/luxfi/gpu (canonical Rust binding to luxfi/accel).
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
# lux-crypto
|
||||
|
||||
Umbrella Rust binding to the Lux cryptography library (`luxcpp/crypto`). This
|
||||
crate re-exports the per-algorithm member crates and retains the original raw
|
||||
FFI surface for historical callers.
|
||||
|
||||
For new code, prefer the per-algorithm crates:
|
||||
|
||||
| Crate | Algorithm | Spec |
|
||||
|-------|-----------|------|
|
||||
| `lux-crypto-secp256k1` | secp256k1 ECDSA public-key recovery | SEC1, EIP-2 |
|
||||
| `lux-crypto-keccak` | Keccak-256 | Ethereum Yellow Paper |
|
||||
| `lux-crypto-sha256` | SHA-256 | FIPS 180-4 |
|
||||
| `lux-crypto-ripemd160` | RIPEMD-160 | ISO/IEC 10118-3 |
|
||||
| `lux-crypto-blake2b` | BLAKE2b | RFC 7693 |
|
||||
| `lux-crypto-blake3` | BLAKE3 | BLAKE3 reference v1.5.0 |
|
||||
| `lux-crypto-ed25519` | Ed25519 | RFC 8032 |
|
||||
| `lux-crypto-aead` | ChaCha20-Poly1305 | RFC 8439 |
|
||||
| `lux-crypto-bls` | BLS12-381 IRTF signatures | draft-irtf-cfrg-bls-signature-05 |
|
||||
| `lux-crypto-mldsa` | ML-DSA | FIPS 204 |
|
||||
| `lux-crypto-mlkem` | ML-KEM | FIPS 203 |
|
||||
| `lux-crypto-slhdsa` | SLH-DSA | FIPS 205 |
|
||||
| `lux-crypto-lamport` | Lamport OTS | Lamport 1979 |
|
||||
| `lux-crypto-banderwagon` | Banderwagon group | EIP-7805 |
|
||||
| `lux-crypto-pedersen` | Pedersen commitments | Verkle |
|
||||
| `lux-crypto-ipa` | Inner Product Argument | Verkle |
|
||||
| `lux-crypto-verkle` | Verkle commit + multiproof | Verkle |
|
||||
| `lux-crypto-evm256` | EVM 256-bit modular arithmetic | EIP-7212 helpers |
|
||||
| `lux-crypto-kzg` | KZG point-evaluation precompile | EIP-4844 |
|
||||
| `lux-crypto-poseidon` | Poseidon2 t=2 BN254 | gnark-crypto v0.20.1 |
|
||||
| `lux-crypto-ntt` | Number-Theoretic Transform | Q = 998244353 |
|
||||
| `lux-crypto-poly_mul` | Polynomial multiplication in `Z_Q[X]/(X^n+1)` | Q = 998244353 |
|
||||
|
||||
## Build
|
||||
|
||||
The crate links static archives produced by `luxcpp/crypto`. Set one of:
|
||||
|
||||
- `CRYPTO_DIR` -- install prefix; archives at `$CRYPTO_DIR/lib/<alg>/lib<alg>_cpu.a`
|
||||
- `CRYPTO_BUILD_DIR` -- cmake build directory; archives at `$CRYPTO_BUILD_DIR/<alg>/lib<alg>_cpu.a`
|
||||
|
||||
If neither is set the build script falls back to a sibling checkout of
|
||||
`luxcpp/crypto` at `../../../../luxcpp/crypto/build-cto`.
|
||||
|
||||
```bash
|
||||
# Build the C archives once
|
||||
git clone https://github.com/luxfi/crypto
|
||||
cd crypto && cmake -S . -B build-cto && cmake --build build-cto
|
||||
|
||||
# Build this crate against them
|
||||
export CRYPTO_BUILD_DIR=$(pwd)/build-cto
|
||||
cargo build -p lux-crypto
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
See `LICENSE` at the repository root. Source files declare `SPDX-License-Identifier`
|
||||
per file; the umbrella project license is the Lux Ecosystem License.
|
||||
Reference in New Issue
Block a user