// file: crates/ksp-wallet-lib/unit_tests/wire.rs // version: 4 const FIXTURE: &[u8] = include_bytes!("../tests/fixtures/kspwallet_v1_wire_only.json"); #[test] fn strict_v1_fixture_parses_and_round_trips_semantically() -> ksp_core_lib::Result<()> { let parsed = match crate::KspWalletEnvelopeV1::parse_json(FIXTURE) { std::result::Result::Ok(parsed) => parsed, std::result::Result::Err(error) => return std::result::Result::Err(error), }; assert_eq!(parsed.format_version(), crate::KSPWALLET_FORMAT_VERSION_V1); assert!(parsed.view_descriptor().enabled()); assert_eq!(parsed.owner_slot().role(), crate::WalletKeySlotRoleV1::Owner); assert_eq!(parsed.view_slot().map(crate::WalletKeySlotV1::role), std::option::Option::Some(crate::WalletKeySlotRoleV1::View)); assert_eq!(parsed.owner_control().payload_version(), 1); assert_eq!(parsed.metadata().payload_version(), 1); assert_eq!(parsed.secret().payload_version(), 1); let serialized = match parsed.to_json_bytes() { std::result::Result::Ok(serialized) => serialized, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let reparsed = match crate::KspWalletEnvelopeV1::parse_json(serialized.as_slice()) { std::result::Result::Ok(reparsed) => reparsed, std::result::Result::Err(error) => return std::result::Result::Err(error), }; assert_eq!(parsed, reparsed); return std::result::Result::Ok(()); } #[test] fn unknown_format_version_is_rejected_before_v1_shape_validation() { let source = std::string::String::from_utf8_lossy(FIXTURE).replace("\"format_version\": 1", "\"format_version\": 2"); let result = crate::KspWalletEnvelopeV1::parse_json(source.as_bytes()); 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 unknown_top_level_field_is_rejected() { let source = std::string::String::from_utf8_lossy(FIXTURE).replacen("{", "{\n \"unexpected\": true,", 1); let result = crate::KspWalletEnvelopeV1::parse_json(source.as_bytes()); 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 padded_or_noncanonical_base64url_is_rejected() { let source = std::string::String::from_utf8_lossy(FIXTURE) .replace("AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8\"", "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=\""); let result = crate::KspWalletEnvelopeV1::parse_json(source.as_bytes()); 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_view_slot() { let source = std::string::String::from_utf8_lossy(FIXTURE).replacen("\"slot_id\": \"ICEiIyQlJicoKSorLC0uLw\"", "\"slot_id\": \"EBESExQVFhcYGRobHB0eHw\"", 1); let result = crate::KspWalletEnvelopeV1::parse_json(source.as_bytes()); 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 zero_or_pathological_kdf_parameters_are_rejected_before_crypto() { let zero_source = std::string::String::from_utf8_lossy(FIXTURE).replacen("\"memory_kib\": 65536", "\"memory_kib\": 0", 1); let zero_result = crate::KspWalletEnvelopeV1::parse_json(zero_source.as_bytes()); assert!(zero_result.is_err()); let zero_error = match zero_result { std::result::Result::Err(error) => error, std::result::Result::Ok(_) => return, }; assert_eq!(zero_error.code(), crate::ERROR_CODE_CRYPTO_PARAMETERS_INVALID); let high_source = std::string::String::from_utf8_lossy(FIXTURE).replacen("\"memory_kib\": 65536", "\"memory_kib\": 1048577", 1); let high_result = crate::KspWalletEnvelopeV1::parse_json(high_source.as_bytes()); assert!(high_result.is_err()); let high_error = match high_result { std::result::Result::Err(error) => error, std::result::Result::Ok(_) => return, }; assert_eq!(high_error.code(), crate::ERROR_CODE_CRYPTO_PARAMETERS_INVALID); } #[test] fn argon2_memory_must_cover_all_lanes_before_crypto() { let source = std::string::String::from_utf8_lossy(FIXTURE).replacen("\"memory_kib\": 65536", "\"memory_kib\": 8", 1).replacen( "\"parallelism\": 1", "\"parallelism\": 2", 1, ); let result = crate::KspWalletEnvelopeV1::parse_json(source.as_bytes()); 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 oversized_document_is_rejected_before_json_or_crypto() { let oversized = std::vec![b' '; crate::KSPWALLET_MAX_FILE_BYTES + 1]; let result = crate::KspWalletEnvelopeV1::parse_json(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 envelope_debug_does_not_render_ciphertext_contents() -> ksp_core_lib::Result<()> { let parsed = match crate::KspWalletEnvelopeV1::parse_json(FIXTURE) { std::result::Result::Ok(parsed) => parsed, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let rendered = format!("{parsed:?}"); assert!(rendered.contains("ciphertext_bytes")); assert!(!rendered.contains("gIGCg4SFhoeIiYqLjI2Oj5CRkpOUlZaXmJmam5ydnp8")); assert!(!rendered.contains("wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t_g4eLj5OXm5-jp6uvs7e7v")); return std::result::Result::Ok(()); }