GAP-M4: BIP-322 Signature Format Standardization¶
EPCC Implementation Plan¶
Version: 1.1.0 | Created: 2025-12-27 | Author: Claude Code Status: ✅ IMPLEMENTED | Priority: P0 (Critical Path) Estimate: 8-12 hours | Dependencies: None (unlocks GAP-Q1, GAP-Q2)
Executive Summary¶
This plan implements BIP-322 Generic Signed Message Format for the AEGIS override workflow, replacing the current Ed25519-based signature scheme with a Bitcoin-native approach using BIP-340 Schnorr signatures. This is a critical path item that unlocks post-quantum hardening (GAP-Q1, GAP-Q2).
Key Decisions¶
| Decision | Choice | Rationale |
|---|---|---|
| Primary Library | btclib | 100% test coverage, BIP-340 support, MIT license, academic rigor |
| Signature Algorithm | BIP-340 Schnorr on secp256k1 | Bitcoin-native, aggregatable, BIP-322 required |
| Migration Strategy | Hybrid with deprecation path | Backward compatibility, gradual rollout |
| Message Format | Simple BIP-322 | Sufficient for dual-key override, simpler than Full |
1. Research & Validation¶
1.1 BIP-322 Specification Analysis¶
Source: BIP-322 Official Specification
BIP-322 defines three signature formats: 1. Legacy: P2PKH only (backward compatible) 2. Simple: Witness stack, base64-encoded (our target) 3. Full: Complete transaction serialization
Key Technical Requirements:
Message Hash = SHA256_tag("BIP0322-signed-message", message)
= SHA256(SHA256("BIP0322-signed-message") || SHA256("BIP0322-signed-message") || message)
Virtual Transaction Structure:
to_spend:
nVersion = 0
vin[0].scriptSig = OP_0 PUSH32[message_hash]
vout[0].scriptPubKey = message_challenge (address)
to_sign:
vin[0].prevout = to_spend.txid:0
vin[0].scriptWitness = message_signature
vout[0].scriptPubKey = OP_RETURN
1.2 Current Implementation Gap¶
| Aspect | Current (Ed25519) | Required (BIP-322) |
|---|---|---|
| Curve | Curve25519 | secp256k1 |
| Algorithm | EdDSA | Schnorr (BIP-340) |
| Message Format | JSON → SHA-256 | BIP-340 tagged hash |
| Signature Size | 64 bytes | 64 bytes |
| Key Size | 32 bytes | 32 bytes (x-only pubkey) |
| Bitcoin Compatible | No | Yes |
| Post-Quantum Hybrid Ready | Via liboqs | Via liboqs |
1.3 Python Library Evaluation¶
| Library | BIP-340 | BIP-322 | Test Coverage | Maintenance | Verdict |
|---|---|---|---|---|---|
| btclib | ✅ Full | ❌ Partial | 100% | Active (2023) | Selected |
| python-bitcoinlib | ✅ | ❌ | ~80% | Moderate | Backup |
| bitcoinlib | ✅ | ❌ | ~90% | Active | Alternative |
| secp256k1-py | ✅ (low-level) | ❌ | 95% | Active | Low-level only |
Selected: btclib>=2023.7.12 for BIP-340 Schnorr, with custom BIP-322 wrapper.
1.4 Industry Validation¶
Bitcoin Core Status (as of 2025): - PR #24058: Basic BIP-322 support merged - PR #16440: Generic signmessage ongoing - BIP Status: Draft (actively developed)
Production Implementations: - rust-bitcoin/bip322: Rust reference (v0.0.11) - bip322-js: JavaScript implementation (v3.0.0) - LegReq/bip0322-signatures: Python Jupyter demo
2. Architecture Overview¶
2.1 High-Level Design¶
┌─────────────────────────────────────────────────────────────────────────────┐
│ Signature Provider Interface │
│ (Abstract Base Class) │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────┐ ┌─────────────────────┐ ┌─────────────────────┐ │
│ │ Ed25519Provider │ │ BIP322Provider │ │ HybridPQProvider │ │
│ │ (Legacy/Fallback) │ │ (BIP-340 Schnorr) │ │ (Future: GAP-Q1) │ │
│ └─────────────────────┘ └─────────────────────┘ └─────────────────────┘ │
│ │ │ │ │
│ └────────────────────────┼────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────┐ │
│ │ DualSignatureValidator │ │
│ │ (Updated: Provider-based) │ │
│ └───────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────┐ │
│ │ OverrideWorkflow │ │
│ │ (Unchanged API) │ │
│ └───────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
2.2 Component Interactions¶
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Risk Lead │ │ Security Lead│ │ Proposer │
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘
│ │ │
│ sign(message) │ sign(message) │ request_override()
▼ ▼ ▼
┌──────────────────────────────────────────────────────────┐
│ OverrideWorkflow │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ DualSignatureValidator │ │
│ │ ┌─────────────────────┐ ┌────────────────────────┐ │ │
│ │ │ create_message_hash │ │ validate_signature │ │ │
│ │ │ (BIP-340 tagged) │ │ (provider-delegated) │ │ │
│ │ └─────────────────────┘ └────────────────────────┘ │ │
│ │ │ │ │ │
│ │ ▼ ▼ │ │
│ │ ┌─────────────────────────────────────────────────┐│ │
│ │ │ SignatureProvider (interface) ││ │
│ │ │ ┌──────────────┐ ┌──────────────────────────┐ ││ │
│ │ │ │ Ed25519 │ │ BIP322Provider │ ││ │
│ │ │ │ (deprecated) │ │ • sign(key, msg_hash) │ ││ │
│ │ │ │ │ │ • verify(sig, msg, pk) │ ││ │
│ │ │ │ │ │ • generate_keypair() │ ││ │
│ │ │ └──────────────┘ └──────────────────────────┘ ││ │
│ │ └─────────────────────────────────────────────────┘│ │
│ └─────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────┘
2.3 Technology Stack¶
| Layer | Technology | Version | Purpose |
|---|---|---|---|
| Crypto Core | btclib | ≥2023.7.12 | BIP-340 Schnorr, secp256k1 |
| Fallback | cryptography | ≥41.0.0 | Ed25519 (existing) |
| Serialization | base64, json | stdlib | Message encoding |
| Hashing | hashlib | stdlib | SHA-256, tagged hashes |
| Future PQ | liboqs-python | ≥0.9.0 | ML-DSA (GAP-Q1) |
3. Implementation Strategy (EPCC Plan)¶
3.1 Phase 1: Foundation (2-3 hours)¶
E: Estimate¶
- Create
SignatureProviderabstract base class - Implement BIP-340 tagged hash function
- Add btclib dependency
P: Plan¶
File: src/crypto/__init__.py (new module)
"""Cryptographic primitives for AEGIS."""
from .providers import SignatureProvider, Ed25519Provider, BIP322Provider
from .bip340 import tagged_hash, BIP322_TAG
File: src/crypto/bip340.py
"""BIP-340 tagged hash implementation."""
import hashlib
BIP322_TAG = b"BIP0322-signed-message"
def tagged_hash(tag: bytes, message: bytes) -> bytes:
"""
Compute BIP-340 tagged hash.
H_tag(m) = SHA256(SHA256(tag) || SHA256(tag) || m)
Args:
tag: Tag bytes (e.g., BIP322_TAG)
message: Message to hash
Returns:
32-byte tagged hash
"""
tag_hash = hashlib.sha256(tag).digest()
return hashlib.sha256(tag_hash + tag_hash + message).digest()
File: src/crypto/providers.py
"""Signature provider interface and implementations."""
from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Protocol, Tuple
@dataclass
class SignatureResult:
"""Result of a signing operation."""
signature: bytes
public_key: bytes
algorithm: str
message_hash: bytes
class SignatureProvider(Protocol):
"""Protocol for signature providers."""
@property
def algorithm_name(self) -> str:
"""Return algorithm identifier (e.g., 'bip322', 'ed25519')."""
...
def generate_keypair(self) -> Tuple[bytes, bytes]:
"""Generate (private_key, public_key) pair."""
...
def sign(self, message_hash: bytes, private_key: bytes) -> bytes:
"""Sign message hash with private key."""
...
def verify(self, signature: bytes, message_hash: bytes, public_key: bytes) -> bool:
"""Verify signature against message hash and public key."""
...
C: Commit¶
- [ ] Create
src/crypto/module structure - [ ] Implement
tagged_hash()with BIP-322 test vectors - [ ] Define
SignatureProviderprotocol - [ ] Add unit tests for tagged hash
C: Check¶
- [ ] Test vectors from BIP-322 spec pass:
""→c90c269c4f8fcbe6880f72a721ddfbf1914268a794cbb21cfafee13770ae19f1"Hello World"→f0eb03b1a75ac6d9847f55c624a99169b5dccba2a31f5b23bea77ba270de0a7a
3.2 Phase 2: BIP-322 Provider (3-4 hours)¶
E: Estimate¶
- Implement
BIP322Providerusing btclib - Support Simple format (witness stack)
- Integrate with secp256k1 curve
P: Plan¶
File: src/crypto/bip322_provider.py
"""BIP-322 signature provider using btclib."""
import base64
from typing import Tuple
from btclib.ecc import Curve
from btclib.bip340 import sign as schnorr_sign, verify as schnorr_verify
from btclib.hashes import tagged_hash as btclib_tagged_hash
from .providers import SignatureProvider
from .bip340 import BIP322_TAG, tagged_hash
# secp256k1 curve for Bitcoin
SECP256K1 = Curve.secp256k1()
class BIP322Provider:
"""
BIP-322 Simple signature provider.
Uses BIP-340 Schnorr signatures on secp256k1 curve.
Implements the Simple format (witness stack, base64-encoded).
Security Properties:
- 128-bit security level
- Non-malleable (x-only pubkeys)
- Batch-verifiable (future optimization)
- Compatible with Bitcoin Taproot
"""
@property
def algorithm_name(self) -> str:
return "bip322-simple"
def generate_keypair(self) -> Tuple[bytes, bytes]:
"""
Generate secp256k1 keypair for BIP-340 signing.
Returns:
(private_key, x_only_public_key) as 32-byte each
"""
from btclib.ecc import SSA
# Generate random private key
private_key = SSA.gen_keys()
# Get x-only public key (BIP-340 format)
x_only_pubkey = private_key.pubkey.x.to_bytes(32, 'big')
return private_key.key.to_bytes(32, 'big'), x_only_pubkey
def create_message_hash(
self,
proposal_id: str,
justification: str,
failed_gates: list[str],
) -> bytes:
"""
Create BIP-322 message hash for signing.
Uses BIP-340 tagged hash with "BIP0322-signed-message" tag.
Args:
proposal_id: Proposal identifier
justification: Override justification
failed_gates: List of failed gate names
Returns:
32-byte message hash
"""
import json
canonical = json.dumps(
{
"proposal_id": proposal_id,
"justification": justification,
"failed_gates": sorted(failed_gates),
"version": "1.0",
},
sort_keys=True,
)
return tagged_hash(BIP322_TAG, canonical.encode())
def sign(self, message_hash: bytes, private_key: bytes) -> bytes:
"""
Sign message hash with BIP-340 Schnorr.
Args:
message_hash: 32-byte message hash
private_key: 32-byte secp256k1 private key
Returns:
64-byte Schnorr signature
"""
return schnorr_sign(message_hash, private_key)
def verify(
self,
signature: bytes,
message_hash: bytes,
public_key: bytes,
) -> bool:
"""
Verify BIP-340 Schnorr signature.
Args:
signature: 64-byte signature
message_hash: 32-byte message hash
public_key: 32-byte x-only public key
Returns:
True if valid, False otherwise
"""
try:
return schnorr_verify(message_hash, public_key, signature)
except Exception:
return False
def encode_simple(self, signature: bytes) -> str:
"""
Encode signature in BIP-322 Simple format.
Simple format: base64(witness_stack)
For single-sig, witness = [signature]
Args:
signature: 64-byte Schnorr signature
Returns:
Base64-encoded witness stack
"""
# Witness stack: length-prefixed signature
# For simple single-sig: just the signature
witness = bytes([len(signature)]) + signature
return base64.b64encode(witness).decode('ascii')
def decode_simple(self, encoded: str) -> bytes:
"""
Decode BIP-322 Simple format to raw signature.
Args:
encoded: Base64-encoded witness stack
Returns:
64-byte Schnorr signature
"""
witness = base64.b64decode(encoded)
# Extract signature (skip length prefix)
sig_len = witness[0]
return witness[1:1+sig_len]
C: Commit¶
- [ ] Add
btclib>=2023.7.12to requirements.txt - [ ] Implement
BIP322Providerclass - [ ] Add
encode_simple()/decode_simple()for wire format - [ ] Unit tests with BIP-322 test vectors
C: Check¶
- [ ] Test vector validation:
3.3 Phase 3: Provider Integration (2-3 hours)¶
E: Estimate¶
- Refactor
DualSignatureValidatorto use providers - Maintain backward compatibility with Ed25519
- Add provider selection configuration
P: Plan¶
File: src/workflows/override.py (modifications)
# Add at top of file
from src.crypto.providers import SignatureProvider
from src.crypto.bip322_provider import BIP322Provider
from src.crypto.ed25519_provider import Ed25519Provider # Existing, wrapped
class DualSignatureValidator:
"""
Validate dual signatures for override authorization.
Supports multiple signature providers:
- BIP322Provider: BIP-340 Schnorr (recommended)
- Ed25519Provider: Ed25519 (legacy, deprecated)
Provider is selected at initialization and applies to all operations.
"""
REQUIRED_ROLES = {"risk_lead", "security_lead"}
def __init__(
self,
expiration_hours: int = 24,
provider: SignatureProvider | None = None,
):
"""
Initialize validator with signature provider.
Args:
expiration_hours: Hours until override request expires
provider: Signature provider (default: BIP322Provider)
"""
self.expiration_hours = expiration_hours
self.provider = provider or BIP322Provider()
def create_message_hash(
self,
proposal_id: str,
justification: str,
failed_gates: list[str],
) -> str:
"""
Create canonical message hash for signing.
Uses provider-specific hash algorithm:
- BIP322: BIP-340 tagged hash
- Ed25519: SHA-256 of JSON
Returns:
Hex-encoded message hash
"""
if hasattr(self.provider, 'create_message_hash'):
return self.provider.create_message_hash(
proposal_id, justification, failed_gates
).hex()
else:
# Fallback for legacy providers
return self._legacy_message_hash(
proposal_id, justification, failed_gates
)
def validate_signature(
self,
signature: str,
message_hash: str,
public_key: str,
) -> bool:
"""
Validate signature using configured provider.
Args:
signature: Base64-encoded signature
message_hash: Hex-encoded message hash
public_key: Base64-encoded public key
Returns:
True if valid
"""
try:
sig_bytes = base64.b64decode(signature)
msg_bytes = bytes.fromhex(message_hash)
key_bytes = base64.b64decode(public_key)
return self.provider.verify(sig_bytes, msg_bytes, key_bytes)
except Exception as e:
logger.warning(f"Signature validation failed: {e}")
return False
C: Commit¶
- [ ] Wrap existing Ed25519 code as
Ed25519Provider - [ ] Refactor
DualSignatureValidatorfor provider injection - [ ] Add configuration for provider selection
- [ ] Update existing tests to use provider abstraction
C: Check¶
- [ ] All 51 existing override tests pass
- [ ] New BIP-322 provider tests pass
- [ ] Ed25519 backward compatibility verified
3.4 Phase 4: Migration & Documentation (1-2 hours)¶
E: Estimate¶
- Add deprecation warnings for Ed25519
- Update specification documentation
- Create migration guide
P: Plan¶
Deprecation Strategy:
import warnings
class Ed25519Provider:
def __init__(self):
warnings.warn(
"Ed25519Provider is deprecated and will be removed in v2.0. "
"Use BIP322Provider for new implementations.",
DeprecationWarning,
stacklevel=2
)
Configuration (schema/interface-contract.yaml update):
signature_provider:
type: string
enum: ["bip322", "ed25519"]
default: "bip322"
description: "Signature algorithm for override workflow"
deprecated_values:
ed25519: "Deprecated in v1.2, removed in v2.0"
C: Commit¶
- [ ] Add deprecation warnings to Ed25519Provider
- [ ] Update interface-contract.yaml schema
- [ ] Create migration guide in docs/
- [ ] Update CLAUDE.md with new cryptographic standards
C: Check¶
- [ ] Documentation complete
- [ ] Deprecation warnings appear in logs
- [ ] Migration path clear and tested
4. Technical Excellence¶
4.1 Design Patterns¶
| Pattern | Application | Source |
|---|---|---|
| Strategy | SignatureProvider interface | Gang of Four |
| Factory | Provider instantiation based on config | Python best practices |
| Adapter | Wrapping btclib API | Integration patterns |
4.2 Performance Considerations¶
| Operation | Ed25519 | BIP-340 Schnorr | Notes |
|---|---|---|---|
| Key Generation | ~50μs | ~80μs | Acceptable |
| Sign | ~70μs | ~100μs | Acceptable |
| Verify | ~150μs | ~200μs | Acceptable |
| Batch Verify (n) | O(n) | O(n/2) | BIP-340 advantage |
Benchmark: Both algorithms well under 1ms per operation, no performance concerns.
4.3 Security Measures¶
| Measure | Implementation |
|---|---|
| Constant-time verification | btclib uses libsecp256k1 FFI |
| Side-channel resistance | Not for production per btclib disclaimer |
| Key storage | Delegated to external KMS (unchanged) |
| Audit logging | All validation attempts logged |
4.4 Test Coverage Requirements¶
| Category | Target | Method |
|---|---|---|
| Unit tests | 100% | pytest with coverage |
| BIP-322 vectors | 100% | Official test vectors |
| Integration | Override workflow E2E | pytest-asyncio |
| Security | Injection/malformed inputs | Fuzzing |
5. Development Workflow¶
5.1 Setup¶
# Add new dependency
echo "btclib>=2023.7.12" >> requirements.txt
pip install -r requirements.txt
# Run existing tests (ensure no breakage)
pytest tests/workflows/test_override.py -v
# Create new test file
touch tests/crypto/test_bip322.py
5.2 TDD Approach¶
# tests/crypto/test_bip322.py
import pytest
from src.crypto.bip340 import tagged_hash, BIP322_TAG
class TestBIP340TaggedHash:
"""BIP-322 test vectors from specification."""
def test_empty_message(self):
result = tagged_hash(BIP322_TAG, b"")
expected = bytes.fromhex(
"c90c269c4f8fcbe6880f72a721ddfbf1914268a794cbb21cfafee13770ae19f1"
)
assert result == expected
def test_hello_world(self):
result = tagged_hash(BIP322_TAG, b"Hello World")
expected = bytes.fromhex(
"f0eb03b1a75ac6d9847f55c624a99169b5dccba2a31f5b23bea77ba270de0a7a"
)
assert result == expected
5.3 CI Integration¶
# Add to existing CI
- name: Test BIP-322 module
run: |
pytest tests/crypto/ -v --cov=src/crypto
pytest tests/workflows/test_override.py -v
6. Success Criteria & Metrics¶
6.1 Functional Requirements¶
| Requirement | Metric | Target |
|---|---|---|
| BIP-322 test vectors | Pass rate | 100% |
| Override workflow | E2E tests | All passing |
| Ed25519 compatibility | Existing tests | No regression |
| Provider switching | Config-based | Working |
6.2 Quality Gates¶
| Gate | Threshold | Tool |
|---|---|---|
| Test coverage | ≥90% | pytest-cov |
| Type checking | 0 errors | mypy --strict |
| Linting | 0 violations | ruff |
| Security scan | 0 high/critical | bandit |
6.3 Performance Benchmarks¶
| Operation | Target | Measurement |
|---|---|---|
| Sign latency | <1ms p99 | pytest-benchmark |
| Verify latency | <1ms p99 | pytest-benchmark |
| Memory overhead | <1MB | memory_profiler |
7. Risk Mitigation¶
7.1 Identified Risks¶
| Risk | Probability | Impact | Mitigation |
|---|---|---|---|
| btclib API breaking changes | Low | Medium | Pin version, monitor releases |
| BIP-322 spec changes | Low | High | Track BIP status, draft tolerance |
| Ed25519 migration resistance | Medium | Low | Long deprecation window |
| Performance regression | Low | Medium | Benchmark before/after |
7.2 Rollback Plan¶
- Provider abstraction allows instant rollback to Ed25519
- Configuration flag:
signature_provider: ed25519 - No data migration needed (signatures remain valid)
8. Dependencies & Prerequisites¶
8.1 New Dependencies¶
8.2 Unlocked by This Implementation¶
| Gap | Description | Dependency |
|---|---|---|
| GAP-Q1 | Post-Quantum Signatures | Uses BIP-322 as base layer |
| GAP-Q2 | Post-Quantum Encryption | Uses signature infrastructure |
| GAP-C2 | Override Mechanism | Full Bitcoin compatibility |
9. File Changes Summary¶
| File | Change Type | Description |
|---|---|---|
src/crypto/__init__.py | New | Module init, exports |
src/crypto/bip340.py | New | Tagged hash implementation |
src/crypto/providers.py | New | Provider protocol definition |
src/crypto/bip322_provider.py | New | BIP-322 provider |
src/crypto/ed25519_provider.py | New | Ed25519 wrapper (deprecated) |
src/workflows/override.py | Modified | Provider injection |
tests/crypto/test_bip340.py | New | Tagged hash tests |
tests/crypto/test_bip322_provider.py | New | Provider tests |
tests/workflows/test_override.py | Modified | Provider-aware tests |
requirements.txt | Modified | Add btclib |
schema/interface-contract.yaml | Modified | Provider config |
10. References¶
10.1 Specifications¶
10.2 Libraries¶
10.3 Internal Documents¶
Changelog¶
| Version | Date | Author | Changes |
|---|---|---|---|
| 1.0.0 | 2025-12-27 | Claude Code | Initial EPCC plan with full research validation |