Skip to main content

pakery_spake2plus/
encoding.rs

1//! RFC 9383 transcript encoding.
2//!
3//! SPAKE2+ uses 8-byte little-endian length prefixes (same as SPAKE2).
4//! The transcript has 10 fields (vs 6 in SPAKE2).
5
6use alloc::vec::Vec;
7use zeroize::Zeroizing;
8
9/// Encode a length as 8-byte little-endian.
10fn encode_le_u64(len: usize) -> [u8; 8] {
11    (len as u64).to_le_bytes()
12}
13
14/// Append LE64(len) || data to buf.
15fn append_lv(buf: &mut Vec<u8>, data: &[u8]) {
16    buf.extend_from_slice(&encode_le_u64(data.len()));
17    buf.extend_from_slice(data);
18}
19
20/// Build the SPAKE2+ transcript TT per RFC 9383 section 3.3.
21///
22/// ```text
23/// TT = len(Context)    || Context
24///   || len(idProver)   || idProver
25///   || len(idVerifier) || idVerifier
26///   || len(M)          || M
27///   || len(N)          || N
28///   || len(shareP)     || shareP
29///   || len(shareV)     || shareV
30///   || len(Z)          || Z
31///   || len(V)          || V
32///   || len(w0)         || w0
33/// ```
34#[allow(clippy::too_many_arguments)]
35pub fn build_transcript(
36    context: &[u8],
37    id_prover: &[u8],
38    id_verifier: &[u8],
39    m: &[u8],
40    n: &[u8],
41    share_p: &[u8],
42    share_v: &[u8],
43    z: &[u8],
44    v: &[u8],
45    w0: &[u8],
46) -> Zeroizing<Vec<u8>> {
47    let mut tt = Vec::new();
48    append_lv(&mut tt, context);
49    append_lv(&mut tt, id_prover);
50    append_lv(&mut tt, id_verifier);
51    append_lv(&mut tt, m);
52    append_lv(&mut tt, n);
53    append_lv(&mut tt, share_p);
54    append_lv(&mut tt, share_v);
55    append_lv(&mut tt, z);
56    append_lv(&mut tt, v);
57    append_lv(&mut tt, w0);
58    Zeroizing::new(tt)
59}
60
61#[cfg(test)]
62mod tests {
63    use super::*;
64
65    #[test]
66    fn test_encode_le_u64() {
67        assert_eq!(encode_le_u64(0), [0, 0, 0, 0, 0, 0, 0, 0]);
68        assert_eq!(encode_le_u64(1), [1, 0, 0, 0, 0, 0, 0, 0]);
69        assert_eq!(encode_le_u64(256), [0, 1, 0, 0, 0, 0, 0, 0]);
70    }
71
72    #[test]
73    fn test_append_lv() {
74        let mut buf = Vec::new();
75        append_lv(&mut buf, b"test");
76        assert_eq!(buf.len(), 8 + 4);
77        assert_eq!(&buf[..8], &[4, 0, 0, 0, 0, 0, 0, 0]);
78        assert_eq!(&buf[8..], b"test");
79    }
80
81    #[test]
82    fn test_build_transcript_10_fields() {
83        let tt = build_transcript(
84            b"ctx", b"P", b"V", b"MM", b"NN", b"sP", b"sV", b"ZZ", b"VV", b"w",
85        );
86        // 10 fields: each has 8-byte LE length prefix
87        // 3 + 1 + 1 + 2 + 2 + 2 + 2 + 2 + 2 + 1 = 18 bytes data + 10*8 = 80 prefix
88        assert_eq!(tt.len(), 80 + 18);
89    }
90
91    #[test]
92    fn test_build_transcript_empty_fields() {
93        let tt = build_transcript(b"", b"", b"", b"M", b"N", b"", b"", b"", b"", b"");
94        // 10 fields: 0+0+0+1+1+0+0+0+0+0 = 2 bytes data + 80 prefix
95        assert_eq!(tt.len(), 82);
96    }
97
98    #[test]
99    fn test_transcript_field_order() {
100        let tt = build_transcript(b"C", b"P", b"V", b"M", b"N", b"sP", b"sV", b"Z", b"V", b"w");
101        // Check first field: context = "C"
102        assert_eq!(&tt[0..8], &[1, 0, 0, 0, 0, 0, 0, 0]);
103        assert_eq!(tt[8], b'C');
104        // Check second field: idProver = "P"
105        assert_eq!(&tt[9..17], &[1, 0, 0, 0, 0, 0, 0, 0]);
106        assert_eq!(tt[17], b'P');
107    }
108}