wire_vectors.rs (5897B)
1 use chacha20poly1305::aead::{Aead, KeyInit, Payload}; 2 use chacha20poly1305::{Key, XChaCha20Poly1305, XNonce}; 3 use radroots_secrets::context::{ 4 EnvelopeContext, EnvelopePurpose, EnvelopeSubject, PayloadSchemaId, 5 }; 6 7 const V1_ENVELOPE_HEX: &str = "52525331000101010100000007000c656e76656c6f70652d6b6579222222222222222222222222222222222222222222222222000000204b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b00000028f106837e33d690e7c5287abdd815ce9257b7b5b176ea9596abf3b7fe745aec5a8c2487a553d4659d"; 8 const V2_CONTEXT_HEX: &str = "0001726164726f6f74732e656e76656c6f70655f636f6e746578742e76310019726164726f6f74732e707269766174655f61727469666163740010707269766174655f617274696661637400203031303130313031303130313031303130313031303130313031303130313031001674726164652e707269766174655f7465726d732e7631"; 9 const V2_HEADER_HEX: &str = "52525331000201010100000007000c656e76656c6f70652d6b65790001726164726f6f74732e656e76656c6f70655f636f6e746578742e76310019726164726f6f74732e707269766174655f61727469666163740010707269766174655f617274696661637400203031303130313031303130313031303130313031303130313031303130313031001674726164652e707269766174655f7465726d732e7631222222222222222222222222222222222222222222222222000000204b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b00000028"; 10 const V2_CIPHERTEXT_HEX: &str = 11 "f106837e33d690e7c5287abdd815ce9257b7b5b176ea9596f762615d1c221c5c10964c4f799d5d59"; 12 const V2_ENVELOPE_HEX: &str = "52525331000201010100000007000c656e76656c6f70652d6b65790001726164726f6f74732e656e76656c6f70655f636f6e746578742e76310019726164726f6f74732e707269766174655f61727469666163740010707269766174655f617274696661637400203031303130313031303130313031303130313031303130313031303130313031001674726164652e707269766174655f7465726d732e7631222222222222222222222222222222222222222222222222000000204b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b00000028f106837e33d690e7c5287abdd815ce9257b7b5b176ea9596f762615d1c221c5c10964c4f799d5d59"; 13 14 fn context() -> EnvelopeContext { 15 EnvelopeContext::new( 16 EnvelopePurpose::parse("radroots.private_artifact").expect("purpose"), 17 EnvelopeSubject::parse("private_artifact", "01010101010101010101010101010101") 18 .expect("subject"), 19 PayloadSchemaId::parse("trade.private_terms.v1").expect("schema"), 20 ) 21 } 22 23 fn independent_v2_vector() -> (Vec<u8>, Vec<u8>, Vec<u8>) { 24 let context = context().to_canonical_bytes(); 25 let mut header = Vec::new(); 26 header.extend_from_slice(b"RRS1"); 27 header.extend_from_slice(&2_u16.to_be_bytes()); 28 header.extend_from_slice(&[1, 1, 1]); 29 header.extend_from_slice(&7_u32.to_be_bytes()); 30 header.extend_from_slice(&12_u16.to_be_bytes()); 31 header.extend_from_slice(b"envelope-key"); 32 header.extend_from_slice(&context); 33 header.extend_from_slice(&[0x22; 24]); 34 header.extend_from_slice(&32_u32.to_be_bytes()); 35 header.extend_from_slice(&[0x4b; 32]); 36 header.extend_from_slice(&40_u32.to_be_bytes()); 37 38 let cipher = XChaCha20Poly1305::new(Key::from_slice(&[0x11; 32])); 39 let ciphertext = cipher 40 .encrypt( 41 XNonce::from_slice(&[0x22; 24]), 42 Payload { 43 msg: b"radroots envelope vector", 44 aad: &header, 45 }, 46 ) 47 .expect("independent encryption"); 48 let mut envelope = header.clone(); 49 envelope.extend_from_slice(&ciphertext); 50 (header, ciphertext, envelope) 51 } 52 53 #[test] 54 fn v1_corpus_is_stable_and_strictly_bounded() { 55 let bytes = hex::decode(V1_ENVELOPE_HEX).expect("v1 vector hex"); 56 assert_eq!(&bytes[..4], b"RRS1"); 57 assert_eq!(u16::from_be_bytes([bytes[4], bytes[5]]), 1); 58 assert!(bytes.len() < radroots_secrets::envelope::ENVELOPE_MAX_BYTES); 59 let decoded = radroots_secrets::EncryptedEnvelope::decode(&bytes).expect("v1 corpus decode"); 60 assert_eq!(decoded.encode().expect("v1 re-encode"), bytes); 61 } 62 63 #[test] 64 fn v2_vector_freezes_context_header_ciphertext_and_envelope() { 65 let (header, ciphertext, envelope) = independent_v2_vector(); 66 assert_eq!(hex::encode(context().to_canonical_bytes()), V2_CONTEXT_HEX); 67 assert_eq!(hex::encode(header), V2_HEADER_HEX); 68 assert_eq!(hex::encode(ciphertext), V2_CIPHERTEXT_HEX); 69 assert_eq!(hex::encode(envelope), V2_ENVELOPE_HEX); 70 } 71 72 #[test] 73 fn v2_header_positions_and_length_prefixes_are_exact() { 74 let (header, ciphertext, envelope) = independent_v2_vector(); 75 let context_bytes = context().to_canonical_bytes(); 76 assert_eq!(&header[..4], b"RRS1"); 77 assert_eq!(&header[4..6], &2_u16.to_be_bytes()); 78 assert_eq!(&header[6..9], &[1, 1, 1]); 79 assert_eq!(&header[9..13], &7_u32.to_be_bytes()); 80 assert_eq!(&header[13..15], &12_u16.to_be_bytes()); 81 assert_eq!(&header[15..27], b"envelope-key"); 82 assert_eq!(&header[27..27 + context_bytes.len()], &context_bytes); 83 let nonce_offset = 27 + context_bytes.len(); 84 assert_eq!(&header[nonce_offset..nonce_offset + 24], &[0x22; 24]); 85 let wrapped_length_offset = nonce_offset + 24; 86 assert_eq!( 87 &header[wrapped_length_offset..wrapped_length_offset + 4], 88 &32_u32.to_be_bytes() 89 ); 90 assert_eq!( 91 &header[wrapped_length_offset + 4..wrapped_length_offset + 36], 92 &[0x4b; 32] 93 ); 94 assert_eq!(&header[header.len() - 4..], &40_u32.to_be_bytes()); 95 assert_eq!(ciphertext.len(), 40); 96 assert_eq!(envelope.len(), header.len() + ciphertext.len()); 97 } 98 99 #[test] 100 fn vector_corruption_and_truncation_change_authenticated_material() { 101 let (_, _, envelope) = independent_v2_vector(); 102 for offset in [0, 4, 6, 8, 9, 13, 15, 27, envelope.len() - 1] { 103 let mut corrupted = envelope.clone(); 104 corrupted[offset] ^= 1; 105 assert_ne!(hex::encode(corrupted), V2_ENVELOPE_HEX); 106 } 107 for length in [0, 1, 4, 6, 27, envelope.len() - 1] { 108 assert_ne!(hex::encode(&envelope[..length]), V2_ENVELOPE_HEX); 109 } 110 }