Skip to content

Commit

Permalink
[ADD] Allow to create key using point (x,y) on curve
Browse files Browse the repository at this point in the history
  • Loading branch information
mccwdev committed Nov 21, 2023
1 parent 3f523a5 commit d69a2b4
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 31 deletions.
69 changes: 43 additions & 26 deletions bitcoinlib/keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,9 @@ def get_key_format(key, is_private=None):
elif isinstance(key, bytes) and len(key) == 32:
key_format = 'bin'
is_private = True
elif isinstance(key, tuple):
key_format = 'point'
is_private = False
elif len(key) == 130 and key[:2] == '04' and not is_private:
key_format = 'public_uncompressed'
is_private = False
Expand Down Expand Up @@ -737,7 +740,7 @@ def __init__(self, import_key=None, network=None, compressed=True, password='',
12127227708610754620337553985245292396444216111803695028419544944213442390363
:param import_key: If specified import given private or public key. If not specified a new private key is generated.
:type import_key: str, int, bytes
:type import_key: str, int, bytes, tuple
:param network: Bitcoin, testnet, litecoin or other network
:type network: str, Network
:param compressed: Is key compressed or not, default is True
Expand Down Expand Up @@ -807,32 +810,46 @@ def __init__(self, import_key=None, network=None, compressed=True, password='',

if not self.is_private:
self.secret = None
pub_key = to_hexstring(import_key)
if len(pub_key) == 130:
self._public_uncompressed_hex = pub_key
self.x_hex = pub_key[2:66]
self.y_hex = pub_key[66:130]
self._y = int(self.y_hex, 16)
self.compressed = False
if self._y % 2:
prefix = '03'
else:
prefix = '02'
self.public_hex = pub_key
self.public_compressed_hex = prefix + self.x_hex
else:
self.public_hex = pub_key
self.x_hex = pub_key[2:66]
if self.key_format == 'point':
self.compressed = True
self._x = int(self.x_hex, 16)
self.public_compressed_hex = pub_key
self.public_compressed_byte = bytes.fromhex(self.public_compressed_hex)
if self._public_uncompressed_hex:
self._public_uncompressed_byte = bytes.fromhex(self._public_uncompressed_hex)
if self.compressed:
self.public_byte = self.public_compressed_byte
self._x = import_key[0]
self._y = import_key[1]
self.x_bytes = self._x.to_bytes(32, 'big')
self.y_bytes = self._y.to_bytes(32, 'big')
self.x_hex = self.x_bytes.hex()
self.y_hex = self.y_bytes.hex()
prefix = '03' if self._y % 2 else '02'
self.public_hex = prefix + self.x_hex
self.public_compressed_hex = prefix + self.x_hex
self.public_byte = (b'\3' if self._y % 2 else b'\2') + self.x_bytes
else:
self.public_byte = self.public_uncompressed_byte
pub_key = to_hexstring(import_key)
if len(pub_key) == 130:
self._public_uncompressed_hex = pub_key
self.x_hex = pub_key[2:66]
self.y_hex = pub_key[66:130]
self._y = int(self.y_hex, 16)
self.compressed = False
if self._y % 2:
prefix = '03'
else:
prefix = '02'
self.public_hex = pub_key
self.public_compressed_hex = prefix + self.x_hex
else:
self.public_hex = pub_key
self.x_hex = pub_key[2:66]
self.compressed = True
self._x = int(self.x_hex, 16)
self.public_compressed_hex = pub_key
self.public_compressed_byte = bytes.fromhex(self.public_compressed_hex)
if self._public_uncompressed_hex:
self._public_uncompressed_byte = bytes.fromhex(self._public_uncompressed_hex)
if self.compressed:
self.public_byte = self.public_compressed_byte
else:
self.public_byte = self.public_uncompressed_byte

elif self.is_private and self.key_format == 'decimal':
self.secret = int(import_key)
self.private_hex = change_base(self.secret, 10, 16, 64)
Expand Down Expand Up @@ -1422,7 +1439,7 @@ def __init__(self, import_key=None, key=None, chain=None, depth=0, parent_finger
<HDKey(public_hex=0363c152144dcd5253c1216b733fdc6eb8a94ab2cd5caa8ead5e59ab456ff99927, wif_public=xpub661MyMwAqRbcEYS8w7XLSVeEsBXy79zSzH1J8vCdxAZningWLdN3zgtU6SmypHzZG2cYrwpGkWJqRxS6EAW77gd7CHFoXNpBd3LN8xjAyCW, network=bitcoin)>
:param import_key: HD Key to import in WIF format or as byte with key (32 bytes) and chain (32 bytes)
:type import_key: str, bytes, int
:type import_key: str, bytes, int, tuple
:param key: Private or public key (length 32)
:type key: bytes
:param chain: A chain code (length 32)
Expand Down
18 changes: 13 additions & 5 deletions tests/test_keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,13 @@ def test_keys_inverse(self):
self.assertEqual(k_inv.x, inv_x)
self.assertEqual(k_inv.y, inv_y)

def test_keys_inverse2(self):
k = HDKey()
pub_k = k.public()
self.assertEqual(k.address(), pub_k.address())
self.assertEqual((-k).address(), pub_k.inverse().address())
self.assertEqual((-k).address(), k.inverse().address())

def test_dict_and_json_outputs(self):
k = HDKey()
k.address(script_type='p2wsh', encoding='bech32')
Expand All @@ -99,12 +106,13 @@ def test_path_expand(self):
self.assertRaisesRegexp(BKeyError, "Please provide path as list with at least 1 item",
path_expand, 5)

def test_key_inverse(self):
def test_keys_create_public_point(self):
k = HDKey()
pub_k = k.public()
self.assertEqual(k.address(), pub_k.address())
self.assertEqual((-k).address(), pub_k.inverse().address())
self.assertEqual((-k).address(), k.inverse().address())
p = (k.x, k.y)
k2 = HDKey(p)
self.assertEqual(k, k2)
self.assertEqual(k.public(), k2)
self.assertEqual(k.address(), k2.address())


class TestGetKeyFormat(unittest.TestCase):
Expand Down

0 comments on commit d69a2b4

Please sign in to comment.