diff --git a/README.md b/README.md index 5237a85..d0c9f87 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,18 @@ PyPi installation includes prebuilt libraries for common platforms (win, macos, If you want to build the lib yourself, see: [Building secp256k1 for `embit`](/secp256k1/README.md). +### DNSSEC Prover Binaries + +`embit` includes prebuilt DNSSEC prover binaries for BIP-353 support. These binaries are generated from [dnssec-prover](https://github.com/TheBlueMatt/dnssec-prover) Rust tool and provide cross-platform DNSSEC validation capabilities. + +**Supported Platforms:** +- macOS (ARM64, x86_64) +- Linux (ARM64, ARMv6, ARMv7, x86_64) +- Windows (AMD64) + +**Note:** MicroPython compatibility is uncertain and may need investigation for embedded platforms. + +For information on building these binaries yourself, see [Building DNSSEC Prover Binaries](./docs/recepies/dnssec-prover_build_instructions.md). ## Using non-English BIP39 wordlists [BIP39](https://github.com/bitcoin/bips/blob/master/bip-0039/bip-0039-wordlists.md) defines wordlists for: diff --git a/docs/recepies/dnssec-prover_build_instructions.md b/docs/recepies/dnssec-prover_build_instructions.md new file mode 100644 index 0000000..a3b3bf4 --- /dev/null +++ b/docs/recepies/dnssec-prover_build_instructions.md @@ -0,0 +1,283 @@ +# Building DNSSEC Prover Binaries + +This document explains how the DNSSEC prover binaries in `src/embit/util/prebuilt/` were created and how to reproduce them. + +## Overview + +The DNSSEC prover binaries provide offline DNSSEC validation capabilities for BIP-353 support in `embit`. They are generated from [@TheBlueMatt's dnssec-prover](https://github.com/TheBlueMatt/dnssec-prover) Rust tool using uniffi for cross-compilation. + +## Binary Files + +The following prebuilt binaries are included: + +| Platform | Architecture | File | +|----------|--------------|------| +| macOS | ARM64 | `libuniffi_dnssec_prover_darwin_arm64.dylib` | +| macOS | x86_64 | `libuniffi_dnssec_prover_darwin_x86_64.dylib` | +| Linux | ARM64 | `libuniffi_dnssec_prover_linux_aarch64.so` | +| Linux | ARMv6 | `libuniffi_dnssec_prover_linux_armv6l.so` | +| Linux | ARMv7 | `libuniffi_dnssec_prover_linux_armv7l.so` | +| Linux | x86_64 | `libuniffi_dnssec_prover_linux_x86_64.so` | +| Windows | AMD64 | `libuniffi_dnssec_prover_windows_amd64.dll` | + +### Prerequisites + +- **Docker**: Install Docker Desktop or Docker Engine +- **Python**: Version 3.10 or 3.12 + +## Docker Build Approach + +Instead of cross-compilation, we use Docker to build natively for most platforms. This approach is more reliable and avoids linker issues. + +## Build Process + +### 1. Build for macOS ARM64 (Apple Silicon) + +```bash +# Not available for cross-compilation. Must be built from macOS ARM64 +rustup target add aarch64-apple-darwin && +cargo build --release --target aarch64-apple-darwin --manifest-path uniffi/Cargo.toml && +cp uniffi/target/aarch64-apple-darwin/release/libdnssec_prover_uniffi.dylib dist/libuniffi_dnssec_prover_darwin_arm64.dylib +``` + +### 2. Build for macOS x86_64 (Intel) + +```bash +# Not available for cross-compilation. Must be built from macOS x86_64 +rustup target add x86_64-apple-darwin && +cargo build --release --target x86_64-apple-darwin --manifest-path uniffi/Cargo.toml && +cp uniffi/target/x86_64-apple-darwin/release/libdnssec_prover_uniffi.dylib dist/libuniffi_dnssec_prover_darwin_x86_64.dylib +``` + +### 3. Build for Linux ARM64 + +```bash +# Build using Docker with ARM64 Linux +docker run --rm --platform linux/arm64 -v $(pwd):/workspace -w /workspace rust:latest bash -c " +rustup target add aarch64-unknown-linux-gnu && +cargo build --release --target aarch64-unknown-linux-gnu --manifest-path uniffi/Cargo.toml && +cp uniffi/target/aarch64-unknown-linux-gnu/release/libdnssec_prover_uniffi.so dist/libuniffi_dnssec_prover_linux_aarch64.so +" +``` + +### 4. Build for Linux ARMv6 + +```bash +# Build using Docker with ARMv6 Linux (use linux/arm for compatibility) +docker run --rm --platform linux/arm -v $(pwd):/workspace -w /workspace rust:latest bash -c " +rustup target add arm-unknown-linux-gnueabihf && +cargo build --release --target arm-unknown-linux-gnueabihf --manifest-path uniffi/Cargo.toml && +cp uniffi/target/arm-unknown-linux-gnueabihf/release/libdnssec_prover_uniffi.so dist/libuniffi_dnssec_prover_linux_armv6l.so +" +``` + +### 5. Build for Linux ARMv7 + +```bash +# Build using Docker with ARMv7 Linux (use linux/arm for compatibility) +docker run --rm --platform linux/arm -v $(pwd):/workspace -w /workspace rust:latest bash -c " +rustup target add armv7-unknown-linux-gnueabihf && +cargo build --release --target armv7-unknown-linux-gnueabihf --manifest-path uniffi/Cargo.toml && +cp uniffi/target/armv7-unknown-linux-gnueabihf/release/libdnssec_prover_uniffi.so dist/libuniffi_dnssec_prover_linux_armv7l.so +" +``` + +### 6. Build for Linux x86_64 + +```bash +# Build using Docker with x86_64 Linux +docker run --rm --platform linux/amd64 -v $(pwd):/workspace -w /workspace rust:latest bash -c " +rustup target add x86_64-unknown-linux-gnu && +cargo build --release --target x86_64-unknown-linux-gnu --manifest-path uniffi/Cargo.toml && +cp uniffi/target/x86_64-unknown-linux-gnu/release/libdnssec_prover_uniffi.so dist/libuniffi_dnssec_prover_linux_x86_64.so +" +``` + +### 7. Build for Windows AMD64 + +```bash +# Build using Docker with Windows container +# rust:latest is not published as a Windows container, so we compile from an x86_64 Linux container +docker run --rm --platform linux/amd64 -v $(pwd):/workspace -w /workspace rust:latest bash -c " +apt-get update && +apt-get install -y mingw-w64 && +rustup target add x86_64-pc-windows-gnu && +cargo build --release --target x86_64-pc-windows-gnu --manifest-path uniffi/Cargo.toml && +cp uniffi/target/x86_64-pc-windows-gnu/release/dnssec_prover_uniffi.dll dist/libuniffi_dnssec_prover_windows_amd64.dll +" +``` + +## Complete Build Script + +Here's a script that builds everything using Docker: + +```bash +# Create complete build script (remove macOS builds if running from Linux) +cat > build-all-platforms.sh << 'EOF' +#!/bin/bash +set -e + +echo "Building DNSSEC Prover for all platforms using Docker..." + +mkdir -p dist + +# macOS builds +echo "Building macOS ARM64..." +rustup target add aarch64-apple-darwin && +cargo build --release --target aarch64-apple-darwin --manifest-path uniffi/Cargo.toml && +cp uniffi/target/aarch64-apple-darwin/release/libdnssec_prover_uniffi.dylib dist/libuniffi_dnssec_prover_darwin_arm64.dylib + +echo "Building macOS x86_64..." +rustup target add x86_64-apple-darwin && +cargo build --release --target x86_64-apple-darwin --manifest-path uniffi/Cargo.toml && +cp uniffi/target/x86_64-apple-darwin/release/libdnssec_prover_uniffi.dylib dist/libuniffi_dnssec_prover_darwin_x86_64.dylib + +# Linux builds (Docker) +echo "Building Linux ARM64..." +docker run --rm --platform linux/arm64 -v $(pwd):/workspace -w /workspace rust:latest bash -c " +rustup target add aarch64-unknown-linux-gnu && +cargo build --release --target aarch64-unknown-linux-gnu --manifest-path uniffi/Cargo.toml && +cp uniffi/target/aarch64-unknown-linux-gnu/release/libdnssec_prover_uniffi.so dist/libuniffi_dnssec_prover_linux_aarch64.so +" + +echo "Building Linux ARMv6..." +docker run --rm --platform linux/arm -v $(pwd):/workspace -w /workspace rust:latest bash -c " +rustup target add arm-unknown-linux-gnueabihf && +cargo build --release --target arm-unknown-linux-gnueabihf --manifest-path uniffi/Cargo.toml && +cp uniffi/target/arm-unknown-linux-gnueabihf/release/libdnssec_prover_uniffi.so dist/libuniffi_dnssec_prover_linux_armv6l.so +" + +echo "Building Linux ARMv7..." +docker run --rm --platform linux/arm -v $(pwd):/workspace -w /workspace rust:latest bash -c " +rustup target add armv7-unknown-linux-gnueabihf && +cargo build --release --target armv7-unknown-linux-gnueabihf --manifest-path uniffi/Cargo.toml && +cp uniffi/target/armv7-unknown-linux-gnueabihf/release/libdnssec_prover_uniffi.so dist/libuniffi_dnssec_prover_linux_armv7l.so +" + +echo "Building Linux x86_64..." +docker run --rm --platform linux/amd64 -v $(pwd):/workspace -w /workspace rust:latest bash -c " +rustup target add x86_64-unknown-linux-gnu && +cargo build --release --target x86_64-unknown-linux-gnu --manifest-path uniffi/Cargo.toml && +cp uniffi/target/x86_64-unknown-linux-gnu/release/libdnssec_prover_uniffi.so dist/libuniffi_dnssec_prover_linux_x86_64.so +" + +# Windows build (Docker) +echo "Building Windows AMD64..." +docker run --rm --platform linux/amd64 -v $(pwd):/workspace -w /workspace rust:latest bash -c " +apt-get update && +apt-get install -y mingw-w64 && +rustup target add x86_64-pc-windows-gnu && +cargo build --release --target x86_64-pc-windows-gnu --manifest-path uniffi/Cargo.toml && +cp uniffi/target/x86_64-pc-windows-gnu/release/dnssec_prover_uniffi.dll dist/libuniffi_dnssec_prover_windows_amd64.dll +" + +shasum -a 256 dist/libuniffi_dnssec_prover_* +EOF + +# Make executable and run +chmod +x build-all-platforms.sh +./build-all-platforms.sh +``` + +### Expected hash output: +```bash +6ef0d1d762a0171b0ff2d5b09730096667be252ebf9cb7aafe5a09de4a5e839b dist/libuniffi_dnssec_prover_darwin_arm64.dylib +35128508261ac7e2185fdeb7f8af0f811e8924cf4237e5d717bd9b7aa1be6f85 dist/libuniffi_dnssec_prover_darwin_x86_64.dylib +678c0e0566b361099b800bc457b74f041afb4b88aed8eee8681b21ffccf5ce0d dist/libuniffi_dnssec_prover_linux_aarch64.so +9a7762204ca92e34b64c97dfbb52a3f25210e00c950409288d91e97feb9d5e45 dist/libuniffi_dnssec_prover_linux_armv6l.so +88e8d790bd21f686b7828798bcd0fb99adb9d1513be774f4a7f0cde462c77ff4 dist/libuniffi_dnssec_prover_linux_armv7l.so +66fab93c1ab388f4e09775e9be6cc70a9f8fe0778924719ea8758dafdfd7087a dist/libuniffi_dnssec_prover_linux_x86_64.so +4d13fe7e0ee376105098f2132e58fa90bb892a9154212c86236405eb3d14a612 dist/libuniffi_dnssec_prover_windows_amd64.dll +``` + +The binaries can then be moved to the `src/embit/util/prebuilt/` directory. + +## Generate Python Bindings + +After building all platforms, generate Python bindings: + +```bash +cd uniffi +cargo run --bin uniffi-bindgen generate \ + --library ../dist/libuniffi_dnssec_prover_linux_x86_64.so \ + --out-dir ../python-bindings \ + --language python +``` + +## Required Patch for dnssec_prover.py + +The generated Python bindings need a patch to fix library loading and adapt them to the same approach used in `embit` for secp256k1 bindings. +Apply this patch to `python-bindings/dnssec_prover.py`: + +```diff +--- a/python-bindings/dnssec_prover.py ++++ b/python-bindings/dnssec_prover.py +@@ -429,27 +429,50 @@ + def _uniffi_load_indirect(): + """ + This is how we find and load the dynamic library provided by the component. +- For now we just look it up by name. ++ We use the same pattern as ctypes_secp256k1.py for consistency. + """ +- if sys.platform == "darwin": +- libname = "lib{}.dylib" +- elif sys.platform.startswith("win"): +- # As of python3.8, ctypes does not seem to search $PATH when loading DLLs. +- # We could use `os.add_dll_directory` to configure the search path, but +- # it doesn't feel right to mess with application-wide settings. Let's +- # assume that the `.dll` is next to the `.py` file and load by full path. +- libname = os.path.join( +- os.path.dirname(__file__), +- "{}.dll", ++ library_path = None ++ extension = "" ++ if platform.system() == "Darwin": ++ extension = ".dylib" ++ elif platform.system() == "Linux": ++ extension = ".so" ++ elif platform.system() == "Windows": ++ extension = ".dll" + ++ # Try prebuilt platform-specific library first ++ path = os.path.join( ++ os.path.dirname(__file__), ++ "prebuilt/libuniffi_dnssec_prover_%s_%s%s" ++ % (platform.system().lower(), platform.machine().lower(), extension), ++ ) ++ if os.path.isfile(path): ++ return ctypes.cdll.LoadLibrary(path) + ++ # Try searching system paths ++ if not library_path: ++ library_path = ctypes.util.find_library("libuniffi_dnssec_prover") ++ if not library_path: ++ library_path = ctypes.util.find_library("uniffi_dnssec_prover") + ++ # Platform-specific fallback locations ++ if not library_path: ++ if platform.system() == "Linux" and os.path.isfile( ++ "/usr/local/lib/libuniffi_dnssec_prover.so" ++ ): ++ library_path = "/usr/local/lib/libuniffi_dnssec_prover.so" ++ elif platform.system() == "Darwin" and os.path.isfile( ++ "/usr/local/lib/libuniffi_dnssec_prover.dylib" ++ ): ++ library_path = "/usr/local/lib/libuniffi_dnssec_prover.dylib" + ++ if not library_path: ++ raise RuntimeError( ++ f"Can't find libuniffi_dnssec_prover library for {platform.system().lower()}_{platform.machine().lower()}. " ++ "Make sure to compile and install it, or place it in the prebuilt/ directory." ++ ) + ++ return ctypes.cdll.LoadLibrary(library_path) +``` + +Then, move the bindings file to the `src/embit/util/` directory. + +## References + +- [dnssec-prover](https://github.com/TheBlueMatt/dnssec-prover) +- [uniffi Documentation](https://mozilla.github.io/uniffi-rs/) +- [BIP-353: DNS Payment Instructions](https://github.com/bitcoin/bips/blob/master/bip-0353.mediawiki) +- [Rust Cross-compilation Guide](https://rust-lang.github.io/rustup/cross-compilation.html) \ No newline at end of file diff --git a/src/embit/bip21.py b/src/embit/bip21.py new file mode 100644 index 0000000..058607a --- /dev/null +++ b/src/embit/bip21.py @@ -0,0 +1,284 @@ +"""BIP21 Bitcoin URI handling""" + +import re +from urllib.parse import quote, unquote, urlparse, parse_qs +from decimal import Decimal, InvalidOperation, ROUND_FLOOR +from .base import EmbitBase, EmbitError + + +class BIP21Error(EmbitError): + """BIP21 URI parsing/validation error""" + pass + + +class BitcoinURI(EmbitBase): + """ + Bitcoin URI handler implementing BIP-21/BIP-321. + Used by BIP-353 implementation to extract payment information from a human-readable name. + + Supports: + - Standard bitcoin: URIs + - URL encoding/decoding + - Required field validation (req- prefix) + - Standard fields: amount, label, message + - Silent payments addresses (sp parameter) + - Bech32(m) addresses (bc parameter) + """ + + # Standard field names + FIELD_MESSAGE = "message" + FIELD_LABEL = "label" + FIELD_AMOUNT = "amount" + FIELD_BC = "bc" # Bech32(m) address parameter + FIELD_SILENT_PAYMENT = "sp" + FIELD_POP = "pop" + + # Fields that MUST NOT have duplicates + NO_DUPLICATE_FIELDS = {FIELD_LABEL, FIELD_MESSAGE, FIELD_AMOUNT, FIELD_POP} + + # Fields that are payment instructions and MAY have duplicates + PAYMENT_INSTRUCTION_FIELDS = {FIELD_BC, FIELD_SILENT_PAYMENT} + + # Constants + BITCOIN_SCHEME = "bitcoin" + MAX_BITCOIN = 21000000 + SATOSHIS_PER_BITCOIN = 100000000 + SMALLEST_UNIT_EXPONENT = 8 + + # Bech32/Bech32m addresses: max 90 characters total (from BIP173) + # Minimum: HRP (2 chars) + "1" + 6 chars checksum + data (min 2 chars) = 11 chars + # Base58 addresses (P2PKH/P2SH): typically 26-35 characters. We take bech32 as reference. + MIN_BECH32_LENGTH = 11 + MAX_BECH32_LENGTH = 90 + + def __init__(self, uri_string: str): + """ + Parse a Bitcoin URI string. + + Args: + uri_string: The URI string to parse + + Raises: + BIP21Error: If the URI is invalid + """ + self.uri_string = uri_string + self.parameters = {} + + # Parse the URI + try: + parsed = urlparse(uri_string) + except Exception as e: + raise BIP21Error(f"Bad URI syntax: {e}") + + # Validate scheme + if parsed.scheme.lower() != self.BITCOIN_SCHEME: + raise BIP21Error(f"Unsupported URI scheme: {parsed.scheme}") + + # Extract address and parameters + path = parsed.path + + # Split address from query params + if "?" in uri_string: + address_part = path + query_part = parsed.query + else: + address_part = path + query_part = "" + + # Parse address if present + if address_part: + try: + # Basic address length validation. We rely on the coordinator to validate the address before. + if len(address_part) < self.MIN_BECH32_LENGTH or len(address_part) > self.MAX_BECH32_LENGTH: + raise ValueError("Invalid address length") + self._put_with_validation("uri_address", address_part) + except Exception as e: + raise BIP21Error(f"Invalid address: {e}") + + # Initialize payment instruction fields as lists + for field in self.PAYMENT_INSTRUCTION_FIELDS: + self.parameters[field] = [] + + # Parse query parameters + if query_part: + params = parse_qs(query_part, keep_blank_values=True) + for name, values in params.items(): + name_lower = name.lower() + + # Check for forbidden duplicates + if name_lower in self.NO_DUPLICATE_FIELDS and len(values) > 1: + raise BIP21Error(f"Multiple query parameters with key '{name}' are not allowed") + + # Handle payment instruction fields that can have multiple values + if name_lower in self.PAYMENT_INSTRUCTION_FIELDS: + for value in values: + if value: + self._parse_parameter(name_lower, value) + else: + # For other fields, take the first value only + value = values[0] if values else "" + self._parse_parameter(name_lower, value) + + def _parse_parameter(self, name: str, value: str): + """Parse a single parameter name/value pair""" + if name == self.FIELD_AMOUNT and value: + try: + # Parse amount as decimal with up to 8 decimal places + amount_decimal = Decimal(value.replace(',', '.')) + + if amount_decimal < 0: + raise ValueError("Negative amount") + + if amount_decimal.as_tuple().exponent < -self.SMALLEST_UNIT_EXPONENT: + raise ValueError("Amount has more than 8 decimal places") + + # Convert to satoshis + satoshis = int(amount_decimal * self.SATOSHIS_PER_BITCOIN) + if satoshis > self.MAX_BITCOIN * self.SATOSHIS_PER_BITCOIN: + raise ValueError("Amount exceeds maximum") + + self._put_with_validation(self.FIELD_AMOUNT, satoshis) + except (InvalidOperation, ValueError, OverflowError) as e: + raise BIP21Error(f"'{value}' is not a valid amount: {e}") + + elif name == self.FIELD_BC and value: + # Parse bc parameter (Bech32m address) and add to list + try: + decoded_bc = unquote(value) + # Basic validation for Bech32m address format (case-insensitive) + if not decoded_bc.lower().startswith('bc1') and not decoded_bc.lower().startswith('tb1'): + raise ValueError("Bech32m address must start with 'bc1'/'tb1'") + # Add to list of bc addresses (normalized to lowercase) + self.parameters[self.FIELD_BC].append(decoded_bc.lower()) + except Exception as e: + raise BIP21Error(f"'{value}' is not a valid Bech32m address: {e}") + + elif name == self.FIELD_SILENT_PAYMENT and value: + # Parse silent payment address and add to list + try: + decoded_sp = unquote(value) + # Basic validation for silent payment address format + if not decoded_sp.startswith('sp1'): + raise ValueError("Silent payment address must start with 'sp1'") + # Add to list of silent payment addresses + self.parameters[self.FIELD_SILENT_PAYMENT].append(decoded_sp.lower()) + except Exception as e: + raise BIP21Error(f"'{value}' is not a valid silent payment address: {e}") + + elif name.startswith("req-"): + # Required parameter we don't know about + raise BIP21Error(f"'{name}' is required but not known, this URI is not valid") + + else: + # Known or unknown optional parameter + if value: + decoded_value = unquote(value) + self._put_with_validation(name, decoded_value) + + def _put_with_validation(self, key: str, value: str): + """Add parameter with duplicate validation for non-payment instruction fields""" + if key in self.parameters and key not in self.PAYMENT_INSTRUCTION_FIELDS: + raise BIP21Error(f"'{key}' is duplicated, URI is invalid") + self.parameters[key] = value + + def get_address(self) -> str: + """Get the Bitcoin address from the URI path""" + return self.parameters.get("uri_address") + + def get_amount(self) -> int: + """Get the amount in satoshis, or None if not specified""" + return self.parameters.get(self.FIELD_AMOUNT) + + def get_label(self) -> str: + """Get the label parameter""" + return self.parameters.get(self.FIELD_LABEL) + + def get_message(self) -> str: + """Get the message parameter""" + return self.parameters.get(self.FIELD_MESSAGE) + + def get_bc_addresses(self) -> list[str]: + """Get all bech32(m) addresses from the bc parameters""" + return self.parameters.get(self.FIELD_BC, []) + + def get_silent_payment_addresses(self) -> list[str]: + """Get all silent payment addresses from the sp parameters""" + return self.parameters.get(self.FIELD_SILENT_PAYMENT, []) + + def get_parameter(self, name: str): + """Get a parameter by name""" + return self.parameters.get(name) + + def __str__(self) -> str: + """String representation showing all parameters""" + items = [] + for key, value in self.parameters.items(): + items.append(f"'{key}'='{value}'") + return f"BitcoinURI[{','.join(items)}]" + + def to_uri_string(self) -> str: + """Get the original URI string""" + return self.uri_string + + @classmethod + def from_address(cls, address: str): + """Create a BitcoinURI from just an address""" + return cls(f"{cls.BITCOIN_SCHEME}:{address}") + + @classmethod + def from_silent_payment(cls, sp_address: str): + """Create a BitcoinURI from a silent payment address""" + encoded_sp = quote(sp_address, safe='') + return cls(f"{cls.BITCOIN_SCHEME}:?{cls.FIELD_SILENT_PAYMENT}={encoded_sp}") + + @classmethod + def build_uri(cls, address: str = None, amount: int = None, label: str = None, message: str = None, silent_payment: str = None) -> str: + """ + Build a Bitcoin URI from components. + + Args: + address: Bitcoin address (optional) + amount: Amount in satoshis (optional) + label: Label text (optional) + message: Message text (optional) + silent_payment: Silent payment address (optional) + + Returns: + Complete Bitcoin URI string + """ + if amount is not None and amount < 0: + raise ValueError("Amount must be positive") + + # Handle silent payment URIs (no address in path) + if silent_payment and not address: + uri = f"{cls.BITCOIN_SCHEME}:" + elif address: + uri = f"{cls.BITCOIN_SCHEME}:{address}" + else: + uri = f"{cls.BITCOIN_SCHEME}:" + + params = [] + + if silent_payment: + encoded_sp = quote(silent_payment, safe='') + params.append(f"{cls.FIELD_SILENT_PAYMENT}={encoded_sp}") + + if amount is not None: + # Convert satoshis to BTC with proper decimal formatting + btc_amount = Decimal(amount) / cls.SATOSHIS_PER_BITCOIN + # Format to remove trailing zeros + amount_str = f"{btc_amount:f}".rstrip('0').rstrip('.') + params.append(f"{cls.FIELD_AMOUNT}={amount_str}") + + if label: + encoded_label = quote(label, safe='') + params.append(f"{cls.FIELD_LABEL}={encoded_label}") + + if message: + encoded_message = quote(message, safe='') + params.append(f"{cls.FIELD_MESSAGE}={encoded_message}") + + if params: + uri += "?" + "&".join(params) + + return uri \ No newline at end of file diff --git a/src/embit/bip353.py b/src/embit/bip353.py new file mode 100644 index 0000000..8ea9d9c --- /dev/null +++ b/src/embit/bip353.py @@ -0,0 +1,94 @@ +import json +from .base import EmbitError +from . import bip21 +from .util.dnssec_prover import verify_byte_stream, InternalError + + +class DNSSECProofValidationError(EmbitError): + pass + + +def verify_dns_proof(hrn: str, proof: bytes) -> dict: + """ + Verify a DNS proof for a given human-readable name. + + Args: + hrn: Human-readable name to resolve from the proof + proof: The DNS proof to verify + + Returns: + Dictionary containing verification result with only TXT records + + Raises: + DNSSECProofValidationError: When DNSSEC proof verification fails + """ + try: + result = verify_byte_stream(proof, hrn) + parsed_result = json.loads(result) + return parsed_result + + except InternalError as e: + raise DNSSECProofValidationError(f"DNSSEC proof verification failed for HRN '{hrn}': {e}") + except Exception as e: + raise DNSSECProofValidationError(f"Unexpected error during DNSSEC proof verification for HRN '{hrn}': {e}") + + +def get_payment_info_from_hrn(hrn: str, proof: bytes) -> dict: + """ + Extract complete payment information from a human-readable name using DNS proof verification. + + This function returns all available information from the Bitcoin URI, including + traditional addresses, silent payment addresses, amounts, labels, etc. + Only processes TXT records from DNS proof. + + Args: + hrn: Human-readable name to resolve + proof: DNS proof bytes + + Returns: + Dictionary containing all payment information from the URI. + """ + result = verify_dns_proof(hrn, proof) + + if "error" in result: + raise Exception(result["error"]) + + # Extract the Bitcoin URI from the verified TXT DNS records only + verified_rrs = result.get("verified_rrs", []) + txt_records = [rr for rr in verified_rrs if rr.get("type") == "txt"] + + if not txt_records: + raise Exception(f"No TXT records found in DNSSEC proof for HRN '{hrn}'. BIP353 requires Bitcoin payment information to be stored in TXT records.") + + # Filter TXT records that start with "bitcoin:" (case insensitive) + bitcoin_txt_records = [rr for rr in txt_records if rr.get("contents", "").lower().startswith("bitcoin:")] + + if not bitcoin_txt_records: + raise Exception(f"No TXT records containing Bitcoin URI found for HRN '{hrn}'. BIP353 requires exactly one TXT record starting with 'bitcoin:'.") + + if len(bitcoin_txt_records) > 1: + raise Exception(f"Multiple TXT records starting with 'bitcoin:' found for HRN '{hrn}' ({len(bitcoin_txt_records)} records). BIP353 requires exactly one TXT record containing Bitcoin payment information.") + + try: + bitcoin_uri_obj = bip21.BitcoinURI(bitcoin_txt_records[0]["contents"]) + except Exception as e: + raise Exception(f"Invalid Bitcoin URI in TXT record for HRN '{hrn}': {e}") + + payment_info = { + "uri": bitcoin_txt_records[0]["contents"], + "hrn": bitcoin_txt_records[0]["name"], + "uri_address": bitcoin_uri_obj.get_address(), + "bc_addresses": bitcoin_uri_obj.get_bc_addresses(), + "silent_payment_addresses": bitcoin_uri_obj.get_silent_payment_addresses(), + } + + # Validate that at least one payment method is available + has_regular_address = payment_info["uri_address"] is not None + has_bc_addresses = len(payment_info["bc_addresses"]) > 0 + has_silent_payment = len(payment_info["silent_payment_addresses"]) > 0 + + if not has_regular_address and not has_bc_addresses and not has_silent_payment: + raise Exception(f"Bitcoin URI in HRN '{hrn}' must contain either a regular Bitcoin address, bech32(m) address (bc parameter), or a silent payment address") + + # Remove None values to clean up the response + return {k: v for k, v in payment_info.items() if v is not None} \ No newline at end of file diff --git a/src/embit/psbt.py b/src/embit/psbt.py index 54497c2..ca80e49 100644 --- a/src/embit/psbt.py +++ b/src/embit/psbt.py @@ -489,6 +489,7 @@ def __init__(self, unknown: dict = {}, vout=None, compress=CompressMode.KEEP_ALL self.bip32_derivations = OrderedDict() self.taproot_bip32_derivations = OrderedDict() self.taproot_internal_key = None + self.dnssec_proof = None self.parse_unknowns() def clear_metadata(self, compress=CompressMode.CLEAR_ALL): @@ -501,6 +502,7 @@ def clear_metadata(self, compress=CompressMode.CLEAR_ALL): self.bip32_derivations = OrderedDict() self.taproot_bip32_derivations = OrderedDict() self.taproot_internal_key = None + self.dnssec_proof = None def update(self, other): self.value = other.value if other.value is not None else self.value @@ -511,6 +513,7 @@ def update(self, other): self.bip32_derivations.update(other.bip32_derivations) self.taproot_bip32_derivations.update(other.taproot_bip32_derivations) self.taproot_internal_key = other.taproot_internal_key + self.dnssec_proof = other.dnssec_proof or self.dnssec_proof @property def vout(self): @@ -568,6 +571,23 @@ def read_value(self, stream, k): der = DerivationPath.read_from(b) self.taproot_bip32_derivations[pub] = (leaf_hashes, der) + # PSBT_OUT_DNSSEC_PROOF (BIP-353) + elif k[0] == 0x35: # PSBT_OUT_DNSSEC_PROOF + if len(k) != 1: + raise PSBTError("Invalid DNSSEC proof key") + elif self.dnssec_proof is not None: + raise PSBTError("Duplicated DNSSEC proof") + else: + # First byte is length of human-readable name + name_len = v[0] + if name_len >= len(v): + raise PSBTError("Invalid DNSSEC proof format") + # Extract name and proof + name = v[1:1+name_len] + proof = v[1+name_len:] + self.dnssec_proof = (name, proof) + return + else: if k in self.unknown: raise PSBTError("Duplicated key") @@ -609,6 +629,12 @@ def write_to(self, stream, skip_separator=False, version=None, **kwargs) -> int: + derivation.serialize(), ) + # PSBT_OUT_DNSSEC_PROOF (BIP-353) + if self.dnssec_proof is not None: + name, proof = self.dnssec_proof + r += ser_string(stream, b"\x35") # PSBT_OUT_DNSSEC_PROOF + r += ser_string(stream, bytes([len(name)]) + name + proof) + # unknown for key in self.unknown: r += ser_string(stream, key) diff --git a/src/embit/util/dnssec_prover.py b/src/embit/util/dnssec_prover.py new file mode 100644 index 0000000..51a7968 --- /dev/null +++ b/src/embit/util/dnssec_prover.py @@ -0,0 +1,1283 @@ + + +# This file was autogenerated by some hot garbage in the `uniffi` crate. +# Trust me, you don't want to mess with it! + +# Common helper code. +# +# Ideally this would live in a separate .py file where it can be unittested etc +# in isolation, and perhaps even published as a re-useable package. +# +# However, it's important that the details of how this helper code works (e.g. the +# way that different builtin types are passed across the FFI) exactly match what's +# expected by the rust code on the other side of the interface. In practice right +# now that means coming from the exact some version of `uniffi` that was used to +# compile the rust component. The easiest way to ensure this is to bundle the Python +# helpers directly inline like we're doing here. + +from __future__ import annotations +import os +import sys +import ctypes +import enum +import struct +import contextlib +import datetime +import threading +import itertools +import traceback +import typing +import platform + +# Used for default argument values +_DEFAULT = object() + + +class _UniffiRustBuffer(ctypes.Structure): + _fields_ = [ + ("capacity", ctypes.c_uint64), + ("len", ctypes.c_uint64), + ("data", ctypes.POINTER(ctypes.c_char)), + ] + + @staticmethod + def default(): + return _UniffiRustBuffer(0, 0, None) + + @staticmethod + def alloc(size): + return _rust_call(_UniffiLib.ffi_dnssec_prover_uniffi_rustbuffer_alloc, size) + + @staticmethod + def reserve(rbuf, additional): + return _rust_call(_UniffiLib.ffi_dnssec_prover_uniffi_rustbuffer_reserve, rbuf, additional) + + def free(self): + return _rust_call(_UniffiLib.ffi_dnssec_prover_uniffi_rustbuffer_free, self) + + def __str__(self): + return "_UniffiRustBuffer(capacity={}, len={}, data={})".format( + self.capacity, + self.len, + self.data[0:self.len] + ) + + @contextlib.contextmanager + def alloc_with_builder(*args): + """Context-manger to allocate a buffer using a _UniffiRustBufferBuilder. + + The allocated buffer will be automatically freed if an error occurs, ensuring that + we don't accidentally leak it. + """ + builder = _UniffiRustBufferBuilder() + try: + yield builder + except: + builder.discard() + raise + + @contextlib.contextmanager + def consume_with_stream(self): + """Context-manager to consume a buffer using a _UniffiRustBufferStream. + + The _UniffiRustBuffer will be freed once the context-manager exits, ensuring that we don't + leak it even if an error occurs. + """ + try: + s = _UniffiRustBufferStream.from_rust_buffer(self) + yield s + if s.remaining() != 0: + raise RuntimeError("junk data left in buffer at end of consume_with_stream") + finally: + self.free() + + @contextlib.contextmanager + def read_with_stream(self): + """Context-manager to read a buffer using a _UniffiRustBufferStream. + + This is like consume_with_stream, but doesn't free the buffer afterwards. + It should only be used with borrowed `_UniffiRustBuffer` data. + """ + s = _UniffiRustBufferStream.from_rust_buffer(self) + yield s + if s.remaining() != 0: + raise RuntimeError("junk data left in buffer at end of read_with_stream") + +class _UniffiForeignBytes(ctypes.Structure): + _fields_ = [ + ("len", ctypes.c_int32), + ("data", ctypes.POINTER(ctypes.c_char)), + ] + + def __str__(self): + return "_UniffiForeignBytes(len={}, data={})".format(self.len, self.data[0:self.len]) + + +class _UniffiRustBufferStream: + """ + Helper for structured reading of bytes from a _UniffiRustBuffer + """ + + def __init__(self, data, len): + self.data = data + self.len = len + self.offset = 0 + + @classmethod + def from_rust_buffer(cls, buf): + return cls(buf.data, buf.len) + + def remaining(self): + return self.len - self.offset + + def _unpack_from(self, size, format): + if self.offset + size > self.len: + raise InternalError("read past end of rust buffer") + value = struct.unpack(format, self.data[self.offset:self.offset+size])[0] + self.offset += size + return value + + def read(self, size): + if self.offset + size > self.len: + raise InternalError("read past end of rust buffer") + data = self.data[self.offset:self.offset+size] + self.offset += size + return data + + def read_i8(self): + return self._unpack_from(1, ">b") + + def read_u8(self): + return self._unpack_from(1, ">B") + + def read_i16(self): + return self._unpack_from(2, ">h") + + def read_u16(self): + return self._unpack_from(2, ">H") + + def read_i32(self): + return self._unpack_from(4, ">i") + + def read_u32(self): + return self._unpack_from(4, ">I") + + def read_i64(self): + return self._unpack_from(8, ">q") + + def read_u64(self): + return self._unpack_from(8, ">Q") + + def read_float(self): + v = self._unpack_from(4, ">f") + return v + + def read_double(self): + return self._unpack_from(8, ">d") + +class _UniffiRustBufferBuilder: + """ + Helper for structured writing of bytes into a _UniffiRustBuffer. + """ + + def __init__(self): + self.rbuf = _UniffiRustBuffer.alloc(16) + self.rbuf.len = 0 + + def finalize(self): + rbuf = self.rbuf + self.rbuf = None + return rbuf + + def discard(self): + if self.rbuf is not None: + rbuf = self.finalize() + rbuf.free() + + @contextlib.contextmanager + def _reserve(self, num_bytes): + if self.rbuf.len + num_bytes > self.rbuf.capacity: + self.rbuf = _UniffiRustBuffer.reserve(self.rbuf, num_bytes) + yield None + self.rbuf.len += num_bytes + + def _pack_into(self, size, format, value): + with self._reserve(size): + # XXX TODO: I feel like I should be able to use `struct.pack_into` here but can't figure it out. + for i, byte in enumerate(struct.pack(format, value)): + self.rbuf.data[self.rbuf.len + i] = byte + + def write(self, value): + with self._reserve(len(value)): + for i, byte in enumerate(value): + self.rbuf.data[self.rbuf.len + i] = byte + + def write_i8(self, v): + self._pack_into(1, ">b", v) + + def write_u8(self, v): + self._pack_into(1, ">B", v) + + def write_i16(self, v): + self._pack_into(2, ">h", v) + + def write_u16(self, v): + self._pack_into(2, ">H", v) + + def write_i32(self, v): + self._pack_into(4, ">i", v) + + def write_u32(self, v): + self._pack_into(4, ">I", v) + + def write_i64(self, v): + self._pack_into(8, ">q", v) + + def write_u64(self, v): + self._pack_into(8, ">Q", v) + + def write_float(self, v): + self._pack_into(4, ">f", v) + + def write_double(self, v): + self._pack_into(8, ">d", v) + + def write_c_size_t(self, v): + self._pack_into(ctypes.sizeof(ctypes.c_size_t) , "@N", v) +# A handful of classes and functions to support the generated data structures. +# This would be a good candidate for isolating in its own ffi-support lib. + +class InternalError(Exception): + pass + +class _UniffiRustCallStatus(ctypes.Structure): + """ + Error runtime. + """ + _fields_ = [ + ("code", ctypes.c_int8), + ("error_buf", _UniffiRustBuffer), + ] + + # These match the values from the uniffi::rustcalls module + CALL_SUCCESS = 0 + CALL_ERROR = 1 + CALL_UNEXPECTED_ERROR = 2 + + @staticmethod + def default(): + return _UniffiRustCallStatus(code=_UniffiRustCallStatus.CALL_SUCCESS, error_buf=_UniffiRustBuffer.default()) + + def __str__(self): + if self.code == _UniffiRustCallStatus.CALL_SUCCESS: + return "_UniffiRustCallStatus(CALL_SUCCESS)" + elif self.code == _UniffiRustCallStatus.CALL_ERROR: + return "_UniffiRustCallStatus(CALL_ERROR)" + elif self.code == _UniffiRustCallStatus.CALL_UNEXPECTED_ERROR: + return "_UniffiRustCallStatus(CALL_UNEXPECTED_ERROR)" + else: + return "_UniffiRustCallStatus()" + +def _rust_call(fn, *args): + # Call a rust function + return _rust_call_with_error(None, fn, *args) + +def _rust_call_with_error(error_ffi_converter, fn, *args): + # Call a rust function and handle any errors + # + # This function is used for rust calls that return Result<> and therefore can set the CALL_ERROR status code. + # error_ffi_converter must be set to the _UniffiConverter for the error class that corresponds to the result. + call_status = _UniffiRustCallStatus.default() + + args_with_error = args + (ctypes.byref(call_status),) + result = fn(*args_with_error) + _uniffi_check_call_status(error_ffi_converter, call_status) + return result + +def _uniffi_check_call_status(error_ffi_converter, call_status): + if call_status.code == _UniffiRustCallStatus.CALL_SUCCESS: + pass + elif call_status.code == _UniffiRustCallStatus.CALL_ERROR: + if error_ffi_converter is None: + call_status.error_buf.free() + raise InternalError("_rust_call_with_error: CALL_ERROR, but error_ffi_converter is None") + else: + raise error_ffi_converter.lift(call_status.error_buf) + elif call_status.code == _UniffiRustCallStatus.CALL_UNEXPECTED_ERROR: + # When the rust code sees a panic, it tries to construct a _UniffiRustBuffer + # with the message. But if that code panics, then it just sends back + # an empty buffer. + if call_status.error_buf.len > 0: + msg = _UniffiConverterString.lift(call_status.error_buf) + else: + msg = "Unknown rust panic" + raise InternalError(msg) + else: + raise InternalError("Invalid _UniffiRustCallStatus code: {}".format( + call_status.code)) + +def _uniffi_trait_interface_call(call_status, make_call, write_return_value): + try: + return write_return_value(make_call()) + except Exception as e: + call_status.code = _UniffiRustCallStatus.CALL_UNEXPECTED_ERROR + call_status.error_buf = _UniffiConverterString.lower(repr(e)) + +def _uniffi_trait_interface_call_with_error(call_status, make_call, write_return_value, error_type, lower_error): + try: + try: + return write_return_value(make_call()) + except error_type as e: + call_status.code = _UniffiRustCallStatus.CALL_ERROR + call_status.error_buf = lower_error(e) + except Exception as e: + call_status.code = _UniffiRustCallStatus.CALL_UNEXPECTED_ERROR + call_status.error_buf = _UniffiConverterString.lower(repr(e)) +class _UniffiHandleMap: + """ + A map where inserting, getting and removing data is synchronized with a lock. + """ + + def __init__(self): + # type Handle = int + self._map = {} # type: Dict[Handle, Any] + self._lock = threading.Lock() + self._counter = itertools.count() + + def insert(self, obj): + with self._lock: + handle = next(self._counter) + self._map[handle] = obj + return handle + + def get(self, handle): + try: + with self._lock: + return self._map[handle] + except KeyError: + raise InternalError("UniffiHandleMap.get: Invalid handle") + + def remove(self, handle): + try: + with self._lock: + return self._map.pop(handle) + except KeyError: + raise InternalError("UniffiHandleMap.remove: Invalid handle") + + def __len__(self): + return len(self._map) +# Types conforming to `_UniffiConverterPrimitive` pass themselves directly over the FFI. +class _UniffiConverterPrimitive: + @classmethod + def lift(cls, value): + return value + + @classmethod + def lower(cls, value): + return value + +class _UniffiConverterPrimitiveInt(_UniffiConverterPrimitive): + @classmethod + def check_lower(cls, value): + try: + value = value.__index__() + except Exception: + raise TypeError("'{}' object cannot be interpreted as an integer".format(type(value).__name__)) + if not isinstance(value, int): + raise TypeError("__index__ returned non-int (type {})".format(type(value).__name__)) + if not cls.VALUE_MIN <= value < cls.VALUE_MAX: + raise ValueError("{} requires {} <= value < {}".format(cls.CLASS_NAME, cls.VALUE_MIN, cls.VALUE_MAX)) + +class _UniffiConverterPrimitiveFloat(_UniffiConverterPrimitive): + @classmethod + def check_lower(cls, value): + try: + value = value.__float__() + except Exception: + raise TypeError("must be real number, not {}".format(type(value).__name__)) + if not isinstance(value, float): + raise TypeError("__float__ returned non-float (type {})".format(type(value).__name__)) + +# Helper class for wrapper types that will always go through a _UniffiRustBuffer. +# Classes should inherit from this and implement the `read` and `write` static methods. +class _UniffiConverterRustBuffer: + @classmethod + def lift(cls, rbuf): + with rbuf.consume_with_stream() as stream: + return cls.read(stream) + + @classmethod + def lower(cls, value): + with _UniffiRustBuffer.alloc_with_builder() as builder: + cls.write(value, builder) + return builder.finalize() + +# Contains loading, initialization code, and the FFI Function declarations. +# Define some ctypes FFI types that we use in the library + +""" +Function pointer for a Rust task, which a callback function that takes a opaque pointer +""" +_UNIFFI_RUST_TASK = ctypes.CFUNCTYPE(None, ctypes.c_void_p, ctypes.c_int8) + +def _uniffi_future_callback_t(return_type): + """ + Factory function to create callback function types for async functions + """ + return ctypes.CFUNCTYPE(None, ctypes.c_uint64, return_type, _UniffiRustCallStatus) + +def _uniffi_load_indirect(): + """ + This is how we find and load the dynamic library provided by the component. + We use the same pattern as ctypes_secp256k1.py for consistency. + """ + library_path = None + extension = "" + if platform.system() == "Darwin": + extension = ".dylib" + elif platform.system() == "Linux": + extension = ".so" + elif platform.system() == "Windows": + extension = ".dll" + + # Try prebuilt platform-specific library first + path = os.path.join( + os.path.dirname(__file__), + "prebuilt/libuniffi_dnssec_prover_%s_%s%s" + % (platform.system().lower(), platform.machine().lower(), extension), + ) + if os.path.isfile(path): + return ctypes.cdll.LoadLibrary(path) + + # Try searching system paths + if not library_path: + library_path = ctypes.util.find_library("libuniffi_dnssec_prover") + if not library_path: + library_path = ctypes.util.find_library("uniffi_dnssec_prover") + + # Platform-specific fallback locations + if not library_path: + if platform.system() == "Linux" and os.path.isfile( + "/usr/local/lib/libuniffi_dnssec_prover.so" + ): + library_path = "/usr/local/lib/libuniffi_dnssec_prover.so" + elif platform.system() == "Darwin" and os.path.isfile( + "/usr/local/lib/libuniffi_dnssec_prover.dylib" + ): + library_path = "/usr/local/lib/libuniffi_dnssec_prover.dylib" + + if not library_path: + raise RuntimeError( + f"Can't find libuniffi_dnssec_prover library for {platform.system().lower()}_{platform.machine().lower()}. " + "Make sure to compile and install it, or place it in the prebuilt/ directory." + ) + + return ctypes.cdll.LoadLibrary(library_path) + +def _uniffi_check_contract_api_version(lib): + # Get the bindings contract version from our ComponentInterface + bindings_contract_version = 26 + # Get the scaffolding contract version by calling the into the dylib + scaffolding_contract_version = lib.ffi_dnssec_prover_uniffi_uniffi_contract_version() + if bindings_contract_version != scaffolding_contract_version: + raise InternalError("UniFFI contract version mismatch: try cleaning and rebuilding your project") + +def _uniffi_check_api_checksums(lib): + if lib.uniffi_dnssec_prover_uniffi_checksum_func_init_proof_builder() != 15940: + raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + if lib.uniffi_dnssec_prover_uniffi_checksum_func_verify_byte_stream() != 930: + raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + if lib.uniffi_dnssec_prover_uniffi_checksum_method_proofbuilder_get_next_query() != 19765: + raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + if lib.uniffi_dnssec_prover_uniffi_checksum_method_proofbuilder_get_unverified_proof() != 4620: + raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + if lib.uniffi_dnssec_prover_uniffi_checksum_method_proofbuilder_process_query_response() != 48750: + raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + +# A ctypes library to expose the extern-C FFI definitions. +# This is an implementation detail which will be called internally by the public API. + +_UniffiLib = _uniffi_load_indirect() +UNIFFI_RUST_FUTURE_CONTINUATION_CALLBACK = ctypes.CFUNCTYPE(None,ctypes.c_uint64,ctypes.c_int8, +) +UNIFFI_FOREIGN_FUTURE_FREE = ctypes.CFUNCTYPE(None,ctypes.c_uint64, +) +UNIFFI_CALLBACK_INTERFACE_FREE = ctypes.CFUNCTYPE(None,ctypes.c_uint64, +) +class UniffiForeignFuture(ctypes.Structure): + _fields_ = [ + ("handle", ctypes.c_uint64), + ("free", UNIFFI_FOREIGN_FUTURE_FREE), + ] +class UniffiForeignFutureStructU8(ctypes.Structure): + _fields_ = [ + ("return_value", ctypes.c_uint8), + ("call_status", _UniffiRustCallStatus), + ] +UNIFFI_FOREIGN_FUTURE_COMPLETE_U8 = ctypes.CFUNCTYPE(None,ctypes.c_uint64,UniffiForeignFutureStructU8, +) +class UniffiForeignFutureStructI8(ctypes.Structure): + _fields_ = [ + ("return_value", ctypes.c_int8), + ("call_status", _UniffiRustCallStatus), + ] +UNIFFI_FOREIGN_FUTURE_COMPLETE_I8 = ctypes.CFUNCTYPE(None,ctypes.c_uint64,UniffiForeignFutureStructI8, +) +class UniffiForeignFutureStructU16(ctypes.Structure): + _fields_ = [ + ("return_value", ctypes.c_uint16), + ("call_status", _UniffiRustCallStatus), + ] +UNIFFI_FOREIGN_FUTURE_COMPLETE_U16 = ctypes.CFUNCTYPE(None,ctypes.c_uint64,UniffiForeignFutureStructU16, +) +class UniffiForeignFutureStructI16(ctypes.Structure): + _fields_ = [ + ("return_value", ctypes.c_int16), + ("call_status", _UniffiRustCallStatus), + ] +UNIFFI_FOREIGN_FUTURE_COMPLETE_I16 = ctypes.CFUNCTYPE(None,ctypes.c_uint64,UniffiForeignFutureStructI16, +) +class UniffiForeignFutureStructU32(ctypes.Structure): + _fields_ = [ + ("return_value", ctypes.c_uint32), + ("call_status", _UniffiRustCallStatus), + ] +UNIFFI_FOREIGN_FUTURE_COMPLETE_U32 = ctypes.CFUNCTYPE(None,ctypes.c_uint64,UniffiForeignFutureStructU32, +) +class UniffiForeignFutureStructI32(ctypes.Structure): + _fields_ = [ + ("return_value", ctypes.c_int32), + ("call_status", _UniffiRustCallStatus), + ] +UNIFFI_FOREIGN_FUTURE_COMPLETE_I32 = ctypes.CFUNCTYPE(None,ctypes.c_uint64,UniffiForeignFutureStructI32, +) +class UniffiForeignFutureStructU64(ctypes.Structure): + _fields_ = [ + ("return_value", ctypes.c_uint64), + ("call_status", _UniffiRustCallStatus), + ] +UNIFFI_FOREIGN_FUTURE_COMPLETE_U64 = ctypes.CFUNCTYPE(None,ctypes.c_uint64,UniffiForeignFutureStructU64, +) +class UniffiForeignFutureStructI64(ctypes.Structure): + _fields_ = [ + ("return_value", ctypes.c_int64), + ("call_status", _UniffiRustCallStatus), + ] +UNIFFI_FOREIGN_FUTURE_COMPLETE_I64 = ctypes.CFUNCTYPE(None,ctypes.c_uint64,UniffiForeignFutureStructI64, +) +class UniffiForeignFutureStructF32(ctypes.Structure): + _fields_ = [ + ("return_value", ctypes.c_float), + ("call_status", _UniffiRustCallStatus), + ] +UNIFFI_FOREIGN_FUTURE_COMPLETE_F32 = ctypes.CFUNCTYPE(None,ctypes.c_uint64,UniffiForeignFutureStructF32, +) +class UniffiForeignFutureStructF64(ctypes.Structure): + _fields_ = [ + ("return_value", ctypes.c_double), + ("call_status", _UniffiRustCallStatus), + ] +UNIFFI_FOREIGN_FUTURE_COMPLETE_F64 = ctypes.CFUNCTYPE(None,ctypes.c_uint64,UniffiForeignFutureStructF64, +) +class UniffiForeignFutureStructPointer(ctypes.Structure): + _fields_ = [ + ("return_value", ctypes.c_void_p), + ("call_status", _UniffiRustCallStatus), + ] +UNIFFI_FOREIGN_FUTURE_COMPLETE_POINTER = ctypes.CFUNCTYPE(None,ctypes.c_uint64,UniffiForeignFutureStructPointer, +) +class UniffiForeignFutureStructRustBuffer(ctypes.Structure): + _fields_ = [ + ("return_value", _UniffiRustBuffer), + ("call_status", _UniffiRustCallStatus), + ] +UNIFFI_FOREIGN_FUTURE_COMPLETE_RUST_BUFFER = ctypes.CFUNCTYPE(None,ctypes.c_uint64,UniffiForeignFutureStructRustBuffer, +) +class UniffiForeignFutureStructVoid(ctypes.Structure): + _fields_ = [ + ("call_status", _UniffiRustCallStatus), + ] +UNIFFI_FOREIGN_FUTURE_COMPLETE_VOID = ctypes.CFUNCTYPE(None,ctypes.c_uint64,UniffiForeignFutureStructVoid, +) +_UniffiLib.uniffi_dnssec_prover_uniffi_fn_clone_proofbuilder.argtypes = ( + ctypes.c_void_p, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.uniffi_dnssec_prover_uniffi_fn_clone_proofbuilder.restype = ctypes.c_void_p +_UniffiLib.uniffi_dnssec_prover_uniffi_fn_free_proofbuilder.argtypes = ( + ctypes.c_void_p, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.uniffi_dnssec_prover_uniffi_fn_free_proofbuilder.restype = None +_UniffiLib.uniffi_dnssec_prover_uniffi_fn_method_proofbuilder_get_next_query.argtypes = ( + ctypes.c_void_p, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.uniffi_dnssec_prover_uniffi_fn_method_proofbuilder_get_next_query.restype = _UniffiRustBuffer +_UniffiLib.uniffi_dnssec_prover_uniffi_fn_method_proofbuilder_get_unverified_proof.argtypes = ( + ctypes.c_void_p, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.uniffi_dnssec_prover_uniffi_fn_method_proofbuilder_get_unverified_proof.restype = _UniffiRustBuffer +_UniffiLib.uniffi_dnssec_prover_uniffi_fn_method_proofbuilder_process_query_response.argtypes = ( + ctypes.c_void_p, + _UniffiRustBuffer, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.uniffi_dnssec_prover_uniffi_fn_method_proofbuilder_process_query_response.restype = None +_UniffiLib.uniffi_dnssec_prover_uniffi_fn_func_init_proof_builder.argtypes = ( + _UniffiRustBuffer, + ctypes.c_uint16, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.uniffi_dnssec_prover_uniffi_fn_func_init_proof_builder.restype = _UniffiRustBuffer +_UniffiLib.uniffi_dnssec_prover_uniffi_fn_func_verify_byte_stream.argtypes = ( + _UniffiRustBuffer, + _UniffiRustBuffer, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.uniffi_dnssec_prover_uniffi_fn_func_verify_byte_stream.restype = _UniffiRustBuffer +_UniffiLib.ffi_dnssec_prover_uniffi_rustbuffer_alloc.argtypes = ( + ctypes.c_uint64, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.ffi_dnssec_prover_uniffi_rustbuffer_alloc.restype = _UniffiRustBuffer +_UniffiLib.ffi_dnssec_prover_uniffi_rustbuffer_from_bytes.argtypes = ( + _UniffiForeignBytes, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.ffi_dnssec_prover_uniffi_rustbuffer_from_bytes.restype = _UniffiRustBuffer +_UniffiLib.ffi_dnssec_prover_uniffi_rustbuffer_free.argtypes = ( + _UniffiRustBuffer, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.ffi_dnssec_prover_uniffi_rustbuffer_free.restype = None +_UniffiLib.ffi_dnssec_prover_uniffi_rustbuffer_reserve.argtypes = ( + _UniffiRustBuffer, + ctypes.c_uint64, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.ffi_dnssec_prover_uniffi_rustbuffer_reserve.restype = _UniffiRustBuffer +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_poll_u8.argtypes = ( + ctypes.c_uint64, + UNIFFI_RUST_FUTURE_CONTINUATION_CALLBACK, + ctypes.c_uint64, +) +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_poll_u8.restype = None +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_cancel_u8.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_cancel_u8.restype = None +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_free_u8.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_free_u8.restype = None +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_complete_u8.argtypes = ( + ctypes.c_uint64, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_complete_u8.restype = ctypes.c_uint8 +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_poll_i8.argtypes = ( + ctypes.c_uint64, + UNIFFI_RUST_FUTURE_CONTINUATION_CALLBACK, + ctypes.c_uint64, +) +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_poll_i8.restype = None +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_cancel_i8.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_cancel_i8.restype = None +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_free_i8.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_free_i8.restype = None +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_complete_i8.argtypes = ( + ctypes.c_uint64, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_complete_i8.restype = ctypes.c_int8 +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_poll_u16.argtypes = ( + ctypes.c_uint64, + UNIFFI_RUST_FUTURE_CONTINUATION_CALLBACK, + ctypes.c_uint64, +) +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_poll_u16.restype = None +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_cancel_u16.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_cancel_u16.restype = None +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_free_u16.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_free_u16.restype = None +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_complete_u16.argtypes = ( + ctypes.c_uint64, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_complete_u16.restype = ctypes.c_uint16 +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_poll_i16.argtypes = ( + ctypes.c_uint64, + UNIFFI_RUST_FUTURE_CONTINUATION_CALLBACK, + ctypes.c_uint64, +) +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_poll_i16.restype = None +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_cancel_i16.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_cancel_i16.restype = None +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_free_i16.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_free_i16.restype = None +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_complete_i16.argtypes = ( + ctypes.c_uint64, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_complete_i16.restype = ctypes.c_int16 +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_poll_u32.argtypes = ( + ctypes.c_uint64, + UNIFFI_RUST_FUTURE_CONTINUATION_CALLBACK, + ctypes.c_uint64, +) +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_poll_u32.restype = None +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_cancel_u32.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_cancel_u32.restype = None +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_free_u32.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_free_u32.restype = None +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_complete_u32.argtypes = ( + ctypes.c_uint64, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_complete_u32.restype = ctypes.c_uint32 +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_poll_i32.argtypes = ( + ctypes.c_uint64, + UNIFFI_RUST_FUTURE_CONTINUATION_CALLBACK, + ctypes.c_uint64, +) +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_poll_i32.restype = None +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_cancel_i32.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_cancel_i32.restype = None +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_free_i32.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_free_i32.restype = None +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_complete_i32.argtypes = ( + ctypes.c_uint64, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_complete_i32.restype = ctypes.c_int32 +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_poll_u64.argtypes = ( + ctypes.c_uint64, + UNIFFI_RUST_FUTURE_CONTINUATION_CALLBACK, + ctypes.c_uint64, +) +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_poll_u64.restype = None +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_cancel_u64.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_cancel_u64.restype = None +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_free_u64.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_free_u64.restype = None +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_complete_u64.argtypes = ( + ctypes.c_uint64, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_complete_u64.restype = ctypes.c_uint64 +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_poll_i64.argtypes = ( + ctypes.c_uint64, + UNIFFI_RUST_FUTURE_CONTINUATION_CALLBACK, + ctypes.c_uint64, +) +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_poll_i64.restype = None +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_cancel_i64.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_cancel_i64.restype = None +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_free_i64.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_free_i64.restype = None +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_complete_i64.argtypes = ( + ctypes.c_uint64, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_complete_i64.restype = ctypes.c_int64 +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_poll_f32.argtypes = ( + ctypes.c_uint64, + UNIFFI_RUST_FUTURE_CONTINUATION_CALLBACK, + ctypes.c_uint64, +) +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_poll_f32.restype = None +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_cancel_f32.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_cancel_f32.restype = None +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_free_f32.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_free_f32.restype = None +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_complete_f32.argtypes = ( + ctypes.c_uint64, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_complete_f32.restype = ctypes.c_float +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_poll_f64.argtypes = ( + ctypes.c_uint64, + UNIFFI_RUST_FUTURE_CONTINUATION_CALLBACK, + ctypes.c_uint64, +) +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_poll_f64.restype = None +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_cancel_f64.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_cancel_f64.restype = None +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_free_f64.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_free_f64.restype = None +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_complete_f64.argtypes = ( + ctypes.c_uint64, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_complete_f64.restype = ctypes.c_double +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_poll_pointer.argtypes = ( + ctypes.c_uint64, + UNIFFI_RUST_FUTURE_CONTINUATION_CALLBACK, + ctypes.c_uint64, +) +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_poll_pointer.restype = None +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_cancel_pointer.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_cancel_pointer.restype = None +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_free_pointer.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_free_pointer.restype = None +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_complete_pointer.argtypes = ( + ctypes.c_uint64, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_complete_pointer.restype = ctypes.c_void_p +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_poll_rust_buffer.argtypes = ( + ctypes.c_uint64, + UNIFFI_RUST_FUTURE_CONTINUATION_CALLBACK, + ctypes.c_uint64, +) +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_poll_rust_buffer.restype = None +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_cancel_rust_buffer.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_cancel_rust_buffer.restype = None +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_free_rust_buffer.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_free_rust_buffer.restype = None +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_complete_rust_buffer.argtypes = ( + ctypes.c_uint64, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_complete_rust_buffer.restype = _UniffiRustBuffer +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_poll_void.argtypes = ( + ctypes.c_uint64, + UNIFFI_RUST_FUTURE_CONTINUATION_CALLBACK, + ctypes.c_uint64, +) +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_poll_void.restype = None +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_cancel_void.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_cancel_void.restype = None +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_free_void.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_free_void.restype = None +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_complete_void.argtypes = ( + ctypes.c_uint64, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.ffi_dnssec_prover_uniffi_rust_future_complete_void.restype = None +_UniffiLib.uniffi_dnssec_prover_uniffi_checksum_func_init_proof_builder.argtypes = ( +) +_UniffiLib.uniffi_dnssec_prover_uniffi_checksum_func_init_proof_builder.restype = ctypes.c_uint16 +_UniffiLib.uniffi_dnssec_prover_uniffi_checksum_func_verify_byte_stream.argtypes = ( +) +_UniffiLib.uniffi_dnssec_prover_uniffi_checksum_func_verify_byte_stream.restype = ctypes.c_uint16 +_UniffiLib.uniffi_dnssec_prover_uniffi_checksum_method_proofbuilder_get_next_query.argtypes = ( +) +_UniffiLib.uniffi_dnssec_prover_uniffi_checksum_method_proofbuilder_get_next_query.restype = ctypes.c_uint16 +_UniffiLib.uniffi_dnssec_prover_uniffi_checksum_method_proofbuilder_get_unverified_proof.argtypes = ( +) +_UniffiLib.uniffi_dnssec_prover_uniffi_checksum_method_proofbuilder_get_unverified_proof.restype = ctypes.c_uint16 +_UniffiLib.uniffi_dnssec_prover_uniffi_checksum_method_proofbuilder_process_query_response.argtypes = ( +) +_UniffiLib.uniffi_dnssec_prover_uniffi_checksum_method_proofbuilder_process_query_response.restype = ctypes.c_uint16 +_UniffiLib.ffi_dnssec_prover_uniffi_uniffi_contract_version.argtypes = ( +) +_UniffiLib.ffi_dnssec_prover_uniffi_uniffi_contract_version.restype = ctypes.c_uint32 + +_uniffi_check_contract_api_version(_UniffiLib) +_uniffi_check_api_checksums(_UniffiLib) + +# Public interface members begin here. + + +class _UniffiConverterUInt16(_UniffiConverterPrimitiveInt): + CLASS_NAME = "u16" + VALUE_MIN = 0 + VALUE_MAX = 2**16 + + @staticmethod + def read(buf): + return buf.read_u16() + + @staticmethod + def write(value, buf): + buf.write_u16(value) + +class _UniffiConverterString: + @staticmethod + def check_lower(value): + if not isinstance(value, str): + raise TypeError("argument must be str, not {}".format(type(value).__name__)) + return value + + @staticmethod + def read(buf): + size = buf.read_i32() + if size < 0: + raise InternalError("Unexpected negative string length") + utf8_bytes = buf.read(size) + return utf8_bytes.decode("utf-8") + + @staticmethod + def write(value, buf): + utf8_bytes = value.encode("utf-8") + buf.write_i32(len(utf8_bytes)) + buf.write(utf8_bytes) + + @staticmethod + def lift(buf): + with buf.consume_with_stream() as stream: + return stream.read(stream.remaining()).decode("utf-8") + + @staticmethod + def lower(value): + with _UniffiRustBuffer.alloc_with_builder() as builder: + builder.write(value.encode("utf-8")) + return builder.finalize() + +class _UniffiConverterBytes(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + size = buf.read_i32() + if size < 0: + raise InternalError("Unexpected negative byte string length") + return buf.read(size) + + @staticmethod + def check_lower(value): + try: + memoryview(value) + except TypeError: + raise TypeError("a bytes-like object is required, not {!r}".format(type(value).__name__)) + + @staticmethod + def write(value, buf): + buf.write_i32(len(value)) + buf.write(value) + + + +class ProofBuilderProtocol(typing.Protocol): + def get_next_query(self, ): + raise NotImplementedError + def get_unverified_proof(self, ): + raise NotImplementedError + def process_query_response(self, response: "bytes"): + raise NotImplementedError + + +class ProofBuilder: + _pointer: ctypes.c_void_p + + def __init__(self, *args, **kwargs): + raise ValueError("This class has no default constructor") + + def __del__(self): + # In case of partial initialization of instances. + pointer = getattr(self, "_pointer", None) + if pointer is not None: + _rust_call(_UniffiLib.uniffi_dnssec_prover_uniffi_fn_free_proofbuilder, pointer) + + def _uniffi_clone_pointer(self): + return _rust_call(_UniffiLib.uniffi_dnssec_prover_uniffi_fn_clone_proofbuilder, self._pointer) + + # Used by alternative constructors or any methods which return this type. + @classmethod + def _make_instance_(cls, pointer): + # Lightly yucky way to bypass the usual __init__ logic + # and just create a new instance with the required pointer. + inst = cls.__new__(cls) + inst._pointer = pointer + return inst + + + def get_next_query(self, ) -> "typing.Optional[bytes]": + return _UniffiConverterOptionalBytes.lift( + _rust_call(_UniffiLib.uniffi_dnssec_prover_uniffi_fn_method_proofbuilder_get_next_query,self._uniffi_clone_pointer(),) + ) + + + + + + def get_unverified_proof(self, ) -> "typing.Optional[bytes]": + return _UniffiConverterOptionalBytes.lift( + _rust_call(_UniffiLib.uniffi_dnssec_prover_uniffi_fn_method_proofbuilder_get_unverified_proof,self._uniffi_clone_pointer(),) + ) + + + + + + def process_query_response(self, response: "bytes") -> None: + _UniffiConverterBytes.check_lower(response) + + _rust_call_with_error(_UniffiConverterTypeProofBuildingError,_UniffiLib.uniffi_dnssec_prover_uniffi_fn_method_proofbuilder_process_query_response,self._uniffi_clone_pointer(), + _UniffiConverterBytes.lower(response)) + + + + + + + +class _UniffiConverterTypeProofBuilder: + + @staticmethod + def lift(value: int): + return ProofBuilder._make_instance_(value) + + @staticmethod + def check_lower(value: ProofBuilder): + if not isinstance(value, ProofBuilder): + raise TypeError("Expected ProofBuilder instance, {} found".format(type(value).__name__)) + + @staticmethod + def lower(value: ProofBuilderProtocol): + if not isinstance(value, ProofBuilder): + raise TypeError("Expected ProofBuilder instance, {} found".format(type(value).__name__)) + return value._uniffi_clone_pointer() + + @classmethod + def read(cls, buf: _UniffiRustBuffer): + ptr = buf.read_u64() + if ptr == 0: + raise InternalError("Raw pointer value was null") + return cls.lift(ptr) + + @classmethod + def write(cls, value: ProofBuilderProtocol, buf: _UniffiRustBuffer): + buf.write_u64(cls.lower(value)) + + +# ProofBuildingError +# We want to define each variant as a nested class that's also a subclass, +# which is tricky in Python. To accomplish this we're going to create each +# class separately, then manually add the child classes to the base class's +# __dict__. All of this happens in dummy class to avoid polluting the module +# namespace. +class ProofBuildingError(Exception): + pass + +_UniffiTempProofBuildingError = ProofBuildingError + +class ProofBuildingError: # type: ignore + class InvalidResponse(_UniffiTempProofBuildingError): + + def __repr__(self): + return "ProofBuildingError.InvalidResponse({})".format(repr(str(self))) + _UniffiTempProofBuildingError.InvalidResponse = InvalidResponse # type: ignore + class ServerFailure(_UniffiTempProofBuildingError): + + def __repr__(self): + return "ProofBuildingError.ServerFailure({})".format(repr(str(self))) + _UniffiTempProofBuildingError.ServerFailure = ServerFailure # type: ignore + class NoSuchName(_UniffiTempProofBuildingError): + + def __repr__(self): + return "ProofBuildingError.NoSuchName({})".format(repr(str(self))) + _UniffiTempProofBuildingError.NoSuchName = NoSuchName # type: ignore + class MissingRecord(_UniffiTempProofBuildingError): + + def __repr__(self): + return "ProofBuildingError.MissingRecord({})".format(repr(str(self))) + _UniffiTempProofBuildingError.MissingRecord = MissingRecord # type: ignore + class Unauthenticated(_UniffiTempProofBuildingError): + + def __repr__(self): + return "ProofBuildingError.Unauthenticated({})".format(repr(str(self))) + _UniffiTempProofBuildingError.Unauthenticated = Unauthenticated # type: ignore + class NoResponseExpected(_UniffiTempProofBuildingError): + + def __repr__(self): + return "ProofBuildingError.NoResponseExpected({})".format(repr(str(self))) + _UniffiTempProofBuildingError.NoResponseExpected = NoResponseExpected # type: ignore + +ProofBuildingError = _UniffiTempProofBuildingError # type: ignore +del _UniffiTempProofBuildingError + + +class _UniffiConverterTypeProofBuildingError(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + variant = buf.read_i32() + if variant == 1: + return ProofBuildingError.InvalidResponse( + _UniffiConverterString.read(buf), + ) + if variant == 2: + return ProofBuildingError.ServerFailure( + _UniffiConverterString.read(buf), + ) + if variant == 3: + return ProofBuildingError.NoSuchName( + _UniffiConverterString.read(buf), + ) + if variant == 4: + return ProofBuildingError.MissingRecord( + _UniffiConverterString.read(buf), + ) + if variant == 5: + return ProofBuildingError.Unauthenticated( + _UniffiConverterString.read(buf), + ) + if variant == 6: + return ProofBuildingError.NoResponseExpected( + _UniffiConverterString.read(buf), + ) + raise InternalError("Raw enum value doesn't match any cases") + + @staticmethod + def check_lower(value): + if isinstance(value, ProofBuildingError.InvalidResponse): + return + if isinstance(value, ProofBuildingError.ServerFailure): + return + if isinstance(value, ProofBuildingError.NoSuchName): + return + if isinstance(value, ProofBuildingError.MissingRecord): + return + if isinstance(value, ProofBuildingError.Unauthenticated): + return + if isinstance(value, ProofBuildingError.NoResponseExpected): + return + + @staticmethod + def write(value, buf): + if isinstance(value, ProofBuildingError.InvalidResponse): + buf.write_i32(1) + if isinstance(value, ProofBuildingError.ServerFailure): + buf.write_i32(2) + if isinstance(value, ProofBuildingError.NoSuchName): + buf.write_i32(3) + if isinstance(value, ProofBuildingError.MissingRecord): + buf.write_i32(4) + if isinstance(value, ProofBuildingError.Unauthenticated): + buf.write_i32(5) + if isinstance(value, ProofBuildingError.NoResponseExpected): + buf.write_i32(6) + + + +class _UniffiConverterOptionalBytes(_UniffiConverterRustBuffer): + @classmethod + def check_lower(cls, value): + if value is not None: + _UniffiConverterBytes.check_lower(value) + + @classmethod + def write(cls, value, buf): + if value is None: + buf.write_u8(0) + return + + buf.write_u8(1) + _UniffiConverterBytes.write(value, buf) + + @classmethod + def read(cls, buf): + flag = buf.read_u8() + if flag == 0: + return None + elif flag == 1: + return _UniffiConverterBytes.read(buf) + else: + raise InternalError("Unexpected flag byte for optional type") + + + +class _UniffiConverterOptionalTypeProofBuilder(_UniffiConverterRustBuffer): + @classmethod + def check_lower(cls, value): + if value is not None: + _UniffiConverterTypeProofBuilder.check_lower(value) + + @classmethod + def write(cls, value, buf): + if value is None: + buf.write_u8(0) + return + + buf.write_u8(1) + _UniffiConverterTypeProofBuilder.write(value, buf) + + @classmethod + def read(cls, buf): + flag = buf.read_u8() + if flag == 0: + return None + elif flag == 1: + return _UniffiConverterTypeProofBuilder.read(buf) + else: + raise InternalError("Unexpected flag byte for optional type") + +# Async support + +def init_proof_builder(name: "str",ty: "int") -> "typing.Optional[ProofBuilder]": + _UniffiConverterString.check_lower(name) + + _UniffiConverterUInt16.check_lower(ty) + + return _UniffiConverterOptionalTypeProofBuilder.lift(_rust_call(_UniffiLib.uniffi_dnssec_prover_uniffi_fn_func_init_proof_builder, + _UniffiConverterString.lower(name), + _UniffiConverterUInt16.lower(ty))) + + +def verify_byte_stream(stream: "bytes",name_to_resolve: "str") -> "str": + _UniffiConverterBytes.check_lower(stream) + + _UniffiConverterString.check_lower(name_to_resolve) + + return _UniffiConverterString.lift(_rust_call(_UniffiLib.uniffi_dnssec_prover_uniffi_fn_func_verify_byte_stream, + _UniffiConverterBytes.lower(stream), + _UniffiConverterString.lower(name_to_resolve))) + + +__all__ = [ + "InternalError", + "ProofBuildingError", + "init_proof_builder", + "verify_byte_stream", + "ProofBuilder", +] + diff --git a/src/embit/util/prebuilt/libuniffi_dnssec_prover_darwin_arm64.dylib b/src/embit/util/prebuilt/libuniffi_dnssec_prover_darwin_arm64.dylib new file mode 100755 index 0000000..092af47 Binary files /dev/null and b/src/embit/util/prebuilt/libuniffi_dnssec_prover_darwin_arm64.dylib differ diff --git a/src/embit/util/prebuilt/libuniffi_dnssec_prover_darwin_x86_64.dylib b/src/embit/util/prebuilt/libuniffi_dnssec_prover_darwin_x86_64.dylib new file mode 100755 index 0000000..720b5a3 Binary files /dev/null and b/src/embit/util/prebuilt/libuniffi_dnssec_prover_darwin_x86_64.dylib differ diff --git a/src/embit/util/prebuilt/libuniffi_dnssec_prover_linux_aarch64.so b/src/embit/util/prebuilt/libuniffi_dnssec_prover_linux_aarch64.so new file mode 100755 index 0000000..7909b69 Binary files /dev/null and b/src/embit/util/prebuilt/libuniffi_dnssec_prover_linux_aarch64.so differ diff --git a/src/embit/util/prebuilt/libuniffi_dnssec_prover_linux_armv6l.so b/src/embit/util/prebuilt/libuniffi_dnssec_prover_linux_armv6l.so new file mode 100755 index 0000000..9c9b365 Binary files /dev/null and b/src/embit/util/prebuilt/libuniffi_dnssec_prover_linux_armv6l.so differ diff --git a/src/embit/util/prebuilt/libuniffi_dnssec_prover_linux_armv7l.so b/src/embit/util/prebuilt/libuniffi_dnssec_prover_linux_armv7l.so new file mode 100755 index 0000000..73aad65 Binary files /dev/null and b/src/embit/util/prebuilt/libuniffi_dnssec_prover_linux_armv7l.so differ diff --git a/src/embit/util/prebuilt/libuniffi_dnssec_prover_linux_x86_64.so b/src/embit/util/prebuilt/libuniffi_dnssec_prover_linux_x86_64.so new file mode 100755 index 0000000..ea90667 Binary files /dev/null and b/src/embit/util/prebuilt/libuniffi_dnssec_prover_linux_x86_64.so differ diff --git a/src/embit/util/prebuilt/libuniffi_dnssec_prover_windows_amd64.dll b/src/embit/util/prebuilt/libuniffi_dnssec_prover_windows_amd64.dll new file mode 100755 index 0000000..7f9c7b0 Binary files /dev/null and b/src/embit/util/prebuilt/libuniffi_dnssec_prover_windows_amd64.dll differ diff --git a/tests/tests/test_bip21.py b/tests/tests/test_bip21.py new file mode 100644 index 0000000..fe5002a --- /dev/null +++ b/tests/tests/test_bip21.py @@ -0,0 +1,74 @@ +from unittest import TestCase +from embit import bip21 + + +# BIP21 and BIP321 test vectors +# https://github.com/bitcoin/bips/blob/master/bip-0021.mediawiki#examples +# https://github.com/bitcoin/bips/blob/master/bip-0321.mediawiki#examples + +VECTORS_BIP21_VALID = [ + "bitcoin:175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W", + "bitcoin:175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W?label=Luke-Jr", + "bitcoin:175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W?amount=20.3&label=Luke-Jr", + "bitcoin:175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W?amount=50&label=Luke-Jr&message=Donation%20for%20project%20xyz", + "bitcoin:175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W?somethingyoudontunderstand=50&somethingelseyoudontget=999", +] + +VECTORS_BIP21_INVALID = [ + "bitcoin:175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W?req-somethingyoudontunderstand=50&req-somethingelseyoudontget=999", + "bitcoin:175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W?label=Luke-Jr&label=Matt", + "bitcoin:175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W?amount=42&amount=10", + "bitcoin:175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W?amount=42&amount=42", + "bitcoin:175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W?pop=callback%3a&req-pop=callback%3a", +] + + +class Bip21Test(TestCase): + def test_bip21_valid_uris(self): + """Test parsing of valid BIP21 URIs from the specification""" + for i, uri_string in enumerate(VECTORS_BIP21_VALID): + with self.subTest(i=i, uri=uri_string): + # Should decode without raising an exception + uri = bip21.BitcoinURI(uri_string) + + # Basic validation - all should have an address + self.assertIsNotNone(uri.get_address()) + self.assertEqual(uri.get_address(), "175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W") + + def test_bip21_invalid_uris(self): + """Test that invalid BIP21 URIs raise appropriate exceptions""" + for i, uri_string in enumerate(VECTORS_BIP21_INVALID): + with self.subTest(i=i, uri=uri_string): + with self.assertRaises(bip21.BIP21Error): + bip21.BitcoinURI(uri_string) + + def test_multiple_segwit_addresses(self): + """Test multiple segwit addresses (bc parameters) according to BIP21""" + uri_string = "bitcoin:?bc=bc1qufgy354j3kmvuch987xe4s40836x3h0lg8f5n2&bc=bc1p5swkugezn97763tl0yty6556856uug0q6jflljvep9m4p7339x5qzyrh4g" + uri = bip21.BitcoinURI(uri_string) + + # Should have no regular address in URI path + self.assertIsNone(uri.get_address()) + + # Should have exactly 2 bc addresses + bc_addresses = uri.get_bc_addresses() + self.assertEqual(len(bc_addresses), 2) + self.assertEqual(bc_addresses[0], "bc1qufgy354j3kmvuch987xe4s40836x3h0lg8f5n2") + self.assertEqual(bc_addresses[1], "bc1p5swkugezn97763tl0yty6556856uug0q6jflljvep9m4p7339x5qzyrh4g") + + def test_uppercase_uris(self): + """Test uppercase URIs commonly used in QR codes""" + # Test uppercase URI with address in path + uri1 = bip21.BitcoinURI("BITCOIN:BC1QUFGY354J3KMVUCH987XE4S40836X3H0LG8F5N2?BC=BC1P5SWKUGEZN97763TL0YTY6556856UUG0Q6JFLLJVEP9M4P7339X5QZYRH4G") + self.assertEqual(uri1.get_address(), "BC1QUFGY354J3KMVUCH987XE4S40836X3H0LG8F5N2") + self.assertEqual(len(uri1.get_bc_addresses()), 1) + # bc parameter should be normalized to lowercase + self.assertEqual(uri1.get_bc_addresses()[0], "bc1p5swkugezn97763tl0yty6556856uug0q6jflljvep9m4p7339x5qzyrh4g") + + # Test uppercase URI with only bc parameters + uri2 = bip21.BitcoinURI("BITCOIN:?BC=BC1QUFGY354J3KMVUCH987XE4S40836X3H0LG8F5N2&BC=BC1P5SWKUGEZN97763TL0YTY6556856UUG0Q6JFLLJVEP9M4P7339X5QZYRH4G") + self.assertIsNone(uri2.get_address()) + self.assertEqual(len(uri2.get_bc_addresses()), 2) + # bc parameters should be normalized to lowercase + self.assertEqual(uri2.get_bc_addresses()[0], "bc1qufgy354j3kmvuch987xe4s40836x3h0lg8f5n2") + self.assertEqual(uri2.get_bc_addresses()[1], "bc1p5swkugezn97763tl0yty6556856uug0q6jflljvep9m4p7339x5qzyrh4g") \ No newline at end of file diff --git a/tests/tests/test_bip353.py b/tests/tests/test_bip353.py new file mode 100644 index 0000000..ce8e323 --- /dev/null +++ b/tests/tests/test_bip353.py @@ -0,0 +1,69 @@ +from unittest import TestCase +from embit import bip353 + + +VECTORS_BIP353_VALID_PROOFS = [ + # Test vector from Sparrow Wallet + ("craig.user._bitcoin-payment.sparrowwallet.com.", "00002e000100002bb30113003008000002a30068993280687d83004f66005a48a604886288cc78c2a35e48816b7a182a349f397f2cd4c1bfa6de634acc9b9b0d2236fd8f257fa8641ae46da7ca17a697c965beabb5477ea6d0cc198b77c8cb9398f8f6fd36c7dc32439409625209b7c3d12108f2c973ea735f764ee629135ed67f016e63949a84e1f120b5146a27221180a0fbd0d632cc900c488b709260f2d479e6d787f2f9fa31222cacdbb696ddc3789744c691d27a8be4486fd7a74b51e417dfb9a9ba8f148f468c536debb4a7dc3803ea6213c55c3efd19cbf29059e5e460803e9656bdac7feacc38bf2bb8a9a3cbc5025841c1b71a58246cab007209bf2f22d4fdd4b80fe6d3bce9e5d2bb80df1949d62f09feb3a5bffe2a1bc6ab000030000100002bb301080100030803010001b11b182a464c3adc6535aa59613bda7a61cac86945c20b773095941194f4b9f516e8bd924b1e50e3fe83918b51e54529d4e5a1e45303df8462241d5e05979979ae5bf9c6c598c08a496e17f3bd3732d5aebe62667b61db1bbe178f27ac99408165a230d6aee78348e6c67789541f845b2ada96667f8dd16ae44f9e260c4a138b3bb1015965ebe609434a06464bd7d29bac47c3017e83c0f89bca1a9e3bdd0813715f3484292df589bc632e27d37efc02837cb85d770d5bd53a36edc99a8294771aa93cf22406f5506c8cf850ed85c1a475dee5c2d3700b3f5631d903524b849995c20cb407ed411f70b428ae3d642716fe239335aa961a752e67fb6dca0bf729000030000100002bb301080100030803010001b6aec4b48567e2925a2d9c4fa4c96e6dddf86215a9bd8dd579c38ccb1199ed1be89946a7f72fc2633909a2792d0eed1b5afb2ee4c78d865a76d6cd9369d999c96af6be0a2274b8f2e9e0a0065bd20257570f08bc14c16f5616426881a83dbce6926e391c138a2ec317efa7349264de2e791c9b7d4a6048ee6eedf27bf1ece398ff0d229f18377cb1f6b98d1228ef217b8146c0c73851b89a6fc37c621ca187e16428a743ffea0072e185ef93e39525cee3ad01e0c94d2e511c8c313322c29ab91631e1856049a36898684c3056e5997473816fb547acb0be6e660bdfa89a5cb28b3669d8625f3f018c7b3b8a4860e774ee8261811ce7f96c461bc162c1a374f3000030000100002bb301080101030803010001acffb409bcc939f831f7a1e5ec88f7a59255ec53040be432027390a4ce896d6f9086f3c5e177fbfe118163aaec7af1462c47945944c4e2c026be5e98bbcded25978272e1e3e079c5094d573f0e83c92f02b32d3513b1550b826929c80dd0f92cac966d17769fd5867b647c3f38029abdc48152eb8f207159ecc5d232c7c1537c79f4b7ac28ff11682f21681bf6d6aba555032bf6f9f036beb2aaa5b3778d6eebfba6bf9ea191be4ab0caea759e2f773a1f9029c73ecb8d5735b9321db085f1b8e2d8038fe2941992548cee0d67dd4547e11dd63af9c9fc1c5466fb684cf009d7197c2cf79e792ab501e6a8a1ca519af2cb9b5f6367e94c0d47502451357be1b5000030000100002bb301080101030803010001af7a8deba49d995a792aefc80263e991efdbc86138a931deb2c65d5682eab5d3b03738e3dfdc89d96da64c86c0224d9ce02514d285da3068b19054e5e787b2969058e98e12566c8c808c40c0b769e1db1a24a1bd9b31e303184a31fc7bb56b85bbba8abc02cd5040a444a36d47695969849e16ad856bb58e8fac8855224400319bdab224d83fc0e66aab32ff74bfeaf0f91c454e6850a1295207bbd4cdde8f6ffb08faa9755c2e3284efa01f99393e18786cb132f1e66ebc6517318e1ce8a3b7337ebb54d035ab57d9706ecd9350d4afacd825e43c8668eece89819caf6817af62dc4fbd82f0e33f6647b2b6bda175f14607f59f4635451e6b27df282ef73d8703636f6d00002b00010001474a00244d060d028acbb0cd28f41250a80a491389424d341522d946b0da0c0291f2d3d771d7805a03636f6d00002e00010001474a0113002b080100015180689978d068884740b569009f50ff461b38abba3d30ace990cb95740c3faae42082ef882a551c6b4a30d2b29caa59b0556ea80efc4a6bc126ba77d86d7e926fb741380018d038935154e0ec37485a479d8d3a5b5d79f15c7be24d5c46b58581b8a6dd55f44de72d20a3b232134a18fcfb14123c94d6d0cb249f90e56439e84df2cbae4da72491ba54b9ca2e8436fbdbb9591bdd93ce0411cf35002bc24c376526ed1711743d38ca227915f6e5e3e4c314617ad0d4e038646e885800e8853a79b7a160b5375bf492c19a7f5752718f11116b9a3278eaf19f34ee597fe315eaba1ce86c52625e4dfdcacc8d04994ee7600c4ec51357a2c23e936b05399153df6a31edc4e3d507976904dea64403636f6d00002e0001000006a4005700300d010001518068920efb687e474f4d0603636f6d00b2e671a909bab6910567084b8347cb199b924a4acf9e1a2602ba0adaa3b056890609bd88ee767161672bbe89466e2c035c0bce3a755f33b910047fa27a90b9c203636f6d0000300001000006a400440100030df17b60fb56d522f8634153e785c0a978532ea76de39d34bb356d3d042e07f29f7b992176cc83acef7b78ea750425203b18f2b8228b3cdd2bc7eb13c3e3035a7e03636f6d0000300001000006a400440101030db71f0465101ddbe2bf0c9455d12fa16c1cda44f4bf1ba2553418ad1f3aa9b06973f21b84eb532cf4035ee8d4832ca26d89306a7d32560c0cb0129d450ac108350d73706172726f7777616c6c657403636f6d00002b00010000546000243dc40d0256040d991c1075c4a8555445f9a5ce52ce6801aaf45d3e87663e7fbd68bc312b0d73706172726f7777616c6c657403636f6d00002b0001000054600024cf3b0d02656da59836422f5e198e73fc35e6a89bc0838deaac565e71a19804fc1250e4ce0d73706172726f7777616c6c657403636f6d00002e0001000054600057002b0d020001518068901cfc6886d214504103636f6d00d763f6d6ecb0f5e5b982f845d5fd5846ee9ce3a4a8cca71e8b7525476d6b6d2a3e196730e8b6bcfbe6774dd204519e609aa708f6151fe0247ccde98d2bd8c55d0d73706172726f7777616c6c657403636f6d00002e0001000007e5006500300d0200000e106891efe0687e29603dc40d73706172726f7777616c6c657403636f6d002b96ad4cdc8619f89d74317373ff0b40b9de3132cf957ee57c653c204d1d3611d6264d6baefb1c45c1fe2d499cc77587183f4900a1f0512b0478a60e4944c0410d73706172726f7777616c6c657403636f6d0000300001000007e500440100030d24c8364b3f942b0062f1c63880b959b2e7827f1cffff8d5e38f7fde1b22d621d1c4a0cd9a9b0c6c70b1c94543ccdc5502481aebd6e2b44656c9ea339ac81e83b0d73706172726f7777616c6c657403636f6d0000300001000007e500440100030d95676c7b25e7794a8a7e4b19ed638e47aca735d02ce2dd08b2886c20c31a2cb9e7cc8b85023a46eeb637020119dcaa6bbc0747e12340fa813199799de579de8a0d73706172726f7777616c6c657403636f6d0000300001000007e500440101030db0372521337fd56d8b62e917b7866b7faa753d25322e12b52a3eb5ff9f4c9f66227f508fe33ba139f2f1354fe3ded6d3da76d49be926198dc2940f2c5282c7fe0d73706172726f7777616c6c657403636f6d0000300001000007e500440101030dddf917743f320a49f6218d706218b6cae574f1db7688555e0d5f0455405d6865993f0147fb4b33baa207b28d232c9e70419ddcae72050311098cd4cfaa07969b0563726169670475736572105f626974636f696e2d7061796d656e740d73706172726f7777616c6c657403636f6d000010000100000e10003332626974636f696e3a6263317177746865343378657561736b6c636c71346b766872656c75763368753932727a656a34326a730563726169670475736572105f626974636f696e2d7061796d656e740d73706172726f7777616c6c657403636f6d00002e000100000e10006500100d0500000e106891efe0687e2960bb260d73706172726f7777616c6c657403636f6d00e7ae93d23b747737554c4d52dd1ec0f58c411c6a474da46c3c24d0db970d86e91bf91b5eabeb1ed59121678ef534a25a6f75ce0588e6524439c11d208f301d46"), + # Valid test vector from BIP-353. This record requires following a CNAME to a different domain. + ("a.x_domain_cname_wild.user._bitcoin-payment.dnssec_proof_tests.bitcoin.ninja.", "00002e0001000149d60113003008000002a30068993280687d83004f66005a48a604886288cc78c2a35e48816b7a182a349f397f2cd4c1bfa6de634acc9b9b0d2236fd8f257fa8641ae46da7ca17a697c965beabb5477ea6d0cc198b77c8cb9398f8f6fd36c7dc32439409625209b7c3d12108f2c973ea735f764ee629135ed67f016e63949a84e1f120b5146a27221180a0fbd0d632cc900c488b709260f2d479e6d787f2f9fa31222cacdbb696ddc3789744c691d27a8be4486fd7a74b51e417dfb9a9ba8f148f468c536debb4a7dc3803ea6213c55c3efd19cbf29059e5e460803e9656bdac7feacc38bf2bb8a9a3cbc5025841c1b71a58246cab007209bf2f22d4fdd4b80fe6d3bce9e5d2bb80df1949d62f09feb3a5bffe2a1bc6ab0000300001000149d601080100030803010001b11b182a464c3adc6535aa59613bda7a61cac86945c20b773095941194f4b9f516e8bd924b1e50e3fe83918b51e54529d4e5a1e45303df8462241d5e05979979ae5bf9c6c598c08a496e17f3bd3732d5aebe62667b61db1bbe178f27ac99408165a230d6aee78348e6c67789541f845b2ada96667f8dd16ae44f9e260c4a138b3bb1015965ebe609434a06464bd7d29bac47c3017e83c0f89bca1a9e3bdd0813715f3484292df589bc632e27d37efc02837cb85d770d5bd53a36edc99a8294771aa93cf22406f5506c8cf850ed85c1a475dee5c2d3700b3f5631d903524b849995c20cb407ed411f70b428ae3d642716fe239335aa961a752e67fb6dca0bf7290000300001000149d601080100030803010001b6aec4b48567e2925a2d9c4fa4c96e6dddf86215a9bd8dd579c38ccb1199ed1be89946a7f72fc2633909a2792d0eed1b5afb2ee4c78d865a76d6cd9369d999c96af6be0a2274b8f2e9e0a0065bd20257570f08bc14c16f5616426881a83dbce6926e391c138a2ec317efa7349264de2e791c9b7d4a6048ee6eedf27bf1ece398ff0d229f18377cb1f6b98d1228ef217b8146c0c73851b89a6fc37c621ca187e16428a743ffea0072e185ef93e39525cee3ad01e0c94d2e511c8c313322c29ab91631e1856049a36898684c3056e5997473816fb547acb0be6e660bdfa89a5cb28b3669d8625f3f018c7b3b8a4860e774ee8261811ce7f96c461bc162c1a374f30000300001000149d601080101030803010001acffb409bcc939f831f7a1e5ec88f7a59255ec53040be432027390a4ce896d6f9086f3c5e177fbfe118163aaec7af1462c47945944c4e2c026be5e98bbcded25978272e1e3e079c5094d573f0e83c92f02b32d3513b1550b826929c80dd0f92cac966d17769fd5867b647c3f38029abdc48152eb8f207159ecc5d232c7c1537c79f4b7ac28ff11682f21681bf6d6aba555032bf6f9f036beb2aaa5b3778d6eebfba6bf9ea191be4ab0caea759e2f773a1f9029c73ecb8d5735b9321db085f1b8e2d8038fe2941992548cee0d67dd4547e11dd63af9c9fc1c5466fb684cf009d7197c2cf79e792ab501e6a8a1ca519af2cb9b5f6367e94c0d47502451357be1b50000300001000149d601080101030803010001af7a8deba49d995a792aefc80263e991efdbc86138a931deb2c65d5682eab5d3b03738e3dfdc89d96da64c86c0224d9ce02514d285da3068b19054e5e787b2969058e98e12566c8c808c40c0b769e1db1a24a1bd9b31e303184a31fc7bb56b85bbba8abc02cd5040a444a36d47695969849e16ad856bb58e8fac8855224400319bdab224d83fc0e66aab32ff74bfeaf0f91c454e6850a1295207bbd4cdde8f6ffb08faa9755c2e3284efa01f99393e18786cb132f1e66ebc6517318e1ce8a3b7337ebb54d035ab57d9706ecd9350d4afacd825e43c8668eece89819caf6817af62dc4fbd82f0e33f6647b2b6bda175f14607f59f4635451e6b27df282ef73d8703636f6d00002b000100007a7100244d060d028acbb0cd28f41250a80a491389424d341522d946b0da0c0291f2d3d771d7805a03636f6d00002e000100007a710113002b08010001518068977e9068864d00b569002d3014cdeea855ed967775aa53a2671069c3d5c3a47b4e51fd4926fda34c7f6bcf970272fe18c516cbfc0119b024badb77082f1b956d948f3fd92e05557835ca10379cf523583e137e4d1d35047c4ca07d1ff11708241a091a6167d4538c1a084de6be6360997767cad3d44e7530461c8e8c744bfc146c02b00360e29eb4b6d6ec8ff3a1fe8eb44e3098ecf865ddf8f19d66b99105d961218925cab2c01ff6ac3ac9d364089536b6e2e71d2ec063949eb71137fc646897113f92673c8a74c23bb66283c80bd26d9d70ef3fe9024ecf299895a1e856954e7a5b11f528a5953baf75f6d5db6ccbc5f031b667f612a6c7d27096e40e8bf7fb2fda709900bc221ee903636f6d00002e00010000171a005700300d010001518068920efb687e474f4d0603636f6d00b2e671a909bab6910567084b8347cb199b924a4acf9e1a2602ba0adaa3b056890609bd88ee767161672bbe89466e2c035c0bce3a755f33b910047fa27a90b9c203636f6d00003000010000171a00440100030df17b60fb56d522f8634153e785c0a978532ea76de39d34bb356d3d042e07f29f7b992176cc83acef7b78ea750425203b18f2b8228b3cdd2bc7eb13c3e3035a7e03636f6d00003000010000171a00440101030db71f0465101ddbe2bf0c9455d12fa16c1cda44f4bf1ba2553418ad1f3aa9b06973f21b84eb532cf4035ee8d4832ca26d89306a7d32560c0cb0129d450ac108350b6d617474636f72616c6c6f03636f6d00002b00010000546000249f000d02594d2813e04a1d2660ff3c0afc5579b9ec0fe72cc206dc6f248bbe6dd652e1950b6d617474636f72616c6c6f03636f6d00002b0001000054600024e2f50d02f0e161567d468087ff27b051abc94476178a7cb635da1aa705e05c77ca81de520b6d617474636f72616c6c6f03636f6d00002e0001000054600057002b0d020001518068900e906886c3a8504103636f6d006d674908febdc7e78aa921806332f66656388c9ecb0305391d70a251886654f841426c54969009b261b3ab5c9cb5f2f9ba94fe5327722c079e89fb1f33963c330b6d617474636f72616c6c6f03636f6d00002e00010000456b006300300d0200093a80689827d568859dbde2f50b6d617474636f72616c6c6f03636f6d00f8fcb4e5ea52960df7464a5dc487043b6fb8fb8a083393c18902c47bd0c536f2cf138da967bc8be8599ef28a6bb781ce95e4b9617d2c3dbc8c5029092d80c4bc0b6d617474636f72616c6c6f03636f6d00003000010000456b00440100030dfd9dbc34cb5053a2c4a6b3d0dc60fc65d8a992dc1e080f6deeddba7fe6b25217730de64c9a1ce986b3f81f556881fe0e7b5b20c8ae381c4fefdbc311aa7d22ee0b6d617474636f72616c6c6f03636f6d00003000010000456b00440101030dec7c1fa1752495c42d2224eace96ed74144e9cb811608dd91594974bdc723fdc5b38a37c3340f1deca68a7ec82248822954b2994de5ac99ff6e9db95fd42c94b046d6174740475736572105f626974636f696e2d7061796d656e740b6d617474636f72616c6c6f03636f6d000010000100000d8201ecff626974636f696e3a626331717a7477793678656e337a647474377a3076726761706d6a74667a3861636a6b6670356670376c3f6c6e6f3d6c6e6f317a7235717975677167736b726b37306b716d7571377633646e7232666e6d68756b7073396e386875743438766b7170716e736b743273767371776a616b70376b36707968746b7578773779326b716d73786c777275687a7176307a736e686839713374397868783339737563367173723037656b6d3565736479756d307736366d6e783876647175777670376470356a70376a337635637036616a3077333239666e6b7171763630713936737a356e6b726335723935716666783030327135337471646beb3878396d32746d7438356a74706d63796376666e727078336c723435683267376e6133736563377867756374667a7a636d386a6a71746a3579613237746536306a303376707430767139746d326e3979786c32686e67666e6d79676573613235733475347a6c78657771707670393478743772757234726878756e776b74686b39766c79336c6d356868307071763461796d6371656a6c6773736e6c707a776c6767796b6b616a7037796a73356a76723261676b79797063646c6a323830637934366a70796e73657a72636a326b7761326c797238787664366c666b706834787278746b327863336c7071046d6174740475736572105f626974636f696e2d7061796d656e740b6d617474636f72616c6c6f03636f6d00002e000100000d82006300100d0500000e106894172f68818d17a7150b6d617474636f72616c6c6f03636f6d00ba9ee201fe8e135c732d61f8ba32580a7a4e6f3e490cd520b7e4afe9004f257c1b986dd2ef8d4588f4d3810da04249c48b88a6c284f43be5703220ee2a955320056e696e6a6100002b0001000015640024b4020802c8f816a7a575bdb2f997f682aab2653ba2cb5eddb69b036a30742a33befaf141056e696e6a6100002e0001000015640113002b0801000151806896d5d06885a440b56900a41759b94a4adf6192a0fb6e0f3ee388c15cd5b4f80fe961b1efbe5f93c2941c41ed1b71e9cdb5ccd651ffaf4d3c3158b341f21ccfbdf99b80485ceae57641e094919cc5ffe219c4ee25e3aa6bd02ba378de69bda940da8d1a873942acc683b25f41641fbf922833311af6fba9443532a37fd601a8dfb000f5a749b5ece5c847bab87c770605d1b2fa5c5528a4c78388b0a99ff5ca49580777a3854f472b06aa28a1bc53d4dda596ca6df1275227a107e6520605c919fc7048081fd3d396784a49928a3f32f1445f5fa56d2a4be8c2f5f7da68deeb974e7023b507cffea1e69d6706a62560321fa3a7492256715b229dcc802f51321bbb201bf76571dfa55ef4056e696e6a6100002e000100000c9e01190030080100000e10689f57ec68839a5cb402056e696e6a61006e05a6c0c66f0da44f5905afbb29f819692ccc8e867b45c25839bc5b7ed203383d2df06a284b3414b71848a77bebeb209333c1aeb52700cf3e630232e29d4befe5e708a0fa5fed527e6977ff41607ec531c8aa55be8cfac4beb38fd08b73a01deb25dd1b046c1e27ea210f1e9198672e8931b1eeafa6b24355fbaeb336c86bfb455ce4eea1b60c7218b3e077930be6250d4f81c9b73d9cecf9126e6962dddd489674ae560dfc18e63ef2d6a71c8347dbdca986937cc9ff2f793c0ee196bbef70784ec2cb7261393e32ba31db67043dc418fd17a74800194e77ab88130fa5e9736acd63f0d6b32ebee665bf4d95344f1d71cda00b2de99ce2e3a52b8e61b2c413056e696e6a61000030000100000c9e0088010003080301000197edb59d4f181e2761dd8d0465854339afc71fd89e47155981ddd175cdce79477552aeaf7b5a08fc4ac6025555f60582f2060e630edfb35b9c7cc30990fb9c3dc9f2fd036c962f67b94c9670d4ceaacd77973bca82ab7c9615f7e4320dda5b6d74dec673017c6fa448b5542a804e08ac873c509c1945ff734c320491e4b18e6d056e696e6a61000030000100000c9e00880100030803010001d28cb7e2bb163d5815838bedeca1006dde8551b379cb963c8a2cb42bc360127e3a5cf88ffc851a67414815b875f65d78c39b58d2fb29a1d4e76d50cb6b4a58a11fe2fb7c1b6db7bf7d72f5a1401e381c57fcc76f599cc73f05095d2bd14d9895e4fa1cff21bd760598a734b640102d11bc159c6b2ae73dbfd2741518142584d1056e696e6a61000030000100000c9e01080101030803010001c71e4c9dc49249a27b3ef42fcb8d56b4c3ac76715c7ad01c41d0d432590d15c3c62cee4b2d29ac35f2d72c9b32a70a0243cbd08aeebc9f6f1e0e63d9e1cdfe133c455a82435c0780b750012c942f7ed5b662eec8d9ae885a58993fa78d7561fedcd11d9e1c171acb02d0025837ba61a3c0a6756427414470c9ca0906b298168b5f4d9640e62e1b75dd06be664104ed32cfe447fa21f37401712c720a0dca4bb1bc20f3fa3103cad336bde20b16f73948e6b80dda0a528db536a958868c3870ecddcfb02dfb3cb4d22e2ad49a4b9f78c90ff6c7e10e301b3fb36fe859a94c30084660665741c14ff60d0535013fcf439840ecad82fc9278e4ace68ba95e70824907626974636f696e056e696e6a6100002b000100000e100024716c0d023f7ad5a303e9c1cd1474b8df2ae56f3f82da8637ca55db4d9a2bb85960ca698e07626974636f696e056e696e6a6100002b000100000e100024768a0d02ce46a9aff9a06e789c1bdfe250b0ef6ba8d39a53b2a3427c551f5ad375e059b607626974636f696e056e696e6a6100002e000100000e100099002b080200000e10689f57ec68839a5ce694056e696e6a610042a265ca325eebc262b0f2d80a07985dd07cd8b4889adc02ca652b279253ed12ce0e381c7e174dc5ae05e230aa63a0ad614c1aa93e25027e3b1c1c9d85a8a4d2ecc1697a8892fddca9e7b8de63092db3ccd09895eb625b494008d2be8ead86edd91b08bfc5cbce55588174df0c4a6a10657a79536dc63ca9df23fbd7a5a0264207626974636f696e056e696e6a6100002e00010000542d006100300d0200093a806898c73468863d1c716c07626974636f696e056e696e6a610002fd1bf18c2ebb5ece5e28ca76bf30650695636c55633bccae8179c2d1ee7b97e78d188e08ffc869a8f67847bf557516ac12465ebea1acc281d6d636fdab612007626974636f696e056e696e6a6100003000010000542d00440100030dff753a27b08c3e48a642b210d6fcc444ff9ed4faf9c1241103db4ed3c19a95c3afbb52c0c02eb392ee048cc9e28ac2d272b1053bdc052bc18d5de05d7710196c07626974636f696e056e696e6a6100003000010000542d00440101030df65551925ce6321888e685981823d617fbc10f329bffe4081bf18c2372632a5548010bf62e6556a92722629275e0bd001e3d7837d325a353f6a851c5b96525962036397661326d75643937367167717372643861627375683975356e367432686907626974636f696e056e696e6a6100002e00010000003c006100320d030000003c68995d346886d31cd0e207626974636f696e056e696e6a6100f2edbe6baf1ea78801d1c86c388a3e4f4d7e596558a70a23f63229d1677436f910943aad865d26f59eef25819ac5dcc75e1ea40931ebd44fc78a565b0de5952f2036397661326d75643937367167717372643861627375683975356e367432686907626974636f696e056e696e6a6100003200010000003c002a0100000008a89d709785072f1f143ad5cecc99536c8932ccca13e290b69519eec12c0006000180000002016113785f646f6d61696e5f636e616d655f77696c640475736572105f626974636f696e2d7061796d656e7412646e737365635f70726f6f665f746573747307626974636f696e056e696e6a6100000500010000001e002c046d6174740475736572105f626974636f696e2d7061796d656e740b6d617474636f72616c6c6f03636f6d00016113785f646f6d61696e5f636e616d655f77696c640475736572105f626974636f696e2d7061796d656e7412646e737365635f70726f6f665f746573747307626974636f696e056e696e6a6100002e00010000001e006100050d060000001e68950be3688281cbd0e207626974636f696e056e696e6a61000f9d965ddf9abf817af035eb83ef29b398e76f82d41fc769f77fab49321669e06dac97f91ed8b954e0a340b64a2f26c2687a28f5e12fdbf392bbdfeb9285875c"), + # Valid test vector from BIP-353. + ("override.x_domain_cname_wild.user._bitcoin-payment.dnssec_proof_tests.bitcoin.ninja.", "00002e000100008a350113003008000002a30068993280687d83004f66005a48a604886288cc78c2a35e48816b7a182a349f397f2cd4c1bfa6de634acc9b9b0d2236fd8f257fa8641ae46da7ca17a697c965beabb5477ea6d0cc198b77c8cb9398f8f6fd36c7dc32439409625209b7c3d12108f2c973ea735f764ee629135ed67f016e63949a84e1f120b5146a27221180a0fbd0d632cc900c488b709260f2d479e6d787f2f9fa31222cacdbb696ddc3789744c691d27a8be4486fd7a74b51e417dfb9a9ba8f148f468c536debb4a7dc3803ea6213c55c3efd19cbf29059e5e460803e9656bdac7feacc38bf2bb8a9a3cbc5025841c1b71a58246cab007209bf2f22d4fdd4b80fe6d3bce9e5d2bb80df1949d62f09feb3a5bffe2a1bc6ab000030000100008a3501080100030803010001b11b182a464c3adc6535aa59613bda7a61cac86945c20b773095941194f4b9f516e8bd924b1e50e3fe83918b51e54529d4e5a1e45303df8462241d5e05979979ae5bf9c6c598c08a496e17f3bd3732d5aebe62667b61db1bbe178f27ac99408165a230d6aee78348e6c67789541f845b2ada96667f8dd16ae44f9e260c4a138b3bb1015965ebe609434a06464bd7d29bac47c3017e83c0f89bca1a9e3bdd0813715f3484292df589bc632e27d37efc02837cb85d770d5bd53a36edc99a8294771aa93cf22406f5506c8cf850ed85c1a475dee5c2d3700b3f5631d903524b849995c20cb407ed411f70b428ae3d642716fe239335aa961a752e67fb6dca0bf729000030000100008a3501080100030803010001b6aec4b48567e2925a2d9c4fa4c96e6dddf86215a9bd8dd579c38ccb1199ed1be89946a7f72fc2633909a2792d0eed1b5afb2ee4c78d865a76d6cd9369d999c96af6be0a2274b8f2e9e0a0065bd20257570f08bc14c16f5616426881a83dbce6926e391c138a2ec317efa7349264de2e791c9b7d4a6048ee6eedf27bf1ece398ff0d229f18377cb1f6b98d1228ef217b8146c0c73851b89a6fc37c621ca187e16428a743ffea0072e185ef93e39525cee3ad01e0c94d2e511c8c313322c29ab91631e1856049a36898684c3056e5997473816fb547acb0be6e660bdfa89a5cb28b3669d8625f3f018c7b3b8a4860e774ee8261811ce7f96c461bc162c1a374f3000030000100008a3501080101030803010001acffb409bcc939f831f7a1e5ec88f7a59255ec53040be432027390a4ce896d6f9086f3c5e177fbfe118163aaec7af1462c47945944c4e2c026be5e98bbcded25978272e1e3e079c5094d573f0e83c92f02b32d3513b1550b826929c80dd0f92cac966d17769fd5867b647c3f38029abdc48152eb8f207159ecc5d232c7c1537c79f4b7ac28ff11682f21681bf6d6aba555032bf6f9f036beb2aaa5b3778d6eebfba6bf9ea191be4ab0caea759e2f773a1f9029c73ecb8d5735b9321db085f1b8e2d8038fe2941992548cee0d67dd4547e11dd63af9c9fc1c5466fb684cf009d7197c2cf79e792ab501e6a8a1ca519af2cb9b5f6367e94c0d47502451357be1b5000030000100008a3501080101030803010001af7a8deba49d995a792aefc80263e991efdbc86138a931deb2c65d5682eab5d3b03738e3dfdc89d96da64c86c0224d9ce02514d285da3068b19054e5e787b2969058e98e12566c8c808c40c0b769e1db1a24a1bd9b31e303184a31fc7bb56b85bbba8abc02cd5040a444a36d47695969849e16ad856bb58e8fac8855224400319bdab224d83fc0e66aab32ff74bfeaf0f91c454e6850a1295207bbd4cdde8f6ffb08faa9755c2e3284efa01f99393e18786cb132f1e66ebc6517318e1ce8a3b7337ebb54d035ab57d9706ecd9350d4afacd825e43c8668eece89819caf6817af62dc4fbd82f0e33f6647b2b6bda175f14607f59f4635451e6b27df282ef73d87056e696e6a6100002b000100011e7f0024b4020802c8f816a7a575bdb2f997f682aab2653ba2cb5eddb69b036a30742a33befaf141056e696e6a6100002e000100011e7f0113002b080100015180689827506886f5c0b569005653d237e182a326851f70489ac7f622872e47684dd0d0de3caf17dfdd479efd1a7da7d8df1ff69a4459842d8a266611a66689521a636858d227b723af0c438d493b074de99acbd685547d7f7692743fec6af2167ee8567a0b0807dfb1bc53367d3a41397ba4ddf2e76f6922b23f034202546667755f624337ef9401c093b712445178e6fca3d4452c25ab99d32417ec0a031fb39c867c5f88114df1e13266ff15aba34c5f571fe91a877ed576ab528b3508a201424c1ba547c38fcbe6cda5362921d8bb747a5e427288d06c22cba8b04448af6a0fa99cda7109ed16e64b970073e3255ed3fafcf1542529d89cbb799e767bd760787159e1bd1c7d4f34995e73056e696e6a6100002e000100000a8901190030080100000e10689f57ec68839a5cb402056e696e6a61006e05a6c0c66f0da44f5905afbb29f819692ccc8e867b45c25839bc5b7ed203383d2df06a284b3414b71848a77bebeb209333c1aeb52700cf3e630232e29d4befe5e708a0fa5fed527e6977ff41607ec531c8aa55be8cfac4beb38fd08b73a01deb25dd1b046c1e27ea210f1e9198672e8931b1eeafa6b24355fbaeb336c86bfb455ce4eea1b60c7218b3e077930be6250d4f81c9b73d9cecf9126e6962dddd489674ae560dfc18e63ef2d6a71c8347dbdca986937cc9ff2f793c0ee196bbef70784ec2cb7261393e32ba31db67043dc418fd17a74800194e77ab88130fa5e9736acd63f0d6b32ebee665bf4d95344f1d71cda00b2de99ce2e3a52b8e61b2c413056e696e6a61000030000100000a890088010003080301000197edb59d4f181e2761dd8d0465854339afc71fd89e47155981ddd175cdce79477552aeaf7b5a08fc4ac6025555f60582f2060e630edfb35b9c7cc30990fb9c3dc9f2fd036c962f67b94c9670d4ceaacd77973bca82ab7c9615f7e4320dda5b6d74dec673017c6fa448b5542a804e08ac873c509c1945ff734c320491e4b18e6d056e696e6a61000030000100000a8900880100030803010001d28cb7e2bb163d5815838bedeca1006dde8551b379cb963c8a2cb42bc360127e3a5cf88ffc851a67414815b875f65d78c39b58d2fb29a1d4e76d50cb6b4a58a11fe2fb7c1b6db7bf7d72f5a1401e381c57fcc76f599cc73f05095d2bd14d9895e4fa1cff21bd760598a734b640102d11bc159c6b2ae73dbfd2741518142584d1056e696e6a61000030000100000a8901080101030803010001c71e4c9dc49249a27b3ef42fcb8d56b4c3ac76715c7ad01c41d0d432590d15c3c62cee4b2d29ac35f2d72c9b32a70a0243cbd08aeebc9f6f1e0e63d9e1cdfe133c455a82435c0780b750012c942f7ed5b662eec8d9ae885a58993fa78d7561fedcd11d9e1c171acb02d0025837ba61a3c0a6756427414470c9ca0906b298168b5f4d9640e62e1b75dd06be664104ed32cfe447fa21f37401712c720a0dca4bb1bc20f3fa3103cad336bde20b16f73948e6b80dda0a528db536a958868c3870ecddcfb02dfb3cb4d22e2ad49a4b9f78c90ff6c7e10e301b3fb36fe859a94c30084660665741c14ff60d0535013fcf439840ecad82fc9278e4ace68ba95e70824907626974636f696e056e696e6a6100002b000100000e100024716c0d023f7ad5a303e9c1cd1474b8df2ae56f3f82da8637ca55db4d9a2bb85960ca698e07626974636f696e056e696e6a6100002b000100000e100024768a0d02ce46a9aff9a06e789c1bdfe250b0ef6ba8d39a53b2a3427c551f5ad375e059b607626974636f696e056e696e6a6100002e000100000e100099002b080200000e10689f57ec68839a5ce694056e696e6a610042a265ca325eebc262b0f2d80a07985dd07cd8b4889adc02ca652b279253ed12ce0e381c7e174dc5ae05e230aa63a0ad614c1aa93e25027e3b1c1c9d85a8a4d2ecc1697a8892fddca9e7b8de63092db3ccd09895eb625b494008d2be8ead86edd91b08bfc5cbce55588174df0c4a6a10657a79536dc63ca9df23fbd7a5a0264207626974636f696e056e696e6a6100002e0001000043a6006100300d0200093a806898c73468863d1c716c07626974636f696e056e696e6a610002fd1bf18c2ebb5ece5e28ca76bf30650695636c55633bccae8179c2d1ee7b97e78d188e08ffc869a8f67847bf557516ac12465ebea1acc281d6d636fdab612007626974636f696e056e696e6a610000300001000043a600440100030dff753a27b08c3e48a642b210d6fcc444ff9ed4faf9c1241103db4ed3c19a95c3afbb52c0c02eb392ee048cc9e28ac2d272b1053bdc052bc18d5de05d7710196c07626974636f696e056e696e6a610000300001000043a600440101030df65551925ce6321888e685981823d617fbc10f329bffe4081bf18c2372632a5548010bf62e6556a92722629275e0bd001e3d7837d325a353f6a851c5b9652596086f7665727269646513785f646f6d61696e5f636e616d655f77696c640475736572105f626974636f696e2d7061796d656e7412646e737365635f70726f6f665f746573747307626974636f696e056e696e6a6100001000010000001e002b2a626974636f696e3a314a424d617474527a744b4446324b52533376686a4a5841376834374e45736e3263086f7665727269646513785f646f6d61696e5f636e616d655f77696c640475736572105f626974636f696e2d7061796d656e7412646e737365635f70726f6f665f746573747307626974636f696e056e696e6a6100002e00010000001e006100100d070000001e68950db36882839bd0e207626974636f696e056e696e6a610075ec00352dd04506619d06904e95448002f60b7d566194a6624ce850c6fe0026f9f95ecda41dfa6a66733bd6285903305766b31a6097c89656e6c69906c0bd74"), + # More than one TXT record is allowed if it does not start with (case insensitive) "bitcoin:" + ("simple.user._bitcoin-payment.dnssec_proof_tests.bitcoin.ninja.", "0673696d706c650475736572105f626974636f696e2d7061796d656e7412646e737365635f70726f6f665f746573747307626974636f696e056e696e6a6100001000010000001e001110626974636f696e20697320636f6f6c210673696d706c650475736572105f626974636f696e2d7061796d656e7412646e737365635f70726f6f665f746573747307626974636f696e056e696e6a6100001000010000001e003736626974636f696e3a3f62633d626331717a7477793678656e337a647474377a3076726761706d6a74667a3861636a6b6670356670376c0673696d706c650475736572105f626974636f696e2d7061796d656e7412646e737365635f70726f6f665f746573747307626974636f696e056e696e6a6100002e00010000001e006100100d060000001e68a70b7e68948166d0e207626974636f696e056e696e6a61007e51ba5c43c4d18dc62a54d4647d1fcf2cfa594cd6bfddfe834c7b9bc6f1849490972441ea34283c6c7dbab3ed783deabebb3f1081ade02e57795a85220051bc07626974636f696e056e696e6a6100003000010001614500440100030dff753a27b08c3e48a642b210d6fcc444ff9ed4faf9c1241103db4ed3c19a95c3afbb52c0c02eb392ee048cc9e28ac2d272b1053bdc052bc18d5de05d7710196c07626974636f696e056e696e6a6100003000010001614500440101030df65551925ce6321888e685981823d617fbc10f329bffe4081bf18c2372632a5548010bf62e6556a92722629275e0bd001e3d7837d325a353f6a851c5b965259607626974636f696e056e696e6a6100002e000100016145006100300d0200093a806898c73468863d1c716c07626974636f696e056e696e6a610002fd1bf18c2ebb5ece5e28ca76bf30650695636c55633bccae8179c2d1ee7b97e78d188e08ffc869a8f67847bf557516ac12465ebea1acc281d6d636fdab612007626974636f696e056e696e6a6100002b000100000cfc0024716c0d023f7ad5a303e9c1cd1474b8df2ae56f3f82da8637ca55db4d9a2bb85960ca698e07626974636f696e056e696e6a6100002b000100000cfc0024768a0d02ce46a9aff9a06e789c1bdfe250b0ef6ba8d39a53b2a3427c551f5ad375e059b607626974636f696e056e696e6a6100002e000100000cfc0099002b080200000e1068a89268688cd4d892dc056e696e6a61004d88897d7d5f5d920ca436e81ee67b1bb22f1a96309693454ab9d0b06434794162cc42846a4b51a40475a5b44e940c04dd1ac2c902a190ca2251ae89184520b0f3f22c12c51403d0a6e1cd95792e8f6f5036cd2d50dbdfa5eba3b2ee70ff78d40dc2eba2cc899af26954a55e5b5d344d478cabd9423418495caee45c550b1144056e696e6a61000030000100000cfc0088010003080301000197edb59d4f181e2761dd8d0465854339afc71fd89e47155981ddd175cdce79477552aeaf7b5a08fc4ac6025555f60582f2060e630edfb35b9c7cc30990fb9c3dc9f2fd036c962f67b94c9670d4ceaacd77973bca82ab7c9615f7e4320dda5b6d74dec673017c6fa448b5542a804e08ac873c509c1945ff734c320491e4b18e6d056e696e6a61000030000100000cfc01080101030803010001c71e4c9dc49249a27b3ef42fcb8d56b4c3ac76715c7ad01c41d0d432590d15c3c62cee4b2d29ac35f2d72c9b32a70a0243cbd08aeebc9f6f1e0e63d9e1cdfe133c455a82435c0780b750012c942f7ed5b662eec8d9ae885a58993fa78d7561fedcd11d9e1c171acb02d0025837ba61a3c0a6756427414470c9ca0906b298168b5f4d9640e62e1b75dd06be664104ed32cfe447fa21f37401712c720a0dca4bb1bc20f3fa3103cad336bde20b16f73948e6b80dda0a528db536a958868c3870ecddcfb02dfb3cb4d22e2ad49a4b9f78c90ff6c7e10e301b3fb36fe859a94c30084660665741c14ff60d0535013fcf439840ecad82fc9278e4ace68ba95e708249056e696e6a61000030000100000cfc00880100030803010001d28cb7e2bb163d5815838bedeca1006dde8551b379cb963c8a2cb42bc360127e3a5cf88ffc851a67414815b875f65d78c39b58d2fb29a1d4e76d50cb6b4a58a11fe2fb7c1b6db7bf7d72f5a1401e381c57fcc76f599cc73f05095d2bd14d9895e4fa1cff21bd760598a734b640102d11bc159c6b2ae73dbfd2741518142584d1056e696e6a6100002e000100000cfc01190030080100000e1068a89268688cd4d8b402056e696e6a6100646e04cf5f76daa775ff98905ded3d2caad68d3198fc161340d558262c7ba7da28e7538a7f32d5a6485e4448037e8edf55759b7059fdc084f1017f790aa7c0d8f0e4af6c9af04e4afd16ce6052c30da59314c9c98075fd32bb77a81f34bea6190631b9d0a4351a764c14152ea217e6271647bf95b0df4c3d7cc00937bec01b65ad1b5209c976a4843e399f173f196a012c0876bc9afa3457d940d6ad7b9d50b3d496836d7342cf3aec75ac4785563e20574ddd2dab1cfb6b5da32508edf7186aa516b0451b221251652ee44353710cbe35928b79fff4293c5e8eff7ab371ce43595e2300305bda0108b911c3ef7d4d15c70d68686fddf28422d5fcbcdb967e84056e696e6a6100002b0001000151800024b4020802c8f816a7a575bdb2f997f682aab2653ba2cb5eddb69b036a30742a33befaf141056e696e6a6100002e0001000151800113002b08010001518068a55650689424c0b569007f4d7bbce2d77380e115f8469a3c6d1d4bd9be1145eeee843332f1d54da134a25f7d259ccfa36a2e833106139ba37bc28651b7717dabfc8c450e889c5d5d6d39d12f1004252fa08692794af4a7f29a94f6611f9b000ba899d90d6a32e91c54e63720811546b251fb265b7f62615391ab330f83a96841f0367347e67522112f63f3efda891ae0f7265ed5b89673f184027cdb9b5e94b16a9b1f99877cc8e078abf246cad1f76a48a34474977dbd89dd3b41f381df89a82e2939aeb06830f656253e4e347cd04ee6dba0c8377e818f919f80a7e6aefa0f8196d04845bc26b498b0115022ff75e363711e55337de505e50df69b840699160467516d15e8235aa4cf00003000010002a30001080100030803010001b11b182a464c3adc6535aa59613bda7a61cac86945c20b773095941194f4b9f516e8bd924b1e50e3fe83918b51e54529d4e5a1e45303df8462241d5e05979979ae5bf9c6c598c08a496e17f3bd3732d5aebe62667b61db1bbe178f27ac99408165a230d6aee78348e6c67789541f845b2ada96667f8dd16ae44f9e260c4a138b3bb1015965ebe609434a06464bd7d29bac47c3017e83c0f89bca1a9e3bdd0813715f3484292df589bc632e27d37efc02837cb85d770d5bd53a36edc99a8294771aa93cf22406f5506c8cf850ed85c1a475dee5c2d3700b3f5631d903524b849995c20cb407ed411f70b428ae3d642716fe239335aa961a752e67fb6dca0bf72900003000010002a30001080101030803010001af7a8deba49d995a792aefc80263e991efdbc86138a931deb2c65d5682eab5d3b03738e3dfdc89d96da64c86c0224d9ce02514d285da3068b19054e5e787b2969058e98e12566c8c808c40c0b769e1db1a24a1bd9b31e303184a31fc7bb56b85bbba8abc02cd5040a444a36d47695969849e16ad856bb58e8fac8855224400319bdab224d83fc0e66aab32ff74bfeaf0f91c454e6850a1295207bbd4cdde8f6ffb08faa9755c2e3284efa01f99393e18786cb132f1e66ebc6517318e1ce8a3b7337ebb54d035ab57d9706ecd9350d4afacd825e43c8668eece89819caf6817af62dc4fbd82f0e33f6647b2b6bda175f14607f59f4635451e6b27df282ef73d8700003000010002a30001080101030803010001acffb409bcc939f831f7a1e5ec88f7a59255ec53040be432027390a4ce896d6f9086f3c5e177fbfe118163aaec7af1462c47945944c4e2c026be5e98bbcded25978272e1e3e079c5094d573f0e83c92f02b32d3513b1550b826929c80dd0f92cac966d17769fd5867b647c3f38029abdc48152eb8f207159ecc5d232c7c1537c79f4b7ac28ff11682f21681bf6d6aba555032bf6f9f036beb2aaa5b3778d6eebfba6bf9ea191be4ab0caea759e2f773a1f9029c73ecb8d5735b9321db085f1b8e2d8038fe2941992548cee0d67dd4547e11dd63af9c9fc1c5466fb684cf009d7197c2cf79e792ab501e6a8a1ca519af2cb9b5f6367e94c0d47502451357be1b500003000010002a30001080100030803010001b6aec4b48567e2925a2d9c4fa4c96e6dddf86215a9bd8dd579c38ccb1199ed1be89946a7f72fc2633909a2792d0eed1b5afb2ee4c78d865a76d6cd9369d999c96af6be0a2274b8f2e9e0a0065bd20257570f08bc14c16f5616426881a83dbce6926e391c138a2ec317efa7349264de2e791c9b7d4a6048ee6eedf27bf1ece398ff0d229f18377cb1f6b98d1228ef217b8146c0c73851b89a6fc37c621ca187e16428a743ffea0072e185ef93e39525cee3ad01e0c94d2e511c8c313322c29ab91631e1856049a36898684c3056e5997473816fb547acb0be6e660bdfa89a5cb28b3669d8625f3f018c7b3b8a4860e774ee8261811ce7f96c461bc162c1a374f300002e00010002a3000113003008000002a30068a66180688ab2004f66007d114bf33e6d20e7588fdfde9a9e2a8ebd5593ce8bc06419e1d1a4afa8766abc1626504ea6243ca5fc9bce55a3f03c0e7eb488409690d84c3beb30f299da007ab69299d0e203c5b44b94328248768089a9b68982650e9ca2d37526307cbd9f06690e58c3c4a1703b1d1193ea0501417944b937b593294e339571eec42a3c700ce075d9c446e5d32bf979396c1c7935f69bd7df2826567fe582a3615d6095368be991b1e5dc39c0af3b0c39e0e353bbcb928e52e886193ac88da0cd8665ea992b642975aa5486f2e6514d0b5086c66ddd58fe8f7a27ecb128211e3ca1bdc4cad91f3cc9b63c526ebaea86075318e33bfc5a53c919e6f01262b24d178eb2cd04b1") +] + +VECTORS_BIP353_INVALID_PROOFS = [ + # Invalid proof + ("invalid.test._bitcoin-payment.example.com.", "deadbeefcafebabe"), + # This proof is missing the required NSEC3 record to prove the lack of an override to the wildcard CNAME entry. + ("a.x_domain_cname_wild.user._bitcoin-payment.dnssec_proof_tests.bitcoin.ninja.", "36612e785f646f6d61696e5f636e616d655f77696c6440646e737365635f70726f6f665f74657374732e626974636f696e2e6e696e6a61016113785f646f6d61696e5f636e616d655f77696c640475736572105f626974636f696e2d7061796d656e7412646e737365635f70726f6f665f746573747307626974636f696e056e696e6a6100000500010000001e002c046d6174740475736572105f626974636f696e2d7061796d656e740b6d617474636f72616c6c6f03636f6d00016113785f646f6d61696e5f636e616d655f77696c640475736572105f626974636f696e2d7061796d656e7412646e737365635f70726f6f665f746573747307626974636f696e056e696e6a6100002e00010000001e006100050d060000001e68a4c58868923b70d0e207626974636f696e056e696e6a6100849202c2306be651b9a5901331d9bf363e74ba758c16e993c4f93fe4c1a08e7bc46baa64b22e9540435b4b6b83fc4800c58d27bd9b2fe82d02ed6d6f9059cfa5046d6174740475736572105f626974636f696e2d7061796d656e740b6d617474636f72616c6c6f03636f6d000010000100000e1001ecff626974636f696e3a626331717a7477793678656e337a647474377a3076726761706d6a74667a3861636a6b6670356670376c3f6c6e6f3d6c6e6f317a7235717975677167736b726b37306b716d7571377633646e7232666e6d68756b7073396e386875743438766b7170716e736b743273767371776a616b70376b36707968746b7578773779326b716d73786c777275687a7176307a736e686839713374397868783339737563367173723037656b6d3565736479756d307736366d6e783876647175777670376470356a70376a337635637036616a3077333239666e6b7171763630713936737a356e6b726335723935716666783030327135337471646beb3878396d32746d7438356a74706d63796376666e727078336c723435683267376e6133736563377867756374667a7a636d386a6a71746a3579613237746536306a303376707430767139746d326e3979786c32686e67666e6d79676573613235733475347a6c78657771707670393478743772757234726878756e776b74686b39766c79336c6d356868307071763461796d6371656a6c6773736e6c707a776c6767796b6b616a7037796a73356a76723261676b79797063646c6a323830637934366a70796e73657a72636a326b7761326c797238787664366c666b706834787278746b327863336c7071046d6174740475736572105f626974636f696e2d7061796d656e740b6d617474636f72616c6c6f03636f6d00002e000100000e10006300100d0500000e1068a57e5a6892f44226480b6d617474636f72616c6c6f03636f6d004515e7d3cdf4137c316d4d36972fab9273df457789a9fabb16c4689e1644ae9080c7505e06daebc136d3c43054d0c69d185ac5616afca8998edf828e63a68286206f3266653035326d646a61336638746c3563353667706668646661696661687007626974636f696e056e696e6a6100002e00010000001e006100320d030000003c68a5076a68927d52d0e207626974636f696e056e696e6a6100620532890060f3f82a90a05ab32afe628ef58a58fbed98df2fbb1d3d00064b05111503f67cbd0f485f6196f007ddb3e6ec087e31f94221bf2b388179d31373b207626974636f696e056e696e6a6100003000010002a54400440100030dff753a27b08c3e48a642b210d6fcc444ff9ed4faf9c1241103db4ed3c19a95c3afbb52c0c02eb392ee048cc9e28ac2d272b1053bdc052bc18d5de05d7710196c07626974636f696e056e696e6a6100003000010002a54400440101030df65551925ce6321888e685981823d617fbc10f329bffe4081bf18c2372632a5548010bf62e6556a92722629275e0bd001e3d7837d325a353f6a851c5b965259607626974636f696e056e696e6a6100002e00010002a544006100300d0200093a806898c73468863d1c716c07626974636f696e056e696e6a610002fd1bf18c2ebb5ece5e28ca76bf30650695636c55633bccae8179c2d1ee7b97e78d188e08ffc869a8f67847bf557516ac12465ebea1acc281d6d636fdab612007626974636f696e056e696e6a6100002b000100000bf50024768a0d02ce46a9aff9a06e789c1bdfe250b0ef6ba8d39a53b2a3427c551f5ad375e059b607626974636f696e056e696e6a6100002b000100000bf50024716c0d023f7ad5a303e9c1cd1474b8df2ae56f3f82da8637ca55db4d9a2bb85960ca698e07626974636f696e056e696e6a6100002e000100000bf50099002b080200000e1068a89268688cd4d892dc056e696e6a61004d88897d7d5f5d920ca436e81ee67b1bb22f1a96309693454ab9d0b06434794162cc42846a4b51a40475a5b44e940c04dd1ac2c902a190ca2251ae89184520b0f3f22c12c51403d0a6e1cd95792e8f6f5036cd2d50dbdfa5eba3b2ee70ff78d40dc2eba2cc899af26954a55e5b5d344d478cabd9423418495caee45c550b11440b6d617474636f72616c6c6f03636f6d00003000010003d52200440100030d8b1cf07c86f18c19c8c3146db093893648dcc1ab5fb79e99ccbab4aa06f98d52ac27b92e215d9da98d7535f3c2ce038fbb9d41b9c63d3845d444feffc1f71ed70b6d617474636f72616c6c6f03636f6d00003000010003d52200440101030dec7c1fa1752495c42d2224eace96ed74144e9cb811608dd91594974bdc723fdc5b38a37c3340f1deca68a7ec82248822954b2994de5ac99ff6e9db95fd42c94b0b6d617474636f72616c6c6f03636f6d00003000010003d52200440100030dfd9dbc34cb5053a2c4a6b3d0dc60fc65d8a992dc1e080f6deeddba7fe6b25217730de64c9a1ce986b3f81f556881fe0e7b5b20c8ae381c4fefdbc311aa7d22ee0b6d617474636f72616c6c6f03636f6d00002e00010003d522006300300d0200093a80689c35ca6889abb2e2f50b6d617474636f72616c6c6f03636f6d0016857529219dd561091a51c72a27e25d963094f8dcd93b11f0917d45466251acb1402bb6fa4529bd20b7db697df429c4ba830685fc290ee5a8a937c2d34530c00b6d617474636f72616c6c6f03636f6d00002b00010001518000249f000d02594d2813e04a1d2660ff3c0afc5579b9ec0fe72cc206dc6f248bbe6dd652e1950b6d617474636f72616c6c6f03636f6d00002b0001000151800024e2f50d02f0e161567d468087ff27b051abc94476178a7cb635da1aa705e05c77ca81de520b6d617474636f72616c6c6f03636f6d00002e0001000151800057002b0d0200015180689a991368914e2b504103636f6d0070ac13ac37043e66f191553c554ee9307a91a4cf27bd724949e52032a2b464cadb776cd362918159344d9685b84650436449914f25fc62cc6dd6b761f5658283056e696e6a61000030000100000d2b00880100030803010001d28cb7e2bb163d5815838bedeca1006dde8551b379cb963c8a2cb42bc360127e3a5cf88ffc851a67414815b875f65d78c39b58d2fb29a1d4e76d50cb6b4a58a11fe2fb7c1b6db7bf7d72f5a1401e381c57fcc76f599cc73f05095d2bd14d9895e4fa1cff21bd760598a734b640102d11bc159c6b2ae73dbfd2741518142584d1056e696e6a61000030000100000d2b01080101030803010001c71e4c9dc49249a27b3ef42fcb8d56b4c3ac76715c7ad01c41d0d432590d15c3c62cee4b2d29ac35f2d72c9b32a70a0243cbd08aeebc9f6f1e0e63d9e1cdfe133c455a82435c0780b750012c942f7ed5b662eec8d9ae885a58993fa78d7561fedcd11d9e1c171acb02d0025837ba61a3c0a6756427414470c9ca0906b298168b5f4d9640e62e1b75dd06be664104ed32cfe447fa21f37401712c720a0dca4bb1bc20f3fa3103cad336bde20b16f73948e6b80dda0a528db536a958868c3870ecddcfb02dfb3cb4d22e2ad49a4b9f78c90ff6c7e10e301b3fb36fe859a94c30084660665741c14ff60d0535013fcf439840ecad82fc9278e4ace68ba95e708249056e696e6a61000030000100000d2b0088010003080301000197edb59d4f181e2761dd8d0465854339afc71fd89e47155981ddd175cdce79477552aeaf7b5a08fc4ac6025555f60582f2060e630edfb35b9c7cc30990fb9c3dc9f2fd036c962f67b94c9670d4ceaacd77973bca82ab7c9615f7e4320dda5b6d74dec673017c6fa448b5542a804e08ac873c509c1945ff734c320491e4b18e6d056e696e6a6100002e000100000d2b01190030080100000e1068a89268688cd4d8b402056e696e6a6100646e04cf5f76daa775ff98905ded3d2caad68d3198fc161340d558262c7ba7da28e7538a7f32d5a6485e4448037e8edf55759b7059fdc084f1017f790aa7c0d8f0e4af6c9af04e4afd16ce6052c30da59314c9c98075fd32bb77a81f34bea6190631b9d0a4351a764c14152ea217e6271647bf95b0df4c3d7cc00937bec01b65ad1b5209c976a4843e399f173f196a012c0876bc9afa3457d940d6ad7b9d50b3d496836d7342cf3aec75ac4785563e20574ddd2dab1cfb6b5da32508edf7186aa516b0451b221251652ee44353710cbe35928b79fff4293c5e8eff7ab371ce43595e2300305bda0108b911c3ef7d4d15c70d68686fddf28422d5fcbcdb967e84056e696e6a6100002b0001000151800024b4020802c8f816a7a575bdb2f997f682aab2653ba2cb5eddb69b036a30742a33befaf141056e696e6a6100002e0001000151800113002b08010001518068a404d06892d340b5690081d888ee9bd744185f24bfbaaae70172bb03b6c0a899dcd6801c44b2e3e6acd5fe2192f4f06e5690ace176cb08557f2d5ceef5e9d9eb72f4f639dcf34650eebdef5cb91cd546fbdbd8a529d7ef9bb2168160ae800790d7a7266b531174d8d9be06cad2f1d0f062d9431f7de4beceb02f30ddc97bba44535a703e2d3cc01d2341bb48eb7b25d938f0ed2a7d4c53818e7cb883dc14ffce5899504265869fd3133953c96f48b99ff2055acd53b9f69d2eb8a911bb1f05e2fb8d1d0cc391e1b9ae19fc32b00727a9d8551e1a09319ba94da44cc317bb66e8f372385560cdf8455f858d4afd602307d982d996f729a16c263efa9b8cebe7862b7f9a88ac9fbaf445a603636f6d00003000010000026600440100030df17b60fb56d522f8634153e785c0a978532ea76de39d34bb356d3d042e07f29f7b992176cc83acef7b78ea750425203b18f2b8228b3cdd2bc7eb13c3e3035a7e03636f6d00003000010000026600440101030db71f0465101ddbe2bf0c9455d12fa16c1cda44f4bf1ba2553418ad1f3aa9b06973f21b84eb532cf4035ee8d4832ca26d89306a7d32560c0cb0129d450ac1083503636f6d00002e000100000266005700300d0100015180689f3dfb688b764f4d0603636f6d00d87373f3d5d404f7cdc3aace633c96357bc606ced620792597e1308b1d6c5902668c4d3c1a37bab8486ba24485d57a9d98157fe706424b2043bfb40306d0148403636f6d00002b00010001518000244d060d028acbb0cd28f41250a80a491389424d341522d946b0da0c0291f2d3d771d7805a03636f6d00002e0001000151800113002b08010001518068a404d06892d340b5690024fbea2f86eb0e472114b9da41f1da69e4ef2d02ffcd6bdd987239f288795603d5f54308b238ef19f2747d6a1bc0e775782025561b0f14d1dd19be6bf36b495a3e2d538d5532c5c6e1056c06aa4e81de07f637313955a7761d313db0c6cd0b5c17b4ee22d530d6cdc0c4e195df85b354bbe32027537f1ca21f2815b2d0dbd154582124b3c4c27f1c3aea16f6bfe7e6f5c80aaccd61601bdd8f947ef5c30f004482617cf97629b8b89d8e7320719c979c6a9defdf79b743ecef08bdf2f90796da018fdc6be1e0629fe706044e0f8ad1b25927eca09074a502eb9136eefe5903ad3f97866ab014f1ec96e1f38e3aba66520a25ace386f4897e1ecfd774ce0a0f8a00003000010002a30001080101030803010001acffb409bcc939f831f7a1e5ec88f7a59255ec53040be432027390a4ce896d6f9086f3c5e177fbfe118163aaec7af1462c47945944c4e2c026be5e98bbcded25978272e1e3e079c5094d573f0e83c92f02b32d3513b1550b826929c80dd0f92cac966d17769fd5867b647c3f38029abdc48152eb8f207159ecc5d232c7c1537c79f4b7ac28ff11682f21681bf6d6aba555032bf6f9f036beb2aaa5b3778d6eebfba6bf9ea191be4ab0caea759e2f773a1f9029c73ecb8d5735b9321db085f1b8e2d8038fe2941992548cee0d67dd4547e11dd63af9c9fc1c5466fb684cf009d7197c2cf79e792ab501e6a8a1ca519af2cb9b5f6367e94c0d47502451357be1b500003000010002a30001080101030803010001af7a8deba49d995a792aefc80263e991efdbc86138a931deb2c65d5682eab5d3b03738e3dfdc89d96da64c86c0224d9ce02514d285da3068b19054e5e787b2969058e98e12566c8c808c40c0b769e1db1a24a1bd9b31e303184a31fc7bb56b85bbba8abc02cd5040a444a36d47695969849e16ad856bb58e8fac8855224400319bdab224d83fc0e66aab32ff74bfeaf0f91c454e6850a1295207bbd4cdde8f6ffb08faa9755c2e3284efa01f99393e18786cb132f1e66ebc6517318e1ce8a3b7337ebb54d035ab57d9706ecd9350d4afacd825e43c8668eece89819caf6817af62dc4fbd82f0e33f6647b2b6bda175f14607f59f4635451e6b27df282ef73d8700003000010002a30001080100030803010001b11b182a464c3adc6535aa59613bda7a61cac86945c20b773095941194f4b9f516e8bd924b1e50e3fe83918b51e54529d4e5a1e45303df8462241d5e05979979ae5bf9c6c598c08a496e17f3bd3732d5aebe62667b61db1bbe178f27ac99408165a230d6aee78348e6c67789541f845b2ada96667f8dd16ae44f9e260c4a138b3bb1015965ebe609434a06464bd7d29bac47c3017e83c0f89bca1a9e3bdd0813715f3484292df589bc632e27d37efc02837cb85d770d5bd53a36edc99a8294771aa93cf22406f5506c8cf850ed85c1a475dee5c2d3700b3f5631d903524b849995c20cb407ed411f70b428ae3d642716fe239335aa961a752e67fb6dca0bf72900003000010002a30001080100030803010001b6aec4b48567e2925a2d9c4fa4c96e6dddf86215a9bd8dd579c38ccb1199ed1be89946a7f72fc2633909a2792d0eed1b5afb2ee4c78d865a76d6cd9369d999c96af6be0a2274b8f2e9e0a0065bd20257570f08bc14c16f5616426881a83dbce6926e391c138a2ec317efa7349264de2e791c9b7d4a6048ee6eedf27bf1ece398ff0d229f18377cb1f6b98d1228ef217b8146c0c73851b89a6fc37c621ca187e16428a743ffea0072e185ef93e39525cee3ad01e0c94d2e511c8c313322c29ab91631e1856049a36898684c3056e5997473816fb547acb0be6e660bdfa89a5cb28b3669d8625f3f018c7b3b8a4860e774ee8261811ce7f96c461bc162c1a374f300002e00010002a3000113003008000002a30068a66180688ab2004f66007d114bf33e6d20e7588fdfde9a9e2a8ebd5593ce8bc06419e1d1a4afa8766abc1626504ea6243ca5fc9bce55a3f03c0e7eb488409690d84c3beb30f299da007ab69299d0e203c5b44b94328248768089a9b68982650e9ca2d37526307cbd9f06690e58c3c4a1703b1d1193ea0501417944b937b593294e339571eec42a3c700ce075d9c446e5d32bf979396c1c7935f69bd7df2826567fe582a3615d6095368be991b1e5dc39c0af3b0c39e0e353bbcb928e52e886193ac88da0cd8665ea992b642975aa5486f2e6514d0b5086c66ddd58fe8f7a27ecb128211e3ca1bdc4cad91f3cc9b63c526ebaea86075318e33bfc5a53c919e6f01262b24d178eb2cd04b1"), +] + +VECTORS_BIP353_VALID_PROOFS_INVALID_BIP353 = [ + # This proof contains two TXT records which start with, case insensitively, "bitcoin:". It is a valid DNSSEC proof but should fail BIP 353-specific validation. + ("invalid.user._bitcoin-payment.dnssec_proof_tests.bitcoin.ninja.", "07696e76616c69640475736572105f626974636f696e2d7061796d656e7412646e737365635f70726f6f665f746573747307626974636f696e056e696e6a6100001000010000001e003736426954634f694e3a3f42433d626331717a7477793678656e337a647474377a3076726761706d6a74667a3861636a6b6670356670376c07696e76616c69640475736572105f626974636f696e2d7061796d656e7412646e737365635f70726f6f665f746573747307626974636f696e056e696e6a6100001000010000001e003736626974636f696e3a3f62633d626331717a7477793678656e337a647474377a3076726761706d6a74667a3861636a6b6670356670376c07696e76616c69640475736572105f626974636f696e2d7061796d656e7412646e737365635f70726f6f665f746573747307626974636f696e056e696e6a6100002e00010000001e006100100d060000001e68a70b256894810dd0e207626974636f696e056e696e6a6100a7ed29b28f64a885e6496f080befc724da98afe8879a985c1f82b15dc6f97cf595b8a3b62edc58870291448a7f4017fba8c3e8380ab32a0883439fba965773ac07626974636f696e056e696e6a610000300001000161f300440100030dff753a27b08c3e48a642b210d6fcc444ff9ed4faf9c1241103db4ed3c19a95c3afbb52c0c02eb392ee048cc9e28ac2d272b1053bdc052bc18d5de05d7710196c07626974636f696e056e696e6a610000300001000161f300440101030df65551925ce6321888e685981823d617fbc10f329bffe4081bf18c2372632a5548010bf62e6556a92722629275e0bd001e3d7837d325a353f6a851c5b965259607626974636f696e056e696e6a6100002e0001000161f3006100300d0200093a806898c73468863d1c716c07626974636f696e056e696e6a610002fd1bf18c2ebb5ece5e28ca76bf30650695636c55633bccae8179c2d1ee7b97e78d188e08ffc869a8f67847bf557516ac12465ebea1acc281d6d636fdab612007626974636f696e056e696e6a6100002b000100000daa0024716c0d023f7ad5a303e9c1cd1474b8df2ae56f3f82da8637ca55db4d9a2bb85960ca698e07626974636f696e056e696e6a6100002b000100000daa0024768a0d02ce46a9aff9a06e789c1bdfe250b0ef6ba8d39a53b2a3427c551f5ad375e059b607626974636f696e056e696e6a6100002e000100000daa0099002b080200000e1068a89268688cd4d892dc056e696e6a61004d88897d7d5f5d920ca436e81ee67b1bb22f1a96309693454ab9d0b06434794162cc42846a4b51a40475a5b44e940c04dd1ac2c902a190ca2251ae89184520b0f3f22c12c51403d0a6e1cd95792e8f6f5036cd2d50dbdfa5eba3b2ee70ff78d40dc2eba2cc899af26954a55e5b5d344d478cabd9423418495caee45c550b1144056e696e6a61000030000100000daa01080101030803010001c71e4c9dc49249a27b3ef42fcb8d56b4c3ac76715c7ad01c41d0d432590d15c3c62cee4b2d29ac35f2d72c9b32a70a0243cbd08aeebc9f6f1e0e63d9e1cdfe133c455a82435c0780b750012c942f7ed5b662eec8d9ae885a58993fa78d7561fedcd11d9e1c171acb02d0025837ba61a3c0a6756427414470c9ca0906b298168b5f4d9640e62e1b75dd06be664104ed32cfe447fa21f37401712c720a0dca4bb1bc20f3fa3103cad336bde20b16f73948e6b80dda0a528db536a958868c3870ecddcfb02dfb3cb4d22e2ad49a4b9f78c90ff6c7e10e301b3fb36fe859a94c30084660665741c14ff60d0535013fcf439840ecad82fc9278e4ace68ba95e708249056e696e6a61000030000100000daa0088010003080301000197edb59d4f181e2761dd8d0465854339afc71fd89e47155981ddd175cdce79477552aeaf7b5a08fc4ac6025555f60582f2060e630edfb35b9c7cc30990fb9c3dc9f2fd036c962f67b94c9670d4ceaacd77973bca82ab7c9615f7e4320dda5b6d74dec673017c6fa448b5542a804e08ac873c509c1945ff734c320491e4b18e6d056e696e6a61000030000100000daa00880100030803010001d28cb7e2bb163d5815838bedeca1006dde8551b379cb963c8a2cb42bc360127e3a5cf88ffc851a67414815b875f65d78c39b58d2fb29a1d4e76d50cb6b4a58a11fe2fb7c1b6db7bf7d72f5a1401e381c57fcc76f599cc73f05095d2bd14d9895e4fa1cff21bd760598a734b640102d11bc159c6b2ae73dbfd2741518142584d1056e696e6a6100002e000100000daa01190030080100000e1068a89268688cd4d8b402056e696e6a6100646e04cf5f76daa775ff98905ded3d2caad68d3198fc161340d558262c7ba7da28e7538a7f32d5a6485e4448037e8edf55759b7059fdc084f1017f790aa7c0d8f0e4af6c9af04e4afd16ce6052c30da59314c9c98075fd32bb77a81f34bea6190631b9d0a4351a764c14152ea217e6271647bf95b0df4c3d7cc00937bec01b65ad1b5209c976a4843e399f173f196a012c0876bc9afa3457d940d6ad7b9d50b3d496836d7342cf3aec75ac4785563e20574ddd2dab1cfb6b5da32508edf7186aa516b0451b221251652ee44353710cbe35928b79fff4293c5e8eff7ab371ce43595e2300305bda0108b911c3ef7d4d15c70d68686fddf28422d5fcbcdb967e84056e696e6a6100002b0001000151800024b4020802c8f816a7a575bdb2f997f682aab2653ba2cb5eddb69b036a30742a33befaf141056e696e6a6100002e0001000151800113002b08010001518068a55650689424c0b569007f4d7bbce2d77380e115f8469a3c6d1d4bd9be1145eeee843332f1d54da134a25f7d259ccfa36a2e833106139ba37bc28651b7717dabfc8c450e889c5d5d6d39d12f1004252fa08692794af4a7f29a94f6611f9b000ba899d90d6a32e91c54e63720811546b251fb265b7f62615391ab330f83a96841f0367347e67522112f63f3efda891ae0f7265ed5b89673f184027cdb9b5e94b16a9b1f99877cc8e078abf246cad1f76a48a34474977dbd89dd3b41f381df89a82e2939aeb06830f656253e4e347cd04ee6dba0c8377e818f919f80a7e6aefa0f8196d04845bc26b498b0115022ff75e363711e55337de505e50df69b840699160467516d15e8235aa4cf00003000010002a30001080101030803010001acffb409bcc939f831f7a1e5ec88f7a59255ec53040be432027390a4ce896d6f9086f3c5e177fbfe118163aaec7af1462c47945944c4e2c026be5e98bbcded25978272e1e3e079c5094d573f0e83c92f02b32d3513b1550b826929c80dd0f92cac966d17769fd5867b647c3f38029abdc48152eb8f207159ecc5d232c7c1537c79f4b7ac28ff11682f21681bf6d6aba555032bf6f9f036beb2aaa5b3778d6eebfba6bf9ea191be4ab0caea759e2f773a1f9029c73ecb8d5735b9321db085f1b8e2d8038fe2941992548cee0d67dd4547e11dd63af9c9fc1c5466fb684cf009d7197c2cf79e792ab501e6a8a1ca519af2cb9b5f6367e94c0d47502451357be1b500003000010002a30001080100030803010001b6aec4b48567e2925a2d9c4fa4c96e6dddf86215a9bd8dd579c38ccb1199ed1be89946a7f72fc2633909a2792d0eed1b5afb2ee4c78d865a76d6cd9369d999c96af6be0a2274b8f2e9e0a0065bd20257570f08bc14c16f5616426881a83dbce6926e391c138a2ec317efa7349264de2e791c9b7d4a6048ee6eedf27bf1ece398ff0d229f18377cb1f6b98d1228ef217b8146c0c73851b89a6fc37c621ca187e16428a743ffea0072e185ef93e39525cee3ad01e0c94d2e511c8c313322c29ab91631e1856049a36898684c3056e5997473816fb547acb0be6e660bdfa89a5cb28b3669d8625f3f018c7b3b8a4860e774ee8261811ce7f96c461bc162c1a374f300003000010002a30001080100030803010001b11b182a464c3adc6535aa59613bda7a61cac86945c20b773095941194f4b9f516e8bd924b1e50e3fe83918b51e54529d4e5a1e45303df8462241d5e05979979ae5bf9c6c598c08a496e17f3bd3732d5aebe62667b61db1bbe178f27ac99408165a230d6aee78348e6c67789541f845b2ada96667f8dd16ae44f9e260c4a138b3bb1015965ebe609434a06464bd7d29bac47c3017e83c0f89bca1a9e3bdd0813715f3484292df589bc632e27d37efc02837cb85d770d5bd53a36edc99a8294771aa93cf22406f5506c8cf850ed85c1a475dee5c2d3700b3f5631d903524b849995c20cb407ed411f70b428ae3d642716fe239335aa961a752e67fb6dca0bf72900003000010002a30001080101030803010001af7a8deba49d995a792aefc80263e991efdbc86138a931deb2c65d5682eab5d3b03738e3dfdc89d96da64c86c0224d9ce02514d285da3068b19054e5e787b2969058e98e12566c8c808c40c0b769e1db1a24a1bd9b31e303184a31fc7bb56b85bbba8abc02cd5040a444a36d47695969849e16ad856bb58e8fac8855224400319bdab224d83fc0e66aab32ff74bfeaf0f91c454e6850a1295207bbd4cdde8f6ffb08faa9755c2e3284efa01f99393e18786cb132f1e66ebc6517318e1ce8a3b7337ebb54d035ab57d9706ecd9350d4afacd825e43c8668eece89819caf6817af62dc4fbd82f0e33f6647b2b6bda175f14607f59f4635451e6b27df282ef73d8700002e00010002a3000113003008000002a30068a66180688ab2004f66007d114bf33e6d20e7588fdfde9a9e2a8ebd5593ce8bc06419e1d1a4afa8766abc1626504ea6243ca5fc9bce55a3f03c0e7eb488409690d84c3beb30f299da007ab69299d0e203c5b44b94328248768089a9b68982650e9ca2d37526307cbd9f06690e58c3c4a1703b1d1193ea0501417944b937b593294e339571eec42a3c700ce075d9c446e5d32bf979396c1c7935f69bd7df2826567fe582a3615d6095368be991b1e5dc39c0af3b0c39e0e353bbcb928e52e886193ac88da0cd8665ea992b642975aa5486f2e6514d0b5086c66ddd58fe8f7a27ecb128211e3ca1bdc4cad91f3cc9b63c526ebaea86075318e33bfc5a53c919e6f01262b24d178eb2cd04b1") +] + +VECTORS_VALID_BIP353_PAYMENT_INFO = [ + {'uri': 'bitcoin:bc1qwthe43xeuasklclq4kvhreluv3hu92rzej42js', 'hrn': 'craig.user._bitcoin-payment.sparrowwallet.com.', 'uri_address': 'bc1qwthe43xeuasklclq4kvhreluv3hu92rzej42js', 'bc_addresses': [], 'silent_payment_addresses': []}, + {'uri': 'bitcoin:bc1qztwy6xen3zdtt7z0vrgapmjtfz8acjkfp5fp7l?lno=lno1zr5qyugqgskrk70kqmuq7v3dnr2fnmhukps9n8hut48vkqpqnskt2svsqwjakp7k6pyhtkuxw7y2kqmsxlwruhzqv0zsnhh9q3t9xhx39suc6qsr07ekm5esdyum0w66mnx8vdquwvp7dp5jp7j3v5cp6aj0w329fnkqqv60q96sz5nkrc5r95qffx002q53tqdk8x9m2tmt85jtpmcycvfnrpx3lr45h2g7na3sec7xguctfzzcm8jjqtj5ya27te60j03vpt0vq9tm2n9yxl2hngfnmygesa25s4u4zlxewqpvp94xt7rur4rhxunwkthk9vly3lm5hh0pqv4aymcqejlgssnlpzwlggykkajp7yjs5jvr2agkyypcdlj280cy46jpynsezrcj2kwa2lyr8xvd6lfkph4xrxtk2xc3lpq', 'hrn': 'matt.user._bitcoin-payment.mattcorallo.com.', 'uri_address': 'bc1qztwy6xen3zdtt7z0vrgapmjtfz8acjkfp5fp7l', 'bc_addresses': [], 'silent_payment_addresses': []}, + {'uri': 'bitcoin:1JBMattRztKDF2KRS3vhjJXA7h47NEsn2c', 'hrn': 'override.x_domain_cname_wild.user._bitcoin-payment.dnssec_proof_tests.bitcoin.ninja.', 'uri_address': '1JBMattRztKDF2KRS3vhjJXA7h47NEsn2c', 'bc_addresses': [], 'silent_payment_addresses': []}, + {'uri': 'bitcoin:?bc=bc1qztwy6xen3zdtt7z0vrgapmjtfz8acjkfp5fp7l', 'hrn': 'simple.user._bitcoin-payment.dnssec_proof_tests.bitcoin.ninja.', 'bc_addresses': ["bc1qztwy6xen3zdtt7z0vrgapmjtfz8acjkfp5fp7l"], 'silent_payment_addresses': []}, +] + +class Bip353Test(TestCase): + def test_bip353(self): + for i, (hrn, proof) in enumerate(VECTORS_BIP353_VALID_PROOFS): + result = bip353.verify_dns_proof(hrn, bytes.fromhex(proof)) + # Verify the proof is valid + self.assertNotIn("error", result) + self.assertIn("verified_rrs", result) + self.assertTrue(len(result["verified_rrs"]) > 0) + + # Extract and verify the Bitcoin address + info = bip353.get_payment_info_from_hrn(hrn, bytes.fromhex(proof)) + expected_info = VECTORS_VALID_BIP353_PAYMENT_INFO[i] + self.assertEqual(info, expected_info) + + def test_bip353_invalid_proof(self): + for hrn, invalid_proof in VECTORS_BIP353_INVALID_PROOFS: + result = bip353.verify_dns_proof(hrn, bytes.fromhex(invalid_proof)) + # Verify the proof is invalid + self.assertIn("error", result) + + # Verify that get_payment_info_from_hrn raises an exception + with self.assertRaises(Exception): + bip353.get_payment_info_from_hrn(hrn, bytes.fromhex(invalid_proof)) + + def test_bip353_valid_proofs_invalid_bip353(self): + for hrn, invalid_proof in VECTORS_BIP353_VALID_PROOFS_INVALID_BIP353: + result = bip353.verify_dns_proof(hrn, bytes.fromhex(invalid_proof)) + # Verify the proof is valid + self.assertNotIn("error", result) + self.assertIn("verified_rrs", result) + self.assertTrue(len(result["verified_rrs"]) > 0) + + # Verify that get_payment_info_from_hrn raises an exception + with self.assertRaises(Exception): + bip353.get_payment_info_from_hrn(hrn, bytes.fromhex(invalid_proof)) \ No newline at end of file