poseidon.js 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.validateOpts = validateOpts;
  4. exports.splitConstants = splitConstants;
  5. exports.poseidon = poseidon;
  6. /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
  7. // Poseidon Hash: https://eprint.iacr.org/2019/458.pdf, https://www.poseidon-hash.info
  8. const modular_js_1 = require("./modular.js");
  9. function validateOpts(opts) {
  10. const { Fp, mds, reversePartialPowIdx: rev, roundConstants: rc } = opts;
  11. const { roundsFull, roundsPartial, sboxPower, t } = opts;
  12. (0, modular_js_1.validateField)(Fp);
  13. for (const i of ['t', 'roundsFull', 'roundsPartial']) {
  14. if (typeof opts[i] !== 'number' || !Number.isSafeInteger(opts[i]))
  15. throw new Error(`Poseidon: invalid param ${i}=${opts[i]} (${typeof opts[i]})`);
  16. }
  17. // MDS is TxT matrix
  18. if (!Array.isArray(mds) || mds.length !== t)
  19. throw new Error('Poseidon: wrong MDS matrix');
  20. const _mds = mds.map((mdsRow) => {
  21. if (!Array.isArray(mdsRow) || mdsRow.length !== t)
  22. throw new Error(`Poseidon MDS matrix row: ${mdsRow}`);
  23. return mdsRow.map((i) => {
  24. if (typeof i !== 'bigint')
  25. throw new Error(`Poseidon MDS matrix value=${i}`);
  26. return Fp.create(i);
  27. });
  28. });
  29. if (rev !== undefined && typeof rev !== 'boolean')
  30. throw new Error(`Poseidon: invalid param reversePartialPowIdx=${rev}`);
  31. if (roundsFull % 2 !== 0)
  32. throw new Error(`Poseidon roundsFull is not even: ${roundsFull}`);
  33. const rounds = roundsFull + roundsPartial;
  34. if (!Array.isArray(rc) || rc.length !== rounds)
  35. throw new Error('Poseidon: wrong round constants');
  36. const roundConstants = rc.map((rc) => {
  37. if (!Array.isArray(rc) || rc.length !== t)
  38. throw new Error(`Poseidon wrong round constants: ${rc}`);
  39. return rc.map((i) => {
  40. if (typeof i !== 'bigint' || !Fp.isValid(i))
  41. throw new Error(`Poseidon wrong round constant=${i}`);
  42. return Fp.create(i);
  43. });
  44. });
  45. if (!sboxPower || ![3, 5, 7].includes(sboxPower))
  46. throw new Error(`Poseidon wrong sboxPower=${sboxPower}`);
  47. const _sboxPower = BigInt(sboxPower);
  48. let sboxFn = (n) => (0, modular_js_1.FpPow)(Fp, n, _sboxPower);
  49. // Unwrapped sbox power for common cases (195->142μs)
  50. if (sboxPower === 3)
  51. sboxFn = (n) => Fp.mul(Fp.sqrN(n), n);
  52. else if (sboxPower === 5)
  53. sboxFn = (n) => Fp.mul(Fp.sqrN(Fp.sqrN(n)), n);
  54. return Object.freeze({ ...opts, rounds, sboxFn, roundConstants, mds: _mds });
  55. }
  56. function splitConstants(rc, t) {
  57. if (typeof t !== 'number')
  58. throw new Error('poseidonSplitConstants: wrong t');
  59. if (!Array.isArray(rc) || rc.length % t)
  60. throw new Error('poseidonSplitConstants: wrong rc');
  61. const res = [];
  62. let tmp = [];
  63. for (let i = 0; i < rc.length; i++) {
  64. tmp.push(rc[i]);
  65. if (tmp.length === t) {
  66. res.push(tmp);
  67. tmp = [];
  68. }
  69. }
  70. return res;
  71. }
  72. function poseidon(opts) {
  73. const _opts = validateOpts(opts);
  74. const { Fp, mds, roundConstants, rounds, roundsPartial, sboxFn, t } = _opts;
  75. const halfRoundsFull = _opts.roundsFull / 2;
  76. const partialIdx = _opts.reversePartialPowIdx ? t - 1 : 0;
  77. const poseidonRound = (values, isFull, idx) => {
  78. values = values.map((i, j) => Fp.add(i, roundConstants[idx][j]));
  79. if (isFull)
  80. values = values.map((i) => sboxFn(i));
  81. else
  82. values[partialIdx] = sboxFn(values[partialIdx]);
  83. // Matrix multiplication
  84. values = mds.map((i) => i.reduce((acc, i, j) => Fp.add(acc, Fp.mulN(i, values[j])), Fp.ZERO));
  85. return values;
  86. };
  87. const poseidonHash = function poseidonHash(values) {
  88. if (!Array.isArray(values) || values.length !== t)
  89. throw new Error(`Poseidon: wrong values (expected array of bigints with length ${t})`);
  90. values = values.map((i) => {
  91. if (typeof i !== 'bigint')
  92. throw new Error(`Poseidon: wrong value=${i} (${typeof i})`);
  93. return Fp.create(i);
  94. });
  95. let round = 0;
  96. // Apply r_f/2 full rounds.
  97. for (let i = 0; i < halfRoundsFull; i++)
  98. values = poseidonRound(values, true, round++);
  99. // Apply r_p partial rounds.
  100. for (let i = 0; i < roundsPartial; i++)
  101. values = poseidonRound(values, false, round++);
  102. // Apply r_f/2 full rounds.
  103. for (let i = 0; i < halfRoundsFull; i++)
  104. values = poseidonRound(values, true, round++);
  105. if (round !== rounds)
  106. throw new Error(`Poseidon: wrong number of rounds: last round=${round}, total=${rounds}`);
  107. return values;
  108. };
  109. // For verification in tests
  110. poseidonHash.roundConstants = roundConstants;
  111. return poseidonHash;
  112. }
  113. //# sourceMappingURL=poseidon.js.map