1use borsh::{BorshDeserialize, BorshSerialize};
18use serde::{Deserialize, Serialize};
19
20use crate::errors::{CommonError, Result};
21
22pub type Hash256 = [u8; 32];
26
27#[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 pub const fn new(bytes: [u8; 20]) -> Self {
51 Self(bytes)
52 }
53
54 pub const fn as_bytes(&self) -> &[u8; 20] {
56 &self.0
57 }
58
59 pub fn to_hex(self) -> String {
61 hex::encode(self.0)
62 }
63
64 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#[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 pub const fn new(bytes: [u8; 32]) -> Self {
105 Self(bytes)
106 }
107
108 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#[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 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#[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 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#[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 pub const fn new(bytes: [u8; 32]) -> Self {
199 Self(bytes)
200 }
201}
202
203pub type Amount = u128;
209
210pub type Height = u64;
212
213pub type Timestamp = u64;
215
216pub type Nonce = u64;
219
220pub type Gas = u64;
222
223pub const ATOMS_PER_ARK: Amount = 1_000_000_000;
225
226pub const ARK_SUPPLY_CAP: Amount = 1_000_000_000 * ATOMS_PER_ARK;
228
229#[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 pub const fn new(bytes: Hash256) -> Self {
251 Self(bytes)
252 }
253
254 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#[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 pub const fn new(bytes: Hash256) -> Self {
287 Self(bytes)
288 }
289
290 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#[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 pub const fn new(bytes: Hash256) -> Self {
321 Self(bytes)
322 }
323
324 pub const fn as_bytes(&self) -> &Hash256 {
326 &self.0
327 }
328}
329
330#[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 pub const fn new(bytes: Hash256) -> Self {
352 Self(bytes)
353 }
354
355 pub const fn as_bytes(&self) -> &Hash256 {
357 &self.0
358 }
359}
360
361pub const DOMAIN_TX: &[u8] = b"arknet-tx-v1";
367
368pub const DOMAIN_BLOCK: &[u8] = b"arknet-block-v1";
370
371pub const DOMAIN_TX_ROOT: &[u8] = b"arknet-tx-root-v1";
373
374pub const DOMAIN_RECEIPT_ROOT: &[u8] = b"arknet-receipt-root-v1";
376
377#[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 = 0x01,
400 Dilithium = 0x02,
402 Falcon = 0x03,
404 Sphincs = 0x04,
406 HybridEd25519Dilithium = 0x05,
408}
409
410impl SignatureScheme {
411 pub const fn is_active(&self) -> bool {
413 matches!(self, SignatureScheme::Ed25519)
414 }
415
416 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 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#[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 = 0x01,
457 Kyber = 0x02,
459 HybridX25519Kyber = 0x03,
461}
462
463impl KemScheme {
464 pub const fn is_active(&self) -> bool {
466 matches!(self, KemScheme::X25519)
467 }
468}
469
470#[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 Ristretto255 = 0x01,
488 LatticeVrf = 0x02,
490}
491
492impl VrfScheme {
493 pub const fn is_active(&self) -> bool {
495 matches!(self, VrfScheme::Ristretto255)
496 }
497}
498
499#[derive(
506 Clone, PartialEq, Eq, Hash, Debug, BorshSerialize, BorshDeserialize, Serialize, Deserialize,
507)]
508pub struct PubKey {
509 pub scheme: SignatureScheme,
511 pub bytes: Vec<u8>,
513}
514
515impl PubKey {
516 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 pub fn ed25519(bytes: [u8; 32]) -> Self {
531 Self {
532 scheme: SignatureScheme::Ed25519,
533 bytes: bytes.to_vec(),
534 }
535 }
536}
537
538#[derive(
542 Clone, PartialEq, Eq, Hash, Debug, BorshSerialize, BorshDeserialize, Serialize, Deserialize,
543)]
544pub struct Signature {
545 pub scheme: SignatureScheme,
547 pub bytes: Vec<u8>,
549}
550
551impl Signature {
552 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 pub fn ed25519(bytes: [u8; 64]) -> Self {
567 Self {
568 scheme: SignatureScheme::Ed25519,
569 bytes: bytes.to_vec(),
570 }
571 }
572}
573
574#[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 IntelTdx = 0x01,
597 AmdSevSnp = 0x02,
599 ArmCca = 0x03,
601}
602
603#[derive(
615 Clone, PartialEq, Eq, Hash, Debug, BorshSerialize, BorshDeserialize, Serialize, Deserialize,
616)]
617pub struct TeeCapability {
618 pub platform: TeePlatform,
620 pub quote: Vec<u8>,
622 pub enclave_pubkey: PubKey,
625}
626
627pub const MAX_TEE_QUOTE_BYTES: usize = 16 * 1024;
631
632#[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 pub const NONE: RoleBitmap = RoleBitmap(0);
653 pub const VALIDATOR: RoleBitmap = RoleBitmap(0b0001);
655 pub const ROUTER: RoleBitmap = RoleBitmap(0b0010);
657 pub const COMPUTE: RoleBitmap = RoleBitmap(0b0100);
659 pub const VERIFIER: RoleBitmap = RoleBitmap(0b1000);
661
662 pub const fn has(&self, other: RoleBitmap) -> bool {
664 (self.0 & other.0) == other.0
665 }
666
667 pub const fn with(self, other: RoleBitmap) -> Self {
669 Self(self.0 | other.0)
670 }
671
672 pub const fn without(self, other: RoleBitmap) -> Self {
674 Self(self.0 & !other.0)
675 }
676
677 pub const fn is_empty(&self) -> bool {
679 self.0 == 0
680 }
681}
682
683#[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 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 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 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}