UNPKG

@pgchain/blockchain-libs

Version:
498 lines 21.7 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); }) : (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.Provider = void 0; const js_sdk_1 = __importDefault(require("@onekeyfe/js-sdk")); // @ts-ignore const pathUtils = __importStar(require("@onekeyfe/js-sdk/lib/utils/pathUtils")); const bignumber_js_1 = __importDefault(require("bignumber.js")); const BitcoinJS = __importStar(require("bitcoinjs-lib")); const bitcoinjs_message_1 = __importDefault(require("bitcoinjs-message")); const bs58check_1 = __importDefault(require("bs58check")); const precondtion_1 = require("../../../basic/precondtion"); const secret_1 = require("../../../secret"); const curves_1 = require("../../../secret/curves"); const abc_1 = require("../../abc"); const blockbook_1 = require("./blockbook"); const addressEncodings_1 = __importDefault(require("./sdk/addressEncodings")); const networks_1 = require("./sdk/networks"); const vsize_1 = require("./sdk/vsize"); const validator = (pubkey, msghash, signature) => (0, secret_1.verify)('secp256k1', pubkey, msghash, signature); class Provider extends abc_1.BaseProvider { get network() { return (0, networks_1.getNetwork)(this.chainInfo.code); } get blockbook() { return this.clientSelector((client) => client instanceof blockbook_1.BlockBook); } get versionBytesToEncodings() { if (typeof this._versionBytesToEncodings === 'undefined') { const network = this.network; const tmp = { public: { [network.bip32.public]: [addressEncodings_1.default.P2PKH] }, private: { [network.bip32.private]: [addressEncodings_1.default.P2PKH] }, }; Object.entries(network.segwitVersionBytes || {}).forEach(([encoding, { public: publicVersionBytes, private: privateVersionBytes },]) => { tmp.public[publicVersionBytes] = [ ...(tmp.public[publicVersionBytes] || []), encoding, ]; tmp.private[privateVersionBytes] = [ ...(tmp.private[privateVersionBytes] || []), encoding, ]; }); this._versionBytesToEncodings = tmp; } return this._versionBytesToEncodings; } isValidExtendedKey(xkey, category) { const decodedXkey = typeof xkey === 'string' ? bs58check_1.default.decode(xkey) : xkey; if (decodedXkey.length !== 78) { return false; } const versionBytes = parseInt(decodedXkey.slice(0, 4).toString('hex'), 16); if (category === 'pub') { return (typeof this.versionBytesToEncodings.public[versionBytes] !== 'undefined'); } return (typeof this.versionBytesToEncodings.private[versionBytes] !== 'undefined'); } isValidXpub(xpub) { return this.isValidExtendedKey(xpub, 'pub'); } isValidXprv(xprv) { return this.isValidExtendedKey(xprv, 'prv'); } xprvToXpub(xprv) { const decodedXprv = bs58check_1.default.decode(xprv); (0, precondtion_1.check)(this.isValidXprv(decodedXprv)); const privateKey = decodedXprv.slice(46, 78); const publicKey = (0, secret_1.compressPublicKey)('secp256k1', curves_1.secp256k1.publicFromPrivate(privateKey)); return bs58check_1.default.encode(Buffer.concat([decodedXprv.slice(0, 45), publicKey])); } getAccount(params, addressEncoding) { const decodedXpub = bs58check_1.default.decode(params.xpub); (0, precondtion_1.check)(this.isValidXpub(decodedXpub)); const versionBytes = parseInt(decodedXpub.slice(0, 4).toString('hex'), 16); const encoding = addressEncoding !== null && addressEncoding !== void 0 ? addressEncoding : this.versionBytesToEncodings.public[versionBytes][0]; (0, precondtion_1.check)(typeof encoding !== 'undefined'); let usedXpub = params.xpub; switch (encoding) { case addressEncodings_1.default.P2PKH: usedXpub = `pkh(${params.xpub})`; break; case addressEncodings_1.default.P2SH_P2WPKH: usedXpub = `sh(wpkh(${params.xpub}))`; break; case addressEncodings_1.default.P2WPKH: usedXpub = `wpkh(${params.xpub})`; break; default: // no-op } let requestParams = {}; switch (params.type) { case 'simple': requestParams = { details: 'basic' }; break; case 'details': requestParams = { details: 'tokenBalances', tokens: 'derived' }; break; case 'history': requestParams = { details: 'txs', pageSize: 50, to: params.to }; break; default: // no-op } return this.blockbook.then((client) => client.getAccount(usedXpub, requestParams)); } xpubToAddresses(xpub, relativePaths, addressEncoding) { // Only used to generate addresses locally. const decodedXpub = bs58check_1.default.decode(xpub); const versionBytes = parseInt(decodedXpub.slice(0, 4).toString('hex'), 16); const encoding = addressEncoding !== null && addressEncoding !== void 0 ? addressEncoding : this.versionBytesToEncodings.public[versionBytes][0]; const ret = {}; const startExtendedKey = { chainCode: decodedXpub.slice(13, 45), key: decodedXpub.slice(45, 78), }; const cache = new Map(); for (const path of relativePaths) { let extendedKey = startExtendedKey; let relPath = ''; const parts = path.split('/'); for (const part of parts) { relPath += relPath === '' ? part : `/${part}`; if (cache.has(relPath)) { extendedKey = cache.get(relPath); continue; } const index = part.endsWith("'") ? parseInt(part.slice(0, -1)) + 2 ** 31 : parseInt(part); extendedKey = (0, secret_1.CKDPub)('secp256k1', extendedKey, index); cache.set(relPath, extendedKey); } const { address } = this.pubkeyToPayment(extendedKey.key, encoding); if (typeof address === 'string' && address.length > 0) { ret[path] = address; } } return ret; } async pubkeyToAddress(verifier, encoding) { const pubkey = await verifier.getPubkey(true); console.log('pub', pubkey.toString('hex'), encoding); const payment = this.pubkeyToPayment(pubkey, encoding); const { address } = payment; (0, precondtion_1.check)(typeof address === 'string' && address); return address; } async verifyAddress(address) { let encoding = undefined; try { const decoded = BitcoinJS.address.fromBase58Check(address); if (decoded.version === this.network.pubKeyHash && decoded.hash.length === 20) { encoding = addressEncodings_1.default.P2PKH; } else if (decoded.version === this.network.scriptHash && decoded.hash.length === 20) { // Cannot distinguish between legacy P2SH and P2SH_P2WPKH encoding = addressEncodings_1.default.P2SH_P2WPKH; } } catch (e) { try { const decoded = BitcoinJS.address.fromBech32(address); if (decoded.version === 0x00 && decoded.prefix === this.network.bech32 && decoded.data.length === 20) { encoding = addressEncodings_1.default.P2WPKH; } } catch (e) { // ignored } } return encoding ? { displayAddress: address, normalizedAddress: address, encoding, isValid: true, } : { isValid: false }; } async buildUnsignedTx(unsignedTx) { const { inputs, outputs, payload: { opReturn }, } = unsignedTx; let { feeLimit, feePricePerUnit } = unsignedTx; if (inputs.length > 0 && outputs.length > 0) { const inputAddressEncodings = await this.parseAddressEncodings(inputs.map((i) => i.address)); const outputAddressEncodings = await this.parseAddressEncodings(outputs.map((i) => i.address)); if (inputAddressEncodings.length === inputs.length && outputAddressEncodings.length === outputs.length) { const vsize = (0, vsize_1.estimateVsize)(inputAddressEncodings, outputAddressEncodings, opReturn); feeLimit = feeLimit && feeLimit.gte(vsize) ? feeLimit : new bignumber_js_1.default(vsize); } } feeLimit = feeLimit || new bignumber_js_1.default(vsize_1.PLACEHOLDER_VSIZE); feePricePerUnit = feePricePerUnit || (await this.blockbook .then((client) => client.getFeePricePerUnit()) .then((fee) => fee.normal.price)); return Object.assign({}, unsignedTx, { feeLimit, feePricePerUnit, }); } async signTransaction(unsignedTx, signers) { const psdt = await this.packTransaction(unsignedTx, signers); for (let i = 0; i < unsignedTx.inputs.length; ++i) { const address = unsignedTx.inputs[i].address; const signer = signers[address]; const publicKey = await signer.getPubkey(true); await psdt.signInputAsync(i, { publicKey, sign: async (hash) => { const [sig] = await signer.sign(hash); return sig; }, }); } psdt.validateSignaturesOfAllInputs(validator); psdt.finalizeAllInputs(); const tx = psdt.extractTransaction(); return { txid: tx.getId(), rawTx: tx.toHex(), }; } async signMessage({ message }, signer, address) { (0, precondtion_1.check)(address, '"Address" required'); const validation = await this.verifyAddress(address); (0, precondtion_1.check)(validation.isValid, 'Invalid Address'); let signOptions = undefined; if (validation.encoding === addressEncodings_1.default.P2WPKH) { signOptions = { segwitType: 'p2wpkh' }; } else if (validation.encoding === addressEncodings_1.default.P2SH_P2WPKH) { signOptions = { segwitType: 'p2sh(p2wpkh)' }; } const sig = await bitcoinjs_message_1.default.signAsync(message, { sign: async (digest) => { const [signature, recovery] = await signer.sign(Buffer.from(digest)); return { signature, recovery }; }, }, true, this.network.messagePrefix, signOptions); return sig.toString('base64'); } async verifyMessage(address, { message }, signature) { const validation = await this.verifyAddress(address); (0, precondtion_1.check)(validation.isValid, 'Invalid Address'); const checkSegwitAlways = validation.encoding === addressEncodings_1.default.P2WPKH || validation.encoding === addressEncodings_1.default.P2SH_P2WPKH; return bitcoinjs_message_1.default.verify(message, address, signature, this.network.messagePrefix, checkSegwitAlways); } pubkeyToPayment(pubkey, encoding) { let payment = { pubkey: pubkey, network: this.network, }; switch (encoding) { case addressEncodings_1.default.P2PKH: payment = BitcoinJS.payments.p2pkh(payment); break; case addressEncodings_1.default.P2WPKH: payment = BitcoinJS.payments.p2wpkh(payment); break; case addressEncodings_1.default.P2SH_P2WPKH: payment = BitcoinJS.payments.p2sh({ redeem: BitcoinJS.payments.p2wpkh(payment), network: this.network, }); break; default: throw new Error(`Invalid encoding: ${encoding}`); } return payment; } parseAddressEncodings(addresses) { return Promise.all(addresses.map((address) => this.verifyAddress(address))).then((results) => results .filter((i) => i.isValid) .map((i) => i.encoding)); } async packTransaction(unsignedTx, signers) { var _a; const { inputs, outputs, payload: { opReturn }, } = unsignedTx; const [inputAddressesEncodings, nonWitnessPrevTxs] = await this.collectInfoForSoftwareSign(unsignedTx); const psbt = new BitcoinJS.Psbt({ network: this.network }); for (let i = 0; i < inputs.length; ++i) { const input = inputs[i]; const utxo = input.utxo; (0, precondtion_1.check)(utxo); const encoding = inputAddressesEncodings[i]; const mixin = {}; switch (encoding) { case addressEncodings_1.default.P2PKH: mixin.nonWitnessUtxo = Buffer.from(nonWitnessPrevTxs[utxo.txid]); break; case addressEncodings_1.default.P2WPKH: mixin.witnessUtxo = { script: (0, precondtion_1.checkIsDefined)(this.pubkeyToPayment(await signers[input.address].getPubkey(true), encoding)).output, value: utxo.value.integerValue().toNumber(), }; break; case addressEncodings_1.default.P2SH_P2WPKH: { const payment = (0, precondtion_1.checkIsDefined)(this.pubkeyToPayment(await signers[input.address].getPubkey(true), encoding)); mixin.witnessUtxo = { script: payment.output, value: utxo.value.integerValue().toNumber(), }; mixin.redeemScript = (_a = payment.redeem) === null || _a === void 0 ? void 0 : _a.output; } break; } psbt.addInput({ hash: utxo.txid, index: utxo.vout, ...mixin, }); } outputs.forEach((output) => { psbt.addOutput({ address: output.address, value: output.value.integerValue().toNumber(), }); }); if (typeof opReturn === 'string') { const embed = BitcoinJS.payments.embed({ data: [(0, vsize_1.loadOPReturn)(opReturn)], }); psbt.addOutput({ script: (0, precondtion_1.checkIsDefined)(embed.output), value: 0, }); } return psbt; } async collectInfoForSoftwareSign(unsignedTx) { const { inputs } = unsignedTx; const inputAddressesEncodings = await this.parseAddressEncodings(inputs.map((i) => i.address)); (0, precondtion_1.check)(inputAddressesEncodings.length === inputs.length, 'Found invalid address from inputs'); const nonWitnessInputPrevTxids = Array.from(new Set(inputAddressesEncodings .map((encoding, index) => { if (encoding === addressEncodings_1.default.P2PKH) { return (0, precondtion_1.checkIsDefined)(inputs[index].utxo).txid; } }) .filter((i) => !!i))); const nonWitnessPrevTxs = await this.collectTxs(nonWitnessInputPrevTxids); return [inputAddressesEncodings, nonWitnessPrevTxs]; } async collectTxs(txids) { const blockbook = await this.blockbook; const lookup = {}; for (let i = 0, batchSize = 5; i < txids.length; i += batchSize) { const batchTxids = txids.slice(i, i + batchSize); const txs = await Promise.all(batchTxids.map((txid) => blockbook.getRawTransaction(txid))); batchTxids.forEach((txid, index) => (lookup[txid] = txs[index])); } return lookup; } get hardwareCoinName() { var _a; const name = (_a = this.chainInfo.implOptions) === null || _a === void 0 ? void 0 : _a.hardwareCoinName; (0, precondtion_1.check)(typeof name === 'string' && name, `Please config hardwareCoinName for ${this.chainInfo.code}`); return name; } async hardwareGetXpubs(paths, showOnDevice) { const resp = await this.wrapHardwareCall(() => js_sdk_1.default.getPublicKey({ bundle: paths.map((path) => ({ path, coin: this.hardwareCoinName, showOnTrezor: showOnDevice, })), })); return resp.map((i) => ({ path: i.serializedPath, xpub: i.xpub, })); } async hardwareGetAddress(path, showOnDevice, addressToVerify) { const params = { path, coin: this.hardwareCoinName, showOnTrezor: showOnDevice, }; typeof addressToVerify === 'string' && Object.assign(params, { address: addressToVerify }); const { address } = await this.wrapHardwareCall(() => js_sdk_1.default.getAddress(params)); return address; } async hardwareSignTransaction(unsignedTx, signers) { const { inputs, outputs } = unsignedTx; const prevTxids = Array.from(new Set(inputs.map((i) => i.utxo.txid))); const prevTxs = await this.collectTxs(prevTxids); const { serializedTx } = await this.wrapHardwareCall(() => js_sdk_1.default.signTransaction({ useEmptyPassphrase: true, coin: this.hardwareCoinName, inputs: inputs.map((i) => buildHardwareInput(i, signers[i.address])), outputs: outputs.map((o) => buildHardwareOutput(o)), refTxs: Object.values(prevTxs).map((i) => buildPrevTx(i)), })); const tx = BitcoinJS.Transaction.fromHex(serializedTx); return { txid: tx.getId(), rawTx: serializedTx }; } async hardwareSignMessage({ message }, signer) { const { signature } = await this.wrapHardwareCall(() => js_sdk_1.default.signMessage({ path: signer, message, coin: this.hardwareCoinName, })); return signature; } async hardwareVerifyMessage(address, { message }, signature) { const { message: resp } = await this.wrapHardwareCall(() => js_sdk_1.default.verifyMessage({ address, signature, message, coin: this.hardwareCoinName, })); return resp === 'Message verified'; } } exports.Provider = Provider; const buildPrevTx = (rawTx) => { const tx = BitcoinJS.Transaction.fromHex(rawTx); return { hash: tx.getId(), version: tx.version, inputs: tx.ins.map((i) => ({ prev_hash: i.hash.reverse().toString('hex'), prev_index: i.index, script_sig: i.script.toString('hex'), sequence: i.sequence, })), bin_outputs: tx.outs.map((o) => ({ amount: o.value, script_pubkey: o.script.toString('hex'), })), lock_time: tx.locktime, }; }; const buildHardwareInput = (input, path) => { const addressN = pathUtils.getHDPath(path); const scriptType = pathUtils.getScriptType(addressN); const utxo = input.utxo; (0, precondtion_1.check)(utxo); return { prev_index: utxo.vout, prev_hash: utxo.txid, amount: utxo.value.integerValue().toString(), address_n: addressN, script_type: scriptType, }; }; const buildHardwareOutput = (output) => { const { isCharge, bip44Path } = output.payload || {}; if (isCharge && bip44Path) { const addressN = pathUtils.getHDPath(bip44Path); const scriptType = pathUtils.getScriptType(addressN); return { script_type: scriptType, address_n: addressN, amount: output.value.integerValue().toString(), }; } return { script_type: 'PAYTOADDRESS', address: output.address, amount: output.value.integerValue().toString(), }; }; //# sourceMappingURL=provider.js.map