UNPKG

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