Files
nightmarket/client/poseidonCipher.js
2022-04-04 14:35:25 +01:00

125 lines
3.0 KiB
JavaScript

/*
Copied from: https://github.com/iden3/circomlib/pull/60
*/
const assert = require("assert");
const Scalar = require("ffjavascript").Scalar;
const ZqField = require("ffjavascript").ZqField;
const poseidonPerm = require("./poseidonPerm");
// Prime 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001
const F = new ZqField(Scalar.fromString("21888242871839275222246405745257275088548364400416034343698204186575808495617"));
const two128 = F.e("340282366920938463463374607431768211456");
function encrypt(msg, key, nonce) {
assert(Array.isArray(msg));
assert(Array.isArray(key));
assert(key.length === 2);
msg = msg.map((x) => F.e(x));
// The nonce must be less than 2 ^ 128
assert(nonce < two128);
const message = [...msg];
// Pad the message if needed
while (message.length % 3 > 0) {
message.push(F.zero);
}
let cipherLength = message.length;
// Create the initial state
let state = [
F.zero,
F.e(key[0]),
F.e(key[1]),
F.add(
F.e(nonce),
F.mul(F.e(msg.length), two128),
),
];
const ciphertext = [];
for (let i = 0; i < cipherLength / 3; i++) {
// Iterate Poseidon on the state
state = poseidonPerm(state);
// Absorb three elements of message
state[1] = F.add(state[1], F.e(message[i * 3]));
state[2] = F.add(state[2], F.e(message[i * 3 + 1]));
state[3] = F.add(state[3], F.e(message[i * 3 + 2]));
// Release three elements of the ciphertext
ciphertext.push(state[1]);
ciphertext.push(state[2]);
ciphertext.push(state[3]);
}
// Iterate Poseidon on the state one last time
state = poseidonPerm(state);
// Release the last ciphertext element
ciphertext.push(state[1]);
return ciphertext;
}
function decrypt(ciphertext, key, nonce, length) {
assert(Array.isArray(ciphertext));
assert(Array.isArray(key));
assert(key.length === 2);
// Create the initial state
let state = [
F.zero,
F.e(key[0]),
F.e(key[1]),
F.add(
F.e(nonce),
F.mul(F.e(length), two128),
),
];
const message = [];
let n = Math.floor(ciphertext.length / 3);
for (let i = 0; i < n; i++) {
// Iterate Poseidon on the state
state = poseidonPerm(state);
// Release three elements of the message
message.push(F.sub(ciphertext[i * 3], state[1]));
message.push(F.sub(ciphertext[i * 3 + 1], state[2]));
message.push(F.sub(ciphertext[i * 3 + 2], state[3]));
// Modify the state
state[1] = ciphertext[i * 3];
state[2] = ciphertext[i * 3 + 1];
state[3] = ciphertext[i * 3 + 2];
}
// If length > 3, check if the last (3 - (l mod 3)) elements of the message
// are 0
if (length > 3) {
if (length % 3 === 2) {
assert(F.eq(message[message.length - 1], F.zero));
} else if (length % 3 === 1) {
assert(F.eq(message[message.length - 1], F.zero));
assert(F.eq(message[message.length - 2], F.zero));
}
}
// Iterate Poseidon on the state one last time
state = poseidonPerm(state);
// Check the last ciphertext element
assert(F.eq(ciphertext[ciphertext.length - 1], state[1]));
return message.slice(0, length);
}
module.exports = { encrypt, decrypt };