Goal
Create and sign a standard raw Bitcoin transaction.
This is a second iteration of the previous project Creating and signing a standard raw Bitcoin transaction and draws heavily on the work already done there.
This will involve:
- Creating a standard raw Bitcoin transaction.
- Signing the transaction using ECDSA cryptography.
- Broadcasting the transaction so that it can be mined, which will confirm its validity.
My working definition of a standard transaction:
- It has at least one input and at least one output.
- All input and output addresses are Pay-To-Public-Key-Hash (P2PKH).
- All input scriptSigs contain uncompressed public keys.
Note: The form of the transaction that is actually signed differs slightly for each input address.
Contents
- Goal
- Contents
- Brief Summary
- Summary
- Downloadable Assets
- Recipes For Using Various Downloadable Assets
- Notes
- Further Work
- Project Log
Brief Summary
During this project, I developed a toolset that allows me to precisely and quickly:
1) generate a Bitcoin address from a private key,
2) construct and sign a standard Bitcoin transaction with one input and one output,
and
3) construct and sign a nonstandard Bitcoin transaction with one standard input and one nonstandard output.
Please read the Recipes For Using Various Downloadable Assets section if you would like to know how to use this toolset.
Here are the notable occurrences in this project:
- I generated two standard addresses, a "source address" and a "target" address.
- I looked up the then-current receiving address on my LocalBitcoins account. This was the "final" address. It was nonstandard.
- I transferred some bitcoin from my LocalBitcoins account to the source address.
- I constructed and signed a standard transaction that moved the available bitcoin from the source address to the target address. This transaction was then broadcast and mined.
- I constructed and signed a nonstandard transaction that moved the available bitcoin from the target address to the final address. This transaction was then broadcast and mined.
Please read the Summary section for a more thorough description of this project.
Summary
Original plan of action for this project:
- Generate an address: the "source" address.
- Generate a second address: the "target" address.
- Look up my current receiving address on my LocalBitcoins account: the "final" address. This will be a nonstandard address.
- Transfer some bitcoin from my LocalBitcoins account to the source address. I'll call this tx0. "tx" = transaction.
- Look up recent mining fees.
- Construct a standard raw Bitcoin transaction that moves the available bitcoin from the source address to the target address. Be careful to include a mining fee. This is tx1.
- Sign the transaction using the private key of the source address and ECDSA cryptography. Before signing, the transaction must be altered slightly to get the transaction-in-signable-form.
- Embed the transaction signature into the transaction.
- Broadcast the transaction.
- Confirm that the transaction has been mined (included in a block in the Bitcoin blockchain). This verifies that the transaction was valid (i.e. in the correct format, with a mathematically valid signature).
- Look up recent mining fees.
- Construct a second nonstandard raw Bitcoin transaction that moves the available bitcoin from the target address to the final address. Be careful to include a mining fee. This is tx2.
- Sign the transaction using the private key of the target address and ECDSA cryptography. Before signing, the transaction must be altered slightly to get the transaction-in-signable-form.
- Embed the transaction signature into the transaction.
- Broadcast the transaction.
- Confirm that the transaction has been mined (included in a block in the Bitcoin blockchain). This verifies that the transaction was valid (i.e. in the correct format, with a mathematically valid signature).
What I actually did during the project:
- Created a project directory:
creating_and_signing_a_standard_raw_bitcoin_transaction_2
- Within the project directory, created a work directory named "work".
- Downloaded the asset
generate_bitcoin_address_2.py
along with its dependencies:
-- bjorn_edstrom_ripemd160.py
-- ecdsa-0.10.tar.gz
-- pypy_sha256.py
- Moved these downloaded files to the work directory.
- Unzipped ecdsa-0.10.tar.gz and copied the directory "ecdsa" within it to the work directory.
- Rewrote
generate_bitcoin_address_2.py
into the two files
generate_bitcoin_address_3.py
and
bitcoin_functions.py.
- Used
generate_bitcoin_address_3.py
to generate the source address.
- Used
generate_bitcoin_address_3.py
to generate the target address.
- Looked up my current receiving address on my LocalBitcoins account, the final address.
- Estimated an amount of bitcoin that would cover the high end of expected transaction and deposit fees, with extra so as to have room to manoeuvre.
- Transferred some bitcoin from my LocalBitcoins account to the source address. This transfer is included in a Bitcoin transaction, created by LocalBitcoins, that I refer to as tx0 during this project.
- Used a web service to look up recent mining fees.
- Chose excerpts from the first iteration of this project, Creating and signing a standard raw Bitcoin transaction, that outline the format of a standard raw transaction and the data that must be gathered in order to construct it.
- Wrote a script
create_transaction.py
that can construct and sign a single-input, single-output standard transaction.
- For the needs of
create_transaction.py
I wrote a supporting file
transaction.py
and additional functions for
bitcoin_functions.py.
- Looked up some of the necessary transaction input data on a web service. Some additional work was required to process the data.
- Using
create_transaction.py,
constructed and signed a standard raw Bitcoin transaction that moves the available bitcoin from the source address to the target address. A mining fee was included, set at the low end of the then-current fee range. This was tx1.
- The script
create_transaction.py
automated these steps in the original plan, so that they were achieved in one step:
{
- Construct a standard raw Bitcoin transaction that moves the available bitcoin from the source address to the target address. Be careful to include a mining fee. This is tx1.
- Sign the transaction using the private key of the source address and ECDSA cryptography. Before signing, the transaction must be altered slightly to get the transaction-in-signable-form.
- Embed the transaction signature into the transaction.
}
- Used a web service to successfully decode this transaction, which indicated that it was formatted correctly.
- Used a web service to successfully broadcast this signed transaction. Time taken for mining + 6-block-deep confirmation = 4.5 hours. This verified that the transaction was valid (i.e. in the correct format, with a mathematically valid signature).
- Used a web service to look up recent mining fees.
- Chose an asset and excerpts from the first iteration of this project, Creating and signing a standard raw Bitcoin transaction, that outline the format of a Pay-To-Script-Hash (P2SH) transaction output and how to obtain the redeem script hash from the P2SH address.
- Wrote a script
create_nonstandard_transaction.py
that can construct and sign a single-input, single-output nonstandard transaction, where the input is standard Pay-To-Public-Key-Hash (P2PKH) and the output is P2SH.
- For the needs of
create_nonstandard_transaction.py,
I wrote a supporting file
nonstandard_transaction.py
and additional functions for
nonstandard_bitcoin_functions.py.
I also added or altered functions in
bitcoin_functions.py.
- Using
create_nonstandard_transaction.py,
constructed and signed a standard raw Bitcoin transaction that moves the available bitcoin from the target address to the final address. A mining fee was included, set at the low end of the then-current fee range. This was tx2.
- The script
create_nonstandard_transaction.py
automated these steps in the original plan, so that they were achieved in one step:
{
- Construct a second nonstandard raw Bitcoin transaction that moves the available bitcoin from the target address to the final address. Be careful to include a mining fee. This is tx2.
- Sign the transaction using the private key of the target address and ECDSA cryptography. Before signing, the transaction must be altered slightly to get the transaction-in-signable-form.
- Embed the transaction signature into the transaction.
}
- Used a web service to successfully decode this transaction, which indicated that it was formatted correctly.
- Used a web service to successfully broadcast this signed transaction. Time taken for mining + 6-block-deep confirmation ~= 6 hours. This verified that the transaction was valid (i.e. in the correct format, with a mathematically valid signature).
- Rewrote
create_transaction.py,
incorporating various improvements developed in
create_nonstandard_transaction.py.
- Rewrote
create_nonstandard_transaction.py
slightly.
- Rewrote various other code files to refactor, tidy up output, make the code cleaner, add notes (author, date, description, environments, comments, instructions), and do a complete check of the final state of the code.
- Performed final runs of these scripts:
create_transaction.py
create_nonstandard_transaction.py
and
generate_bitcoin_address_3.py,
confirming that their output matched the output produced and used earlier in the project.
Financial Information:
- I calculated an amount of bitcoin that was large enough to ensure that I would be able to pay two Bitcoin transaction fees + the LocalBitcoins deposit fee, with extra so as to have room to manoeuver. This was 0.00242 bitcoin, or about $15 at then-current prices.
- I transferred 0.00242 bitcoin in tx0.
- LocalBitcoins charged a transaction fee of 0.00005 bitcoin for tx0. This was subtracted from my LocalBitcoins wallet balance, not from the amount that was actually sent.
- For tx1, I used a transaction fee of 223 satoshis (0.00000223 bitcoin).
- For tx2, I used a transaction fee of 225 satoshis (0.00000225 bitcoin).
- When it accepted and processed tx2, LocalBitcoins charged a deposit fee of 0.00015000 bitcoin.
- Total bitcoin spent: LocalBitcoins transaction fee for tx0 + tx1 transaction fee + tx2 transaction fee + LocalBitcoins deposit fee for tx2 = 0.00005000 + 0.00000223 + 0.00000225 + 0.00015000 = 0.00020448 bitcoin.
Recipes:
The following recipes can be found in the Recipes For Using Various Downloadable Assets section.
- Recipe 1: Setup
-- Some preparation for the other recipes.
- Recipe 2: generate_bitcoin_address_3.py
-- Generating a standard Bitcoin address from a private key.
- Recipe 3: create_transaction.py
-- Constructing and signing a standard Bitcoin transaction with one input and one output.
- Recipe 4: create_nonstandard_transaction.py
-- Constructing and signing a nonstandard Bitcoin transaction with one input and one output.
Notes section:
The Notes section contains the following parts:
- Definitions and acryonyms
- Web services used during this project
- Gathering data in order to construct a new transaction using the scripts created in this project
- Pay-To-Script-Hash (P2SH) addresses
- Addresses generated and/or used in this project
- Transactions created during this project
Downloadable Assets
My work computer: Aineko, a 2008 Macbook running Mac OS X 10.6.8 Snow Leopard.
aineko:work stjohnpiano$ python --version
Python 2.7.13
My working definition of a standard transaction:
- It has at least one input and at least one output.
- All input and output addresses are Pay-To-Public-Key-Hash (P2PKH).
- All input scriptSigs contain uncompressed public keys.
My working definition of a nonstandard transaction:
- It has at least one input and at least one output.
- At least one input or output address is not a standard Pay-To-Public-Key-Hash (P2PKH) address.
I define a "standard" address to be an uncompressed single-signature Pay-To-Public-Key-Hash (P2PKH) address.
The following assets were developed during this project and were run using Python 2.7.13 on Aineko.
Description: A library of functions for handling standard Bitcoin data types.
bitcoin_functions.py
Description: A script that creates and signs a nonstandard Bitcoin transaction with one standard P2PKH input and one nonstandard P2SH output.
create_nonstandard_transaction.py
Description: A script that creates and signs a standard Bitcoin transaction with one input and one output.
create_transaction.py
Description: A script that generates a standard Bitcoin address from a private key.
generate_bitcoin_address_3.py
Description: A library of functions for handling nonstandard Bitcoin data types.
nonstandard_bitcoin_functions.py
Description: A library of classes for nonstandard Bitcoin transactions.
nonstandard_transaction.py
Description: A library of classes for standard Bitcoin transactions.
transaction.py
Note: The asset
generate_bitcoin_address_3.py
was based on the asset
generate_bitcoin_address_2.py [paywalled],
associated with the article Verifying a signed deed of the GPG 1.4.10 source code. Please see this previous article for details concerning this previous asset.
The following assets are associated with the article Reading and verifying a standard raw bitcoin transaction. Please read the article for information and advice on these assets.
Description: An implementation of RIPEMD-160, written by Björn Edström.
bjorn_edstrom_ripemd160.py [paywalled]
Description: A Python implementation of ECDSA cryptography, written by Peter Pearson.
ecdsa-0.10.tar.gz [paywalled]
Description: A Python implementation of SHA256.
pypy_sha256.py [paywalled]
Recipes For Using Various Downloadable Assets
Recipe list:
- Recipe 1: Setup
-- Preparation for the other recipes.
- Recipe 2: generate_bitcoin_address_3.py
-- Generating a standard Bitcoin address from a private key.
- Recipe 3: create_transaction.py
-- Constructing and signing a standard Bitcoin transaction with one input and one output.
- Recipe 4: create_nonstandard_transaction.py
-- Constructing and signing a nonstandard Bitcoin transaction with one input and one output.
Recipe 1: Setup
Initial condition: You must have Python 2.7.x installed on your computer. These code assets were tested under Python 2.7.13 running on Mac OS X 10.6.8 (Snow Leopard), and should run successfully on other versions of Python 2.7.
Steps:
- Create a work directory.
- Browse to the Downloadable Assets section of this article.
- Download the asset bjorn_edstrom_ripemd160.py.
- Download the asset ecdsa-0.10.tar.gz.
- Download the asset pypy_sha256.py.
- Download the asset bitcoin_functions.py.
- Move these assets into the work directory.
- Unpack the zipped tape archive file ecdsa-0.10.tar.gz. This can be done by opening a terminal, changing directory to the work directory, and running the following command:
tar -zxvf ecdsa-0.10.tar.gz
This unpacking should produce a new directory named ecdsa-0.10 in the work directory. The directory ecdsa-0.10 should contain a directory named ecdsa. Copy the ecdsa directory into the work directory.
Recipe 2: generate_bitcoin_address_3.py
Notes:
- generate_bitcoin_address_3.py is a script that generates a standard Bitcoin address from a private key.
- All control values in this script are strings.
Steps:
- Work through Recipe 1: Setup.
- Generate a random private key that is between 1 and 32 bytes long. This requires some work (e.g. using dice) and is not covered in this recipe.
Note: Ideally a private key should be 32 bytes long. It can be raw bytes (printable ASCII characters) or hex bytes (hex characters). Hex characters must be lowercase. 1 byte is represented by 2 hex characters, so a 32-byte private key will be 64 hex characters long. I recommend hex bytes, as these can represent all possible private keys. I find that raw byte private keys are convenient during testing.
- Browse to the Downloadable Assets section of this article.
- Download the asset generate_bitcoin_address_3.py. Move it into the work directory.
- Open the file generate_bitcoin_address_3.py in a text editor. Scroll to the section of text that lies between
##### START CONTROLS
and
##### END CONTROLS
- If the private key is in raw bytes, set the variable
private_key_type
to
"raw_bytes"
and set the variable
private_key_raw_bytes
to the private key. - If the private key is in hex bytes, set the variable
private_key_type
to
"hex_bytes"
and set the variable
private_key_hex_bytes
to the private key. - Open a terminal and change directory to the work directory.
- Run the following command:
python generate_bitcoin_address_3.py
- The output should contain the Bitcoin address that corresponds to your private key.
Note: Please go to the "Addresses generated and/or used in this project" part of the Notes section to see two examples of the output of this script.
Recipe 3: create_transaction.py
Notes:
- create_transaction.py is a script that creates and signs a standard Bitcoin transaction with one input and one output. The input address and the output address must both be standard P2PKH addresses. Standard P2PKH addresses start with the character '1'.
- All control values in this script are strings.
- New transactions spend unspent-outputs-of-previous-transactions.
- The bitcoin balance of an address is the sum of the unspent outputs sent by previous transactions to this address.
- To spend bitcoin from an address, the bitcoin amount must be constructed from complete unspent outputs. Any change can be sent in a new unspent output back to the original address or another that you control. Unspent outputs cannot be broken into smaller unspent outputs prior to spending - new unspent outputs (larger or smaller) must be created by a new transaction.
Definitions:
- input address: the address that currently contains the bitcoin that you want to transfer in this transaction.
- output address: the address to which the bitcoin will be transferred by this transaction.
- previous_output_hash: big-endian 32-byte double-SHA256 hash of a previous transaction.
- txid: "transaction id", the little-endian form of the previous_output_hash.
Steps:
- Work through Recipe 1: Setup.
- Gather the following pieces of information:
-- The txid of the previous transaction that transferred the unspent output to the input address.
-- The index of this unspent output in the previous transaction (the "previous_output_index"). This index is implicit and starts at zero.
-- The private key (in hex bytes) of the input address.
-- The bitcoin amount (or satoshi amount) contained within the unspent output. Let's call this the "input amount".
- Choose the following pieces of information:
-- The output address.
-- The bitcoin amount (or satoshi amount) that you wish to transfer to the output address. Let's call this the "output amount".
-- The change address. Currently, this is the same as the output address. Any value not assigned to an output address will be assigned to the change address. The fee will then be subtracted from the value assigned to the change address.
-- The fee (in satoshi) or fee_rate (in satoshi / byte). The fee must be an integer e.g.
"225"
. The fee_rate must be an integer e.g.
"1"
or a float e.g.
"1.2"
. You should look up the range of fee rates for transactions that are currently being mined. - Generate a random value that is between 1 and 32 bytes long. This requires some work (e.g. using dice) and is not covered in this recipe.
Note: This random value will be used for creating the ECDSA signature of the transaction. Every ECDSA signature you ever make should use a different random value. Ideally a random value should be 32 bytes long. It can be raw bytes (printable ASCII characters) or hex bytes (hex characters). Hex characters must be lowercase. 1 byte is represented by 2 hex characters, so a 32-byte random value will be 64 hex characters long. I recommend hex bytes, as these can represent all possible random values. I find that raw byte random values are convenient during testing.
- Browse to the Downloadable Assets section of this article.
- Download the asset transaction.py.
- Download the asset create_transaction.py.
- Move these assets into the work directory.
- Open the file create_transaction.py in a text editor. Scroll to the section of text that lies between
##### START CONTROLS
and
##### END CONTROLS
- Set the variable
random_value
to the random value. - If the random value is in raw bytes, set the variable
random_value_type
to
"raw_bytes"
. - If the random value is in hex bytes, set the variable
random_value_type
to
"hex_bytes"
. - The variable
input_data
is a dictionary that contains several other variables. -- Set the value of the variable
input_data["txid"]
to be the txid. -- Set the value of the variable
input_data["previous_output_index"]
to be the index of the unspent output. -- Set the value of the variable
input_data["private_key_hex"]
to be the private key. -- If the input amount is in satoshi, set the value of the variable
input_data["satoshi_amount"]
to be the input amount. -- If the input amount is in bitcoin, set the value of the variable
input_data["bitcoin_amount"]
to be the input amount. Note: Currently, if both of the variables
input_data["satoshi_amount"]
and
input_data["bitcoin_amount"]
are set, then
input_data["bitcoin_amount"]
will take precedence. You can comment out the unused variable of the pair by placing a number sign ('#') at the start of the relevant line. - The variable
output_data
is a dictionary that contains several other variables. -- Set the value of the variable
output_data["address"]
to be the output address.-- If the output amount is in satoshi, set the value of the variable
output_data["satoshi_amount"]
to be the output amount.-- If the output amount is in bitcoin, set the value of the variable
output_data["bitcoin_amount"]
to be the output amount.Note: Currently, if both of the variables
output_data["satoshi_amount"]
and
output_data["bitcoin_amount"]
are set,
output_data["bitcoin_amount"]
will take precedence. You can comment out the unused variable of the pair by placing a number sign ('#') at the start of the relevant line. - Set the variable
change_address
to be the change address.- If you have chosen to set the fee as an absolute number of satoshi, set the variable
fee
to be the fee amount and set the variable
fee_type
to be
"fee"
. - If you have chosen to set the fee as a fee rate (in satoshi / byte), set the variable
fee_rate
to be the fee rate and set the variable
fee_type
to be
"fee_rate"
. - Open a terminal and change directory to the work directory.
- Run the following command:
python create_transaction.py
- The output should contain the signed transaction as a hex byte sequence without spaces.
Note: Please go to the "Transactions created during this project" part of the Notes section to see an example of the output of this script.
Recipe 4: create_nonstandard_transaction.py
Notes:
- create_nonstandard_transaction.py is a script that creates and signs a nonstandard Bitcoin transaction with one input and one output. The input address must be a standard P2PKH address and the output address must be a nonstandard P2SH address. P2PKH addresses start with the character '1'. P2SH addresses start with the character '3'.
- All control values in this script are strings.
- New transactions spend unspent-outputs-of-previous-transactions.
- The bitcoin balance of an address is the sum of the unspent outputs sent by previous transactions to this address.
- To spend bitcoin from an address, the bitcoin amount must be constructed from complete unspent outputs. Any change can be sent in a new unspent output back to the original address or another that you control. Unspent outputs cannot be broken into smaller unspent outputs prior to spending - new unspent outputs (larger or smaller) must be created by a new transaction.
Definitions:
- input address: the address that currently contains the bitcoin that you want to transfer in this transaction.
- output address: the address to which the bitcoin will be transferred by this transaction.
- previous_output_hash: big-endian 32-byte double-SHA256 hash of a previous transaction.
- txid: "transaction id", the little-endian form of the previous_output_hash.
Steps:
- Work through Recipe 1: Setup.
- Gather the following pieces of information:
-- The txid of the previous transaction that transferred the unspent output to the input address.
-- The index of this unspent output in the previous transaction (the "previous_output_index"). This index is implicit and starts at zero.
-- The private key (in hex bytes) of the input address.
-- The bitcoin amount (or satoshi amount) contained within the unspent output. Let's call this the "input amount".
- Choose the following pieces of information:
-- The output address.
-- The bitcoin amount (or satoshi amount) that you wish to transfer to the output address. Let's call this the "output amount".
-- The change address. Currently, this is the same as the output address. Any value not assigned to an output address will be assigned to the change address. The fee will then be subtracted from the value assigned to the change address.
-- The fee (in satoshi) or fee_rate (in satoshi / byte). The fee must be an integer e.g.
"225"
. The fee_rate must be an integer e.g.
"1"
or a float e.g.
"1.2"
. You should look up the range of fee rates for transactions that are currently being mined. - Generate a random value that is between 1 and 32 bytes long. This requires some work (e.g. using dice) and is not covered in this recipe.
Note: This random value will be used for creating the ECDSA signature of the transaction. Every ECDSA signature you ever make should use a different random value. Ideally a random value should be 32 bytes long. It can be raw bytes (printable ASCII characters) or hex bytes (hex characters). Hex characters must be lowercase. 1 byte is represented by 2 hex characters, so a 32-byte random value will be 64 hex characters long. I recommend hex bytes, as these can represent all possible random values. I find that raw byte random values are convenient during testing.
- Browse to the Downloadable Assets section of this article.
- Download the asset transaction.py.
- Download the asset nonstandard_bitcoin_functions.py
- Download the asset nonstandard_transaction.py.
- Download the asset create_nonstandard_transaction.py
- Move these assets into the work directory.
- Open the file create_nonstandard_transaction.py in a text editor. Scroll to the section of text that lies between
##### START CONTROLS
and
##### END CONTROLS
- Set the variable
random_value
to the random value. - If the random value is in raw bytes, set the variable
random_value_type
to
"raw_bytes"
. - If the random value is in hex bytes, set the variable
random_value_type
to
"hex_bytes"
. - The variable
input_data
is a dictionary that contains several other variables. -- Set the value of the variable
input_data["txid"]
to be the txid. -- Set the value of the variable
input_data["previous_output_index"]
to be the index of the unspent output. -- Set the value of the variable
input_data["private_key_hex"]
to be the private key. -- If the input amount is in satoshi, set the value of the variable
input_data["satoshi_amount"]
to be the input amount. -- If the input amount is in bitcoin, set the value of the variable
input_data["bitcoin_amount"]
to be the input amount.Note: Currently, if both of the variables
input_data["satoshi_amount"]
and
input_data["bitcoin_amount"]
are set, then
input_data["bitcoin_amount"]
will take precedence. You can comment out the unused variable of the pair by placing a number sign ('#') at the start of the relevant line. -- Set the value of the variable
input_data["input_type"]
to be
"p2pkh"
. - The variable
output_data
is a dictionary that contains several other variables. -- Set the value of the variable
output_data["address"]
to be the output address.-- If the output amount is in satoshi, set the value of the variable
output_data["satoshi_amount"]
to be the output amount.-- If the output amount is in bitcoin, set the value of the variable
output_data["bitcoin_amount"]
to be the output amount.Note: Currently, if both of the variables
output_data["satoshi_amount"]
and
output_data["bitcoin_amount"]
are set,
output_data["bitcoin_amount"]
will take precedence. You can comment out the unused variable of the pair by placing a number sign ('#') at the start of the relevant line. -- Set the value of the variable
output_data["output_type"]
to be
"p2sh"
. - Set the variable
change_address
to be the change address.- If you have chosen to set the fee as an absolute number of satoshi, set the variable
fee
to be the fee amount and set the variable
fee_type
to be
"fee"
. - If you have chosen to set the fee as a fee rate (in satoshi / byte), set the variable
fee_rate
to be the fee rate and set the variable
fee_type
to be
"fee_rate"
. - Open a terminal and change directory to the work directory.
- Run the following command:
python create_nonstandard_transaction.py
- The output should contain the signed transaction as a hex byte sequence without spaces.
Note: Please go to the "Transactions created during this project" part of the Notes section to see an example of the output of this script.
Notes
Parts:
- Definitions and acryonyms
- Web services used during this project
- Gathering data in order to construct a new transaction using the scripts created in this project
- Pay-To-Script-Hash (P2SH) addresses
- Addresses generated and/or used in this project
- Transactions created during this project
Definitions and acryonyms
txid = "transaction id"
P2PKH = Pay-To-Public-Key-Hash
P2SH = Pay-To-Script-Hash
I'm going to define:
- previous_output_hash = big-endian 32-byte double-SHA256 hash of a previous transaction.
- txid = little-endian form of the previous_output_hash
My working definition of a standard transaction:
- It has at least one input and at least one output.
- All input and output addresses are Pay-To-Public-Key-Hash (P2PKH).
- All input scriptSigs contain uncompressed public keys.
My working definition of a nonstandard transaction:
- It has at least one input and at least one output.
- At least one input or output address is not a standard Pay-To-Public-Key-Hash (P2PKH) address.
Web services used during this project
I used
bitcoinfees.earn.com
to observe the then-current transaction fees and thereby choose fees for my transactions. It displays estimated delays (in blocks and in minutes) for ranges of fee rates (in satoshis / byte).
I used the search field at
live.blockcypher.com
to search for transactions by txid.
On the resulting transaction pages e.g.
live.blockcypher.com/btc/tx/e4609e0f1ca854b8b07381f32ba31adbad9713205f5a4f3f56a5a32853d47855
I was able to click Advanced Details / API Call, leading to a raw text dump of the particular transaction, e.g.
api.blockcypher.com/v1/btc/main/txs/e4609e0f1ca854b8b07381f32ba31adbad9713205f5a4f3f56a5a32853d47855?limit=50&includeHex=true
I used
live.blockcypher.com/btc/decodetx
to decode signed transactions, indicating that they were formatted correctly.
I used
live.blockcypher.com/btc/pushtx
to upload signed transactions for broadcast to the Bitcoin network.
Gathering data in order to construct a new transaction using the scripts created in this project
New transactions spend unspent-outputs-of-previous-transactions.
The scripts
- create_transaction.py
- create_nonstandard_transaction.py
can derive various other necessary values from the control values.
Please note that these scripts only support creating transactions with a single input and a single output.
create_transaction.py:
The controls are:
- [input] random_value
- [input] txid
- [input] previous_output_index
- [input] private_key_hex
- [input] bitcoin_amount (or satoshi_amount)
- [output] address
- [output] bitcoin_amount (or satoshi_amount)
- [output] change_address
- [output] fee (or fee_rate)
Notes:
- [input] random_value: This is the random entropy used for signing the new transaction with private_key_hex using ECDSA cryptography. It must be new and random for every signature ever made.
- [input] txid: This is the transaction id (txid) of an existing transaction containing an unspent output.
- [input] previous_output_index: This is the implicit index of the unspent output in the list of outputs in the existing transaction that contains it.
- [input] private_key_hex: This is the private key of the address containing the unspent output.
- [input] bitcoin_amount (or satoshi_amount): This is the bitcoin amount contained in the unspent output.
- [output] address: This is the address to which the new transaction will transfer bitcoin.
- [output] bitcoin_amount (or satoshi_amount): This is the amount to be transferred to the output address.
- [output] change_address: This is the address that will receive any unassigned input bitcoin. Currently, this is the same as the output address. The fee will be subtracted from the amount directed to the change address output.
- [output] fee (or fee_rate): Specifies the fee to be paid to a miner for including the new transaction in a new block. The fee is stored in the transaction implicitly as the difference between the total input value and the total output value.
create_nonstandard_transaction.py:
The controls are the same as those for
create_transaction.py,
with two additional ones:
- [input] input_type
- [output] output_type
Notes:
- [input] input_type: This is the nonstandard transaction input type. It can be either "p2pkh" (standard) or "p2sh" (nonstandard). Currently, only p2pkh is supported for nonstandard transaction inputs.
- [output] output_type: This is the nonstandard transaction output type. It can be either "p2pkh" (standard) or "p2sh" (nonstandard). Currently, only p2sh is supported for nonstandard transaction outputs.
Pay-To-Script-Hash (P2SH) addresses
Pay-To-Script-Hash (P2SH) addresses begin with a '3' character. Example:
The final address
328cTqexYnQRbN5Dgs12D89sYiPPvtWVbF
begins with a '3' and is therefore a P2SH address.
The reason for this is: A string of 21 bytes that starts with the '05' byte, once converted into base58check encoding (with a 4-byte checksum added by the encoding function), always starts with the base58 symbol '3'.
I examined P2SH addresses in some detail in the first iteration of this project:
Creating and signing a standard raw Bitcoin transaction
Summaries of my results can be found in this previous project, in the Notes / Discovery section, in these parts:
- P2SH multi-signature addresses - security
- P2SH multi-signature addresses - results from this project
Addresses generated and/or used in this project
Source address:
aineko:work stjohnpiano$ ./generate_bitcoin_address_3.py
### START GENERATION OF BITCOIN ADDRESS
Private key (raw bytes): the_mote_in_god's_eye
Private key (hex bytes): 7468655f6d6f74655f696e5f676f6427735f657965
Private key (32 hex bytes): 00000000000000000000007468655f6d6f74655f696e5f676f6427735f657965
Private key (WIF): 5HpHagT65TZzG1PH41Hd3yo15N3JqZyeumt1YxQeHYAsFab3Qss
Bitcoin address: 138obEZkdWaWEQ4x8ZAYw4MybHSZtX1Nam
### END GENERATION OF BITCOIN ADDRESS
Target address:
aineko:work stjohnpiano$ ./generate_bitcoin_address_3.py
### START GENERATION OF BITCOIN ADDRESS
Private key (raw bytes): roadside_picnic
Private key (hex bytes): 726f6164736964655f7069636e6963
Private key (32 hex bytes): 0000000000000000000000000000000000726f6164736964655f7069636e6963
Private key (WIF): 5HpHagT65TZzG1PH3CSu63k8Dxn19XHMLQhSCuE8qpbeuFK2mGG
Bitcoin address: 13xPBB175FtPbPQ84iB8KuawaVy3mHrady
### END GENERATION OF BITCOIN ADDRESS
Final address (my then-current receiving address on LocalBitcoins):
328cTqexYnQRbN5Dgs12D89sYiPPvtWVbF
Transactions created during this project
I'll use:
- txid0 to mean the txid (transaction id) of tx0
- txid1 to mean the txid of tx1
- txid2 to mean the txid of tx2
tx0:
I transferred some bitcoin from my LocalBitcoins account to the source address. LocalBitcoins created a larger transaction, which included many other transfers, that executed this transfer.
txid0:
e4609e0f1ca854b8b07381f32ba31adbad9713205f5a4f3f56a5a32853d47855
Link:
live.blockcypher.com/btc/tx/e4609e0f1ca854b8b07381f32ba31adbad9713205f5a4f3f56a5a32853d47855
Key details:
- block_hash:
00000000000000000022ae51b839f59396e49ffb8217bd10fa00a486360ef2e9
- block_height:
543926
- fees:
69494
- size:
1348
- confirmed:
2018-10-01T14:41:56Z
From some reading, the "Z" suffix on Unix timestamps indicates UTC time values.
UTC = Coordinated Universal Time (identical to GMT = Greenwich Mean Time).
tx1:
tx1 transferred the available bitcoin from the source address to the target address, minus a transaction fee.
txid1:
745e224ccba0a033c55ea80523f207da18b903418ac1f5d293eed62c19e0334d
Link:
live.blockcypher.com/btc/tx/745e224ccba0a033c55ea80523f207da18b903418ac1f5d293eed62c19e0334d
Key details:
- block_hash:
0000000000000000000eecb4423435b1e52db78e8c83ae9e271978c2ef9d186e
- block_height:
544268
- size:
223 bytes
- fees:
223
- confirmed:
2018-10-03T23:25:01Z
Actual fee rate for tx1 is 223 / 223 = 1 (satoshi / byte).
Time taken for mining + 6-block-deep confirmation = 4.5 hours.
The estimated delay for a transaction with this fee rate was:
3-23 blocks (25-300 minutes) for a fee rate of 1-2 satoshis / byte.
tx2:
tx2 transferred the available bitcoin from the target address to the final address, minus a transaction fee.
txid2:
598f47dfb44da351c8576fb975bdab3b023777bb1c1d8ba727afd528ca4d7cf2
Link:
live.blockcypher.com/btc/tx/598f47dfb44da351c8576fb975bdab3b023777bb1c1d8ba727afd528ca4d7cf2
Key details:
- block_hash:
0000000000000000000d1f94b44eafbe52692ec790caf63a045d25e3080ba095
- block_height:
545334
- size:
221 bytes
- fees:
225
- confirmed:
2018-10-11T16:00:06Z
Actual fee rate for tx2 is 225 / 221 ~= 1.0181 (satoshi / byte).
Time taken for mining + 6-block-deep confirmation ~= 6 hours.
The estimated delay for a transaction with this fee rate was:
2-11 blocks (10-180 minutes) for a fee rate of 1-2 satoshis / byte.
Creation of tx1 (final run of the script - the final result has been confirmed to be identical to the original transaction that was actually broadcast):
aineko:work stjohnpiano$ ./create_transaction.py
### START CREATION OF BITCOIN TRANSACTION
- Fee type: fee_rate
- Fee rate: 1.0 (satoshi / byte)
- Number of inputs (i.e. as-yet-unspent outputs): 1
- Number of outputs: 1
- Change address: 13xPBB175FtPbPQ84iB8KuawaVy3mHrady
- Amount to be sent to the change address: 0.002
- Input addresses, with total-value-to-be-sent:
-- 138obEZkdWaWEQ4x8ZAYw4MybHSZtX1Nam: 0.00242000
- Output addresses, with total-value-to-be-received:
-- 13xPBB175FtPbPQ84iB8KuawaVy3mHrady: 0.00200000
- Total value of all inputs: 0.00242000
- Total value of all outputs: 0.00200000
- Total value of all inputs is greater than total value of all outputs.
-- Some input value has not been assigned to an output.
-- Extra value: 0.00042000
-- This extra value will be sent to the change address.
-- Change address: 13xPBB175FtPbPQ84iB8KuawaVy3mHrady
-- New amount to be sent to change address: 0.00242000
- Estimated transaction size: 223 bytes
- Fee rate: 1.0 (satoshi / byte)
- Calculate 223 * 1.0 and round up to nearest satoshi.
- Final fee: 223 (satoshi)
- Final fee rate (using estimated transaction size): 1.0000 (satoshi per byte)
- Fee subtracted from amount to be sent to change address.
- New amount to be sent to change address: 241777 (satoshi)
Input 0:
Input (without signature):
- previous_output_hash: 5578d45328a3a5563f4f5a5f201397addb1aa32bf38173b0b854a81c0f9e60e4
- previous_output_index: 08000000
- sequence: ffffffff
- private_key_hex: 00000000000000000000007468655f6d6f74655f696e5f676f6427735f657965
- public_key_hex: 041ad06846fd7cf9998a827485d8dd5aaba9eccc385ba7759a6e9055fbdf90d7513c0d11fe5e5dcfcf8d4946c67f6c45f8e7f7d7a9c254ca8ebde1ffd64ab9dd58
- script_length_scriptPubKey: 19
- scriptPubKey: 76a914176a0e0c2b9b0e77d630712ad301b749102a304488ac
- script_length: None
- scriptSig: None
- address: 138obEZkdWaWEQ4x8ZAYw4MybHSZtX1Nam
- previous_output_index_int: 8
- txid: e4609e0f1ca854b8b07381f32ba31adbad9713205f5a4f3f56a5a32853d47855
Output 0:
Output:
- value: 71b0030000000000
- script_length: 19
- scriptPubKey: 76a9142069a3fae01db74cef12d1d01811afdf6a3e1c2e88ac
- address: 13xPBB175FtPbPQ84iB8KuawaVy3mHrady
- bitcoin_amount: 0.00241777
- satoshi_amount: 241777
- public_key_hash_hex: 2069a3fae01db74cef12d1d01811afdf6a3e1c2e
Transaction (unsigned form):
- version: 01000000
- input_count: 01
- Input:
-- previous_output_hash: 5578d45328a3a5563f4f5a5f201397addb1aa32bf38173b0b854a81c0f9e60e4
-- previous_output_index: 08000000
-- script_length: None
-- scriptSig: None
-- sequence: ffffffff
- output_count: 01
- Output:
-- value: 71b0030000000000
-- script_length: 19
-- scriptPubKey: 76a9142069a3fae01db74cef12d1d01811afdf6a3e1c2e88ac
- block_lock_time: 00000000
Transaction (signable form):
- version: 01000000
- input_count: 01
- Input [to be used to sign this signable form]:
-- previous_output_hash: 5578d45328a3a5563f4f5a5f201397addb1aa32bf38173b0b854a81c0f9e60e4
-- previous_output_index: 08000000
-- script_length_scriptPubKey: 19
-- scriptPubKey: 76a914176a0e0c2b9b0e77d630712ad301b749102a304488ac
-- sequence: ffffffff
- output_count: 01
- Output:
-- value: 71b0030000000000
-- script_length: 19
-- scriptPubKey: 76a9142069a3fae01db74cef12d1d01811afdf6a3e1c2e88ac
- block_lock_time: 00000000
- hash_type_4_byte: 01000000
Transaction (signed form):
- version: 01000000
- input_count: 01
- Input:
-- previous_output_hash: 5578d45328a3a5563f4f5a5f201397addb1aa32bf38173b0b854a81c0f9e60e4
-- previous_output_index: 08000000
-- script_length: 8a
-- scriptSig: 473044022017ece35581a034a4838a577fe438f108cf22764927a1ad197ed379460c0764cd022066723723eff05c10bac9a2f63b3e782e24fde290701c9408f3d1054722adad360141041ad06846fd7cf9998a827485d8dd5aaba9eccc385ba7759a6e9055fbdf90d7513c0d11fe5e5dcfcf8d4946c67f6c45f8e7f7d7a9c254ca8ebde1ffd64ab9dd58
-- sequence: ffffffff
- output_count: 01
- Output:
-- value: 71b0030000000000
-- script_length: 19
-- scriptPubKey: 76a9142069a3fae01db74cef12d1d01811afdf6a3e1c2e88ac
- block_lock_time: 00000000
- hash_type_4_byte: 01000000
Signed transaction:
01000000015578d45328a3a5563f4f5a5f201397addb1aa32bf38173b0b854a81c0f9e60e4080000008a473044022017ece35581a034a4838a577fe438f108cf22764927a1ad197ed379460c0764cd022066723723eff05c10bac9a2f63b3e782e24fde290701c9408f3d1054722adad360141041ad06846fd7cf9998a827485d8dd5aaba9eccc385ba7759a6e9055fbdf90d7513c0d11fe5e5dcfcf8d4946c67f6c45f8e7f7d7a9c254ca8ebde1ffd64ab9dd58ffffffff0171b00300000000001976a9142069a3fae01db74cef12d1d01811afdf6a3e1c2e88ac0000000001000000
### END CREATION OF BITCOIN TRANSACTION
Creation of tx2 (final run of the script - the final result has been confirmed to be identical to the original transaction that was actually broadcast):
aineko:work stjohnpiano$ ./create_nonstandard_transaction.py
### START CREATION OF NONSTANDARD BITCOIN TRANSACTION
- Fee type: fee
- Fee: 225 (satoshi)
- Number of inputs (i.e. as-yet-unspent outputs): 1
- Number of outputs: 1
- Change address: 328cTqexYnQRbN5Dgs12D89sYiPPvtWVbF
- Amount to be sent to the change address: 0.00241000
- Input addresses, with total-value-to-be-sent:
-- 13xPBB175FtPbPQ84iB8KuawaVy3mHrady: 0.00241777
- Output addresses, with total-value-to-be-received:
-- 328cTqexYnQRbN5Dgs12D89sYiPPvtWVbF: 0.00241000
- Total value of all inputs: 0.00241777
- Total value of all outputs: 0.00241000
- Total value of all inputs is greater than total value of all outputs.
-- Some input value has not been assigned to an output.
-- Extra value: 0.00000777
-- This extra value will be sent to the change address.
-- Change address: 328cTqexYnQRbN5Dgs12D89sYiPPvtWVbF
-- New amount to be sent to change address: 0.00241777
- Estimated transaction size: 221 bytes
- Final fee: 225 (satoshi)
- Final fee rate (using estimated transaction size): 1.0181 (satoshi per byte)
- Fee subtracted from amount to be sent to change address.
- New amount to be sent to change address: 241552 (satoshi)
Input 0:
Input (without signature):
- previous_output_hash: 4d33e0192cd6ee93d2f5c18a4103b918da07f22305a85ec533a0a0cb4c225e74
- previous_output_index: 00000000
- sequence: ffffffff
- private_key_hex: 0000000000000000000000000000000000726f6164736964655f7069636e6963
- public_key_hex: 04b60eb0f9211eb7778ef54963b0a55c986d69f4dee4642ef7e7a16fc16c85eeda9a764032b5836c43e05ba520edd2a9040b9409b74b27da5b2fb1bfa48687b1a1
- script_length_scriptPubKey: 19
- scriptPubKey: 76a9142069a3fae01db74cef12d1d01811afdf6a3e1c2e88ac
- script_length: None
- scriptSig: None
- address: 13xPBB175FtPbPQ84iB8KuawaVy3mHrady
- previous_output_index_int: 0
- txid: 745e224ccba0a033c55ea80523f207da18b903418ac1f5d293eed62c19e0334d
Output 0:
Output:
- value: 90af030000000000
- script_length: 17
- script: a91404d73b326fb56e5d09e5f0b4bb3b83b7ff5fb05f87
- address: 328cTqexYnQRbN5Dgs12D89sYiPPvtWVbF
- bitcoin_amount: 0.00241552
- satoshi_amount: 241552
- redeem_script_hash_hex: 04d73b326fb56e5d09e5f0b4bb3b83b7ff5fb05f
Nonstandard Transaction (unsigned form):
- version: 01000000
- input_count: 01
- Input:
-- previous_output_hash: 4d33e0192cd6ee93d2f5c18a4103b918da07f22305a85ec533a0a0cb4c225e74
-- previous_output_index: 00000000
-- script_length: None
-- scriptSig: None
-- sequence: ffffffff
- output_count: 01
- Output [P2SH]:
-- value: 90af030000000000
-- script_length: 17
-- script: a91404d73b326fb56e5d09e5f0b4bb3b83b7ff5fb05f87
- block_lock_time: 00000000
Nonstandard Transaction (signable form):
- version: 01000000
- input_count: 01
- Input [to be used to sign this signable form]:
-- previous_output_hash: 4d33e0192cd6ee93d2f5c18a4103b918da07f22305a85ec533a0a0cb4c225e74
-- previous_output_index: 00000000
-- script_length_scriptPubKey: 19
-- scriptPubKey: 76a9142069a3fae01db74cef12d1d01811afdf6a3e1c2e88ac
-- sequence: ffffffff
- output_count: 01
- Output [P2SH]:
-- value: 90af030000000000
-- script_length: 17
-- script: a91404d73b326fb56e5d09e5f0b4bb3b83b7ff5fb05f87
- block_lock_time: 00000000
- hash_type_4_byte: 01000000
Nonstandard Transaction (signed form):
- version: 01000000
- input_count: 01
- Input:
-- previous_output_hash: 4d33e0192cd6ee93d2f5c18a4103b918da07f22305a85ec533a0a0cb4c225e74
-- previous_output_index: 00000000
-- script_length: 8a
-- scriptSig: 47304402201c26a71ec1993096081a9d8a39ad54613e428a4f28632a9bcb978a4321dfae4a0220546b3e0326be273ce3f2f780e269bec65346ed56fa99a0b65b42dd2f73782acf014104b60eb0f9211eb7778ef54963b0a55c986d69f4dee4642ef7e7a16fc16c85eeda9a764032b5836c43e05ba520edd2a9040b9409b74b27da5b2fb1bfa48687b1a1
-- sequence: ffffffff
- output_count: 01
- Output [P2SH]:
-- value: 90af030000000000
-- script_length: 17
-- script: a91404d73b326fb56e5d09e5f0b4bb3b83b7ff5fb05f87
- block_lock_time: 00000000
- hash_type_4_byte: 01000000
Signed transaction:
01000000014d33e0192cd6ee93d2f5c18a4103b918da07f22305a85ec533a0a0cb4c225e74000000008a47304402201c26a71ec1993096081a9d8a39ad54613e428a4f28632a9bcb978a4321dfae4a0220546b3e0326be273ce3f2f780e269bec65346ed56fa99a0b65b42dd2f73782acf014104b60eb0f9211eb7778ef54963b0a55c986d69f4dee4642ef7e7a16fc16c85eeda9a764032b5836c43e05ba520edd2a9040b9409b74b27da5b2fb1bfa48687b1a1ffffffff0190af03000000000017a91404d73b326fb56e5d09e5f0b4bb3b83b7ff5fb05f870000000001000000
### END CREATION OF BITCOIN TRANSACTION
Further Work
The P2SH outputScript is 3 single-byte opcodes + a 20-byte redeemScriptHash i.e. 23 bytes. Will a nonstandard transaction with a single P2PKH input and a single P2SH output always be 221 bytes long?
In contrast, the P2PKH scriptPubKey is 5 single-byte opcodes + a 20-byte publicKeyHash i.e. 25 bytes. Will a standard transaction with a single P2PKH input and a single P2PKH output always be 223 bytes long?
Add an "Actual transaction size" output (in bytes) to
create_transaction.py
and
create_nonstandard_transaction.py,
to be displayed after the transaction has been signed and its signature has been embedded into it.
- Also a corresponding "Actual fee rate" output (in satoshi / byte).
Add a control for "scriptPubKey of unspent output" to
create_transaction.py
and
create_nonstandard_transaction.py,
which should be confirmed to be identical to the scriptPubKey derived from the corresponding private_key_hex control.
- The scriptPubKey of an unspent output should be extracted from the previous transaction that contains this unspent output.
- This is a check that ensures that any strangeness concerning the unspent output's scriptPubKey-in-the-blockchain will be caught at the start of the script, rather than when the transaction is broadcast and never mined.
Need a way to look up an output by its index in a previous transaction that is better than reading the transaction and manually counting outputs.
Construct and broadcast a standard transaction with two inputs and one output.
- test whether, within the transaction-in-signable-form, the input-not-being-used-to-sign is removed and its script_length var_int set to 0x00.
Construct and broadcast a standard transaction with one input and two outputs.
Construct and broadcast a standard transaction with two inputs and two outputs.
In transaction.py, in the class Transaction, write a validate_self() instance method, that will all the properties of a transaction prior to signing (and the properties of all its inputs and outputs). It should be called prior to signing.
- Also do this for
nonstandard_transaction.py.
In transaction.py, in the class Transaction, in the method get_signed_form(), check that the transaction has actually been signed (i.e. that every input has an associated scriptSig). This check should be performed prior to constructing and returning the signed_form.
- Also add this check to the method
get_description_of_signed_form().
- Also add both these checks to
nonstandard_transaction.py.
In create_transaction.py, validate the length domain of the random_value in the preparation phase, long before transaction signing is performed.
- Also do this for
create_nonstandard_transaction.py.
In create_transaction.py, how to handle the case where multiple transaction outputs have the change_address? Currently, the change address is more accurately a single output sent to that address. If two transaction outputs have the change address, the transaction fee can only be subtracted from one of these outputs. The fee could be subtracted from the total of all outputs sent to the change address. Alternatively, raise an error if multiple outputs have the same address.
- Same problem exists in
create_nonstandard_transaction.py.
Construct and broadcast a transaction that sends two outputs to the same address.
In bitcoin_functions.py, in the function
private_key_hex_to_public_key_hex(input)
investigate whether:
- verifying_key_bytes is always 64 bytes long.
- any padding / checking is done explicitly and carefully in the ECDSA library.
In bitcoin_functions.py, in the function
int_to_var_int(input),
expand it so that it can handle var_ints that are larger than 1 byte.
In bitcoin_functions.py, in the function
satoshi_to_bitcoin(input):
- how many satoshi can actually exist, in total? I.e. what should be the maximum permissible input value?
In bitcoin_functions.py, in the function
bitcoin_address_to_public_key_hash_hex(input)
confirm that first byte in the result is 0x00.
In bitcoin_functions.py, write a function called
validate_string_is_digits().
This can then be used to validate the fee control value (if fee is used rather than fee_rate) in
create_transaction.py.
- Also add this validation check in
create_nonstandard_transaction.py.
Construct and broadcast a nonstandard transaction with one input and two outputs, one P2PKH output and one P2SH output.
In nonstandard_bitcoin_functions.py, in the function
validate_p2sh_address(input),
are p2sh addresses all the same length? if so, run a validate_string_length() on the input.
In create_transaction.py, add a validate_txid() check. On second thought, this should actually just be a validate_hex_length() check. txids are 32-byte little-endian hashes and should be able to have any value.
- also do this for
create_nonstandard_transaction.py.
In create_transaction.py, add a validate_address() check.
In create_nonstandard_transaction.py, add a
validate_nonstandard_address()
check.
- This should check for different types of addresses and call the appropriate validation functions.
Construct and broadcast a transaction that spends two inputs from the same address.
Project Log
Plan of action:
- Generate an address: the "source" address.
- Generate a second address: the "target" address.
- Look up my current receiving address on my LocalBitcoins account: the "final" address. This will be a nonstandard address.
- Transfer some bitcoin from my LocalBitcoins account to the source address. I'll call this tx0. "tx" = transaction.
- Look up recent mining fees.
- Construct a standard raw Bitcoin transaction that moves the available bitcoin from the source address to the target address. Be careful to include a mining fee. This is tx1.
- Sign the transaction using the private key of the source address and ECDSA cryptography. Before signing, the transaction must be altered slightly to get the transaction-in-signable-form.
- Embed the transaction signature into the transaction.
- Broadcast the transaction.
- Confirm that the transaction has been mined (included in a block in the Bitcoin blockchain). This verifies that the transaction was valid (i.e. in the correct format, with a mathematically valid signature).
- Look up recent mining fees.
- Construct a second nonstandard raw Bitcoin transaction that moves the available bitcoin from the target address to the final address. Be careful to include a mining fee. This is tx2.
- Sign the transaction using the private key of the target address and ECDSA cryptography. Before signing, the transaction must be altered slightly to get the transaction-in-signable-form.
- Embed the transaction signature into the transaction.
- Broadcast the transaction.
- Confirm that the transaction has been mined (included in a block in the Bitcoin blockchain). This verifies that the transaction was valid (i.e. in the correct format, with a mathematically valid signature).
My work computer: Aineko, a 2008 Macbook running Mac OS X 10.6.8 Snow Leopard.
aineko:work stjohnpiano$ python --version
Python 2.7.13
Create a project directory:
creating_and_signing_a_standard_raw_bitcoin_transaction_2
In the project directory, create a work directory named "work".
Step 1) Generate an address: the "source" address.
Browse to the previous project Reading and verifying a standard raw bitcoin transaction, go to the Downloadable Assets section, download the following assets, and place them in the work directory.
- bjorn_edstrom_ripemd160.py
- ecdsa-0.10.tar.gz
- pypy_sha256.py
Browse to the previous project Verifying a signed deed of the GPG 1.4.10 source code, go to the Downloadable Assets section, download the following asset, and place it in the work directory.
- generate_bitcoin_address_2.py
generate_bitcoin_address_2.py
relies on the other three assets.
Open a terminal. Change directory to the work directory.
aineko:work stjohnpiano$ ls -1
bjorn_edstrom_ripemd160.py
ecdsa-0.10.tar.gz
generate_bitcoin_address_2.py
pypy_sha256.py
Unpack the zipped tape archive file ecdsa-0.10.tar.gz. The following command can be used to do this:
tar -zxvf ecdsa-0.10.tar.gz
aineko:work stjohnpiano$ ls -1
bjorn_edstrom_ripemd160.py
ecdsa-0.10
ecdsa-0.10.tar.gz
generate_bitcoin_address_2.py
pypy_sha256.py
The unpacking has produced a new directory named ecdsa-0.10. ecdsa-0.10 should contain a directory named ecdsa. Copy the ecdsa directory to the work directory. The ecdsa library can now be imported from a Python script in the work directory.
Create a temporary archive directory "tmp" in the work directory.
Move:
- ecdsa-0.10
- ecdsa-0.10.tar.gz
to tmp.
Next: I'm going to rewrite
generate_bitcoin_address_2.py
into
generate_bitcoin_address_3.py
and
bitcoin_functions.py.
Use the command below to make the script
generate_bitcoin_address_3.py
executable.
aineko:work stjohnpiano$ chmod 700 generate_bitcoin_address_3.py
[development occurs here]
Ok. It's ready. I'll use the raw bytes private key
"the_mote_in_god's_eye"
for the source address.
aineko:work stjohnpiano$ ls -1
bitcoin_functions.py
bitcoin_functions.pyc
bjorn_edstrom_ripemd160.py
bjorn_edstrom_ripemd160.pyc
ecdsa
generate_bitcoin_address_2.py
generate_bitcoin_address_3.py
pypy_sha256.py
pypy_sha256.pyc
aineko:work stjohnpiano$ ./generate_bitcoin_address_3.py
### START GENERATION OF BITCOIN ADDRESS
Private key (raw bytes): the_mote_in_god's_eye
Private key (hex bytes): 7468655f6d6f74655f696e5f676f6427735f657965
Private key (32 hex bytes): 00000000000000000000007468655f6d6f74655f696e5f676f6427735f657965
Private key (WIF): 5HpHagT65TZzG1PH41Hd3yo15N3JqZyeumt1YxQeHYAsFab3Qss
Bitcoin address: 138obEZkdWaWEQ4x8ZAYw4MybHSZtX1Nam
### END GENERATION OF BITCOIN ADDRESS
Source address:
- Private key (raw bytes):
"the_mote_in_god's_eye"
- Bitcoin address:
138obEZkdWaWEQ4x8ZAYw4MybHSZtX1Nam
Step 2) Generate a second address: the "target" address.
I'll use the raw bytes private key
"roadside_picnic"
for the target address.
aineko:work stjohnpiano$ ./generate_bitcoin_address_3.py
### START GENERATION OF BITCOIN ADDRESS
Private key (raw bytes): roadside_picnic
Private key (hex bytes): 726f6164736964655f7069636e6963
Private key (32 hex bytes): 0000000000000000000000000000000000726f6164736964655f7069636e6963
Private key (WIF): 5HpHagT65TZzG1PH3CSu63k8Dxn19XHMLQhSCuE8qpbeuFK2mGG
Bitcoin address: 13xPBB175FtPbPQ84iB8KuawaVy3mHrady
### END GENERATION OF BITCOIN ADDRESS
Target address:
- Private key (raw bytes):
"roadside_picnic"
- Bitcoin address:
13xPBB175FtPbPQ84iB8KuawaVy3mHrady
Step 3) Look up my current receiving address on my LocalBitcoins account: the "final" address. This will be a nonstandard address.
Log in to my LocalBitcoins account.
My current receiving address is:
328cTqexYnQRbN5Dgs12D89sYiPPvtWVbF
This is the "final" address.
Current deposit fee for incoming transactions:
0.00015 BTC
Step 4) Transfer some bitcoin from my LocalBitcoins account to the source address. I'll call this tx0. "tx" = transaction.
How much to transfer?
Well, I need pay to two Bitcoin transaction fees + the LocalBitcoins deposit fee, with some extra so as to have room to manoeuvre.
LocalBitcoins also charges a sending fee of 0.00005 bitcoin. This will be deducted from my LocalBitcoins wallet, not from the transaction amount.
I'll look up the current transaction fee.
Browse to:
bitcoinfees.earn.com
The fees are displayed in satoshis per byte of transaction data. A satoshi is 0.00000001 bitcoin.
I'll use the term "fee rate" to indicate the cost of a transaction in satoshis / byte.
I'll use the term "fee" to indicate the cost of a transaction in satoshis.
I'll assume that I'll use a high fee rate during tx1 and tx2, in order to have slack, although I will attempt to pay a smaller one.
A fee rate of 60 satoshis / byte looks quite high.
I can roughly assume that a standard transaction with 1 input and 1 output will be about 225 bytes.
A fee will therefore be 225 bytes * 60 satoshis / byte = 225 * 60 = 13500 satoshis.
I'll do 2 transactions. 2 * 13500 = 27000 satoshis.
Add the LocalBitcoins deposit fee, which is:
0.00015 BTC = 0.00015000 BTC = 15000 satoshis
So:
27000 satoshis + 15000 satoshis = 42000 satoshis
42000 satoshis = 0.00042 bitcoin
I'll add 0.002 bitcoin extra (about $13 at current prices).
So:
0.00042 + 0.002 = 0.00242 bitcoin
In my LocalBitcoins account, transfer 0.00242 bitcoin to the source address:
138obEZkdWaWEQ4x8ZAYw4MybHSZtX1Nam
LocalBitcoins Transaction fee: 0.00005 BTC
Browse to LocalBitcoins / Wallet / Transactions.
Shows "Pending".
Wait. Refresh.
Shows txid:
e4609e0f1ca854b8b07381f32ba31adbad9713205f5a4f3f56a5a32853d47855
Browse to:
live.blockcypher.com
Search for this txid.
It exists in the blockcypher database and has 1 confirmation.
[some time passes]
Refresh. It now has 6+ confirmations.
Step 5) Look up recent mining fees.
Browse to:
bitcoinfees.earn.com
I see that a transaction with a fee rate of 1-2 satoshis / byte will probably be mined. Estimated delay = 3-23 blocks (25-300 minutes).
Step 6) Construct a standard raw Bitcoin transaction that moves the available bitcoin from the source address to the target address. Be careful to include a mining fee. This is tx1.
Excerpts from the previous project Creating and signing a standard raw Bitcoin transaction:
This is my current working definition of the raw format of a standard Bitcoin transaction.
Standard transaction:
- version: 4 bytes (little-endian)
- input_count: (var_int)
- [for each input:]
-- previous_output_hash (aka "txid"): 32 bytes (big-endian)
-- previous_output_index: 4 bytes (little-endian)
-- script_length: (var_int)
-- scriptSig [see below]
-- sequence: 4 bytes (little-endian)
- output_count: (var_int)
- [for each output:]
-- value: 8 bytes (little-endian)
-- script_length: (var_int)
-- scriptPubKey [see below]
- block lock time: 4 bytes
scriptSig:
- PUSHDATA: 47 (approximately)
- [derived property] PUSHDATA decimal value: 71 (approximately)
- signature_data: 71 bytes (approximately)
- PUSHDATA: 41
- [derived property] PUSHDATA decimal value: 65
- public_key_data: 65 bytes ("04", 32-byte big-endian X value, 32-byte big-endian Y value)
scriptPubKey:
- OP_DUP: 76
- OP_HASH160: a9
- PUSHDATA: 14
- [derived property] PUSHDATA decimal value: 20
- public_key_hash: 20 bytes (big-endian)
- OP_EQUALVERIFY: 88
- OP_CHECKSIG: ac
[...]
- A var_int ("variable integer") value is used to store a number of variable byte length. It can be used to indicate the length in bytes of the next item, e.g. script_length, which is a number equal to the number of bytes in scriptSig. It can also be used to store a number, e.g. input_count (i.e. number of input items). A one-byte var_int has a maximum value of 0xFC (252 in decimal).
- The transaction fee is stored implicitly in a transaction as the difference between the sum of the input values and the sum of the output values.
[...]
- My working definition of a standard address is: Pay-To-Public-Key-Hash (P2PKH) where the public key was not compressed before being hashed.
- My working definition of a standard input is: an unspent output stored in a standard address.
- The inputs for a new transaction are a set of as-yet-unspent outputs from previous transactions. An input transfers value out of a previous address and an output transfers value into a new address. (It would perhaps be more accurate to say that value is "associated with" an address, rather than that the value is "contained within" it.) A transaction is a collection of inputs and outputs, formatted into a single item.
- scriptPubKey is the "destination" of the bitcoin in a transaction and contains the hash of a public key. The corresponding private key is required in order to spend this bitcoin later.
- The previous_output_hash is:
-- the hash of the previous transaction that:
--- contains the unspent output that:
---- will be used as an input in this new transaction
- The previous_output_index is the implicit index (starting at 0) of the unspent output in the list of unspent outputs in the previous block.
- The domain of the opcode PUSHDATA is 1-75 in decimal (0x01-0x4b in hex).
[...]
Gathering data in order to construct a new transaction
Transaction data that should be originally chosen when the transaction is being planned:
- [for each output:]
-- value: 8 bytes (little-endian)
Notes:
- Include at least one "change address" output. Inputs should be added as necessary to achieve the desired output values. The sum of the inputs may be greater than the sum of the desired output values, i.e. some bitcoin may be left over. Send this left-over bitcoin to the change address output. Include an additional input that is specifically for paying the as-yet-unknown transaction fee. Send the bitcoin from this input to the change address output.
Transaction data that can be known immediately:
- version: 4 bytes (little-endian) = 01 00 00 00
- [for each input:]
-- sequence: 4 bytes (little-endian) = ff ff ff ff
- block lock time: 4 bytes = 00 00 00 00
- hash type: 4 bytes (little-endian) = 01 00 00 00
- hash type: 1 byte = 01
Notes:
- I think that the version is always hardcoded as '1' for the main Bitcoin network. In 4 little-endian hex bytes, this is 01 00 00 00.
- [Hearsay] Sequence can always be set to 0xffffffff. It is a nonfunctional legacy feature that was designed to allow multiple signers to agree to update a transaction.
- [Hearsay] Block lock time can always be set to 0x00000000. It was designed to ask miners not to mine a transaction until a specified block height is reached. A block lock time of 0x00000000 specifies that the transaction can be mined at any block height after 0.
- The 4-byte hash_type is appended to the transaction before signing.
- The 1-byte hash_type is appended to the transaction signature.
Transaction data that can be derived from the destination addresses:
- output_count: (var_int)
- [for each output:]
-- script_length: (var_int)
-- scriptPubKey [see below]
Notes:
- The destination addresses must be standard addresses (i.e. single-signature Pay-To-Public-Key-Hash (P2PKH)). For other destination addresses (e.g. P2SH multi-signature), the scriptPubKey format below will be different.
scriptPubKey:
- OP_DUP: 76
- OP_HASH160: a9
- PUSHDATA: 14
- [derived property] PUSHDATA decimal value: 20
- public_key_hash: 20 bytes (big-endian)
- OP_EQUALVERIFY: 88
- OP_CHECKSIG: ac
Transaction data that can be acquired by analysing previous transactions on the blockchain:
- input_count: (var_int)
- [for each input:]
-- previous_output_hash (aka "txid"): 32 bytes (big-endian)
-- previous_output_index: 4 bytes (little-endian)
-- scriptPubKey of relevant unspent output from previous transaction
Notes:
- "scriptPubKey of relevant unspent output from previous transaction" is used when signing a transaction to make a signature for a particular input. It can be derived from the input address. However, it should also be found within the relevant unspent output in a previous transaction, and then compared against the version derived from the input address as a sanity check.
Transaction data that must be calculated after all other data has been assembled:
-- script_length: (var_int)
-- scriptSig [see below]
scriptSig:
- PUSHDATA: 46 (approximately)
- [derived property] PUSHDATA decimal value: 70 (approximately)
- signature_data: 70 bytes (approximately)
- PUSHDATA: 41
- [derived property] PUSHDATA decimal value: 65
- public_key_data: 65 bytes ("04", 32-byte big-endian X value, 32-byte big-endian Y value)
Notes:
- public_key_data in the scriptSig can be derived from the private key of the input address.
- signature_data in the scriptSig is the signature created by signing the entire transaction using the private key of one particular input address.
I'm going to define:
- previous_output_hash = big-endian 32-byte double-SHA256 hash of a previous transaction.
- txid = little-endian form of the previous_output_hash
Next: Gather data in order to construct new transaction tx1.
tx1 will transfer balance from source address to target address, minus a transaction fee.
Source address:
- Private key (raw bytes):
"the_mote_in_god's_eye"
- Bitcoin address:
138obEZkdWaWEQ4x8ZAYw4MybHSZtX1Nam
- Current balance: 0.00242 BTC
Target address:
- Private key (raw bytes):
"roadside_picnic"
- Bitcoin address:
13xPBB175FtPbPQ84iB8KuawaVy3mHrady
Transaction data that should be originally chosen when the transaction is being planned:
- [for each output:]
-- value: 8 bytes (little-endian)
-- value: 8 bytes (little-endian)
We have 1 output. The output value will be the single input (with a value of 0.00242 BTC = 242000 satoshis) minus the fee.
Assuming a transaction size of 225 bytes, and an initial fee rate of 1 byte / satoshi, the the transaction fee is:
225 bytes * 1 byte / satoshi = 225 satoshis.
The output value = 0.242000 - 225 satoshis = 241775 satoshis
I need to calculate the 8-byte little-endian form of 241775.
Hm. I'll write a new script:
- create_transaction.py
Its inputs will be the transaction input information as specified in the excerpt above.
Any general bitcoin-related functions needed for this script will go into
- bitcoin_functions.py
[development occurs here]
I'll also write transaction.py, which will hold classes for:
- standard transaction
- standard output
- standard input
[development occurs here]
Hm. Ok. Next, need to look at:
Transaction data that can be acquired by analysing previous transactions on the blockchain:
- input_count: (var_int)
- [for each input:]
-- previous_output_hash (aka "txid"): 32 bytes (big-endian)
-- previous_output_index: 4 bytes (little-endian)
-- scriptPubKey of relevant unspent output from previous transaction
Notes:
- "scriptPubKey of relevant unspent output from previous transaction" is used when signing a transaction to make a signature for a particular input. It can be derived from the input address. However, it should also be found within the relevant unspent output in a previous transaction, and then compared against the version derived from the input address as a sanity check.
Ok. Need to look up tx0 in a blockchain information service.
Its txid is:
e4609e0f1ca854b8b07381f32ba31adbad9713205f5a4f3f56a5a32853d47855
This txid is the little-endian form of the previous_output_hash for this input.
Browse to:
live.blockcypher.com
Search for this txid.
Result:
live.blockcypher.com/btc/tx/e4609e0f1ca854b8b07381f32ba31adbad9713205f5a4f3f56a5a32853d47855
Search on page for source address.
138obEZkdWaWEQ4x8ZAYw4MybHSZtX1Nam
Hm. Not enough information.
Click button "API Call".
Ah. Result = a raw text dump of the transaction. Search for source address.
I find:
{
"value": 242000,
"script": "76a914176a0e0c2b9b0e77d630712ad301b749102a304488ac",
"addresses": [
"138obEZkdWaWEQ4x8ZAYw4MybHSZtX1Nam"
],
"script_type": "pay-to-pubkey-hash"
}
Hm. Well the "script" value is the scriptPubKey I need for the signable form of tx1.
I still need to know the index of this output in the transaction. This index is implicit and zero-indexed.
There are 39 outputs. Copy them into a text file. Search for the source address, then count up from there until the top output is reached.
I count 8, not including the source address's output. Starting from the top, from 0, this gives the source address' output an index of 8.
In order to get the previous_output_hash (which is big-endian), I need to reverse the byte order of the txid (which is little-endian).
- txid:
e4609e0f1ca854b8b07381f32ba31adbad9713205f5a4f3f56a5a32853d47855
aineko:work stjohnpiano$ python
Python 2.7.13 (default, Dec 18 2016, 05:35:59)
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import bitcoin_functions as bf
>>> txid = "e4609e0f1ca854b8b07381f32ba31adbad9713205f5a4f3f56a5a32853d47855"
>>> previous_output_hash = bf.reverse_hex_order(txid)
>>> previous_output_hash
>>> '5578d45328a3a5563f4f5a5f201397addb1aa32bf38173b0b854a81c0f9e60e4'
>>>
- previous_output_hash:
5578d45328a3a5563f4f5a5f201397addb1aa32bf38173b0b854a81c0f9e60e4
- previous_output_index: 8
- scriptPubKey of relevant unspent output from previous transaction:
76a914176a0e0c2b9b0e77d630712ad301b749102a304488ac
Also, input address (source address):
138obEZkdWaWEQ4x8ZAYw4MybHSZtX1Nam
Hm. I'll make a input.create() function that takes a txid as input.
[development occurs here]
Ok. Finished.
[various attempts to sign and broadcast the transaction occur here and are omitted.]
create_transaction.py
#!/opt/local/bin/python | |
# dependencies | |
import ecdsa | |
import bitcoin_functions as bf | |
import transaction as tr | |
def main(): | |
##### START CONTROLS | |
random_value = "The Face of God dhcmrlchtdj" # Every ECDSA signature (one for each input) requires entropy. | |
# random_value_type acceptable values: ["raw_bytes", "hex_bytes"] | |
random_value_type = "raw_bytes" | |
input = { | |
"txid": "e4609e0f1ca854b8b07381f32ba31adbad9713205f5a4f3f56a5a32853d47855", | |
"previous_output_index": 8, | |
"private_key_hex": "7468655f6d6f74655f696e5f676f6427735f657965", | |
"bitcoin_amount": "0.00242", | |
} | |
output = { | |
"address": "13xPBB175FtPbPQ84iB8KuawaVy3mHrady", | |
"bitcoin_amount": "0.002", | |
} | |
change_address = "13xPBB175FtPbPQ84iB8KuawaVy3mHrady" | |
# note: the fee will be subtracted from the amount that is being sent to the change address. | |
fee_rate = 1 # satoshi / byte | |
##### END CONTROLS | |
##### START SETTINGS | |
random_value_type_options = ["raw_bytes", "hex_bytes"] | |
##### END SETTINGS | |
print "\n### START CREATION OF BITCOIN TRANSACTION\n" | |
output = tr.StandardOutput.create_from_address( | |
address = output["address"], | |
bitcoin_amount = output["bitcoin_amount"], | |
) | |
input = tr.StandardInput.create_from_txid( | |
txid = input["txid"], | |
previous_output_index = input["previous_output_index"], | |
private_key_hex = input["private_key_hex"], | |
bitcoin_amount = input["bitcoin_amount"], | |
) | |
inputs = [input] | |
outputs = [output] | |
random_values = [random_value] | |
print "- Change address: %s" % change_address | |
# future: at this position, | |
# - print out input addresses and the total value being spent from each of those addresses | |
# - print out output addresses and the total value being sent to each of those addresses | |
# confirm that total input value >= total output value | |
input_total = 0 # in satoshis | |
output_total = 0 # in satoshis | |
for input in inputs: | |
input_total += input.satoshi_amount | |
for output in outputs: | |
output_total += output.satoshi_amount | |
print "- Inputs: %d" % len(inputs) | |
print "- Total input value: %s" % bf.satoshi_to_bitcoin(input_total) | |
print "- Outputs: %d" % len(outputs) | |
print "- Total output value: %s" % bf.satoshi_to_bitcoin(output_total) | |
if not (input_total >= output_total): | |
message = "Total input value must be greater than or equal to total output value." | |
message += "\nTotal input value: %s" % bf.satoshi_to_bitcoin(input_total) | |
message += "\nTotal output value: %s" % bf.satoshi_to_bitcoin(output_total) | |
stop(message) | |
print "- Total input value is greater than or equal to total output value." | |
# check random values | |
if random_value_type not in random_value_type_options: | |
message = "random_value_type not in random_value_type_options list." | |
message += "\nrandom_value_type_options: %s" % str(random_value_type_options) | |
message += "\nrandom_value_type: %s" % random_value_type | |
stop(message) | |
if random_value_type == "raw_bytes": | |
random_values = [bf.bytes_to_hex(value) for value in random_values] | |
# change_address: | |
# need to: | |
# - find the change_output corresponding to the change_address. | |
# - send any extra input value to the change_output. | |
# - calculate approximate fee and subtract fee from value in change_output | |
# find the change_output corresponding to the change_address. | |
change_output = None | |
for output in outputs: | |
if output.address == change_address: | |
change_output = output | |
break | |
if change_output == None: | |
message = "There must be a change address." | |
stop(message) | |
# - send any extra input value to the change_output. | |
if input_total > output_total: | |
# these amounts are in satoshis. | |
extra = input_total - output_total | |
new_amount = change_output.satoshi_amount + extra | |
change_output.set_satoshi_amount(new_amount) | |
print "- The inputs contain %d satoshis that have not been assigned to an output. The extra satoshis have been assigned to the change_address." % extra | |
# validate fee_rate | |
if fee_rate < 0: | |
#validate_int(fee_rate) # fee_rate could be a float. | |
message = "Fee rate (value=%s) must be positive." % str(fee_rate) | |
stop(message) | |
# estimate size of transaction | |
input_count_size = bf.hex_len(bf.int_to_var_int(len(inputs))) | |
inputs_size = len(inputs) * ( \ | |
32 + # previous_output_hash (32 bytes) | |
4 + # previous_output_index (4 bytes) | |
1 + # script_length (1-byte var_int) | |
138 + # byte length of scriptSig [approximately] | |
4 # sequence (4 bytes) | |
) | |
output_count_size = bf.hex_len(bf.int_to_var_int(len(outputs))) | |
outputs_size = len(outputs) * ( \ | |
8 + # value | |
1 + # script_length (1-byte var_int) | |
25 # byte length of a standard scriptPubKey | |
) | |
estimated_tran_size = ( | |
4 + # version (4 bytes) | |
input_count_size + # input_count | |
inputs_size + # byte length of concatenated inputs | |
output_count_size + # output_count | |
outputs_size + # byte length of concatenated outputs | |
4 # block lock time (4 bytes) | |
) | |
# calculate approximate fee | |
fee = estimated_tran_size * fee_rate # fee is in satoshis | |
print "- Fee rate: %s satoshis / byte" % str(fee_rate) | |
print "- Estimated transaction size: %d bytes" % estimated_tran_size | |
print "- Calculated fee: %d satoshis" % fee | |
if fee > change_output.satoshi_amount: | |
message = "The fee is greater than the satoshi amount that is assigned to the change address." | |
message += "\nFee (satoshis): %d" % fee | |
message += "\nSatoshi amount assigned to change address: %d" % change_output.satoshi_amount | |
stop(message) | |
# subtract fee from value assigned to change_output | |
original_amount = change_output.satoshi_amount | |
new_amount = change_output.satoshi_amount - fee | |
change_output.set_satoshi_amount(new_amount) | |
print "- Fee (%d satoshis) subtracted from satoshi value (%d satoshis) assigned to change address." % (fee, original_amount) | |
print "- New amount to be sent to change address: %d satoshis" % change_output.satoshi_amount | |
tran = tr.StandardTransaction.create(inputs, outputs) | |
#print "" | |
#print tran.outputs[0].get_description() | |
#print tran.inputs[0].get_description() | |
print "" | |
print tran.get_description() | |
print "" | |
print tran.get_description_of_signable_form(input_index=0) | |
tran.sign(random_values) | |
print "" | |
print tran.get_description_of_signed_form() | |
print "" | |
print "Signed transaction:" | |
print tran.get_signed_form() | |
print "\n### END CREATION OF BITCOIN TRANSACTION\n" | |
def stop(message): | |
# raise an Exception to get a traceback. | |
raise Exception("\n\nERROR: %s\n" % message) | |
if __name__ == '__main__': main() |
Earlier, I ran this command to make the script
create_transaction.py
executable.
aineko:work stjohnpiano$ chmod 700 create_transaction.py
Run the script.
aineko:work stjohnpiano$ ./create_transaction.py
### START CREATION OF BITCOIN TRANSACTION
- Change address: 13xPBB175FtPbPQ84iB8KuawaVy3mHrady
- Inputs: 1
- Total input value: 0.00242000
- Outputs: 1
- Total output value: 0.00200000
- Total input value is greater than or equal to total output value.
- The inputs contain 42000 satoshis that have not been assigned to an output. The extra satoshis have been assigned to the change_address.
- Fee rate: 1 satoshis / byte
- Estimated transaction size: 223 bytes
- Calculated fee: 223 satoshis
- Fee (223 satoshis) subtracted from satoshi value (242000 satoshis) assigned to change address.
- New amount to be sent to change address: 241777 satoshis
Transaction [Standard] (unsigned form):
- version: 01000000
- input_count: 01
- Input:
-- previous_output_hash: 5578d45328a3a5563f4f5a5f201397addb1aa32bf38173b0b854a81c0f9e60e4
-- previous_output_index: 08000000
-- script_length: None
-- scriptSig: None
-- sequence: ffffffff
- output_count: 01
- Output:
-- value: 71b0030000000000
-- script_length: 19
-- scriptPubKey: 76a9142069a3fae01db74cef12d1d01811afdf6a3e1c2e88ac
- block_lock_time: 00000000
Transaction [Standard] (signable form):
- version: 01000000
- input_count: 01
- Input [to be used to sign this signable form]:
-- previous_output_hash: 5578d45328a3a5563f4f5a5f201397addb1aa32bf38173b0b854a81c0f9e60e4
-- previous_output_index: 08000000
-- script_length_scriptPubKey: 19
-- scriptPubKey: 76a914176a0e0c2b9b0e77d630712ad301b749102a304488ac
-- sequence: ffffffff
- output_count: 01
- Output:
-- value: 71b0030000000000
-- script_length: 19
-- scriptPubKey: 76a9142069a3fae01db74cef12d1d01811afdf6a3e1c2e88ac
- block_lock_time: 00000000
- hash_type_4_byte: 01000000
Transaction [Standard] (signed form):
- version: 01000000
- input_count: 01
- Input:
-- previous_output_hash: 5578d45328a3a5563f4f5a5f201397addb1aa32bf38173b0b854a81c0f9e60e4
-- previous_output_index: 08000000
-- script_length: 8a
-- scriptSig: 473044022017ece35581a034a4838a577fe438f108cf22764927a1ad197ed379460c0764cd022066723723eff05c10bac9a2f63b3e782e24fde290701c9408f3d1054722adad360141041ad06846fd7cf9998a827485d8dd5aaba9eccc385ba7759a6e9055fbdf90d7513c0d11fe5e5dcfcf8d4946c67f6c45f8e7f7d7a9c254ca8ebde1ffd64ab9dd58
-- sequence: ffffffff
- output_count: 01
- Output:
-- value: 71b0030000000000
-- script_length: 19
-- scriptPubKey: 76a9142069a3fae01db74cef12d1d01811afdf6a3e1c2e88ac
- block_lock_time: 00000000
- hash_type_4_byte: 01000000
Signed transaction:
01000000015578d45328a3a5563f4f5a5f201397addb1aa32bf38173b0b854a81c0f9e60e4080000008a473044022017ece35581a034a4838a577fe438f108cf22764927a1ad197ed379460c0764cd022066723723eff05c10bac9a2f63b3e782e24fde290701c9408f3d1054722adad360141041ad06846fd7cf9998a827485d8dd5aaba9eccc385ba7759a6e9055fbdf90d7513c0d11fe5e5dcfcf8d4946c67f6c45f8e7f7d7a9c254ca8ebde1ffd64ab9dd58ffffffff0171b00300000000001976a9142069a3fae01db74cef12d1d01811afdf6a3e1c2e88ac0000000001000000
### END CREATION OF BITCOIN TRANSACTION
Signed transaction:
01000000015578d45328a3a5563f4f5a5f201397addb1aa32bf38173b0b854a81c0f9e60e4080000008a473044022017ece35581a034a4838a577fe438f108cf22764927a1ad197ed379460c0764cd022066723723eff05c10bac9a2f63b3e782e24fde290701c9408f3d1054722adad360141041ad06846fd7cf9998a827485d8dd5aaba9eccc385ba7759a6e9055fbdf90d7513c0d11fe5e5dcfcf8d4946c67f6c45f8e7f7d7a9c254ca8ebde1ffd64ab9dd58ffffffff0171b00300000000001976a9142069a3fae01db74cef12d1d01811afdf6a3e1c2e88ac0000000001000000
Let's see if a blockchain information service can decode this transaction.
Browse to:
live.blockcypher.com/btc/decodetx
Paste the signed transaction into the text box named "Transaction Hex". Network is set to "Bitcoin".
Click "Decode Transaction".
Result:
{
"addresses": [
"138obEZkdWaWEQ4x8ZAYw4MybHSZtX1Nam",
"13xPBB175FtPbPQ84iB8KuawaVy3mHrady"
],
"block_height": -1,
"block_index": -1,
"confirmations": 0,
"double_spend": false,
"fees": 223,
"hash": "745e224ccba0a033c55ea80523f207da18b903418ac1f5d293eed62c19e0334d",
"inputs": [
{
"addresses": [
"138obEZkdWaWEQ4x8ZAYw4MybHSZtX1Nam"
],
"age": 543926,
"output_index": 8,
"output_value": 242000,
"prev_hash": "e4609e0f1ca854b8b07381f32ba31adbad9713205f5a4f3f56a5a32853d47855",
"script": "473044022017ece35581a034a4838a577fe438f108cf22764927a1ad197ed379460c0764cd022066723723eff05c10bac9a2f63b3e782e24fde290701c9408f3d1054722adad360141041ad06846fd7cf9998a827485d8dd5aaba9eccc385ba7759a6e9055fbdf90d7513c0d11fe5e5dcfcf8d4946c67f6c45f8e7f7d7a9c254ca8ebde1ffd64ab9dd58",
"script_type": "pay-to-pubkey-hash",
"sequence": 4294967295
}
],
"outputs": [
{
"addresses": [
"13xPBB175FtPbPQ84iB8KuawaVy3mHrady"
],
"script": "76a9142069a3fae01db74cef12d1d01811afdf6a3e1c2e88ac",
"script_type": "pay-to-pubkey-hash",
"value": 241777
}
],
"preference": "low",
"received": "2018-10-03T18:55:02.145100664Z",
"relayed_by": "23.20.8.168",
"size": 223,
"total": 241777,
"ver": 1,
"vin_sz": 1,
"vout_sz": 1
}
Looks good. No immediate error reported.
Let's broadcast the signed transaction.
Browse to:
live.blockcypher.com/btc/pushtx
Paste the signed transaction into the text box named "Transaction Hex". Network is set to "Bitcoin".
Click "Broadcast Transaction".
Result:
Webpage loads:
live.blockcypher.com/btc/tx/745e224ccba0a033c55ea80523f207da18b903418ac1f5d293eed62c19e0334d
Details:
- Transaction Successfully Broadcst
- AMOUNT TRANSACTED: 0.00241777 BTC
- FEES: 0.00000223 BTC
- Miner Preference: LOW
- Size: 223 bytes
- Version: 1
- Relayed By: 23.20.8.168
Time: 8:11 pm
Time: 9 am (next day)
Refresh:
live.blockcypher.com/btc/tx/745e224ccba0a033c55ea80523f207da18b903418ac1f5d293eed62c19e0334d
Confirmations: 6+
Click "Advanced Details". Click "API Call".
Result:
{
"block_hash": "0000000000000000000eecb4423435b1e52db78e8c83ae9e271978c2ef9d186e",
"block_height": 544268,
"block_index": 1131,
"hash": "745e224ccba0a033c55ea80523f207da18b903418ac1f5d293eed62c19e0334d",
"hex": "01000000015578d45328a3a5563f4f5a5f201397addb1aa32bf38173b0b854a81c0f9e60e4080000008a473044022017ece35581a034a4838a577fe438f108cf22764927a1ad197ed379460c0764cd022066723723eff05c10bac9a2f63b3e782e24fde290701c9408f3d1054722adad360141041ad06846fd7cf9998a827485d8dd5aaba9eccc385ba7759a6e9055fbdf90d7513c0d11fe5e5dcfcf8d4946c67f6c45f8e7f7d7a9c254ca8ebde1ffd64ab9dd58ffffffff0171b00300000000001976a9142069a3fae01db74cef12d1d01811afdf6a3e1c2e88ac00000000",
"addresses": [
"138obEZkdWaWEQ4x8ZAYw4MybHSZtX1Nam",
"13xPBB175FtPbPQ84iB8KuawaVy3mHrady"
],
"total": 241777,
"fees": 223,
"size": 223,
"preference": "low",
"relayed_by": "23.20.8.168",
"confirmed": "2018-10-03T23:25:01Z",
"received": "2018-10-03T18:55:16.947Z",
"ver": 1,
"double_spend": false,
"vin_sz": 1,
"vout_sz": 1,
"confirmations": 59,
"confidence": 1,
"inputs": [
{
"prev_hash": "e4609e0f1ca854b8b07381f32ba31adbad9713205f5a4f3f56a5a32853d47855",
"output_index": 8,
"script": "473044022017ece35581a034a4838a577fe438f108cf22764927a1ad197ed379460c0764cd022066723723eff05c10bac9a2f63b3e782e24fde290701c9408f3d1054722adad360141041ad06846fd7cf9998a827485d8dd5aaba9eccc385ba7759a6e9055fbdf90d7513c0d11fe5e5dcfcf8d4946c67f6c45f8e7f7d7a9c254ca8ebde1ffd64ab9dd58",
"output_value": 242000,
"sequence": 4294967295,
"addresses": [
"138obEZkdWaWEQ4x8ZAYw4MybHSZtX1Nam"
],
"script_type": "pay-to-pubkey-hash",
"age": 543926
}
],
"outputs": [
{
"value": 241777,
"script": "76a9142069a3fae01db74cef12d1d01811afdf6a3e1c2e88ac",
"addresses": [
"13xPBB175FtPbPQ84iB8KuawaVy3mHrady"
],
"script_type": "pay-to-pubkey-hash"
}
]
}
Excellent.
Key details:
- block_hash:
0000000000000000000eecb4423435b1e52db78e8c83ae9e271978c2ef9d186e
- block_height: 544268
- confirmed: 2018-10-03 at 23:25:01 UTC
- received: 2018-10-03 at 18:55:16.947 UTC
- txid:
745e224ccba0a033c55ea80523f207da18b903418ac1f5d293eed62c19e0334d
From some reading, the "Z" suffix on Unix timestamps indicates UTC time values.
UTC = Coordinated Universal Time (identical to GMT = Greenwich Mean Time).
18:55 to 23:25 = 4.5 hours for confirmation.
Good.
I have completed steps 6-10:
- Construct a standard raw Bitcoin transaction that moves the available bitcoin from the source address to the target address. Be careful to include a mining fee. This is tx1.
- Sign the transaction using the private key of the source address and ECDSA cryptography. Before signing, the transaction must be altered slightly to get the transaction-in-signable-form.
- Embed the transaction signature into the transaction.
- Broadcast the transaction.
- Confirm that the transaction has been mined (included in a block in the Bitcoin blockchain). This verifies that the transaction was valid (i.e. in the correct format, with a mathematically valid signature).
Step 11) Look up recent mining fees.
Browse to:
bitcoinfees.earn.com
I see that a transaction with a fee rate of 1-2 satoshis / byte will probably be mined. Estimated delay = 2-11 blocks (10-180 minutes).
Steps 12-16)
- Construct a second nonstandard raw Bitcoin transaction that moves the available bitcoin from the target address to the final address. Be careful to include a mining fee. This is tx2.
- Sign the transaction using the private key of the target address and ECDSA cryptography. Before signing, the transaction must be altered slightly to get the transaction-in-signable-form.
- Embed the transaction signature into the transaction.
- Broadcast the transaction.
- Confirm that the transaction has been mined (included in a block in the Bitcoin blockchain). This verifies that the transaction was valid (i.e. in the correct format, with a mathematically valid signature).
The final address
328cTqexYnQRbN5Dgs12D89sYiPPvtWVbF
begins with a '3' and is therefore a Pay-To-Script-Hash (P2SH) address.
I examined P2SH addresses in some detail in the first iteration of this project:
Creating and signing a standard raw Bitcoin transaction
Summaries of my results can be found in this previous project, in the Notes / Discovery section, in these parts:
- P2SH multi-signature addresses - security
- P2SH multi-signature addresses - results from this project
Excerpts from this previous project:
P2SH multi-signature addresses - results from this project
To send to a P2SH multi-signature address, I don't need to worry about how to spend from it. I just need to construct an outputScript from the P2SH address and use it in the transaction output.
The P2SH outputScript is:
OP_HASH160 PUSHDATA(20) [20-byte op_hash160 hash of redeemScript] OP_EQUAL
- 20 in hex is 0x14.
The address format of P2SH addresses is:
base58-encode( [one-byte version] + [20-byte hash of redeemScript] + [4-byte checksum] )
On the main network, the version byte is 0x05.
[...]
I then constructed an outputScript from the redeemScript hash.
outputScript:
- OP_HASH160: a9
- PUSHDATA: 14
- [derived property] PUSHDATA decimal value: 20
- public_key_hash: 31 6f 8d 5c 41 d8 8d 3a 0d f1 79 fc 5a d7 65 d5 7f 7f 46 67
- OP_EQUAL: 87
I was able to construct and sign a valid transaction that transferred bitcoin from the P2PKH target address to the P2SH multi-signature LocalBitcoins receiving address. This was transaction 2.
In this previous project, I also wrote a script
get_script_hash_from_p2sh_address.py
that extracts the script hash from the P2SH address.
Browse to this previous project, go to the Downloadable Digital Assets section, and download
get_script_hash_from_p2sh_address.py
I also included an example of how to use this script. Go to the Recipes For Using Various Downloadable Assets section, and find Recipe 3.
Excerpt:
Recipe 3: get_script_hash_from_p2sh_address.py
1) Set the variableaddressto contain the P2SH Bitcoin address whose script hash you wish to extract.
2) Run the script.
aineko:work stjohnpiano$ python get_script_hash_from_p2sh_address.py
address: 36CQfj2Yt54sZttJYTb5ywuS7YGEQLfzCE
result_int2 integer value lies within possible domain of integer values for: [0x05] + [20-byte script hash]
in hex: 05316f8d5c41d88d3a0df179fc5ad765d57f7f46678b3e88cf
remove checksum: 05316f8d5c41d88d3a0df179fc5ad765d57f7f4667
remove first 0x05 byte.
script hash: 316f8d5c41d88d3a0df179fc5ad765d57f7f4667
Future: Check the validity of the checksum in the P2SH address, rather than just removing it.
Using this previous work, I will write a new script
create_nonstandard_transaction.py
that will create a transaction that sends bitcoin to a P2SH address. This will be tx2.
I will also write supporting library files:
- nonstandard_transaction.py
- nonstandard_bitcoin_functions.py
For the transaction input, I need the details of the currently-unspent output stored in (associated with) the target address.
txid:
745e224ccba0a033c55ea80523f207da18b903418ac1f5d293eed62c19e0334d
In the data (included earlier) for this txid from
live.blockcypher.com
I can see that:
- previous_output_index = 0, because a) output indices are implicit and 0-indexed and b) there is only 1 output in this transaction.
- value = 241777 (in satoshis)
I need the private key of the address for this unspent output in order to sign tx2.
I created the address
13xPBB175FtPbPQ84iB8KuawaVy3mHrady
in this project, so I know that
private_key_hex =
726f6164736964655f7069636e6963
Details for the desired output of tx2:
- address:
328cTqexYnQRbN5Dgs12D89sYiPPvtWVbF
- satoshi_amount:
241777 (the specified fee will be subtracted from this).
I'll set a fee of 225 satoshis. This will probably be slightly above 1 satoshi / byte.
[development occurs here]
Ok. Finished.
create_nonstandard_transaction.py
#!/opt/local/bin/python | |
# NOTES: | |
# - All inputs are strings. If any inputs are not to be used, they can be set to the empty string {""}. | |
# - p2pkh = Pay-To-Public-Key-Hash (standard input / output type) | |
# - p2sh = Pay-To-Script-Hash (nonstandard, can be used for multisignature) | |
# in input_data, value can be included as either satoshi (e.g. "satoshi_amount": "241777") or bitcoin (e.g. "bitcoin_amount": "0.00241777"). | |
# The change address is really a single output sent to that address. Currently, if two unspent outputs are sent to the change address, only one can be used to pay the transaction fee. | |
# dependencies | |
import ecdsa | |
import bitcoin_functions as bf | |
import transaction as tr | |
import nonstandard_bitcoin_functions as nbf | |
import nonstandard_transaction as ntr | |
import math | |
def main(): | |
##### START CONTROLS | |
random_value = "Mighty is Brahma dhcmrlchtdj" # Every ECDSA signature (one for each input) requires entropy. | |
# random_value_type options: ["raw_bytes", "hex_bytes"] | |
random_value_type = "raw_bytes" | |
input_data = { | |
"txid": "745e224ccba0a033c55ea80523f207da18b903418ac1f5d293eed62c19e0334d", | |
"previous_output_index": "0", | |
"private_key_hex": "726f6164736964655f7069636e6963", | |
"satoshi_amount": "241777", | |
"input_type": "p2pkh", | |
} | |
output_data = { | |
"address": "328cTqexYnQRbN5Dgs12D89sYiPPvtWVbF", | |
"satoshi_amount": "241000", | |
"output_type": "p2sh", | |
} | |
change_address = "328cTqexYnQRbN5Dgs12D89sYiPPvtWVbF" | |
# note: the fee will be subtracted from the amount that is being sent to the change address. | |
fee = "225" # satoshi | |
fee_rate = "1" # satoshi / byte | |
# fee_type options: ["fee", "fee_rate"] | |
fee_type = "fee" # | |
##### END CONTROLS | |
##### START SETTINGS | |
random_value_type_options = ["raw_bytes", "hex_bytes"] | |
fee_type_options = ["fee", "fee_rate"] | |
input_type_options = ["p2pkh", "p2sh"] | |
output_type_options = input_type_options[:] # copy list. | |
##### END SETTINGS | |
# preparation | |
# some values will be converted to integers. | |
# - fee, input["previous_output_index"], input["satoshi_amount"], output["satoshi_amount"] | |
# some values will be converted to floats: | |
# - fee_rate | |
bf.validate_string(random_value) | |
bf.validate_string(random_value_type) | |
bf.validate_string(change_address) | |
bf.validate_string(fee) | |
bf.validate_string(fee_rate) | |
bf.validate_string(fee_type) | |
bf.validate_item_in_list(fee_type, fee_type_options) | |
# future: validate_string_is_digits() for e.g. fee prior to int() conversion. | |
if fee_type == "fee": | |
fee = int(fee) | |
bf.validate_positive_int(fee) | |
elif fee_type == "fee_rate": | |
fee_rate = float(fee_rate) | |
if fee_rate < 0: | |
message = "Fee rate is negative. Fee rate: %s" % str(fee_rate) | |
stop(message) | |
bf.validate_item_in_list(random_value_type, random_value_type_options) | |
bf.validate_item_in_list(input_data["input_type"], input_type_options) | |
bf.validate_item_in_list(output_data["output_type"], output_type_options) | |
bf.validate_string(input_data["previous_output_index"]) | |
input_data["previous_output_index"] = int(input_data["previous_output_index"]) | |
bf.validate_string(input_data["satoshi_amount"]) | |
input_data["satoshi_amount"] = int(input_data["satoshi_amount"]) | |
bf.validate_string(output_data["satoshi_amount"]) | |
output_data["satoshi_amount"] = int(output_data["satoshi_amount"]) | |
if "bitcoin_amount" in input_data: | |
b = bitcoin_to_satoshi(input_data["bitcoin_amount"]) | |
input_data["satoshi_amount"] = b | |
elif "satoshi_amount" in input_data: | |
s = input_data["satoshi_amount"] | |
input_data["bitcoin_amount"] = bf.satoshi_to_bitcoin(s) | |
if "bitcoin_amount" in output_data: | |
b = bitcoin_to_satoshi(output_data["bitcoin_amount"]) | |
output_data["satoshi_amount"] = b | |
elif "satoshi_amount" in output_data: | |
s = output_data["satoshi_amount"] | |
output_data["bitcoin_amount"] = bf.satoshi_to_bitcoin(s) | |
bf.validate_list_contains_items(input_data.keys(), items=["txid", "previous_output_index", "private_key_hex", "satoshi_amount", "bitcoin_amount"]) | |
bf.validate_list_contains_items(output_data.keys(), items=["address", "satoshi_amount", "bitcoin_amount"]) | |
# future: validate_txid() | |
bf.validate_string(input_data["txid"]) | |
bf.validate_positive_int(input_data["previous_output_index"]) | |
bf.validate_hex(input_data["private_key_hex"]) | |
bf.validate_positive_int(input_data["satoshi_amount"]) | |
bf.validate_bitcoin_amount(input_data["bitcoin_amount"]) | |
# future: validate_bitcoin_address() | |
bf.validate_string(output_data["address"]) | |
bf.validate_positive_int(output_data["satoshi_amount"]) | |
bf.validate_bitcoin_amount(output_data["bitcoin_amount"]) | |
print "\n### START CREATION OF NONSTANDARD BITCOIN TRANSACTION\n" | |
print "- Fee type: %s" % fee_type | |
if fee_type == "fee": | |
print "- Fee: %s (satoshi)" % fee | |
elif fee_type == "fee_rate": | |
print "- Fee rate: %s (satoshi / byte)" % fee_rate | |
# create inputs | |
if input_data["input_type"] == "p2pkh": | |
input = tr.Input.create_from_txid( | |
txid = input_data["txid"], | |
previous_output_index = input_data["previous_output_index"], | |
private_key_hex = input_data["private_key_hex"], | |
bitcoin_amount = input_data["bitcoin_amount"], | |
) | |
# create outputs | |
if output_data["output_type"] == "p2pkh": | |
pass | |
elif output_data["output_type"] == "p2sh": | |
output = ntr.P2SHOutput.create_from_address( | |
address = output_data["address"], | |
bitcoin_amount = output_data["bitcoin_amount"], | |
) | |
inputs = [input] | |
outputs = [output] | |
random_values = [random_value] | |
# if random_value type is "raw_bytes", convert to hex. | |
if random_value_type == "raw_bytes": | |
random_values = [bf.bytes_to_hex(value) for value in random_values] | |
print "- Number of inputs (i.e. as-yet-unspent outputs): %d" % len(inputs) | |
print "- Number of outputs: %d" % len(outputs) | |
print "- Change address: %s" % change_address | |
# find the change_output corresponding to the change_address. | |
# future: how to handle the case where multiple outputs are sent to the change_address? | |
change_output = None | |
addresses = [] | |
for output in outputs: | |
addresses.append(output.address) | |
if output.address == change_address: | |
change_output = output | |
break | |
if change_output == None: | |
message = "Change address not found in output addresses." | |
message += "\nOutput addresses:" | |
for address in addresses: | |
message += "\n- %s" % address | |
message += "\nChange address: %s" % change_address | |
stop(message) | |
# Display the value to be sent to the change address. | |
print "- Amount to be sent to the change address: %s" % change_output.bitcoin_amount | |
# print out input addresses and the total value being spent from each one. | |
# future: create and test a transaction that spends two inputs from the same address. | |
input_addresses = {} | |
for input in inputs: | |
value = input.satoshi_amount | |
address = input.address | |
if address not in input_addresses: | |
input_addresses[address] = value | |
else: | |
input_addresses[address] += value | |
print "- Input addresses, with total-value-to-be-sent:" | |
for address in input_addresses: | |
satoshi = input_addresses[address] | |
bitcoin = bf.satoshi_to_bitcoin(satoshi) | |
print "-- %s: %s" % (address, bitcoin) | |
# print out output addresses and the total value being sent to each one. | |
# future: create and test a transaction that sends two outputs to the same address. | |
output_addresses = {} | |
for output in outputs: | |
value = output.satoshi_amount | |
address = output.address | |
if address not in output_addresses: | |
output_addresses[address] = value | |
else: | |
output_addresses[address] += value | |
print "- Output addresses, with total-value-to-be-received:" | |
for address in output_addresses: | |
satoshi = output_addresses[address] | |
bitcoin = bf.satoshi_to_bitcoin(satoshi) | |
print "-- %s: %s" % (address, bitcoin) | |
# confirm that total input value >= total output value | |
input_total = 0 # in satoshis | |
output_total = 0 # in satoshis | |
for input in inputs: | |
input_total += input.satoshi_amount | |
for output in outputs: | |
output_total += output.satoshi_amount | |
print "- Total value of all inputs: %s" % bf.satoshi_to_bitcoin(input_total) | |
print "- Total value of all outputs: %s" % bf.satoshi_to_bitcoin(output_total) | |
# send any extra input value (i.e. not assigned to be sent to an output) to the change_output. | |
# these amounts are in satoshis. | |
if input_total == output_total: | |
print "- Total value of all inputs exactly matches total value of all outputs." | |
elif input_total > output_total: | |
extra = input_total - output_total | |
new_amount = change_output.satoshi_amount + extra | |
change_output.set_satoshi_amount(new_amount) | |
print "- Total value of all inputs is greater than total value of all outputs." | |
print "-- Some input value has not been assigned to an output." | |
print "-- Extra value: %s" % bf.satoshi_to_bitcoin(extra) | |
print "-- This extra value will be sent to the change address." | |
print "-- Change address: %s" % change_output.address | |
print "-- New amount to be sent to change address: %s" % change_output.bitcoin_amount | |
elif input_total < output_total: | |
difference = output_total - input_total | |
message = "Total value of all inputs is less than total value of all outputs." | |
message += "\n- Difference: %s" % bf.satoshi_to_bitcoin(lack) | |
stop(message) | |
# estimate size of transaction | |
# we're only handling 1 input at the moment, so this code still works. | |
input_count_size = bf.hex_len(bf.int_to_var_int(len(inputs))) | |
inputs_size = len(inputs) * ( \ | |
32 + # previous_output_hash (32 bytes) | |
4 + # previous_output_index (4 bytes) | |
1 + # script_length (1-byte var_int) | |
138 + # byte length of scriptSig [approximately] | |
4 # sequence (4 bytes) | |
) | |
# distinguish between p2pkh and p2sh outputs. | |
output_size_total = 0 # bytes | |
output_count_size = bf.hex_len(bf.int_to_var_int(len(outputs))) | |
for output in outputs: | |
if hasattr(output, "output_type"): | |
if output.output_type == "p2sh": | |
output_size = ( \ | |
8 + # value | |
1 + # script_length (1-byte var_int) | |
25 # byte length of a standard outputScript (containing a redeemScript hash). | |
) | |
else: | |
# p2pkh output. | |
output_size = ( \ | |
8 + # value | |
1 + # script_length (1-byte var_int) | |
25 # byte length of a standard scriptPubKey | |
) | |
output_size_total += output_size | |
estimated_tran_size = ( | |
4 + # version (4 bytes) | |
input_count_size + # input_count | |
inputs_size + # byte length of concatenated inputs | |
output_count_size + # output_count | |
output_size_total + # byte length of concatenated outputs | |
4 # block lock time (4 bytes) | |
) | |
print "- Estimated transaction size: %d bytes" % estimated_tran_size | |
# calculate approximate fee | |
final_fee = None | |
if fee_type == "fee": | |
final_fee = fee | |
print "- Final fee: %d (satoshi)" % fee | |
elif fee_type == "fee_rate": | |
final_fee = estimated_tran_size * fee_rate | |
# round up to the nearest satoshi. | |
final_fee = int(math.ceil(float(final_fee))) | |
print "- Fee rate: %s (satoshi / byte)" % str(fee_rate) | |
print "- Calculate %d * %s and round up." % (estimated_tran_size, str(fee_rate)) | |
print "- Final fee: %d (satoshi)" % final_fee | |
if final_fee == None: | |
message = "Final fee not calculated." | |
stop(message) | |
print "- Final fee rate: %.4f (satoshi per byte)" % (float(final_fee) / estimated_tran_size) | |
# check whether the fee can be paid from the amount assigned to the change address. | |
if final_fee > change_output.satoshi_amount: | |
message = "The fee is greater than the satoshi amount that is assigned to the change address." | |
message += "\nFee: %d (satoshi)" % fee | |
message += "\nAmount to be sent to change address: %d (satoshi)" % change_output.satoshi_amount | |
message += "\nDifference: %d (satoshi)" % (fee - change_output.satoshi_amount) | |
stop(message) | |
# subtract fee from value assigned to change_output | |
original_amount = change_output.satoshi_amount | |
new_amount = change_output.satoshi_amount - final_fee | |
change_output.set_satoshi_amount(new_amount) | |
print "- Fee subtracted from amount to be sent to change address." | |
print "- New amount to be sent to change address: %d (satoshi)" % change_output.satoshi_amount | |
# create transaction | |
tran = ntr.NonstandardTransaction.create(inputs, outputs) | |
print "" | |
print "Input 0:" | |
print tran.inputs[0].get_description() | |
print "" | |
print "Output 0:" | |
print tran.outputs[0].get_description() | |
print "" | |
print tran.get_description() | |
print "" | |
print tran.get_description_of_signable_form(input_index=0) | |
tran.sign(random_values) | |
print "" | |
print tran.get_description_of_signed_form() | |
print "" | |
print "Signed transaction:" | |
print tran.get_signed_form() | |
print "\n### END CREATION OF BITCOIN TRANSACTION\n" | |
def stop(message): | |
# raise an Exception to get a traceback. | |
raise Exception("\n\nERROR: %s\n" % message) | |
if __name__ == '__main__': main() |
Earlier, I ran this command to make the script
create_nonstandard_transaction.py
executable.
aineko:work stjohnpiano$ chmod 700 create_nonstandard_transaction.py
Run the script.
aineko:work stjohnpiano$ ./create_nonstandard_transaction.py
### START CREATION OF NONSTANDARD BITCOIN TRANSACTION
- Fee type: fee
- Fee: 225 (satoshi)
- Number of inputs (i.e. as-yet-unspent outputs): 1
- Number of outputs: 1
- Change address: 328cTqexYnQRbN5Dgs12D89sYiPPvtWVbF
- Amount to be sent to the change address: 0.00241000
- Input addresses, with total-value-to-be-sent:
-- 13xPBB175FtPbPQ84iB8KuawaVy3mHrady: 0.00241777
- Output addresses, with total-value-to-be-received:
-- 328cTqexYnQRbN5Dgs12D89sYiPPvtWVbF: 0.00241000
- Total value of all inputs: 0.00241777
- Total value of all outputs: 0.00241000
- Total value of all inputs is greater than total value of all outputs.
-- Some input value has not been assigned to an output.
-- Extra value: 0.00000777
-- This extra value will be sent to the change address.
-- Change address: 328cTqexYnQRbN5Dgs12D89sYiPPvtWVbF
-- New amount to be sent to change address: 0.00241777
- Estimated transaction size: 223 bytes
- Final fee: 225 (satoshi)
- Final fee rate: 1.0090 (satoshi per byte)
- Fee subtracted from amount to be sent to change address.
- New amount to be sent to change address: 241552 (satoshi)
Input 0:
Input (without signature):
- previous_output_hash: 4d33e0192cd6ee93d2f5c18a4103b918da07f22305a85ec533a0a0cb4c225e74
- previous_output_index: 00000000
- sequence: ffffffff
- private_key_hex: 0000000000000000000000000000000000726f6164736964655f7069636e6963
- public_key_hex: 04b60eb0f9211eb7778ef54963b0a55c986d69f4dee4642ef7e7a16fc16c85eeda9a764032b5836c43e05ba520edd2a9040b9409b74b27da5b2fb1bfa48687b1a1
- script_length_scriptPubKey: 19
- scriptPubKey: 76a9142069a3fae01db74cef12d1d01811afdf6a3e1c2e88ac
- script_length: None
- scriptSig: None
- address: 13xPBB175FtPbPQ84iB8KuawaVy3mHrady
- previous_output_index_int: 0
- txid: 745e224ccba0a033c55ea80523f207da18b903418ac1f5d293eed62c19e0334d
Output 0:
Output:
- value: 90af030000000000
- script_length: 17
- script: a91404d73b326fb56e5d09e5f0b4bb3b83b7ff5fb05f87
- address: 328cTqexYnQRbN5Dgs12D89sYiPPvtWVbF
- bitcoin_amount: 0.00241552
- satoshi_amount: 241552
- redeem_script_hash_hex: 04d73b326fb56e5d09e5f0b4bb3b83b7ff5fb05f
Nonstandard Transaction (unsigned form):
- version: 01000000
- input_count: 01
- Input:
-- previous_output_hash: 4d33e0192cd6ee93d2f5c18a4103b918da07f22305a85ec533a0a0cb4c225e74
-- previous_output_index: 00000000
-- script_length: None
-- scriptSig: None
-- sequence: ffffffff
- output_count: 01
- Output [P2SH]:
-- value: 90af030000000000
-- script_length: 17
-- script: a91404d73b326fb56e5d09e5f0b4bb3b83b7ff5fb05f87
- block_lock_time: 00000000
Nonstandard Transaction (signable form):
- version: 01000000
- input_count: 01
- Input [to be used to sign this signable form]:
-- previous_output_hash: 4d33e0192cd6ee93d2f5c18a4103b918da07f22305a85ec533a0a0cb4c225e74
-- previous_output_index: 00000000
-- script_length_scriptPubKey: 19
-- scriptPubKey: 76a9142069a3fae01db74cef12d1d01811afdf6a3e1c2e88ac
-- sequence: ffffffff
- output_count: 01
- Output [P2SH]:
-- value: 90af030000000000
-- script_length: 17
-- script: a91404d73b326fb56e5d09e5f0b4bb3b83b7ff5fb05f87
- block_lock_time: 00000000
- hash_type_4_byte: 01000000
Nonstandard Transaction (signed form):
- version: 01000000
- input_count: 01
- Input:
-- previous_output_hash: 4d33e0192cd6ee93d2f5c18a4103b918da07f22305a85ec533a0a0cb4c225e74
-- previous_output_index: 00000000
-- script_length: 8a
-- scriptSig: 47304402201c26a71ec1993096081a9d8a39ad54613e428a4f28632a9bcb978a4321dfae4a0220546b3e0326be273ce3f2f780e269bec65346ed56fa99a0b65b42dd2f73782acf014104b60eb0f9211eb7778ef54963b0a55c986d69f4dee4642ef7e7a16fc16c85eeda9a764032b5836c43e05ba520edd2a9040b9409b74b27da5b2fb1bfa48687b1a1
-- sequence: ffffffff
- output_count: 01
- Output [P2SH]:
-- value: 90af030000000000
-- script_length: 17
-- script: a91404d73b326fb56e5d09e5f0b4bb3b83b7ff5fb05f87
- block_lock_time: 00000000
- hash_type_4_byte: 01000000
Signed transaction:
01000000014d33e0192cd6ee93d2f5c18a4103b918da07f22305a85ec533a0a0cb4c225e74000000008a47304402201c26a71ec1993096081a9d8a39ad54613e428a4f28632a9bcb978a4321dfae4a0220546b3e0326be273ce3f2f780e269bec65346ed56fa99a0b65b42dd2f73782acf014104b60eb0f9211eb7778ef54963b0a55c986d69f4dee4642ef7e7a16fc16c85eeda9a764032b5836c43e05ba520edd2a9040b9409b74b27da5b2fb1bfa48687b1a1ffffffff0190af03000000000017a91404d73b326fb56e5d09e5f0b4bb3b83b7ff5fb05f870000000001000000
### END CREATION OF BITCOIN TRANSACTION
Signed transaction:
01000000014d33e0192cd6ee93d2f5c18a4103b918da07f22305a85ec533a0a0cb4c225e74000000008a47304402201c26a71ec1993096081a9d8a39ad54613e428a4f28632a9bcb978a4321dfae4a0220546b3e0326be273ce3f2f780e269bec65346ed56fa99a0b65b42dd2f73782acf014104b60eb0f9211eb7778ef54963b0a55c986d69f4dee4642ef7e7a16fc16c85eeda9a764032b5836c43e05ba520edd2a9040b9409b74b27da5b2fb1bfa48687b1a1ffffffff0190af03000000000017a91404d73b326fb56e5d09e5f0b4bb3b83b7ff5fb05f870000000001000000
Let's see if a blockchain information service can decode this transaction.
Browse to:
live.blockcypher.com/btc/decodetx
Paste the signed transaction into the text box named "Transaction Hex". Network is set to "Bitcoin".
Click "Decode Transaction".
Result:
{
"addresses": [
"13xPBB175FtPbPQ84iB8KuawaVy3mHrady",
"328cTqexYnQRbN5Dgs12D89sYiPPvtWVbF"
],
"block_height": -1,
"block_index": -1,
"confirmations": 0,
"double_spend": false,
"fees": 225,
"hash": "598f47dfb44da351c8576fb975bdab3b023777bb1c1d8ba727afd528ca4d7cf2",
"inputs": [
{
"addresses": [
"13xPBB175FtPbPQ84iB8KuawaVy3mHrady"
],
"age": 544268,
"output_index": 0,
"output_value": 241777,
"prev_hash": "745e224ccba0a033c55ea80523f207da18b903418ac1f5d293eed62c19e0334d",
"script": "47304402201c26a71ec1993096081a9d8a39ad54613e428a4f28632a9bcb978a4321dfae4a0220546b3e0326be273ce3f2f780e269bec65346ed56fa99a0b65b42dd2f73782acf014104b60eb0f9211eb7778ef54963b0a55c986d69f4dee4642ef7e7a16fc16c85eeda9a764032b5836c43e05ba520edd2a9040b9409b74b27da5b2fb1bfa48687b1a1",
"script_type": "pay-to-pubkey-hash",
"sequence": 4294967295
}
],
"outputs": [
{
"addresses": [
"328cTqexYnQRbN5Dgs12D89sYiPPvtWVbF"
],
"script": "a91404d73b326fb56e5d09e5f0b4bb3b83b7ff5fb05f87",
"script_type": "pay-to-script-hash",
"value": 241552
}
],
"preference": "low",
"received": "2018-10-11T10:05:01.110355968Z",
"relayed_by": "184.73.80.90",
"size": 221,
"total": 241552,
"ver": 1,
"vin_sz": 1,
"vout_sz": 1
}
Looks good. No immediate error reported.
Let's broadcast the signed transaction.
Browse to:
live.blockcypher.com/btc/pushtx
Paste the signed transaction into the text box named "Transaction Hex". Network is set to "Bitcoin".
Click "Broadcast Transaction".
Result:
Webpage loads:
live.blockcypher.com/btc/tx/598f47dfb44da351c8576fb975bdab3b023777bb1c1d8ba727afd528ca4d7cf2
Details:
- Transaction Successfully Broadcst
- AMOUNT TRANSACTED: 0.00241552 BTC
- FEES: 0.00000225 BTC
- Confirmations: 0/6
- Miner Preference: LOW
- Size: 221 bytes
- Version: 1
- Relayed By: 54.198.103.112
Time: 11:19 am
Note:
- tx2's size is 221 bytes, not 223 bytes as I expected. This is because the P2SH outputScript is 3 single-byte opcodes + a 20-byte redeemScriptHash i.e. 23 bytes, rather than the 25-byte P2PKH scriptPubKey.
In the code in
create_nonstandard_transaction.py,
in the estimate-transaction-size code, I used "25" as the estimated size of the p2sh outputScript.
- Change this to "23".
Final fee rate is 225 / 221 ~= 1.0181 (satoshi / byte).
Log into LocalBitcoins.
Browse to Wallet / Receive bitcoins.
An incoming transaction is listed.
- Time: 11:07 am [Note: I checked my computer clock. It is 12 minutes fast.]
- Address:
328cTqexYnQRbN5Dgs12D89sYiPPvtWVbF
- BTC: 0.00241552
- Confirmed in about: 30 minutes
Time: 12:25 pm
Refresh transaction page.
- Confirmations: 0/6
Time: 15:26 pm
Refresh transaction page.
- Confirmations: 0/6
Time: 18:17 pm
Refresh transaction page.
- Confirmations: 6+
Click Advanced Details. Then click API Call.
Result = a new page containing a raw text dump of the transaction.
api.blockcypher.com/v1/btc/main/txs/598f47dfb44da351c8576fb975bdab3b023777bb1c1d8ba727afd528ca4d7cf2?limit=50&includeHex=true
{
"block_hash": "0000000000000000000d1f94b44eafbe52692ec790caf63a045d25e3080ba095",
"block_height": 545334,
"block_index": 1276,
"hash": "598f47dfb44da351c8576fb975bdab3b023777bb1c1d8ba727afd528ca4d7cf2",
"hex": "01000000014d33e0192cd6ee93d2f5c18a4103b918da07f22305a85ec533a0a0cb4c225e74000000008a47304402201c26a71ec1993096081a9d8a39ad54613e428a4f28632a9bcb978a4321dfae4a0220546b3e0326be273ce3f2f780e269bec65346ed56fa99a0b65b42dd2f73782acf014104b60eb0f9211eb7778ef54963b0a55c986d69f4dee4642ef7e7a16fc16c85eeda9a764032b5836c43e05ba520edd2a9040b9409b74b27da5b2fb1bfa48687b1a1ffffffff0190af03000000000017a91404d73b326fb56e5d09e5f0b4bb3b83b7ff5fb05f8700000000",
"addresses": [
"13xPBB175FtPbPQ84iB8KuawaVy3mHrady",
"328cTqexYnQRbN5Dgs12D89sYiPPvtWVbF"
],
"total": 241552,
"fees": 225,
"size": 221,
"preference": "low",
"relayed_by": "54.198.103.112",
"confirmed": "2018-10-11T16:00:06Z",
"received": "2018-10-11T10:06:29.33Z",
"ver": 1,
"double_spend": false,
"vin_sz": 1,
"vout_sz": 1,
"confirmations": 11,
"confidence": 1,
"inputs": [
{
"prev_hash": "745e224ccba0a033c55ea80523f207da18b903418ac1f5d293eed62c19e0334d",
"output_index": 0,
"script": "47304402201c26a71ec1993096081a9d8a39ad54613e428a4f28632a9bcb978a4321dfae4a0220546b3e0326be273ce3f2f780e269bec65346ed56fa99a0b65b42dd2f73782acf014104b60eb0f9211eb7778ef54963b0a55c986d69f4dee4642ef7e7a16fc16c85eeda9a764032b5836c43e05ba520edd2a9040b9409b74b27da5b2fb1bfa48687b1a1",
"output_value": 241777,
"sequence": 4294967295,
"addresses": [
"13xPBB175FtPbPQ84iB8KuawaVy3mHrady"
],
"script_type": "pay-to-pubkey-hash",
"age": 544268
}
],
"outputs": [
{
"value": 241552,
"script": "a91404d73b326fb56e5d09e5f0b4bb3b83b7ff5fb05f87",
"spent_by": "7ba0f7d7354036ae4985c27891e9dc70f302c79d315a11ab1da8422e1e72aebe",
"addresses": [
"328cTqexYnQRbN5Dgs12D89sYiPPvtWVbF"
],
"script_type": "pay-to-script-hash"
}
]
}
Excellent.
Key details:
- block_hash:
0000000000000000000d1f94b44eafbe52692ec790caf63a045d25e3080ba095
- block_height: 545334
- confirmed: 2018-10-11T16:00:06 UTC
- received: 2018-10-11T10:06:29.33 UTC
- txid ("hash" in the results above):
598f47dfb44da351c8576fb975bdab3b023777bb1c1d8ba727afd528ca4d7cf2
From some reading, the "Z" suffix on Unix timestamps indicates UTC time values.
UTC = Coordinated Universal Time (identical to GMT = Greenwich Mean Time).
10:06 to 16:00 ~= 6 hours for confirmation.
Good.
Log into LocalBitcoins.
Browse to Wallet / Transactions.
- Latest received transaction:
-- Datetime: 10/11/2018 17:02
-- Received BTC: 0.00226552
-- Description: Deposit to 328cTq......
-- Deposit Fee BTC: 0.00015000
I have completed steps 12-16:
- Construct a second nonstandard raw Bitcoin transaction that moves the available bitcoin from the target address to the final address. Be careful to include a mining fee. This is tx2.
- Sign the transaction using the private key of the target address and ECDSA cryptography. Before signing, the transaction must be altered slightly to get the transaction-in-signable-form.
- Embed the transaction signature into the transaction.
- Broadcast the transaction.
- Confirm that the transaction has been mined (included in a block in the Bitcoin blockchain). This verifies that the transaction was valid (i.e. in the correct format, with a mathematically valid signature).
I'll rewrite
create_transaction.py,
incorporating various improvements developed in
create_nonstandard_transaction.py.
I'll add a temporary test condition that raises an error if the transaction is not the expected value i.e. the signed standard transaction created earlier, tx1:
01000000015578d45328a3a5563f4f5a5f201397addb1aa32bf38173b0b854a81c0f9e60e4080000008a473044022017ece35581a034a4838a577fe438f108cf22764927a1ad197ed379460c0764cd022066723723eff05c10bac9a2f63b3e782e24fde290701c9408f3d1054722adad360141041ad06846fd7cf9998a827485d8dd5aaba9eccc385ba7759a6e9055fbdf90d7513c0d11fe5e5dcfcf8d4946c67f6c45f8e7f7d7a9c254ca8ebde1ffd64ab9dd58ffffffff0171b00300000000001976a9142069a3fae01db74cef12d1d01811afdf6a3e1c2e88ac0000000001000000
[development occurs here]
I've also rewritten
create_nonstandard_transaction.py
slightly, in order to handle
bitcoin_amount
properly (in input_data and output_data in CONTROLS). Ok. Let's do a final run-through.
create_transaction.py
#!/opt/local/bin/python | |
# DETAILS: | |
# - Author: StJohn Piano | |
# - Date: 2018-10-05 | |
# - Description: This script creates and signs a standard Bitcoin transaction with one input and one output. | |
# ENVIRONMENTS | |
# - Tested on: | |
# -- Python 2.7.13 running on Mac OS X 10.6.8 (Snow Leopard). | |
# NOTES: | |
# | |
# - My working definition of a standard transaction: | |
# -- It has at least one input and at least one output. | |
# -- All input and output addresses are Pay-To-Public-Key-Hash (P2PKH). | |
# -- All input scriptSigs contain uncompressed public keys. | |
# | |
# - All input variables for this script are strings. If any inputs are not to be used, they can be set to the empty string {""}. | |
# | |
# - The change address is more accurately a single output sent to that address. Currently, if two transaction outputs have the change address, the transaction fee can only be subtracted from one of these outputs. | |
# | |
# - in input_data, value can be included as either satoshi (e.g. "satoshi_amount": "242000") or bitcoin (e.g. "bitcoin_amount": "0.00242000"). | |
# | |
# - Hex characters in input variable values must be lowercase. | |
# DEPENDENCIES | |
import bitcoin_functions as bf | |
import transaction as tr | |
import math | |
def main(): | |
##### START CONTROLS | |
# Note: Hex characters in input variable values must be lowercase. | |
random_value = "The Face of God dhcmrlchtdj" | |
# random_value must be between 1 and 32 bytes. If random_value_type is "raw_bytes", then random_value must be between 1 and 32 ASCII characters. If random_value_type is "hex_bytes", then random_value must be an even number of hex characters and between 2 and 64 hex characters (1 byte is represented by 2 hex characters). | |
# Note: Every ECDSA signature (one for each input in a transaction) requires new random entropy. | |
# random_value_type options: ["raw_bytes", "hex_bytes"] | |
random_value_type = "raw_bytes" | |
input_data = { | |
"txid": "e4609e0f1ca854b8b07381f32ba31adbad9713205f5a4f3f56a5a32853d47855", | |
"previous_output_index": "8", | |
"private_key_hex": "7468655f6d6f74655f696e5f676f6427735f657965", | |
#"satoshi_amount": "242000", | |
"bitcoin_amount": "0.00242", | |
} | |
output_data = { | |
"address": "13xPBB175FtPbPQ84iB8KuawaVy3mHrady", | |
#"satoshi_amount": "200000", | |
"bitcoin_amount": "0.002", | |
} | |
# note: all inputs and outputs are assumed to be Pay-To-Public-Key-Hash (P2PKH). | |
change_address = "13xPBB175FtPbPQ84iB8KuawaVy3mHrady" | |
# note: the fee will be subtracted from the amount that is being sent to the change address. | |
fee = "223" # satoshi | |
fee_rate = "1" # satoshi / byte | |
# fee_type options: ["fee", "fee_rate"] | |
fee_type = "fee_rate" | |
##### END CONTROLS | |
##### START SETTINGS | |
random_value_type_options = ["raw_bytes", "hex_bytes"] | |
fee_type_options = ["fee", "fee_rate"] | |
##### END SETTINGS | |
# preparation | |
# some values will be converted to integers. | |
# - fee, input["previous_output_index"], input["satoshi_amount"], output["satoshi_amount"] | |
# some values will be converted to floats: | |
# - fee_rate | |
bf.validate_string(random_value) | |
bf.validate_string(random_value_type) | |
bf.validate_string(change_address) | |
bf.validate_string(fee) | |
bf.validate_string(fee_rate) | |
bf.validate_string(fee_type) | |
bf.validate_item_in_list(fee_type, fee_type_options) | |
# future: validate_string_is_digits() for e.g. fee prior to int() conversion. | |
if fee_type == "fee": | |
fee = int(fee) | |
bf.validate_positive_int(fee) | |
elif fee_type == "fee_rate": | |
fee_rate = float(fee_rate) | |
if fee_rate < 0: | |
message = "Fee rate is negative. Fee rate: %s" % str(fee_rate) | |
stop(message) | |
bf.validate_item_in_list(random_value_type, random_value_type_options) | |
bf.validate_string(input_data["previous_output_index"]) | |
input_data["previous_output_index"] = int(input_data["previous_output_index"]) | |
if "bitcoin_amount" in input_data: | |
bf.validate_bitcoin_amount(input_data["bitcoin_amount"]) | |
b = bf.bitcoin_to_satoshi(input_data["bitcoin_amount"]) | |
input_data["satoshi_amount"] = b | |
elif "satoshi_amount" in input_data: | |
s = input_data["satoshi_amount"] | |
bf.validate_string(s) | |
input_data["satoshi_amount"] = int(s) | |
s = input_data["satoshi_amount"] | |
input_data["bitcoin_amount"] = bf.satoshi_to_bitcoin(s) | |
if "bitcoin_amount" in output_data: | |
bf.validate_bitcoin_amount(output_data["bitcoin_amount"]) | |
b = bf.bitcoin_to_satoshi(output_data["bitcoin_amount"]) | |
output_data["satoshi_amount"] = b | |
elif "satoshi_amount" in output_data: | |
s = output_data["satoshi_amount"] | |
bf.validate_string(s) | |
output_data["satoshi_amount"] = int(s) | |
s = output_data["satoshi_amount"] | |
output_data["bitcoin_amount"] = bf.satoshi_to_bitcoin(s) | |
bf.validate_list_contains_items(input_data.keys(), items=["txid", "previous_output_index", "private_key_hex", "satoshi_amount", "bitcoin_amount"]) | |
bf.validate_list_contains_items(output_data.keys(), items=["address", "satoshi_amount", "bitcoin_amount"]) | |
# future: validate_txid() | |
bf.validate_string(input_data["txid"]) | |
bf.validate_positive_int(input_data["previous_output_index"]) | |
bf.validate_hex(input_data["private_key_hex"]) | |
bf.validate_positive_int(input_data["satoshi_amount"]) | |
bf.validate_bitcoin_amount(input_data["bitcoin_amount"]) | |
# future: validate_bitcoin_address() | |
bf.validate_string(output_data["address"]) | |
bf.validate_positive_int(output_data["satoshi_amount"]) | |
bf.validate_bitcoin_amount(output_data["bitcoin_amount"]) | |
print "\n### START CREATION OF BITCOIN TRANSACTION\n" | |
print "- Fee type: %s" % fee_type | |
if fee_type == "fee": | |
print "- Fee: %s (satoshi)" % fee | |
elif fee_type == "fee_rate": | |
print "- Fee rate: %s (satoshi / byte)" % fee_rate | |
# create inputs | |
input = tr.Input.create_from_txid( | |
txid = input_data["txid"], | |
previous_output_index = input_data["previous_output_index"], | |
private_key_hex = input_data["private_key_hex"], | |
bitcoin_amount = input_data["bitcoin_amount"], | |
) | |
# create outputs | |
output = tr.Output.create_from_address( | |
address = output_data["address"], | |
bitcoin_amount = output_data["bitcoin_amount"], | |
) | |
inputs = [input] | |
outputs = [output] | |
random_values = [random_value] | |
# if random_value type is "raw_bytes", convert to hex. | |
if random_value_type == "raw_bytes": | |
random_values = [bf.bytes_to_hex(value) for value in random_values] | |
print "- Number of inputs (i.e. as-yet-unspent outputs): %d" % len(inputs) | |
print "- Number of outputs: %d" % len(outputs) | |
print "- Change address: %s" % change_address | |
# find the change_output corresponding to the change_address. | |
# future: how to handle the case where multiple transaction outputs have the change_address? | |
change_output = None | |
addresses = [] | |
for output in outputs: | |
addresses.append(output.address) | |
if output.address == change_address: | |
change_output = output | |
break | |
if change_output == None: | |
message = "Change address not found in output addresses." | |
message += "\nOutput addresses:" | |
for address in addresses: | |
message += "\n- %s" % address | |
message += "\nChange address: %s" % change_address | |
stop(message) | |
# Display the value to be sent to the change address. | |
print "- Amount to be sent to the change address: %s" % change_output.bitcoin_amount | |
# print out input addresses and the total value being spent from each one. | |
# future: create and test a transaction that spends two inputs from the same address. | |
input_addresses = {} | |
for input in inputs: | |
value = input.satoshi_amount | |
address = input.address | |
if address not in input_addresses: | |
input_addresses[address] = value | |
else: | |
input_addresses[address] += value | |
print "- Input addresses, with total-value-to-be-sent:" | |
for address in input_addresses: | |
satoshi = input_addresses[address] | |
bitcoin = bf.satoshi_to_bitcoin(satoshi) | |
print "-- %s: %s" % (address, bitcoin) | |
# print out output addresses and the total value being sent to each one. | |
# future: create and test a transaction that sends two outputs to the same address. | |
output_addresses = {} | |
for output in outputs: | |
value = output.satoshi_amount | |
address = output.address | |
if address not in output_addresses: | |
output_addresses[address] = value | |
else: | |
output_addresses[address] += value | |
print "- Output addresses, with total-value-to-be-received:" | |
for address in output_addresses: | |
satoshi = output_addresses[address] | |
bitcoin = bf.satoshi_to_bitcoin(satoshi) | |
print "-- %s: %s" % (address, bitcoin) | |
# confirm that total input value >= total output value | |
input_total = 0 # in satoshis | |
output_total = 0 # in satoshis | |
for input in inputs: | |
input_total += input.satoshi_amount | |
for output in outputs: | |
output_total += output.satoshi_amount | |
print "- Total value of all inputs: %s" % bf.satoshi_to_bitcoin(input_total) | |
print "- Total value of all outputs: %s" % bf.satoshi_to_bitcoin(output_total) | |
# send any extra input value (i.e. not assigned to be sent to an output) to the change_output. | |
# these amounts are in satoshis. | |
if input_total == output_total: | |
print "- Total value of all inputs exactly matches total value of all outputs." | |
elif input_total > output_total: | |
extra = input_total - output_total | |
new_amount = change_output.satoshi_amount + extra | |
change_output.set_satoshi_amount(new_amount) | |
print "- Total value of all inputs is greater than total value of all outputs." | |
print "-- Some input value has not been assigned to an output." | |
print "-- Extra value: %s" % bf.satoshi_to_bitcoin(extra) | |
print "-- This extra value will be sent to the change address." | |
print "-- Change address: %s" % change_output.address | |
print "-- New amount to be sent to change address: %s" % change_output.bitcoin_amount | |
elif input_total < output_total: | |
difference = output_total - input_total | |
message = "Total value of all inputs is less than total value of all outputs." | |
message += "\n- Difference: %s" % bf.satoshi_to_bitcoin(lack) | |
stop(message) | |
# estimate size of transaction | |
# we're only handling 1 input and 1 output at the moment | |
input_count_size = bf.hex_len(bf.int_to_var_int(len(inputs))) | |
inputs_size = len(inputs) * ( \ | |
32 + # previous_output_hash (32 bytes) | |
4 + # previous_output_index (4 bytes) | |
1 + # script_length (1-byte var_int) | |
138 + # byte length of scriptSig [approximately] | |
4 # sequence (4 bytes) | |
) | |
output_count_size = bf.hex_len(bf.int_to_var_int(len(outputs))) | |
outputs_size = len(outputs) * ( \ | |
8 + # value | |
1 + # script_length (1-byte var_int) | |
25 # byte length of a standard scriptPubKey | |
) | |
estimated_tran_size = ( | |
4 + # version (4 bytes) | |
input_count_size + # input_count | |
inputs_size + # byte length of concatenated inputs | |
output_count_size + # output_count | |
outputs_size + # byte length of concatenated outputs | |
4 # block lock time (4 bytes) | |
) | |
print "- Estimated transaction size: %d bytes" % estimated_tran_size | |
# calculate approximate fee | |
final_fee = None | |
if fee_type == "fee": | |
final_fee = fee | |
print "- Final fee: %d (satoshi)" % final_fee | |
elif fee_type == "fee_rate": | |
final_fee = estimated_tran_size * fee_rate | |
# round up to the nearest satoshi. | |
final_fee = int(math.ceil(float(final_fee))) | |
print "- Fee rate: %s (satoshi / byte)" % str(fee_rate) | |
print "- Calculate %d * %s and round up to nearest satoshi." % (estimated_tran_size, str(fee_rate)) | |
print "- Final fee: %d (satoshi)" % final_fee | |
if final_fee == None: | |
message = "Final fee not calculated." | |
stop(message) | |
print "- Final fee rate (using estimated transaction size): %.4f (satoshi per byte)" % (float(final_fee) / estimated_tran_size) | |
# check whether the fee can be paid from the amount assigned to the change address. | |
if final_fee > change_output.satoshi_amount: | |
message = "The fee is greater than the satoshi amount that is assigned to the change address." | |
message += "\nFee: %d (satoshi)" % final_fee | |
message += "\nAmount to be sent to change address: %d (satoshi)" % change_output.satoshi_amount | |
message += "\nDifference: %d (satoshi)" % (final_fee - change_output.satoshi_amount) | |
stop(message) | |
# subtract fee from value assigned to change_output | |
original_amount = change_output.satoshi_amount | |
new_amount = change_output.satoshi_amount - final_fee | |
change_output.set_satoshi_amount(new_amount) | |
print "- Fee subtracted from amount to be sent to change address." | |
print "- New amount to be sent to change address: %d (satoshi)" % change_output.satoshi_amount | |
# create transaction | |
tran = tr.Transaction.create(inputs, outputs) | |
print "" | |
print "Input 0:" | |
print tran.inputs[0].get_description() | |
print "" | |
print "Output 0:" | |
print tran.outputs[0].get_description() | |
print "" | |
print tran.get_description() | |
print "" | |
print tran.get_description_of_signable_form(input_index=0) | |
tran.sign(random_values) | |
print "" | |
print tran.get_description_of_signed_form() | |
print "" | |
print "Signed transaction:" | |
print tran.get_signed_form() | |
print "\n### END CREATION OF BITCOIN TRANSACTION\n" | |
def stop(message): | |
bf.stop(message) | |
if __name__ == '__main__': main() |
Run the script.
aineko:work stjohnpiano$ ./create_transaction.py
### START CREATION OF BITCOIN TRANSACTION
- Fee type: fee_rate
- Fee rate: 1.0 (satoshi / byte)
- Number of inputs (i.e. as-yet-unspent outputs): 1
- Number of outputs: 1
- Change address: 13xPBB175FtPbPQ84iB8KuawaVy3mHrady
- Amount to be sent to the change address: 0.002
- Input addresses, with total-value-to-be-sent:
-- 138obEZkdWaWEQ4x8ZAYw4MybHSZtX1Nam: 0.00242000
- Output addresses, with total-value-to-be-received:
-- 13xPBB175FtPbPQ84iB8KuawaVy3mHrady: 0.00200000
- Total value of all inputs: 0.00242000
- Total value of all outputs: 0.00200000
- Total value of all inputs is greater than total value of all outputs.
-- Some input value has not been assigned to an output.
-- Extra value: 0.00042000
-- This extra value will be sent to the change address.
-- Change address: 13xPBB175FtPbPQ84iB8KuawaVy3mHrady
-- New amount to be sent to change address: 0.00242000
- Estimated transaction size: 223 bytes
- Fee rate: 1.0 (satoshi / byte)
- Calculate 223 * 1.0 and round up to nearest satoshi.
- Final fee: 223 (satoshi)
- Final fee rate (using estimated transaction size): 1.0000 (satoshi per byte)
- Fee subtracted from amount to be sent to change address.
- New amount to be sent to change address: 241777 (satoshi)
Input 0:
Input (without signature):
- previous_output_hash: 5578d45328a3a5563f4f5a5f201397addb1aa32bf38173b0b854a81c0f9e60e4
- previous_output_index: 08000000
- sequence: ffffffff
- private_key_hex: 00000000000000000000007468655f6d6f74655f696e5f676f6427735f657965
- public_key_hex: 041ad06846fd7cf9998a827485d8dd5aaba9eccc385ba7759a6e9055fbdf90d7513c0d11fe5e5dcfcf8d4946c67f6c45f8e7f7d7a9c254ca8ebde1ffd64ab9dd58
- script_length_scriptPubKey: 19
- scriptPubKey: 76a914176a0e0c2b9b0e77d630712ad301b749102a304488ac
- script_length: None
- scriptSig: None
- address: 138obEZkdWaWEQ4x8ZAYw4MybHSZtX1Nam
- previous_output_index_int: 8
- txid: e4609e0f1ca854b8b07381f32ba31adbad9713205f5a4f3f56a5a32853d47855
Output 0:
Output:
- value: 71b0030000000000
- script_length: 19
- scriptPubKey: 76a9142069a3fae01db74cef12d1d01811afdf6a3e1c2e88ac
- address: 13xPBB175FtPbPQ84iB8KuawaVy3mHrady
- bitcoin_amount: 0.00241777
- satoshi_amount: 241777
- public_key_hash_hex: 2069a3fae01db74cef12d1d01811afdf6a3e1c2e
Transaction (unsigned form):
- version: 01000000
- input_count: 01
- Input:
-- previous_output_hash: 5578d45328a3a5563f4f5a5f201397addb1aa32bf38173b0b854a81c0f9e60e4
-- previous_output_index: 08000000
-- script_length: None
-- scriptSig: None
-- sequence: ffffffff
- output_count: 01
- Output:
-- value: 71b0030000000000
-- script_length: 19
-- scriptPubKey: 76a9142069a3fae01db74cef12d1d01811afdf6a3e1c2e88ac
- block_lock_time: 00000000
Transaction (signable form):
- version: 01000000
- input_count: 01
- Input [to be used to sign this signable form]:
-- previous_output_hash: 5578d45328a3a5563f4f5a5f201397addb1aa32bf38173b0b854a81c0f9e60e4
-- previous_output_index: 08000000
-- script_length_scriptPubKey: 19
-- scriptPubKey: 76a914176a0e0c2b9b0e77d630712ad301b749102a304488ac
-- sequence: ffffffff
- output_count: 01
- Output:
-- value: 71b0030000000000
-- script_length: 19
-- scriptPubKey: 76a9142069a3fae01db74cef12d1d01811afdf6a3e1c2e88ac
- block_lock_time: 00000000
- hash_type_4_byte: 01000000
Transaction (signed form):
- version: 01000000
- input_count: 01
- Input:
-- previous_output_hash: 5578d45328a3a5563f4f5a5f201397addb1aa32bf38173b0b854a81c0f9e60e4
-- previous_output_index: 08000000
-- script_length: 8a
-- scriptSig: 473044022017ece35581a034a4838a577fe438f108cf22764927a1ad197ed379460c0764cd022066723723eff05c10bac9a2f63b3e782e24fde290701c9408f3d1054722adad360141041ad06846fd7cf9998a827485d8dd5aaba9eccc385ba7759a6e9055fbdf90d7513c0d11fe5e5dcfcf8d4946c67f6c45f8e7f7d7a9c254ca8ebde1ffd64ab9dd58
-- sequence: ffffffff
- output_count: 01
- Output:
-- value: 71b0030000000000
-- script_length: 19
-- scriptPubKey: 76a9142069a3fae01db74cef12d1d01811afdf6a3e1c2e88ac
- block_lock_time: 00000000
- hash_type_4_byte: 01000000
Signed transaction:
01000000015578d45328a3a5563f4f5a5f201397addb1aa32bf38173b0b854a81c0f9e60e4080000008a473044022017ece35581a034a4838a577fe438f108cf22764927a1ad197ed379460c0764cd022066723723eff05c10bac9a2f63b3e782e24fde290701c9408f3d1054722adad360141041ad06846fd7cf9998a827485d8dd5aaba9eccc385ba7759a6e9055fbdf90d7513c0d11fe5e5dcfcf8d4946c67f6c45f8e7f7d7a9c254ca8ebde1ffd64ab9dd58ffffffff0171b00300000000001976a9142069a3fae01db74cef12d1d01811afdf6a3e1c2e88ac0000000001000000
### END CREATION OF BITCOIN TRANSACTION
Signed transaction:
01000000015578d45328a3a5563f4f5a5f201397addb1aa32bf38173b0b854a81c0f9e60e4080000008a473044022017ece35581a034a4838a577fe438f108cf22764927a1ad197ed379460c0764cd022066723723eff05c10bac9a2f63b3e782e24fde290701c9408f3d1054722adad360141041ad06846fd7cf9998a827485d8dd5aaba9eccc385ba7759a6e9055fbdf90d7513c0d11fe5e5dcfcf8d4946c67f6c45f8e7f7d7a9c254ca8ebde1ffd64ab9dd58ffffffff0171b00300000000001976a9142069a3fae01db74cef12d1d01811afdf6a3e1c2e88ac0000000001000000
Let's test that this is the same as the transaction (tx1) that I broadcast earlier.
[the signed transaction generated just now]
[the signed transaction generated much earlier in the project]
equal
aineko:work stjohnpiano$ s1="01000000015578d45328a3a5563f4f5a5f201397addb1aa32bf38173b0b854a81c0f9e60e4080000008a473044022017ece35581a034a4838a577fe438f108cf22764927a1ad197ed379460c0764cd022066723723eff05c10bac9a2f63b3e782e24fde290701c9408f3d1054722adad360141041ad06846fd7cf9998a827485d8dd5aaba9eccc385ba7759a6e9055fbdf90d7513c0d11fe5e5dcfcf8d4946c67f6c45f8e7f7d7a9c254ca8ebde1ffd64ab9dd58ffffffff0171b00300000000001976a9142069a3fae01db74cef12d1d01811afdf6a3e1c2e88ac0000000001000000"
[the signed transaction generated much earlier in the project]
aineko:work stjohnpiano$ s2="01000000015578d45328a3a5563f4f5a5f201397addb1aa32bf38173b0b854a81c0f9e60e4080000008a473044022017ece35581a034a4838a577fe438f108cf22764927a1ad197ed379460c0764cd022066723723eff05c10bac9a2f63b3e782e24fde290701c9408f3d1054722adad360141041ad06846fd7cf9998a827485d8dd5aaba9eccc385ba7759a6e9055fbdf90d7513c0d11fe5e5dcfcf8d4946c67f6c45f8e7f7d7a9c254ca8ebde1ffd64ab9dd58ffffffff0171b00300000000001976a9142069a3fae01db74cef12d1d01811afdf6a3e1c2e88ac0000000001000000"
aineko:work stjohnpiano$ [[ "$s1" = "$s2" ]] && echo equal || echo not-equal
equal
Excellent.
I'll also do a final run-through of
create_nonstandard_transaction.py.
create_nonstandard_transaction.py
#!/opt/local/bin/python | |
# DETAILS: | |
# - Author: StJohn Piano | |
# - Date: 2018-10-05 | |
# - Description: This script creates and signs a nonstandard Bitcoin transaction with one standard input (p2pkh) and one nonstandard output (p2sh). | |
# ENVIRONMENTS | |
# - Tested on: | |
# -- Python 2.7.13 running on Mac OS X 10.6.8 (Snow Leopard). | |
# NOTES: | |
# | |
# - All input variables for this script are strings. If any inputs are not to be used, they can be set to the empty string {""}. | |
# | |
# - The change address is more accurately a single output sent to that address. Currently, if two transaction outputs have the change address, the transaction fee can only be subtracted from one of these outputs. | |
# | |
# - in input_data, value can be included as either satoshi (e.g. "satoshi_amount": "241777") or bitcoin (e.g. "bitcoin_amount": "0.00241777"). | |
# | |
# - p2pkh = Pay-To-Public-Key-Hash (standard input / output type) | |
# - p2sh = Pay-To-Script-Hash (nonstandard, can be used for multisignature) | |
# DEPENDENCIES | |
import bitcoin_functions as bf | |
import transaction as tr | |
import nonstandard_bitcoin_functions as nbf | |
import nonstandard_transaction as ntr | |
import math | |
def main(): | |
##### START CONTROLS | |
random_value = "Mighty is Brahma dhcmrlchtdj" | |
# random_value must be between 1 and 32 bytes. If random_value_type is "raw_bytes", then random_value must be between 1 and 32 ASCII characters. If random_value_type is "hex_bytes", then random_value must be an even number of hex characters and between 2 and 64 hex characters. | |
# Note: Every ECDSA signature (one for each input in a transaction) requires new random entropy. | |
# random_value_type options: ["raw_bytes", "hex_bytes"] | |
random_value_type = "raw_bytes" | |
input_data = { | |
"txid": "745e224ccba0a033c55ea80523f207da18b903418ac1f5d293eed62c19e0334d", | |
"previous_output_index": "0", | |
"private_key_hex": "726f6164736964655f7069636e6963", | |
"satoshi_amount": "241777", | |
#"bitcoin_amount": "0.00241777", | |
"input_type": "p2pkh", | |
} | |
output_data = { | |
"address": "328cTqexYnQRbN5Dgs12D89sYiPPvtWVbF", | |
"satoshi_amount": "241000", | |
#"bitcoin_amount": "0.00241", | |
"output_type": "p2sh", | |
} | |
change_address = "328cTqexYnQRbN5Dgs12D89sYiPPvtWVbF" | |
# note: the fee will be subtracted from the amount that is being sent to the change address. | |
fee = "225" # satoshi | |
fee_rate = "1" # satoshi / byte | |
# fee_type options: ["fee", "fee_rate"] | |
fee_type = "fee" | |
##### END CONTROLS | |
##### START SETTINGS | |
random_value_type_options = ["raw_bytes", "hex_bytes"] | |
fee_type_options = ["fee", "fee_rate"] | |
input_type_options = ["p2pkh", "p2sh"] | |
output_type_options = input_type_options[:] # copy list. | |
##### END SETTINGS | |
# preparation | |
# some values will be converted to integers. | |
# - fee, input["previous_output_index"], input["satoshi_amount"], output["satoshi_amount"] | |
# some values will be converted to floats: | |
# - fee_rate | |
bf.validate_string(random_value) | |
bf.validate_string(random_value_type) | |
bf.validate_string(change_address) | |
bf.validate_string(fee) | |
bf.validate_string(fee_rate) | |
bf.validate_string(fee_type) | |
bf.validate_item_in_list(fee_type, fee_type_options) | |
# future: validate_string_is_digits() for e.g. fee prior to int() conversion. | |
if fee_type == "fee": | |
fee = int(fee) | |
bf.validate_positive_int(fee) | |
elif fee_type == "fee_rate": | |
fee_rate = float(fee_rate) | |
if fee_rate < 0: | |
message = "Fee rate is negative. Fee rate: %s" % str(fee_rate) | |
stop(message) | |
bf.validate_item_in_list(random_value_type, random_value_type_options) | |
bf.validate_item_in_list(input_data["input_type"], input_type_options) | |
bf.validate_item_in_list(output_data["output_type"], output_type_options) | |
bf.validate_string(input_data["previous_output_index"]) | |
input_data["previous_output_index"] = int(input_data["previous_output_index"]) | |
if "bitcoin_amount" in input_data: | |
bf.validate_bitcoin_amount(input_data["bitcoin_amount"]) | |
b = bf.bitcoin_to_satoshi(input_data["bitcoin_amount"]) | |
input_data["satoshi_amount"] = b | |
elif "satoshi_amount" in input_data: | |
s = input_data["satoshi_amount"] | |
bf.validate_string(s) | |
input_data["satoshi_amount"] = int(s) | |
s = input_data["satoshi_amount"] | |
input_data["bitcoin_amount"] = bf.satoshi_to_bitcoin(s) | |
if "bitcoin_amount" in output_data: | |
bf.validate_bitcoin_amount(output_data["bitcoin_amount"]) | |
b = bf.bitcoin_to_satoshi(output_data["bitcoin_amount"]) | |
output_data["satoshi_amount"] = b | |
elif "satoshi_amount" in output_data: | |
s = output_data["satoshi_amount"] | |
bf.validate_string(s) | |
output_data["satoshi_amount"] = int(s) | |
s = output_data["satoshi_amount"] | |
output_data["bitcoin_amount"] = bf.satoshi_to_bitcoin(s) | |
bf.validate_list_contains_items(input_data.keys(), items=["txid", "previous_output_index", "private_key_hex", "satoshi_amount", "bitcoin_amount"]) | |
bf.validate_list_contains_items(output_data.keys(), items=["address", "satoshi_amount", "bitcoin_amount"]) | |
# future: validate_txid() | |
bf.validate_string(input_data["txid"]) | |
bf.validate_positive_int(input_data["previous_output_index"]) | |
bf.validate_hex(input_data["private_key_hex"]) | |
bf.validate_positive_int(input_data["satoshi_amount"]) | |
bf.validate_bitcoin_amount(input_data["bitcoin_amount"]) | |
# future: validate_bitcoin_address() | |
bf.validate_string(output_data["address"]) | |
bf.validate_positive_int(output_data["satoshi_amount"]) | |
bf.validate_bitcoin_amount(output_data["bitcoin_amount"]) | |
print "\n### START CREATION OF NONSTANDARD BITCOIN TRANSACTION\n" | |
print "- Fee type: %s" % fee_type | |
if fee_type == "fee": | |
print "- Fee: %s (satoshi)" % fee | |
elif fee_type == "fee_rate": | |
print "- Fee rate: %s (satoshi / byte)" % fee_rate | |
# create inputs | |
if input_data["input_type"] == "p2pkh": | |
input = tr.Input.create_from_txid( | |
txid = input_data["txid"], | |
previous_output_index = input_data["previous_output_index"], | |
private_key_hex = input_data["private_key_hex"], | |
bitcoin_amount = input_data["bitcoin_amount"], | |
) | |
# create outputs | |
if output_data["output_type"] == "p2pkh": | |
pass | |
elif output_data["output_type"] == "p2sh": | |
output = ntr.P2SHOutput.create_from_address( | |
address = output_data["address"], | |
bitcoin_amount = output_data["bitcoin_amount"], | |
) | |
inputs = [input] | |
outputs = [output] | |
random_values = [random_value] | |
# if random_value type is "raw_bytes", convert to hex. | |
if random_value_type == "raw_bytes": | |
random_values = [bf.bytes_to_hex(value) for value in random_values] | |
print "- Number of inputs (i.e. as-yet-unspent outputs): %d" % len(inputs) | |
print "- Number of outputs: %d" % len(outputs) | |
print "- Change address: %s" % change_address | |
# find the change_output corresponding to the change_address. | |
# future: how to handle the case where multiple transaction outputs have the change_address? | |
change_output = None | |
addresses = [] | |
for output in outputs: | |
addresses.append(output.address) | |
if output.address == change_address: | |
change_output = output | |
break | |
if change_output == None: | |
message = "Change address not found in output addresses." | |
message += "\nOutput addresses:" | |
for address in addresses: | |
message += "\n- %s" % address | |
message += "\nChange address: %s" % change_address | |
stop(message) | |
# Display the value to be sent to the change address. | |
print "- Amount to be sent to the change address: %s" % change_output.bitcoin_amount | |
# print out input addresses and the total value being spent from each one. | |
# future: create and test a transaction that spends two inputs from the same address. | |
input_addresses = {} | |
for input in inputs: | |
value = input.satoshi_amount | |
address = input.address | |
if address not in input_addresses: | |
input_addresses[address] = value | |
else: | |
input_addresses[address] += value | |
print "- Input addresses, with total-value-to-be-sent:" | |
for address in input_addresses: | |
satoshi = input_addresses[address] | |
bitcoin = bf.satoshi_to_bitcoin(satoshi) | |
print "-- %s: %s" % (address, bitcoin) | |
# print out output addresses and the total value being sent to each one. | |
# future: create and test a transaction that sends two outputs to the same address. | |
output_addresses = {} | |
for output in outputs: | |
value = output.satoshi_amount | |
address = output.address | |
if address not in output_addresses: | |
output_addresses[address] = value | |
else: | |
output_addresses[address] += value | |
print "- Output addresses, with total-value-to-be-received:" | |
for address in output_addresses: | |
satoshi = output_addresses[address] | |
bitcoin = bf.satoshi_to_bitcoin(satoshi) | |
print "-- %s: %s" % (address, bitcoin) | |
# confirm that total input value >= total output value | |
input_total = 0 # in satoshis | |
output_total = 0 # in satoshis | |
for input in inputs: | |
input_total += input.satoshi_amount | |
for output in outputs: | |
output_total += output.satoshi_amount | |
print "- Total value of all inputs: %s" % bf.satoshi_to_bitcoin(input_total) | |
print "- Total value of all outputs: %s" % bf.satoshi_to_bitcoin(output_total) | |
# send any extra input value (i.e. not assigned to be sent to an output) to the change_output. | |
# these amounts are in satoshis. | |
if input_total == output_total: | |
print "- Total value of all inputs exactly matches total value of all outputs." | |
elif input_total > output_total: | |
extra = input_total - output_total | |
new_amount = change_output.satoshi_amount + extra | |
change_output.set_satoshi_amount(new_amount) | |
print "- Total value of all inputs is greater than total value of all outputs." | |
print "-- Some input value has not been assigned to an output." | |
print "-- Extra value: %s" % bf.satoshi_to_bitcoin(extra) | |
print "-- This extra value will be sent to the change address." | |
print "-- Change address: %s" % change_output.address | |
print "-- New amount to be sent to change address: %s" % change_output.bitcoin_amount | |
elif input_total < output_total: | |
difference = output_total - input_total | |
message = "Total value of all inputs is less than total value of all outputs." | |
message += "\n- Difference: %s" % bf.satoshi_to_bitcoin(lack) | |
stop(message) | |
# estimate size of transaction | |
# we're only handling 1 input at the moment, so this code still works. | |
input_count_size = bf.hex_len(bf.int_to_var_int(len(inputs))) | |
inputs_size = len(inputs) * ( \ | |
32 + # previous_output_hash (32 bytes) | |
4 + # previous_output_index (4 bytes) | |
1 + # script_length (1-byte var_int) | |
138 + # byte length of scriptSig [approximately] | |
4 # sequence (4 bytes) | |
) | |
# distinguish between p2pkh and p2sh outputs. | |
output_size_total = 0 # bytes | |
output_count_size = bf.hex_len(bf.int_to_var_int(len(outputs))) | |
for output in outputs: | |
if hasattr(output, "output_type"): | |
if output.output_type == "p2sh": | |
output_size = ( \ | |
8 + # value | |
1 + # script_length (1-byte var_int) | |
23 # byte length of a P2SH outputScript (containing a redeemScript hash). | |
) | |
else: | |
# p2pkh output. | |
output_size = ( \ | |
8 + # value | |
1 + # script_length (1-byte var_int) | |
25 # byte length of a standard scriptPubKey | |
) | |
output_size_total += output_size | |
estimated_tran_size = ( | |
4 + # version (4 bytes) | |
input_count_size + # input_count | |
inputs_size + # byte length of concatenated inputs | |
output_count_size + # output_count | |
output_size_total + # byte length of concatenated outputs | |
4 # block lock time (4 bytes) | |
) | |
print "- Estimated transaction size: %d bytes" % estimated_tran_size | |
# calculate approximate fee | |
final_fee = None | |
if fee_type == "fee": | |
final_fee = fee | |
print "- Final fee: %d (satoshi)" % final_fee | |
elif fee_type == "fee_rate": | |
final_fee = estimated_tran_size * fee_rate | |
# round up to the nearest satoshi. | |
final_fee = int(math.ceil(float(final_fee))) | |
print "- Fee rate: %s (satoshi / byte)" % str(fee_rate) | |
print "- Calculate %d * %s and round up to nearest satoshi." % (estimated_tran_size, str(fee_rate)) | |
print "- Final fee: %d (satoshi)" % final_fee | |
if final_fee == None: | |
message = "Final fee not calculated." | |
stop(message) | |
print "- Final fee rate (using estimated transaction size): %.4f (satoshi per byte)" % (float(final_fee) / estimated_tran_size) | |
# check whether the fee can be paid from the amount assigned to the change address. | |
if final_fee > change_output.satoshi_amount: | |
message = "The fee is greater than the satoshi amount that is assigned to the change address." | |
message += "\nFee: %d (satoshi)" % final_fee | |
message += "\nAmount to be sent to change address: %d (satoshi)" % change_output.satoshi_amount | |
message += "\nDifference: %d (satoshi)" % (final_fee - change_output.satoshi_amount) | |
stop(message) | |
# subtract fee from value assigned to change_output | |
original_amount = change_output.satoshi_amount | |
new_amount = change_output.satoshi_amount - final_fee | |
change_output.set_satoshi_amount(new_amount) | |
print "- Fee subtracted from amount to be sent to change address." | |
print "- New amount to be sent to change address: %d (satoshi)" % change_output.satoshi_amount | |
# create transaction | |
tran = ntr.NonstandardTransaction.create(inputs, outputs) | |
print "" | |
print "Input 0:" | |
print tran.inputs[0].get_description() | |
print "" | |
print "Output 0:" | |
print tran.outputs[0].get_description() | |
print "" | |
print tran.get_description() | |
print "" | |
print tran.get_description_of_signable_form(input_index=0) | |
tran.sign(random_values) | |
print "" | |
print tran.get_description_of_signed_form() | |
print "" | |
print "Signed transaction:" | |
print tran.get_signed_form() | |
print "\n### END CREATION OF BITCOIN TRANSACTION\n" | |
def stop(message): | |
bf.stop(message) | |
if __name__ == '__main__': main() |
Run the script.
aineko:work stjohnpiano$ ./create_nonstandard_transaction.py
### START CREATION OF NONSTANDARD BITCOIN TRANSACTION
- Fee type: fee
- Fee: 225 (satoshi)
- Number of inputs (i.e. as-yet-unspent outputs): 1
- Number of outputs: 1
- Change address: 328cTqexYnQRbN5Dgs12D89sYiPPvtWVbF
- Amount to be sent to the change address: 0.00241000
- Input addresses, with total-value-to-be-sent:
-- 13xPBB175FtPbPQ84iB8KuawaVy3mHrady: 0.00241777
- Output addresses, with total-value-to-be-received:
-- 328cTqexYnQRbN5Dgs12D89sYiPPvtWVbF: 0.00241000
- Total value of all inputs: 0.00241777
- Total value of all outputs: 0.00241000
- Total value of all inputs is greater than total value of all outputs.
-- Some input value has not been assigned to an output.
-- Extra value: 0.00000777
-- This extra value will be sent to the change address.
-- Change address: 328cTqexYnQRbN5Dgs12D89sYiPPvtWVbF
-- New amount to be sent to change address: 0.00241777
- Estimated transaction size: 221 bytes
- Final fee: 225 (satoshi)
- Final fee rate (using estimated transaction size): 1.0181 (satoshi per byte)
- Fee subtracted from amount to be sent to change address.
- New amount to be sent to change address: 241552 (satoshi)
Input 0:
Input (without signature):
- previous_output_hash: 4d33e0192cd6ee93d2f5c18a4103b918da07f22305a85ec533a0a0cb4c225e74
- previous_output_index: 00000000
- sequence: ffffffff
- private_key_hex: 0000000000000000000000000000000000726f6164736964655f7069636e6963
- public_key_hex: 04b60eb0f9211eb7778ef54963b0a55c986d69f4dee4642ef7e7a16fc16c85eeda9a764032b5836c43e05ba520edd2a9040b9409b74b27da5b2fb1bfa48687b1a1
- script_length_scriptPubKey: 19
- scriptPubKey: 76a9142069a3fae01db74cef12d1d01811afdf6a3e1c2e88ac
- script_length: None
- scriptSig: None
- address: 13xPBB175FtPbPQ84iB8KuawaVy3mHrady
- previous_output_index_int: 0
- txid: 745e224ccba0a033c55ea80523f207da18b903418ac1f5d293eed62c19e0334d
Output 0:
Output:
- value: 90af030000000000
- script_length: 17
- script: a91404d73b326fb56e5d09e5f0b4bb3b83b7ff5fb05f87
- address: 328cTqexYnQRbN5Dgs12D89sYiPPvtWVbF
- bitcoin_amount: 0.00241552
- satoshi_amount: 241552
- redeem_script_hash_hex: 04d73b326fb56e5d09e5f0b4bb3b83b7ff5fb05f
Nonstandard Transaction (unsigned form):
- version: 01000000
- input_count: 01
- Input:
-- previous_output_hash: 4d33e0192cd6ee93d2f5c18a4103b918da07f22305a85ec533a0a0cb4c225e74
-- previous_output_index: 00000000
-- script_length: None
-- scriptSig: None
-- sequence: ffffffff
- output_count: 01
- Output [P2SH]:
-- value: 90af030000000000
-- script_length: 17
-- script: a91404d73b326fb56e5d09e5f0b4bb3b83b7ff5fb05f87
- block_lock_time: 00000000
Nonstandard Transaction (signable form):
- version: 01000000
- input_count: 01
- Input [to be used to sign this signable form]:
-- previous_output_hash: 4d33e0192cd6ee93d2f5c18a4103b918da07f22305a85ec533a0a0cb4c225e74
-- previous_output_index: 00000000
-- script_length_scriptPubKey: 19
-- scriptPubKey: 76a9142069a3fae01db74cef12d1d01811afdf6a3e1c2e88ac
-- sequence: ffffffff
- output_count: 01
- Output [P2SH]:
-- value: 90af030000000000
-- script_length: 17
-- script: a91404d73b326fb56e5d09e5f0b4bb3b83b7ff5fb05f87
- block_lock_time: 00000000
- hash_type_4_byte: 01000000
Nonstandard Transaction (signed form):
- version: 01000000
- input_count: 01
- Input:
-- previous_output_hash: 4d33e0192cd6ee93d2f5c18a4103b918da07f22305a85ec533a0a0cb4c225e74
-- previous_output_index: 00000000
-- script_length: 8a
-- scriptSig: 47304402201c26a71ec1993096081a9d8a39ad54613e428a4f28632a9bcb978a4321dfae4a0220546b3e0326be273ce3f2f780e269bec65346ed56fa99a0b65b42dd2f73782acf014104b60eb0f9211eb7778ef54963b0a55c986d69f4dee4642ef7e7a16fc16c85eeda9a764032b5836c43e05ba520edd2a9040b9409b74b27da5b2fb1bfa48687b1a1
-- sequence: ffffffff
- output_count: 01
- Output [P2SH]:
-- value: 90af030000000000
-- script_length: 17
-- script: a91404d73b326fb56e5d09e5f0b4bb3b83b7ff5fb05f87
- block_lock_time: 00000000
- hash_type_4_byte: 01000000
Signed transaction:
01000000014d33e0192cd6ee93d2f5c18a4103b918da07f22305a85ec533a0a0cb4c225e74000000008a47304402201c26a71ec1993096081a9d8a39ad54613e428a4f28632a9bcb978a4321dfae4a0220546b3e0326be273ce3f2f780e269bec65346ed56fa99a0b65b42dd2f73782acf014104b60eb0f9211eb7778ef54963b0a55c986d69f4dee4642ef7e7a16fc16c85eeda9a764032b5836c43e05ba520edd2a9040b9409b74b27da5b2fb1bfa48687b1a1ffffffff0190af03000000000017a91404d73b326fb56e5d09e5f0b4bb3b83b7ff5fb05f870000000001000000
### END CREATION OF BITCOIN TRANSACTION
Signed transaction:
01000000014d33e0192cd6ee93d2f5c18a4103b918da07f22305a85ec533a0a0cb4c225e74000000008a47304402201c26a71ec1993096081a9d8a39ad54613e428a4f28632a9bcb978a4321dfae4a0220546b3e0326be273ce3f2f780e269bec65346ed56fa99a0b65b42dd2f73782acf014104b60eb0f9211eb7778ef54963b0a55c986d69f4dee4642ef7e7a16fc16c85eeda9a764032b5836c43e05ba520edd2a9040b9409b74b27da5b2fb1bfa48687b1a1ffffffff0190af03000000000017a91404d73b326fb56e5d09e5f0b4bb3b83b7ff5fb05f870000000001000000
Let's test that this is the same as the transaction (tx2) that I broadcast earlier.
[the signed transaction generated just now]
[the signed transaction generated much earlier in the project]
equal
aineko:work stjohnpiano$ s1="01000000014d33e0192cd6ee93d2f5c18a4103b918da07f22305a85ec533a0a0cb4c225e74000000008a47304402201c26a71ec1993096081a9d8a39ad54613e428a4f28632a9bcb978a4321dfae4a0220546b3e0326be273ce3f2f780e269bec65346ed56fa99a0b65b42dd2f73782acf014104b60eb0f9211eb7778ef54963b0a55c986d69f4dee4642ef7e7a16fc16c85eeda9a764032b5836c43e05ba520edd2a9040b9409b74b27da5b2fb1bfa48687b1a1ffffffff0190af03000000000017a91404d73b326fb56e5d09e5f0b4bb3b83b7ff5fb05f870000000001000000"
[the signed transaction generated much earlier in the project]
aineko:work stjohnpiano$ s2="01000000014d33e0192cd6ee93d2f5c18a4103b918da07f22305a85ec533a0a0cb4c225e74000000008a47304402201c26a71ec1993096081a9d8a39ad54613e428a4f28632a9bcb978a4321dfae4a0220546b3e0326be273ce3f2f780e269bec65346ed56fa99a0b65b42dd2f73782acf014104b60eb0f9211eb7778ef54963b0a55c986d69f4dee4642ef7e7a16fc16c85eeda9a764032b5836c43e05ba520edd2a9040b9409b74b27da5b2fb1bfa48687b1a1ffffffff0190af03000000000017a91404d73b326fb56e5d09e5f0b4bb3b83b7ff5fb05f870000000001000000"
aineko:work stjohnpiano$ [[ "$s1" = "$s2" ]] && echo equal || echo not-equal
equal
Excellent.
And finally, a run-through of
generate_bitcoin_address_3.py,
because I have changed it and some of the functions it relies upon.
generate_bitcoin_address_3.py
#!/opt/local/bin/python | |
# DETAILS | |
# - Author: StJohn Piano | |
# - Date: 2018-10-05 | |
# - Description: This script generates a standard Bitcoin address from a private key. | |
# ENVIRONMENTS | |
# - Tested on: | |
# -- Python 2.7.13 running on Mac OS X 10.6.8 (Snow Leopard). | |
# NOTES | |
# | |
# - I define a "standard" address to be an uncompressed single-signature Pay-To-Public-Key-Hash (P2PKH) address. | |
# - This script was developed under Python 2.7.13 running on Mac OS X 10.6.8. | |
# | |
# This script takes as input one of the following two options: | |
# -- 1) a private key in raw byte form e.g. | |
# "hello_world" | |
# -- 2) a private key in hex form e.g. | |
# "2851cc22ea3fdfc71ae6282f54b801178f1e8ead64d9c916b211f109d80c0dba" | |
# | |
# - This script outputs: | |
# -- The private key in raw bytes, if this is the chosen input type. | |
# -- The private key in hex bytes. | |
# -- The private key in WIF form. WIF = Wallet Import Format. | |
# -- The Bitcoin address. | |
# | |
# - Dependencies: | |
# -- bitcoin_functions.py: A set of functions written by StJohn Piano. | |
# INSTRUCTIONS: | |
# | |
# 1) Choose the type of private key. | |
# - Set private_key_type to "raw_bytes" to use the value of private_key_raw_bytes as the private key when this script is run. | |
# - Set private_key_type to "hex_bytes" to use the value of private_key_hex_bytes as the private key when this script is run. | |
# | |
# 2) Set the value of the private key input. | |
# - private_key_raw_bytes must be a sequence of printable ASCII characters. | |
# - private_key_hex_bytes must be a sequence of hex characters. Hex characters in input variable values must be lowercase. | |
# Notes: | |
# - If an input is not used, it can be set to an empty string "". | |
# - The printable ASCII characters (newline, tab, and space are not shown), in order, are: | |
# ! " # $ % & ' ( ) * + , - . / 0 1 2 3 4 5 6 7 8 9 : ; < = > ? @ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ \ ] ^ _ ` a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~ | |
# -- Using a starting index of zero for the ASCII character set, the indices of the printable characters are: | |
# --- 9 (tab) | |
# --- 10 (newline) | |
# --- 32 (space) | |
# --- [33 to 126 inclusive] (the other printable characters) | |
# - hex characters: | |
# -- 0 1 2 3 4 5 6 7 8 9 a b c d e f | |
# - When used, a private key must be at least 1 byte long and at most 32 bytes long. | |
# -- For private_key_raw_bytes this is between 1 and 32 ASCII characters. | |
# -- For private_key_hex_bytes this is between 2 and 64 hex characters (1 byte is represented by 2 hex characters). | |
# DEPENDENCIES | |
import bitcoin_functions as bf | |
def main(): | |
##### START CONTROLS | |
# Input variables are all strings. Unused input variables can be set to the empty string {""}. | |
# Possible values for private_key_type: | |
# ["raw_bytes", "hex_bytes"] | |
private_key_type = "raw_bytes" | |
private_key_raw_bytes = "roadside_picnic" | |
# Hex characters in input variable values must be lowercase. | |
private_key_hex_bytes = "2851cc22ea3fdfc71ae6282f54b801178f1e8ead64d9c916b211f109d80c0dba" | |
##### END CONTROLS | |
##### START SETTINGS | |
private_key_type_options = ["raw_bytes", "hex_bytes"] | |
##### END SETTINGS | |
# preparation | |
bf.validate_item_in_list(private_key_type, private_key_type_options) | |
print "\n### START GENERATION OF BITCOIN ADDRESS\n" | |
if private_key_type == "raw_bytes": | |
bf.validate_string(private_key_raw_bytes) | |
input = private_key_raw_bytes | |
print "Private key (raw bytes): %s" % input | |
bf.validate_bitcoin_private_key_bytes(input) | |
input = bf.bytes_to_hex(input) | |
print "Private key (hex bytes): %s" % input | |
input = bf.left_pad_hex_to_length_x(input, 32) | |
print "Private key (32 hex bytes): %s" % input | |
elif private_key_type == "hex_bytes": | |
bf.validate_hex(private_key_hex_bytes) | |
input = private_key_hex_bytes | |
print "Private key (hex bytes): %s" % input | |
input = bf.left_pad_hex_to_length_x(input, 32) | |
print "Private key (32 hex bytes): %s" % input | |
bf.validate_bitcoin_private_key_hex(input) | |
private_key_wif = bf.private_key_hex_to_wif(input) | |
print "Private key (WIF): %s" % private_key_wif | |
bitcoin_address = bf.private_key_hex_to_bitcoin_address(input) | |
print "Bitcoin address: %s " % bitcoin_address | |
print "\n### END GENERATION OF BITCOIN ADDRESS\n" | |
if __name__ == '__main__': main() |
Run the script.
aineko:work stjohnpiano$ ./generate_bitcoin_address_3.py
### START GENERATION OF BITCOIN ADDRESS
Private key (raw bytes): roadside_picnic
Private key (hex bytes): 726f6164736964655f7069636e6963
Private key (32 hex bytes): 0000000000000000000000000000000000726f6164736964655f7069636e6963
Private key (WIF): 5HpHagT65TZzG1PH3CSu63k8Dxn19XHMLQhSCuE8qpbeuFK2mGG
Bitcoin address: 13xPBB175FtPbPQ84iB8KuawaVy3mHrady
### END GENERATION OF BITCOIN ADDRESS
Address generated here:
13xPBB175FtPbPQ84iB8KuawaVy3mHrady
Confirm that the address generated here matches the address generated earlier (the target address).
[the address generated just now]
[the target address generated much earlier in the project]
equal
aineko:work stjohnpiano$ s1="13xPBB175FtPbPQ84iB8KuawaVy3mHrady"
[the target address generated much earlier in the project]
aineko:work stjohnpiano$ s2="13xPBB175FtPbPQ84iB8KuawaVy3mHrady"
aineko:work stjohnpiano$ [[ "$s1" = "$s2" ]] && echo equal || echo not-equal
equal
Good.
Loose end: Delete temporary archive directory "tmp" in the work directory.
That's the end of this project.
[start of notes]
Changes from the original text:
- I have not always preserved the format of any excerpts from webpages on other sites (e.g. not preserving the original bold/italic styles, changing the list structures, not preserving hyperlinks).
- I have not always preserved the format of any computer output (e.g. from running bash commands). Examples: Setting input lines in bold text, adding/removing newlines in order to make a sequence of commands easier to read, using hyphens for lists and sublists instead of indentation, breaking wide tables into consecutive sections.
[end of notes]