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