Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 121 additions & 0 deletions 88.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
NIP-88
======

Discreet Log Contract Oracles on Nostr
-----------------

`draft` `optional`

This NIP describes two event kinds, `88` and `89`, for [Discreet Log Contract (DLC)](https://bitcoinops.org/en/topics/discreet-log-contracts/) oracles to publish their announcements and attestations over Nostr. Clients can consume these signed events to create conditional payment contracts which fulfill differently based on the oracles' attestations.

## Format

@joemphilips joemphilips Feb 14, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I think it is better to mention that this NIP is for DLC v0. DLC might update to V1 in the future, but we can have another NIP in that case.
In this way, clients can avoid fetching a new version of DLC that they cannot understand.


DLC protocol messages are binary-serialized messages described concretely in [this document](https://github.com/discreetlogcontracts/dlcspecs/blob/master/Messaging.md). Whenever embedding DLC messages inside Nostr events (which are encoded as JSON), we serialize those DLC messages in base64.

## DLC Oracle Gossip

DLCs require an oracle to attest to the outcome of real world events. This is done by the oracle signing a message containing the outcome of the event. Before they attest to the outcome, they must create an announcement where they publish the intent to sign the future event. This announcement is then used by the DLC participants to create the contract. Here we define two events, `kind:88` and `kind:89` that are used to publish the oracle's announcement and attestations respectively.

### `kind:88`

```jsonc
{
"kind": 88,
"content": "BA/cNhCpdD25j/MwDaa4F42QIq8NsOGmaW1MxyswZnipGWirwoxPhL1SmoHcp1JuCjYXF...",
"tags": [
[
"relays", // the relays the oracle will publish attestations to
"wss://nostr.mutinywallet.com",
"wss://relay.damus.io"
],
[
"title",
"Optional Event Title"
],
[
"description",
"An optional human-readable description of the event which the oracle will attest to, in plain text."
],

// optional, if this is a numeric event for an asset pair
["n", "BTC"],
["n", "USD"]
],
"pubkey": "97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322",
"created_at": 1679673265,
"id": "30efed56a035b2549fcaeec0bf2c1595f9a9b3bb4b1a38abaf8ee9041c4b7d93",
"sig": "f2cb581a84ed10e4dc84937bd98e27acac71ab057255f6aa8dfa561808c981fe8870f4a03c1e3666784d82a9c802d3704e174371aa13d63e2aeaf24ff5374d9d"
}
```

The `content` field must be the base64-encoding of a binary-serialized [`oracle_annoucement` object](https://github.com/discreetlogcontracts/dlcspecs/blob/master/Messaging.md#the-oracle_annoucement-type).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: typo: oracle_annoucement -> oracle_announcement.
Same for the linked URL.


The optional `title` tag gives observers a short human-readable title with which to display the announcement in cards, hyperlinks, etc. It _should_ be at most 100 characters of UTF-8 text. Clients _should_ ignore or truncate titles longer than 100 characters. This tag must NOT be parsed as markdown or HTML.

The optional `description` tag provides a human-readable summary of the real-world event which this announcement is for. The `description` should give observers context, so that they know how the real-world event in question will be reflected in the oracle's final attestation. This tag must NOT be parsed as markdown or HTML.

The optional `n` tag is described further down this document.

Upon receiving an announcement event of kind `88`, clients _should_ validate:
- the base64-encoded announcement data contains a copy of the correct oracle attestation pubkey. The oracle's attestation key may be distinct from the oracle's Nostr key.
- the announcement is signed correctly by the expected oracle attestation pubkey.
- [the event descriptor included in the announcement](https://github.com/discreetlogcontracts/dlcspecs/blob/master/Messaging.md#the-event_descriptor-type) matches the tags in the `kind:88` event.

### `kind:89`

```jsonc
{
"kind": 89,
"content": "w7HSaUaPQn7Fa00PoUwTqkR2+wXHCPjD8Da5f4OcJ0EACsUw6uSdQgUDLLG9o/e9daS...",
"tags": [
[
"e", // the Nostr event id of the announcement
"30efed56a035b2549fcaeec0bf2c1595f9a9b3bb4b1a38abaf8ee9041c4b7d93",
],

// optional, if this is a numeric attestation for an asset pair
["n", "BTC"],
["n", "USD"]
Comment on lines +76 to +78

@tvolk131 tvolk131 Jan 14, 2025

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it beneficial to include this in kind-89 since you can look up the kind-88 announcement using the e tag to get the same data? The use-case for querying announcements by asset pair makes sense, but I don't see the value in being able to query for attestations by asset pair.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about this as well, but i believe @vitorpamplona wants to use these attestations for displaying current asset prices to users in GUI based on their "trusted oracle" list.

While technically you could do a two-step process as you say - first look up the announcement, then search for the corresponding attestation - it is easier and more efficient to look up attestations by asset, especially if the current price is all you care about.

Otherwise, you'd need to parse the announcements, find one that is supposed to have matured recently, and then find its corresponding attestation event.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More background in this PR: #1658

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh makes sense, thanks for the explanation!

],
"pubkey": "97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322",
"created_at": 1679673265,
"id": "30efed56a035b2549fcaeec0bf2c1595f9a9b3bb4b1a38abaf8ee9041c4b7d93",
"sig": "f2cb581a84ed10e4dc84937bd98e27acac71ab057255f6aa8dfa561808c981fe8870f4a03c1e3666784d82a9c802d3704e174371aa13d63e2aeaf24ff5374d9d"
}
```

The `content` field must be the base64-encoding of a binary-serialized [`oracle_attestation` object](https://github.com/discreetlogcontracts/dlcspecs/blob/master/Messaging.md#the-oracle_attestation-type).

Note that the `e` tag is the _Nostr event identifier_ for the `kind:88` announcement event, which is distinct from the identifier embedded [in the announcement](https://github.com/discreetlogcontracts/dlcspecs/blob/master/Messaging.md#oracle_event) or [in the attestation itself](https://github.com/discreetlogcontracts/dlcspecs/blob/master/Messaging.md#oracle_attestation). The `e` tag is intended to be used to look up the corresponding announcement event.

Upon receiving an attestation, clients _should_ validate:
- the base64-encoded attestation data [contains a copy](https://github.com/discreetlogcontracts/dlcspecs/blob/master/Messaging.md#the-oracle_attestation-type) of the correct oracle attestation pubkey. This should be the same as the pubkey contained in the corresponding announcement event, of `kind:88`.
- the `event_id` field inside the `oracle_attestation` object matches the `event_id` field in the original `oracle_announcement` object, referred to by the `e` tag.
- the attestation signatures are valid under the oracle's attestation key.

### The `n` Tag

DLCs are often numeric events, in which the oracle signs the relative price of two assets. In this common case, kind `88` or `89` events may include exactly two `n` tags which indicate the ticker symbols of the assets whose relative value is being signed.

The order of the tags implies a specific denomination of the price attestation: The attestation's outcome should be **the value of the first symbol in units of the second symbol.** For example, `[... ["n", "BTC"], ["n", "USD"]]` indicates the attestation supposedly signs the price of `BTC` in units of `USD`.

### Oracle Pubkeys

Oracles are responsible for choosing how to manage their attestation keypair(s). Oracle announcements and attestations embed their own copies of the oracle's BIP340 attestation public key. Oracles should ideally never change their attestation key pair between events, but they _may_ choose to migrate their attestation key, or use different keys for different types of events if they desire. Clients are responsible for choosing secure policies regarding which oracle attestation keys to trust and use.

**By default, all clients must accept announcement/attestation signatures issued by the oracle's Nostr key.** This requirement allows oracles to fall back on using their Nostr key pair if managing multiple keys is unnecessary, or if their existing attestation key(s) become unusable.

@joemphilips joemphilips Feb 14, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How should users know which keypair is Oracle's nostr key?
IIUC kind:88 does not assure that the event (Nostr event, not an event in DLC context) was created by the Oracle.

IMHO, this problem should be fixed by either

  1. Always assume that NIP88-compliant oracles must use the same key for both DLC and Nostr.
  2. Add another required tag "signature" to kind:88 event. Signature by Oracle's DLC key, which signs to the same thing to the "sig" field. This can be optional in the case Nostr key and DLC key are the same.
  3. Add another event kind just to link the Oracle's DLC key and Oracle's Nostr key

I think 2 is the simplest

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, this (the mechanism to tie attestation key and nostr key) is necessary only when we want the fallback mechanism. But (as I said it in the comment below), this mechanism itself should be removed so we don't need to have another tag field.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't resolve this thread, so please resolve it if you can

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think "must" is too strong here.
What if Oracle's Nostr key got compromised?
What if an Oracle intentionally signs to two outcomes, one with the Nostr key and one with the DLC key?

IMHO, we should not include the fallback mechanism in here to make the spec simpler.
We should always assume the Oracle key and the Nostr key are independent. Users can just use the same key for both Nostr and DLC if they are reckless.


### `kind:10088`

Kind `10088` lists a user's trusted oracles, for the purpose of 3rd party protocols negotating DLCs or DLC-adjacent conditional payment contracts with the user. A kind `10088` event contains one or more `s` tags with a oracle's Nostr pubkey, and one or more relays where that oracle's announcement events (`kind:88`) may be found.

```jsonc
{
"kind": 10088,
"tags": [
["s", "4fd5e210530e4f6b2cb083795834bfe5108324f1ed9f00ab73b9e8fcfe5f12fe", "wss://bitagent.prices"],
// ...
],
//...
}
```
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ They exist to document what may be implemented by [Nostr](https://github.com/nos
| `43` | Channel Hide Message | [28](28.md) |
| `44` | Channel Mute User | [28](28.md) |
| `64` | Chess (PGN) | [64](64.md) |
| `88` | DLC Oracle Announcement | [88](88.md) |
| `89` | DLC Oracle Attestation | [88](88.md) |
| `818` | Merge Requests | [54](54.md) |
| `1021` | Bid | [15](15.md) |
| `1022` | Bid confirmation | [15](15.md) |
Expand Down Expand Up @@ -176,6 +178,7 @@ They exist to document what may be implemented by [Nostr](https://github.com/nos
| `10030` | User emoji list | [51](51.md) |
| `10050` | Relay list to receive DMs | [51](51.md), [17](17.md) |
| `10063` | User server list | [Blossom][blossom] |
| `10088` | Trusted DLC Oracle List | [88](88.md) |
| `10096` | File storage server list | [96](96.md) |
| `13194` | Wallet Info | [47](47.md) |
| `21000` | Lightning Pub RPC | [Lightning.Pub][lnpub] |
Expand Down