Skip to content

Commit

Permalink
Add random key pair generation (#1421)
Browse files Browse the repository at this point in the history
* Move `KeyPair` to separate file; Add `KeyPair.generate()`

* Remove `KeyPair` from `stark_curve_signer.py`; Update generate method docstring

* Format

* Update `test_generate`

* Fix `test_generate`

* Format

* Use `secrets.token_bytes()` to generate random number

* Update starknet_py/net/signer/test_key_pair.py

Co-authored-by: kkawula <57270771+kkawula@users.noreply.github.com>

* Move `test_key_pair.py` to test directory

---------

Co-authored-by: kkawula <57270771+kkawula@users.noreply.github.com>
  • Loading branch information
franciszekjob and kkawula authored Aug 26, 2024
1 parent cadb3dc commit 61c4a1c
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 43 deletions.
2 changes: 1 addition & 1 deletion docs/account_creation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ and then creating the transaction which will charge the fee from the address.
Deploying an account with DeployAccount transaction requires the following:

- class_hash of the account contract
- generating a private key and deployment salt
- private key and deployment salt
- computing an address based on the account's secrets
- prefunding an address with the fee tokens (e.g. using the token bridge)
- creating and signing a DeployAccount transaction with generated secrets
Expand Down
56 changes: 56 additions & 0 deletions starknet_py/net/signer/key_pair.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
from dataclasses import dataclass
from secrets import token_bytes

from eth_keyfile.keyfile import extract_key_from_keyfile

from starknet_py.constants import FIELD_PRIME
from starknet_py.hash.utils import private_to_stark_key
from starknet_py.net.client_models import Hash


@dataclass
class KeyPair:
private_key: int
public_key: int

def __init__(self, private_key: Hash, public_key: Hash):
if isinstance(private_key, str):
self.private_key = int(private_key, 0)
else:
self.private_key = private_key

if isinstance(public_key, str):
self.public_key = int(public_key, 0)
else:
self.public_key = public_key

@staticmethod
def generate() -> "KeyPair":
"""
Create a key pair from a randomly generated private key.
:return: KeyPair object.
"""
random_int = int.from_bytes(token_bytes(32), byteorder="big")
private_key = random_int % FIELD_PRIME
public_key = private_to_stark_key(private_key)
return KeyPair(private_key=private_key, public_key=public_key)

@staticmethod
def from_private_key(key: Hash) -> "KeyPair":
if isinstance(key, str):
key = int(key, 0)
return KeyPair(private_key=key, public_key=private_to_stark_key(key))

@staticmethod
def from_keystore(path: str, password: str) -> "KeyPair":
"""
Create a key pair from a keystore file.
The keystore file should follow the Ethereum keystore format.
:param path: Path to the keystore file.
:param password: Password to decrypt the keystore file.
:return: KeyPair object.
"""
key = extract_key_from_keyfile(path, password)
return KeyPair.from_private_key(int.from_bytes(key, byteorder="big"))
43 changes: 2 additions & 41 deletions starknet_py/net/signer/stark_curve_signer.py
Original file line number Diff line number Diff line change
@@ -1,53 +1,14 @@
from dataclasses import dataclass
from typing import List

from eth_keyfile.keyfile import extract_key_from_keyfile

from starknet_py.hash.utils import message_signature, private_to_stark_key
from starknet_py.net.client_models import Hash
from starknet_py.hash.utils import message_signature
from starknet_py.net.models import AddressRepresentation, parse_address
from starknet_py.net.models.chains import ChainId
from starknet_py.net.models.transaction import AccountTransaction
from starknet_py.net.signer.base_signer import BaseSigner
from starknet_py.net.signer.key_pair import KeyPair
from starknet_py.utils.typed_data import TypedData


@dataclass
class KeyPair:
private_key: int
public_key: int

def __init__(self, private_key: Hash, public_key: Hash):
if isinstance(private_key, str):
self.private_key = int(private_key, 0)
else:
self.private_key = private_key

if isinstance(public_key, str):
self.public_key = int(public_key, 0)
else:
self.public_key = public_key

@staticmethod
def from_private_key(key: Hash) -> "KeyPair":
if isinstance(key, str):
key = int(key, 0)
return KeyPair(private_key=key, public_key=private_to_stark_key(key))

@staticmethod
def from_keystore(path: str, password: str) -> "KeyPair":
"""
Create a key pair from a keystore file.
The keystore file should follow the Ethereum keystore format.
:param path: Path to the keystore file.
:param password: Password to decrypt the keystore file.
:return: KeyPair object.
"""
key = extract_key_from_keyfile(path, password)
return KeyPair.from_private_key(int.from_bytes(key, byteorder="big"))


class StarkCurveSigner(BaseSigner):
def __init__(
self,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ async def test_deploy_prefunded_account(
eth_fee_contract: Contract,
client: Client,
):
# pylint: disable=import-outside-toplevel, too-many-locals
# pylint: disable=import-outside-toplevel, too-many-locals, unused-variable
full_node_client_fixture = client
# docs: start
from starknet_py.hash.address import compute_address
Expand All @@ -29,6 +29,9 @@ async def test_deploy_prefunded_account(

key_pair = KeyPair.from_private_key(private_key)

# You can also generate a key pair
key_pair_generated = KeyPair.generate()

# Compute an address
address = compute_address(
salt=salt,
Expand Down
11 changes: 11 additions & 0 deletions starknet_py/tests/unit/signer/test_key_pair.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from starknet_py.constants import FIELD_PRIME
from starknet_py.net.signer.key_pair import KeyPair


def test_generate_key_pair():
key_pair = KeyPair.generate()
hex_private_key = hex(key_pair.private_key)[2:]
padded_hex_private_key = hex_private_key.zfill(64)
assert len(padded_hex_private_key) == 64
assert key_pair.private_key < FIELD_PRIME
assert key_pair.public_key < FIELD_PRIME

0 comments on commit 61c4a1c

Please sign in to comment.