Created
July 6, 2020 23:46
-
-
Save gabmontes/497f8ec6a06d130e19948374359ec09f to your computer and use it in GitHub Desktop.
Sweep BCH paper wallets with easy
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use strict' | |
require('dotenv').config() | |
const { connect } = require('@bloq/cloud-sdk') | |
// Create a Bloq Connect client | |
const bloq = (coin = 'btc', network = 'mainnet') => connect.http({ | |
coin, | |
network, | |
auth: { | |
clientId: process.env.BLOQ_CLIENT_ID, | |
clientSecret: process.env.BLOQ_CLIENT_SECRET, | |
}, | |
}) | |
module.exports = bloq |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use strict' | |
const bip38 = require('bip38') | |
const bitcore = require('bitcore-lib') | |
// Decrypt a password protected private key, check the address and obtain the | |
// key in WIF format. | |
function decrypt({ key, password, address }) { | |
console.log('decrypting', address) | |
const { privateKey, compressed } = bip38.decrypt(key, password) | |
const bitcorePrivateKey = compressed | |
? new bitcore.PrivateKey(privateKey.toString('hex')) | |
: bitcore.PrivateKey.fromBuffer(privateKey) | |
if (bitcorePrivateKey.toAddress().toString() !== address) { | |
throw new Error(`Could not decrypt ${address}`) | |
} | |
return { | |
address, | |
wif: bitcorePrivateKey.toWIF(), | |
} | |
} | |
decrypt.all = (keysData) => keysData.map(decrypt) | |
module.exports = decrypt |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use strict' | |
const { toCashAddress } = require('bchaddrjs') | |
const fs = require('fs') | |
const path = require('path') | |
const bitbox = require('bitbox-sdk') | |
const bloq = require('./bloq')('bch') | |
const decrypt = require('./decrypt') | |
const wallets = require('./wallets') // [{ address, key, password }] | |
// Helper to cache intermediate data to files | |
function cached(filename, fn) { | |
return function () { | |
try { | |
return Promise.resolve(require(filename)) | |
} catch (err) { | |
return Promise.resolve(fn()).then(function (res) { | |
fs.writeFileSync( | |
path.join(__dirname, filename), | |
JSON.stringify(res, null, 2) | |
) | |
return res | |
}) | |
} | |
} | |
} | |
const getUtxos = cached('./utxos.json', () => | |
bloq | |
.addressUnspentOutputs(wallets.map((k) => toCashAddress(k.address))) | |
.then((utxos) => | |
Promise.all( | |
utxos.map((utxo) => | |
bloq.rawTransaction(utxo.txid).then((tx) => Object.assign(utxo, tx)) | |
) | |
) | |
) | |
) | |
const printTotal = function (utxos) { | |
console.log( | |
'Total BCH', | |
utxos.map((u) => u.amount).reduce((s, a) => s + a) | |
) | |
} | |
const decryptKeys = cached('./decoded.json', function () { | |
const utxos = require('./utxos.json') | |
return decrypt | |
.all( | |
wallets.filter((wallet) => | |
utxos.find((utxo) => utxo.address === toCashAddress(wallet.address)) | |
) | |
) | |
.map(({ address, wif }) => ({ | |
address, | |
cashAddress: toCashAddress(address), | |
wif, | |
})) | |
}) | |
const buildTransaction = function () { | |
const utxos = require('./utxos.json') | |
.filter((u) => !u.spent && !u.skip) | |
.slice(0, 1) // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< utxos to use | |
const decoded = require('./decoded.json') | |
const tb = new bitbox.TransactionBuilder('mainnet') | |
const signatures = [] | |
utxos.forEach(function (utxo) { | |
tb.addInput(utxo.txid, utxo.vout) | |
signatures.push(utxo) | |
}) | |
const total = utxos.map((u) => u.satoshis).reduce((s, a) => s + a) | |
const fee = 1 * 225 // 1 sat/byte <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< fee | |
const outValue = total - fee | |
console.log('Out', total / 100000000, fee) | |
const xpubData = require('./xpub.json') // { xpub, index } | |
const addresses = new Array(2) // <<<<<<<<<<<<<<<<<<<< num of output addresses | |
.fill(null) | |
.map((_, i) => xpubData.index + i) | |
.map((i) => new bitbox.Address().fromXPub(xpubData.xpub, `0/${i}`)) | |
const outs = addresses.map((address) => ({ | |
address, | |
weight: Math.ceil(Math.random() * 20 + 1), | |
})) | |
const totalWeight = outs.reduce((t, o) => t + o.weight, 0) | |
outs.forEach(function ({ address, weight }) { | |
tb.addOutput(address, Math.floor((outValue * weight) / totalWeight)) | |
}) | |
signatures.forEach(function ({ address, satoshis }, i) { | |
console.log('signing', address) | |
tb.sign( | |
i, | |
new bitbox.ECPair().fromWIF( | |
decoded.find((d) => d.cashAddress === address).wif | |
), | |
undefined, | |
tb.hashTypes.SIGHASH_ALL, | |
satoshis, | |
tb.signatureAlgorithms.ECDSA | |
) | |
}) | |
const tx = tb.build() | |
const txHex = tx.toHex() | |
return Promise.all([ | |
new bitbox.RawTransactions().decodeRawTransaction(txHex), | |
txHex, | |
txHex.length, | |
]).then((o) => JSON.stringify(o, null, 2)) | |
} | |
Promise.resolve() | |
.then(getUtxos) | |
.then(printTotal) | |
.then(decryptKeys) | |
.then(buildTransaction) | |
.then(console.log) | |
.catch(console.error) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment