Metadata-Version: 2.4
Name: bip46
Version: 1.1.0
Summary: A Python implementation of BIP46: Address Scheme for Timelocked Fidelity Bonds
Project-URL: Repository, https://github.com/dni/bip46
Author-email: dni <dni@lnbits.com>
License-Expression: MIT
License-File: LICENSE
Requires-Python: >=3.12
Requires-Dist: bech32<2.0.0,>=1.2.0
Requires-Dist: bip32>=5.0.0
Requires-Dist: click<9.0.0,>=8.1.7
Description-Content-Type: text/markdown

# BIP46 - Address Scheme for Timelocked Fidelity Bonds

[![PyPI](https://img.shields.io/pypi/v/bip46)](https://pypi.org/project/bip46/)

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

## Example

### Create a redeem script from a mnemonic

```python
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.

```python
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.

```python
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:

```sh
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)

```python
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

```python
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)

```python
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")
```
