@pgchain/blockchain-libs
Version:
PGWallet Blockchain Libs
498 lines • 21.7 kB
JavaScript
;
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