Skip to main content

host_encoding/
lib.rs

1//! Pure encoding/decoding functions for DOTNS and sp-statement-store.
2//!
3//! No I/O. No threads. WASM-safe.
4
5pub mod dotns;
6pub mod extrinsic;
7pub mod identity;
8pub mod statement_store;
9
10// ---------------------------------------------------------------------------
11// Substrate MultiSignature SCALE encoding
12// ---------------------------------------------------------------------------
13
14/// Crypto scheme selector for MultiSignature SCALE encoding.
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum CryptoScheme {
17    /// Ed25519 — SCALE variant byte 0x00
18    Ed25519,
19    /// Sr25519 — SCALE variant byte 0x01
20    Sr25519,
21    /// ECDSA (secp256k1) — SCALE variant byte 0x02
22    Ecdsa,
23}
24
25/// Wrap a raw signature in a Substrate `MultiSignature` SCALE envelope.
26///
27/// Substrate MultiSignature enum order:
28/// - Ed25519: variant 0x00, 64 bytes
29/// - Sr25519: variant 0x01, 64 bytes
30/// - Ecdsa:   variant 0x02, 65 bytes
31///
32/// Returns `Err` if `sig_bytes` has the wrong length for the chosen scheme.
33pub fn encode_multi_signature(sig_bytes: &[u8], scheme: CryptoScheme) -> Result<Vec<u8>, String> {
34    let (variant_byte, expected_len) = match scheme {
35        CryptoScheme::Ed25519 => (0x00u8, 64usize),
36        CryptoScheme::Sr25519 => (0x01u8, 64usize),
37        CryptoScheme::Ecdsa => (0x02u8, 65usize),
38    };
39
40    if sig_bytes.len() != expected_len {
41        return Err(format!(
42            "expected {} bytes for {:?} signature, got {}",
43            expected_len,
44            scheme,
45            sig_bytes.len(),
46        ));
47    }
48
49    let mut out = Vec::with_capacity(1 + expected_len);
50    out.push(variant_byte);
51    out.extend_from_slice(sig_bytes);
52    Ok(out)
53}
54
55/// Hex-encode bytes as a `0x`-prefixed lowercase string.
56pub fn hex_encode(bytes: &[u8]) -> String {
57    let mut s = String::with_capacity(2 + bytes.len() * 2);
58    s.push_str("0x");
59    for b in bytes {
60        s.push_str(&format!("{b:02x}"));
61    }
62    s
63}
64
65/// Decode a `0x`-prefixed (or bare) hex string into bytes.
66///
67/// Returns `None` if the string contains invalid hex characters or has an odd length.
68pub fn hex_decode(s: &str) -> Option<Vec<u8>> {
69    let s = s.strip_prefix("0x").unwrap_or(s);
70    if !s.len().is_multiple_of(2) {
71        return None;
72    }
73    let mut out = Vec::with_capacity(s.len() / 2);
74    for i in (0..s.len()).step_by(2) {
75        out.push(u8::from_str_radix(&s[i..i + 2], 16).ok()?);
76    }
77    Some(out)
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83
84    #[test]
85    fn test_sr25519_multi_signature_prefix_and_length() {
86        let sig = [0xabu8; 64];
87        let out = encode_multi_signature(&sig, CryptoScheme::Sr25519).unwrap();
88        assert_eq!(out.len(), 65);
89        assert_eq!(out[0], 0x01);
90        assert_eq!(&out[1..], sig.as_ref());
91    }
92
93    #[test]
94    fn test_ed25519_multi_signature_prefix_and_length() {
95        let sig = [0xabu8; 64];
96        let out = encode_multi_signature(&sig, CryptoScheme::Ed25519).unwrap();
97        assert_eq!(out.len(), 65);
98        assert_eq!(out[0], 0x00);
99        assert_eq!(&out[1..], sig.as_ref());
100    }
101
102    #[test]
103    fn test_ecdsa_multi_signature_prefix_and_length() {
104        let sig = [0xabu8; 65];
105        let out = encode_multi_signature(&sig, CryptoScheme::Ecdsa).unwrap();
106        assert_eq!(out.len(), 66);
107        assert_eq!(out[0], 0x02);
108        assert_eq!(&out[1..], sig.as_ref());
109    }
110
111    #[test]
112    fn test_multi_signature_rejects_wrong_sr25519_length() {
113        let sig = [0x00u8; 63];
114        assert!(encode_multi_signature(&sig, CryptoScheme::Sr25519).is_err());
115    }
116
117    #[test]
118    fn test_multi_signature_rejects_wrong_ecdsa_length() {
119        // ECDSA expects 65 bytes; 64 must be rejected.
120        let sig = [0x00u8; 64];
121        assert!(encode_multi_signature(&sig, CryptoScheme::Ecdsa).is_err());
122    }
123
124    #[test]
125    fn test_multi_signature_pinned_zeros() {
126        let sig = [0u8; 64];
127        let out = encode_multi_signature(&sig, CryptoScheme::Sr25519).unwrap();
128        // Expected: [0x01, 0x00 × 64] — 65 bytes total.
129        assert_eq!(out.len(), 65);
130        assert_eq!(out[0], 0x01);
131        assert!(out[1..].iter().all(|&b| b == 0x00));
132    }
133}