Skip to main content

tenzro_types/
primitives.rs

1//! Core primitive types for Tenzro Network
2//!
3//! This module defines the fundamental building blocks used throughout
4//! the Tenzro Network blockchain.
5
6use serde::{Deserialize, Serialize};
7use std::fmt;
8
9/// A 32-byte hash value used throughout Tenzro Network
10///
11/// Hashes are used for block hashes, transaction hashes, Merkle roots,
12/// and other cryptographic commitments.
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
14pub struct Hash(pub [u8; 32]);
15
16impl Hash {
17    /// Creates a new Hash from a 32-byte array
18    pub fn new(bytes: [u8; 32]) -> Self {
19        Self(bytes)
20    }
21
22    /// Returns the hash as a byte slice
23    pub fn as_bytes(&self) -> &[u8] {
24        &self.0
25    }
26
27    /// Creates a Hash from a byte slice
28    ///
29    /// Returns None if the slice is not exactly 32 bytes
30    pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
31        if bytes.len() == 32 {
32            let mut arr = [0u8; 32];
33            arr.copy_from_slice(bytes);
34            Some(Self(arr))
35        } else {
36            None
37        }
38    }
39
40    /// Returns a zero hash (all zeros)
41    pub fn zero() -> Self {
42        Self([0u8; 32])
43    }
44
45    /// Combines two hashes using SHA-256
46    ///
47    /// This method is collision-resistant, unlike XOR-based combination.
48    /// It concatenates the two hashes and returns the SHA-256 hash of the result.
49    pub fn combine(&self, other: &Hash) -> Self {
50        use sha2::{Digest, Sha256};
51        let mut hasher = Sha256::new();
52        hasher.update(self.0);
53        hasher.update(other.0);
54        let result = hasher.finalize();
55        Self(result.into())
56    }
57}
58
59impl fmt::Display for Hash {
60    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61        write!(f, "{}", hex::encode(self.0))
62    }
63}
64
65impl From<[u8; 32]> for Hash {
66    fn from(bytes: [u8; 32]) -> Self {
67        Self(bytes)
68    }
69}
70
71impl Default for Hash {
72    fn default() -> Self {
73        Self::zero()
74    }
75}
76
77/// An address on Tenzro Network
78///
79/// Addresses are derived from public keys and identify accounts,
80/// smart contracts, and other entities on the network.
81#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
82pub struct Address(pub [u8; 32]);
83
84impl Address {
85    /// Creates a new Address from a 32-byte array
86    pub fn new(bytes: [u8; 32]) -> Self {
87        Self(bytes)
88    }
89
90    /// Returns the address as a byte slice
91    pub fn as_bytes(&self) -> &[u8] {
92        &self.0
93    }
94
95    /// Creates an Address from a byte slice
96    ///
97    /// Returns None if the slice is not exactly 32 bytes
98    pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
99        if bytes.len() == 32 {
100            let mut arr = [0u8; 32];
101            arr.copy_from_slice(bytes);
102            Some(Self(arr))
103        } else {
104            None
105        }
106    }
107
108    /// Returns a zero address (all zeros)
109    pub fn zero() -> Self {
110        Self([0u8; 32])
111    }
112
113    /// Encodes the address as a base58 string
114    pub fn to_base58(&self) -> String {
115        bs58::encode(&self.0).into_string()
116    }
117
118    /// Decodes an address from a base58 string
119    pub fn from_base58(s: &str) -> Result<Self, bs58::decode::Error> {
120        let bytes = bs58::decode(s).into_vec()?;
121        Self::from_bytes(&bytes).ok_or(bs58::decode::Error::BufferTooSmall)
122    }
123
124    /// Validates that a hex string is properly formatted
125    ///
126    /// Returns true if the string is valid hexadecimal (with or without 0x prefix)
127    pub fn is_valid_hex(s: &str) -> bool {
128        let s = s.strip_prefix("0x").unwrap_or(s);
129        s.len() == 64 && s.chars().all(|c| c.is_ascii_hexdigit())
130    }
131
132    /// Creates an address from a hex string.
133    ///
134    /// For 20-byte Ethereum addresses (40 hex chars), validates EIP-55 checksum.
135    /// For 32-byte Tenzro addresses (64 hex chars), validates hex format.
136    /// Use this as the default entry point for hex address parsing.
137    pub fn from_hex(s: &str) -> Result<Self, crate::error::TenzroError> {
138        Self::from_hex_checksummed(s)
139    }
140
141    /// Creates an address from a hex string with EIP-55 checksum validation
142    ///
143    /// For Ethereum-style addresses (20 bytes), this validates the EIP-55 mixed-case
144    /// checksum. The hex string should be 40 characters (or 42 with 0x prefix).
145    pub fn from_hex_checksummed(s: &str) -> Result<Self, crate::error::TenzroError> {
146        let s = s.strip_prefix("0x").unwrap_or(s);
147
148        // For 20-byte Ethereum addresses, validate EIP-55 checksum
149        if s.len() == 40 {
150            // Decode the hex
151            let bytes = hex::decode(s).map_err(|e| crate::error::TenzroError::InvalidAddress(format!("Invalid hex: {}", e)))?;
152
153            // Compute Keccak-256 hash of the lowercase address
154            use sha3::{Digest, Keccak256};
155            let mut hasher = Keccak256::new();
156            hasher.update(s.to_lowercase().as_bytes());
157            let hash = hasher.finalize();
158
159            // Verify checksum (mixed case encoding)
160            for (i, c) in s.chars().enumerate() {
161                if c.is_alphabetic() {
162                    let hash_byte = hash[i / 2];
163                    let hash_nibble = if i % 2 == 0 {
164                        hash_byte >> 4
165                    } else {
166                        hash_byte & 0x0f
167                    };
168
169                    let should_be_uppercase = hash_nibble >= 8;
170                    if c.is_uppercase() != should_be_uppercase {
171                        return Err(crate::error::TenzroError::InvalidAddress("Invalid EIP-55 checksum".to_string()));
172                    }
173                }
174            }
175
176            // Pad to 32 bytes for our Address type
177            let mut addr = [0u8; 32];
178            addr[12..].copy_from_slice(&bytes);
179            Ok(Self(addr))
180        } else if s.len() == 64 {
181            // 32-byte address (full Tenzro address)
182            let bytes = hex::decode(s).map_err(|e| crate::error::TenzroError::InvalidAddress(format!("Invalid hex: {}", e)))?;
183            let mut addr = [0u8; 32];
184            addr.copy_from_slice(&bytes);
185            Ok(Self(addr))
186        } else {
187            Err(crate::error::TenzroError::InvalidAddress(format!("Invalid address length: expected 40 or 64 hex chars, got {}", s.len())))
188        }
189    }
190}
191
192impl fmt::Display for Address {
193    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
194        write!(f, "{}", self.to_base58())
195    }
196}
197
198impl From<[u8; 32]> for Address {
199    fn from(bytes: [u8; 32]) -> Self {
200        Self(bytes)
201    }
202}
203
204impl Default for Address {
205    fn default() -> Self {
206        Self::zero()
207    }
208}
209
210/// A cryptographic signature on Tenzro Network
211///
212/// Signatures are used to prove ownership and authorize transactions.
213///
214/// Both `bytes` and `public_key` are bounded at deserialization time to
215/// `MAX_SIGNATURE_BYTES` and `MAX_PUBLIC_KEY_BYTES` respectively, to protect
216/// against OOM via untrusted payloads (HIGH #69 in the production audit).
217#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
218#[derive(Default)]
219pub struct Signature {
220    /// The signature bytes
221    #[serde(deserialize_with = "crate::validation::bounded_signature_bytes")]
222    pub bytes: Vec<u8>,
223    /// The public key used to verify the signature
224    #[serde(deserialize_with = "crate::validation::bounded_public_key_bytes")]
225    pub public_key: Vec<u8>,
226}
227
228impl Signature {
229    /// Creates a new Signature
230    pub fn new(bytes: Vec<u8>, public_key: Vec<u8>) -> Self {
231        Self { bytes, public_key }
232    }
233}
234
235
236/// The height of a block in the Tenzro Network blockchain
237///
238/// Block height starts at 0 for the genesis block and increments by 1 for each block.
239#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
240pub struct BlockHeight(pub u64);
241
242impl BlockHeight {
243    /// Creates a new BlockHeight
244    pub fn new(height: u64) -> Self {
245        Self(height)
246    }
247
248    /// Returns the genesis block height (0)
249    pub fn genesis() -> Self {
250        Self(0)
251    }
252
253    /// Returns the next block height
254    pub fn next(self) -> Self {
255        Self(self.0 + 1)
256    }
257
258    /// Returns the previous block height, or None if this is the genesis block
259    pub fn prev(self) -> Option<Self> {
260        if self.0 > 0 {
261            Some(Self(self.0 - 1))
262        } else {
263            None
264        }
265    }
266}
267
268impl fmt::Display for BlockHeight {
269    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
270        write!(f, "{}", self.0)
271    }
272}
273
274impl From<u64> for BlockHeight {
275    fn from(height: u64) -> Self {
276        Self(height)
277    }
278}
279
280impl Default for BlockHeight {
281    fn default() -> Self {
282        Self::genesis()
283    }
284}
285
286impl BlockHeight {
287    /// Returns the height as a u64
288    pub fn as_u64(&self) -> u64 {
289        self.0
290    }
291}
292
293impl std::ops::Add<u64> for BlockHeight {
294    type Output = Self;
295
296    fn add(self, rhs: u64) -> Self::Output {
297        Self(self.0 + rhs)
298    }
299}
300
301impl std::ops::Sub<u64> for BlockHeight {
302    type Output = Self;
303
304    fn sub(self, rhs: u64) -> Self::Output {
305        Self(self.0.saturating_sub(rhs))
306    }
307}
308
309/// A transaction nonce for replay protection
310///
311/// The nonce ensures that each transaction from an account is unique
312/// and prevents replay attacks.
313#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
314pub struct Nonce(pub u64);
315
316impl Nonce {
317    /// Creates a new Nonce
318    pub fn new(value: u64) -> Self {
319        Self(value)
320    }
321
322    /// Returns the initial nonce (0)
323    pub fn initial() -> Self {
324        Self(0)
325    }
326
327    /// Returns the next nonce
328    pub fn next(self) -> Self {
329        Self(self.0 + 1)
330    }
331}
332
333impl fmt::Display for Nonce {
334    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
335        write!(f, "{}", self.0)
336    }
337}
338
339impl From<u64> for Nonce {
340    fn from(value: u64) -> Self {
341        Self(value)
342    }
343}
344
345/// A timestamp representing Unix time in milliseconds
346///
347/// Used for block timestamps and time-based operations.
348#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
349#[derive(Default)]
350pub struct Timestamp(pub i64);
351
352
353impl Timestamp {
354    /// Creates a new Timestamp
355    pub fn new(millis: i64) -> Self {
356        Self(millis)
357    }
358
359    /// Returns the current timestamp
360    pub fn now() -> Self {
361        Self(chrono::Utc::now().timestamp_millis())
362    }
363
364    /// Returns the timestamp as milliseconds since Unix epoch
365    pub fn as_millis(&self) -> i64 {
366        self.0
367    }
368
369    /// Returns the timestamp as seconds since Unix epoch
370    pub fn as_secs(&self) -> i64 {
371        self.0 / 1000
372    }
373}
374
375impl From<Timestamp> for std::time::SystemTime {
376    fn from(ts: Timestamp) -> Self {
377        std::time::UNIX_EPOCH + std::time::Duration::from_millis(ts.0 as u64)
378    }
379}
380
381impl fmt::Display for Timestamp {
382    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
383        write!(f, "{}", self.0)
384    }
385}
386
387impl From<i64> for Timestamp {
388    fn from(millis: i64) -> Self {
389        Self(millis)
390    }
391}
392
393/// The chain identifier for Tenzro Network
394///
395/// Used to prevent cross-chain replay attacks and identify the network.
396///
397/// Deserialization enforces the EIP-2294 range `[1, (u64::MAX / 2) - 36]`
398/// to reject obviously-bad chain ids early (HIGH #70 in the production audit).
399#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
400pub struct ChainId(pub u64);
401
402impl<'de> Deserialize<'de> for ChainId {
403    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
404    where
405        D: serde::Deserializer<'de>,
406    {
407        let value = u64::deserialize(deserializer)?;
408        let chain = ChainId(value);
409        crate::validation::validate_chain_id(chain).map_err(serde::de::Error::custom)?;
410        Ok(chain)
411    }
412}
413
414impl ChainId {
415    /// Mainnet chain ID
416    pub const MAINNET: Self = Self(1);
417
418    /// Testnet chain ID
419    pub const TESTNET: Self = Self(1337);
420
421    /// Devnet chain ID
422    pub const DEVNET: Self = Self(31337);
423
424    /// Creates a new ChainId without validation.
425    ///
426    /// Prefer [`ChainId::try_new`] when constructing from untrusted input.
427    pub fn new(id: u64) -> Self {
428        Self(id)
429    }
430
431    /// Creates a new ChainId, returning an error if `id` is outside the
432    /// allowed range `[CHAIN_ID_MIN, CHAIN_ID_MAX]`.
433    ///
434    /// The upper bound (`(u64::MAX / 2) - 36`) follows EIP-2294 to avoid
435    /// overflow when combined with EIP-155 transaction signing.
436    pub fn try_new(id: u64) -> Result<Self, crate::error::TenzroError> {
437        let chain = Self(id);
438        crate::validation::validate_chain_id(chain)?;
439        Ok(chain)
440    }
441
442    /// Returns the mainnet chain ID
443    pub fn mainnet() -> Self {
444        Self::MAINNET
445    }
446
447    /// Returns the testnet chain ID
448    pub fn testnet() -> Self {
449        Self::TESTNET
450    }
451
452    /// Returns the devnet chain ID
453    pub fn devnet() -> Self {
454        Self::DEVNET
455    }
456
457    /// Returns the inner u64 chain id.
458    pub fn as_u64(&self) -> u64 {
459        self.0
460    }
461
462    /// Validates that the chain ID is within the allowed range.
463    ///
464    /// Allowed range follows EIP-2294: `[1, (u64::MAX / 2) - 36]`.
465    pub fn is_valid(&self) -> bool {
466        crate::validation::validate_chain_id(*self).is_ok()
467    }
468}
469
470impl fmt::Display for ChainId {
471    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
472        write!(f, "{}", self.0)
473    }
474}
475
476impl From<u64> for ChainId {
477    fn from(id: u64) -> Self {
478        Self(id)
479    }
480}
481
482/// Serde adapter for `u128` fields that may carry values larger than
483/// `u64::MAX` (e.g. base-unit token amounts at 10^18 precision).
484///
485/// `serde_json`'s default number deserializer falls through `u64 → i64 →
486/// f64` and refuses values exceeding `u64::MAX` (~1.84×10^19) — which
487/// breaks reference templates whose delegation caps are denominated in
488/// 10^18-base-unit TNZO (e.g. `5_000_000_000_000_000_000_000`).
489///
490/// This adapter accepts both:
491///   - JSON numbers (any integer up to `u128::MAX` that fits in a
492///     `serde_json::Number`)
493///   - JSON strings (decimal digits only, no sign, no underscores)
494///
495/// On serialization it emits a number when the value fits `u64` and a
496/// decimal string otherwise, preserving readability for typical small
497/// values while remaining lossless for large ones.
498///
499/// Apply via `#[serde(with = "u128_serde")]` on a `u128` field.
500pub mod u128_serde {
501    use serde::de::{self, Visitor};
502    use serde::{Deserializer, Serializer};
503    use std::fmt;
504
505    pub fn serialize<S: Serializer>(value: &u128, serializer: S) -> Result<S::Ok, S::Error> {
506        if *value <= u64::MAX as u128 {
507            serializer.serialize_u64(*value as u64)
508        } else {
509            serializer.serialize_str(&value.to_string())
510        }
511    }
512
513    pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<u128, D::Error> {
514        struct U128Visitor;
515
516        impl<'de> Visitor<'de> for U128Visitor {
517            type Value = u128;
518
519            fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
520                f.write_str("a non-negative integer or decimal string fitting in u128")
521            }
522
523            fn visit_u64<E: de::Error>(self, v: u64) -> Result<u128, E> {
524                Ok(v as u128)
525            }
526
527            fn visit_u128<E: de::Error>(self, v: u128) -> Result<u128, E> {
528                Ok(v)
529            }
530
531            fn visit_i64<E: de::Error>(self, v: i64) -> Result<u128, E> {
532                if v < 0 {
533                    Err(E::custom(format!("negative integer {v} is not a valid u128")))
534                } else {
535                    Ok(v as u128)
536                }
537            }
538
539            fn visit_i128<E: de::Error>(self, v: i128) -> Result<u128, E> {
540                if v < 0 {
541                    Err(E::custom(format!("negative integer {v} is not a valid u128")))
542                } else {
543                    Ok(v as u128)
544                }
545            }
546
547            fn visit_str<E: de::Error>(self, s: &str) -> Result<u128, E> {
548                s.parse::<u128>()
549                    .map_err(|e| E::custom(format!("invalid u128 string {s:?}: {e}")))
550            }
551
552            fn visit_string<E: de::Error>(self, s: String) -> Result<u128, E> {
553                self.visit_str(&s)
554            }
555        }
556
557        deserializer.deserialize_any(U128Visitor)
558    }
559}
560
561/// `Option<u128>` adapter mirroring [`u128_serde`] semantics. Apply via
562/// `#[serde(default, with = "u128_serde_opt")]` on an `Option<u128>` field.
563///
564/// Accepts JSON `null`/missing → `None`, JSON number or decimal string → `Some`.
565/// Serializes `None` as `null`, `Some(v)` as a number when it fits `u64`,
566/// otherwise as a decimal string.
567pub mod u128_serde_opt {
568    use serde::de::{self, Visitor};
569    use serde::{Deserializer, Serializer};
570    use std::fmt;
571
572    pub fn serialize<S: Serializer>(
573        value: &Option<u128>,
574        serializer: S,
575    ) -> Result<S::Ok, S::Error> {
576        match value {
577            None => serializer.serialize_none(),
578            Some(v) if *v <= u64::MAX as u128 => serializer.serialize_u64(*v as u64),
579            Some(v) => serializer.serialize_str(&v.to_string()),
580        }
581    }
582
583    pub fn deserialize<'de, D: Deserializer<'de>>(
584        deserializer: D,
585    ) -> Result<Option<u128>, D::Error> {
586        struct OptU128Visitor;
587
588        impl<'de> Visitor<'de> for OptU128Visitor {
589            type Value = Option<u128>;
590
591            fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
592                f.write_str("null, a non-negative integer, or a decimal string fitting in u128")
593            }
594
595            fn visit_unit<E: de::Error>(self) -> Result<Option<u128>, E> {
596                Ok(None)
597            }
598
599            fn visit_none<E: de::Error>(self) -> Result<Option<u128>, E> {
600                Ok(None)
601            }
602
603            fn visit_some<D2: Deserializer<'de>>(
604                self,
605                deserializer: D2,
606            ) -> Result<Option<u128>, D2::Error> {
607                super::u128_serde::deserialize(deserializer).map(Some)
608            }
609
610            fn visit_u64<E: de::Error>(self, v: u64) -> Result<Option<u128>, E> {
611                Ok(Some(v as u128))
612            }
613
614            fn visit_u128<E: de::Error>(self, v: u128) -> Result<Option<u128>, E> {
615                Ok(Some(v))
616            }
617
618            fn visit_i64<E: de::Error>(self, v: i64) -> Result<Option<u128>, E> {
619                if v < 0 {
620                    Err(E::custom(format!("negative integer {v} is not a valid u128")))
621                } else {
622                    Ok(Some(v as u128))
623                }
624            }
625
626            fn visit_i128<E: de::Error>(self, v: i128) -> Result<Option<u128>, E> {
627                if v < 0 {
628                    Err(E::custom(format!("negative integer {v} is not a valid u128")))
629                } else {
630                    Ok(Some(v as u128))
631                }
632            }
633
634            fn visit_str<E: de::Error>(self, s: &str) -> Result<Option<u128>, E> {
635                s.parse::<u128>()
636                    .map(Some)
637                    .map_err(|e| E::custom(format!("invalid u128 string {s:?}: {e}")))
638            }
639
640            fn visit_string<E: de::Error>(self, s: String) -> Result<Option<u128>, E> {
641                self.visit_str(&s)
642            }
643        }
644
645        deserializer.deserialize_any(OptU128Visitor)
646    }
647}
648
649#[cfg(test)]
650mod u128_serde_tests {
651    use serde::{Deserialize, Serialize};
652
653    #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
654    struct Wrap {
655        #[serde(with = "super::u128_serde")]
656        v: u128,
657    }
658
659    #[test]
660    fn round_trips_small_value_as_number() {
661        let w = Wrap { v: 42 };
662        let json = serde_json::to_string(&w).unwrap();
663        assert_eq!(json, r#"{"v":42}"#);
664        let back: Wrap = serde_json::from_str(&json).unwrap();
665        assert_eq!(back, w);
666    }
667
668    #[test]
669    fn round_trips_large_value_as_string() {
670        let w = Wrap { v: 5_000_000_000_000_000_000_000u128 };
671        let json = serde_json::to_string(&w).unwrap();
672        assert_eq!(json, r#"{"v":"5000000000000000000000"}"#);
673        let back: Wrap = serde_json::from_str(&json).unwrap();
674        assert_eq!(back, w);
675    }
676
677    #[test]
678    fn deserializes_large_raw_number() {
679        // The whole point: accept the existing template JSON shape.
680        let json = r#"{"v":5000000000000000000000}"#;
681        let back: Wrap = serde_json::from_str(json).unwrap();
682        assert_eq!(back.v, 5_000_000_000_000_000_000_000u128);
683    }
684
685    #[test]
686    fn deserializes_string_at_u64_boundary() {
687        let json = r#"{"v":"18446744073709551616"}"#; // u64::MAX + 1
688        let back: Wrap = serde_json::from_str(json).unwrap();
689        assert_eq!(back.v, (u64::MAX as u128) + 1);
690    }
691
692    #[test]
693    fn rejects_negative_string() {
694        let json = r#"{"v":"-1"}"#;
695        assert!(serde_json::from_str::<Wrap>(json).is_err());
696    }
697
698    #[test]
699    fn rejects_negative_number() {
700        let json = r#"{"v":-1}"#;
701        assert!(serde_json::from_str::<Wrap>(json).is_err());
702    }
703}