Skip to main content

rhdl_ed25519_scalable_core/
digest.rs

1//! SHA-512 round functions and Ed25519 digest helpers used by the rings
2//! and the controllers. All are RHDL kernels, so they become plain
3//! combinational logic wherever they are called.
4//!
5//! The SHA-512 functions follow FIPS 180-4 section 4.1.3 on 64-bit words.
6//! The digest helpers convert between the two byte orders in play: SHA-512
7//! reads and writes big-endian words, while the cores' ports and Ed25519's
8//! scalars and points are little-endian (word 0 holds bytes 0..8, and the
9//! least significant byte of each word comes first).
10
11use rhdl::prelude::*;
12
13use crate::wire::{ScalarWords, Words512};
14
15/// Rotate the 64-bit `value` right by `amount` bits.
16///
17/// `amount` is 7 bits wide so that the complementary left shift `64 - amount`
18/// fits. Callers pass constants in `1..64`; there is no special case for 0.
19#[kernel]
20pub fn rotr(value: b64, amount: b7) -> b64 {
21    (value >> amount) | (value << (b7(64) - amount))
22}
23
24/// SHA-512 message-schedule function `σ0(x) = ROTR^1 ^ ROTR^8 ^ SHR^7`.
25///
26/// Applied to `W[t-15]` when the schedule extends to word `W[t]`.
27#[kernel]
28pub fn small_sigma0(value: b64) -> b64 {
29    rotr(value, b7(1)) ^ rotr(value, b7(8)) ^ (value >> 7)
30}
31
32/// SHA-512 message-schedule function `σ1(x) = ROTR^19 ^ ROTR^61 ^ SHR^6`.
33///
34/// Applied to `W[t-2]` when the schedule extends to word `W[t]`.
35#[kernel]
36pub fn small_sigma1(value: b64) -> b64 {
37    rotr(value, b7(19)) ^ rotr(value, b7(61)) ^ (value >> 6)
38}
39
40/// SHA-512 round function `Σ0(a) = ROTR^28 ^ ROTR^34 ^ ROTR^39`.
41///
42/// Part of `T2 = Σ0(a) + Maj(a, b, c)`.
43#[kernel]
44pub fn big_sigma0(value: b64) -> b64 {
45    rotr(value, b7(28)) ^ rotr(value, b7(34)) ^ rotr(value, b7(39))
46}
47
48/// SHA-512 round function `Σ1(e) = ROTR^14 ^ ROTR^18 ^ ROTR^41`.
49///
50/// Part of `T1 = h + Σ1(e) + Ch(e, f, g) + K[t] + W[t]`.
51#[kernel]
52pub fn big_sigma1(value: b64) -> b64 {
53    rotr(value, b7(14)) ^ rotr(value, b7(18)) ^ rotr(value, b7(41))
54}
55
56// A case ROM is deliberate here.  It is only 5 Kib, has combinational read
57// semantics matching the three-phase schedule, and does not consume a BRAM
58// read-latency slot on the round path.
59/// SHA-512 round constant `K_t` for round `t` in `0..80` (FIPS 180-4
60/// section 4.2.3).
61///
62/// The default arm returns `K_79`; round numbers 80..128 never occur.
63#[kernel]
64pub fn round_constant(round: b7) -> b64 {
65    match round {
66        Bits::<7>(0) => b64(0x428a2f98d728ae22),
67        Bits::<7>(1) => b64(0x7137449123ef65cd),
68        Bits::<7>(2) => b64(0xb5c0fbcfec4d3b2f),
69        Bits::<7>(3) => b64(0xe9b5dba58189dbbc),
70        Bits::<7>(4) => b64(0x3956c25bf348b538),
71        Bits::<7>(5) => b64(0x59f111f1b605d019),
72        Bits::<7>(6) => b64(0x923f82a4af194f9b),
73        Bits::<7>(7) => b64(0xab1c5ed5da6d8118),
74        Bits::<7>(8) => b64(0xd807aa98a3030242),
75        Bits::<7>(9) => b64(0x12835b0145706fbe),
76        Bits::<7>(10) => b64(0x243185be4ee4b28c),
77        Bits::<7>(11) => b64(0x550c7dc3d5ffb4e2),
78        Bits::<7>(12) => b64(0x72be5d74f27b896f),
79        Bits::<7>(13) => b64(0x80deb1fe3b1696b1),
80        Bits::<7>(14) => b64(0x9bdc06a725c71235),
81        Bits::<7>(15) => b64(0xc19bf174cf692694),
82        Bits::<7>(16) => b64(0xe49b69c19ef14ad2),
83        Bits::<7>(17) => b64(0xefbe4786384f25e3),
84        Bits::<7>(18) => b64(0x0fc19dc68b8cd5b5),
85        Bits::<7>(19) => b64(0x240ca1cc77ac9c65),
86        Bits::<7>(20) => b64(0x2de92c6f592b0275),
87        Bits::<7>(21) => b64(0x4a7484aa6ea6e483),
88        Bits::<7>(22) => b64(0x5cb0a9dcbd41fbd4),
89        Bits::<7>(23) => b64(0x76f988da831153b5),
90        Bits::<7>(24) => b64(0x983e5152ee66dfab),
91        Bits::<7>(25) => b64(0xa831c66d2db43210),
92        Bits::<7>(26) => b64(0xb00327c898fb213f),
93        Bits::<7>(27) => b64(0xbf597fc7beef0ee4),
94        Bits::<7>(28) => b64(0xc6e00bf33da88fc2),
95        Bits::<7>(29) => b64(0xd5a79147930aa725),
96        Bits::<7>(30) => b64(0x06ca6351e003826f),
97        Bits::<7>(31) => b64(0x142929670a0e6e70),
98        Bits::<7>(32) => b64(0x27b70a8546d22ffc),
99        Bits::<7>(33) => b64(0x2e1b21385c26c926),
100        Bits::<7>(34) => b64(0x4d2c6dfc5ac42aed),
101        Bits::<7>(35) => b64(0x53380d139d95b3df),
102        Bits::<7>(36) => b64(0x650a73548baf63de),
103        Bits::<7>(37) => b64(0x766a0abb3c77b2a8),
104        Bits::<7>(38) => b64(0x81c2c92e47edaee6),
105        Bits::<7>(39) => b64(0x92722c851482353b),
106        Bits::<7>(40) => b64(0xa2bfe8a14cf10364),
107        Bits::<7>(41) => b64(0xa81a664bbc423001),
108        Bits::<7>(42) => b64(0xc24b8b70d0f89791),
109        Bits::<7>(43) => b64(0xc76c51a30654be30),
110        Bits::<7>(44) => b64(0xd192e819d6ef5218),
111        Bits::<7>(45) => b64(0xd69906245565a910),
112        Bits::<7>(46) => b64(0xf40e35855771202a),
113        Bits::<7>(47) => b64(0x106aa07032bbd1b8),
114        Bits::<7>(48) => b64(0x19a4c116b8d2d0c8),
115        Bits::<7>(49) => b64(0x1e376c085141ab53),
116        Bits::<7>(50) => b64(0x2748774cdf8eeb99),
117        Bits::<7>(51) => b64(0x34b0bcb5e19b48a8),
118        Bits::<7>(52) => b64(0x391c0cb3c5c95a63),
119        Bits::<7>(53) => b64(0x4ed8aa4ae3418acb),
120        Bits::<7>(54) => b64(0x5b9cca4f7763e373),
121        Bits::<7>(55) => b64(0x682e6ff3d6b2b8a3),
122        Bits::<7>(56) => b64(0x748f82ee5defb2fc),
123        Bits::<7>(57) => b64(0x78a5636f43172f60),
124        Bits::<7>(58) => b64(0x84c87814a1f0ab72),
125        Bits::<7>(59) => b64(0x8cc702081a6439ec),
126        Bits::<7>(60) => b64(0x90befffa23631e28),
127        Bits::<7>(61) => b64(0xa4506cebde82bde9),
128        Bits::<7>(62) => b64(0xbef9a3f7b2c67915),
129        Bits::<7>(63) => b64(0xc67178f2e372532b),
130        Bits::<7>(64) => b64(0xca273eceea26619c),
131        Bits::<7>(65) => b64(0xd186b8c721c0c207),
132        Bits::<7>(66) => b64(0xeada7dd6cde0eb1e),
133        Bits::<7>(67) => b64(0xf57d4f7fee6ed178),
134        Bits::<7>(68) => b64(0x06f067aa72176fba),
135        Bits::<7>(69) => b64(0x0a637dc5a2c898a6),
136        Bits::<7>(70) => b64(0x113f9804bef90dae),
137        Bits::<7>(71) => b64(0x1b710b35131c471b),
138        Bits::<7>(72) => b64(0x28db77f523047d84),
139        Bits::<7>(73) => b64(0x32caab7b40c72493),
140        Bits::<7>(74) => b64(0x3c9ebe0a15c9bebc),
141        Bits::<7>(75) => b64(0x431d67c49c100d4c),
142        Bits::<7>(76) => b64(0x4cc5d4becb3e42b6),
143        Bits::<7>(77) => b64(0x597f299cfc657e2a),
144        Bits::<7>(78) => b64(0x5fcb6fab3ad6faec),
145        _ => b64(0x6c44198c4a475817),
146    }
147}
148
149/// Byte-reverse a 64-bit word (SHA-512 is big-endian, the ports little-endian).
150///
151/// The reversal is pure wiring: no gates, only a permutation of bits.
152#[kernel]
153pub fn reverse_bytes(value: b64) -> b64 {
154    let mut result = b64(0);
155    for byte in 0..8 {
156        // Byte `byte` (counted from the least significant end) moves to
157        // position `7 - byte`.
158        result |= ((value >> (byte * 8) as u128).resize::<8>()).resize::<64>()
159            << ((7 - byte) * 8) as u128;
160    }
161    result
162}
163
164/// A 256-bit little-endian value as four big-endian SHA-512 message words.
165///
166/// Word `k` of the result holds bytes `8k..8k + 8` of the 32-byte string, in
167/// the order SHA-512 consumes them. Used to place a seed or nonce prefix in a
168/// message block.
169#[kernel]
170pub fn words256(value: ScalarWords) -> [b64; 4] {
171    [
172        reverse_bytes(value.words[0]),
173        reverse_bytes(value.words[1]),
174        reverse_bytes(value.words[2]),
175        reverse_bytes(value.words[3]),
176    ]
177}
178
179/// SHA-512 state words as the little-endian 512-bit digest.
180///
181/// `state` is the final chaining value `H0..H7` (big-endian words). The
182/// result is the 64-byte digest read as a little-endian integer, which is how
183/// Ed25519 interprets SHA-512 output.
184#[kernel]
185pub fn digest_words(state: [b64; 8]) -> Words512 {
186    let mut words = [b64(0); 8];
187    for index in 0..8 {
188        words[index] = reverse_bytes(state[index]);
189    }
190    Words512 { words }
191}
192
193/// Clamped secret scalar `a` from `SHA-512(seed)` (RFC 8032 section 5.1.5).
194///
195/// `state` is the final SHA-512 state of the 32-byte seed. The scalar is the
196/// lower 32 bytes of the digest, clamped.
197#[kernel]
198pub fn secret_from_digest(state: [b64; 8]) -> ScalarWords {
199    let digest = digest_words(state);
200    // Lower half of the digest (bytes 0..32) as a little-endian integer.
201    let mut words = [
202        digest.words[0],
203        digest.words[1],
204        digest.words[2],
205        digest.words[3],
206    ];
207    // Clear bits 0..3, so `a` is a multiple of the cofactor 8.
208    words[0] &= b64(0xfffffffffffffff8);
209    // Clear bit 255 and set bit 254, so `a` has a fixed bit length.
210    words[3] = (words[3] & b64(0x3fffffffffffffff)) | b64(0x4000000000000000);
211    ScalarWords { words }
212}
213
214/// Nonce prefix (upper half of `SHA-512(seed)`, bytes 32..64), which
215/// RFC 8032 hashes with the message to derive the per-signature nonce `r`.
216///
217/// `state` is the final SHA-512 state of the 32-byte seed.
218#[kernel]
219pub fn prefix_from_digest(state: [b64; 8]) -> ScalarWords {
220    let digest = digest_words(state);
221    ScalarWords {
222        words: [
223            digest.words[4],
224            digest.words[5],
225            digest.words[6],
226            digest.words[7],
227        ],
228    }
229}
230