Skip to main content

arknet_common/
types.rs

1//! Core primitive types used throughout arknet.
2//!
3//! These types are the on-the-wire and on-chain vocabulary. Layout must stay
4//! stable: any breaking change is a protocol hard fork.
5//!
6//! # Crypto agility
7//!
8//! Signatures, public keys, and KEM keys carry a [`SignatureScheme`] /
9//! [`KemScheme`] / [`VrfScheme`] tag in their first byte. At launch only the
10//! `0x01` variants (Ed25519, X25519, Ristretto255 VRF, BLS12-381 threshold)
11//! are implemented, but the wire format reserves the remaining space so a
12//! governance-scheduled post-quantum migration can ship without breaking
13//! transaction encoding.
14//!
15//! See `docs/SECURITY.md` §4 (Cryptographic Primitives) and §12 (PQ Migration).
16
17use borsh::{BorshDeserialize, BorshSerialize};
18use serde::{Deserialize, Serialize};
19
20use crate::errors::{CommonError, Result};
21
22// ─── Hashes & identifiers ─────────────────────────────────────────────────
23
24/// 256-bit digest. SHA-256 on-chain, BLAKE3 for fast local hashing.
25pub type Hash256 = [u8; 32];
26
27/// 20-byte account address. Derived as `blake3(pubkey_bytes)[0..20]`.
28///
29/// Addresses are displayed in bech32 as `ark1…` (mainnet) / `arktest1…` (testnet).
30/// See `docs/PROTOCOL_SPEC.md` §2.
31#[derive(
32    Clone,
33    Copy,
34    PartialEq,
35    Eq,
36    PartialOrd,
37    Ord,
38    Hash,
39    Debug,
40    Default,
41    BorshSerialize,
42    BorshDeserialize,
43    Serialize,
44    Deserialize,
45)]
46pub struct Address(pub [u8; 20]);
47
48impl Address {
49    /// Construct from raw bytes.
50    pub const fn new(bytes: [u8; 20]) -> Self {
51        Self(bytes)
52    }
53
54    /// Borrow the underlying byte array.
55    pub const fn as_bytes(&self) -> &[u8; 20] {
56        &self.0
57    }
58
59    /// Hex-encode without `0x` prefix.
60    pub fn to_hex(self) -> String {
61        hex::encode(self.0)
62    }
63
64    /// Parse a hex-encoded address (with or without `0x` prefix).
65    pub fn from_hex(s: &str) -> Result<Self> {
66        let s = s.strip_prefix("0x").unwrap_or(s);
67        let bytes = hex::decode(s).map_err(|e| CommonError::InvalidArgument(e.to_string()))?;
68        if bytes.len() != 20 {
69            return Err(CommonError::InvalidArgument(format!(
70                "expected 20-byte address, got {} bytes",
71                bytes.len()
72            )));
73        }
74        let mut out = [0u8; 20];
75        out.copy_from_slice(&bytes);
76        Ok(Self(out))
77    }
78}
79
80impl std::fmt::Display for Address {
81    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
82        write!(f, "0x{}", self.to_hex())
83    }
84}
85
86/// Node identifier — 32-byte hash of the node's consensus pubkey.
87#[derive(
88    Clone,
89    Copy,
90    PartialEq,
91    Eq,
92    Hash,
93    Debug,
94    Default,
95    BorshSerialize,
96    BorshDeserialize,
97    Serialize,
98    Deserialize,
99)]
100pub struct NodeId(pub [u8; 32]);
101
102impl NodeId {
103    /// Construct from raw bytes.
104    pub const fn new(bytes: [u8; 32]) -> Self {
105        Self(bytes)
106    }
107
108    /// Borrow the underlying byte array.
109    pub const fn as_bytes(&self) -> &[u8; 32] {
110        &self.0
111    }
112}
113
114impl std::fmt::Display for NodeId {
115    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
116        write!(f, "node:{}", hex::encode(self.0))
117    }
118}
119
120/// Inference job identifier. Unique per job.
121///
122/// Derived as `blake3(user_pubkey || router_id || nonce || timestamp_ms)`.
123#[derive(
124    Clone,
125    Copy,
126    PartialEq,
127    Eq,
128    Hash,
129    Debug,
130    Default,
131    BorshSerialize,
132    BorshDeserialize,
133    Serialize,
134    Deserialize,
135)]
136pub struct JobId(pub [u8; 32]);
137
138impl JobId {
139    /// Construct from raw bytes.
140    pub const fn new(bytes: [u8; 32]) -> Self {
141        Self(bytes)
142    }
143}
144
145impl std::fmt::Display for JobId {
146    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
147        write!(f, "job:{}", hex::encode(self.0))
148    }
149}
150
151/// Computation pool identifier — `hash(model_id || quantization)[0..16]`.
152#[derive(
153    Clone,
154    Copy,
155    PartialEq,
156    Eq,
157    Hash,
158    Debug,
159    Default,
160    BorshSerialize,
161    BorshDeserialize,
162    Serialize,
163    Deserialize,
164)]
165pub struct PoolId(pub [u8; 16]);
166
167impl PoolId {
168    /// Construct from raw bytes.
169    pub const fn new(bytes: [u8; 16]) -> Self {
170        Self(bytes)
171    }
172}
173
174impl std::fmt::Display for PoolId {
175    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
176        write!(f, "pool:{}", hex::encode(self.0))
177    }
178}
179
180/// Payment channel identifier.
181#[derive(
182    Clone,
183    Copy,
184    PartialEq,
185    Eq,
186    Hash,
187    Debug,
188    Default,
189    BorshSerialize,
190    BorshDeserialize,
191    Serialize,
192    Deserialize,
193)]
194pub struct ChannelId(pub [u8; 32]);
195
196impl ChannelId {
197    /// Construct from raw bytes.
198    pub const fn new(bytes: [u8; 32]) -> Self {
199        Self(bytes)
200    }
201}
202
203// ─── Numeric types ────────────────────────────────────────────────────────
204
205/// Token amount in atomic units. 1 ARK = [`ATOMS_PER_ARK`] `ark_atom` (9 decimals).
206///
207/// Always `u128`. Never represent token amounts as floats.
208pub type Amount = u128;
209
210/// Block height.
211pub type Height = u64;
212
213/// Unix timestamp in milliseconds.
214pub type Timestamp = u64;
215
216/// Per-account transaction counter. Increments by 1 with each committed tx
217/// from the same sender; replay is detected at the state-application layer.
218pub type Nonce = u64;
219
220/// Gas units consumed by a transaction. Fee markets price in `ark_atom/gas`.
221pub type Gas = u64;
222
223/// Atomic units per whole ARK token.
224pub const ATOMS_PER_ARK: Amount = 1_000_000_000;
225
226/// Protocol-level hard cap on ARK supply (1B ARK).
227pub const ARK_SUPPLY_CAP: Amount = 1_000_000_000 * ATOMS_PER_ARK;
228
229// ─── State / block / tx identifiers ───────────────────────────────────────
230
231/// Transaction hash. Distinct newtype from block hashes to prevent cross-type
232/// collision attacks — see [`DOMAIN_TX`] / [`DOMAIN_BLOCK`].
233#[derive(
234    Clone,
235    Copy,
236    PartialEq,
237    Eq,
238    Hash,
239    Debug,
240    Default,
241    BorshSerialize,
242    BorshDeserialize,
243    Serialize,
244    Deserialize,
245)]
246pub struct TxHash(pub Hash256);
247
248impl TxHash {
249    /// Construct from raw bytes.
250    pub const fn new(bytes: Hash256) -> Self {
251        Self(bytes)
252    }
253
254    /// Borrow the underlying digest.
255    pub const fn as_bytes(&self) -> &Hash256 {
256        &self.0
257    }
258}
259
260impl std::fmt::Display for TxHash {
261    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
262        write!(f, "tx:{}", hex::encode(self.0))
263    }
264}
265
266/// Block hash. Computed over a canonical [`BlockHeader`] borsh encoding.
267#[derive(
268    Clone,
269    Copy,
270    PartialEq,
271    Eq,
272    PartialOrd,
273    Ord,
274    Hash,
275    Debug,
276    Default,
277    BorshSerialize,
278    BorshDeserialize,
279    Serialize,
280    Deserialize,
281)]
282pub struct BlockHash(pub Hash256);
283
284impl BlockHash {
285    /// Construct from raw bytes.
286    pub const fn new(bytes: Hash256) -> Self {
287        Self(bytes)
288    }
289
290    /// Borrow the underlying digest.
291    pub const fn as_bytes(&self) -> &Hash256 {
292        &self.0
293    }
294}
295
296impl std::fmt::Display for BlockHash {
297    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
298        write!(f, "block:{}", hex::encode(self.0))
299    }
300}
301
302/// Merkle root of the state trie. Included in every [`BlockHeader`].
303#[derive(
304    Clone,
305    Copy,
306    PartialEq,
307    Eq,
308    Hash,
309    Debug,
310    Default,
311    BorshSerialize,
312    BorshDeserialize,
313    Serialize,
314    Deserialize,
315)]
316pub struct StateRoot(pub Hash256);
317
318impl StateRoot {
319    /// Construct from raw bytes.
320    pub const fn new(bytes: Hash256) -> Self {
321        Self(bytes)
322    }
323
324    /// Borrow the underlying digest.
325    pub const fn as_bytes(&self) -> &Hash256 {
326        &self.0
327    }
328}
329
330/// Application-layer state digest after applying a block's transactions.
331///
332/// Kept distinct from [`StateRoot`] so light clients can verify application
333/// state commitments without interpreting the full state trie.
334#[derive(
335    Clone,
336    Copy,
337    PartialEq,
338    Eq,
339    Hash,
340    Debug,
341    Default,
342    BorshSerialize,
343    BorshDeserialize,
344    Serialize,
345    Deserialize,
346)]
347pub struct AppHash(pub Hash256);
348
349impl AppHash {
350    /// Construct from raw bytes.
351    pub const fn new(bytes: Hash256) -> Self {
352        Self(bytes)
353    }
354
355    /// Borrow the underlying digest.
356    pub const fn as_bytes(&self) -> &Hash256 {
357        &self.0
358    }
359}
360
361// ─── Hash domain tags ─────────────────────────────────────────────────────
362
363/// Domain tag for transaction hashes. Prepended to borsh-encoded bytes before
364/// hashing so that `hash(tx) != hash(block)` even when their borsh happens
365/// to collide.
366pub const DOMAIN_TX: &[u8] = b"arknet-tx-v1";
367
368/// Domain tag for block (header) hashes. See [`DOMAIN_TX`].
369pub const DOMAIN_BLOCK: &[u8] = b"arknet-block-v1";
370
371/// Domain tag for block-body tx Merkle roots.
372pub const DOMAIN_TX_ROOT: &[u8] = b"arknet-tx-root-v1";
373
374/// Domain tag for block-body receipt Merkle roots.
375pub const DOMAIN_RECEIPT_ROOT: &[u8] = b"arknet-receipt-root-v1";
376
377// ─── Crypto scheme tags ───────────────────────────────────────────────────
378
379/// Signature scheme identifier. First byte of every encoded [`Signature`] / [`PubKey`].
380///
381/// Versioned from day one so a post-quantum migration is a protocol upgrade,
382/// not a wire-format rewrite. See `docs/SECURITY.md` §12.
383#[repr(u8)]
384#[derive(
385    Clone,
386    Copy,
387    PartialEq,
388    Eq,
389    Hash,
390    Debug,
391    BorshSerialize,
392    BorshDeserialize,
393    Serialize,
394    Deserialize,
395)]
396#[borsh(use_discriminant = true)]
397pub enum SignatureScheme {
398    /// Ed25519 (EdDSA over Curve25519). Genesis default.
399    Ed25519 = 0x01,
400    /// Reserved for ML-DSA / Dilithium (NIST FIPS 204). Post-quantum.
401    Dilithium = 0x02,
402    /// Reserved for Falcon (NIST FIPS 205 draft). Post-quantum, smaller sigs than Dilithium.
403    Falcon = 0x03,
404    /// Reserved for SLH-DSA / SPHINCS+ (NIST FIPS 205). Hash-based, stateless.
405    Sphincs = 0x04,
406    /// Reserved for hybrid Ed25519 + Dilithium (belt-and-braces during migration).
407    HybridEd25519Dilithium = 0x05,
408}
409
410impl SignatureScheme {
411    /// Schemes currently implemented and accepted by consensus.
412    pub const fn is_active(&self) -> bool {
413        matches!(self, SignatureScheme::Ed25519)
414    }
415
416    /// Expected public-key length in bytes for this scheme.
417    pub const fn pubkey_len(&self) -> usize {
418        match self {
419            SignatureScheme::Ed25519 => 32,
420            SignatureScheme::Dilithium => 1312,
421            SignatureScheme::Falcon => 897,
422            SignatureScheme::Sphincs => 32,
423            SignatureScheme::HybridEd25519Dilithium => 32 + 1312,
424        }
425    }
426
427    /// Expected signature length in bytes for this scheme.
428    pub const fn signature_len(&self) -> usize {
429        match self {
430            SignatureScheme::Ed25519 => 64,
431            SignatureScheme::Dilithium => 2420,
432            SignatureScheme::Falcon => 666,
433            SignatureScheme::Sphincs => 17088,
434            SignatureScheme::HybridEd25519Dilithium => 64 + 2420,
435        }
436    }
437}
438
439/// Key-encapsulation-mechanism (KEM) identifier. Used for prompt encryption.
440#[repr(u8)]
441#[derive(
442    Clone,
443    Copy,
444    PartialEq,
445    Eq,
446    Hash,
447    Debug,
448    BorshSerialize,
449    BorshDeserialize,
450    Serialize,
451    Deserialize,
452)]
453#[borsh(use_discriminant = true)]
454pub enum KemScheme {
455    /// X25519 elliptic-curve Diffie-Hellman. Genesis default.
456    X25519 = 0x01,
457    /// Reserved for ML-KEM / Kyber (NIST FIPS 203). Post-quantum.
458    Kyber = 0x02,
459    /// Reserved for hybrid X25519 + Kyber during migration.
460    HybridX25519Kyber = 0x03,
461}
462
463impl KemScheme {
464    /// Schemes currently implemented.
465    pub const fn is_active(&self) -> bool {
466        matches!(self, KemScheme::X25519)
467    }
468}
469
470/// Verifiable Random Function scheme — used for unpredictable verifier selection.
471#[repr(u8)]
472#[derive(
473    Clone,
474    Copy,
475    PartialEq,
476    Eq,
477    Hash,
478    Debug,
479    BorshSerialize,
480    BorshDeserialize,
481    Serialize,
482    Deserialize,
483)]
484#[borsh(use_discriminant = true)]
485pub enum VrfScheme {
486    /// ECVRF over Ristretto255. Genesis default.
487    Ristretto255 = 0x01,
488    /// Reserved for a future lattice-based VRF construction.
489    LatticeVrf = 0x02,
490}
491
492impl VrfScheme {
493    /// Schemes currently implemented.
494    pub const fn is_active(&self) -> bool {
495        matches!(self, VrfScheme::Ristretto255)
496    }
497}
498
499// ─── Versioned public keys & signatures ───────────────────────────────────
500
501/// A scheme-tagged public key.
502///
503/// On-chain encoding is `scheme_byte || key_bytes`. Length of `key_bytes`
504/// is determined by [`SignatureScheme::pubkey_len`].
505#[derive(
506    Clone, PartialEq, Eq, Hash, Debug, BorshSerialize, BorshDeserialize, Serialize, Deserialize,
507)]
508pub struct PubKey {
509    /// Scheme identifier.
510    pub scheme: SignatureScheme,
511    /// Raw key bytes (length must match `scheme.pubkey_len()`).
512    pub bytes: Vec<u8>,
513}
514
515impl PubKey {
516    /// Construct a new public key. Returns an error if `bytes.len()` doesn't match the scheme.
517    pub fn new(scheme: SignatureScheme, bytes: Vec<u8>) -> Result<Self> {
518        if bytes.len() != scheme.pubkey_len() {
519            return Err(CommonError::InvalidArgument(format!(
520                "pubkey length for {:?} must be {}, got {}",
521                scheme,
522                scheme.pubkey_len(),
523                bytes.len()
524            )));
525        }
526        Ok(Self { scheme, bytes })
527    }
528
529    /// Construct an Ed25519 pubkey from a fixed-size array.
530    pub fn ed25519(bytes: [u8; 32]) -> Self {
531        Self {
532            scheme: SignatureScheme::Ed25519,
533            bytes: bytes.to_vec(),
534        }
535    }
536}
537
538/// A scheme-tagged signature.
539///
540/// On-chain encoding is `scheme_byte || sig_bytes`.
541#[derive(
542    Clone, PartialEq, Eq, Hash, Debug, BorshSerialize, BorshDeserialize, Serialize, Deserialize,
543)]
544pub struct Signature {
545    /// Scheme identifier.
546    pub scheme: SignatureScheme,
547    /// Raw signature bytes (length must match `scheme.signature_len()`).
548    pub bytes: Vec<u8>,
549}
550
551impl Signature {
552    /// Construct a new signature. Returns an error if `bytes.len()` doesn't match the scheme.
553    pub fn new(scheme: SignatureScheme, bytes: Vec<u8>) -> Result<Self> {
554        if bytes.len() != scheme.signature_len() {
555            return Err(CommonError::InvalidArgument(format!(
556                "signature length for {:?} must be {}, got {}",
557                scheme,
558                scheme.signature_len(),
559                bytes.len()
560            )));
561        }
562        Ok(Self { scheme, bytes })
563    }
564
565    /// Construct an Ed25519 signature from a fixed-size array.
566    pub fn ed25519(bytes: [u8; 64]) -> Self {
567        Self {
568            scheme: SignatureScheme::Ed25519,
569            bytes: bytes.to_vec(),
570        }
571    }
572}
573
574// ─── TEE platform + capability ───────────────────────────────────────
575
576/// Trusted Execution Environment platform identifier.
577///
578/// The chain records which TEE platform a compute node is attested on
579/// so verifiers know which root-of-trust CA to check the quote against.
580#[repr(u8)]
581#[derive(
582    Clone,
583    Copy,
584    PartialEq,
585    Eq,
586    Hash,
587    Debug,
588    BorshSerialize,
589    BorshDeserialize,
590    Serialize,
591    Deserialize,
592)]
593#[borsh(use_discriminant = true)]
594pub enum TeePlatform {
595    /// Intel Trust Domain Extensions (TDX). Available on 4th-gen Xeon+.
596    IntelTdx = 0x01,
597    /// AMD Secure Encrypted Virtualization — Secure Nested Paging.
598    AmdSevSnp = 0x02,
599    /// ARM Confidential Compute Architecture (CCA). Reserved.
600    ArmCca = 0x03,
601}
602
603/// On-chain TEE capability record for a compute node.
604///
605/// Submitted via `Transaction::RegisterTeeCapability`. The `quote`
606/// bytes are platform-specific attestation evidence; at genesis the
607/// chain validates structural well-formedness (non-empty, bounded
608/// size). Full cryptographic verification against Intel/AMD root CAs
609/// is activated by governance once the verification library is audited.
610///
611/// `enclave_pubkey` is generated *inside* the enclave and bound to the
612/// attestation quote. Users encrypt prompts to this key — the host OS
613/// never sees plaintext.
614#[derive(
615    Clone, PartialEq, Eq, Hash, Debug, BorshSerialize, BorshDeserialize, Serialize, Deserialize,
616)]
617pub struct TeeCapability {
618    /// Which TEE platform produced the attestation.
619    pub platform: TeePlatform,
620    /// Raw attestation quote bytes (Intel TDX report / AMD SNP attestation).
621    pub quote: Vec<u8>,
622    /// Public key generated inside the enclave, bound to the quote.
623    /// Users encrypt prompts to this key for confidential inference.
624    pub enclave_pubkey: PubKey,
625}
626
627/// Maximum size of a TEE attestation quote (16 KiB). Intel TDX quotes
628/// are typically ~5 KB; AMD SEV-SNP reports ~4 KB. This cap prevents
629/// abuse while leaving room for certificate chains.
630pub const MAX_TEE_QUOTE_BYTES: usize = 16 * 1024;
631
632// ─── Role bitmap ──────────────────────────────────────────────────────────
633
634/// Bitmap of active roles on a node. Multiple roles can be enabled simultaneously.
635#[derive(
636    Clone,
637    Copy,
638    PartialEq,
639    Eq,
640    Hash,
641    Debug,
642    Default,
643    BorshSerialize,
644    BorshDeserialize,
645    Serialize,
646    Deserialize,
647)]
648pub struct RoleBitmap(pub u8);
649
650impl RoleBitmap {
651    /// Empty bitmap (no roles enabled).
652    pub const NONE: RoleBitmap = RoleBitmap(0);
653    /// L1 validator role.
654    pub const VALIDATOR: RoleBitmap = RoleBitmap(0b0001);
655    /// L2 router role.
656    pub const ROUTER: RoleBitmap = RoleBitmap(0b0010);
657    /// L2 compute role.
658    pub const COMPUTE: RoleBitmap = RoleBitmap(0b0100);
659    /// L2 verifier role.
660    pub const VERIFIER: RoleBitmap = RoleBitmap(0b1000);
661
662    /// `true` if the given role is enabled.
663    pub const fn has(&self, other: RoleBitmap) -> bool {
664        (self.0 & other.0) == other.0
665    }
666
667    /// Set a role bit (returns a new bitmap).
668    pub const fn with(self, other: RoleBitmap) -> Self {
669        Self(self.0 | other.0)
670    }
671
672    /// Clear a role bit (returns a new bitmap).
673    pub const fn without(self, other: RoleBitmap) -> Self {
674        Self(self.0 & !other.0)
675    }
676
677    /// `true` if no roles are enabled.
678    pub const fn is_empty(&self) -> bool {
679        self.0 == 0
680    }
681}
682
683// ─── Tests ────────────────────────────────────────────────────────────────
684
685#[cfg(test)]
686mod tests {
687    use super::*;
688
689    #[test]
690    fn atoms_per_ark_is_billion() {
691        assert_eq!(ATOMS_PER_ARK, 1_000_000_000);
692    }
693
694    #[test]
695    fn supply_cap_is_1b_ark() {
696        assert_eq!(ARK_SUPPLY_CAP, 1_000_000_000_000_000_000u128);
697    }
698
699    #[test]
700    fn address_hex_roundtrip() {
701        let a = Address::new([0x42; 20]);
702        let s = a.to_hex();
703        assert_eq!(s.len(), 40);
704        let b = Address::from_hex(&s).unwrap();
705        assert_eq!(a, b);
706    }
707
708    #[test]
709    fn address_hex_accepts_0x_prefix() {
710        let raw = "0x4242424242424242424242424242424242424242";
711        let a = Address::from_hex(raw).unwrap();
712        assert_eq!(a.0, [0x42; 20]);
713    }
714
715    #[test]
716    fn address_from_hex_rejects_wrong_length() {
717        assert!(Address::from_hex("abcd").is_err());
718    }
719
720    #[test]
721    fn signature_scheme_lengths_match_specs() {
722        // Values here are the authoritative spec — do not change without architecture review.
723        assert_eq!(SignatureScheme::Ed25519.pubkey_len(), 32);
724        assert_eq!(SignatureScheme::Ed25519.signature_len(), 64);
725        assert_eq!(SignatureScheme::Dilithium.pubkey_len(), 1312);
726        assert_eq!(SignatureScheme::Dilithium.signature_len(), 2420);
727        assert_eq!(SignatureScheme::Falcon.pubkey_len(), 897);
728        assert_eq!(SignatureScheme::Falcon.signature_len(), 666);
729    }
730
731    #[test]
732    fn only_ed25519_active_at_genesis() {
733        assert!(SignatureScheme::Ed25519.is_active());
734        assert!(!SignatureScheme::Dilithium.is_active());
735        assert!(!SignatureScheme::Falcon.is_active());
736        assert!(!SignatureScheme::Sphincs.is_active());
737        assert!(!SignatureScheme::HybridEd25519Dilithium.is_active());
738    }
739
740    #[test]
741    fn only_x25519_kem_active_at_genesis() {
742        assert!(KemScheme::X25519.is_active());
743        assert!(!KemScheme::Kyber.is_active());
744    }
745
746    #[test]
747    fn only_ristretto_vrf_active_at_genesis() {
748        assert!(VrfScheme::Ristretto255.is_active());
749        assert!(!VrfScheme::LatticeVrf.is_active());
750    }
751
752    #[test]
753    fn pubkey_constructor_enforces_length() {
754        assert!(PubKey::new(SignatureScheme::Ed25519, vec![0; 32]).is_ok());
755        assert!(PubKey::new(SignatureScheme::Ed25519, vec![0; 31]).is_err());
756        assert!(PubKey::new(SignatureScheme::Ed25519, vec![0; 33]).is_err());
757    }
758
759    #[test]
760    fn signature_constructor_enforces_length() {
761        assert!(Signature::new(SignatureScheme::Ed25519, vec![0; 64]).is_ok());
762        assert!(Signature::new(SignatureScheme::Ed25519, vec![0; 63]).is_err());
763    }
764
765    #[test]
766    fn ed25519_constructors_produce_valid_values() {
767        let pk = PubKey::ed25519([0xaa; 32]);
768        assert_eq!(pk.scheme, SignatureScheme::Ed25519);
769        assert_eq!(pk.bytes.len(), 32);
770
771        let sig = Signature::ed25519([0xbb; 64]);
772        assert_eq!(sig.scheme, SignatureScheme::Ed25519);
773        assert_eq!(sig.bytes.len(), 64);
774    }
775
776    #[test]
777    fn role_bitmap_operations() {
778        let mut roles = RoleBitmap::NONE;
779        assert!(roles.is_empty());
780
781        roles = roles.with(RoleBitmap::ROUTER).with(RoleBitmap::COMPUTE);
782        assert!(roles.has(RoleBitmap::ROUTER));
783        assert!(roles.has(RoleBitmap::COMPUTE));
784        assert!(!roles.has(RoleBitmap::VALIDATOR));
785        assert!(!roles.has(RoleBitmap::VERIFIER));
786        assert!(!roles.is_empty());
787
788        let without_router = roles.without(RoleBitmap::ROUTER);
789        assert!(!without_router.has(RoleBitmap::ROUTER));
790        assert!(without_router.has(RoleBitmap::COMPUTE));
791    }
792
793    #[test]
794    fn borsh_roundtrip_pubkey() {
795        let pk = PubKey::ed25519([0x11; 32]);
796        let bytes = borsh::to_vec(&pk).unwrap();
797        let decoded: PubKey = borsh::from_slice(&bytes).unwrap();
798        assert_eq!(pk, decoded);
799    }
800
801    #[test]
802    fn borsh_roundtrip_signature() {
803        let sig = Signature::ed25519([0x22; 64]);
804        let bytes = borsh::to_vec(&sig).unwrap();
805        let decoded: Signature = borsh::from_slice(&bytes).unwrap();
806        assert_eq!(sig, decoded);
807    }
808
809    #[test]
810    fn borsh_roundtrip_address() {
811        let a = Address::new([0x33; 20]);
812        let bytes = borsh::to_vec(&a).unwrap();
813        let decoded: Address = borsh::from_slice(&bytes).unwrap();
814        assert_eq!(a, decoded);
815    }
816
817    #[test]
818    fn borsh_roundtrip_tx_hash() {
819        let h = TxHash::new([0x44; 32]);
820        let bytes = borsh::to_vec(&h).unwrap();
821        let decoded: TxHash = borsh::from_slice(&bytes).unwrap();
822        assert_eq!(h, decoded);
823    }
824
825    #[test]
826    fn borsh_roundtrip_block_hash() {
827        let h = BlockHash::new([0x55; 32]);
828        let bytes = borsh::to_vec(&h).unwrap();
829        let decoded: BlockHash = borsh::from_slice(&bytes).unwrap();
830        assert_eq!(h, decoded);
831    }
832
833    #[test]
834    fn borsh_roundtrip_state_root() {
835        let r = StateRoot::new([0x66; 32]);
836        let bytes = borsh::to_vec(&r).unwrap();
837        let decoded: StateRoot = borsh::from_slice(&bytes).unwrap();
838        assert_eq!(r, decoded);
839    }
840
841    #[test]
842    fn borsh_roundtrip_app_hash() {
843        let h = AppHash::new([0x77; 32]);
844        let bytes = borsh::to_vec(&h).unwrap();
845        let decoded: AppHash = borsh::from_slice(&bytes).unwrap();
846        assert_eq!(h, decoded);
847    }
848
849    #[test]
850    fn hash_domain_tags_are_distinct() {
851        // Each pair must differ — if two domains collide, cross-type hashes
852        // could clash intentionally or by accident.
853        assert_ne!(DOMAIN_TX, DOMAIN_BLOCK);
854        assert_ne!(DOMAIN_TX, DOMAIN_TX_ROOT);
855        assert_ne!(DOMAIN_TX, DOMAIN_RECEIPT_ROOT);
856        assert_ne!(DOMAIN_BLOCK, DOMAIN_TX_ROOT);
857        assert_ne!(DOMAIN_BLOCK, DOMAIN_RECEIPT_ROOT);
858        assert_ne!(DOMAIN_TX_ROOT, DOMAIN_RECEIPT_ROOT);
859    }
860
861    #[test]
862    fn tee_platform_discriminants_are_stable() {
863        assert_eq!(TeePlatform::IntelTdx as u8, 0x01);
864        assert_eq!(TeePlatform::AmdSevSnp as u8, 0x02);
865        assert_eq!(TeePlatform::ArmCca as u8, 0x03);
866    }
867
868    #[test]
869    fn tee_capability_borsh_roundtrip() {
870        let cap = TeeCapability {
871            platform: TeePlatform::IntelTdx,
872            quote: vec![0xde, 0xad, 0xbe, 0xef],
873            enclave_pubkey: PubKey::ed25519([0x99; 32]),
874        };
875        let bytes = borsh::to_vec(&cap).unwrap();
876        let decoded: TeeCapability = borsh::from_slice(&bytes).unwrap();
877        assert_eq!(cap, decoded);
878    }
879
880    #[test]
881    fn tx_and_block_hash_newtypes_are_distinct_from_byte_arrays() {
882        // This is a type-level check that the newtypes compile apart — if the
883        // function accepted either, this would fail to compile.
884        fn take_tx(_: TxHash) {}
885        fn take_block(_: BlockHash) {}
886
887        take_tx(TxHash::new([0; 32]));
888        take_block(BlockHash::new([0; 32]));
889    }
890}