Jump to content

Bash Client to Electrumx Server: Welcome to the Beginning


buzzkillb
 Share

Recommended Posts

Been wondering how to make use of the electrumx server, but in bash. First I needed to connect to the electrumx server.

#!/bin/bash
echo "get block header"
(echo '{"method" : "blockchain.block.header", "params": ["1"], "id": "msg_id"}'; sleep 1) | ncat --ssl electrumx1.denarius.pro 50002

This outputs.

block header
{"jsonrpc": "2.0", "result": "06000000cd8f82c4c28201fd89def0dba541d66432bff9c1bb16fc1c6201dabb5d0d0000e4abd1522f390c5a2b83add7c8d29b875e1e0691dc43267f0406e9ddfea329c76ab74159ffff0f1e000d8425", "id": "msg_id"}

So now to get a balance. The thing with electrumx server is that you need a scripthash to call any address functions. But I don't have that. All I have is a bash terminal and a Denarius address. So what do I do? Line by line dissect how people create an address and then work backwards to the scripthash.

I need this image, but down to up. source: https://learnmeabitcoin.com/guide/p2pkh

image.png.ebff74b830d1ea33a3beb830d8b8d3cb.png

This is not so easy as I can't find anyone doing this backwards in bash. But I found lots of posts how to go forwards.

Here is how to get it before converting to big endian.

#!/bin/bash
. denarius.sh

#DUP HASH160
begin="76A914"
echo $begin
#EQUALVERIFY CHECKSIG
end="88AC"
echo $end

echo "decodeBase58"
decoded="$(decodeBase58 DCMRvR6MUppPgP8vrMKuni4FL5de8SjicG)"

echo "Remove 00 bytes"
echo ${decoded#??}
removefront=$(echo "${decoded#??}")
echo $removefront

echo "Remove checksum"
removeback=$(echo "${removefront%????????}")
echo "95 characters base58"
echo $removeback


echo "now what?"
echo $begin$removeback$end
echo "convert to scripthash"

echo -n $begin$removeback$end | xxd -r -p | sha256sum | cut -d' ' -f1
scripthash=$(echo -n $begin$removeback$end | xxd -r -p | sha256sum | cut -d' ' -f1)
echo $scripthash

Will also need grondilu bitcoin-bash-tools. https://github.com/grondilu/bitcoin-bash-tools Get the bitcoin.sh and throw that into the same directory you are going to test this out in, for obvious reasons I renamed bitcoin.sh to denarius.sh.

First I am assuming a standard P2PKH address with OP_DUP OP_HASH160 hashedpublickey OP_EQUALVERIFY OP_CHECKSIG

image.thumb.png.4117e19d11f34076761567b2f2af354d.png

#DUP HASH160
begin="76A914"
echo $begin
#EQUALVERIFY CHECKSIG
end="88AC"
echo $end

Then decodeBase58 of our Denarius address, works on bitcoin obviously.

image.png.34861a731fa03c35069e0f28f8a47bbb.png

echo "decodeBase58"
decoded="$(decodeBase58 DCMRvR6MUppPgP8vrMKuni4FL5de8SjicG)"

Then remove 00 bytes from the front of this. The #?? removes 2 characters from the left of a string.

image.png.5bb321862717851130ba60e8a893659f.png

echo "Remove 00 bytes"
echo ${decoded#??}
removefront=$(echo "${decoded#??}")
echo $removefront

Now remove the checksum. The %???????? removes 8 characters from the right of the string.

image.png.fd9636e8ef934953cd178abcc8a021d2.png

echo "Remove checksum"
removeback=$(echo "${removefront%????????}")
echo "95 characters base58"
echo $removeback

Now what? How to convert to scripthash? We can echo the the uncompressed public key, kind of.

echo "now what?"
echo $begin$removeback$end
echo "convert to scripthash"

sha256 this in proper format, using xxd and then sha256 that.

echo -n $begin$removeback$end | xxd -r -p | sha256sum | cut -d' ' -f1
scripthash=$(echo -n $begin$removeback$end | xxd -r -p | sha256sum | cut -d' ' -f1)
echo $scripthash

At the end from that address I get.

3735fe8239322122222f8a8f42d9b6545bc517e2086116e57953fdc9cc7f6115

For now I have a separate function I found to convert to big endian, since electrumx server wants it this way.
source: https://electrumx.readthedocs.io/en/latest/protocol-basics.html#script-hashes

#!/bin/bash

#echo 6191c3b590bfcfa0475e877c302da1e323497acf3b42c08d8fa28e364edf018b | ./bigendian.sh
#8b01df4e368ea28f8dc0423bcf7a4923e3a12d307c875e47a0cfbf90b5c39161

# check stdin
if [ -t 0 ]; then exit; fi

v=`cat /dev/stdin`
i=${#v}

while [ $i -gt 0 ]
do
    i=$[$i-2]
    echo -n ${v:$i:2}
done

echo

I run this like

echo 3735fe8239322122222f8a8f42d9b6545bc517e2086116e57953fdc9cc7f6115 | ./bigendian.sh

and get the scripthash electrumx server wants

15617fccc9fd5379e5166108e217c55b54b6d9428f8a2f222221323982fe3537

Like bash magic we can use that scripthash to talk to the electrumx server to get a balance.

#!/bin/bash
echo "get balance"
(echo '{"method" : "blockchain.scripthash.get_balance", "params": ["15617fccc9fd5379e5166108e217c55b54b6d9428f8a2f222221323982fe3537"], "id": "msg_id"}'; sleep 1) | ncat --ssl electrumx1.denarius.pro 50002

And we get the balance.

get balance
{"jsonrpc": "2.0", "result": {"confirmed": 125740854, "unconfirmed": 0}, "id": "msg_id"}

And a large step forward in creating a basic terminal wallet for any device that can use bash, ncat and has port 50002 open.

  • Like 3
Link to comment
Share on other sites

  • 2 weeks later...
  • 1 month later...

How I did this for python3, trying to learn python.

import os
import ecdsa
import hashlib
import base58
import binascii
import codecs
import struct

denariusAddress= "DDD6SzCwXSEcTPHmNwEQX6xbUs2Rf3svNX"
print("D Addy:" + denariusAddress)

#base58decode denarius address
addrToBytes = base58.b58decode(denariusAddress)
print(addrToBytes)
decodedToHex = addrToBytes.hex()
print(decodedToHex)

#remove prefix
removeZeroBytes = 2
decodedToHexnoPrefix = decodedToHex[removeZeroBytes:]
print(decodedToHexnoPrefix)

#remove checksum
removeChecksum = 40
decodedNoPrefixnoChecksum = decodedToHexnoPrefix[:removeChecksum]
print(decodedNoPrefixnoChecksum)

#Add OP_DUP OP_HASH160 OP_EQUALVERIFY OP_CHECKSIG
opDup = "76"
opHash160 = "A9"
opsBuffer = "14"
opEqualVerify = "88"
opChecksig = "AC"

preparedtoHash = opDup + opHash160 + opsBuffer + decodedNoPrefixnoChecksum + opEqualVerify + opChecksig
print(preparedtoHash.upper())


hashedKey = codecs.decode(preparedtoHash.upper(), 'hex')
s = hashlib.new('sha256', hashedKey).digest()
r = hashlib.new('ripemd160', s).digest()

convertBigEndian = (codecs.encode(s, 'hex').decode("utf-8"))
print(convertBigEndian)

scriptHash = codecs.encode(codecs.decode(convertBigEndian, 'hex')[::-1], 'hex').decode()
print(scriptHash)

then throw that last value into something like this

import socket
import json
from time import sleep

port = 50001
host = 'electrumx1.denarius.pro'

content = {
    "method": "blockchain.scripthash.get_balance",
    "params": ["1fbfac24c0ed8084288904ba34eb891f1feaec146065cf7c08209167235ca3dc"],
    "id": 0
}

def electrumx(host, port, content):
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect((host, port))
    sock.sendall(json.dumps(content).encode('utf-8')+b'\n')
    sleep(0.5)
    sock.shutdown(socket.SHUT_WR)
    res = ""
    while True:
        data = sock.recv(1024)
        if (not data):
            break
        res += data.decode()
    print(res)
    sock.close()

electrumx(host, port, content)

 

Link to comment
Share on other sites

Still learning python 3, this will sign a bitcoin tx based on a sample I found. Want to convert this next to sign a Denarius tx, and work out some of the forced variables I put in here. The idea is to not pull any bitcoin modules in.

import struct
import base58
import hashlib
import ecdsa
import codecs
import binascii
from hashlib import sha256

#References
#https://klmoney.wordpress.com/bitcoin-dissecting-transactions-part-2-building-a-transaction-by-hand/
#http://www.righto.com/2014/02/bitcoins-hard-way-using-raw-bitcoin.html
#https://github.com/shirriff/bitcoin-code
#https://bitcoin.stackexchange.com/questions/3374/how-to-redeem-a-basic-tx
###################################################################################
#Needs work on checking script lengths to check bytes and throw them in, instead of manually putting in

def double_hash(num):
    first_hash = hashlib.sha256(binascii.unhexlify(num)).hexdigest()
    #print(first_hash, "first hash")
    second_hash = hashlib.sha256(binascii.unhexlify(first_hash)).hexdigest()
    return second_hash

#set this up, construction message to be signed
#Add four-byte version field
version = "01000000"
#One-byte variant specifying the number of inputs
number_of_inputs = "01"
#32-byte hash of the transaction from which we want to redeem an output
previous_tx_hash = "416e9b4555180aaa0c417067a46607bc58c96f0131b2f41f7d0fb665eab03a7e"
#vout 1 would be -> 01000000
#The index of the previous Output must be a 4 byte entry in little endian format.
v_out = "00000000"
#one-byte variant which denotes the length of the scriptSig (0x19 = 25 bytes)
script_length = "19"
#temporary scriptSig which, again, is the scriptPubKey of the output we want to redeem
scriptpubkey = "76a91499b1ebcfc11a13df5161aba8160460fe1601d54188ac"
#four-byte field denoting the sequence
sequence = "ffffffff"
#one-byte varint containing the number of outputs in our new transaction
number_of_outputs = "01"
#8-byte field (64 bit integer) containing the amount we want to redeem from the specified output
#100000 denariis, or example 0.00100000
amount_to_send = 20000
amount_to_send_bytes = (hex(amount_to_send)[2:].zfill(16))
print(amount_to_send_bytes)
amount_to_send_bytes_flipped = codecs.encode(codecs.decode(amount_to_send_bytes, 'hex')[::-1], 'hex').decode()
print(amount_to_send_bytes_flipped)
#The Script Length field is Ox19 (25 bytes) which is the length in bytes of the scriptPubKey above
script_length = "19"
print(script_length)
#output script
script_pub_key = "76a914e81d742e2c3c7acd4c29de090fc2c4d4120b2bf888ac"
#four-byte "lock time" field
lock_time = "00000000"
#For normal transactions, the SigHash code will be 1 for SIGHASH_ALL.
#This signature hash type means that the signature includes all the Inputs and Outputs minus the scriptSig.
#The SigHash code is padded to four bytes and entered in little endian format
sighash_code = "01000000"

complete_tx_message = (
    version
    + number_of_inputs
    + previous_tx_hash
    + v_out
    + script_length
    + scriptpubkey
    + sequence
    + number_of_outputs
    + amount_to_send_bytes_flipped
    + script_length
    + script_pub_key
    + lock_time
    + sighash_code
)
print("Complete rawtx to Sign")
print(complete_tx_message)

#double hash complete_tx_message - 2 methods both in hex
double_hash_complete_tx_message = double_hash(complete_tx_message)
print("double hashed")
print(double_hash_complete_tx_message)
#or
header_complete_tx_message = bytes.fromhex(complete_tx_message)
print("double hashed another method")
print(sha256(sha256(header_complete_tx_message).digest()).digest().hex())

#derive signature
#The private key associated with the previous Output’s address, in hex format
privkey = '3cd0560f5b27591916c643a0b7aa69d03839380a738d2e912990dcc573715d2c'
print("private key")
print(privkey)
privateKey_bytes = bytes.fromhex(privkey)
print("private key in bytes")
print(privateKey_bytes)


hash_2 = bytes.fromhex(double_hash_complete_tx_message)
print("double hashed in bytes")
print(hash_2)

sk = ecdsa.SigningKey.from_string(privateKey_bytes, curve=ecdsa.SECP256k1)
print(sk)
verifying_key = sk.get_verifying_key()
print(verifying_key)
public_key = bytes.fromhex("04") + verifying_key.to_string()
print("public key")
print(public_key)
signature = sk.sign_digest(hash_2, sigencode = ecdsa.util.sigencode_der_canonize)
print("Signature")
print(signature)
signer_convert = binascii.hexlify(signature)
print("signer in hex bytes")
print(signer_convert)
signer = (signer_convert.decode("utf-8"))
print("Signer in Hex")
print(signer)


#The PUSHDATA opcode 0x47 (or decimal 71) is the number of bytes that will be pushed onto the stack. This includes the one byte sigHash code.
pushdata_op = "47"
sighash_code = "01"
pushdata_op_second = "21"
#Recent transactions use compressed public keys as in this example.
#Compressed public keys are 32 bytes with a one byte prefix of 02 or 03.
#Uncompressed public keys are 64 bytes plus a prefix of 04.
pubkey_compressed = "03bf350d2821375158a608b51e3e898e507fe47f2d2e8c774de4a9a7edecf74eda"
####GET THIS SOMEHOW
sighash_script_length = "6a"


scriptsig = (
    pushdata_op
    + signer
    + sighash_code
    + pushdata_op_second
    + pubkey_compressed
)
print("scriptSig")
print(scriptsig)

prepare_to_broadcast = (
    version
    + number_of_inputs
    + previous_tx_hash
    + v_out
    + sighash_script_length
    + scriptsig
    + sequence
    + number_of_outputs
    + amount_to_send_bytes_flipped
    + script_length
    + script_pub_key
    + lock_time
)

print("Broadcast")
print(prepare_to_broadcast)

print(version)
print(number_of_inputs)
print(previous_tx_hash)
print(v_out)
print(sighash_script_length)
print(scriptsig)
print(sequence)
print(number_of_outputs)
print(amount_to_send_bytes_flipped)
print(script_length)
print(script_pub_key)
print(lock_time)

 

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

 Share

×
×
  • Create New...