104 lines
3.5 KiB
JavaScript
104 lines
3.5 KiB
JavaScript
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
|
import { sha256 } from '@noble/hashes/sha256';
|
|
import { utf8ToBytes } from '@noble/hashes/utils';
|
|
import { Fp, validateField } from '../esm/abstract/modular.js';
|
|
import { poseidon } from '../esm/abstract/poseidon.js';
|
|
import * as u from '../esm/abstract/utils.js';
|
|
|
|
// Poseidon hash https://docs.starkware.co/starkex/stark-curve.html
|
|
export const Fp253 = Fp(
|
|
BigInt('14474011154664525231415395255581126252639794253786371766033694892385558855681')
|
|
); // 2^253 + 2^199 + 1
|
|
export const Fp251 = Fp(
|
|
BigInt('3618502788666131213697322783095070105623107215331596699973092056135872020481')
|
|
); // 2^251 + 17 * 2^192 + 1
|
|
|
|
function poseidonRoundConstant(Fp, name, idx) {
|
|
const val = Fp.fromBytes(sha256(utf8ToBytes(`${name}${idx}`)));
|
|
return Fp.create(val);
|
|
}
|
|
|
|
// NOTE: doesn't check eiginvalues and possible can create unsafe matrix. But any filtration here will break compatibility with starknet
|
|
// Please use only if you really know what you doing.
|
|
// https://eprint.iacr.org/2019/458.pdf Section 2.3 (Avoiding Insecure Matrices)
|
|
export function _poseidonMDS(Fp, name, m, attempt = 0) {
|
|
const x_values = [];
|
|
const y_values = [];
|
|
for (let i = 0; i < m; i++) {
|
|
x_values.push(poseidonRoundConstant(Fp, `${name}x`, attempt * m + i));
|
|
y_values.push(poseidonRoundConstant(Fp, `${name}y`, attempt * m + i));
|
|
}
|
|
if (new Set([...x_values, ...y_values]).size !== 2 * m)
|
|
throw new Error('X and Y values are not distinct');
|
|
return x_values.map((x) => y_values.map((y) => Fp.inv(Fp.sub(x, y))));
|
|
}
|
|
|
|
const MDS_SMALL = [
|
|
[3, 1, 1],
|
|
[1, -1, 1],
|
|
[1, 1, -2],
|
|
].map((i) => i.map(BigInt));
|
|
|
|
export function poseidonBasic(opts, mds) {
|
|
validateField(opts.Fp);
|
|
if (!Number.isSafeInteger(opts.rate) || !Number.isSafeInteger(opts.capacity))
|
|
throw new Error(`Wrong poseidon opts: ${opts}`);
|
|
const m = opts.rate + opts.capacity;
|
|
const rounds = opts.roundsFull + opts.roundsPartial;
|
|
const roundConstants = [];
|
|
for (let i = 0; i < rounds; i++) {
|
|
const row = [];
|
|
for (let j = 0; j < m; j++) row.push(poseidonRoundConstant(opts.Fp, 'Hades', m * i + j));
|
|
roundConstants.push(row);
|
|
}
|
|
const res = poseidon({
|
|
...opts,
|
|
t: m,
|
|
sboxPower: 3,
|
|
reversePartialPowIdx: true, // Why?!
|
|
mds,
|
|
roundConstants,
|
|
});
|
|
res.m = m;
|
|
res.rate = opts.rate;
|
|
res.capacity = opts.capacity;
|
|
return res;
|
|
}
|
|
|
|
export function poseidonCreate(opts, mdsAttempt = 0) {
|
|
const m = opts.rate + opts.capacity;
|
|
if (!Number.isSafeInteger(mdsAttempt)) throw new Error(`Wrong mdsAttempt=${mdsAttempt}`);
|
|
return poseidonBasic(opts, _poseidonMDS(opts.Fp, 'HadesMDS', m, mdsAttempt));
|
|
}
|
|
|
|
export const poseidonSmall = poseidonBasic(
|
|
{ Fp: Fp251, rate: 2, capacity: 1, roundsFull: 8, roundsPartial: 83 },
|
|
MDS_SMALL
|
|
);
|
|
|
|
export function poseidonHash(x, y, fn = poseidonSmall) {
|
|
return fn([x, y, 2n])[0];
|
|
}
|
|
|
|
export function poseidonHashFunc(x, y, fn = poseidonSmall) {
|
|
return u.numberToVarBytesBE(poseidonHash(u.bytesToNumberBE(x), u.bytesToNumberBE(y), fn));
|
|
}
|
|
|
|
export function poseidonHashSingle(x, fn = poseidonSmall) {
|
|
return fn([x, 0n, 1n])[0];
|
|
}
|
|
|
|
export function poseidonHashMany(values, fn = poseidonSmall) {
|
|
const { m, rate } = fn;
|
|
if (!Array.isArray(values)) throw new Error('bigint array expected in values');
|
|
const padded = Array.from(values); // copy
|
|
padded.push(1n);
|
|
while (padded.length % rate !== 0) padded.push(0n);
|
|
let state = new Array(m).fill(0n);
|
|
for (let i = 0; i < padded.length; i += rate) {
|
|
for (let j = 0; j < rate; j++) state[j] += padded[i + j];
|
|
state = fn(state);
|
|
}
|
|
return state[0];
|
|
}
|