Skip to content

Commit 5b65e02

Browse files
committed
keccak: Pass fuzz tests vs OpenSSL
1 parent 06bca11 commit 5b65e02

File tree

7 files changed

+196
-92
lines changed

7 files changed

+196
-92
lines changed

constantine.nimble

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,8 @@ const testDesc: seq[tuple[path: string, useGMP: bool]] = @[
362362

363363
# Hashing vs OpenSSL
364364
# ----------------------------------------------------------
365-
("tests/t_hash_sha256_vs_openssl.nim", false), # skip OpenSSL tests on Windows
365+
("tests/t_hash_sha256_vs_openssl.nim", false),
366+
("tests/t_hash_sha3_vs_openssl.nim", false),
366367

367368
# Ciphers
368369
# ----------------------------------------------------------

constantine/hashes.nim

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,15 @@ func hash*(
6363
# Exports
6464
# -----------------------------------------------------------------------
6565

66-
import ./hashes/h_sha256
67-
export h_sha256
66+
import ./hashes/[
67+
h_keccak,
68+
h_sha256
69+
]
70+
export
71+
h_keccak,
72+
h_sha256
6873

69-
static: doAssert sha256 is CryptoHash
74+
static:
75+
doAssert keccak256 is CryptoHash
76+
doAssert sha256 is CryptoHash
77+
doAssert sha3_256 is CryptoHash

constantine/hashes/h_keccak.nim

Lines changed: 11 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,8 @@
66
# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0).
77
# at your option. This file may not be copied, modified, or distributed except according to those terms.
88

9-
import ../zoo_exports
10-
119
import
12-
../platforms/[abstractions, views],
10+
constantine/platforms/[abstractions, views],
1311
./keccak/keccak_generic
1412

1513
# Keccak, the hash function underlying SHA3
@@ -93,9 +91,8 @@ type
9391
# Similarly after a squeeze, absorb_offset is incremented by the sponge rate.
9492
# The real offset can be recovered with a substraction
9593
# to properly update the state.
96-
9794
H {.align: 64.}: KeccakState
98-
buf {.align: 64.}: array[bits div 8, byte]
95+
buf {.align: 64.}: array[200 - 2*(bits div 8), byte]
9996
absorb_offset: int32
10097
squeeze_offset: int32
10198

@@ -125,11 +122,13 @@ func absorbBuffer(ctx: var KeccakContext) {.inline.} =
125122

126123
template digestSize*(H: type KeccakContext): int =
127124
## Returns the output size in bytes
128-
KeccakContext.bits shr 3
125+
# hardcoded for now or concept match issue with CryptoHash
126+
32
129127

130128
template internalBlockSize*(H: type KeccakContext): int =
131129
## Returns the byte size of the hash function ingested blocks
132-
2 * (KeccakContext.bits shr 3)
130+
# hardcoded for now or concept match issue with CryptoHash
131+
200
133132

134133
func init*(ctx: var KeccakContext) {.inline.} =
135134
## Initialize or reinitialize a Keccak context
@@ -145,9 +144,6 @@ func absorb*(ctx: var KeccakContext, message: openArray[byte]) =
145144
## Additionally ensure that the message(s) passed were stored
146145
## in memory considered secure for your threat model.
147146

148-
if message.len == 0:
149-
return
150-
151147
var pos = int ctx.absorb_offset
152148
var cur = 0
153149
var bytesLeft = message.len
@@ -183,13 +179,11 @@ func absorb*(ctx: var KeccakContext, message: openArray[byte]) =
183179
ctx.buf.rawCopy(dStart = pos, message, sStart = cur, len = bytesLeft)
184180

185181
# Epilogue
186-
ctx.absorb_offset = int32 bytesLeft
182+
ctx.absorb_offset = int32(pos+bytesLeft)
187183
# Signal that the next squeeze transition needs a permute
188184
ctx.squeeze_offset = int32 ctx.rate()
189185

190186
func squeeze*(ctx: var KeccakContext, digest: var openArray[byte]) =
191-
if digest.len == 0:
192-
return
193187

194188
var pos = ctx.squeeze_offset
195189
var cur = 0
@@ -246,9 +240,11 @@ func update*(ctx: var KeccakContext, message: openArray[byte]) =
246240
## in memory considered secure for your threat model.
247241
ctx.absorb(message)
248242

249-
func finish*[N: static int](ctx: var KeccakContext, digest: var array[N, byte]) =
243+
func finish*(ctx: var KeccakContext, digest: var array[32, byte]) =
250244
## Finalize a Keccak computation and output the
251-
## message digest to the `digest` buffer
245+
## message digest to the `digest` buffer.
246+
##
247+
## An `update` MUST be called before finish even with empty message.
252248
##
253249
## Security note: this does not clear the internal buffer.
254250
## if sensitive content is used, use "ctx.clear()"
@@ -260,16 +256,3 @@ func clear*(ctx: var KeccakContext) =
260256
## Clear the context internal buffers
261257
# TODO: ensure compiler cannot optimize the code away
262258
ctx.reset()
263-
264-
when isMainModule:
265-
import constantine/serialization/codecs
266-
267-
var msg: array[32, byte]
268-
var digest: array[32, byte]
269-
var ctx: keccak256
270-
271-
ctx.init()
272-
ctx.update(msg)
273-
ctx.finish(digest)
274-
275-
echo digest.toHex()

constantine/hashes/h_sha256.nim

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@
66
# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0).
77
# at your option. This file may not be copied, modified, or distributed except according to those terms.
88

9-
import ../zoo_exports
9+
import constantine/zoo_exports
1010

1111
import
12-
../platforms/[abstractions, views],
13-
../serialization/endians,
12+
constantine/platforms/[abstractions, views],
13+
constantine/serialization/endians,
1414
./sha256/sha256_generic
1515

1616
when UseASM_X86_32:

constantine/hashes/keccak/keccak_generic.nim

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -231,10 +231,10 @@ func xorInSingle(H: var KeccakState, val: byte, offset: int) {.inline.} =
231231
let lane = uint64(val) shl slot # All bits but the one set in `val` are 0, and 0 is neutral element of xor
232232
H.state[offset shr 3] ^= lane
233233

234-
func xorInBlock_generic(H: var KeccakState, msg: array[64, byte]) {.inline.} =
234+
func xorInBlock_generic(H: var KeccakState, msg: array[200 - 2*32, byte]) {.inline.} =
235235
## Add new data into the Keccak state
236236
# This can benefit from vectorized instructions
237-
for i in 0 ..< 8:
237+
for i in 0 ..< msg.len div 8:
238238
H.state[i] ^= uint64.fromBytes(msg, i*8, littleEndian)
239239

240240
func xorInPartial*(H: var KeccakState, msg: openArray[byte]) =
@@ -254,7 +254,7 @@ func xorInPartial*(H: var KeccakState, msg: openArray[byte]) =
254254
# Lastly, this is only called when transitioning
255255
# between absorbing and squeezing, for hashing
256256
# this means once, however long a message to hash is.
257-
var blck: array[64, byte] # zero-init
257+
var blck: array[200 - 2*32, byte] # zero-init
258258
rawCopy(blck, 0, msg, 0, msg.len)
259259
H.xorInBlock_generic(blck)
260260

@@ -283,7 +283,7 @@ func copyOutPartial*(
283283
# Implementation details:
284284
# we could avoid a temporary block
285285
# see `xorInPartial` for rationale
286-
var blck {.noInit.}: array[64, byte]
286+
var blck {.noInit.}: array[200 - 2*32, byte]
287287
H.copyOutWords(blck)
288288
rawCopy(dst, 0, blck, hByteOffset, dst.len)
289289

@@ -303,8 +303,8 @@ func hashMessageBlocks_generic*(
303303
## a permutation is needed in-between
304304

305305
var message = message
306-
const rate = 64 # TODO: make a generic Keccak state with auto-derived rate
307-
const numRounds = 24 # TODO: auto derive number of rounds
306+
const rate = 200 - 2*32 # TODO: make a generic Keccak state with auto-derived rate
307+
const numRounds = 24 # TODO: auto derive number of rounds
308308
for _ in 0 ..< numBlocks:
309309
let msg = cast[ptr array[rate, byte]](message)
310310
H.xorInBlock_generic(msg[])
@@ -321,8 +321,8 @@ func squeezeDigestBlocks_generic*(
321321
## i.e. previous operation cannot be an absorb
322322
## a permutation is needed in-between
323323
var digest = digest
324-
const rate = 64 # TODO: make a generic Keccak state with auto-derived rate
325-
const numRounds = 24 # TODO: auto derive number of rounds
324+
const rate = 200 - 2*32 # TODO: make a generic Keccak state with auto-derived rate
325+
const numRounds = 24 # TODO: auto derive number of rounds
326326
for _ in 0 ..< numBlocks:
327327
let msg = cast[ptr array[rate, byte]](digest)
328328
H.copyOutWords(msg[])

tests/t_hash_sha256_vs_openssl.nim

Lines changed: 30 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -28,30 +28,19 @@ else:
2828
# OpenSSL wrapper
2929
# --------------------------------------------------------------------
3030

31-
# OpenSSL removed direct use of their SHA256 function. https://github.com/openssl/openssl/commit/4d49b68504cc494e552bce8e0b82ec8b501d5abe
32-
# It isn't accessible anymore in Windows CI on Github Action.
33-
# But the new API isn't expose on Linux :/
34-
35-
# TODO: fix Windows
36-
when not defined(windows):
37-
proc SHA256[T: byte|char](
38-
msg: openarray[T],
39-
digest: ptr array[32, byte] = nil
40-
): ptr array[32, byte] {.noconv, dynlib: DLLSSLName, importc.}
41-
42-
# proc EVP_Q_digest[T: byte|char](
43-
# ossl_libctx: pointer,
44-
# algoName: cstring,
45-
# propq: cstring,
46-
# data: openArray[T],
47-
# digest: var array[32, byte],
48-
# size: ptr uint): int32 {.noconv, dynlib: DLLSSLName, importc.}
49-
50-
proc SHA256_OpenSSL[T: byte|char](
51-
digest: var array[32, byte],
52-
s: openArray[T]) =
53-
discard SHA256(s, digest.addr)
54-
# discard EVP_Q_digest(nil, "SHA256", nil, s, digest, nil)
31+
proc EVP_Q_digest[T: byte|char](
32+
ossl_libctx: pointer,
33+
algoName: cstring,
34+
propq: cstring,
35+
data: openArray[T],
36+
digest: var array[32, byte],
37+
size: ptr uint): int32 {.noconv, dynlib: DLLSSLName, importc.}
38+
39+
proc SHA256_OpenSSL[T: byte|char](
40+
digest: var array[32, byte],
41+
s: openArray[T]) =
42+
# discard SHA256(s, digest.addr)
43+
discard EVP_Q_digest(nil, "SHA256", nil, s, digest, nil)
5544

5645
# Test
5746
# --------------------------------------------------------------------
@@ -84,16 +73,15 @@ proc sanityABC2 =
8473

8574
doAssert bufCt == hashed
8675

87-
when not defined(windows):
88-
proc innerTest(rng: var RngState, sizeRange: Slice[int]) =
89-
let size = rng.random_unsafe(sizeRange)
90-
let msg = rng.random_byte_seq(size)
76+
proc innerTest(rng: var RngState, sizeRange: Slice[int]) =
77+
let size = rng.random_unsafe(sizeRange)
78+
let msg = rng.random_byte_seq(size)
9179

92-
var bufCt, bufOssl: array[32, byte]
80+
var bufCt, bufOssl: array[32, byte]
9381

94-
sha256.hash(bufCt, msg)
95-
SHA256_OpenSSL(bufOssl, msg)
96-
doAssert bufCt == bufOssl, "Test failed with message of length " & $size
82+
sha256.hash(bufCt, msg)
83+
SHA256_OpenSSL(bufOssl, msg)
84+
doAssert bufCt == bufOssl, "Test failed with message of length " & $size
9785

9886
proc chunkTest(rng: var RngState, sizeRange: Slice[int]) =
9987
let size = rng.random_unsafe(sizeRange)
@@ -131,12 +119,9 @@ proc main() =
131119
var rng: RngState
132120
rng.seed(0xFACADE)
133121

134-
when not defined(windows):
135-
echo "SHA256 - 0 <= size < 64 - exhaustive"
136-
for i in 0 ..< 64:
137-
rng.innerTest(i .. i)
138-
else:
139-
echo "SHA256 - 0 <= size < 64 - exhaustive [SKIPPED]"
122+
echo "SHA256 - 0 <= size < 64 - exhaustive"
123+
for i in 0 ..< 64:
124+
rng.innerTest(i .. i)
140125

141126
echo "SHA256 - 0 <= size < 64 - exhaustive chunked"
142127
for i in 0 ..< 64:
@@ -146,18 +131,14 @@ proc main() =
146131
for _ in 0 ..< SmallSizeIters:
147132
rng.chunkTest(0 ..< 1024)
148133

149-
when not defined(windows):
150-
echo "SHA256 - 64 <= size < 1024B"
151-
for _ in 0 ..< SmallSizeIters:
152-
rng.innerTest(0 ..< 1024)
153-
154-
echo "SHA256 - 1MB <= size < 50MB"
155-
for _ in 0 ..< LargeSizeIters:
156-
rng.innerTest(1_000_000 ..< 50_000_000)
134+
echo "SHA256 - 64 <= size < 1024B"
135+
for _ in 0 ..< SmallSizeIters:
136+
rng.innerTest(0 ..< 1024)
157137

158-
echo "SHA256 - Differential testing vs OpenSSL - SUCCESS"
138+
echo "SHA256 - 1MB <= size < 50MB"
139+
for _ in 0 ..< LargeSizeIters:
140+
rng.innerTest(1_000_000 ..< 50_000_000)
159141

160-
else:
161-
echo "SHA256 - Differential testing vs OpenSSL - [SKIPPED]"
142+
echo "SHA256 - Differential testing vs OpenSSL - SUCCESS"
162143

163144
main()

0 commit comments

Comments
 (0)