New Version of Poseidon

This commit is contained in:
Jordi Baylina
2020-08-09 17:13:04 +02:00
parent 5269afee0a
commit 86c6a2a6f5
18 changed files with 3715 additions and 375 deletions

View File

@@ -112,8 +112,7 @@ function signPoseidon(prv, msg) {
const Fr = new F1Field(babyJub.subOrder);
r = Fr.e(r);
const R8 = babyJub.mulPointEscalar(babyJub.Base8, r);
const hash = poseidon.createHash(6, 8, 57);
const hm = hash([R8[0], R8[1], A[0], A[1], msg]);
const hm = poseidon([R8[0], R8[1], A[0], A[1], msg]);
const S = Fr.add(r , Fr.mul(hm, s));
return {
R8: R8,
@@ -180,8 +179,7 @@ function verifyPoseidon(msg, sig, A) {
if (!babyJub.inCurve(A)) return false;
if (sig.S>= babyJub.subOrder) return false;
const hash = poseidon.createHash(6, 8, 57);
const hm = hash([sig.R8[0], sig.R8[1], A[0], A[1], msg]);
const hm = poseidon([sig.R8[0], sig.R8[1], A[0], A[1], msg]);
const Pleft = babyJub.mulPointEscalar(babyJub.Base8, sig.S);
let Pright = babyJub.mulPointEscalar(A, Scalar.mul(hm, 8));

View File

@@ -154,6 +154,17 @@ class Contract {
}
push(data) {
if (typeof data === "number") {
let isNeg;
if (data<0) {
isNeg = true;
data = -data;
}
data = data.toString(16);
if (data.length % 2 == 1) data = "0" + data;
data = "0x" + data;
if (isNeg) data = "-"+data;
}
const d = Web3Utils.hexToBytes(Web3Utils.toHex(data));
if (d.length == 0 || d.length > 32) {
throw new Error("Assertion failed");

View File

@@ -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;
function poseidon(inputs) {
assert(inputs.length > 0);
assert(inputs.length < N_ROUNDS_P.length - 1);
const t = inputs.length + 1;
const nRoundsF = N_ROUNDS_F;
const nRoundsP = N_ROUNDS_P[t - 2];
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]));
if (r < nRoundsF / 2 || r >= nRoundsF / 2 + nRoundsP) {
state = state.map(a => pow5(a));
} else {
state[0] = pow5(state[0]);
}
// 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)
);
}
}
return true;
return F.normalize(state[0]);
}
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;
};
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;
};
function ark(state, c) {
for (let j=0; j<state.length; j++ ) {
state[j] = F.add(state[j], c);
}
}
function sigma(a) {
return F.mul(a, F.square(F.square(a,a)));
}
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]) );
}
}
for (let i=0; i<state.length; i++) state[i] = newState[i];
}
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;

3449
src/poseidon_constants.json Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -2,14 +2,13 @@
// License: LGPL-3.0+
//
const Poseidon = require("./poseidon.js");
const Contract = require("./evmasm");
const { unstringifyBigInts } = require("ffjavascript").utils;
const SEED = "poseidon";
const NROUNDSF = 8;
const NROUNDSP = 57;
const T = 6;
const { C:K, M } = unstringifyBigInts(require("./poseidon_constants.json"));
const N_ROUNDS_F = 8;
const N_ROUNDS_P = [56, 57, 56, 60, 60, 63, 64, 63];
function toHex256(a) {
let S = a.toString(16);
@@ -17,38 +16,38 @@ function toHex256(a) {
return "0x" + S;
}
function createCode(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;
function createCode(nInputs) {
if (( nInputs<1) || (nInputs>8)) throw new Error("Invalid number of inputs. Must be 1<=nInputs<=8");
const t = nInputs + 1;
const nRoundsF = N_ROUNDS_F;
const nRoundsP = N_ROUNDS_P[t - 2];
// const nRoundsF = 2;
// const nRoundsP = 2;
const K = Poseidon.getConstants(t, seed, nRoundsP + nRoundsF);
const M = Poseidon.getMatrix(t, seed, nRoundsP + nRoundsF);
const C = new Contract();
function saveM() {
for (let i=0; i<t; i++) {
for (let j=0; j<t; j++) {
C.push(toHex256(M[i][j]));
C.push(toHex256(M[t-2][j][i]));
C.push((1+i*t+j)*32);
C.mstore();
}
}
}
function ark(r) {
C.push(toHex256(K[r])); // K, st, q
function ark(r) { // st, q
for (let i=0; i<t; i++) {
C.dup(1+t); // q, K, st, q
C.dup(1); // K, q, K, st, q
C.dup(3+i); // st[i], K, q, K, st, q
C.addmod(); // newSt[i], K, st, q
C.swap(2 + i); // xx, K, st, q
C.dup(t); // q, st, q
C.push(toHex256(K[t-2][r*t+i])); // K, q, st, q
C.dup(2+i); // st[i], K, q, st, q
C.addmod(); // newSt[i], st, q
C.swap(1 + i); // xx, st, q
C.pop();
}
C.pop();
}
function sigma(p) {
@@ -115,17 +114,17 @@ function createCode(t, nRoundsF, nRoundsP, seed) {
C.push("0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001"); // q
// Load 6 values from the call data.
// Load t values from the call data.
// The function has a single array param param
// [Selector (4)] [Pointer (32)][Length (32)] [data1 (32)] ....
// We ignore the pointer and the length and just load 6 values to the state
// (Stack positions 0-5) If the array is shorter, we just set zeros.
// We ignore the pointer and the length and just load t values to the state
// (Stack positions 0-{t-1}) If the array is shorter, we just set zeros.
for (let i=0; i<t; i++) {
C.push(0x44+(0x20*(t-1-i)));
C.calldataload();
}
for (let i=0; i<nRoundsF+nRoundsP; i++) {
for (let i=0; i<nRoundsF+nRoundsP-1; i++) {
ark(i);
if ((i<nRoundsF/2) || (i>=nRoundsP+nRoundsF/2)) {
for (let j=0; j<t; j++) {
@@ -142,6 +141,13 @@ function createCode(t, nRoundsF, nRoundsP, seed) {
C.label(strLabel);
}
C.push(toHex256(K[t-2][(nRoundsF+nRoundsP-1)*t])); // K, st, q
C.dup(t+1); // q, K, st, q
C.swap(2); // st[0], K, q, st\st[0]
C.addmod(); // st q
sigma(0);
C.push("0x00");
C.mstore(); // Save it to pos 0;
C.push("0x20");

View File

@@ -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);

View File

@@ -1,5 +1,5 @@
const poseidonGenContract = require("./poseidon_gencontract");
console.log(poseidonGenContract.createCode(6, 8, 57));
console.log(poseidonGenContract.createCode(5));

View File

@@ -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) {
return hash([left, right]);
return poseidon([left, right]);
};
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;

View File

@@ -1,7 +1,9 @@
const F = require("./poseidon.js").F;
const Scalar = require("ffjavascript").Scalar;
const utils = require("ffjavascript").utils;
const ZqField = require("ffjavascript").ZqField;
// Prime 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001
const F = new ZqField(Scalar.fromString("21888242871839275222246405745257275088548364400416034343698204186575808495617"));
class SMTMemDb {
constructor() {