Skip to content

Commit

Permalink
More asynch work. Closes #9
Browse files Browse the repository at this point in the history
  • Loading branch information
FrankC01 committed Nov 22, 2022
1 parent 24d38cf commit aaabe06
Show file tree
Hide file tree
Showing 13 changed files with 170 additions and 32 deletions.
6 changes: 5 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"python.envFile": "${workspaceFolder}/pysuienv",
// "python.envFile": "${workspaceFolder}/pysuienv",
"python.envFile": "${workspaceFolder}/pysui-dev.env",
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true,
"python.formatting.provider": "black",
Expand All @@ -20,6 +21,9 @@
"import-outside-toplevel",
"--disable",
"subprocess-run-check",
// This is because of false positive in create_new_address of sui_crypto.py
"--disable",
"unpacking-non-sequence"
],
"python.linting.pydocstyleEnabled": true,
"python.linting.pydocstyleArgs": [
Expand Down
9 changes: 5 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased] - Current dev version - 0.1.0

Breaking Changes (noted in bold)
**Breaking** Changes (noted in bold)

### Added
- Pushed `pysui` 0.0.11 to [PyPi](https://pypi.org/project/pysui/)
- `get_gas_from_faucet` **devnet** only [enhancement](https://github.com/FrankC01/pysui/issues/23)
- Integration testing (**devnet synchronous**) [enhancement](https://github.com/FrankC01/pysui/issues/24)
- Started SuiAsynchClient [enhancement](https://github.com/FrankC01/pysui/issues/17)
- GetPastObject builder implementing `sui_tryGetPastObject` [closes](https://github.com/FrankC01/pysui/issues/25)
- Support for mnemonics and derivation path[closes](https://github.com/FrankC01/pysui/issues/9)

### Fixed
- Secp256k1 buh fixed [issue](https://github.com/FrankC01/pysui/issues/26)
- **Secp256k1** bug fixed [issue](https://github.com/FrankC01/pysui/issues/26)

### Changed
- SuiClient and SuiAsynchClient deriving from abstracts `Provider`
- **SuiClient** and **SuiAsynchClient** deriving from abstracts `Provider`

### Removed
- `SyncHttpRPC` and `AsyncHttpRPC` from `abstracts/client_rpc.py` as part of [issue](https://github.com/FrankC01/pysui/issues/17)
- **SyncHttpRPC** and **AsyncHttpRPC** from `abstracts/client_rpc.py` as part of [issue](https://github.com/FrankC01/pysui/issues/17)
## [0.0.11] - 2022-11-18

Breaking Release
Expand Down
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ Refer to the [Change](CHANGELOG.md) log for recent additions, changes, fixes and

## Known issues or missing capability
* Doesn't support `sui_batchTransaction` RPC API yet
* Doesn't use mnemonics yet (see [Issue](https://github.com/FrankC01/pysui/issues/9))

## Ready to run
Requires:
Expand Down
2 changes: 2 additions & 0 deletions pysui-dev.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Python Path
PYTHONPATH=$(PYTHONPATH):.:pysui:samples
10 changes: 5 additions & 5 deletions pysui/sui/sui_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,23 +82,23 @@ def _write_keypair(self, keypair: KeyPair, file_path: str = None) -> None:
else:
raise SuiFileNotFound((filepath))

def create_new_keypair_and_address(self, scheme: SignatureScheme) -> str:
def create_new_keypair_and_address(self, scheme: SignatureScheme) -> tuple[str, str]:
"""Create a new keypair and address identifier and return the address string.
The scheme defines generation of ED25519 or SECP256K1 keypairs.
"""
if scheme == SignatureScheme.ED25519:
keypair, address = create_new_address(scheme)
mnen, keypair, address = create_new_address(scheme)
self._addresses[address.address] = address
self._address_keypair[address.address] = keypair
self._write_keypair(keypair)
return address.identifier
return mnen, address.identifier
if scheme == SignatureScheme.SECP256K1:
keypair, address = create_new_address(scheme)
mnen, keypair, address = create_new_address(scheme)
self._addresses[address.address] = address
self._address_keypair[address.address] = keypair
self._write_keypair(keypair)
return address.identifier
return mnen, address.identifier

raise NotImplementedError

Expand Down
2 changes: 1 addition & 1 deletion pysui/sui/sui_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
"""Length of keypair string."""

# ED25519 Keypair derivation path
# m / purpose' / coin_type' / account' / change / address_index
# m / purpose' / coin_type' / account' / change' / address_index'
ED25519_DEFAULT_KEYPATH: str = "m/44'/784'/0'/0'/0'"
# ED25519 from keystring bytes
ED25519_KEYPAIR_BYTES_LEN: int = 64
Expand Down
141 changes: 131 additions & 10 deletions pysui/sui/sui_crypto.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,32 @@
"""Sui Crpto Utilities."""

import base64

from typing import Union
import secp256k1
import bip_utils
from bip_utils.addr.addr_key_validator import AddrKeyValidator
from bip_utils.bip.bip39.bip39_mnemonic_decoder import Bip39MnemonicDecoder
from bip_utils.utils.mnemonic.mnemonic_validator import MnemonicValidator
import mnemonic
from nacl.signing import SigningKey, VerifyKey
from nacl.encoding import Base64Encoder


from ..abstracts import KeyPair, PrivateKey, PublicKey, SignatureScheme
from .sui_excepts import SuiInvalidKeyPair, SuiInvalidKeystringLength
from .sui_constants import (
from pysui.abstracts import KeyPair, PrivateKey, PublicKey, SignatureScheme
from pysui.sui.sui_excepts import SuiInvalidKeyPair, SuiInvalidKeystringLength
from pysui.sui.sui_constants import (
SUI_KEYPAIR_LEN,
ED25519_DEFAULT_KEYPATH,
ED25519_PUBLICKEY_BYTES_LEN,
ED25519_PRIVATEKEY_BYTES_LEN,
ED25519_KEYPAIR_BYTES_LEN,
SECP256K1_DEFAULT_KEYPATH,
SECP256K1_KEYPAIR_BYTES_LEN,
SECP256K1_PUBLICKEY_BYTES_LEN,
SECP256K1_PRIVATEKEY_BYTES_LEN,
)
from .sui_types import SuiSignature, SuiAddress
from pysui.sui.sui_types import SuiSignature, SuiAddress


# Edwards Curve Keys
Expand Down Expand Up @@ -225,6 +234,101 @@ def __repr__(self) -> str:


# Utility functions
def _valid_mnemonic(mnemonics: Union[str, list[str]] = "") -> str:
"""_valid_mnemonic Validate, or generate, valid mnemonic phrase.
:param mnemonics: mnemonic phrase, defaults to ""
:type mnemonics: Union[str, list[str]], optional
:raises ValueError: If the mnemonic is invalid
:return: mnemonic keyphrase
:rtype: str
"""
mnemonics = mnemonics or mnemonic.Mnemonic("english").generate()
if isinstance(mnemonics, list):
mnemonics = " ".join(mnemonics)

if MnemonicValidator(Bip39MnemonicDecoder()).IsValid(mnemonics):
return mnemonics
else:
raise ValueError(f"{mnemonics} is not a valid mnemonic phrase.")


def _valid_pubkey(key_valmethod: str, pub_key: bytes) -> Union[None, TypeError, ValueError]:
"""_valid_pubkey Validate the public key.
Public key bytes may be from secp256k1 or ed25519
:param key_valmethod: Validator for keytype string
:type key_valmethod: str
:param pub_key: Public key bytes
:type pub_key: bytes
:raises TypeError: Invalid public key
:raises ValueError: Invalid public key
:return: None for valid public key
:rtype: Union[None, TypeError, ValueError]
"""
try:
getattr(AddrKeyValidator, key_valmethod)(pub_key)
except TypeError as texc:
raise texc
except ValueError as vexc:
raise vexc


def _generate_secp256k1(
mnemonics: Union[str, list[str]] = "", derv_path: str = None
) -> tuple[str, SuiKeyPairSECP256K1]:
"""_generate_secp256k1 Create a mnemonic seed and use derivation path for secp256k1 keypair.
:param mnemonics: _description_, defaults to ""
:type mnemonics: Union[str, list[str]], optional
:param derv_path: _description_, defaults to None
:type derv_path: str, optional
:return: _description_
:rtype: KeyPair
"""
mnemonic_phrase = _valid_mnemonic(mnemonics)
derv_path = derv_path or SECP256K1_DEFAULT_KEYPATH
# Generate seed from mnemonic phrase and optional password
seed_bytes = bip_utils.Bip39SeedGenerator(mnemonic_phrase).Generate()
bip32_ctx = bip_utils.Bip32Slip10Secp256k1.FromSeedAndPath(seed_bytes, derv_path)
# Get private key bytes list
prv_key = bip32_ctx.PrivateKey().Raw().ToBytes()
# Instantiate secp256k1 library keypair
# 1. Private, or signer, key
secp_priv = secp256k1.PrivateKey(prv_key, raw=True)
# 2. Public, or verifier, key
secp_pub = secp_priv.pubkey.serialize(compressed=True)
_valid_pubkey("ValidateAndGetSecp256k1Key", secp_pub)
return mnemonic_phrase, SuiKeyPairSECP256K1(secp_pub, secp_priv.private_key)


def _generate_ed25519(mnemonics: Union[str, list[str]] = "", derv_path: str = None) -> tuple[str, SuiKeyPairED25519]:
"""_generate_secp256k1 Create a mnemonic seed and use derivation path for ed25519 keypair.
:param mnemonics: _description_, defaults to ""
:type mnemonics: Union[str, list[str]], optional
:param derv_path: _description_, defaults to None
:type derv_path: str, optional
:return: _description_
:rtype: KeyPair
"""
mnemonic_phrase = _valid_mnemonic(mnemonics)
derv_path = derv_path or ED25519_DEFAULT_KEYPATH
# Generate seed from mnemonic phrase and optional password
seed_bytes = bip_utils.Bip39SeedGenerator(mnemonic_phrase).Generate()
bip32_ctx = bip_utils.Bip32Slip10Ed25519.FromSeedAndPath(seed_bytes, derv_path)
# Get private key bytes list
prv_key = bip32_ctx.PrivateKey().Raw().ToBytes()
# Instantiate ed25519 library keypair
# Private, or signer, key
ed_priv = SigningKey(base64.b64encode(prv_key), encoder=Base64Encoder)
ed_enc_prv = ed_priv.encode()
# Public, or verifier, key
ed_pub = ed_priv.verify_key
ed_enc_pub = ed_pub.encode()
_valid_pubkey("ValidateAndGetEd25519Key", ed_enc_pub)
return mnemonic_phrase, SuiKeyPairED25519(ed_enc_pub, ed_enc_prv)


def keypair_from_keystring(keystring: str) -> KeyPair:
Expand All @@ -240,18 +344,35 @@ def keypair_from_keystring(keystring: str) -> KeyPair:
raise NotImplementedError


def create_new_keypair(keytype: SignatureScheme = SignatureScheme.ED25519) -> KeyPair:
def create_new_keypair(
keytype: SignatureScheme = SignatureScheme.ED25519, mnemonics: Union[str, list[str]] = None, derv_path: str = None
) -> tuple[str, KeyPair]:
"""Generate a new keypair."""
match keytype:
case SignatureScheme.ED25519:
return SuiKeyPairED25519.unique()
return _generate_ed25519(mnemonics, derv_path)
# return SuiKeyPairED25519.unique()
case SignatureScheme.SECP256K1:
return SuiKeyPairSECP256K1.unique()
return _generate_secp256k1(mnemonics, derv_path)
# return SuiKeyPairSECP256K1.unique()
case _:
raise NotImplementedError


def create_new_address(keytype: SignatureScheme) -> tuple[KeyPair, SuiAddress]:
def create_new_address(
keytype: SignatureScheme, mnemonics: Union[str, list[str]] = None, derv_path: str = None
) -> tuple[str, KeyPair, SuiAddress]:
"""Create a new keypair and address for a key type."""
new_kp = create_new_keypair(keytype)
return (new_kp, SuiAddress.from_bytes(new_kp.to_bytes()))
mnem, new_kp = create_new_keypair(keytype, mnemonics, derv_path)
return mnem, new_kp, SuiAddress.from_bytes(new_kp.to_bytes())


if __name__ == "__main__":
mnen, secp_kp, secp_addy = create_new_address(SignatureScheme.SECP256K1)
mnen, ed_kp, ed_addy = create_new_address(SignatureScheme.ED25519)
# secp = _generate_secp256k1()
# print(secp)
print(base64.b64encode(b"Hello"))

print(f"secp signed = {secp_kp.private_key.sign(b'Hello')}")
print(f"ed signed = {ed_kp.private_key.sign(b'Hello')}")
5 changes: 4 additions & 1 deletion pysui/sui/sui_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,10 @@ class SuiAddress(SuiBaseType):
def __init__(self, identifier: str) -> None:
"""Initialize address."""
identifier = identifier if len(identifier) != SUI_ADDRESS_STRING_LEN else SuiString(f"0x{identifier}")
super().__init__(SuiString(identifier))
if isinstance(identifier, SuiString):
super().__init__(identifier)
else:
super().__init__(SuiString(identifier))
# Alias for transaction validation
self.address = identifier

Expand Down
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ secp256k1==0.14.0
pynacl==1.5.0
httpx==0.23.0
h2==4.1.0
mnemonic==0.20
bip-utils==2.7.0
6 changes: 4 additions & 2 deletions samples/cmds.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,11 @@ def _detail_gas(gas_objects: SuiRpcResult):
def sui_new_address(wallet: SuiWallet, args: argparse.Namespace) -> None:
"""Generate a new SUI address."""
if args.ed25519:
print(wallet.create_new_keypair_and_address(SignatureScheme.ED25519))
mnen, address = wallet.create_new_keypair_and_address(SignatureScheme.ED25519)
else:
print(wallet.create_new_keypair_and_address(SignatureScheme.SECP256K1))
mnen, address = wallet.create_new_keypair_and_address(SignatureScheme.SECP256K1)
print(f"Keep this passphrase '{mnen}'")
print(f"For new address {address}")


def sui_package(wallet: SuiWallet, args: argparse.Namespace) -> None:
Expand Down
2 changes: 1 addition & 1 deletion samples/faux_wallet.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ def api_exists(self, api_name: str) -> bool:
"""Check if API supported in RPC host."""
return self._client.api_exists(api_name)

def create_new_keypair_and_address(self, sigscheme: SignatureScheme) -> str:
def create_new_keypair_and_address(self, sigscheme: SignatureScheme) -> tuple[str, str]:
"""Create new keypair and address."""
return self._client.config.create_new_keypair_and_address(sigscheme)

Expand Down
12 changes: 6 additions & 6 deletions samples/wallet.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,18 @@
import sys


PROJECT_DIR = pathlib.Path(os.path.dirname(__file__))
PROJECT_DIR = pathlib.Path(os.path.dirname(__file__))
PARENT = PROJECT_DIR.parent

sys.path.insert(0, PROJECT_DIR)
sys.path.insert(0, os.path.join(PARENT, "pysui"))
sys.path.insert(0, str(PROJECT_DIR))
sys.path.insert(0, str(PARENT))
sys.path.insert(0, str(os.path.join(PARENT, "pysui")))

from pysui.sui import SuiConfig

from .cmd_args import build_parser
from .cmds import SUI_CMD_DISPATCH
from .faux_wallet import SuiWallet
from samples.cmd_args import build_parser
from samples.cmds import SUI_CMD_DISPATCH
from samples.faux_wallet import SuiWallet


def main():
Expand Down
4 changes: 4 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,17 @@ install_requires =
pynacl == 1.5.0
httpx == 0.23.0
h2 == 4.1.0
mnemonic == 0.20
bip-utils == 2.7.0


[options.packages.find]
exclude =
build*
dist*
docs*
tests*
temp.py
*.tests
*.tests.*
tools*
Expand Down

0 comments on commit aaabe06

Please sign in to comment.