Connito AI
Reference

Signed Messages

Connito uses cryptographically signed messages to authenticate miner submissions. This prevents validators from accepting forged submissions and ensures that every checkpoint is traceable to a registered miner hotkey.

SignedModelSubmitMessage

The SignedModelSubmitMessage is the data structure miners send to validators when submitting a trained checkpoint.

@dataclass
class SignedModelSubmitMessage:
    hotkey: str          # miner's SS58-encoded Bittensor hotkey address
    expert_group: int    # which expert group this submission covers
    checkpoint_url: str  # URL where the validator can download the checkpoint
    block_number: int    # the block at which this submission is valid
    signature: str       # URL-safe Base64 encoded ed25519 signature

Field details:

FieldTypeDescription
hotkeystringSS58-encoded public key (e.g., 5FHneW46...). Identifies the miner uniquely on the Bittensor network.
expert_groupintegerThe expert group ID this checkpoint was trained for. Must match the miner's registered group.
checkpoint_urlstringA publicly accessible URL from which the validator can download the checkpoint binary.
block_numberintegerThe block number at which this submission was created. Validators reject submissions with a block number outside the valid window.
signaturestringURL-safe Base64 encoded ed25519 signature over the canonical serialization of the above fields.

sign_message()

Miners call sign_message() to produce the signature field.

def sign_message(message: SignedModelSubmitMessage, keypair: Keypair) -> str:
    """
    Signs the message fields using the miner's Bittensor keypair.
    Returns: URL-safe Base64 encoded ed25519 signature string.
    """
    canonical = canonicalize(message)  # deterministic serialization
    signature_bytes = keypair.sign(canonical)
    return base64.urlsafe_b64encode(signature_bytes).decode('ascii')

Canonical serialization: The fields are concatenated in a fixed order to produce the bytes that are signed:

def canonicalize(msg: SignedModelSubmitMessage) -> bytes:
    return (
        msg.hotkey.encode('utf-8') +
        b':' +
        str(msg.expert_group).encode('utf-8') +
        b':' +
        msg.checkpoint_url.encode('utf-8') +
        b':' +
        str(msg.block_number).encode('utf-8')
    )

The signing algorithm is ed25519 — the same algorithm Bittensor uses for all on-chain keypair operations. The private key is the miner's hotkey private key; it never leaves the miner's machine.

verify_message()

Validators call verify_message() to authenticate a submission before downloading the checkpoint.

def verify_message(message: SignedModelSubmitMessage, subtensor: Subtensor) -> bool:
    """
    Verifies the message signature using the miner's public key from chain.
    Returns: True if signature is valid and block number is in window.
    """
    # 1. Fetch miner's public key from blockchain using their hotkey address
    public_key = subtensor.get_hotkey_public_key(message.hotkey)
    if public_key is None:
        return False  # hotkey not registered on subnet

    # 2. Verify block number is within acceptable window
    current_block = subtensor.block
    if abs(current_block - message.block_number) > BLOCK_WINDOW:
        return False  # submission is stale or from the future

    # 3. Verify the ed25519 signature
    canonical = canonicalize(message)
    signature_bytes = base64.urlsafe_b64decode(message.signature)
    return public_key.verify(canonical, signature_bytes)

A submission that fails verify_message() is rejected immediately — the validator does not download the checkpoint.

URL-Safe Base64

Signatures are encoded using URL-safe Base64 (base64.urlsafe_b64encode in Python's standard library). This encoding:

  • Uses - instead of + and _ instead of /
  • Produces strings safe to use in HTTP headers, JSON values, and URL query parameters
  • Does not require additional percent-encoding
import base64

# Encoding
encoded = base64.urlsafe_b64encode(signature_bytes).decode('ascii')

# Decoding
decoded = base64.urlsafe_b64decode(encoded.encode('ascii'))

Two-Phase Commit Flow

The signed message is the second phase of a two-phase commit protocol. The full flow:

COMMIT PHASE:
  1. Miner trains expert group → produces checkpoint_bytes
  2. hash = sha256(checkpoint_bytes)
  3. Miner calls WorkerChainCommit.commit_hash(hash, block)
  4. Hash is recorded on-chain (immutable from this point)

SUBMIT PHASE:
  5. Miner constructs SignedModelSubmitMessage with checkpoint_url
  6. Miner calls sign_message() → produces signature
  7. Miner POSTs the signed message to validator's /submit endpoint

VALIDATION:
  8. Validator calls verify_message() → checks signature and block window
  9. Validator fetches checkpoint from checkpoint_url
  10. Validator computes sha256(checkpoint_bytes) and compares to on-chain hash
  11. If hash matches → proceed to Proof-of-Loss evaluation
  12. If hash mismatch → reject submission (miner tried to swap checkpoint after commit)

Why this prevents gaming:

Step 4 locks the checkpoint content before the submit phase opens. Step 12 enforces this lock. A miner who wants to observe other miners' submissions and then submit a better one would need to:

  • Commit a hash before seeing others' submissions (step 3), then
  • Submit a different checkpoint after seeing them (step 5), which would fail hash verification (step 12)

The two-phase commit makes front-running cryptographically impossible.

Security note

The private key used in sign_message() is the miner's hotkey private key. Store this key securely. Anyone with access to your hotkey private key can submit on your behalf and drain your rewards.

Use the BZ_WALLET_PASSWORD environment variable to protect your wallet file at rest.