Skip to content

Commit 5fa8a5d

Browse files
committed
Add the SHA3-256 hash function
There is a substantial amount of bitcoin listening nodes only reachable by Tor. This is backed by some relatively old research, but I would imagine this remains the case today. When it comes to peer-to-peer gossip, Tor nodes are reachable by a 32-byte ed25519 public key. In practice, most applications will run a Tor daemon, and other applications will communicate with Tor via a Socks5 proxy. With Socks5, you may connect to an IPv4, IPv6, or domain. The problem is to go from Tor public key to onion address (domain), you must take a Sha3-256 hash of a public key and some concatinated data. In effect this makes Tor nodes unreachable for those that do not want to use `RustCrypto` or god forbid the Arti project. Here I propose we add this hash to `bitcoin_hashes`. While it is not explicitly a hash used in bitcoin, many bitcoin nodes use Tor, and more importantly, accept inbound connections using Tor. Others are of course free to use this hash however their heart desires. As far as implementation, from a high level, the hash is a number of permutation operations on a state array of 1600 bits. These bits can be visualized/represented as a 5x5 matrix of 64-bit "lanes." I will leave the rest to the code comment in the file. The Keccak team designed this function to be robust theoretically but easy to implement. Each of these lanes may be mutated directly, and I also try to abuse `for_each` to give SIMD hints. To review the implementation, you may follow the psuedo-code provided by the Keccak team, linked below. ref Psuedo-code: https://keccak.team/keccak_specs_summary.html Full Keccak reference: https://keccak.team/files/Keccak-reference-3.0.pdf Tor spec: https://spec.torproject.org/rend-spec/encoding-onion-addresses.html Tor post: https://archive.torproject.org/websites/lists.torproject.org/pipermail/tor-dev/2017-January/011816.html P2P data: https://github.com/virtu/p2p-metrics
1 parent 61a1161 commit 5fa8a5d

File tree

4 files changed

+248
-3
lines changed

4 files changed

+248
-3
lines changed

hashes/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ pub mod sha384;
116116
pub mod sha512;
117117
pub mod sha512_256;
118118
pub mod siphash24;
119+
pub mod sha3_256;
119120

120121
use core::fmt::{self, Write as _};
121122
use core::{convert, hash};
@@ -153,6 +154,8 @@ pub use sha512_256::Hash as Sha512_256;
153154
/// SipHash-2-4: Alias for the [`siphash24::Hash`] hash type.
154155
#[doc(inline)]
155156
pub use siphash24::Hash as Siphash24;
157+
/// SHA3-256: Alias for the [`sha3_256::Hash`] hash type.
158+
pub use sha3_256::Hash as Sha3_256;
156159

157160
/// Attempted to create a hash from an invalid length slice.
158161
#[deprecated(since = "TBD", note = "unused now that `Hash::from_slice` is deprecated")]

hashes/src/sha3_256/mod.rs

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
// SPDX-License-Identifier: CC0-1.0
2+
3+
//! SHA3-256 from the family of hashes based on the Keccak permutation function.
4+
5+
// The Keccak permutation function is defined by five functions and a state array of N-bits,
6+
// commonly 1600 bits. These 1600 bits are often divided into a 5x5 matrix of `u64` little endian
7+
// numbers, commonly called a "lane." A single Keccak round comprises of a theta, rho, pi, chi, and iota step. Each of these
8+
// steps are easily performed on a lane. Keccakf1600 is a function that performs a number of Keccak rounds, each defined with
9+
// a different round-constant.
10+
//
11+
// SHA3-256 is a hash function that accepts arbitrary data and alters a Keccak state via the
12+
// Keccakf1600 function. Data is chunked into fixed-sizes slices, called padded messages, each with
13+
// a size of "bitrate" or "rate" for short. Each padded message block is XOR'd into parts of the
14+
// state array, followed by a call of Keccakf1600. To pad the final message block, a
15+
// domain-specific identifier is appended to the message (`0x06`), followed by an XOR of the last
16+
// byte with `0x80`.
17+
//
18+
// To read this file, follow the example code: https://keccak.team/keccak_specs_summary.html
19+
// For a detailed specification: https://keccak.team/files/Keccak-reference-3.0.pdf
20+
use core::fmt;
21+
22+
crate::internal_macros::general_hash_type! {
23+
256,
24+
false,
25+
"Output of the SHA3-256 hash function."
26+
}
27+
// The number of rows or columns.
28+
const B: usize = 5;
29+
// 1600 bits are divided into 25, 64-bit "lanes."
30+
const NUM_LANES: usize = B * B;
31+
// Let the word size be 64. Let 2^l = 64, then l is 6. In Keccak, the number of rounds is 12 + 2l.
32+
const NUM_ROUNDS: usize = 24;
33+
// The number of bytes "absorbed" into the state-array per message block.
34+
const RATE: usize = 136;
35+
// The number of lanes a message block may be divided into.
36+
const RATE_LANES: usize = RATE / 8;
37+
38+
// These create non-linear relations between rounds to avoid timing analysis.
39+
const ROUND_CONSTANTS: [u64; NUM_ROUNDS] = [
40+
0x0000000000000001,
41+
0x0000000000008082,
42+
0x800000000000808A,
43+
0x8000000080008000,
44+
0x000000000000808B,
45+
0x0000000080000001,
46+
0x8000000080008081,
47+
0x8000000000008009,
48+
0x000000000000008A,
49+
0x0000000000000088,
50+
0x0000000080008009,
51+
0x000000008000000A,
52+
0x000000008000808B,
53+
0x800000000000008B,
54+
0x8000000000008089,
55+
0x8000000000008003,
56+
0x8000000000008002,
57+
0x8000000000000080,
58+
0x000000000000800A,
59+
0x800000008000000A,
60+
0x8000000080008081,
61+
0x8000000000008080,
62+
0x0000000080000001,
63+
0x8000000080008008,
64+
];
65+
66+
const ROTATION_OFFSETS: [[u32; 5]; 5] = [
67+
[0, 36, 3, 41, 18],
68+
[1, 44, 10, 45, 2],
69+
[62, 6, 43, 15, 61],
70+
[28, 55, 25, 21, 56],
71+
[27, 20, 39, 8, 14],
72+
];
73+
74+
// A `x` and `y` index into a flattened matrix.
75+
#[inline(always)]
76+
const fn ind(x: usize, y: usize) -> usize {
77+
x + B * y
78+
}
79+
80+
// A flattened 5x5 matrix of little-endian `u64`.
81+
#[derive(Clone, Default)]
82+
struct KeccakState([u64; NUM_LANES]);
83+
84+
// A row-column labeled output of the current state.
85+
impl fmt::Debug for KeccakState {
86+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87+
for y in 0..B {
88+
for x in 0..B {
89+
writeln!(f, "[{},{}]: {:016x}", x, y, self.lane(x, y).to_le())?;
90+
}
91+
}
92+
Ok(())
93+
}
94+
}
95+
96+
impl KeccakState {
97+
const fn new() -> Self {
98+
Self([0u64; NUM_LANES])
99+
}
100+
101+
#[inline(always)]
102+
fn assign(&mut self, x: usize, y: usize, val: u64) {
103+
self.0[ind(x, y)] = val;
104+
}
105+
106+
#[inline(always)]
107+
const fn lane(&self, x: usize, y: usize) -> u64 {
108+
self.0[ind(x, y)]
109+
}
110+
111+
#[inline(always)]
112+
const fn column_xor(&self, x: usize) -> u64 {
113+
self.0[ind(x, 0)]
114+
^ self.0[ind(x, 1)]
115+
^ self.0[ind(x, 2)]
116+
^ self.0[ind(x, 3)]
117+
^ self.0[ind(x, 4)]
118+
}
119+
120+
#[inline(always)]
121+
fn xor_assign(&mut self, x: usize, y: usize, val: u64) {
122+
self.0[ind(x, y)] ^= val
123+
}
124+
125+
#[inline(always)]
126+
const fn chi(&self, x: usize, y: usize) -> u64 {
127+
self.lane(x, y) ^ (!self.lane((x + 1) % B, y) & self.lane((x + 2) % B, y))
128+
}
129+
}
130+
131+
fn keccak_round(state: &mut KeccakState, round_constant: u64) {
132+
// Theta
133+
let mut c: [u64; B] = [0; B];
134+
(0..B).for_each(|x| {
135+
c[x] = state.column_xor(x);
136+
});
137+
let mut d: [u64; B] = [0; B];
138+
(0..B).for_each(|x| {
139+
// Avoid an underflow here with a mod trick
140+
d[x] = c[(x + B - 1) % B] ^ (c[(x + 1) % B].rotate_left(1));
141+
});
142+
(0..B).for_each(|x| {
143+
(0..B).for_each(|y| {
144+
state.xor_assign(x, y, d[x]);
145+
});
146+
});
147+
148+
// Rho and Pi combined
149+
let mut b = KeccakState::default();
150+
(0..B).for_each(|x| {
151+
(0..B).for_each(|y| {
152+
let offset = ROTATION_OFFSETS[x][y];
153+
let val = state.lane(x, y).rotate_left(offset);
154+
let b_y = ((2 * x) + (3 * y)) % B;
155+
b.assign(y, b_y, val);
156+
});
157+
});
158+
159+
// Chi
160+
(0..B).for_each(|x| {
161+
(0..B).for_each(|y| {
162+
state.assign(x, y, b.chi(x, y));
163+
});
164+
});
165+
166+
// Iota
167+
state.xor_assign(0, 0, round_constant);
168+
}
169+
170+
fn keccakf1600(state: &mut KeccakState) {
171+
for c in ROUND_CONSTANTS {
172+
keccak_round(state, c);
173+
}
174+
}
175+
176+
/// Engine to compute the Sha3-256 hash function.
177+
#[derive(Debug, Clone, Default)]
178+
pub struct HashEngine {
179+
state: KeccakState,
180+
bytes_hashed: u64,
181+
}
182+
183+
impl HashEngine {
184+
/// Construct a new Sha3-256 hash engine.
185+
pub const fn new() -> Self {
186+
Self { state: KeccakState::new(), bytes_hashed: 0 }
187+
}
188+
189+
fn absorb(&mut self, block: [u8; RATE]) {
190+
for lane in 0..RATE_LANES {
191+
let x = lane % 5;
192+
let y = lane / 5;
193+
let mut pad_block = [0u8; 8];
194+
pad_block.copy_from_slice(&block[8 * lane..8 * lane + 8]);
195+
let shuffle = u64::from_le_bytes(pad_block);
196+
self.state.xor_assign(x, y, shuffle);
197+
}
198+
}
199+
}
200+
201+
impl crate::HashEngine for HashEngine {
202+
type Hash = Hash;
203+
type Bytes = [u8; 32];
204+
const BLOCK_SIZE: usize = RATE;
205+
206+
fn input(&mut self, mut data: &[u8]) {
207+
while data.len().ge(&RATE) {
208+
let mut block = [0u8; RATE];
209+
block.copy_from_slice(&data[..RATE]);
210+
self.bytes_hashed += RATE as u64;
211+
self.absorb(block);
212+
keccakf1600(&mut self.state);
213+
data = &data[RATE..];
214+
}
215+
let mut final_block = [0u8; RATE];
216+
final_block[..data.len()].copy_from_slice(data);
217+
self.bytes_hashed += data.len() as u64;
218+
final_block[data.len()] = 0x06;
219+
final_block[RATE - 1] ^= 0x80;
220+
self.absorb(final_block);
221+
keccakf1600(&mut self.state);
222+
}
223+
224+
fn n_bytes_hashed(&self) -> u64 {
225+
self.bytes_hashed
226+
}
227+
228+
fn finalize(self) -> Self::Hash {
229+
let mut out = [0u8; 32];
230+
out[..8].copy_from_slice(&self.state.lane(0, 0).to_le_bytes());
231+
out[8..16].copy_from_slice(&self.state.lane(1, 0).to_le_bytes());
232+
out[16..24].copy_from_slice(&self.state.lane(2, 0).to_le_bytes());
233+
out[24..].copy_from_slice(&self.state.lane(3, 0).to_le_bytes());
234+
Hash(out)
235+
}
236+
}

hashes/tests/api.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@
1414
// Import using module style e.g., `sha256::Hash`.
1515
use bitcoin_hashes::{
1616
hash160, hash_newtype, hkdf, hmac, ripemd160, sha1, sha256, sha256d, sha256t, sha256t_tag,
17-
sha384, sha512, sha512_256, siphash24, FromSliceError, Hash, HashEngine,
17+
sha384, sha3_256, sha512, sha512_256, siphash24, FromSliceError, Hash, HashEngine,
1818
};
1919
// Import using type alias style e.g., `Sha256`.
2020
use bitcoin_hashes::{
21-
Hash160, Hkdf, Hmac, HmacEngine, Ripemd160, Sha1, Sha256, Sha256d, Sha256t, Sha384, Sha512,
21+
Hash160, Hkdf, Hmac, HmacEngine, Ripemd160, Sha1, Sha256, Sha256d, Sha256t, Sha384, Sha3_256, Sha512,
2222
Sha512_256, Siphash24,
2323
};
2424

@@ -56,6 +56,7 @@ struct Hashes<T: Hash> {
5656
j: sha512::Hash,
5757
k: sha512_256::Hash,
5858
l: siphash24::Hash,
59+
m: sha3_256::Hash,
5960
}
6061

6162
impl Hashes<Sha256> {
@@ -78,6 +79,7 @@ impl Hashes<Sha256> {
7879
j: Sha512::hash(&[]),
7980
k: Sha512_256::hash(&[]),
8081
l: siphash,
82+
m: Sha3_256::hash(&[]),
8183
}
8284
}
8385
}
@@ -98,6 +100,7 @@ struct Engines {
98100
i: sha512::HashEngine,
99101
j: sha512_256::HashEngine,
100102
k: siphash24::HashEngine,
103+
l: sha3_256::HashEngine,
101104
}
102105

103106
impl Engines {
@@ -114,6 +117,7 @@ impl Engines {
114117
i: sha512::HashEngine::new(),
115118
j: sha512_256::HashEngine::new(),
116119
k: siphash24::HashEngine::with_keys(0, 0),
120+
l: sha3_256::HashEngine::new(),
117121
}
118122
}
119123
}
@@ -148,6 +152,7 @@ struct Default {
148152
g: sha384::HashEngine,
149153
h: sha512::HashEngine,
150154
i: sha512_256::HashEngine,
155+
j: sha3_256::HashEngine,
151156
}
152157

153158
/// Hash types that require a key.

hashes/tests/regression.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
#![allow(clippy::uninlined_format_args)] // Allow `format!("{}", x)`instead of enforcing `format!("{x}")`
1010

1111
use bitcoin_hashes::{
12-
hash160, ripemd160, sha1, sha256, sha256d, sha256t, sha384, sha512, sha512_256, siphash24,
12+
hash160, ripemd160, sha1, sha256, sha256d, sha256t, sha384, sha3_256, sha512, sha512_256, siphash24,
1313
HashEngine as _, HmacEngine,
1414
};
1515

@@ -36,6 +36,7 @@ impl_regression_test! {
3636
regression_sha256, sha256, "d291c6c5a07fa1d9315cdae090ebe14169fbe0a219cd55a48d0d2104eab6ec51";
3737
regression_sha256d, sha256d, "93a743b022290bde3233a619b21aaebe06c5cf5cc959464c41be35711e37731b";
3838
regression_sha384, sha384, "f545bd83d297978d47a7f26b858a54188499dfb4d7d570a6a2362c765031d57a29d7e002df5e34d184e70b65a4f47153";
39+
regression_sha3_256, sha3_256, "9479c957c295f4e42a31dbd571062610c2c3435310b27a9548b83c0b45f4c9b3";
3940
regression_sha512, sha512, "057d0a37e9e0ac9a93acde0752748da059a27bcf946c7af00692ac1a95db8d21f965f40af22efc4710f100f8d3e43f79f77b1f48e1e400a95b7344b7bc0dfd10";
4041
regression_sha512_256, sha512_256, "e204244c429b5bca037a2a8a6e7ed8a42b808ceaff182560840bb8c5c8e9a2ec";
4142
}

0 commit comments

Comments
 (0)