1 // ring has a garbage API so its use is avoided, but rust-crypto doesn't have RFC-variant poly1305
2 // Instead, we steal rust-crypto's implementation and tweak it to match the RFC.
4 // This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
5 // or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
7 // You may not use this file except in accordance with one or both of these
10 // This is a port of Andrew Moons poly1305-donna
11 // https://github.com/floodyberry/poly1305-donna
13 use util::ser::{Writeable, Writer};
14 use io::{self, Write};
18 use util::chacha20::ChaCha20;
19 use util::poly1305::Poly1305;
20 use bitcoin::hashes::cmp::fixed_time_eq;
22 #[derive(Clone, Copy)]
23 pub struct ChaCha20Poly1305RFC {
31 impl ChaCha20Poly1305RFC {
33 fn pad_mac_16(mac: &mut Poly1305, len: usize) {
35 mac.input(&[0; 16][0..16 - (len % 16)]);
38 pub fn new(key: &[u8], nonce: &[u8], aad: &[u8]) -> ChaCha20Poly1305RFC {
39 assert!(key.len() == 16 || key.len() == 32);
40 assert!(nonce.len() == 12);
42 // Ehh, I'm too lazy to *also* tweak ChaCha20 to make it RFC-compliant
43 assert!(nonce[0] == 0 && nonce[1] == 0 && nonce[2] == 0 && nonce[3] == 0);
45 let mut cipher = ChaCha20::new(key, &nonce[4..]);
46 let mut mac_key = [0u8; 64];
47 let zero_key = [0u8; 64];
48 cipher.process(&zero_key, &mut mac_key);
50 let mut mac = Poly1305::new(&mac_key[..32]);
52 ChaCha20Poly1305RFC::pad_mac_16(&mut mac, aad.len());
59 aad_len: aad.len() as u64,
63 pub fn encrypt(&mut self, input: &[u8], output: &mut [u8], out_tag: &mut [u8]) {
64 assert!(input.len() == output.len());
65 assert!(self.finished == false);
66 self.cipher.process(input, output);
67 self.data_len += input.len();
68 self.mac.input(output);
69 ChaCha20Poly1305RFC::pad_mac_16(&mut self.mac, self.data_len);
71 self.mac.input(&self.aad_len.to_le_bytes());
72 self.mac.input(&(self.data_len as u64).to_le_bytes());
73 self.mac.raw_result(out_tag);
76 // Encrypt `input_output` in-place. To finish and calculate the tag, use `finish_and_get_tag`
78 pub(super) fn encrypt_in_place(&mut self, input_output: &mut [u8]) {
79 debug_assert!(self.finished == false);
80 self.cipher.process_in_place(input_output);
81 self.data_len += input_output.len();
82 self.mac.input(input_output);
85 // If we were previously encrypting with `encrypt_in_place`, this method can be used to finish
86 // encrypting and calculate the tag.
87 pub(super) fn finish_and_get_tag(&mut self, out_tag: &mut [u8]) {
88 debug_assert!(self.finished == false);
89 ChaCha20Poly1305RFC::pad_mac_16(&mut self.mac, self.data_len);
91 self.mac.input(&self.aad_len.to_le_bytes());
92 self.mac.input(&(self.data_len as u64).to_le_bytes());
93 self.mac.raw_result(out_tag);
96 pub fn decrypt(&mut self, input: &[u8], output: &mut [u8], tag: &[u8]) -> bool {
97 assert!(input.len() == output.len());
98 assert!(self.finished == false);
100 self.finished = true;
102 self.mac.input(input);
104 self.data_len += input.len();
105 ChaCha20Poly1305RFC::pad_mac_16(&mut self.mac, self.data_len);
106 self.mac.input(&self.aad_len.to_le_bytes());
107 self.mac.input(&(self.data_len as u64).to_le_bytes());
109 let mut calc_tag = [0u8; 16];
110 self.mac.raw_result(&mut calc_tag);
111 if fixed_time_eq(&calc_tag, tag) {
112 self.cipher.process(input, output);
121 pub use self::real_chachapoly::ChaCha20Poly1305RFC;
123 /// Enables simultaneously writing and encrypting a byte stream into a Writer.
124 struct ChaChaPolyWriter<'a, W: Writer> {
125 pub chacha: &'a mut ChaCha20Poly1305RFC,
126 pub write: &'a mut W,
129 impl<'a, W: Writer> Writer for ChaChaPolyWriter<'a, W> {
130 // Encrypt then write bytes from `src` into Self::write.
131 // `ChaCha20Poly1305RFC::finish_and_get_tag` can be called to retrieve the tag after all writes
133 fn write_all(&mut self, src: &[u8]) -> Result<(), io::Error> {
135 while src_idx < src.len() {
136 let mut write_buffer = [0; 8192];
137 let bytes_written = (&mut write_buffer[..]).write(&src[src_idx..]).expect("In-memory writes can't fail");
138 self.chacha.encrypt_in_place(&mut write_buffer[..bytes_written]);
139 self.write.write_all(&write_buffer[..bytes_written])?;
140 src_idx += bytes_written;
146 /// Enables the use of the serialization macros for objects that need to be simultaneously encrypted and
147 /// serialized. This allows us to avoid an intermediate Vec allocation.
148 pub(crate) struct ChaChaPolyWriteAdapter<'a, W: Writeable> {
150 pub writeable: &'a W,
153 impl<'a, W: Writeable> ChaChaPolyWriteAdapter<'a, W> {
154 #[allow(unused)] // This will be used for onion messages soon
155 pub fn new(rho: [u8; 32], writeable: &'a W) -> ChaChaPolyWriteAdapter<'a, W> {
156 Self { rho, writeable }
160 impl<'a, T: Writeable> Writeable for ChaChaPolyWriteAdapter<'a, T> {
161 // Simultaneously write and encrypt Self::writeable.
162 fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
163 let mut chacha = ChaCha20Poly1305RFC::new(&self.rho, &[0; 12], &[]);
164 let mut chacha_stream = ChaChaPolyWriter { chacha: &mut chacha, write: w };
165 self.writeable.write(&mut chacha_stream)?;
166 let mut tag = [0 as u8; 16];
167 chacha.finish_and_get_tag(&mut tag);
175 mod fuzzy_chachapoly {
176 #[derive(Clone, Copy)]
177 pub struct ChaCha20Poly1305RFC {
181 impl ChaCha20Poly1305RFC {
182 pub fn new(key: &[u8], nonce: &[u8], _aad: &[u8]) -> ChaCha20Poly1305RFC {
183 assert!(key.len() == 16 || key.len() == 32);
184 assert!(nonce.len() == 12);
186 // Ehh, I'm too lazy to *also* tweak ChaCha20 to make it RFC-compliant
187 assert!(nonce[0] == 0 && nonce[1] == 0 && nonce[2] == 0 && nonce[3] == 0);
189 let mut tag = [0; 16];
190 tag.copy_from_slice(&key[0..16]);
192 ChaCha20Poly1305RFC {
198 pub fn encrypt(&mut self, input: &[u8], output: &mut [u8], out_tag: &mut [u8]) {
199 assert!(input.len() == output.len());
200 assert!(self.finished == false);
202 output.copy_from_slice(&input);
203 out_tag.copy_from_slice(&self.tag);
204 self.finished = true;
207 pub(super) fn encrypt_in_place(&mut self, _input_output: &mut [u8]) {
208 assert!(self.finished == false);
209 self.finished = true;
212 pub(super) fn finish_and_get_tag(&mut self, out_tag: &mut [u8]) {
213 out_tag.copy_from_slice(&self.tag);
214 self.finished = true;
217 pub fn decrypt(&mut self, input: &[u8], output: &mut [u8], tag: &[u8]) -> bool {
218 assert!(input.len() == output.len());
219 assert!(self.finished == false);
221 if tag[..] != self.tag[..] { return false; }
222 output.copy_from_slice(input);
223 self.finished = true;
229 pub use self::fuzzy_chachapoly::ChaCha20Poly1305RFC;