Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add random key pair generation #1421

Merged
merged 12 commits into from
Aug 26, 2024
Merged
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
franciszekjob marked this conversation as resolved.
Show resolved Hide resolved
- 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()

franciszekjob marked this conversation as resolved.
Show resolved Hide resolved
# 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
Loading