Bitcoin Script can enforce signatures, hashes, and time locks. It cannot directly read the exchange rate, the result of a football match, or whether a shipment arrived.
Discreet Log Contracts (DLCs) bridge that gap without putting a general-purpose oracle language on Bitcoin. An oracle publishes a signed attestation about an external event. The contracting parties prepare possible settlement transactions in advance, and the attestation determines which transaction can be completed.
The oracle does not control the funds. It only reveals information. The participants construct the contract, fund it, and decide the possible outcomes before the external event occurs.
The Basic Contract
Suppose Alice and Bob want to bet on the Bitcoin/USD price at a future time.
They agree on:
- An oracle public key
- An event identifier, such as
BTCUSD/2026-09-01T12:00Z - Possible outcomes, such as
above-70000andbelow-70000 - The amount each party contributes
- A refund time if the oracle never attests
They then construct a funding transaction that locks both contributions into a 2-of-2 Taproot output. The contract is not yet settled. Settlement transactions, called Contract Execution Transactions, or CETs, are prepared for each possible oracle outcome.
Funding output
│
├── CET: oracle says above-70000 → Alice receives the payout
├── CET: oracle says below-70000 → Bob receives the payout
└── Refund transaction after timeout
The parties exchange signatures for the CETs using adaptor signatures. Neither party can broadcast a CET before the oracle's attestation reveals the missing signing information.
Why Adaptor Signatures Matter
An adaptor signature is a signature that is mathematically close to complete. It becomes a valid signature only after a secret value is added.
In a DLC, the secret is related to the oracle's signature. Before the oracle attests, the CET signatures held by the parties are incomplete. After the oracle publishes an attestation, the relevant adaptor signature can be completed.
The same cryptographic secret can also be extracted from the completed signature. This lets the other party complete their corresponding settlement transaction.
Conceptually:
partial_signature + oracle_secret = valid_signature
valid_signature - partial_signature = oracle_secret
The actual construction uses elliptic-curve points and scalar arithmetic. Do not implement this algebra from a blog post in production. Use a reviewed DLC library and test against known vectors.
Oracle Attestations
An oracle commits to an event before the event occurs. A simple numeric event can be represented by a set of outcome messages:
event_id = "BTCUSD/2026-09-01T12:00Z"
outcomes = [
"above-70000",
"below-70000"
]
The oracle precommits to nonces or public points associated with the outcomes. Later, it publishes an attestation:
{
"event_id": "BTCUSD/2026-09-01T12:00Z",
"outcome": "above-70000",
"signature": "oracle-signature"
}
The participants verify:
- The oracle public key is the one committed to in the contract.
- The event identifier matches.
- The attested outcome is one of the outcomes used to construct the CET set.
- The signature is valid.
- The resulting settlement transaction has the expected outputs and fees.
An oracle can be honest, compromised, unavailable, or ambiguous. A DLC does not eliminate oracle trust; it makes the trust assumption narrow and explicit.
Contract Construction
A production DLC implementation has a state machine that is more complicated than the betting example suggests:
from dataclasses import dataclass
from enum import Enum
class DLCState(Enum):
OFFERED = "offered"
ACCEPTED = "accepted"
FUNDING_SIGNED = "funding_signed"
FUNDING_BROADCAST = "funding_broadcast"
FUNDED = "funded"
ATTESTED = "attested"
CET_SIGNED = "cet_signed"
SETTLED = "settled"
REFUNDED = "refunded"
FAILED = "failed"
@dataclass
class ContractTerms:
contract_id: str
oracle_pubkey: bytes
event_id: str
outcomes: list[str]
collateral_a_sat: int
collateral_b_sat: int
refund_locktime: int
@dataclass
class DLC:
terms: ContractTerms
state: DLCState
funding_txid: str | None = None
attested_outcome: str | None = None
The offer must contain enough information for the counterparty to independently reconstruct and verify every transaction:
- Funding output script
- CET transaction templates
- Refund transaction
- Fee rates and input/output amounts
- Oracle announcement
- Contract identifiers
If a party cannot independently derive the same transactions, the protocol is not safe to sign.
Funding and Settlement
The funding transaction spends inputs from both parties and creates the DLC funding output. Each party signs the transaction only after checking:
- Their input amounts
- The other party's input amounts
- The exact funding output
- Change outputs
- Fee allocation
- The refund transaction
- All CETs
After the funding transaction confirms, the parties wait for the oracle event.
When the oracle attests:
def select_cet(
cets: dict[str, bytes],
oracle_event_id: str,
attestation: dict,
oracle
) -> bytes:
if attestation["event_id"] != oracle_event_id:
raise ValueError("Attestation is for a different event")
outcome = attestation["outcome"]
if outcome not in cets:
raise ValueError(f"Unknown outcome: {outcome}")
if not oracle.verify(
event_id=oracle_event_id,
outcome=outcome,
signature=attestation["signature"]
):
raise ValueError("Invalid oracle attestation")
return cets[outcome]
The winning CET is completed with the oracle-derived information and broadcast. The losing CETs remain unusable.
If the oracle never attests, the refund transaction becomes valid after its lock time. This is an important liveness property: the contract should not permanently lock funds because an oracle disappeared.
Numeric Outcomes and Payout Curves
DLCs do not need to be limited to binary bets. A numeric event can use many outcome points:
BTC price:
60,000
61,000
62,000
...
80,000
Each outcome maps to a CET with a different payout. The more precise the payout curve, the more outcome points and transaction templates the participants must prepare.
For a price contract, the terms might define:
def linear_payout(
price: int,
lower: int,
upper: int,
collateral_a: int,
collateral_b: int
) -> tuple[int, int]:
if price <= lower:
return collateral_a + collateral_b, 0
if price >= upper:
return 0, collateral_a + collateral_b
fraction = (price - lower) / (upper - lower)
a_payout = int((collateral_a + collateral_b) * (1 - fraction))
return a_payout, collateral_a + collateral_b - a_payout
The contract must use discrete outcomes that the oracle can actually attest to. A continuous mathematical formula is not enough; the participants still need a finite set of signed messages or a compatible numeric attestation scheme.
Multi-Oracle Contracts
A contract can require agreement from multiple oracles. For example:
- 2 of 3 price oracles
- One sports-data oracle plus one timestamp oracle
- A threshold oracle committee
This reduces dependence on a single oracle but increases contract size, construction complexity, and the number of failure modes.
The security question changes from:
Will this oracle publish the correct outcome?
to:
What threshold of oracles can collude, fail, or disagree before the contract becomes unsafe or unusable?
The answer belongs in the contract specification, not in marketing language.
DLCs Compared With Other Bitcoin Contracts
HTLCs use a hash preimage and a time lock. They are excellent for atomic exchange when both parties can coordinate through a secret, but they do not encode arbitrary external outcomes.
Multisig requires a defined set of signers to approve a spend. It is direct and widely supported, but signers remain online or must be available when settlement occurs.
DLCs move the external-event decision into an oracle attestation while keeping the settlement transactions preconstructed and enforceable by Bitcoin.
The oracle is not a custodian. It cannot unilaterally spend the funding output if the contract was constructed correctly. It can still cause the wrong outcome by signing a false message, which is why oracle selection and redundancy matter.
Operational Risks
The hard problems in a DLC are not limited to elliptic-curve cryptography:
- Oracle ambiguity: Different messages may describe the same event differently.
- Timestamp boundaries: Data providers can disagree around the exact observation time.
- Fee changes: A CET prepared months ago may be underpriced when it needs to confirm.
- Funding confirmation: The contract must handle reorgs and confirmation depth.
- Refund timing: The refund must leave enough room for fee bumping and confirmation.
- Wallet recovery: Participants need durable backups of contract state, nonces, and signatures.
- Privacy: Funding and settlement transactions may reveal contract relationships.
- Oracle censorship: A delayed attestation can make a contract difficult to settle even if it is not dishonest.
The correct engineering approach is to define the threat model, use a reviewed implementation, test every transaction path on regtest, and make the refund path a first-class path rather than an afterthought.
What DLCs Make Possible
DLCs can support:
- Hedging against exchange-rate movements
- Peer-to-peer derivatives
- Parametric insurance
- Sports and event markets
- Cross-domain settlement with external data
- Futures contracts without a custodial exchange
They do not make external data trustless. They make the oracle trust assumption auditable and keep the funds under Bitcoin's contract rules.
That distinction is the reason DLCs matter: the contract can be non-custodial even when its outcome depends on information outside the chain.
Further Reading
- Discreet Log Contracts — https://github.com/discreetlogcontracts/dlcspecs
- DLC specifications — https://github.com/discreetlogcontracts/dlcspecs/blob/master/README.md
- Discreet Log Contracts paper, Dryja — https://adiabat.github.io/dlc.pdf
- Bitcoin Dev Kit — https://bitcoindevkit.org/
- Rust-DLC — https://github.com/p2pderivatives/rust-dlc
Top comments (0)