Требуемые знания:
- 09-elliptic-curves
- 10-secp256k1-ed25519
ECDSA: цифровая подпись с визуализацией
Зачем это нужно в блокчейне
Каждая транзакция Bitcoin и Ethereum подписана алгоритмом ECDSA. Когда вы отправляете BTC, ваш кошелек вычисляет подпись (r, s) используя приватный ключ. Любой может проверить подпись с помощью публичного ключа, но никто не может подделать её.
Цифровая подпись — это математическое доказательство того, что именно вы авторизовали транзакцию. Без ECDSA блокчейн невозможен: не было бы способа доказать, что именно владелец средств инициировал перевод.
В Bitcoin каждая транзакция содержит поле
scriptSigс ECDSA-подписью. В Ethereum подпись (v, r, s) включена в каждую транзакцию и используется для восстановления адреса отправителя черезecrecover.
Что такое цифровая подпись
Цифровая подпись обеспечивает три свойства:
| Свойство | Описание | Аналогия |
|---|---|---|
| Аутентификация | Подтверждение личности подписанта | Как подпись на документе |
| Целостность | Гарантия, что сообщение не изменено | Как печать на конверте |
| Неотказуемость | Подписант не может отрицать подпись | Как нотариальное заверение |
В отличие от рукописной подписи, цифровая подпись математически привязана к конкретному сообщению. Нельзя “перенести” подпись с одной транзакции на другую.
ECDSA: подпись по шагам
Пройдем алгоритм подписи ECDSA пошагово. Нажимайте “Далее” чтобы увидеть каждый этап вычисления:
Формулы ECDSA подписи
Дано:
- d — приватный ключ (секретное число)
- h = SHA-256(сообщение) — хеш сообщения
- G — генератор кривой, n — порядок группы
Алгоритм:
- Выбираем случайное k из [1, n-1]
- Вычисляем R = k * G (скалярное умножение на кривой)
- Берем r = R.x mod n (x-координата точки R)
- Вычисляем s = k^(-1) * (h + r * d) mod n
- Подпись: (r, s)
Верификация подписи
Любой, зная публичный ключ Q и сообщение, может проверить подпись:
Алгоритм верификации
Дано: подпись (r, s), хеш h, публичный ключ Q = d * G
- Вычисляем s_inv = s^(-1) mod n
- Вычисляем u1 = h * s_inv mod n
- Вычисляем u2 = r * s_inv mod n
- Вычисляем точку P = u1 * G + u2 * Q
- Проверяем: P.x mod n == r ?
Если равны — подпись валидна!
Код на Python: пошаговая подпись ECDSA
from ecdsa import SECP256k1, SigningKey
from ecdsa.numbertheory import inverse_mod
import hashlib, os
# Параметры кривой secp256k1
curve = SECP256k1
G = curve.generator
n = curve.order
# 1. Генерация ключей
d = int.from_bytes(os.urandom(32), 'big') % (n - 1) + 1 # приватный ключ
Q = d * G # публичный ключ
# 2. Хеш сообщения
message = b"Transfer 1 BTC to Alice"
h = int(hashlib.sha256(message).hexdigest(), 16)
# 3. Подпись (пошагово)
k = int.from_bytes(os.urandom(32), 'big') % (n - 1) + 1 # случайный nonce
R = k * G # точка на кривой
r = R.x() % n # x-координата mod n
s = (inverse_mod(k, n) * (h + r * d)) % n # формула ECDSA
print(f"Подпись: r = {hex(r)}")
print(f" s = {hex(s)}")
# 4. Верификация
s_inv = inverse_mod(s, n)
u1 = (h * s_inv) % n
u2 = (r * s_inv) % n
P = u1 * G + u2 * Q
assert P.x() % n == r, "Подпись невалидна!"
print("Подпись верифицирована!")
Подпись через библиотеку ecdsa
from ecdsa import SigningKey, SECP256k1
# Генерация ключей
sk = SigningKey.generate(curve=SECP256k1)
vk = sk.get_verifying_key()
# Подпись
message = b"Transfer 1 BTC to Alice"
signature = sk.sign(message)
# Верификация
assert vk.verify(signature, message)
print("Подпись верифицирована!")
КРИТИЧЕСКИ ВАЖНО: Безопасность nonce
Nonce k — самое уязвимое место ECDSA. Если атакующий узнает k или если k повторяется — приватный ключ мгновенно раскрывается.
Атака повторного использования nonce
Если две подписи (r, s1) и (r, s2) используют один и тот же k:
s1 = k^(-1) * (h1 + r*d) mod n
s2 = k^(-1) * (h2 + r*d) mod n
Вычитаем:
s1 - s2 = k^(-1) * (h1 - h2) mod n
Находим k:
k = (h1 - h2) * (s1 - s2)^(-1) mod n
Находим приватный ключ:
d = (s * k - h) * r^(-1) mod n
Это не теоретическая угроза! В 2010 году хакеры fail0verflow извлекли приватный ключ Sony из прошивки PlayStation 3. Sony использовала фиксированное значение k = 4 (константа!) для подписи всех обновлений. Одинаковое r в двух подписях позволило восстановить приватный ключ и запустить любой код на PS3.
ПРАВИЛО: Никогда не используйте фиксированный или предсказуемый nonce k. Даже “хороший” генератор случайных чисел на слабом оборудовании может привести к утечке ключей.
RFC 6979: детерминированный nonce
Решение проблемы случайного nonce — RFC 6979 (2013). Вместо генерации случайного k, вычисляем его детерминированно:
k = HMAC-SHA256(приватный_ключ, хеш_сообщения)
Преимущества:
- Детерминированность: один и тот же ключ + сообщение всегда дают одинаковый k
- Уникальность: разные сообщения дают разные k (свойство HMAC)
- Не нужен генератор случайных чисел: работает на устройствах без хорошего /dev/urandom
- Проверяемость: можно верифицировать, что подпись создана правильно
from ecdsa import SigningKey, SECP256k1
sk = SigningKey.generate(curve=SECP256k1)
# Детерминированная подпись (RFC 6979) — РЕКОМЕНДУЕМЫЙ подход
sig1 = sk.sign_deterministic(b"Transfer 1 BTC")
sig2 = sk.sign_deterministic(b"Transfer 1 BTC")
assert sig1 == sig2 # Одинаковый ввод -> одинаковая подпись
print("Детерминированные подписи совпадают!")
Математический уровень: почему верификация работает
Покажем алгебраически, что верификация корректна.
Дано: подпись (r, s), где s = k^(-1)(h + r*d) mod n
Верификация: P = u1G + u2Q, где u1 = hs^(-1), u2 = rs^(-1)
Доказательство:
P = u1*G + u2*Q
= u1*G + u2*(d*G) (подставляем Q = d*G)
= (u1 + u2*d)*G (линейность скалярного умножения)
Подставляем u1 и u2:
= (h*s^(-1) + r*s^(-1)*d)*G
= s^(-1)*(h + r*d)*G (выносим s^(-1))
Подставляем s = k^(-1)*(h + r*d):
= (k^(-1)*(h + r*d))^(-1) * (h + r*d) * G
= k * (h + r*d)^(-1) * (h + r*d) * G
= k * G
= R
Значит P = R, и P.x mod n = R.x mod n = r. QED.
ECDSA в Ethereum: ecrecover
В Ethereum ECDSA имеет особенность — параметр v (recovery id):
Подпись Ethereum: (v, r, s)
v = 27 или 28 (какая из двух возможных точек R)
Функция ecrecover(hash, v, r, s) восстанавливает публичный ключ из подписи. Это используется в smart-контрактах для проверки подписей (EIP-191, EIP-712).
Практика
Откройте Jupyter notebook 07-ecdsa.ipynb для практики:
- Ручная реализация ECDSA подписи и верификации
- Демонстрация атаки повторного использования nonce
- Детерминированная подпись (RFC 6979)
- Восстановление публичного ключа из подписи
Итоги
| Концепция | Формула | Значение |
|---|---|---|
| Подпись | s = k^(-1)(h + rd) mod n | Связывает приватный ключ с сообщением |
| Верификация | P = u1G + u2Q, P.x == r? | Проверка без знания d |
| Nonce | k — случайный и уникальный | Повтор k раскрывает d! |
| RFC 6979 | k = HMAC(d, h) | Детерминированный безопасный nonce |
Следующий урок: EdDSA и подписи Шнорра — более современные и безопасные альтернативы ECDSA.
Закончили урок?
Отметьте его как пройденный, чтобы отслеживать свой прогресс