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}