2

In an effort to recover some old dust, I've been working on a sweep script to be used on a bunch of old private keys with a little BTC locked inside multiple p2pk utxos.

First I tried using python's bitcoinlib, but after creating and signing the hex, it was rejected on broadcast with {"code":-26,"message":"mandatory-script-verify-flag-failed (Signature is found in scriptCode)"}

Further investigation concluded that in bitcoinlib "... the ‘pubkey’ lockscript and ‘signature’ unlocking script are ancient and not supported by BitcoinLib at the moment."

Next I tried bitgo-utxo-lib, and again it created and signed hex but was rejected on broadcast with the same error.

Can anyone please point me to a library or method by which I can correctly create and sign a raw transaction which will broadcast without an error? Examples in any common modern programming language appreciated.

Antoine Poinsot
  • 8,334
  • 2
  • 17
  • 34
smk
  • 29
  • 5

2 Answers2

4

You can achieve this without any script, simply using the Bitcoin Core wallet. For each public key you received funds on you can import a pk() descriptor containing the corresponding private key, WIF-encoded. Then you can rescan the block chain for the wallet to get knowledge of the outputs paying to you. Finally you can sweep those funds as you would normally spend from a Bitcoin Core wallet.


Let me guide you through an example to achieve this on regtest. Note the WIF encoding differs on test networks, but the process is the same on mainnet. I'll be using the private key cSWVo8YMehg6STxqw6xGgmMJE6Ac6RCJ7owcrBZRAT7QANtyAAEj, corresponding to public key 023ad6336ae257527ff59ec4356fa5d2ac33fff5e04be9e5958dfed9b0100b7948.

First, create a descriptor for the private key and get its checksum using getdescriptorinfo:

$ bitcoin-cli -regtest getdescriptorinfo "pk(cSWVo8YMehg6STxqw6xGgmMJE6Ac6RCJ7owcrBZRAT7QANtyAAEj)"                                                                                                                                                                                                               
{                                                                                                                                                                                  
  "descriptor": "pk(023ad6336ae257527ff59ec4356fa5d2ac33fff5e04be9e5958dfed9b0100b7948)#vl2598s2",                                                                                 
  "checksum": "0hjwa0sj",                                                                                                                                                          
  "isrange": false,                                                                       
  "issolvable": true,                                                                                                                                                              
  "hasprivatekeys": true                                                                                                                                                           
}

Then create a wallet and import this descriptor:

$ bitcoin-cli -regtest createwallet p2pk_recovery false true                                                                                                                                                                                                                                                                               
{                                                                                                                                                                                                                                                                                                                                                                          
  "name": "p2pk_recovery",                                                                                                                                                                                                                                                                                                                                                 
  "warning": ""                                                                                                                                                                                                                                                                                                                                                            
}
$ bitcoin-cli -regtest -rpcwallet=p2pk_recovery importdescriptors '[{"desc":"pk(cSWVo8YMehg6STxqw6xGgmMJE6Ac6RCJ7owcrBZRAT7QANtyAAEj)#0hjwa0sj","timestamp":"now"}]'                                                                                                                                                                       
[                                                                                                                                                                                                                                                                                                                                                                          
  {                                                                                                                                                                                                                                                                                                                                                                        
    "success": true                                                                                                                                                                                                                                                                                                                                                        
  }                                                                                                                                                                                                                                                                                                                                                                        
]
$ bitcoin-cli -regtest -rpcwallet=p2pk_recovery listdescriptors                                                                                                                                                                                                                                                                            
{                                                                                                                                                                                                                                                                                                                                                                          
  "wallet_name": "p2pk_recovery",                                                                                                                                                                                                                                                                                                                                          
  "descriptors": [                                                                                                                                                                                                                                                                                                                                                         
    {                                                                                                                                                                                                                                                                                                                                                                      
      "desc": "pk(023ad6336ae257527ff59ec4356fa5d2ac33fff5e04be9e5958dfed9b0100b7948)#vl2598s2",                                                                                                                                                                                                                                                                           
      "timestamp": 1681809105,                                                                                                                                                                                                                                                                                                                                             
      "active": false                                                                                                                                                                                                                                                                                                                                                      
    }                                                                                                                                                                                                                                                                                                                                                                      
  ]                                                                                                                                                                                                                                                                                                                                                                        
}

Then rescan the chain. I didn't have to do this on my end, but you can use rescanblockchain. If you already know the height of the outputs you are looking for i strongly advise you specify a range to speed up the rescan. For instance if you know you've only got outputs between blocks 100 and 150:

$ bitcoin-cli -regtest -rpcwallet=p2pk_recovery rescanblockchain 100 150                                                                                                                                                                                                                                                                   
{
  "start_height": 100,
  "stop_height": 150
}

By now you should have at least one utxo for at least one of the imported descriptors in your wallet:

$ bitcoin-cli -regtest -rpcwallet=p2pk_recovery listunspent                                                                                                                                                                                                                                                                                
[                                                                                                                                                                                                                                                                                                                                                                          
  {                                                                                                                                                                                                                                                                                                                                                                        
    "txid": "29c3b60341cff26bdce49c3611259ac50cddde4225307c6a130f12abfdb7e22a",                                                                                                                                                                                                                                                                                            
    "vout": 0,                                                                                                                                                                                                                                                                                                                                                             
    "address": "myzWnxTFbp4La7uSAK4ppyg45bTw96hQ2S",                                                                                                                                                                                                                                                                                                                       
    "label": "",                                                                                                                                                                                                                                                                                                                                                           
    "scriptPubKey": "21023ad6336ae257527ff59ec4356fa5d2ac33fff5e04be9e5958dfed9b0100b7948ac",                                                                                                                                                                                                                                                                              
    "amount": 0.00051000,                                                                                                                                                                                                                                                                                                                                                  
    "confirmations": 1,                                                                                                                                                                                                                                                                                                                                                    
    "spendable": true,                                                                                                                                                                                                                                                                                                                                                     
    "solvable": true,                                                                                                                                                                                                                                                                                                                                                      
    "desc": "pk([caa89f91]023ad6336ae257527ff59ec4356fa5d2ac33fff5e04be9e5958dfed9b0100b7948)#kmv3ht9h",                                                                                                                                                                                                                                                                   
    "parent_descs": [                                                                                                                                                                                                                                                                                                                                                      
      "pk(023ad6336ae257527ff59ec4356fa5d2ac33fff5e04be9e5958dfed9b0100b7948)#vl2598s2"                                                                                                                                                                                                                                                                                    
    ],                                                                                                                                                                                                                                                                                                                                                                     
    "safe": true                                                                                                                                                                                                                                                                                                                                                           
  }                                                                                                                                                                                                                                                                                                                                                                        
]

You can then trivially spend the coins from your wallet. For instance:

$ bitcoin-cli -named -regtest -rpcwallet=p2pk_recovery sendtoaddress address=bcrt1pne0s48y6akyw2zd4uzua033u9dxeywxu7wd6fzu3l2dcf7sc3qhsd87l2x amount=0.0005 fee_rate=1
de3c9371404c07493e8c5f43149b91c5c1ed556ebc93ace47617d96026d4902d

If you are using Bitcoin Core version 24 or higher you can also use the sendall command that allows for an easier sweep of all confirmed coins in the wallet since you don't have to set the amount. For instance (with the same value as above):

bitcoin-cli -named -regtest -rpcwallet=p2pk_recovery sendall recipients='["bcrt1pne0s48y6akyw2zd4uzua033u9dxeywxu7wd6fzu3l2dcf7sc3qhsd87l2x"]' fee_rate=1

Note also that i'm using a 1sat/vb feerate on regtest, but on mainnet you want to use an accurate fee estimation instead of this arbitrary value.

Antoine Poinsot
  • 8,334
  • 2
  • 17
  • 34
  • Excellent answer. If you’re on a recent version of Bitcoin Core, you could also use the sendall RPC to sweep everything into a new address. You should perhaps explicitly comment on the feerate, as currently the mempool is kinda bonkers. – Murch May 03 '23 at 15:31
  • Thanks. Yeah i started trying sendall and for some reason figured i'd just stick to the familiar sendtoaddress. I've added some comments on using sendall and on the feerate. Feel free to amend if you think it could be clearer. – Antoine Poinsot May 03 '23 at 15:51
0

Thanks for the info! I was not aware of these RPC methods. I had a little trouble implementing tho.

$ ./bitcoin-cli getdescriptorinfo "pk($privkey)"

 {
   "descriptor": "pk($PUBKEY)#8m2***n6",
   "checksum": "tpd***ym",
   "isrange": false,
   "issolvable": true,
   "hasprivatekeys": true
 }

$ ./bitcoin-cli createwallet p2pk_recovery_test false true

 {
   "name": "p2pk_recovery_test",
   "warning": ""
 }

$ ./bitcoin-cli -rpcwallet=p2pk_recovery_test importdescriptors '[{"desc":"pk($PUBKEY)#8m2***n6","timestamp":"now"}]'

  error code: -4
  error message:
  importdescriptors is not available for non-descriptor wallets

In the end between a friend and I, we got a working script using an mix of bitcoinjs-lib, bitgo-utxo-lib, ecpair and tiny-secp256k1

Link to script at https://github.com/gcharang/nn-return-btc and posted below for reference

const bitcoinjs = require('bitcoinjs-lib');
const bitcoin = require("bitgo-utxo-lib");
const axios = require('axios');
const ecpair = require('ecpair');
const secp256k1 = require('tiny-secp256k1');

// Set your wif key const wifKey = ""; // Set the destination address const DESTINATION_ADDRESS = ""; const ECPair = ecpair.ECPairFactory(secp256k1); const keyPair = ECPair.fromWIF(wifKey);

// Use bitgo-utxo-lib to get address let BTC_ADDRESS = bitcoin.ECPair.fromWIF(wifKey).getAddress();

const processUtxos = async utxos => { let inputs = []; let hex_dict = {}; for (const utxo of utxos) { // Get raw hex of tx from api, and store in dict to avoid multiple calls for same tx if (hex_dict[utxo.tx_hash_big_endian] == undefined) { console.log("Gettng raw hex for tx: " + utxo.tx_hash_big_endian) hex_dict[utxo.tx_hash_big_endian] = await axios.get(https://blockchain.info/rawtx/${utxo.tx_hash_big_endian}?format=hex); } // Add input to array inputs.push({ txId: utxo.tx_hash_big_endian, vout: utxo.tx_output_n, satoshis: utxo.value, rawhex: hex_dict[utxo.tx_hash_big_endian].data }); if (inputs.length == 250) { break; }
}

// Create psbt object (https://thebitcoinmanual.com/articles/what-is-psbt/)
const psbt = new bitcoinjs.Psbt();
psbt.setVersion(2);
psbt.setLocktime(0);

// Add inputs to psbt object
inputs.forEach((input) => {
    console.log("Adding input: " + input.txId + " vout " + input.vout);
    psbt.addInput({
        hash: input.txId,
        index: input.vout,
        sequence: 0xffffffff,
        // non-segwit inputs now require passing the raw hex of the previous tx as Buffer
        nonWitnessUtxo: Buffer.from(
            input.rawhex,
            'hex',
        )
    })
});

let fee = 1200*(inputs.length); // in satoshis. Tweak this according to current fee rate via https://buybitcoinworldwide.com/fee-calculator/ for <= 48 blocks
const totalValue = inputs.reduce((sum, input) => sum + input.satoshis, 0);
// Add output to psbt object
psbt.addOutput({
    address: DESTINATION_ADDRESS,
    value: totalValue - fee,
});
// Sign inputs
inputs.forEach((input, index) => {
    console.log("signing input "+ index + " of " + inputs.length + " inputs");
    psbt.signInput(index, keyPair);
});
// Finalize inputs
psbt.finalizeAllInputs();
// Print signed transaction hex
console.log(psbt.extractTransaction().toHex())
console.log("Copy the above hex and decode it via https://live.blockcypher.com/btc/decodetx/");
console.log("If it decodes and is correct, broadcast it via https://blockstream.info/tx/push");
if (inputs.length == 250) {
    console.log("There is a limit of 250 inputs per transaction. Please run this script again to sweep the remaining inputs.");
}

}

async function consolidateUtxos() { try { // Get UTXOs from address const utxosResponse = await axios.get(https://blockchain.info/unspent?active=${BTC_ADDRESS}); let utxos = utxosResponse.data["unspent_outputs"]; utxos = utxos.filter(utxo => utxo.confirmations > 6) //spend only confirmed utxos if (utxos.length === 0) { console.log(No UTXOs found for address ${BTC_ADDRESS}); return; } await processUtxos(utxos) } catch (error) { console.error('Error consolidating UTXOs:', error.message); } }

consolidateUtxos();

smk
  • 29
  • 5