// 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(()); }