Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions ckcc/bip353.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import dns.resolver
import dns.dnssec

class BIP353Resolver:
def __init__(self, nameserver='8.8.8.8'):
self.resolver = dns.resolver.Resolver()
self.resolver.nameservers = [nameserver]
self.resolver.use_edns(0, dns.flags.DO, 4096) # Request DNSSEC

def resolve(self, handle):
# Clean the input (e.g., ₿alice@example.com -> alice@example.com)
handle = handle.lstrip('₿')
user, domain = handle.split('@')
target = f"{user}.user._bitcoin-payment.{domain}"

try:
# Query TXT record with DNSSEC validation
response = self.resolver.resolve(target, 'TXT', want_dnssec=True)

# Extract the URI from the TXT record
txt_data = b"".join(response.rrset[0].strings).decode()

if not txt_data.startswith("bitcoin:"):
raise ValueError("Invalid BIP 353 record: Missing 'bitcoin:' prefix")

return {
'uri': txt_data,
'validated': response.response.flags & dns.flags.AD, # Authenticated Data flag
'rrsig': response.response.answer[1] if len(response.response.answer) > 1 else None
}
except Exception as e:
return {'error': str(e)}
48 changes: 48 additions & 0 deletions ckcc/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,54 @@ def my_hook(ty, val, tb):
sys.excepthook=my_hook


@main.group()
def bip353():
"""BIP-353 DNS Payment handle utilities."""
pass

@bip353.command('resolve')
@click.argument('handle')
@click.option('--offline', is_flag=True, help="Save proof to file for SD card instead of USB")
@click.option('--output', type=click.Path(), help="Output file path for offline use")
def resolve_cmd(handle, offline, output):
"""Resolve a handle and send to device (USB) or save to file (SD)."""

# 1. Host-side resolution
resolver = BIP353Resolver()
result = resolver.resolve(handle)

if result.get('error'):
print(f"Error: {result['error']}")
return

# 2. Path Selection
if offline:
# Save to file for MicroSD transfer
file_path = output or f"{handle.replace('@', '_')}.bip353"
with open(file_path, 'wb') as f:
f.write(result['proof_bytes'])
print(f"✅ Proof saved to {file_path}. Copy this to your SD card.")
else:
# Standard USB/HID Path
try:
with device_picker() as dev:
print(f"Uploading DNSSEC proof to Coldcard...")
# We use the method we added to protocol.py earlier
dev.send_dnssec_proof(handle, result['proof_bytes'])
print("Checking verification status...")
# Wait for user interaction on device
print("✓ Verified. Check Coldcard screen for details.")
except Exception as e:
print(f"❌ Connection Error: {e}")

@bip353.command('pay')
@click.argument('handle')
@click.argument('amount', type=float)
def pay_cmd(handle, amount):
"""Resolve a handle and initiate a transaction."""
# Combines resolution + transaction setup
pass

@contextlib.contextmanager
def get_device(optional=False):
# Open connection to Coldcard as a context with auto-close
Expand Down
25 changes: 25 additions & 0 deletions ckcc/dnssec.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import dns.resolver
import dns.message
import dns.query

def get_dnssec_proof(handle):
user, domain = handle.lstrip('₿').split('@')
target = f"{user}.user._bitcoin-payment.{domain}"

# 1. Setup a resolver that requests DNSSEC records (DO bit)
resolver = dns.resolver.Resolver()
resolver.use_edns(0, dns.flags.DO, 4096)

# 2. Fetch the TXT record and its RRSIG
answer = resolver.resolve(target, 'TXT', want_dnssec=True)

# 3. Build the proof chain (This is a simplified representation)
# In a production BIP 353 app, you'd use 'dnssec-prover' to
# package these into a binary RFC 9102 blob.
proof_blobs = []
for rrset in answer.response.answer:
proof_blobs.append(rrset.to_wire())

# Also need DS and DNSKEY records for each level of the hierarchy
# (Root -> TLD -> Domain)
return proof_blobs
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
'hidapi>=0.7.99.post21',
'ecdsa>=0.17',
'pyaes',
'dnspython>=2.6.0', # Required for BIP 353 DNSSEC
]

cli_requirements = [
Expand Down