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
8use crate::error::{validate, Result};
9use crate::mac::MacAlgorithm;
10use crate::types::Tag;
11use dcrypt_common::security::{SecretBuffer, SecretVec};
12use dcrypt_internal::zeroing::{Zeroize, ZeroizeOnDrop, Zeroizing};
13
14/// Size of the Poly1305 key in bytes (32 B)
15pub const POLY1305_KEY_SIZE: usize = 32;
16/// Size of the Poly1305 authentication tag in bytes (16 B)
17pub const POLY1305_TAG_SIZE: usize = 16;
18
19/// Marker for the Poly1305 algorithm (type-level)
20pub enum Poly1305Algorithm {}
21
22impl MacAlgorithm for Poly1305Algorithm {
23    const KEY_SIZE: usize = POLY1305_KEY_SIZE;
24    const TAG_SIZE: usize = POLY1305_TAG_SIZE;
25    const BLOCK_SIZE: usize = 16;
26
27    fn name() -> &'static str {
28        "Poly1305"
29    }
30}
31
32/// Poly1305 MAC (branch-free limb arithmetic)
33pub struct Poly1305 {
34    r: SecretBuffer<24>, // 130-bit key r stored as 3 u64s (24 bytes)
35    s: SecretBuffer<16>, // 128-bit key s stored as 2 u64s (16 bytes)
36    data: SecretVec,     // exact-size buffered input
37}
38
39impl Zeroize for Poly1305 {
40    fn zeroize(&mut self) {
41        self.r.zeroize();
42        self.s.zeroize();
43        self.data.zeroize();
44    }
45}
46
47impl Drop for Poly1305 {
48    fn drop(&mut self) {
49        self.zeroize();
50    }
51}
52
53impl ZeroizeOnDrop for Poly1305 {}
54
55impl Poly1305 {
56    /* ------------------------------------------------------------------ */
57    /*                           INITIALISATION                           */
58    /* ------------------------------------------------------------------ */
59
60    /// Construct a new `Poly1305` context from a 32-byte key.
61    ///
62    /// The key is split into the clamped `r` portion (first 16 bytes) and the
63    /// `s` portion (last 16 bytes) exactly as specified in RFC 8439 §2.5.2.
64    pub fn new(key: &[u8]) -> Result<Self> {
65        validate::length("Poly1305 key", key.len(), POLY1305_KEY_SIZE)?;
66
67        // ---- split & clamp r -------------------------------------------
68        let mut r_bytes = Zeroizing::new([0u8; 16]);
69        r_bytes.copy_from_slice(&key[..16]);
70        r_bytes[3] &= 15;
71        r_bytes[7] &= 15;
72        r_bytes[11] &= 15;
73        r_bytes[15] &= 15;
74        r_bytes[4] &= 252;
75        r_bytes[8] &= 252;
76        r_bytes[12] &= 252;
77
78        // Keep key-derived temporaries inside zeroizing containers.
79        let mut r = SecretBuffer::<24>::zeroed();
80        r.as_mut()[..16].copy_from_slice(r_bytes.as_ref());
81
82        // ---- split s ---------------------------------------------------
83        let mut s = SecretBuffer::<16>::zeroed();
84        s.as_mut().copy_from_slice(&key[16..32]);
85
86        Ok(Self {
87            r,
88            s,
89            data: SecretVec::empty(),
90        })
91    }
92
93    /* ------------------------------------------------------------------ */
94    /*                           HELPER METHODS                           */
95    /* ------------------------------------------------------------------ */
96
97    /// Extract r values from the secure buffer
98    fn get_r(&self) -> Zeroizing<[u64; 3]> {
99        let bytes = self.r.as_ref();
100        let mut words = Zeroizing::new([0u64; 3]);
101        for (word_index, word) in words.iter_mut().enumerate() {
102            for byte_index in 0..8 {
103                *word |= u64::from(bytes[word_index * 8 + byte_index]) << (byte_index * 8);
104            }
105        }
106        words
107    }
108
109    /// Extract s values from the secure buffer
110    fn get_s(&self) -> Zeroizing<[u64; 2]> {
111        let bytes = self.s.as_ref();
112        let mut words = Zeroizing::new([0u64; 2]);
113        for (word_index, word) in words.iter_mut().enumerate() {
114            for byte_index in 0..8 {
115                *word |= u64::from(bytes[word_index * 8 + byte_index]) << (byte_index * 8);
116            }
117        }
118        words
119    }
120
121    /* ------------------------------------------------------------------ */
122    /*                                UPDATE                               */
123    /* ------------------------------------------------------------------ */
124
125    /// Absorb additional message data into the MAC state.
126    ///
127    /// This can be called zero or more times before [`Self::finalize`].  
128    /// Data is internally buffered in 16-byte blocks.  
129    /// Always returns `Ok(())` (provided for API symmetry).
130    pub fn update(&mut self, chunk: &[u8]) -> Result<()> {
131        if !chunk.is_empty() {
132            self.data.extend_from_slice(chunk);
133        }
134        Ok(())
135    }
136
137    /* ------------------------------------------------------------------ */
138    /*                               FINALISE                              */
139    /* ------------------------------------------------------------------ */
140
141    /// Consume the context and return the 16-byte authentication tag.
142    ///
143    /// After this call the `Poly1305` instance must be discarded because its
144    /// internal key material has been moved.
145    pub fn finalize(self) -> Tag<POLY1305_TAG_SIZE> {
146        // 1) polynomial evaluation h = Σ (block · r^i)
147        let mut h = Zeroizing::new([0u64; 3]);
148        let r = self.get_r();
149
150        for block in self.data.chunks(16) {
151            let mut buf = Zeroizing::new([0u8; 16]);
152            buf[..block.len()].copy_from_slice(block);
153            let n2 = if block.len() == 16 {
154                1
155            } else {
156                buf[block.len()] = 1;
157                0
158            };
159            let mut n = Zeroizing::new([0u64; 2]);
160            for (word_index, word) in n.iter_mut().enumerate() {
161                for byte_index in 0..8 {
162                    *word |= u64::from(buf[word_index * 8 + byte_index]) << (byte_index * 8);
163                }
164            }
165
166            // h += n (carry-prop)
167            let mut sum = Zeroizing::new(u128::from(h[0]) + u128::from(n[0]));
168            h[0] = *sum as u64;
169            *sum = u128::from(h[1]) + u128::from(n[1]) + (*sum >> 64);
170            h[1] = *sum as u64;
171            h[2] = h[2].wrapping_add(n2).wrapping_add((*sum >> 64) as u64);
172
173            let reduced = mul_reduce(&h, &r);
174            h.copy_from_slice(&*reduced);
175        }
176
177        // 2) final reduction mod p = 2^130 − 5 (branch-free)
178        const P0: u64 = 0xffff_ffff_ffff_fffb;
179        const P1: u64 = 0xffff_ffff_ffff_ffff;
180        const P2: u64 = 3;
181
182        let mut g = Zeroizing::new([0u64; 3]);
183        let mut borrow = Zeroizing::new(0u64);
184        let mut wide = Zeroizing::new((1u128 << 64) + u128::from(h[0]) - u128::from(P0));
185        g[0] = *wide as u64;
186        *borrow = 1 - (*wide >> 64) as u64;
187        *wide = (1u128 << 64) + u128::from(h[1]) - u128::from(P1) - u128::from(*borrow);
188        g[1] = *wide as u64;
189        *borrow = 1 - (*wide >> 64) as u64;
190        *wide = (1u128 << 64) + u128::from(h[2]) - u128::from(P2) - u128::from(*borrow);
191        g[2] = *wide as u64;
192        *borrow = 1 - (*wide >> 64) as u64;
193
194        // mask = 0xFFFF… when borrow2 == 0, else 0x0
195        let mask = Zeroizing::new(borrow.wrapping_sub(1));
196        h[0] = (h[0] & !*mask) | (g[0] & *mask);
197        h[1] = (h[1] & !*mask) | (g[1] & *mask);
198        h[2] = (h[2] & !*mask) | (g[2] & *mask);
199
200        // 3) add s (mod 2^128)
201        let s = self.get_s();
202        *wide = u128::from(h[0]) + u128::from(s[0]);
203        let mut tag_words = Zeroizing::new([0u64; 2]);
204        tag_words[0] = *wide as u64;
205        *wide = u128::from(h[1]) + u128::from(s[1]) + (*wide >> 64);
206        tag_words[1] = *wide as u64;
207
208        let mut out = [0u8; POLY1305_TAG_SIZE];
209        for (word_index, word) in tag_words.iter().enumerate() {
210            for byte_index in 0..8 {
211                out[word_index * 8 + byte_index] = (word >> (byte_index * 8)) as u8;
212            }
213        }
214        Tag::new(out)
215    }
216}
217
218/* ---------------------------------------------------------------------- */
219/*                SCHOOLBOOK MUL & REDUCE (2^130 − 5)                     */
220/* ---------------------------------------------------------------------- */
221fn mul_reduce(h: &[u64; 3], r: &[u64; 3]) -> Zeroizing<[u64; 3]> {
222    let operands = Zeroizing::new([
223        u128::from(h[0]),
224        u128::from(h[1]),
225        u128::from(h[2]),
226        u128::from(r[0]),
227        u128::from(r[1]),
228        u128::from(r[2]),
229    ]);
230
231    // schoolbook multiply
232    let mut products = Zeroizing::new([0u128; 5]);
233    products[0] = operands[0] * operands[3];
234    products[1] = operands[0] * operands[4] + operands[1] * operands[3];
235    products[2] = operands[0] * operands[5] + operands[1] * operands[4] + operands[2] * operands[3];
236    products[3] = operands[1] * operands[5] + operands[2] * operands[4];
237    products[4] = operands[2] * operands[5];
238
239    // propagate carries
240    for limb in 0..4 {
241        products[limb + 1] += products[limb] >> 64;
242        products[limb] &= u128::from(u64::MAX);
243    }
244    products[4] &= u128::from(u64::MAX);
245
246    // fold bits ≥2^130 back in via 2^130 ≡ 5 (mod p)
247    let high = Zeroizing::new((products[2] >> 2) + (products[3] << 62) + (products[4] << 126));
248
249    // combine low limbs with folded carry
250    let mut reduced = Zeroizing::new([0u128; 3]);
251    reduced[0] = products[0] + *high * 5;
252    reduced[1] = products[1];
253    reduced[2] = products[2] & 0x3;
254
255    // final carry
256    reduced[1] += reduced[0] >> 64;
257    reduced[0] &= u128::from(u64::MAX);
258    reduced[2] += reduced[1] >> 64;
259    reduced[1] &= u128::from(u64::MAX);
260    reduced[2] &= 0x3fff_ffff_ffff_ffff;
261
262    let mut result = Zeroizing::new([0u64; 3]);
263    for (output, value) in result.iter_mut().zip(reduced.iter()) {
264        *output = *value as u64;
265    }
266    result
267}
268
269#[cfg(test)]
270mod tests;