xek-sdk
Version:
SDK for katana blockchain
285 lines (260 loc) • 7.96 kB
JavaScript
;
const crypto = require('crypto');
const bip39 = require('bip39');
const forge = require('node-forge');
const ed25519 = forge.pki.ed25519;
const cryptoJS = require('crypto-js');
const {PRIVATE_KEY_SIZE, ADDRESS_SIZE, CURVE_TYPE, GAS_LIMIT, TX_FEE, BLAKE3_SIZE } = require('./constant');
const utils = require('./utils/utils');
const blake3 = require("blake3-js");
class Wallet {
static async generateWallet(walletName, password) {
try {
const randomByte = crypto.randomBytes(16);
const mnemonic = bip39.entropyToMnemonic(randomByte.toString('hex'));
const seed = await bip39.mnemonicToSeed(mnemonic);
const keyPair = ed25519.generateKeyPair({seed: seed});
const publicKey = Buffer.from(keyPair.publicKey).toString('hex');
const privateKey = Buffer.from(keyPair.privateKey).toString('hex');
const address = await Wallet.getAddressFromPublicKey(keyPair.publicKey);
const cipherText = Wallet.encryptWallet(privateKey, password);
const wallet = {
walletName,
mnemonic,
publicKey,
address,
keyEncrypt: cipherText.toString()
};
return wallet;
} catch (e) {
console.log('Error when generate wallet', e.message);
return null;
}
}
static verifyInputs(inputs) {
const len = inputs.length;
for (let i = 0; i < len; i++) {
let input = inputs[i];
if (!utils.isAddress(input.Address) || !utils.isUint(input.Amount) || !utils.isUint(input.Sequence)) {
return false;
}
}
return true;
};
static verifyOutputs(outputs) {
const len = outputs.length;
for (let i = 0; i < len; i++) {
let output = outputs[i];
if (!utils.isAddress(output.Address) || !utils.isUint(output.Amount)) {
return false;
}
}
return true;
}
static verifyTransaction(tx) {
if (!tx) return false;
let txType = tx.Type;
switch (txType) {
case 'SendTx': {
return Wallet.verifyInputs(tx.Payload.Inputs) && Wallet.verifyOutputs(tx.Payload.Outputs);
}
default:
return false;
}
}
static encryptWallet(privateKey, password) {
try {
const cipherText = cryptoJS.AES.encrypt(privateKey, password);
return cipherText;
} catch (e) {
console.log('Error when encryptWallet');
return null;
}
}
static async decryptWallet(cipherText, password) {
try {
const bytes = cryptoJS.AES.decrypt(cipherText, password);
const privateKey = bytes.toString(cryptoJS.enc.Utf8);
let publicKey = await Wallet.getPublicKeyOffline(privateKey, CURVE_TYPE);
const address = await Wallet.getAddressFromPublicKey(publicKey.PublicKey);
publicKey = publicKey.PublicKey.toString('hex');
const walletRestore = {address, publicKey, privateKey};
return walletRestore;
} catch (e) {
console.log('Error when decryptWallet', e);
return null;
}
}
static async restoreWallet(mnemonic, walletName, password) {
try {
const seed = await bip39.mnemonicToSeed(mnemonic);
const keyPair = ed25519.generateKeyPair({seed: seed});
const publicKey = Buffer.from(keyPair.publicKey).toString('hex');
const privateKey = Buffer.from(keyPair.privateKey).toString('hex');
console.log(privateKey);
const address = await Wallet.getAddressFromPublicKey(keyPair.publicKey);
const cipherText = Wallet.encryptWallet(privateKey, password);
const walletImport = {
walletName,
publicKey,
address,
keyEncrypt: cipherText.toString()
};
return walletImport;
} catch (e) {
console.log('import wallet error', e);
return null;
}
}
static async getAddressFromPublicKey(publicKey) {
if (!publicKey) return null;
try {
// let blake3 = await load();
// console.log('blake3:', blake3);
return blake3.newRegular().update(publicKey).finalize(BLAKE3_SIZE).toString().slice(0, ADDRESS_SIZE).toUpperCase();
// return blake3.hash(publicKey, {length: BLAKE3_SIZE}).toString('hex').slice(0, ADDRESS_SIZE).toUpperCase();
} catch (e) {
console.log('error get address from public key', e);
return null;
}
}
static getPublicKeyOffline(privateKey, curveType) {
if (privateKey && privateKey.length !== PRIVATE_KEY_SIZE) return null;
try {
const privateByte = utils.encode(privateKey + '');
const publicByte = privateByte.slice(64);
let publicKeyOffline = utils.decode(publicByte);
publicKeyOffline = {
PublicKey: Buffer.from(publicKeyOffline, 'hex'),
CurveType: curveType
};
return publicKeyOffline;
} catch (e) {
console.log('error get public key offline', e);
return null;
}
}
static async signatureOffline(txData, privateKey) {
try {
const signature = await ed25519.sign({message: txData, privateKey: Buffer.from(privateKey, 'hex')});
return signature;
} catch (e) {
console.log('signature err', e);
return null;
}
}
static createTx(from, to, amount, sequence, CHAINID) {
const tx = {
ChainID: CHAINID,
Type: 'SendTx',
Payload: {
Inputs: [{
Address: from,
Amount: amount,
Sequence: sequence,
}],
Outputs: [{
Address: to,
Amount: amount,
}],
},
// thuannd start
Fee: TX_FEE,
// thuannd end
};
if (!Wallet.verifyTransaction(tx)) {
console.log('Transaction verify failed');
return null;
}
return Buffer.from(JSON.stringify(tx));
}
// thuannd start: don't send fee to admin anymore
// static createTxWithFee(from, to, amount, sequence, CHAINID, receive, fee) {
// let amountSend = new BigNumber(amount).minus(new BigNumber(fee)).toNumber();
// const tx = {
// ChainID: CHAINID,
// Type: 'SendTx',
// Payload: {
// Inputs: [{
// Address: from,
// Amount: amount,
// Sequence: sequence,
// }],
// Outputs: [
// {
// Address: to,
// Amount: amountSend,
// },
// {
// Address: receive,
// Amount: fee,
// }],
// },
// Fee: TX_FEE,
// };
// if (!Wallet.verifyTransaction(tx)) {
// console.log('Transaction verify failed');
// return null;
// }
// return Buffer.from(JSON.stringify(tx));
// }
// thuannd end
static createEnvelopTx(from, publicKey, signature, tx) {
const txEnvelope = {
Envelope: {
Signatories: [
{
Address: Buffer.from(from, 'hex'),
PublicKey: publicKey,
Signature: {
CurveType: 1,
Signature: signature,
},
},
],
Tx: tx,
},
};
return txEnvelope;
}
static async createTransactionDeploy(from, data, sequence, chainID) {
if (data.indexOf('0x') === 0) data = data.slice(2);
let tx = {
ChainID: chainID,
Type: 'CallTx',
Payload: {
Input: {
Address: from,
Sequence: sequence,
},
GasLimit: GAS_LIMIT,
Data: data
},
// thuannd start
Fee: TX_FEE
// thuannd end
};
return Buffer.from(JSON.stringify(tx));
};
static async createTransactionTransact(from, contractAddr, data, sequence, CHAIN_ID) {
if (data.indexOf('0x') === 0) data = data.slice(2);
let tx = {
ChainID: CHAIN_ID,
Type: 'CallTx',
Payload: {
Input: {
Address: from,
Sequence: sequence
},
Address: contractAddr,
GasLimit: GAS_LIMIT,
Data: data
},
// thuannd start
Fee: TX_FEE
// thuannd end
};
return Buffer.from(JSON.stringify(tx));
};
}
module.exports = Wallet;