initial import of main veilid core
This commit is contained in:
		
							
								
								
									
										287
									
								
								veilid-core/src/dht/crypto.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										287
									
								
								veilid-core/src/dht/crypto.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,287 @@ | ||||
| use super::key::*; | ||||
| use crate::intf::*; | ||||
| use crate::xx::*; | ||||
| use crate::*; | ||||
| use chacha20poly1305 as ch; | ||||
| use chacha20poly1305::aead::{AeadInPlace, NewAead}; | ||||
| use core::convert::TryInto; | ||||
| use curve25519_dalek as cd; | ||||
| use ed25519_dalek as ed; | ||||
| use serde::{Deserialize, Serialize}; | ||||
| use serde_big_array::*; | ||||
| use uluru; | ||||
| use x25519_dalek as xd; | ||||
|  | ||||
| pub type SharedSecret = [u8; 32]; | ||||
| pub type Nonce = [u8; 24]; | ||||
|  | ||||
| const DH_CACHE_SIZE: usize = 1024; | ||||
| pub const ENCRYPTION_OVERHEAD: usize = 16; | ||||
|  | ||||
| big_array! { | ||||
|     BigArray; | ||||
|     DH_CACHE_SIZE | ||||
| } | ||||
|  | ||||
| type DHCache = uluru::LRUCache<DHCacheEntry, DH_CACHE_SIZE>; | ||||
|  | ||||
| #[derive(Serialize, Deserialize)] | ||||
| struct DHCacheEntry { | ||||
|     key: DHTKey, | ||||
|     secret: DHTKeySecret, | ||||
|     shared_secret: SharedSecret, | ||||
| } | ||||
|  | ||||
| fn cache_to_bytes(cache: &DHCache) -> Vec<u8> { | ||||
|     let cnt: usize = cache.len(); | ||||
|     let mut out: Vec<u8> = Vec::with_capacity(cnt * (32 + 32 + 32)); | ||||
|     for e in cache.iter() { | ||||
|         out.extend(&e.key.bytes); | ||||
|         out.extend(&e.secret.bytes); | ||||
|         out.extend(&e.shared_secret); | ||||
|     } | ||||
|     let mut rev: Vec<u8> = Vec::with_capacity(out.len()); | ||||
|     for d in out.chunks(32 + 32 + 32).rev() { | ||||
|         rev.extend(d); | ||||
|     } | ||||
|     rev | ||||
| } | ||||
|  | ||||
| fn bytes_to_cache(bytes: &[u8], cache: &mut DHCache) { | ||||
|     for d in bytes.chunks(32 + 32 + 32) { | ||||
|         let e = DHCacheEntry { | ||||
|             key: DHTKey::new(d[0..32].try_into().expect("asdf")), | ||||
|             secret: DHTKeySecret::new(d[32..64].try_into().expect("asdf")), | ||||
|             shared_secret: d[64..96].try_into().expect("asdf"), | ||||
|         }; | ||||
|         cache.insert(e); | ||||
|     } | ||||
| } | ||||
|  | ||||
| struct CryptoInner { | ||||
|     table_store: TableStore, | ||||
|     node_id: DHTKey, | ||||
|     node_id_secret: DHTKeySecret, | ||||
|     dh_cache: DHCache, | ||||
|     flush_future: Option<SystemPinBoxFuture<()>>, | ||||
| } | ||||
|  | ||||
| #[derive(Clone)] | ||||
| pub struct Crypto { | ||||
|     config: VeilidConfig, | ||||
|     inner: Arc<Mutex<CryptoInner>>, | ||||
| } | ||||
|  | ||||
| impl Crypto { | ||||
|     fn new_inner(table_store: TableStore) -> CryptoInner { | ||||
|         CryptoInner { | ||||
|             table_store: table_store, | ||||
|             node_id: Default::default(), | ||||
|             node_id_secret: Default::default(), | ||||
|             dh_cache: DHCache::default(), | ||||
|             flush_future: None, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub fn new(config: VeilidConfig, table_store: TableStore) -> Self { | ||||
|         Self { | ||||
|             config: config, | ||||
|             inner: Arc::new(Mutex::new(Self::new_inner(table_store))), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub async fn init(&self) -> Result<(), String> { | ||||
|         trace!("Crypto::init"); | ||||
|  | ||||
|         // make local copy of node id for easy access | ||||
|         let mut inner = self.inner.lock(); | ||||
|         let c = self.config.get(); | ||||
|         inner.node_id = c.network.node_id; | ||||
|         inner.node_id_secret = c.network.node_id_secret; | ||||
|  | ||||
|         // load caches if they are valid for this node id | ||||
|         let mut db = inner.table_store.open("crypto_caches", 1).await?; | ||||
|         let caches_valid = match db.load(0, b"node_id").await? { | ||||
|             Some(v) => v.as_slice() == inner.node_id.bytes, | ||||
|             None => false, | ||||
|         }; | ||||
|         if caches_valid { | ||||
|             match db.load(0, b"dh_cache").await? { | ||||
|                 Some(b) => { | ||||
|                     bytes_to_cache(&b, &mut inner.dh_cache); | ||||
|                 } | ||||
|                 None => (), | ||||
|             }; | ||||
|         } else { | ||||
|             drop(db); | ||||
|             inner.table_store.delete("crypto_caches").await?; | ||||
|             db = inner.table_store.open("crypto_caches", 1).await?; | ||||
|             db.store(0, b"node_id", &inner.node_id.bytes).await?; | ||||
|         } | ||||
|  | ||||
|         // Schedule flushing | ||||
|         let this = self.clone(); | ||||
|         inner.flush_future = Some(Box::pin(interval(60000, move || { | ||||
|             let this = this.clone(); | ||||
|             async move { | ||||
|                 if let Err(e) = this.flush().await { | ||||
|                     warn!("flush failed: {}", e); | ||||
|                 } | ||||
|             } | ||||
|         }))); | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     pub async fn flush(&self) -> Result<(), String> { | ||||
|         //trace!("Crypto::flush"); | ||||
|         let (table_store, cache_bytes) = { | ||||
|             let inner = self.inner.lock(); | ||||
|             let cache_bytes = cache_to_bytes(&inner.dh_cache); | ||||
|             (inner.table_store.clone(), cache_bytes) | ||||
|         }; | ||||
|  | ||||
|         let db = table_store.open("crypto_caches", 1).await?; | ||||
|         db.store(0, b"dh_cache", &cache_bytes).await?; | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     pub async fn terminate(&self) { | ||||
|         trace!("Crypto::terminate"); | ||||
|         let flush_future = self.inner.lock().flush_future.take(); | ||||
|         if let Some(f) = flush_future { | ||||
|             f.await; | ||||
|         } | ||||
|         trace!("starting termination flush"); | ||||
|         match self.flush().await { | ||||
|             Ok(_) => { | ||||
|                 trace!("finished termination flush"); | ||||
|                 () | ||||
|             } | ||||
|             Err(e) => { | ||||
|                 error!("failed termination flush: {}", e); | ||||
|                 () | ||||
|             } | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     fn ed25519_to_x25519_pk(key: &ed::PublicKey) -> Result<xd::PublicKey, ()> { | ||||
|         let bytes = key.to_bytes(); | ||||
|         let compressed = cd::edwards::CompressedEdwardsY(bytes); | ||||
|         let point = compressed.decompress().ok_or(())?; | ||||
|         let mp = point.to_montgomery(); | ||||
|         Ok(xd::PublicKey::from(mp.to_bytes())) | ||||
|     } | ||||
|     fn ed25519_to_x25519_sk(key: &ed::SecretKey) -> Result<xd::StaticSecret, ()> { | ||||
|         let exp = ed::ExpandedSecretKey::from(key); | ||||
|         let bytes: [u8; ed::EXPANDED_SECRET_KEY_LENGTH] = exp.to_bytes(); | ||||
|         let lowbytes: [u8; 32] = bytes[0..32].try_into().map_err(drop)?; | ||||
|         Ok(xd::StaticSecret::from(lowbytes)) | ||||
|     } | ||||
|  | ||||
|     pub fn cached_dh(&self, key: &DHTKey, secret: &DHTKeySecret) -> Result<SharedSecret, ()> { | ||||
|         if let Some(c) = self | ||||
|             .inner | ||||
|             .lock() | ||||
|             .dh_cache | ||||
|             .find(|entry| entry.key == *key && entry.secret == *secret) | ||||
|         { | ||||
|             return Ok(c.shared_secret); | ||||
|         } | ||||
|  | ||||
|         let ss = Self::compute_dh(key, secret)?; | ||||
|         self.inner.lock().dh_cache.insert(DHCacheEntry { | ||||
|             key: key.clone(), | ||||
|             secret: secret.clone(), | ||||
|             shared_secret: ss.clone(), | ||||
|         }); | ||||
|         Ok(ss) | ||||
|     } | ||||
|  | ||||
|     /////////// | ||||
|     // These are safe to use regardless of initialization status | ||||
|  | ||||
|     pub fn compute_dh(key: &DHTKey, secret: &DHTKeySecret) -> Result<SharedSecret, ()> { | ||||
|         assert!(key.valid); | ||||
|         assert!(secret.valid); | ||||
|         let pk_ed = match ed::PublicKey::from_bytes(&key.bytes) { | ||||
|             Ok(v) => v, | ||||
|             Err(e) => { | ||||
|                 trace!("compute_dh error: {:?}", e); | ||||
|                 return Err(()); | ||||
|             } | ||||
|         }; | ||||
|         let pk_xd = Self::ed25519_to_x25519_pk(&pk_ed)?; | ||||
|         let sk_ed = match ed::SecretKey::from_bytes(&secret.bytes) { | ||||
|             Ok(v) => v, | ||||
|             Err(e) => { | ||||
|                 trace!("compute_dh error: {:?}", e); | ||||
|                 return Err(()); | ||||
|             } | ||||
|         }; | ||||
|         let sk_xd = Self::ed25519_to_x25519_sk(&sk_ed)?; | ||||
|         Ok(sk_xd.diffie_hellman(&pk_xd).to_bytes()) | ||||
|     } | ||||
|  | ||||
|     pub fn get_random_nonce() -> Nonce { | ||||
|         let mut nonce = [0u8; 24]; | ||||
|         let _ = random_bytes(&mut nonce).unwrap(); | ||||
|         nonce | ||||
|     } | ||||
|  | ||||
|     pub fn get_random_secret() -> SharedSecret { | ||||
|         let mut s = [0u8; 32]; | ||||
|         let _ = random_bytes(&mut s).unwrap(); | ||||
|         s | ||||
|     } | ||||
|  | ||||
|     pub fn decrypt_in_place( | ||||
|         body: &mut Vec<u8>, | ||||
|         nonce: &Nonce, | ||||
|         shared_secret: &SharedSecret, | ||||
|         associated_data: Option<&[u8]>, | ||||
|     ) -> Result<(), ()> { | ||||
|         let key = ch::Key::from(shared_secret.clone()); | ||||
|         let xnonce = ch::XNonce::from(nonce.clone()); | ||||
|         let aead = ch::XChaCha20Poly1305::new(&key); | ||||
|         aead.decrypt_in_place(&xnonce, associated_data.unwrap_or(b""), body) | ||||
|             .map_err(|e| trace!("decryption failure: {}", e)) | ||||
|     } | ||||
|  | ||||
|     pub fn decrypt( | ||||
|         body: &[u8], | ||||
|         nonce: &Nonce, | ||||
|         shared_secret: &SharedSecret, | ||||
|         associated_data: Option<&[u8]>, | ||||
|     ) -> Result<Vec<u8>, ()> { | ||||
|         let mut out = body.to_vec(); | ||||
|         let _ = Self::decrypt_in_place(&mut out, nonce, shared_secret, associated_data)?; | ||||
|         Ok(out) | ||||
|     } | ||||
|  | ||||
|     pub fn encrypt_in_place( | ||||
|         body: &mut Vec<u8>, | ||||
|         nonce: &Nonce, | ||||
|         shared_secret: &SharedSecret, | ||||
|         associated_data: Option<&[u8]>, | ||||
|     ) -> Result<(), ()> { | ||||
|         let key = ch::Key::from(shared_secret.clone()); | ||||
|         let xnonce = ch::XNonce::from(nonce.clone()); | ||||
|         let aead = ch::XChaCha20Poly1305::new(&key); | ||||
|  | ||||
|         aead.encrypt_in_place(&xnonce, associated_data.unwrap_or(b""), body) | ||||
|             .map_err(|e| trace!("encryption failure: {}", e)) | ||||
|     } | ||||
|  | ||||
|     pub fn encrypt( | ||||
|         body: &[u8], | ||||
|         nonce: &Nonce, | ||||
|         shared_secret: &SharedSecret, | ||||
|         associated_data: Option<&[u8]>, | ||||
|     ) -> Result<Vec<u8>, ()> { | ||||
|         let mut out = body.to_vec(); | ||||
|         let _ = Self::encrypt_in_place(&mut out, nonce, shared_secret, associated_data)?; | ||||
|         Ok(out) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										267
									
								
								veilid-core/src/dht/envelope.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										267
									
								
								veilid-core/src/dht/envelope.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,267 @@ | ||||
| use super::crypto::*; | ||||
| use super::key::*; | ||||
| use crate::xx::*; | ||||
| use core::convert::TryInto; | ||||
|  | ||||
| // #[repr(C, packed)] | ||||
| // struct EnvelopeHeader { | ||||
| //     // Size is at least 8 bytes. Depending on the version specified, the size may vary and should be case to the appropriate struct | ||||
| //     magic: [u8; 4],              // 0x00: 0x56 0x4C 0x49 0x44 ("VLID") | ||||
| //     version: u8,                 // 0x04: 0 = EnvelopeV0 | ||||
| //     min_version: u8,             // 0x05: 0 = EnvelopeV0 | ||||
| //     max_version: u8,             // 0x06: 0 = EnvelopeV0 | ||||
| //     reserved: u8,                // 0x07: Reserved for future use | ||||
| // } | ||||
|  | ||||
| // #[repr(C, packed)] | ||||
| // struct EnvelopeV0 { | ||||
| //     // Size is 106 bytes. | ||||
| //     magic: [u8; 4],              // 0x00: 0x56 0x4C 0x49 0x44 ("VLID") | ||||
| //     version: u8,                 // 0x04: 0 = EnvelopeV0 | ||||
| //     min_version: u8,             // 0x05: 0 = EnvelopeV0 | ||||
| //     max_version: u8,             // 0x06: 0 = EnvelopeV0 | ||||
| //     reserved: u8,                // 0x07: Reserved for future use | ||||
| //     size: u16,                   // 0x08: Total size of the envelope including the encrypted operations message. Maximum size is 65,507 bytes, which is the data size limit for a single UDP message on IPv4. | ||||
| //     timestamp: u64,              // 0x0A: Duration since UNIX_EPOCH in microseconds when this message is sent. Messages older than 10 seconds are dropped. | ||||
| //     nonce: [u8; 24],             // 0x12: Random nonce for replay protection and for x25519 | ||||
| //     sender_id: [u8; 32],         // 0x2A: Node ID of the message source, which is the Ed25519 public key of the sender (must be verified with find_node if this is a new node_id/address combination) | ||||
| //     recipient_id: [u8; 32],      // 0x4A: Node ID of the intended recipient, which is the Ed25519 public key of the recipient (must be the receiving node, or a relay lease holder) | ||||
| //                                  // 0x6A: message is appended (operations) | ||||
| //                                  //       encrypted by XChaCha20Poly1305(nonce,x25519(recipient_id, sender_secret_key)) | ||||
| //                                  //       decryptable by XChaCha20Poly1305(nonce,x25519(sender_id, recipient_secret_key)) | ||||
| //                                  //       entire header needs to be included in message digest, relays are not allowed to modify the envelope without invalidating the signature. | ||||
| // } | ||||
|  | ||||
| pub const MAX_ENVELOPE_SIZE: usize = 65507; | ||||
| pub const MIN_ENVELOPE_SIZE: usize = 106; | ||||
| pub const AEAD_ADDITIONAL_SIZE: usize = 16; | ||||
| pub const ENVELOPE_MAGIC: &[u8; 4] = b"VLID"; | ||||
| pub const MIN_VERSION: u8 = 0u8; | ||||
| pub const MAX_VERSION: u8 = 0u8; | ||||
| pub type EnvelopeNonce = [u8; 24]; | ||||
|  | ||||
| #[derive(Debug, Clone, PartialEq, Eq, Default)] | ||||
| pub struct Envelope { | ||||
|     version: u8, | ||||
|     min_version: u8, | ||||
|     max_version: u8, | ||||
|     timestamp: u64, | ||||
|     nonce: EnvelopeNonce, | ||||
|     sender_id: DHTKey, | ||||
|     recipient_id: DHTKey, | ||||
| } | ||||
|  | ||||
| impl Envelope { | ||||
|     pub fn new( | ||||
|         version: u8, | ||||
|         timestamp: u64, | ||||
|         nonce: EnvelopeNonce, | ||||
|         sender_id: DHTKey, | ||||
|         recipient_id: DHTKey, | ||||
|     ) -> Self { | ||||
|         assert!(sender_id.valid); | ||||
|         assert!(recipient_id.valid); | ||||
|  | ||||
|         assert!(version >= MIN_VERSION); | ||||
|         assert!(version <= MAX_VERSION); | ||||
|         Self { | ||||
|             version: version, | ||||
|             min_version: MIN_VERSION, | ||||
|             max_version: MAX_VERSION, | ||||
|             timestamp: timestamp, | ||||
|             nonce: nonce, | ||||
|             sender_id: sender_id, | ||||
|             recipient_id: recipient_id, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub fn from_data(data: &[u8]) -> Result<Envelope, ()> { | ||||
|         // Ensure we are at least the length of the envelope | ||||
|         if data.len() < MIN_ENVELOPE_SIZE { | ||||
|             trace!("envelope too small: len={}", data.len()); | ||||
|             return Err(()); | ||||
|         } | ||||
|  | ||||
|         // Verify magic number | ||||
|         let magic: [u8; 4] = data[0x00..0x04].try_into().map_err(drop)?; | ||||
|         if magic != *ENVELOPE_MAGIC { | ||||
|             trace!("bad magic number: len={:?}", magic); | ||||
|             return Err(()); | ||||
|         } | ||||
|  | ||||
|         // Check version | ||||
|         let version = data[0x04]; | ||||
|         if version > MAX_VERSION || version < MIN_VERSION { | ||||
|             trace!("unsupported protocol version: version={}", version); | ||||
|             return Err(()); | ||||
|         } | ||||
|  | ||||
|         // Get min version | ||||
|         let min_version = data[0x05]; | ||||
|         if min_version > version { | ||||
|             trace!( | ||||
|                 "invalid version information in envelope: min_version={}, version={}", | ||||
|                 min_version, | ||||
|                 version, | ||||
|             ); | ||||
|             return Err(()); | ||||
|         } | ||||
|  | ||||
|         // Get max version | ||||
|         let max_version = data[0x06]; | ||||
|         if version > max_version || min_version > max_version { | ||||
|             trace!( | ||||
|                 "invalid version information in envelope: min_version={}, version={}, max_version={}", | ||||
|                 min_version, | ||||
|                 version, | ||||
|                 max_version | ||||
|             ); | ||||
|             return Err(()); | ||||
|         } | ||||
|  | ||||
|         // 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(drop)?); | ||||
|         if (size as usize) > MAX_ENVELOPE_SIZE { | ||||
|             trace!("envelope size is too large: size={}", size); | ||||
|             return Err(()); | ||||
|         } | ||||
|         if (size as usize) != data.len() { | ||||
|             trace!( | ||||
|                 "size doesn't match envelope size: size={} data.len()={}", | ||||
|                 size, | ||||
|                 data.len() | ||||
|             ); | ||||
|             return Err(()); | ||||
|         } | ||||
|  | ||||
|         // Get the timestamp | ||||
|         let timestamp: u64 = u64::from_le_bytes(data[0x0A..0x12].try_into().map_err(drop)?); | ||||
|  | ||||
|         // Get nonce and sender node id | ||||
|         let nonce: EnvelopeNonce = data[0x12..0x2A].try_into().map_err(drop)?; | ||||
|         let sender_id: [u8; 32] = data[0x2A..0x4A].try_into().map_err(drop)?; | ||||
|         let recipient_id: [u8; 32] = data[0x4A..0x6A].try_into().map_err(drop)?; | ||||
|         let sender_id_dhtkey = DHTKey::new(sender_id); | ||||
|         let recipient_id_dhtkey = DHTKey::new(recipient_id); | ||||
|  | ||||
|         // Ensure sender_id and recipient_id are not the same | ||||
|         if sender_id_dhtkey == recipient_id_dhtkey { | ||||
|             trace!( | ||||
|                 "sender_id should not be same as recipient_id: {}", | ||||
|                 recipient_id_dhtkey.encode() | ||||
|             ); | ||||
|             return Err(()); | ||||
|         } | ||||
|  | ||||
|         // Return envelope | ||||
|         Ok(Self { | ||||
|             version: version, | ||||
|             min_version: min_version, | ||||
|             max_version: max_version, | ||||
|             timestamp: timestamp, | ||||
|             nonce: nonce, | ||||
|             sender_id: sender_id_dhtkey, | ||||
|             recipient_id: recipient_id_dhtkey, | ||||
|         }) | ||||
|     } | ||||
|  | ||||
|     pub fn decrypt_body( | ||||
|         &self, | ||||
|         crypto: Crypto, | ||||
|         data: &[u8], | ||||
|         node_id_secret: &DHTKeySecret, | ||||
|     ) -> Result<Vec<u8>, ()> { | ||||
|         // Get DH secret | ||||
|         let dh_secret = crypto.cached_dh(&self.sender_id, node_id_secret)?; | ||||
|  | ||||
|         // Decrypt message and authenticate, including the envelope header as associated data to authenticate | ||||
|         let body = Crypto::decrypt( | ||||
|             &data[0x6A..], | ||||
|             &self.nonce, | ||||
|             &dh_secret, | ||||
|             Some(&data[0..MIN_ENVELOPE_SIZE]), | ||||
|         )?; | ||||
|  | ||||
|         Ok(body) | ||||
|     } | ||||
|  | ||||
|     pub fn to_encrypted_data( | ||||
|         &self, | ||||
|         crypto: Crypto, | ||||
|         body: &[u8], | ||||
|         node_id_secret: &DHTKeySecret, | ||||
|     ) -> Result<Vec<u8>, ()> { | ||||
|         // Ensure sender node id is valid | ||||
|         if !self.sender_id.valid { | ||||
|             return Err(()); | ||||
|         } | ||||
|         // Ensure recipient node id is valid | ||||
|         if !self.recipient_id.valid { | ||||
|             return Err(()); | ||||
|         } | ||||
|  | ||||
|         // Ensure body isn't too long | ||||
|         let envelope_size: usize = body.len() + MIN_ENVELOPE_SIZE + AEAD_ADDITIONAL_SIZE; | ||||
|         if envelope_size > MAX_ENVELOPE_SIZE { | ||||
|             return Err(()); | ||||
|         } | ||||
|         let mut data: Vec<u8> = Vec::with_capacity(envelope_size); | ||||
|         data.resize(envelope_size, 0u8); | ||||
|  | ||||
|         // Write magic | ||||
|         data[0x00..0x04].copy_from_slice(ENVELOPE_MAGIC); | ||||
|         // Write version | ||||
|         data[0x04] = self.version; | ||||
|         // Write min version | ||||
|         data[0x05] = self.min_version; | ||||
|         // Write max version | ||||
|         data[0x06] = self.max_version; | ||||
|         // Write size | ||||
|         data[0x08..0x0A].copy_from_slice(&(envelope_size as u16).to_le_bytes()); | ||||
|         // Write timestamp | ||||
|         data[0x0A..0x12].copy_from_slice(&self.timestamp.to_le_bytes()); | ||||
|         // Write nonce | ||||
|         data[0x12..0x2A].copy_from_slice(&self.nonce); | ||||
|         // Write sender node id | ||||
|         data[0x2A..0x4A].copy_from_slice(&self.sender_id.bytes); | ||||
|         // Write recipient node id | ||||
|         data[0x4A..0x6A].copy_from_slice(&self.recipient_id.bytes); | ||||
|  | ||||
|         // Generate dh secret | ||||
|         let dh_secret = crypto | ||||
|             .cached_dh(&self.recipient_id, node_id_secret) | ||||
|             .map_err(drop)?; | ||||
|  | ||||
|         // Encrypt and authenticate message | ||||
|         let encrypted_body = | ||||
|             Crypto::encrypt(body, &self.nonce, &dh_secret, Some(&data[0..0x6A])).map_err(drop)?; | ||||
|  | ||||
|         // Write body | ||||
|         data[0x6A..].copy_from_slice(encrypted_body.as_slice()); | ||||
|  | ||||
|         Ok(data) | ||||
|     } | ||||
|  | ||||
|     pub fn get_version(&self) -> u8 { | ||||
|         self.version | ||||
|     } | ||||
|  | ||||
|     pub fn get_min_max_version(&self) -> (u8, u8) { | ||||
|         (self.min_version, self.max_version) | ||||
|     } | ||||
|  | ||||
|     pub fn get_timestamp(&self) -> u64 { | ||||
|         self.timestamp | ||||
|     } | ||||
|  | ||||
|     pub fn get_nonce(&self) -> EnvelopeNonce { | ||||
|         self.nonce | ||||
|     } | ||||
|  | ||||
|     pub fn get_sender_id(&self) -> DHTKey { | ||||
|         self.sender_id | ||||
|     } | ||||
|     pub fn get_recipient_id(&self) -> DHTKey { | ||||
|         self.recipient_id | ||||
|     } | ||||
| } | ||||
							
								
								
									
										432
									
								
								veilid-core/src/dht/key.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										432
									
								
								veilid-core/src/dht/key.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,432 @@ | ||||
| use crate::xx::*; | ||||
| use core::cmp::{Eq, Ord, Ordering, PartialEq, PartialOrd}; | ||||
| use core::convert::{TryFrom, TryInto}; | ||||
| use core::fmt; | ||||
| use hex; | ||||
|  | ||||
| use crate::veilid_rng::*; | ||||
| use ed25519_dalek::{Keypair, PublicKey, Signature}; | ||||
| use serde::{Deserialize, Serialize}; | ||||
|  | ||||
| use data_encoding::BASE64URL_NOPAD; | ||||
| use digest::generic_array::typenum::U64; | ||||
| use digest::{Digest, Output}; | ||||
| use generic_array::GenericArray; | ||||
|  | ||||
| ////////////////////////////////////////////////////////////////////// | ||||
|  | ||||
| #[allow(dead_code)] | ||||
| pub const DHT_KEY_LENGTH: usize = 32; | ||||
| #[allow(dead_code)] | ||||
| pub const DHT_KEY_LENGTH_ENCODED: usize = 43; | ||||
| #[allow(dead_code)] | ||||
| pub const DHT_KEY_SECRET_LENGTH: usize = 32; | ||||
| #[allow(dead_code)] | ||||
| pub const DHT_KEY_SECRET_LENGTH_ENCODED: usize = 43; | ||||
| #[allow(dead_code)] | ||||
| pub const DHT_SIGNATURE_LENGTH: usize = 64; | ||||
| #[allow(dead_code)] | ||||
| pub const DHT_SIGNATURE_LENGTH_ENCODED: usize = 86; | ||||
|  | ||||
| ////////////////////////////////////////////////////////////////////// | ||||
|  | ||||
| macro_rules! byte_array_type { | ||||
|     ($name:ident, $size:expr) => { | ||||
|         #[derive(Clone, Copy)] | ||||
|         pub struct $name { | ||||
|             pub bytes: [u8; $size], | ||||
|             pub valid: bool, | ||||
|         } | ||||
|  | ||||
|         impl Serialize for $name { | ||||
|             fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> | ||||
|             where | ||||
|                 S: serde::Serializer, | ||||
|             { | ||||
|                 let s: String; | ||||
|                 if self.valid { | ||||
|                     s = self.encode(); | ||||
|                 } else { | ||||
|                     s = "".to_owned(); | ||||
|                 } | ||||
|                 s.serialize(serializer) | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         impl<'de> Deserialize<'de> for $name { | ||||
|             fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> | ||||
|             where | ||||
|                 D: serde::Deserializer<'de>, | ||||
|             { | ||||
|                 let s = String::deserialize(deserializer)?; | ||||
|                 if s == "" { | ||||
|                     return Ok($name::default()); | ||||
|                 } | ||||
|                 $name::try_decode(s.as_str()).map_err(|e| serde::de::Error::custom(e)) | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         impl $name { | ||||
|             pub fn new(bytes: [u8; $size]) -> Self { | ||||
|                 Self { | ||||
|                     bytes: bytes, | ||||
|                     valid: true, | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             pub fn try_from_vec(v: Vec<u8>) -> Result<Self, String> { | ||||
|                 let mut this = Self { | ||||
|                     bytes: [0u8; $size], | ||||
|                     valid: true, | ||||
|                 }; | ||||
|  | ||||
|                 if v.len() != $size { | ||||
|                     return Err(format!( | ||||
|                         "Expected a Vec of length {} but it was {}", | ||||
|                         $size, | ||||
|                         v.len() | ||||
|                     )); | ||||
|                 } | ||||
|  | ||||
|                 for n in 0..v.len() { | ||||
|                     this.bytes[n] = v[n]; | ||||
|                 } | ||||
|  | ||||
|                 Ok(this) | ||||
|             } | ||||
|  | ||||
|             pub fn bit(&self, index: usize) -> bool { | ||||
|                 assert!(index < ($size * 8)); | ||||
|                 let bi = index / 8; | ||||
|                 let ti = 7 - (index % 8); | ||||
|                 ((self.bytes[bi] >> ti) & 1) != 0 | ||||
|             } | ||||
|  | ||||
|             pub fn first_nonzero_bit(&self) -> Option<usize> { | ||||
|                 for i in 0..$size { | ||||
|                     let b = self.bytes[i]; | ||||
|                     if b != 0 { | ||||
|                         for n in 0..8 { | ||||
|                             if ((b >> (7 - n)) & 1u8) != 0u8 { | ||||
|                                 return Some((i * 8) + n); | ||||
|                             } | ||||
|                         } | ||||
|                         panic!("wtf") | ||||
|                     } | ||||
|                 } | ||||
|                 None | ||||
|             } | ||||
|  | ||||
|             pub fn nibble(&self, index: usize) -> u8 { | ||||
|                 assert!(index < ($size * 2)); | ||||
|                 let bi = index / 2; | ||||
|                 if index & 1 == 0 { | ||||
|                     (self.bytes[bi] >> 4) & 0xFu8 | ||||
|                 } else { | ||||
|                     self.bytes[bi] & 0xFu8 | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             pub fn first_nonzero_nibble(&self) -> Option<(usize, u8)> { | ||||
|                 for i in 0..($size * 2) { | ||||
|                     let n = self.nibble(i); | ||||
|                     if n != 0 { | ||||
|                         return Some((i, n)); | ||||
|                     } | ||||
|                 } | ||||
|                 None | ||||
|             } | ||||
|  | ||||
|             pub fn encode(&self) -> String { | ||||
|                 assert!(self.valid); | ||||
|                 BASE64URL_NOPAD.encode(&self.bytes) | ||||
|             } | ||||
|  | ||||
|             pub fn try_decode(input: &str) -> Result<Self, String> { | ||||
|                 let mut bytes = [0u8; $size]; | ||||
|  | ||||
|                 let res = BASE64URL_NOPAD.decode_len(input.len()); | ||||
|                 match res { | ||||
|                     Ok(v) => { | ||||
|                         if v != $size { | ||||
|                             return Err("Incorrect length in decode".to_owned()); | ||||
|                         } | ||||
|                     } | ||||
|                     Err(_) => { | ||||
|                         return Err("Failed to decode".to_owned()); | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 let res = BASE64URL_NOPAD.decode_mut(input.as_bytes(), &mut bytes); | ||||
|                 match res { | ||||
|                     Ok(_) => Ok(Self::new(bytes)), | ||||
|                     Err(_) => Err("Failed to decode".to_owned()), | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         impl PartialOrd for $name { | ||||
|             fn partial_cmp(&self, other: &$name) -> Option<Ordering> { | ||||
|                 Some(self.cmp(other)) | ||||
|             } | ||||
|         } | ||||
|         impl Ord for $name { | ||||
|             fn cmp(&self, other: &$name) -> Ordering { | ||||
|                 if !self.valid && !other.valid { | ||||
|                     return Ordering::Equal; | ||||
|                 } | ||||
|                 if !self.valid && other.valid { | ||||
|                     return Ordering::Less; | ||||
|                 } | ||||
|                 if self.valid && !other.valid { | ||||
|                     return Ordering::Greater; | ||||
|                 } | ||||
|  | ||||
|                 for n in 0..$size { | ||||
|                     if self.bytes[n] < other.bytes[n] { | ||||
|                         return Ordering::Less; | ||||
|                     } | ||||
|                     if self.bytes[n] > other.bytes[n] { | ||||
|                         return Ordering::Greater; | ||||
|                     } | ||||
|                 } | ||||
|                 Ordering::Equal | ||||
|             } | ||||
|         } | ||||
|         impl PartialEq<$name> for $name { | ||||
|             fn eq(&self, other: &$name) -> bool { | ||||
|                 if self.valid != other.valid { | ||||
|                     return false; | ||||
|                 } | ||||
|                 for n in 0..$size { | ||||
|                     if self.bytes[n] != other.bytes[n] { | ||||
|                         return false; | ||||
|                     } | ||||
|                 } | ||||
|                 true | ||||
|             } | ||||
|         } | ||||
|         impl Eq for $name {} | ||||
|         impl Default for $name { | ||||
|             fn default() -> Self { | ||||
|                 let mut this = $name::new([0u8; $size]); | ||||
|                 this.valid = false; | ||||
|                 this | ||||
|             } | ||||
|         } | ||||
|         impl fmt::Display for $name { | ||||
|             fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||||
|                 write!(f, "{}", String::from(self)) | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         impl fmt::Debug for $name { | ||||
|             fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||||
|                 write!(f, concat!(stringify!($name), "("))?; | ||||
|                 write!(f, "{}", String::from(self))?; | ||||
|                 write!(f, ")") | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         impl From<&$name> for String { | ||||
|             fn from(value: &$name) -> Self { | ||||
|                 if !value.valid { | ||||
|                     return "".to_owned(); | ||||
|                 } | ||||
|                 let mut s = String::new(); | ||||
|                 for n in 0..($size / 8) { | ||||
|                     let b: [u8; 8] = value.bytes[n * 8..(n + 1) * 8].try_into().unwrap(); | ||||
|                     s.push_str(hex::encode(b).as_str()); | ||||
|                 } | ||||
|                 s | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         impl TryFrom<String> for $name { | ||||
|             type Error = String; | ||||
|             fn try_from(value: String) -> Result<Self, Self::Error> { | ||||
|                 $name::try_from(value.as_str()) | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         impl TryFrom<&str> for $name { | ||||
|             type Error = String; | ||||
|             fn try_from(value: &str) -> Result<Self, Self::Error> { | ||||
|                 let mut out = $name::default(); | ||||
|                 if value == "" { | ||||
|                     return Ok(out); | ||||
|                 } | ||||
|                 if value.len() != ($size * 2) { | ||||
|                     return Err(concat!(stringify!($name), " is incorrect length").to_owned()); | ||||
|                 } | ||||
|                 match hex::decode_to_slice(value, &mut out.bytes) { | ||||
|                     Ok(_) => { | ||||
|                         out.valid = true; | ||||
|                         Ok(out) | ||||
|                     } | ||||
|                     Err(err) => Err(format!("{}", err)), | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
| } | ||||
|  | ||||
| byte_array_type!(DHTKey, DHT_KEY_LENGTH); | ||||
| byte_array_type!(DHTKeySecret, DHT_KEY_SECRET_LENGTH); | ||||
| byte_array_type!(DHTSignature, DHT_SIGNATURE_LENGTH); | ||||
| byte_array_type!(DHTKeyDistance, DHT_KEY_LENGTH); | ||||
|  | ||||
| ///////////////////////////////////////// | ||||
|  | ||||
| struct Blake3Digest512 { | ||||
|     dig: blake3::Hasher, | ||||
| } | ||||
|  | ||||
| impl Digest for Blake3Digest512 { | ||||
|     type OutputSize = U64; | ||||
|  | ||||
|     fn new() -> Self { | ||||
|         Self { | ||||
|             dig: blake3::Hasher::new(), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn update(&mut self, data: impl AsRef<[u8]>) { | ||||
|         self.dig.update(data.as_ref()); | ||||
|     } | ||||
|  | ||||
|     fn chain(mut self, data: impl AsRef<[u8]>) -> Self | ||||
|     where | ||||
|         Self: Sized, | ||||
|     { | ||||
|         self.update(data); | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     fn finalize(self) -> Output<Self> { | ||||
|         let mut b = [0u8; 64]; | ||||
|         self.dig.finalize_xof().fill(&mut b); | ||||
|         let mut out = GenericArray::<u8, U64>::default(); | ||||
|         for n in 0..64 { | ||||
|             out[n] = b[n]; | ||||
|         } | ||||
|         out | ||||
|     } | ||||
|  | ||||
|     fn finalize_reset(&mut self) -> Output<Self> { | ||||
|         let mut b = [0u8; 64]; | ||||
|         self.dig.finalize_xof().fill(&mut b); | ||||
|         let mut out = GenericArray::<u8, U64>::default(); | ||||
|         for n in 0..64 { | ||||
|             out[n] = b[n]; | ||||
|         } | ||||
|         self.reset(); | ||||
|         out | ||||
|     } | ||||
|  | ||||
|     fn reset(&mut self) { | ||||
|         self.dig.reset(); | ||||
|     } | ||||
|  | ||||
|     fn output_size() -> usize { | ||||
|         64 | ||||
|     } | ||||
|  | ||||
|     fn digest(data: &[u8]) -> Output<Self> { | ||||
|         let mut dig = blake3::Hasher::new(); | ||||
|         dig.update(data); | ||||
|         let mut b = [0u8; 64]; | ||||
|         dig.finalize_xof().fill(&mut b); | ||||
|         let mut out = GenericArray::<u8, U64>::default(); | ||||
|         for n in 0..64 { | ||||
|             out[n] = b[n]; | ||||
|         } | ||||
|         out | ||||
|     } | ||||
| } | ||||
|  | ||||
| ///////////////////////////////////////// | ||||
|  | ||||
| pub fn generate_secret() -> (DHTKey, DHTKeySecret) { | ||||
|     let mut csprng = VeilidRng {}; | ||||
|     let keypair = Keypair::generate(&mut csprng); | ||||
|     let dht_key = DHTKey::new(keypair.public.to_bytes()); | ||||
|     let dht_key_secret = DHTKeySecret::new(keypair.secret.to_bytes()); | ||||
|  | ||||
|     (dht_key, dht_key_secret) | ||||
| } | ||||
|  | ||||
| pub fn sign( | ||||
|     dht_key: &DHTKey, | ||||
|     dht_key_secret: &DHTKeySecret, | ||||
|     data: &[u8], | ||||
| ) -> Result<DHTSignature, String> { | ||||
|     assert!(dht_key.valid); | ||||
|     assert!(dht_key_secret.valid); | ||||
|  | ||||
|     let mut kpb: [u8; DHT_KEY_SECRET_LENGTH + DHT_KEY_LENGTH] = | ||||
|         [0u8; DHT_KEY_SECRET_LENGTH + DHT_KEY_LENGTH]; | ||||
|  | ||||
|     kpb[..DHT_KEY_SECRET_LENGTH].copy_from_slice(&dht_key_secret.bytes); | ||||
|     kpb[DHT_KEY_SECRET_LENGTH..].copy_from_slice(&dht_key.bytes); | ||||
|     let keypair = Keypair::from_bytes(&kpb).map_err(|_| "Keypair is invalid".to_owned())?; | ||||
|  | ||||
|     let mut dig = Blake3Digest512::new(); | ||||
|     dig.update(data); | ||||
|  | ||||
|     let sig = keypair | ||||
|         .sign_prehashed(dig, None) | ||||
|         .map_err(|_| "Signature failed".to_owned())?; | ||||
|  | ||||
|     let dht_sig = DHTSignature::new(sig.to_bytes().clone()); | ||||
|     Ok(dht_sig) | ||||
| } | ||||
|  | ||||
| pub fn verify(dht_key: &DHTKey, data: &[u8], signature: &DHTSignature) -> Result<(), String> { | ||||
|     assert!(dht_key.valid); | ||||
|     assert!(signature.valid); | ||||
|     let pk = | ||||
|         PublicKey::from_bytes(&dht_key.bytes).map_err(|_| "Public key is invalid".to_owned())?; | ||||
|     let sig = | ||||
|         Signature::from_bytes(&signature.bytes).map_err(|_| "Signature is invalid".to_owned())?; | ||||
|  | ||||
|     let mut dig = Blake3Digest512::new(); | ||||
|     dig.update(data); | ||||
|  | ||||
|     pk.verify_prehashed(dig, None, &sig) | ||||
|         .map_err(|_| "Verification failed".to_owned())?; | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
| pub fn generate_hash(data: &[u8]) -> DHTKey { | ||||
|     DHTKey::new(*blake3::hash(data).as_bytes()) | ||||
| } | ||||
|  | ||||
| pub fn validate_hash(data: &[u8], dht_key: &DHTKey) -> bool { | ||||
|     assert!(dht_key.valid); | ||||
|     let bytes = *blake3::hash(data).as_bytes(); | ||||
|  | ||||
|     bytes == dht_key.bytes | ||||
| } | ||||
|  | ||||
| pub fn validate_key(dht_key: &DHTKey, dht_key_secret: &DHTKeySecret) -> bool { | ||||
|     let data = vec![0u8; 512]; | ||||
|     let sig = match sign(&dht_key, &dht_key_secret, &data) { | ||||
|         Ok(s) => s, | ||||
|         Err(_) => { | ||||
|             return false; | ||||
|         } | ||||
|     }; | ||||
|     verify(&dht_key, &data, &sig).is_ok() | ||||
| } | ||||
|  | ||||
| pub fn distance(key1: &DHTKey, key2: &DHTKey) -> DHTKeyDistance { | ||||
|     assert!(key1.valid); | ||||
|     assert!(key2.valid); | ||||
|     let mut bytes = [0u8; DHT_KEY_LENGTH]; | ||||
|  | ||||
|     for n in 0..DHT_KEY_LENGTH { | ||||
|         bytes[n] = key1.bytes[n] ^ key2.bytes[n]; | ||||
|     } | ||||
|  | ||||
|     DHTKeyDistance::new(bytes) | ||||
| } | ||||
							
								
								
									
										11
									
								
								veilid-core/src/dht/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								veilid-core/src/dht/mod.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| pub mod crypto; | ||||
| pub mod envelope; | ||||
| pub mod key; | ||||
| pub mod receipt; | ||||
| pub mod value; | ||||
|  | ||||
| pub use crypto::*; | ||||
| pub use envelope::*; | ||||
| pub use key::*; | ||||
| pub use receipt::*; | ||||
| pub use value::*; | ||||
							
								
								
									
										170
									
								
								veilid-core/src/dht/receipt.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										170
									
								
								veilid-core/src/dht/receipt.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,170 @@ | ||||
| use super::envelope::{MAX_VERSION, MIN_VERSION}; | ||||
| use super::key::*; | ||||
| use crate::xx::*; | ||||
| use core::convert::TryInto; | ||||
|  | ||||
| // #[repr(C, packed)] | ||||
| // struct ReceiptHeader { | ||||
| //     // Size is at least 8 bytes. Depending on the version specified, the size may vary and should be case to the appropriate struct | ||||
| //     magic: [u8; 4],              // 0x00: 0x52 0x43 0x50 0x54 ("RCPT") | ||||
| //     version: u8,                 // 0x04: 0 = ReceiptV0 | ||||
| //     reserved: u8,                // 0x05: Reserved for future use | ||||
| // } | ||||
|  | ||||
| // #[repr(C, packed)] | ||||
| // struct ReceiptV0 { | ||||
| //     // Size is 106 bytes. | ||||
| //     magic: [u8; 4],              // 0x00: 0x52 0x43 0x50 0x54 ("RCPT") | ||||
| //     version: u8,                 // 0x04: 0 = ReceiptV0 | ||||
| //     reserved: u8,                // 0x05: Reserved for future use | ||||
| //     size: u16,                   // 0x06: Total size of the receipt including the extra data and the signature. Maximum size is 1152 bytes. | ||||
| //     nonce: [u8; 24],             // 0x08: 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],         // 0x20: Node ID of the message source, which is the Ed25519 public key of the sender (must be verified with find_node if this is a new node_id/address combination) | ||||
| //     extra_data: [u8; ??],        // 0x40: Extra data is appended (arbitrary extra data, not encrypted by receipt itself, maximum size is 1024 bytes) | ||||
| //     signature: [u8; 64],         // 0x?? (end-0x40): Ed25519 signature of the entire receipt including header and extra data is appended to the packet | ||||
| // } | ||||
|  | ||||
| pub const MAX_RECEIPT_SIZE: usize = 1152; | ||||
| pub const MAX_EXTRA_DATA_SIZE: usize = 1024; | ||||
| pub const MIN_RECEIPT_SIZE: usize = 128; | ||||
| pub const RECEIPT_MAGIC: &[u8; 4] = b"RCPT"; | ||||
| pub type ReceiptNonce = [u8; 24]; | ||||
|  | ||||
| #[derive(Debug, Clone, PartialEq, Eq, Default)] | ||||
| pub struct Receipt { | ||||
|     version: u8, | ||||
|     nonce: ReceiptNonce, | ||||
|     sender_id: DHTKey, | ||||
|     extra_data: Vec<u8>, | ||||
| } | ||||
|  | ||||
| impl Receipt { | ||||
|     pub fn try_new<D: AsRef<[u8]>>( | ||||
|         version: u8, | ||||
|         nonce: ReceiptNonce, | ||||
|         sender_id: DHTKey, | ||||
|         extra_data: D, | ||||
|     ) -> Result<Self, String> { | ||||
|         assert!(sender_id.valid); | ||||
|         if extra_data.as_ref().len() > MAX_EXTRA_DATA_SIZE { | ||||
|             return Err("extra data too large for receipt".to_owned()); | ||||
|         } | ||||
|         Ok(Self { | ||||
|             version: version, | ||||
|             nonce: nonce, | ||||
|             sender_id: sender_id, | ||||
|             extra_data: Vec::from(extra_data.as_ref()), | ||||
|         }) | ||||
|     } | ||||
|  | ||||
|     pub fn from_signed_data(data: &[u8]) -> Result<Receipt, ()> { | ||||
|         // Ensure we are at least the length of the envelope | ||||
|         if data.len() < MIN_RECEIPT_SIZE { | ||||
|             trace!("receipt too small: len={}", data.len()); | ||||
|             return Err(()); | ||||
|         } | ||||
|  | ||||
|         // Verify magic number | ||||
|         let magic: [u8; 4] = data[0x00..0x04].try_into().map_err(drop)?; | ||||
|         if magic != *RECEIPT_MAGIC { | ||||
|             trace!("bad magic number: len={:?}", magic); | ||||
|             return Err(()); | ||||
|         } | ||||
|  | ||||
|         // Check version | ||||
|         let version = data[0x04]; | ||||
|         if version > MAX_VERSION || version < MIN_VERSION { | ||||
|             trace!("unsupported protocol version: version={}", version); | ||||
|             return Err(()); | ||||
|         } | ||||
|  | ||||
|         // 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[0x06..0x08].try_into().map_err(drop)?); | ||||
|         if (size as usize) > MAX_RECEIPT_SIZE { | ||||
|             trace!("receipt size is too large: size={}", size); | ||||
|             return Err(()); | ||||
|         } | ||||
|         if (size as usize) != data.len() { | ||||
|             trace!( | ||||
|                 "size doesn't match receipt size: size={} data.len()={}", | ||||
|                 size, | ||||
|                 data.len() | ||||
|             ); | ||||
|             return Err(()); | ||||
|         } | ||||
|  | ||||
|         // Get sender id | ||||
|         let sender_id_dhtkey = DHTKey::new(data[0x20..0x40].try_into().map_err(drop)?); | ||||
|         // Get signature | ||||
|         let signature = DHTSignature::new(data[(data.len() - 64)..].try_into().map_err(drop)?); | ||||
|  | ||||
|         // Validate signature | ||||
|         verify(&sender_id_dhtkey, &data[0..(data.len() - 64)], &signature).map_err(drop)?; | ||||
|  | ||||
|         // Get nonce | ||||
|         let nonce: ReceiptNonce = data[0x08..0x20].try_into().map_err(drop)?; | ||||
|  | ||||
|         // Get extra data and signature | ||||
|         let extra_data: Vec<u8> = Vec::from(&data[0x40..(data.len() - 64)]); | ||||
|  | ||||
|         // Return receipt | ||||
|         Ok(Self { | ||||
|             version: version, | ||||
|             nonce: nonce, | ||||
|             sender_id: sender_id_dhtkey, | ||||
|             extra_data: extra_data, | ||||
|         }) | ||||
|     } | ||||
|  | ||||
|     pub fn to_signed_data(&self, secret: &DHTKeySecret) -> Result<Vec<u8>, ()> { | ||||
|         // Ensure sender node id is valid | ||||
|         if !self.sender_id.valid { | ||||
|             return Err(()); | ||||
|         } | ||||
|  | ||||
|         // Ensure extra data isn't too long | ||||
|         let receipt_size: usize = self.extra_data.len() + MIN_RECEIPT_SIZE; | ||||
|         if receipt_size > MAX_RECEIPT_SIZE { | ||||
|             return Err(()); | ||||
|         } | ||||
|         let mut data: Vec<u8> = Vec::with_capacity(receipt_size); | ||||
|         data.resize(receipt_size, 0u8); | ||||
|  | ||||
|         // Write magic | ||||
|         data[0x00..0x04].copy_from_slice(RECEIPT_MAGIC); | ||||
|         // Write version | ||||
|         data[0x04] = self.version; | ||||
|         // Write size | ||||
|         data[0x06..0x08].copy_from_slice(&(receipt_size as u16).to_le_bytes()); | ||||
|         // Write nonce | ||||
|         data[0x08..0x20].copy_from_slice(&self.nonce); | ||||
|         // Write sender node id | ||||
|         data[0x20..0x40].copy_from_slice(&self.sender_id.bytes); | ||||
|         // Write extra data | ||||
|         if self.extra_data.len() > 0 { | ||||
|             data[0x40..(receipt_size - 64)].copy_from_slice(self.extra_data.as_slice()); | ||||
|         } | ||||
|         // Sign the receipt | ||||
|         let signature = | ||||
|             sign(&self.sender_id, secret, &data[0..(receipt_size - 64)]).map_err(drop)?; | ||||
|         // 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_nonce(&self) -> ReceiptNonce { | ||||
|         self.nonce | ||||
|     } | ||||
|  | ||||
|     pub fn get_sender_id(&self) -> DHTKey { | ||||
|         self.sender_id | ||||
|     } | ||||
|     pub fn get_extra_data(&self) -> &[u8] { | ||||
|         &self.extra_data | ||||
|     } | ||||
| } | ||||
							
								
								
									
										0
									
								
								veilid-core/src/dht/value.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								veilid-core/src/dht/value.rs
									
									
									
									
									
										Normal file
									
								
							
		Reference in New Issue
	
	Block a user