Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/template-release' into template-…
Browse files Browse the repository at this point in the history
…release
  • Loading branch information
Snedashkovsky committed Mar 31, 2024
2 parents c11e193 + 745235f commit 7e834ee
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 22 deletions.
2 changes: 1 addition & 1 deletion cybertensor/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
nest_asyncio.apply()

# Cybertensor code and protocol version.
__version__ = "0.1.6"
__version__ = "0.1.7"
version_split = __version__.split(".")
__version_as_int__ = (
(100 * int(version_split[0]))
Expand Down
10 changes: 8 additions & 2 deletions cybertensor/axon.py
Original file line number Diff line number Diff line change
Expand Up @@ -840,7 +840,9 @@ async def default_verify(self, synapse: cybertensor.Synapse):
"""
# Build the keypair from the dendrite_hotkey
if synapse.dendrite is not None:
keypair = Keypair(address=synapse.dendrite.hotkey) # type: ignore
cybertensor.logging.info(f"dendrite: {synapse.dendrite}")
# keypair = Keypair(address=synapse.dendrite.hotkey, public_key=synapse.dendrite.pubkey)
keypair = Keypair(address=synapse.dendrite.hotkey)
# Build the signature messages.
message = (f"{synapse.dendrite.nonce}.{synapse.dendrite.hotkey}.{self.wallet.hotkey.address}."
f"{synapse.dendrite.uuid}.{synapse.computed_body_hash}")
Expand All @@ -857,7 +859,11 @@ async def default_verify(self, synapse: cybertensor.Synapse):
):
raise Exception("Nonce is too small")

if not keypair.verify(message, synapse.dendrite.signature): # type: ignore
verified = keypair.verify(message, synapse.dendrite.signature)
cybertensor.logging.info(f"\nAXON VERIFY MSG: {message}")
cybertensor.logging.info(f"AXON VERIFY SGN: {synapse.dendrite.signature}")
cybertensor.logging.info(f"AXON VERIFY : {verified}")
if not verified:
raise Exception(
f"Signature mismatch with {message} and {synapse.dendrite.signature}"
)
Expand Down
9 changes: 8 additions & 1 deletion cybertensor/dendrite.py
Original file line number Diff line number Diff line change
Expand Up @@ -620,18 +620,25 @@ def preprocess_synapse_for_request(
nonce=time.monotonic_ns(),
uuid=self.uuid,
hotkey=self.keypair.address,
pubkey=self.keypair.public_key
)

# Build the Axon headers using the target axon's details
synapse.axon = cybertensor.TerminalInfo(
ip=target_axon_info.ip,
port=target_axon_info.port,
hotkey=target_axon_info.hotkey,
pubkey=None
)

# Sign the request using the dendrite, axon info, and the synapse body hash
message = f"{synapse.dendrite.nonce}.{synapse.dendrite.hotkey}.{synapse.axon.hotkey}.{synapse.dendrite.uuid}.{synapse.body_hash}"
synapse.dendrite.signature = f"0x{self.keypair.sign(message).hex()}"
signed = self.keypair.sign(message)
cybertensor.logging.info(f"\nDENDRITE ADDR {self.keypair.address}")
cybertensor.logging.info(f"DENDRITE MSG {message}")
cybertensor.logging.info(f"DENDRITE SGN {signed}")
cybertensor.logging.info(f"DENDRITE SGN 0x{signed.hex()}\n")
synapse.dendrite.signature = f"0x{signed.hex()}"

return synapse

Expand Down
111 changes: 93 additions & 18 deletions cybertensor/keypair.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,28 +15,40 @@
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.

import base64
import hashlib
import logging
from typing import Optional, Union

from bech32 import ( # pylint: disable=wrong-import-order
bech32_encode,
convertbits,
)
from bip39 import bip39_generate, bip39_validate
from cosmpy.crypto.address import Address
from cosmpy.crypto.hashfuncs import ripemd160
from cosmpy.crypto.keypairs import PrivateKey, PublicKey
from cosmpy.mnemonic import (
derive_child_key_from_mnemonic,
COSMOS_HD_PATH,
validate_mnemonic_and_normalise,
)

from cybertensor import __chain_address_prefix__
from ecdsa import ( # type: ignore # pylint: disable=wrong-import-order
SECP256k1,
SigningKey,
VerifyingKey,
)
from cybertensor.errors import ConfigurationError


class Keypair:
def __init__(
self,
address: str = None,
public_key: Union[bytes, str] = None,
private_key: Union[bytes, str] = None,
prefix: Optional[str] = None,
self,
address: str = None,
public_key: Union[bytes, str] = None,
private_key: Union[bytes, str] = None,
prefix: Optional[str] = None,
):
"""
Allows generation of Keypairs from a variety of input combination, such as a public/private key combination,
Expand All @@ -62,8 +74,12 @@ def __init__(
public_key = private_key_obj.public_key.public_key
address = Address(PublicKey(private_key_obj.public_key), prefix).__str__()

if not public_key:
raise ConfigurationError("No public key provided")
if public_key and isinstance(public_key, str):
# self.public_key = bytes(public_key, 'utf-8')
self.public_key = public_key

# if not public_key:
# raise ValueError("No public key provided")

self.public_key: bytes = public_key

Expand Down Expand Up @@ -105,7 +121,7 @@ def validate_mnemonic(cls, mnemonic: str) -> bool:

@classmethod
def create_from_mnemonic(
cls, mnemonic: str, prefix: Optional[str] = None
cls, mnemonic: str, prefix: Optional[str] = None
) -> "Keypair":
"""
Create a Keypair for given mnemonic
Expand Down Expand Up @@ -135,7 +151,7 @@ def create_from_mnemonic(

@classmethod
def create_from_private_key(
cls, private_key: Union[bytes, str], prefix: Optional[str] = None
cls, private_key: Union[bytes, str], prefix: Optional[str] = None
) -> "Keypair":
"""
Creates Keypair for specified public/private keys
Expand All @@ -158,6 +174,45 @@ def create_from_private_key(
prefix=prefix,
)

def get_address_from_public_key(self, public_key: str) -> str:
"""
Get the address from the public key.
:param public_key: the public key
:return: str
"""
public_key_bytes = bytes.fromhex(public_key)
s = hashlib.new("sha256", public_key_bytes).digest()
r = ripemd160(s)
five_bit_r = convertbits(r, 8, 5)
if five_bit_r is None: # pragma: nocover
raise TypeError("Unsuccessful bech32.convertbits call")

## TODO add configuration for chain prefix
address = bech32_encode(__chain_address_prefix__, five_bit_r)
return address

def recover_message(self, message: bytes, signature: str) -> tuple[Address, ...]:
public_keys = self.recover_public_keys_from_message(message, signature)
addresses = [
self.get_address_from_public_key(public_key) for public_key in public_keys
]
return tuple(addresses)

def recover_public_keys_from_message(self, message: bytes, signature: str) -> tuple[str, ...]:
signature_b64 = base64.b64decode(signature)
verifying_keys = VerifyingKey.from_public_key_recovery(
signature_b64,
message,
SECP256k1,
hashfunc=hashlib.sha256,
)
public_keys = [
verifying_key.to_string("compressed").hex()
for verifying_key in verifying_keys
]
return tuple(public_keys)

def sign(self, data: Union[bytes, str]) -> bytes:
"""
Creates a signature for given data
Expand All @@ -181,9 +236,14 @@ def sign(self, data: Union[bytes, str]) -> bytes:
if not self.private_key:
raise ConfigurationError("No private key set to create signatures")

# return signature
# TODO think about update to ADR-36
return PrivateKey(self.private_key).sign(message=data, deterministic=True)
signature_compact = PrivateKey(self.private_key).sign(data, deterministic=True)
signature_base64_str = base64.b64encode(signature_compact).decode("utf-8").encode()

logging.debug(f"\nKEYPAIR address: {self.address}")
logging.debug(f"KEYPAIR Signing data: {data}")
logging.debug(f"KEYPAIR Generated signature: {signature_base64_str}\n")

return signature_base64_str

def verify(self, data: Union[bytes, str], signature: Union[bytes, str]) -> bool:
"""
Expand All @@ -209,11 +269,26 @@ def verify(self, data: Union[bytes, str], signature: Union[bytes, str]) -> bool:
if type(signature) is str and signature[0:2] == "0x":
signature = bytes.fromhex(signature[2:])

if type(signature) is not bytes:
raise TypeError(f"Signature should be of type bytes or a hex-string, given signature type is {type(signature)}")

# TODO think about update to ADR-36
return PublicKey(self.public_key).verify(message=data, signature=signature)
for address in self.recover_message(data, signature):
if self.address == address:
logging.debug(f"KEYPAIR Verified data: True")
return True
logging.debug(f"KEYPAIR Verified data: False")
return False

# recovered_addresses = self.recover_message(data, signature)
# logging.debug(f"\nKEYPAIR Verifying data: {data}")
# logging.debug(f"KEYPAIR Recovered addresses: {recovered_addresses}")
# if self.address == recovered_addresses[0]:
# logging.debug(f"KEYPAIR Verified data: True 1")
# return True
#
# if self.address == recovered_addresses[1]:
# logging.debug(f"KEYPAIR Verified data: True 2\n")
# return True
#
# logging.debug(f"KEYPAIR Verified data: False\n")
# return False

def __repr__(self):
if self.address:
Expand Down
9 changes: 9 additions & 0 deletions cybertensor/synapse.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,15 @@ class Config:
allow_mutation=True,
)

# The cybertensor version on the terminal as an int.
pubkey: Optional[str] = pydantic.Field(
title="pubkey",
description="The pubkey string of the terminal wallet.",
examples="",
default=None,
allow_mutation=True,
)


class Synapse(pydantic.BaseModel):
"""
Expand Down

0 comments on commit 7e834ee

Please sign in to comment.