Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Changed

- Optimize `Ripemd160` (#20).

### Fixed

- `Ripemd160` and `Hmac` Digests now guard against accidentally writing more data after calling `sum()`.

## [0.2.0] - 2026-04-22

### Changed
Expand Down
65 changes: 65 additions & 0 deletions bench/ripemd160.bench.mo
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import Array "mo:core/Array";
import Nat8 "mo:core/Nat8";

import Bench "mo:bench-helper";

import Ripemd160 "../src/Ripemd160";

module {
public func init() : Bench.V1 {
let schema : Bench.Schema = {
name = "RIPEMD-160";
description = "RIPEMD-160 one-shot hash across message sizes, plus incremental digest";
rows = ["hash (one-shot)", "Digest (incremental, 64-byte chunks)"];
cols = ["len 0", "len 32", "len 64", "len 256", "len 1024"];
};

let datas : [[Nat8]] = [
[],
Array.tabulate<Nat8>(32, func i { Nat8.fromNat((i * 3 + 1) % 256) }),
Array.tabulate<Nat8>(64, func i { Nat8.fromNat((i * 7 + 5) % 256) }),
Array.tabulate<Nat8>(256, func i { Nat8.fromNat((i * 11 + 13) % 256) }),
Array.tabulate<Nat8>(1024, func i { Nat8.fromNat((i * 17 + 9) % 256) }),
];

// Pre-split each input into 64-byte chunks for the incremental row.
let chunkedDatas : [[[Nat8]]] = Array.tabulate<[[Nat8]]>(
datas.size(),
func(ci) {
let data = datas[ci];
let total = data.size();
if (total == 0) {
[];
} else {
let nChunks = (total + 63) / 64;
Array.tabulate<[Nat8]>(
nChunks,
func(k) {
let start = k * 64;
let end = if (start + 64 < total) { start + 64 } else { total };
Array.tabulate<Nat8>(end - start, func(j) { data[start + j] });
},
);
};
},
);

func run(ri : Nat, ci : Nat) {
switch (ri) {
case (0) {
ignore Ripemd160.hash(datas[ci]);
};
case (1) {
let d = Ripemd160.Digest();
for (chunk in chunkedDatas[ci].values()) {
d.write(chunk);
};
ignore d.sum();
};
case (_) {};
};
};

Bench.V1(schema, run);
};
};
14 changes: 14 additions & 0 deletions src/Hmac.mo
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,18 @@ module {
create : () -> Digest;
};

// HMAC instance.
//
// NOTE: An Hmac is one-shot. Calling `sum()` finalizes and consumes the
// instance: `closed` is set, and any subsequent call to `sum()` or
// `writeArray()` will trap. To compute another HMAC, construct a new Hmac.
public type Hmac = {
// Append `data` to the message being authenticated. Traps if the Hmac
// has already been finalized via `sum()`.
writeArray : ([Nat8]) -> ();
// Finalize the HMAC and return the tag. Consumes the Hmac: the instance
// becomes closed, and any further call to `sum()` or `writeArray()`
// will trap. Callers must create a new Hmac for another computation.
sum : () -> Blob;
};

Expand Down Expand Up @@ -48,6 +58,7 @@ module {
let outerDigest : Digest = digestFactory.create();
let innerPad : Nat8 = 0x36;
let outerPad : Nat8 = 0x5c;
var closed = false;

do {
let blockSize = digestFactory.blockSize;
Expand Down Expand Up @@ -99,10 +110,13 @@ module {
};

public func writeArray(data : [Nat8]) {
assert (not closed);
innerDigest.writeArray(data);
};

public func sum() : Blob {
assert (not closed);
closed := true;
let innerHash = innerDigest.sum().toArray();
outerDigest.writeArray(innerHash);
outerDigest.sum();
Expand Down
Loading
Loading