#![allow(dead_code)] #![allow(clippy::absurd_extreme_comparisons)] use super::*; use crate::*; use core::convert::TryInto; /// Out-of-band receipts are versioned along with envelope versions /// /// These are the formats for the on-the-wire serialization performed by this module /// /// #[repr(C, packed)] /// struct ReceiptHeader { /// // Size is at least 4 bytes. Depending on the version specified, the size may vary and should be case to the appropriate struct /// magic: [u8; 3], // 0x00: 0x52 0x43 0x50 ("RCP") /// version: u8, // 0x03: 0 = ReceiptV0 /// } /// /// #[repr(C, packed)] /// struct ReceiptV0 { /// // Size is 66 bytes without extra data and signature, 130 with signature /// magic: [u8; 3], // 0x00: 0x52 0x43 0x50 ("RCP") /// version: u8, // 0x03: 0 = ReceiptV0 /// crypto_kind: [u8; 4], // 0x04: CryptoSystemVersion FOURCC code /// size: u16, // 0x08: Total size of the receipt including the extra data and the signature. Maximum size is 1380 bytes. /// nonce: [u8; 24], // 0x0A: Randomly chosen bytes that represent a unique receipt. Could be used to encrypt the extra data, but it's not required. /// sender_id: [u8; 32], // 0x22: Node ID of the message source, which is the public key of the sender /// extra_data: [u8; ??], // 0x42: Extra data is appended (arbitrary extra data, not encrypted by receipt itself, maximum size is 1250 bytes) /// signature: [u8; 64], // 0x?? (end-0x40): Signature of the entire receipt including header and extra data is appended to the packet /// } pub const MAX_RECEIPT_SIZE: usize = 1380; pub const MAX_EXTRA_DATA_SIZE: usize = MAX_RECEIPT_SIZE - MIN_RECEIPT_SIZE; // 1250 pub const MIN_RECEIPT_SIZE: usize = 130; pub const RECEIPT_MAGIC: &[u8; 3] = b"RCP"; #[derive(Debug, Clone, PartialEq, Eq, Default)] pub struct Receipt { version: u8, crypto_kind: CryptoKind, nonce: Nonce, sender_id: PublicKey, extra_data: Vec, } impl Receipt { pub fn try_new>( version: u8, crypto_kind: CryptoKind, nonce: Nonce, sender_id: PublicKey, extra_data: D, ) -> Result { assert!(VALID_ENVELOPE_VERSIONS.contains(&version)); assert!(VALID_CRYPTO_KINDS.contains(&crypto_kind)); if extra_data.as_ref().len() > MAX_EXTRA_DATA_SIZE { apibail_parse_error!( "extra data too large for receipt", extra_data.as_ref().len() ); } Ok(Self { version, crypto_kind, nonce, sender_id, extra_data: Vec::from(extra_data.as_ref()), }) } pub fn from_signed_data(crypto: Crypto, data: &[u8]) -> Result { // Ensure we are at least the length of the envelope if data.len() < MIN_RECEIPT_SIZE { apibail_parse_error!("receipt too small", data.len()); } // Verify magic number let magic: [u8; 3] = data[0x00..0x03] .try_into() .map_err(VeilidAPIError::internal)?; if magic != *RECEIPT_MAGIC { apibail_generic!("bad magic number"); } // Check version let version = data[0x03]; if !VALID_ENVELOPE_VERSIONS.contains(&version) { apibail_parse_error!("unsupported envelope version", version); } // Check crypto kind let crypto_kind = FourCC( data[0x04..0x08] .try_into() .map_err(VeilidAPIError::internal)?, ); let Some(vcrypto) = crypto.get(crypto_kind) else { apibail_parse_error!("unsupported crypto kind", crypto_kind); }; // Get size and ensure it matches the size of the envelope and is less than the maximum message size let size: u16 = u16::from_le_bytes( data[0x08..0x0A] .try_into() .map_err(VeilidAPIError::internal)?, ); if (size as usize) > MAX_RECEIPT_SIZE { apibail_parse_error!("receipt size is too large", size); } if (size as usize) != data.len() { apibail_parse_error!( "size doesn't match receipt size", format!("size={} data.len()={}", size, data.len()) ); } // Get sender id let sender_id = PublicKey::new( data[0x22..0x42] .try_into() .map_err(VeilidAPIError::internal)?, ); // Get signature let signature = Signature::new( data[(data.len() - 64)..] .try_into() .map_err(VeilidAPIError::internal)?, ); // Validate signature vcrypto .verify(&sender_id, &data[0..(data.len() - 64)], &signature) .map_err(VeilidAPIError::generic)?; // Get nonce let nonce: Nonce = Nonce::new( data[0x0A..0x22] .try_into() .map_err(VeilidAPIError::internal)?, ); // Get extra data and signature let extra_data: Vec = Vec::from(&data[0x42..(data.len() - 64)]); // Return receipt Ok(Self { version, crypto_kind, nonce, sender_id, extra_data, }) } pub fn to_signed_data( &self, crypto: Crypto, secret: &SecretKey, ) -> Result, VeilidAPIError> { // Ensure extra data isn't too long let receipt_size: usize = self.extra_data.len() + MIN_RECEIPT_SIZE; if receipt_size > MAX_RECEIPT_SIZE { apibail_parse_error!("receipt too large", receipt_size); } // Get crypto version let vcrypto = crypto .get(self.crypto_kind) .expect("need to ensure only valid crypto kinds here"); let mut data: Vec = vec![0u8; receipt_size]; // Write magic data[0x00..0x03].copy_from_slice(RECEIPT_MAGIC); // Write version data[0x03] = self.version; // Write crypto kind data[0x04..0x08].copy_from_slice(&self.crypto_kind.0); // Write size data[0x08..0x0A].copy_from_slice(&(receipt_size as u16).to_le_bytes()); // Write nonce data[0x0A..0x22].copy_from_slice(&self.nonce.bytes); // Write sender node id data[0x22..0x42].copy_from_slice(&self.sender_id.bytes); // Write extra data if !self.extra_data.is_empty() { data[0x42..(receipt_size - 64)].copy_from_slice(self.extra_data.as_slice()); } // Sign the receipt let signature = vcrypto .sign(&self.sender_id, secret, &data[0..(receipt_size - 64)]) .map_err(VeilidAPIError::generic)?; // Append the signature data[(receipt_size - 64)..].copy_from_slice(&signature.bytes); Ok(data) } pub fn get_version(&self) -> u8 { self.version } pub fn get_crypto_kind(&self) -> CryptoKind { self.crypto_kind } pub fn get_nonce(&self) -> Nonce { self.nonce } pub fn get_sender_id(&self) -> PublicKey { self.sender_id } pub fn get_extra_data(&self) -> &[u8] { &self.extra_data } }