Skip to content

Instantly share code, notes, and snippets.

@gabmontes
Created July 6, 2020 23:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gabmontes/497f8ec6a06d130e19948374359ec09f to your computer and use it in GitHub Desktop.
Save gabmontes/497f8ec6a06d130e19948374359ec09f to your computer and use it in GitHub Desktop.
Sweep BCH paper wallets with easy
'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
'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
'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