Skip to main content

A Python implementation of BIP46: Address Scheme for Timelocked Fidelity Bonds

Project description

BIP46 - Address Scheme for Timelocked Fidelity Bonds

PyPI

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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

bip46-1.1.0.tar.gz (36.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

bip46-1.1.0-py3-none-any.whl (11.3 kB view details)

Uploaded Python 3

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

Hashes for bip46-1.1.0.tar.gz
Algorithm Hash digest
SHA256 05832f96c086be6d8ef55dad7fdc085363dfd824250a2bd5333e5a76a829fe65
MD5 5de47e1d5115d283bd824f5d0b73544a
BLAKE2b-256 e1279f3e21bc2125822f2d0de9b70f7546207f14eede9b2f6343a1edf1c7a8a7

See more details on using hashes here.

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

Hashes for bip46-1.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 6312d8a54cb5c1f7e87da967f199e272c904cb2966b72e4ffc4b258f8ffe0cde
MD5 f0759aaf4f2230521a6ca57a74317d6e
BLAKE2b-256 e080151b3e880a502a7d2769fcd127bcf5ebdf0db1a26325114aedd1788b4b11

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page