UNPKG

bitcore-wallet-client

Version:
276 lines (252 loc) 8.55 kB
const ncrypto = require('crypto'); // renamed in order to prevent redeclaration of block-scoped variable 'crypto' from typescript dom library 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<void>((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); // if (new Date(parsedEccPayload.expirationDate) < Date.now()) { // return console.log('The currently published ECC keys are expired'); // } 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 => { // Here we are just generating the pubkey hash (btc address) of each of the public keys received for easy lookup later // as this is what will be provided by the x-identity header let a = ncrypto.createHash('sha256').update(pubkey, 'hex').digest(); let b = ncrypto.createHash('rmd160').update(a).digest('hex'); let c = '00' + b; // This is assuming livenet 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 }; // Add Bitpay's testnet 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);