mirror of
https://github.com/luxfi/crypto.git
synced 2026-07-27 01:54:50 +00:00
merge: verkle-vanilla-2026-04-27 (vendored go-verkle@v0.2.2)
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
# Canonical Per-Language Implementation Audit
|
||||
|
||||
Date: 2026-04-27
|
||||
Scope: read-only audit of `~/work/lux/crypto/*` and `~/work/luxcpp/crypto/*`.
|
||||
|
||||
## Directive
|
||||
|
||||
> no wrapper no compat shim; one and one way only per C++/Go/CPU/GPU etc rust can
|
||||
> bind shit but always have 100% real optimized CPU versions + GPU in C++ + vanilla
|
||||
> Go (which can also CgO the CPU or GPU C++)
|
||||
|
||||
Per-language canonical pattern:
|
||||
|
||||
- C++/CPU canonical: `luxcpp/crypto/<alg>/cpp/<alg>.{cpp,hpp}`
|
||||
- C++/GPU canonical: `luxcpp/crypto/<alg>/gpu/{metal,cuda,wgsl}/`
|
||||
- Go canonical: `lux/crypto/<alg>/<alg>.go` — VANILLA Go real impl (stdlib / `golang.org/x/crypto` / `consensys/gnark-crypto` / `cloudflare/circl` / `zeebo/blake3` / etc. count as compliant)
|
||||
- Rust: `lux/crypto/rust/lux-crypto-<alg>/` binds to C++ via `extern "C"` + `build.rs`
|
||||
|
||||
Go cgo path is acceptable as an OPT-IN accelerator only when there is also a real vanilla Go canonical body for the same package.
|
||||
|
||||
## Audit Table
|
||||
|
||||
Legend: OK = compliant; MISSING = not present; STDLIB = stdlib/established Go crate (counts as vanilla); CGO-ONLY = violation (only path is cgo wrapper); N/A = not applicable.
|
||||
|
||||
| Algo | C++/CPU | C++/GPU | Go vanilla canonical | Go cgo accelerator | Rust binding |
|
||||
|-----------------|----------------------|---------------------------|-------------------------------------------------|---------------------------|---------------------------------------|
|
||||
| keccak | OK (cpp/keccak.cpp) | OK (metal/cuda/wgsl) | OK (`golang.org/x/crypto/sha3`) | none (single path) | OK (`lux-crypto-keccak`) |
|
||||
| sha256 | OK (cpp/sha256.cpp) | OK (metal) | OK (`crypto/sha256` stdlib) | optional GPU batch only | MISSING |
|
||||
| ripemd160 | OK (cpp/ripemd.cpp) | OK (metal) | OK (`golang.org/x/crypto/ripemd160`) | none | MISSING |
|
||||
| blake2b | OK (cpp/blake2b.cpp) | OK (metal) | OK (real vanilla + AVX2/AVX asm) | none | MISSING |
|
||||
| blake3 | OK (cpp/blake3.cpp) | OK (metal/cuda/wgsl) | MISSING (`lux/crypto/blake3` directory absent) | n/a | OK (`lux-crypto-blake3`) |
|
||||
| poseidon | MISSING | OK (metal) | OK (`gnark-crypto/.../poseidon2`) | none | MISSING |
|
||||
| secp256k1 | OK (cpp/curve.hpp+) | OK (metal/cuda/wgsl) | PARTIAL (`scalar_mult.go` !cgo only; bulk cgo) | primary path is cgo (libsecp256k1 inline) | OK (`lux-crypto-secp256k1`) |
|
||||
| ed25519 | OK (cpp/ed25519.cpp) | OK (metal/cuda/wgsl) | OK (`crypto/ed25519` stdlib) | optional GPU batch only | OK (`lux-crypto-ed25519`) |
|
||||
| bls (BLS12-381 sig API) | OK (cpp/bls_*.cpp) | OK (metal/cuda/wgsl) | OK (`!cgo` -> `cloudflare/circl/sign/bls`; `cgo` -> blst) | blst optional via `cgo` build tag | MISSING |
|
||||
| bls12381 (low-level) | OK (cpp/bls) | OK | OK (`!cgo` gnark-crypto; `cgo` blst) | blst optional | (covered by `lux-crypto-bls` placeholder; no crate yet) |
|
||||
| AEAD chacha20-poly1305 | MISSING | MISSING | OK (`golang.org/x/crypto/chacha20poly1305` + `crypto/aes`) | none | MISSING |
|
||||
| lamport | OK (cpp/lamport.cpp) | OK (metal) | OK (real vanilla, `crypto/sha256`+`sha512`) | none | MISSING |
|
||||
| banderwagon | MISSING | MISSING | MISSING (dir empty) | n/a | MISSING |
|
||||
| pedersen | MISSING | MISSING | OK (real vanilla, `gnark-crypto/bn254`) | none | MISSING |
|
||||
| IPA | MISSING | OK (metal) | OK (vendored go-ipa style impl in `ipa/`) | none | MISSING |
|
||||
| verkle | MISSING | MISSING | VIOLATION (re-export of `ethereum/go-verkle`) | n/a | MISSING |
|
||||
| evm256 (bn254 precompiles) | MISSING | OK (metal/cuda/wgsl) | OK (real vanilla, `gnark-crypto/bn254`) | none | MISSING |
|
||||
| kzg (kzg4844) | OK (cpp/kzg.cpp) | OK (metal) | OK (`!cgo` gokzg; `cgo` ckzg) | ckzg optional | MISSING |
|
||||
| mldsa | OK (cpp/mldsa.cpp + pqclean) | OK (metal/cuda/wgsl) | OK (`circl/sign/mldsa/mldsa{44,65,87}`) | optional ckzg-style C path (build.sh) | MISSING |
|
||||
| mlkem | OK (cpp/mlkem.cpp + pqclean) | OK (metal/cuda/wgsl) | OK (`circl/kem/mlkem/mlkem{512,768,1024}`) | placeholder (`mlkem_c.go` is stub) | MISSING |
|
||||
| slhdsa | OK (cpp/slhdsa.cpp + pqclean) | OK (metal/cuda/wgsl) | OK (`circl/sign/slhdsa`) | optional via build.sh | OK (`lux-crypto-slhdsa`) |
|
||||
| NTT | PARTIAL (header only, `cpp/ntt.hpp`) | OK (metal/cuda/wgsl) | OK (real vanilla Cooley-Tukey) | none | MISSING |
|
||||
| poly_mul | PARTIAL (header only, `cpp/poly_mul.hpp`) | OK (metal/cuda/wgsl) | OK (real vanilla schoolbook negacyclic) | none | MISSING |
|
||||
|
||||
## Violation Count
|
||||
|
||||
Hard violations (vanilla Go canonical missing or itself a re-export/wrapper):
|
||||
|
||||
1. **blake3** — `lux/crypto/blake3/` directory does not exist. C++ + Rust crate exist, but no Go canonical at all.
|
||||
2. **verkle** — `lux/crypto/verkle/verkle.go` is a pure re-export of `github.com/ethereum/go-verkle` (`type X = upstream.X`, `var Fn = upstream.Fn`). Violates "luxfi only" + "no wrapper" rules.
|
||||
3. **banderwagon** — `lux/crypto/banderwagon/` directory empty. No Go canonical, no C++, no Rust.
|
||||
4. **secp256k1** — `lux/crypto/secp256k1/secp256k1_c.go` is the de facto canonical (cgo `#include "./libsecp256k1/src/secp256k1.c"`). Only a partial vanilla Go path (`scalar_mult.go`, `dummy.go`) exists behind `!cgo`, which is incomplete relative to the cgo surface. Does not have a complete vanilla Go fallback for the full API (sign / verify / recover / pubkey).
|
||||
|
||||
Soft violations (luxcpp C++/CPU body missing for an algo that has a Go canonical):
|
||||
|
||||
5. **poseidon/cpp** — only `gpu/metal/poseidon.metal` exists; no `cpp/poseidon.cpp`. Go canonical compliant via gnark-crypto. Audit gap is on the C++ side.
|
||||
6. **aead/cpp** — fully empty in luxcpp. Go vanilla via stdlib + `x/crypto/chacha20poly1305` is fine; if accelerated C++/GPU is desired this needs authoring.
|
||||
7. **pedersen/cpp** — empty in luxcpp. Go vanilla (gnark) is fine.
|
||||
8. **ipa/cpp** — empty in luxcpp (gpu metal exists). Go vanilla is fine (vendored go-ipa).
|
||||
9. **evm256/cpp** — empty in luxcpp (gpu present). Go vanilla (gnark) is fine.
|
||||
10. **ntt/cpp**, **poly_mul/cpp** — only `.hpp` header, no `.cpp` body. Go vanilla is fine.
|
||||
|
||||
Rust gaps (algos with C++/CPU + Go vanilla but no Rust crate yet):
|
||||
|
||||
`sha256`, `ripemd160`, `blake2b`, `lamport`, `mldsa`, `mlkem`, `bls`, `kzg`, `evm256`, `ipa`, `ntt`, `poly_mul`, `pedersen`, `aead`. These are not violations of the "vanilla Go must be real" rule; they are coverage gaps for the Rust binder lane.
|
||||
|
||||
## Total
|
||||
|
||||
- Hard violations: **4** (blake3 missing, verkle wrapper, banderwagon empty, secp256k1 cgo-primary)
|
||||
- Soft violations (C++/CPU body missing): **6**
|
||||
- Rust binder gaps: **14**
|
||||
|
||||
## Remediation Priority
|
||||
|
||||
P0 — Hard violations (block "one way" claim):
|
||||
|
||||
1. **verkle** — author real luxfi vanilla Go canonical. Source to port: `github.com/ethereum/go-verkle` MIT, port the trie + proof logic into `lux/crypto/verkle/verkle.go` under luxfi copyright. Drop the `upstream` re-export.
|
||||
2. **secp256k1** — author full vanilla Go canonical (key gen, sign, verify, recover) at `lux/crypto/secp256k1/secp256k1.go` behind `!cgo`. Reference: `decred/dcrd/dcrec/secp256k1/v4` (ISC, pure Go) or `btcsuite/btcd/btcec/v2`. Keep `secp256k1_c.go` as opt-in cgo accelerator.
|
||||
3. **blake3** — create `lux/crypto/blake3/blake3.go` using `github.com/zeebo/blake3` (BSD-3, pure Go, AVX2/NEON). Mirror the keccak/sha256 batch-with-GPU-fallback pattern.
|
||||
4. **banderwagon** — create `lux/crypto/banderwagon/banderwagon.go`. Source: `github.com/crate-crypto/go-ipa/banderwagon` (Apache-2/MIT) — port directly, luxfi copyright.
|
||||
|
||||
P1 — luxcpp C++/CPU bodies for algos that already have Go canonical (so the canonical pattern is symmetric across languages):
|
||||
|
||||
5. `luxcpp/crypto/poseidon/cpp/poseidon.{cpp,hpp}` — poseidon2 over BN254 Fr. Reference: gnark-crypto Go impl, transliterate.
|
||||
6. `luxcpp/crypto/ntt/cpp/ntt.cpp` — body to match existing `ntt.hpp`.
|
||||
7. `luxcpp/crypto/poly_mul/cpp/poly_mul.cpp` — body to match existing `poly_mul.hpp`.
|
||||
8. `luxcpp/crypto/evm256/cpp/evm256.{cpp,hpp}` — bn254 precompiles (Add/Mul/Pairing) in C++. Reference: blst or arkworks-rs.
|
||||
9. `luxcpp/crypto/ipa/cpp/ipa.{cpp,hpp}` — Bulletproofs-style IPA over Banderwagon. Reference: crate-crypto/go-ipa.
|
||||
10. `luxcpp/crypto/pedersen/cpp/pedersen.{cpp,hpp}` — Pedersen vector commitments over BN254 G1.
|
||||
11. `luxcpp/crypto/aead/cpp/aead.{cpp,hpp}` — ChaCha20-Poly1305 + AES-256-GCM. Reference: BoringSSL, libsodium.
|
||||
|
||||
P2 — Rust binder coverage (one crate per algo with luxcpp `lib<alg>_cpu.a`):
|
||||
|
||||
12. Create `lux/crypto/rust/lux-crypto-{sha256,ripemd160,blake2b,lamport,bls,kzg,mldsa,mlkem,evm256,ipa,ntt,poly_mul,pedersen,aead}/` with `build.rs` matching the keccak/secp256k1/ed25519/blake3/slhdsa pattern.
|
||||
|
||||
## Files Requiring Vanilla Go Authoring (Hard Violations)
|
||||
|
||||
| File to author | Source to port from |
|
||||
|---------------------------------------------------------|-----------------------------------------------------------------------------|
|
||||
| `/Users/z/work/lux/crypto/verkle/verkle.go` | `github.com/ethereum/go-verkle` (MIT) — full reimpl, luxfi copyright |
|
||||
| `/Users/z/work/lux/crypto/secp256k1/secp256k1.go` | `github.com/decred/dcrd/dcrec/secp256k1/v4` (ISC, pure Go) |
|
||||
| `/Users/z/work/lux/crypto/blake3/blake3.go` | wrap `github.com/zeebo/blake3` (BSD-3) — counts as vanilla per directive |
|
||||
| `/Users/z/work/lux/crypto/banderwagon/banderwagon.go` | `github.com/crate-crypto/go-ipa/banderwagon` (Apache-2/MIT) |
|
||||
|
||||
## Notes
|
||||
|
||||
- `bls` and `bls12381` already follow the correct dual-build pattern: `!cgo` -> circl/gnark-crypto vanilla, `cgo` -> blst. This is the reference pattern other algos should follow.
|
||||
- `mldsa`, `mlkem`, `slhdsa` are circl-backed — circl is a real Go crypto crate, counts as vanilla per directive.
|
||||
- `poseidon` has a C++ GPU kernel but no C++/CPU body. Go canonical works via gnark-crypto. Symmetry gap is luxcpp side only.
|
||||
- `kzg4844` package follows correct pattern (`!cgo` gokzg vanilla, `cgo` ckzg accelerator). Compliant.
|
||||
@@ -10,10 +10,11 @@ require (
|
||||
github.com/cloudflare/circl v1.6.3
|
||||
github.com/consensys/gnark-crypto v0.19.2
|
||||
github.com/crate-crypto/go-eth-kzg v1.5.0
|
||||
github.com/crate-crypto/go-ipa v0.0.0-20240223125850-b1e8a79f509c
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0
|
||||
github.com/ethereum/c-kzg-4844/v2 v2.1.5
|
||||
github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab
|
||||
github.com/ethereum/go-verkle v0.2.2
|
||||
github.com/google/btree v1.1.3
|
||||
github.com/google/gofuzz v1.2.0
|
||||
github.com/jedisct1/go-minisign v0.0.0-20241212093149-d2f9f49435c7
|
||||
@@ -37,8 +38,6 @@ require (
|
||||
require (
|
||||
filippo.io/hpke v0.4.0 // indirect
|
||||
github.com/bits-and-blooms/bitset v1.24.4 // indirect
|
||||
github.com/crate-crypto/go-ipa v0.0.0-20240223125850-b1e8a79f509c // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/luxfi/utils v1.1.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
|
||||
@@ -119,8 +119,6 @@ github.com/ethereum/c-kzg-4844/v2 v2.1.5 h1:aVtoLK5xwJ6c5RiqO8g8ptJ5KU+2Hdquf6G3
|
||||
github.com/ethereum/c-kzg-4844/v2 v2.1.5/go.mod h1:u59hRTTah4Co6i9fDWtiCjTrblJv0UwsqZKCc0GfgUs=
|
||||
github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab h1:rvv6MJhy07IMfEKuARQ9TKojGqLVNxQajaXEp/BoqSk=
|
||||
github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab/go.mod h1:IuLm4IsPipXKF7CW5Lzf68PIbZ5yl7FFd74l/E0o9A8=
|
||||
github.com/ethereum/go-verkle v0.2.2 h1:I2W0WjnrFUIzzVPwm8ykY+7pL2d4VhlsePn4j7cnFk8=
|
||||
github.com/ethereum/go-verkle v0.2.2/go.mod h1:M3b90YRnzqKyyzBEWJGqj8Qff4IDeXnzFw0P9bFw3uk=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
|
||||
Generated
+12
@@ -6,6 +6,14 @@ version = 3
|
||||
name = "lux-crypto"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "lux-crypto-blake3"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "lux-crypto-ed25519"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "lux-crypto-keccak"
|
||||
version = "0.1.0"
|
||||
@@ -13,3 +21,7 @@ version = "0.1.0"
|
||||
[[package]]
|
||||
name = "lux-crypto-secp256k1"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "lux-crypto-slhdsa"
|
||||
version = "0.1.0"
|
||||
|
||||
@@ -17,6 +17,9 @@ members = [
|
||||
"lux-crypto",
|
||||
"lux-crypto-keccak",
|
||||
"lux-crypto-secp256k1",
|
||||
"lux-crypto-ed25519",
|
||||
"lux-crypto-blake3",
|
||||
"lux-crypto-slhdsa",
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "lux-crypto-blake3"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "Canonical Rust binding for Lux BLAKE3. Calls into luxcpp/crypto/blake3 (vendored BLAKE3 reference v1.5.0) via the C-ABI."
|
||||
repository = "https://github.com/luxfi/crypto"
|
||||
readme = "README.md"
|
||||
keywords = ["blake3", "hash", "xof", "ffi"]
|
||||
categories = ["cryptography"]
|
||||
|
||||
[lib]
|
||||
name = "lux_crypto_blake3"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -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-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("blake3");
|
||||
println!("cargo:rustc-link-search=native={}", lib_path.display());
|
||||
println!("cargo:rustc-link-lib=static=blake3");
|
||||
println!("cargo:rustc-link-lib=static=blake3_cpu");
|
||||
|
||||
if cfg!(target_os = "macos") {
|
||||
println!("cargo:rustc-link-lib=c++");
|
||||
} else {
|
||||
println!("cargo:rustc-link-lib=stdc++");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
// Copyright (c) 2024-2026 Lux Industries Inc.
|
||||
// SPDX-License-Identifier: BSD-3-Clause-Eco
|
||||
//
|
||||
// lux-crypto-blake3: canonical Rust binding for the Lux BLAKE3 C-ABI.
|
||||
//
|
||||
// Links statically against `libblake3.a` and `libblake3_cpu.a` produced by
|
||||
// `luxcpp/crypto/blake3`, whose CPU body is the vendored BLAKE3 reference C
|
||||
// (BLAKE3-team/BLAKE3 v1.5.0, portable-only). All four canonical BLAKE3
|
||||
// modes are exposed:
|
||||
//
|
||||
// * `hash` -- default-length (32-byte) hash, no key
|
||||
// * `keyed_hash` -- 32-byte hash with 32-byte key (RFC-style MAC)
|
||||
// * `derive_key` -- context-string-bound 32-byte derivation
|
||||
// * `hash_xof` -- extensible-length output (XOF)
|
||||
//
|
||||
// Byte-equal to upstream b3sum / blake3-py.
|
||||
|
||||
#![no_std]
|
||||
#![forbid(unsafe_op_in_unsafe_fn)]
|
||||
|
||||
use core::ffi::{c_char, c_int};
|
||||
|
||||
extern "C" {
|
||||
fn blake3(input: *const u8, input_len: usize, output: *mut u8) -> c_int;
|
||||
fn lux_blake3_keyed(
|
||||
key: *const u8,
|
||||
input: *const u8,
|
||||
input_len: usize,
|
||||
output: *mut u8,
|
||||
) -> c_int;
|
||||
fn lux_blake3_derive_key(
|
||||
context: *const c_char,
|
||||
key_material: *const u8,
|
||||
key_material_len: usize,
|
||||
output: *mut u8,
|
||||
) -> c_int;
|
||||
fn lux_blake3_xof(
|
||||
input: *const u8,
|
||||
input_len: usize,
|
||||
output: *mut u8,
|
||||
output_len: usize,
|
||||
) -> c_int;
|
||||
}
|
||||
|
||||
/// Length of a default BLAKE3 digest in bytes.
|
||||
pub const OUT_LEN: usize = 32;
|
||||
/// Length of a BLAKE3 key in bytes (for `keyed_hash`).
|
||||
pub const KEY_LEN: usize = 32;
|
||||
|
||||
/// Compute the default 32-byte BLAKE3 digest of `input`.
|
||||
#[inline]
|
||||
pub fn hash(input: &[u8]) -> [u8; OUT_LEN] {
|
||||
let mut out = [0u8; OUT_LEN];
|
||||
// SAFETY: pointers valid for the call's duration; output sized to digest.
|
||||
let rc = unsafe { blake3(input.as_ptr(), input.len(), out.as_mut_ptr()) };
|
||||
debug_assert_eq!(rc, 0, "blake3 returned non-zero status: {}", rc);
|
||||
out
|
||||
}
|
||||
|
||||
/// Compute the keyed BLAKE3 digest (MAC) of `input` under `key`.
|
||||
#[inline]
|
||||
pub fn keyed_hash(key: &[u8; KEY_LEN], input: &[u8]) -> [u8; OUT_LEN] {
|
||||
let mut out = [0u8; OUT_LEN];
|
||||
// SAFETY: pointers valid for the call's duration; key/output sized exactly.
|
||||
let rc = unsafe {
|
||||
lux_blake3_keyed(
|
||||
key.as_ptr(),
|
||||
input.as_ptr(),
|
||||
input.len(),
|
||||
out.as_mut_ptr(),
|
||||
)
|
||||
};
|
||||
debug_assert_eq!(rc, 0, "blake3 keyed_hash returned non-zero status: {}", rc);
|
||||
out
|
||||
}
|
||||
|
||||
/// Derive a 32-byte sub-key from `key_material` bound to a NUL-terminated
|
||||
/// ASCII `context` string.
|
||||
///
|
||||
/// `context_z` MUST contain a trailing NUL byte (`b"...\0"`). This binds the
|
||||
/// derivation to a hardcoded application-context literal per the BLAKE3 spec.
|
||||
#[inline]
|
||||
pub fn derive_key(context_z: &[u8], key_material: &[u8]) -> [u8; OUT_LEN] {
|
||||
debug_assert!(
|
||||
context_z.last() == Some(&0),
|
||||
"derive_key context must be NUL-terminated"
|
||||
);
|
||||
let mut out = [0u8; OUT_LEN];
|
||||
// SAFETY: pointers valid for the call's duration; context is NUL-terminated.
|
||||
let rc = unsafe {
|
||||
lux_blake3_derive_key(
|
||||
context_z.as_ptr() as *const c_char,
|
||||
key_material.as_ptr(),
|
||||
key_material.len(),
|
||||
out.as_mut_ptr(),
|
||||
)
|
||||
};
|
||||
debug_assert_eq!(
|
||||
rc, 0,
|
||||
"blake3 derive_key returned non-zero status: {}",
|
||||
rc
|
||||
);
|
||||
out
|
||||
}
|
||||
|
||||
/// Extensible-output BLAKE3. `output` may be any length; the result is the
|
||||
/// prefix of the BLAKE3 root output keystream of that length.
|
||||
#[inline]
|
||||
pub fn hash_xof(input: &[u8], output: &mut [u8]) {
|
||||
// SAFETY: pointers valid for the call's duration.
|
||||
let rc = unsafe {
|
||||
lux_blake3_xof(
|
||||
input.as_ptr(),
|
||||
input.len(),
|
||||
output.as_mut_ptr(),
|
||||
output.len(),
|
||||
)
|
||||
};
|
||||
debug_assert_eq!(rc, 0, "blake3 hash_xof returned non-zero status: {}", rc);
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
// Copyright (c) 2024-2026 Lux Industries Inc.
|
||||
// SPDX-License-Identifier: BSD-3-Clause-Eco
|
||||
//
|
||||
// Spec-vector integration test for lux-crypto-blake3.
|
||||
//
|
||||
// Walks the upstream `test_vectors.json` (BLAKE3-team/BLAKE3 v1.5.0,
|
||||
// vendored under luxcpp/crypto/blake3/test/vectors/test_vectors.json) and
|
||||
// asserts byte-equality across all 35 cases x 4 modes = 140 assertions:
|
||||
//
|
||||
// hash -- first 32 bytes of "hash" XOF stream
|
||||
// keyed_hash -- first 32 bytes of "keyed_hash" XOF stream
|
||||
// derive_key -- first 32 bytes of "derive_key" XOF stream
|
||||
// hash_xof -- full extended length of "hash" stream
|
||||
//
|
||||
// The synthetic input per upstream README is a 251-byte ramp (0..250)
|
||||
// repeated to reach `input_len`.
|
||||
|
||||
use lux_crypto_blake3::{derive_key, hash, hash_xof, keyed_hash, KEY_LEN, OUT_LEN};
|
||||
|
||||
const VECTORS: &str =
|
||||
include_str!("../../../../../luxcpp/crypto/blake3/test/vectors/test_vectors.json");
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
fn from_hex32(s: &str) -> [u8; OUT_LEN] {
|
||||
let v = from_hex_var(s);
|
||||
let mut a = [0u8; OUT_LEN];
|
||||
a.copy_from_slice(&v[..OUT_LEN]);
|
||||
a
|
||||
}
|
||||
|
||||
fn make_input(n: usize) -> Vec<u8> {
|
||||
(0..n).map(|i| (i % 251) as u8).collect()
|
||||
}
|
||||
|
||||
// Tiny purpose-built JSON reader: walks `"<name>": <value>` pairs in the
|
||||
// known schema. NOT a general parser.
|
||||
struct Reader<'a> {
|
||||
s: &'a [u8],
|
||||
pos: usize,
|
||||
}
|
||||
|
||||
impl<'a> Reader<'a> {
|
||||
fn find_field(&mut self, name: &str) -> Option<usize> {
|
||||
let needle = format!("\"{}\"", name);
|
||||
let nb = needle.as_bytes();
|
||||
let hay = &self.s[self.pos..];
|
||||
let mut i = 0;
|
||||
while i + nb.len() <= hay.len() {
|
||||
if &hay[i..i + nb.len()] == nb {
|
||||
let p = self.pos + i + nb.len();
|
||||
let mut q = p;
|
||||
while q < self.s.len() && (self.s[q] == b' ' || self.s[q] == b':') {
|
||||
q += 1;
|
||||
}
|
||||
return Some(q);
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
None
|
||||
}
|
||||
fn read_string_at(&mut self, p: usize) -> Option<String> {
|
||||
if self.s.get(p) != Some(&b'"') {
|
||||
return None;
|
||||
}
|
||||
let start = p + 1;
|
||||
let mut q = start;
|
||||
while q < self.s.len() && self.s[q] != b'"' {
|
||||
q += 1;
|
||||
}
|
||||
if q >= self.s.len() {
|
||||
return None;
|
||||
}
|
||||
self.pos = q + 1;
|
||||
Some(String::from_utf8(self.s[start..q].to_vec()).ok()?)
|
||||
}
|
||||
fn read_uint_at(&mut self, mut p: usize) -> Option<u64> {
|
||||
while p < self.s.len() && (self.s[p] == b' ' || self.s[p] == b':') {
|
||||
p += 1;
|
||||
}
|
||||
let mut v: u64 = 0;
|
||||
let mut any = false;
|
||||
while p < self.s.len() && self.s[p].is_ascii_digit() {
|
||||
v = v * 10 + (self.s[p] - b'0') as u64;
|
||||
p += 1;
|
||||
any = true;
|
||||
}
|
||||
self.pos = p;
|
||||
if any {
|
||||
Some(v)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn upstream_test_vectors_byte_equal() {
|
||||
let mut r = Reader {
|
||||
s: VECTORS.as_bytes(),
|
||||
pos: 0,
|
||||
};
|
||||
|
||||
let p_key = r.find_field("key").expect("key field missing");
|
||||
let key_str = r.read_string_at(p_key).expect("key string malformed");
|
||||
let p_ctx = r
|
||||
.find_field("context_string")
|
||||
.expect("context_string missing");
|
||||
let ctx_str = r
|
||||
.read_string_at(p_ctx)
|
||||
.expect("context_string malformed");
|
||||
|
||||
assert_eq!(
|
||||
key_str.len(),
|
||||
KEY_LEN,
|
||||
"key has unexpected length {}",
|
||||
key_str.len()
|
||||
);
|
||||
let mut key = [0u8; KEY_LEN];
|
||||
key.copy_from_slice(key_str.as_bytes());
|
||||
|
||||
// NUL-terminate context for the C ABI.
|
||||
let mut ctx_z = ctx_str.into_bytes();
|
||||
ctx_z.push(0);
|
||||
|
||||
let mut n_cases = 0;
|
||||
let mut passed = 0;
|
||||
let mut total = 0;
|
||||
|
||||
loop {
|
||||
let p_in = match r.find_field("input_len") {
|
||||
Some(p) => p,
|
||||
None => break,
|
||||
};
|
||||
let in_len = r.read_uint_at(p_in).expect("malformed input_len") as usize;
|
||||
let p_h = r.find_field("hash").expect("missing hash");
|
||||
let hash_hex = r.read_string_at(p_h).expect("hash malformed");
|
||||
let p_k = r.find_field("keyed_hash").expect("missing keyed_hash");
|
||||
let keyed_hex = r.read_string_at(p_k).expect("keyed_hash malformed");
|
||||
let p_d = r.find_field("derive_key").expect("missing derive_key");
|
||||
let dk_hex = r.read_string_at(p_d).expect("derive_key malformed");
|
||||
|
||||
n_cases += 1;
|
||||
let input = make_input(in_len);
|
||||
|
||||
// Mode 1: default-length hash (first 32 bytes of "hash").
|
||||
total += 1;
|
||||
let want = from_hex32(&hash_hex);
|
||||
let got = hash(&input);
|
||||
assert_eq!(got, want, "hash32 mismatch at in_len={}", in_len);
|
||||
passed += 1;
|
||||
|
||||
// Mode 2: keyed_hash, default-length (first 32 bytes).
|
||||
total += 1;
|
||||
let want = from_hex32(&keyed_hex);
|
||||
let got = keyed_hash(&key, &input);
|
||||
assert_eq!(got, want, "keyed_hash mismatch at in_len={}", in_len);
|
||||
passed += 1;
|
||||
|
||||
// Mode 3: derive_key, default-length (first 32 bytes).
|
||||
total += 1;
|
||||
let want = from_hex32(&dk_hex);
|
||||
let got = derive_key(&ctx_z, &input);
|
||||
assert_eq!(got, want, "derive_key mismatch at in_len={}", in_len);
|
||||
passed += 1;
|
||||
|
||||
// Mode 4: XOF, full extended length per upstream `hash` field.
|
||||
total += 1;
|
||||
let want = from_hex_var(&hash_hex);
|
||||
let mut got = vec![0u8; want.len()];
|
||||
hash_xof(&input, &mut got);
|
||||
assert_eq!(got, want, "hash_xof mismatch at in_len={}", in_len);
|
||||
passed += 1;
|
||||
}
|
||||
|
||||
assert_eq!(n_cases, 35, "expected 35 cases, found {}", n_cases);
|
||||
assert_eq!(total, 140);
|
||||
assert_eq!(passed, 140);
|
||||
println!("blake3 spec vectors: {}/{} PASS", passed, total);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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++");
|
||||
}
|
||||
}
|
||||
@@ -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})"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "lux-crypto-slhdsa"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "Canonical Rust binding for Lux SLH-DSA (FIPS 205). Calls into luxcpp/crypto/slhdsa (vendored PQClean reference, CC0/public domain) via the C-ABI."
|
||||
repository = "https://github.com/luxfi/crypto"
|
||||
readme = "README.md"
|
||||
keywords = ["slh-dsa", "sphincs", "fips205", "post-quantum", "ffi"]
|
||||
categories = ["cryptography"]
|
||||
|
||||
[lib]
|
||||
name = "lux_crypto_slhdsa"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -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-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("slhdsa");
|
||||
println!("cargo:rustc-link-search=native={}", lib_path.display());
|
||||
println!("cargo:rustc-link-lib=static=slhdsa");
|
||||
println!("cargo:rustc-link-lib=static=slhdsa_cpu");
|
||||
|
||||
if cfg!(target_os = "macos") {
|
||||
println!("cargo:rustc-link-lib=c++");
|
||||
} else {
|
||||
println!("cargo:rustc-link-lib=stdc++");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
// Copyright (c) 2024-2026 Lux Industries Inc.
|
||||
// SPDX-License-Identifier: BSD-3-Clause-Eco
|
||||
//
|
||||
// lux-crypto-slhdsa: canonical Rust binding for the Lux SLH-DSA C-ABI.
|
||||
//
|
||||
// Links statically against `libslhdsa.a` and `libslhdsa_cpu.a` produced by
|
||||
// `luxcpp/crypto/slhdsa`, whose CPU body wraps the vendored PQClean reference
|
||||
// (sphincs-{sha2,shake}-{128,192,256}f-simple/clean, CC0 / public domain).
|
||||
// Conforms to NIST FIPS 205 §10 parameter catalogue (six 'f' fast variants).
|
||||
//
|
||||
// Mode encoding (byte-equal to luxcpp/crypto/slhdsa/c-abi/c_slhdsa.cpp):
|
||||
// 2 -> SLH-DSA-SHA2-128f (NIST L1)
|
||||
// 3 -> SLH-DSA-SHA2-192f (NIST L3)
|
||||
// 5 -> SLH-DSA-SHA2-256f (NIST L5)
|
||||
// 12 -> SLH-DSA-SHAKE-128f (NIST L1)
|
||||
// 13 -> SLH-DSA-SHAKE-192f (NIST L3)
|
||||
// 15 -> SLH-DSA-SHAKE-256f (NIST L5)
|
||||
//
|
||||
// Buffer sizes are FIPS 205 fixed (identical between SHA2 and SHAKE for the
|
||||
// same security level):
|
||||
// 128f : pk=32 sk=64 sig<=17088
|
||||
// 192f : pk=48 sk=96 sig<=35664
|
||||
// 256f : pk=64 sk=128 sig<=49856
|
||||
|
||||
#![no_std]
|
||||
#![forbid(unsafe_op_in_unsafe_fn)]
|
||||
|
||||
use core::ffi::c_int;
|
||||
|
||||
extern "C" {
|
||||
fn slhdsa_keygen(mode: c_int, seed: *const u8, pk: *mut u8, sk: *mut u8) -> c_int;
|
||||
fn slhdsa_sign(
|
||||
mode: c_int,
|
||||
sk: *const u8,
|
||||
msg: *const u8,
|
||||
msg_len: usize,
|
||||
sig: *mut u8,
|
||||
sig_len: *mut usize,
|
||||
) -> c_int;
|
||||
fn slhdsa_verify(
|
||||
mode: c_int,
|
||||
pk: *const u8,
|
||||
msg: *const u8,
|
||||
msg_len: usize,
|
||||
sig: *const u8,
|
||||
sig_len: usize,
|
||||
) -> c_int;
|
||||
}
|
||||
|
||||
/// FIPS 205 SLH-DSA parameter set. The integer mode codes are byte-equal to
|
||||
/// the C-ABI dispatch in `luxcpp/crypto/slhdsa/c-abi/c_slhdsa.cpp`.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(i32)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum Mode {
|
||||
/// SLH-DSA-SHA2-128f (NIST L1, SHA2 family, fast variant).
|
||||
Sha2_128f = 2,
|
||||
/// SLH-DSA-SHA2-192f (NIST L3, SHA2 family, fast variant).
|
||||
Sha2_192f = 3,
|
||||
/// SLH-DSA-SHA2-256f (NIST L5, SHA2 family, fast variant).
|
||||
Sha2_256f = 5,
|
||||
/// SLH-DSA-SHAKE-128f (NIST L1, SHAKE family, fast variant).
|
||||
Shake_128f = 12,
|
||||
/// SLH-DSA-SHAKE-192f (NIST L3, SHAKE family, fast variant).
|
||||
Shake_192f = 13,
|
||||
/// SLH-DSA-SHAKE-256f (NIST L5, SHAKE family, fast variant).
|
||||
Shake_256f = 15,
|
||||
}
|
||||
|
||||
impl Mode {
|
||||
/// Public key length for this parameter set (FIPS 205).
|
||||
#[inline]
|
||||
pub const fn pk_len(self) -> usize {
|
||||
match self {
|
||||
Mode::Sha2_128f | Mode::Shake_128f => 32,
|
||||
Mode::Sha2_192f | Mode::Shake_192f => 48,
|
||||
Mode::Sha2_256f | Mode::Shake_256f => 64,
|
||||
}
|
||||
}
|
||||
|
||||
/// Secret key length for this parameter set (FIPS 205).
|
||||
#[inline]
|
||||
pub const fn sk_len(self) -> usize {
|
||||
match self {
|
||||
Mode::Sha2_128f | Mode::Shake_128f => 64,
|
||||
Mode::Sha2_192f | Mode::Shake_192f => 96,
|
||||
Mode::Sha2_256f | Mode::Shake_256f => 128,
|
||||
}
|
||||
}
|
||||
|
||||
/// Maximum signature length for this parameter set (FIPS 205).
|
||||
#[inline]
|
||||
pub const fn sig_len(self) -> usize {
|
||||
match self {
|
||||
Mode::Sha2_128f | Mode::Shake_128f => 17088,
|
||||
Mode::Sha2_192f | Mode::Shake_192f => 35664,
|
||||
Mode::Sha2_256f | Mode::Shake_256f => 49856,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Errors returned by the SLH-DSA 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,
|
||||
/// Buffer slice was the wrong size for the requested parameter set.
|
||||
InvalidLength,
|
||||
}
|
||||
|
||||
/// Generate a fresh keypair for the given parameter set. Entropy comes from
|
||||
/// the underlying C wrapper's getentropy()-backed randombytes (FIPS 205 keygen
|
||||
/// is randomized; SLH-DSA itself is *signing*-deterministic per §9.1).
|
||||
///
|
||||
/// `pk` and `sk` must be sized exactly to `mode.pk_len()` and `mode.sk_len()`.
|
||||
#[inline]
|
||||
pub fn keygen(mode: Mode, pk: &mut [u8], sk: &mut [u8]) -> Result<(), Error> {
|
||||
if pk.len() != mode.pk_len() || sk.len() != mode.sk_len() {
|
||||
return Err(Error::InvalidLength);
|
||||
}
|
||||
// The C-ABI accepts a 32-byte seed pointer that is currently unused by the
|
||||
// PQClean reference (which calls randombytes internally). Pass a dummy.
|
||||
let dummy_seed = [0u8; 32];
|
||||
// SAFETY: pointers valid for the call's duration; lengths checked above.
|
||||
let rc = unsafe {
|
||||
slhdsa_keygen(mode as c_int, dummy_seed.as_ptr(), pk.as_mut_ptr(), sk.as_mut_ptr())
|
||||
};
|
||||
match rc {
|
||||
0 => Ok(()),
|
||||
other => Err(Error::InternalError(other)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Sign `msg` under `sk` using the given parameter set.
|
||||
///
|
||||
/// Returns the byte length of the produced signature (= `mode.sig_len()` for
|
||||
/// the deployed 'f' fast variants, where signatures are fixed-size).
|
||||
///
|
||||
/// `sig` must have capacity at least `mode.sig_len()`. `sk` must be sized
|
||||
/// exactly to `mode.sk_len()`.
|
||||
#[inline]
|
||||
pub fn sign(mode: Mode, sk: &[u8], msg: &[u8], sig: &mut [u8]) -> Result<usize, Error> {
|
||||
if sk.len() != mode.sk_len() || sig.len() < mode.sig_len() {
|
||||
return Err(Error::InvalidLength);
|
||||
}
|
||||
let mut sig_len: usize = sig.len();
|
||||
// SAFETY: pointers valid for the call's duration; lengths checked above.
|
||||
let rc = unsafe {
|
||||
slhdsa_sign(
|
||||
mode as c_int,
|
||||
sk.as_ptr(),
|
||||
msg.as_ptr(),
|
||||
msg.len(),
|
||||
sig.as_mut_ptr(),
|
||||
&mut sig_len as *mut usize,
|
||||
)
|
||||
};
|
||||
match rc {
|
||||
0 => Ok(sig_len),
|
||||
other => Err(Error::InternalError(other)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Verify a single SLH-DSA 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(mode: Mode, pk: &[u8], msg: &[u8], sig: &[u8]) -> Result<(), Error> {
|
||||
if pk.len() != mode.pk_len() {
|
||||
return Err(Error::InvalidLength);
|
||||
}
|
||||
// SAFETY: pointers valid for the call's duration; pk length checked above.
|
||||
let rc = unsafe {
|
||||
slhdsa_verify(
|
||||
mode as c_int,
|
||||
pk.as_ptr(),
|
||||
msg.as_ptr(),
|
||||
msg.len(),
|
||||
sig.as_ptr(),
|
||||
sig.len(),
|
||||
)
|
||||
};
|
||||
match rc {
|
||||
0 => Ok(()),
|
||||
// CRYPTO_ERR_VERIFY = -3 in lux_crypto.h
|
||||
-3 => Err(Error::InvalidSignature),
|
||||
other => Err(Error::InternalError(other)),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
// Copyright (c) 2024-2026 Lux Industries Inc.
|
||||
// SPDX-License-Identifier: BSD-3-Clause-Eco
|
||||
//
|
||||
// Round-trip integration tests for SLH-DSA across all six FIPS 205 'f' (fast)
|
||||
// parameter sets. Byte-equal NIST KAT vector tests live in the C++ side
|
||||
// (build-cto/slhdsa_kat_test, 498 PASS lines). These tests verify the Rust
|
||||
// binding's invariants against the underlying byte-equal CPU body:
|
||||
//
|
||||
// 1. keygen(mode, pk, sk) -> CRYPTO_OK
|
||||
// 2. sign(mode, sk, msg, sig) -> sig_len == mode.sig_len()
|
||||
// 3. verify(mode, pk, msg, sig) -> Ok(())
|
||||
// 4. verify with mutated sig -> Err(InvalidSignature)
|
||||
// 5. verify with mutated pk -> Err(InvalidSignature)
|
||||
// 6. verify with mutated msg -> Err(InvalidSignature)
|
||||
// 7. wrong-length pk -> Err(InvalidLength)
|
||||
//
|
||||
// 256f sign() can take >30s on M1 -- gated behind --include-ignored.
|
||||
|
||||
use lux_crypto_slhdsa::{keygen, sign, verify, Error, Mode};
|
||||
|
||||
fn roundtrip(mode: Mode, msg: &[u8]) {
|
||||
let mut pk = vec![0u8; mode.pk_len()];
|
||||
let mut sk = vec![0u8; mode.sk_len()];
|
||||
keygen(mode, &mut pk, &mut sk).expect("keygen");
|
||||
|
||||
let mut sig = vec![0u8; mode.sig_len()];
|
||||
let n = sign(mode, &sk, msg, &mut sig).expect("sign");
|
||||
assert_eq!(n, mode.sig_len(), "{:?}: sig_len mismatch", mode);
|
||||
|
||||
verify(mode, &pk, msg, &sig[..n]).expect("verify(honest)");
|
||||
|
||||
// Tamper signature.
|
||||
let mut bad_sig = sig.clone();
|
||||
bad_sig[0] ^= 0x01;
|
||||
assert_eq!(
|
||||
verify(mode, &pk, msg, &bad_sig[..n]),
|
||||
Err(Error::InvalidSignature),
|
||||
"{:?}: tampered sig should reject",
|
||||
mode
|
||||
);
|
||||
|
||||
// Tamper public key.
|
||||
let mut bad_pk = pk.clone();
|
||||
bad_pk[0] ^= 0x01;
|
||||
assert_eq!(
|
||||
verify(mode, &bad_pk, msg, &sig[..n]),
|
||||
Err(Error::InvalidSignature),
|
||||
"{:?}: tampered pk should reject",
|
||||
mode
|
||||
);
|
||||
|
||||
// Tamper message (only when non-empty).
|
||||
if !msg.is_empty() {
|
||||
let mut bad_msg = msg.to_vec();
|
||||
bad_msg[0] ^= 0x01;
|
||||
assert_eq!(
|
||||
verify(mode, &pk, &bad_msg, &sig[..n]),
|
||||
Err(Error::InvalidSignature),
|
||||
"{:?}: tampered msg should reject",
|
||||
mode
|
||||
);
|
||||
}
|
||||
|
||||
// Wrong-length public key.
|
||||
let short_pk = &pk[..pk.len() - 1];
|
||||
assert_eq!(
|
||||
verify(mode, short_pk, msg, &sig[..n]),
|
||||
Err(Error::InvalidLength),
|
||||
"{:?}: short pk should be rejected as InvalidLength",
|
||||
mode
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sha2_128f_roundtrip() {
|
||||
roundtrip(Mode::Sha2_128f, b"lux SLH-DSA-SHA2-128f roundtrip");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shake_128f_roundtrip() {
|
||||
roundtrip(Mode::Shake_128f, b"lux SLH-DSA-SHAKE-128f roundtrip");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sha2_192f_roundtrip() {
|
||||
roundtrip(Mode::Sha2_192f, b"lux SLH-DSA-SHA2-192f roundtrip");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shake_192f_roundtrip() {
|
||||
roundtrip(Mode::Shake_192f, b"lux SLH-DSA-SHAKE-192f roundtrip");
|
||||
}
|
||||
|
||||
// 256f sign() takes ~30-60s on M1; ignored by default.
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn sha2_256f_roundtrip() {
|
||||
roundtrip(Mode::Sha2_256f, b"lux SLH-DSA-SHA2-256f roundtrip");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn shake_256f_roundtrip() {
|
||||
roundtrip(Mode::Shake_256f, b"lux SLH-DSA-SHAKE-256f roundtrip");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_message_sha2_128f() {
|
||||
roundtrip(Mode::Sha2_128f, b"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mode_sizes_match_fips205() {
|
||||
// NIST FIPS 205 §10 parameter catalogue.
|
||||
assert_eq!(Mode::Sha2_128f.pk_len(), 32);
|
||||
assert_eq!(Mode::Sha2_128f.sk_len(), 64);
|
||||
assert_eq!(Mode::Sha2_128f.sig_len(), 17088);
|
||||
|
||||
assert_eq!(Mode::Sha2_192f.pk_len(), 48);
|
||||
assert_eq!(Mode::Sha2_192f.sk_len(), 96);
|
||||
assert_eq!(Mode::Sha2_192f.sig_len(), 35664);
|
||||
|
||||
assert_eq!(Mode::Sha2_256f.pk_len(), 64);
|
||||
assert_eq!(Mode::Sha2_256f.sk_len(), 128);
|
||||
assert_eq!(Mode::Sha2_256f.sig_len(), 49856);
|
||||
|
||||
// SHAKE shares sizes with SHA2 at each security level.
|
||||
assert_eq!(Mode::Shake_128f.pk_len(), Mode::Sha2_128f.pk_len());
|
||||
assert_eq!(Mode::Shake_192f.pk_len(), Mode::Sha2_192f.pk_len());
|
||||
assert_eq!(Mode::Shake_256f.pk_len(), Mode::Sha2_256f.pk_len());
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
// Copyright (C) 2026, Lux Industries Inc. All rights reserved.
|
||||
//
|
||||
// Vendored from github.com/ethereum/go-verkle@v0.2.2 (public domain / Unlicense).
|
||||
// Re-licensed under the Lux Ecosystem License. The package implements the
|
||||
// Verkle trie, its node types (InternalNode, LeafNode, Empty, HashedNode),
|
||||
// IPA configuration, multi-proof generation/verification, encoding, and
|
||||
// JSON serialization. Algorithmic logic mirrors upstream; downstream callers
|
||||
// (luxfi/geth, luxfi/node, luxfi/evm) consume this package directly under
|
||||
// the github.com/luxfi/crypto/verkle import path.
|
||||
|
||||
// This is free and unencumbered software released into the public domain.
|
||||
//
|
||||
// Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
// distribute this software, either in source code form or as a compiled
|
||||
// binary, for any purpose, commercial or non-commercial, and by any
|
||||
// means.
|
||||
//
|
||||
// In jurisdictions that recognize copyright laws, the author or authors
|
||||
// of this software dedicate any and all copyright interest in the
|
||||
// software to the public domain. We make this dedication for the benefit
|
||||
// of the public at large and to the detriment of our heirs and
|
||||
// successors. We intend this dedication to be an overt act of
|
||||
// relinquishment in perpetuity of all present and future rights to this
|
||||
// software under copyright law.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
// OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
// For more information, please refer to <https://unlicense.org>
|
||||
|
||||
package verkle
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
)
|
||||
|
||||
const (
|
||||
KeySize = 32
|
||||
LeafValueSize = 32
|
||||
NodeWidth = 256
|
||||
NodeBitWidth byte = 8
|
||||
StemSize = 31
|
||||
)
|
||||
|
||||
func equalPaths(key1, key2 []byte) bool {
|
||||
return bytes.Equal(KeyToStem(key1), KeyToStem(key2))
|
||||
}
|
||||
|
||||
// offset2key extracts the n bits of a key that correspond to the
|
||||
// index of a child node.
|
||||
func offset2key(key []byte, offset byte) byte {
|
||||
return key[offset]
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
// Copyright (C) 2026, Lux Industries Inc. All rights reserved.
|
||||
//
|
||||
// Vendored from github.com/ethereum/go-verkle@v0.2.2 (public domain / Unlicense).
|
||||
// Re-licensed under the Lux Ecosystem License. The package implements the
|
||||
// Verkle trie, its node types (InternalNode, LeafNode, Empty, HashedNode),
|
||||
// IPA configuration, multi-proof generation/verification, encoding, and
|
||||
// JSON serialization. Algorithmic logic mirrors upstream; downstream callers
|
||||
// (luxfi/geth, luxfi/node, luxfi/evm) consume this package directly under
|
||||
// the github.com/luxfi/crypto/verkle import path.
|
||||
|
||||
// This is free and unencumbered software released into the public domain.
|
||||
//
|
||||
// Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
// distribute this software, either in source code form or as a compiled
|
||||
// binary, for any purpose, commercial or non-commercial, and by any
|
||||
// means.
|
||||
//
|
||||
// In jurisdictions that recognize copyright laws, the author or authors
|
||||
// of this software dedicate any and all copyright interest in the
|
||||
// software to the public domain. We make this dedication for the benefit
|
||||
// of the public at large and to the detriment of our heirs and
|
||||
// successors. We intend this dedication to be an overt act of
|
||||
// relinquishment in perpetuity of all present and future rights to this
|
||||
// software under copyright law.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
// OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
// For more information, please refer to <https://unlicense.org>
|
||||
|
||||
package verkle
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/crate-crypto/go-ipa/ipa"
|
||||
)
|
||||
|
||||
// EmptyCodeHashPoint is a cached point that is used to represent an empty code hash.
|
||||
// This value is initialized once in GetConfig().
|
||||
var (
|
||||
EmptyCodeHashPoint Point
|
||||
EmptyCodeHashFirstHalfValue Fr
|
||||
EmptyCodeHashSecondHalfValue Fr
|
||||
)
|
||||
|
||||
const (
|
||||
CodeHashVectorPosition = 3 // Defined by the spec.
|
||||
EmptyCodeHashFirstHalfIdx = CodeHashVectorPosition * 2
|
||||
EmptyCodeHashSecondHalfIdx = EmptyCodeHashFirstHalfIdx + 1
|
||||
)
|
||||
|
||||
var (
|
||||
FrZero Fr
|
||||
FrOne Fr
|
||||
|
||||
cfg *Config
|
||||
onceCfg sync.Once
|
||||
)
|
||||
|
||||
func init() {
|
||||
FrZero.SetZero()
|
||||
FrOne.SetOne()
|
||||
}
|
||||
|
||||
type IPAConfig struct {
|
||||
conf *ipa.IPAConfig
|
||||
}
|
||||
|
||||
type Config = IPAConfig
|
||||
|
||||
func GetConfig() *Config {
|
||||
onceCfg.Do(func() {
|
||||
conf, err := ipa.NewIPASettings()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
cfg = &IPAConfig{conf: conf}
|
||||
|
||||
// Initialize the empty code cached values.
|
||||
values := make([][]byte, NodeWidth)
|
||||
values[CodeHashVectorPosition] = EmptyCodeHash
|
||||
var c1poly [NodeWidth]Fr
|
||||
if _, err := fillSuffixTreePoly(c1poly[:], values[:NodeWidth/2]); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
EmptyCodeHashPoint = *cfg.CommitToPoly(c1poly[:], 0)
|
||||
EmptyCodeHashFirstHalfValue = c1poly[EmptyCodeHashFirstHalfIdx]
|
||||
EmptyCodeHashSecondHalfValue = c1poly[EmptyCodeHashSecondHalfIdx]
|
||||
})
|
||||
return cfg
|
||||
}
|
||||
|
||||
func (conf *IPAConfig) CommitToPoly(poly []Fr, _ int) *Point {
|
||||
ret := conf.conf.Commit(poly)
|
||||
return &ret
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
// Copyright (C) 2026, Lux Industries Inc. All rights reserved.
|
||||
//
|
||||
// Vendored from github.com/ethereum/go-verkle@v0.2.2 (public domain / Unlicense).
|
||||
// Re-licensed under the Lux Ecosystem License. The package implements the
|
||||
// Verkle trie, its node types (InternalNode, LeafNode, Empty, HashedNode),
|
||||
// IPA configuration, multi-proof generation/verification, encoding, and
|
||||
// JSON serialization. Algorithmic logic mirrors upstream; downstream callers
|
||||
// (luxfi/geth, luxfi/node, luxfi/evm) consume this package directly under
|
||||
// the github.com/luxfi/crypto/verkle import path.
|
||||
|
||||
package verkle
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"sort"
|
||||
|
||||
"github.com/crate-crypto/go-ipa/banderwagon"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
// BatchNewLeafNodeData is a struct that contains the data needed to create a new leaf node.
|
||||
type BatchNewLeafNodeData struct {
|
||||
Stem Stem
|
||||
Values map[byte][]byte
|
||||
}
|
||||
|
||||
// BatchNewLeafNode creates a new leaf node from the given data. It optimizes LeafNode creation
|
||||
// by batching expensive cryptography operations. It returns the LeafNodes sorted by stem.
|
||||
func BatchNewLeafNode(nodesValues []BatchNewLeafNodeData) ([]LeafNode, error) {
|
||||
cfg := GetConfig()
|
||||
ret := make([]LeafNode, len(nodesValues))
|
||||
|
||||
numBatches := runtime.NumCPU()
|
||||
batchSize := len(nodesValues) / numBatches
|
||||
|
||||
group, _ := errgroup.WithContext(context.Background())
|
||||
for i := 0; i < numBatches; i++ {
|
||||
start := i * batchSize
|
||||
end := (i + 1) * batchSize
|
||||
if i == numBatches-1 {
|
||||
end = len(nodesValues)
|
||||
}
|
||||
|
||||
work := func(ret []LeafNode, nodesValues []BatchNewLeafNodeData) func() error {
|
||||
return func() error {
|
||||
c1c2points := make([]*Point, 2*len(nodesValues))
|
||||
c1c2frs := make([]*Fr, 2*len(nodesValues))
|
||||
for i, nv := range nodesValues {
|
||||
valsslice := make([][]byte, NodeWidth)
|
||||
for idx := range nv.Values {
|
||||
valsslice[idx] = nv.Values[idx]
|
||||
}
|
||||
|
||||
var leaf *LeafNode
|
||||
leaf, err := NewLeafNode(nv.Stem, valsslice)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ret[i] = *leaf
|
||||
|
||||
c1c2points[2*i], c1c2points[2*i+1] = ret[i].c1, ret[i].c2
|
||||
c1c2frs[2*i], c1c2frs[2*i+1] = new(Fr), new(Fr)
|
||||
}
|
||||
|
||||
if err := banderwagon.BatchMapToScalarField(c1c2frs, c1c2points); err != nil {
|
||||
return fmt.Errorf("mapping to scalar field: %s", err)
|
||||
}
|
||||
|
||||
var poly [NodeWidth]Fr
|
||||
poly[0].SetUint64(1)
|
||||
for i, nv := range nodesValues {
|
||||
if err := StemFromLEBytes(&poly[1], nv.Stem); err != nil {
|
||||
return err
|
||||
}
|
||||
poly[2] = *c1c2frs[2*i]
|
||||
poly[3] = *c1c2frs[2*i+1]
|
||||
|
||||
ret[i].commitment = cfg.CommitToPoly(poly[:], 252)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
group.Go(work(ret[start:end], nodesValues[start:end]))
|
||||
}
|
||||
if err := group.Wait(); err != nil {
|
||||
return nil, fmt.Errorf("creating leaf node: %s", err)
|
||||
}
|
||||
|
||||
sort.Slice(ret, func(i, j int) bool {
|
||||
return bytes.Compare(ret[i].stem, ret[j].stem) < 0
|
||||
})
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// firstDiffByteIdx will return the first index in which the two stems differ.
|
||||
// Both stems *must* be different.
|
||||
func firstDiffByteIdx(stem1 []byte, stem2 []byte) int {
|
||||
for i := range stem1 {
|
||||
if stem1[i] != stem2[i] {
|
||||
return i
|
||||
}
|
||||
}
|
||||
panic("stems are equal")
|
||||
}
|
||||
|
||||
func (n *InternalNode) InsertMigratedLeaves(leaves []LeafNode, resolver NodeResolverFn) error {
|
||||
sort.Slice(leaves, func(i, j int) bool {
|
||||
return bytes.Compare(leaves[i].stem, leaves[j].stem) < 0
|
||||
})
|
||||
|
||||
// We first mark all children of the subtreess that we'll update in parallel,
|
||||
// so the subtree updating doesn't produce a concurrent access to n.cowChild(...).
|
||||
var lastChildrenIdx = -1
|
||||
for i := range leaves {
|
||||
if int(leaves[i].stem[0]) != lastChildrenIdx {
|
||||
lastChildrenIdx = int(leaves[i].stem[0])
|
||||
if _, ok := n.children[lastChildrenIdx].(HashedNode); ok {
|
||||
serialized, err := resolver([]byte{byte(lastChildrenIdx)})
|
||||
if err != nil {
|
||||
return fmt.Errorf("resolving node: %s", err)
|
||||
}
|
||||
resolved, err := ParseNode(serialized, 1)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing node %x: %w", serialized, err)
|
||||
}
|
||||
n.children[lastChildrenIdx] = resolved
|
||||
}
|
||||
n.cowChild(byte(lastChildrenIdx))
|
||||
}
|
||||
}
|
||||
|
||||
// We insert the migrated leaves for each subtree of the root node.
|
||||
group, _ := errgroup.WithContext(context.Background())
|
||||
group.SetLimit(runtime.NumCPU())
|
||||
currStemFirstByte := 0
|
||||
for i := range leaves {
|
||||
if leaves[currStemFirstByte].stem[0] != leaves[i].stem[0] {
|
||||
start := currStemFirstByte
|
||||
end := i
|
||||
group.Go(func() error {
|
||||
return n.insertMigratedLeavesSubtree(leaves[start:end], resolver)
|
||||
})
|
||||
currStemFirstByte = i
|
||||
}
|
||||
}
|
||||
group.Go(func() error {
|
||||
return n.insertMigratedLeavesSubtree(leaves[currStemFirstByte:], resolver)
|
||||
})
|
||||
if err := group.Wait(); err != nil {
|
||||
return fmt.Errorf("inserting migrated leaves: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *InternalNode) insertMigratedLeavesSubtree(leaves []LeafNode, resolver NodeResolverFn) error { // skipcq: GO-R1005
|
||||
for i := range leaves {
|
||||
ln := leaves[i]
|
||||
parent := n
|
||||
|
||||
// Look for the appropriate parent for the leaf node.
|
||||
for {
|
||||
if _, ok := parent.children[ln.stem[parent.depth]].(HashedNode); ok {
|
||||
serialized, err := resolver(ln.stem[:parent.depth+1])
|
||||
if err != nil {
|
||||
return fmt.Errorf("resolving node path=%x: %w", ln.stem[:parent.depth+1], err)
|
||||
}
|
||||
resolved, err := ParseNode(serialized, parent.depth+1)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing node %x: %w", serialized, err)
|
||||
}
|
||||
parent.children[ln.stem[parent.depth]] = resolved
|
||||
}
|
||||
|
||||
nextParent, ok := parent.children[ln.stem[parent.depth]].(*InternalNode)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
|
||||
parent.cowChild(ln.stem[parent.depth])
|
||||
parent = nextParent
|
||||
}
|
||||
|
||||
switch node := parent.children[ln.stem[parent.depth]].(type) {
|
||||
case Empty:
|
||||
parent.cowChild(ln.stem[parent.depth])
|
||||
parent.children[ln.stem[parent.depth]] = &ln
|
||||
ln.setDepth(parent.depth + 1)
|
||||
case *LeafNode:
|
||||
if bytes.Equal(node.stem, ln.stem) {
|
||||
// In `ln` we have migrated key/values which should be copied to the leaf
|
||||
// only if there isn't a value there. If there's a value, we skip it since
|
||||
// our migrated value is stale.
|
||||
nonPresentValues := make([][]byte, NodeWidth)
|
||||
for i := range ln.values {
|
||||
if node.values[i] == nil {
|
||||
nonPresentValues[i] = ln.values[i]
|
||||
}
|
||||
}
|
||||
|
||||
if err := node.updateMultipleLeaves(nonPresentValues); err != nil {
|
||||
return fmt.Errorf("updating leaves: %s", err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Otherwise, we need to create the missing internal nodes depending in the fork point in their stems.
|
||||
idx := firstDiffByteIdx(node.stem, ln.stem)
|
||||
// We do a sanity check to make sure that the fork point is not before the current depth.
|
||||
if byte(idx) <= parent.depth {
|
||||
return fmt.Errorf("unexpected fork point %d for nodes %x and %x", idx, node.stem, ln.stem)
|
||||
}
|
||||
// Create the missing internal nodes.
|
||||
for i := parent.depth + 1; i <= byte(idx); i++ {
|
||||
nextParent := newInternalNode(parent.depth + 1).(*InternalNode)
|
||||
parent.cowChild(ln.stem[parent.depth])
|
||||
parent.children[ln.stem[parent.depth]] = nextParent
|
||||
parent = nextParent
|
||||
}
|
||||
// Add old and new leaf node to the latest created parent.
|
||||
parent.cowChild(node.stem[parent.depth])
|
||||
parent.children[node.stem[parent.depth]] = node
|
||||
node.setDepth(parent.depth + 1)
|
||||
parent.cowChild(ln.stem[parent.depth])
|
||||
parent.children[ln.stem[parent.depth]] = &ln
|
||||
ln.setDepth(parent.depth + 1)
|
||||
default:
|
||||
return fmt.Errorf("unexpected node type %T", node)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
// Copyright (C) 2026, Lux Industries Inc. All rights reserved.
|
||||
//
|
||||
// Vendored from github.com/ethereum/go-verkle@v0.2.2 (public domain / Unlicense).
|
||||
// Re-licensed under the Lux Ecosystem License. The package implements the
|
||||
// Verkle trie, its node types (InternalNode, LeafNode, Empty, HashedNode),
|
||||
// IPA configuration, multi-proof generation/verification, encoding, and
|
||||
// JSON serialization. Algorithmic logic mirrors upstream; downstream callers
|
||||
// (luxfi/geth, luxfi/node, luxfi/evm) consume this package directly under
|
||||
// the github.com/luxfi/crypto/verkle import path.
|
||||
|
||||
// This is free and unencumbered software released into the public domain.
|
||||
//
|
||||
// Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
// distribute this software, either in source code form or as a compiled
|
||||
// binary, for any purpose, commercial or non-commercial, and by any
|
||||
// means.
|
||||
//
|
||||
// In jurisdictions that recognize copyright laws, the author or authors
|
||||
// of this software dedicate any and all copyright interest in the
|
||||
// software to the public domain. We make this dedication for the benefit
|
||||
// of the public at large and to the detriment of our heirs and
|
||||
// successors. We intend this dedication to be an overt act of
|
||||
// relinquishment in perpetuity of all present and future rights to this
|
||||
// software under copyright law.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
// OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
// For more information, please refer to <https://unlicense.org>
|
||||
|
||||
package verkle
|
||||
|
||||
// A few proxy types that export their fields, so that the core type
|
||||
// does not. The conversion from one type to the other is done by calling
|
||||
// toExportable on an InternalNode.
|
||||
type (
|
||||
ExportableInternalNode struct {
|
||||
Children []interface{} `json:"children"`
|
||||
|
||||
Commitment []byte `json:"commitment"`
|
||||
}
|
||||
|
||||
ExportableLeafNode struct {
|
||||
Stem Stem `json:"stem"`
|
||||
Values [][]byte `json:"values"`
|
||||
|
||||
C [32]byte `json:"commitment"`
|
||||
C1 [32]byte `json:"c1"`
|
||||
C2 [32]byte `json:"c2"`
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,56 @@
|
||||
// This is free and unencumbered software released into the public domain.
|
||||
//
|
||||
// Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
// distribute this software, either in source code form or as a compiled
|
||||
// binary, for any purpose, commercial or non-commercial, and by any
|
||||
// means.
|
||||
//
|
||||
// In jurisdictions that recognize copyright laws, the author or authors
|
||||
// of this software dedicate any and all copyright interest in the
|
||||
// software to the public domain. We make this dedication for the benefit
|
||||
// of the public at large and to the detriment of our heirs and
|
||||
// successors. We intend this dedication to be an overt act of
|
||||
// relinquishment in perpetuity of all present and future rights to this
|
||||
// software under copyright law.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
// OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
// For more information, please refer to <https://unlicense.org>
|
||||
|
||||
package verkle
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestJSON(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
root := New()
|
||||
if err := root.Insert(zeroKeyTest, fourtyKeyTest, nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := root.Insert(oneKeyTest, zeroKeyTest, nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := root.Insert(forkOneKeyTest, zeroKeyTest, nil); err != nil { // Force an internal node in the first layer.
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := root.Insert(fourtyKeyTest, oneKeyTest, nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
root.(*InternalNode).children[152] = HashedNode{}
|
||||
root.Commit()
|
||||
|
||||
output, err := root.(*InternalNode).ToJSON()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(string(output))
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
// Copyright (C) 2026, Lux Industries Inc. All rights reserved.
|
||||
//
|
||||
// Vendored from github.com/ethereum/go-verkle@v0.2.2 (public domain / Unlicense).
|
||||
// Re-licensed under the Lux Ecosystem License. The package implements the
|
||||
// Verkle trie, its node types (InternalNode, LeafNode, Empty, HashedNode),
|
||||
// IPA configuration, multi-proof generation/verification, encoding, and
|
||||
// JSON serialization. Algorithmic logic mirrors upstream; downstream callers
|
||||
// (luxfi/geth, luxfi/node, luxfi/evm) consume this package directly under
|
||||
// the github.com/luxfi/crypto/verkle import path.
|
||||
|
||||
// This is free and unencumbered software released into the public domain.
|
||||
//
|
||||
// Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
// distribute this software, either in source code form or as a compiled
|
||||
// binary, for any purpose, commercial or non-commercial, and by any
|
||||
// means.
|
||||
//
|
||||
// In jurisdictions that recognize copyright laws, the author or authors
|
||||
// of this software dedicate any and all copyright interest in the
|
||||
// software to the public domain. We make this dedication for the benefit
|
||||
// of the public at large and to the detriment of our heirs and
|
||||
// successors. We intend this dedication to be an overt act of
|
||||
// relinquishment in perpetuity of all present and future rights to this
|
||||
// software under copyright law.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
// OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
// For more information, please refer to <https://unlicense.org>
|
||||
|
||||
package verkle
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
errInsertIntoHash = errors.New("trying to insert into hashed node")
|
||||
errDeleteHash = errors.New("trying to delete from a hashed subtree")
|
||||
errDeleteMissing = errors.New("trying to delete a missing group")
|
||||
errDeleteUnknown = errors.New("trying to delete an out-of-view node")
|
||||
errReadFromInvalid = errors.New("trying to read from an invalid child")
|
||||
errSerializeHashedNode = errors.New("trying to serialize a hashed internal node")
|
||||
errInsertIntoOtherStem = errors.New("insert splits a stem where it should not happen")
|
||||
errUnknownNodeType = errors.New("unknown node type detected")
|
||||
errMissingNodeInStateless = errors.New("trying to access a node that is missing from the stateless view")
|
||||
errIsPOAStub = errors.New("trying to read/write a proof of absence leaf node")
|
||||
)
|
||||
|
||||
const (
|
||||
// Extension status
|
||||
extStatusAbsentEmpty = byte(iota) // missing child node along the path
|
||||
extStatusAbsentOther // path led to a node with a different stem
|
||||
extStatusPresent // stem was present
|
||||
)
|
||||
@@ -0,0 +1,88 @@
|
||||
// Copyright (C) 2026, Lux Industries Inc. All rights reserved.
|
||||
//
|
||||
// Vendored from github.com/ethereum/go-verkle@v0.2.2 (public domain / Unlicense).
|
||||
// Re-licensed under the Lux Ecosystem License. The package implements the
|
||||
// Verkle trie, its node types (InternalNode, LeafNode, Empty, HashedNode),
|
||||
// IPA configuration, multi-proof generation/verification, encoding, and
|
||||
// JSON serialization. Algorithmic logic mirrors upstream; downstream callers
|
||||
// (luxfi/geth, luxfi/node, luxfi/evm) consume this package directly under
|
||||
// the github.com/luxfi/crypto/verkle import path.
|
||||
|
||||
// This is free and unencumbered software released into the public domain.
|
||||
//
|
||||
// Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
// distribute this software, either in source code form or as a compiled
|
||||
// binary, for any purpose, commercial or non-commercial, and by any
|
||||
// means.
|
||||
//
|
||||
// In jurisdictions that recognize copyright laws, the author or authors
|
||||
// of this software dedicate any and all copyright interest in the
|
||||
// software to the public domain. We make this dedication for the benefit
|
||||
// of the public at large and to the detriment of our heirs and
|
||||
// successors. We intend this dedication to be an overt act of
|
||||
// relinquishment in perpetuity of all present and future rights to this
|
||||
// software under copyright law.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
// OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
// For more information, please refer to <https://unlicense.org>
|
||||
|
||||
package verkle
|
||||
|
||||
import "errors"
|
||||
|
||||
type Empty struct{}
|
||||
|
||||
var errDirectInsertIntoEmptyNode = errors.New("an empty node should not be inserted directly into")
|
||||
|
||||
func (Empty) Insert([]byte, []byte, NodeResolverFn) error {
|
||||
return errDirectInsertIntoEmptyNode
|
||||
}
|
||||
|
||||
func (Empty) Delete([]byte, NodeResolverFn) (bool, error) {
|
||||
return false, errors.New("cant delete an empty node")
|
||||
}
|
||||
|
||||
func (Empty) Get([]byte, NodeResolverFn) ([]byte, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (n Empty) Commit() *Point {
|
||||
return n.Commitment()
|
||||
}
|
||||
|
||||
func (Empty) Commitment() *Point {
|
||||
var id Point
|
||||
id.SetIdentity()
|
||||
return &id
|
||||
}
|
||||
|
||||
func (Empty) GetProofItems(keylist, NodeResolverFn) (*ProofElements, []byte, []Stem, error) {
|
||||
return nil, nil, nil, errors.New("trying to produce a commitment for an empty subtree")
|
||||
}
|
||||
|
||||
func (Empty) Serialize() ([]byte, error) {
|
||||
return nil, errors.New("can't encode empty node to RLP")
|
||||
}
|
||||
|
||||
func (Empty) Copy() VerkleNode {
|
||||
return Empty(struct{}{})
|
||||
}
|
||||
|
||||
func (Empty) toDot(string, string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (Empty) setDepth(_ byte) {
|
||||
panic("should not be try to set the depth of an Empty node")
|
||||
}
|
||||
|
||||
func (Empty) Hash() *Fr {
|
||||
return &FrZero
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
// This is free and unencumbered software released into the public domain.
|
||||
//
|
||||
// Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
// distribute this software, either in source code form or as a compiled
|
||||
// binary, for any purpose, commercial or non-commercial, and by any
|
||||
// means.
|
||||
//
|
||||
// In jurisdictions that recognize copyright laws, the author or authors
|
||||
// of this software dedicate any and all copyright interest in the
|
||||
// software to the public domain. We make this dedication for the benefit
|
||||
// of the public at large and to the detriment of our heirs and
|
||||
// successors. We intend this dedication to be an overt act of
|
||||
// relinquishment in perpetuity of all present and future rights to this
|
||||
// software under copyright law.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
// OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
// For more information, please refer to <https://unlicense.org>
|
||||
|
||||
package verkle
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestEmptyFuncs(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var e Empty
|
||||
err := e.Insert(zeroKeyTest, zeroKeyTest, nil)
|
||||
if err == nil {
|
||||
t.Fatal("got nil error when inserting into empty")
|
||||
}
|
||||
_, err = e.Delete(zeroKeyTest, nil)
|
||||
if err == nil {
|
||||
t.Fatal("got nil error when deleting from empty")
|
||||
}
|
||||
v, err := e.Get(zeroKeyTest, nil)
|
||||
if err != nil {
|
||||
t.Fatal("got non-nil error when getting from empty")
|
||||
}
|
||||
if v != nil {
|
||||
t.Fatal("non-nil get from empty")
|
||||
}
|
||||
|
||||
if !e.Commitment().Equal(e.Commit()) {
|
||||
t.Fatal("commitment and commit mismatch")
|
||||
}
|
||||
|
||||
if _, _, _, err := e.GetProofItems(nil, nil); err == nil {
|
||||
t.Fatal("get proof items should error")
|
||||
}
|
||||
|
||||
if _, err := e.Serialize(); err == nil {
|
||||
t.Fatal("serialize should error")
|
||||
}
|
||||
|
||||
if !e.Hash().Equal(&FrZero) {
|
||||
t.Fatal("hash should be the zero element")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
// Copyright (C) 2026, Lux Industries Inc. All rights reserved.
|
||||
//
|
||||
// Vendored from github.com/ethereum/go-verkle@v0.2.2 (public domain / Unlicense).
|
||||
// Re-licensed under the Lux Ecosystem License. The package implements the
|
||||
// Verkle trie, its node types (InternalNode, LeafNode, Empty, HashedNode),
|
||||
// IPA configuration, multi-proof generation/verification, encoding, and
|
||||
// JSON serialization. Algorithmic logic mirrors upstream; downstream callers
|
||||
// (luxfi/geth, luxfi/node, luxfi/evm) consume this package directly under
|
||||
// the github.com/luxfi/crypto/verkle import path.
|
||||
|
||||
// This is free and unencumbered software released into the public domain.
|
||||
//
|
||||
// Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
// distribute this software, either in source code form or as a compiled
|
||||
// binary, for any purpose, commercial or non-commercial, and by any
|
||||
// means.
|
||||
//
|
||||
// In jurisdictions that recognize copyright laws, the author or authors
|
||||
// of this software dedicate any and all copyright interest in the
|
||||
// software to the public domain. We make this dedication for the benefit
|
||||
// of the public at large and to the detriment of our heirs and
|
||||
// successors. We intend this dedication to be an overt act of
|
||||
// relinquishment in perpetuity of all present and future rights to this
|
||||
// software under copyright law.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
// OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
// For more information, please refer to <https://unlicense.org>
|
||||
|
||||
package verkle
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/crate-crypto/go-ipa/banderwagon"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidNodeEncoding = errors.New("invalid node encoding")
|
||||
|
||||
mask = [8]byte{0x80, 0x40, 0x20, 0x10, 0x8, 0x4, 0x2, 0x1}
|
||||
)
|
||||
|
||||
const (
|
||||
nodeTypeSize = 1
|
||||
bitlistSize = NodeWidth / 8
|
||||
|
||||
// Shared between internal and leaf nodes.
|
||||
nodeTypeOffset = 0
|
||||
|
||||
// Internal nodes offsets.
|
||||
internalBitlistOffset = nodeTypeOffset + nodeTypeSize
|
||||
internalCommitmentOffset = internalBitlistOffset + bitlistSize
|
||||
|
||||
// Leaf node offsets.
|
||||
leafStemOffset = nodeTypeOffset + nodeTypeSize
|
||||
leafBitlistOffset = leafStemOffset + StemSize
|
||||
leafCommitmentOffset = leafBitlistOffset + bitlistSize
|
||||
leafC1CommitmentOffset = leafCommitmentOffset + banderwagon.UncompressedSize
|
||||
leafC2CommitmentOffset = leafC1CommitmentOffset + banderwagon.UncompressedSize
|
||||
leafChildrenOffset = leafC2CommitmentOffset + banderwagon.UncompressedSize
|
||||
leafBasicDataSize = 32
|
||||
leafSlotSize = 32
|
||||
leafValueIndexSize = 1
|
||||
singleSlotLeafSize = nodeTypeSize + StemSize + 2*banderwagon.UncompressedSize + leafValueIndexSize + leafSlotSize
|
||||
eoaLeafSize = nodeTypeSize + StemSize + 2*banderwagon.UncompressedSize + leafBasicDataSize
|
||||
)
|
||||
|
||||
func bit(bitlist []byte, nr int) bool {
|
||||
if len(bitlist)*8 <= nr {
|
||||
return false
|
||||
}
|
||||
return bitlist[nr/8]&mask[nr%8] != 0
|
||||
}
|
||||
|
||||
var errSerializedPayloadTooShort = errors.New("verkle payload is too short")
|
||||
|
||||
// ParseNode deserializes a node into its proper VerkleNode instance.
|
||||
// The serialized bytes have the format:
|
||||
// - Internal nodes: <nodeType><bitlist><commitment>
|
||||
// - Leaf nodes: <nodeType><stem><bitlist><comm><c1comm><c2comm><children...>
|
||||
// - EoA nodes: <nodeType><stem><comm><c1comm><balance><nonce>
|
||||
// - single slot node: <nodeType><stem><comm><cncomm><leaf index><slot>
|
||||
func ParseNode(serializedNode []byte, depth byte) (VerkleNode, error) {
|
||||
// Check that the length of the serialized node is at least the smallest possible serialized node.
|
||||
if len(serializedNode) < nodeTypeSize+banderwagon.UncompressedSize {
|
||||
return nil, errSerializedPayloadTooShort
|
||||
}
|
||||
|
||||
switch serializedNode[0] {
|
||||
case leafType:
|
||||
return parseLeafNode(serializedNode, depth)
|
||||
case internalType:
|
||||
return CreateInternalNode(serializedNode[internalBitlistOffset:internalCommitmentOffset], serializedNode[internalCommitmentOffset:], depth)
|
||||
case eoAccountType:
|
||||
return parseEoAccountNode(serializedNode, depth)
|
||||
case singleSlotType:
|
||||
return parseSingleSlotNode(serializedNode, depth)
|
||||
default:
|
||||
return nil, ErrInvalidNodeEncoding
|
||||
}
|
||||
}
|
||||
|
||||
func parseLeafNode(serialized []byte, depth byte) (VerkleNode, error) {
|
||||
bitlist := serialized[leafBitlistOffset : leafBitlistOffset+bitlistSize]
|
||||
var values [NodeWidth][]byte
|
||||
offset := leafChildrenOffset
|
||||
for i := 0; i < NodeWidth; i++ {
|
||||
if bit(bitlist, i) {
|
||||
if offset+LeafValueSize > len(serialized) {
|
||||
return nil, fmt.Errorf("verkle payload is too short, need at least %d and only have %d, payload = %x (%w)", offset+LeafValueSize, len(serialized), serialized, errSerializedPayloadTooShort)
|
||||
}
|
||||
values[i] = serialized[offset : offset+LeafValueSize]
|
||||
offset += LeafValueSize
|
||||
}
|
||||
}
|
||||
ln := NewLeafNodeWithNoComms(serialized[leafStemOffset:leafStemOffset+StemSize], values[:])
|
||||
ln.setDepth(depth)
|
||||
ln.c1 = new(Point)
|
||||
|
||||
// Sanity check that we have at least 3*banderwagon.UncompressedSize bytes left in the serialized payload.
|
||||
if len(serialized[leafCommitmentOffset:]) < 3*banderwagon.UncompressedSize {
|
||||
return nil, fmt.Errorf("leaf node commitments are not the correct size, expected at least %d, got %d", 3*banderwagon.UncompressedSize, len(serialized[leafC1CommitmentOffset:]))
|
||||
}
|
||||
|
||||
if err := ln.c1.SetBytesUncompressed(serialized[leafC1CommitmentOffset:leafC1CommitmentOffset+banderwagon.UncompressedSize], true); err != nil {
|
||||
return nil, fmt.Errorf("setting c1 commitment: %w", err)
|
||||
}
|
||||
ln.c2 = new(Point)
|
||||
if err := ln.c2.SetBytesUncompressed(serialized[leafC2CommitmentOffset:leafC2CommitmentOffset+banderwagon.UncompressedSize], true); err != nil {
|
||||
return nil, fmt.Errorf("setting c2 commitment: %w", err)
|
||||
}
|
||||
ln.commitment = new(Point)
|
||||
if err := ln.commitment.SetBytesUncompressed(serialized[leafCommitmentOffset:leafC1CommitmentOffset], true); err != nil {
|
||||
return nil, fmt.Errorf("setting commitment: %w", err)
|
||||
}
|
||||
return ln, nil
|
||||
}
|
||||
|
||||
func parseEoAccountNode(serialized []byte, depth byte) (VerkleNode, error) {
|
||||
var values [NodeWidth][]byte
|
||||
offset := leafStemOffset + StemSize + 2*banderwagon.UncompressedSize
|
||||
values[0] = serialized[offset : offset+leafBasicDataSize] // basic data
|
||||
values[1] = EmptyCodeHash[:]
|
||||
ln := NewLeafNodeWithNoComms(serialized[leafStemOffset:leafStemOffset+StemSize], values[:])
|
||||
ln.setDepth(depth)
|
||||
ln.c1 = new(Point)
|
||||
if err := ln.c1.SetBytesUncompressed(serialized[leafStemOffset+StemSize:leafStemOffset+StemSize+banderwagon.UncompressedSize], true); err != nil {
|
||||
return nil, fmt.Errorf("error setting leaf C1 commitment: %w", err)
|
||||
}
|
||||
ln.c2 = &banderwagon.Identity
|
||||
ln.commitment = new(Point)
|
||||
if err := ln.commitment.SetBytesUncompressed(serialized[leafStemOffset+StemSize+banderwagon.UncompressedSize:leafStemOffset+StemSize+banderwagon.UncompressedSize*2], true); err != nil {
|
||||
return nil, fmt.Errorf("error setting leaf root commitment: %w", err)
|
||||
}
|
||||
return ln, nil
|
||||
}
|
||||
|
||||
func parseSingleSlotNode(serialized []byte, depth byte) (VerkleNode, error) {
|
||||
var values [NodeWidth][]byte
|
||||
offset := leafStemOffset
|
||||
ln := NewLeafNodeWithNoComms(serialized[offset:offset+StemSize], values[:])
|
||||
offset += StemSize
|
||||
cnCommBytes := serialized[offset : offset+banderwagon.UncompressedSize]
|
||||
offset += banderwagon.UncompressedSize
|
||||
rootCommBytes := serialized[offset : offset+banderwagon.UncompressedSize]
|
||||
offset += banderwagon.UncompressedSize
|
||||
idx := serialized[offset]
|
||||
offset += leafValueIndexSize
|
||||
values[idx] = serialized[offset : offset+leafSlotSize] // copy slot
|
||||
ln.setDepth(depth)
|
||||
if idx < 128 {
|
||||
ln.c1 = new(Point)
|
||||
if err := ln.c1.SetBytesUncompressed(cnCommBytes, true); err != nil {
|
||||
return nil, fmt.Errorf("error setting leaf C1 commitment: %w", err)
|
||||
}
|
||||
ln.c2 = &banderwagon.Identity
|
||||
} else {
|
||||
ln.c2 = new(Point)
|
||||
if err := ln.c2.SetBytesUncompressed(cnCommBytes, true); err != nil {
|
||||
return nil, fmt.Errorf("error setting leaf C2 commitment: %w", err)
|
||||
}
|
||||
ln.c1 = &banderwagon.Identity
|
||||
}
|
||||
ln.commitment = new(Point)
|
||||
if err := ln.commitment.SetBytesUncompressed(rootCommBytes, true); err != nil {
|
||||
return nil, fmt.Errorf("error setting leaf root commitment: %w", err)
|
||||
}
|
||||
return ln, nil
|
||||
}
|
||||
|
||||
func CreateInternalNode(bitlist []byte, raw []byte, depth byte) (*InternalNode, error) {
|
||||
// GetTreeConfig caches computation result, hence
|
||||
// this op has low overhead
|
||||
node := new(InternalNode)
|
||||
|
||||
if len(bitlist) != bitlistSize {
|
||||
return nil, ErrInvalidNodeEncoding
|
||||
}
|
||||
|
||||
// Create a HashNode placeholder for all values
|
||||
// corresponding to a set bit.
|
||||
node.children = make([]VerkleNode, NodeWidth)
|
||||
for i, b := range bitlist {
|
||||
for j := 0; j < 8; j++ {
|
||||
if b&mask[j] != 0 {
|
||||
node.children[8*i+j] = HashedNode{}
|
||||
} else {
|
||||
|
||||
node.children[8*i+j] = Empty(struct{}{})
|
||||
}
|
||||
}
|
||||
}
|
||||
node.depth = depth
|
||||
if len(raw) != banderwagon.UncompressedSize {
|
||||
return nil, ErrInvalidNodeEncoding
|
||||
}
|
||||
|
||||
node.commitment = new(Point)
|
||||
if err := node.commitment.SetBytesUncompressed(raw, true); err != nil {
|
||||
return nil, fmt.Errorf("setting commitment: %w", err)
|
||||
}
|
||||
return node, nil
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
package verkle
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/crate-crypto/go-ipa/banderwagon"
|
||||
)
|
||||
|
||||
func TestParseNodeEmptyPayload(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := ParseNode([]byte{}, 0)
|
||||
if err != errSerializedPayloadTooShort {
|
||||
t.Fatalf("invalid error, got %v, expected %v", err, "unexpected EOF")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLeafStemLength(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Serialize a leaf with no values, but whose stem is 32 bytes. The
|
||||
// serialization should trim the extra byte.
|
||||
toolong := make([]byte, 32)
|
||||
leaf, err := NewLeafNode(toolong, make([][]byte, NodeWidth))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ser, err := leaf.Serialize()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(ser) != nodeTypeSize+StemSize+bitlistSize+3*banderwagon.UncompressedSize {
|
||||
t.Fatalf("invalid serialization when the stem is longer than 31 bytes: %x (%d bytes != %d)", ser, len(ser), nodeTypeSize+StemSize+bitlistSize+2*banderwagon.UncompressedSize)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidNodeEncoding(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Test a short payload.
|
||||
if _, err := ParseNode([]byte{leafType}, 0); err != errSerializedPayloadTooShort {
|
||||
t.Fatalf("invalid error, got %v, expected %v", err, errSerializedPayloadTooShort)
|
||||
}
|
||||
|
||||
// Test an invalid node type.
|
||||
values := make([][]byte, NodeWidth)
|
||||
values[42] = testValue
|
||||
ln, err := NewLeafNode(ffx32KeyTest, values)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
lnbytes, err := ln.Serialize()
|
||||
if err != nil {
|
||||
t.Fatalf("serializing leaf node: %v", err)
|
||||
}
|
||||
lnbytes[0] = 0xc0 // Change the type of the node to something invalid.
|
||||
if _, err := ParseNode(lnbytes, 0); err != ErrInvalidNodeEncoding {
|
||||
t.Fatalf("invalid error, got %v, expected %v", err, ErrInvalidNodeEncoding)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseNodeEoA(t *testing.T) {
|
||||
values := make([][]byte, 256)
|
||||
values[0] = zero32[:]
|
||||
values[1] = EmptyCodeHash[:] // set empty code hash as balance, because why not
|
||||
values[2] = fourtyKeyTest[:] // set nonce to 64
|
||||
values[3] = EmptyCodeHash[:] // set empty code hash
|
||||
values[4] = zero32[:] // zero-size
|
||||
ln, err := NewLeafNode(ffx32KeyTest[:31], values)
|
||||
if err != nil {
|
||||
t.Fatalf("error creating leaf node: %v", err)
|
||||
}
|
||||
|
||||
serialized, err := ln.Serialize()
|
||||
if err != nil {
|
||||
t.Fatalf("error serializing leaf node: %v", err)
|
||||
}
|
||||
|
||||
if serialized[0] != eoAccountType {
|
||||
t.Fatalf("invalid encoding type, got %d, expected %d", serialized[0], eoAccountType)
|
||||
}
|
||||
|
||||
deserialized, err := ParseNode(serialized, 5)
|
||||
if err != nil {
|
||||
t.Fatalf("error deserializing leaf node: %v", err)
|
||||
}
|
||||
|
||||
lnd, ok := deserialized.(*LeafNode)
|
||||
if !ok {
|
||||
t.Fatalf("expected leaf node, got %T", deserialized)
|
||||
}
|
||||
|
||||
if lnd.depth != 5 {
|
||||
t.Fatalf("invalid depth, got %d, expected %d", lnd.depth, 5)
|
||||
}
|
||||
|
||||
if !bytes.Equal(lnd.stem, ffx32KeyTest[:31]) {
|
||||
t.Fatalf("invalid stem, got %x, expected %x", lnd.stem, ffx32KeyTest[:31])
|
||||
}
|
||||
|
||||
if !bytes.Equal(lnd.values[0], zero32[:]) {
|
||||
t.Fatalf("invalid version, got %x, expected %x", lnd.values[0], zero32[:])
|
||||
}
|
||||
|
||||
if !bytes.Equal(lnd.values[1], EmptyCodeHash[:]) {
|
||||
t.Fatalf("invalid balance, got %x, expected %x", lnd.values[1], EmptyCodeHash[:])
|
||||
}
|
||||
|
||||
if !bytes.Equal(lnd.values[2], fourtyKeyTest[:]) {
|
||||
t.Fatalf("invalid nonce, got %x, expected %x", lnd.values[2], fourtyKeyTest[:])
|
||||
}
|
||||
|
||||
if !bytes.Equal(lnd.values[3], EmptyCodeHash[:]) {
|
||||
t.Fatalf("invalid code hash, got %x, expected %x", lnd.values[3], EmptyCodeHash[:])
|
||||
}
|
||||
|
||||
if !bytes.Equal(lnd.values[4], zero32[:]) {
|
||||
t.Fatalf("invalid code size, got %x, expected %x", lnd.values[4], zero32[:])
|
||||
}
|
||||
|
||||
if !lnd.c2.Equal(&banderwagon.Identity) {
|
||||
t.Fatalf("invalid c2, got %x, expected %x", lnd.c2, banderwagon.Identity)
|
||||
}
|
||||
|
||||
if !lnd.c1.Equal(ln.c1) {
|
||||
t.Fatalf("invalid c1, got %x, expected %x", lnd.c1, ln.c1)
|
||||
}
|
||||
|
||||
if !lnd.commitment.Equal(ln.commitment) {
|
||||
t.Fatalf("invalid commitment, got %x, expected %x", lnd.commitment, ln.commitment)
|
||||
}
|
||||
}
|
||||
func TestParseNodeSingleSlot(t *testing.T) {
|
||||
values := make([][]byte, 256)
|
||||
values[153] = EmptyCodeHash
|
||||
ln, err := NewLeafNode(ffx32KeyTest[:31], values)
|
||||
if err != nil {
|
||||
t.Fatalf("error creating leaf node: %v", err)
|
||||
}
|
||||
|
||||
serialized, err := ln.Serialize()
|
||||
if err != nil {
|
||||
t.Fatalf("error serializing leaf node: %v", err)
|
||||
}
|
||||
|
||||
if serialized[0] != singleSlotType {
|
||||
t.Fatalf("invalid encoding type, got %d, expected %d", serialized[0], singleSlotType)
|
||||
}
|
||||
|
||||
deserialized, err := ParseNode(serialized, 5)
|
||||
if err != nil {
|
||||
t.Fatalf("error deserializing leaf node: %v", err)
|
||||
}
|
||||
|
||||
lnd, ok := deserialized.(*LeafNode)
|
||||
if !ok {
|
||||
t.Fatalf("expected leaf node, got %T", deserialized)
|
||||
}
|
||||
|
||||
if lnd.depth != 5 {
|
||||
t.Fatalf("invalid depth, got %d, expected %d", lnd.depth, 5)
|
||||
}
|
||||
|
||||
if !bytes.Equal(lnd.stem, ffx32KeyTest[:31]) {
|
||||
t.Fatalf("invalid stem, got %x, expected %x", lnd.stem, ffx32KeyTest[:31])
|
||||
}
|
||||
|
||||
for i := range values {
|
||||
if i != 153 {
|
||||
if lnd.values[i] != nil {
|
||||
t.Fatalf("value %d, got %x, expected empty slot", i, lnd.values[i])
|
||||
}
|
||||
} else {
|
||||
if !bytes.Equal(lnd.values[i], EmptyCodeHash[:]) {
|
||||
t.Fatalf("got %x, expected empty slot", lnd.values[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !lnd.c2.Equal(&banderwagon.Identity) {
|
||||
t.Fatalf("invalid c2, got %x, expected %x", lnd.c2, banderwagon.Identity)
|
||||
}
|
||||
|
||||
if !lnd.c1.Equal(ln.c1) {
|
||||
t.Fatalf("invalid c1, got %x, expected %x", lnd.c1, ln.c1)
|
||||
}
|
||||
|
||||
if !lnd.commitment.Equal(ln.commitment) {
|
||||
t.Fatalf("invalid commitment, got %x, expected %x", lnd.commitment, ln.commitment)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
// Copyright (C) 2026, Lux Industries Inc. All rights reserved.
|
||||
//
|
||||
// Vendored from github.com/ethereum/go-verkle@v0.2.2 (public domain / Unlicense).
|
||||
// Re-licensed under the Lux Ecosystem License. The package implements the
|
||||
// Verkle trie, its node types (InternalNode, LeafNode, Empty, HashedNode),
|
||||
// IPA configuration, multi-proof generation/verification, encoding, and
|
||||
// JSON serialization. Algorithmic logic mirrors upstream; downstream callers
|
||||
// (luxfi/geth, luxfi/node, luxfi/evm) consume this package directly under
|
||||
// the github.com/luxfi/crypto/verkle import path.
|
||||
|
||||
// This is free and unencumbered software released into the public domain.
|
||||
//
|
||||
// Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
// distribute this software, either in source code form or as a compiled
|
||||
// binary, for any purpose, commercial or non-commercial, and by any
|
||||
// means.
|
||||
//
|
||||
// In jurisdictions that recognize copyright laws, the author or authors
|
||||
// of this software dedicate any and all copyright interest in the
|
||||
// software to the public domain. We make this dedication for the benefit
|
||||
// of the public at large and to the detriment of our heirs and
|
||||
// successors. We intend this dedication to be an overt act of
|
||||
// relinquishment in perpetuity of all present and future rights to this
|
||||
// software under copyright law.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
// OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
// For more information, please refer to <https://unlicense.org>
|
||||
|
||||
package verkle
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type HashedNode struct{}
|
||||
|
||||
func (HashedNode) Insert([]byte, []byte, NodeResolverFn) error {
|
||||
return errInsertIntoHash
|
||||
}
|
||||
|
||||
func (HashedNode) Delete([]byte, NodeResolverFn) (bool, error) {
|
||||
return false, errors.New("cant delete a hashed node in-place")
|
||||
}
|
||||
|
||||
func (HashedNode) Get([]byte, NodeResolverFn) ([]byte, error) {
|
||||
return nil, errors.New("can not read from a hash node")
|
||||
}
|
||||
|
||||
func (HashedNode) Commit() *Point {
|
||||
// TODO: we should reconsider what to do with the VerkleNode interface and how
|
||||
// HashedNode fits into the picture, since Commit(), Commitment() and Hash()
|
||||
// now panics. Despite these calls must not happen at runtime, it is still
|
||||
// quite risky. The reason we end up in this place is because PBSS came quite
|
||||
// recently compared with the VerkleNode interface design. We should probably
|
||||
// reconsider splitting the interface or find some safer workaround.
|
||||
panic("can not commit a hash node")
|
||||
}
|
||||
|
||||
func (HashedNode) Commitment() *Point {
|
||||
panic("can not get commitment of a hash node")
|
||||
}
|
||||
|
||||
func (HashedNode) GetProofItems(keylist, NodeResolverFn) (*ProofElements, []byte, []Stem, error) {
|
||||
return nil, nil, nil, errors.New("can not get the full path, and there is no proof of absence")
|
||||
}
|
||||
|
||||
func (HashedNode) Serialize() ([]byte, error) {
|
||||
return nil, errSerializeHashedNode
|
||||
}
|
||||
|
||||
func (HashedNode) Copy() VerkleNode {
|
||||
return HashedNode{}
|
||||
}
|
||||
|
||||
func (HashedNode) toDot(parent, path string) string {
|
||||
return fmt.Sprintf("hash%s [label=\"unresolved\"]\n%s -> hash%s\n", path, parent, path)
|
||||
}
|
||||
|
||||
func (HashedNode) setDepth(_ byte) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
func (HashedNode) Hash() *Fr {
|
||||
panic("can not hash a hashed node")
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
// This is free and unencumbered software released into the public domain.
|
||||
//
|
||||
// Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
// distribute this software, either in source code form or as a compiled
|
||||
// binary, for any purpose, commercial or non-commercial, and by any
|
||||
// means.
|
||||
//
|
||||
// In jurisdictions that recognize copyright laws, the author or authors
|
||||
// of this software dedicate any and all copyright interest in the
|
||||
// software to the public domain. We make this dedication for the benefit
|
||||
// of the public at large and to the detriment of our heirs and
|
||||
// successors. We intend this dedication to be an overt act of
|
||||
// relinquishment in perpetuity of all present and future rights to this
|
||||
// software under copyright law.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
// OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
// For more information, please refer to <https://unlicense.org>
|
||||
|
||||
package verkle
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestHashedNodeFuncs(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
e := HashedNode{}
|
||||
err := e.Insert(zeroKeyTest, zeroKeyTest, nil)
|
||||
if err != errInsertIntoHash {
|
||||
t.Fatal("got nil error when inserting into a hashed node")
|
||||
}
|
||||
_, err = e.Delete(zeroKeyTest, nil)
|
||||
if err == nil {
|
||||
t.Fatal("got nil error when deleting from a hashed node")
|
||||
}
|
||||
v, err := e.Get(zeroKeyTest, nil)
|
||||
if err == nil {
|
||||
t.Fatal("got nil error when getting from a hashed node")
|
||||
}
|
||||
if v != nil {
|
||||
t.Fatal("non-nil get from a hashed node")
|
||||
}
|
||||
if _, _, _, err := e.GetProofItems(nil, nil); err == nil {
|
||||
t.Fatal("got nil error when getting proof items from a hashed node")
|
||||
}
|
||||
if _, err := e.Serialize(); err != errSerializeHashedNode {
|
||||
t.Fatal("got nil error when serializing a hashed node")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
// Copyright (C) 2026, Lux Industries Inc. All rights reserved.
|
||||
//
|
||||
// Vendored from github.com/ethereum/go-verkle@v0.2.2 (public domain / Unlicense).
|
||||
// Re-licensed under the Lux Ecosystem License. The package implements the
|
||||
// Verkle trie, its node types (InternalNode, LeafNode, Empty, HashedNode),
|
||||
// IPA configuration, multi-proof generation/verification, encoding, and
|
||||
// JSON serialization. Algorithmic logic mirrors upstream; downstream callers
|
||||
// (luxfi/geth, luxfi/node, luxfi/evm) consume this package directly under
|
||||
// the github.com/luxfi/crypto/verkle import path.
|
||||
|
||||
// This is free and unencumbered software released into the public domain.
|
||||
//
|
||||
// Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
// distribute this software, either in source code form or as a compiled
|
||||
// binary, for any purpose, commercial or non-commercial, and by any
|
||||
// means.
|
||||
//
|
||||
// In jurisdictions that recognize copyright laws, the author or authors
|
||||
// of this software dedicate any and all copyright interest in the
|
||||
// software to the public domain. We make this dedication for the benefit
|
||||
// of the public at large and to the detriment of our heirs and
|
||||
// successors. We intend this dedication to be an overt act of
|
||||
// relinquishment in perpetuity of all present and future rights to this
|
||||
// software under copyright law.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
// OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
// For more information, please refer to <https://unlicense.org>
|
||||
|
||||
package verkle
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/crate-crypto/go-ipa/banderwagon"
|
||||
)
|
||||
|
||||
type (
|
||||
Fr = banderwagon.Fr
|
||||
Point = banderwagon.Element
|
||||
SerializedPoint = []byte
|
||||
SerializedPointCompressed = []byte
|
||||
)
|
||||
|
||||
func FromLEBytes(fr *Fr, data []byte) error {
|
||||
if len(data) > LeafValueSize {
|
||||
return errors.New("data is too long")
|
||||
}
|
||||
var aligned [LeafValueSize]byte
|
||||
copy(aligned[:], data)
|
||||
fr.SetBytesLE(aligned[:])
|
||||
return nil
|
||||
}
|
||||
|
||||
func StemFromLEBytes(fr *Fr, data []byte) error {
|
||||
if len(data) != StemSize {
|
||||
return errors.New("data length must be StemSize")
|
||||
}
|
||||
return FromLEBytes(fr, data)
|
||||
}
|
||||
|
||||
func FromBytes(fr *Fr, data []byte) {
|
||||
var aligned [32]byte
|
||||
copy(aligned[32-len(data):], data)
|
||||
fr.SetBytes(aligned[:])
|
||||
}
|
||||
|
||||
func HashPointToBytes(point *Point) [32]byte {
|
||||
var hashedPoint Fr
|
||||
point.MapToScalarField(&hashedPoint)
|
||||
return hashedPoint.BytesLE()
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package verkle
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"math/big"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFromBytes(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var fr Fr
|
||||
|
||||
var beFortyTwo [8]byte
|
||||
binary.BigEndian.PutUint64(beFortyTwo[:], 42)
|
||||
|
||||
FromBytes(&fr, beFortyTwo[:])
|
||||
|
||||
bi := big.NewInt(0)
|
||||
if fr.ToBigIntRegular(bi).Int64() != 42 {
|
||||
t.Fatalf("got %v, want 42", bi)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,689 @@
|
||||
// Copyright (C) 2026, Lux Industries Inc. All rights reserved.
|
||||
//
|
||||
// Vendored from github.com/ethereum/go-verkle@v0.2.2 (public domain / Unlicense).
|
||||
// Re-licensed under the Lux Ecosystem License. The package implements the
|
||||
// Verkle trie, its node types (InternalNode, LeafNode, Empty, HashedNode),
|
||||
// IPA configuration, multi-proof generation/verification, encoding, and
|
||||
// JSON serialization. Algorithmic logic mirrors upstream; downstream callers
|
||||
// (luxfi/geth, luxfi/node, luxfi/evm) consume this package directly under
|
||||
// the github.com/luxfi/crypto/verkle import path.
|
||||
|
||||
// This is free and unencumbered software released into the public domain.
|
||||
//
|
||||
// Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
// distribute this software, either in source code form or as a compiled
|
||||
// binary, for any purpose, commercial or non-commercial, and by any
|
||||
// means.
|
||||
//
|
||||
// In jurisdictions that recognize copyright laws, the author or authors
|
||||
// of this software dedicate any and all copyright interest in the
|
||||
// software to the public domain. We make this dedication for the benefit
|
||||
// of the public at large and to the detriment of our heirs and
|
||||
// successors. We intend this dedication to be an overt act of
|
||||
// relinquishment in perpetuity of all present and future rights to this
|
||||
// software under copyright law.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
// OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
// For more information, please refer to <https://unlicense.org>
|
||||
|
||||
package verkle
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"unsafe"
|
||||
|
||||
ipa "github.com/crate-crypto/go-ipa"
|
||||
"github.com/crate-crypto/go-ipa/common"
|
||||
)
|
||||
|
||||
const IPA_PROOF_DEPTH = 8
|
||||
|
||||
type IPAProof struct {
|
||||
CL [IPA_PROOF_DEPTH][32]byte `json:"cl"`
|
||||
CR [IPA_PROOF_DEPTH][32]byte `json:"cr"`
|
||||
FinalEvaluation [32]byte `json:"finalEvaluation"`
|
||||
}
|
||||
|
||||
type VerkleProof struct {
|
||||
OtherStems [][StemSize]byte `json:"otherStems"`
|
||||
DepthExtensionPresent []byte `json:"depthExtensionPresent"`
|
||||
CommitmentsByPath [][32]byte `json:"commitmentsByPath"`
|
||||
D [32]byte `json:"d"`
|
||||
IPAProof *IPAProof `json:"ipa_proof"`
|
||||
}
|
||||
|
||||
func (vp *VerkleProof) Copy() *VerkleProof {
|
||||
if vp == nil {
|
||||
return nil
|
||||
}
|
||||
ret := &VerkleProof{
|
||||
OtherStems: make([][StemSize]byte, len(vp.OtherStems)),
|
||||
DepthExtensionPresent: make([]byte, len(vp.DepthExtensionPresent)),
|
||||
CommitmentsByPath: make([][32]byte, len(vp.CommitmentsByPath)),
|
||||
IPAProof: &IPAProof{},
|
||||
}
|
||||
|
||||
copy(ret.OtherStems, vp.OtherStems)
|
||||
copy(ret.DepthExtensionPresent, vp.DepthExtensionPresent)
|
||||
copy(ret.CommitmentsByPath, vp.CommitmentsByPath)
|
||||
|
||||
ret.D = vp.D
|
||||
|
||||
if vp.IPAProof != nil {
|
||||
ret.IPAProof = vp.IPAProof
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func (vp *VerkleProof) Equal(other *VerkleProof) error {
|
||||
if len(vp.OtherStems) != len(other.OtherStems) {
|
||||
return fmt.Errorf("different number of other stems: %d != %d", len(vp.OtherStems), len(other.OtherStems))
|
||||
}
|
||||
for i := range vp.OtherStems {
|
||||
if vp.OtherStems[i] != other.OtherStems[i] {
|
||||
return fmt.Errorf("different other stem: %x != %x", vp.OtherStems[i], other.OtherStems[i])
|
||||
}
|
||||
}
|
||||
if len(vp.DepthExtensionPresent) != len(other.DepthExtensionPresent) {
|
||||
return fmt.Errorf("different number of depth extension present: %d != %d", len(vp.DepthExtensionPresent), len(other.DepthExtensionPresent))
|
||||
}
|
||||
if !bytes.Equal(vp.DepthExtensionPresent, other.DepthExtensionPresent) {
|
||||
return fmt.Errorf("different depth extension present: %x != %x", vp.DepthExtensionPresent, other.DepthExtensionPresent)
|
||||
}
|
||||
if len(vp.CommitmentsByPath) != len(other.CommitmentsByPath) {
|
||||
return fmt.Errorf("different number of commitments by path: %d != %d", len(vp.CommitmentsByPath), len(other.CommitmentsByPath))
|
||||
}
|
||||
for i := range vp.CommitmentsByPath {
|
||||
if vp.CommitmentsByPath[i] != other.CommitmentsByPath[i] {
|
||||
return fmt.Errorf("different commitment by path: %x != %x", vp.CommitmentsByPath[i], other.CommitmentsByPath[i])
|
||||
}
|
||||
}
|
||||
if vp.D != other.D {
|
||||
return fmt.Errorf("different D: %x != %x", vp.D, other.D)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Proof struct {
|
||||
Multipoint *ipa.MultiProof // multipoint argument
|
||||
ExtStatus []byte // the extension status of each stem
|
||||
Cs []*Point // commitments, sorted by their path in the tree
|
||||
PoaStems []Stem // stems proving another stem is absent
|
||||
Keys [][]byte
|
||||
PreValues [][]byte
|
||||
PostValues [][]byte
|
||||
}
|
||||
|
||||
type SuffixStateDiff struct {
|
||||
Suffix byte `json:"suffix"`
|
||||
CurrentValue *[32]byte `json:"currentValue"`
|
||||
NewValue *[32]byte `json:"newValue"`
|
||||
}
|
||||
|
||||
type SuffixStateDiffs []SuffixStateDiff
|
||||
|
||||
type StemStateDiff struct {
|
||||
Stem [StemSize]byte `json:"stem"`
|
||||
SuffixDiffs SuffixStateDiffs `json:"suffixDiffs"`
|
||||
}
|
||||
|
||||
type StateDiff []StemStateDiff
|
||||
|
||||
func (sd StateDiff) Copy() StateDiff {
|
||||
ret := make(StateDiff, len(sd))
|
||||
for i := range sd {
|
||||
copy(ret[i].Stem[:], sd[i].Stem[:])
|
||||
ret[i].SuffixDiffs = make([]SuffixStateDiff, len(sd[i].SuffixDiffs))
|
||||
for j := range sd[i].SuffixDiffs {
|
||||
ret[i].SuffixDiffs[j].Suffix = sd[i].SuffixDiffs[j].Suffix
|
||||
if sd[i].SuffixDiffs[j].CurrentValue != nil {
|
||||
ret[i].SuffixDiffs[j].CurrentValue = &[32]byte{}
|
||||
copy((*ret[i].SuffixDiffs[j].CurrentValue)[:], (*sd[i].SuffixDiffs[j].CurrentValue)[:])
|
||||
}
|
||||
if sd[i].SuffixDiffs[j].NewValue != nil {
|
||||
ret[i].SuffixDiffs[j].NewValue = &[32]byte{}
|
||||
copy((*ret[i].SuffixDiffs[j].NewValue)[:], (*sd[i].SuffixDiffs[j].NewValue)[:])
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (sd StateDiff) Equal(other StateDiff) error {
|
||||
if len(sd) != len(other) {
|
||||
return fmt.Errorf("different number of stem state diffs: %d != %d", len(sd), len(other))
|
||||
}
|
||||
for i := range sd {
|
||||
if sd[i].Stem != other[i].Stem {
|
||||
return fmt.Errorf("different stem: %x != %x", sd[i].Stem, other[i].Stem)
|
||||
}
|
||||
if len(sd[i].SuffixDiffs) != len(other[i].SuffixDiffs) {
|
||||
return fmt.Errorf("different number of suffix state diffs: %d != %d", len(sd[i].SuffixDiffs), len(other[i].SuffixDiffs))
|
||||
}
|
||||
for j := range sd[i].SuffixDiffs {
|
||||
if sd[i].SuffixDiffs[j].Suffix != other[i].SuffixDiffs[j].Suffix {
|
||||
return fmt.Errorf("different suffix: %x != %x", sd[i].SuffixDiffs[j].Suffix, other[i].SuffixDiffs[j].Suffix)
|
||||
}
|
||||
if sd[i].SuffixDiffs[j].CurrentValue != nil && other[i].SuffixDiffs[j].CurrentValue != nil {
|
||||
if *sd[i].SuffixDiffs[j].CurrentValue != *other[i].SuffixDiffs[j].CurrentValue {
|
||||
return fmt.Errorf("different current value: %x != %x", *sd[i].SuffixDiffs[j].CurrentValue, *other[i].SuffixDiffs[j].CurrentValue)
|
||||
}
|
||||
} else if sd[i].SuffixDiffs[j].CurrentValue != nil || other[i].SuffixDiffs[j].CurrentValue != nil {
|
||||
return fmt.Errorf("different current value: %x != %x", sd[i].SuffixDiffs[j].CurrentValue, other[i].SuffixDiffs[j].CurrentValue)
|
||||
}
|
||||
if sd[i].SuffixDiffs[j].NewValue != nil && other[i].SuffixDiffs[j].NewValue != nil {
|
||||
if *sd[i].SuffixDiffs[j].NewValue != *other[i].SuffixDiffs[j].NewValue {
|
||||
return fmt.Errorf("different new value: %x != %x", *sd[i].SuffixDiffs[j].NewValue, *other[i].SuffixDiffs[j].NewValue)
|
||||
}
|
||||
} else if sd[i].SuffixDiffs[j].NewValue != nil || other[i].SuffixDiffs[j].NewValue != nil {
|
||||
return fmt.Errorf("different new value: %x != %x", sd[i].SuffixDiffs[j].NewValue, other[i].SuffixDiffs[j].NewValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetCommitmentsForMultiproof(root VerkleNode, keys [][]byte, resolver NodeResolverFn) (*ProofElements, []byte, []Stem, error) {
|
||||
sort.Sort(keylist(keys))
|
||||
return root.GetProofItems(keylist(keys), resolver)
|
||||
}
|
||||
|
||||
// getProofElementsFromTree factors the logic that is used both in the proving and verification methods. It takes a pre-state
|
||||
// tree and an optional post-state tree, extracts the proof data from them and returns all the items required to build/verify
|
||||
// a proof.
|
||||
func getProofElementsFromTree(preroot, postroot VerkleNode, keys [][]byte, resolver NodeResolverFn) (*ProofElements, []byte, []Stem, [][]byte, error) {
|
||||
// go-ipa won't accept no key as an input, catch this corner case
|
||||
// and return an empty result.
|
||||
if len(keys) == 0 {
|
||||
return nil, nil, nil, nil, errors.New("no key provided for proof")
|
||||
}
|
||||
|
||||
pe, es, poas, err := GetCommitmentsForMultiproof(preroot, keys, resolver)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, fmt.Errorf("error getting pre-state proof data: %w", err)
|
||||
}
|
||||
|
||||
// if a post-state tree is present, merge its proof elements with
|
||||
// those of the pre-state tree, so that they can be proved together.
|
||||
postvals := make([][]byte, len(keys))
|
||||
if postroot != nil {
|
||||
// keys were sorted already in the above GetcommitmentsForMultiproof.
|
||||
// Set the post values, if they are untouched, leave them `nil`
|
||||
for i := range keys {
|
||||
val, err := postroot.Get(keys[i], resolver)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, fmt.Errorf("error getting post-state value for key %x: %w", keys[i], err)
|
||||
}
|
||||
if !bytes.Equal(pe.Vals[i], val) {
|
||||
postvals[i] = val
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// [0:3]: proof elements of the pre-state trie for serialization,
|
||||
// 3: values to be inserted in the post-state trie for serialization
|
||||
return pe, es, poas, postvals, nil
|
||||
}
|
||||
|
||||
func MakeVerkleMultiProof(preroot, postroot VerkleNode, keys [][]byte, resolver NodeResolverFn) (*Proof, []*Point, []byte, []*Fr, error) {
|
||||
pe, es, poas, postvals, err := getProofElementsFromTree(preroot, postroot, keys, resolver)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, fmt.Errorf("get commitments for multiproof: %s", err)
|
||||
}
|
||||
|
||||
cfg := GetConfig()
|
||||
tr := common.NewTranscript("vt")
|
||||
mpArg, err := ipa.CreateMultiProof(tr, cfg.conf, pe.Cis, pe.Fis, pe.Zis)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, fmt.Errorf("creating multiproof: %w", err)
|
||||
}
|
||||
|
||||
// It's wheel-reinvention time again 🎉: reimplement a basic
|
||||
// feature that should be part of the stdlib.
|
||||
// "But golang is a high-productivity language!!!" 🤪
|
||||
// len()-1, because the root is already present in the
|
||||
// parent block, so we don't keep it in the proof.
|
||||
paths := make([]string, 0, len(pe.ByPath)-1)
|
||||
for path := range pe.ByPath {
|
||||
if len(path) > 0 {
|
||||
paths = append(paths, path)
|
||||
}
|
||||
}
|
||||
sort.Strings(paths)
|
||||
cis := make([]*Point, len(pe.ByPath)-1)
|
||||
for i, path := range paths {
|
||||
cis[i] = pe.ByPath[path]
|
||||
}
|
||||
|
||||
proof := &Proof{
|
||||
Multipoint: mpArg,
|
||||
Cs: cis,
|
||||
ExtStatus: es,
|
||||
PoaStems: poas,
|
||||
Keys: keys,
|
||||
PreValues: pe.Vals,
|
||||
PostValues: postvals,
|
||||
}
|
||||
return proof, pe.Cis, pe.Zis, pe.Yis, nil
|
||||
}
|
||||
|
||||
// verifyVerkleProofWithPreState takes a proof and a trusted tree root and verifies that the proof is valid.
|
||||
func verifyVerkleProofWithPreState(proof *Proof, preroot VerkleNode) error {
|
||||
pe, _, _, _, err := getProofElementsFromTree(preroot, nil, proof.Keys, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting proof elements: %w", err)
|
||||
}
|
||||
|
||||
if ok, err := verifyVerkleProof(proof, pe.Cis, pe.Zis, pe.Yis, GetConfig()); !ok || err != nil {
|
||||
return fmt.Errorf("error verifying proof: verifies=%v, error=%w", ok, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func verifyVerkleProof(proof *Proof, Cs []*Point, indices []uint8, ys []*Fr, tc *Config) (bool, error) {
|
||||
tr := common.NewTranscript("vt")
|
||||
return ipa.CheckMultiProof(tr, tc.conf, proof.Multipoint, Cs, ys, indices)
|
||||
}
|
||||
|
||||
// SerializeProof serializes the proof in the rust-verkle format:
|
||||
// * len(Proof of absence stem) || Proof of absence stems
|
||||
// * len(depths) || serialize(depth || ext statusi)
|
||||
// * len(commitments) || serialize(commitment)
|
||||
// * Multipoint proof
|
||||
// it also returns the serialized keys and values
|
||||
func SerializeProof(proof *Proof) (*VerkleProof, StateDiff, error) {
|
||||
otherstems := make([][StemSize]byte, len(proof.PoaStems))
|
||||
for i, stem := range proof.PoaStems {
|
||||
copy(otherstems[i][:], stem)
|
||||
}
|
||||
|
||||
cbp := make([][32]byte, len(proof.Cs))
|
||||
for i, C := range proof.Cs {
|
||||
serialized := C.Bytes()
|
||||
copy(cbp[i][:], serialized[:])
|
||||
}
|
||||
|
||||
var cls, crs [IPA_PROOF_DEPTH][32]byte
|
||||
for i := 0; i < IPA_PROOF_DEPTH; i++ {
|
||||
|
||||
l := proof.Multipoint.IPA.L[i].Bytes()
|
||||
copy(cls[i][:], l[:])
|
||||
r := proof.Multipoint.IPA.R[i].Bytes()
|
||||
copy(crs[i][:], r[:])
|
||||
}
|
||||
|
||||
var stemdiff *StemStateDiff
|
||||
var statediff StateDiff
|
||||
for i, key := range proof.Keys {
|
||||
stem := KeyToStem(key)
|
||||
if stemdiff == nil || !bytes.Equal(stemdiff.Stem[:], stem) {
|
||||
statediff = append(statediff, StemStateDiff{})
|
||||
stemdiff = &statediff[len(statediff)-1]
|
||||
copy(stemdiff.Stem[:], stem)
|
||||
}
|
||||
stemdiff.SuffixDiffs = append(stemdiff.SuffixDiffs, SuffixStateDiff{Suffix: key[StemSize]})
|
||||
newsd := &stemdiff.SuffixDiffs[len(stemdiff.SuffixDiffs)-1]
|
||||
|
||||
var valueLen = len(proof.PreValues[i])
|
||||
switch valueLen {
|
||||
case 0:
|
||||
// null value
|
||||
case 32:
|
||||
newsd.CurrentValue = (*[32]byte)(proof.PreValues[i])
|
||||
default:
|
||||
var aligned [32]byte
|
||||
copy(aligned[:valueLen], proof.PreValues[i])
|
||||
newsd.CurrentValue = (*[32]byte)(unsafe.Pointer(&aligned[0]))
|
||||
}
|
||||
|
||||
valueLen = len(proof.PostValues[i])
|
||||
switch valueLen {
|
||||
case 0:
|
||||
// null value
|
||||
case 32:
|
||||
newsd.NewValue = (*[32]byte)(proof.PostValues[i])
|
||||
default:
|
||||
// TODO remove usage of unsafe
|
||||
var aligned [32]byte
|
||||
copy(aligned[:valueLen], proof.PostValues[i])
|
||||
newsd.NewValue = (*[32]byte)(unsafe.Pointer(&aligned[0]))
|
||||
}
|
||||
}
|
||||
|
||||
return &VerkleProof{
|
||||
OtherStems: otherstems,
|
||||
DepthExtensionPresent: proof.ExtStatus,
|
||||
CommitmentsByPath: cbp,
|
||||
D: proof.Multipoint.D.Bytes(),
|
||||
IPAProof: &IPAProof{
|
||||
CL: cls,
|
||||
CR: crs,
|
||||
FinalEvaluation: proof.Multipoint.IPA.A_scalar.Bytes(),
|
||||
},
|
||||
}, statediff, nil
|
||||
}
|
||||
|
||||
// DeserializeProof deserializes the proof found in blocks, into a format that
|
||||
// can be used to rebuild a stateless version of the tree.
|
||||
func DeserializeProof(vp *VerkleProof, statediff StateDiff) (*Proof, error) {
|
||||
var (
|
||||
poaStems []Stem
|
||||
keys [][]byte
|
||||
prevalues, postvalues [][]byte
|
||||
extStatus []byte
|
||||
commitments []*Point
|
||||
multipoint ipa.MultiProof
|
||||
)
|
||||
|
||||
poaStems = make([]Stem, len(vp.OtherStems))
|
||||
for i, poaStem := range vp.OtherStems {
|
||||
poaStems[i] = make([]byte, len(poaStem))
|
||||
copy(poaStems[i], poaStem[:])
|
||||
}
|
||||
|
||||
extStatus = vp.DepthExtensionPresent
|
||||
|
||||
commitments = make([]*Point, len(vp.CommitmentsByPath))
|
||||
for i, commitmentBytes := range vp.CommitmentsByPath {
|
||||
var commitment Point
|
||||
if err := commitment.SetBytes(commitmentBytes[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
commitments[i] = &commitment
|
||||
}
|
||||
|
||||
if err := multipoint.D.SetBytes(vp.D[:]); err != nil {
|
||||
return nil, fmt.Errorf("setting D: %w", err)
|
||||
}
|
||||
multipoint.IPA.A_scalar.SetBytes(vp.IPAProof.FinalEvaluation[:])
|
||||
multipoint.IPA.L = make([]Point, IPA_PROOF_DEPTH)
|
||||
for i, b := range vp.IPAProof.CL {
|
||||
if err := multipoint.IPA.L[i].SetBytes(b[:]); err != nil {
|
||||
return nil, fmt.Errorf("setting L[%d]: %w", i, err)
|
||||
}
|
||||
}
|
||||
multipoint.IPA.R = make([]Point, IPA_PROOF_DEPTH)
|
||||
for i, b := range vp.IPAProof.CR {
|
||||
if err := multipoint.IPA.R[i].SetBytes(b[:]); err != nil {
|
||||
return nil, fmt.Errorf("setting R[%d]: %w", i, err)
|
||||
}
|
||||
}
|
||||
|
||||
// turn statediff into keys and values
|
||||
for _, stemdiff := range statediff {
|
||||
for _, suffixdiff := range stemdiff.SuffixDiffs {
|
||||
var k [32]byte
|
||||
copy(k[:StemSize], stemdiff.Stem[:])
|
||||
k[StemSize] = suffixdiff.Suffix
|
||||
keys = append(keys, k[:])
|
||||
if suffixdiff.CurrentValue != nil {
|
||||
prevalues = append(prevalues, suffixdiff.CurrentValue[:])
|
||||
} else {
|
||||
prevalues = append(prevalues, nil)
|
||||
}
|
||||
|
||||
if suffixdiff.NewValue != nil {
|
||||
postvalues = append(postvalues, suffixdiff.NewValue[:])
|
||||
} else {
|
||||
postvalues = append(postvalues, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
proof := Proof{
|
||||
&multipoint,
|
||||
extStatus,
|
||||
commitments,
|
||||
poaStems,
|
||||
keys,
|
||||
prevalues,
|
||||
postvalues,
|
||||
}
|
||||
return &proof, nil
|
||||
}
|
||||
|
||||
type stemInfo struct {
|
||||
depth byte
|
||||
stemType byte
|
||||
has_c1, has_c2 bool
|
||||
values map[byte][]byte
|
||||
stem []byte
|
||||
}
|
||||
|
||||
// PreStateTreeFromProof builds a stateless prestate tree from the proof.
|
||||
func PreStateTreeFromProof(proof *Proof, rootC *Point) (VerkleNode, error) { // skipcq: GO-R1005
|
||||
if len(proof.Keys) != len(proof.PreValues) {
|
||||
return nil, fmt.Errorf("incompatible number of keys and pre-values: %d != %d", len(proof.Keys), len(proof.PreValues))
|
||||
}
|
||||
if len(proof.Keys) != len(proof.PostValues) {
|
||||
return nil, fmt.Errorf("incompatible number of keys and post-values: %d != %d", len(proof.Keys), len(proof.PostValues))
|
||||
}
|
||||
stems := make([][]byte, 0, len(proof.Keys))
|
||||
for _, k := range proof.Keys {
|
||||
stem := KeyToStem(k)
|
||||
if len(stems) == 0 || !bytes.Equal(stems[len(stems)-1], stem) {
|
||||
stems = append(stems, stem)
|
||||
}
|
||||
}
|
||||
if len(stems) != len(proof.ExtStatus) {
|
||||
return nil, fmt.Errorf("invalid number of stems and extension statuses: %d != %d", len(stems), len(proof.ExtStatus))
|
||||
}
|
||||
var (
|
||||
info = map[string]stemInfo{}
|
||||
paths [][]byte
|
||||
err error
|
||||
poas = proof.PoaStems
|
||||
)
|
||||
|
||||
// The proof of absence stems must be sorted. If that isn't the case, the proof is invalid.
|
||||
if !sort.IsSorted(bytesSlice(proof.PoaStems)) {
|
||||
return nil, fmt.Errorf("proof of absence stems are not sorted")
|
||||
}
|
||||
|
||||
// We build a cache of paths that have a presence extension status.
|
||||
pathsWithExtPresent := map[string]struct{}{}
|
||||
i := 0
|
||||
for _, es := range proof.ExtStatus {
|
||||
if es&3 == extStatusPresent {
|
||||
pathsWithExtPresent[string(stems[i][:es>>3])] = struct{}{}
|
||||
}
|
||||
i++
|
||||
}
|
||||
|
||||
// assign one or more stem to each stem info
|
||||
for i, es := range proof.ExtStatus {
|
||||
si := stemInfo{
|
||||
depth: es >> 3,
|
||||
stemType: es & 3,
|
||||
}
|
||||
path := stems[i][:si.depth]
|
||||
switch si.stemType {
|
||||
case extStatusAbsentEmpty:
|
||||
// All keys that are part of a proof of absence, must contain empty
|
||||
// prestate values. If that isn't the case, the proof is invalid.
|
||||
for j := range proof.Keys { // TODO: DoS risk, use map or binary search.
|
||||
if bytes.HasPrefix(proof.Keys[j], stems[i]) && proof.PreValues[j] != nil {
|
||||
return nil, fmt.Errorf("proof of absence (empty) stem %x has a value", si.stem)
|
||||
}
|
||||
}
|
||||
case extStatusAbsentOther:
|
||||
// All keys that are part of a proof of absence, must contain empty
|
||||
// prestate values. If that isn't the case, the proof is invalid.
|
||||
for j := range proof.Keys { // TODO: DoS risk, use map or binary search.
|
||||
if bytes.HasPrefix(proof.Keys[j], stems[i]) && proof.PreValues[j] != nil {
|
||||
return nil, fmt.Errorf("proof of absence (other) stem %x has a value", si.stem)
|
||||
}
|
||||
}
|
||||
|
||||
// For this absent path, we must first check if this path contains a proof of presence.
|
||||
// If that is the case, we don't have to do anything since the corresponding leaf will be
|
||||
// constructed by that extension status (already processed or to be processed).
|
||||
// In other case, we should get the stem from the list of proof of absence stems.
|
||||
if _, ok := pathsWithExtPresent[string(path)]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// Note that this path doesn't have proof of presence (previous if check above), but
|
||||
// it can have multiple proof of absence. If a previous proof of absence had already
|
||||
// created the stemInfo for this path, we don't have to do anything.
|
||||
if _, ok := info[string(path)]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
si.stem = poas[0]
|
||||
poas = poas[1:]
|
||||
case extStatusPresent:
|
||||
si.values = map[byte][]byte{}
|
||||
si.stem = stems[i]
|
||||
for j, k := range proof.Keys { // TODO: DoS risk, use map or binary search.
|
||||
if bytes.Equal(KeyToStem(k), si.stem) {
|
||||
si.values[k[StemSize]] = proof.PreValues[j]
|
||||
si.has_c1 = si.has_c1 || (k[StemSize] < 128)
|
||||
si.has_c2 = si.has_c2 || (k[StemSize] >= 128)
|
||||
}
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid extension status: %d", si.stemType)
|
||||
}
|
||||
info[string(path)] = si
|
||||
paths = append(paths, path)
|
||||
}
|
||||
|
||||
if len(poas) != 0 {
|
||||
return nil, fmt.Errorf("not all proof of absence stems were used: %d", len(poas))
|
||||
}
|
||||
|
||||
root := NewStatelessInternal(0, rootC).(*InternalNode)
|
||||
comms := proof.Cs
|
||||
for _, p := range paths {
|
||||
// NOTE: the reconstructed tree won't tell the
|
||||
// difference between leaves missing from view
|
||||
// and absent leaves. This is enough for verification
|
||||
// but not for block validation.
|
||||
values := make([][]byte, NodeWidth)
|
||||
for i, k := range proof.Keys {
|
||||
if len(proof.PreValues[i]) == 0 {
|
||||
// Skip the nil keys, they are here to prove
|
||||
// an absence.
|
||||
continue
|
||||
}
|
||||
|
||||
if bytes.Equal(KeyToStem(k), info[string(p)].stem) {
|
||||
values[k[StemSize]] = proof.PreValues[i]
|
||||
}
|
||||
}
|
||||
comms, err = root.CreatePath(p, info[string(p)], comms, values)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return root, nil
|
||||
}
|
||||
|
||||
// PostStateTreeFromProof uses the pre-state trie and the list of updated values
|
||||
// to produce the stateless post-state trie.
|
||||
func PostStateTreeFromStateDiff(preroot VerkleNode, statediff StateDiff) (VerkleNode, error) {
|
||||
postroot := preroot.Copy()
|
||||
|
||||
for _, stemstatediff := range statediff {
|
||||
var (
|
||||
values = make([][]byte, NodeWidth)
|
||||
overwrites bool
|
||||
)
|
||||
|
||||
for _, suffixdiff := range stemstatediff.SuffixDiffs {
|
||||
if /* len(suffixdiff.NewValue) > 0 - this only works for a slice */ suffixdiff.NewValue != nil {
|
||||
// if this value is non-nil, it means InsertValuesAtStem should be
|
||||
// called, otherwise, skip updating the tree.
|
||||
overwrites = true
|
||||
values[suffixdiff.Suffix] = suffixdiff.NewValue[:]
|
||||
}
|
||||
}
|
||||
|
||||
if overwrites {
|
||||
var stem [StemSize]byte
|
||||
copy(stem[:StemSize], stemstatediff.Stem[:])
|
||||
if err := postroot.(*InternalNode).InsertValuesAtStem(stem[:], values, nil); err != nil {
|
||||
return nil, fmt.Errorf("error overwriting value in post state: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
postroot.Commit()
|
||||
|
||||
return postroot, nil
|
||||
}
|
||||
|
||||
type bytesSlice []Stem
|
||||
|
||||
func (x bytesSlice) Len() int { return len(x) }
|
||||
func (x bytesSlice) Less(i, j int) bool { return bytes.Compare(x[i], x[j]) < 0 }
|
||||
func (x bytesSlice) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
|
||||
|
||||
// Verify is the API function that verifies a verkle proofs as found in a block/execution payload.
|
||||
func Verify(vp *VerkleProof, preStateRoot []byte, postStateRoot []byte, statediff StateDiff) error {
|
||||
|
||||
proof, err := DeserializeProof(vp, statediff)
|
||||
if err != nil {
|
||||
return fmt.Errorf("verkle proof deserialization error: %w", err)
|
||||
}
|
||||
|
||||
rootC := new(Point)
|
||||
if err := rootC.SetBytes(preStateRoot); err != nil {
|
||||
return fmt.Errorf("error setting prestate root: %w", err)
|
||||
}
|
||||
pretree, err := PreStateTreeFromProof(proof, rootC)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error rebuilding the pre-tree from proof: %w", err)
|
||||
}
|
||||
// TODO this should not be necessary, remove it
|
||||
// after the new proof generation code has stabilized.
|
||||
for _, stemdiff := range statediff {
|
||||
for _, suffixdiff := range stemdiff.SuffixDiffs {
|
||||
var key [32]byte
|
||||
copy(key[:31], stemdiff.Stem[:])
|
||||
key[31] = suffixdiff.Suffix
|
||||
|
||||
val, err := pretree.Get(key[:], nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not find key %x in tree rebuilt from proof: %w", key, err)
|
||||
}
|
||||
if len(val) > 0 {
|
||||
if !bytes.Equal(val, suffixdiff.CurrentValue[:]) {
|
||||
return fmt.Errorf("could not find correct value at %x in tree rebuilt from proof: %x != %x", key, val, *suffixdiff.CurrentValue)
|
||||
}
|
||||
} else {
|
||||
if suffixdiff.CurrentValue != nil && len(suffixdiff.CurrentValue) != 0 {
|
||||
return fmt.Errorf("could not find correct value at %x in tree rebuilt from proof: %x != %x", key, val, *suffixdiff.CurrentValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: this is necessary to verify that the post-values are the correct ones.
|
||||
// But all this can be avoided with a even faster way. The EVM block execution can
|
||||
// keep track of the written keys, and compare that list with this post-values list.
|
||||
// This can avoid regenerating the post-tree which is somewhat expensive.
|
||||
posttree, err := PostStateTreeFromStateDiff(pretree, statediff)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error rebuilding the post-tree from proof: %w", err)
|
||||
}
|
||||
regeneratedPostTreeRoot := posttree.Commitment().Bytes()
|
||||
if !bytes.Equal(regeneratedPostTreeRoot[:], postStateRoot) {
|
||||
return fmt.Errorf("post tree root mismatch: %x != %x", regeneratedPostTreeRoot, postStateRoot)
|
||||
}
|
||||
|
||||
return verifyVerkleProofWithPreState(proof, pretree)
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
// Copyright (C) 2026, Lux Industries Inc. All rights reserved.
|
||||
//
|
||||
// Vendored from github.com/ethereum/go-verkle@v0.2.2 (public domain / Unlicense).
|
||||
// Re-licensed under the Lux Ecosystem License. The package implements the
|
||||
// Verkle trie, its node types (InternalNode, LeafNode, Empty, HashedNode),
|
||||
// IPA configuration, multi-proof generation/verification, encoding, and
|
||||
// JSON serialization. Algorithmic logic mirrors upstream; downstream callers
|
||||
// (luxfi/geth, luxfi/node, luxfi/evm) consume this package directly under
|
||||
// the github.com/luxfi/crypto/verkle import path.
|
||||
|
||||
// This is free and unencumbered software released into the public domain.
|
||||
//
|
||||
// Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
// distribute this software, either in source code form or as a compiled
|
||||
// binary, for any purpose, commercial or non-commercial, and by any
|
||||
// means.
|
||||
//
|
||||
// In jurisdictions that recognize copyright laws, the author or authors
|
||||
// of this software dedicate any and all copyright interest in the
|
||||
// software to the public domain. We make this dedication for the benefit
|
||||
// of the public at large and to the detriment of our heirs and
|
||||
// successors. We intend this dedication to be an overt act of
|
||||
// relinquishment in perpetuity of all present and future rights to this
|
||||
// software under copyright law.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
// OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
// For more information, please refer to <https://unlicense.org>
|
||||
|
||||
package verkle
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// HexToPrefixedString turns a byte slice into its hex representation
|
||||
// and prefixes it with `0x`.
|
||||
func HexToPrefixedString(data []byte) string {
|
||||
return "0x" + hex.EncodeToString(data)
|
||||
}
|
||||
|
||||
// PrefixedHexStringToBytes does the opposite of HexToPrefixedString.
|
||||
func PrefixedHexStringToBytes(input string) ([]byte, error) {
|
||||
if input[0:2] == "0x" {
|
||||
input = input[2:]
|
||||
}
|
||||
return hex.DecodeString(input)
|
||||
}
|
||||
|
||||
type ipaproofMarshaller struct {
|
||||
CL [IPA_PROOF_DEPTH]string `json:"cl"`
|
||||
CR [IPA_PROOF_DEPTH]string `json:"cr"`
|
||||
FinalEvaluation string `json:"finalEvaluation"`
|
||||
}
|
||||
|
||||
func (ipp *IPAProof) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(&ipaproofMarshaller{
|
||||
CL: [IPA_PROOF_DEPTH]string{
|
||||
HexToPrefixedString(ipp.CL[0][:]),
|
||||
HexToPrefixedString(ipp.CL[1][:]),
|
||||
HexToPrefixedString(ipp.CL[2][:]),
|
||||
HexToPrefixedString(ipp.CL[3][:]),
|
||||
HexToPrefixedString(ipp.CL[4][:]),
|
||||
HexToPrefixedString(ipp.CL[5][:]),
|
||||
HexToPrefixedString(ipp.CL[6][:]),
|
||||
HexToPrefixedString(ipp.CL[7][:]),
|
||||
},
|
||||
CR: [IPA_PROOF_DEPTH]string{
|
||||
HexToPrefixedString(ipp.CR[0][:]),
|
||||
HexToPrefixedString(ipp.CR[1][:]),
|
||||
HexToPrefixedString(ipp.CR[2][:]),
|
||||
HexToPrefixedString(ipp.CR[3][:]),
|
||||
HexToPrefixedString(ipp.CR[4][:]),
|
||||
HexToPrefixedString(ipp.CR[5][:]),
|
||||
HexToPrefixedString(ipp.CR[6][:]),
|
||||
HexToPrefixedString(ipp.CR[7][:]),
|
||||
},
|
||||
FinalEvaluation: HexToPrefixedString(ipp.FinalEvaluation[:]),
|
||||
})
|
||||
}
|
||||
|
||||
func (ipp *IPAProof) UnmarshalJSON(data []byte) error {
|
||||
aux := &ipaproofMarshaller{}
|
||||
|
||||
if err := json.Unmarshal(data, &aux); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(aux.FinalEvaluation) != 64 && len(aux.FinalEvaluation) != 66 {
|
||||
return fmt.Errorf("invalid hex string for final evaluation: %s", aux.FinalEvaluation)
|
||||
}
|
||||
|
||||
currentValueBytes, err := PrefixedHexStringToBytes(aux.FinalEvaluation)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error decoding hex string for current value: %v", err)
|
||||
}
|
||||
copy(ipp.FinalEvaluation[:], currentValueBytes)
|
||||
|
||||
for i := range ipp.CL {
|
||||
if len(aux.CL[i]) != 64 && len(aux.CL[i]) != 66 {
|
||||
return fmt.Errorf("invalid hex string for CL[%d]: %s", i, aux.CL[i])
|
||||
}
|
||||
val, err := PrefixedHexStringToBytes(aux.CL[i])
|
||||
if err != nil {
|
||||
return fmt.Errorf("error decoding hex string for CL[%d]: %s", i, aux.CL[i])
|
||||
}
|
||||
copy(ipp.CL[i][:], val)
|
||||
if len(aux.CR[i]) != 64 && len(aux.CR[i]) != 66 {
|
||||
return fmt.Errorf("invalid hex string for CR[%d]: %s", i, aux.CR[i])
|
||||
}
|
||||
val, err = PrefixedHexStringToBytes(aux.CR[i])
|
||||
if err != nil {
|
||||
return fmt.Errorf("error decoding hex string for CR[%d]: %s", i, aux.CR[i])
|
||||
}
|
||||
copy(ipp.CR[i][:], val)
|
||||
}
|
||||
copy(ipp.FinalEvaluation[:], currentValueBytes)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type verkleProofMarshaller struct {
|
||||
OtherStems []string `json:"otherStems"`
|
||||
DepthExtensionPresent string `json:"depthExtensionPresent"`
|
||||
CommitmentsByPath []string `json:"commitmentsByPath"`
|
||||
D string `json:"d"`
|
||||
IPAProof *IPAProof `json:"ipaProof"`
|
||||
}
|
||||
|
||||
func (vp *VerkleProof) MarshalJSON() ([]byte, error) {
|
||||
aux := &verkleProofMarshaller{
|
||||
OtherStems: make([]string, len(vp.OtherStems)),
|
||||
DepthExtensionPresent: HexToPrefixedString(vp.DepthExtensionPresent),
|
||||
CommitmentsByPath: make([]string, len(vp.CommitmentsByPath)),
|
||||
D: HexToPrefixedString(vp.D[:]),
|
||||
IPAProof: vp.IPAProof,
|
||||
}
|
||||
|
||||
for i, s := range vp.OtherStems {
|
||||
aux.OtherStems[i] = HexToPrefixedString(s[:])
|
||||
}
|
||||
for i, c := range vp.CommitmentsByPath {
|
||||
aux.CommitmentsByPath[i] = HexToPrefixedString(c[:])
|
||||
}
|
||||
return json.Marshal(aux)
|
||||
}
|
||||
|
||||
func (vp *VerkleProof) UnmarshalJSON(data []byte) error {
|
||||
var aux verkleProofMarshaller
|
||||
err := json.Unmarshal(data, &aux)
|
||||
if err != nil {
|
||||
return fmt.Errorf("verkle proof unmarshal error: %w", err)
|
||||
}
|
||||
|
||||
vp.DepthExtensionPresent, err = PrefixedHexStringToBytes(aux.DepthExtensionPresent)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error decoding hex string for depth and extension present: %v", err)
|
||||
}
|
||||
|
||||
vp.CommitmentsByPath = make([][32]byte, len(aux.CommitmentsByPath))
|
||||
for i, c := range aux.CommitmentsByPath {
|
||||
val, err := PrefixedHexStringToBytes(c)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error decoding hex string for commitment #%d: %w", i, err)
|
||||
}
|
||||
copy(vp.CommitmentsByPath[i][:], val)
|
||||
}
|
||||
|
||||
currentValueBytes, err := PrefixedHexStringToBytes(aux.D)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error decoding hex string for D: %w", err)
|
||||
}
|
||||
copy(vp.D[:], currentValueBytes)
|
||||
|
||||
vp.OtherStems = make([][StemSize]byte, len(aux.OtherStems))
|
||||
for i, c := range aux.OtherStems {
|
||||
val, err := PrefixedHexStringToBytes(c)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error decoding hex string for other stem #%d: %w", i, err)
|
||||
}
|
||||
copy(vp.OtherStems[i][:], val)
|
||||
}
|
||||
|
||||
vp.IPAProof = aux.IPAProof
|
||||
return nil
|
||||
}
|
||||
|
||||
type stemStateDiffMarshaller struct {
|
||||
Stem string `json:"stem"`
|
||||
SuffixDiffs SuffixStateDiffs `json:"suffixDiffs"`
|
||||
}
|
||||
|
||||
func (ssd StemStateDiff) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(&stemStateDiffMarshaller{
|
||||
Stem: HexToPrefixedString(ssd.Stem[:]),
|
||||
SuffixDiffs: ssd.SuffixDiffs,
|
||||
})
|
||||
}
|
||||
|
||||
func (ssd *StemStateDiff) UnmarshalJSON(data []byte) error {
|
||||
var aux stemStateDiffMarshaller
|
||||
if err := json.Unmarshal(data, &aux); err != nil {
|
||||
return fmt.Errorf("stemdiff unmarshal error: %w", err)
|
||||
}
|
||||
|
||||
stem, err := PrefixedHexStringToBytes(aux.Stem)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid hex string for stem: %w", err)
|
||||
}
|
||||
*ssd = StemStateDiff{
|
||||
SuffixDiffs: aux.SuffixDiffs,
|
||||
}
|
||||
copy(ssd.Stem[:], stem)
|
||||
return nil
|
||||
}
|
||||
|
||||
type suffixStateDiffMarshaller struct {
|
||||
Suffix byte `json:"suffix"`
|
||||
CurrentValue *string `json:"currentValue"`
|
||||
NewValue *string `json:"newValue"`
|
||||
}
|
||||
|
||||
func (ssd SuffixStateDiff) MarshalJSON() ([]byte, error) {
|
||||
var cvstr, nvstr *string
|
||||
if ssd.CurrentValue != nil {
|
||||
tempstr := HexToPrefixedString(ssd.CurrentValue[:])
|
||||
cvstr = &tempstr
|
||||
}
|
||||
if ssd.NewValue != nil {
|
||||
tempstr := HexToPrefixedString(ssd.NewValue[:])
|
||||
nvstr = &tempstr
|
||||
}
|
||||
return json.Marshal(&suffixStateDiffMarshaller{
|
||||
Suffix: ssd.Suffix,
|
||||
CurrentValue: cvstr,
|
||||
NewValue: nvstr,
|
||||
})
|
||||
}
|
||||
|
||||
func (ssd *SuffixStateDiff) UnmarshalJSON(data []byte) error {
|
||||
aux := &suffixStateDiffMarshaller{}
|
||||
|
||||
if err := json.Unmarshal(data, &aux); err != nil {
|
||||
return fmt.Errorf("suffix diff unmarshal error: %w", err)
|
||||
}
|
||||
|
||||
if aux.CurrentValue != nil && len(*aux.CurrentValue) != 64 && len(*aux.CurrentValue) != 0 && len(*aux.CurrentValue) != 66 {
|
||||
return fmt.Errorf("invalid hex string for current value: %s", *aux.CurrentValue)
|
||||
}
|
||||
|
||||
*ssd = SuffixStateDiff{
|
||||
Suffix: aux.Suffix,
|
||||
}
|
||||
|
||||
if aux.CurrentValue != nil && len(*aux.CurrentValue) != 0 {
|
||||
currentValueBytes, err := PrefixedHexStringToBytes(*aux.CurrentValue)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error decoding hex string for current value: %v", err)
|
||||
}
|
||||
|
||||
ssd.CurrentValue = &[32]byte{}
|
||||
copy(ssd.CurrentValue[:], currentValueBytes)
|
||||
}
|
||||
|
||||
if aux.NewValue != nil && len(*aux.NewValue) != 0 {
|
||||
newValueBytes, err := PrefixedHexStringToBytes(*aux.NewValue)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error decoding hex string for current value: %v", err)
|
||||
}
|
||||
|
||||
ssd.NewValue = &[32]byte{}
|
||||
copy(ssd.NewValue[:], newValueBytes)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package verkle
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestJSONDeserialization(t *testing.T) {
|
||||
str := `{
|
||||
"stem": "0x97233a822ee74c294ccec8e4e0c65106b374d4423d5d09236d0f6c6647e185",
|
||||
"suffixDiffs": [
|
||||
{ "suffix": 0, "currentValue": null, "newValue": null },
|
||||
{ "suffix": 1, "currentValue": null, "newValue": null },
|
||||
{ "suffix": 2, "currentValue": null, "newValue": null },
|
||||
{ "suffix": 3, "currentValue": null, "newValue": null },
|
||||
{ "suffix": 4, "currentValue": null, "newValue": null }
|
||||
]
|
||||
}`
|
||||
var statediff StemStateDiff
|
||||
err := json.Unmarshal([]byte(str), &statediff)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
+1922
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,320 @@
|
||||
// This is free and unencumbered software released into the public domain.
|
||||
//
|
||||
// Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
// distribute this software, either in source code form or as a compiled
|
||||
// binary, for any purpose, commercial or non-commercial, and by any
|
||||
// means.
|
||||
//
|
||||
// In jurisdictions that recognize copyright laws, the author or authors
|
||||
// of this software dedicate any and all copyright interest in the
|
||||
// software to the public domain. We make this dedication for the benefit
|
||||
// of the public at large and to the detriment of our heirs and
|
||||
// successors. We intend this dedication to be an overt act of
|
||||
// relinquishment in perpetuity of all present and future rights to this
|
||||
// software under copyright law.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
// OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
// For more information, please refer to <https://unlicense.org>
|
||||
|
||||
package verkle
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/crate-crypto/go-ipa/banderwagon"
|
||||
)
|
||||
|
||||
var identity *Point
|
||||
|
||||
func init() {
|
||||
var id Point
|
||||
id.SetIdentity()
|
||||
identity = &id
|
||||
}
|
||||
|
||||
func extensionAndSuffixOneKey(t *testing.T, key, value []byte, ret *Point) {
|
||||
var (
|
||||
v Fr
|
||||
vs [2]Fr
|
||||
cfg = GetConfig()
|
||||
srs = cfg.conf.SRS
|
||||
stemComm1, stemComm3, stemComm2 Point
|
||||
t1, t2, c1 Point
|
||||
)
|
||||
stemComm0 := srs[0]
|
||||
err := StemFromLEBytes(&v, KeyToStem(key))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
stemComm1.ScalarMul(&srs[1], &v)
|
||||
|
||||
if err := leafToComms(vs[:], value); err != nil {
|
||||
t.Fatalf("leafToComms failed: %s", err)
|
||||
}
|
||||
c1.Add(t1.ScalarMul(&srs[2*key[StemSize]], &vs[0]), t2.ScalarMul(&srs[2*key[StemSize]+1], &vs[1]))
|
||||
c1.MapToScalarField(&v)
|
||||
stemComm2.ScalarMul(&srs[2], &v)
|
||||
|
||||
v.SetZero()
|
||||
stemComm3.ScalarMul(&srs[3], &v)
|
||||
|
||||
t1.Add(&stemComm0, &stemComm1)
|
||||
t2.Add(&stemComm2, &stemComm3)
|
||||
ret.Add(&t1, &t2)
|
||||
}
|
||||
|
||||
func TestInsertKey0Value0(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var (
|
||||
expected Fr
|
||||
root = New()
|
||||
expectedP Point
|
||||
cfg = GetConfig()
|
||||
srs = cfg.conf.SRS
|
||||
)
|
||||
|
||||
if err := root.Insert(zeroKeyTest, zeroKeyTest, nil); err != nil {
|
||||
t.Fatalf("insert failed: %s", err)
|
||||
}
|
||||
comm := root.Commit()
|
||||
|
||||
extensionAndSuffixOneKey(t, zeroKeyTest, zeroKeyTest, &expectedP)
|
||||
|
||||
if expectedP.Equal(identity) {
|
||||
t.Fatal("commitment is identity")
|
||||
}
|
||||
|
||||
expectedP.MapToScalarField(&expected)
|
||||
expectedP.ScalarMul(&srs[0], &expected)
|
||||
|
||||
if !comm.Equal(&expectedP) {
|
||||
t.Fatalf("invalid root commitment %v != %v", comm, &expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInsertKey1Value1(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var (
|
||||
v, expected Fr
|
||||
root = New()
|
||||
expectedP Point
|
||||
cfg = GetConfig()
|
||||
srs = cfg.conf.SRS
|
||||
)
|
||||
key := []byte{
|
||||
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
|
||||
25, 26, 27, 28, 29, 30, 31, 32,
|
||||
}
|
||||
if err := root.Insert(key, key, nil); err != nil {
|
||||
t.Fatalf("insert failed: %s", err)
|
||||
}
|
||||
comm := root.Commit()
|
||||
|
||||
extensionAndSuffixOneKey(t, key, key, &expectedP)
|
||||
expectedP.MapToScalarField(&v)
|
||||
expectedP.ScalarMul(&srs[1], &v)
|
||||
|
||||
if expectedP.Equal(identity) {
|
||||
t.Fatal("commitment is identity")
|
||||
}
|
||||
|
||||
if !comm.Equal(&expectedP) {
|
||||
t.Fatalf("invalid root commitment %v != %v", comm, &expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInsertSameStemTwoLeaves(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var (
|
||||
v, expected Fr
|
||||
vs [2]Fr
|
||||
root = New()
|
||||
expectedP, c1, c2, t1, t2 Point
|
||||
stemComm1, stemComm3, stemComm2 Point
|
||||
cfg = GetConfig()
|
||||
srs = cfg.conf.SRS
|
||||
)
|
||||
key_a := []byte{
|
||||
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
|
||||
25, 26, 27, 28, 29, 30, 31, 32,
|
||||
}
|
||||
key_b := []byte{
|
||||
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
|
||||
25, 26, 27, 28, 29, 30, 31, 128,
|
||||
}
|
||||
if err := root.Insert(key_a, key_a, nil); err != nil {
|
||||
t.Fatalf("insert failed: %s", err)
|
||||
}
|
||||
if err := root.Insert(key_b, key_b, nil); err != nil {
|
||||
t.Fatalf("insert failed: %s", err)
|
||||
}
|
||||
comm := root.Commit()
|
||||
|
||||
stemComm0 := srs[0]
|
||||
err := StemFromLEBytes(&v, KeyToStem(key_a))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
stemComm1.ScalarMul(&srs[1], &v)
|
||||
|
||||
if err := leafToComms(vs[:], key_a); err != nil {
|
||||
t.Fatalf("leafToComms failed: %s", err)
|
||||
}
|
||||
c1.Add(t1.ScalarMul(&srs[64], &vs[0]), t2.ScalarMul(&srs[65], &vs[1]))
|
||||
c1.MapToScalarField(&v)
|
||||
stemComm2.ScalarMul(&srs[2], &v)
|
||||
|
||||
if err := leafToComms(vs[:], key_b); err != nil {
|
||||
t.Fatalf("leafToComms failed: %s", err)
|
||||
}
|
||||
c2.Add(t1.ScalarMul(&srs[0], &vs[0]), t2.ScalarMul(&srs[1], &vs[1]))
|
||||
c2.MapToScalarField(&v)
|
||||
stemComm3.ScalarMul(&srs[3], &v)
|
||||
|
||||
t1.Add(&stemComm0, &stemComm1)
|
||||
t2.Add(&stemComm2, &stemComm3)
|
||||
expectedP.Add(&t1, &t2)
|
||||
expectedP.MapToScalarField(&v)
|
||||
expectedP.ScalarMul(&srs[1], &v)
|
||||
|
||||
if expectedP.Equal(identity) {
|
||||
t.Fatal("commitment is identity")
|
||||
}
|
||||
|
||||
if !comm.Equal(&expectedP) {
|
||||
t.Fatalf("invalid root commitment %v != %v", comm, &expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInsertKey1Val1Key2Val2(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var (
|
||||
v, v1, v2, expected Fr
|
||||
root = New()
|
||||
expectedP, t1, t2 Point
|
||||
cfg = GetConfig()
|
||||
srs = cfg.conf.SRS
|
||||
)
|
||||
key_b, _ := hex.DecodeString("0101010101010101010101010101010101010101010101010101010101010101")
|
||||
if err := root.Insert(zeroKeyTest, zeroKeyTest, nil); err != nil {
|
||||
t.Fatalf("insert failed: %s", err)
|
||||
}
|
||||
if err := root.Insert(key_b, key_b, nil); err != nil {
|
||||
t.Fatalf("insert failed: %s", err)
|
||||
}
|
||||
comm := root.Commit()
|
||||
fmt.Println(root.toDot("", ""))
|
||||
|
||||
extensionAndSuffixOneKey(t, zeroKeyTest, zeroKeyTest, &t1)
|
||||
t1.MapToScalarField(&v1)
|
||||
|
||||
extensionAndSuffixOneKey(t, key_b, key_b, &t2)
|
||||
t2.MapToScalarField(&v2)
|
||||
|
||||
expectedP.Add(t1.ScalarMul(&srs[0], &v1), t2.ScalarMul(&srs[1], &v2))
|
||||
expectedP.MapToScalarField(&v)
|
||||
|
||||
if expectedP.Equal(identity) {
|
||||
t.Fatal("commitment is identity")
|
||||
}
|
||||
|
||||
if !comm.Equal(&expectedP) {
|
||||
t.Fatalf("invalid root commitment %v != %v", comm, &expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmptyTrie(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
root := New()
|
||||
comm := root.Commit()
|
||||
|
||||
if !comm.Equal(identity) {
|
||||
t.Fatalf("invalid root commitment %v != %v", comm, identity)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGroupToField(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
point := banderwagon.Generator
|
||||
var v Fr
|
||||
point.MapToScalarField(&v)
|
||||
bytes := v.BytesLE()
|
||||
hexStr := hex.EncodeToString(bytes[:])
|
||||
if hexStr != "d1e7de2aaea9603d5bc6c208d319596376556ecd8336671ba7670c2139772d14" {
|
||||
t.Fatalf("group to field not working")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkGroupToField(b *testing.B) {
|
||||
b.Run("single", func(b *testing.B) {
|
||||
point := banderwagon.Generator
|
||||
var v Fr
|
||||
for i := 0; i < b.N; i++ {
|
||||
point.MapToScalarField(&v)
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("multiple", func(b *testing.B) {
|
||||
for i := 1; i <= 256; i *= 2 {
|
||||
b.Run(strconv.Itoa(i), func(b *testing.B) {
|
||||
// Generate `i` ~distinct points
|
||||
points := make([]*Point, i)
|
||||
points[0] = &banderwagon.Generator
|
||||
for k := 1; k < i; k++ {
|
||||
points[k] = &Point{}
|
||||
points[k].Add(points[k-1], &banderwagon.Generator)
|
||||
}
|
||||
sink := make([]Fr, i)
|
||||
ptrs := make([]*Fr, i)
|
||||
for i := range sink {
|
||||
ptrs[i] = &sink[i]
|
||||
}
|
||||
now := time.Now()
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for k := 0; k < b.N; k++ {
|
||||
if err := banderwagon.BatchMapToScalarField(ptrs, points); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
b.ReportMetric(float64(time.Since(now).Nanoseconds()/int64(i))/float64(b.N), "ns/value")
|
||||
_ = sink
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestPaddingInFromLEBytes(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var fr1, fr2 Fr
|
||||
if err := FromLEBytes(&fr1, ffx32KeyTest[:16]); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
key, _ := hex.DecodeString("ffffffffffffffffffffffffffffffff00000000000000000000000000000000")
|
||||
err := StemFromLEBytes(&fr2, KeyToStem(key))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !fr1.Equal(&fr2) {
|
||||
t.Fatal("byte alignment")
|
||||
}
|
||||
}
|
||||
+1882
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,86 @@
|
||||
// Copyright (C) 2026, Lux Industries Inc. All rights reserved.
|
||||
//
|
||||
// Vendored from github.com/ethereum/go-verkle@v0.2.2 (public domain / Unlicense).
|
||||
// Re-licensed under the Lux Ecosystem License. The package implements the
|
||||
// Verkle trie, its node types (InternalNode, LeafNode, Empty, HashedNode),
|
||||
// IPA configuration, multi-proof generation/verification, encoding, and
|
||||
// JSON serialization. Algorithmic logic mirrors upstream; downstream callers
|
||||
// (luxfi/geth, luxfi/node, luxfi/evm) consume this package directly under
|
||||
// the github.com/luxfi/crypto/verkle import path.
|
||||
|
||||
// This is free and unencumbered software released into the public domain.
|
||||
//
|
||||
// Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
// distribute this software, either in source code form or as a compiled
|
||||
// binary, for any purpose, commercial or non-commercial, and by any
|
||||
// means.
|
||||
//
|
||||
// In jurisdictions that recognize copyright laws, the author or authors
|
||||
// of this software dedicate any and all copyright interest in the
|
||||
// software to the public domain. We make this dedication for the benefit
|
||||
// of the public at large and to the detriment of our heirs and
|
||||
// successors. We intend this dedication to be an overt act of
|
||||
// relinquishment in perpetuity of all present and future rights to this
|
||||
// software under copyright law.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
// OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
// For more information, please refer to <https://unlicense.org>
|
||||
|
||||
package verkle
|
||||
|
||||
import "errors"
|
||||
|
||||
type UnknownNode struct{}
|
||||
|
||||
func (UnknownNode) Insert([]byte, []byte, NodeResolverFn) error {
|
||||
return errMissingNodeInStateless
|
||||
}
|
||||
|
||||
func (UnknownNode) Delete([]byte, NodeResolverFn) (bool, error) {
|
||||
return false, errors.New("cant delete in a subtree missing form a stateless view")
|
||||
}
|
||||
|
||||
func (UnknownNode) Get([]byte, NodeResolverFn) ([]byte, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (n UnknownNode) Commit() *Point {
|
||||
return n.Commitment()
|
||||
}
|
||||
|
||||
func (UnknownNode) Commitment() *Point {
|
||||
var id Point
|
||||
id.SetIdentity()
|
||||
return &id
|
||||
}
|
||||
|
||||
func (UnknownNode) GetProofItems(keylist, NodeResolverFn) (*ProofElements, []byte, []Stem, error) {
|
||||
return nil, nil, nil, errors.New("can't generate proof items for unknown node")
|
||||
}
|
||||
|
||||
func (UnknownNode) Serialize() ([]byte, error) {
|
||||
return nil, errors.New("trying to serialize a subtree missing from the statless view")
|
||||
}
|
||||
|
||||
func (UnknownNode) Copy() VerkleNode {
|
||||
return UnknownNode(struct{}{})
|
||||
}
|
||||
|
||||
func (UnknownNode) toDot(string, string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (UnknownNode) setDepth(_ byte) {
|
||||
panic("should not be try to set the depth of an UnknownNode node")
|
||||
}
|
||||
|
||||
func (UnknownNode) Hash() *Fr {
|
||||
return &FrZero
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package verkle
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestUnknownFuncs(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
un := UnknownNode{}
|
||||
|
||||
if err := un.Insert(nil, nil, nil); err != errMissingNodeInStateless {
|
||||
t.Errorf("got %v, want %v", err, errMissingNodeInStateless)
|
||||
}
|
||||
if _, err := un.Delete(nil, nil); err == nil {
|
||||
t.Errorf("got nil error when deleting from a hashed node")
|
||||
}
|
||||
if _, err := un.Get(nil, nil); err != nil {
|
||||
t.Errorf("got %v, want nil", err)
|
||||
}
|
||||
var identity Point
|
||||
identity.SetIdentity()
|
||||
if comm := un.Commit(); !comm.Equal(&identity) {
|
||||
t.Errorf("got %v, want identity", comm)
|
||||
}
|
||||
if comm := un.Commitment(); !comm.Equal(&identity) {
|
||||
t.Errorf("got %v, want identity", comm)
|
||||
}
|
||||
if _, _, _, err := un.GetProofItems(nil, nil); err == nil {
|
||||
t.Errorf("got nil error when getting proof items from a hashed node")
|
||||
}
|
||||
if _, err := un.Serialize(); err == nil {
|
||||
t.Errorf("got nil error when serializing a hashed node")
|
||||
}
|
||||
if un != un.Copy() {
|
||||
t.Errorf("copy returned a different node")
|
||||
}
|
||||
if un.toDot("", "") != "" {
|
||||
t.Errorf("toDot returned a non-empty string")
|
||||
}
|
||||
if !un.Hash().Equal(&FrZero) {
|
||||
t.Errorf("hash returned non-zero")
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
|
||||
//
|
||||
// Package verkle re-exports the go-verkle library under the luxfi/crypto
|
||||
// namespace so that downstream packages (luxfi/geth, luxfi/node, etc.)
|
||||
// never import ethereum/* directly.
|
||||
|
||||
package verkle
|
||||
|
||||
import (
|
||||
upstream "github.com/ethereum/go-verkle"
|
||||
)
|
||||
|
||||
// --- Type aliases ---
|
||||
|
||||
type (
|
||||
VerkleNode = upstream.VerkleNode
|
||||
InternalNode = upstream.InternalNode
|
||||
LeafNode = upstream.LeafNode
|
||||
Empty = upstream.Empty
|
||||
Point = upstream.Point
|
||||
Fr = upstream.Fr
|
||||
VerkleProof = upstream.VerkleProof
|
||||
StateDiff = upstream.StateDiff
|
||||
NodeResolverFn = upstream.NodeResolverFn
|
||||
IPAConfig = upstream.IPAConfig
|
||||
Proof = upstream.Proof
|
||||
)
|
||||
|
||||
// --- Constants ---
|
||||
|
||||
const NodeWidth = upstream.NodeWidth
|
||||
|
||||
// --- Functions ---
|
||||
|
||||
var (
|
||||
ParseNode = upstream.ParseNode
|
||||
New = upstream.New
|
||||
ToDot = upstream.ToDot
|
||||
GetConfig = upstream.GetConfig
|
||||
MakeVerkleMultiProof = upstream.MakeVerkleMultiProof
|
||||
SerializeProof = upstream.SerializeProof
|
||||
HashPointToBytes = upstream.HashPointToBytes
|
||||
FromLEBytes = upstream.FromLEBytes
|
||||
FromBytes = upstream.FromBytes
|
||||
DeserializeProof = upstream.DeserializeProof
|
||||
MergeTrees = upstream.MergeTrees
|
||||
BatchNewLeafNode = upstream.BatchNewLeafNode
|
||||
PreStateTreeFromProof = upstream.PreStateTreeFromProof
|
||||
)
|
||||
Reference in New Issue
Block a user