Skip to main content

pakery_core/
encoding.rs

1//! Encoding utilities: LEB128, length-value concatenation, and ordered concatenation.
2//!
3//! These implement the encoding functions specified in draft-irtf-cfrg-cpace.
4
5use alloc::vec::Vec;
6
7/// Encode a `usize` value using unsigned LEB128 encoding.
8pub fn leb128_encode(mut value: usize) -> Vec<u8> {
9    let mut result = Vec::new();
10    loop {
11        let mut byte = (value & 0x7F) as u8;
12        value >>= 7;
13        if value != 0 {
14            byte |= 0x80;
15        }
16        result.push(byte);
17        if value == 0 {
18            break;
19        }
20    }
21    result
22}
23
24/// Prepend the LEB128-encoded length of `data` to `data`.
25///
26/// Returns `len(data) || data` where `len()` uses unsigned LEB128.
27pub fn prepend_len(data: &[u8]) -> Vec<u8> {
28    let mut result = leb128_encode(data.len());
29    result.extend_from_slice(data);
30    result
31}
32
33/// Length-value concatenation: concatenate `prepend_len(arg)` for each argument.
34pub fn lv_cat(args: &[&[u8]]) -> Vec<u8> {
35    let mut result = Vec::new();
36    for arg in args {
37        result.extend_from_slice(&prepend_len(arg));
38    }
39    result
40}
41
42/// Ordered concatenation of two byte slices.
43///
44/// If `a > b` lexicographically: returns `b"oc" || a || b`
45/// Otherwise: returns `b"oc" || b || a`
46///
47/// # Security
48///
49/// The lexicographic comparison (`a > b`) is **not** constant-time. This is
50/// intentional: the inputs are public protocol messages (party identifiers or
51/// key shares) that are already visible to any network observer, so the
52/// comparison does not leak secret information.
53pub fn o_cat(a: &[u8], b: &[u8]) -> Vec<u8> {
54    let mut result = Vec::with_capacity(2 + a.len() + b.len());
55    result.extend_from_slice(b"oc");
56    if a > b {
57        result.extend_from_slice(a);
58        result.extend_from_slice(b);
59    } else {
60        result.extend_from_slice(b);
61        result.extend_from_slice(a);
62    }
63    result
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69
70    #[test]
71    fn test_leb128_zero() {
72        assert_eq!(leb128_encode(0), vec![0x00]);
73    }
74
75    #[test]
76    fn test_leb128_small() {
77        assert_eq!(leb128_encode(4), vec![0x04]);
78        assert_eq!(leb128_encode(127), vec![0x7F]);
79    }
80
81    #[test]
82    fn test_leb128_128() {
83        assert_eq!(leb128_encode(128), vec![0x80, 0x01]);
84    }
85
86    #[test]
87    fn test_prepend_len_empty() {
88        assert_eq!(prepend_len(b""), vec![0x00]);
89    }
90
91    #[test]
92    fn test_prepend_len_four() {
93        assert_eq!(prepend_len(b"1234"), vec![0x04, 0x31, 0x32, 0x33, 0x34]);
94    }
95
96    #[test]
97    fn test_prepend_len_128_bytes() {
98        let data: Vec<u8> = (0..128u8).collect();
99        let result = prepend_len(&data);
100        assert_eq!(result[0], 0x80);
101        assert_eq!(result[1], 0x01);
102        assert_eq!(&result[2..], &data[..]);
103    }
104
105    #[test]
106    fn test_lv_cat() {
107        let result = lv_cat(&[b"1234", b"5", b"", b"678"]);
108        let expected: Vec<u8> = vec![
109            0x04, 0x31, 0x32, 0x33, 0x34, // prepend_len("1234")
110            0x01, 0x35, // prepend_len("5")
111            0x00, // prepend_len("")
112            0x03, 0x36, 0x37, 0x38, // prepend_len("678")
113        ];
114        assert_eq!(result, expected);
115    }
116
117    #[test]
118    fn test_o_cat_a_greater() {
119        // "b" > "a" is false, "a" > "b" is false => a <= b => oc || b || a
120        let result = o_cat(b"a", b"b");
121        assert_eq!(result, b"ocba");
122    }
123
124    #[test]
125    fn test_o_cat_b_greater() {
126        let result = o_cat(b"b", b"a");
127        assert_eq!(result, b"ocba");
128    }
129
130    #[test]
131    fn test_o_cat_equal() {
132        let result = o_cat(b"x", b"x");
133        assert_eq!(result, b"ocxx");
134    }
135}