Skip to main content

clay_codes/
transforms.rs

1//! Pairwise transforms for Clay codes
2//!
3//! This module implements the core coupling/decoupling transforms from the FAST'18 paper:
4//! - **PRT (Pairwise Reverse Transform)**: C-plane → U-plane
5//! - **PFT (Pairwise Forward Transform)**: U-plane → C-plane
6//!
7//! The transforms use a 2x2 matrix with parameter γ (gamma):
8//! ```text
9//! PRT: [U, U*] = [1, γ; γ, 1] × [C, C*]
10//! PFT: [C, C*] = [1, γ; γ, 1]⁻¹ × [U, U*]
11//! ```
12//!
13//! γ must satisfy: γ ≠ 0 and γ² ≠ 1
14
15use reed_solomon_erasure::galois_8::{add as gf_add, mul as gf_mul, div as gf_div};
16
17/// Gamma value for pairwise transforms.
18/// Must satisfy: γ ≠ 0, γ² ≠ 1
19/// In GF(2^8), 2 works well since 2² = 4 ≠ 1
20pub const GAMMA: u8 = 2;
21
22/// GF(2^8) multiplicative inverse: a^(-1) = 1/a
23#[inline]
24pub fn gf_inv(a: u8) -> u8 {
25    gf_div(1, a)
26}
27
28/// PRT: Pairwise Reverse Transform (C-plane → U-plane)
29///
30/// Computes both U and U* from C and C*:
31/// ```text
32/// [U ]   [1  γ] [C ]
33/// [U*] = [γ  1] [C*]
34/// ```
35///
36/// # Arguments
37/// * `c` - C values (primary)
38/// * `c_star` - C* values (companion)
39///
40/// # Returns
41/// Tuple of (U, U*) vectors
42pub fn prt_compute_both(c: &[u8], c_star: &[u8]) -> (Vec<u8>, Vec<u8>) {
43    let len = c.len();
44    let mut u = vec![0u8; len];
45    let mut u_star = vec![0u8; len];
46
47    for i in 0..len {
48        // U = C + γ*C*
49        u[i] = gf_add(c[i], gf_mul(GAMMA, c_star[i]));
50        // U* = γ*C + C*
51        u_star[i] = gf_add(gf_mul(GAMMA, c[i]), c_star[i]);
52    }
53
54    (u, u_star)
55}
56
57/// PRT with orientation handling
58///
59/// Given c_xy at node (x,y) layer z, and c_sw at node (z_y,y) layer z_sw:
60/// - If xy_is_primary (x < z_y): c_xy is C, c_sw is C*
61/// - Otherwise (x > z_y): c_xy is C*, c_sw is C
62///
63/// # Returns
64/// Tuple of (u_xy, u_sw) - U values for each node at their respective layers
65pub fn prt_compute_both_oriented(c_xy: &[u8], c_sw: &[u8], xy_is_primary: bool) -> (Vec<u8>, Vec<u8>) {
66    let len = c_xy.len();
67    let mut u_xy = vec![0u8; len];
68    let mut u_sw = vec![0u8; len];
69
70    if xy_is_primary {
71        // c_xy is C (primary), c_sw is C* (starred)
72        // u_xy = U = C + γ*C* = c_xy + γ*c_sw
73        // u_sw = U* = γ*C + C* = γ*c_xy + c_sw
74        for i in 0..len {
75            u_xy[i] = gf_add(c_xy[i], gf_mul(GAMMA, c_sw[i]));
76            u_sw[i] = gf_add(gf_mul(GAMMA, c_xy[i]), c_sw[i]);
77        }
78    } else {
79        // c_xy is C* (starred), c_sw is C (primary)
80        // u_xy = U* = γ*C + C* = γ*c_sw + c_xy
81        // u_sw = U = C + γ*C* = c_sw + γ*c_xy
82        for i in 0..len {
83            u_xy[i] = gf_add(gf_mul(GAMMA, c_sw[i]), c_xy[i]);
84            u_sw[i] = gf_add(c_sw[i], gf_mul(GAMMA, c_xy[i]));
85        }
86    }
87
88    (u_xy, u_sw)
89}
90
91/// PFT: Pairwise Forward Transform (U-plane → C-plane)
92///
93/// Computes both C and C* from U and U*:
94/// ```text
95/// [C ]   [1  γ]⁻¹ [U ]
96/// [C*] = [γ  1]   [U*]
97/// ```
98///
99/// The inverse matrix is: (1/(1-γ²)) × [1, -γ; -γ, 1]
100/// In GF(2^8), subtraction = addition, so: (1/(1+γ²)) × [1, γ; γ, 1]
101///
102/// # Arguments
103/// * `u` - U values (primary)
104/// * `u_star` - U* values (companion)
105///
106/// # Returns
107/// Tuple of (C, C*) vectors
108pub fn pft_compute_both(u: &[u8], u_star: &[u8]) -> (Vec<u8>, Vec<u8>) {
109    let len = u.len();
110    let mut c = vec![0u8; len];
111    let mut c_star = vec![0u8; len];
112
113    // det = 1 - γ² = 1 + γ² (in GF(2^8), subtraction = addition)
114    let det = gf_add(1, gf_mul(GAMMA, GAMMA));
115    let det_inv = gf_inv(det);
116
117    for i in 0..len {
118        // C = (U + γ*U*) / det
119        c[i] = gf_mul(gf_add(u[i], gf_mul(GAMMA, u_star[i])), det_inv);
120        // C* = (γ*U + U*) / det
121        c_star[i] = gf_mul(gf_add(gf_mul(GAMMA, u[i]), u_star[i]), det_inv);
122    }
123
124    (c, c_star)
125}
126
127/// Compute C from U and C* (partial PFT)
128///
129/// Used when we have U at one vertex and C* at its companion.
130/// From the PRT equation: U = C + γ*C*
131/// Therefore: C = U - γ*C* = U + γ*C* (in GF(2^8))
132pub fn compute_c_from_u_and_cstar(u_xy: &[u8], c_companion: &[u8]) -> Vec<u8> {
133    let len = u_xy.len();
134    let mut c = vec![0u8; len];
135
136    for i in 0..len {
137        // C = U + γ*C* (using the fact that U = C + γ*C*)
138        c[i] = gf_add(u_xy[i], gf_mul(GAMMA, c_companion[i]));
139    }
140
141    c
142}
143
144/// Compute C* from C and U (partial transform)
145///
146/// Used when we have C at one vertex and U at its companion.
147/// From PRT: U* = γ*C + C*, so C* = U* - γ*C = U* + γ*C
148/// But we have U (not U*) and C, so we use:
149/// U = C + γ*C* and U* = γ*C + C*
150///
151/// If helper is primary (has C), we compute C* from its U* and C:
152/// C* = U* + γ*C (since U* = γ*C + C*)
153pub fn compute_cstar_from_c_and_u(c_helper: &[u8], u_helper: &[u8], helper_is_primary: bool) -> Vec<u8> {
154    let len = c_helper.len();
155    let mut companion_c = vec![0u8; len];
156
157    let gamma_inv = gf_inv(GAMMA);
158
159    if helper_is_primary {
160        // helper has C, u_helper is U* for companion
161        // U* = γ*C + C* => C* = U* + γ*C
162        for i in 0..len {
163            companion_c[i] = gf_add(u_helper[i], gf_mul(GAMMA, c_helper[i]));
164        }
165    } else {
166        // helper has C*, u_helper is U for companion
167        // U = C + γ*C* => C = U + γ*C*
168        // But we want C* given C* (helper) and U... this case shouldn't happen
169        // Actually if helper is not primary, then helper has C*, and we want C
170        // U = C + γ*C* => C = U + γ*C*
171        for i in 0..len {
172            companion_c[i] = gf_mul(gf_add(u_helper[i], c_helper[i]), gamma_inv);
173        }
174    }
175
176    companion_c
177}
178
179/// Compute U from C and U* (partial transform)
180///
181/// From PFT inverse, given C and U*:
182/// det * C = U + γ*U*
183/// Therefore: U = det*C + γ*U* (in GF(2^8))
184pub fn compute_u_from_c_and_ustar(c_xy: &[u8], u_companion: &[u8]) -> Vec<u8> {
185    let len = c_xy.len();
186    let mut u = vec![0u8; len];
187
188    let det = gf_add(1, gf_mul(GAMMA, GAMMA));
189
190    for i in 0..len {
191        // U = det*C + γ*U*
192        u[i] = gf_add(gf_mul(det, c_xy[i]), gf_mul(GAMMA, u_companion[i]));
193    }
194
195    u
196}
197
198#[cfg(test)]
199mod tests {
200    use super::*;
201
202    #[test]
203    fn test_gamma_properties() {
204        // Verify γ ≠ 0
205        assert_ne!(GAMMA, 0);
206        // Verify γ² ≠ 1
207        let gamma_sq = gf_mul(GAMMA, GAMMA);
208        assert_ne!(gamma_sq, 1);
209    }
210
211    #[test]
212    fn test_prt_pft_roundtrip() {
213        let c = vec![0x12, 0x34, 0x56, 0x78];
214        let c_star = vec![0xAB, 0xCD, 0xEF, 0x01];
215
216        // C → U via PRT
217        let (u, u_star) = prt_compute_both(&c, &c_star);
218
219        // U → C via PFT
220        let (c_back, c_star_back) = pft_compute_both(&u, &u_star);
221
222        assert_eq!(c, c_back);
223        assert_eq!(c_star, c_star_back);
224    }
225
226    #[test]
227    fn test_gf_arithmetic() {
228        // Addition is XOR
229        assert_eq!(gf_add(5, 3), 6); // 5 XOR 3 = 6
230
231        // Multiplication
232        assert_eq!(gf_mul(2, 3), 6); // 2 * 3 = 6 in GF(2^8)
233
234        // Inverse: a^(-1) * a = 1
235        assert_eq!(gf_mul(gf_inv(2), 2), 1);
236    }
237}