Skip to content

Commit 5c68c7e

Browse files
authored
feat: Plonky3 compatible poseidon2 hash function (#9)
* fix: Made Poseidon2KoalaBera const as public and added semantic versioning * fix: Exported the koalabear16 module properly from root.zig * fix: corrected lint errors * fix: corrected error in ci workflow * fix: Corrected the GH workflow to release a verion on the release branch * feat: Add KoalaBear24 instance * fix: Fixed lint errors * fix: bug fixes in koalabear24 instance * fix: correction to be rust compatible * fix: bug fix of repeated patterns in hash * fix: bug fix * feat: Added hash-sig specific koalabear implementation * chore: Updated .gitignore * fix: Fixed tests * feat: Added plonky3 compatible poseidon hash function which is compatible with rust implementation * fix: lint errors * fix: refactor and removed duplicate code * fix: fixed inverse in montgomery form
1 parent 3f64d0b commit 5c68c7e

File tree

5 files changed

+111
-0
lines changed

5 files changed

+111
-0
lines changed

build.zig

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ pub fn build(b: *std.Build) void {
2020

2121
b.installArtifact(lib);
2222

23+
const lint_cmd = b.addSystemCommand(&.{ "zig", "fmt", "--check", "src" });
24+
const lint_step = b.step("lint", "Run zig fmt --check on source files");
25+
lint_step.dependOn(&lint_cmd.step);
26+
2327
const main_tests = b.addTest(.{
2428
.root_source_file = b.path("src/main.zig"),
2529
.target = target,

src/fields/generic_montgomery.zig

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ pub fn MontgomeryField31(comptime modulus: u32) type {
4040
return montReduce(@as(u64, self.value));
4141
}
4242

43+
pub fn inverse(out: *MontFieldElem, value: MontFieldElem) void {
44+
const normal = montReduce(@as(u64, value.value));
45+
const inv_normal = modInverse(normal, modulus);
46+
toMontgomery(out, inv_normal);
47+
}
48+
4349
fn montReduce(mont_value: u64) FieldElem {
4450
const tmp = mont_value + (((mont_value & 0xFFFFFFFF) * modulus_prime) & 0xFFFFFFFF) * modulus;
4551
std.debug.assert(tmp % R == 0);
@@ -79,3 +85,32 @@ fn euclideanAlgorithm(a: u64, b: u64) u64 {
7985
}
8086
return @intCast(t);
8187
}
88+
89+
fn modInverse(a: u32, m: u32) u32 {
90+
if (a == 0) return 0;
91+
92+
var old_r = a;
93+
var r = m;
94+
var old_s: i32 = 1;
95+
var s: i32 = 0;
96+
97+
while (r != 0) {
98+
const quotient = old_r / r;
99+
const temp_r = r;
100+
r = old_r - quotient * r;
101+
old_r = temp_r;
102+
103+
const temp_s = s;
104+
s = old_s - @as(i32, @intCast(quotient)) * s;
105+
old_s = temp_s;
106+
}
107+
108+
if (old_r > 1) {
109+
return 0;
110+
}
111+
112+
if (old_s < 0) {
113+
return @as(u32, @intCast(old_s + @as(i32, @intCast(m))));
114+
}
115+
return @as(u32, @intCast(old_s));
116+
}

src/plonky3_poseidon2/README.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Poseidon2 Implementation for Zig
2+
3+
This module exposes the KoalaBear Poseidon2 instances that match Plonky3's parameters while reusing the shared `poseidon2/poseidon2.zig` core and the existing field arithmetic in `fields/koalabear/montgomery.zig`.
4+
5+
## Features
6+
7+
- **Plonky3 Compatible**: Re-exports the KoalaBear Poseidon2 instances and round constants used by Plonky3
8+
- **Shared Core**: Delegates to the generic `poseidon2` engine that powers the rest of this repository
9+
- **KoalaBear Field**: Uses the standard Montgomery formulation from `fields/koalabear/montgomery.zig`
10+
- **Poseidon2-16 and Poseidon2-24**: Provides ready-to-use 16- and 24-width permutations
11+
12+
## Usage
13+
14+
```zig
15+
const poseidon2 = @import("plonky3_poseidon2/root.zig");
16+
17+
// Create Montgomery field elements (KoalaBear)
18+
var state16: [16]poseidon2.Field.MontFieldElem = undefined;
19+
// initialise state16...
20+
poseidon2.poseidon2_16(&state16);
21+
22+
var state24: [24]poseidon2.Field.MontFieldElem = undefined;
23+
// initialise state24...
24+
poseidon2.poseidon2_24(&state24);
25+
```
26+
27+
## API Reference
28+
29+
### Field Alias
30+
- `Field`: Alias of `fields/koalabear/montgomery.zig`'s `MontgomeryField`
31+
32+
### Poseidon2 Types
33+
- `Poseidon2KoalaBear16Plonky3`: The width-16 Poseidon2 instance (Plonky3 parameters)
34+
- `Poseidon2KoalaBear24Plonky3`: The width-24 Poseidon2 instance (Plonky3 parameters)
35+
36+
### Convenience Functions
37+
- `poseidon2_16(state: *[16]Field.MontFieldElem)`: In-place width-16 permutation
38+
- `poseidon2_24(state: *[24]Field.MontFieldElem)`: In-place width-24 permutation
39+
40+
## Implementation Details
41+
42+
Both instances are generated by feeding the Plonky3 round constants into the shared `poseidon2.Poseidon2` template. This keeps the implementation identical to the other Poseidon variants in this repository while guaranteeing constant parity with Plonky3.
43+
44+
## Testing
45+
46+
The implementation has been tested against Plonky3's Poseidon2 implementation to ensure compatibility. All field operations, round constants, and permutation steps match exactly.
47+
48+
## Files
49+
50+
- `root.zig`: Main module exports and thin wrappers over shared instances
51+

src/plonky3_poseidon2/root.zig

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Poseidon2 implementation compatible with Plonky3 KoalaBear field
2+
3+
const koalabear16 = @import("../instances/koalabear16_generic.zig");
4+
const koalabear24 = @import("../instances/koalabear24_generic.zig");
5+
const MontgomeryField = @import("../fields/koalabear/montgomery.zig").MontgomeryField;
6+
7+
pub const Field = MontgomeryField;
8+
9+
pub const Poseidon2KoalaBear16Plonky3 = koalabear16.Poseidon2KoalaBear;
10+
pub const Poseidon2KoalaBear24Plonky3 = koalabear24.Poseidon2KoalaBear;
11+
pub const Poseidon2KoalaBear16 = Poseidon2KoalaBear16Plonky3;
12+
pub const Poseidon2KoalaBear24 = Poseidon2KoalaBear24Plonky3;
13+
14+
pub fn poseidon2_16(state: *[16]Field.MontFieldElem) void {
15+
Poseidon2KoalaBear16Plonky3.permutation(state);
16+
}
17+
18+
pub fn poseidon2_24(state: *[24]Field.MontFieldElem) void {
19+
Poseidon2KoalaBear24Plonky3.permutation(state);
20+
}

src/root.zig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ pub const koalabear = @import("instances/koalabear.zig");
66
pub const koalabear16_generic = @import("instances/koalabear16_generic.zig");
77
pub const koalabear24_generic = @import("instances/koalabear24_generic.zig");
88
pub const poseidon2 = @import("poseidon2/poseidon2.zig");
9+
pub const plonky3_poseidon2 = @import("plonky3_poseidon2/root.zig");
910

1011
// Convenience type exports
1112
pub const Poseidon2BabyBear = babybear16.Poseidon2BabyBear;

0 commit comments

Comments
 (0)