Bitcoin: ключи, адреса и транзакции
Свой блокчейн мы уже собрали: аккаунты, балансы, Ed25519, PoW. Теперь посмотрим на первый и самый живучий блокчейн и специально будем сравнивать. Bitcoin почти в каждом решении пошёл иначе, чем мы, и почти всегда не случайно. Понять почему это лучший способ понять и свой узел, и сам Bitcoin.
Идея
Bitcoin это та же реплицированная машина состояний, что и наш узел, но состояние в ней описано наоборот. У нас состояние это таблица «адрес, сколько у него денег». В Bitcoin состояния «сколько у адреса» не существует вообще. Есть только набор непотраченных выходов транзакций, и кошелёк складывает свой баланс сам, находя выходы, которые может потратить.
Это UTXO-модель. Мы её сознательно не взяли (урок 13 объясняет почему аккаунты проще для учебного узла), но именно UTXO стоит в основе Bitcoin, и у неё есть свои сильные стороны: параллельная проверка, приватность через новые адреса, отсутствие глобального nonce.
Деньги считаются в сатоши: 1 BTC это ровно 100 000 000 сатоши. Целые числа, никаких дробей и никакого float (почему float тут был бы катастрофой, мы разбирали в уроке 20 раздела Rust).
Ключи: снова кривая, но другая
Bitcoin подписывает транзакции на кривой secp256k1, не на ed25519, который мы взяли для своего узла. Математику кривых мы прошли в уроке 04: закрытый ключ это скаляр d, открытый это точка P = d·G, а восстановить d из P мешает задача дискретного логарифма.
Рождается закрытый ключ тривиально: это случайный скаляр от 1 до n минус 1, где n это порядок группы точек, простое число около 2^256. Никакой структуры, просто 32 байта хорошей энтропии, и почти любые 32 байта годятся: диапазон настолько огромен, что случайно промахнуться мимо него нельзя. Для переноса между кошельками ключ записывают в формате WIF: Base58Check от байта версии 0x80, самого ключа и флага сжатия. Та же упаковка с контрольной суммой, что у адресов чуть ниже.
Разница в упаковке. Открытый ключ secp256k1 это точка (x, y), но y восстанавливается из x с точностью до знака, поэтому его хранят в сжатом виде: один байт чётности плюс координата x, всего 33 байта. Несжатый вид (байт 0x04 плюс x плюс y, 65 байт) сейчас почти не используют.
/// Открытый ключ secp256k1 в сжатом виде: 33 байта.
/// Первый байт 0x02 если y чётный, 0x03 если нечётный, дальше x (32 байта).
pub struct CompressedPublicKey([u8; 33]);
impl CompressedPublicKey {
pub fn prefix(&self) -> u8 {
self.0[0]
}
pub fn is_y_even(&self) -> bool {
self.0[0] == 0x02
}
}
Подпись у Bitcoin это ECDSA, а не EdDSA. В уроке 05 мы видели ловушку ECDSA: повтор nonce выдаёт закрытый ключ, поэтому подписи делают детерминированными по RFC 6979. Там же жила малигибельность: из подписи (r, s) тривиально делается вторая валидная (r, n - s), и это меняло txid. Bitcoin прикрыл дыру политикой low-s: начиная с Bitcoin Core 0.11.1 узлы не ретранслируют транзакции с подписью в высокой форме. Важно, что это standardness-политика узлов, а не правило консенсуса: софтфорк BIP-146, который сделал бы low-s обязательным для всех, так и не активировали. По-настоящему проблему изменчивого txid закрыл SegWit, к нему вернёмся в уроке 21.
Адрес это отпечаток ключа, но обёрнутый
У своего узла адрес это первые 20 байт хеша ключа (урок 18). Идея Bitcoin ровно та же, только хеш двойной и результат аккуратно упакован, чтобы человек не ошибся при копировании.
Ядро адреса это HASH160: RIPEMD160(SHA256(pubkey)), ровно 20 байт. Дальше эти 20 байт заворачиваются в один из форматов адреса.
/// Ядро большинства адресов: 20 байт от двойного хеша ключа.
/// SHA-256 даёт стойкость, RIPEMD-160 ужимает до 20 байт.
pub fn hash160(public_key: &[u8]) -> [u8; 20] {
let sha = sha256(public_key);
ripemd160(&sha) // 20 байт
}
Форматов адреса три поколения, и по началу строки видно, какой перед тобой:
- P2PKH, начинается с
1. Base58Check от версии0x00плюс HASH160. Платёж «тому, кто докажет владение ключом с этим хешем». - P2SH, начинается с
3. Base58Check от версии0x05плюс хеш скрипта. Платёж «тому, кто предъявит скрипт с этим хешем и данные к нему». - Bech32, начинается с
bc1. Это SegWit-формат:bc1q...для witness v0,bc1p...для Taproot. Контрольная сумма ловит опечатки лучше Base58 и весь адрес в нижнем регистре.
Общее у всех трёх это встроенная контрольная сумма: адрес с опечаткой почти наверняка не пройдёт проверку, и монеты не уйдут в никуда. У нашего узла такой защиты не было, адрес был просто 20 голых байт.
премиум-урок
Продолжение в премиуме: 1 200 ₽ в месяц или 10 000 ₽ в год. Вместе с ним серверные проверки задач и школьные токены AI-коуча.
Оплата пока не подключена: премиум и курсы открываются промокодом.