Simplicity-powered Price Oracle running on Liquid

Hey fellow devs! Happy to share this price oracle concept with you. This is something we are actively working on to run in prod one day!

Would love to hear your thoughts about that.


Abstract

This document describes an Oracle Network for Elements that provides on-chain verifiable time and price information to Simplicity programs. The network introduces Tick UTXOs that encode time in asset amounts and Oracle Verifier UTXOs that enable verification of threshold-signed price data.

The network utilizes MuSig2-based threshold signing and special assets to issue these primitives without requiring consensus changes to Elements. This document contains the network architecture, issuance and verification mechanisms, key rotation procedures, and security considerations.

Motivation

A set of DeFi protocols or their components cannot be implemented on Elements today, because the chain has no trusted source of either of the following inputs:

  • The current price of BTC in fiat currency.
  • The current time, in a form that a Simplicity program can verify.

The original motivating use case is a lending protocol that needs to liquidate collateral when the collateral asset’s USD price falls below an agreed-upon threshold. To authorize such a liquidation, the contract must know the current collateral price and the current time.

1. Overview

The Oracle network provides external time and price information to Simplicity Programs on Elements. The network is a set of oracle nodes that collectively authorize operations using threshold signatures.

The network exposes two primitives: Tick UTXOs and Oracle Verifier UTXOs. Tick UTXOs provide a time representation that a contract can verify. Oracle Verifier UTXOs provide a public key that can be used to verify price data signed by the network. Both primitives are issued under the control of network-managed assets.

A contract verifies time by validating a Tick UTXO and verifies price information by validating signed price data against the public key contained in an Oracle Verifier UTXO. To prevent the use of stale information, issued Tick UTXOs and Oracle Verifier UTXOs are automatically burned by the network after use or expiration.

Here is an example diagram of how the integrating Simplicity program might look, which utilizes all capabilities:

1.1 Terminology

Term Description
Oracle Network A set of oracle nodes that collectively provide time and price information to Simplicity programs on Elements.
Oracle Node A participant in the Oracle Network that contributes to threshold signatures and network operations.
Tick UTXO A UTXO issued by the Oracle Network whose amount encodes a timestamp. A contract may verify the timestamp by validating the Tick UTXO asset.
Oracle Verifier UTXO A UTXO issued by the Oracle Network whose Taproot inner key contains a network-authorized public key. A contract may use this public key to verify threshold-signed price data.
Signed Price Data Price information is signed by a threshold of oracle nodes and accompanied by metadata, such as the observed and expiration times.
Auth Asset A network-controlled asset used to authorize issuance and management operations.
Combination Key A MuSig2 aggregate public key corresponding to a particular m-of-n node combination.
Combination Proof A Merkle proof demonstrating that a Combination Key belongs to the set of authorized node combinations.
Threshold Signature A signature produced by a sufficient number of oracle nodes to satisfy the network’s signing policy.

2. User Experience

2.1 Account

The Oracle Network maintains an account balance for each user. This balance is used to pay transaction fees on Elements as well as service fees charged by the network. The user funds the account with L-BTC, and the network deducts fees as services are used.

  1. The user creates an account with the Oracle Network.
  2. The network provides a deposit address for funding the account.
  3. The user transfers L-BTC to the provided address.
  4. After the deposit is confirmed, the account balance becomes available for use.
  5. The user requests network services, such as Tick UTXO issuance or signed price data.
  6. The network deducts the required transaction fees and service fees from the account balance.
  7. When the balance is depleted, the user may fund the account again to continue using the network.

2.2 Tick UTXO

A Tick UTXO provides the current time in a form that a Simplicity program can verify. The Oracle Network issues Tick UTXOs on demand and automatically burns them after use or expiration to prevent the use of stale timestamps.

  1. The user requests a Tick UTXO from the Oracle Network.
  2. The network creates and broadcasts a transaction that issues the Tick UTXO.
  3. The user receives information about the issued Tick UTXO.
  4. The user includes the Tick UTXO as an input in a transaction that contains a contract requiring time verification.
  5. The contract verifies the Tick UTXO and derives the timestamp encoded in its amount.
  6. When the transaction is confirmed, the Tick UTXO is burned as part of the spending process.
  7. If the Tick UTXO is not used within its validity period, the network automatically burns it.

2.3 Signed price data

The Oracle Network provides price data signed by a threshold of oracle nodes. To allow a contract to verify a signature, the network also issues an Oracle Verifier UTXO that contains the Combination Key. Oracle Verifier UTXOs are automatically burned after use or expiration to prevent the use of stale price information.

  1. The user requests price information for a particular asset pair.
  2. The network returns the current price data.
  3. If the returned price satisfies the user’s requirements, the user requests verifiable price data.
  4. A threshold of oracle nodes signs the price data, and the network issues an Oracle Verifier UTXO corresponding to the signing node combination.
  5. The network returns the signed price data together with information about the Oracle Verifier UTXO.
  6. The user includes the Oracle Verifier UTXO and the signed price data in a transaction that contains a contract requiring price verification.
  7. The contract verifies the signed price data using the public key contained in the Oracle Verifier UTXO.
  8. When the transaction is confirmed, the Oracle Verifier UTXO is burned as part of the spending process.
  9. If the Oracle Verifier UTXO is not used within its validity period, the network automatically burns it.

3. Network

3.1 Aggregated signature

The network uses MuSig2 to create an aggregate signature. By its nature, MuSig2 can only verify an n-of-n signature. We can emulate m-of-n with a few extra steps: build a Merkle tree that contains all m-of-n combinations, each of which is an m-of-m MuSig2 public key. When we want to verify a signature produced by some combination, we first verify a Merkle proof that the combination is in the tree, and only then do we verify the signature. Almost all signature verifications would require this Merkle proof, except for signed price data, where we can just include the public key of the combination directly in the Oracle Verifier UTXO.

3.2 Assets

The network controls a set of assets, each with different purposes:

  • Auth Asset - a custom asset; if this asset is included in the transaction, this symbolizes network authorization. Unlocked if an m-of-m MuSig2 signature and the Combination Proof are provided.
  • Tick Asset - a custom asset; used to create Tick UTXOs and locked by Auth Asset.
  • Oracle Verifier Asset - a custom asset; used to create Oracle Verifier UTXO and locked by Auth Asset.
  • User L-BTC UTXOs - these are used to pay the broadcasting fee for Elements and operation toll for the network, and are locked by Auth Asset.
  • Treasury L-BTC UTXOs - accumulated tolls so far. Locked by Auth Asset.

3.3 Network key rotation

Initially, each node generates its own key pair and then broadcasts its public key to other nodes. Then the node constructs all possible m-of-n combinations of m-of-m MuSig2s into a Merkle tree. Auth Asset is created, and afterward, Tick Asset with Oracle Verifier Asset.

During key rotation, the same steps are executed as during initialization, except that instead of creating assets, we simply change the Merkle tree root in the Auth Asset. Because other assets are linked to the Auth Asset, no further action is required.

3.4 Signed price data

Each node pulls the price with a constant time period from direct price sources (Blockstream Price Feeds, CEXes, DEXes) and stores the record in the database. When the user calls the verifiable_price_data endpoint, the queried node sends its current price to all other peers that verify it against their own observation and collaborate to produce at least an m-of-n signature, the Combination proof, and an Oracle Verifier UTXO with the current combination’s public key as the inner key. The leader node then returns the signed price data, the Combination proof, the public key, and information about the Oracle Verifier.

The format of signed price data is as follows:

NETWORK_TAG_v1 || feed_id || price || observed_at_unix || expiry_at_unix

3.5 Recovering the malfunctional network

When the number of alive nodes falls below 2/3, recovery of Auth Asset can be performed. Besides the regular key rotation path, Auth Asset has a second spending path. This spending path is timelocked; thus, the healthy network, with at least 2/3 active nodes, should perform the key rotation periodically to push the timelock farther in the future.

4. Primitives specification

4.1 Auth Asset UTXO

The Auth Asset UTXO serves as the root authorization primitive of the Oracle Network. Ownership of the Auth Asset grants the ability to perform network-controlled operations, including issuance of Tick Assets and Oracle Verifier Assets, management of treasury funds, and key rotation.

The Auth Asset is locked by a Simplicity program that verifies a MuSig2 aggregate public key together with a Merkle proof proving that the key belongs to the network’s authorized set of signing combinations.

assert(verify_merkle_proof(pubkey, proof, expected_merkle_root))
assert(check_sig(msg, pubkey, sig))

4.2 Tick UTXO

The network creates the Tick asset, with an initial supply of 0. The Simplicity program locks the inflation of this asset; it checks whether the first input’s asset ID equals the Auth Asset upon issuance. The spending of Tick UTXO is also locked by the Simplicity program; this ensures that all amounts of the Tick asset are sent to OP_RETURN.

Tick Asset Issuance

The Tick Asset may only be issued when authorized by the Auth Asset.

assert(tx.inputs[AUTH_INPUT_INDEX].asset_id == AUTH_ASSET_ID)

Tick UTXO Spending

A Tick UTXO may be spent only if all of its Tick Asset value is burned.

check_tick_asset_ids(tx, input_index, output_index)
check_tick_amounts(tx, input_index, output_index)

assert(tx.outputs[output_index].script_pubkey == OP_RETURN)

Tick UTXO Verification

A contract may derive the current time from the amount of a Tick UTXO.

assert(tx.inputs[TICK_INPUT_INDEX].asset_id == TICK_ASSET_ID)
assert(tx.inputs[TICK_INPUT_INDEX].amount >= some_dynamic_time)

4.3 Oracle Verifier UTXO

It works like Tick UTXO with a slight tweak. The Taproot inner key is the public key of some MuSig2 combination. Suppose the user wants to verify a signature from the network in his Simplicity program. They pass the Oracle Verifier Simplicity’s CMR and its inner key as a witness. The contract builds a hash of the scriptPubKey and verifies that it matches the Oracle Verifier input; the asset ID of this input is also checked. Only after this check can the public key be used to verify the signature.

Oracle Verifier Asset Issuance

The Oracle Verifier Asset may only be issued when authorized by the Auth Asset.

assert(tx.inputs[AUTH_INPUT_INDEX].asset_id == AUTH_ASSET_ID)

Oracle Verifier UTXO Spending

An Oracle Verifier UTXO may be spent only if all of its Oracle Verifier Asset value is burned.

check_oracle_verifier_asset_ids(tx, input_index, output_index)

assert(tx.outputs[output_index].amount == 1)
assert(tx.outputs[output_index].script_pubkey == OP_RETURN)

Oracle Verifier UTXO Verification

A contract may verify a signature against the public key stored in an Oracle Verifier UTXO.

let verifier_input = tx.inputs[ORACLE_VERIFIER_INPUT_INDEX]

assert(verifier_input.asset_id == ORACLE_VERIFIER_ASSET_ID)
assert(verifier_input.script_pubkey, OracleVerifierScript(expected_cmr, pubkey))
assert(check_sig(msg, pubkey, sig))

5. Security

5.1 Rogue node

All actions that the network can perform require a network signature. A signature can be generated only by the majority of nodes. If the rogue node is discovered, then key rotation is performed.

5.2 Price data source

Nodes query price information directly from the data feeds.

5.3 Frontend node price forgery

When the user calls the verifiable_price_data endpoint, the nodes compare their prices to create a signature, and the signed price data is returned to the user for verification.

5.4 Resource Exhaustion

All network operations are performed on the Elements network, except that initial asset creation is paid by the user. So an attacker can’t just spam the Tick UTXO issuance API.

5.5 Loose Ticks and Oracle Verifiers

A vector of attacks exists when very old information is provided to the contract. We avoid this by automatically tracking and burning UTXOs issued by the network.

6. Disclaimer

The Oracle Network operates independently of the Liquid consensus mechanism and should be viewed as a trusted external service rather than a consensus protocol. The Liquid consensus rules do not validate information provided by the network and therefore depend on the honesty of a sufficient number of oracle nodes and the correctness of the external data sources they use.

The network does not attempt to establish global consensus on time or price information; instead, it produces statements endorsed by a threshold of participating nodes. Consequently, a compromise of a sufficient number of oracle nodes, or failures in the underlying data sources, may result in incorrect information being signed and accepted by contracts that rely on the network.

The signatures produced by the network prove that a threshold of oracle nodes endorsed a statement, but they do not prove that the statement itself is correct.

Hi @arvolear! It’s cool to see all of this progress on oracle mechanisms. I have three initial questions about this approach:

(1) I can understand that some protocols might need a time oracle completely separate from a price oracle, although it seems like they would need it in general only for proving that the time is earlier than a specified time, not that it’s later. Do you agree, or do you think there are cases where both are useful?

(2) For protocols that do need a price, I don’t see specifically why they need a separate time oracle statement. Doesn’t the price oracle statement inherently guarantee freshness/timeliness by the same mechanism that you’ve already specified? So couldn’t a protocol or contract consume only a single price oracle statement and thereby automatically get a time-related guarantee out of the same statement?

(3) In this mechanism, oracle statements are used up by spending their UTXOs and enforcing that the underlying tokens were burned (which I think is a very clever approach!). But also, the oracle statements are apparently issued by the oracle for a particular person or entity’s use, possibly in exchange for a small payment to the oracle. Is there anything that inherently stops the oracle statement from being swiped by another party and used by that party instead of the intended user? Is it just that the oracle statements are somewhat difficult to recognize as such before they are used in the intended way? Could there be an “oracle statement griefer” who attempts to watch for the issuance of new oracle statements and immediately consume them for no productive reason?