@shutter-network/shutter-sdk
Version:
TypeScript SDK for interacting with the Shutter crypto library, providing encryption, decryption, and utilities.
296 lines (292 loc) • 8.72 kB
JavaScript
import "./chunk-3GDQP6AS.mjs";
// src/crypto/crypto.ts
import { Buffer } from "buffer";
import { hexToBytes, keccak256, bytesToBigInt, bytesToHex, numberToBytes } from "viem";
import pkg from "lodash";
// src/crypto/get-blst.ts
import { isBrowser, isNode } from "browser-or-node";
var getBlst;
if (isBrowser) {
getBlst = async () => {
if (!window.blst) {
const script = document.createElement("script");
script.src = "/blst.js";
await new Promise((resolve, reject) => {
script.onload = resolve;
script.onerror = () => reject(new Error("Failed to load BLST"));
document.head.appendChild(script);
});
if (!window.blst) {
throw new Error("BLST failed to initialize after loading");
}
}
return window.blst;
};
} else if (isNode) {
getBlst = () => import("./blst-PVOSOVMN.mjs").then((module) => module.default);
} else {
throw new Error("platform not supported.");
}
// src/crypto/crypto.ts
var { zip } = pkg;
var blsSubgroupOrderBytes = [
115,
237,
167,
83,
41,
157,
125,
72,
51,
57,
216,
8,
9,
161,
216,
5,
83,
189,
164,
2,
255,
254,
91,
254,
255,
255,
255,
255,
0,
0,
0,
1
];
var blsSubgroupOrder = bytesToBigInt(Uint8Array.from(blsSubgroupOrderBytes));
async function encryptData(msgHex, identityPreimageHex, eonKeyHex, sigmaHex) {
await ensureBlstInitialized();
const identity = await computeIdentityP1(identityPreimageHex);
const eonKey = await computeEonKeyP2(eonKeyHex);
const encryptedMessage = await encrypt(msgHex, identity, eonKey, sigmaHex);
const encodedData = encodeEncryptedMessage(encryptedMessage);
return encodedData;
}
async function computeIdentityP1(preimage) {
const blst = await getBlst();
const preimageBytes = hexToBytes("0x1" + preimage.slice(2));
const identity = new blst.P1().hash_to(
preimageBytes,
"SHUTTER_V01_BLS12381G1_XMD:SHA-256_SSWU_RO_",
null
);
return identity;
}
async function computeEonKeyP2(eonKeyHex) {
const blst = await getBlst();
const eonKey = new blst.P2(hexToBytes(eonKeyHex));
return eonKey;
}
async function encrypt(msgHex, identity, eonKey, sigmaHex) {
const r = computeR(sigmaHex.slice(2), msgHex.slice(2));
const c1 = await computeC1(r);
const c2 = await computeC2(sigmaHex, r, identity, eonKey);
const c3 = computeC3(
padAndSplit(hexToBytes(msgHex)),
hexToBytes(sigmaHex)
);
return {
VersionId: 3,
c1,
c2,
c3
};
}
function encodeEncryptedMessage(encryptedMessage) {
const c1Length = 96;
const c2Length = 32;
const c3Length = encryptedMessage.c3.length * 32;
const totalLength = 1 + c1Length + c2Length + c3Length;
const bytes = new Uint8Array(totalLength);
bytes[0] = encryptedMessage.VersionId;
bytes.set(encryptedMessage.c1, 1);
bytes.set(encryptedMessage.c2, 1 + c1Length);
encryptedMessage.c3.forEach((block, i) => {
const offset = 1 + c1Length + c2Length + 32 * i;
bytes.set(block, offset);
});
return bytesToHex(bytes);
}
async function decodeEncryptedMessage(encryptedMessage) {
const blst = await getBlst();
const bytes = hexToBytes(encryptedMessage);
if (bytes[0] !== 3) {
throw "Invalid version";
}
const c1 = new blst.P2_Affine(bytes.slice(1, 96 + 1));
const c2 = bytes.slice(96 + 1, 96 + 1 + 32);
const c3 = bytes.slice(96 + 1 + 32);
return {
VersionId: 3,
c1,
c2,
c3
};
}
async function decrypt(encryptedMessageHex, epochSecretKeyHex) {
const blst = await getBlst();
await ensureBlstInitialized();
const decodedMessage = await decodeEncryptedMessage(encryptedMessageHex);
const p = new blst.PT(decodedMessage.c1, new blst.P1_Affine(hexToBytes(epochSecretKeyHex)));
const key = hash2(p);
const sigma = xorBlocks(decodedMessage.c2, key);
const blockCount = decodedMessage.c3.length / 32;
const decryptedBlocks = new Uint8Array(decodedMessage.c3.length);
const keys = computeBlockKeys(sigma, blockCount);
for (let i = 0; i < blockCount; i++) {
const block = decodedMessage.c3.slice(i * 32, (i + 1) * 32);
const decryptedBlock = xorBlocks(block, keys[i]);
decryptedBlocks.set(decryptedBlock, i * 32);
}
return bytesToHex(unpad(decryptedBlocks));
}
async function ensureBlstInitialized() {
const blst = await getBlst();
return new Promise((resolve) => {
if (blst.calledRun) {
console.log("BLST already initialized");
resolve();
} else {
console.log("Waiting for BLST runtime to initialize...");
blst.onRuntimeInitialized = () => {
console.log("BLST runtime initialized.");
resolve();
};
}
});
}
function computeR(sigmaHex, msgHex) {
const preimage = sigmaHex + msgHex;
return hash3(preimage);
}
async function computeC1(r) {
const blst = await getBlst();
const scalar = new blst.Scalar().from_bendian(numberToBytes(r)).to_lendian();
const c1 = blst.P2.generator().mult(scalar).compress();
return c1;
}
async function computeC2(sigmaHex, r, identity, eonKey) {
const blst = await getBlst();
const p = new blst.PT(identity, eonKey);
const preimage = await GTExp(p, r);
const key = hash2(preimage);
const result = xorBlocks(hexToBytes(sigmaHex), key);
return result;
}
function computeC3(messageBlocks, sigma) {
const keys = computeBlockKeys(sigma, messageBlocks.length);
return zip(keys, messageBlocks).map(([key, block]) => {
if (key === void 0 || block === void 0) {
throw new Error("Key or block is undefined");
}
return xorBlocks(key, block);
});
}
function hash2(p) {
const finalExp = p.final_exp().to_bendian();
const result = new Uint8Array(finalExp.length + 1);
result[0] = 2;
result.set(finalExp, 1);
return keccak256(result, "bytes");
}
function hash3(bytesHex) {
const preimage = hexToBytes("0x3" + bytesHex);
const hash = keccak256(preimage, "bytes");
const bigIntHash = bytesToBigInt(hash);
const result = bigIntHash % blsSubgroupOrder;
return result;
}
function hash4(bytes) {
const preimage = new Uint8Array(bytes.length + 1);
preimage[0] = 4;
preimage.set(bytes, 1);
const hash = keccak256(preimage, "bytes");
return hash;
}
function xorBlocks(x, y) {
if (x.length !== y.length) {
throw new Error("Both byte arrays must be of the same length.");
}
const result = new Uint8Array(x.length);
for (let i = 0; i < x.length; i++) {
result[i] = x[i] ^ y[i];
}
return result;
}
function computeBlockKeys(sigma, n) {
return Array.from({ length: n }, (_, x) => {
const suffix = Buffer.alloc(4);
suffix.writeUInt32BE(x, 0);
let suffixLength = 4;
for (let i = 0; i < 3; i++) {
if (suffix[i] !== 0) break;
suffixLength--;
}
const effectiveSuffix = Buffer.from(suffix.slice(4 - suffixLength));
const preimage = Buffer.concat([sigma, effectiveSuffix]);
return hash4(preimage);
});
}
function padAndSplit(bytes) {
const blockSize = 32;
const paddingLength = blockSize - bytes.length % blockSize;
const padded = new Uint8Array(bytes.length + paddingLength);
padded.set(bytes);
padded.fill(paddingLength, bytes.length);
const result = [];
for (let i = 0; i < padded.length; i += blockSize) {
result.push(padded.slice(i, i + blockSize));
}
return result;
}
function unpad(bytes) {
const paddingLength = bytes.at(-1);
if (paddingLength == void 0 || paddingLength == 0 || paddingLength > 32 || paddingLength > bytes.length) {
throw `Invalid padding length: ${paddingLength}`;
}
return bytes.slice(0, bytes.length - paddingLength);
}
async function GTExp(x, exp) {
const blst = await getBlst();
const a = x;
const acc = blst.PT.one();
while (exp > BigInt(0)) {
if (exp & BigInt(1)) {
acc.mul(a);
}
a.sqr();
exp >>= BigInt(1);
}
return acc;
}
// src/crypto/blst/types.ts
var BLST_ERROR = /* @__PURE__ */ ((BLST_ERROR2) => {
BLST_ERROR2[BLST_ERROR2["BLST_SUCCESS"] = 0] = "BLST_SUCCESS";
BLST_ERROR2[BLST_ERROR2["BLST_BAD_ENCODING"] = 1] = "BLST_BAD_ENCODING";
BLST_ERROR2[BLST_ERROR2["BLST_POINT_NOT_ON_CURVE"] = 2] = "BLST_POINT_NOT_ON_CURVE";
BLST_ERROR2[BLST_ERROR2["BLST_POINT_NOT_IN_GROUP"] = 3] = "BLST_POINT_NOT_IN_GROUP";
BLST_ERROR2[BLST_ERROR2["BLST_AGGR_TYPE_MISMATCH"] = 4] = "BLST_AGGR_TYPE_MISMATCH";
BLST_ERROR2[BLST_ERROR2["BLST_VERIFY_FAIL"] = 5] = "BLST_VERIFY_FAIL";
BLST_ERROR2[BLST_ERROR2["BLST_PK_IS_INFINITY"] = 6] = "BLST_PK_IS_INFINITY";
BLST_ERROR2[BLST_ERROR2["BLST_BAD_SCALAR"] = 7] = "BLST_BAD_SCALAR";
return BLST_ERROR2;
})(BLST_ERROR || {});
export {
BLST_ERROR,
computeIdentityP1,
decodeEncryptedMessage,
decrypt,
encodeEncryptedMessage,
encryptData
};
//# sourceMappingURL=index.mjs.map