crypto — Wallets and signing

This module implements key generation, message signing, and external signer support for the O2 Exchange SDK.

See also

The O2 Exchange uses a specific on-chain signing format documented at https://docs.o2.app. The SDK handles this format automatically.

Signer protocol

class o2_sdk.crypto.Signer[source]

Protocol for objects that can sign messages for the O2 Exchange.

Both Wallet and EvmWallet satisfy this protocol. For external signing (hardware wallets, AWS KMS, HSMs), use ExternalSigner or ExternalEvmSigner, or implement this protocol directly.

property b256_address: str

The Fuel B256 address (0x-prefixed, 66-character hex string).

property address_bytes: bytes

The address as raw bytes (32 bytes).

personal_sign(message)[source]

Sign a message using the appropriate personal_sign format.

  • For Fuel-native accounts: \x19Fuel Signed Message:\n prefix + SHA-256.

  • For EVM accounts: \x19Ethereum Signed Message:\n prefix + keccak-256.

Parameters:

message (bytes) – The raw message bytes to sign.

Returns:

A 64-byte Fuel compact signature.

Return type:

bytes

Wallet classes

class o2_sdk.crypto.Wallet[source]

A Fuel-native wallet. Satisfies the Signer protocol.

private_key: bytes

The 32-byte private key.

public_key: bytes

The 65-byte uncompressed secp256k1 public key.

b256_address: str

The Fuel B256 address (sha256(public_key[1:]), 0x-prefixed).

property address_bytes: bytes

The address as raw bytes (32 bytes).

personal_sign(message)[source]

Sign using Fuel’s personalSign format.

Uses fuel_personal_sign_digest() for message framing and fuel_compact_sign() for signing.

Parameters:

message (bytes) – The message bytes.

Returns:

A 64-byte Fuel compact signature.

Return type:

bytes

class o2_sdk.crypto.EvmWallet[source]

An EVM-compatible wallet with B256 zero-padded address. Satisfies the Signer protocol.

private_key: bytes

The 32-byte private key.

public_key: bytes

The 65-byte uncompressed secp256k1 public key.

evm_address: str

The Ethereum-style address (0x-prefixed, 42 characters).

b256_address: str

The Fuel B256 address (EVM address zero-padded to 32 bytes).

property address_bytes: bytes

The address as raw bytes (32 bytes).

personal_sign(message)[source]

Sign using Ethereum’s personal_sign format.

Uses evm_personal_sign_digest() for message framing and fuel_compact_sign() for signing.

Parameters:

message (bytes) – The message bytes.

Returns:

A 64-byte Fuel compact signature.

Return type:

bytes

Wallet generation and loading

o2_sdk.crypto.generate_wallet()[source]

Generate a new Fuel-native wallet with a random private key.

Returns:

A new Wallet.

Return type:

Wallet

o2_sdk.crypto.generate_evm_wallet()[source]

Generate a new EVM-compatible wallet with a random private key.

Returns:

A new EvmWallet.

Return type:

EvmWallet

o2_sdk.crypto.load_wallet(private_key_hex)[source]

Load a Fuel-native wallet from a hex-encoded private key.

Parameters:

private_key_hex (str) – The private key (with or without 0x prefix).

Returns:

The loaded Wallet.

Return type:

Wallet

o2_sdk.crypto.load_evm_wallet(private_key_hex)[source]

Load an EVM-compatible wallet from a hex-encoded private key.

Parameters:

private_key_hex (str) – The private key (with or without 0x prefix).

Returns:

The loaded EvmWallet.

Return type:

EvmWallet

o2_sdk.crypto.generate_keypair()[source]

Generate a secp256k1 keypair and derive the Fuel B256 address.

This is a low-level function; prefer generate_wallet() for most use cases.

Returns:

A tuple of (private_key_hex, public_key_bytes_65, b256_address_hex).

Return type:

tuple[str, bytes, str]

o2_sdk.crypto.generate_evm_keypair()[source]

Generate a secp256k1 keypair with EVM address derivation.

This is a low-level function; prefer generate_evm_wallet() for most use cases.

Returns:

A tuple of (private_key_hex, public_key_bytes_65, evm_address_hex, b256_address_hex).

Return type:

tuple[str, bytes, str, str]

Digest helpers

These functions compute the message digest for each signing scheme without performing the actual ECDSA signing. They are the single source of truth for message framing and are used internally by all personal_sign methods (Wallet, EvmWallet, ExternalSigner, ExternalEvmSigner) and the module-level convenience functions.

o2_sdk.crypto.fuel_personal_sign_digest(message)[source]

Compute the Fuel personalSign digest.

Constructs:

SHA-256(b"\x19Fuel Signed Message:\n" + str(len(message)) + message)

and returns the 32-byte digest.

Parameters:

message (bytes) – The raw message bytes.

Returns:

The 32-byte SHA-256 digest.

Return type:

bytes

o2_sdk.crypto.evm_personal_sign_digest(message)[source]

Compute the Ethereum personal_sign digest.

Constructs:

keccak256(b"\x19Ethereum Signed Message:\n" + str(len(message)) + message)

and returns the 32-byte digest.

Parameters:

message (bytes) – The raw message bytes.

Returns:

The 32-byte keccak-256 digest.

Return type:

bytes

Signing functions

o2_sdk.crypto.fuel_compact_sign(private_key_bytes, digest)[source]

Sign a 32-byte digest and return a 64-byte Fuel compact signature.

The Fuel compact signature format embeds the recovery ID in the MSB of byte 32 (first byte of s):

s[0] = (recovery_id << 7) | (s[0] & 0x7F)

The result is r(32 bytes) + s(32 bytes) = 64 bytes.

Parameters:
  • private_key_bytes (bytes) – The 32-byte private key.

  • digest (bytes) – The 32-byte message digest to sign.

Returns:

The 64-byte Fuel compact signature.

Return type:

bytes

o2_sdk.crypto.personal_sign(private_key_bytes, message_bytes)[source]

Sign a message using Fuel’s personalSign format.

Used for session creation and withdrawals with Fuel-native wallets. Delegates to fuel_personal_sign_digest() for framing and fuel_compact_sign() for signing.

Parameters:
  • private_key_bytes (bytes) – The 32-byte private key.

  • message_bytes (bytes) – The message to sign.

Returns:

A 64-byte Fuel compact signature.

Return type:

bytes

o2_sdk.crypto.raw_sign(private_key_bytes, message_bytes)[source]

Sign a message using raw SHA-256 (no prefix).

Used for session actions (orders, cancels, settlements).

Format: fuel_compact_sign(key, sha256(msg))

Parameters:
  • private_key_bytes (bytes) – The 32-byte private key.

  • message_bytes (bytes) – The message to sign.

Returns:

A 64-byte Fuel compact signature.

Return type:

bytes

o2_sdk.crypto.evm_personal_sign(private_key_bytes, message_bytes)[source]

Sign using Ethereum’s personal_sign prefix + keccak-256.

Used for session creation and withdrawals with EVM wallets. Delegates to evm_personal_sign_digest() for framing and fuel_compact_sign() for signing.

Parameters:
  • private_key_bytes (bytes) – The 32-byte private key.

  • message_bytes (bytes) – The message to sign.

Returns:

A 64-byte Fuel compact signature.

Return type:

bytes

External signer support

For production deployments where private keys are managed by hardware wallets, AWS KMS, Google Cloud KMS, HashiCorp Vault, or other secure enclaves, use the external signer classes.

o2_sdk.crypto.SignDigestFn

Type alias for external signing callbacks.

SignDigestFn = Callable[[bytes], bytes]

The callback receives a 32-byte digest and must return a 64-byte Fuel compact signature. Use to_fuel_compact_signature() to convert from standard (r, s, recovery_id) components.

o2_sdk.crypto.to_fuel_compact_signature(r, s, recovery_id)[source]

Convert standard (r, s, recovery_id) components to a 64-byte Fuel compact signature.

This is a helper for implementing SignDigestFn callbacks when your external signing service returns standard secp256k1 components.

Parameters:
  • r (bytes) – 32-byte r component.

  • s (bytes) – 32-byte s component (must be low-s normalized).

  • recovery_id (int) – Recovery ID (0 or 1).

Returns:

64-byte Fuel compact signature.

Return type:

bytes

Raises:

ValueError – If r or s is not 32 bytes, or recovery_id is not 0 or 1.

def my_kms_sign(digest: bytes) -> bytes:
    r, s, v = kms_client.sign(digest)
    return to_fuel_compact_signature(r, s, v)
class o2_sdk.crypto.ExternalSigner(b256_address, sign_digest)[source]

A Fuel-native signer backed by an external signing function.

The SDK uses fuel_personal_sign_digest() internally for message framing; your callback only needs to sign a raw 32-byte digest.

Parameters:
  • b256_address (str) – The Fuel B256 address for this signer.

  • sign_digest (SignDigestFn) – A callback that signs a 32-byte digest and returns a 64-byte Fuel compact signature.

from o2_sdk import ExternalSigner, to_fuel_compact_signature

def kms_sign(digest: bytes) -> bytes:
    r, s, v = my_kms.sign(key_id="...", digest=digest)
    return to_fuel_compact_signature(r, s, v)

signer = ExternalSigner(
    b256_address="0x1234...abcd",
    sign_digest=kms_sign,
)
session = await client.create_session(owner=signer, markets=["fFUEL/fUSDC"])
class o2_sdk.crypto.ExternalEvmSigner(b256_address, evm_address, sign_digest)[source]

An EVM signer backed by an external signing function.

Same as ExternalSigner but uses evm_personal_sign_digest() for Ethereum personal_sign message framing (prefix + keccak-256 hashing).

Parameters:
  • b256_address (str) – The Fuel B256 address (EVM address zero-padded).

  • evm_address (str) – The Ethereum address.

  • sign_digest (SignDigestFn) – A callback that signs a 32-byte digest.

from o2_sdk import ExternalEvmSigner, to_fuel_compact_signature

signer = ExternalEvmSigner(
    b256_address="0x000000000000000000000000abcd...1234",
    evm_address="0xabcd...1234",
    sign_digest=kms_sign,
)