Status: Implemented in Phase 2 (2026-07-21)
Category: Covenant Design Constraint
Trust Model: Oracle for UX, MTP for Security
Problem: Time-locked covenants need reliable time measurement, but BCH’s Median Time Past (MTP) only advances when blocks are mined (~10 minutes).
Solution: Dual time enforcement system:
Key Insight: The oracle doesn’t need to be trusted because MTP provides an unbreakable safety net. Oracle failure only degrades UX, never security.
Asgaya’s covenant requires time-locked refunds to protect senders:
Scenario:
Requirement: After 8 hours, if recipient hasn’t claimed, sender gets full refund.
Median Time Past (MTP) is Bitcoin Cash’s built-in time measurement:
Problem for 8-hour covenants:
Intended: Covenant created at 10:00 AM → Expires at 6:00 PM (8 hours)
Reality: Block mined at 10:00 AM (MTP: 10:00)
Next block at 10:07 AM (MTP: 10:03) - small advance
Next block at 10:22 AM (MTP: 10:11) - block gap!
Next block at 10:35 AM (MTP: 10:18)
...
Expiry block could be anywhere from 5:45 PM to 6:30 PM
MTP limitations:
UX problem:
User creates covenant at 10:00 AM, expecting 8-hour expiry at 6:00 PM
Reality: MTP-based expiry could trigger anywhere from 5:45 PM to 6:25 PM
Recipient tries to claim at 5:50 PM:
Option A: MTP already advanced past expiry → COVENANT LOCKED (refund only)
Option B: MTP hasn't advanced yet → Claim succeeds
Result: Unpredictable UX, recipient frustrated
Merchant problem:
Merchant wants to cash out recipient's covenant at 5:55 PM
Merchant app checks: "5 minutes left, safe to proceed"
Merchant completes KYC, gets recipient's cash ready
Merchant tries to broadcast claim transaction
Result: "ERROR: Covenant expired" (MTP jumped ahead during KYC)
Now merchant is stuck:
- Gave cash to recipient
- Can't claim covenant (expired)
- Recipient disappears with both cash AND covenant claim code
Bottom line: MTP-only covenants create timing uncertainty that breaks UX and enables fraud.
┌─────────────────────────────────────────────────────────────┐
│ COVENANT PARAMETERS │
├─────────────────────────────────────────────────────────────┤
│ expiryMTP: 1784661278 (MTP timestamp, 8 hours ahead) │
│ expiryOracleTime: 1784661278 (Real-time clock) │
│ oraclePubkey: 02abc...def (Price oracle's signing key) │
└─────────────────────────────────────────────────────────────┘
CLAIM PATH (before expiry):
┌───────────────────────────────────────────────────┐
│ 1. Check oracle signature on latest price data │
│ require(checkDataSig(oracleSig, oracleData)) │
│ 2. Verify oracle timestamp < expiryOracleTime │
│ 3. Verify recipient signature │
│ 4. Calculate BCH amount from EUR price │
│ 5. Split outputs (payment + buffer) │
└───────────────────────────────────────────────────┘
REFUND PATH (after expiry):
┌───────────────────────────────────────────────────┐
│ 1. require(tx.time >= expiryMTP) ← MTP ENFORCEMENT
│ This is UNBREAKABLE - can't be faked │
│ 2. Verify sender signature │
│ 3. Refund full amount to sender │
└───────────────────────────────────────────────────┘
Normal flow (oracle works):
Failure mode (oracle offline):
require(tx.time >= expiryMTP) succeedsKey property: Oracle failure never locks funds forever. MTP provides guaranteed escape hatch.
Scenario 1: Oracle signs future timestamps (claims covenant expired early)
Actual time: 5:55 PM (5 minutes before expiry)
Oracle lies: Signs timestamp of 6:05 PM (10 minutes AFTER expiry)
Recipient tries to claim:
- Covenant checks: oracleTime (6:05) >= expiry (6:00) → REJECT
- Recipient can't claim via oracle path
But MTP hasn't advanced past 6:00 PM yet!
- Sender tries to refund via MTP path
- require(tx.time >= expiryMTP) → FAILS (tx.time still ~5:55)
- Sender can't refund yet
Result: Temporary deadlock (5 minutes)
When MTP advances past 6:00 PM → Sender CAN refund via MTP path
Impact: Oracle can create 5-10 minute UX delay, but can’t steal funds.
Scenario 2: Oracle signs past timestamps (keeps covenant open indefinitely)
Actual time: 6:10 PM (10 minutes after expiry)
Oracle lies: Signs timestamp of 5:50 PM (claims still 10 min before expiry)
Recipient tries to claim:
- Covenant checks: oracleTime (5:50) < expiry (6:00) → ALLOW
- Recipient can claim via oracle path
But sender can ALSO refund:
- require(tx.time >= expiryMTP) → SUCCEEDS (actual MTP is past 6:00)
- Sender can refund via MTP path
Result: Race condition
Resolution:
Impact: Oracle can enable brief race condition, but doesn’t benefit either party. Both parties watching mempool. Sender wins race by monitoring blockchain.
Scenario 3: Oracle refuses to sign (censorship)
Oracle sees recipient is from sanctioned country
Oracle refuses to sign any price updates
Result:
But: This is acceptable! Sender created covenant voluntarily. If oracle censors, sender gets refund - no funds lost.
Mitigation: Use multiple oracle providers (future enhancement)
Phase 1.5 evolution: Distributed Monitoring implements blockchain-as-oracle architecture where every covenant funding becomes a trade signal. Price discovery emerges from reputation-filtered VWAP of real Asgaya trades (on-chain), bootstrapped by Asgaya oracle until network matures. Censorship-resistant (blockchain can’t be shut down), permissionless (anyone can be seller), eliminates single-oracle failure mode.
Scenario 4: Oracle colludes with recipient
Sender and recipient agree on 8-hour covenant
Recipient bribes oracle to sign fake timestamps
Oracle signs timestamp of 5:55 PM when actual time is 6:10 PM
Result: Same as Scenario 2 - race condition
Defense:
Pros:
Cons:
Verdict: Technical purity at the expense of usability. Doesn’t work for real users.
Pros:
Cons:
Verdict: Unacceptable. Introduces trusted third party into “trustless” system.
Idea: Use block height instead of timestamps
Current block: 850,000
Expiry block: 850,048 (48 blocks = ~8 hours)
Pros:
Cons:
Verdict: Better than MTP-only, but still has UX problems. Oracle+MTP is strictly better.
Idea: Use CheckDataSig with challenge-response protocol
Sender challenges oracle every block
If oracle doesn't respond → automatic refund
Pros:
Cons:
Verdict: More complex, more expensive, doesn’t solve the core problem. Oracle+MTP is simpler and better.
File: simple-split.cash
Tests basic covenant mechanics:
No time locks yet - just validates CashScript basics work.
File: time-refund.cash
Adds MTP time enforcement:
function claim(sig recipientSig) {
require(checkSig(recipientSig, recipient));
require(tx.outputs[0].value == paymentSats);
require(tx.outputs[0].lockingBytecode == new LockingBytecodeP2PKH(hash160(recipient)));
require(tx.outputs[1].lockingBytecode == new LockingBytecodeP2PKH(hash160(seller)));
}
function refund(sig senderSig) {
require(checkSig(senderSig, sender));
require(tx.time >= expiryMTP); // ← MTP ENFORCEMENT
require(tx.outputs[0].value == paymentSats);
require(tx.outputs[0].lockingBytecode == new LockingBytecodeP2PKH(hash160(sender)));
require(tx.outputs[1].lockingBytecode == new LockingBytecodeP2PKH(hash160(seller)));
}
Status:
Will add oracle time enforcement:
function claim(sig recipientSig, datasig oracleSig, bytes oracleMessage) {
// Verify oracle signature
require(checkDataSig(oracleSig, oracleMessage, oraclePubkey));
// Parse oracle data: {timestamp, bchPriceInCents}
bytes8 oracleTimestamp = oracleMessage.split(8)[0];
bytes8 bchPrice = oracleMessage.split(8)[1].split(8)[0];
// Check oracle timestamp < expiry
require(int(oracleTimestamp) < expiryOracleTime);
// Calculate BCH amount from EUR price
int bchAmount = (eurAmount * 100) / int(bchPrice);
// ... rest of claim logic
}
function refund(sig senderSig) {
require(checkSig(senderSig, sender));
require(tx.time >= expiryMTP); // ← MTP fallback still here!
// ... rest of refund logic
}
Key properties:
Principle: External data (oracle) can enhance user experience, but must never be the sole security mechanism.
Application:
Principle: System should work (albeit with degraded UX) even if components fail.
Failure modes:
Never: Oracle failure never locks funds permanently
Principle: Don’t fetishize “trustlessness” at the expense of usability. Minimize trust, don’t eliminate it when elimination costs too much UX.
Trade-off:
Key insight: The oracle can’t steal funds because MTP exists. This is very different from “trusted oracle” designs where oracle failure = permanent fund loss.
Principle: Each component should have one job.
Application:
Benefit: Can replace oracle provider without changing covenant logic. Can update MTP fallback window without touching oracle code.
Status: Unresolved
Options:
Trade-offs:
Decision needed: Phase 1 implementation (next session)
Status: Unresolved
Requirements:
Proposed format:
bytes8: Unix timestamp (seconds since epoch)
bytes8: BCH price in cents (e.g., 10000 = €100.00)
bytes32: Signature (ECDSA secp256k1)
Open questions:
Decision needed: Phase 1 implementation
Status: Unresolved
Problem:
Covenant expiry: 6:00 PM
Oracle last signed: 5:30 PM (30 minutes ago)
Current time: 5:55 PM (5 minutes before expiry)
Should covenant accept 30-minute-old oracle data?
Options:
Decision needed: Phase 1 implementation
Status: Unresolved
Question: Should MTP expiry be:
Trade-offs:
Recommendation: 10-minute gap (one block average)
Decision needed: Phase 1 implementation
Implementation:
Design Context:
Research:
knowledge/research_code/ (Python scripts for oracle data)The time oracle + MTP fallback architecture solves a fundamental tension in covenant design:
Key insight: The oracle doesn’t need to be trusted because MTP acts as an unbreakable safety net. Oracle misbehavior can only create temporary UX degradation, never permanent fund loss.
This is the design we’re building.
Document Status: Living document, updated as implementation progresses
Last Updated: 2026-07-21 (Phase 2 complete, Phase 1 pending)
Maintained By: Suso + Coordination (Claude)