Skip to main content

dcrypt_algorithms/mac/poly1305/
mod.rs

1//! Poly1305 message authentication code
2//! Pure-Rust branch-free limb arithmetic implementation.
3//!
4//! Implements the algorithm described in RFC 8439.
5//! Branch-free source is not a blanket side-channel proof for every compiler
6//! and target.
7
8#[cfg(not(feature = "std"))]
9use alloc::vec::Vec;
10
11use crate::error::{validate, Result};
12use crate::mac::MacAlgorithm;
13use crate::types::Tag;
14use dcrypt_common::security::SecretBuffer;
15use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
16
17/// Size of the Poly1305 key in bytes (32 B)
18pub const POLY1305_KEY_SIZE: usize = 32;
19/// Size of the Poly1305 authentication tag in bytes (16 B)
20pub const POLY1305_TAG_SIZE: usize = 16;
21
22/// Marker for the Poly1305 algorithm (type-level)
23pub enum Poly1305Algorithm {}
24
25impl MacAlgorithm for Poly1305Algorithm {
26    const KEY_SIZE: usize = POLY1305_KEY_SIZE;
27    const TAG_SIZE: usize = POLY1305_TAG_SIZE;
28    const BLOCK_SIZE: usize = 16;
29
30    fn name() -> &'static str {
31        "Poly1305"
32    }
33}
34
35/// Poly1305 MAC (branch-free limb arithmetic)
36#[derive(Zeroize, ZeroizeOnDrop)]
37pub struct Poly1305 {
38    r: SecretBuffer<24>,      // 130-bit key r stored as 3 u64s (24 bytes)
39    s: SecretBuffer<16>,      // 128-bit key s stored as 2 u64s (16 bytes)
40    data: Zeroizing<Vec<u8>>, // buffered input
41}
42
43impl Poly1305 {
44    /* ------------------------------------------------------------------ */
45    /*                           INITIALISATION                           */
46    /* ------------------------------------------------------------------ */
47
48    /// Construct a new `Poly1305` context from a 32-byte key.
49    ///
50    /// The key is split into the clamped `r` portion (first 16 bytes) and the
51    /// `s` portion (last 16 bytes) exactly as specified in RFC 8439 §2.5.2.
52    pub fn new(key: &[u8]) -> Result<Self> {
53        validate::length("Poly1305 key", key.len(), POLY1305_KEY_SIZE)?;
54
55        // ---- split & clamp r -------------------------------------------
56        let mut r_bytes = Zeroizing::new([0u8; 16]);
57        r_bytes.copy_from_slice(&key[..16]);
58        r_bytes[3] &= 15;
59        r_bytes[7] &= 15;
60        r_bytes[11] &= 15;
61        r_bytes[15] &= 15;
62        r_bytes[4] &= 252;
63        r_bytes[8] &= 252;
64        r_bytes[12] &= 252;
65
66        // Keep key-derived temporaries inside zeroizing containers.
67        let mut r = SecretBuffer::<24>::zeroed();
68        r.as_mut()[..16].copy_from_slice(r_bytes.as_ref());
69
70        // ---- split s ---------------------------------------------------
71        let mut s = SecretBuffer::<16>::zeroed();
72        s.as_mut().copy_from_slice(&key[16..32]);
73
74        Ok(Self {
75            r,
76            s,
77            data: Zeroizing::new(Vec::new()),
78        })
79    }
80
81    /* ------------------------------------------------------------------ */
82    /*                           HELPER METHODS                           */
83    /* ------------------------------------------------------------------ */
84
85    /// Extract r values from the secure buffer
86    fn get_r(&self) -> [u64; 3] {
87        let bytes = self.r.as_ref();
88        [
89            u64::from_le_bytes(bytes[0..8].try_into().unwrap()),
90            u64::from_le_bytes(bytes[8..16].try_into().unwrap()),
91            u64::from_le_bytes(bytes[16..24].try_into().unwrap()),
92        ]
93    }
94
95    /// Extract s values from the secure buffer
96    fn get_s(&self) -> [u64; 2] {
97        let bytes = self.s.as_ref();
98        [
99            u64::from_le_bytes(bytes[0..8].try_into().unwrap()),
100            u64::from_le_bytes(bytes[8..16].try_into().unwrap()),
101        ]
102    }
103
104    /* ------------------------------------------------------------------ */
105    /*                                UPDATE                               */
106    /* ------------------------------------------------------------------ */
107
108    /// Absorb additional message data into the MAC state.
109    ///
110    /// This can be called zero or more times before [`Self::finalize`].  
111    /// Data is internally buffered in 16-byte blocks.  
112    /// Always returns `Ok(())` (provided for API symmetry).
113    pub fn update(&mut self, chunk: &[u8]) -> Result<()> {
114        if !chunk.is_empty() {
115            self.data.extend_from_slice(chunk);
116        }
117        Ok(())
118    }
119
120    /* ------------------------------------------------------------------ */
121    /*                               FINALISE                              */
122    /* ------------------------------------------------------------------ */
123
124    /// Consume the context and return the 16-byte authentication tag.
125    ///
126    /// After this call the `Poly1305` instance must be discarded because its
127    /// internal key material has been moved.
128    pub fn finalize(self) -> Tag<POLY1305_TAG_SIZE> {
129        // 1) polynomial evaluation h = Σ (block · r^i)
130        let mut h = [0u64; 3];
131        let r = Zeroizing::new(self.get_r());
132
133        for block in self.data.chunks(16) {
134            let mut buf = Zeroizing::new([0u8; 16]);
135            buf[..block.len()].copy_from_slice(block);
136            let n2 = if block.len() == 16 {
137                1
138            } else {
139                buf[block.len()] = 1;
140                0
141            };
142            let n0 = u64::from_le_bytes(buf[0..8].try_into().unwrap());
143            let n1 = u64::from_le_bytes(buf[8..16].try_into().unwrap());
144
145            // h += n (carry-prop)
146            let (h0, c0) = h[0].overflowing_add(n0);
147            let (h1a, c1a) = h[1].overflowing_add(n1);
148            let (h1, c1b) = h1a.overflowing_add(c0 as u64);
149            let c1 = (c1a || c1b) as u64;
150            let (h2a, _) = h[2].overflowing_add(n2);
151            let (h2, _) = h2a.overflowing_add(c1);
152
153            h = mul_reduce([h0, h1, h2], &r);
154        }
155
156        // 2) final reduction mod p = 2^130 − 5 (branch-free)
157        const P0: u64 = 0xffff_ffff_ffff_fffb;
158        const P1: u64 = 0xffff_ffff_ffff_ffff;
159        const P2: u64 = 3;
160
161        let (g0, b0) = h[0].overflowing_sub(P0);
162        let (g1a, b1a) = h[1].overflowing_sub(P1);
163        let (g1, b1b) = g1a.overflowing_sub(b0 as u64);
164        let borrow1 = (b1a || b1b) as u64;
165        let (g2, borrow2_bool) = h[2].overflowing_sub(P2 + borrow1);
166
167        // mask = 0xFFFF… when borrow2 == 0, else 0x0
168        let mask = (borrow2_bool as u64).wrapping_sub(1);
169        h[0] = (h[0] & !mask) | (g0 & mask);
170        h[1] = (h[1] & !mask) | (g1 & mask);
171        h[2] = (h[2] & !mask) | (g2 & mask);
172
173        // 3) add s (mod 2^128)
174        let s = Zeroizing::new(self.get_s());
175        let (t0, carry0) = h[0].overflowing_add(s[0]);
176        let (t1a, _) = h[1].overflowing_add(s[1]);
177        let (t1, _) = t1a.overflowing_add(carry0 as u64);
178
179        let mut out = [0u8; POLY1305_TAG_SIZE];
180        out[..8].copy_from_slice(&t0.to_le_bytes());
181        out[8..16].copy_from_slice(&t1.to_le_bytes());
182        Tag::new(out)
183    }
184}
185
186/* ---------------------------------------------------------------------- */
187/*                SCHOOLBOOK MUL & REDUCE (2^130 − 5)                     */
188/* ---------------------------------------------------------------------- */
189fn mul_reduce(h: [u64; 3], r: &[u64; 3]) -> [u64; 3] {
190    let (h0, h1, h2) = (h[0] as u128, h[1] as u128, h[2] as u128);
191    let (r0, r1, r2) = (r[0] as u128, r[1] as u128, r[2] as u128);
192
193    // schoolbook multiply
194    let mut t0 = h0 * r0;
195    let mut t1 = h0 * r1 + h1 * r0;
196    let mut t2 = h0 * r2 + h1 * r1 + h2 * r0;
197    let mut t3 = h1 * r2 + h2 * r1;
198    let mut t4 = h2 * r2;
199
200    // propagate carries
201    let c1 = (t0 >> 64) as u64;
202    t0 &= u128::from(u64::MAX);
203    t1 += c1 as u128;
204    let c2 = (t1 >> 64) as u64;
205    t1 &= u128::from(u64::MAX);
206    t2 += c2 as u128;
207    let c3 = (t2 >> 64) as u64;
208    t2 &= u128::from(u64::MAX);
209    t3 += c3 as u128;
210    let c4 = (t3 >> 64) as u64;
211    t3 &= u128::from(u64::MAX);
212    t4 += c4 as u128;
213    let _c5 = (t4 >> 64) as u64;
214    t4 &= u128::from(u64::MAX);
215
216    // fold bits ≥2^130 back in via 2^130 ≡ 5 (mod p)
217    let high = (t2 >> 2) + (t3 << 62) + (t4 << 126);
218    let low2 = t2 & 0x3;
219
220    // combine low limbs with folded carry
221    let mut m0 = t0 + high * 5;
222    let mut m1 = t1;
223    let mut m2 = low2;
224
225    // final carry
226    let f1 = (m0 >> 64) as u64;
227    m0 &= u128::from(u64::MAX);
228    m1 += f1 as u128;
229    let f2 = (m1 >> 64) as u64;
230    m1 &= u128::from(u64::MAX);
231    m2 += f2 as u128;
232
233    m2 &= 0x3fff_ffff_ffff_ffff;
234    [m0 as u64, m1 as u64, m2 as u64]
235}
236
237#[cfg(test)]
238mod tests;