@ducatus/ducatus-wallet-client-rev
Version:
Client for @ducatus/ducatus-wallet-service-rev
214 lines • 8.44 kB
JavaScript
var crypto = require('crypto');
var bs58 = require('bs58');
var kbpgp = require('kbpgp');
var request = require('request-promise');
var bitpayPgpKeys = {};
var githubPgpKeys = {};
var importedPgpKeys = {};
var signatureCount = 0;
var eccPayload;
var parsedEccPayload;
var eccKeysHash;
var keyRequests = [];
keyRequests.push((function () {
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(function (pgpKeyFiles) {
var fileDataPromises = [];
pgpKeyFiles.forEach(function (file) {
fileDataPromises.push((function () {
return request({
method: 'GET',
url: file.download_url,
headers: {
'user-agent': 'BitPay Key-Check Utility'
}
}).then(function (body) {
var hash = crypto
.createHash('sha256')
.update(body)
.digest('hex');
githubPgpKeys[hash] = body;
return Promise.resolve();
});
})());
});
return Promise.all(fileDataPromises);
});
})());
keyRequests.push((function () {
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(function (body) {
body.pgpKeys.forEach(function (key) {
var hash = crypto
.createHash('sha256')
.update(key.publicKey)
.digest('hex');
bitpayPgpKeys[hash] = key.publicKey;
});
return Promise.resolve();
});
})());
Promise.all(keyRequests)
.then(function () {
if (Object.keys(githubPgpKeys).length !== Object.keys(bitpayPgpKeys).length) {
console.log('Warning: Different number of keys returned by key lists');
}
var bitpayOnlyKeys = Object.keys(bitpayPgpKeys).filter(function (keyHash) {
return !githubPgpKeys[keyHash];
});
var githubOnlyKeys = Object.keys(githubPgpKeys).filter(function (keyHash) {
return !bitpayPgpKeys[keyHash];
});
if (bitpayOnlyKeys.length) {
console.log('BitPay returned some keys which are not present in github');
Object.keys(bitpayOnlyKeys).forEach(function (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(function (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(function () {
console.log('Importing PGP keys for later use...');
return Promise.all(Object.values(bitpayPgpKeys).map(function (pgpKeyString) {
return new Promise(function (resolve, reject) {
kbpgp.KeyManager.import_from_armored_pgp({ armored: pgpKeyString }, function (err, km) {
if (err) {
return reject(err);
}
importedPgpKeys[km.pgp
.key(km.pgp.primary)
.get_fingerprint()
.toString('hex')] = km;
return resolve();
});
});
}));
})
.then(function () {
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(function (rawEccPayload) {
if (rawEccPayload.indexOf('rate limit') !== -1) {
return Promise.reject('Rate limited by BitPay');
}
eccPayload = rawEccPayload;
parsedEccPayload = JSON.parse(rawEccPayload);
eccKeysHash = crypto
.createHash('sha256')
.update(rawEccPayload)
.digest('hex');
return Promise.resolve();
});
})
.then(function () {
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(function (signatureData) {
console.log('Verifying each signature is valid and comes from the set of PGP keys retrieved earlier');
Promise.all(signatureData.signatures.map(function (signature) {
return new Promise(function (resolve, reject) {
var pgpKey = importedPgpKeys[signature.identifier];
if (!pgpKey) {
return reject("PGP key " + signature.identifier + " missing for signature");
}
var armoredSignature = Buffer.from(signature.signature, 'hex').toString();
kbpgp.unbox({ armored: armoredSignature, data: Buffer.from(eccPayload), keyfetch: pgpKey }, function (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(function () {
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);
var keyMap_1 = {};
console.log('----\nValid keymap for use in bitcoinRpc example:');
parsedEccPayload.publicKeys.forEach(function (pubkey) {
var a = crypto
.createHash('sha256')
.update(pubkey, 'hex')
.digest();
var b = crypto
.createHash('rmd160')
.update(a)
.digest('hex');
var c = '00' + b;
var d = crypto
.createHash('sha256')
.update(c, 'hex')
.digest();
var e = crypto
.createHash('sha256')
.update(d)
.digest('hex');
var pubKeyHash = bs58.encode(Buffer.from(c + e.substr(0, 8), 'hex'));
keyMap_1[pubKeyHash] = {
owner: parsedEccPayload.owner,
networks: ['main'],
domains: parsedEccPayload.domains,
publicKey: pubkey
};
keyMap_1['mh65MN7drqmwpCRZcEeBEE9ceQCQ95HtZc'] = {
owner: 'BitPay (TESTNET ONLY - DO NOT TRUST FOR ACTUAL BITCOIN)',
networks: ['test'],
domains: ['test.bitpay.com'],
publicKey: '03159069584176096f1c89763488b94dbc8d5e1fa7bf91f50b42f4befe4e45295a'
};
});
console.log(keyMap_1);
var fs = require('fs');
fs.writeFileSync('JsonPaymentProtocolKeys.js', 'module.exports = ' + JSON.stringify(keyMap_1, null, 2));
}
else {
return Promise.reject("Insufficient good signatures " + signatureCount + " for a proper validity check");
}
})
.catch(function (err) {
console.log("Error encountered " + err);
});
process.on('unhandledRejection', console.log);
//# sourceMappingURL=fetchBitpayKeysAndVerify.js.map