Skip to content

Commit

Permalink
Add RIPEMD160 hash function and EVM precompile (#505)
Browse files Browse the repository at this point in the history
* [ripemd160] add port of bitcoin-core ripemd160 implementation

* [ripemd160] add `ripemd160` type with standard hash API

* [ripemd160] fix two hasty errors

* [ripemd160] use `reset` in `initialize` to `setZero` on buffer too

Given that the docstring explicitly promises to also reinitialize,
this is the safer bet.

* [hashes] add ripemd160 to list of supported hashes

* [precompiles] add precompile for RIPEMD160

* [tests] add test case for RIPEMD160 precompile

* [openssl] add wrapper for RIPEMD160 hash function via OpenSSL

* [ripemd160] remove left over template & `isMainModule` code

* [ripemd160] export Ripemd160Context from `h_ripemd160`

In this case the context is already defined in the generic
implementation, so we just export it again.

* [tests] add more RIPEMD160 test vectors & fuzzing against OpenSSL

* add RIPEMD160 test vs OpenSSL to nimble file
  • Loading branch information
Vindaar authored Jan 13, 2025
1 parent 2582fb5 commit ef2a91a
Show file tree
Hide file tree
Showing 8 changed files with 590 additions and 2 deletions.
1 change: 1 addition & 0 deletions constantine.nimble
Original file line number Diff line number Diff line change
Expand Up @@ -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
# ----------------------------------------------------------
Expand Down
19 changes: 19 additions & 0 deletions constantine/ethereum_evm_precompiles.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 5 additions & 2 deletions constantine/hashes.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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
87 changes: 87 additions & 0 deletions constantine/hashes/h_ripemd160.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# 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
ripemd160* = Ripemd160Context # defined in generic file atm

export Ripemd160Context

# 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.reset()

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[DigestSize, 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()
Loading

0 comments on commit ef2a91a

Please sign in to comment.