From ca62816b2882b21eff0a88936541f548b4398462 Mon Sep 17 00:00:00 2001 From: joeuhren Date: Sat, 13 Feb 2021 22:48:50 -0800 Subject: [PATCH 1/3] Add P2PK transaction support --- app.js | 5 ++- lib/explorer.js | 116 +++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 99 insertions(+), 22 deletions(-) diff --git a/app.js b/app.js index f4ddf7e9a..4586ae2fc 100644 --- a/app.js +++ b/app.js @@ -19,7 +19,8 @@ var app = express(); bitcoinapi.setWalletDetails(settings.wallet); if (settings.heavy != true) { bitcoinapi.setAccess('only', ['getinfo', 'getnetworkhashps', 'getmininginfo', 'getdifficulty', 'getconnectioncount', - 'getblockcount', 'getblockhash', 'getblock', 'getrawtransaction', 'getpeerinfo', 'gettxoutsetinfo', 'verifymessage']); + 'getblockcount', 'getblockhash', 'getblock', 'getrawtransaction', 'getpeerinfo', 'gettxoutsetinfo', 'verifymessage', + 'getdescriptorinfo', 'deriveaddresses']); } else { // enable additional heavy api calls /* @@ -36,7 +37,7 @@ if (settings.heavy != true) { bitcoinapi.setAccess('only', ['getinfo', 'getstakinginfo', 'getnetworkhashps', 'getdifficulty', 'getconnectioncount', 'getblockcount', 'getblockhash', 'getblock', 'getrawtransaction', 'getmaxmoney', 'getvote', 'getmaxvote', 'getphase', 'getreward', 'getnextrewardestimate', 'getnextrewardwhenstr', - 'getnextrewardwhensec', 'getsupply', 'gettxoutsetinfo', 'verifymessage']); + 'getnextrewardwhensec', 'getsupply', 'gettxoutsetinfo', 'verifymessage', 'getdescriptorinfo', 'deriveaddresses']); } // view engine setup app.set('views', path.join(__dirname, 'views')); diff --git a/lib/explorer.js b/lib/explorer.js index 2a6739b69..43fdf1fba 100644 --- a/lib/explorer.js +++ b/lib/explorer.js @@ -31,6 +31,60 @@ function rpcCommand(params, cb) { }); } +function processVoutAddresses(address_list, vout_value, arr_vout, cb) { + // check if there are any addresses to process + if (address_list != null && address_list.length > 0) { + // check if vout address is unique, if so add to array, if not add its amount to existing index + module.exports.is_unique(arr_vout, address_list[0], function(unique, index) { + if (unique == true) { + // unique vout + module.exports.convert_to_satoshi(parseFloat(vout_value), function(amount_sat){ + arr_vout.push({addresses: address_list[0], amount: amount_sat}); + return cb(arr_vout); + }); + } else { + // already exists + module.exports.convert_to_satoshi(parseFloat(vout_value), function(amount_sat){ + arr_vout[index].amount = arr_vout[index].amount + amount_sat; + return cb(arr_vout); + }); + } + }); + } else { + // no address, move to next vout + return cb(arr_vout); + } +} + +function decodeP2PKaddress(encoded_address, cb) { + // first find the descriptor value + var uri = base_url + 'getdescriptorinfo?descriptor=' + encodeURIComponent('pkh(' + encoded_address.replace(' OP_CHECKSIG', '') + ')'); + request({uri: uri, json: true}, function (error, response, body) { + // check if there was an error + if (error == null) { + // decode the address using the output descriptor + uri = base_url + 'deriveaddresses?descriptor=' + encodeURIComponent(body.descriptor); + request({uri: uri, json: true}, function (error, response, body) { + // check if there was an error + if (error == null) { + // return decoded address + return cb(body); + } else { + // an error occurred + console.log('deriveaddresses error: ' + error); + // return null + return cb(null); + } + }); + } else { + // an error occurred + console.log('getdescriptorinfo error: ' + error); + // return null + return cb(null); + } + }); +} + module.exports = { convert_to_satoshi: function(amount, cb) { @@ -497,24 +551,36 @@ module.exports = { module.exports.syncLoop(vout.length, function (loop) { var i = loop.iteration(); // make sure vout has an address - if (vout[i].scriptPubKey.type != 'nonstandard' && vout[i].scriptPubKey.type != 'nulldata') { - // check if vout address is unique, if so add it array, if not add its amount to existing index - //console.log('vout:' + i + ':' + txid); - module.exports.is_unique(arr_vout, vout[i].scriptPubKey.addresses[0], function(unique, index) { - if (unique == true) { - // unique vout - module.exports.convert_to_satoshi(parseFloat(vout[i].value), function(amount_sat){ - arr_vout.push({addresses: vout[i].scriptPubKey.addresses[0], amount: amount_sat}); - loop.next(); - }); - } else { - // already exists - module.exports.convert_to_satoshi(parseFloat(vout[i].value), function(amount_sat){ - arr_vout[index].amount = arr_vout[index].amount + amount_sat; + if (vout[i].scriptPubKey.type != 'nonstandard' && vout[i].scriptPubKey.type != 'nulldata') { + var address_list = vout[i].scriptPubKey.addresses; + // check if there are one or more addresses in the vout + if (address_list == null || address_list.length == 0) { + // no addresses defined + // try to decode the asm value as P2PK (Pay To Pubkey) + decodeP2PKaddress(vout[i].scriptPubKey.asm, function(decoded_address) { + // check if the address was decoded properly + if (decoded_address != null) { + // process vout addresses + processVoutAddresses(decoded_address, vout[i].value, arr_vout, function(vout_array) { + // save updated array + arr_vout = vout_array; + // move to next vout + loop.next(); + }); + } else { + // could not decode address, move to next vout loop.next(); - }); - } - }); + } + }); + } else { + // process vout addresses + processVoutAddresses(address_list, vout[i].value, arr_vout, function(vout_array) { + // save updated array + arr_vout = vout_array; + // move to next vout + loop.next(); + }); + } } else { // no address, move to next vout loop.next(); @@ -557,13 +623,23 @@ module.exports = { module.exports.syncLoop(tx.vout.length, function (loop) { var i = loop.iteration(); if (tx.vout[i].n == input.vout) { - //module.exports.convert_to_satoshi(parseFloat(tx.vout[i].value), function(amount_sat){ if (tx.vout[i].scriptPubKey.addresses) { addresses.push({hash: tx.vout[i].scriptPubKey.addresses[0], amount:tx.vout[i].value}); - } loop.break(true); loop.next(); - //}); + } else { + // no addresses defined + // try to decode the asm value as P2PK (Pay To Pubkey) + decodeP2PKaddress(tx.vout[i].scriptPubKey.asm, function(decoded_address) { + // check if the address was decoded properly + if (decoded_address != null) { + // save the decoded address + addresses.push({hash: decoded_address, amount: tx.vout[i].value}); + } + loop.break(true); + loop.next(); + }); + } } else { loop.next(); } From 710471f13dad0476115e953bfbaf820b98324914 Mon Sep 17 00:00:00 2001 From: joeuhren Date: Fri, 5 Mar 2021 13:47:43 -0800 Subject: [PATCH 2/3] P2PK transaction improvements --- lib/explorer.js | 127 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 95 insertions(+), 32 deletions(-) diff --git a/lib/explorer.js b/lib/explorer.js index 43fdf1fba..4450f7618 100644 --- a/lib/explorer.js +++ b/lib/explorer.js @@ -56,30 +56,24 @@ function processVoutAddresses(address_list, vout_value, arr_vout, cb) { } } -function decodeP2PKaddress(encoded_address, cb) { - // first find the descriptor value - var uri = base_url + 'getdescriptorinfo?descriptor=' + encodeURIComponent('pkh(' + encoded_address.replace(' OP_CHECKSIG', '') + ')'); - request({uri: uri, json: true}, function (error, response, body) { - // check if there was an error - if (error == null) { - // decode the address using the output descriptor - uri = base_url + 'deriveaddresses?descriptor=' + encodeURIComponent(body.descriptor); - request({uri: uri, json: true}, function (error, response, body) { - // check if there was an error - if (error == null) { - // return decoded address - return cb(body); +function encodeP2PKaddress(p2pk_descriptor, cb) { + // find the descriptor value + module.exports.get_descriptorinfo(p2pk_descriptor, function(descriptor_info) { + // check for errors + if (descriptor_info != null) { + // encode the address using the output descriptor + module.exports.get_deriveaddresses(descriptor_info.descriptor, function(p2pkh_address) { + // check for errors + if (p2pkh_address != null) { + // return P2PKH address + return cb(p2pkh_address); } else { - // an error occurred - console.log('deriveaddresses error: ' + error); - // return null + // address could not be encoded return cb(null); } }); } else { - // an error occurred - console.log('getdescriptorinfo error: ' + error); - // return null + // address could not be encoded return cb(null); } }); @@ -388,7 +382,75 @@ module.exports = { }); } }, - + + get_descriptorinfo: function(descriptor, cb) { + var cmd_name = 'getdescriptorinfo'; + // format the descriptor correctly for use in the getdescriptorinfo cmd + descriptor = 'pkh(' + descriptor.replace(' OP_CHECKSIG', '') + ')'; + + if (settings.use_rpc) { + rpcCommand([{method:cmd_name, parameters: [descriptor]}], function(response){ + // check if there was an error + if (response != null && response != 'There was an error. Check your console.') { + // return the rpc response + return cb(response); + } else { + // an error occurred + console.log(cmd_name + ' error: ' + (response == null ? 'Method not found' : response)); + // return null + return cb(null); + } + }); + } else { + var uri = base_url + cmd_name + '?descriptor=' + encodeURIComponent(descriptor); + request({uri: uri, json: true}, function (error, response, body) { + // check if there was an error + if (error == null && (body.message == null || body.message != 'Method not found')) { + // return the request body + return cb(body); + } else { + // an error occurred + console.log(cmd_name + ' error: ' + (error == null ? body.message : error)); + // return null + return cb(null); + } + }); + } + }, + + get_deriveaddresses: function(descriptor, cb) { + var cmd_name = 'deriveaddresses'; + + if (settings.use_rpc) { + rpcCommand([{method:cmd_name, parameters: [descriptor]}], function(response){ + // check if there was an error + if (response != null && response != 'There was an error. Check your console.') { + // return the rpc response + return cb(response); + } else { + // an error occurred + console.log(cmd_name + ' error: ' + (response == null ? 'Method not found' : response)); + // return null + return cb(null); + } + }); + } else { + var uri = base_url + cmd_name + '?descriptor=' + encodeURIComponent(descriptor); + request({uri: uri, json: true}, function (error, response, body) { + // check if there was an error + if (error == null && (body.message == null || body.message != 'Method not found')) { + // return the request body + return cb(body); + } else { + // an error occurred + console.log(cmd_name + ' error: ' + (error == null ? body.message : error)); + // return null + return cb(null); + } + }); + } + }, + // synchonous loop used to interate through an array, // avoid use unless absolutely neccessary syncLoop: function(iterations, process, exit){ @@ -556,19 +618,20 @@ module.exports = { // check if there are one or more addresses in the vout if (address_list == null || address_list.length == 0) { // no addresses defined - // try to decode the asm value as P2PK (Pay To Pubkey) - decodeP2PKaddress(vout[i].scriptPubKey.asm, function(decoded_address) { - // check if the address was decoded properly - if (decoded_address != null) { + // assume the asm value is a P2PK (Pay To Pubkey) public key that should be encoded as a P2PKH (Pay To Pubkey Hash) address + encodeP2PKaddress(vout[i].scriptPubKey.asm, function(p2pkh_address) { + // check if the address was encoded properly + if (p2pkh_address != null) { // process vout addresses - processVoutAddresses(decoded_address, vout[i].value, arr_vout, function(vout_array) { + processVoutAddresses(p2pkh_address, vout[i].value, arr_vout, function(vout_array) { // save updated array arr_vout = vout_array; // move to next vout loop.next(); }); } else { - // could not decode address, move to next vout + // could not decipher the address, move to next vout + console.log('Failed to find vout address from ' + vout[i].scriptPubKey.asm); loop.next(); } }); @@ -629,12 +692,12 @@ module.exports = { loop.next(); } else { // no addresses defined - // try to decode the asm value as P2PK (Pay To Pubkey) - decodeP2PKaddress(tx.vout[i].scriptPubKey.asm, function(decoded_address) { - // check if the address was decoded properly - if (decoded_address != null) { - // save the decoded address - addresses.push({hash: decoded_address, amount: tx.vout[i].value}); + // assume the asm value is a P2PK (Pay To Pubkey) public key that should be encoded as a P2PKH (Pay To Pubkey Hash) address + encodeP2PKaddress(tx.vout[i].scriptPubKey.asm, function(p2pkh_address) { + // check if the address was encoded properly + if (p2pkh_address != null) { + // save the P2PKH address + addresses.push({hash: p2pkh_address, amount: tx.vout[i].value}); } loop.break(true); loop.next(); From 16db390b07e93731b13618fb4c010b1cff9db776 Mon Sep 17 00:00:00 2001 From: Joe Uhren Date: Sat, 20 Nov 2021 11:05:19 -0700 Subject: [PATCH 3/3] Add support for reading scriptPubKey.address --- lib/explorer.js | 47 ++++++++++++++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/lib/explorer.js b/lib/explorer.js index 4450f7618..d45f516e9 100644 --- a/lib/explorer.js +++ b/lib/explorer.js @@ -618,23 +618,35 @@ module.exports = { // check if there are one or more addresses in the vout if (address_list == null || address_list.length == 0) { // no addresses defined - // assume the asm value is a P2PK (Pay To Pubkey) public key that should be encoded as a P2PKH (Pay To Pubkey Hash) address - encodeP2PKaddress(vout[i].scriptPubKey.asm, function(p2pkh_address) { - // check if the address was encoded properly - if (p2pkh_address != null) { - // process vout addresses - processVoutAddresses(p2pkh_address, vout[i].value, arr_vout, function(vout_array) { - // save updated array - arr_vout = vout_array; - // move to next vout + // check if there is a single address defined + if (vout[i].scriptPubKey.address == null) { + // no single address defined + // assume the asm value is a P2PK (Pay To Pubkey) public key that should be encoded as a P2PKH (Pay To Pubkey Hash) address + encodeP2PKaddress(vout[i].scriptPubKey.asm, function(p2pkh_address) { + // check if the address was encoded properly + if (p2pkh_address != null) { + // process vout addresses + processVoutAddresses(p2pkh_address, vout[i].value, arr_vout, function(vout_array) { + // save updated array + arr_vout = vout_array; + // move to next vout + loop.next(); + }); + } else { + // could not decipher the address, move to next vout + console.log('Failed to find vout address from ' + vout[i].scriptPubKey.asm); loop.next(); - }); - } else { - // could not decipher the address, move to next vout - console.log('Failed to find vout address from ' + vout[i].scriptPubKey.asm); + } + }); + } else { + // process vout addresses + processVoutAddresses([vout[i].scriptPubKey.address], vout[i].value, arr_vout, function(vout_array) { + // save updated array + arr_vout = vout_array; + // move to next vout loop.next(); - } - }); + }); + } } else { // process vout addresses processVoutAddresses(address_list, vout[i].value, arr_vout, function(vout_array) { @@ -686,8 +698,9 @@ module.exports = { module.exports.syncLoop(tx.vout.length, function (loop) { var i = loop.iteration(); if (tx.vout[i].n == input.vout) { - if (tx.vout[i].scriptPubKey.addresses) { - addresses.push({hash: tx.vout[i].scriptPubKey.addresses[0], amount:tx.vout[i].value}); + if (tx.vout[i].scriptPubKey.addresses || tx.vout[i].scriptPubKey.address) { + var new_address = tx.vout[i].scriptPubKey.address || tx.vout[i].scriptPubKey.addresses[0]; + addresses.push({hash: new_address, amount:tx.vout[i].value}); loop.break(true); loop.next(); } else {