@tiplink/api
Version:
Api for creating and sending TipLinks
249 lines (248 loc) • 11.3 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.encodeUTF8 = exports.decodeUTF8 = exports.decryptDataUsingDH = exports.decryptMessage = exports.encryptMessage = exports.generateNonce = exports.decryptPrivateKey = exports.encryptPublicKey = exports.generatePublicPrivateKey = exports.decrypt = exports.encrypt = exports.generateKey = exports.generateRandomSalt = exports.DEFAULT_TIPLINK_KEYLENGTH = void 0;
const tweetnacl_1 = __importDefault(require("tweetnacl"));
const tweetnacl_util_1 = require("tweetnacl-util");
Object.defineProperty(exports, "decodeUTF8", { enumerable: true, get: function () { return tweetnacl_util_1.decodeUTF8; } });
Object.defineProperty(exports, "encodeUTF8", { enumerable: true, get: function () { return tweetnacl_util_1.encodeUTF8; } });
exports.DEFAULT_TIPLINK_KEYLENGTH = 12;
const crypto_1 = require("crypto");
const crypto = crypto_1.webcrypto;
const bs58_1 = __importDefault(require("bs58"));
/*
Convert a string into an ArrayBuffer
from https://developers.google.com/web/updates/2012/06/How-to-convert-ArrayBuffer-to-and-from-String
*/
function stringToArrayBuffer(str) {
const buf = new ArrayBuffer(str.length);
const bufView = new Uint8Array(buf);
for (let i = 0, strLen = str.length; i < strLen; i++) {
bufView[i] = str.charCodeAt(i);
}
return buf;
}
// Crypto encrypting links in db and apikeys
const KEY_LENGTH_BYTES = 256;
const SALT_LENGTH_BYTES = 12;
const MODULUS_LENGTH = 4096;
const DIGEST_ALGO = "SHA-512";
const HASH_ALGO = "AES-GCM"; // Alternatives 'AES-CTR' 'AES-CBC' 'AES-GCM'
const HASH_ALGO_WTIH_PUBLIC_PRIVATE = "RSA-OAEP";
const EXPORT_HASH_ALGO = "A256GCM";
// const EXPORT_PUBLIC_PRIVATE_ALGO = "RSA-OAEP-512";
const EXPORT_FORMAT = "jwk";
const EXPORT_PUBLIC_FORMAT = "spki";
const EXPORT_PRIVATE_FORMAT = "pkcs8";
const generateRandomSalt = () => {
return Buffer.from(crypto.getRandomValues(new Uint8Array(SALT_LENGTH_BYTES))).toString("hex");
};
exports.generateRandomSalt = generateRandomSalt;
const generateKey = () => __awaiter(void 0, void 0, void 0, function* () {
const key = yield crypto.subtle.generateKey({
name: HASH_ALGO,
length: KEY_LENGTH_BYTES,
}, true, ["encrypt", "decrypt"]);
// @ts-ignore
return exportKey(key);
});
exports.generateKey = generateKey;
const exportKey = (key) => __awaiter(void 0, void 0, void 0, function* () {
// @ts-ignore
const exportableKey = yield crypto.subtle.exportKey(EXPORT_FORMAT, key);
// if (exportableKey.alg === EXPORT_HASH_ALGO) {
if (exportableKey.k === undefined) {
throw Error("Export Key Error");
}
return exportableKey.k;
// }
});
const importKey = (key) => {
const jwkKey = {
alg: EXPORT_HASH_ALGO,
k: key,
ext: true,
key_ops: ["encrypt", "decrypt"],
kty: "oct",
};
return crypto.subtle.importKey("jwk", jwkKey, {
name: HASH_ALGO,
length: KEY_LENGTH_BYTES,
}, true, ["encrypt", "decrypt"]);
};
const encrypt = (data, key, salt) => __awaiter(void 0, void 0, void 0, function* () {
const ec = new TextEncoder();
const iv = Uint8Array.from(Buffer.from(salt, "hex"));
const encKey = yield importKey(key);
const ciphertext = yield crypto.subtle.encrypt({
name: HASH_ALGO,
iv: iv,
}, encKey, ec.encode(data));
return Buffer.from(ciphertext).toString("hex");
});
exports.encrypt = encrypt;
const decrypt = (data, key, salt) => __awaiter(void 0, void 0, void 0, function* () {
const ciphertext = Uint8Array.from(Buffer.from(data, "hex"));
const iv = Uint8Array.from(Buffer.from(salt, "hex"));
const decKey = yield importKey(key);
const dec = new TextDecoder();
const plaintext = yield crypto.subtle.decrypt({
name: HASH_ALGO,
iv: iv,
}, decKey, ciphertext);
return dec.decode(plaintext);
});
exports.decrypt = decrypt;
const generatePublicPrivateKey = () => __awaiter(void 0, void 0, void 0, function* () {
const { publicKey, privateKey } = yield crypto.subtle.generateKey({
name: HASH_ALGO_WTIH_PUBLIC_PRIVATE,
modulusLength: MODULUS_LENGTH,
publicExponent: new Uint8Array([1, 0, 1]),
hash: DIGEST_ALGO,
}, true, ["encrypt", "decrypt"]);
return {
// @ts-ignore
publicKey: yield exportPublicKey(publicKey),
// @ts-ignore
privateKey: yield exportPrivateKey(privateKey),
};
});
exports.generatePublicPrivateKey = generatePublicPrivateKey;
const exportPublicKey = (publicKey) => __awaiter(void 0, void 0, void 0, function* () {
// @ts-ignore
const exportableKey = yield crypto.subtle.exportKey(EXPORT_PUBLIC_FORMAT, publicKey);
// TODO type check this?
return btoa(
// @ts-ignore
String.fromCharCode.apply(null, new Uint8Array(exportableKey)));
// return Buffer.from(exportableKey).toString('hex');
});
const importPublicKey = (publicKey) => __awaiter(void 0, void 0, void 0, function* () {
// const arrBuf = Uint8Array.from(Buffer.from(publicKey, 'hex'));
const arrBuf = stringToArrayBuffer(atob(publicKey));
return crypto.subtle.importKey(EXPORT_PUBLIC_FORMAT, arrBuf, {
name: HASH_ALGO_WTIH_PUBLIC_PRIVATE,
// modulusLength: MODULUS_LENGTH,
// publicExponent: new Uint8Array([1, 0, 1]),
hash: DIGEST_ALGO,
}, true, ["encrypt"]);
});
const exportPrivateKey = (privateKey) => __awaiter(void 0, void 0, void 0, function* () {
// @ts-ignore
const exportableKey = yield crypto.subtle.exportKey(EXPORT_PRIVATE_FORMAT, privateKey);
// TODO type check this?
return btoa(
// @ts-ignore
String.fromCharCode.apply(null, new Uint8Array(exportableKey)));
// return Buffer.from(exportableKey).toString('hex');
});
const importPrivateKey = (privateKey) => __awaiter(void 0, void 0, void 0, function* () {
const arrBuf = stringToArrayBuffer(atob(privateKey));
return crypto.subtle.importKey(EXPORT_PRIVATE_FORMAT, arrBuf, {
name: HASH_ALGO_WTIH_PUBLIC_PRIVATE,
// modulusLength: MODULUS_LENGTH,
// publicExponent: new Uint8Array([1, 0, 1]),
hash: DIGEST_ALGO,
}, true, ["decrypt"]);
});
const encryptPublicKey = (data, publicKey) => __awaiter(void 0, void 0, void 0, function* () {
const ec = new TextEncoder();
const encKey = yield importPublicKey(publicKey);
const ciphertext = yield crypto.subtle.encrypt({
name: HASH_ALGO_WTIH_PUBLIC_PRIVATE,
}, encKey, ec.encode(data));
return Buffer.from(ciphertext).toString("hex");
});
exports.encryptPublicKey = encryptPublicKey;
const decryptPrivateKey = (data, privateKey) => __awaiter(void 0, void 0, void 0, function* () {
const ciphertext = Uint8Array.from(Buffer.from(data, "hex"));
const decKey = yield importPrivateKey(privateKey);
const dec = new TextDecoder();
const plaintext = yield crypto.subtle.decrypt({
name: HASH_ALGO_WTIH_PUBLIC_PRIVATE,
// iv: iv,
}, decKey, ciphertext);
return dec.decode(plaintext);
});
exports.decryptPrivateKey = decryptPrivateKey;
const generateNonce = () => __awaiter(void 0, void 0, void 0, function* () {
return Buffer.from(tweetnacl_1.default.randomBytes(24)).toString("hex");
});
exports.generateNonce = generateNonce;
const encryptMessage = (keypair, nonce, message) => __awaiter(void 0, void 0, void 0, function* () {
const nonceArray = Uint8Array.from(Buffer.from(nonce, "hex"));
return tweetnacl_1.default.secretbox((0, tweetnacl_util_1.decodeUTF8)(message), nonceArray, keypair.secretKey.slice(32));
});
exports.encryptMessage = encryptMessage;
const decryptMessage = (keypair, nonce, encryptedMessage) => __awaiter(void 0, void 0, void 0, function* () {
const nonceArray = Uint8Array.from(Buffer.from(nonce, "hex"));
const encryptedMessageArray = Uint8Array.from(Buffer.from(encryptedMessage, "hex"));
// const encryptedMessageArray = stringToArrayBuffer(window.atob(encryptedMessage));
const res = tweetnacl_1.default.secretbox.open(encryptedMessageArray, nonceArray, keypair.secretKey.slice(32));
if (res === null) {
return "";
}
else {
return (0, tweetnacl_util_1.encodeUTF8)(res);
}
});
exports.decryptMessage = decryptMessage;
// encrypted using symmetric key encryption generated from a Diffie-Hellman key exchange
// https://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange'
// export const encryptDataUsingDH = (
// dataToEncrypt: { [key: string]: any },
// recipientEncryptionPubKey: Uint8Array | string // bs58 encoded string or nacl box public key (not solana keypair)
// ) => {
// // nacl box keypair (not solana wallet keypair)
// const keypair = nacl.box.keyPair();
// const nonce = nacl.randomBytes(24);
// const dataBuffer = Buffer.from(JSON.stringify(dataToEncrypt));
// const recipientPubKey =
// typeof recipientEncryptionPubKey === "string"
// ? bs58.decode(recipientEncryptionPubKey)
// : recipientEncryptionPubKey;
// const encryptedData = nacl.box(
// dataBuffer,
// nonce,
// recipientPubKey,
// keypair.secretKey
// );
// return {
// nonceUint8Arr: nonce,
// nonceBs58: bs58.encode(nonce),
// encryptedDataUint8Arr: encryptedData,
// encryptedDataBs58: bs58.encode(encryptedData),
// encryptionKeypairToHold: keypair,
// };
// };
const decryptDataUsingDH = (dataToDecrypt, // string must be bs58 encoded
nonce, // string must be bs58 encoded
senderEncryptionPubKey, // string must be bs58 encoded
encryptionKeypair) => {
const encryptedData = typeof dataToDecrypt === "string"
? bs58_1.default.decode(dataToDecrypt)
: dataToDecrypt;
const encryptionNonce = typeof nonce == "string" ? bs58_1.default.decode(nonce) : nonce;
const senderPubKey = typeof senderEncryptionPubKey === "string"
? bs58_1.default.decode(senderEncryptionPubKey)
: senderEncryptionPubKey;
const decryptedData = tweetnacl_1.default.box.open(encryptedData, encryptionNonce, senderPubKey, encryptionKeypair.secretKey);
if (!decryptedData)
return;
const decryptonDataJson = JSON.parse(Buffer.from(decryptedData).toString("utf8"));
// json.parse returns "any" typed json so ok to silence eslint error
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return decryptonDataJson;
};
exports.decryptDataUsingDH = decryptDataUsingDH;