asgayapedia

Manual Covenant Construction

Status: ⚠️ Implemented but not validated - moved to WebView approach (August 2026)
Platform: Android (Kotlin)
Originally Targeted: CashScript v0.13+ (P2SH32 format)


Why This Matters

The 367-byte principle: A static bytecode template plus 8 constructor parameters replaces an entire CashScript runtime. This is the covenant simplicity principle made tangible.

What this proves: Covenants are portable. They exist as pure cryptographic constructions—hash functions, script opcodes, and addresses. Any platform that can hash and encode can create covenants. No JavaScript runtime, no WebView, no external dependencies.

Architectural significance:

  1. User sovereignty: Covenant construction happens entirely on-device. No external service knows what covenant you created, when you created it, or who it’s for.

  2. Portability: The same construction logic works on Android, iOS, desktop, hardware wallets, or server-side validators. It’s just bytes and hashing.

  3. Auditability: The entire covenant constructor is ~800 lines of readable Kotlin. A security auditor can verify every step without understanding CashScript internals.

  4. Future-proofing: If Asgaya succeeds (hundreds of users), we can optimize further—native secp256k1, custom ECDSA, or even hardware acceleration. The architecture supports it.

This is not just an implementation detail. It’s proof that decentralized payment covenants can exist on consumer devices without intermediation.


The Construction Process

A Bitcoin Cash covenant is a P2SH32 script with this structure:

[Constructor Parameters] + [Bytecode Template] → SHA256 → CashAddr P2SH32 Address

Step 1: Encode Constructor Parameters

Inputs (v2.5):

  1. senderPubkey - 33 bytes (compressed public key)
  2. recipientPubkey - 33 bytes
  3. sellerPubkey - 33 bytes
  4. oraclePubkey - 33 bytes
  5. eurCents - int (e.g., 10000 = €100.00) — payment amount in cents
  6. expiryOracleTime - int (Unix timestamp)
  7. initialBchPriceInCents - int (e.g., 65000 = €650/BCH) — BCH price in EUR cents
  8. minPricePercent - int (e.g., 93 = 7% drop tolerance)

Bitcoin Script encoding rules:

Result: 150 bytes of encoded parameters

Step 2: Append Bytecode Template

The v2.5 bytecode template is 367 bytes of Bitcoin Script opcodes compiled from the PriceOracle contract.

Source: price-oracle-v2.5.json artifact (compiled July 27, 2026)

This bytecode is static and shared across all v2.5 covenants. It implements:

Complete script: 150 bytes (params) + 367 bytes (bytecode) = 517 bytes

Step 3: Hash with SHA256 (P2SH32)

CashScript v0.13+ uses P2SH32 format:

Why P2SH32?

Result: 32-byte script hash (e.g., c47fc431...)

Step 4: Encode as CashAddr Address

CashAddr P2SH32 format:

Example output:

bchtest:vzrz8l3p3f62ungalsuxvkv7el37v8ayvn8647mvm056xafn3lwly7x4hucsr2

This is the address you fund to create a covenant. The 32-byte hash uniquely identifies the covenant script.


Implementation (AsgayaHusk)

File Structure

app/src/main/java/com/asgaya/husk/covenant/
├── CovenantBuilder.kt         # Core construction logic
├── CovenantConstants.kt       # v2.5 bytecode + oracle params
├── CashAddrEncoder.kt         # P2SH32 address formatting
└── CovenantTest.kt            # Validation tests

Dependencies

BouncyCastle for Bitcoin crypto:

implementation("org.bouncycastle:bcprov-jdk18on:1.78")

Android’s standard library lacks Bitcoin-specific algorithms (RIPEMD160, secp256k1). BouncyCastle provides them.

Note: Android 14+ (API 34+) includes some BouncyCastle algorithms natively, but the explicit dependency ensures consistent behavior across all API levels (AsgayaHusk targets API 26+).

Usage Example

// Define covenant parameters
val params = CovenantBuilder.CovenantParams(
    senderPubkey = senderKey.publicKey,
    recipientPubkey = recipientKey.publicKey,
    sellerPubkey = sellerKey.publicKey,
    oraclePubkey = fetchOraclePubkey(),  // From Pi-chan oracle /oracle/info (no hardcoded keys since Aug 16)
    eurCents = 10000,                  // €100.00
    expiryOracleTime = currentTime + 8.hours,
    initialBchPriceInCents = 65000,    // €650/BCH
    minPricePercent = 93               // 7% drop tolerance
)

// Build covenant
val builder = CovenantBuilder()
val result = builder.buildCovenant(params)

// Result contains:
// - result.address: "bchtest:v..."
// - result.scriptHash: [32 bytes]
// - result.redeemScript: [517 bytes]

⚠️ Note: This API exists in the codebase but was not validated to produce correct covenant addresses. Production covenant creation currently uses the WebView Covenant Bridge.

Validation

Cross-platform verification:

# Android builds covenant
adb logcat | grep CovenantBuilder
# Script hash: c47fc431...

# CashScript builds same covenant
node validate-android-covenant.mjs
# Script hash: c47fc431... ✅ MATCH

If script hashes match, construction is correct. Address format differences (Android vzrz... vs CashScript pvp8...) are cosmetic—both encode the same 32-byte hash.


Why Not Use CashScript Directly?

The arguments below describe why manual construction remains the long-term goal. For Phase 0, we chose WebView + CashScript for speed of validation—see Reality Check.


We could embed a JavaScript runtime (WebView) and call CashScript. But:

  1. Security surface: A JavaScript engine is 10MB+ of attack surface. Our manual constructor is <1KB of auditable code.

  2. Performance: JavaScript initialization takes 100-500ms. Manual construction takes <50ms.

  3. Dependency risk: CashScript updates could break our app. Static bytecode is frozen—v2.5 covenants work forever.

  4. Platform lock-in: WebView ties us to platforms with JavaScript support. Manual construction works anywhere (iOS, hardware wallets, embedded systems).

  5. User sovereignty: No one can force us to change covenant construction logic. The bytecode is copied, not fetched.

The trade-off: We maintain our own covenant constructor. But it’s 800 lines of simple code, tested against CashScript output. That’s acceptable overhead for the benefits.


Security Considerations

What’s Safe

Script hash validation: If our hash matches CashScript’s hash, the covenant is correct
Static bytecode: v2.5 bytecode is frozen, can’t change unexpectedly
On-device construction: No external service sees your covenant parameters
Deterministic output: Same params always produce same address

What Needs Care

⚠️ Private key storage: WIF keys stored in SharedPreferences (plaintext for testing)
⚠️ Parameter validation: Malformed params produce invalid covenants
⚠️ Oracle pubkey: Hardcoded in CovenantConstants (must match Pi-chan’s oracle)
⚠️ Network flag: IS_TESTNET toggle—double-check before mainnet

Production TODO:


Limitations

What this does NOT do:

  1. Transaction construction: Building the funding transaction requires UTXO selection, fee calculation, and signing. That’s the next layer (see Asgaya Trinity).

  2. Covenant claiming: Unlocking a covenant requires constructing witness scripts with oracle signatures. Also next layer.

  3. Parameter negotiation: This assumes you already know recipient’s pubkey, seller’s pubkey, current BCH price, and agreed EUR amount. Coordination is separate (Telegram, Nostr, or bulletin board).

  4. Bytecode compilation: We use pre-compiled v2.5 bytecode. If covenant logic changes (v3.0), we need a new artifact.

Scope boundary: This module does ONE thing—turn covenant parameters into a P2SH32 address. Everything before (parameter negotiation) and after (transaction construction) is handled elsewhere.


Versioning

Current: v2.5 (July 27, 2026)

If covenant logic changes:

  1. RaspberryPi compiles new CashScript contract
  2. Extract bytecode hex from artifact
  3. Update CovenantConstants.BYTECODE_V25_HEX
  4. Run validation tests against CashScript
  5. Bump version constant

Bytecode fingerprint (v2.5):

db7c643e5730713b88962d84c83626ecffbaa0e327de25bbe196a412310bc509

Use this to verify you have the correct v2.5 bytecode.


⚠️ Reality Check: What Actually Happened (August 2026)

The honest assessment: We implemented manual covenant construction in Kotlin, but we failed to validate that it produces addresses matching CashScript’s output.

What we tried:

What didn’t work:

What we did instead (August 1-2, 2026):

Why the pivot was right:

  1. Proof over perfection: WebView approach is validated and working
  2. Time value: 2 days of WebView work > 2 weeks of manual construction debugging
  3. Maintenance: Official SDK updates automatically, manual code would need constant sync
  4. Risk reduction: Using battle-tested library vs. maintaining custom Bitcoin Script encoder

Is manual construction abandoned?

No, just deferred. The architectural vision is still sound:

But pragmatism won: We need proven covenant infrastructure for Phase 0. Manual construction can be revisited later if:

Documentation principle: Own what didn’t work. This document describes the ideal, but production uses WebView. Both approaches are documented honestly.

See also: WebView Covenant Bridge - what’s actually running in production


Further Reading


Last updated: August 3, 2026 (Reality Check added)
Implemented in: AsgayaHusk v0.1 (Android)
Validates against: CashScript v0.13.2, price-oracle-v2.5.json