├── .github └── workflows │ └── ci.yml ├── .gitignore ├── LICENSE ├── README.md ├── build.zig └── src ├── fields ├── babybear │ ├── montgomery.zig │ └── naive.zig └── generic_montgomery.zig ├── instances └── babybear16.zig ├── main.zig └── poseidon2 └── poseidon2.zig /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Lint and test 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | workflow_dispatch: 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | 15 | - name: Set up Zig 16 | uses: korandoru/setup-zig@v1 17 | with: 18 | zig-version: 0.14.0 19 | 20 | - name: Build 21 | run: zig build 22 | 23 | lint: 24 | runs-on: ubuntu-latest 25 | steps: 26 | - uses: actions/checkout@v2 27 | 28 | - name: Set up Zig 29 | uses: korandoru/setup-zig@v1 30 | with: 31 | zig-version: 0.14.0 32 | 33 | - name: Lint 34 | run: zig fmt --check src/*.zig 35 | 36 | test: 37 | runs-on: ubuntu-latest 38 | steps: 39 | - uses: actions/checkout@v2 40 | 41 | - name: Set up Zig 42 | uses: korandoru/setup-zig@v1 43 | with: 44 | zig-version: 0.14.0 45 | 46 | - name: Test 47 | run: zig build test 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # This file is for zig-specific build artifacts. 2 | # If you have OS-specific or editor-specific files to ignore, 3 | # such as *.swp or .DS_Store, put those in your global 4 | # ~/.gitignore and put this in your ~/.gitconfig: 5 | # 6 | # [core] 7 | # excludesfile = ~/.gitignore 8 | # 9 | # Cheers! 10 | # -andrewrk 11 | 12 | .zig-cache/ 13 | zig-out/ 14 | /release/ 15 | /debug/ 16 | /build/ 17 | /build-*/ 18 | /docgen_tmp/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Ignacio Hagopian 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # poseidon 2 | 3 | A Zig implementation of the Poseidon2 cryptographic hash function. 4 | 5 | ## Supported Configurations 6 | 7 | Currently, this implementation provides: 8 | 9 | - BabyBear finite field with a width of 16 elements 10 | - Generic Montgomery form implementation for finite fields of 31 bits or less 11 | - Compression mode, since it's the recommended mode for Merkle Trees compared to the sponge construction. 12 | 13 | The generic implementation makes it straightforward to add support for additional 31-bit fields. 14 | 15 | ## Project Motivation 16 | 17 | 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. 18 | 19 | With time this repository can keep expaning on features: 20 | 21 | - Add support for more finite fields. 22 | - Add support for the sponge construction. 23 | - Add benchmarks and optimizations. 24 | 25 | ## Compatibility 26 | 27 | This implementation has been cross-validated against the [reference repository](https://github.com/HorizenLabs/poseidon2) cited in the Poseidon2 paper to ensure correctness. 28 | 29 | ## License 30 | 31 | MIT 32 | -------------------------------------------------------------------------------- /build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | pub fn build(b: *std.Build) void { 4 | const target = b.standardTargetOptions(.{}); 5 | const optimize = b.standardOptimizeOption(.{}); 6 | 7 | _ = b.addModule("poseidon", .{ 8 | .root_source_file = .{ 9 | .cwd_relative = "src/poseidon2/poseidon2.zig", 10 | }, 11 | }); 12 | 13 | const lib = b.addStaticLibrary(.{ 14 | .name = "zig-poseidon", 15 | .root_source_file = .{ .cwd_relative = "src/main.zig" }, 16 | .target = target, 17 | .optimize = optimize, 18 | }); 19 | 20 | b.installArtifact(lib); 21 | 22 | const main_tests = b.addTest(.{ 23 | .root_source_file = .{ .cwd_relative = "src/main.zig" }, 24 | .target = target, 25 | .optimize = optimize, 26 | }); 27 | 28 | const run_main_tests = b.addRunArtifact(main_tests); 29 | run_main_tests.has_side_effects = true; 30 | 31 | const test_step = b.step("test", "Run library tests"); 32 | test_step.dependOn(&run_main_tests.step); 33 | } 34 | -------------------------------------------------------------------------------- /src/fields/babybear/montgomery.zig: -------------------------------------------------------------------------------- 1 | pub const MontgomeryField = @import("../generic_montgomery.zig").MontgomeryField31(15 * (1 << 27) + 1); 2 | -------------------------------------------------------------------------------- /src/fields/babybear/naive.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | const modulus = 15 * (1 << 27) + 1; 4 | pub const FieldElem = u32; 5 | pub const MontFieldElem = u32; 6 | 7 | pub fn toMontgomery(out1: *MontFieldElem, value: FieldElem) void { 8 | out1.* = value; 9 | } 10 | 11 | pub fn toNormal(out1: MontFieldElem) FieldElem { 12 | return out1; 13 | } 14 | 15 | pub fn square(out1: *MontFieldElem, value: MontFieldElem) void { 16 | mul(out1, value, value); 17 | } 18 | 19 | pub fn add(out1: *MontFieldElem, elem1: MontFieldElem, elem2: MontFieldElem) void { 20 | var tmp: u64 = elem1; 21 | tmp += elem2; 22 | tmp %= modulus; 23 | out1.* = @intCast(tmp); 24 | } 25 | 26 | pub fn mul(out1: *MontFieldElem, elem1: MontFieldElem, elem2: MontFieldElem) void { 27 | var tmp: u64 = elem1; 28 | tmp *= elem2; 29 | tmp %= modulus; 30 | out1.* = @intCast(tmp); 31 | } 32 | -------------------------------------------------------------------------------- /src/fields/generic_montgomery.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | // MontgomeryField31 is a generic implementation of a field with modulus with at most 31 bits. 4 | pub fn MontgomeryField31(comptime modulus: u32) type { 5 | const R: u64 = 1 << 32; 6 | const R_square_mod_modulus: u64 = @intCast((@as(u128, R) * @as(u128, R)) % modulus); 7 | 8 | // modulus_prime = -modulus^-1 mod R 9 | const modulus_prime = R - euclideanAlgorithm(modulus, R) % R; 10 | std.debug.assert(modulus * modulus_prime % R == R - 1); 11 | 12 | return struct { 13 | pub const FieldElem = u32; 14 | pub const MontFieldElem = struct { 15 | value: u32, 16 | }; 17 | 18 | pub fn toMontgomery(out: *MontFieldElem, value: FieldElem) void { 19 | out.* = .{ .value = montReduce(@as(u64, value) * R_square_mod_modulus) }; 20 | } 21 | 22 | pub fn square(out1: *MontFieldElem, value: MontFieldElem) void { 23 | mul(out1, value, value); 24 | } 25 | 26 | pub fn mul(out1: *MontFieldElem, value: MontFieldElem, arg2: MontFieldElem) void { 27 | out1.* = .{ .value = montReduce(@as(u64, value.value) * @as(u64, arg2.value)) }; 28 | } 29 | 30 | pub fn add(out1: *MontFieldElem, value: MontFieldElem, arg2: MontFieldElem) void { 31 | var tmp = value.value + arg2.value; 32 | if (tmp > modulus) { 33 | tmp -= modulus; 34 | } 35 | out1.* = .{ .value = tmp }; 36 | } 37 | 38 | pub fn toNormal(self: MontFieldElem) FieldElem { 39 | return montReduce(@as(u64, self.value)); 40 | } 41 | 42 | fn montReduce(mont_value: u64) FieldElem { 43 | const tmp = mont_value + (((mont_value & 0xFFFFFFFF) * modulus_prime) & 0xFFFFFFFF) * modulus; 44 | std.debug.assert(tmp % R == 0); 45 | const t = tmp >> 32; 46 | if (t >= modulus) { 47 | return @intCast(t - modulus); 48 | } 49 | return @intCast(t); 50 | } 51 | }; 52 | } 53 | 54 | fn euclideanAlgorithm(a: u64, b: u64) u64 { 55 | var t: i64 = 0; 56 | var new_t: i64 = 1; 57 | var r: i64 = @intCast(b); 58 | var new_r: i64 = @intCast(a); 59 | 60 | while (new_r != 0) { 61 | const quotient = r / new_r; 62 | 63 | const temp_t = t; 64 | t = new_t; 65 | new_t = temp_t - quotient * new_t; 66 | 67 | const temp_r = r; 68 | r = new_r; 69 | new_r = temp_r - quotient * new_r; 70 | } 71 | 72 | if (r != 1) { 73 | @compileError("modular inverse does not exist"); 74 | } 75 | 76 | if (t < 0) { 77 | t += @intCast(b); 78 | } 79 | return @intCast(t); 80 | } 81 | -------------------------------------------------------------------------------- /src/instances/babybear16.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const poseidon2 = @import("../poseidon2/poseidon2.zig"); 3 | const babybear = @import("../fields/babybear/montgomery.zig").MontgomeryField; 4 | 5 | const WIDTH = 16; 6 | const EXTERNAL_ROUNDS = 8; 7 | const INTERNAL_ROUNDS = 13; 8 | const SBOX_DEGREE = 7; 9 | 10 | const DIAGONAL = [WIDTH]u32{ 11 | parseHex("0a632d94"), 12 | parseHex("6db657b7"), 13 | parseHex("56fbdc9e"), 14 | parseHex("052b3d8a"), 15 | parseHex("33745201"), 16 | parseHex("5c03108c"), 17 | parseHex("0beba37b"), 18 | parseHex("258c2e8b"), 19 | parseHex("12029f39"), 20 | parseHex("694909ce"), 21 | parseHex("6d231724"), 22 | parseHex("21c3b222"), 23 | parseHex("3c0904a5"), 24 | parseHex("01d6acda"), 25 | parseHex("27705c83"), 26 | parseHex("5231c802"), 27 | }; 28 | 29 | const Poseidon2BabyBear = poseidon2.Poseidon2( 30 | babybear, 31 | WIDTH, 32 | INTERNAL_ROUNDS, 33 | EXTERNAL_ROUNDS, 34 | SBOX_DEGREE, 35 | DIAGONAL, 36 | EXTERNAL_RCS, 37 | INTERNAL_RCS, 38 | ); 39 | 40 | const EXTERNAL_RCS = [EXTERNAL_ROUNDS][WIDTH]u32{ 41 | .{ 42 | parseHex("69cbb6af"), 43 | parseHex("46ad93f9"), 44 | parseHex("60a00f4e"), 45 | parseHex("6b1297cd"), 46 | parseHex("23189afe"), 47 | parseHex("732e7bef"), 48 | parseHex("72c246de"), 49 | parseHex("2c941900"), 50 | parseHex("0557eede"), 51 | parseHex("1580496f"), 52 | parseHex("3a3ea77b"), 53 | parseHex("54f3f271"), 54 | parseHex("0f49b029"), 55 | parseHex("47872fe1"), 56 | parseHex("221e2e36"), 57 | parseHex("1ab7202e"), 58 | }, 59 | .{ 60 | parseHex("487779a6"), 61 | parseHex("3851c9d8"), 62 | parseHex("38dc17c0"), 63 | parseHex("209f8849"), 64 | parseHex("268dcee8"), 65 | parseHex("350c48da"), 66 | parseHex("5b9ad32e"), 67 | parseHex("0523272b"), 68 | parseHex("3f89055b"), 69 | parseHex("01e894b2"), 70 | parseHex("13ddedde"), 71 | parseHex("1b2ef334"), 72 | parseHex("7507d8b4"), 73 | parseHex("6ceeb94e"), 74 | parseHex("52eb6ba2"), 75 | parseHex("50642905"), 76 | }, 77 | .{ 78 | parseHex("05453f3f"), 79 | parseHex("06349efc"), 80 | parseHex("6922787c"), 81 | parseHex("04bfff9c"), 82 | parseHex("768c714a"), 83 | parseHex("3e9ff21a"), 84 | parseHex("15737c9c"), 85 | parseHex("2229c807"), 86 | parseHex("0d47f88c"), 87 | parseHex("097e0ecc"), 88 | parseHex("27eadba0"), 89 | parseHex("2d7d29e4"), 90 | parseHex("3502aaa0"), 91 | parseHex("0f475fd7"), 92 | parseHex("29fbda49"), 93 | parseHex("018afffd"), 94 | }, 95 | .{ 96 | parseHex("0315b618"), 97 | parseHex("6d4497d1"), 98 | parseHex("1b171d9e"), 99 | parseHex("52861abd"), 100 | parseHex("2e5d0501"), 101 | parseHex("3ec8646c"), 102 | parseHex("6e5f250a"), 103 | parseHex("148ae8e6"), 104 | parseHex("17f5fa4a"), 105 | parseHex("3e66d284"), 106 | parseHex("0051aa3b"), 107 | parseHex("483f7913"), 108 | parseHex("2cfe5f15"), 109 | parseHex("023427ca"), 110 | parseHex("2cc78315"), 111 | parseHex("1e36ea47"), 112 | }, 113 | .{ 114 | parseHex("7290a80d"), 115 | parseHex("6f7e5329"), 116 | parseHex("598ec8a8"), 117 | parseHex("76a859a0"), 118 | parseHex("6559e868"), 119 | parseHex("657b83af"), 120 | parseHex("13271d3f"), 121 | parseHex("1f876063"), 122 | parseHex("0aeeae37"), 123 | parseHex("706e9ca6"), 124 | parseHex("46400cee"), 125 | parseHex("72a05c26"), 126 | parseHex("2c589c9e"), 127 | parseHex("20bd37a7"), 128 | parseHex("6a2d3d10"), 129 | parseHex("20523767"), 130 | }, 131 | .{ 132 | parseHex("5b8fe9c4"), 133 | parseHex("2aa501d6"), 134 | parseHex("1e01ac3e"), 135 | parseHex("1448bc54"), 136 | parseHex("5ce5ad1c"), 137 | parseHex("4918a14d"), 138 | parseHex("2c46a83f"), 139 | parseHex("4fcf6876"), 140 | parseHex("61d8d5c8"), 141 | parseHex("6ddf4ff9"), 142 | parseHex("11fda4d3"), 143 | parseHex("02933a8f"), 144 | parseHex("170eaf81"), 145 | parseHex("5a9c314f"), 146 | parseHex("49a12590"), 147 | parseHex("35ec52a1"), 148 | }, 149 | .{ 150 | parseHex("58eb1611"), 151 | parseHex("5e481e65"), 152 | parseHex("367125c9"), 153 | parseHex("0eba33ba"), 154 | parseHex("1fc28ded"), 155 | parseHex("066399ad"), 156 | parseHex("0cbec0ea"), 157 | parseHex("75fd1af0"), 158 | parseHex("50f5bf4e"), 159 | parseHex("643d5f41"), 160 | parseHex("6f4fe718"), 161 | parseHex("5b3cbbde"), 162 | parseHex("1e3afb3e"), 163 | parseHex("296fb027"), 164 | parseHex("45e1547b"), 165 | parseHex("4a8db2ab"), 166 | }, 167 | .{ 168 | parseHex("59986d19"), 169 | parseHex("30bcdfa3"), 170 | parseHex("1db63932"), 171 | parseHex("1d7c2824"), 172 | parseHex("53b33681"), 173 | parseHex("0673b747"), 174 | parseHex("038a98a3"), 175 | parseHex("2c5bce60"), 176 | parseHex("351979cd"), 177 | parseHex("5008fb73"), 178 | parseHex("547bca78"), 179 | parseHex("711af481"), 180 | parseHex("3f93bf64"), 181 | parseHex("644d987b"), 182 | parseHex("3c8bcd87"), 183 | parseHex("608758b8"), 184 | }, 185 | }; 186 | 187 | const INTERNAL_RCS = [INTERNAL_ROUNDS]u32{ 188 | parseHex("5a8053c0"), 189 | parseHex("693be639"), 190 | parseHex("3858867d"), 191 | parseHex("19334f6b"), 192 | parseHex("128f0fd8"), 193 | parseHex("4e2b1ccb"), 194 | parseHex("61210ce0"), 195 | parseHex("3c318939"), 196 | parseHex("0b5b2f22"), 197 | parseHex("2edb11d5"), 198 | parseHex("213effdf"), 199 | parseHex("0cac4606"), 200 | parseHex("241af16d"), 201 | }; 202 | 203 | fn parseHex(s: []const u8) u32 { 204 | @setEvalBranchQuota(100_000); 205 | return std.fmt.parseInt(u32, s, 16) catch @compileError("OOM"); 206 | } 207 | 208 | // Tests vectors were generated from the Poseidon2 reference repository: github.com/HorizenLabs/poseidon2 209 | const testVector = struct { 210 | input_state: [WIDTH]u32, 211 | output_state: [WIDTH]u32, 212 | }; 213 | test "reference repo" { 214 | @setEvalBranchQuota(100_000); 215 | 216 | const finite_fields = [_]type{ 217 | @import("../fields/babybear/montgomery.zig").MontgomeryField, 218 | @import("../fields/babybear/naive.zig"), 219 | }; 220 | inline for (finite_fields) |F| { 221 | const TestPoseidon2BabyBear = poseidon2.Poseidon2( 222 | F, 223 | WIDTH, 224 | INTERNAL_ROUNDS, 225 | EXTERNAL_ROUNDS, 226 | SBOX_DEGREE, 227 | DIAGONAL, 228 | EXTERNAL_RCS, 229 | INTERNAL_RCS, 230 | ); 231 | const tests_vectors = [_]testVector{ 232 | .{ 233 | .input_state = std.mem.zeroes([WIDTH]u32), 234 | .output_state = .{ 1337856655, 1843094405, 328115114, 964209316, 1365212758, 1431554563, 210126733, 1214932203, 1929553766, 1647595522, 1496863878, 324695999, 1569728319, 1634598391, 597968641, 679989771 }, 235 | }, 236 | .{ 237 | .input_state = [_]F.FieldElem{42} ** 16, 238 | .output_state = .{ 1000818763, 32822117, 1516162362, 1002505990, 932515653, 770559770, 350012663, 846936440, 1676802609, 1007988059, 883957027, 738985594, 6104526, 338187715, 611171673, 414573522 }, 239 | }, 240 | }; 241 | for (tests_vectors) |test_vector| { 242 | try std.testing.expectEqual(test_vector.output_state, testPermutation(TestPoseidon2BabyBear, test_vector.input_state)); 243 | } 244 | } 245 | } 246 | 247 | test "finite field implementation coherency" { 248 | const Poseidon2BabyBearNaive = poseidon2.Poseidon2( 249 | @import("../fields/babybear/naive.zig"), 250 | WIDTH, 251 | INTERNAL_ROUNDS, 252 | EXTERNAL_ROUNDS, 253 | SBOX_DEGREE, 254 | DIAGONAL, 255 | EXTERNAL_RCS, 256 | INTERNAL_RCS, 257 | ); 258 | const Poseidon2BabyBearOptimized = poseidon2.Poseidon2( 259 | @import("../fields/babybear/montgomery.zig").MontgomeryField, 260 | WIDTH, 261 | INTERNAL_ROUNDS, 262 | EXTERNAL_ROUNDS, 263 | SBOX_DEGREE, 264 | DIAGONAL, 265 | EXTERNAL_RCS, 266 | INTERNAL_RCS, 267 | ); 268 | var rand = std.Random.DefaultPrng.init(42); 269 | for (0..10_000) |_| { 270 | var input_state: [WIDTH]u32 = undefined; 271 | for (0..WIDTH) |index| { 272 | input_state[index] = @truncate(rand.next()); 273 | } 274 | 275 | try std.testing.expectEqual(testPermutation(Poseidon2BabyBearNaive, input_state), testPermutation(Poseidon2BabyBearOptimized, input_state)); 276 | } 277 | } 278 | 279 | fn testPermutation(comptime Poseidon2: type, state: [WIDTH]u32) [WIDTH]u32 { 280 | const F = Poseidon2.Field; 281 | var mont_state: [WIDTH]F.MontFieldElem = undefined; 282 | inline for (0..WIDTH) |j| { 283 | F.toMontgomery(&mont_state[j], state[j]); 284 | } 285 | Poseidon2.permutation(&mont_state); 286 | var ret: [WIDTH]u32 = undefined; 287 | inline for (0..WIDTH) |j| { 288 | ret[j] = F.toNormal(mont_state[j]); 289 | } 290 | return ret; 291 | } 292 | -------------------------------------------------------------------------------- /src/main.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | test "babyBear16" { 4 | std.testing.log_level = .debug; 5 | _ = @import("instances/babybear16.zig"); 6 | } 7 | -------------------------------------------------------------------------------- /src/poseidon2/poseidon2.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const assert = std.debug.assert; 3 | 4 | pub fn Poseidon2( 5 | comptime F: type, 6 | comptime width: comptime_int, 7 | comptime int_rounds: comptime_int, 8 | comptime ext_rounds: comptime_int, 9 | comptime sbox_degree: comptime_int, 10 | internal_diagonal: [width]u32, 11 | external_rcs: [ext_rounds][width]u32, 12 | internal_rcs: [int_rounds]u32, 13 | ) type { 14 | comptime var ext_rcs: [ext_rounds][width]F.MontFieldElem = undefined; 15 | for (0..ext_rounds) |i| { 16 | for (0..width) |j| { 17 | F.toMontgomery(&ext_rcs[i][j], external_rcs[i][j]); 18 | } 19 | } 20 | comptime var int_rcs: [int_rounds]F.MontFieldElem = undefined; 21 | for (0..int_rounds) |i| { 22 | F.toMontgomery(&int_rcs[i], internal_rcs[i]); 23 | } 24 | comptime var int_diagonal: [width]F.MontFieldElem = undefined; 25 | for (0..width) |i| { 26 | F.toMontgomery(&int_diagonal[i], internal_diagonal[i]); 27 | } 28 | return struct { 29 | pub const Field = F; 30 | pub const State = [width]F.MontFieldElem; 31 | 32 | pub fn compress(comptime output_len: comptime_int, input: [width]F.FieldElem) [output_len]F.FieldElem { 33 | assert(output_len <= width, "output_len must be <= width"); 34 | 35 | var state: State = undefined; 36 | inline for (0..width) |i| { 37 | F.toMontgomery(&state[i], input[i]); 38 | } 39 | permutation(&state); 40 | inline for (0..width) |i| { 41 | F.add(&state[i], state[i], input[i]); 42 | F.fromMontgomery(&state[i], state[i]); 43 | } 44 | return state[0..output_len]; 45 | } 46 | 47 | pub fn permutation(state: *State) void { 48 | mulExternal(state); 49 | inline for (0..ext_rounds / 2) |r| { 50 | addRCs(state, r); 51 | inline for (0..width) |i| { 52 | state[i] = sbox(state[i]); 53 | } 54 | mulExternal(state); 55 | } 56 | 57 | const start = ext_rounds / 2; 58 | const end = start + int_rounds; 59 | for (start..end) |r| { 60 | F.add(&state[0], state[0], int_rcs[r - start]); 61 | state[0] = sbox(state[0]); 62 | mulInternal(state); 63 | } 64 | 65 | inline for (end..end + ext_rounds / 2) |r| { 66 | addRCs(state, r - int_rounds); 67 | inline for (0..width) |i| { 68 | state[i] = sbox(state[i]); 69 | } 70 | mulExternal(state); 71 | } 72 | } 73 | 74 | inline fn mulExternal(state: *State) void { 75 | if (width < 8) { 76 | @compileError("only widths >= 8 are supported"); 77 | } 78 | if (width % 4 != 0) { 79 | @compileError("only widths multiple of 4 are supported"); 80 | } 81 | mulM4(state); 82 | 83 | // Calculate the "base" result as if we're doing 84 | // circ(M4, M4, ...) * state. 85 | var base = std.mem.zeroes([4]F.MontFieldElem); 86 | inline for (0..4) |i| { 87 | inline for (0..width / 4) |j| { 88 | F.add(&base[i], base[i], state[(j << 2) + i]); 89 | } 90 | } 91 | // base has circ(M4, M4, ...)*state, add state now 92 | // to add the corresponding extra M4 "through the diagonal". 93 | for (0..width) |i| { 94 | F.add(&state[i], state[i], base[i & 0b11]); 95 | } 96 | } 97 | 98 | // mulM4 calculates 'M4*state' in a way we can later can calculate 99 | // circ(2*M4, M4, ...)*state from it. 100 | inline fn mulM4(input: *State) void { 101 | // Use HorizenLabs minimal multiplication algorithm to perform 102 | // the least amount of operations for it. Similar to an 103 | // addition/multiplication chain. 104 | const t4 = width / 4; 105 | inline for (0..t4) |i| { 106 | const start_index = i * 4; 107 | var t_0: F.MontFieldElem = undefined; 108 | F.add(&t_0, input[start_index], input[start_index + 1]); 109 | var t_1: F.MontFieldElem = undefined; 110 | F.add(&t_1, input[start_index + 2], input[start_index + 3]); 111 | var t_2: F.MontFieldElem = undefined; 112 | F.add(&t_2, input[start_index + 1], input[start_index + 1]); 113 | F.add(&t_2, t_2, t_1); 114 | var t_3: F.MontFieldElem = undefined; 115 | F.add(&t_3, input[start_index + 3], input[start_index + 3]); 116 | F.add(&t_3, t_3, t_0); 117 | var t_4 = t_1; 118 | F.add(&t_4, t_4, t_4); 119 | F.add(&t_4, t_4, t_4); 120 | F.add(&t_4, t_4, t_3); 121 | var t_5 = t_0; 122 | F.add(&t_5, t_5, t_5); 123 | F.add(&t_5, t_5, t_5); 124 | F.add(&t_5, t_5, t_2); 125 | var t_6 = t_3; 126 | F.add(&t_6, t_6, t_5); 127 | var t_7 = t_2; 128 | F.add(&t_7, t_7, t_4); 129 | input[start_index] = t_6; 130 | input[start_index + 1] = t_5; 131 | input[start_index + 2] = t_7; 132 | input[start_index + 3] = t_4; 133 | } 134 | } 135 | 136 | inline fn mulInternal(state: *State) void { 137 | // Calculate (1, ...) * state. 138 | var state_sum = state[0]; 139 | inline for (1..width) |i| { 140 | F.add(&state_sum, state_sum, state[i]); 141 | } 142 | // Add corresponding diagonal factor. 143 | inline for (0..state.len) |i| { 144 | F.mul(&state[i], state[i], int_diagonal[i]); 145 | F.add(&state[i], state[i], state_sum); 146 | } 147 | } 148 | 149 | inline fn sbox(e: F.MontFieldElem) F.MontFieldElem { 150 | return switch (sbox_degree) { 151 | 7 => blk: { 152 | var e_squared: F.MontFieldElem = undefined; 153 | F.square(&e_squared, e); 154 | var e_forth: F.MontFieldElem = undefined; 155 | F.square(&e_forth, e_squared); 156 | var res: F.MontFieldElem = undefined; 157 | F.mul(&res, e_forth, e_squared); 158 | F.mul(&res, res, e); 159 | break :blk res; 160 | }, 161 | else => @compileError("sbox degree not supported"), 162 | }; 163 | } 164 | 165 | inline fn addRCs(state: *State, round: u8) void { 166 | inline for (0..width) |i| { 167 | F.add(&state[i], state[i], ext_rcs[round][i]); 168 | } 169 | } 170 | }; 171 | } 172 | --------------------------------------------------------------------------------