A Python implementation of BIP46: Address Scheme for Timelocked Fidelity Bonds
Project description
BIP46 - Address Scheme for Timelocked Fidelity Bonds
Python implementation of BIP46: Address Scheme for Timelocked Fidelity Bonds.
Example
Create a redeem script from a mnemonic
from datetime import UTC, datetime
from bip46 import (
create_redeemscript,
hdkey_derive,
hdkey_from_mnemonic,
hdkey_to_pubkey,
lockdate_to_derivation_path,
redeemscript_address,
redeemscript_pubkey,
)
network = "mainnet"
lock_date = datetime(2042, 6, 1, tzinfo=UTC)
lock_path = lockdate_to_derivation_path(lock_date, network=network)
hdkey = hdkey_from_mnemonic(
"abandon abandon abandon abandon abandon abandon"
" abandon abandon abandon abandon abandon about",
network=network,
)
redeem_key = hdkey_derive(hdkey, lock_path)
redeem_pub_key = hdkey_to_pubkey(redeem_key)
redeem_script = create_redeemscript(lock_date, redeem_pub_key)
script_pubkey = redeemscript_pubkey(redeem_script)
script_address = redeemscript_address(script_pubkey, network=network)
Create addresses from an xpub
BIP46 paths include hardened account components: m/84'/coin_type'/0'/2/index.
A public key cannot derive those hardened components, so pass an account-level xpub
for m/84'/coin_type'/0'. The library will derive the public BIP46 suffix
2/index from that account xpub.
from datetime import UTC, datetime
from bip46 import (
create_redeemscript,
hdkey_derive,
hdkey_from_xpub,
hdkey_to_pubkey,
lockdate_to_derivation_path,
redeemscript_address,
redeemscript_pubkey,
)
network = "mainnet"
lock_date = datetime(2042, 6, 1, tzinfo=UTC)
lock_path = lockdate_to_derivation_path(lock_date, network=network)
hdkey = hdkey_from_xpub("xpub...")
redeem_key = hdkey_derive(hdkey, lock_path)
redeem_pub_key = hdkey_to_pubkey(redeem_key)
redeem_script = create_redeemscript(lock_date, redeem_pub_key)
script_pubkey = redeemscript_pubkey(redeem_script)
script_address = redeemscript_address(script_pubkey, network=network)
Create and sign a fidelity bond certificate
Per BIP46, the certificate message contains a certificate/identity pubkey (an online key, independent of the fidelity bond address), and is signed with the timelock private key. This lets the timelock key stay offline — only the identity key needs to be hot. The verifier recovers the timelock pubkey from the signature to confirm bond ownership.
import base64
from bip46 import (
create_certificate_message,
recover_from_signature_and_message,
sign_certificate_message,
)
# identity/certificate pubkey — can be any key, does not have to match the timelock key
cert_pubkey_hex = "02..."
# timelock private key (the offline key that owns the fidelity bond UTXO)
timelock_private_key = bytes.fromhex("...")
message = create_certificate_message(cert_pubkey_hex)
signature_bytes = sign_certificate_message(timelock_private_key, message)
signature_b64 = base64.b64encode(signature_bytes).decode()
# verify: recovered pubkey is the timelock pubkey, proving ownership of the bond
recovered_timelock_pubkey = recover_from_signature_and_message(signature_b64, message)
The CLI also accepts an account xpub:
XPUB="xpub..." uv run bip46 create-timelock 2042 6 mainnet
Scanning and verifying on-chain with electrs or mempool.space
bip46 is chain-query agnostic. Use any HTTP client against an electrs-compatible API (electrs, mempool.space, Blockstream Explorer) to look up addresses and UTXOs.
Check if a timelock address has funds (electrs)
import httpx
from bip46 import (
create_redeemscript,
hdkey_derive,
hdkey_from_mnemonic,
hdkey_to_pubkey,
lockdate_to_derivation_path,
redeemscript_address,
redeemscript_pubkey,
)
from datetime import UTC, datetime
ELECTRS = "https://blockstream.info/api" # or mempool.space/api
lock_date = datetime(2042, 6, 1, tzinfo=UTC)
hdkey = hdkey_from_mnemonic("your mnemonic ...", network="mainnet")
path = lockdate_to_derivation_path(lock_date, network="mainnet")
child = hdkey_derive(hdkey, path)
pubkey = hdkey_to_pubkey(child)
redeemscript = create_redeemscript(lock_date, pubkey)
address = redeemscript_address(redeemscript_pubkey(redeemscript), network="mainnet")
txs = httpx.get(f"{ELECTRS}/address/{address}/txs").raise_for_status().json()
print(f"{address}: {len(txs)} transaction(s)")
Find the UTXO for a timelock address
utxos = httpx.get(f"{ELECTRS}/address/{address}/utxo").raise_for_status().json()
for utxo in utxos:
print(utxo["txid"], utxo["vout"], utxo["value"], "sats")
Verify a fidelity bond proof (certificate + UTXO)
import base64
import httpx
from bip46 import verify_certificate
ELECTRS = "https://blockstream.info/api"
# received from the bond holder
cert_pubkey_hex = "02..." # identity pubkey embedded in the cert
cert_signature_b64 = "..." # base64 signature over the cert message
timelock_address = "bc1q..." # the fidelity bond address
# 1. verify the certificate — recovered pubkey must match the timelock address's key
# (derive the expected timelock pubkey from the address or from the redeemscript)
expected_timelock_pubkey: bytes = ... # hdkey_to_pubkey(hdkey_derive(hdkey, path))
assert verify_certificate(cert_signature_b64, cert_pubkey_hex, expected_timelock_pubkey)
# 2. confirm the address actually holds a UTXO on-chain
utxos = httpx.get(f"{ELECTRS}/address/{timelock_address}/utxo").raise_for_status().json()
assert utxos, "no unspent output found for timelock address"
total_sats = sum(u["value"] for u in utxos)
print(f"bond value: {total_sats} sats")
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file bip46-1.1.0.tar.gz.
File metadata
- Download URL: bip46-1.1.0.tar.gz
- Upload date:
- Size: 36.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.24 {"installer":{"name":"uv","version":"0.11.24","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
05832f96c086be6d8ef55dad7fdc085363dfd824250a2bd5333e5a76a829fe65
|
|
| MD5 |
5de47e1d5115d283bd824f5d0b73544a
|
|
| BLAKE2b-256 |
e1279f3e21bc2125822f2d0de9b70f7546207f14eede9b2f6343a1edf1c7a8a7
|
File details
Details for the file bip46-1.1.0-py3-none-any.whl.
File metadata
- Download URL: bip46-1.1.0-py3-none-any.whl
- Upload date:
- Size: 11.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.24 {"installer":{"name":"uv","version":"0.11.24","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6312d8a54cb5c1f7e87da967f199e272c904cb2966b72e4ffc4b258f8ffe0cde
|
|
| MD5 |
f0759aaf4f2230521a6ca57a74317d6e
|
|
| BLAKE2b-256 |
e080151b3e880a502a7d2769fcd127bcf5ebdf0db1a26325114aedd1788b4b11
|