@ -1,121 +1,49 @@ |
|||||
const Scalar = require("ffjavascript").Scalar; |
|
||||
const blake2b = require("blake2b"); |
|
||||
const assert = require("assert"); |
const assert = require("assert"); |
||||
|
const Scalar = require("ffjavascript").Scalar; |
||||
const ZqField = require("ffjavascript").ZqField; |
const ZqField = require("ffjavascript").ZqField; |
||||
const utils = require("ffjavascript").utils; |
|
||||
|
const { unstringifyBigInts } = require("ffjavascript").utils; |
||||
|
|
||||
|
// Prime 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001
|
||||
const F = new ZqField(Scalar.fromString("21888242871839275222246405745257275088548364400416034343698204186575808495617")); |
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; |
@ -1,16 +0,0 @@ |
|||||
|
|
||||
|
|
||||
const Poseidon = require("./poseidon.js"); |
|
||||
|
|
||||
const C = Poseidon.getConstants(); |
|
||||
|
|
||||
let S = "[\n"; |
|
||||
|
|
||||
for (let i=0; i<C.length; i++) { |
|
||||
S = S + " " + C[i].toString(); |
|
||||
if (i<C.length-1) S = S + ","; |
|
||||
S = S + "\n"; |
|
||||
} |
|
||||
S=S+ "]\n"; |
|
||||
|
|
||||
console.log(S); |
|
@ -1,5 +1,5 @@ |
|||||
const poseidonGenContract = require("./poseidon_gencontract"); |
const poseidonGenContract = require("./poseidon_gencontract"); |
||||
|
|
||||
|
|
||||
console.log(poseidonGenContract.createCode(6, 8, 57)); |
|
||||
|
console.log(poseidonGenContract.createCode(5)); |
||||
|
|
@ -1,13 +1,18 @@ |
|||||
const Poseidon = require("./poseidon"); |
|
||||
|
|
||||
const hash = Poseidon.createHash(6, 8, 57); |
|
||||
|
const ZqField = require("ffjavascript").ZqField; |
||||
|
const Scalar = require("ffjavascript").Scalar; |
||||
|
|
||||
|
const poseidon = require("./poseidon"); |
||||
|
|
||||
|
const F = new ZqField(Scalar.fromString("21888242871839275222246405745257275088548364400416034343698204186575808495617")); |
||||
|
|
||||
|
|
||||
exports.hash0 = function (left, right) { |
exports.hash0 = function (left, right) { |
||||
return hash([left, right]); |
|
||||
|
return poseidon([left, right]); |
||||
}; |
}; |
||||
|
|
||||
exports.hash1 = function(key, value) { |
exports.hash1 = function(key, value) { |
||||
return hash([key, value, Poseidon.F.one]); |
|
||||
|
return poseidon([key, value, F.one]); |
||||
}; |
}; |
||||
|
|
||||
exports.F = Poseidon.F; |
|
||||
|
exports.F = F; |
@ -1,3 +1,3 @@ |
|||||
include "../../circuits/poseidon.circom" |
include "../../circuits/poseidon.circom" |
||||
|
|
||||
component main = Poseidon(2, 3, 8, 57); |
|
||||
|
component main = Poseidon(2); |
@ -1,3 +1,3 @@ |
|||||
include "../../circuits/poseidon.circom" |
include "../../circuits/poseidon.circom" |
||||
|
|
||||
component main = Poseidon(2, 6, 8, 57); |
|
||||
|
component main = Poseidon(5); |