Skip to content

Node Wallet

dYdX wallet derivation and signing helpers.

KeyPair dataclass

Secp256k1 key pair used for Cosmos direct signing.

Source code in pkg/src/dydx/node/wallet.py
@dataclass
class KeyPair:
  """Secp256k1 key pair used for Cosmos direct signing."""

  key: PrivateKey
  """Private key used for signing."""

  @classmethod
  def from_mnemonic(cls, mnemonic: str) -> 'KeyPair':
    """Create a key pair from a mnemonic.

    Args:
      mnemonic: BIP-39 mnemonic phrase.

    Returns:
      Secp256k1 key pair for Cosmos signing.
    """
    return cls(PrivateKey(private_key_bytes_from_mnemonic(mnemonic)))

  @classmethod
  def from_hex(cls, value: str) -> 'KeyPair':
    """Create a key pair from a hex private key.

    Args:
      value: Hex-encoded private key.

    Returns:
      Secp256k1 key pair for Cosmos signing.
    """
    return cls(PrivateKey.from_hex(value))

  @property
  def public_key_bytes(self) -> bytes:
    """Return the compressed public key bytes.

    Returns:
      Compressed secp256k1 public key bytes.
    """
    return self.key.public_key.format(compressed=True)

  def sign(self, message: bytes) -> bytes:
    """Sign bytes with canonical secp256k1 output.

    Args:
      message: Bytes to sign.

    Returns:
      Canonical signature bytes.
    """
    return canonize_signature(self.key.sign_recoverable(message))

key instance-attribute

Private key used for signing.

public_key_bytes property

Return the compressed public key bytes.

Returns:

Type Description
bytes

Compressed secp256k1 public key bytes.

from_hex(value) classmethod

Create a key pair from a hex private key.

Parameters:

Name Type Description Default
value str

Hex-encoded private key.

required

Returns:

Type Description
KeyPair

Secp256k1 key pair for Cosmos signing.

Source code in pkg/src/dydx/node/wallet.py
@classmethod
def from_hex(cls, value: str) -> 'KeyPair':
  """Create a key pair from a hex private key.

  Args:
    value: Hex-encoded private key.

  Returns:
    Secp256k1 key pair for Cosmos signing.
  """
  return cls(PrivateKey.from_hex(value))

from_mnemonic(mnemonic) classmethod

Create a key pair from a mnemonic.

Parameters:

Name Type Description Default
mnemonic str

BIP-39 mnemonic phrase.

required

Returns:

Type Description
KeyPair

Secp256k1 key pair for Cosmos signing.

Source code in pkg/src/dydx/node/wallet.py
@classmethod
def from_mnemonic(cls, mnemonic: str) -> 'KeyPair':
  """Create a key pair from a mnemonic.

  Args:
    mnemonic: BIP-39 mnemonic phrase.

  Returns:
    Secp256k1 key pair for Cosmos signing.
  """
  return cls(PrivateKey(private_key_bytes_from_mnemonic(mnemonic)))

sign(message)

Sign bytes with canonical secp256k1 output.

Parameters:

Name Type Description Default
message bytes

Bytes to sign.

required

Returns:

Type Description
bytes

Canonical signature bytes.

Source code in pkg/src/dydx/node/wallet.py
def sign(self, message: bytes) -> bytes:
  """Sign bytes with canonical secp256k1 output.

  Args:
    message: Bytes to sign.

  Returns:
    Canonical signature bytes.
  """
  return canonize_signature(self.key.sign_recoverable(message))

Wallet dataclass

dYdX wallet plus account metadata required for signing.

Source code in pkg/src/dydx/node/wallet.py
@dataclass
class Wallet:
  """dYdX wallet plus account metadata required for signing."""

  key_pair: KeyPair
  """Secp256k1 key pair used for signing."""
  account_number: int = 0
  """Cosmos account number used in direct-sign documents."""
  sequence: int = 0
  """Cosmos account sequence used by the next signed transaction."""

  @classmethod
  def from_mnemonic(
    cls, mnemonic: str, *, account_number: int = 0, sequence: int = 0,
  ) -> 'Wallet':
    """Create a wallet from a mnemonic.

    Args:
      mnemonic: BIP-39 mnemonic phrase.
      account_number: Cosmos account number to attach to the wallet.
      sequence: Cosmos account sequence to attach to the wallet.

    Returns:
      Wallet with key material and account metadata.
    """
    return cls(
      key_pair=KeyPair.from_mnemonic(mnemonic),
      account_number=account_number,
      sequence=sequence,
    )

  @classmethod
  def from_hex(cls, value: str, *, account_number: int = 0, sequence: int = 0) -> 'Wallet':
    """Create a wallet from a hex private key.

    Args:
      value: Hex-encoded private key.
      account_number: Cosmos account number to attach to the wallet.
      sequence: Cosmos account sequence to attach to the wallet.

    Returns:
      Wallet with key material and account metadata.
    """
    return cls(
      key_pair=KeyPair.from_hex(value),
      account_number=account_number,
      sequence=sequence,
    )

  @property
  def public_key(self) -> secp256k1.PubKey:
    """Return the Cosmos secp256k1 public key message.

    Returns:
      Cosmos secp256k1 public key protobuf message.
    """
    return secp256k1.PubKey(key=self.key_pair.public_key_bytes)

  @property
  def address(self) -> str:
    """Return the dYdX bech32 account address.

    Returns:
      dYdX bech32 account address derived from the compressed public key.
    """
    sha256_hash = hashlib.sha256(self.key_pair.public_key_bytes).digest()
    ripemd160_hash = RIPEMD160.new(sha256_hash).digest()
    data = bech32.convertbits(ripemd160_hash, 8, 5)
    if data is None:
      raise ValueError('Could not convert dYdX wallet bytes to bech32 data')
    return bech32.bech32_encode('dydx', data)

  def sign(self, message: bytes) -> bytes:
    """Sign bytes with the wallet key.

    Args:
      message: Bytes to sign.

    Returns:
      Canonical signature bytes.
    """
    return self.key_pair.sign(message)

  def with_account(self, *, account_number: int, sequence: int) -> 'Wallet':
    """Return a wallet copy with account metadata.

    Args:
      account_number: Cosmos account number.
      sequence: Cosmos account sequence.

    Returns:
      Wallet copy with the same key pair and updated account metadata.
    """
    return Wallet(
      key_pair=self.key_pair,
      account_number=account_number,
      sequence=sequence,
    )

account_number = 0 class-attribute instance-attribute

Cosmos account number used in direct-sign documents.

address property

Return the dYdX bech32 account address.

Returns:

Type Description
str

dYdX bech32 account address derived from the compressed public key.

key_pair instance-attribute

Secp256k1 key pair used for signing.

public_key property

Return the Cosmos secp256k1 public key message.

Returns:

Type Description
PubKey

Cosmos secp256k1 public key protobuf message.

sequence = 0 class-attribute instance-attribute

Cosmos account sequence used by the next signed transaction.

from_hex(value, *, account_number=0, sequence=0) classmethod

Create a wallet from a hex private key.

Parameters:

Name Type Description Default
value str

Hex-encoded private key.

required
account_number int

Cosmos account number to attach to the wallet.

0
sequence int

Cosmos account sequence to attach to the wallet.

0

Returns:

Type Description
Wallet

Wallet with key material and account metadata.

Source code in pkg/src/dydx/node/wallet.py
@classmethod
def from_hex(cls, value: str, *, account_number: int = 0, sequence: int = 0) -> 'Wallet':
  """Create a wallet from a hex private key.

  Args:
    value: Hex-encoded private key.
    account_number: Cosmos account number to attach to the wallet.
    sequence: Cosmos account sequence to attach to the wallet.

  Returns:
    Wallet with key material and account metadata.
  """
  return cls(
    key_pair=KeyPair.from_hex(value),
    account_number=account_number,
    sequence=sequence,
  )

from_mnemonic(mnemonic, *, account_number=0, sequence=0) classmethod

Create a wallet from a mnemonic.

Parameters:

Name Type Description Default
mnemonic str

BIP-39 mnemonic phrase.

required
account_number int

Cosmos account number to attach to the wallet.

0
sequence int

Cosmos account sequence to attach to the wallet.

0

Returns:

Type Description
Wallet

Wallet with key material and account metadata.

Source code in pkg/src/dydx/node/wallet.py
@classmethod
def from_mnemonic(
  cls, mnemonic: str, *, account_number: int = 0, sequence: int = 0,
) -> 'Wallet':
  """Create a wallet from a mnemonic.

  Args:
    mnemonic: BIP-39 mnemonic phrase.
    account_number: Cosmos account number to attach to the wallet.
    sequence: Cosmos account sequence to attach to the wallet.

  Returns:
    Wallet with key material and account metadata.
  """
  return cls(
    key_pair=KeyPair.from_mnemonic(mnemonic),
    account_number=account_number,
    sequence=sequence,
  )

sign(message)

Sign bytes with the wallet key.

Parameters:

Name Type Description Default
message bytes

Bytes to sign.

required

Returns:

Type Description
bytes

Canonical signature bytes.

Source code in pkg/src/dydx/node/wallet.py
def sign(self, message: bytes) -> bytes:
  """Sign bytes with the wallet key.

  Args:
    message: Bytes to sign.

  Returns:
    Canonical signature bytes.
  """
  return self.key_pair.sign(message)

with_account(*, account_number, sequence)

Return a wallet copy with account metadata.

Parameters:

Name Type Description Default
account_number int

Cosmos account number.

required
sequence int

Cosmos account sequence.

required

Returns:

Type Description
Wallet

Wallet copy with the same key pair and updated account metadata.

Source code in pkg/src/dydx/node/wallet.py
def with_account(self, *, account_number: int, sequence: int) -> 'Wallet':
  """Return a wallet copy with account metadata.

  Args:
    account_number: Cosmos account number.
    sequence: Cosmos account sequence.

  Returns:
    Wallet copy with the same key pair and updated account metadata.
  """
  return Wallet(
    key_pair=self.key_pair,
    account_number=account_number,
    sequence=sequence,
  )

canonize_signature(signature)

Return the canonical 64-byte secp256k1 signature.

Parameters:

Name Type Description Default
signature bytes

Recoverable secp256k1 signature bytes.

required

Returns:

Type Description
bytes

Canonical r || s signature bytes.

Source code in pkg/src/dydx/node/wallet.py
def canonize_signature(signature: bytes) -> bytes:
  """Return the canonical 64-byte secp256k1 signature.

  Args:
    signature: Recoverable secp256k1 signature bytes.

  Returns:
    Canonical `r || s` signature bytes.
  """
  r = int.from_bytes(signature[:32], 'big')
  s = int.from_bytes(signature[32:64], 'big')
  if s > GROUP_ORDER_INT // 2:
    s = GROUP_ORDER_INT - s
  return int_to_bytes(r).rjust(32, b'\x00') + int_to_bytes(s).rjust(32, b'\x00')

private_key_bytes_from_mnemonic(mnemonic)

Derive the default Cosmos private key from a mnemonic.

Parameters:

Name Type Description Default
mnemonic str

BIP-39 mnemonic phrase.

required

Returns:

Type Description
bytes

Raw private key bytes for the default Cosmos derivation path.

Source code in pkg/src/dydx/node/wallet.py
def private_key_bytes_from_mnemonic(mnemonic: str) -> bytes:
  """Derive the default Cosmos private key from a mnemonic.

  Args:
    mnemonic: BIP-39 mnemonic phrase.

  Returns:
    Raw private key bytes for the default Cosmos derivation path.
  """
  seed = Bip39SeedGenerator(mnemonic).Generate()
  return (
    Bip44.FromSeed(seed, Bip44Coins.COSMOS)
    .DeriveDefaultPath()
    .PrivateKey()
    .Raw()
    .ToBytes()
  )