Some checks failed
CI / lint (push) Successful in 1m37s
CI / test-python (push) Successful in 1m49s
CI / test-zig (push) Successful in 1m39s
CI / test-wasm (push) Successful in 1m54s
CI / test (push) Successful in 14m44s
CI / miri (push) Successful in 14m18s
CI / build (push) Successful in 1m9s
CI / fuzz-regression (push) Successful in 9m9s
CI / publish (push) Failing after 1m10s
CI / publish-python (push) Failing after 1m46s
CI / publish-wasm (push) Has been cancelled
Signed-off-by: Kamal Tufekcic <kamal@lo.sh>
113 lines
4 KiB
Rust
113 lines
4 KiB
Rust
//! Build script for soliton.
|
|
//!
|
|
//! Embeds the EFF large wordlist for verification phrase generation.
|
|
//! The wordlist is checked into the repo at `src/eff_large_wordlist.txt`
|
|
//! and SHA-256 verified at build time.
|
|
//!
|
|
//! All cryptographic dependencies are pure Rust — no C compilation or linking needed.
|
|
|
|
use sha2::{Digest, Sha256};
|
|
use std::env;
|
|
use std::fs;
|
|
use std::io::Write;
|
|
use std::path::PathBuf;
|
|
|
|
/// SHA-256 hash of the canonical EFF large wordlist (7776 lines).
|
|
/// <https://www.eff.org/files/2016/07/18/eff_large_wordlist.txt>
|
|
const EFF_WORDLIST_SHA256: &str =
|
|
"addd35536511597a02fa0a9ff1e5284677b8883b83e986e43f15a3db996b903e";
|
|
|
|
fn main() {
|
|
println!("cargo:rerun-if-changed=build.rs");
|
|
println!("cargo:rerun-if-changed=src/eff_large_wordlist.txt");
|
|
|
|
generate_wordlist();
|
|
}
|
|
|
|
/// Read the EFF large wordlist from the in-repo cached copy and generate
|
|
/// a Rust source file with the words as a static array. The file is
|
|
/// written to OUT_DIR and included via `include!` in the verification
|
|
/// module.
|
|
///
|
|
/// The EFF large wordlist contains 7776 words (6^5), one per line, prefixed
|
|
/// with a dice roll number and a tab. We strip the prefix and keep only the
|
|
/// words.
|
|
fn generate_wordlist() {
|
|
// Cargo guarantees OUT_DIR and CARGO_MANIFEST_DIR for build scripts.
|
|
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
|
|
let wordlist_rs = out_dir.join("eff_wordlist.rs");
|
|
let cache_path = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap())
|
|
.join("src")
|
|
.join("eff_large_wordlist.txt");
|
|
|
|
// Skip regeneration if the output already exists AND the cached source
|
|
// passes SHA-256 verification. Safe because the output is deterministically
|
|
// generated from the source — same input always produces identical output.
|
|
// The SHA-256 check protects against tampered build caches.
|
|
if wordlist_rs.exists() {
|
|
if let Ok(cached) = fs::read_to_string(&cache_path) {
|
|
verify_wordlist_hash(&cached);
|
|
return;
|
|
}
|
|
}
|
|
|
|
let raw = fs::read_to_string(&cache_path)
|
|
.expect("EFF wordlist not available: src/eff_large_wordlist.txt missing from repo");
|
|
|
|
verify_wordlist_hash(&raw);
|
|
|
|
// Parse: each line is "DDDDD\tword\n" (5-digit dice roll, tab, word).
|
|
let words: Vec<&str> = raw
|
|
.lines()
|
|
.filter_map(|line| {
|
|
let line = line.trim();
|
|
if line.is_empty() {
|
|
return None;
|
|
}
|
|
line.split('\t').nth(1)
|
|
})
|
|
.collect();
|
|
|
|
// Panic: build scripts communicate errors by panicking. A count mismatch
|
|
// indicates a corrupted or non-canonical wordlist that passed SHA-256
|
|
// (hash collision or wrong file).
|
|
assert_eq!(
|
|
words.len(),
|
|
7776,
|
|
"EFF wordlist should have 7776 entries, got {}",
|
|
words.len()
|
|
);
|
|
|
|
// Panic on I/O failure: build scripts have no recovery path — Cargo
|
|
// treats a panic as a build error and reports the message to the user.
|
|
let mut out = fs::File::create(&wordlist_rs).unwrap();
|
|
writeln!(out, "/// EFF large wordlist (7776 words).").unwrap();
|
|
writeln!(
|
|
out,
|
|
"/// Source: <https://www.eff.org/dice>, checked into repo at src/eff_large_wordlist.txt."
|
|
)
|
|
.unwrap();
|
|
writeln!(out, "pub(crate) static EFF_WORDLIST: [&str; 7776] = [").unwrap();
|
|
for word in &words {
|
|
writeln!(out, " \"{word}\",").unwrap();
|
|
}
|
|
writeln!(out, "];").unwrap();
|
|
}
|
|
|
|
/// Verify the SHA-256 hash of the EFF wordlist content.
|
|
///
|
|
/// # Security
|
|
///
|
|
/// Panics with a clear message if the hash doesn't match, preventing
|
|
/// use of a tampered or corrupted wordlist in verification phrases.
|
|
/// This is the sole integrity gate for the in-repo wordlist.
|
|
fn verify_wordlist_hash(content: &str) {
|
|
let computed = format!("{:x}", Sha256::digest(content.as_bytes()));
|
|
|
|
assert_eq!(
|
|
computed, EFF_WORDLIST_SHA256,
|
|
"EFF wordlist SHA-256 mismatch!\n expected: {}\n computed: {}\n\
|
|
The wordlist may be corrupted or tampered with.",
|
|
EFF_WORDLIST_SHA256, computed
|
|
);
|
|
}
|