v0.2.6-pre.015

This commit is contained in:
2026-08-22 03:21:49 +02:00
parent bd561cad47
commit d370027c02
27 changed files with 2630 additions and 364 deletions

View File

@@ -0,0 +1,55 @@
// file: crates/ksp-wallet-lib/unit_tests/transcript_v2.rs
// version: 1
const FIXTURE: &[u8] = include_bytes!("../tests/fixtures/kspwallet_v2_wire_only.bin");
#[test]
fn v2_state_transcript_is_domain_separated_and_deterministic() -> ksp_core_lib::Result<()> {
let envelope = match crate::KspWalletEnvelopeV2::parse_binary(FIXTURE) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let first = envelope.state_transcript();
let second = envelope.state_transcript();
assert_eq!(first, second);
assert!(first.starts_with(crate::KSPWALLET_V2_STATE_TRANSCRIPT_DOMAIN));
assert!(!first.starts_with(crate::KSPWALLET_V1_STATE_TRANSCRIPT_DOMAIN));
return std::result::Result::Ok(());
}
#[test]
fn v2_owner_and_view_slot_aad_are_role_domain_separated() -> ksp_core_lib::Result<()> {
let envelope = match crate::KspWalletEnvelopeV2::parse_binary(FIXTURE) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let owner = envelope.owner_slot_aad();
let view = match envelope.view_slot_aad() {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err(ksp_core_lib::Error::new(crate::ERROR_CODE_FORMAT_INVALID, "fixture VIEW slot is missing"));
},
};
assert!(owner.starts_with(crate::KSPWALLET_V2_OWNER_SLOT_AAD_DOMAIN));
assert!(view.starts_with(crate::KSPWALLET_V2_VIEW_SLOT_AAD_DOMAIN));
assert_ne!(owner, view);
return std::result::Result::Ok(());
}
#[test]
fn v2_compartment_aad_is_domain_separated_by_kind() -> ksp_core_lib::Result<()> {
let envelope = match crate::KspWalletEnvelopeV2::parse_binary(FIXTURE) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let owner_control = envelope.compartment_aad(crate::WalletCompartmentKindV2::OwnerControl);
let metadata = envelope.compartment_aad(crate::WalletCompartmentKindV2::Metadata);
let secret = envelope.compartment_aad(crate::WalletCompartmentKindV2::Secret);
assert!(owner_control.starts_with(crate::KSPWALLET_V2_OWNER_CONTROL_AAD_DOMAIN));
assert!(metadata.starts_with(crate::KSPWALLET_V2_METADATA_AAD_DOMAIN));
assert!(secret.starts_with(crate::KSPWALLET_V2_SECRET_AAD_DOMAIN));
assert_ne!(owner_control, metadata);
assert_ne!(metadata, secret);
assert_ne!(owner_control, secret);
return std::result::Result::Ok(());
}

View File

@@ -0,0 +1,136 @@
// file: crates/ksp-wallet-lib/unit_tests/wire_v2.rs
// version: 1
const FIXTURE: &[u8] = include_bytes!("../tests/fixtures/kspwallet_v2_wire_only.bin");
const V1_JSON_FIXTURE: &[u8] = include_bytes!("../tests/fixtures/kspwallet_v1_wire_only.json");
#[test]
fn strict_v2_fixture_parses_and_round_trips_byte_exact() -> ksp_core_lib::Result<()> {
let parsed = match crate::KspWalletEnvelopeV2::parse_binary(FIXTURE) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
assert_eq!(parsed.format_version(), crate::KSPWALLET_FORMAT_VERSION_V2);
assert!(parsed.view_descriptor().enabled());
assert_eq!(parsed.owner_slot().role(), crate::WalletKeySlotRoleV2::Owner);
assert_eq!(parsed.view_slot().map(crate::WalletKeySlotV2::role), std::option::Option::Some(crate::WalletKeySlotRoleV2::View));
assert_eq!(parsed.owner_control().kind(), crate::WalletCompartmentKindV2::OwnerControl);
assert_eq!(parsed.metadata().kind(), crate::WalletCompartmentKindV2::Metadata);
assert_eq!(parsed.secret().kind(), crate::WalletCompartmentKindV2::Secret);
let encoded = match parsed.to_binary_bytes() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
assert_eq!(encoded.as_slice(), FIXTURE);
return std::result::Result::Ok(());
}
#[test]
fn v2_wire_fixture_is_materially_smaller_than_equivalent_v1_json_fixture() {
assert!(FIXTURE.len() < V1_JSON_FIXTURE.len());
assert_eq!(FIXTURE.len(), 628);
assert_eq!(V1_JSON_FIXTURE.len(), 2037);
}
#[test]
fn unknown_v2_format_version_is_rejected_before_body_parsing() {
let mut source = FIXTURE.to_vec();
source[9..11].copy_from_slice(3_u16.to_be_bytes().as_slice());
let result = crate::KspWalletEnvelopeV2::parse_binary(source.as_slice());
assert!(result.is_err());
let error = match result {
std::result::Result::Err(error) => error,
std::result::Result::Ok(_) => return,
};
assert_eq!(error.code(), crate::ERROR_CODE_FORMAT_VERSION_UNSUPPORTED);
}
#[test]
fn declared_length_mismatch_and_trailing_bytes_are_rejected() {
let mut mismatched = FIXTURE.to_vec();
mismatched[11..15].copy_from_slice(627_u32.to_be_bytes().as_slice());
let mismatch_result = crate::KspWalletEnvelopeV2::parse_binary(mismatched.as_slice());
assert!(mismatch_result.is_err());
let mut trailing = FIXTURE.to_vec();
trailing.push(0);
trailing[11..15].copy_from_slice(629_u32.to_be_bytes().as_slice());
let trailing_result = crate::KspWalletEnvelopeV2::parse_binary(trailing.as_slice());
assert!(trailing_result.is_err());
}
#[test]
fn unknown_header_flags_are_rejected() {
let mut source = FIXTURE.to_vec();
source[15..17].copy_from_slice(2_u16.to_be_bytes().as_slice());
let result = crate::KspWalletEnvelopeV2::parse_binary(source.as_slice());
assert!(result.is_err());
let error = match result {
std::result::Result::Err(error) => error,
std::result::Result::Ok(_) => return,
};
assert_eq!(error.code(), crate::ERROR_CODE_FORMAT_INVALID);
}
#[test]
fn enabled_view_descriptor_must_match_the_v2_view_slot() {
let mut source = FIXTURE.to_vec();
source[49] ^= 0x01;
let result = crate::KspWalletEnvelopeV2::parse_binary(source.as_slice());
assert!(result.is_err());
let error = match result {
std::result::Result::Err(error) => error,
std::result::Result::Ok(_) => return,
};
assert_eq!(error.code(), crate::ERROR_CODE_FORMAT_INVALID);
}
#[test]
fn v2_key_slots_require_canonical_owner_then_view_order() {
let mut source = FIXTURE.to_vec();
source[65] = 2;
let result = crate::KspWalletEnvelopeV2::parse_binary(source.as_slice());
assert!(result.is_err());
let error = match result {
std::result::Result::Err(error) => error,
std::result::Result::Ok(_) => return,
};
assert_eq!(error.code(), crate::ERROR_CODE_FORMAT_INVALID);
}
#[test]
fn v2_pathological_kdf_parameters_are_rejected_before_crypto() {
let mut source = FIXTURE.to_vec();
source[87..91].copy_from_slice(0_u32.to_be_bytes().as_slice());
let result = crate::KspWalletEnvelopeV2::parse_binary(source.as_slice());
assert!(result.is_err());
let error = match result {
std::result::Result::Err(error) => error,
std::result::Result::Ok(_) => return,
};
assert_eq!(error.code(), crate::ERROR_CODE_CRYPTO_PARAMETERS_INVALID);
}
#[test]
fn v2_oversized_document_is_rejected_before_body_allocation() {
let oversized = std::vec![0_u8; crate::KSPWALLET_MAX_FILE_BYTES + 1];
let result = crate::KspWalletEnvelopeV2::parse_binary(oversized.as_slice());
assert!(result.is_err());
let error = match result {
std::result::Result::Err(error) => error,
std::result::Result::Ok(_) => return,
};
assert_eq!(error.code(), crate::ERROR_CODE_FORMAT_INVALID);
}
#[test]
fn v2_envelope_debug_does_not_render_ciphertext_contents() -> ksp_core_lib::Result<()> {
let parsed = match crate::KspWalletEnvelopeV2::parse_binary(FIXTURE) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let rendered = format!("{parsed:?}");
assert!(rendered.contains("ciphertext_bytes"));
assert!(!rendered.contains("128, 129, 130, 131"));
assert!(!rendered.contains("224, 225, 226, 227"));
return std::result::Result::Ok(());
}