-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathposeidon.js
116 lines (105 loc) · 4.06 KB
/
poseidon.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
// source: https://github.com/iden3/circomlib/blob/v0.5.5/src/poseidon.js
const { F } = require("./utils");
function unstringifyBigInts(o) {
if (typeof o == "string" && /^[0-9]+$/.test(o)) {
return BigInt(o);
} else if (typeof o == "string" && /^0x[0-9a-fA-F]+$/.test(o)) {
return BigInt(o);
} else if (Array.isArray(o)) {
return o.map(unstringifyBigInts);
} else if (typeof o == "object") {
if (o === null) return null;
const res = {};
const keys = Object.keys(o);
keys.forEach((k) => {
res[k] = unstringifyBigInts(o[k]);
});
return res;
} else {
return o;
}
}
// Parameters are generated by a reference script https://extgit.iaik.tugraz.at/krypto/hadeshash/-/blob/master/code/generate_parameters_grain.sage
// Used like so: sage generate_parameters_grain.sage 1 0 254 2 8 56 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001
const opt = unstringifyBigInts(require("./poseidon_constants_opt.json"));
// Using recommended parameters from whitepaper https://eprint.iacr.org/2019/458.pdf (table 2, table 8)
// Generated by https://extgit.iaik.tugraz.at/krypto/hadeshash/-/blob/master/code/calc_round_numbers.py
// And rounded up to nearest integer that divides by t
const N_ROUNDS_F = 8;
const N_ROUNDS_P = [
56, 57, 56, 60, 60, 63, 64, 63, 60, 66, 60, 65, 70, 60, 64, 68
];
const pow5 = (a) => F.mul(a, F.square(F.square(a)));
Object.assign(module.exports, {
poseidon: function (inputs) {
if (!Array.isArray(inputs)) {
throw new Error("Invalid inputs: expected an array");
} else if (
inputs.length == 0 ||
inputs.length >= N_ROUNDS_P.length - 1
) {
throw new Error(
`Invalid inputs length. Expected 0 < inputs.length < ${
N_ROUNDS_P.length - 1
}.`
);
}
const t = inputs.length + 1;
const nRoundsF = N_ROUNDS_F;
const nRoundsP = N_ROUNDS_P[t - 2];
const C = opt.C[t - 2];
const S = opt.S[t - 2];
const M = opt.M[t - 2];
const P = opt.P[t - 2];
let state = [F.zero, ...inputs.map((a) => F.e(a))];
state = state.map((a, i) => F.add(a, C[i]));
for (let r = 0; r < nRoundsF / 2 - 1; r++) {
state = state.map((a) => pow5(a));
state = state.map((a, i) => F.add(a, C[(r + 1) * t + i]));
state = state.map((_, i) =>
state.reduce(
(acc, a, j) => F.add(acc, F.mul(M[j][i], a)),
F.zero
)
);
}
state = state.map((a) => pow5(a));
state = state.map((a, i) =>
F.add(a, C[(nRoundsF / 2 - 1 + 1) * t + i])
);
state = state.map((_, i) =>
state.reduce((acc, a, j) => F.add(acc, F.mul(P[j][i], a)), F.zero)
);
for (let r = 0; r < nRoundsP; r++) {
state[0] = pow5(state[0]);
state[0] = F.add(state[0], C[(nRoundsF / 2 + 1) * t + r]);
const s0 = state.reduce((acc, a, j) => {
return F.add(acc, F.mul(S[(t * 2 - 1) * r + j], a));
}, F.zero);
for (let k = 1; k < t; k++) {
state[k] = F.add(
state[k],
F.mul(state[0], S[(t * 2 - 1) * r + t + k - 1])
);
}
state[0] = s0;
}
for (let r = 0; r < nRoundsF / 2 - 1; r++) {
state = state.map((a) => pow5(a));
state = state.map((a, i) =>
F.add(a, C[(nRoundsF / 2 + 1) * t + nRoundsP + r * t + i])
);
state = state.map((_, i) =>
state.reduce(
(acc, a, j) => F.add(acc, F.mul(M[j][i], a)),
F.zero
)
);
}
state = state.map((a) => pow5(a));
state = state.map((_, i) =>
state.reduce((acc, a, j) => F.add(acc, F.mul(M[j][i], a)), F.zero)
);
return F.normalize(state[0]);
}
});