use borsh::{BorshDeserialize, BorshSerialize};
use serde::{Deserialize, Serialize};
use crate::errors::{CommonError, Result};
pub type Hash256 = [u8; 32];
#[derive(
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
Debug,
Default,
BorshSerialize,
BorshDeserialize,
Serialize,
Deserialize,
)]
pub struct Address(pub [u8; 20]);
impl Address {
pub const fn new(bytes: [u8; 20]) -> Self {
Self(bytes)
}
pub const fn as_bytes(&self) -> &[u8; 20] {
&self.0
}
pub fn to_hex(self) -> String {
hex::encode(self.0)
}
pub fn from_hex(s: &str) -> Result<Self> {
let s = s.strip_prefix("0x").unwrap_or(s);
let bytes = hex::decode(s).map_err(|e| CommonError::InvalidArgument(e.to_string()))?;
if bytes.len() != 20 {
return Err(CommonError::InvalidArgument(format!(
"expected 20-byte address, got {} bytes",
bytes.len()
)));
}
let mut out = [0u8; 20];
out.copy_from_slice(&bytes);
Ok(Self(out))
}
}
impl std::fmt::Display for Address {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "0x{}", self.to_hex())
}
}
#[derive(
Clone,
Copy,
PartialEq,
Eq,
Hash,
Debug,
Default,
BorshSerialize,
BorshDeserialize,
Serialize,
Deserialize,
)]
pub struct NodeId(pub [u8; 32]);
impl NodeId {
pub const fn new(bytes: [u8; 32]) -> Self {
Self(bytes)
}
pub const fn as_bytes(&self) -> &[u8; 32] {
&self.0
}
}
impl std::fmt::Display for NodeId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "node:{}", hex::encode(self.0))
}
}
#[derive(
Clone,
Copy,
PartialEq,
Eq,
Hash,
Debug,
Default,
BorshSerialize,
BorshDeserialize,
Serialize,
Deserialize,
)]
pub struct JobId(pub [u8; 32]);
impl JobId {
pub const fn new(bytes: [u8; 32]) -> Self {
Self(bytes)
}
}
impl std::fmt::Display for JobId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "job:{}", hex::encode(self.0))
}
}
#[derive(
Clone,
Copy,
PartialEq,
Eq,
Hash,
Debug,
Default,
BorshSerialize,
BorshDeserialize,
Serialize,
Deserialize,
)]
pub struct PoolId(pub [u8; 16]);
impl PoolId {
pub const fn new(bytes: [u8; 16]) -> Self {
Self(bytes)
}
}
impl std::fmt::Display for PoolId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "pool:{}", hex::encode(self.0))
}
}
#[derive(
Clone,
Copy,
PartialEq,
Eq,
Hash,
Debug,
Default,
BorshSerialize,
BorshDeserialize,
Serialize,
Deserialize,
)]
pub struct ChannelId(pub [u8; 32]);
impl ChannelId {
pub const fn new(bytes: [u8; 32]) -> Self {
Self(bytes)
}
}
pub type Amount = u128;
pub type Height = u64;
pub type Timestamp = u64;
pub type Nonce = u64;
pub type Gas = u64;
pub const ATOMS_PER_ARK: Amount = 1_000_000_000;
pub const ARK_SUPPLY_CAP: Amount = 1_000_000_000 * ATOMS_PER_ARK;
#[derive(
Clone,
Copy,
PartialEq,
Eq,
Hash,
Debug,
Default,
BorshSerialize,
BorshDeserialize,
Serialize,
Deserialize,
)]
pub struct TxHash(pub Hash256);
impl TxHash {
pub const fn new(bytes: Hash256) -> Self {
Self(bytes)
}
pub const fn as_bytes(&self) -> &Hash256 {
&self.0
}
}
impl std::fmt::Display for TxHash {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "tx:{}", hex::encode(self.0))
}
}
#[derive(
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
Debug,
Default,
BorshSerialize,
BorshDeserialize,
Serialize,
Deserialize,
)]
pub struct BlockHash(pub Hash256);
impl BlockHash {
pub const fn new(bytes: Hash256) -> Self {
Self(bytes)
}
pub const fn as_bytes(&self) -> &Hash256 {
&self.0
}
}
impl std::fmt::Display for BlockHash {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "block:{}", hex::encode(self.0))
}
}
#[derive(
Clone,
Copy,
PartialEq,
Eq,
Hash,
Debug,
Default,
BorshSerialize,
BorshDeserialize,
Serialize,
Deserialize,
)]
pub struct StateRoot(pub Hash256);
impl StateRoot {
pub const fn new(bytes: Hash256) -> Self {
Self(bytes)
}
pub const fn as_bytes(&self) -> &Hash256 {
&self.0
}
}
#[derive(
Clone,
Copy,
PartialEq,
Eq,
Hash,
Debug,
Default,
BorshSerialize,
BorshDeserialize,
Serialize,
Deserialize,
)]
pub struct AppHash(pub Hash256);
impl AppHash {
pub const fn new(bytes: Hash256) -> Self {
Self(bytes)
}
pub const fn as_bytes(&self) -> &Hash256 {
&self.0
}
}
pub const DOMAIN_TX: &[u8] = b"arknet-tx-v1";
pub const DOMAIN_BLOCK: &[u8] = b"arknet-block-v1";
pub const DOMAIN_TX_ROOT: &[u8] = b"arknet-tx-root-v1";
pub const DOMAIN_RECEIPT_ROOT: &[u8] = b"arknet-receipt-root-v1";
#[repr(u8)]
#[derive(
Clone,
Copy,
PartialEq,
Eq,
Hash,
Debug,
BorshSerialize,
BorshDeserialize,
Serialize,
Deserialize,
)]
#[borsh(use_discriminant = true)]
pub enum SignatureScheme {
Ed25519 = 0x01,
Dilithium = 0x02,
Falcon = 0x03,
Sphincs = 0x04,
HybridEd25519Dilithium = 0x05,
}
impl SignatureScheme {
pub const fn is_active(&self) -> bool {
matches!(self, SignatureScheme::Ed25519)
}
pub const fn pubkey_len(&self) -> usize {
match self {
SignatureScheme::Ed25519 => 32,
SignatureScheme::Dilithium => 1312,
SignatureScheme::Falcon => 897,
SignatureScheme::Sphincs => 32,
SignatureScheme::HybridEd25519Dilithium => 32 + 1312,
}
}
pub const fn signature_len(&self) -> usize {
match self {
SignatureScheme::Ed25519 => 64,
SignatureScheme::Dilithium => 2420,
SignatureScheme::Falcon => 666,
SignatureScheme::Sphincs => 17088,
SignatureScheme::HybridEd25519Dilithium => 64 + 2420,
}
}
}
#[repr(u8)]
#[derive(
Clone,
Copy,
PartialEq,
Eq,
Hash,
Debug,
BorshSerialize,
BorshDeserialize,
Serialize,
Deserialize,
)]
#[borsh(use_discriminant = true)]
pub enum KemScheme {
X25519 = 0x01,
Kyber = 0x02,
HybridX25519Kyber = 0x03,
}
impl KemScheme {
pub const fn is_active(&self) -> bool {
matches!(self, KemScheme::X25519)
}
}
#[repr(u8)]
#[derive(
Clone,
Copy,
PartialEq,
Eq,
Hash,
Debug,
BorshSerialize,
BorshDeserialize,
Serialize,
Deserialize,
)]
#[borsh(use_discriminant = true)]
pub enum VrfScheme {
Ristretto255 = 0x01,
LatticeVrf = 0x02,
}
impl VrfScheme {
pub const fn is_active(&self) -> bool {
matches!(self, VrfScheme::Ristretto255)
}
}
#[derive(
Clone, PartialEq, Eq, Hash, Debug, BorshSerialize, BorshDeserialize, Serialize, Deserialize,
)]
pub struct PubKey {
pub scheme: SignatureScheme,
pub bytes: Vec<u8>,
}
impl PubKey {
pub fn new(scheme: SignatureScheme, bytes: Vec<u8>) -> Result<Self> {
if bytes.len() != scheme.pubkey_len() {
return Err(CommonError::InvalidArgument(format!(
"pubkey length for {:?} must be {}, got {}",
scheme,
scheme.pubkey_len(),
bytes.len()
)));
}
Ok(Self { scheme, bytes })
}
pub fn ed25519(bytes: [u8; 32]) -> Self {
Self {
scheme: SignatureScheme::Ed25519,
bytes: bytes.to_vec(),
}
}
}
#[derive(
Clone, PartialEq, Eq, Hash, Debug, BorshSerialize, BorshDeserialize, Serialize, Deserialize,
)]
pub struct Signature {
pub scheme: SignatureScheme,
pub bytes: Vec<u8>,
}
impl Signature {
pub fn new(scheme: SignatureScheme, bytes: Vec<u8>) -> Result<Self> {
if bytes.len() != scheme.signature_len() {
return Err(CommonError::InvalidArgument(format!(
"signature length for {:?} must be {}, got {}",
scheme,
scheme.signature_len(),
bytes.len()
)));
}
Ok(Self { scheme, bytes })
}
pub fn ed25519(bytes: [u8; 64]) -> Self {
Self {
scheme: SignatureScheme::Ed25519,
bytes: bytes.to_vec(),
}
}
}
#[repr(u8)]
#[derive(
Clone,
Copy,
PartialEq,
Eq,
Hash,
Debug,
BorshSerialize,
BorshDeserialize,
Serialize,
Deserialize,
)]
#[borsh(use_discriminant = true)]
pub enum TeePlatform {
IntelTdx = 0x01,
AmdSevSnp = 0x02,
ArmCca = 0x03,
}
#[derive(
Clone, PartialEq, Eq, Hash, Debug, BorshSerialize, BorshDeserialize, Serialize, Deserialize,
)]
pub struct TeeCapability {
pub platform: TeePlatform,
pub quote: Vec<u8>,
pub enclave_pubkey: PubKey,
}
pub const MAX_TEE_QUOTE_BYTES: usize = 16 * 1024;
#[derive(
Clone,
Copy,
PartialEq,
Eq,
Hash,
Debug,
Default,
BorshSerialize,
BorshDeserialize,
Serialize,
Deserialize,
)]
pub struct RoleBitmap(pub u8);
impl RoleBitmap {
pub const NONE: RoleBitmap = RoleBitmap(0);
pub const VALIDATOR: RoleBitmap = RoleBitmap(0b0001);
pub const ROUTER: RoleBitmap = RoleBitmap(0b0010);
pub const COMPUTE: RoleBitmap = RoleBitmap(0b0100);
pub const VERIFIER: RoleBitmap = RoleBitmap(0b1000);
pub const fn has(&self, other: RoleBitmap) -> bool {
(self.0 & other.0) == other.0
}
pub const fn with(self, other: RoleBitmap) -> Self {
Self(self.0 | other.0)
}
pub const fn without(self, other: RoleBitmap) -> Self {
Self(self.0 & !other.0)
}
pub const fn is_empty(&self) -> bool {
self.0 == 0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn atoms_per_ark_is_billion() {
assert_eq!(ATOMS_PER_ARK, 1_000_000_000);
}
#[test]
fn supply_cap_is_1b_ark() {
assert_eq!(ARK_SUPPLY_CAP, 1_000_000_000_000_000_000u128);
}
#[test]
fn address_hex_roundtrip() {
let a = Address::new([0x42; 20]);
let s = a.to_hex();
assert_eq!(s.len(), 40);
let b = Address::from_hex(&s).unwrap();
assert_eq!(a, b);
}
#[test]
fn address_hex_accepts_0x_prefix() {
let raw = "0x4242424242424242424242424242424242424242";
let a = Address::from_hex(raw).unwrap();
assert_eq!(a.0, [0x42; 20]);
}
#[test]
fn address_from_hex_rejects_wrong_length() {
assert!(Address::from_hex("abcd").is_err());
}
#[test]
fn signature_scheme_lengths_match_specs() {
assert_eq!(SignatureScheme::Ed25519.pubkey_len(), 32);
assert_eq!(SignatureScheme::Ed25519.signature_len(), 64);
assert_eq!(SignatureScheme::Dilithium.pubkey_len(), 1312);
assert_eq!(SignatureScheme::Dilithium.signature_len(), 2420);
assert_eq!(SignatureScheme::Falcon.pubkey_len(), 897);
assert_eq!(SignatureScheme::Falcon.signature_len(), 666);
}
#[test]
fn only_ed25519_active_at_genesis() {
assert!(SignatureScheme::Ed25519.is_active());
assert!(!SignatureScheme::Dilithium.is_active());
assert!(!SignatureScheme::Falcon.is_active());
assert!(!SignatureScheme::Sphincs.is_active());
assert!(!SignatureScheme::HybridEd25519Dilithium.is_active());
}
#[test]
fn only_x25519_kem_active_at_genesis() {
assert!(KemScheme::X25519.is_active());
assert!(!KemScheme::Kyber.is_active());
}
#[test]
fn only_ristretto_vrf_active_at_genesis() {
assert!(VrfScheme::Ristretto255.is_active());
assert!(!VrfScheme::LatticeVrf.is_active());
}
#[test]
fn pubkey_constructor_enforces_length() {
assert!(PubKey::new(SignatureScheme::Ed25519, vec![0; 32]).is_ok());
assert!(PubKey::new(SignatureScheme::Ed25519, vec![0; 31]).is_err());
assert!(PubKey::new(SignatureScheme::Ed25519, vec![0; 33]).is_err());
}
#[test]
fn signature_constructor_enforces_length() {
assert!(Signature::new(SignatureScheme::Ed25519, vec![0; 64]).is_ok());
assert!(Signature::new(SignatureScheme::Ed25519, vec![0; 63]).is_err());
}
#[test]
fn ed25519_constructors_produce_valid_values() {
let pk = PubKey::ed25519([0xaa; 32]);
assert_eq!(pk.scheme, SignatureScheme::Ed25519);
assert_eq!(pk.bytes.len(), 32);
let sig = Signature::ed25519([0xbb; 64]);
assert_eq!(sig.scheme, SignatureScheme::Ed25519);
assert_eq!(sig.bytes.len(), 64);
}
#[test]
fn role_bitmap_operations() {
let mut roles = RoleBitmap::NONE;
assert!(roles.is_empty());
roles = roles.with(RoleBitmap::ROUTER).with(RoleBitmap::COMPUTE);
assert!(roles.has(RoleBitmap::ROUTER));
assert!(roles.has(RoleBitmap::COMPUTE));
assert!(!roles.has(RoleBitmap::VALIDATOR));
assert!(!roles.has(RoleBitmap::VERIFIER));
assert!(!roles.is_empty());
let without_router = roles.without(RoleBitmap::ROUTER);
assert!(!without_router.has(RoleBitmap::ROUTER));
assert!(without_router.has(RoleBitmap::COMPUTE));
}
#[test]
fn borsh_roundtrip_pubkey() {
let pk = PubKey::ed25519([0x11; 32]);
let bytes = borsh::to_vec(&pk).unwrap();
let decoded: PubKey = borsh::from_slice(&bytes).unwrap();
assert_eq!(pk, decoded);
}
#[test]
fn borsh_roundtrip_signature() {
let sig = Signature::ed25519([0x22; 64]);
let bytes = borsh::to_vec(&sig).unwrap();
let decoded: Signature = borsh::from_slice(&bytes).unwrap();
assert_eq!(sig, decoded);
}
#[test]
fn borsh_roundtrip_address() {
let a = Address::new([0x33; 20]);
let bytes = borsh::to_vec(&a).unwrap();
let decoded: Address = borsh::from_slice(&bytes).unwrap();
assert_eq!(a, decoded);
}
#[test]
fn borsh_roundtrip_tx_hash() {
let h = TxHash::new([0x44; 32]);
let bytes = borsh::to_vec(&h).unwrap();
let decoded: TxHash = borsh::from_slice(&bytes).unwrap();
assert_eq!(h, decoded);
}
#[test]
fn borsh_roundtrip_block_hash() {
let h = BlockHash::new([0x55; 32]);
let bytes = borsh::to_vec(&h).unwrap();
let decoded: BlockHash = borsh::from_slice(&bytes).unwrap();
assert_eq!(h, decoded);
}
#[test]
fn borsh_roundtrip_state_root() {
let r = StateRoot::new([0x66; 32]);
let bytes = borsh::to_vec(&r).unwrap();
let decoded: StateRoot = borsh::from_slice(&bytes).unwrap();
assert_eq!(r, decoded);
}
#[test]
fn borsh_roundtrip_app_hash() {
let h = AppHash::new([0x77; 32]);
let bytes = borsh::to_vec(&h).unwrap();
let decoded: AppHash = borsh::from_slice(&bytes).unwrap();
assert_eq!(h, decoded);
}
#[test]
fn hash_domain_tags_are_distinct() {
assert_ne!(DOMAIN_TX, DOMAIN_BLOCK);
assert_ne!(DOMAIN_TX, DOMAIN_TX_ROOT);
assert_ne!(DOMAIN_TX, DOMAIN_RECEIPT_ROOT);
assert_ne!(DOMAIN_BLOCK, DOMAIN_TX_ROOT);
assert_ne!(DOMAIN_BLOCK, DOMAIN_RECEIPT_ROOT);
assert_ne!(DOMAIN_TX_ROOT, DOMAIN_RECEIPT_ROOT);
}
#[test]
fn tee_platform_discriminants_are_stable() {
assert_eq!(TeePlatform::IntelTdx as u8, 0x01);
assert_eq!(TeePlatform::AmdSevSnp as u8, 0x02);
assert_eq!(TeePlatform::ArmCca as u8, 0x03);
}
#[test]
fn tee_capability_borsh_roundtrip() {
let cap = TeeCapability {
platform: TeePlatform::IntelTdx,
quote: vec![0xde, 0xad, 0xbe, 0xef],
enclave_pubkey: PubKey::ed25519([0x99; 32]),
};
let bytes = borsh::to_vec(&cap).unwrap();
let decoded: TeeCapability = borsh::from_slice(&bytes).unwrap();
assert_eq!(cap, decoded);
}
#[test]
fn tx_and_block_hash_newtypes_are_distinct_from_byte_arrays() {
fn take_tx(_: TxHash) {}
fn take_block(_: BlockHash) {}
take_tx(TxHash::new([0; 32]));
take_block(BlockHash::new([0; 32]));
}
}