Skip to content
Merged
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
153 changes: 142 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,159 @@ A Zig implementation of the Poseidon2 cryptographic hash function.

## Supported Configurations

Currently, this implementation provides:
This implementation provides:

### Finite Fields

- **BabyBear field** (p = 2³¹ - 2²⁷ + 1 = 0x78000001)
- Width: 16 elements
- S-Box degree: 7
- Internal rounds: 13
- External rounds: 8
- Use case: Ethereum Lean chain

- **KoalaBear field** (p = 2³¹ - 2²⁴ + 1 = 0x7f000001)
- Width: 16 elements
- S-Box degree: 3
- Internal rounds: 20
- External rounds: 8
- Use case: plonky3, Rust hash-sig compatibility

### Features

- BabyBear finite field with a width of 16 elements
- Generic Montgomery form implementation for finite fields of 31 bits or less
- Compression mode, since it's the recommended mode for Merkle Trees compared to the sponge construction.
- Compression mode (recommended for Merkle Trees)
- Both naive and optimized (Montgomery) implementations for verification
- Comprehensive test suite ensuring consistency between implementations

The generic implementation makes it straightforward to add support for additional 31-bit fields.
## Installation

## Project Motivation
Add `zig-poseidon` as a dependency in your `build.zig.zon`:

```zig
.dependencies = .{
.poseidon = .{
.url = "https://github.com/jsign/zig-poseidon/archive/<commit-hash>.tar.gz",
.hash = "<hash>",
},
},
```

## Usage

### Using BabyBear16

```zig
const std = @import("std");
const babybear16 = @import("babybear16");

pub fn main() !void {
const Field = babybear16.Poseidon2BabyBear.Field;

// Prepare input state (16 field elements)
var input_state: [16]u32 = .{0} ** 16;
input_state[0] = 42;

// Convert to Montgomery form
var mont_state: [16]Field.MontFieldElem = undefined;
for (0..16) |i| {
Field.toMontgomery(&mont_state[i], input_state[i]);
}

// Apply permutation
babybear16.Poseidon2BabyBear.permutation(&mont_state);

// Convert back to normal form
var output_state: [16]u32 = undefined;
for (0..16) |i| {
output_state[i] = Field.toNormal(mont_state[i]);
}

std.debug.print("Output: {any}\n", .{output_state});
}
```

### Using KoalaBear16 (Rust hash-sig compatible)

```zig
const std = @import("std");
const koalabear16 = @import("koalabear16");

This repository was created primarily to support the upcoming Ethereum Beam chain. The implementation will be updated to match the required configuration once the specifications are finalized.
pub fn main() !void {
const Field = koalabear16.Poseidon2KoalaBear.Field;

// Prepare input state (16 field elements)
var input_state: [16]u32 = .{0} ** 16;
input_state[0] = 42;

// Convert to Montgomery form
var mont_state: [16]Field.MontFieldElem = undefined;
for (0..16) |i| {
Field.toMontgomery(&mont_state[i], input_state[i]);
}

// Apply permutation
koalabear16.Poseidon2KoalaBear.permutation(&mont_state);

// Convert back to normal form
var output_state: [16]u32 = undefined;
for (0..16) |i| {
output_state[i] = Field.toNormal(mont_state[i]);
}

std.debug.print("Output: {any}\n", .{output_state});
}
```

With time this repository can keep expaning on features:
## Building and Testing

- Add support for more finite fields.
- Add support for the sponge construction.
- Add benchmarks and optimizations.
```bash
# Build the library
zig build

# Run all tests (includes BabyBear and KoalaBear)
zig build test
```

## Field Comparison

| Feature | BabyBear | KoalaBear |
|---------|----------|-----------|
| **Prime** | 2³¹ - 2²⁷ + 1 | 2³¹ - 2²⁴ + 1 |
| **Hex Value** | 0x78000001 | 0x7f000001 |
| **Width** | 16 | 16 |
| **S-Box Degree** | 7 | 3 |
| **Internal Rounds** | 13 | 20 |
| **External Rounds** | 8 | 8 |
| **Compatible With** | Ethereum Lean | plonky3, Rust hash-sig |

**Important:** Different fields produce completely different hash outputs! Choose the field that matches your target system.

## Project Motivation

This repository was created primarily to support the upcoming Ethereum Lean chain. The KoalaBear field was added to enable compatibility with Rust's hash-sig implementation and plonky3.

## Compatibility

This implementation has been cross-validated against the [reference repository](https://github.com/HorizenLabs/poseidon2) cited in the Poseidon2 paper to ensure correctness.
- **BabyBear**: Cross-validated against the [Poseidon2 reference repository](https://github.com/HorizenLabs/poseidon2)
- **KoalaBear**: Compatible with [plonky3](https://github.com/Plonky3/Plonky3) and [Rust hash-sig](https://github.com/b-wagn/hash-sig)

Both implementations include tests ensuring the naive and optimized (Montgomery) implementations produce identical outputs.

## Future Enhancements

- Add support for more finite fields
- Add support for the sponge construction
- Add benchmarks and performance optimizations
- Add more S-Box degrees as needed

## License

MIT

## References

- [Poseidon2 Paper](https://eprint.iacr.org/2023/323)
- [HorizenLabs/poseidon2 Reference Implementation](https://github.com/HorizenLabs/poseidon2)
- [Plonky3](https://github.com/Plonky3/Plonky3)
- [Rust hash-sig](https://github.com/b-wagn/hash-sig)
21 changes: 15 additions & 6 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,32 @@ pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});

_ = b.addModule("poseidon", .{
.root_source_file = .{
.cwd_relative = "src/poseidon2/poseidon2.zig",
},
// Generic Poseidon2 module
_ = b.addModule("poseidon2", .{
.root_source_file = b.path("src/poseidon2/poseidon2.zig"),
});

// BabyBear16 instance
_ = b.addModule("babybear16", .{
.root_source_file = b.path("src/instances/babybear16.zig"),
});

// KoalaBear16 instance (compatible with Rust hash-sig)
_ = b.addModule("koalabear16", .{
.root_source_file = b.path("src/instances/koalabear16.zig"),
});

const lib = b.addStaticLibrary(.{
.name = "zig-poseidon",
.root_source_file = .{ .cwd_relative = "src/main.zig" },
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});

b.installArtifact(lib);

const main_tests = b.addTest(.{
.root_source_file = .{ .cwd_relative = "src/main.zig" },
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
Expand Down
3 changes: 3 additions & 0 deletions src/fields/koalabear/montgomery.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// KoalaBear field: p = 2^31 - 2^24 + 1 = 127 * 2^24 + 1 = 2130706433 = 0x7f000001
// This field is used in plonky3 and Rust hash-sig implementations
pub const MontgomeryField = @import("../generic_montgomery.zig").MontgomeryField31(127 * (1 << 24) + 1);
32 changes: 32 additions & 0 deletions src/fields/koalabear/naive.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
const std = @import("std");

// KoalaBear field: p = 2^31 - 2^24 + 1 = 127 * 2^24 + 1 = 2130706433 = 0x7f000001
const modulus = 127 * (1 << 24) + 1;
pub const FieldElem = u32;
pub const MontFieldElem = u32;

pub fn toMontgomery(out1: *MontFieldElem, value: FieldElem) void {
out1.* = value;
}

pub fn toNormal(out1: MontFieldElem) FieldElem {
return out1;
}

pub fn square(out1: *MontFieldElem, value: MontFieldElem) void {
mul(out1, value, value);
}

pub fn add(out1: *MontFieldElem, elem1: MontFieldElem, elem2: MontFieldElem) void {
var tmp: u64 = elem1;
tmp += elem2;
tmp %= modulus;
out1.* = @intCast(tmp);
}

pub fn mul(out1: *MontFieldElem, elem1: MontFieldElem, elem2: MontFieldElem) void {
var tmp: u64 = elem1;
tmp *= elem2;
tmp %= modulus;
out1.* = @intCast(tmp);
}
Loading