|
|
@ -1,121 +1,49 @@ |
|
|
|
const Scalar = require("ffjavascript").Scalar; |
|
|
|
const blake2b = require("blake2b"); |
|
|
|
const assert = require("assert"); |
|
|
|
const Scalar = require("ffjavascript").Scalar; |
|
|
|
const ZqField = require("ffjavascript").ZqField; |
|
|
|
const utils = require("ffjavascript").utils; |
|
|
|
const { unstringifyBigInts } = require("ffjavascript").utils; |
|
|
|
|
|
|
|
// Prime 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001
|
|
|
|
const F = new ZqField(Scalar.fromString("21888242871839275222246405745257275088548364400416034343698204186575808495617")); |
|
|
|
exports.F = F; |
|
|
|
|
|
|
|
const SEED = "poseidon"; |
|
|
|
const NROUNDSF = 8; |
|
|
|
const NROUNDSP = 57; |
|
|
|
const T = 6; |
|
|
|
// 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 { C, M } = unstringifyBigInts(require("./poseidon_constants.json")); |
|
|
|
|
|
|
|
function getPseudoRandom(seed, n) { |
|
|
|
const res = []; |
|
|
|
let input = Buffer.from(seed); |
|
|
|
let h = blake2b(32).update(input).digest(); |
|
|
|
while (res.length<n) { |
|
|
|
const n = F.normalize(utils.leBuff2int(Buffer.from(h))); |
|
|
|
res.push(n); |
|
|
|
h = blake2b(32).update(h).digest(); |
|
|
|
} |
|
|
|
// 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]; |
|
|
|
|
|
|
|
return res; |
|
|
|
} |
|
|
|
const pow5 = a => F.mul(a, F.square(F.square(a, a))); |
|
|
|
|
|
|
|
function allDifferent(v) { |
|
|
|
for (let i=0; i<v.length; i++) { |
|
|
|
if (F.isZero(v[i])) return false; |
|
|
|
for (let j=i+1; j<v.length; j++) { |
|
|
|
if (F.eq(v[i],v[j])) return false; |
|
|
|
} |
|
|
|
} |
|
|
|
return true; |
|
|
|
} |
|
|
|
|
|
|
|
exports.getMatrix = (t, seed, nRounds) => { |
|
|
|
if (typeof seed === "undefined") seed = SEED; |
|
|
|
if (typeof nRounds === "undefined") nRounds = NROUNDSF + NROUNDSP; |
|
|
|
if (typeof t === "undefined") t = T; |
|
|
|
assert(t<=6); // Force the same matrix for all.
|
|
|
|
t=6; |
|
|
|
let nonce = "0000"; |
|
|
|
let cmatrix = getPseudoRandom(seed+"_matrix_"+nonce, t*2); |
|
|
|
while (!allDifferent(cmatrix)) { |
|
|
|
nonce = (Number(nonce)+1)+""; |
|
|
|
while(nonce.length<4) nonce = "0"+nonce; |
|
|
|
cmatrix = getPseudoRandom(seed+"_matrix_"+nonce, t*2); |
|
|
|
} |
|
|
|
|
|
|
|
const M = new Array(t); |
|
|
|
for (let i=0; i<t; i++) { |
|
|
|
M[i] = new Array(t); |
|
|
|
for (let j=0; j<t; j++) { |
|
|
|
M[i][j] = F.normalize(F.inv(F.sub(cmatrix[i], cmatrix[t+j]))); |
|
|
|
} |
|
|
|
} |
|
|
|
return M; |
|
|
|
}; |
|
|
|
function poseidon(inputs) { |
|
|
|
assert(inputs.length > 0); |
|
|
|
assert(inputs.length < N_ROUNDS_P.length - 1); |
|
|
|
|
|
|
|
exports.getConstants = (t, seed, nRounds) => { |
|
|
|
if (typeof seed === "undefined") seed = SEED; |
|
|
|
if (typeof nRounds === "undefined") nRounds = NROUNDSF + NROUNDSP; |
|
|
|
if (typeof t === "undefined") t = T; |
|
|
|
const cts = getPseudoRandom(seed+"_constants", nRounds); |
|
|
|
return cts; |
|
|
|
}; |
|
|
|
const t = inputs.length + 1; |
|
|
|
const nRoundsF = N_ROUNDS_F; |
|
|
|
const nRoundsP = N_ROUNDS_P[t - 2]; |
|
|
|
|
|
|
|
function ark(state, c) { |
|
|
|
for (let j=0; j<state.length; j++ ) { |
|
|
|
state[j] = F.add(state[j], c); |
|
|
|
} |
|
|
|
} |
|
|
|
let state = [...inputs.map(a => F.e(a)), F.zero]; |
|
|
|
for (let r = 0; r < nRoundsF + nRoundsP; r++) { |
|
|
|
state = state.map((a, i) => F.add(a, C[t - 2][r * t + i])); |
|
|
|
|
|
|
|
function sigma(a) { |
|
|
|
return F.mul(a, F.square(F.square(a,a))); |
|
|
|
} |
|
|
|
if (r < nRoundsF / 2 || r >= nRoundsF / 2 + nRoundsP) { |
|
|
|
state = state.map(a => pow5(a)); |
|
|
|
} else { |
|
|
|
state[0] = pow5(state[0]); |
|
|
|
} |
|
|
|
|
|
|
|
function mix(state, M) { |
|
|
|
const newState = new Array(state.length); |
|
|
|
for (let i=0; i<state.length; i++) { |
|
|
|
newState[i] = F.zero; |
|
|
|
for (let j=0; j<state.length; j++) { |
|
|
|
newState[i] = F.add(newState[i], F.mul(M[i][j], state[j]) ); |
|
|
|
// no matrix multiplication in the last round
|
|
|
|
if (r < nRoundsF + nRoundsP - 1) { |
|
|
|
state = state.map((_, i) => |
|
|
|
state.reduce((acc, a, j) => F.add(acc, F.mul(M[t - 2][j][i], a)), F.zero) |
|
|
|
); |
|
|
|
} |
|
|
|
} |
|
|
|
for (let i=0; i<state.length; i++) state[i] = newState[i]; |
|
|
|
return F.normalize(state[0]); |
|
|
|
} |
|
|
|
|
|
|
|
exports.createHash = (t, nRoundsF, nRoundsP, seed) => { |
|
|
|
|
|
|
|
if (typeof seed === "undefined") seed = SEED; |
|
|
|
if (typeof nRoundsF === "undefined") nRoundsF = NROUNDSF; |
|
|
|
if (typeof nRoundsP === "undefined") nRoundsP = NROUNDSP; |
|
|
|
if (typeof t === "undefined") t = T; |
|
|
|
|
|
|
|
assert(nRoundsF % 2 == 0); |
|
|
|
const C = exports.getConstants(t, seed, nRoundsF + nRoundsP); |
|
|
|
const M = exports.getMatrix(t, seed, nRoundsF + nRoundsP); |
|
|
|
return function(inputs) { |
|
|
|
let state = []; |
|
|
|
assert(inputs.length <= t); |
|
|
|
assert(inputs.length > 0); |
|
|
|
for (let i=0; i<inputs.length; i++) state[i] = F.e(inputs[i]); |
|
|
|
for (let i=inputs.length; i<t; i++) state[i] = F.zero; |
|
|
|
|
|
|
|
for (let i=0; i< nRoundsF + nRoundsP; i++) { |
|
|
|
ark(state, C[i]); |
|
|
|
if ((i<nRoundsF/2) || (i >= nRoundsF/2 + nRoundsP)) { |
|
|
|
for (let j=0; j<t; j++) state[j] = sigma(state[j]); |
|
|
|
} else { |
|
|
|
state[0] = sigma(state[0]); |
|
|
|
} |
|
|
|
mix(state, M); |
|
|
|
} |
|
|
|
return F.normalize(state[0]); |
|
|
|
}; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
module.exports = poseidon; |