Initialize repository snapshot
This commit is contained in:
10
vaultmesh-core/Cargo.toml
Normal file
10
vaultmesh-core/Cargo.toml
Normal file
@@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "vaultmesh-core"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
blake3 = "1"
|
||||
130
vaultmesh-core/src/did.rs
Normal file
130
vaultmesh-core/src/did.rs
Normal file
@@ -0,0 +1,130 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// VaultMesh DID
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
||||
pub struct Did(String);
|
||||
|
||||
impl Did {
|
||||
pub fn new(did_type: DidType, identifier: &str) -> Self {
|
||||
Did(format!("did:vm:{}:{}", did_type.as_str(), identifier))
|
||||
}
|
||||
|
||||
pub fn parse(s: &str) -> Result<Self, DidParseError> {
|
||||
if !s.starts_with("did:vm:") {
|
||||
return Err(DidParseError::InvalidPrefix);
|
||||
}
|
||||
Ok(Did(s.to_string()))
|
||||
}
|
||||
|
||||
pub fn did_type(&self) -> Option<DidType> {
|
||||
let parts: Vec<&str> = self.0.split(':').collect();
|
||||
if parts.len() >= 3 {
|
||||
DidType::from_str(parts[2])
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn identifier(&self) -> Option<&str> {
|
||||
let parts: Vec<&str> = self.0.split(':').collect();
|
||||
if parts.len() >= 4 {
|
||||
Some(parts[3])
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub enum DidType {
|
||||
Node,
|
||||
Human,
|
||||
Agent,
|
||||
Service,
|
||||
Mesh,
|
||||
Portal,
|
||||
Guardian,
|
||||
Skill,
|
||||
}
|
||||
|
||||
impl DidType {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
DidType::Node => "node",
|
||||
DidType::Human => "human",
|
||||
DidType::Agent => "agent",
|
||||
DidType::Service => "service",
|
||||
DidType::Mesh => "mesh",
|
||||
DidType::Portal => "portal",
|
||||
DidType::Guardian => "guardian",
|
||||
DidType::Skill => "skill",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_str(s: &str) -> Option<Self> {
|
||||
match s {
|
||||
"node" => Some(DidType::Node),
|
||||
"human" => Some(DidType::Human),
|
||||
"agent" => Some(DidType::Agent),
|
||||
"service" => Some(DidType::Service),
|
||||
"mesh" => Some(DidType::Mesh),
|
||||
"portal" => Some(DidType::Portal),
|
||||
"guardian" => Some(DidType::Guardian),
|
||||
"skill" => Some(DidType::Skill),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum DidParseError {
|
||||
InvalidPrefix,
|
||||
MissingType,
|
||||
MissingIdentifier,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_did_new() {
|
||||
let did = Did::new(DidType::Human, "alice");
|
||||
assert_eq!(did.as_str(), "did:vm:human:alice");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_did_parse_valid() {
|
||||
let did = Did::parse("did:vm:guardian:local").unwrap();
|
||||
assert_eq!(did.did_type(), Some(DidType::Guardian));
|
||||
assert_eq!(did.identifier(), Some("local"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_did_parse_invalid_prefix() {
|
||||
let result = Did::parse("did:web:example.com");
|
||||
assert!(matches!(result, Err(DidParseError::InvalidPrefix)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_did_type_roundtrip() {
|
||||
for did_type in [
|
||||
DidType::Node, DidType::Human, DidType::Agent,
|
||||
DidType::Service, DidType::Mesh, DidType::Portal,
|
||||
DidType::Guardian, DidType::Skill,
|
||||
] {
|
||||
let s = did_type.as_str();
|
||||
let parsed = DidType::from_str(s);
|
||||
assert_eq!(parsed, Some(did_type));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_did_type_unknown() {
|
||||
assert_eq!(DidType::from_str("unknown"), None);
|
||||
}
|
||||
}
|
||||
147
vaultmesh-core/src/hash.rs
Normal file
147
vaultmesh-core/src/hash.rs
Normal file
@@ -0,0 +1,147 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// VaultMesh hash with algorithm prefix
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct VmHash(String);
|
||||
|
||||
impl VmHash {
|
||||
/// Create hash from bytes using Blake3
|
||||
pub fn blake3(data: &[u8]) -> Self {
|
||||
let hash = blake3::hash(data);
|
||||
VmHash(format!("blake3:{}", hash.to_hex()))
|
||||
}
|
||||
|
||||
/// Create hash from JSON-serializable value
|
||||
pub fn from_json<T: Serialize>(value: &T) -> Result<Self, serde_json::Error> {
|
||||
let json = serde_json::to_vec(value)?;
|
||||
Ok(Self::blake3(&json))
|
||||
}
|
||||
|
||||
/// Create hash from file contents
|
||||
pub fn from_file(path: &std::path::Path) -> std::io::Result<Self> {
|
||||
let contents = std::fs::read(path)?;
|
||||
Ok(Self::blake3(&contents))
|
||||
}
|
||||
|
||||
/// Get the raw hex value without prefix
|
||||
pub fn hex(&self) -> &str {
|
||||
self.0.strip_prefix("blake3:").unwrap_or(&self.0)
|
||||
}
|
||||
|
||||
/// Get full prefixed value
|
||||
pub fn as_str(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute Merkle root from list of hashes
|
||||
pub fn merkle_root(hashes: &[VmHash]) -> VmHash {
|
||||
if hashes.is_empty() {
|
||||
return VmHash::blake3(b"empty");
|
||||
}
|
||||
if hashes.len() == 1 {
|
||||
return hashes[0].clone();
|
||||
}
|
||||
|
||||
let mut current_level: Vec<VmHash> = hashes.to_vec();
|
||||
|
||||
while current_level.len() > 1 {
|
||||
let mut next_level = Vec::new();
|
||||
|
||||
for chunk in current_level.chunks(2) {
|
||||
let combined = if chunk.len() == 2 {
|
||||
format!("{}{}", chunk[0].hex(), chunk[1].hex())
|
||||
} else {
|
||||
format!("{}{}", chunk[0].hex(), chunk[0].hex())
|
||||
};
|
||||
next_level.push(VmHash::blake3(combined.as_bytes()));
|
||||
}
|
||||
|
||||
current_level = next_level;
|
||||
}
|
||||
|
||||
current_level.remove(0)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_vmhash_blake3_deterministic() {
|
||||
let data = b"test data";
|
||||
let h1 = VmHash::blake3(data);
|
||||
let h2 = VmHash::blake3(data);
|
||||
assert_eq!(h1, h2);
|
||||
assert!(h1.as_str().starts_with("blake3:"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vmhash_different_inputs() {
|
||||
let h1 = VmHash::blake3(b"hello");
|
||||
let h2 = VmHash::blake3(b"world");
|
||||
assert_ne!(h1, h2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vmhash_from_json() {
|
||||
#[derive(Serialize)]
|
||||
struct TestStruct { value: i32 }
|
||||
|
||||
let obj = TestStruct { value: 42 };
|
||||
let hash = VmHash::from_json(&obj).unwrap();
|
||||
assert!(hash.as_str().starts_with("blake3:"));
|
||||
assert_eq!(hash.hex().len(), 64); // 256 bits = 64 hex chars
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vmhash_hex_extraction() {
|
||||
let hash = VmHash::blake3(b"test");
|
||||
let hex = hash.hex();
|
||||
assert_eq!(hex.len(), 64);
|
||||
assert!(!hex.contains("blake3:"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_merkle_root_empty() {
|
||||
let hashes: Vec<VmHash> = vec![];
|
||||
let root = merkle_root(&hashes);
|
||||
let expected = VmHash::blake3(b"empty");
|
||||
assert_eq!(root, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_merkle_root_single() {
|
||||
let h = VmHash::blake3(b"single");
|
||||
let root = merkle_root(&[h.clone()]);
|
||||
assert_eq!(root, h);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_merkle_root_pair() {
|
||||
let h1 = VmHash::blake3(b"left");
|
||||
let h2 = VmHash::blake3(b"right");
|
||||
let root = merkle_root(&[h1.clone(), h2.clone()]);
|
||||
|
||||
// Verify it's not just one of the inputs
|
||||
assert_ne!(root, h1);
|
||||
assert_ne!(root, h2);
|
||||
|
||||
// Verify determinism
|
||||
let root2 = merkle_root(&[h1, h2]);
|
||||
assert_eq!(root, root2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_merkle_root_odd_count() {
|
||||
let h1 = VmHash::blake3(b"one");
|
||||
let h2 = VmHash::blake3(b"two");
|
||||
let h3 = VmHash::blake3(b"three");
|
||||
|
||||
let root = merkle_root(&[h1.clone(), h2.clone(), h3.clone()]);
|
||||
|
||||
// Should be deterministic
|
||||
let root2 = merkle_root(&[h1, h2, h3]);
|
||||
assert_eq!(root, root2);
|
||||
}
|
||||
}
|
||||
7
vaultmesh-core/src/lib.rs
Normal file
7
vaultmesh-core/src/lib.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
pub mod receipt;
|
||||
pub mod did;
|
||||
pub mod hash;
|
||||
|
||||
pub use receipt::{Receipt, ReceiptHeader, ReceiptMeta, Scroll};
|
||||
pub use did::{Did, DidType, DidParseError};
|
||||
pub use hash::{VmHash, merkle_root};
|
||||
79
vaultmesh-core/src/receipt.rs
Normal file
79
vaultmesh-core/src/receipt.rs
Normal file
@@ -0,0 +1,79 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Universal receipt header present in all receipts
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ReceiptHeader {
|
||||
pub receipt_type: String,
|
||||
pub timestamp: DateTime<Utc>,
|
||||
pub root_hash: String,
|
||||
pub tags: Vec<String>,
|
||||
}
|
||||
|
||||
/// Receipt metadata for tracking and querying
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ReceiptMeta {
|
||||
pub scroll: Scroll,
|
||||
pub sequence: u64,
|
||||
pub anchor_epoch: Option<u64>,
|
||||
pub proof_path: Option<String>,
|
||||
}
|
||||
|
||||
/// Scroll identifiers
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Scroll {
|
||||
Drills,
|
||||
Compliance,
|
||||
Guardian,
|
||||
Treasury,
|
||||
Mesh,
|
||||
OffSec,
|
||||
Identity,
|
||||
Observability,
|
||||
Automation,
|
||||
PsiField,
|
||||
}
|
||||
|
||||
impl Scroll {
|
||||
pub fn jsonl_path(&self) -> &'static str {
|
||||
match self {
|
||||
Scroll::Drills => "receipts/drills/drill_runs.jsonl",
|
||||
Scroll::Compliance => "receipts/compliance/oracle_answers.jsonl",
|
||||
Scroll::Guardian => "receipts/guardian/anchor_events.jsonl",
|
||||
Scroll::Treasury => "receipts/treasury/treasury_events.jsonl",
|
||||
Scroll::Mesh => "receipts/mesh/mesh_events.jsonl",
|
||||
Scroll::OffSec => "receipts/offsec/offsec_events.jsonl",
|
||||
Scroll::Identity => "receipts/identity/identity_events.jsonl",
|
||||
Scroll::Observability => "receipts/observability/observability_events.jsonl",
|
||||
Scroll::Automation => "receipts/automation/automation_events.jsonl",
|
||||
Scroll::PsiField => "receipts/psi/psi_events.jsonl",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn root_file(&self) -> &'static str {
|
||||
match self {
|
||||
Scroll::Drills => "ROOT.drills.txt",
|
||||
Scroll::Compliance => "ROOT.compliance.txt",
|
||||
Scroll::Guardian => "ROOT.guardian.txt",
|
||||
Scroll::Treasury => "ROOT.treasury.txt",
|
||||
Scroll::Mesh => "ROOT.mesh.txt",
|
||||
Scroll::OffSec => "ROOT.offsec.txt",
|
||||
Scroll::Identity => "ROOT.identity.txt",
|
||||
Scroll::Observability => "ROOT.observability.txt",
|
||||
Scroll::Automation => "ROOT.automation.txt",
|
||||
Scroll::PsiField => "ROOT.psi.txt",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Generic receipt wrapper
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Receipt<T> {
|
||||
#[serde(flatten)]
|
||||
pub header: ReceiptHeader,
|
||||
#[serde(flatten)]
|
||||
pub meta: ReceiptMeta,
|
||||
#[serde(flatten)]
|
||||
pub body: T,
|
||||
}
|
||||
Reference in New Issue
Block a user