UNPKG

bitcore-wallet-client

Version:
202 lines 7.99 kB
const ncrypto = require('crypto'); const bs58 = require('bs58'); const kbpgp = require('kbpgp'); const request = require('request-promise'); let bitpayPgpKeys = {}; let githubPgpKeys = {}; let importedPgpKeys = {}; let signatureCount = 0; let eccPayload; let parsedEccPayload; let eccKeysHash; let keyRequests = []; keyRequests.push((() => { console.log('Fetching keys from github.com/bitpay/pgp-keys...'); return request({ method: 'GET', url: 'https://api.github.com/repos/bitpay/pgp-keys/contents/keys', headers: { 'user-agent': 'BitPay Key-Check Utility' }, json: true }).then(pgpKeyFiles => { let fileDataPromises = []; pgpKeyFiles.forEach(file => { fileDataPromises.push((() => { return request({ method: 'GET', url: file.download_url, headers: { 'user-agent': 'BitPay Key-Check Utility' } }).then(body => { let hash = ncrypto.createHash('sha256').update(body).digest('hex'); githubPgpKeys[hash] = body; return Promise.resolve(); }); })()); }); return Promise.all(fileDataPromises); }); })()); keyRequests.push((() => { console.log('Fetching keys from bitpay.com/pgp-keys...'); return request({ method: 'GET', url: 'https://bitpay.com/pgp-keys.json', headers: { 'user-agent': 'BitPay Key-Check Utility' }, json: true }).then(body => { body.pgpKeys.forEach(function (key) { let hash = ncrypto .createHash('sha256') .update(key.publicKey) .digest('hex'); bitpayPgpKeys[hash] = key.publicKey; }); return Promise.resolve(); }); })()); Promise.all(keyRequests) .then(() => { if (Object.keys(githubPgpKeys).length !== Object.keys(bitpayPgpKeys).length) { console.log('Warning: Different number of keys returned by key lists'); } let bitpayOnlyKeys = Object.keys(bitpayPgpKeys).filter(keyHash => { return !githubPgpKeys[keyHash]; }); let githubOnlyKeys = Object.keys(githubPgpKeys).filter(keyHash => { return !bitpayPgpKeys[keyHash]; }); if (bitpayOnlyKeys.length) { console.log('BitPay returned some keys which are not present in github'); Object.keys(bitpayOnlyKeys).forEach(keyHash => { console.log(`Hash ${keyHash} Key: ${bitpayOnlyKeys[keyHash]}`); }); } if (githubOnlyKeys.length) { console.log('GitHub returned some keys which are not present in BitPay'); Object.keys(githubOnlyKeys).forEach(keyHash => { console.log(`Hash ${keyHash} Key: ${githubOnlyKeys[keyHash]}`); }); } if (!githubOnlyKeys.length && !bitpayOnlyKeys.length) { console.log(`Both sites returned ${Object.keys(githubPgpKeys).length} keys. Key lists from both are identical.`); return Promise.resolve(); } else { return Promise.reject('Aborting signature checks due to key mismatch'); } }) .then(() => { console.log('Importing PGP keys for later use...'); return Promise.all(Object.values(bitpayPgpKeys).map(pgpKeyString => { return new Promise((resolve, reject) => { kbpgp.KeyManager.import_from_armored_pgp({ armored: pgpKeyString }, (err, km) => { if (err) { return reject(err); } importedPgpKeys[km.pgp.key(km.pgp.primary).get_fingerprint().toString('hex')] = km; return resolve(); }); }); })); }) .then(() => { console.log('Fetching current ECC keys from bitpay.com/signingKeys/paymentProtocol.json'); return request({ method: 'GET', url: 'https://bitpay.com/signingKeys/paymentProtocol.json', headers: { 'user-agent': 'BitPay Key-Check Utility' } }).then(rawEccPayload => { if (rawEccPayload.indexOf('rate limit') !== -1) { return Promise.reject('Rate limited by BitPay'); } eccPayload = rawEccPayload; parsedEccPayload = JSON.parse(rawEccPayload); eccKeysHash = ncrypto .createHash('sha256') .update(rawEccPayload) .digest('hex'); return Promise.resolve(); }); }) .then(() => { console.log(`Fetching signatures for ECC payload with hash ${eccKeysHash}`); return request({ method: 'GET', url: `https://bitpay.com/signatures/${eccKeysHash}.json`, headers: { 'user-agent': 'BitPay Key-Check Utility' }, json: true }).then(signatureData => { console.log('Verifying each signature is valid and comes from the set of PGP keys retrieved earlier'); Promise.all(signatureData.signatures.map(signature => { return new Promise((resolve, reject) => { let pgpKey = importedPgpKeys[signature.identifier]; if (!pgpKey) { return reject(`PGP key ${signature.identifier} missing for signature`); } let armoredSignature = Buffer.from(signature.signature, 'hex').toString(); kbpgp.unbox({ armored: armoredSignature, data: Buffer.from(eccPayload), keyfetch: pgpKey }, (err, result) => { if (err) { return reject(`Unable to verify signature from ${signature.identifier} ${err}`); } signatureCount++; console.log(`Good signature from ${signature.identifier} (${pgpKey .get_userids()[0] .get_username()})`); return Promise.resolve(); }); }); })); }); }) .then(() => { if (signatureCount >= Object.keys(bitpayPgpKeys).length / 2) { console.log(`----\nThe following ECC key set has been verified against signatures from ${signatureCount} of the ${Object.keys(bitpayPgpKeys).length} published BitPay PGP keys.`); console.log(eccPayload); let keyMap = {}; console.log('----\nValid keymap for use in bitcoinRpc example:'); parsedEccPayload.publicKeys.forEach(pubkey => { let a = ncrypto.createHash('sha256').update(pubkey, 'hex').digest(); let b = ncrypto.createHash('rmd160').update(a).digest('hex'); let c = '00' + b; let d = ncrypto.createHash('sha256').update(c, 'hex').digest(); let e = ncrypto.createHash('sha256').update(d).digest('hex'); let pubKeyHash = bs58.encode(Buffer.from(c + e.substr(0, 8), 'hex')); keyMap[pubKeyHash] = { owner: parsedEccPayload.owner, networks: ['main'], domains: parsedEccPayload.domains, publicKey: pubkey }; keyMap['mh65MN7drqmwpCRZcEeBEE9ceQCQ95HtZc'] = { owner: 'BitPay (TESTNET ONLY - DO NOT TRUST FOR ACTUAL BITCOIN)', networks: ['test'], domains: ['test.bitpay.com'], publicKey: '03159069584176096f1c89763488b94dbc8d5e1fa7bf91f50b42f4befe4e45295a' }; }); console.log(keyMap); const fs = require('fs'); fs.writeFileSync('JsonPaymentProtocolKeys.js', 'module.exports = ' + JSON.stringify(keyMap, null, 2)); } else { return Promise.reject(`Insufficient good signatures ${signatureCount} for a proper validity check`); } }) .catch(err => { console.log(`Error encountered ${err}`); }); process.on('unhandledRejection', console.log); //# sourceMappingURL=fetchBitpayKeysAndVerify.js.map