@bsv/sdk
Version:
BSV Blockchain Software Development Kit
580 lines • 22.3 kB
JavaScript
"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