Перейти к основному содержимому

Практикум — шаг 2: криптография

Разработчику

← Шаг 1 · Шаг 2 · Шаг 3 →

Блокчейн фиксирует что произошло; криптография доказывает кто это инициировал. Добавляем подписи переводов и шифрование секрета в покое.

Библиотека: cryptography (обёртка над OpenSSL).


Пара ключей Ed25519

Для учебного проекта выбран Ed25519 (короткие ключи, быстрые подписи). В Bitcoin — ECDSA/secp256k1; идея та же: приватный ключ подписывает, публичный проверяет.

@dataclass(frozen=True)
class KeyPair:
private_key: Ed25519PrivateKey
public_key: Ed25519PublicKey

@classmethod
def generate(cls) -> KeyPair:
private_key = Ed25519PrivateKey.generate()
return cls(private_key=private_key, public_key=private_key.public_key())

def public_hex(self) -> str:
raw = self.public_key.public_bytes(
encoding=serialization.Encoding.Raw,
format=serialization.PublicFormat.Raw,
)
return raw.hex()

Адрес кошелька в практикуме — hex публичного ключа (в mainnet адреса строятся иначе, через хеши и префиксы сети).


Подпись перевода

Транзакция — структура с полями sender, recipient, asset, amount, nonce. Подписываем канонический JSON без поля signature:

def payload_dict(self) -> dict[str, Any]:
return {
"type": "transfer",
"sender": self.sender,
"recipient": self.recipient,
"asset": self.asset,
"amount": self.amount,
"nonce": self.nonce,
}

def sign(self, keys: KeyPair) -> None:
message = canonical_json(self.payload_dict()).encode("utf-8")
sig = keys.sign(message)
self.signature_b64 = base64.b64encode(sig).decode("ascii")

Зачем nonce: защита от повторного воспроизведения (replay). Каждый успешный перевод увеличивает счётчик отправителя — как sequence в Ethereum.

Проверка на узле:

def verify_signature(self) -> bool:
public_key = public_key_from_hex(self.sender)
message = canonical_json(self.payload_dict()).encode("utf-8")
signature = base64.b64decode(self.signature_b64.encode("ascii"))
return verify_ed25519(public_key, message, signature)
Смежный материал

Глубже про PKI, TLS и хранение секретов — информационная безопасность.


Симметричное шифрование ключа (vault)

Приватный ключ нельзя хранить в открытом виде на диске. Учебный vault:

  1. PBKDF2-HMAC-SHA256 (200 000 итераций) из пароля пользователя.
  2. AES-GCM для шифрования PEM приватного ключа.
  3. В blob сохраняются salt, nonce, ciphertext.
def encrypt_private_key_pem(private_key: Ed25519PrivateKey, password: str) -> str:
salt = os.urandom(16)
kdf = PBKDF2HMAC(algorithm=hashes.SHA256(), length=32, salt=salt, iterations=200_000)
key = kdf.derive(password.encode("utf-8"))
aesgcm = AESGCM(key)
nonce = os.urandom(12)
pem = private_key.private_bytes(...)
ciphertext = aesgcm.encrypt(nonce, pem, None)
return base64.urlsafe_b64encode(salt + nonce + ciphertext).decode("ascii")
ПрактикаВ продакшене
Пароль в CLIHSM, KMS (AWS CloudHSM, Vault)
Файл blob на дискеАппаратный токен, MPC-кошельки
Один алгоритмРотация ключей, политики доступа

Практическое задание

  1. Сгенерируйте KeyPair, подпишите строку b"test", измените один байт сообщения — убедитесь, что verify возвращает False.
  2. Зашифруйте PEM с паролем demo, расшифруйте, сравните публичные ключи.
  3. Добавьте в tests/test_ledger.py тест на подпись и расшифровку vault (пример — в шаге 4).

Что дальше

Шаг 3 — ledger, балансы и mempool: как подписанные переводы попадают в блок.

См. также

Другие статьи этого же раздела в боковом меню (как на странице "О разделе").