refactor: split the library into fumi-core and make it embeddable

This commit is contained in:
randogoth 2026-09-28 23:21:08 +03:00
parent d3cd0afb4f
commit 8fb5661b53
28 changed files with 975 additions and 724 deletions

119
core/src/vectors.rs Normal file
View file

@ -0,0 +1,119 @@
//! The committed reference vectors (`vectors.json`) as the tests' source of
//! truth, so the file and the derivations cannot drift apart. Language ports
//! and FFI bindings pin the same operations against the same file without
//! regenerating anything from the reference stack.
use serde::Deserialize;
use crate::crypto::KEY_LEN;
#[derive(Deserialize)]
pub struct Vectors {
/// The master every derivation below starts from, hex-encoded.
pub master_hex: String,
/// `identity_seed(master, n)` per rotation index, base32.
pub identity_seeds_b32: std::collections::BTreeMap<String, String>,
/// `Identity::from_seed(seed_n).pk()` per rotation index, base32.
pub identity_pks_b32: std::collections::BTreeMap<String, String>,
/// The X25519 half per rotation index, base32.
pub identity_xprivs_b32: std::collections::BTreeMap<String, String>,
pub accept_key_b32: String,
/// `Account::new(master, 0).token_for(pk_1)`, base32.
pub accept_token_for_pk1_b32: String,
pub rotation_cert: RotationCertVector,
pub accept_mac: AcceptMacVector,
pub envelope: EnvelopeVector,
pub hkdf_rfc5869_case1: HkdfVector,
pub hmac_rfc4231_case1: HmacVector,
/// `ed25519_to_x25519(pk_0)`, base32 — libsodium's pk_to_curve25519.
pub ed25519_to_x25519_pk0_b32: String,
pub rns_bind: RnsBindVector,
}
#[derive(Deserialize)]
pub struct RotationCertVector {
pub username: String,
pub old_index: u32,
pub new_index: u32,
pub when: i64,
pub cert_hex: String,
}
#[derive(Deserialize)]
pub struct AcceptMacVector {
/// The token is `token_for(pk_1)` over this message id, base32.
pub message_id_b32: String,
pub mac_hex: String,
}
#[derive(Deserialize)]
pub struct EnvelopeVector {
pub sender_index: u32,
pub recipient_index: u32,
pub when: i64,
/// The fixed ephemeral scalar the reference client used, hex.
pub ephemeral_hex: String,
pub padded_b32: String,
pub unpadded_b32: String,
pub message_id_hex: String,
pub body: String,
pub sender_pk_b32: String,
}
#[derive(Deserialize)]
pub struct HkdfVector {
pub ikm_hex: String,
pub salt_hex: String,
pub info_hex: String,
/// The first 32 bytes of the 42-byte OKM.
pub okm_hex_32: String,
}
#[derive(Deserialize)]
pub struct HmacVector {
pub key_hex: String,
pub data: String,
pub mac_hex: String,
}
#[derive(Deserialize)]
pub struct RnsBindVector {
pub destination_hex: String,
pub link_id_hex: String,
pub h_hex: String,
pub server_static_hex: String,
}
pub fn load() -> Vectors {
serde_json::from_str(include_str!("../vectors.json")).expect("vectors.json parses")
}
fn b32_at(map: &std::collections::BTreeMap<String, String>, n: u32) -> &str {
map.get(&n.to_string()).expect("vectors.json has the index")
}
impl Vectors {
pub fn master(&self) -> [u8; KEY_LEN] {
data_encoding::HEXLOWER
.decode(self.master_hex.as_bytes())
.expect("master_hex decodes")
.try_into()
.expect("master is 32 bytes")
}
pub fn seed_b32(&self, n: u32) -> &str {
b32_at(&self.identity_seeds_b32, n)
}
pub fn pk_b32(&self, n: u32) -> &str {
b32_at(&self.identity_pks_b32, n)
}
pub fn xpriv_b32(&self, n: u32) -> &str {
b32_at(&self.identity_xprivs_b32, n)
}
pub fn hex(&self, hex: &str) -> Vec<u8> {
data_encoding::HEXLOWER.decode(hex.as_bytes()).expect("hex decodes")
}
}