UNPKG

@bsv/sdk

Version:

BSV Blockchain Software Development Kit

580 lines 22.3 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); // import { AESWrappercbc } from './aescbc' const Random_js_1 = __importDefault(require("../primitives/Random.js")); const PrivateKey_js_1 = __importDefault(require("../primitives/PrivateKey.js")); const PublicKey_js_1 = __importDefault(require("../primitives/PublicKey.js")); const Point_js_1 = __importDefault(require("../primitives/Point.js")); const Hash = __importStar(require("../primitives/Hash.js")); const utils_js_1 = require("../primitives/utils.js"); function AES(key) { if (this._tables[0][0][0] === 0) this._precompute(); let tmp, encKey, decKey; const sbox = this._tables[0][4]; const decTable = this._tables[1]; const keyLen = key.length; let rcon = 1; if (keyLen !== 4 && keyLen !== 6 && keyLen !== 8) { throw new Error('invalid aes key size'); } this._key = [(encKey = key.slice(0)), (decKey = [])]; // schedule encryption keys let i; for (i = keyLen; i < 4 * keyLen + 28; i++) { tmp = encKey[i - 1]; // apply sbox if (i % keyLen === 0 || (keyLen === 8 && i % keyLen === 4)) { tmp = (sbox[tmp >>> 24] << 24) ^ (sbox[(tmp >> 16) & 255] << 16) ^ (sbox[(tmp >> 8) & 255] << 8) ^ sbox[tmp & 255]; // shift rows and add rcon if (i % keyLen === 0) { tmp = (tmp << 8) ^ (tmp >>> 24) ^ (rcon << 24); rcon = (rcon << 1) ^ ((rcon >> 7) * 283); } } encKey[i] = encKey[i - keyLen] ^ tmp; } // schedule decryption keys for (let j = 0; i > 0; j++, i--) { tmp = encKey[(j & 3) !== 0 ? i : i - 4]; if (i <= 4 || j < 4) { decKey[j] = tmp; } else { decKey[j] = decTable[0][sbox[tmp >>> 24]] ^ decTable[1][sbox[(tmp >> 16) & 255]] ^ decTable[2][sbox[(tmp >> 8) & 255]] ^ decTable[3][sbox[tmp & 255]]; } } } AES.prototype = { /** * Encrypt an array of 4 big-endian words. * @param {Array} data The plaintext. * @return {Array} The ciphertext. */ encrypt: function (data) { return this._crypt(data, 0); }, /** * Decrypt an array of 4 big-endian words. * @param {Array} data The ciphertext. * @return {Array} The plaintext. */ decrypt: function (data) { return this._crypt(data, 1); }, /** * The expanded S-box and inverse S-box tables. These will be computed * on the client so that we don't have to send them down the wire. * * There are two tables, _tables[0] is for encryption and * _tables[1] is for decryption. * * The first 4 sub-tables are the expanded S-box with MixColumns. The * last (_tables[01][4]) is the S-box itself. * * @private */ _tables: [ [ new Uint32Array(256), new Uint32Array(256), new Uint32Array(256), new Uint32Array(256), new Uint32Array(256) ], [ new Uint32Array(256), new Uint32Array(256), new Uint32Array(256), new Uint32Array(256), new Uint32Array(256) ] ], // Expand the S-box tables. _precompute: function () { const encTable = this._tables[0]; const decTable = this._tables[1]; const sbox = encTable[4]; const sboxInv = decTable[4]; let i; let x; let xInv; const d = new Uint8Array(256); const th = new Uint8Array(256); let x2; let x4; let x8; let s; let tEnc; let tDec; // Compute double and third tables for (i = 0; i < 256; i++) { th[(d[i] = (i << 1) ^ ((i >> 7) * 283)) ^ i] = i; } for (x = xInv = 0; sbox[x] === 0; x ^= (x2 !== 0 ? x2 : 1), xInv = th[xInv] !== 0 ? th[xInv] : 1) { // Compute sbox s = xInv ^ (xInv << 1) ^ (xInv << 2) ^ (xInv << 3) ^ (xInv << 4); s = (s >> 8) ^ (s & 255) ^ 99; sbox[x] = s; sboxInv[s] = x; // Compute MixColumns x8 = d[(x4 = d[(x2 = d[x])])]; tDec = (x8 * 0x1010101) ^ (x4 * 0x10001) ^ (x2 * 0x101) ^ (x * 0x1010100); tEnc = (d[s] * 0x101) ^ (s * 0x1010100); for (i = 0; i < 4; i++) { encTable[i][x] = tEnc = (tEnc << 24) ^ (tEnc >>> 8); decTable[i][s] = tDec = (tDec << 24) ^ (tDec >>> 8); } } }, /** * Encryption and decryption core. * @param {Array} input Four words to be encrypted or decrypted. * @param dir The direction, 0 for encrypt and 1 for decrypt. * @return {Array} The four encrypted or decrypted words. * @private */ _crypt: function (input, dir) { if (input.length !== 4) { throw new Error('invalid aes block size'); } const key = this._key[dir]; // state variables a,b,c,d are loaded with pre-whitened data let a = input[0] ^ key[0]; let b = input[dir === 1 ? 3 : 1] ^ key[1]; let c = input[2] ^ key[2]; let d = input[dir === 1 ? 1 : 3] ^ key[3]; let a2; let b2; let c2; const nInnerRounds = key.length / 4 - 2; let i; let kIndex = 4; const out = new Uint32Array(4); const // <--- this is slower in Node, about the same in Chrome */ table = this._tables[dir]; // load up the tables const t0 = table[0]; const t1 = table[1]; const t2 = table[2]; const t3 = table[3]; const sbox = table[4]; // Inner rounds. Cribbed from OpenSSL. for (i = 0; i < nInnerRounds; i++) { a2 = t0[a >>> 24] ^ t1[(b >> 16) & 255] ^ t2[(c >> 8) & 255] ^ t3[d & 255] ^ key[kIndex]; b2 = t0[b >>> 24] ^ t1[(c >> 16) & 255] ^ t2[(d >> 8) & 255] ^ t3[a & 255] ^ key[kIndex + 1]; c2 = t0[c >>> 24] ^ t1[(d >> 16) & 255] ^ t2[(a >> 8) & 255] ^ t3[b & 255] ^ key[kIndex + 2]; d = t0[d >>> 24] ^ t1[(a >> 16) & 255] ^ t2[(b >> 8) & 255] ^ t3[c & 255] ^ key[kIndex + 3]; kIndex += 4; a = a2; b = b2; c = c2; } // Last round. for (i = 0; i < 4; i++) { out[dir === 1 ? 3 & -i : i] = (sbox[a >>> 24] << 24) ^ (sbox[(b >> 16) & 255] << 16) ^ (sbox[(c >> 8) & 255] << 8) ^ sbox[d & 255] ^ key[kIndex++]; a2 = a; a = b; b = c; c = d; d = a2; } return out; } }; // eslint-disable-next-line @typescript-eslint/no-extraneous-class class AESWrapper { static encrypt(messageBuf, keyBuf) { const key = AESWrapper.buf2Words(keyBuf); const message = AESWrapper.buf2Words(messageBuf); const a = new AES(key); const enc = a.encrypt(message); const encBuf = AESWrapper.words2Buf(enc); return encBuf; } static decrypt(encBuf, keyBuf) { const enc = AESWrapper.buf2Words(encBuf); const key = AESWrapper.buf2Words(keyBuf); const a = new AES(key); const message = a.decrypt(enc); const messageBuf = AESWrapper.words2Buf(message); return messageBuf; } static buf2Words(buf) { if (buf.length % 4 !== 0) { throw new Error('buf length must be a multiple of 4'); } const words = []; for (let i = 0; i < buf.length / 4; i++) { const val = buf[i * 4] * 0x1000000 + // Shift the first byte by 24 bits ((buf[i * 4 + 1] << 16) | // Shift the second byte by 16 bits (buf[i * 4 + 2] << 8) | // Shift the third byte by 8 bits buf[i * 4 + 3]); // The fourth byte words.push(val); } return words; } static words2Buf(words) { const buf = new Array(words.length * 4); for (let i = 0; i < words.length; i++) { const word = words[i]; buf[i * 4] = (word >>> 24) & 0xff; buf[i * 4 + 1] = (word >>> 16) & 0xff; buf[i * 4 + 2] = (word >>> 8) & 0xff; buf[i * 4 + 3] = word & 0xff; } return buf; } } // eslint-disable-next-line @typescript-eslint/no-extraneous-class class CBC { static buf2BlocksBuf(buf, blockSize) { const bytesize = blockSize / 8; const blockBufs = []; for (let i = 0; i <= buf.length / bytesize; i++) { let blockBuf = buf.slice(i * bytesize, i * bytesize + bytesize); if (blockBuf.length < blockSize) { blockBuf = CBC.pkcs7Pad(blockBuf, blockSize); } blockBufs.push(blockBuf); } return blockBufs; } static blockBufs2Buf(blockBufs) { let last = blockBufs[blockBufs.length - 1]; last = CBC.pkcs7Unpad(last); blockBufs[blockBufs.length - 1] = last; const buf = blockBufs.flat(); return buf; } static encrypt(messageBuf, ivBuf, blockCipher /* TODO: type */, cipherKeyBuf) { const blockSize = ivBuf.length * 8; const blockBufs = CBC.buf2BlocksBuf(messageBuf, blockSize); const encBufs = CBC.encryptBlocks(blockBufs, ivBuf, blockCipher, cipherKeyBuf); const encBuf = encBufs.flat(); return encBuf; } static decrypt(encBuf, ivBuf, blockCipher /* TODO: type */, cipherKeyBuf) { const bytesize = ivBuf.length; const encBufs = []; for (let i = 0; i < encBuf.length / bytesize; i++) { encBufs.push(encBuf.slice(i * bytesize, i * bytesize + bytesize)); } const blockBufs = CBC.decryptBlocks(encBufs, ivBuf, blockCipher, cipherKeyBuf); const buf = CBC.blockBufs2Buf(blockBufs); return buf; } static encryptBlock(blockBuf, ivBuf, blockCipher /* TODO: type */, cipherKeyBuf) { const xorbuf = CBC.xorBufs(blockBuf, ivBuf); const encBuf = blockCipher.encrypt(xorbuf, cipherKeyBuf); return encBuf; } static decryptBlock(encBuf, ivBuf, blockCipher /* TODO: type */, cipherKeyBuf) { const xorbuf = blockCipher.decrypt(encBuf, cipherKeyBuf); const blockBuf = CBC.xorBufs(xorbuf, ivBuf); return blockBuf; } static encryptBlocks(blockBufs, ivBuf, blockCipher /* TODO: type */, cipherKeyBuf) { const encBufs = []; for (let i = 0; i < blockBufs.length; i++) { const blockBuf = blockBufs[i]; const encBuf = CBC.encryptBlock(blockBuf, ivBuf, blockCipher, cipherKeyBuf); encBufs.push(encBuf); ivBuf = encBuf; } return encBufs; } static decryptBlocks(encBufs, ivBuf, blockCipher /* TODO: type */, cipherKeyBuf) { const blockBufs = []; for (let i = 0; i < encBufs.length; i++) { const encBuf = encBufs[i]; const blockBuf = CBC.decryptBlock(encBuf, ivBuf, blockCipher, cipherKeyBuf); blockBufs.push(blockBuf); ivBuf = encBuf; } return blockBufs; } static pkcs7Pad(buf, blockSize) { const bytesize = blockSize / 8; const padbytesize = bytesize - buf.length; const pad = new Array(padbytesize); pad.fill(padbytesize); const paddedbuf = [...buf, ...pad]; return paddedbuf; } static pkcs7Unpad(paddedbuf) { const padlength = paddedbuf[paddedbuf.length - 1]; const padbuf = paddedbuf.slice(paddedbuf.length - padlength, paddedbuf.length); const padbuf2 = new Array(padlength); padbuf2.fill(padlength); if ((0, utils_js_1.toHex)(padbuf) !== (0, utils_js_1.toHex)(padbuf2)) { throw new Error('invalid padding'); } return paddedbuf.slice(0, paddedbuf.length - padlength); } static xorBufs(buf1, buf2) { if (buf1.length !== buf2.length) { throw new Error('bufs must have the same length'); } const buf = new Array(buf1.length); for (let i = 0; i < buf1.length; i++) { buf[i] = buf1[i] ^ buf2[i]; } return buf; } } // eslint-disable-next-line @typescript-eslint/no-extraneous-class class AESCBC { static encrypt(messageBuf, cipherKeyBuf, ivBuf, concatIvBuf = true) { ivBuf = ivBuf ?? (0, Random_js_1.default)(128 / 8); const ctBuf = CBC.encrypt(messageBuf, ivBuf, AESWrapper, cipherKeyBuf); if (concatIvBuf) { return [...ivBuf, ...ctBuf]; } else { return [...ctBuf]; } } static decrypt(encBuf, cipherKeyBuf, ivBuf) { if (ivBuf == null) { ivBuf = encBuf.slice(0, 128 / 8); const ctBuf = encBuf.slice(128 / 8); return CBC.decrypt(ctBuf, ivBuf, AESWrapper, cipherKeyBuf); } else { const ctBuf = encBuf; return CBC.decrypt(ctBuf, ivBuf, AESWrapper, cipherKeyBuf); } } } /** * @class ECIES * Implements the Electrum ECIES protocol for encrypted communication. * * @prprecated This class is deprecated in favor of the BRC-78 standard for portable encrypted messages, * which provides a more comprehensive and secure solution by integrating with BRC-42 and BRC-43 standards. */ // eslint-disable-next-line @typescript-eslint/no-extraneous-class class ECIES { /** * Generates the initialization vector (iv), encryption key (kE), and MAC key (kM) * using the sender's private key and receiver's public key. * * @param {PrivateKey} privKey - The sender's private key. * @param {PublicKey} pubKey - The receiver's public key. * @returns {Object} An object containing the iv, kE, and kM as number arrays. */ static ivkEkM(privKey, pubKey) { const r = privKey; const KB = pubKey; const P = KB.mul(r); const S = new PublicKey_js_1.default(P.x, P.y); const Sbuf = S.encode(true); const hash = Hash.sha512(Sbuf); return { iv: hash.slice(0, 16), kE: hash.slice(16, 32), kM: hash.slice(32, 64) }; } /** * Encrypts a given message using the Electrum ECIES method. * * @param {number[]} messageBuf - The message to be encrypted, in number array format. * @param {PublicKey} toPublicKey - The public key of the recipient. * @param {PrivateKey} [fromPrivateKey] - The private key of the sender. If not provided, a random private key is used. * @param {boolean} [noKey=false] - If true, does not include the sender's public key in the encrypted message. * @returns {number[]} The encrypted message as a number array. */ static electrumEncrypt(messageBuf, toPublicKey, fromPrivateKey, noKey = false) { let Rbuf = null; if (fromPrivateKey == null) { fromPrivateKey = PrivateKey_js_1.default.fromRandom(); } if (!noKey) { Rbuf = fromPrivateKey.toPublicKey().encode(true); } const { iv, kE, kM } = ECIES.ivkEkM(fromPrivateKey, toPublicKey); const ciphertext = AESCBC.encrypt(messageBuf, kE, iv, false); const BIE1 = (0, utils_js_1.toArray)('BIE1', 'utf8'); let encBuf; if (Rbuf !== undefined && Rbuf !== null && Rbuf.length > 0) { encBuf = [...BIE1, ...Rbuf, ...ciphertext]; } else { encBuf = [...BIE1, ...ciphertext]; } const hmac = Hash.sha256hmac(kM, encBuf); return [...encBuf, ...hmac]; } /** * Decrypts a message encrypted using the Electrum ECIES method. * * @param {number[]} encBuf - The encrypted message buffer. * @param {PrivateKey} toPrivateKey - The private key of the recipient. * @param {PublicKey} [fromPublicKey=null] - The public key of the sender. If not provided, it is extracted from the message. * @returns {number[]} The decrypted message as a number array. */ static electrumDecrypt(encBuf, toPrivateKey, fromPublicKey) { const tagLength = 32; const magic = encBuf.slice(0, 4); if ((0, utils_js_1.encode)(magic, 'utf8') !== 'BIE1') { throw new Error('Invalid Magic'); } let offset = 4; // Determine if the sender's public key is included in encBuf let Rbuf = null; if (encBuf.length - offset - tagLength >= 33) { const firstByte = encBuf[offset]; if (firstByte === 0x02 || firstByte === 0x03) { // Compressed public key Rbuf = encBuf.slice(offset, offset + 33); offset += 33; } else if (firstByte === 0x04) { // Uncompressed public key Rbuf = encBuf.slice(offset, offset + 65); offset += 65; } } if (Rbuf !== null) { if (fromPublicKey == null) { fromPublicKey = PublicKey_js_1.default.fromString((0, utils_js_1.toHex)(Rbuf)); } } else { if (fromPublicKey == null) { throw new Error('Sender public key is required'); } } const { iv, kE, kM } = ECIES.ivkEkM(toPrivateKey, fromPublicKey); const ciphertext = encBuf.slice(offset, encBuf.length - tagLength); const hmac = encBuf.slice(encBuf.length - tagLength, encBuf.length); const hmac2 = Hash.sha256hmac(kM, encBuf.slice(0, encBuf.length - tagLength)); if ((0, utils_js_1.toHex)(hmac) !== (0, utils_js_1.toHex)(hmac2)) { throw new Error('Invalid checksum'); } return AESCBC.decrypt(ciphertext, kE, iv); } /** * Encrypts a given message using the Bitcore variant of ECIES. * * @param {number[]} messageBuf - The message to be encrypted, in number array format. * @param {PublicKey} toPublicKey - The public key of the recipient. * @param {PrivateKey} [fromPrivateKey] - The private key of the sender. If not provided, a random private key is used. * @param {number[]} [ivBuf] - The initialization vector for encryption. If not provided, a random IV is used. * @returns {number[]} The encrypted message as a number array. */ static bitcoreEncrypt(messageBuf, toPublicKey, fromPrivateKey, ivBuf) { if (fromPrivateKey == null) { fromPrivateKey = PrivateKey_js_1.default.fromRandom(); } if (ivBuf == null) { ivBuf = (0, Random_js_1.default)(16); } const r = fromPrivateKey; const RPublicKey = fromPrivateKey.toPublicKey(); const RBuf = RPublicKey.encode(true); const KB = toPublicKey; const P = KB.mul(r); const S = P.getX(); const Sbuf = S.toArray('be', 32); const kEkM = Hash.sha512(Sbuf); const kE = kEkM.slice(0, 32); const kM = kEkM.slice(32, 64); const c = AESCBC.encrypt(messageBuf, kE, ivBuf); const d = Hash.sha256hmac(kM, [...c]); const encBuf = [...RBuf, ...c, ...d]; return encBuf; } /** * Decrypts a message encrypted using the Bitcore variant of ECIES. * * @param {number[]} encBuf - The encrypted message buffer. * @param {PrivateKey} toPrivateKey - The private key of the recipient. * @returns {number[]} The decrypted message as a number array. */ static bitcoreDecrypt(encBuf, toPrivateKey) { const kB = toPrivateKey; const fromPublicKey = PublicKey_js_1.default.fromString((0, utils_js_1.toHex)(encBuf.slice(0, 33))); const R = fromPublicKey; const P = R.mul(kB); if (P.eq(new Point_js_1.default(0, 0))) { throw new Error('P equals 0'); } const S = P.getX(); const Sbuf = S.toArray('be', 32); const kEkM = Hash.sha512(Sbuf); const kE = kEkM.slice(0, 32); const kM = kEkM.slice(32, 64); const c = encBuf.slice(33, encBuf.length - 32); const d = encBuf.slice(encBuf.length - 32, encBuf.length); const d2 = Hash.sha256hmac(kM, c); if ((0, utils_js_1.toHex)(d) !== (0, utils_js_1.toHex)(d2)) { throw new Error('Invalid checksum'); } const messageBuf = AESCBC.decrypt(c, kE); return [...messageBuf]; } } exports.default = ECIES; //# sourceMappingURL=ECIES.js.map