♻️ bevy_vanth, vanth, vanth_derive, vanth_transport: Many changes in preparation for networking

- Created new crate `bevy_vanth` with basic plugin structure for Bevy integration.
- Refactored `Id` generation in `vanth` to use `OsRng` and removed redundant `to_u128_pair`/`from_u128_pair` methods.
- Moved networking functionality into new `net` module with `Node`, `Packet`, and `Message` types.
- Updated `vanth_derive` to use `proc-macro-crate` for reliable crate path resolution.
- Added `rand` dependency to replace custom ID generation logic.
- Updated `Cargo.toml`/`Cargo.lock` with new dependencies: `bevy_app`, `nix`, `cfg_aliases`, `proc-macro-crate`.
- Modified `README.md` with improved project description.
- Added commented clippy check in `flake.nix`.
- Added `smol`, `async-process`, and `async-trait` dependencies in root and `vanth` crate.
- Integrated `vanth` crate into `bevy_vanth` and added serde dependency.
- Reorganized test files into module structure for `cli` and `vanth` crates.
- Created new modules `compress` and `ecc` in `vanth`.
- Implemented `Node` with async `run` method and `Backend` trait for networking in `vanth`.
- Renamed `Memory` backend to `InMemoryStore` in `vanth` store module.
This commit is contained in:
Markus Scully 2025-08-17 11:56:34 +03:00
parent 5262a266c0
commit 5afe3b61fb
Signed by: mascully
GPG key ID: 93CA5814B698101C
34 changed files with 1887 additions and 483 deletions

774
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -12,6 +12,7 @@ name = "vanth"
[workspace.dependencies]
clap = { version = "4.5.42", features = ["derive"] }
bevy_ecs = "0.16.1"
bevy_app = "0.16.1"
bincode = "2.0.1"
serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.140"
@ -22,8 +23,16 @@ syn = { version = "2.0", features = ["full"] }
proc-macro2 = "1.0"
rusqlite = { version = "0.32.1", features = ["bundled"] }
tempfile = "3.12.0"
rand_core = "0.6.4"
rand_chacha = { version = "0.3.1", features = ["serde1"] }
rand = "0.9.2"
rand_chacha = { version = "0.9.0", features = ["serde"] }
rand_core = "0.9.0"
tracing = "0.1.41"
tracing-subscriber = "0.3.19"
assert_cmd = "2.0.17"
proc-macro-crate = "3.3.0"
smol = "2.0.2"
async-process = "2.4.0"
async-trait = "0.1.89"
chacha20poly1305 = "0.10.1"
serde_bytes = "0.11.17"
serde_arrays = "0.2.0"

View file

@ -1,89 +1,9 @@
# Vanth
Vanth is a content-addressed database as a library designed for entity-component-system (ECS) applications.
Vanth is a content-addressed data storage and task memoization framework designed for entity-component-system (ECS) applications.
Inspired by [Salsa](https://github.com/salsa-rs/salsa) and [Bevy](https://bevy.org/).
It is currently experimental and should not be used for anything.
## Library usage
Any type that implements `serde::Serialize` can be hashed using `vanth::hash` to produce a `vanth::ContentHash`.
```rust
let x = "hello";
assert_eq!(vanth::hash(&x).hex(), "ea8f163db38682925e4491c5e58d4bb3506ef8c14eb78a86e908c5624a67200f");
```
Derive or implement the `vanth::Vanth` trait for types you want to store in the database.
```rust
use serde::{Deserialize, Serialize};
use vanth::Vanth;
#[derive(Deserialize, Serialize, Vanth)]
struct Data {
value: u32,
}
```
This generates a method `Vanth::ty()` which returns a `vanth::Ty`. This should represent the type's fully qualified name - its module path followed by the type itself and any generics it has. E.g. `Data::ty().to_string()` could return `"my::crate::module::Data"`.
The derive macro only works for basic types right now and is not implemented for `std` types. Moving or renaming types or modules will change the type name, necessitating a database migration. This is not supported yet.
This should be used with caution. There are good reasons why `std::any::TypeId` is opaque.
### Database storage
```rust
use vanth::store::{Store, StoreParams};
// Or use `Store::in_memory`.
let mut store = Store::sqlite_from_path(
"path/to/my_database.sqlite".into(),
StoreParams { create_if_not_exists: true, ..Default::default() },
);
let hash = store.write(Data { value: 5 }).unwrap();
let my_data: Data = store.get_from_hash(hash).unwrap();
```
## CLI usage
You can run the Vanth CLI with Nix using `nix run git+https://git.mascully.com/mascully/vanth.git`.
Use `--` to pass arguments, e.g. `nix run git+https://git.mascully.com/mascully/vanth.git -- --help`.
### Syntax
Write a component to the database:
```bash
$ vanth write --db /path/to/db.sqlite --ty my::type::Name --value '{ "field": "value" }'
a1cf81d8afe4e72604ea5c13bbd9b6cce14bd98c3a2f036f7156c4464a88ec09
```
Get a component from the database:
```bash
$ vanth get --db /path/to/db.sqlite --ty my::type::Name a1cf81d8afe4e72604ea5c13bbd9b6cce14bd98c3a2f036f7156c4464a88ec09
{"field":"value"}
```
Get all components of a type from the database:
```bash
$ vanth get-all --db /path/to/db.sqlite --ty my::type::Name
{"field":"value1"}
{"field":"value2"}
{"field":"value3"}
```
Delete a component by its content hash:
```bash
$ vanth delete --db /path/to/db.sqlite ea8f163db38682925e4491c5e58d4bb3506ef8c14eb78a86e908c5624a67200f
```
Delete all components of a type:
```bash
$ vanth delete-all --db /path/to/db.sqlite --ty my::type::Name
```
See the [crate-level documentation](crates/vanth/README.md) for more details.

View file

@ -0,0 +1,10 @@
[package]
name = "bevy_vanth"
version.workspace = true
edition.workspace = true
[dependencies]
bevy_ecs.workspace = true
bevy_app.workspace = true
serde.workspace = true
vanth = { path = "../vanth" }

View file

@ -0,0 +1,56 @@
use bevy_ecs::{component::Component, system::Query, world::World as BevyWorld};
use serde::{Deserialize, Serialize};
use vanth::{ActiveTask, Module, Root, Task, Vanth, entity::TaskId};
// TODO: Should there be one plugin per root, or one global plugin with many root components?
// #[derive(Component)]
// pub struct VanthRoot {}
pub struct BevyVanthPlugin {
root: Root,
}
impl BevyVanthPlugin {
pub fn from_root(root: Root) -> Self {
Self { root }
}
}
// impl bevy_app::Plugin for BevyVanthPlugin {
// fn build(&self, app: &mut bevy_app::App) {
// app.add_systems(bevy_app::Update, process_active_tasks);
// }
// }
pub struct BevyPluginModule<M: Module>(M);
impl<M> From<M> for BevyPluginModule<M>
where
M: Module + Send + Sync,
{
fn from(value: M) -> Self {
Self(value)
}
}
impl<M> bevy_app::Plugin for BevyPluginModule<M>
where
M: Module + Send + Sync + 'static,
{
fn build(&self, app: &mut bevy_app::App) {
// app.add_systems(bevy_app::FixedPreUpdate, run_vanth).finish();
}
}
fn process_active_tasks(tasks: Query<()>) {}
pub fn run_task<I, O, T: Task<Input = I, Output = O>>(world: &mut BevyWorld, task: T) -> TaskId {
todo!()
}
pub fn creation_bevy_plugin<M>(module: M) -> impl bevy_app::Plugin
where
M: Module + Send + Sync + 'static,
{
BevyPluginModule(module)
}

View file

@ -0,0 +1,42 @@
use bevy_app::App;
use bevy_vanth::BevyPluginModule;
use serde::{Deserialize, Serialize};
use vanth::{Module, Vanth};
mod bevy_tests {
use bevy_app::Plugin;
use vanth::Root;
use super::*;
#[derive(Deserialize, Serialize, Vanth)]
struct TestData {
number: u32,
}
#[derive(Deserialize, Serialize, Vanth)]
struct TestTask {
a: TestData,
b: TestData,
}
#[derive(Deserialize, Serialize, Vanth)]
struct TestModule {}
impl Module for TestModule {
}
#[test]
fn test_bevy() {
let root = Root::local();
let module = TestModule {};
let module_bevy_plugin = bevy_vanth::creation_bevy_plugin(module);
let bevy_vanth_plugin = bevy_vanth::BevyVanthPlugin::from_root(root);
let mut app = App::new();
app.add_plugins(module_bevy_plugin);
// app.add_plugins(bevy_vanth_plugin);
}
}

View file

View file

@ -7,6 +7,8 @@ use serde_json::{Value, json};
use tempfile::tempdir;
use vanth::{ContentHash, Vanth, hash as vanth_hash};
pub mod nu;
fn run_vanth(args: &[&str], input: Option<&str>) -> (String, String, i32) {
let mut cmd = Command::cargo_bin("vanth").unwrap();
let output = cmd.args(args).write_stdin(input.unwrap_or("")).output().unwrap();

View file

@ -16,6 +16,10 @@ blake3.workspace = true
vanth_derive = { path = "../vanth_derive" }
rusqlite.workspace = true
tracing.workspace = true
rand = { workspace = true }
smol.workspace = true
async-process.workspace = true
async-trait.workspace = true
[dev-dependencies]
tempfile = { workspace = true }

85
crates/vanth/README.md Normal file
View file

@ -0,0 +1,85 @@
# Vanth
## Library usage
Any type that implements `serde::Serialize` can be hashed using `vanth::hash` to produce a `vanth::ContentHash`.
```rust
let x = "hello";
assert_eq!(vanth::hash(&x).hex(), "ea8f163db38682925e4491c5e58d4bb3506ef8c14eb78a86e908c5624a67200f");
```
Derive or implement the `vanth::Vanth` trait for types you want to store in the database.
```rust
use serde::{Deserialize, Serialize};
use vanth::Vanth;
#[derive(Deserialize, Serialize, Vanth)]
struct Data {
value: u32,
}
```
This generates a method `Vanth::ty()` which returns a `vanth::Ty`. This should represent the type's fully qualified name - its module path followed by the type itself and any generics it has. E.g. `Data::ty().to_string()` could return `"my::crate::module::Data"`.
The derive macro only works for basic types right now and is not implemented for `std` types. Moving or renaming types or modules will change the type name, necessitating a database migration. This is not supported yet.
This should be used with caution. There are good reasons why `std::any::TypeId` is opaque.
### Database storage
```rust
use vanth::store::{Store, StoreParams};
// Or use `Store::in_memory`.
let mut store = Store::sqlite_from_path(
"path/to/my_database.sqlite",
StoreParams { create_if_not_exists: true, ..Default::default() },
);
let hash = store.write(Data { value: 5 }).unwrap();
let my_data: Data = store.get_from_hash(hash).unwrap();
```
## CLI usage
You can run the Vanth CLI with Nix using `nix run git+https://git.mascully.com/mascully/vanth.git`.
Use `--` to pass arguments, e.g. `nix run git+https://git.mascully.com/mascully/vanth.git -- --help`.
### Syntax
Write a component to the database:
```bash
$ vanth write --db /path/to/db.sqlite --ty my::type::Name --value '{ "field": "value" }'
a1cf81d8afe4e72604ea5c13bbd9b6cce14bd98c3a2f036f7156c4464a88ec09
```
Get a component from the database:
```bash
$ vanth get --db /path/to/db.sqlite --ty my::type::Name a1cf81d8afe4e72604ea5c13bbd9b6cce14bd98c3a2f036f7156c4464a88ec09
{"field":"value"}
```
Get all components of a type from the database:
```bash
$ vanth get-all --db /path/to/db.sqlite --ty my::type::Name
{"field":"value1"}
{"field":"value2"}
{"field":"value3"}
```
Delete a component by its content hash:
```bash
$ vanth delete --db /path/to/db.sqlite ea8f163db38682925e4491c5e58d4bb3506ef8c14eb78a86e908c5624a67200f
```
Delete all components of a type:
```bash
$ vanth delete-all --db /path/to/db.sqlite --ty my::type::Name
```

View file

@ -1,12 +1,12 @@
use serde::{Deserialize, Serialize};
use vanth::{hash, Vanth};
// use serde::{Deserialize, Serialize};
// use vanth::{hash, Vanth};
#[derive(Clone, Debug, Deserialize, Serialize, Vanth)]
struct Foo {
value: i32,
}
// #[derive(Clone, Debug, Deserialize, Serialize, Vanth)]
// struct Foo {
// value: i32,
// }
fn main() {
let x = "hello";
println!("Hash: {:?}", hash(&x).hex());
// let x = "hello";
// println!("Hash: {:?}", hash(&x).hex());
}

View file

@ -0,0 +1,11 @@
pub trait Compress {
type Output;
fn compress(&self) -> Self::Output;
}
pub trait Decompress {
type Output;
fn decompress(&self) -> Self::Output;
}

1
crates/vanth/src/ecc.rs Normal file
View file

@ -0,0 +1 @@
//! Error-correcting codes or something.

View file

@ -1,11 +1,13 @@
use crate::hashing_serializer::{self, HashingSerializer};
use crate::{hash, ComponentMarker, ContentHash, ModuleMarker, Task, TaskMarker, Ty, Vanth};
use rand::TryRngCore;
use rand::rngs::OsRng;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt::{self, Debug, Display};
use std::hash::{Hash, Hasher};
use std::marker::PhantomData;
use crate::hash;
use crate::hashing_serializer::{self, HashingSerializer};
#[derive(Clone, Copy, Deserialize)]
pub struct EntityId([u8; 32]);
@ -15,8 +17,14 @@ impl From<String> for EntityId {
}
}
pub type TaskId = Id<TaskMarker>;
pub type ModuleId = Id<ModuleMarker>;
pub type ComponentId = Id<ComponentMarker>;
/// A generic identifier type that can be used for different entity types
#[derive(Clone, Copy, Serialize, Deserialize)]
#[derive(Serialize, Deserialize)]
pub struct Id<T: ?Sized> {
/// The raw identifier value
pub value: [u8; 32],
@ -25,6 +33,20 @@ pub struct Id<T: ?Sized> {
_marker: PhantomData<T>,
}
impl<T: ?Sized> Copy for Id<T> {}
impl<T: ?Sized> Clone for Id<T> {
fn clone(&self) -> Self {
Self::new(self.value)
}
}
impl<T: ?Sized> std::fmt::Debug for Id<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Id<{}>", std::any::type_name::<T>())
}
}
impl<T: ?Sized> Id<T> {
/// Create a new ID with the given value
pub fn new(value: [u8; 32]) -> Self {
@ -34,17 +56,15 @@ impl<T: ?Sized> Id<T> {
}
}
/// Create an ID from a u64 (fills only first 8 bytes)
pub fn zero() -> Self {
Self::new([0; 32])
}
/// Generate a random ID
pub fn random() -> Self {
let mut value = [0u8; 32];
// Simple random generation for demonstration
for i in 0..32 {
value[i] = (std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos() as u8)
.wrapping_add(i as u8);
}
OsRng.try_fill_bytes(&mut value).unwrap();
Self::new(value)
}
@ -67,42 +87,6 @@ impl<T: ?Sized> Id<T> {
}
Self::new(bytes)
}
/// Convert the ID to a pair of u128 values for handling larger values
/// Returns (high_bits, low_bits)
pub fn to_u128_pair(&self) -> (u128, u128) {
let mut high = 0u128;
let mut low = 0u128;
// First 16 bytes go to low
for i in 0..16 {
low |= (self.value[i] as u128) << (i * 8);
}
// Next 16 bytes go to high
for i in 0..16 {
high |= (self.value[i + 16] as u128) << (i * 8);
}
(high, low)
}
/// Create an ID from a pair of u128 values
pub fn from_u128_pair(high: u128, low: u128) -> Self {
let mut bytes = [0u8; 32];
// Low bits fill first 16 bytes
for i in 0..16 {
bytes[i] = ((low >> (i * 8)) & 0xFF) as u8;
}
// High bits fill next 16 bytes
for i in 0..16 {
bytes[i + 16] = ((high >> (i * 8)) & 0xFF) as u8;
}
Self::new(bytes)
}
}
impl<T: ?Sized> PartialEq for Id<T> {
@ -119,34 +103,28 @@ impl<T: ?Sized> Hash for Id<T> {
}
}
impl<T: ?Sized> Debug for Id<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let (high, low) = self.to_u128_pair();
write!(f, "Id<{}>({:016x}{:016x})", std::any::type_name::<T>(), high, low)
}
#[derive(Debug)]
pub struct Entity {
pub id: Id<Entity>,
pub components: HashMap<Ty, Box<dyn EntityComponent>>,
}
impl<T: ?Sized> Display for Id<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let (high, low) = self.to_u128_pair();
write!(f, "{:016x}{:016x}", high, low)
}
pub trait EntityComponent: Debug {}
impl<T> EntityComponent for T where T: Component + Debug {}
pub trait Component: Clone + Vanth {
fn component_id(&self) -> &'static str;
}
pub struct ContentHash {
value: [u8; 32],
#[derive(Clone, Debug, Deserialize, Serialize, Vanth)]
pub struct EntityContents {
components: Vec<ComponentContents>,
}
impl ContentHash {}
pub trait Entity {
fn entity_id() -> Id<dyn Entity>
where
Self: Sized;
#[derive(Clone, Debug, Deserialize, Serialize, Vanth)]
pub struct ComponentContents {
pub content_hash: ContentHash,
pub ty: Ty,
pub data: Vec<u8>,
}
pub trait Component: Send + Sync + 'static {
fn component_id() -> &'static str;
}
// impl<'de, T> Component for T where T: Serialize + Deserialize<'de> {}

View file

@ -1,57 +1,44 @@
use std::marker::PhantomData;
#![doc = include_str!("../README.md")]
use bevy_ecs::{prelude::*, query::QueryData};
use std::{
collections::{HashMap, HashSet},
marker::PhantomData,
};
use async_trait::async_trait;
use bevy_ecs::{
prelude::{Component as BevyComponent, Entity as BevyEntity},
query::QueryData,
};
use serde::{Deserialize, Serialize, de::DeserializeOwned};
use crate::entity::EntityId;
use crate::{
entity::{Entity, EntityId, Id, TaskId},
store::Store,
};
pub mod compress;
pub mod ecc;
pub mod entity;
pub mod hashing_serializer;
pub mod network;
pub mod nix;
pub mod std_impls;
pub mod store;
pub use hashing_serializer::hash;
pub use network::Node;
pub use vanth_derive::Vanth;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Clone, Debug, Deserialize, Serialize)]
pub enum Error {
Other(String),
}
/// A view of all of the [`Node`]s in a cluster.
pub struct Network {
// TODO
}
/// A Vanth server.
pub struct Node {
// TODO
}
impl Node {
pub fn new() -> Self {
Self {}
impl From<smol::io::Error> for Error {
fn from(value: smol::io::Error) -> Self {
Self::Other(value.to_string())
}
pub fn entity_count(&self) -> usize {
todo!()
}
pub fn run() {
todo!()
}
pub fn save(entity_id: impl Into<EntityId>) -> Result<()> {
// TODO
Ok(())
}
// pub fn load(entity_id: impl Into<EntityId>) -> Result<Option<EntityContents>> {
// // TODO
// Ok(None)
// }
}
#[derive(Clone, Debug, Deserialize, Serialize)]
@ -75,65 +62,57 @@ pub struct Value {
data: Vec<u8>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Hash)]
pub struct EventTy(Ty);
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Hash)]
pub struct TaskTy(Ty);
/// A wrapper for the fully-qualified name of a Rust type. This should be univerisally unique for a given type within a
/// given project.
#[derive(Clone, Debug, Deserialize, Serialize, Eq, Hash)]
#[derive(Clone, Deserialize, Serialize, PartialEq, Eq, Hash)]
pub struct Ty {
pub path: Vec<String>,
}
impl std::fmt::Debug for Ty {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_string())
}
}
impl ToString for Ty {
fn to_string(&self) -> String {
self.path.join("::")
}
}
impl PartialEq for Ty {
fn eq(&self, other: &Self) -> bool {
self.path == other.path
}
}
impl<T: AsRef<str>> PartialEq<T> for Ty {
fn eq(&self, other: &T) -> bool {
self.to_string() == *other.as_ref()
}
}
#[derive(Copy, Clone, Debug, Deserialize, BevyComponent, Serialize, PartialEq, Eq, Hash, Vanth)]
pub struct VanthVersion {
pub version_number: u64,
pub extra_data: u64,
}
/// All types stored in the Vanth database should implement this trait.
pub trait Vanth {
/// Get the [`Ty`] representing this type.
fn ty() -> Ty;
}
macro_rules! impl_vanth {
// TODO
() => {};
}
// impl_vanth!(std::string::String)
// TODO: Impl for different tuple sizes
pub trait VanthTuple {}
// #[derive(Clone, Debug, Deserialize, Serialize)]
// pub struct EntityContents {
// components: Vec<ComponentContents>
// }
#[derive(Clone, Debug)]
pub struct ComponentContents<T: Vanth> {
content_hash: ContentHash,
data: Vec<u8>,
_marker: PhantomData<T>,
}
// use a macro to implement VanthTuiple here.
/// A 32 byte BLAKE3 hash representing the contents of some value.
///
/// This can be generated with the [`hash`] function.
#[derive(Copy, Clone, Debug, Deserialize, Component, Serialize, PartialEq, Eq, Hash)]
#[derive(Copy, Clone, Debug, Deserialize, BevyComponent, Serialize, PartialEq, Eq, Hash, Vanth)]
pub struct ContentHash {
pub hash: [u8; 32],
}
@ -144,13 +123,13 @@ impl ContentHash {
}
}
#[derive(Clone, Debug, Deserialize, Component, Serialize)]
#[derive(Clone, Debug, Deserialize, BevyComponent, Serialize)]
pub struct Reference<T: Clone + Serialize> {
value: ReferenceValue,
_marker: PhantomData<T>,
}
#[derive(Clone, Debug, Deserialize, Component, Serialize)]
#[derive(Clone, Debug, Deserialize, BevyComponent, Serialize)]
pub enum ReferenceValue {
Absent,
Retrieving(ReferenceRetrievalTask),
@ -167,7 +146,7 @@ impl<T: Clone + Serialize> Reference<T> {
}
}
#[derive(Component, Clone, Debug, Deserialize, Serialize)]
#[derive(BevyComponent, Clone, Debug, Deserialize, Serialize)]
pub struct ReferenceRetrievalTask {}
impl Future for ReferenceRetrievalTask {
@ -182,5 +161,157 @@ pub struct Handle<T> {
_marker: PhantomData<T>,
}
#[async_trait]
pub trait Task: Vanth + Serialize + DeserializeOwned {
type Input: Vanth + Serialize;
type Output: Vanth + Serialize + DeserializeOwned;
type Resource: Vanth;
}
pub struct TaskMarker;
pub struct ModuleMarker;
pub struct ComponentMarker;
#[derive(BevyComponent)]
pub enum ActiveTask<T: Task> {
Requested(T::Input),
// Running(Box<dyn Future<Output = T::Output> + Send + Sync>),
// TODO
Running(TaskId),
Completed(T::Output),
}
pub struct TaskCache {
pub values: HashMap<TaskId, Vec<u8>>,
}
/// A representation of the approximate computational cost of completing a task.
pub struct Cost {
value: f64,
}
/// A world which Vanth entities live in. Lifetimes `'v` of [`Vanth<'v>`] types are tied to the lifetime of the `Root`.
pub struct Root {}
#[derive(Debug)]
pub struct Root {
authority: Authority,
current_epoch: u64,
store: Store,
registered_components: HashSet<Ty>,
registered_modules: HashMap<Ty, BoxedModule>,
}
#[derive(Debug)]
pub struct BoxedModule {}
impl Root {
pub fn local() -> Self {
Self::new(Authority::Local, Store::in_memory().unwrap())
}
pub fn new(authority: Authority, store: Store) -> Self {
Self {
authority,
current_epoch: 0,
store,
registered_components: HashSet::new(),
registered_modules: HashMap::new(),
}
}
pub fn register_module<M: Module>(&mut self) {
let boxed_module = BoxedModule {};
self.registered_modules.insert(M::ty(), boxed_module);
}
pub async fn run_module_task<R, M, T>(&mut self, module: &mut M, input: T::Input) -> T::Output
where
M: Module + ProvideResource<R> + ExecuteTask<T>,
T: Task<Resource = R>,
{
let mut resources = module.get_resource();
module.execute_task(input, &mut resources).await
}
}
pub trait Module: Vanth + Serialize + DeserializeOwned {}
#[async_trait]
pub trait ExecuteTask<T>
where
T: Task,
{
async fn execute_task(&self, input: T::Input, resource: &mut T::Resource) -> T::Output;
}
pub trait ProvideResource<R>: Module {
fn get_resource(&self) -> R;
}
// TODO: Implement for other tuple sizes.
impl<M, R1, R2> ProvideResource<(R1, R2)> for M
where
M: Module + ProvideResource<R1> + ProvideResource<R2>,
{
fn get_resource(&self) -> (R1, R2) {
(self.get_resource(), self.get_resource())
}
}
impl<M, R1, R2, R3> ProvideResource<(R1, R2, R3)> for M
where
M: Module + ProvideResource<R1> + ProvideResource<R2> + ProvideResource<R3>,
{
fn get_resource(&self) -> (R1, R2, R3) {
(self.get_resource(), self.get_resource(), self.get_resource())
}
}
pub trait Shell: Module {
// <Self as Module>::Monads
}
pub struct ModuleRegistration {
// types: Vec<Ty>,
// events: Vec<EventTy>,
handler: Box<dyn ModuleRegistrationHandler>,
}
impl ModuleRegistration {
fn add_type<T: Vanth>(&mut self) {
self.handler.add_type(T::ty());
}
fn add_task<T: Task>(&mut self) -> TaskId {
todo!()
// self.handler.add_task(T::Input::ty(), T::Output::ty())
}
fn add_resource<R: Vanth>(&mut self) {
self.handler.add_resource(R::ty())
}
}
pub trait ModuleRegistrationHandler {
fn add_type(&self, ty: Ty);
fn add_task(&self, input_ty: Ty, output_ty: Ty) -> TaskId;
fn add_resource(&self, resource_ty: Ty);
}
pub trait TaskSystem {}
// impl <F> TaskSystem for F where F: Fn()
impl ModuleRegistration {}
#[derive(Clone, Debug, Deserialize, Serialize, Vanth)]
pub enum Authority {
/// No networking or synchronization.
Local,
Master(MasterAuthority),
}
#[derive(Clone, Debug, Deserialize, Serialize, Vanth)]
pub struct MasterAuthority {
// TODO
public_key: Vec<u8>,
}

145
crates/vanth/src/network.rs Normal file
View file

@ -0,0 +1,145 @@
use std::collections::HashMap;
use std::net::Ipv6Addr;
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use smol::net::UdpSocket;
use crate::entity::{Entity, EntityContents};
use crate::{ContentHash, Error};
use crate::{
Vanth,
entity::{EntityId, Id},
};
#[derive(Debug)]
pub enum NetworkError {
Timeout {
duration_waited_secs: f64,
},
Other(String)
}
pub struct Packet {
pub source_node: Id<Node>,
pub signature: Vec<u8>,
pub message: Message,
}
#[derive(Clone, Debug, Deserialize, Serialize, Vanth)]
pub struct Message {
pub events: Vec<Event>,
}
#[derive(Clone, Debug, Deserialize, Serialize, Vanth)]
pub enum Event {
RequestValue(ContentHash),
UpsertEntity(EntityContents),
DeleteEntity(EntityContents),
}
#[derive(Clone, Debug, Deserialize, Serialize, Vanth)]
pub enum Address {
Uri(String),
Id(Id<Address>),
}
#[derive(Clone, Debug, Deserialize, Serialize, Vanth)]
pub struct NetworkParams {
id: Id<Network>,
initial_peers: Vec<Address>,
}
/// A view of all of the [`Node`]s in a cluster.
#[derive(Debug)]
pub struct Network {
// TODO
id: Id<Network>,
peers: HashMap<Id<Node>, Peer>,
}
#[derive(Clone, Debug, Deserialize, Serialize, Vanth)]
pub struct Peer {
id: Id<Node>,
last_confirmed_epoch: u64,
}
/// A Vanth server.
#[derive(Clone, Debug, Deserialize, Serialize, Vanth)]
pub struct Node {
id: Id<Node>,
config: NodeConfig,
ip_address: Option<Ipv6Addr>,
}
#[derive(Clone, Debug, Deserialize, Serialize, Vanth)]
pub struct NodeConfig {
pub port: u16,
networks: Vec<NetworkParams>,
}
impl Default for NodeConfig {
fn default() -> Self {
Self {
// Sidereal orbital period of Vanth in 100's of seconds.
port: 8241,
networks: Vec::new(),
}
}
}
impl Node {
pub fn from_config(config: impl Into<NodeConfig>) -> Self {
Self {
id: Id::random(),
config: config.into(),
ip_address: None,
}
}
pub fn with_id(id: Id<Node>, config: impl Into<NodeConfig>) -> Self {
Self {
id,
config: config.into(),
ip_address: None,
}
}
pub fn entity_count(&self) -> usize {
todo!()
}
pub async fn run(self) -> Result<(), Error> {
let socket = UdpSocket::bind(("127.0.0.1", self.config.port)).await?;
Ok(())
}
pub fn save(entity_id: impl Into<EntityId>) -> Result<(), Error> {
// TODO
Ok(())
}
/// Attempt to open a UDP connection to the provided [`Node`].
pub async fn connect_to(network: Id<Network>, node: &Node) -> Result<(), Error> {
// TODO
Ok(())
}
// pub fn load(entity_id: impl Into<EntityId>) -> Result<Option<EntityContents>> {
// // TODO
// Ok(None)
// }
}
#[async_trait]
pub trait Backend {
type ConnectionHandle;
type Error;
// TODO: Improve efficiency or something.
async fn connect<'a>(&mut self, address: &Address) -> Result<Self::ConnectionHandle, Self::Error>;
async fn close(&mut self, connection: Self::ConnectionHandle) -> Result<(), Self::Error>;
async fn poll(&mut self) -> impl Iterator<Item = Result<(Self::ConnectionHandle, Vec<u8>), Self::Error>>;
async fn send(&mut self, data: &[u8]) -> Result<(), Self::Error>;
}

View file

@ -0,0 +1,68 @@
use super::{Ty, Vanth};
impl Vanth for () {
fn ty() -> Ty {
Ty {
path: vec!["()".into()],
}
}
}
macro_rules! impl_tuple_vanth {
($($T:ident),+ $(,)?) => {
impl<$($T),+> Vanth for ($($T,)+)
where
$($T: Vanth),+
{
fn ty() -> Ty {
// Join the inner type strings with ", " and wrap in parentheses.
Ty {
path: vec![format!(
"({})",
vec![$($T::ty().to_string()),+].join(", ")
)],
}
}
}
};
}
impl_tuple_vanth!(T1);
impl_tuple_vanth!(T1, T2);
impl_tuple_vanth!(T1, T2, T3);
impl_tuple_vanth!(T1, T2, T3, T4);
impl_tuple_vanth!(T1, T2, T3, T4, T5);
impl_tuple_vanth!(T1, T2, T3, T4, T5, T6);
impl_tuple_vanth!(T1, T2, T3, T4, T5, T6, T7);
impl_tuple_vanth!(T1, T2, T3, T4, T5, T6, T7, T8);
impl_tuple_vanth!(T1, T2, T3, T4, T5, T6, T7, T8, T9);
impl_tuple_vanth!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10);
impl_tuple_vanth!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11);
impl_tuple_vanth!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12);
impl<T> Vanth for Option<T>
where
T: Vanth,
{
fn ty() -> Ty {
Ty {
path: vec![format!("Option<{}>", T::ty().to_string())],
}
}
}
impl<T, E> Vanth for Result<T, E>
where
T: Vanth,
E: Vanth,
{
fn ty() -> Ty {
Ty {
path: vec![format!(
"Result<{}, {}>",
T::ty().to_string(),
E::ty().to_string()
)],
}
}
}

View file

@ -1,4 +1,8 @@
use std::{collections::HashMap, marker::PhantomData, path::PathBuf};
use std::{
collections::HashMap,
marker::PhantomData,
path::{Path, PathBuf},
};
use rusqlite::{Connection, named_params, params};
@ -6,7 +10,7 @@ use bevy_ecs::prelude::*;
use serde::{Deserialize, Serialize, de::DeserializeOwned};
use tracing::trace;
use crate::{ComponentContents, ContentHash, Ty, Vanth, hash};
use crate::{ContentHash, Ty, Vanth, entity::ComponentContents, hash};
#[derive(Debug)]
pub struct Store {
@ -58,7 +62,7 @@ impl Default for StoreParams {
impl Store {
/// Use an SQLite backend with a database file at the provided path.
pub fn sqlite_from_path(path: PathBuf, params: StoreParams) -> Result<Self> {
pub fn sqlite_from_path(path: impl AsRef<Path>, params: StoreParams) -> Result<Self> {
use rusqlite::OpenFlags;
// Base flags for URI handling and disabling mutexes.
let mut flags = OpenFlags::SQLITE_OPEN_URI | OpenFlags::SQLITE_OPEN_NO_MUTEX;
@ -76,7 +80,7 @@ impl Store {
}
// Open the SQLite connection with the computed flags.
let connection = rusqlite::Connection::open_with_flags(path, flags)?;
let connection = rusqlite::Connection::open_with_flags(path.as_ref(), flags)?;
Ok(Self {
backend: Box::new(Sqlite { connection }),
})
@ -85,7 +89,7 @@ impl Store {
/// Use an in-memory backend.
pub fn in_memory() -> Result<Self> {
Ok(Self {
backend: Box::new(Memory::new()),
backend: Box::new(InMemoryStore::new()),
})
}
@ -102,14 +106,14 @@ impl Store {
self.backend.get_from_hash(ty, content_hash)
}
pub fn get_all_of_type<T: Vanth>(&mut self) -> Result<Vec<ComponentContents<T>>> {
pub fn get_all_of_type<T: Vanth>(&mut self) -> Result<Vec<ComponentContents>> {
let raw_items = self.backend.get_all_of_ty(T::ty())?;
let mut results = Vec::new();
for (content_hash, data) in raw_items {
results.push(ComponentContents {
_marker: PhantomData,
content_hash,
data,
ty: T::ty(),
});
}
Ok(results)
@ -153,6 +157,7 @@ pub struct Cache {
// backend: Backend,
}
// TODO: Change to storing entities (groups of components) instead of components.
pub trait Backend: std::fmt::Debug + Send {
fn get_from_hash(&mut self, ty: Ty, content_hash: ContentHash) -> Result<Option<Vec<u8>>>;
@ -229,7 +234,7 @@ impl Backend for Sqlite {
drop(statement);
transaction.commit()?;
Ok(results)
}
@ -272,17 +277,17 @@ impl Backend for Sqlite {
/// In-memory storage with one table per type.
#[derive(Debug, Deserialize, Serialize)]
pub struct Memory {
pub struct InMemoryStore {
tables: HashMap<Ty, HashMap<ContentHash, Vec<u8>>>,
}
impl Memory {
impl InMemoryStore {
pub fn new() -> Self {
Self { tables: HashMap::new() }
}
}
impl Backend for Memory {
impl Backend for InMemoryStore {
fn get_from_hash(&mut self, ty: Ty, content_hash: ContentHash) -> Result<Option<Vec<u8>>> {
Ok(self.tables.get(&ty).and_then(|table| table.get(&content_hash)).cloned())
}
@ -315,3 +320,13 @@ impl Backend for Memory {
Ok(())
}
}
/// One table per type. Keys and values are both blobs.
#[derive(Debug, Deserialize, Serialize)]
pub struct FsStore {
path: PathBuf,
}
// impl Backend for FsStore {
// }

View file

@ -1,60 +0,0 @@
/// A 192-bit hash value.
pub struct Hash([u8; 24]);
impl Hash {
pub fn from_bytes(value: [u8; 24]) -> Self {
Hash(value)
}
}
impl std::fmt::Display for Hash {
/// The Base52 representation of the hash. This will always be 34 ASCII characters long.
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut result = String::with_capacity(34);
for chunk in self.0.chunks(4) {
let mut value = 0u32;
for (i, &byte) in chunk.iter().enumerate() {
value |= (byte as u32) << (8 * i);
}
for _ in 0..6 {
let digit = value % 52;
value /= 52;
let c = if digit < 26 {
(b'A' + digit as u8) as char
} else {
(b'a' + (digit - 26) as u8) as char
};
result.push(c);
}
}
write!(f, "{}", result)
}
}
impl Hash {
pub fn to_string_truncated(&self, length: usize) -> String {
self.to_string()[0..length].to_string()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_hash_to_string() {
let pairs = [(0u8, "AAAAAA"), (255u8, "ZZZZZZ"), (1u8, "AAAAAB")];
let input = [0u8; 24];
let hash = Hash::from_bytes(input);
let result = hash.to_string();
assert_eq!(result.len(), 34);
// String should only contain A-Z and a-z
assert!(result.chars().all(|c| c.is_ascii_alphabetic()));
// With all zero bytes, should start with 'AAAAAA'
assert_eq!(&result[0..6], "AAAAAA");
}
}

View file

@ -1,39 +0,0 @@
use bevy_ecs::component::Component;
use serde::{Deserialize, Serialize, de::DeserializeOwned};
use vanth::Vanth;
// TODO: derive `Vanth`
#[derive(Debug, Deserialize, Component, Serialize)]
struct Foo {}
#[test]
fn test_derive() {
#[derive(Deserialize, Serialize, Vanth)]
struct Foo<T: Vanth> {
field_a: i32,
field_b: String,
inner: T,
}
#[derive(Deserialize, Serialize, Vanth)]
struct Bar {
field_a: i32,
}
#[derive(Deserialize, Serialize, Vanth)]
struct Qux<T: Vanth, S: Vanth> {
field_a: i32,
field_b: String,
inner: T,
inner_2: S,
}
let base = "integration::derive::";
assert_eq!(Bar::ty(), format!("{base}Bar"));
assert_eq!(Foo::<Bar>::ty(), format!("{base}Foo<{base}Bar>"));
assert_eq!(
Qux::<Bar, Foo<Bar>>::ty(),
format!("{base}Qux<{base}Bar,{base}Foo<{base}Bar>>")
);
}

View file

@ -1,7 +0,0 @@
use serde::{Deserialize, Serialize};
use vanth::{Node, Reference};
mod derive;
mod fs;
mod reference;
mod store;

View file

@ -0,0 +1,47 @@
use std::collections::HashMap;
use async_trait::async_trait;
use smol::channel::{self, Receiver, Sender};
use vanth::{
entity::Id,
network::{self, Address, Packet},
};
struct TestNetBackend {
sockets: HashMap<Id<Address>, (Sender<Packet>, Receiver<Packet>)>,
}
#[async_trait]
impl network::Backend for TestNetBackend {
type ConnectionHandle = Id<Address>;
type Error = String;
async fn connect<'a>(&mut self, address: &Address) -> Result<Self::ConnectionHandle, Self::Error> {
let id = match address {
Address::Id(id) => id,
_ => unimplemented!(),
};
let socket = channel::unbounded();
assert!(self.sockets.insert(*id, socket).is_none());
Ok(*id)
}
async fn close(&mut self, connection: Self::ConnectionHandle) -> Result<(), Self::Error> {
todo!()
}
async fn poll(&mut self) -> impl Iterator<Item = Result<(Self::ConnectionHandle, Vec<u8>), Self::Error>> {
todo!();
[].into_iter()
}
async fn send(&mut self, data: &[u8]) -> Result<(), Self::Error> {
todo!()
}
}
#[test]
fn test_network() {
// let id_1 = Id::random();
// let address_1 = Address::Id(id);
}

138
crates/vanth/tests/test.rs Normal file
View file

@ -0,0 +1,138 @@
use bevy_ecs::component::Component;
use serde::{Deserialize, Serialize, de::DeserializeOwned};
use vanth::{Node, Reference, Vanth};
mod fs;
mod net;
mod reference;
mod store;
// TODO: derive `Vanth`
#[derive(Debug, Deserialize, Component, Serialize)]
struct Foo {}
#[test]
fn test_derive() {
mod vanth {}
use ::vanth::Vanth;
#[derive(Deserialize, Serialize, Vanth)]
struct Foo<T: ::vanth::Vanth> {
field_a: i32,
field_b: String,
inner: T,
}
#[derive(Deserialize, Serialize, Vanth)]
struct Bar {
field_a: i32,
}
#[derive(Deserialize, Serialize, Vanth)]
struct Qux<T: Vanth, S: Vanth> {
field_a: i32,
field_b: String,
inner: T,
inner_2: S,
}
let base = "test::";
assert_eq!(Bar::ty(), format!("{base}Bar"));
assert_eq!(Foo::<Bar>::ty(), format!("{base}Foo<{base}Bar>"));
assert_eq!(
Qux::<Bar, Foo<Bar>>::ty(),
format!("{base}Qux<{base}Bar,{base}Foo<{base}Bar>>")
);
}
mod task_tests {
use async_trait::async_trait;
use smol::block_on;
use vanth::{ExecuteTask, Module, Root, Task};
use super::*;
#[derive(Deserialize, Serialize, Vanth)]
struct TestData(u32);
#[derive(Deserialize, Serialize, Vanth)]
struct TestTask {
a: TestData,
b: TestData,
}
impl Task for TestTask {
type Input = (TestData, TestData);
type Output = TestData;
type Resource = ();
}
#[derive(Deserialize, Serialize, Vanth)]
struct TestModule {}
impl Module for TestModule {}
#[async_trait]
impl ExecuteTask<TestTask> for TestModule {
async fn execute_task(&self, input: (TestData, TestData), resource: &mut ()) -> TestData {
let (TestData(a), TestData(b)) = input;
TestData(a + b)
}
}
#[test]
fn test_task() {
let root = Root::local();
let module = TestModule {};
let input = (TestData(2), TestData(3));
let mut resources = ();
let result = block_on(module.execute_task(input, &mut resources));
assert_eq!(result.0, 5);
}
}
mod module_tests {
use async_trait::async_trait;
use smol::block_on;
use vanth::{ExecuteTask, Module, Root, Task};
use super::*;
#[derive(Deserialize, Serialize, Vanth)]
struct TestData(u32);
#[derive(Deserialize, Serialize, Vanth)]
struct TestTask {
a: TestData,
b: TestData,
}
impl Task for TestTask {
type Input = (TestData, TestData);
type Output = TestData;
type Resource = ();
}
#[derive(Deserialize, Serialize, Vanth)]
struct TestModule {}
impl Module for TestModule {}
#[async_trait]
impl ExecuteTask<TestTask> for TestModule {
async fn execute_task(&self, input: (TestData, TestData), resource: &mut ()) -> TestData {
let (TestData(a), TestData(b)) = input;
TestData(a + b)
}
}
#[test]
fn test_task() {
let mut root = Root::local();
let module = TestModule {};
// let module_id = root.register_module(module);
}
}

View file

@ -9,4 +9,5 @@ proc-macro = true
[dependencies]
quote.workspace = true
syn.workspace = true
proc-macro2.workspace = true
proc-macro2.workspace = true
proc-macro-crate.workspace = true

View file

@ -1,4 +1,6 @@
use proc_macro::TokenStream;
use proc_macro_crate::{crate_name, FoundCrate};
use proc_macro2::Span;
use quote::quote;
use syn::parse_quote;
use syn::{DeriveInput, GenericParam, Generics, parse_macro_input};
@ -11,6 +13,15 @@ pub fn vanth_derive(input: TokenStream) -> TokenStream {
let mut generics = input.generics.clone();
let vanth_path = match crate_name("vanth") {
Ok(FoundCrate::Itself) => quote!(crate),
Ok(FoundCrate::Name(name)) => {
let ident = syn::Ident::new(&name, Span::call_site());
quote!(::#ident)
}
Err(_) => quote!(vanth),
};
let type_params: Vec<syn::Ident> = generics
.params
.iter()
@ -25,7 +36,7 @@ pub fn vanth_derive(input: TokenStream) -> TokenStream {
let mut where_clause = generics.where_clause.clone().unwrap_or_else(|| parse_quote!(where));
for tp in &type_params {
where_clause.predicates.push(parse_quote!(#tp : vanth::Vanth));
where_clause.predicates.push(parse_quote!(#tp : #vanth_path::Vanth));
}
let (impl_generics, ty_generics, _) = generics.split_for_impl();
@ -34,19 +45,19 @@ pub fn vanth_derive(input: TokenStream) -> TokenStream {
quote! { String::new() }
} else {
quote! {
format!("<{}>", [#(#type_params::ty().path.join("::")),*].join(","))
format!("<{}>", [#( <#type_params as #vanth_path::Vanth>::ty().path.join("::") ),*].join(","))
}
};
let expanded = quote! {
impl #impl_generics vanth::Vanth for #name #ty_generics #where_clause {
fn ty() -> vanth::Ty {
impl #impl_generics #vanth_path::Vanth for #name #ty_generics #where_clause {
fn ty() -> #vanth_path::Ty {
let module_path = module_path!();
let mut path: Vec<String> = module_path.split("::").map(|s| s.to_string()).collect();
let base_name = stringify!(#name);
let generics_str = #generics_code;
path.push(format!("{}{}", base_name, generics_str));
vanth::Ty { path }
#vanth_path::Ty { path }
}
}
};

View file

@ -0,0 +1,25 @@
[package]
name = "vanth_transport"
version.workspace = true
edition.workspace = true
[lib]
path = "src/lib.rs"
doctest = false
[dependencies]
digest.workspace = true
serde.workspace = true
serde_json.workspace = true
blake3.workspace = true
vanth = { path = "../vanth" }
vanth_derive = { path = "../vanth_derive" }
tracing.workspace = true
rand = { workspace = true }
smol.workspace = true
async-process.workspace = true
async-trait.workspace = true
ntrulp = { path = "/home/asraelite/dev/ntrulp", features = ["ntrup1277"] }
chacha20poly1305.workspace = true
serde_bytes.workspace = true
serde_arrays.workspace = true

View file

@ -0,0 +1,26 @@
use serde::{Serialize, de::DeserializeOwned};
use vanth::Vanth;
pub mod ntru;
pub enum CryptoError {
RngGenerationFailed,
InvalidKey,
}
pub trait Asymmetric {
type PublicKey: Vanth + Serialize + DeserializeOwned;
type PrivateKey: Vanth + Serialize + DeserializeOwned;
type Seed;
fn generate_default_public_key(&mut self, private_key: &Self::PrivateKey) -> Result<Self::PublicKey, CryptoError>;
fn generate_private_key(&mut self, seed: Self::Seed) -> Result<Self::PrivateKey, CryptoError>;
}
pub trait MultiKeyAsymmetric: Asymmetric {
fn generate_public_key(
&mut self,
seed: Self::Seed,
private_key: &Self::PrivateKey,
) -> Result<Self::PublicKey, CryptoError>;
}

View file

@ -0,0 +1,151 @@
use super::{Asymmetric, CryptoError};
use ntrulp::{
key::{priv_key::PrivKey, pub_key::PubKey},
poly::{r3::R3, rq::Rq},
};
use serde::{Deserialize, Serialize, ser::SerializeStruct as _};
use vanth::Vanth;
const NTRU_COEFFS_LEN: usize = 1277;
pub struct Ntru {}
#[derive(Vanth)]
pub struct PrivateKey(PrivKey);
impl From<PrivKey> for PrivateKey {
fn from(value: PrivKey) -> Self {
PrivateKey(value)
}
}
impl Serialize for PrivateKey {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut s = serializer.serialize_struct("PrivateKey", 2)?;
s.serialize_field("f", &self.0.0.coeffs.to_vec())?;
s.serialize_field("g", &self.0.1.coeffs.to_vec())?;
s.end()
}
}
impl<'de> Deserialize<'de> for PrivateKey {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
struct PrivateKeyHelper {
f: Vec<i8>,
g: Vec<i8>,
}
let helper = PrivateKeyHelper::deserialize(deserializer)?;
let f = {
let slice = helper.f.as_slice();
if slice.len() != NTRU_COEFFS_LEN {
return Err(serde::de::Error::invalid_length(
slice.len(),
&"invalid coeffs length for PrivateKey.f",
));
}
let mut coeffs = [0i8; NTRU_COEFFS_LEN];
coeffs.copy_from_slice(slice);
R3 { coeffs }
};
let g = {
let slice = helper.g.as_slice();
if slice.len() != NTRU_COEFFS_LEN {
return Err(serde::de::Error::invalid_length(
slice.len(),
&"invalid coeffs length for PrivateKey.g",
));
}
let mut coeffs = [0i8; NTRU_COEFFS_LEN];
coeffs.copy_from_slice(slice);
R3 { coeffs }
};
Ok(PrivateKey(PrivKey(f, g)))
}
}
#[derive(Vanth)]
pub struct PublicKey(PubKey);
impl From<PubKey> for PublicKey {
fn from(value: PubKey) -> Self {
PublicKey(value)
}
}
impl Serialize for PublicKey {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut s = serializer.serialize_struct("PublicKey", 1)?;
s.serialize_field("coeffs", &self.0.coeffs.to_vec())?;
s.end()
}
}
impl<'de> Deserialize<'de> for PublicKey {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
struct PublicKeyHelper {
coeffs: Vec<i16>,
}
let helper = PublicKeyHelper::deserialize(deserializer)?;
let slice = helper.coeffs.as_slice();
if slice.len() != NTRU_COEFFS_LEN {
return Err(serde::de::Error::invalid_length(
slice.len(),
&"invalid coeffs length for PublicKey.coeffs",
));
}
let mut coeffs = [0i16; NTRU_COEFFS_LEN];
coeffs.copy_from_slice(slice);
Ok(PublicKey(Rq { coeffs }))
}
}
// TODO: Use seed during generation, or in some other way make the random number generator more generic.
impl Asymmetric for Ntru {
type PrivateKey = PrivateKey;
type PublicKey = PublicKey;
// TODO: Use correct type
type Seed = u64;
fn generate_default_public_key(&mut self, private_key: &Self::PrivateKey) -> Result<Self::PublicKey, CryptoError> {
let pubkey = PubKey::from_sk(&private_key.0).map_err(|_| CryptoError::InvalidKey)?;
Ok(PublicKey(pubkey))
}
fn generate_private_key(&mut self, seed: Self::Seed) -> Result<Self::PrivateKey, CryptoError> {
let mut rng = rand::rng();
let f: Rq = Rq::from(ntrulp::rng::short_random(&mut rng).unwrap());
let mut g: R3;
let mut attempt_count = 0;
let sk: PrivKey = loop {
g = R3::from(ntrulp::rng::random_small(&mut rng));
if let Ok(s) = PrivKey::compute(&f, &g) {
break s;
};
attempt_count += 1;
if attempt_count > 100 {
return Err(CryptoError::RngGenerationFailed);
}
};
Ok(PrivateKey(sk))
}
}

View file

@ -0,0 +1,44 @@
use std::collections::HashMap;
use async_trait::async_trait;
use smol::net::UdpSocket;
use vanth::{
entity::Id,
network::{Address, Backend},
};
pub mod crypto;
pub struct UdpBackend {
open_sockets: HashMap<Id<UdpSocket>, UdpSocket>,
}
#[async_trait]
impl Backend for UdpBackend {
type ConnectionHandle = Id<UdpSocket>;
type Error = String;
async fn connect<'a>(&mut self, address: &Address) -> Result<Self::ConnectionHandle, Self::Error> {
let socket = match address {
Address::Uri(uri) => UdpSocket::bind(uri).await.map_err(|e| e.to_string())?,
_ => return Err(format!("Address {:?} is not supported for the UDP backend.", address)),
};
let id = Id::random();
assert!(self.open_sockets.insert(id, socket).is_none());
Ok(id)
}
async fn close(&mut self, connection: Self::ConnectionHandle) -> Result<(), Self::Error> {
todo!();
Ok(())
}
async fn poll(&mut self) -> impl Iterator<Item = Result<(Self::ConnectionHandle, Vec<u8>), Self::Error>> {
todo!();
[].into_iter()
}
async fn send(&mut self, data: &[u8]) -> Result<(), Self::Error> {
todo!()
}
}

View file

@ -0,0 +1,50 @@
use serde_json::{from_value, to_value, Value};
use vanth_transport::crypto::ntru::{PrivateKey, PublicKey};
use ntrulp::{
key::{priv_key::PrivKey as NtruPrivKey, pub_key::PubKey as NtruPubKey},
poly::{r3::R3, rq::Rq},
};
fn generate_keypair() -> (NtruPrivKey, NtruPubKey) {
let mut rng = rand::rng();
let f: Rq = Rq::from(ntrulp::rng::short_random(&mut rng).expect("failed to sample short_random"));
let mut g: R3;
let sk = loop {
g = R3::from(ntrulp::rng::random_small(&mut rng));
match NtruPrivKey::compute(&f, &g) {
Ok(s) => break s,
Err(_) => continue,
};
};
let pk = NtruPubKey::from_sk(&sk).expect("failed to derive public key from secret key");
(sk, pk)
}
#[test]
fn test_private_key_serde_roundtrip() {
let (sk, _) = generate_keypair();
// Round-trip via serde_json Value without touching internal fields.
let wrapped: PrivateKey = sk.into();
let json1: Value = to_value(&wrapped).expect("failed to serialize PrivateKey");
let reparsed: PrivateKey = from_value(json1.clone()).expect("failed to deserialize PrivateKey");
let json2: Value = to_value(&reparsed).expect("failed to serialize PrivateKey again");
assert_eq!(json2, json1, "PrivateKey JSON did not round-trip losslessly.");
}
#[test]
fn test_public_key_serde_roundtrip() {
let (_, pk) = generate_keypair();
// Round-trip via serde_json Value without touching internal fields.
let wrapped: PublicKey = pk.into();
let json1: Value = to_value(&wrapped).expect("failed to serialize PublicKey");
let reparsed: PublicKey = from_value(json1.clone()).expect("failed to deserialize PublicKey");
let json2: Value = to_value(&reparsed).expect("failed to serialize PublicKey again");
assert_eq!(json2, json1, "PublicKey JSON did not round-trip losslessly.");
}

View file

@ -94,6 +94,18 @@
'';
}
);
clippyCheck = craneLib.cargoClippy (
commonArgs
// craneArgs
// {
inherit cargoArtifacts;
pname = "vanth-clippy";
version = "0.1.0";
# Fail on any lint warning and cover all targets/features.
cargoClippyExtraArgs = "--all-targets --all-features -- -D warnings";
}
);
in
{
packages = rec {
@ -101,6 +113,10 @@
default = vanth;
};
checks = {
# clippy = clippyCheck;
};
devShells.default = craneLib.devShell {
inputsFrom = [ vanth ];
packages = packages;