UNPKG

xek-sdk

Version:

SDK for katana blockchain

285 lines (260 loc) 7.96 kB
'use strict'; 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;