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