inital commit, already did quite a few things.
This commit is contained in:
commit
a3619df8cf
6 changed files with 2321 additions and 0 deletions
6
README.md
Normal file
6
README.md
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
# Chatochi notes
|
||||
|
||||
My notes on running bitcoin and lighting nodes, as well as in learning more
|
||||
about learning more through Mastering Bitcoin and Mastering the Lightning
|
||||
Network.
|
||||
|
||||
2048
code_samples/bip39-english-words.txt
Normal file
2048
code_samples/bip39-english-words.txt
Normal file
File diff suppressed because it is too large
Load diff
100
code_samples/seed_playground.py
Normal file
100
code_samples/seed_playground.py
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
from typing import List, Dict, Tuple
|
||||
|
||||
|
||||
def get_words_from_file(file_path) -> Tuple[dict, dict]:
|
||||
with open(file_path, "r") as words_file:
|
||||
|
||||
lines_contents = words_file.read().splitlines()
|
||||
|
||||
words = {index: word for index, word in enumerate(lines_contents)}
|
||||
indexes = {word: index for index, word in enumerate(lines_contents)}
|
||||
|
||||
return words, indexes
|
||||
|
||||
|
||||
WORDS, INDEXES = get_words_from_file("bip39-english-words.txt")
|
||||
|
||||
|
||||
class MnemonicWord:
|
||||
def __init__(self, word: str):
|
||||
word = word.lstrip().rstrip().lower()
|
||||
if word not in WORDS.values():
|
||||
raise ValueError(f"'{word}' is not a valid bip39 word.")
|
||||
self.word = word
|
||||
|
||||
def as_int(self):
|
||||
return INDEXES[self.word]
|
||||
|
||||
def as_binary(self):
|
||||
return bin(INDEXES[self.word])
|
||||
|
||||
|
||||
class MnemonicPhrase:
|
||||
|
||||
allowed_phrase_lengths = [12, 18, 24]
|
||||
|
||||
def __init__(self, words: List[MnemonicWord]):
|
||||
if len(words) not in self.allowed_phrase_lengths:
|
||||
raise ValueError(
|
||||
f"The mnemonic phrase length should be one of {self.allowed_phrase_lengths}, but it was {len(words)}"
|
||||
)
|
||||
self.words = {index: word for index, word in enumerate(words)}
|
||||
|
||||
def __iter__(self):
|
||||
for index, word in self.words.items():
|
||||
yield word
|
||||
|
||||
def __getitem__(self, item):
|
||||
return self.words[item]
|
||||
|
||||
def as_bin(self):
|
||||
binary_representation = "0b" + "".join(
|
||||
[word.as_binary()[2:] for word in self]
|
||||
)
|
||||
return binary_representation
|
||||
|
||||
def as_int(self):
|
||||
return int(
|
||||
self.as_bin(), base=2
|
||||
)
|
||||
|
||||
|
||||
class Seed:
|
||||
def __init__(self, seed_as_int: int):
|
||||
self._seed_as_int = seed_as_int
|
||||
|
||||
@property
|
||||
def as_mnemonic(self) -> List[str]:
|
||||
|
||||
|
||||
return self._seed_as_mnemonic(self._seed)
|
||||
pass
|
||||
|
||||
@property
|
||||
def as_integer(self) -> int:
|
||||
return self._seed_as_int
|
||||
|
||||
@property
|
||||
def as_binary(self) -> str:
|
||||
return bin(self._seed_as_int)
|
||||
|
||||
@property
|
||||
def as_hex(self) -> str:
|
||||
return hex(self._seed_as_int)
|
||||
|
||||
@classmethod
|
||||
def from_mnemonic(cls, mnemonic_phrase: MnemonicPhrase):
|
||||
seed_as_int = mnemonic_phrase.as_int()
|
||||
return cls(seed_as_int)
|
||||
|
||||
@classmethod
|
||||
def from_binary(cls, binary_seed_representation: str):
|
||||
return cls(int(binary_seed_representation, base=2))
|
||||
|
||||
@classmethod
|
||||
def from_integer(cls, int_seed_representation: int):
|
||||
return cls(int_seed_representation)
|
||||
|
||||
@classmethod
|
||||
def from_hex(cls, hex_seed_representation: str):
|
||||
return cls(int(hex_seed_representation, base=16))
|
||||
101
code_samples/test_seed_playground.py
Normal file
101
code_samples/test_seed_playground.py
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
import pytest
|
||||
|
||||
from seed_playground import WORDS, MnemonicWord, MnemonicPhrase
|
||||
|
||||
|
||||
def is_a_bin_num_with_0b(possible_bin_num):
|
||||
allowed_chars = {"0", "1", "b"}
|
||||
if possible_bin_num[:2] == "0b" and set(possible_bin_num) <= allowed_chars:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def twelve_valid_words():
|
||||
raw_words = [
|
||||
"primary",
|
||||
"find",
|
||||
"roof",
|
||||
"forget",
|
||||
"subject",
|
||||
"present",
|
||||
"pipe",
|
||||
"primary",
|
||||
"flavor",
|
||||
"grant",
|
||||
"remain",
|
||||
"present",
|
||||
]
|
||||
mnemonic_words = [MnemonicWord(a_word) for a_word in raw_words]
|
||||
return mnemonic_words
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def a_valid_mnemonic_phrase(twelve_valid_words):
|
||||
return MnemonicPhrase(twelve_valid_words)
|
||||
|
||||
|
||||
def test_reading_the_file_works():
|
||||
|
||||
there_are_2048_words = len(WORDS) == 2048
|
||||
|
||||
first_word_is_abandon = WORDS[0] == "abandon"
|
||||
last_word_is_zoo = WORDS[2047] == "zoo"
|
||||
word_1957_is_virus = WORDS[1956] == "virus"
|
||||
|
||||
assert (
|
||||
there_are_2048_words
|
||||
and first_word_is_abandon
|
||||
and last_word_is_zoo
|
||||
and word_1957_is_virus
|
||||
)
|
||||
|
||||
|
||||
def test_passing_bip39_valid_word_instantiates_mnemonic_word_correctly():
|
||||
word = MnemonicWord("abandon")
|
||||
assert isinstance(word, MnemonicWord)
|
||||
|
||||
|
||||
def test_passing_random_word_to_mnemonic_word_raises_exception():
|
||||
with pytest.raises(ValueError):
|
||||
word = MnemonicWord("Machiavelli")
|
||||
|
||||
|
||||
def test_mnemonic_word_can_be_represented_as_binary():
|
||||
word = MnemonicWord("abandon")
|
||||
word_as_bin = word.as_binary()
|
||||
assert is_a_bin_num_with_0b(word_as_bin)
|
||||
|
||||
|
||||
def test_mnemonic_word_can_be_represented_as_int():
|
||||
word = MnemonicWord("abandon")
|
||||
|
||||
word_as_int = word.as_int()
|
||||
|
||||
assert isinstance(word_as_int, int)
|
||||
|
||||
|
||||
def test_mnemonic_phrase_instantiates_properly_with_12_words(
|
||||
twelve_valid_words
|
||||
):
|
||||
phrase = MnemonicPhrase(twelve_valid_words)
|
||||
assert isinstance(phrase, MnemonicPhrase)
|
||||
|
||||
|
||||
def test_mnemonic_phrase_fails_with_13_words(twelve_valid_words):
|
||||
twelve_valid_words.append(MnemonicWord("abandon"))
|
||||
thirteen_valid_words = twelve_valid_words
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
phrase = MnemonicPhrase(thirteen_valid_words)
|
||||
|
||||
|
||||
def test_mnemonic_phrase_is_iterable(a_valid_mnemonic_phrase):
|
||||
for word in a_valid_mnemonic_phrase:
|
||||
word
|
||||
|
||||
|
||||
def test_mnemonic_phrase_as_bin_returns_a_valid_bin(a_valid_mnemonic_phrase):
|
||||
bin_representation = a_valid_mnemonic_phrase.as_bin()
|
||||
|
||||
assert is_a_bin_num_with_0b(bin_representation)
|
||||
65
mastering_bitcoin_notes.md
Normal file
65
mastering_bitcoin_notes.md
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
# Mastering Bitcoin notes
|
||||
|
||||
So, after years of messing around with Bitcoin, the time has finally come to
|
||||
reach the bottom of the rabbit hole and understand things down to the code
|
||||
level.
|
||||
|
||||
I'm going to use this document to keep my notes and thoughts while going
|
||||
through the book.
|
||||
|
||||
----------
|
||||
|
||||
"There's a lot more to Bitcoin than first meets the eye." Joder, ya te digo.
|
||||
|
||||
----------
|
||||
|
||||
Finally, I get to discover what the hell is an `SPV`: simplified payment
|
||||
verification, which is fancy pants language for a client that keeps keys and
|
||||
performs operations but relies on another full-node for following the protocol
|
||||
and getting the blockchain data from other peers. So, if I understand
|
||||
correctly, an Electrum Personal Server fits the definition of an SPV.
|
||||
|
||||
----------
|
||||
|
||||
Andreas mentions that we should talk about "mnemonic phrase", and not a "seed
|
||||
phrase", because "even though common, its use is incorrect". But, why? -> So
|
||||
it works this way: technically, the seed is a 512-bit piece of data, which is
|
||||
the actual piece of information used to generate the keys of a wallet. The
|
||||
mnemonic is just a human-readeable proxy to this seed, hence why mnemonic !=
|
||||
seed.
|
||||
|
||||
This sparked my curiosity and I have been reading more low level details on how
|
||||
seed generation works.
|
||||
|
||||
The first thing that's required is randomness. To get this, a series of bits is
|
||||
generated. Specifically, the entropy should be between 128 and 256 bits (that
|
||||
means, 128 to 256 random zeros and ones).
|
||||
|
||||
The checksum for this entropy is computed the following way:
|
||||
|
||||
- Generate the SHA256 hash of the entropy.
|
||||
- Starting from the left, grab one bit of the hash for every 32 bits of length
|
||||
in the original entropy (if the entropy is 128 bits, 128/32 = 4, you grab the 4
|
||||
first bits of the hash).
|
||||
|
||||
```python
|
||||
|
||||
entropy = "01000010011111001011100101101111001010010110101011011111000001101000110111111001010000101000000110011010110011110110010000011001"
|
||||
entropy_len = len(entropy)
|
||||
|
||||
import hashlib
|
||||
|
||||
hash_of_entropy = hashlib.sha256(entropy)
|
||||
|
||||
print(hash_of_entropy)
|
||||
|
||||
|
||||
|
||||
|
||||
```
|
||||
|
||||
----------
|
||||
|
||||
Another interesting fact: miner fees for a transaction are not explicitly
|
||||
specified anywhere, but are simply calculated as the difference between the
|
||||
inputs and the outputs of the transaction.
|
||||
1
requirements.txt
Normal file
1
requirements.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
pytest
|
||||
Loading…
Add table
Add a link
Reference in a new issue