Files
samiandClaude Opus 5.5 2be9a3baad M1: persistent vault, local unlock key, sync client, test vectors
kv-store: SQLite (bundled) and in-memory Store holding only ciphertext
envelopes, with base copies for three-way merges and a 10-version history.

kv-ffi: VaultCore::open(path); persisted account record; local unlock key
(enable/unlock_with_local_key/disable); async sync API (configure_server,
set_device_signer, register_account, login, sync_now, logout, devices);
DeviceSigner foreign trait; IntegrityAlarm/PinMismatch/... error variants.

kv-sync: reqwest + rustls (TLS 1.3 only, ring) with SPKI pinning; OPAQUE
client on kv-proto's OpaqueSuite; RFC 9421 signing per kv_proto::httpsig;
pull/push with If-Match, field-level three-way merge, edit-beats-delete;
signed manifest (canonical CBOR, Merkle root, prev chain, Ed25519) with
rollback/fork/Merkle checks as hard integrity alarms. Tested against a
wiremock fake server, plus an #[ignore] test against kestrove-server.

kv-crypto: gen-vectors example and tests reproducing kestrove-spec/vectors.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-15 21:52:14 +09:00

101 lines
3.9 KiB
Rust

//! The code must reproduce the published test vectors byte for byte, and the vectors must decode.
use std::path::PathBuf;
use kv_crypto::{aead::SymKey, seal::EncKeypair, secret_key::SecretKey};
use kv_proto::Envelope;
use serde_json::Value;
use uuid::Uuid;
#[path = "../vectors/gen.rs"]
mod gen;
fn dir() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/vectors")
}
fn load(name: &str) -> Value {
let text = std::fs::read_to_string(dir().join(format!("{name}.json"))).expect("vector file (run gen-vectors)");
serde_json::from_str(&text).unwrap()
}
#[test]
fn code_reproduces_vector_files() {
let spec = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../../kestrove-spec/vectors");
for (name, doc) in gen::generate() {
let rendered = gen::render(&doc);
let on_disk = std::fs::read_to_string(dir().join(format!("{name}.json"))).unwrap();
assert_eq!(rendered, on_disk, "{name}.json differs from what the code produces");
// When the spec repo is checked out next to this one, it must carry the same files.
if let Ok(published) = std::fs::read_to_string(spec.join(format!("{name}.json"))) {
assert_eq!(published, on_disk, "kestrove-spec/vectors/{name}.json is stale");
}
}
assert_eq!(gen::FILES.len(), gen::generate().len());
}
#[test]
fn envelopes_open() {
for v in load("envelope")["vectors"].as_array().unwrap() {
let key: [u8; 32] = gen::unhex(v["key"].as_str().unwrap()).try_into().unwrap();
let key = SymKey::from_bytes(Uuid::parse_str(v["kid"].as_str().unwrap()).unwrap(), key);
let env: Envelope = serde_json::from_value(v["envelope"].clone()).unwrap();
let pt = key.open(&env, &gen::unhex(v["aad"].as_str().unwrap())).unwrap();
assert_eq!(gen::hex(&pt), v["plaintext"].as_str().unwrap());
assert_eq!(gen::hex(&env.ct), v["ciphertext"].as_str().unwrap());
}
}
#[test]
fn hpke_opens() {
for v in load("hpke")["vectors"].as_array().unwrap() {
let kp = EncKeypair::from_secret_bytes(
Uuid::parse_str(v["recipient_kid"].as_str().unwrap()).unwrap(),
&gen::unhex(v["recipient_sk"].as_str().unwrap()),
)
.unwrap();
assert_eq!(gen::hex(&kp.public_bytes()), v["recipient_pk"].as_str().unwrap());
let env: Envelope = serde_json::from_value(v["envelope"].clone()).unwrap();
let pt = kp.open(&env, &gen::unhex(v["aad"].as_str().unwrap())).unwrap();
assert_eq!(gen::hex(&pt), v["plaintext"].as_str().unwrap());
}
}
#[test]
fn secret_keys_parse() {
let doc = load("secret_key_format");
for v in doc["valid"].as_array().unwrap() {
let k = SecretKey::parse(v["display"].as_str().unwrap()).unwrap();
assert_eq!(gen::hex(k.expose()), v["raw"].as_str().unwrap());
}
for v in doc["alternate_inputs"].as_array().unwrap() {
let k = SecretKey::parse(v["input"].as_str().unwrap()).unwrap();
assert_eq!(gen::hex(k.expose()), v["raw"].as_str().unwrap());
}
for v in doc["invalid"].as_array().unwrap() {
assert!(SecretKey::parse(v["input"].as_str().unwrap()).is_err(), "{v}");
}
}
#[test]
fn rfc6238_codes_are_the_published_ones() {
// Spot-check against the RFC 6238 Appendix B table itself, not only our own output.
let doc = load("totp");
let find = |t: u64, alg: &str| {
doc["vectors"]
.as_array()
.unwrap()
.iter()
.find(|v| v["time"] == t && v["uri"].as_str().unwrap().contains(&format!("algorithm={alg}&")))
.unwrap()["code"]
.as_str()
.unwrap()
.to_string()
};
assert_eq!(find(59, "SHA1"), "94287082");
assert_eq!(find(59, "SHA256"), "46119246");
assert_eq!(find(59, "SHA512"), "90693936");
assert_eq!(find(20_000_000_000, "SHA1"), "65353130");
assert_eq!(find(20_000_000_000, "SHA512"), "47863826");
}