UNPKG

@coti-io/coti-sdk-typescript

Version:

A library for encryption, decryption and cryptographic utilities for the COTI blockchain.

236 lines (235 loc) 10.8 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.encryptNumber = exports.decodeUint = exports.encodeUint = exports.encodeKey = exports.encodeString = exports.generateRandomAesKeySizeNumber = exports.decryptString = exports.decryptUint = exports.buildStringInputText = exports.buildInputText = exports.signInputText = exports.sign = exports.recoverUserKey = exports.decryptRSA = exports.generateRSAKeyPair = exports.decrypt = exports.encrypt = void 0; const node_forge_1 = __importDefault(require("node-forge")); const ethers_1 = require("ethers"); const BLOCK_SIZE = 16; // AES block size in bytes const HEX_BASE = 16; const EIGHT_BYTES = 8; function encrypt(key, plaintext) { // Ensure plaintext is smaller than 128 bits (16 bytes) if (plaintext.length > BLOCK_SIZE) { throw new RangeError("Plaintext size must be 128 bits or smaller."); } // Generate a random value 'r' of the same length as the block size const r = node_forge_1.default.random.getBytesSync(BLOCK_SIZE); // Get the encrypted random value 'r' const encryptedR = encryptNumber(r, key); // Pad the plaintext with zeros if it's smaller than the block size const plaintextPadded = new Uint8Array([...new Uint8Array(BLOCK_SIZE - plaintext.length), ...plaintext]); // XOR the encrypted random value 'r' with the plaintext to obtain the ciphertext const ciphertext = new Uint8Array(BLOCK_SIZE); for (let i = 0; i < BLOCK_SIZE; i++) { ciphertext[i] = encryptedR[i] ^ plaintextPadded[i]; } return { ciphertext, r: encodeString(r) }; } exports.encrypt = encrypt; function decrypt(key, r, ciphertext) { if (ciphertext.length !== BLOCK_SIZE) { throw new RangeError("Ciphertext size must be 128 bits."); } // Ensure random size is 128 bits (16 bytes) if (r.length != BLOCK_SIZE) { throw new RangeError("Random size must be 128 bits."); } // Get the encrypted random value 'r' const encryptedR = encryptNumber(r, key); // XOR the encrypted random value 'r' with the ciphertext to obtain the plaintext const plaintext = new Uint8Array(BLOCK_SIZE); for (let i = 0; i < encryptedR.length; i++) { plaintext[i] = encryptedR[i] ^ ciphertext[i]; } return plaintext; } exports.decrypt = decrypt; function generateRSAKeyPair() { // Generate a new RSA key pair const rsaKeyPair = node_forge_1.default.pki.rsa.generateKeyPair({ bits: 2048 }); // Convert keys to DER format const privateKey = node_forge_1.default.asn1.toDer(node_forge_1.default.pki.privateKeyToAsn1(rsaKeyPair.privateKey)).data; const publicKey = node_forge_1.default.asn1.toDer(node_forge_1.default.pki.publicKeyToAsn1(rsaKeyPair.publicKey)).data; return { privateKey: encodeString(privateKey), publicKey: encodeString(publicKey) }; } exports.generateRSAKeyPair = generateRSAKeyPair; function decryptRSA(privateKey, ciphertext) { // Convert privateKey from Uint8Array to PEM format const privateKeyPEM = node_forge_1.default.pki.privateKeyToPem(node_forge_1.default.pki.privateKeyFromAsn1(node_forge_1.default.asn1.fromDer(node_forge_1.default.util.createBuffer(privateKey)))); // Decrypt using RSA-OAEP const rsaPrivateKey = node_forge_1.default.pki.privateKeyFromPem(privateKeyPEM); const decrypted = rsaPrivateKey.decrypt(node_forge_1.default.util.hexToBytes(ciphertext), 'RSA-OAEP', { md: node_forge_1.default.md.sha256.create() }); const decryptedBytes = encodeString(decrypted); const userKey = []; for (let i = 0; i < decryptedBytes.length; i++) { userKey.push(decryptedBytes[i] .toString(16) .padStart(2, '0') // make sure each cell is one byte ); } return userKey.join(""); } exports.decryptRSA = decryptRSA; function recoverUserKey(privateKey, encryptedKeyShare0, encryptedKeyShare1) { const decryptedKeyShare0 = decryptRSA(privateKey, encryptedKeyShare0); const decryptedKeyShare1 = decryptRSA(privateKey, encryptedKeyShare1); const bufferKeyShare0 = encodeKey(decryptedKeyShare0); const bufferKeyShare1 = encodeKey(decryptedKeyShare1); const aesKeyBytes = new Uint8Array(BLOCK_SIZE); for (let i = 0; i < BLOCK_SIZE; i++) { aesKeyBytes[i] = bufferKeyShare0[i] ^ bufferKeyShare1[i]; } const aesKey = []; let byte = ''; for (let i = 0; i < aesKeyBytes.length; i++) { byte = aesKeyBytes[i].toString(HEX_BASE).padStart(2, '0'); // ensure that the zero byte is represented using two digits aesKey.push(byte); } return aesKey.join(""); } exports.recoverUserKey = recoverUserKey; function sign(message, privateKey) { const key = new ethers_1.SigningKey(privateKey); const sig = key.sign(message); return new Uint8Array([...(0, ethers_1.getBytes)(sig.r), ...(0, ethers_1.getBytes)(sig.s), ...(0, ethers_1.getBytes)(`0x0${sig.v - 27}`)]); } exports.sign = sign; function signInputText(sender, contractAddress, functionSelector, ct) { const message = (0, ethers_1.solidityPackedKeccak256)(["address", "address", "bytes4", "uint256"], [sender.wallet.address, contractAddress, functionSelector, ct]); return sign(message, sender.wallet.privateKey); } exports.signInputText = signInputText; function buildInputText(plaintext, sender, contractAddress, functionSelector) { if (plaintext >= BigInt(2) ** BigInt(64)) { throw new RangeError("Plaintext size must be 64 bits or smaller."); } // Convert the plaintext to bytes const plaintextBytes = encodeUint(plaintext); // Convert user key to bytes const keyBytes = encodeKey(sender.userKey); // Encrypt the plaintext using AES key const { ciphertext, r } = encrypt(keyBytes, plaintextBytes); const ct = new Uint8Array([...ciphertext, ...r]); // Convert the ciphertext to BigInt const ctInt = decodeUint(ct); const signature = signInputText(sender, contractAddress, functionSelector, ctInt); return { ciphertext: ctInt, signature: signature }; } exports.buildInputText = buildInputText; function buildStringInputText(plaintext, sender, contractAddress, functionSelector) { let encoder = new TextEncoder(); // Encode the plaintext string into bytes (UTF-8 encoded) let encodedStr = encoder.encode(plaintext); const inputText = { ciphertext: { value: new Array() }, signature: new Array() }; // Process the encoded string in chunks of 8 bytes // We use 8 bytes since we will use ctUint64 to store // each chunk of 8 characters for (let startIdx = 0; startIdx < encodedStr.length; startIdx += EIGHT_BYTES) { const endIdx = Math.min(startIdx + EIGHT_BYTES, encodedStr.length); const byteArr = new Uint8Array([...encodedStr.slice(startIdx, endIdx), ...new Uint8Array(EIGHT_BYTES - (endIdx - startIdx))]); // pad the end of the string with zeros if needed const it = buildInputText(decodeUint(byteArr), // convert the 8-byte hex string into a number sender, contractAddress, functionSelector); inputText.ciphertext.value.push(it.ciphertext); inputText.signature.push(it.signature); } return inputText; } exports.buildStringInputText = buildStringInputText; function decryptUint(ciphertext, userKey) { // Convert ciphertext to Uint8Array let ctArray = new Uint8Array(); while (ciphertext > 0) { const temp = new Uint8Array([Number(ciphertext & BigInt(255))]); ctArray = new Uint8Array([...temp, ...ctArray]); ciphertext >>= BigInt(8); } ctArray = new Uint8Array([...new Uint8Array(32 - ctArray.length), ...ctArray]); // Split CT into two 128-bit arrays r and cipher const cipher = ctArray.subarray(0, BLOCK_SIZE); const r = ctArray.subarray(BLOCK_SIZE); const userKeyBytes = encodeKey(userKey); // Decrypt the cipher const decryptedMessage = decrypt(userKeyBytes, r, cipher); return decodeUint(decryptedMessage); } exports.decryptUint = decryptUint; function decryptString(ciphertext, userKey) { let encodedStr = new Uint8Array(); for (let i = 0; i < ciphertext.value.length; i++) { const decrypted = decryptUint(BigInt(ciphertext.value[i]), userKey); encodedStr = new Uint8Array([...encodedStr, ...encodeUint(decrypted)]); } const decoder = new TextDecoder(); return decoder .decode(encodedStr) .replace(/\0/g, ''); } exports.decryptString = decryptString; function generateRandomAesKeySizeNumber() { return node_forge_1.default.random.getBytesSync(BLOCK_SIZE); } exports.generateRandomAesKeySizeNumber = generateRandomAesKeySizeNumber; function encodeString(str) { return new Uint8Array([...str.split('').map((char) => parseInt(char.codePointAt(0)?.toString(HEX_BASE), HEX_BASE))]); } exports.encodeString = encodeString; function encodeKey(userKey) { const keyBytes = new Uint8Array(16); for (let i = 0; i < 32; i += 2) { keyBytes[i / 2] = parseInt(userKey.slice(i, i + 2), HEX_BASE); } return keyBytes; } exports.encodeKey = encodeKey; function encodeUint(plaintext) { // Convert the plaintext to bytes in little-endian format const plaintextBytes = new Uint8Array(BLOCK_SIZE); // Allocate a buffer of size 16 bytes for (let i = 15; i >= 0; i--) { plaintextBytes[i] = Number(plaintext & BigInt(255)); plaintext >>= BigInt(8); } return plaintextBytes; } exports.encodeUint = encodeUint; function decodeUint(plaintextBytes) { const plaintext = []; let byte = ''; for (let i = 0; i < plaintextBytes.length; i++) { byte = plaintextBytes[i].toString(HEX_BASE).padStart(2, '0'); // ensure that the zero byte is represented using two digits plaintext.push(byte); } return BigInt("0x" + plaintext.join("")); } exports.decodeUint = decodeUint; function encryptNumber(r, key) { // Ensure key size is 128 bits (16 bytes) if (key.length != BLOCK_SIZE) { throw new RangeError("Key size must be 128 bits."); } // Create a new AES cipher using the provided key const cipher = node_forge_1.default.cipher.createCipher('AES-ECB', node_forge_1.default.util.createBuffer(key)); // Encrypt the random value 'r' using AES in ECB mode cipher.start(); cipher.update(node_forge_1.default.util.createBuffer(r)); cipher.finish(); // Get the encrypted random value 'r' as a Buffer and ensure it's exactly 16 bytes const encryptedR = encodeString(cipher.output.data).slice(0, BLOCK_SIZE); return encryptedR; } exports.encryptNumber = encryptNumber;