Files
basicswap/basicswap/util/extkey.py
2024-11-15 18:53:54 +02:00

138 lines
4.0 KiB
Python

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (c) 2024 tecnovert
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
from .crypto import blake256, hash160, hmac_sha512, ripemd160
from coincurve.keys import PrivateKey, PublicKey
def BIP32Hash(chaincode: bytes, child_no: int, key_data_type: int, keydata: bytes):
return hmac_sha512(
chaincode,
key_data_type.to_bytes(1, "big") + keydata + child_no.to_bytes(4, "big"),
)
def hash160_dcr(data: bytes) -> bytes:
return ripemd160(blake256(data))
class ExtKeyPair:
__slots__ = (
"_depth",
"_fingerprint",
"_child_no",
"_chaincode",
"_key",
"_pubkey",
"hash_func",
)
def __init__(self, coin_type=1):
if coin_type == 4:
self.hash_func = hash160_dcr
else:
self.hash_func = hash160
def set_seed(self, seed: bytes) -> None:
hashout: bytes = hmac_sha512(b"Bitcoin seed", seed)
self._key = hashout[:32]
self._pubkey = None
self._chaincode = hashout[32:]
self._depth = 0
self._child_no = 0
self._fingerprint = b"\0" * 4
def has_key(self) -> bool:
return False if self._key is None else True
def neuter(self) -> None:
if self._key is None:
raise ValueError("Already neutered")
self._pubkey = PublicKey.from_secret(self._key).format()
self._key = None
def derive(self, child_no: int):
out = ExtKeyPair()
out._depth = self._depth + 1
out._child_no = child_no
if (child_no >> 31) == 0:
if self._key:
K = PublicKey.from_secret(self._key)
k_encoded = K.format()
else:
K = PublicKey(self._pubkey)
k_encoded = self._pubkey
out._fingerprint = self.hash_func(k_encoded)[:4]
new_hash = BIP32Hash(self._chaincode, child_no, k_encoded[0], k_encoded[1:])
out._chaincode = new_hash[32:]
if self._key:
k = PrivateKey(self._key)
k.add(new_hash[:32], update=True)
out._key = k.secret
out._pubkey = None
else:
K.add(new_hash[:32], update=True)
out._key = None
out._pubkey = K.format()
else:
k = PrivateKey(self._key)
out._fingerprint = self.hash_func(
self._pubkey
if self._pubkey
else PublicKey.from_secret(self._key).format()
)[:4]
new_hash = BIP32Hash(self._chaincode, child_no, 0, self._key)
out._chaincode = new_hash[32:]
k.add(new_hash[:32], update=True)
out._key = k.secret
out._pubkey = None
out.hash_func = self.hash_func
return out
def encode_v(self) -> bytes:
return (
self._depth.to_bytes(1, "big")
+ self._fingerprint
+ self._child_no.to_bytes(4, "big")
+ self._chaincode
+ b"\x00"
+ self._key
)
def encode_p(self) -> bytes:
pubkey = (
PublicKey.from_secret(self._key).format()
if self._pubkey is None
else self._pubkey
)
return (
self._depth.to_bytes(1, "big")
+ self._fingerprint
+ self._child_no.to_bytes(4, "big")
+ self._chaincode
+ pubkey
)
def decode(self, data: bytes) -> None:
if len(data) != 74:
raise ValueError("Unexpected extkey length")
self._depth = data[0]
self._fingerprint = data[1:5]
self._child_no = int.from_bytes(data[5:9], "big")
self._chaincode = data[9:41]
if data[41] == 0:
self._key = data[42:]
self._pubkey = None
else:
self._key = None
self._pubkey = data[41:]