# ---
# jupyter:
#   jupytext:
#     text_representation:
#       extension: .py
#       format_name: light
#       format_version: '1.5'
#       jupytext_version: 1.14.4
#   kernelspec:
#     display_name: Python 3
#     language: python
#     name: python3
# ---

# + [markdown] colab_type="text" id="view-in-github"
# <a href="https://colab.research.google.com/github/xamevou/someJupyterNotebooks/blob/main/KeyAndAddressGeneration.ipynb" target="_parent"><img src="https://colab.research.google.com/assets/colab-badge.svg" alt="Open In Colab"/></a>
# -

# # Keys and addresses in Bitcoin
#
# Taken from example 5 of chaper 4 in Antonopoulos' book.

# First, we need the bitcoin package. 
# I will also use the logging module because it's a better practice to log than to print.

# + colab={"base_uri": "https://localhost:8080/"} id="8xq8H11tvNEC" outputId="0f74aae9-56c7-4fba-f5fc-190381f11b1e"
# # !pip install bitcoin   # Uncomment if needed
from __future__ import print_function
import bitcoin
import logging
logging.basicConfig(level=logging.INFO, format='%(levelname)s - %(message)s')
# Uncomment if you want to disable logs
# logging.disable(logging.INFO)
# -

# ## Generate a random private key
#
# We generate a random number but, to be a valid private key, it must be an integer between 1 and `bitcoin.N`. This last number, in the standard secp256k1, is the size $p$ of the Finite Field $F_p$ where the elliptic curve $y^2=x^3+7$ is defined. Its value is: $2^{256} - 2^{32} - 2^9 - 2^8 - 2^7 - 2^6 - 2^4 - 1$.
#
# *Note: In Antonopoulos' book, the example 4.3 is in cpp. If you want to reproduce that example here, change `bitcoin.random_key()` with the following string: `private_key = "038109007313a5807b2eccc082c8c3fbb988a973cacf1a7df9ce725c31b14776"`*

# + colab={"base_uri": "https://localhost:8080/"} id="suTTkTFTt9EI" outputId="6d18df92-7139-40ed-df1d-35e7b298f909"
logging.info('We generate random numbers until we find one lower than ' + str(bitcoin.N))

valid_private_key = False
while not valid_private_key:    
    private_key = bitcoin.random_key()
    decoded_private_key = bitcoin.decode_privkey(private_key, 'hex')
    valid_private_key = 0 < decoded_private_key < bitcoin.N

print("Private Key (hex) is: ", private_key)
print("Private Key (decimal) is: ", decoded_private_key)
# -

# ### WIF
# The private key can be converted to Wallet Import Format (WIF). Supposedly, the WIF format encoding allows easy copy of a big number.
#

wif_encoded_private_key = bitcoin.encode_privkey(decoded_private_key, 'wif')
print("Private Key (WIF) is: ", wif_encoded_private_key)

#
# We add suffix "01" to the original private key to create a "compressed" private key. Basically, adding "01" does not compress anything; it's just a signal for the wallet software to use that private key for generating compressed public keys ONLY. The goal is to keep compatibility of newer wallets with the old ones, which don't manage "compressed" keys.
# More info on "compression" later. 
#
# We can also generate a WIF format from the compressed private key (WIF-compressed)

# + colab={"base_uri": "https://localhost:8080/"} id="OR5QHleUuEZ1" outputId="a106c76b-8358-4cfa-882c-f67b760f84bd"
logging.info('Now, compression. Note the 01 added at the end of the already generated private key.')

compressed_private_key = private_key + '01'
print("Private Key Compressed (hex) is: ", compressed_private_key)
wif_compressed_private_key = bitcoin.encode_privkey(bitcoin.decode_privkey(compressed_private_key, 'hex'), 'wif')
print("Private Key (WIF-Compressed) is: ", wif_compressed_private_key)
# -

# ## From Private Key to Public Key
#
# We multiply the private key and the Elliptic Curve generator point G, `bitcoin.G`. The result is a point that will be used as public key. 
#
# The G point, for those interested, is $(55066263022277343669578718895168534326250603453777594175500187360389116729240, 32670510020758816978083085130507043184471273380659243275938904335757337482424)$

# + colab={"base_uri": "https://localhost:8080/"} id="Y-6K6qVWuGPy" outputId="5ccbf459-7dcb-440d-8154-297125ce71d7"
public_key = bitcoin.fast_multiply(bitcoin.G, decoded_private_key)
print("Public Key (x,y) is:", public_key)
# -

# Now we encode that public key as hex. It is done by concatenation of prefix 04 with x (in hex) and y (in hex). 

# + colab={"base_uri": "https://localhost:8080/"} id="CsiEppUhuIFk" outputId="edd23916-1ef6-4e9c-bdc6-55e0b632e0f0"
logging.info('The x component in hex is: ' + hex(public_key[0]) + 
             '\nThe y component in hex is: ' + hex(public_key[1]))

hex_encoded_public_key = bitcoin.encode_pubkey(public_key, 'hex')
print("\nPublic Key (hex) is:", hex_encoded_public_key)
# -

# ### On "compression"
#
# The public key is a point, as you see, but we can manage with only the x component.
# "Compression" makes reference to the process of using only the x component of the (x,y) pair that forms the public key. Every use of "compression" refers to that process. For instance, a "compressed private key" is one that will be used only for generating compressed public keys.
#
# So now we compress the public key, adjusting prefix depending on whether $y$ is even or odd.

# + colab={"base_uri": "https://localhost:8080/"} id="4MkNOB8puJyi" outputId="f0050ddd-26ca-4391-ac2b-396fae4cbf8b"
(public_key_x, public_key_y) = public_key
compressed_prefix = '02' if (public_key_y % 2) == 0 else '03'
hex_compressed_public_key = compressed_prefix + (bitcoin.encode(public_key_x, 16).zfill(64))
print("Compressed Public Key (hex) is:", hex_compressed_public_key)
# -

# ## Generation of the bitcoin address from the public key
#
# Finally, we will generate the address that corresponds to the public key. This will be in b58check, which is done by double hashing, adding a checksum, etc. It's the base for creating bitcoin QR images.

# + colab={"base_uri": "https://localhost:8080/"} id="97GUjt_ouLgx" outputId="84927a9b-2b46-4fd8-beb9-893a84a94424"
logging.info("Generating address in b58check from our public key " + str(public_key))
print("\nBitcoin Address (b58check) is:", bitcoin.pubkey_to_address(public_key))
# -

# The address will be compressed next (meaning: it will be generated from the compressed public key, the one with x component only).

# + colab={"base_uri": "https://localhost:8080/"} id="gdBuIPixuNb0" outputId="4d348173-d6f4-4104-e31c-9d1fc532f149"
logging.info("Generating compressed address from our compressed public key " + hex_compressed_public_key)

print("\nCompressed Bitcoin Address (b58check) is:", bitcoin.pubkey_to_address(hex_compressed_public_key))

# + [markdown] id="idB0V-DavVnl"
# Please note that **the address generated is different if we started with the (x,y) public key or with the compressed (x) one.** The wallet software must take care for the compatibility between old non-compressed addresses and compressed ones, by looking at the "01" suffix in the private key.
#
# And that's all !
# -

# ## NOTE
# As you see, this process proceeds in a one-to-one fashion: one seed, one private key, one public key, one address (compressed or not). Obviously we want more flexibility in our wallets. That's what the seed words are for: with the words, several seeds are generated, which lead to several private keys, etc. 
#
# From the words to the keys, that should be a topic for another Notebook. 

StarcoderdataJupyterScriptsDedupFiltered