rust: add per-crate READMEs and PUBLISHING runbook for crates.io

All 23 lux-crypto-* crates now have:
- README.md with algorithm description, build instructions
  (CRYPTO_DIR / CRYPTO_BUILD_DIR), source attribution
- workspace-inherited authors/homepage/repository/documentation
- crates.io-ready metadata (description, keywords, categories)

cargo publish --dry-run --no-verify passes for all 23 crates.

PUBLISHING.md documents the publish runbook: license decision required
before upload, cargo login flow, name reservation order, dry-run sweep,
ordered upload, native-archive dependency (Option C documented), docs.rs
follow-up, versioning policy, source attribution table.

This prepares the crates for a human with a crates.io token to run the
real cargo publish step. No upload happens automatically -- the LICENSE
decision (BSD-3-Clause vs Lux Ecosystem License) must be confirmed first.
This commit is contained in:
Hanzo AI
2026-04-27 13:44:00 -07:00
parent f2f3b502ee
commit da84f99872
31 changed files with 1846 additions and 3 deletions
+225
View File
@@ -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.
+27
View File
@@ -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.
+31
View File
@@ -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.
+26
View File
@@ -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.
+31
View File
@@ -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.
+17
View File
@@ -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
+29
View File
@@ -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.
+56
View File
@@ -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++");
}
}
+213
View File
@@ -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)
}
+225
View File
@@ -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);
}
+17
View File
@@ -0,0 +1,17 @@
[package]
name = "lux-crypto-ed25519"
version = "0.1.0"
edition.workspace = true
license.workspace = true
rust-version.workspace = true
description = "Canonical Rust binding for Lux Ed25519. Calls into luxcpp/crypto/ed25519 (vendored ed25519-donna, public domain) via the C-ABI."
repository = "https://github.com/luxfi/crypto"
readme = "README.md"
keywords = ["ed25519", "eddsa", "signature", "rfc8032", "ffi"]
categories = ["cryptography"]
[lib]
name = "lux_crypto_ed25519"
[lints]
workspace = true
+31
View File
@@ -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.
+35
View File
@@ -0,0 +1,35 @@
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-ed25519")
};
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("ed25519");
println!("cargo:rustc-link-search=native={}", lib_path.display());
println!("cargo:rustc-link-lib=static=ed25519");
println!("cargo:rustc-link-lib=static=ed25519_cpu");
if cfg!(target_os = "macos") {
println!("cargo:rustc-link-lib=c++");
} else {
println!("cargo:rustc-link-lib=stdc++");
}
}
+114
View File
@@ -0,0 +1,114 @@
// Copyright (c) 2024-2026 Lux Industries Inc.
// SPDX-License-Identifier: BSD-3-Clause-Eco
//
// lux-crypto-ed25519: canonical Rust binding for the Lux Ed25519 C-ABI.
//
// Links statically against `libed25519.a` and `libed25519_cpu.a` produced by
// `luxcpp/crypto/ed25519`, whose CPU body wraps the vendored ed25519-donna
// reference (Andrew Moon, public domain). Conforms to RFC 8032 §5.1 (PureEdDSA
// over Curve25519, "Ed25519").
//
// The C-ABI uses the canonical 32-byte secret form: `sk` is the 32-byte seed
// (RFC 8032 §5.1.5 "private key"). The expanded 64-byte NaCl form
// (seed || pk) is internal to the C++ wrapper.
//
// All three core operations (`keygen`, `sign`, `verify`) are byte-equal to
// libsodium / ed25519-dalek across the RFC 8032 §7.1 vector set.
#![no_std]
#![forbid(unsafe_op_in_unsafe_fn)]
use core::ffi::c_int;
extern "C" {
fn ed25519_keygen(seed: *const u8, sk: *mut u8, pk: *mut u8) -> c_int;
fn ed25519_sign(
sk: *const u8,
msg: *const u8,
msg_len: usize,
sig: *mut u8,
) -> c_int;
fn ed25519_verify(
pk: *const u8,
msg: *const u8,
msg_len: usize,
sig: *const u8,
) -> c_int;
}
/// Length of a 32-byte Ed25519 seed (RFC 8032 §5.1.5 "private key").
pub const SEED_LEN: usize = 32;
/// Length of a 32-byte Ed25519 secret key (= seed; canonical form).
pub const SECRET_KEY_LEN: usize = 32;
/// Length of a 32-byte Ed25519 public key.
pub const PUBLIC_KEY_LEN: usize = 32;
/// Length of a 64-byte Ed25519 signature (R || S, RFC 8032 §5.1.6).
pub const SIGNATURE_LEN: usize = 64;
/// Errors returned by the Ed25519 operations.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Error {
/// Underlying C-ABI call returned a non-zero status.
InternalError(c_int),
/// `verify` rejected the (msg, sig, pk) triple.
InvalidSignature,
}
/// Derive the (secret_key, public_key) pair from a 32-byte seed.
///
/// `secret_key` is the canonical RFC 8032 form: the 32-byte seed itself.
/// The expanded NaCl 64-byte form (seed || pk) is internal to the C wrapper.
#[inline]
pub fn keygen(seed: &[u8; SEED_LEN]) -> ([u8; SECRET_KEY_LEN], [u8; PUBLIC_KEY_LEN]) {
let mut sk = [0u8; SECRET_KEY_LEN];
let mut pk = [0u8; PUBLIC_KEY_LEN];
// SAFETY: pointers valid for the call's duration; output buffers sized exactly.
let rc = unsafe { ed25519_keygen(seed.as_ptr(), sk.as_mut_ptr(), pk.as_mut_ptr()) };
debug_assert_eq!(rc, 0, "ed25519_keygen returned non-zero status: {}", rc);
(sk, pk)
}
/// Sign `msg` under `sk`. The signature is deterministic per RFC 8032 §5.1.6.
#[inline]
pub fn sign(sk: &[u8; SECRET_KEY_LEN], msg: &[u8]) -> [u8; SIGNATURE_LEN] {
let mut sig = [0u8; SIGNATURE_LEN];
// SAFETY: pointers valid for the call's duration; sig buffer sized exactly.
let rc = unsafe {
ed25519_sign(
sk.as_ptr(),
msg.as_ptr(),
msg.len(),
sig.as_mut_ptr(),
)
};
debug_assert_eq!(rc, 0, "ed25519_sign returned non-zero status: {}", rc);
sig
}
/// Verify a single Ed25519 signature.
///
/// Returns `Ok(())` iff the signature is valid for `(pk, msg)`. Returns
/// `Err(Error::InvalidSignature)` when the C-ABI rejects the triple, and
/// `Err(Error::InternalError)` for any other non-zero status.
#[inline]
pub fn verify(
pk: &[u8; PUBLIC_KEY_LEN],
msg: &[u8],
sig: &[u8; SIGNATURE_LEN],
) -> Result<(), Error> {
// SAFETY: pointers valid for the call's duration.
let rc = unsafe {
ed25519_verify(
pk.as_ptr(),
msg.as_ptr(),
msg.len(),
sig.as_ptr(),
)
};
match rc {
0 => Ok(()),
// CRYPTO_ERR_VERIFY = -3 in lux_crypto.h
-3 => Err(Error::InvalidSignature),
other => Err(Error::InternalError(other)),
}
}
@@ -0,0 +1,281 @@
// Copyright (c) 2024-2026 Lux Industries Inc.
// SPDX-License-Identifier: BSD-3-Clause-Eco
//
// RFC 8032 §7.1 Known-Answer Tests for Ed25519 (PureEdDSA over Curve25519).
//
// Vectors 1, 2, 3, 1024, SHA(abc) come straight from the RFC.
// Vectors 4, 5, 6 come from the Bernstein/Lange `sign.input` corpus that
// ed25519-donna's regression suite uses.
//
// Each vector is checked four ways:
// 1. keygen(seed) -> pk byte-equals the published public key
// 2. sign(sk, msg) -> sig byte-equals the published signature
// 3. verify(pk, msg, sig) -> accepts the published signature
// 4. verify with mutated sig and mutated pk -> InvalidSignature
use lux_crypto_ed25519::{
keygen, sign, verify, Error, PUBLIC_KEY_LEN, SEED_LEN, SIGNATURE_LEN,
};
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 byte: {:#x}", c),
}
};
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
}
fn from_hex_n<const N: usize>(s: &str) -> [u8; N] {
let v = from_hex_var(s);
assert_eq!(v.len(), N, "expected {} bytes, got {}", N, v.len());
let mut a = [0u8; N];
a.copy_from_slice(&v);
a
}
struct Vector {
name: &'static str,
seed: &'static str,
pk: &'static str,
sig: &'static str,
msg: &'static str,
}
// RFC 8032 §7.1 — TEST 1, TEST 2, TEST 3, TEST 1024, TEST SHA(abc).
// Bernstein/Lange sign.input #4, #5, #6.
const VECTORS: &[Vector] = &[
Vector {
name: "RFC8032 TEST 1 (empty)",
seed: "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
pk: "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a",
sig: concat!(
"e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555f",
"b8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b",
),
msg: "",
},
Vector {
name: "RFC8032 TEST 2 (1-byte 0x72)",
seed: "4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb",
pk: "3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c",
sig: concat!(
"92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da",
"085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00",
),
msg: "72",
},
Vector {
name: "RFC8032 TEST 3 (2-byte af82)",
seed: "c5aa8df43f9f837bedb7442f31dcb7b166d38535076f094b85ce3a2e0b4458f7",
pk: "fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025",
sig: concat!(
"6291d657deec24024827e69c3abe01a30ce548a284743a445e3680d7db5ac3ac",
"18ff9b538d16f290ae67f760984dc6594a7c15e9716ed28dc027beceea1ec40a",
),
msg: "af82",
},
Vector {
name: "donna sign.input #4 (3-byte cbc77b)",
seed: "0d4a05b07352a5436e180356da0ae6efa0345ff7fb1572575772e8005ed978e9",
pk: "e61a185bcef2613a6c7cb79763ce945d3b245d76114dd440bcf5f2dc1aa57057",
sig: concat!(
"d9868d52c2bebce5f3fa5a79891970f309cb6591e3e1702a70276fa97c24b3a8",
"e58606c38c9758529da50ee31b8219cba45271c689afa60b0ea26c99db19b00c",
),
msg: "cbc77b",
},
Vector {
name: "donna sign.input #5 (4-byte 5f4c8989)",
seed: "6df9340c138cc188b5fe4464ebaa3f7fc206a2d55c3434707e74c9fc04e20ebb",
pk: "c0dac102c4533186e25dc43128472353eaabdb878b152aeb8e001f92d90233a7",
sig: concat!(
"124f6fc6b0d100842769e71bd530664d888df8507df6c56dedfdb509aeb93416",
"e26b918d38aa06305df3095697c18b2aa832eaa52edc0ae49fbae5a85e150c07",
),
msg: "5f4c8989",
},
Vector {
name: "donna sign.input #6 (5-byte 18b6bec097)",
seed: "b780381a65edf8b78f6945e8dbec7941ac049fd4c61040cf0c324357975a293c",
pk: "e253af0766804b869bb1595be9765b534886bbaab8305bf50dbc7f899bfb5f01",
sig: concat!(
"b2fc46ad47af464478c199e1f8be169f1be6327c7f9a0a6689371ca94caf0406",
"4a01b22aff1520abd58951341603faed768cf78ce97ae7b038abfe456aa17c09",
),
msg: "18b6bec097",
},
Vector {
name: "RFC8032 TEST 1024 (1023-byte msg)",
seed: "f5e5767cf153319517630f226876b86c8160cc583bc013744c6bf255f5cc0ee5",
pk: "278117fc144c72340f67d0f2316e8386ceffbf2b2428c9c51fef7c597f1d426e",
sig: concat!(
"0aab4c900501b3e24d7cdf4663326a3a87df5e4843b2cbdb67cbf6e460fec350",
"aa5371b1508f9f4528ecea23c436d94b5e8fcd4f681e30a6ac00a9704a188a03",
),
msg: concat!(
"08b8b2b733424243760fe426a4b54908632110a66c2f6591eabd3345e3e4eb98",
"fa6e264bf09efe12ee50f8f54e9f77b1e355f6c50544e23fb1433ddf73be84d8",
"79de7c0046dc4996d9e773f4bc9efe5738829adb26c81b37c93a1b270b20329d",
"658675fc6ea534e0810a4432826bf58c941efb65d57a338bbd2e26640f89ffbc",
"1a858efcb8550ee3a5e1998bd177e93a7363c344fe6b199ee5d02e82d522c4fe",
"ba15452f80288a821a579116ec6dad2b3b310da903401aa62100ab5d1a36553e",
"06203b33890cc9b832f79ef80560ccb9a39ce767967ed628c6ad573cb116dbef",
"efd75499da96bd68a8a97b928a8bbc103b6621fcde2beca1231d206be6cd9ec7",
"aff6f6c94fcd7204ed3455c68c83f4a41da4af2b74ef5c53f1d8ac70bdcb7ed1",
"85ce81bd84359d44254d95629e9855a94a7c1958d1f8ada5d0532ed8a5aa3fb2",
"d17ba70eb6248e594e1a2297acbbb39d502f1a8c6eb6f1ce22b3de1a1f40cc24",
"554119a831a9aad6079cad88425de6bde1a9187ebb6092cf67bf2b13fd65f270",
"88d78b7e883c8759d2c4f5c65adb7553878ad575f9fad878e80a0c9ba63bcbcc",
"2732e69485bbc9c90bfbd62481d9089beccf80cfe2df16a2cf65bd92dd597b07",
"07e0917af48bbb75fed413d238f5555a7a569d80c3414a8d0859dc65a46128ba",
"b27af87a71314f318c782b23ebfe808b82b0ce26401d2e22f04d83d1255dc51a",
"ddd3b75a2b1ae0784504df543af8969be3ea7082ff7fc9888c144da2af58429e",
"c96031dbcad3dad9af0dcbaaaf268cb8fcffead94f3c7ca495e056a9b47acdb7",
"51fb73e666c6c655ade8297297d07ad1ba5e43f1bca32301651339e22904cc8c",
"42f58c30c04aafdb038dda0847dd988dcda6f3bfd15c4b4c4525004aa06eeff8",
"ca61783aacec57fb3d1f92b0fe2fd1a85f6724517b65e614ad6808d6f6ee34df",
"f7310fdc82aebfd904b01e1dc54b2927094b2db68d6f903b68401adebf5a7e08",
"d78ff4ef5d63653a65040cf9bfd4aca7984a74d37145986780fc0b16ac451649",
"de6188a7dbdf191f64b5fc5e2ab47b57f7f7276cd419c17a3ca8e1b939ae49e4",
"88acba6b965610b5480109c8b17b80e1b7b750dfc7598d5d5011fd2dcc5600a3",
"2ef5b52a1ecc820e308aa342721aac0943bf6686b64b2579376504ccc493d97e",
"6aed3fb0f9cd71a43dd497f01f17c0e2cb3797aa2a2f256656168e6c496afc5f",
"b93246f6b1116398a346f1a641f3b041e989f7914f90cc2c7fff357876e506b5",
"0d334ba77c225bc307ba537152f3f1610e4eafe595f6d9d90d11faa933a15ef1",
"369546868a7f3a45a96768d40fd9d03412c091c6315cf4fde7cb68606937380d",
"b2eaaa707b4c4185c32eddcdd306705e4dc1ffc872eeee475a64dfac86aba41c",
"0618983f8741c5ef68d3a101e8a3b8cac60c905c15fc910840b94c00a0b9d0",
),
},
Vector {
name: "RFC8032 TEST SHA(abc) (64-byte SHA-512(abc) msg)",
seed: "833fe62409237b9d62ec77587520911e9a759cec1d19755b7da901b96dca3d42",
pk: "ec172b93ad5e563bf4932c70e1245034c35467ef2efd4d64ebf819683467e2bf",
sig: concat!(
"dc2a4459e7369633a52b1bf277839a00201009a3efbf3ecb69bea2186c26b589",
"09351fc9ac90b3ecfdfbc7c66431e0303dca179c138ac17ad9bef1177331a704",
),
msg: concat!(
"ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a",
"2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f",
),
},
];
#[test]
fn rfc8032_section_7_1_vectors_byte_equal() {
let mut passed = 0;
for v in VECTORS {
let seed = from_hex_n::<SEED_LEN>(v.seed);
let want_pk = from_hex_n::<PUBLIC_KEY_LEN>(v.pk);
let want_sig = from_hex_n::<SIGNATURE_LEN>(v.sig);
let msg = from_hex_var(v.msg);
// 1. keygen reproduces the published public key.
let (sk, pk) = keygen(&seed);
assert_eq!(pk, want_pk, "{}: keygen pk mismatch", v.name);
// sk in the C ABI is the canonical 32-byte seed.
assert_eq!(
sk, seed,
"{}: secret_key should byte-equal seed (RFC 8032 §5.1.5)",
v.name
);
// 2. sign reproduces the published signature byte-for-byte.
let sig = sign(&sk, &msg);
assert_eq!(sig, want_sig, "{}: sign sig mismatch", v.name);
// 3. verify accepts the published signature.
verify(&want_pk, &msg, &want_sig)
.unwrap_or_else(|e| panic!("{}: verify(published) failed: {:?}", v.name, e));
// 4a. verify rejects a bit-flipped signature.
let mut bad_sig = want_sig;
bad_sig[0] ^= 0x01;
assert_eq!(
verify(&want_pk, &msg, &bad_sig),
Err(Error::InvalidSignature),
"{}: verify(corrupted-sig) should fail",
v.name
);
// 4b. verify rejects when the public key is wrong.
let mut bad_pk = want_pk;
bad_pk[0] ^= 0x01;
assert_eq!(
verify(&bad_pk, &msg, &want_sig),
Err(Error::InvalidSignature),
"{}: verify(wrong-pk) should fail",
v.name
);
passed += 1;
}
assert_eq!(passed, VECTORS.len(), "all vectors must pass");
assert!(passed >= 8, "RFC 8032 §7.1 requires at least 8 vectors");
}
#[test]
fn deterministic_signing() {
// RFC 8032 §5.1.6: signatures are deterministic. The same (sk, msg) input
// must produce byte-equal signatures across calls.
let seed = [0x42u8; SEED_LEN];
let (sk, _pk) = keygen(&seed);
let msg = b"deterministic ed25519 (RFC 8032 5.1.6)";
let sig1 = sign(&sk, msg);
let sig2 = sign(&sk, msg);
assert_eq!(sig1, sig2, "signing must be deterministic");
}
#[test]
fn random_roundtrip() {
// Deterministic xorshift64* PRNG so a failure reproduces on any host.
let mut state: u64 = 0xC0DE_BEEF_CAFE_F00D;
let mut next = || {
state ^= state >> 12;
state ^= state << 25;
state ^= state >> 27;
state.wrapping_mul(0x2545_F491_4F6C_DD1D)
};
for i in 0..16 {
let mut seed = [0u8; SEED_LEN];
for chunk in seed.chunks_mut(8) {
chunk.copy_from_slice(&next().to_le_bytes());
}
let msg_len = (next() as usize) % 257; // 0..=256
let mut msg = vec![0u8; msg_len];
for chunk in msg.chunks_mut(8) {
let n = next();
for (j, b) in chunk.iter_mut().enumerate() {
*b = (n >> (8 * j)) as u8;
}
}
let (sk, pk) = keygen(&seed);
let sig = sign(&sk, &msg);
verify(&pk, &msg, &sig)
.unwrap_or_else(|e| panic!("random#{i}: verify(self-signed) failed: {e:?}"));
// Mutating the signature must cause verify to fail.
for byte_idx in [0usize, 31, 32, 63] {
let mut bad = sig;
bad[byte_idx] ^= 0x80;
assert_eq!(
verify(&pk, &msg, &bad),
Err(Error::InvalidSignature),
"random#{i}: verify must reject mutated sig (byte {byte_idx})"
);
}
}
}
+31
View File
@@ -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.
+27
View File
@@ -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.
+32
View File
@@ -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.
+31
View File
@@ -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.
+28
View File
@@ -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.
+33
View File
@@ -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.
+33
View File
@@ -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.
+27
View File
@@ -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.
+34
View File
@@ -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.
+27
View File
@@ -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.
+29
View File
@@ -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.
+26
View File
@@ -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.
+32
View File
@@ -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.
+31
View File
@@ -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.
+10 -3
View File
@@ -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).
+57
View File
@@ -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.