From 53c007ae7ba14fe772cbc5f29185cacf5187e54e Mon Sep 17 00:00:00 2001 From: Vindaar Date: Tue, 31 Dec 2024 12:52:24 +0100 Subject: [PATCH 01/12] [ripemd160] add port of bitcoin-core ripemd160 implementation --- .../hashes/ripemd160/ripemd160_generic.nim | 338 ++++++++++++++++++ 1 file changed, 338 insertions(+) create mode 100644 constantine/hashes/ripemd160/ripemd160_generic.nim diff --git a/constantine/hashes/ripemd160/ripemd160_generic.nim b/constantine/hashes/ripemd160/ripemd160_generic.nim new file mode 100644 index 00000000..4034ea57 --- /dev/null +++ b/constantine/hashes/ripemd160/ripemd160_generic.nim @@ -0,0 +1,338 @@ +# Constantine +# Copyright (c) 2018-2019 Status Research & Development GmbH +# Copyright (c) 2020-Present Mamy André-Ratsimbazafy +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0). +# at your option. This file may not be copied, modified, or distributed except according to those terms. + +## Port of the bitcoin-core implementation of ripemd160: +## https://github.com/bitcoin-core/gui/blob/228aba2c4d9ac0b2ca3edd3c2cdf0a92e55f669b/src/crypto/ripemd160.cpp + +import + constantine/serialization/endians, + constantine/platforms/abstractions +import std / macros + +type Word* = uint32 +const + DigestSize* = 20 + BlockSize* = 64 + HashSize* = DigestSize div sizeof(Word) # 5 uint32 = 20 bytes = 160 bits + +type + Ripemd160Context* = object + s*: array[HashSize, uint32] + buf {.align: 64.}: array[BlockSize, byte] + bytes: uint64 + +template f1(x, y, z: uint32): untyped = x xor y xor z +template f2(x, y, z: uint32): untyped = (x and y) or ((not x) and z) +template f3(x, y, z: uint32): untyped = (x or (not y)) xor z +template f4(x, y, z: uint32): untyped = (x and z) or (y and (not z)) +template f5(x, y, z: uint32): untyped = x xor (y or (not z)) + +proc initialize*(s: var array[HashSize, uint32]) = + ## Initialize RIPEMD-160 state. + s[0] = 0x67452301'u32 + s[1] = 0xEFCDAB89'u32 + s[2] = 0x98BADCFE'u32 + s[3] = 0x10325476'u32 + s[4] = 0xC3D2E1F0'u32 + +proc initRipemdCtx(): Ripemd160Context = + result.s.initialize() + +template rol(x: uint32, i: int32): untyped = (x shl i) or (x shr (32 - i)) + +template Round(a: var uint32, b: uint32, c: var uint32, d, e, f, x, k: uint32, r: int32): untyped = + a = rol(a + f + x + k, r) + e + c = rol(c, 10) + +template R11(a: var uint32, b: uint32, c: var uint32, d, e, x: uint32, r: int32): untyped = Round(a, b, c, d, e, f1(b, c, d), x, 0, r) +template R21(a: var uint32, b: uint32, c: var uint32, d, e, x: uint32, r: int32): untyped = Round(a, b, c, d, e, f2(b, c, d), x, 0x5A827999'u32, r) +template R31(a: var uint32, b: uint32, c: var uint32, d, e, x: uint32, r: int32): untyped = Round(a, b, c, d, e, f3(b, c, d), x, 0x6ED9EBA1'u32, r) +template R41(a: var uint32, b: uint32, c: var uint32, d, e, x: uint32, r: int32): untyped = Round(a, b, c, d, e, f4(b, c, d), x, 0x8F1BBCDC'u32, r) +template R51(a: var uint32, b: uint32, c: var uint32, d, e, x: uint32, r: int32): untyped = Round(a, b, c, d, e, f5(b, c, d), x, 0xA953FD4E'u32, r) + +template R12(a: var uint32, b: uint32, c: var uint32, d, e, x: uint32, r: int32): untyped = Round(a, b, c, d, e, f5(b, c, d), x, 0x50A28BE6'u32, r) +template R22(a: var uint32, b: uint32, c: var uint32, d, e, x: uint32, r: int32): untyped = Round(a, b, c, d, e, f4(b, c, d), x, 0x5C4DD124'u32, r) +template R32(a: var uint32, b: uint32, c: var uint32, d, e, x: uint32, r: int32): untyped = Round(a, b, c, d, e, f3(b, c, d), x, 0x6D703EF3'u32, r) +template R42(a: var uint32, b: uint32, c: var uint32, d, e, x: uint32, r: int32): untyped = Round(a, b, c, d, e, f2(b, c, d), x, 0x7A6D76E9'u32, r) +template R52(a: var uint32, b: uint32, c: var uint32, d, e, x: uint32, r: int32): untyped = Round(a, b, c, d, e, f1(b, c, d), x, 0, r) + +template ReadLE32(chunk: openArray[byte], offset: int): untyped = uint32.fromBytes(chunk, offset, littleEndian) + +macro generateWs(): untyped = + ## Generates `var w = ReadLE(chunk, *4)` for `idx in [0,16]`. + result = newStmtList() + for i in 0 ..< 16: + let wId = ident("w" & $i) + let idx = i * 4 + result.add quote do: + var `wId` = ReadLE32(chunk, `idx`) + +proc transform(s: var array[HashSize, uint32], chunk: openArray[byte]) = + ## Perform a RIPEMD-160 transformation, processing a 64-byte chunk. */ + var + a1 = s[0] + b1 = s[1] + c1 = s[2] + d1 = s[3] + e1 = s[4] + a2 = a1 + b2 = b1 + c2 = c1 + d2 = d1 + e2 = e1 + # generate `w` variables from `chunk` + generateWs() + + R11(a1, b1, c1, d1, e1, w0 , 11) + R12(a2, b2, c2, d2, e2, w5 , 8) + R11(e1, a1, b1, c1, d1, w1 , 14) + R12(e2, a2, b2, c2, d2, w14, 9) + R11(d1, e1, a1, b1, c1, w2 , 15) + R12(d2, e2, a2, b2, c2, w7 , 9) + R11(c1, d1, e1, a1, b1, w3 , 12) + R12(c2, d2, e2, a2, b2, w0 , 11) + R11(b1, c1, d1, e1, a1, w4 , 5) + R12(b2, c2, d2, e2, a2, w9 , 13) + R11(a1, b1, c1, d1, e1, w5 , 8) + R12(a2, b2, c2, d2, e2, w2 , 15) + R11(e1, a1, b1, c1, d1, w6 , 7) + R12(e2, a2, b2, c2, d2, w11, 15) + R11(d1, e1, a1, b1, c1, w7 , 9) + R12(d2, e2, a2, b2, c2, w4 , 5) + R11(c1, d1, e1, a1, b1, w8 , 11) + R12(c2, d2, e2, a2, b2, w13, 7) + R11(b1, c1, d1, e1, a1, w9 , 13) + R12(b2, c2, d2, e2, a2, w6 , 7) + R11(a1, b1, c1, d1, e1, w10, 14) + R12(a2, b2, c2, d2, e2, w15, 8) + R11(e1, a1, b1, c1, d1, w11, 15) + R12(e2, a2, b2, c2, d2, w8 , 11) + R11(d1, e1, a1, b1, c1, w12, 6) + R12(d2, e2, a2, b2, c2, w1 , 14) + R11(c1, d1, e1, a1, b1, w13, 7) + R12(c2, d2, e2, a2, b2, w10, 14) + R11(b1, c1, d1, e1, a1, w14, 9) + R12(b2, c2, d2, e2, a2, w3 , 12) + R11(a1, b1, c1, d1, e1, w15, 8) + R12(a2, b2, c2, d2, e2, w12, 6) + + R21(e1, a1, b1, c1, d1, w7 , 7) + R22(e2, a2, b2, c2, d2, w6 , 9) + R21(d1, e1, a1, b1, c1, w4 , 6) + R22(d2, e2, a2, b2, c2, w11, 13) + R21(c1, d1, e1, a1, b1, w13, 8) + R22(c2, d2, e2, a2, b2, w3 , 15) + R21(b1, c1, d1, e1, a1, w1 , 13) + R22(b2, c2, d2, e2, a2, w7 , 7) + R21(a1, b1, c1, d1, e1, w10, 11) + R22(a2, b2, c2, d2, e2, w0 , 12) + R21(e1, a1, b1, c1, d1, w6 , 9) + R22(e2, a2, b2, c2, d2, w13, 8) + R21(d1, e1, a1, b1, c1, w15, 7) + R22(d2, e2, a2, b2, c2, w5 , 9) + R21(c1, d1, e1, a1, b1, w3 , 15) + R22(c2, d2, e2, a2, b2, w10, 11) + R21(b1, c1, d1, e1, a1, w12, 7) + R22(b2, c2, d2, e2, a2, w14, 7) + R21(a1, b1, c1, d1, e1, w0 , 12) + R22(a2, b2, c2, d2, e2, w15, 7) + R21(e1, a1, b1, c1, d1, w9 , 15) + R22(e2, a2, b2, c2, d2, w8 , 12) + R21(d1, e1, a1, b1, c1, w5 , 9) + R22(d2, e2, a2, b2, c2, w12, 7) + R21(c1, d1, e1, a1, b1, w2 , 11) + R22(c2, d2, e2, a2, b2, w4 , 6) + R21(b1, c1, d1, e1, a1, w14, 7) + R22(b2, c2, d2, e2, a2, w9 , 15) + R21(a1, b1, c1, d1, e1, w11, 13) + R22(a2, b2, c2, d2, e2, w1 , 13) + R21(e1, a1, b1, c1, d1, w8 , 12) + R22(e2, a2, b2, c2, d2, w2 , 11) + + R31(d1, e1, a1, b1, c1, w3 , 11) + R32(d2, e2, a2, b2, c2, w15, 9) + R31(c1, d1, e1, a1, b1, w10, 13) + R32(c2, d2, e2, a2, b2, w5 , 7) + R31(b1, c1, d1, e1, a1, w14, 6) + R32(b2, c2, d2, e2, a2, w1 , 15) + R31(a1, b1, c1, d1, e1, w4 , 7) + R32(a2, b2, c2, d2, e2, w3 , 11) + R31(e1, a1, b1, c1, d1, w9 , 14) + R32(e2, a2, b2, c2, d2, w7 , 8) + R31(d1, e1, a1, b1, c1, w15, 9) + R32(d2, e2, a2, b2, c2, w14, 6) + R31(c1, d1, e1, a1, b1, w8 , 13) + R32(c2, d2, e2, a2, b2, w6 , 6) + R31(b1, c1, d1, e1, a1, w1 , 15) + R32(b2, c2, d2, e2, a2, w9 , 14) + R31(a1, b1, c1, d1, e1, w2 , 14) + R32(a2, b2, c2, d2, e2, w11, 12) + R31(e1, a1, b1, c1, d1, w7 , 8) + R32(e2, a2, b2, c2, d2, w8 , 13) + R31(d1, e1, a1, b1, c1, w0 , 13) + R32(d2, e2, a2, b2, c2, w12, 5) + R31(c1, d1, e1, a1, b1, w6 , 6) + R32(c2, d2, e2, a2, b2, w2 , 14) + R31(b1, c1, d1, e1, a1, w13, 5) + R32(b2, c2, d2, e2, a2, w10, 13) + R31(a1, b1, c1, d1, e1, w11, 12) + R32(a2, b2, c2, d2, e2, w0 , 13) + R31(e1, a1, b1, c1, d1, w5 , 7) + R32(e2, a2, b2, c2, d2, w4 , 7) + R31(d1, e1, a1, b1, c1, w12, 5) + R32(d2, e2, a2, b2, c2, w13, 5) + + R41(c1, d1, e1, a1, b1, w1 , 11) + R42(c2, d2, e2, a2, b2, w8 , 15) + R41(b1, c1, d1, e1, a1, w9 , 12) + R42(b2, c2, d2, e2, a2, w6 , 5) + R41(a1, b1, c1, d1, e1, w11, 14) + R42(a2, b2, c2, d2, e2, w4 , 8) + R41(e1, a1, b1, c1, d1, w10, 15) + R42(e2, a2, b2, c2, d2, w1 , 11) + R41(d1, e1, a1, b1, c1, w0 , 14) + R42(d2, e2, a2, b2, c2, w3 , 14) + R41(c1, d1, e1, a1, b1, w8 , 15) + R42(c2, d2, e2, a2, b2, w11, 14) + R41(b1, c1, d1, e1, a1, w12, 9) + R42(b2, c2, d2, e2, a2, w15, 6) + R41(a1, b1, c1, d1, e1, w4 , 8) + R42(a2, b2, c2, d2, e2, w0 , 14) + R41(e1, a1, b1, c1, d1, w13, 9) + R42(e2, a2, b2, c2, d2, w5 , 6) + R41(d1, e1, a1, b1, c1, w3 , 14) + R42(d2, e2, a2, b2, c2, w12, 9) + R41(c1, d1, e1, a1, b1, w7 , 5) + R42(c2, d2, e2, a2, b2, w2 , 12) + R41(b1, c1, d1, e1, a1, w15, 6) + R42(b2, c2, d2, e2, a2, w13, 9) + R41(a1, b1, c1, d1, e1, w14, 8) + R42(a2, b2, c2, d2, e2, w9 , 12) + R41(e1, a1, b1, c1, d1, w5 , 6) + R42(e2, a2, b2, c2, d2, w7 , 5) + R41(d1, e1, a1, b1, c1, w6 , 5) + R42(d2, e2, a2, b2, c2, w10, 15) + R41(c1, d1, e1, a1, b1, w2 , 12) + R42(c2, d2, e2, a2, b2, w14, 8) + + R51(b1, c1, d1, e1, a1, w4 , 9) + R52(b2, c2, d2, e2, a2, w12, 8) + R51(a1, b1, c1, d1, e1, w0 , 15) + R52(a2, b2, c2, d2, e2, w15, 5) + R51(e1, a1, b1, c1, d1, w5 , 5) + R52(e2, a2, b2, c2, d2, w10, 12) + R51(d1, e1, a1, b1, c1, w9 , 11) + R52(d2, e2, a2, b2, c2, w4 , 9) + R51(c1, d1, e1, a1, b1, w7 , 6) + R52(c2, d2, e2, a2, b2, w1 , 12) + R51(b1, c1, d1, e1, a1, w12, 8) + R52(b2, c2, d2, e2, a2, w5 , 5) + R51(a1, b1, c1, d1, e1, w2 , 13) + R52(a2, b2, c2, d2, e2, w8 , 14) + R51(e1, a1, b1, c1, d1, w10, 12) + R52(e2, a2, b2, c2, d2, w7 , 6) + R51(d1, e1, a1, b1, c1, w14, 5) + R52(d2, e2, a2, b2, c2, w6 , 8) + R51(c1, d1, e1, a1, b1, w1 , 12) + R52(c2, d2, e2, a2, b2, w2 , 13) + R51(b1, c1, d1, e1, a1, w3 , 13) + R52(b2, c2, d2, e2, a2, w13, 6) + R51(a1, b1, c1, d1, e1, w8 , 14) + R52(a2, b2, c2, d2, e2, w14, 5) + R51(e1, a1, b1, c1, d1, w11, 11) + R52(e2, a2, b2, c2, d2, w0 , 15) + R51(d1, e1, a1, b1, c1, w6 , 8) + R52(d2, e2, a2, b2, c2, w3 , 13) + R51(c1, d1, e1, a1, b1, w15, 5) + R52(c2, d2, e2, a2, b2, w9 , 11) + R51(b1, c1, d1, e1, a1, w13, 6) + R52(b2, c2, d2, e2, a2, w11, 11) + + let t = s[0] + s[0] = s[1] + c1 + d2 + s[1] = s[2] + d1 + e2 + s[2] = s[3] + e1 + a2 + s[3] = s[4] + a1 + b2 + s[4] = t + b1 + c2 + +#template toPtr(x: openArray[byte]): untyped = cast[pointer](x[0].addr) +template `+!`(x: openArray[byte], offset: int|uint64): untyped = cast[pointer](cast[uint64](x[0].addr) + uint64(offset)) + +proc write*(ctx: var Ripemd160Context, data: openArray[byte], length: uint64) = + var bufsize = ctx.bytes mod 64 + var dataPos = 0'u64 + if bufsize > 0 and bufsize + length >= 64: + # Fill the buffer, and process it. + copyMem(ctx.buf +! bufsize, data +! dataPos, 64 - bufsize) + ctx.bytes += 64 - bufsize + dataPos += 64 - bufsize + ctx.s.transform(ctx.buf) + bufsize = 0 + + while length - dataPos >= 64: + # Process full chunks directly from the source. + ctx.s.transform(toOpenArray(data, dataPos.int, length.int - 1)) + ctx.bytes += 64 + dataPos += 64 + + if length > dataPos: + # Fill the buffer with what remains. + copyMem(ctx.buf +! bufsize, data +! dataPos, length - dataPos) + ctx.bytes += length - dataPos + +template ReadLE32(chunk: openArray[byte], offset: int): untyped = uint32.fromBytes(chunk, offset, littleEndian) + +proc arrayFirst(x: byte): array[64, byte] = result[0] = x + +proc finalize*(ctx: var Ripemd160Context, hash: var array[DigestSize, byte]) = + const pad: array[64, byte] = arrayFirst(0x80) + var sizedesc: array[8, byte] + sizedesc.blobFrom(ctx.bytes shl 3, 0, littleEndian) + + ctx.write(pad, 1 + ((119 - (ctx.bytes mod 64)) mod 64)) + ctx.write(sizedesc, 8) + + hash.blobFrom(ctx.s[0], 0, littleEndian) + hash.blobFrom(ctx.s[1], 4, littleEndian) + hash.blobFrom(ctx.s[2], 8, littleEndian) + hash.blobFrom(ctx.s[3], 12, littleEndian) + hash.blobFrom(ctx.s[4], 16, littleEndian) + +proc reset*(ctx: var Ripemd160Context) = + ctx.bytes = 0 + ctx.s.initialize() + ctx.buf.setZero() + +when isMainModule: + import constantine/serialization/codecs + var ctx = initRipemdCtx() + + + template test(input: string, digest: string): untyped = + ctx.reset() + ctx.write(toOpenArrayByte(input, 0, input.len-1), input.len.uint64) + var dgst: array[DigestSize, byte] + ctx.finalize(dgst) + echo dgst.toHex() + doAssert dgst.toHex() == digest + + const Empty = "0x9c1185a5c5e9fc54612808977ee8f548b2258d31" + test("", Empty) + + const MessageDigest = "message digest" + const MDHash = "0x5d0689ef49d2fae572b881b123a85ffa21595f36" + test(MessageDigest, MDHash) + + import std / strutils + let Digits = repeat("1234567890", 8) + const DigitsHash = "0x9b752e45573d4b39f4dbd3323cab82bf63326bfb" + test(Digits, DigitsHash) + + let As = repeat("a", 1_000_000) + const AsHash = "0x52783243c1697bdbe16d37f97f68f08325dc1528" + test(As, AsHash) From 7626fc7835580297c018490ad9be67f96fbde5a9 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Tue, 31 Dec 2024 12:52:40 +0100 Subject: [PATCH 02/12] [ripemd160] add `ripemd160` type with standard hash API --- constantine/hashes/h_ripemd160.nim | 85 ++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 constantine/hashes/h_ripemd160.nim diff --git a/constantine/hashes/h_ripemd160.nim b/constantine/hashes/h_ripemd160.nim new file mode 100644 index 00000000..9a866d8c --- /dev/null +++ b/constantine/hashes/h_ripemd160.nim @@ -0,0 +1,85 @@ +# Constantine +# Copyright (c) 2018-2019 Status Research & Development GmbH +# Copyright (c) 2020-Present Mamy André-Ratsimbazafy +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0). +# at your option. This file may not be copied, modified, or distributed except according to those terms. + +import constantine/zoo_exports + +import + constantine/platforms/[abstractions, views], + constantine/serialization/endians, + ./ripemd160/ripemd160_generic + + +# RIPEMD-160, a hash function from the RIPE family +# -------------------------------------------------------------------------------- +# +# References: +# - ISO: ISO/IEC 10118-3:2004, https://www.iso.org/standard/67116.html (latest revision) +# - https://homes.esat.kuleuven.be/~bosselae/ripemd160.html +# -> Includes a reference implementation in C, however only accessible via the Wayback Machine +# as of Dec 2024. +# - Bitcoin implementation: +# https://github.com/bitcoin-core/btcdeb/blob/e2c2e7b9fe2ecc0884129b53813a733f93a6e2c7/crypto/ripemd160.cpp#L242 +# +# Vectors: +# - https://homes.esat.kuleuven.be/~bosselae/ripemd160.html +# - [ ] Find Bitcoin vectors + +# Types and constants +# ---------------------------------------------------------------- + +type + ripemd256* = Ripemd160Context # defined in generic file atm + +# Internals +# ---------------------------------------------------------------- +# defined in `ripemd160/ripemd160_generic.nim` at the moment + +# No exceptions allowed in core cryptographic operations +{.push raises: [].} +{.push checks: off.} + +# Public API +# ---------------------------------------------------------------- + +template digestSize*(H: type ripemd160): int = + ## Returns the output size in bytes + DigestSize + +template internalBlockSize*(H: type ripemd160): int = + ## Returns the byte size of the hash function ingested blocks + BlockSize + +func init*(ctx: var Ripemd160Context) = + ## Initialize or reinitialize a Ripemd160 context + ctx.initialize() + +func update*(ctx: var Ripemd160Context, message: openarray[byte]) = + ## Append a message to a Ripemd160 context for incremental Ripemd160 computation. + ## + ## Security note: the tail of your message might be stored + ## in an internal buffer. + ## if sensitive content is used, ensure that + ## `ctx.finish(...)` and `ctx.clear()` are called as soon as possible. + ## Additionally ensure that the message(s) passed was(were) stored + ## in memory considered secure for your threat model. + ctx.write(message, message.len.uint64) + +func finish*(ctx: var Ripemd160Context, digest: var array[HashSize, byte]) = + ## Finalize a Ripemd160 computation and output the + ## message digest to the `digest` buffer. + ## + ## Security note: this does not clear the internal buffer. + ## if sensitive content is used, use "ctx.clear()" + ## and also make sure that the message(s) passed were stored + ## in memory considered secure for your threat model. + ctx.finalize(digest) + +func clear*(ctx: var Ripemd160Context) = + ## Clear the context internal buffers + # TODO: ensure compiler cannot optimize the code away + ctx.reset() From 9d04e83a3eb4e7b74cef9dd9d3a84d1593aae699 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Tue, 31 Dec 2024 20:41:19 +0100 Subject: [PATCH 03/12] [ripemd160] fix two hasty errors --- constantine/hashes/h_ripemd160.nim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/constantine/hashes/h_ripemd160.nim b/constantine/hashes/h_ripemd160.nim index 9a866d8c..4e0ab368 100644 --- a/constantine/hashes/h_ripemd160.nim +++ b/constantine/hashes/h_ripemd160.nim @@ -33,7 +33,7 @@ import # ---------------------------------------------------------------- type - ripemd256* = Ripemd160Context # defined in generic file atm + ripemd160* = Ripemd160Context # defined in generic file atm # Internals # ---------------------------------------------------------------- @@ -69,7 +69,7 @@ func update*(ctx: var Ripemd160Context, message: openarray[byte]) = ## in memory considered secure for your threat model. ctx.write(message, message.len.uint64) -func finish*(ctx: var Ripemd160Context, digest: var array[HashSize, byte]) = +func finish*(ctx: var Ripemd160Context, digest: var array[DigestSize, byte]) = ## Finalize a Ripemd160 computation and output the ## message digest to the `digest` buffer. ## From 22ef74968857fe0558636afe49d7183ca5342b4f Mon Sep 17 00:00:00 2001 From: Vindaar Date: Tue, 31 Dec 2024 20:42:05 +0100 Subject: [PATCH 04/12] [ripemd160] use `reset` in `initialize` to `setZero` on buffer too Given that the docstring explicitly promises to also reinitialize, this is the safer bet. --- constantine/hashes/h_ripemd160.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/constantine/hashes/h_ripemd160.nim b/constantine/hashes/h_ripemd160.nim index 4e0ab368..cf782901 100644 --- a/constantine/hashes/h_ripemd160.nim +++ b/constantine/hashes/h_ripemd160.nim @@ -56,7 +56,7 @@ template internalBlockSize*(H: type ripemd160): int = func init*(ctx: var Ripemd160Context) = ## Initialize or reinitialize a Ripemd160 context - ctx.initialize() + ctx.reset() func update*(ctx: var Ripemd160Context, message: openarray[byte]) = ## Append a message to a Ripemd160 context for incremental Ripemd160 computation. From 2827fcc5c965a96c9b091f7d4b1e19d6337e3d96 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Tue, 31 Dec 2024 20:42:44 +0100 Subject: [PATCH 05/12] [hashes] add ripemd160 to list of supported hashes --- constantine/hashes.nim | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/constantine/hashes.nim b/constantine/hashes.nim index 55da1a6f..0f723d30 100644 --- a/constantine/hashes.nim +++ b/constantine/hashes.nim @@ -65,13 +65,16 @@ func hash*( import ./hashes/[ h_keccak, - h_sha256 + h_sha256, + h_ripemd160 ] export h_keccak, - h_sha256 + h_sha256, + h_ripemd160 static: doAssert keccak256 is CryptoHash doAssert sha256 is CryptoHash doAssert sha3_256 is CryptoHash + doAssert ripemd160 is CryptoHash From 91d6da4f88373ad4a588e94001d22c98edd3e8f2 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Tue, 31 Dec 2024 20:43:00 +0100 Subject: [PATCH 06/12] [precompiles] add precompile for RIPEMD160 --- constantine/ethereum_evm_precompiles.nim | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/constantine/ethereum_evm_precompiles.nim b/constantine/ethereum_evm_precompiles.nim index 852c6c32..9062555e 100644 --- a/constantine/ethereum_evm_precompiles.nim +++ b/constantine/ethereum_evm_precompiles.nim @@ -67,6 +67,25 @@ func eth_evm_sha256*(r: var openArray[byte], inputs: openArray[byte]): CttEVMSta sha256.hash(cast[ptr array[32, byte]](r[0].addr)[], inputs) return cttEVM_Success +func eth_evm_ripemd160*(r: var openArray[byte], inputs: openArray[byte]): CttEVMStatus {.libPrefix: prefix_ffi, meter.} = + ## RIPEMD160 + ## + ## Inputs: + ## - Message to hash + ## + ## Output: + ## - 32-byte digest (first 12 bytes zero) + ## - status code: + ## cttEVM_Success + ## cttEVM_InvalidOutputSize + + if r.len != 32: + return cttEVM_InvalidOutputSize + + # Need to only write to last 20 bytes. Hence fist `toOpenArray` & then cast & deref + ripemd160.hash(cast[ptr array[20, byte]](toOpenArray(r, 12, 31)[0].addr)[], inputs) + return cttEVM_Success + func eth_evm_modexp_result_size*(size: var uint64, inputs: openArray[byte]): CttEVMStatus {.noInline, tags:[Alloca, Vartime], libPrefix: prefix_ffi, meter.} = ## Helper for `eth_evm_modexp`. Returns the size required to be allocated based on the ## given input. Call this function first, then allocate space for the result buffer From 6a4bee8e84f43eece7d820c34bdaa74f16893ae2 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Tue, 31 Dec 2024 20:43:10 +0100 Subject: [PATCH 07/12] [tests] add test case for RIPEMD160 precompile --- tests/t_ethereum_evm_precompiles.nim | 29 ++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tests/t_ethereum_evm_precompiles.nim b/tests/t_ethereum_evm_precompiles.nim index 69b0c642..2738493d 100644 --- a/tests/t_ethereum_evm_precompiles.nim +++ b/tests/t_ethereum_evm_precompiles.nim @@ -128,9 +128,38 @@ proc testSha256() = stdout.write "Success\n" +proc testRipemd160() = + # https://github.com/ethereum/go-ethereum/blob/v1.14.0/core/vm/contracts_test.go#L216-L224 + let input = "38d18acb67d25c8bb9942764b62f18e17054f66a817bd4295423adf9ed98873e000000000000000000000000000000000000000000000000000000000000001b38d18acb67d25c8bb9942764b62f18e17054f66a817bd4295423adf9ed98873e789d1dd423d25f0772d2748d60f7e4b81bb14d086eba8e8e8efb6dcff8a4ae02" + let expected = "0000000000000000000000009215b8d9882ff46f0dfde6684d78e831467f65e6" + + echo "Running RIPEMD160 tests" + stdout.write " Testing RIPEMD160 ... " + + var inputbytes = newSeq[byte](input.len div 2) + inputbytes.fromHex(input) + + var expectedbytes = newSeq[byte](expected.len div 2) + expectedbytes.fromHex(expected) + + var r = newSeq[byte](expected.len div 2) + + let status = eth_evm_ripemd160(r, inputbytes) + if status != cttEVM_Success: + reset(r) + + doAssert r == expectedbytes, "[Test Failure]\n" & + " eth_evm_ripemd160 status: " & $status & "\n" & + " " & "result: " & r.toHex() & "\n" & + " " & "expected: " & expectedbytes.toHex() & '\n' + + stdout.write "Success\n" + + # ---------------------------------------------------------------------- testSha256() +testRipemd160() runPrecompileTests("modexp.json", eth_evm_modexp, 0) runPrecompileTests("modexp_eip2565.json", eth_evm_modexp, 0) From c831d569b0b6c838b67d1dd3f46e42b8c229f810 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Sun, 12 Jan 2025 18:45:29 +0100 Subject: [PATCH 08/12] [openssl] add wrapper for RIPEMD160 hash function via OpenSSL --- tests/openssl_wrapper.nim | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/openssl_wrapper.nim b/tests/openssl_wrapper.nim index a5be8268..a7a4f5d1 100644 --- a/tests/openssl_wrapper.nim +++ b/tests/openssl_wrapper.nim @@ -46,6 +46,16 @@ when not defined(windows): discard SHA256(s, digest.addr) # discard EVP_Q_digest(nil, "SHA256", nil, s, digest, nil) + proc RIPEMD160[T: byte|char]( + msg: openarray[T], + digest: ptr array[20, byte] = nil + ): ptr array[20, byte] {.noconv, dynlib: DLL_SSL_Name, importc.} + + proc RIPEMD160_OpenSSL*[T: byte|char]( + digest: var array[20, byte], + s: openArray[T]) = + discard RIPEMD160(s, digest.addr) + type BIGNUM_Obj* = object EC_KEY_Obj* = object From 15f66bc7cb44b9af0befd542bcffee553d86fab3 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Sun, 12 Jan 2025 18:45:49 +0100 Subject: [PATCH 09/12] [ripemd160] remove left over template & `isMainModule` code --- .../hashes/ripemd160/ripemd160_generic.nim | 30 ------------------- 1 file changed, 30 deletions(-) diff --git a/constantine/hashes/ripemd160/ripemd160_generic.nim b/constantine/hashes/ripemd160/ripemd160_generic.nim index 4034ea57..bd982d7c 100644 --- a/constantine/hashes/ripemd160/ripemd160_generic.nim +++ b/constantine/hashes/ripemd160/ripemd160_generic.nim @@ -260,7 +260,6 @@ proc transform(s: var array[HashSize, uint32], chunk: openArray[byte]) = s[3] = s[4] + a1 + b2 s[4] = t + b1 + c2 -#template toPtr(x: openArray[byte]): untyped = cast[pointer](x[0].addr) template `+!`(x: openArray[byte], offset: int|uint64): untyped = cast[pointer](cast[uint64](x[0].addr) + uint64(offset)) proc write*(ctx: var Ripemd160Context, data: openArray[byte], length: uint64) = @@ -307,32 +306,3 @@ proc reset*(ctx: var Ripemd160Context) = ctx.bytes = 0 ctx.s.initialize() ctx.buf.setZero() - -when isMainModule: - import constantine/serialization/codecs - var ctx = initRipemdCtx() - - - template test(input: string, digest: string): untyped = - ctx.reset() - ctx.write(toOpenArrayByte(input, 0, input.len-1), input.len.uint64) - var dgst: array[DigestSize, byte] - ctx.finalize(dgst) - echo dgst.toHex() - doAssert dgst.toHex() == digest - - const Empty = "0x9c1185a5c5e9fc54612808977ee8f548b2258d31" - test("", Empty) - - const MessageDigest = "message digest" - const MDHash = "0x5d0689ef49d2fae572b881b123a85ffa21595f36" - test(MessageDigest, MDHash) - - import std / strutils - let Digits = repeat("1234567890", 8) - const DigitsHash = "0x9b752e45573d4b39f4dbd3323cab82bf63326bfb" - test(Digits, DigitsHash) - - let As = repeat("a", 1_000_000) - const AsHash = "0x52783243c1697bdbe16d37f97f68f08325dc1528" - test(As, AsHash) From 3a1cca739a1f25ee573620ea4bb73dcab2516b24 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Sun, 12 Jan 2025 18:46:10 +0100 Subject: [PATCH 10/12] [ripemd160] export Ripemd160Context from `h_ripemd160` In this case the context is already defined in the generic implementation, so we just export it again. --- constantine/hashes/h_ripemd160.nim | 2 ++ 1 file changed, 2 insertions(+) diff --git a/constantine/hashes/h_ripemd160.nim b/constantine/hashes/h_ripemd160.nim index cf782901..10ecc23b 100644 --- a/constantine/hashes/h_ripemd160.nim +++ b/constantine/hashes/h_ripemd160.nim @@ -35,6 +35,8 @@ import type ripemd160* = Ripemd160Context # defined in generic file atm +export Ripemd160Context + # Internals # ---------------------------------------------------------------- # defined in `ripemd160/ripemd160_generic.nim` at the moment From e672baa98becf0e366573f232c3b5d161ed66e55 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Sun, 12 Jan 2025 18:46:55 +0100 Subject: [PATCH 11/12] [tests] add more RIPEMD160 test vectors & fuzzing against OpenSSL --- tests/t_hash_ripemd160_vs_openssl.nim | 131 ++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 tests/t_hash_ripemd160_vs_openssl.nim diff --git a/tests/t_hash_ripemd160_vs_openssl.nim b/tests/t_hash_ripemd160_vs_openssl.nim new file mode 100644 index 00000000..aafafbd0 --- /dev/null +++ b/tests/t_hash_ripemd160_vs_openssl.nim @@ -0,0 +1,131 @@ +import + # Internals + constantine/hashes, + constantine/serialization/codecs, + # Helpers + helpers/prng_unsafe + +## NOTE: For a reason that evades me at the moment, if we only `import` +## the wrapper, we get a linker error of the form: +## +## @mopenssl_wrapper.nim.c:(.text+0x110): undefined reference to `Dl_1073742356_' +## /usr/bin/ld: warning: creating DT_TEXTREL in a PIE +## +## So for the moment, we just include the wrapper. +include ./openssl_wrapper + +# Test cases +# -------------------------------------------------------------------- + +# imports for test vector construction +from std / strutils import repeat, join +import std / sequtils +proc sanityTestVectors() = + ## Test vectors from: + ## https://homes.esat.kuleuven.be/~bosselae/ripemd160.html + let vectors = { + "" : "0x9c1185a5c5e9fc54612808977ee8f548b2258d31", + "a" : "0x0bdc9d2d256b3ee9daae347be6f4dc835a467ffe", + "abc" : "0x8eb208f7e05d987a9b044a8e98c6b087f15a0bfc", + "message digest" : "0x5d0689ef49d2fae572b881b123a85ffa21595f36", + toSeq('a'..'z').join() : "0xf71c27109c692c1b56bbdceb5b9d2865b3708dbc", + "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" : "0x12a053384a9c0c88e405a06c27dcf49ada62eb2b", + concat(toSeq('A'..'Z'), toSeq('a'..'z'), toSeq('0'..'9')).join() : "0xb0e20b6e3116640286ed3a87a5713079b21f5189", + repeat("1234567890", 8) : "0x9b752e45573d4b39f4dbd3323cab82bf63326bfb", + repeat("a", 1_000_000) : "0x52783243c1697bdbe16d37f97f68f08325dc1528" + } + + template test(input: string, digest: string): untyped = + + var exp = array[20, byte].fromHex(digest) + var dgst: array[20, byte] + ripemd160.hash(dgst, input) + doAssert dgst == exp + + for v in vectors: + test(v[0], v[1]) + +# Differential fuzzing +# -------------------------------------------------------------------- + +const SmallSizeIters = 64 +const LargeSizeIters = 1 + +when not defined(windows): + proc innerTest(rng: var RngState, sizeRange: Slice[int]) = + let size = rng.random_unsafe(sizeRange) + let msg = rng.random_byte_seq(size) + + var bufCt, bufOssl: array[20, byte] + + ripemd160.hash(bufCt, msg) + RIPEMD160_OpenSSL(bufOssl, msg) + doAssert bufCt == bufOssl, "Test failed with message of length " & $size + +proc chunkTest(rng: var RngState, sizeRange: Slice[int]) = + let size = rng.random_unsafe(sizeRange) + let msg = rng.random_byte_seq(size) + + let chunkSize = rng.random_unsafe(2 ..< 20) + + var bufOnePass: array[20, byte] + ripemd160.hash(bufOnePass, msg) + + var bufChunked: array[20, byte] + let maxChunk = max(2, sizeRange.b div 10) # Consume up to 10% at once + + var ctx: Ripemd160Context + ctx.init() + var cur = 0 + while size - cur > 0: + let chunkSize = rng.random_unsafe(0 ..< maxChunk) + let stop = min(cur+chunkSize-1, size-1) + let consumed = stop-cur+1 + ctx.update(msg.toOpenArray(cur, stop)) + cur += consumed + + ctx.finish(bufChunked) + + doAssert bufOnePass == bufChunked + +# -------------------------------------------------------------------- + +proc main() = + echo "\n------------------------------------------------------\n" + echo "RIPEMD160 - sanity checks" + sanityTestVectors() + + echo "RIPEMD160 - Starting differential testing vs OpenSSL (except on Windows)" + + var rng: RngState + rng.seed(0xFACADE) + + when not defined(windows): + echo "RIPEMD160 - 0 <= size < 64 - exhaustive" + for i in 0 ..< 64: + rng.innerTest(i .. i) + else: + echo "RIPEMD160 - 0 <= size < 64 - exhaustive [SKIPPED]" + + echo "RIPEMD160 - 0 <= size < 64 - exhaustive chunked" + for i in 0 ..< 64: + rng.chunkTest(i .. i) + + echo "RIPEMD160 - 64 <= size < 1024B - chunked" + for _ in 0 ..< SmallSizeIters: + rng.chunkTest(0 ..< 1024) + + when not defined(windows): + echo "RIPEMD160 - 64 <= size < 1024B" + for _ in 0 ..< SmallSizeIters: + rng.innerTest(0 ..< 1024) + + echo "RIPEMD160 - 1MB <= size < 50MB" + for _ in 0 ..< LargeSizeIters: + rng.innerTest(1_000_000 ..< 50_000_000) + + echo "RIPEMD160 - Differential testing vs OpenSSL - SUCCESS" + else: + echo "RIPEMD160 - Differential testing vs OpenSSL - [SKIPPED]" + +main() From d691b4d75da26ce1591eabd7e44ecea8f3c6d022 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Sun, 12 Jan 2025 18:47:49 +0100 Subject: [PATCH 12/12] add RIPEMD160 test vs OpenSSL to nimble file --- constantine.nimble | 1 + 1 file changed, 1 insertion(+) diff --git a/constantine.nimble b/constantine.nimble index 28a47c35..3caad5cf 100644 --- a/constantine.nimble +++ b/constantine.nimble @@ -365,6 +365,7 @@ const testDesc: seq[tuple[path: string, useGMP: bool]] = @[ # ---------------------------------------------------------- ("tests/t_hash_sha256_vs_openssl.nim", false), ("tests/t_hash_keccak_sha3_vs_openssl.nim", false), + ("tests/t_hash_ripemd160_vs_openssl.nim", false), # Ciphers # ----------------------------------------------------------