Skip to content

Commit

Permalink
⚡️ benchmarks
Browse files Browse the repository at this point in the history
  • Loading branch information
AbdelStark committed Aug 8, 2024
1 parent f11874f commit 69e2692
Show file tree
Hide file tree
Showing 7 changed files with 263 additions and 82 deletions.
11 changes: 10 additions & 1 deletion .github/workflows/check.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Zig BDHKE Test
name: Zig BDHKE Test and Benchmark

on:
push:
Expand Down Expand Up @@ -44,3 +44,12 @@ jobs:
- name: Build and Run
run: zig build run

- name: Run benchmarks with report
run: zig build bench -- --report

- name: Upload benchmark report
uses: actions/upload-artifact@v3
with:
name: benchmark-report
path: benchmark_report.csv
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,6 @@ Cargo.lock
#.idea/

**/.zig-cache
**/zig-out
**/zig-out

**/benchmark_report.csv
50 changes: 50 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,56 @@ Final verification successful
BDHKE test completed successfully
```

## Benchmarks

This project includes performance benchmarks for each step of the BDHKE process, as well as the end-to-end flow. The benchmarks are implemented using Zig's built-in benchmarking functionality.

### Running Benchmarks Locally

To run the benchmarks on your local machine:

```sh
# Run benchmarks without generating a report
zig build bench

# Run benchmarks and generate a CSV report
zig build bench -- --report

# Run benchmarks without generating a report (explicit)
zig build bench -- --report=false
```

The benchmarks will be compiled with the ReleaseFast optimization level, providing the most accurate performance metrics.

### Benchmark Results

The benchmark results are presented in a table format, displaying both nanosecond and millisecond timings:

| Operation | Time (ns) | Time (ms) |
| ---------------- | --------: | --------: |
| hashToPoint | 21449 | 0.021 |
| step1Alice | 20671 | 0.021 |
| step2Bob | 647647 | 0.648 |
| step3Alice | 149790 | 0.150 |
| verify | 170793 | 0.171 |
| End-to-End BDHKE | 992257 | 0.992 |

### Benchmark Report

When run with the `--report` option, a CSV file named `benchmark_report.csv` will be generated in the project root directory. This file contains the operation names and their corresponding execution times in nanoseconds.

### Benchmark Results in CI

The benchmarks are also run as part of our Continuous Integration (CI) pipeline on GitHub Actions. The workflow runs the benchmarks with the report generation option enabled. You can view the results of the latest benchmark run in the "Actions" tab of the GitHub repository, under the "Run benchmarks with report" step of the most recent workflow run.

The benchmark report CSV file is saved as an artifact and can be downloaded from the GitHub Actions page for each workflow run.

### Notes on Benchmark Results

- Benchmark results can vary based on the hardware and system load. For consistent comparisons, always use the same machine and ensure minimal background processes.
- The CI benchmark results may differ from local results due to differences in hardware and environment.
- These benchmarks are meant to provide relative performance metrics and may not represent absolute real-world performance in all scenarios.

## Resources

- [Cashu documentation](https://docs.cashu.space/)
Expand Down
67 changes: 28 additions & 39 deletions build.zig
Original file line number Diff line number Diff line change
@@ -1,32 +1,16 @@
const std = @import("std");

// Although this function looks imperative, note that its job is to
// declaratively construct a build graph that will be executed by an external
// runner.
pub fn build(b: *std.Build) void {
// Standard target options allows the person running `zig build` to choose
// what target to build for. Here we do not override the defaults, which
// means any target is allowed, and the default is native. Other options
// for restricting supported target set are available.
const target = b.standardTargetOptions(.{});

// Standard optimization options allow the person running `zig build` to select
// between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not
// set a preferred release mode, allowing the user to decide how to optimize.
const optimize = b.standardOptimizeOption(.{});

const lib = b.addStaticLibrary(.{
.name = "coconut",
// In this case the main source file is merely a path, however, in more
// complicated build scripts, this could be a generated file.
.root_source_file = b.path("src/root.zig"),
.root_source_file = b.path("src/lib.zig"),
.target = target,
.optimize = optimize,
});

// This declares intent for the library to be installed into the standard
// location when the user invokes the "install" step (the default step when
// running `zig build`).
b.installArtifact(lib);

const exe = b.addExecutable(.{
Expand All @@ -36,38 +20,20 @@ pub fn build(b: *std.Build) void {
.optimize = optimize,
});

// This declares intent for the executable to be installed into the
// standard location when the user invokes the "install" step (the default
// step when running `zig build`).
b.installArtifact(exe);

// This *creates* a Run step in the build graph, to be executed when another
// step is evaluated that depends on it. The next line below will establish
// such a dependency.
const run_cmd = b.addRunArtifact(exe);

// By making the run step depend on the install step, it will be run from the
// installation directory rather than directly from within the cache directory.
// This is not necessary, however, if the application depends on other installed
// files, this ensures they will be present and in the expected location.
run_cmd.step.dependOn(b.getInstallStep());

// This allows the user to pass arguments to the application in the build
// command itself, like this: `zig build run -- arg1 arg2 etc`
if (b.args) |args| {
run_cmd.addArgs(args);
}

// This creates a build step. It will be visible in the `zig build --help` menu,
// and can be selected like this: `zig build run`
// This will evaluate the `run` step rather than the default, which is "install".
const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step);

// Creates a step for unit testing. This only builds the test executable
// but does not run it.
const lib_unit_tests = b.addTest(.{
.root_source_file = b.path("src/root.zig"),
.root_source_file = b.path("src/lib.zig"),
.target = target,
.optimize = optimize,
});
Expand All @@ -82,10 +48,33 @@ pub fn build(b: *std.Build) void {

const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);

// Similar to creating the run step earlier, this exposes a `test` step to
// the `zig build --help` menu, providing a way for the user to request
// running the unit tests.
const test_step = b.step("test", "Run unit tests");
test_step.dependOn(&run_lib_unit_tests.step);
test_step.dependOn(&run_exe_unit_tests.step);

// Add benchmark step
const bench = b.addExecutable(.{
.name = "benchmark",
.root_source_file = b.path("src/benchmarks.zig"),
.target = target,
.optimize = .ReleaseFast,
});

const run_bench = b.addRunArtifact(bench);

// Add option for report generation
const report_option = b.option(bool, "report", "Generate benchmark report (default: false)") orelse false;

// Pass the report option to the benchmark executable
if (report_option) {
run_bench.addArg("--report");
}

// Pass any additional arguments to the benchmark executable
if (b.args) |args| {
run_bench.addArgs(args);
}

const bench_step = b.step("bench", "Run benchmarks");
bench_step.dependOn(&run_bench.step);
}
82 changes: 41 additions & 41 deletions src/bdhke.zig
Original file line number Diff line number Diff line change
Expand Up @@ -10,47 +10,6 @@ const BDHKEError = error{
VerificationFailed,
};

/// Hashes a message to a point on the secp256k1 curve
fn hashToPoint(message: []const u8) BDHKEError!Point {
const domain_separator = "Secp256k1_HashToCurve_Cashu_";
var initial_hasher = crypto.hash.sha2.Sha256.init(.{});
initial_hasher.update(domain_separator);
initial_hasher.update(message);
var msg_to_hash: [32]u8 = undefined;
initial_hasher.final(&msg_to_hash);

var counter: u32 = 0;
while (counter < 0x10000) : (counter += 1) {
var to_hash: [36]u8 = undefined;
@memcpy(to_hash[0..32], &msg_to_hash);

// Manually write little-endian bytes
to_hash[32] = @intCast(counter & 0xFF);
to_hash[33] = @intCast((counter >> 8) & 0xFF);
to_hash[34] = @intCast((counter >> 16) & 0xFF);
to_hash[35] = @intCast((counter >> 24) & 0xFF);

var hasher = crypto.hash.sha2.Sha256.init(.{});
hasher.update(&to_hash);
var hash: [32]u8 = undefined;
hasher.final(&hash);

// Attempt to create a public key
var compressed_point: [33]u8 = undefined;
compressed_point[0] = 0x02; // Set to compressed point format
@memcpy(compressed_point[1..], &hash);

if (Point.fromSec1(&compressed_point)) |point| {
return point;
} else |_| {
// If point creation fails, continue to next iteration
continue;
}
}

return BDHKEError.InvalidPoint;
}

/// Step 1: Alice blinds the message
pub fn step1Alice(secret_msg: []const u8, blinding_factor: Point) !Point {
const Y = try hashToPoint(secret_msg);
Expand Down Expand Up @@ -127,6 +86,47 @@ fn hashE(points: []const Point) !Scalar {
return Scalar.fromBytes(result, .little) catch unreachable;
}

/// Hashes a message to a point on the secp256k1 curve
pub fn hashToPoint(message: []const u8) BDHKEError!Point {
const domain_separator = "Secp256k1_HashToCurve_Cashu_";
var initial_hasher = crypto.hash.sha2.Sha256.init(.{});
initial_hasher.update(domain_separator);
initial_hasher.update(message);
var msg_to_hash: [32]u8 = undefined;
initial_hasher.final(&msg_to_hash);

var counter: u32 = 0;
while (counter < 0x10000) : (counter += 1) {
var to_hash: [36]u8 = undefined;
@memcpy(to_hash[0..32], &msg_to_hash);

// Manually write little-endian bytes
to_hash[32] = @intCast(counter & 0xFF);
to_hash[33] = @intCast((counter >> 8) & 0xFF);
to_hash[34] = @intCast((counter >> 16) & 0xFF);
to_hash[35] = @intCast((counter >> 24) & 0xFF);

var hasher = crypto.hash.sha2.Sha256.init(.{});
hasher.update(&to_hash);
var hash: [32]u8 = undefined;
hasher.final(&hash);

// Attempt to create a public key
var compressed_point: [33]u8 = undefined;
compressed_point[0] = 0x02; // Set to compressed point format
@memcpy(compressed_point[1..], &hash);

if (Point.fromSec1(&compressed_point)) |point| {
return point;
} else |_| {
// If point creation fails, continue to next iteration
continue;
}
}

return BDHKEError.InvalidPoint;
}

/// End-to-end test scenario for BDHKE
pub fn testBDHKE() !void {
// Initialize with deterministic values
Expand Down
Loading

0 comments on commit 69e2692

Please sign in to comment.