Some checks failed
morphit-release / Build + publish release tarball (push) Has been cancelled
625 lines
24 KiB
JavaScript
625 lines
24 KiB
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 });
|
|
exports.cryptoUtils = exports.encodeMemo = exports.decodeMemo = exports.Signature = exports.PrivateKey = exports.PublicKey = void 0;
|
|
/**
|
|
* @file Blurt crypto helpers.
|
|
* @author BeBlurt <https://beblurt.com/@beblurt>
|
|
* @description adaptation from Johan Nordberg <code@johan-nordberg.com> crypto helpers.
|
|
* @license
|
|
* Copyright (c) 2017 Johan Nordberg. All Rights Reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without modification,
|
|
* are permitted provided that the following conditions are met:
|
|
*
|
|
* 1. Redistribution of source code must retain the above copyright notice, this
|
|
* list of conditions and the following disclaimer.
|
|
*
|
|
* 2. Redistribution in binary form must reproduce the above copyright notice,
|
|
* this list of conditions and the following disclaimer in the documentation
|
|
* and/or other materials provided with the distribution.
|
|
*
|
|
* 3. Neither the name of the copyright holder nor the names of its contributors
|
|
* may be used to endorse or promote products derived from this software without
|
|
* specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
|
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
|
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
|
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
|
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
* You acknowledge that this software is not designed, licensed or intended for use
|
|
* in the design, construction, operation or maintenance of any military facility.
|
|
*/
|
|
const assert_1 = __importDefault(require("assert"));
|
|
const bs58_1 = __importDefault(require("bs58"));
|
|
const aesjs = require('aes-js');
|
|
const nobleSha2 = require('@noble/hashes/sha2');
|
|
const nobleLegacy = require('@noble/hashes/legacy');
|
|
// eslint-disable-next-line import/order
|
|
const ByteBuffer = require('bytebuffer/dist/bytebuffer');
|
|
const Long = ByteBuffer.Long;
|
|
const secp256k1 = __importStar(require("@noble/secp256k1"));
|
|
const nobleHmac = require('@noble/hashes/hmac');
|
|
secp256k1.utils.hmacSha256Sync = (key, ...messages) => (nobleHmac.hmac(nobleSha2.sha256, key, secp256k1.utils.concatBytes(...messages)));
|
|
const errors_1 = require("./errors");
|
|
const serializer_1 = require("./chain/serializer");
|
|
const deserializer_1 = require("./chain/deserializer");
|
|
const client_1 = require("./client");
|
|
const utils_1 = require("./utils");
|
|
/** Network id used in WIF-encoding */
|
|
const NETWORK_ID = Buffer.from([0x80]);
|
|
/** Regexp for a graphene account */
|
|
const regExpAccount = /^(?=.{3,16}$)[a-z][0-9a-z\-]{1,}[0-9a-z]([\.][a-z][0-9a-z\-]{1,}[0-9a-z]){0,}$/;
|
|
/** Regexp for a graphene account with @ */
|
|
const regExpAtAccount = /^@(?=.{3,16}$)[a-z][0-9a-z\-]{1,}[0-9a-z]([\.][a-z][0-9a-z\-]{1,}[0-9a-z]){0,}$/;
|
|
/** From Buffer to Uint8Array */
|
|
const toUint8Array = (buf) => {
|
|
const ab = new ArrayBuffer(buf.length);
|
|
const view = new Uint8Array(ab);
|
|
for (let i = 0; i < buf.length; ++i) {
|
|
view[i] = buf[i];
|
|
}
|
|
return view;
|
|
};
|
|
/** From ArrayBuffer to Buffer */
|
|
const toBuffer = (ab) => {
|
|
const buf = Buffer.alloc(ab.byteLength);
|
|
const view = new Uint8Array(ab);
|
|
for (let i = 0; i < buf.length; ++i) {
|
|
buf[i] = view[i];
|
|
}
|
|
return buf;
|
|
};
|
|
/**
|
|
* Converts a string, number or Long to a ByteBuffer object.
|
|
*
|
|
* @param o - The value to be converted.
|
|
* @returns A ByteBuffer object.
|
|
* @throws An error if the input is not a string, number or Long.
|
|
*/
|
|
const toByteBuffer = (o) => {
|
|
if (typeof o === 'string') {
|
|
return Long.fromString(o);
|
|
}
|
|
else if (typeof o === 'number') {
|
|
return Long.fromNumber(o);
|
|
}
|
|
else if (o instanceof ByteBuffer.Long) {
|
|
return o;
|
|
}
|
|
else {
|
|
throw new Error('Input is not a string, number or Long');
|
|
}
|
|
};
|
|
/**
|
|
* Generates a unique 64 bit unsigned number string. Being time based,
|
|
* this is careful to never choose the same nonce twice. This value could
|
|
* be recorded in the blockchain for a long time.
|
|
* @returns The unique nonce.
|
|
*/
|
|
let unique_nonce_entropy = Math.floor(Math.random() * 0xFFFF);
|
|
const uniqueNonce = () => {
|
|
let long = BigInt(Date.now());
|
|
const entropy = ++unique_nonce_entropy % 0xFFFF;
|
|
if (entropy === 0) {
|
|
const last = Number(long >> BigInt(16));
|
|
const now = Date.now();
|
|
if (now <= last) {
|
|
long += BigInt(1);
|
|
}
|
|
unique_nonce_entropy = 0;
|
|
}
|
|
long = (long << BigInt(16)) | BigInt(entropy);
|
|
return long.toString(16).padStart(16, '0');
|
|
};
|
|
/** Return ripemd160 hash of input */
|
|
const ripemd160 = (input) => Buffer.from(nobleLegacy.ripemd160(hashInput(input)));
|
|
/** Return sha256 hash of input */
|
|
const sha256 = (input) => Buffer.from(nobleSha2.sha256(hashInput(input)));
|
|
/** Return sha512 hash of input */
|
|
const sha512 = (input) => Buffer.from(nobleSha2.sha512(hashInput(input)));
|
|
/** Normalize input for hash libraries. */
|
|
const hashInput = (input) => typeof input === 'string' ? Buffer.from(input) : input;
|
|
/** PKCS#7 block padding used by Node's createCipheriv autoPadding default. */
|
|
const pkcs7Pad = (input, blockSize = 16) => {
|
|
const remainder = input.length % blockSize;
|
|
const padding = remainder === 0 ? blockSize : blockSize - remainder;
|
|
return Buffer.concat([Buffer.from(input), Buffer.alloc(padding, padding)]);
|
|
};
|
|
/** Remove PKCS#7 block padding produced by AES-CBC encryption. */
|
|
const pkcs7Unpad = (input, blockSize = 16) => {
|
|
(0, assert_1.default)(input.length > 0 && input.length % blockSize === 0, 'invalid pkcs7 payload length');
|
|
const padding = input[input.length - 1];
|
|
(0, assert_1.default)(padding > 0 && padding <= blockSize, 'invalid pkcs7 padding');
|
|
for (let i = input.length - padding; i < input.length; i++) {
|
|
(0, assert_1.default)(input[i] === padding, 'invalid pkcs7 padding');
|
|
}
|
|
return Buffer.from(input).slice(0, input.length - padding);
|
|
};
|
|
/** Encrypt AES-256-CBC with PKCS#7 padding. */
|
|
const aes256CbcEncrypt = (key, iv, input) => {
|
|
const cipher = new aesjs.ModeOfOperation.cbc(Array.from(key), Array.from(iv));
|
|
return Buffer.from(cipher.encrypt(Array.from(pkcs7Pad(input))));
|
|
};
|
|
/** Decrypt AES-256-CBC with PKCS#7 padding. */
|
|
const aes256CbcDecrypt = (key, iv, input) => {
|
|
const decipher = new aesjs.ModeOfOperation.cbc(Array.from(key), Array.from(iv));
|
|
return Buffer.from(pkcs7Unpad(decipher.decrypt(Array.from(input))));
|
|
};
|
|
/** Return 2-round sha256 hash of input */
|
|
const doubleSha256 = (input) => sha256(sha256(input));
|
|
/** Encode public key with bs58+ripemd160-checksum */
|
|
const encodePublic = (key, prefix) => {
|
|
const checksum = ripemd160(key);
|
|
return prefix + bs58_1.default.encode(Buffer.concat([key, toUint8Array(checksum).slice(0, 4)]));
|
|
};
|
|
/** Decode bs58+ripemd160-checksum encoded public key */
|
|
const decodePublic = (encodedKey) => {
|
|
const prefix = encodedKey.slice(0, 3);
|
|
encodedKey = encodedKey.slice(3);
|
|
const uint8Array = bs58_1.default.decode(encodedKey);
|
|
const checksum = uint8Array.slice(-4);
|
|
const key = uint8Array.slice(0, -4);
|
|
const bufKey = Buffer.from(key);
|
|
const checksumVerify = toUint8Array(ripemd160(bufKey)).slice(0, 4);
|
|
if (Buffer.compare(toBuffer(checksumVerify), toBuffer(checksum)) !== 0) {
|
|
throw new Error('public key checksum mismatch');
|
|
}
|
|
return { key: bufKey, prefix };
|
|
};
|
|
/** Encode bs58+doubleSha256-checksum private key */
|
|
const encodePrivate = (key) => {
|
|
assert_1.default.strictEqual(key.readUInt8(0), 0x80, 'private key network id mismatch');
|
|
const checksum = doubleSha256(key);
|
|
return bs58_1.default.encode(Buffer.concat([key, toUint8Array(checksum).slice(0, 4)]));
|
|
};
|
|
/** Decode bs58+doubleSha256-checksum encoded private key */
|
|
const decodePrivate = (encodedKey) => {
|
|
const uint8Array = bs58_1.default.decode(encodedKey);
|
|
const toCompare = toBuffer(uint8Array.slice(0, 1));
|
|
if (Buffer.compare(NETWORK_ID, toCompare) !== 0) {
|
|
throw new Error('private key network id mismatch');
|
|
}
|
|
const checksum = uint8Array.slice(-4);
|
|
const key = uint8Array.slice(0, -4);
|
|
const bufKey = Buffer.from(key);
|
|
const dSha256 = sha256(sha256(bufKey));
|
|
const checksumVerify = toUint8Array(dSha256).slice(0, 4);
|
|
if (Buffer.compare(toBuffer(checksumVerify), toBuffer(checksum)) !== 0) {
|
|
throw new Error('private key checksum mismatch');
|
|
}
|
|
return bufKey;
|
|
};
|
|
/** Return true if signature is canonical, otherwise false */
|
|
const isCanonicalSignature = (signature) => (!(signature[0] & 0x80) &&
|
|
!(signature[0] === 0 && !(signature[1] & 0x80)) &&
|
|
!(signature[32] & 0x80) &&
|
|
!(signature[32] === 0 && !(signature[33] & 0x80)));
|
|
/** Return true if string is wif, otherwise false */
|
|
const isWif = (privWif) => {
|
|
try {
|
|
const bufWif = Buffer.from(bs58_1.default.decode(privWif));
|
|
if (bufWif.length !== 37) {
|
|
return false;
|
|
}
|
|
if (Buffer.compare(bufWif.slice(0, 1), NETWORK_ID) !== 0) {
|
|
return false;
|
|
}
|
|
const privKey = bufWif.slice(0, -4);
|
|
const checksum = bufWif.slice(-4);
|
|
const newChecksum = doubleSha256(privKey).slice(0, 4);
|
|
return Buffer.compare(checksum, newChecksum) === 0;
|
|
}
|
|
catch (e) {
|
|
return false;
|
|
}
|
|
};
|
|
/**
|
|
* ECDSA (secp256k1) public key.
|
|
*/
|
|
class PublicKey {
|
|
constructor(key, prefix = client_1.DEFAULT_ADDRESS_PREFIX) {
|
|
this.key = key;
|
|
this.prefix = prefix;
|
|
(0, assert_1.default)(secp256k1.Point.fromHex(key), 'invalid public key');
|
|
this.uncompressed = Buffer.from(secp256k1.Point.fromHex(key).toRawBytes(false));
|
|
}
|
|
/**
|
|
* Create a new instance from a WIF-encoded key.
|
|
*/
|
|
static fromString(wif) {
|
|
const { key, prefix } = decodePublic(wif);
|
|
return new PublicKey(key, prefix);
|
|
}
|
|
static fromBuffer(key) {
|
|
(0, assert_1.default)(secp256k1.Point.fromHex(key), 'invalid buffer as public key');
|
|
return { key };
|
|
}
|
|
/**
|
|
* Create a new instance.
|
|
*/
|
|
static from(value) {
|
|
if (value instanceof PublicKey) {
|
|
return value;
|
|
}
|
|
else {
|
|
return PublicKey.fromString(value);
|
|
}
|
|
}
|
|
/**
|
|
* Verify a 32-byte signature.
|
|
* @param message 32-byte message to verify.
|
|
* @param signature Signature to verify.
|
|
*/
|
|
verify(message, signature) {
|
|
return secp256k1.verify(signature.data, message, this.key, { strict: false });
|
|
}
|
|
/**
|
|
* Return a WIF-encoded representation of the key.
|
|
*/
|
|
toString() {
|
|
return encodePublic(this.key, this.prefix);
|
|
}
|
|
/**
|
|
* Return JSON representation of this key, same as toString().
|
|
*/
|
|
toJSON() {
|
|
return this.toString();
|
|
}
|
|
/**
|
|
* Used by `utils.inspect` and `console.log` in node.js.
|
|
*/
|
|
inspect() {
|
|
return `PublicKey: ${this.toString()}`;
|
|
}
|
|
}
|
|
exports.PublicKey = PublicKey;
|
|
/**
|
|
* ECDSA (secp256k1) private key.
|
|
*/
|
|
class PrivateKey {
|
|
constructor(key) {
|
|
this.key = key;
|
|
(0, assert_1.default)(secp256k1.utils.isValidPrivateKey(key), 'invalid private key');
|
|
}
|
|
/**
|
|
* Convenience to create a new instance from WIF string or buffer.
|
|
*/
|
|
static from(value) {
|
|
if (typeof value === 'string') {
|
|
return PrivateKey.fromString(value);
|
|
}
|
|
else {
|
|
return new PrivateKey(value);
|
|
}
|
|
}
|
|
/**
|
|
* Create a new instance from a WIF-encoded key.
|
|
*/
|
|
static fromString(wif) {
|
|
const decoded = toUint8Array(decodePrivate(wif)).slice(1);
|
|
return new PrivateKey(toBuffer(decoded));
|
|
}
|
|
/**
|
|
* Create a new instance from a seed.
|
|
*/
|
|
static fromSeed(seed) {
|
|
return new PrivateKey(sha256(seed));
|
|
}
|
|
/**
|
|
* Create key from username and password.
|
|
*/
|
|
static fromLogin(username, password, role = 'active') {
|
|
const seed = username + role + password;
|
|
return PrivateKey.fromSeed(seed);
|
|
}
|
|
/**
|
|
* Sign message.
|
|
* @param message 32-byte message.
|
|
*/
|
|
sign(message) {
|
|
let rv;
|
|
let attempts = 0;
|
|
do {
|
|
const options = {
|
|
data: sha256(Buffer.concat([message, Buffer.alloc(1, ++attempts)]))
|
|
};
|
|
const [signature, recid] = secp256k1.signSync(message, this.key, {
|
|
der: false,
|
|
extraEntropy: options.data,
|
|
recovered: true
|
|
});
|
|
rv = { signature, recid };
|
|
} while (!isCanonicalSignature(toBuffer(rv.signature)));
|
|
return new Signature(toBuffer(rv.signature), rv.recid);
|
|
}
|
|
/**
|
|
* Derive the public key for this private key.
|
|
*/
|
|
createPublic(prefix) {
|
|
return new PublicKey(Buffer.from(secp256k1.getPublicKey(this.key, true)), prefix);
|
|
}
|
|
/**
|
|
* Return a WIF-encoded representation of the key.
|
|
*/
|
|
toString() {
|
|
return encodePrivate(Buffer.concat([NETWORK_ID, this.key]));
|
|
}
|
|
/**
|
|
* Get shared secret for memo cryptography
|
|
*/
|
|
getSharedSecret(publicKey) {
|
|
const sharedPoint = Buffer.from(secp256k1.getSharedSecret(this.key, publicKey.key, false));
|
|
return sha512(sharedPoint.slice(1, 33));
|
|
}
|
|
/**
|
|
* Used by `utils.inspect` and `console.log` in node.js. Does not show the full key
|
|
* to get the full encoded key you need to explicitly call {@link toString}.
|
|
*/
|
|
inspect() {
|
|
const key = this.toString();
|
|
return `PrivateKey: ${key.slice(0, 6)}...${key.slice(-6)}`;
|
|
}
|
|
}
|
|
exports.PrivateKey = PrivateKey;
|
|
/**
|
|
* ECDSA (secp256k1) signature.
|
|
*/
|
|
class Signature {
|
|
constructor(data, recovery) {
|
|
this.data = data;
|
|
this.recovery = recovery;
|
|
assert_1.default.strictEqual(data.length, 64, 'invalid signature');
|
|
}
|
|
static fromBuffer(buffer) {
|
|
assert_1.default.strictEqual(buffer.length, 65, 'invalid signature');
|
|
const recovery = buffer.readUInt8(0) - 31;
|
|
const data = toUint8Array(buffer).slice(1);
|
|
return new Signature(toBuffer(data), recovery);
|
|
}
|
|
static fromString(string) {
|
|
return Signature.fromBuffer(Buffer.from(string, 'hex'));
|
|
}
|
|
/**
|
|
* Recover public key from signature by providing original signed message.
|
|
* @param message 32-byte message that was used to create the signature.
|
|
*/
|
|
recover(message, prefix) {
|
|
return new PublicKey(Buffer.from(secp256k1.recoverPublicKey(message, this.data, this.recovery, true)), prefix);
|
|
}
|
|
toBuffer() {
|
|
const buffer = Buffer.alloc(65);
|
|
buffer.writeUInt8(this.recovery + 31, 0);
|
|
this.data.copy(buffer, 1);
|
|
return buffer;
|
|
}
|
|
toString() {
|
|
return this.toBuffer().toString('hex');
|
|
}
|
|
}
|
|
exports.Signature = Signature;
|
|
/**
|
|
* Return the sha256 transaction digest.
|
|
* @param chainId The chain id to use when creating the hash.
|
|
*/
|
|
const transactionDigest = (transaction, chainId = client_1.DEFAULT_CHAIN_ID) => {
|
|
const buffer = new ByteBuffer(ByteBuffer.DEFAULT_CAPACITY, ByteBuffer.LITTLE_ENDIAN);
|
|
try {
|
|
serializer_1.Types.Transaction(buffer, transaction);
|
|
}
|
|
catch (cause) {
|
|
throw new errors_1.SerializationError(cause);
|
|
}
|
|
buffer.flip();
|
|
const transactionData = Buffer.from(buffer.toBuffer());
|
|
const digest = sha256(Buffer.concat([chainId, transactionData]));
|
|
return digest;
|
|
};
|
|
/**
|
|
* Return copy of transaction with signature appended to signatures array.
|
|
* @param transaction Transaction to sign.
|
|
* @param keys Key(s) to sign transaction with.
|
|
* @param chainId Chain id used when computing the transaction digest.
|
|
*/
|
|
// eslint-disable-next-line max-len
|
|
const signTransaction = (transaction, keys, chainId = client_1.DEFAULT_CHAIN_ID) => {
|
|
const digest = transactionDigest(transaction, chainId);
|
|
const signedTransaction = (0, utils_1.copy)(transaction);
|
|
if (!signedTransaction.signatures) {
|
|
signedTransaction.signatures = [];
|
|
}
|
|
if (!Array.isArray(keys)) {
|
|
keys = [keys];
|
|
}
|
|
for (const key of keys) {
|
|
const signature = key.sign(digest);
|
|
signedTransaction.signatures.push(signature.toString());
|
|
}
|
|
return signedTransaction;
|
|
};
|
|
const generateTrxId = (transaction) => {
|
|
const buffer = new ByteBuffer(ByteBuffer.DEFAULT_CAPACITY, ByteBuffer.LITTLE_ENDIAN);
|
|
try {
|
|
serializer_1.Types.Transaction(buffer, transaction);
|
|
}
|
|
catch (cause) {
|
|
throw new errors_1.SerializationError(cause);
|
|
}
|
|
buffer.flip();
|
|
const transactionData = Buffer.from(buffer.toBuffer());
|
|
return exports.cryptoUtils.sha256(transactionData).toString('hex').slice(0, 40);
|
|
};
|
|
/**
|
|
* Memo Encode/Decode
|
|
*/
|
|
/**
|
|
* remove varint length prefix
|
|
*
|
|
* @param decryptedMessage - Buffer of the decrypted message
|
|
* @returns the decrypted message minus the varint length prefix
|
|
*/
|
|
const removeVarintLengthPrefix = (decryptedMessage) => {
|
|
const mbuf = ByteBuffer.fromBinary(decryptedMessage.toString('binary'), ByteBuffer.LITTLE_ENDIAN);
|
|
try {
|
|
mbuf.mark();
|
|
return '#' + mbuf.readVString();
|
|
}
|
|
catch (e) {
|
|
mbuf.reset();
|
|
// Sender did not length-prefix the memo
|
|
const memo = Buffer.from(mbuf.toString('binary'), 'binary').toString('utf-8');
|
|
return '#' + memo;
|
|
}
|
|
};
|
|
/**
|
|
* Decrypts an encrypted memo.
|
|
*
|
|
* @param memo - The encrypted memo to decrypt (need to start with #).
|
|
* @param receiverPrivateMemoKey - The private Memo key of the recipient.
|
|
* @returns The decrypted message.
|
|
*/
|
|
const decodeMemo = (memo, receiverPrivateMemoKey) => {
|
|
try {
|
|
(0, assert_1.default)(typeof receiverPrivateMemoKey === 'string' || receiverPrivateMemoKey.toString(), 'Invalid Receiver private MEMO key!');
|
|
if (!memo.startsWith('#')) {
|
|
return memo;
|
|
}
|
|
const privateKey = typeof receiverPrivateMemoKey === 'string' ? PrivateKey.from(receiverPrivateMemoKey) : receiverPrivateMemoKey;
|
|
memo = memo.substring(1);
|
|
const memoBuff = bs58_1.default.decode(memo);
|
|
const deserialized = (0, deserializer_1.EncryptedMemoDeserializer)(Buffer.from(memoBuff));
|
|
// const { from, to, nonce, check, encrypted } = deserialized
|
|
const { from, nonce, check, encrypted } = deserialized;
|
|
const publicKey = new PublicKey(from.key);
|
|
const nonceLong = toByteBuffer(nonce);
|
|
// Appending nonce to buffer "ebuf" and rehash with sha512
|
|
const S = privateKey.getSharedSecret(publicKey);
|
|
let ebuf = new ByteBuffer(ByteBuffer.DEFAULT_CAPACITY, ByteBuffer.LITTLE_ENDIAN);
|
|
ebuf.writeUint64(nonceLong);
|
|
ebuf.append(S.toString('binary'), 'binary');
|
|
ebuf = Buffer.from(ebuf.copy(0, ebuf.offset).toBinary(), 'binary');
|
|
const encryption_key = sha512(ebuf);
|
|
const iv = encryption_key.slice(32, 48);
|
|
const tag = encryption_key.slice(0, 32);
|
|
// check if first 64 bit of sha256 hash treated as uint64_t truncated to 32 bits.
|
|
let checksum = sha256(encryption_key);
|
|
checksum = checksum.slice(0, 4);
|
|
const cbuf = ByteBuffer.fromBinary(checksum.toString('binary'), ByteBuffer.LITTLE_ENDIAN);
|
|
checksum = cbuf.readUint32();
|
|
(0, assert_1.default)(check === checksum, 'Invalid nonce!');
|
|
const binaryMessage = toUint8Array(encrypted);
|
|
const decryptedMessage = aes256CbcDecrypt(tag, iv, binaryMessage);
|
|
return removeVarintLengthPrefix(decryptedMessage);
|
|
}
|
|
catch (e) {
|
|
throw e;
|
|
}
|
|
};
|
|
exports.decodeMemo = decodeMemo;
|
|
/**
|
|
* Encrypt a memo.
|
|
*
|
|
* @param memo - The memo to encrypt (need to start with #).
|
|
* @param senderPrivateMemoKey - The private Memo key of the sender.
|
|
* @param receiverPublicMemoKey - The publicKey Memo key of the recipient.
|
|
* @returns The encrypted message.
|
|
*/
|
|
const encodeMemo = (memo, senderPrivateMemoKey, receiverPublicMemoKey) => {
|
|
try {
|
|
(0, assert_1.default)(typeof senderPrivateMemoKey === 'string' || senderPrivateMemoKey.toString(), 'Invalid Sender private MEMO key!');
|
|
(0, assert_1.default)(typeof receiverPublicMemoKey === 'string' || receiverPublicMemoKey.toString(), 'Invalid Receiver public MEMO key!');
|
|
if (!memo.startsWith('#')) {
|
|
return memo;
|
|
}
|
|
const privateKey = typeof senderPrivateMemoKey === 'string' ? PrivateKey.from(senderPrivateMemoKey) : senderPrivateMemoKey;
|
|
const publicKey = typeof receiverPublicMemoKey === 'string' ? PublicKey.from(receiverPublicMemoKey) : receiverPublicMemoKey;
|
|
memo = memo.substring(1);
|
|
const mbuf = new ByteBuffer(ByteBuffer.DEFAULT_CAPACITY, ByteBuffer.LITTLE_ENDIAN);
|
|
mbuf.writeVString(memo);
|
|
const memoBuff = Buffer.from(mbuf.flip().toBinary(), 'binary');
|
|
const nonceLong = toByteBuffer(uniqueNonce());
|
|
// Appending nonce to buffer "ebuf" and rehash with sha512
|
|
const S = privateKey.getSharedSecret(publicKey);
|
|
let ebuf = new ByteBuffer(ByteBuffer.DEFAULT_CAPACITY, ByteBuffer.LITTLE_ENDIAN);
|
|
ebuf.writeUint64(nonceLong);
|
|
ebuf.append(S.toString('binary'), 'binary');
|
|
ebuf = Buffer.from(ebuf.copy(0, ebuf.offset).toBinary(), 'binary');
|
|
const encryption_key = sha512(ebuf);
|
|
const iv = encryption_key.slice(32, 48);
|
|
const tag = encryption_key.slice(0, 32);
|
|
// check if first 64 bit of sha256 hash treated as uint64_t truncated to 32 bits.
|
|
let check = sha256(encryption_key);
|
|
check = check.slice(0, 4);
|
|
const cbuf = ByteBuffer.fromBinary(check.toString('binary'), ByteBuffer.LITTLE_ENDIAN);
|
|
check = cbuf.readUint32();
|
|
let message = toUint8Array(memoBuff);
|
|
message = aes256CbcEncrypt(tag, iv, message);
|
|
const lbuf = new ByteBuffer(ByteBuffer.DEFAULT_CAPACITY, ByteBuffer.LITTLE_ENDIAN);
|
|
serializer_1.Types.Memo(lbuf, {
|
|
check,
|
|
encrypted: message,
|
|
from: privateKey.createPublic(),
|
|
nonce: nonceLong,
|
|
to: publicKey
|
|
});
|
|
lbuf.flip();
|
|
const data = Buffer.from(lbuf.toBuffer());
|
|
return '#' + bs58_1.default.encode(data);
|
|
}
|
|
catch (e) {
|
|
throw e;
|
|
}
|
|
};
|
|
exports.encodeMemo = encodeMemo;
|
|
/** Misc crypto utility functions. */
|
|
exports.cryptoUtils = {
|
|
decodePrivate,
|
|
doubleSha256,
|
|
encodePrivate,
|
|
encodePublic,
|
|
generateTrxId,
|
|
isCanonicalSignature,
|
|
isWif,
|
|
regExpAccount,
|
|
regExpAtAccount,
|
|
ripemd160,
|
|
sha256,
|
|
signTransaction,
|
|
toBuffer,
|
|
toByteBuffer,
|
|
toUint8Array,
|
|
transactionDigest,
|
|
uniqueNonce
|
|
};
|