// file: crates/ksp-wallet-lib/src/transcript.rs // version: 2 //! Deterministic `.kspwallet` V1 state-transcript and AEAD-AAD encoding. const TAG_COMPARTMENT_ALGORITHM: u16 = 0x0202; const TAG_COMPARTMENT_CIPHERTEXT: u16 = 0x0204; const TAG_COMPARTMENT_KIND: u16 = 0x0200; const TAG_COMPARTMENT_NONCE: u16 = 0x0203; const TAG_COMPARTMENT_VERSION: u16 = 0x0201; const TAG_FORMAT_VERSION: u16 = 0x0002; const TAG_KDF_ALGORITHM: u16 = 0x0102; const TAG_KDF_ITERATIONS: u16 = 0x0105; const TAG_KDF_MEMORY_KIB: u16 = 0x0104; const TAG_KDF_PARALLELISM: u16 = 0x0106; const TAG_KDF_SALT: u16 = 0x0107; const TAG_KDF_VERSION: u16 = 0x0103; const TAG_MAGIC: u16 = 0x0001; const TAG_OWNER_AUTH_PUBLIC_KEY: u16 = 0x0003; const TAG_SLOT_ID: u16 = 0x0100; const TAG_SLOT_ROLE: u16 = 0x0101; const TAG_STATE_SIGNATURE_ALGORITHM: u16 = 0x0500; const TAG_VIEW_ENABLED: u16 = 0x0010; const TAG_VIEW_ROLE: u16 = 0x0011; const TAG_VIEW_SLOT_ID: u16 = 0x0012; const TAG_WRAP_ALGORITHM: u16 = 0x0108; const TAG_WRAP_CIPHERTEXT: u16 = 0x010A; const TAG_WRAP_NONCE: u16 = 0x0109; /// Builds the normative OWNER state-signature transcript for one validated V1 envelope. pub(crate) fn state_transcript(envelope: &crate::KspWalletEnvelopeV1) -> std::vec::Vec { let mut output = start(crate::KSPWALLET_V1_STATE_TRANSCRIPT_DOMAIN); push_common(&mut output, envelope); push_bool(&mut output, TAG_VIEW_ENABLED, envelope.view_descriptor().enabled()); push_bytes(&mut output, TAG_VIEW_ROLE, crate::WalletKeySlotRoleV1::View.as_str().as_bytes()); match envelope.view_descriptor().slot_id() { std::option::Option::Some(slot_id) => push_bytes(&mut output, TAG_VIEW_SLOT_ID, slot_id), std::option::Option::None => push_bytes(&mut output, TAG_VIEW_SLOT_ID, &[]), } push_slot(&mut output, envelope.owner_slot(), true); push_compartment(&mut output, envelope.owner_control(), true); push_compartment(&mut output, envelope.metadata(), true); push_compartment(&mut output, envelope.secret(), true); push_bytes(&mut output, TAG_STATE_SIGNATURE_ALGORITHM, envelope.state_signature().algorithm().as_str().as_bytes()); return output; } /// Builds the normative wrapping AAD for one validated OWNER or VIEW key slot. pub(crate) fn slot_aad(envelope: &crate::KspWalletEnvelopeV1, slot: &crate::WalletKeySlotV1) -> std::vec::Vec { let domain = match slot.role() { crate::WalletKeySlotRoleV1::Owner => crate::KSPWALLET_V1_OWNER_SLOT_AAD_DOMAIN, crate::WalletKeySlotRoleV1::View => crate::KSPWALLET_V1_VIEW_SLOT_AAD_DOMAIN, }; let mut output = start(domain); push_common(&mut output, envelope); push_slot(&mut output, slot, false); return output; } /// Builds the normative AEAD AAD for one validated encrypted compartment. pub(crate) fn compartment_aad(envelope: &crate::KspWalletEnvelopeV1, compartment: &crate::WalletEncryptedCompartmentV1) -> std::vec::Vec { let domain = match compartment.kind() { crate::WalletCompartmentKindV1::OwnerControl => crate::KSPWALLET_V1_OWNER_CONTROL_AAD_DOMAIN, crate::WalletCompartmentKindV1::Metadata => crate::KSPWALLET_V1_METADATA_AAD_DOMAIN, crate::WalletCompartmentKindV1::Secret => crate::KSPWALLET_V1_SECRET_AAD_DOMAIN, }; let mut output = start(domain); push_common(&mut output, envelope); push_compartment(&mut output, compartment, false); return output; } fn start(domain: &[u8]) -> std::vec::Vec { let mut output = std::vec::Vec::with_capacity(512); output.extend_from_slice(domain); output.push(0); return output; } fn push_common(output: &mut std::vec::Vec, envelope: &crate::KspWalletEnvelopeV1) { push_bytes(output, TAG_MAGIC, crate::KSPWALLET_MAGIC.as_bytes()); push_u32(output, TAG_FORMAT_VERSION, envelope.format_version()); push_bytes(output, TAG_OWNER_AUTH_PUBLIC_KEY, envelope.owner_auth_public_key()); } fn push_slot(output: &mut std::vec::Vec, slot: &crate::WalletKeySlotV1, include_wrap_payload: bool) { push_bytes(output, TAG_SLOT_ID, slot.slot_id()); push_bytes(output, TAG_SLOT_ROLE, slot.role().as_str().as_bytes()); push_bytes(output, TAG_KDF_ALGORITHM, slot.kdf().algorithm().as_str().as_bytes()); push_u32(output, TAG_KDF_VERSION, slot.kdf().version()); push_u32(output, TAG_KDF_MEMORY_KIB, slot.kdf().memory_kib()); push_u32(output, TAG_KDF_ITERATIONS, slot.kdf().iterations()); push_u32(output, TAG_KDF_PARALLELISM, slot.kdf().parallelism()); push_bytes(output, TAG_KDF_SALT, slot.kdf().salt()); push_bytes(output, TAG_WRAP_ALGORITHM, slot.wrap().algorithm().as_str().as_bytes()); if include_wrap_payload { push_bytes(output, TAG_WRAP_NONCE, slot.wrap().nonce()); push_bytes(output, TAG_WRAP_CIPHERTEXT, slot.wrap().ciphertext()); } } fn push_compartment(output: &mut std::vec::Vec, compartment: &crate::WalletEncryptedCompartmentV1, include_ciphertext: bool) { push_bytes(output, TAG_COMPARTMENT_KIND, compartment.kind().as_str().as_bytes()); push_u32(output, TAG_COMPARTMENT_VERSION, compartment.payload_version()); push_bytes(output, TAG_COMPARTMENT_ALGORITHM, compartment.algorithm().as_str().as_bytes()); if include_ciphertext { push_bytes(output, TAG_COMPARTMENT_NONCE, compartment.nonce()); push_bytes(output, TAG_COMPARTMENT_CIPHERTEXT, compartment.ciphertext()); } } fn push_bool(output: &mut std::vec::Vec, tag: u16, value: bool) { let byte = if value { 1_u8 } else { 0_u8 }; push_bytes(output, tag, &[byte]); } fn push_u32(output: &mut std::vec::Vec, tag: u16, value: u32) { push_bytes(output, tag, value.to_be_bytes().as_slice()); } fn push_bytes(output: &mut std::vec::Vec, tag: u16, value: &[u8]) { output.extend_from_slice(tag.to_be_bytes().as_slice()); let length = value.len() as u64; output.extend_from_slice(length.to_be_bytes().as_slice()); output.extend_from_slice(value); } #[cfg(test)] #[path = "../unit_tests/transcript.rs"] mod tests;