#![cfg(feature = "net")]
use crate::net::schema::{NETWORK_ID, SCHEMA_ENVELOPE};
use base64::{engine::general_purpose::STANDARD as BASE64, Engine as _};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use std::fmt;
pub const SCHEMA_BLOB: &str = "mfenx.powerhouse.blob.v1";
pub const TOPIC_BLOBS: &str = "mfenx/powerhouse/blobs/v1";
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct BlobJson {
pub schema: String,
pub network: String,
pub namespace: String,
pub hash: String,
pub size: u64,
pub data: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub data_shards: Option<u8>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub parity_shards: Option<u8>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub share_root: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub attestation_sig: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub attestation_pk: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub publisher_pk: Option<String>,
}
impl BlobJson {
pub fn from_bytes(namespace: impl Into<String>, data: &[u8]) -> Self {
let hash = blake2b_hex(data);
Self {
schema: SCHEMA_BLOB.to_string(),
network: NETWORK_ID.to_string(),
namespace: namespace.into(),
hash,
size: data.len() as u64,
data: BASE64.encode(data),
data_shards: None,
parity_shards: None,
share_root: None,
attestation_sig: None,
attestation_pk: None,
publisher_pk: None,
}
}
pub fn decode_data(&self) -> Result<Vec<u8>, BlobCodecError> {
BASE64
.decode(self.data.as_bytes())
.map_err(|err| BlobCodecError::Decode(err.to_string()))
}
pub fn validate(&self) -> Result<(), BlobCodecError> {
if self.schema != SCHEMA_BLOB {
return Err(BlobCodecError::InvalidSchema {
expected: SCHEMA_BLOB,
found: self.schema.clone(),
});
}
if self.network != NETWORK_ID {
return Err(BlobCodecError::InvalidNetwork {
expected: NETWORK_ID,
found: self.network.clone(),
});
}
let decoded = self.decode_data()?;
if decoded.len() as u64 != self.size {
return Err(BlobCodecError::SizeMismatch {
expected: self.size,
actual: decoded.len() as u64,
});
}
let recomputed = blake2b_hex(&decoded);
if recomputed != self.hash {
return Err(BlobCodecError::HashMismatch {
expected: self.hash.clone(),
actual: recomputed,
});
}
Ok(())
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct BlobEnvelope {
pub schema: String,
#[serde(default = "default_schema_version")]
pub schema_version: u32,
pub public_key: String,
pub node_id: String,
pub payload: String,
pub signature: String,
}
impl BlobEnvelope {
pub fn validate(&self) -> Result<(), BlobCodecError> {
if self.schema != SCHEMA_ENVELOPE {
return Err(BlobCodecError::InvalidSchema {
expected: SCHEMA_ENVELOPE,
found: self.schema.clone(),
});
}
if self.schema_version == 0 {
return Err(BlobCodecError::InvalidVersion(self.schema_version));
}
Ok(())
}
}
fn default_schema_version() -> u32 {
1
}
fn blake2b_hex(data: &[u8]) -> String {
let mut hasher = blake2::Blake2b::<blake2::digest::consts::U32>::new();
hasher.update(data);
hex::encode(hasher.finalize())
}
#[derive(Debug, Clone)]
pub enum BlobCodecError {
InvalidSchema {
expected: &'static str,
found: String,
},
InvalidNetwork {
expected: &'static str,
found: String,
},
Decode(String),
SizeMismatch {
expected: u64,
actual: u64,
},
HashMismatch {
expected: String,
actual: String,
},
InvalidVersion(u32),
}
impl fmt::Display for BlobCodecError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidSchema { expected, found } => {
write!(f, "invalid schema: expected {expected}, found {found}")
}
Self::InvalidNetwork { expected, found } => {
write!(f, "invalid network: expected {expected}, found {found}")
}
Self::Decode(reason) => write!(f, "decode error: {reason}"),
Self::SizeMismatch { expected, actual } => {
write!(f, "size mismatch: expected {expected}, actual {actual}")
}
Self::HashMismatch { expected, actual } => {
write!(f, "hash mismatch: expected {expected}, actual {actual}")
}
Self::InvalidVersion(v) => write!(f, "invalid envelope version {v}"),
}
}
}
impl std::error::Error for BlobCodecError {}
pub fn sha256_digest(data: &[u8]) -> [u8; 32] {
let mut hasher = Sha256::new();
hasher.update(data);
let mut out = [0u8; 32];
out.copy_from_slice(&hasher.finalize());
out
}