Drand timelock encryption for Bittensor commit-reveal weights and general-purpose commitments. Rust core with Python bindings via PyO3.
get_encrypted_commitis replaced byget_encrypted_commit_v2. The old modulo-based epoch math ((block + netuid + 1) / (tempo + 1)) is gone. The chain now uses a stateful epoch counter (SubnetEpochIndex), owner-triggered pending epochs, and configurable tempo. The new function simulates the chain'sblock_steppipeline to predict the exact reveal block.- Breaking change:
get_encrypted_commit_v2requires epoch schedule state instead oftempo + current_block + netuid. The SDK (bittensor>=11.0.0) provides this viasubtensor.get_epoch_schedule_state(netuid). - Added
encrypt_mlkem768andmlkem_kdf_idfor ML-KEM-768 + XChaCha20Poly1305 encryption.
from bittensor_drand import get_encrypted_commit_v2pip install bittensor-drandFor development (build from source):
git clone https://github.com/opentensor/bittensor-drand.git
cd bittensor-drand
python3 -m venv venv
. venv/bin/activate
pip install maturin bittensor
maturin developThe Bittensor SDK handles all the epoch state plumbing internally. You just call set_weights:
import bittensor as bt
sub = bt.Subtensor("local") # or "finney" for mainnet
wallet = bt.Wallet()
result, message = sub.set_weights(
wallet=wallet,
netuid=1,
uids=[0, 1, 2],
weights=[0.5, 0.3, 0.2],
wait_for_inclusion=True,
wait_for_finalization=True,
)
print(f"Success: {result}, message: {message}")If you need lower-level control (custom clients, miners, tooling):
import bittensor as bt
from bittensor_drand import get_encrypted_commit_v2
sub = bt.Subtensor("local")
netuid = 1
current_block = sub.get_current_block()
# Fetch epoch schedule state from chain
schedule = sub.get_epoch_schedule_state(netuid, block=current_block)
reveal_period = sub.get_subnet_reveal_period_epochs(netuid=netuid)
uids = [1, 3]
weights = [100, 200] # u16 values after convert_weights_and_uids_for_emit
version_key = 843000
wallet = bt.Wallet()
commit_bytes, reveal_round = get_encrypted_commit_v2(
uids=uids,
weights=weights,
version_key=version_key,
last_epoch_block=schedule.last_epoch_block,
pending_epoch_at=schedule.pending_epoch_at,
subnet_epoch_index=schedule.subnet_epoch_index,
tempo=schedule.tempo,
blocks_since_last_step=schedule.blocks_since_last_step,
current_block=current_block,
subnet_reveal_period_epochs=reveal_period,
block_time=12.0,
hotkey=wallet.hotkey.public_key,
)
print(f"Encrypted commit: {len(commit_bytes)} bytes, reveal round: {reveal_round}")from bittensor_drand import get_encrypted_commitment
encrypted, reveal_round = get_encrypted_commitment(
"my secret data",
blocks_until_reveal=10,
block_time=12.0,
)
print(f"Encrypted: {len(encrypted)} bytes, reveal at round {reveal_round}")from bittensor_drand import encrypt, decrypt
encrypted, reveal_round = encrypt(b"binary payload", n_blocks=5, block_time=12.0)
# Later, after the reveal round has passed:
decrypted = decrypt(encrypted, no_errors=False)from bittensor_drand import encrypt_at_round
encrypted, reveal_round = encrypt_at_round(b"payload", reveal_round=17200000)When decrypting multiple ciphertexts for the same round, fetch the signature once:
from bittensor_drand import decrypt_with_signature, get_signature_for_round
sig = get_signature_for_round(reveal_round=17200000)
plaintext = decrypt_with_signature(encrypted, sig)For post-quantum key encapsulation (used by the chain's NextKey rotation):
from bittensor_drand import encrypt_mlkem768, mlkem_kdf_id
# pk_bytes: 1184-byte ML-KEM-768 public key from NextKey storage
blob = encrypt_mlkem768(pk_bytes, b"plaintext", include_key_hash=True)
# Blob format (include_key_hash=True):
# [key_hash(16)][u16 kem_len LE][kem_ct][nonce24][aead_ct]
kdf = mlkem_kdf_id() # b"v1" — raw shared secret, no HKDF- Start a local subtensor node with configurable tempo support:
LOCALNET_IMAGE_NAME=ghcr.io/opentensor/subtensor-localnet:pr-2638 ./scripts/localnet.sh-
Create a subnet and configure hyperparameters:
- Set
commit_reveal_weights_enabledtoTrue - Set
tempoto your desired value (e.g.360) - Set
weights_rate_limitto0(for faster testing)
- Set
-
Register a wallet to the subnet.
-
Run a weight-setting script:
import bittensor as bt
from bittensor import logging
logging.set_info()
sub = bt.Subtensor("local")
wallet = bt.Wallet()
result, message = sub.set_weights(
wallet=wallet,
netuid=1,
uids=[0],
weights=[1.0],
wait_for_inclusion=True,
wait_for_finalization=True,
)
logging.info(f"Result: {result}, message: {message}")- Wait for the reveal epoch, then verify weights were applied:
import bittensor as bt
sub = bt.Subtensor("local")
print(sub.weights(netuid=1))pip install maturin
maturin develop
cargo test
pytest tests/ -v