The Tari RFCs
Tari is a community-driven project. The documents presented in this RFC collection have typically gone through several iterations before reaching this point:
- Ideas and questions are posted in #tari-dev on #FreeNode IRC. This is typically short-form content with rapid feedback. Often, these conversations will lead to someone posting an issue or RFC pull request.
- RFCs are "Requests for Comment", so although the proposals in these documents are usually well-thought out, they are not cast in stone. RFCs can, and should, undergo further evaluation and discussion by the community. RFC comments are best made using Github issues.
New RFC's should follow the format given in the RFC template.
Lifecycle
RFCs go through the following lifecycle, which roughly corresponds to the COSS:
Status | Description | |
---|---|---|
Draft | Changes, additions and revisions can be expected. | |
Stable | Typographical and cosmetic changes aside, no further changes should be made. Changes to the Tari code base w.r.t. a stable RFC will lead to the RFC becoming out of date, deprecated, or retired. | |
Out of date | This RFC has become stale due to changes in the code base. Contributions will be accepted to make it stable again if the changes are relatively minor, otherwise it should eventually become deprecated or retired. | |
Deprecated | This RFC has been replaced by a newer RFC document, but is still is use in some places and/or versions of Tari. | |
Retired | The RFC is no longer in use on the Tari network. |
RFC-0001/Overview
Overview of Tari Network
Maintainer(s): Cayle Sharrock
Licence
Copyright 2018 The Tari Development Community
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Language
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as shown here.
Disclaimer
This document and its content are intended for information purposes only and may be subject to change or update without notice.
This document may include preliminary concepts that may or may not be in the process of being developed by the Tari community. The release of this document is intended solely for review and discussion by the community of the technological merits of the potential system outlined herein.
Goals
The aim of this proposal is to provide a very high-level perspective of the moving parts of the Tari protocol.
Related Requests for Comment
- RFC-0303: Digital asset network
- RFC-0131: Tari mining
- RFC-0201: TariScript
- RFC-0202: TariScript Opcodes
Description
Abstract
Tari's aims to be the most useful, decentralized platform that empowers anyone to create digitally scarce things people love.
This statement packages some important concepts.
To be useful, creators and users need to be able to interact with their digital assets in the way that they've been accustomed to in Web 2.0. This means that there is a safe, secure, responsive interface between human and the machines that manage their information. It means that the user experience is smooth and intuitive. It means that the network protocol itself is flexible and capable enough to provide every type of assets creators can imagine.
It also scales, so that the entire network does not become a victim of its own success and grind to a halt when more than a few hundred people actually try to use it.
Decentralisation is central to Tari's philosophy (pun intended). The tape is wearing thin on the newsreel that reports yet another centralised custodian, gatekeeper or "authority" becoming a single point of failure. This has led to thousands of people losing their crypto-assets, as well as their trust in the ideas of crypto-assets.
Tari has a strong focus on developer experience. The Tari community obsesses over building beautiful, clean, intuitive APIs and templates, which lead to beautiful developer tools. The idea is to widen the pool and allow anyone to create safe, secure, performant digital assets, rather than provide a gated community of rent-extracting "smart-contract developers".
In general, some of these goals are in direct opposition to each other. Speed, security and decentralisation typically form a trilemma that means that you need to settle for two out of the three.
Tari attempts to resolve the dilemma by splitting operations over two discrete layers. Underpinning everything is a base layer that focuses on security and manages global state, and a second, digital assets layer that focuses on rapid finalisation and scalability.
Multiple Layers
The distributed system trilemma tells us that these requirements are mutually exclusive.
We can't have fast, cheap digital assets and also highly secure and decentralized currency tokens on a single system.
Tari overcomes this constraint by building two layers:
- A base layer that provides a public ledger of Tari coin transactions, secured by PoW to maximize security.
- A DAN consisting of a highly scalable, efficient side-chain that each manages the state of all digital asset.
The DAN layer gives up some security guarantees in exchange for performance. However, in the case of a liveness failure (wherein parts of the DAN cannot make progress), the base layer intervenes to break the deadlock and allow the DAN to continue.
Base Layer
The Tari base layer has the following primary features:
- PoW-based blockchain using Nakamoto consensus
- Transactions and blocks based on the Mimblewimble protocol
Mimblewimble is a blockchain protocol that offers some key advantages over other UTXO-based cryptocurrencies such as Bitcoin:
- Transactions are private. This means that casual observers cannot ascertain the amounts being transferred or the identities of the parties involved.
- Mimblewimble has a different set of security guarantees to Bitcoin. The upshot of this is that you can throw away UTXOs once they are spent and still verify the integrity of the ledger.
- Multi-signature transactions can be easily aggregated, making such transactions very compact, and completely hiding the parties involved, or the fact that there were multiple parties involved at all.
"Mimblewimble is the most sound, scalable 'base layer' protocol we know" -- @fluffypony
In addition to this, Tari has made some novel additions to the basic Mimblewimble protocol. Primarily, these were invented to allow the DAN to be built on top of Tari, but have found some great applications generally:
- TariScript. Similar to Bitcoin script, TariScript (RFC-201, RFC-202) provides limited "smart contract" functionality on the base layer protocol.
- One-sided payments. Vanilla Mimblewimble requires both sender and receiver to participate in the creation of transactions. It is impossible in MimblewimbleCoin, for example, to post a "tip jar" address and let people unilaterally send you funds. However, this is possible in Tari, thanks to TariScript.
- Stealth addresses. Want to allow people to send you funds using one-sided payments without revealing your public key (and thus, who you are) to the world? Stealth addresses have you covered.
- Covenants. Covenants allow you to construct complex chains of transactions that follow predefined rules.
- Burn transactions. Tari offers unequivocal "burn" transactions that render the burnt outputs permanently unspendable. This is an important mechanism that underpins the DAN economy.
Proof of Work
Tari is mined using a hybrid approach. On average, 60% of block rewards come from Monero merge-mining, while 40% come from the Sha3x algorithm. Blocks are produced every 2 minutes, on average.
The role of the base layer
The Base Layer fulfils these, and only these, major roles:
- It manages and enforces the accounting and consensus rules of the base Tari (XTR) token. This includes standard payments, and simple smart contracts such as one-sided payments and cross-chain atomic swaps.
- It maintains the Validator node register.
- Maintain a register of smart contract templates. This allows users to verify that digital assets are running the code that they expect and includes functionality like version tracking.
- Provides a global reference clock for the digital assets layer to help it resolve certain operational failure modes.
Digital Assets Network
The DAN is focused on achieving high speed and scalability while maintaining a high degree of decentralisation.
The DAN itself is made up of two conceptual levels. On the more fundamental level, the consensus layer uses Cerberus and emergent HotStuff to reach consensus on state changes in the DAN in a highly scalable, decentralised way.
A big, and probably the biggest, advantage of this approach is that assets in disparate smart contracts can easily interact, without the need for slow, honey pot-shaped bridges.
Then there is the semantic layer, which is where the Tari contracts are compiled, run and verified inside sandboxed Tari virtual machines.
Together, these levels provide that smart contract enabled digital assets layer, that we've simply been calling the DAN.
Interplay of the layers
In general, the base layer knows nothing about the specifics of what is happening on the side-chain. It only cares that no Tari is created or destroyed, and that the flow of funds in and out of side chains are carried out by the appropriate authorised agents.
This is by design: the network cannot scale if details of digital asset contracts have to be tracked on the base layer. We envisage that there could be hundreds of thousands of contracts deployed on Tari. Some of those contracts may be enormous; imagine controlling every piece of inventory and their live statistics for a massively multiplayer online role-playing game (MMORPG). The base layer is also too slow. If any state relies on base layer transactions being confirmed, there is an immediate lag before that state change can be considered final, which kills the latency properties we seek for the DAN.
It is better to keep the two networks almost totally decoupled from the outset, and allow each network to play to its strength.
Change Log
Date | Change | Author |
---|---|---|
18 Dec 2018 | First outline | CjS77 |
30 Mar 2019 | First draft v0.0.1 | CjS77 |
19 Jun 2019 | Propose payment channel layer | CjS77 |
22 Jun 2021 | Remove payment channel layer proposal | SimianZa |
14 Jan 2022 | Update image. Expound on Base layer responsibilities | CjS77 |
10 Nov 2022 | Update overview for Cerberus | CjS77 |
The Tari Base Layer
The Tari Base Layer network comprises the following major pieces of software:
- Base Layer full node implementation. The base layer full nodes are the consensus-critical pieces of software for the Tari base layer and cryptocurrency. The base nodes validate and transmit transactions and blocks, and maintain consensus about the longest valid proof-of-work blockchain.
- Peer-to-peer communications network. All blockchain systems need a messaging mechanism. The Tari project has built its own peer-to-peer, end-to-end encrypted, DHT-based communications platform. It utilises the Noise protocol and Tor to be highly secure and anonymous. Devices behind NATs and firewalls can use Tari's communication tools with ease.
- Mining software. Miners perform proof-of-work to secure the base layer and compete to submit the
next valid block into the Tari blockchain. Tari uses two Proof of Work (PoW) algorithms, the first is
merge-mined
with Monero, and the second is the native SHA3x algorithm.
The Tari source provides three alternatives for Tari miners:
- A standalone miner for SHA3 mining
- A merge-mining proxy to be used with XMRig to merge mine Tari with Monero
- Wallet software. Client software and Application Programming Interfaces (APIs) offering means to construct transactions, query nodes for information and maintain personal private keys. The reference design includes a wallet library, an C FFI interface, gRPC client and server code, a multi-platform console text-based wallet, and Aurora, the mobile wallet.
The RFCs in this section go into great describing how the various components work, and how they fit together to provide the backbone of the Tari ecosystem.
RFC-0110/BaseNodes
Base Layer Full Nodes (Base Nodes)
Maintainer(s): Cayle Sharrock, S W van heerden and Stanley Bondi
Licence
Copyright 2019 The Tari Development Community
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Language
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as shown here.
Disclaimer
This document and its content are intended for information purposes only and may be subject to change or update without notice.
This document may include preliminary concepts that may or may not be in the process of being developed by the Tari community. The release of this document is intended solely for review and discussion by the community of the technological merits of the potential system outlined herein.
Goals
The aim of this Request for Comment (RFC) is to describe the roles that base nodes play in the Tari network as well as their general approach for doing so.
Related Requests for Comment
$$ \newcommand{\so}{\gamma} % script offset $$
Description
Broad Requirements
Tari Base Nodes form a peer-to-peer network for a proof-of-work based blockchain running the Mimblewimble protocol. The proof-of-work is performed via hybrid mining, that is merge mining with Monero and stand-alone SHA 3. Arguments for this design are presented in the overview.
Tari Base Nodes MUST carry out the following tasks:
- validate all Tari coin transactions;
- propagate valid transactions to peer nodes;
- validate all new blocks received;
- propagate validated new blocks to peer nodes;
- connect to peer nodes to catch up (sync) with their blockchain state;
- provide historical block information to peers that are syncing.
Once the Digital Assets Network (DAN) goes live, Base Nodes will also need to support the tasks described in RFC-0303_DAN. These requirements may involve but are not limited to:
- maintain an index of validator node registrations;
To carry out these tasks effectively, Base Nodes SHOULD:
- save the blockchain into an indexed local database;
- maintain an index of all Unspent Transaction Outputs (UTXOs);
- maintain a list of all pending, valid transactions that have not yet been mined (the mempool);
- manage a list of Base Node peers present on the network.
Tari Base Nodes MAY implement chain pruning strategies that are features of Mimblewimble, including transaction block compaction techniques.
Tari Base Nodes MAY also implement the following services via an Application Programming Interface (API) to clients:
- Block queries
- Kernel data queries
- Transaction queries
- Submission of new transactions
Such clients may include "light" clients, block explorers, wallets and Tari applications.
Transaction Validation and Propagation
Base nodes can be notified of new transactions by:
- connected peers;
- clients via APIs.
When a new transaction has been received, it is then passed to the mempool service where it will be validated and either stored or rejected.
The transaction is validated as follows:
- All inputs to the transaction are valid UTXOs in the UTXO set or are outputs in the current block.
- No inputs are duplicated.
- All inputs are able to be spent (they are not time-locked).
- All inputs are signed by their owners.
- All outputs have valid range proofs.
- No outputs currently exist in the current UTXO set.
- The transaction does not have timelocks applied, limiting it from being mined and added to the blockchain before a specified block height or timestamp has been reached.
- The transaction excess has a valid signature.
- The transaction weight does not exceed the maximum permitted in a single block as defined by consensus.
- The transaction excess is a valid public key. This proves that: $$ \Sigma \left( \mathrm{inputs} - \mathrm{outputs} - \mathrm{fees} \right) = 0 $$.
- The transaction excess has a unique value across the whole chain.
- The Tari script of each input must execute successfully and return the public key that signs the script signature.
- The script offset \( \so\) is calculated and verified as per RFC-0201_TariScript.
Rejected transactions are dropped without service interruption and noted in log files.
Timelocked transactions are rejected by the mempool. The onus is on the client to submit transactions once they are able to be spent.
Note: More detailed information is available in the timelocks RFC document.
Valid transactions are:
- added to the mempool;
- forwarded to peers using the transaction BroadcastStrategy.
Block/Transaction Weight
The weight of a transaction / block measured in "grams". Input, output and kernel weights reflect their respective relative storage and computation cost. Transaction fees are typically proportional to a transaction body's total weight, creating incentive to reduce the size of the UTXO set.
With a target block size of S
and 1 gram to represent N
bytes, we have
a maximum block weight of S/N
grams.
With an S
of 1MiB and N
of 16, the block and transaction body weights are as follows:
Byte size | Natural Weight | Adjust | Final | |
---|---|---|---|---|
Output | ||||
- Per output | 832 | 52 | 0 | 52 |
- Tari Script | variable | size_of(script) / 16 | 0 | size_of(script) / 16 |
- Output Features | variable | size_of(features) / 16 | 0 | size_of(features) / 16 |
Input | 169 | 11 | -2 | 9 |
Kernel size | 113 | 8 | 2 | 10 |
Pseudocode:
output_weight = num_outputs * PER_OUTPUT_GRAMS(53)
foreach output in outputs:
output_weight += serialize(output.script) / BYTES_PER_GRAM
output_weight += serialize(output.features) / BYTES_PER_GRAM
input_weight = num_inputs * PER_INPUT_GRAMS(9)
kernel_weight = num_kernels * PER_KERNEL_GRAMS(10)
weight = output_weight + input_weight + kernel_weight
where the capitalized values are hard-coded constants.
Block Validation and Propagation
The block validation and propagation process is analogous to that of transactions. New blocks are received from the peer-to-peer network, or from an API call if the Base Node is connected to a Miner.
When a new block is received, it is passed to the block validation service. The validation service checks that:
- The block has not been processed before.
- Every transaction in the block is valid.
- The proof-of-work is valid.
- The block header is well-formed.
- The block is being added to the chain with the highest accumulated proof-of-work.
- It is possible for the chain to temporarily fork; Base Nodes SHOULD store orphaned forks up to some configured depth.
- It is possible that blocks may be received out of order. Base Nodes SHOULD keep blocks that have block heights greater than the current chain tip for some preconfigured period.
- The sum of all excesses is a valid public key. This proves that: $$ \Sigma \left( \mathrm{inputs} - \mathrm{outputs} - \mathrm{fees} \right) = 0$$.
- That all kernel excess values are unique for that block and the entire chain.
- Check if a block contains already spent outputs, reject that block.
- The Tari script of every input must execute successfully and return the public key that signs the script signature.
- The script offset \( \so\) is calculated and verified as per RFC-0201_TariScript.
Because Mimblewimble blocks can simply be seen as large transactions with multiple inputs and outputs, the block validation service checks all transaction verification on the block as well.
Rejected blocks are dropped silently.
Base Nodes are not obliged to accept connections from any peer node on the network. In particular:
- Base Nodes MAY refuse connections from peers that have been added to a denylist.
- Base Nodes MAY be configured to exclusively connect to a given set of peer nodes.
Validated blocks are
- added to the blockchain;
- forwarded to every connected base node peer using the block flood BroadcastStrategy.
In addition, when a block has been validated and added to the blockchain:
- The mempool MUST also remove all transactions that are present in the newly validated block.
- The UTXO set MUST be updated by removing all inputs in the block, and adding all the new outputs into it.
Peer Validation and Propagation
When a peer attempts to connect or is shared by a trusted peer the base node will perform a peer validation. Ensuring the peer has a valid id, signature, and peer address before adding or propagating the peer back to the network.
Synchronizing and Pruning of the Chain
Syncing and pruning are discussed in detail in RFC-0140.
Archival Nodes
Archival nodes are used to keep a complete history of the blockchain since genesis block. They do not employ pruning at all. These nodes will allow full syncing of the blockchain, because normal nodes will not keep the full history to enable this. These nodes must sync from another archival node.
Pruned Nodes
Pruned nodes take advantage of the cryptography of mimblewimble to allow them to prune spent inputs and outputs beyond the pruning horizon and still validate the integrity of the blockchain i.e. no coins were destroyed or created beyond what is allowed by consensus rules. A sufficient number of blocks back from the tip should be configured because reorgs are no longer possible beyond that horizon. These nodes can sync from any other base node (archival and pruned).
Change Log
Date | Change | Author |
---|---|---|
07 Jan 2019 | First draft | CjS77 |
25 Jan 2019 | Pruning and cut-through | SWvheerden |
07 Feb 2021 | Syncing | SWvheerden |
23 Sep 2021 | Block weights | sbondi |
19 Oct 2022 | Stabilizing updates | brianp |
RFC-0111/BaseNodesArchitecture
Base Node Architecture
Maintainer(s): Cayle Sharrock
Licence
Copyright 2022 The Tari Development Community
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Language
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as shown here.
Disclaimer
This document and its content are intended for information purposes only and may be subject to change or update without notice.
This document may include preliminary concepts that may or may not be in the process of being developed by the Tari community. The release of this document is intended solely for review and discussion by the community of the technological merits of the potential system outlined herein.
Goals
The aim of this Request for Comment (RFC) is to describe the high-level Base Node architecture.
Architectural Layout
The Base Node architecture is designed to be modular, robust and performant.
The major components are separated into separate modules. Each module exposes a public Application Programming Interface (API), which communicates with other modules using asynchronous messages via futures.
Base Node Service
The Base Node Service fields requests for the local nodes chain state and also accepts newly mined blocks that are propagating across the network. The service subscribes to NewBlock and BaseNodeRequest messages via the P2P comms interface. These messages are propagated across the P2P network and can also be received directly from other nodes. The service also provides a local interface to its functionality via an asynchronous Request-Response API.
The P2P message types this service subscribes to are:
-
NewBlock: A newly mined block is being propagated over the network. If the node has not seen the block before, the node will validate it. Its action depends on the validation outcome:
- Invalid block - drop the block.
- Valid block appending to the longest chain - add the block to the local state and propagate the block to peers.
- Valid block forking off main chain - add the block to the local state and propagate the block to peers.
- Valid block building off unknown block - add the orphan block to the local state.
-
BaseNodeServiceRequest: A collection of requests for chain data from the node.
Base Node State Machine Service
This service is essentially a finite state machine that synchronises its blockchain state with its peers. When the state machine decides it needs to synchronise its chain state with a peer it uses the Base Node Sync RPC service to do so. The RPC service allows for streaming of headers and blocks in a far more efficient manner than using the P2P messaging.
This service does not provide a local API but does provide an event stream and Status Info watch channel for other modules to subscribe to.
Mempool and Mempool Sync Services
The mempool service tracks valid transactions that the node knows about, but that have not yet been included in a block. The mempool is ephemeral and non-consensus critical, and as such may be a memory-only data structure. Maintaining a large mempool is far more important for Base Nodes serving miners than those serving wallets. The mempool structure itself is a set of hash maps as described in RFC-0190
When either the node reboots, or it synchronises a default number of 5 blocks, the Mempool sync service will contact peers and sync valid mempool transactions from them. After it has synced this service runs to field such requests from other peers.
The Mempool service handles Mempool Service Requests which it can receive from the P2P comms stack via its subscriptions, via the Mempool RPC service and via an internal Request-Response API. All these interfaces provide the following calls:
- SubmitTransaction: Submit a transaction to be validated and included in the mempool. If the transaction is invalid it will be rejected with a reason.
- GetTxStateByExcess: Request the state of a transaction if it exists in the mempool using its excess signature
- GetStats and getState: Request information about the current status of the mempool.
Liveness Service
The Liveness service can be used by other modules to test the liveness of a specific peer and also periodically tests a
set of its connected peers for liveness. This service subscribes to Ping
P2P messages and responds with Pong
s. The
service gathers data about the monitored peer's liveness such as its latency. The Ping
and Pong` messages also contain
a copy of this nodes current Chain Metadata for use by the receiving nodes Chain Metadata Service.
Chain Metadata Service
The Chain Metadata Service maintains this nodes current Chain Metadata state to be sent out via Ping
and Pong
messages by the Liveness service. This node also monitors the Chain Metadata received from other peers in the Ping
and
Pong
messages received by the Liveness service. Once a full round of Pong
messages are received this service will
emit this data as an event which the Base Node State Machine monitors.
Distributed Hash Table (DHT) Service
Peer discovery is a key service that blockchain nodes provide so that the peer mesh network can be navigated by the full nodes making up the network.
In Tari, the peer-to-peer network is not only used by full nodes (Base Nodes), but also by Validator Nodes, and Tari and Digital Assets Network (DAN) clients.
For this reason, peer management is handled internally by the Comms layer. If a Base Node wants to propagate a message,
new block or transaction, for example, it simply selects a BROADCAST
strategy for the message and the Comms layer
will do the rest.
When a node wishes to query a peer for its peer list, this request will be handled by the DHTService
. It will
communicate with its Comms module's Peer Manager, and provide that information to the peer.
Blockchain Database
The blockchain database module is responsible for providing a persistent storage solution for blockchain state data. This module is used by the Base Node Service, Base Node State Machine, Mempool Service and the RPC servers. For Tari, this is delivered using the Lightning Memory-mapped Database (LMDB). LMDB is highly performant, intelligent and straightforward to use. An LMDB is essentially treated as a hash map data structure that transparently handles memory caching, disk Input/Output (I/O) and multi-threaded access. This module is shared by many services and so must be thread-safe.
Communication Interfaces
P2P communications
The Tari Peer to Peer messaging protocol is defined in RFC-0172. It is a fire-and-forget style protocol. Messages can be sent directly to a known peer, sent indirectly to an offline or unknown peer and broadcast to a set of peers. When a message is sent to specific peer it is propagated to the peers local neighbourhood and stored by those peers until it comes online to receive the message. Messages that are broadcast will be propagated around the network until the whole network has received them, they are not stored.
RPC Services
Fire-and-forget messaging is not efficient for point to point communications between online peers. For these applications the Base Node provides RPC services that present an API for clients to interact with. These RPC services provide a Request-Response interface defined by Profobuf for clients to use. RPC also allows for streaming of data which is much more efficient when transferring large amounts of data.
Examples of RPC services running in Base Node are:
- Wallet RPC service: An RPC interface containing methods used by wallets to submit and query transactions on a Base Node
- Base Node Sync RPC Service: Used by the Base Node State Machine Service to synchronise blocks
- Mempool RPC Service: Provides the Mempool Service API via RPC
gRPC Interface
Base Nodes need to provide a local communication interface in addition to the P2P and RPC communication interface. This is best achieved using gRPC. The Base Node gRPC interface provides access to the public API methods of the Base Node Service, the mempool module and the blockchain state module, as discussed above.
gRPC access is useful for tools such as local User Interfaces (UIs) to a running Base Node; client wallets running on the same machine as the Base Node that want a more direct communication interface to the node than the P2P network provides; third-party applications such as block explorers; and, of course, miners.
A non-exhaustive list of methods the base node module API will expose includes:
- Blockchain state calls, including:
- checking whether a given Unspent Transaction Output (UTXO) is in the current UTXO set;
- requesting the latest block height;
- requesting the total accumulated work on the longest chain;
- requesting a specific block at a given height;
- requesting the Merklish root commitment of the current UTXO set;
- requesting a block header for a given height;
- requesting the block header for the chain tip;
- validating signatures for a given transaction kernel;
- validating a new block without adding it to the state tree;
- validating and adding a (validated) new block to the state, and informing of the result (orphaned, fork, reorg, etc.).
- Mempool calls
- The number of unconfirmed transactions
- Returning a list of transaction ranked by some criterion (of interest to miners)
- The current size of the mempool (in transaction weight)
- Block and transaction validation calls
- Block synchronisation calls
Change Log
Date | Change | Author |
---|---|---|
2 Jul 2019 | First outline | CjS77 |
11 Aug 2019 | Updates | CjS77 |
15 Jun 2021 | Significant updates | SimianZa |
11 Sep 2022 | Minor update | JorgeAnt |
18 Jan 2023 | Minor update | JorgeAnt |
RFC-0120/Consensus
Base Layer Consensus
Maintainer(s): Cayle Sharrock, Stanley Bondi and SW van heerden
Licence
Copyright 2019 The Tari Development Community
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Language
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as shown here.
Disclaimer
This document and its content are intended for information purposes only and may be subject to change or update without notice.
This document may include preliminary concepts that may or may not be in the process of being developed by the Tari community. The release of this document is intended solely for review and discussion by the community of the technological merits of the potential system outlined herein.
Goals
The aim of this Request for Comment (RFC) is to describe the fields that a block should contain as well as all consensus rules that will determine the validity of a block.
Related Requests for Comment
Description
Blockchain consensus is a set of rules that a majority of nodes agree on that determines the state of the blockchain.
This RFC details the consensus rules for the Tari network.
Blocks
Every block MUST:
-
have exactly one valid block header, as per the Block Headers section
-
have exactly one coinbase transaction
-
have a total transaction weight less than the consensus maximum
-
be able to calculate matching Merkle roots (kernel_mr, output_mr, witness_mr, and input_mr)
-
each transaction input MUST:
- be of an allowed transaction input version
- spend an existing valid UTXO with a maturity less than the current block height
- satisfy the covenant attached to the UTXO
- have a valid script signature
- be in a canonical order (see Transaction ordering)
-
each transaction output MUST:
- be of an allowed transaction output version
- have a unique domain separated hash (
version || features || commitment || script || covenant || encrypted_values
) with the domain (transaction_output
) - have a unique commitment in the current UTXO set
- be in a canonical order (see Transaction ordering)
- have a valid range proof
- have a valid metadata signature
- contain only allowed opcodes in the script
-
each transaction kernel MUST
- have a valid kernel excess signature
- have a unique excess
-
have a valid total script offset, \( \gamma \), see script-offset.
-
the number of
BURNED
outputs MUST equal the number ofBURNED_KERNEL
kernels exactly, -
the commitment values of each burnt output MUST match the commitment value of each corresponding
BURNED_KERNEL
exactly. -
the transaction commitments and kernels MUST balance, as follows:
$$ \begin{align} &\sum_i\mathrm{Cout_{i}} - \sum_j\mathrm{Cin_{j}} + \text{fees} \cdot H \stackrel{?}{=} \sum_k\mathrm{K_k} + \text{offset} \\ & \text{for each output}, i, \\ & \text{for each input}, j, \\ & \text{for each kernel excess}, k \\ & \text{and }\textit{offset }\text{is the total kernel offset} \\ \end{align} \tag{1} $$
If a block does not conform to the above, the block SHOULD be discarded and MAY ban the peer that sent it.
Coinbase
A coinbase transaction contained in a block MUST:
- be the only transaction in the block with the coinbase flag
- consist of exactly one output and one kernel (no input)
- have a valid kernel signature
- have a value exactly equal to the emission at the block height it was minted (see emission schedule) plus the total transaction fees within the block
- have a lock-height as per consensus
- can not have a offset except 0
- can not have a script offset except 0
A coinbase transaction contained in a block CAN:
- include any arbitrary 64 bytes of extra data, coinbase-extra
Block Headers
Every block header MUST contain the following fields:
- version;
- height;
- prev_hash;
- timestamp;
- output_mr;
- output_mmr_size;
- input_mr;
- witness_mr;
- kernel_mr;
- kernel_mmr_size;
- total_kernel_offset;
- script_kernel_offset;
- nonce;
- pow.
The block header MUST conform to the following:
- The nonce and PoW must be valid for the block header.
- The [achieved difficulty] MUST be greater than or equal to the target difficulty.
- The FTL and MTP rules, detailed below.
- The block hash must not appear in the bad block list.
The Merkle roots are validated as part of the full block validation, detailed in Blocks.
If the block header does not conform to any of the above, the block SHOULD be rejected and MAY ban the peer that sent it.
Version
This is the version currently running on the chain.
The version MUST conform to the following:
- It is represented as an unsigned 16-bit integer.
- Version numbers MUST be incremented whenever there is a change in the blockchain schema or validation rules starting from 0.
- The version must be one of the allowed versions for the consensus rules at this block's height.
Height
A counter indicating how many blocks have passed since the genesis block (inclusive).
The height MUST conform to the following:
- Represented as an unsigned 64-bit integer.
- The height MUST be exactly one more than the block referenced in the
prev_hash
block header field. - The genesis block MUST have a height of 0.
Prev_hash
This is the hash of the previous block's header.
The prev_hash MUST conform to the following:
- represented as an array of unsigned 8-bit integers (bytes) in little-endian format.
- MUST be a hash of the entire contents of the previous block's header using the domain (
block_header
).
Timestamp
This is the timestamp at which the block was mined.
The timestamp MUST conform to the following:
Output_mr
The output_mr
MUST be calculated as follows: Hash (TXO MMR root
|| Hash(spent TXO bitmap
)).
The TXO MMR root
is the MMR root that commits to every transaction output that has ever existed since
the genesis block.
The spent TXO bitmap
is a compact serialized roaring bitmap containing all the output MMR leaf indexes
of all the outputs that have ever been spent.
The output_mr MUST conform to the following:
- Represented as an array of unsigned 8-bit integers (bytes) in little-endian format.
- The hashing function used MUST be blake2b with a 256-bit digest.
Output_mmr_size
This is the total size of the leaves in the output Merkle mountain range.
The Output_mmr_size MUST conform to the following:
- Represented as a single unsigned 64-bit integer.
Input_mr
This is the Merkle root of all the inputs in the block, which consists of the hashed inputs. It is used to prove that all inputs are correct and not changed after mining. This MUST be constructed by adding, in order, the hash of every input contained in the block.
The input_mr MUST conform to the following:
- Represented as an array of unsigned 8-bit integers (bytes) in little-endian format.
- The hashing function must be blake2b with a 256-bit digest.
Witness_mr
This is the Merkle root of the output witness data, specifically all created outputs’ range proofs and
metadata signatures. This MUST be constructed by
Hash ( RangeProof
|| metadata commitment signature
), in order, for every output contained in the block.
The witness_mr MUST conform to the following:
- Represented as an array of unsigned 8-bit integers (bytes) in little-endian format.
- The hashing function used must be blake2b with a 256-bit digest.
Kernel_mr
This is the Merkle root of the kernels.
The kernel_mr MUST conform to the following:
- Must be transmitted as an array of unsigned 8-bit integers (bytes) in little-endian format.
- The hashing function used must be blake2b with a 256-bit digest.
Kernel_mmr_size
This is the total size of the leaves in the kernel Merkle mountain range.
The Kernel_mmr_size MUST conform to the following:
- Represented as a single unsigned 64-bit integer.
Total_kernel_offset
This is the total summed offset of all the transactions in this block.
The total_kernel_offset MUST conform to the following:
- Must be transmitted as an array of unsigned 8-bit integers (bytes) in little-endian format
Total_script_offset
This is the total summed script offset of all the transactions in this block.
The total_script_offset MUST conform to the following:
- Must be transmitted as an array of unsigned 8-bit integers (bytes) in little-endian format
Nonce
This is the nonce used in solving the Proof of Work.
The nonce MUST conform to the following:
- MUST be transmitted as an unsigned 64-bit integer;
- for RandomX blocks, thus MUST be 0
PoW
This is the Proof of Work algorithm used to solve the Proof of Work. This is used in conjunction with the Nonce.
The [PoW] MUST contain the following:
- pow_algo as an enum (0 for RandomX, 1 for Sha3x).
- pow_data for RandomX blocks as an array of unsigned 8-bit integers (bytes) in little-endian format, containing the RandomX merge-mining Proof-of-Work data.
- the RandomX seed, stored as
randomx_key
within the RandomX block, must have not been first seen in a block with confirmations more thanmax_randomx_seed_height
.
- the RandomX seed, stored as
- pow_data for Sha3x blocks MUST be empty.
Difficulty Calculation
The target difficulty represents how difficult it is to mine a given block. This difficulty is not fixed and needs to constantly adjust to changing network hash rates.
The difficulty adjustment MUST be calculated using a linear-weighted moving average (LWMA) algorithm (2) $$ \newcommand{\solvetime}{ \mathrm{ST_i} } \newcommand{\solvetimemax}{ \mathrm{ST_{max}} } $$
Symbol | Value | Description |
---|---|---|
N | 90 | Target difficulty block window |
T | SHA3x: 300 RandomX: 200 | Target block time in seconds. The value used depends on the PoW algorithm being used. |
\( \solvetimemax \) | SHA3x: 1800 RandomX: 1200 | Maximum solve time. This is six times the target time of the current PoW algorithm. |
\( \solvetime \) | variable | The timestamp difference in seconds between block i and i - 1 where \( 1 \le \solvetime \le \solvetimemax \) |
\( \mathrm{D_{avg}} \) | variable | The average difficulty of the last N blocks |
$$ \begin{align} & \textit{weighted_solve_time} = \sum\limits_{i=1}^N(\solvetime*i) \\ & \textit{weighted_target_time} = (\sum\limits_{i=1}^Ni) * \mathrm{T} \\ & \textit{difficulty} = \mathrm{D_{avg}} * \frac{\textit{weighted_target_time}}{\textit{weighted_solve_time}}\\ \end{align} \tag{2} $$
It is important to note that the two proof of work algorithms are calculated independently. i.e., if the current block uses SHA3x proof of work, the block window and solve times only include SHA3x blocks and vice versa.
FTL
The Future Time Limit. This is how far into the future a time is accepted as a valid time. Any time that is more than the FTL is rejected until such a time that it is not more than the FTL. The FTL is calculated as (T*N)/20 with T and N defined as: T: Target time - This is the ideal time that should pass between blocks that have been mined. N: Block window - This is the number of blocks used when calculating difficulty adjustments.
MTP
The Median Time Passed (MTP) is the lower bound calculated by taking the median average timestamp of the last N blocks. Any block with a timestamp that is less than MTP will be rejected.
Total accumulated proof of work
This is defined as the total accumulated proof of work done on the blockchain. Tari uses two independent proof of work algorithms rated at different difficulties. To compare them, we simply multiply them together into one number: $$ \begin{align} \textit{accumulated_randomx_difficulty} * \textit{accumulated_sha3x_difficulty} \end{align} \tag{3} $$ This value is used to compare chain tips to determine the strongest chain.
Transaction Ordering
The order in which transaction inputs, outputs, and kernels are added to the Merkle mountain range completely changes the final Merkle root. Input, output, and kernel ordering within a block is, therefore, part of the consensus.
The block MUST be transmitted in canonical ordering. The advantage of this approach is that sorting does not need to be done by the whole network, and verification of sorting is exceptionally cheap.
- Transaction outputs are sorted lexicographically by the byte representation of their Pedersen commitment i.e. ( \(k \cdot G + v \cdot H\) ).
- Transaction kernels are sorted lexicographically by the excess signature byte representation.
- Transaction inputs are sorted lexicographically by the hash of the output that is spent by the input.
Change Log
Date | Change | Author |
---|---|---|
11 Oct 2022 | First stable | SWvHeerden |
13 Mar 2023 | Add mention of coinbase extra | SWvHeerden |
05 Jun 2023 | Add coinbase excess rule | SWvHeerden |
01 Aug 2023 | Add Randomx rule, fix Sha and Monero names | SWvHeerden |
RFC-0131/Mining
Full-node Mining on Tari Base Layer
Maintainer(s): Hansie Odendaal
Licence
Copyright 2020 The Tari Development Community
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Language
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as shown here.
Disclaimer
This document and its content are intended for information purposes only and may be subject to change or update without notice.
This document may include preliminary concepts that may or may not be in the process of being developed by the Tari community. The release of this document is intended solely for review and discussion by the community of the technological merits of the potential system outlined herein.
Goals
This document describes the final proof-of-work strategy proposal for Tari main net.
Related Requests for Comment
This RFC replaces and deprecates RFC-0130: Mining
Description
The following proposal draws from many of the key points of debate from the Tari community on the topic of Tari’s main chain proof of work strategy. The early working assumption was that Tari would be 100% merged mined by Monero.
Having a single, merge mined Proof of Work (PoW) algorithm would be nice, but the risks of hash rate attacks are real and meaningful. Double-spends and altering history can happen with >50% hash power, while selfish mining and eclipse attacks can happen with >33% hash power for a poorly connected attacker and >25% for a well-connected attacker (see Merged Mining: Analysis of Effects and Implications). Any non-merge mined PoW algorithm that is currently employed is even more vulnerable, especially if one can simply buy hash rate on platforms like NiceHash.
Hybrid mining is a strategy that apportions blocks across multiple PoW algorithms. If the hybrid algorithms are independent, then one can get at most x% of the total hash rate, where x is the fraction of blocks apportioned to that algorithm. As a result, the threat of a double-spend or selfish mining attack is mitigated, and in some cases eliminated.
This proposal puts forward Hybrid mining as the Tari PoW algorithm.
The choice of algorithms
In hybrid mining, "independence" of algorithms is key. If the same mining hardware can be used on multiple PoW algorithms in the hybrid mining scheme, you may as well not bother with hybrid mining because miners can simply switch between them.
In practice, no set of algorithms is genuinely independent. The best we can do is try to choose algorithms that work best on CPUs, GPUs, and ASICs. In truth, the distinction between GPUs and ASICs is only a matter of time. Any "GPU-friendly" algorithm is ASIC-friendly, too; it's just a case of whether the capital outlay for fabricating them is worth it, and this will eventually become true for any algorithm that supplies PoW for a growing market cap.
With this in mind, we should only choose one GPU/ASIC algorithm and one for CPUs.
An excellent technical choice would be merge mining with Monero using RandomX as a CPU-only algorithm and SHA3, also known as Keccak , for a GPU/ASIC-friendly algorithm. Using a custom configuration of such a simple and well-understood algorithm means there is a low likelihood of unforeseen optimizations that will give a single miner a considerable advantage. It also means that it stands a good chance of being "commoditized" when ASICs are eventually manufactured. This would mean that SHA3 ASICs are widely available from multiple suppliers.
The block distribution
A 50/50 split in hash rate among algorithms minimises the chance of hash rate attacks. However, sufficient buy-in is required, especially with regard to merge mining RandomX with Monero. To make it worthwhile for a Monero pool operator to merge mine Tari, but still guard against hash rate attacks and to be inclusive of independent Tari supporters and enthusiasts, a 60/40 split is employed in favour of merge mining RandomX with Monero.
The difficulty adjustment strategy
The choice of difficulty adjustment algorithm is important. In typical hybrid mining strategies, each algorithm operates completely independently with a scaled target block time. Tari testnet has been running very successfully using the Linear Weighted Moving Average (LWMA) from Bitcoin & Zcash Clones version 2018-11-27. This LWMA difficulty adjustment algorithm has also been tested in simulations, and it proved to be a good choice in the multi-PoW scene as well.
Final proposal, hybrid mining details
Tari's proof-of-work mining algorithm is summarized below:
- Two mining algorithms, with an average combined target block time of 120 s, to match Monero's block interval.
- A log-weighted moving average difficulty adjustment algorithm using a window of 90 blocks.
Tari mining hash
First, the block header is hashed with the 256-bit Blake2b hashing algorithm using
domain-separated hashing, using the domain
com.tari.base_layer.core.blocks
. The fields are hashed in the order:
- version
- block height
- previous header hash
- timestamp
- input Merkle root
- output Merkle root
- output Merkle mountain range size
- witness Merkle root
- kernel Merkle root
- kernel Merkle mountain range size
- total kernel offset
- total script offset
This hash is used in both the SHA-3 and RandomX proof-of-work algorithms. The header version for the Tari Genesis block is 1.
RandomX
Monero blocks that are merge-mining Tari MUST include the Tari mining hash in the extra field of the Monero coinbase transaction.
Tari also imposes the following consensus rules:
- The
seed_hash
MUST only be used for 3000 blocks, after which a block MUST be discarded if it's used again. - The little-endian difficulty MUST be equal to or greater than the target for that block as determined by the LWMA for Tari.
- The LWMA MUST use a target time of 200 seconds.
- MUST set the header field PoW:pow_algo as 0 for a Monero block
- MUST encode the following data into the Pow:Pow_data field:
- Monero BlockHeader,
- RandomX VM key,
- Monero transaction count,
- Monero merkle root,
- Monero coinbase merkle proof and,
- Monero coinbase transaction
Sha-3x
Tari's independent proof-of-work algorithm is very straightforward.
Calculate the triple hash of the following input data:
- Nonce (8 bytes)
- Tari mining hash (32 bytes)
- PoW record (for Sha-3x, this is always a single byte of value 1)
That is, the nonce in little-endian format, mining hash and the PoW record are chained together and hashed by the Keccak Sha3-256 algorithm. The result is hashed again, and this result is hashed a third time. The result of the third hash is compared to the target value of the current block difficulty.
If the entire 64-bit nonce space is exhausted without finding a valid block, the mining algorithm must request a new Tari mining hash from the Tari base node. The simplest way to do this is to update the timestamp field in the block header, keeping everything else constant.
Tari imposes the following consensus rules:
- The Big endian difficulty MUST be equal or greater than the target difficulty for that block as determined by the
LWMA for Tari. The difficulty and target are related by the equation
difficulty = (2^256 - 1) / target
. - MUST set the header field PoW:pow_algo as 1 for a Sha block.
- The PoW:pow_data field is empty
- The LWMA MUST use a target time of 300 seconds.
A triple hash is selected to keep the requirements on hardware miners (FPGAs, ASICs) fairly low. But we also want to avoid making the proof-of-work immediately "NiceHashable". There are several coins that already use a single or double SHA3 hash, and we'd like to avoid having that hashrate immediately deployable against Tari.
Historical note: In general, little-endian representations for integers are used in Tari. There are a few places where big-endian representations are used, in the PoW hash and in some merge-mined fields in particular. The latter is required to be compatible with the Monero specification. But we also use the big-endian representation for the block hash due to a historical convention. The Bitcoin white paper describes the block hash target as containing "a certain number of leading zeros" (paraphrased). This is obviously a big-endian representation. If we used little-endian, our block hashes would have trailing zeroes. So we use the big-endian form to satisfy the expected in block explorers and such that block hashes should always start with a series of zeroes.
Stabilisation note
This RFC is stable as of PR#4862
Change Log
Date | Change | Author |
---|---|---|
2022-11-25 | Update mining hash decsription | CjS77 |
2022-10-26 | Finalise SHA-3 algorithm | CjS77 |
2022-10-11 | First outline | SWvHeerden |
RFC-0132/MergeMiningMonero
Tari protocol for Merge Mining with Monero
Maintainer(s): Stanley Bondi
Licence
Copyright 2020 The Tari Development Community
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Language
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as shown here.
Disclaimer
This document and its content are intended for information purposes only and may be subject to change or update without notice.
This document may include preliminary concepts that may or may not be in the process of being developed by the Tari community. The release of this document is intended solely for review and discussion by the community of the technological merits of the potential system outlined herein.
Goals
This document describes the specific protocol Tari uses to merge mine with Monero.
Related Requests for Comment
Introduction
Tari employs a hybrid mining strategy, accepting 2 mining algorithms whose difficulties are independent of each other as discussed in RFC-0131_Mining.html. This RFC details a protocol to enable Tari to accept Monero proof of work, enabling participating miners a chance to produce a valid block for either or both chain without additional mining effort.
The protocol must enable a Tari base node to make the following assertions:
REQ 1. The achieved mining difficulty exceeds target difficulty as dictated by Tari consensus,
REQ 2. The Monero block was constructed after the current Tari tip block. This is to prevent a miner from submitting blocks from the parent chain that satisfy the auxiliary chain's difficulty without doing new work.
It's worth noting that a Tari base node never has to contact or download data from Monero to make these assertions.
Merge Mining on Tari
A new Tari block template is obtained from a Tari base node by calling the get_new_block_template
gRPC method, setting Monero
as the chosen PoW algorithm.
The Monero
algorithm must be selected so that the correct mining difficulty for the Monero algorithm is returned. Remember, that Monero and SHA difficulties
are independent (See RFC-0131_Mining.html). Next, a coinbase transaction is requested from a Tari Wallet for a give height by calling
the get_coinbase
gRPC function.
Next, the coinbase transaction is added to the new block template and passed back to the base node for the new MMR roots to be calculated.
Furthermore, the base node constructs a Blake256 hash of some of the Tari header fields. We'll call this hash the merge mining hash \( h_m \) that commits to
the following header fields in order: version
, height
,prev_hash
,timestamp
,input_mr
, output_mr
,output_mmr_size
,kernel_mr
,
kernel_mmr_size
,total_kernel_offset
,total_script_offset
. Note, this hash does not include the pow
and nonce
fields, as these fields are set as part of mining.
To have the chance of mining a Monero block as well as a Tari block, we must obtain a new valid monero block template, by calling get_block_template.
This returns a blocktemplate_blob
, that is, a serialized Monero block containing the Monero block header, coinbase and a list of hashes referencing the
transactions included in the block. Additionally, a blockhashing_blob
is a fixed size blob containing serialized_monero_header
, merkle_tree_root
and
txn_count
concatenated together. The merkle_tree_root
is a merkle root of the coinbase + the transaction hashes contained in the block.
pub struct Block {
/// The block header
pub header: BlockHeader,
/// Coinbase transaction a.k.a miner transaction
pub miner_tx: Transaction,
/// References to the transactions included in this block
pub tx_hashes: Vec<hash::Hash>,
}
fig 1. The Monero block struct
Next, modify the Monero block template by including the merge mining hash \( h_m \) in the extra fields of the coinbase transaction. Monero has a merge mining subfield
to accommodate this data. Importantly, the extra field data part of the coinbase transaction hash and therefore the merkle_tree_root
, the blockhashing_blob
must be
reconstructed. A rust port of Monero's tree hash algorithm is needed to achieve this. The coinbase hash MUST be the first element to be hashed when constructing the merkle_tree_root
.
This satisfies REQ 2, proving that the proof-of-work was performed for the Tari block.
The block may now be mined. Once a solution is found that satisfies the Tari difficulty, the miner must include enough data to allow the Tari blockchain to assert REQ 1 and REQ 2.
Concretely, A miner must serialize MoneroPowData
using Monero consensus encoding and add it to the pow_data
field in the Tari header.
pub struct MoneroPowData {
/// Monero header fields
header: MoneroBlockHeader,
/// randomX vm key
randomx_key: FixedByteArray, // Fixed 64 bytes
/// transaction count
transaction_count: u16,
/// transaction root
merkle_root: MoneroHash,
/// Coinbase merkle proof hashes
coinbase_merkle_proof: MerkleProof,
/// Coinbase tx from Monero
coinbase_tx: MoneroTransaction,
}
fig 2. Monero PoW data struct serialized in Tari blocks
pub struct MerkleProof {
branch: Vec<Hash>,
depth: u16,
path_bitmap: u32,
}
fig 3. Merkle proof struct
A verifier may now check that the coinbase_tx
contains the merge mining hash \( h_m \), and validate the coinbase_merkle_proof
against the transaction_root
.
The coinbase_merkle_proof
contains the minimal proof required to construct the transaction_root
.
For example, a proof for a merkle tree of 4 hashes will require 2 hashes (h_1, h_23) of 32 bytes each, 4 bytes for the path bitmap and 2 bytes for the depth.
Root*
/ \
h_c1* h_23
/ \
h_c* h_1
* Not included in proof
Serialisation
For Monero proof-of-work, Monero consensus encoding MUST be used to serialize the MoneroPowData
struct. Given the same inputs,
this encoding will byte-for-byte the same. The encoding uses VarInt for all integer types, allowing byte-savings, in particular
for fields that typically contain small values. Importantly, extra bytes that a miner could tack onto the end of the pow_data
field
are expressly disallowed.
Merge Mining Proxy
The Tari merge mining proxy proxies the Monero daemon RPC interface. It behaves as a middleware that implements the merge mining protocol detailed above. This allows existing Monero miners to merge mine with Tari without having to make changes to mining software.
The proxy must be configured to connect to a monerod
instance, a Tari base node, and a Tari console wallet. Most requests
are forwarded "as is" to monerod
, however some are intercepted and augmented before being returned to the miner.
get_block_template
Once monerod
has provided the block template response, the proxy retrieves a Tari block template and coinbase,
and assembles the Tari block. The merge mining hash \( h_m \) is generated and added to the Monero coinbase. The modified
blockhashing_blob
and blocktemplate_blob
are returned to the miner. The difficulty is set to min(monero_difficulty, tari_difficulty)
so that the miner submits the found block at either chain's difficulty. The Tari block template is cached for later submission.
submit_block
The miner submits a solved Monero block (at a difficulty of min(monero_difficulty, tari_difficulty)
) to the proxy. The cached
Tari block is retrieved, enriched with the MoneroPowData
struct and submitted to the Tari base node.
Change Log
Date | Change | Author |
---|---|---|
26 Oct 2022 | Stablise RFC | CjS77 |
21 Oct 2022 | Update fields | Cifko |
RFC-0140/SyncAndSeeding
Synchronizing the Blockchain: Archival and Pruned Modes
Maintainer(s): S W van Heerden
Licence
Copyright 2022 The Tari Development Community
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Language
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as shown here.
Disclaimer
This document and its content are intended for information purposes only and may be subject to change or update without notice.
This document may include preliminary concepts that may or may not be in the process of being developed by the Tari community. The release of this document is intended solely for review and discussion by the community of the technological merits of the potential system outlined herein.
Goals
This Request for Comment (RFC) describes the syncing and pruning process.
Related Requests for Comment
Descriptions
Syncing
When a new node comes online, loses connection or encounters a chain reorganization that is longer than it can tolerate, it must enter syncing mode. This will allow it to recover its state to the newest up-to-date state. Syncing can be divided into two SynchronizationStrategys: complete sync and horizon sync. Complete sync means that the node communicates with an archive node to get the complete history of every single block from genesis block. Horizon Sync involves the node getting every block from its pruning horizon to current head, as well as every block header up to the genesis block.
To determine if the node needs to synchronise, the node will monitor the broadcasted chain_metadata
messages provided by its neighbours. The fields in the chain_metadata
messages MUST be:
Field | Description |
---|---|
height_of_longest_chain | 64-bit unsigned |
best_block | hash of the tip block (32-bit) |
pruning_horizon | 64-bit unsigned |
pruned_height | 64-bit unsigned |
accumulated_difficulty | 128-bit unsigned |
timestamp | 64-bit unsigned |
Complete Sync
Complete sync is only available from archival nodes, as these will be the only nodes that will be able to supply the complete history required to sync every block with every transaction from genesis block up onto current head.
Complete Sync Process
Once the base node has determined that it is lagging behind the network tip it will start to synchronise with the peer it determines to have all the data required to synchronise.
The syncing process MUST be done in the following steps:
- Set SynchronizationState to
header_sync
. - Sync all missing headers from the genesis block to the current chain tip. The initial header sync allows the node to confirm that the syncing peer does indeed have a fully intact chain from which to sync that adheres to this node's consensus rules and has a valid proof-of-work that is higher than any competing chains.
- Set SynchronizationState to
block_sync
. - Start downloading blocks from sync peer starting with the oldest block in our database. A fresh node will start from the genesis block.
- Download all block up to current head, validating and adding the blocks to the local chain storage as we go.
- Once all blocks have been downloaded up and including the current network tip set the SynchronizationState to
listening
.
After this process, the node will be in sync, and will be able to process blocks and transactions normally as they arrive.
Horizon Sync Process
The horizon sync process MUST be done in the following steps:
- Set SynchronizationState to
header_sync
. - Sync all missing headers from the genesis block to the current chain tip. The initial header sync allows the node to confirm that the syncing peer does indeed have a fully intact chain from which to sync that adheres to this nodes consensus rules and has a valid proof-of-work that is higher than any competing chains.
- Set SynchronizationState to
horizon_sync
. - Download all kernels from the current network tip back to this node's pruning horizon.
- Validate kernel MMR root against headers.
- Download all utxo's from the current network tip back to this node's pruning horizon.
- Validate outputs and utxo MMR.
- Validate the chain balances with the expected total emission that the final sync height.
- Once all kernels and utxos have been downloaded from the network tip back to this node's pruning horizon set
the SynchronizationState to
block_sync
. This hands over further syncing to the standard sync protocol which should return to thelistening
state if no further data has been received from peers.
After this process, the node will be in sync, and will be able to process blocks and transactions normally as they arrive.
Keeping in Sync
The node that is in the listening
state SHOULD periodically test a subset of its peers with ping messages to ensure
that they are alive. When a node sends a ping message, it MUST include the all the chain_metadata
fields. The
receiving node MUST reply with a pong message, which should also include its version of the chain_metadata
information.
When a node receives pong replies from the current ping round, or the timeout expires, the collected chain_metadata
replies will be examined to determine what the current best chain is, i.e. the chain with the most accumulated work.
If the best chain is longer than out chain data the node will set SynchronizationState to header_sync
and catch up
with the network.
Chain Forks
Chain forks occur in all decentralized proof-of-work blockchains. When the local node is in the listening
state it
will detect that it has fallen behind other nodes in the network. It will then perform a header sync and during the
header sync process will be able to detect that a chain fork has occurred. The header sync process will then determine
which chain is the correct chain with the highest accumulated work. If required this node will switch the best chain
and proceed to sync the new blocks required to catch up to the correct chain. This process is called a chain
reorganization or reorg.
Pruning
In Mimblewimble, the state can be completely verified using the current UTXO set (which contains the output commitments and range proofs), the set of excess signatures (contained in the transaction kernels) and the PoW. The full block and transaction history is not required. This allows base layer nodes to remove old spent inputs from the blockchain storage.
Pruning is only for the benefit of the local Base Node, as it reduces the local blockchain size. Pruning only happens after the block is older than the pruning horizon height. A Base Node will either run in archival mode or pruned mode. If the Base Node is running in archive mode, it MUST NOT prune.
When running in pruning mode, Base Nodes SHOULD remove all spent outputs that are older than the pruning horizon in their current stored UTXO set when a new block is received from another Base Node.
Change Log
Date | Change | Author |
---|---|---|
07 Feb 2019 | First draft | SWvheerden |
13 Jul 2021 | Update to better reflect the current implementation | philipr-za |
30 Sep 2022 | Rename title for clarity | stringhandler |
10 Nov 2022 | Minor update to reflect implementation | mrnaveira |
10 Oct 2023 | Minor update to reflect prune mode pruning interval | SWvheerden |
RFC-0150/Wallets
Base Layer Wallet Module
Maintainer(s): Cayle Sharrock
Licence
Copyright 2019 The Tari Development Community
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Language
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as shown here.
Disclaimer
This document and its content are intended for information purposes only and may be subject to change or update without notice.
This document may include preliminary concepts that may or may not be in the process of being developed by the Tari community. The release of this document is intended solely for review and discussion by the community of the technological merits of the potential system outlined herein.
Goals
The aim of this Request for Comment (RFC) is to propose the functionality and techniques required by the Base Layer Tari wallet module. The module exposes the core wallet functionality on which user-facing wallet applications may be built.
Related Requests for Comment
This RFC is derived from a proposal first made in this issue.
Description
Key Responsibilities
The wallet software is responsible for constructing and negotiating transactions for transferring and receiving Tari coins on the base layer. It should also provide functionality to generate, store and recover a master seed key and derived cryptographic keypairs that can be used for Base Layer addresses and signing of transactions.
Functional Details
A detailed description of the required functionality of the Tari software wallet is provided in three parts:
- basic transaction functionality,
- key management features, and
- the different methods for recovering the wallet state of the Tari software wallet.
Basic Transaction Functionality
- Wallet MUST be able to send and receive Tari coins using Mimblewimble transactions.
- Wallet SHOULD be able to establish a connection with other user wallets to interactively negotiate:
- construction of the transaction,
- signing of multi-signature transactions.
- Wallet SHOULD be implemented as a library or Application Programming Interface (API) so that Graphical User Interface (GUI) or Command Line Interface (CLI) applications can be developed on top of it.
- Wallet MUST be able to establish connection to the base node, submit transactions and monitor the Tari blockchain.
- Wallet SHOULD maintain an internal ledger to keep track of the Tari coin balance.
- Wallet MAY offer transaction fee estimation, taking into account:
- transaction byte size
- network congestion
- desired transaction priority
- Wallet SHOULD be able to monitor and present states (
Spent
,Unspent
orUnconfirmed
) of previously submitted transactions, by querying information from the connected base node. - Wallet SHOULD present the total
Spent
,Unspent
orUnconfirmed
transactions in a summarized form. - Wallet SHOULD be able to update its software to patch potential security vulnerabilities. Automatic updating SHOULD be enabled by default, but users can decide to opt out.
- Wallet SHOULD feature a caching mechanism for querying operations to reduce bandwidth consumption.
Key Management Features
- Wallet MUST be able to generate a master seed key for the wallet by using at least one of the following methods:
- input from the user (e.g. when restoring a wallet or in testing),
- user-defined set of mnemonic word sequences using known word lists,
- cryptographically secure random number generator.
- Wallet SHOULD be able to generate derived, transactional, cryptographic keypairs from the master seed key using deterministic keypair generation.
- Wallet SHOULD store wallet state using a password or passphrase encrypted persistent key-value database.
- Wallet SHOULD provide the ability to store backup of the wallet state to a single encrypted file to simplify wallet recovery and reconstruction at a later stage.
- Wallet MAY provide the ability to export the master seed key or the wallet state as a printable paper wallet, using coded markers.
State Recovery
- Wallet MUST be able to reconstruct the wallet state from a manually entered master seed key.
- Wallet MUST have a mechanism to systematically scan through the Tari blockchain and mempool for
Unspent
andUnconfirmed
transactions, using keys derived from the master key. - The master seed key SHOULD be derivable from a set of mnemonic word sequences using known word lists.
- Wallet MAY enable the reconstruction of the master seed key by scanning a coded marker of a paper wallet.
State Recovery: Process Overview
If the wallet database has been lost, corrupted or otherwise damaged, the outputs contained within (UTXOs) can still be recovered from the Tari blockchain, given you provide the valid recovery keys. When the wallet is first initialized in recovery mode, it attempts to synchronize with available base nodes, pulling blocks, attempting to recognize outputs attributed to that particular wallet.
If one can successfully decrypt the encrypted value, then the UTXO is successfully recognized. The next step is to attempt the mask (blinding factor) recovery by rewinding the range proof. All recognized and verified outputs are stored in the newly initialized, local wallet database, available for further spending.
The recovery of simple and stealth one-sided outputs is a bit more complex as we first have to recognize the output by its script pattern, before we can try to decrypt the encrypted value.
An output is recognized if it matches either of the following input script patterns:
- The standard output is the simplest, having a single
Nop
instruction. - The simple one-sided is matched by the
[Opcode::PushPubKey(scanned_pk)]
so if thescanned_pk
matches the key derived from the recovery phrase - it's recognized, - The stealth one-sided is similar to its simple counterpart with only the script pattern being different
[Opcode::PushPubKey(nonce), Opcode::Drop, Opcode::PushPubKey(scanned_pk)]
, matching by the last providedOpcode::PushPubKey(scanned_pk)
instruction.
Change Log
Date | Change | Author |
---|---|---|
26 Oct 2022 | Stabilized RFC | CjS77 |
14 Nov 2022 | Added table of contents, recovery process overview and a few minor adjustments | agubarev & hansieodendaal |
RFC-0155/TariAddress
TariAddress specification
Maintainer(s):SW van Heerden
Licence
Copyright 2024. The Tari Development Community
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Language
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as shown here.
Disclaimer
This document and its content are intended for information purposes only and may be subject to change or update without notice.
This document may include preliminary concepts that may or may not be in the process of being developed by the Tari community. The release of this document is intended solely for review and discussion by the community regarding the technological merits of the potential system outlined herein.
Goals
This document outlines the specification for Tari Address, which are encoded wallet addresses used to verify wallet addresses, features, and networks. The address should have human-readable network and feature identification and contain all information required to send transactions to a wallet owning an address.
Related Requests for Comment
None
Description
Wallet addresses should contain all the necessary information for sending transactions to the corresponding wallet. Initially, a wallet must declare its supported features and the network it operates on. To maintain readability, distinct identifiers are required for both the features and the network.
For typical interactive Mimblewimble (MW) transactions, a public key is necessary for communication. The private key associated with this MUST BE securely stored by the node to prevent spoofing. This private key functions as the spend key for deriving the actual spend key for the Unspent Transaction Output (UTXO).
However, when non-interactive transactions are initiated, the process becomes more complex. If the receiving wallet is a standard one, it possesses all the essential information for spending the transaction. Yet, if the recipient utilizes a hardware device like a ledger, the spending information is inaccessible to the wallet. Thus, a secondary view key becomes necessary. While the wallet can share the private key of this view key with another party for UTXO viewing purposes, it cannot be used for spending.
Additionally, a checksum can be included to detect errors when encoding the address as bytes. Emojis can also be easily incorporated into the encoding process by assigning each u8 an emoji.
The specification
Address
Each address consists of four parts: View key, Spend key, Network, and Features.
View key
This key allows a node to grant view access to its transactions. Possession of the private key in this key pair SHOULD enable an entity to view all transactions associated with a wallet.
Spend key
Utilized to compute the spend key of a UTXO and communicate with the node over the network. The private key associated with this key pair must be securely kept hidden.
Features
Indicates the supported features of the wallet, such as interactive and one-sided transactions. Currently, this is represented by an encoded u8, with each bit denoting a specific feature.
Network
Specifies the Tari network the wallet operates on, e.g., Esmeralda, Nextnet, etc.
Checksum
The checksum is only included when encoding the address as bytes, hex, or emojis. For the checksum, the: DammSum algorithm is employed,
with k = 8
and m = 32
, resulting in an 8-bit checksum.
Encoding
Bytes
When generating a byte representation of the wallet, the following format is used: [0]: Network encoded as u8 [1]: Raw u8 representing features [2..33]: Public view key encoded as u8 [35..65]: Public spend key encoded as u8 [66]: DammSum checksum
For nodes lacking a distinct view key, where the view key and spend key are identical, their addresses can be encoded as follows: [0]: Network encoded as u8 [1]: Raw u8 representing features [2..33]: Public spend key encoded as u8 [34]: DammSum checksum
Hex
Each byte in the byte representation is encoded as two hexadecimal characters.
Emoji Encoding
An emoji alphabet of 256 characters has been selected, each assigned a unique index from 0 to 255 inclusive. The list of chosen emojis is as follows:
🦋 | 📟 | 🌈 | 🌊 | 🎯 | 🐋 | 🌙 | 🤔 | 🌕 | ⭐ | 🎋 | 🌰 | 🌴 | 🌵 | 🌲 | 🌸 |
🌹 | 🌻 | 🌽 | 🍀 | 🍁 | 🍄 | 🥑 | 🍆 | 🍇 | 🍈 | 🍉 | 🍊 | 🍋 | 🍌 | 🍍 | 🍎 |
🍐 | 🍑 | 🍒 | 🍓 | 🍔 | 🍕 | 🍗 | 🍚 | 🍞 | 🍟 | 🥝 | 🍣 | 🍦 | 🍩 | 🍪 | 🍫 |
🍬 | 🍭 | 🍯 | 🥐 | 🍳 | 🥄 | 🍵 | 🍶 | 🍷 | 🍸 | 🍾 | 🍺 | 🍼 | 🎀 | 🎁 | 🎂 |
🎃 | 🤖 | 🎈 | 🎉 | 🎒 | 🎓 | 🎠 | 🎡 | 🎢 | 🎣 | 🎤 | 🎥 | 🎧 | 🎨 | 🎩 | 🎪 |
🎬 | 🎭 | 🎮 | 🎰 | 🎱 | 🎲 | 🎳 | 🎵 | 🎷 | 🎸 | 🎹 | 🎺 | 🎻 | 🎼 | 🎽 | 🎾 |
🎿 | 🏀 | 🏁 | 🏆 | 🏈 | ⚽ | 🏠 | 🏥 | 🏦 | 🏭 | 🏰 | 🐀 | 🐉 | 🐊 | 🐌 | 🐍 |
🦁 | 🐐 | 🐑 | 🐔 | 🙈 | 🐗 | 🐘 | 🐙 | 🐚 | 🐛 | 🐜 | 🐝 | 🐞 | 🐢 | 🐣 | 🐨 |
🦀 | 🐪 | 🐬 | 🐭 | 🐮 | 🐯 | 🐰 | 🦆 | 🦂 | 🐴 | 🐵 | 🐶 | 🐷 | 🐸 | 🐺 | 🐻 |
🐼 | 🐽 | 🐾 | 👀 | 👅 | 👑 | 👒 | 🧢 | 💅 | 👕 | 👖 | 👗 | 👘 | 👙 | 💃 | 👛 |
👞 | 👟 | 👠 | 🥊 | 👢 | 👣 | 🤡 | 👻 | 👽 | 👾 | 🤠 | 👃 | 💄 | 💈 | 💉 | 💊 |
💋 | 👂 | 💍 | 💎 | 💐 | 💔 | 🔒 | 🧩 | 💡 | 💣 | 💤 | 💦 | 💨 | 💩 | ➕ | 💯 |
💰 | 💳 | 💵 | 💺 | 💻 | 💼 | 📈 | 📜 | 📌 | 📎 | 📖 | 📿 | 📡 | ⏰ | 📱 | 📷 |
🔋 | 🔌 | 🚰 | 🔑 | 🔔 | 🔥 | 🔦 | 🔧 | 🔨 | 🔩 | 🔪 | 🔫 | 🔬 | 🔭 | 🔮 | 🔱 |
🗽 | 😂 | 😇 | 😈 | 🤑 | 😍 | 😎 | 😱 | 😷 | 🤢 | 👍 | 👶 | 🚀 | 🚁 | 🚂 | 🚚 |
🚑 | 🚒 | 🚓 | 🛵 | 🚗 | 🚜 | 🚢 | 🚦 | 🚧 | 🚨 | 🚪 | 🚫 | 🚲 | 🚽 | 🚿 | 🧲 |
These emojis are selected to ensure:
- Exclusion of similar-looking emojis to avoid confusion.
- Only the "base" emoji are considered; modified emojis (e.g., skin tones, gender modifiers) are excluded.
- Match emoji's used by Yat.
Each byte is encoded using the emoji listed at the corresponding index.
Change Log
Date | Change | Author |
---|---|---|
2024-05-31 | Initial stable | SWvHeerden |
RFC-0160/BlockSerialization
Tari Block Binary Serialization
Maintainer(s): Stanley Bondi
Licence
Copyright 2021 The Tari Development Community
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Language
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as shown here.
Disclaimer
This document and its content are intended for information purposes only and may be subject to change or update without notice.
This document may include preliminary concepts that may or may not be in the process of being developed by the Tari community. The release of this document is intended solely for review and discussion by the community of the technological merits of the potential system outlined herein.
Goals
The aim of this Request for Comment (RFC) is to specify the binary serialization of:
- a mined Tari block
- a Tari block mining template
This is to facilitate interoperability of mining software and hardware.
Related Requests for Comment
Specification
By reviewing the block and mining template fields below, we have the following underlying data types for serialization:
bool
u8
u16
u64
i64
array
of type[u8; n]
Vec<T>
whereT
isu8
,enum
orarray
For 1. to 5. and all numbers, Base 128 Varint encoding MUST be used.
From the Protocol Buffers documentation:
Varints are a method of serializing integers using one or more bytes. Smaller numbers take a smaller number of bytes. Each byte in a varint, except the last byte, has the most significant bit (msb) set – this indicates that there are further bytes to come. The lower 7 bits of each byte are used to store the two's complement representation of the number in groups of 7 bits, least significant group first.
For 6. to 7., the dynamically sized array
and Vec
type, the encoded array MUST be preceded by a number indicating the
length of the array. This length MUST also be encoded as a varint. By prepending the length of the array, the decoder
knows how many elements to decode as part of the sequence.
Block field ordering
Using this varint encoding, all fields of the complete block MUST be encoded in the following order:
- Version
- Height
- Previous block hash
- Timestamp
- Output Merkle root
- Witness Merkle root
- Output Merkle mountain range size
- Kernel Merkle root
- Kernel Merkle mountain range size
- Input Merkle root
- Total kernel offset
- Total script offset
- Nonce
- Proof of work algorithm
- Proof of work supplemental data
- Transaction inputs - for each input:
- Version
- Spent output - for each output:
- Version
- Features
- Version
- Maturity
- Output type
- Sidechain features
- Metadata
- Commitment
- Script
- Sender offset public key
- Covenant
- Encrypted value
- Minimum value promise
- Input data (vector of Stack items)
- Script signature
- Transaction outputs - for each output:
- Version
- Features
- Version
- Maturity
- Output type
- Sidechain features
- Metadata
- Commitment
- Range proof
- Script
- Sender offset public key
- Metadata signature
- Covenant
- Encrypted value
- Minimum value promise
- Transaction kernels - for each kernel:
- Version
- Features
- Fee
- Lock height
- Excess
- Excess signature public nonce
- Excess signature
- Burn commitment
Mining template field ordering
The new block template is provided to miners to complete. Its fields MUST also be encoded using varints, in the following order:
- Version
- Height
- Previous block hash
- Total kernel offset
- Total script offset
- Proof of work algorithm
- Proof of work supplemental data
- Transaction inputs - for each input:
- Version
- Spent output - for each output:
- Version
- Features
- Version
- Maturity
- Output type
- Sidechain features
- Metadata
- Commitment
- Script
- Sender offset public key
- Covenant
- Encrypted value
- Minimum value promise
- Input data (vector of Stack items)
- Script signature
- Transaction outputs - for each output:
- Version
- Features
- Version
- Maturity
- Output type
- Sidechain features
- Metadata
- Commitment
- Range proof
- Script
- Sender offset public key
- Metadata signature
- Covenant
- Encrypted value
- Minimum value promise
- Transaction kernels - for each kernel:
- Version
- Features
- Fee
- Lock height
- Excess
- Excess signature public nonce
- Excess signature
- Burn commitment
- Target difficulty
- Reward
- Total fees
Value encryption
The value of the value commitment MUST be encrypted using ChaCha20Poly1305 Authenticated Encryption with Additional Data (AEAD). The AEAD key MUST be 32 bytes in size and produced using a domain separated hash of the private key and commitment.
Hash domains
To use a single hash function for producing a sampling of multiple independent hash functions, it's common to employ domain separation. Tari uses the hashing API within the tari codebase to achieve proper hash domain separation.
The following functional areas MUST each use a separate hash domain that is unique in the tari codebase:
- kernel Merkle Mointain Range;
- witness MMR;
- output MMR;
- input MMR;
- value encryption.
To achieve interoperability with other blockchains like Bitcoin and Monero, for example an atomic swap, TariScript MUST NOT make use of hash domains.
Tari Block and Mining Template - Data Types
A Tari block is composed of the block header and aggregate body.
Here we describe the respective Rust types of these fields in the tari codebase, and their underlying data types:
Block Header
Field | Abstract Type | Data Type | Description |
---|---|---|---|
Version | u16 | u16 | The Tari protocol version number, used for soft/hard forks |
Height | u64 | u64 | Height of this block since the genesis block |
Previous Block Hash | BlockHash | [u8;32] | Hash of the previous block in the chain |
Timestamp | EpochTime | u64 | Timestamp at which the block was built (number of seconds since Unix epoch) |
Output Merkle Root | BlockHash | [u8;32] | Merkle Root of the unspent transaction ouputs |
Witness Merkle Root | BlockHash | [u8;32] | MMR root of the witness proofs |
Output MMR Size | u64 | u64 | The size (number of leaves) of the output and range proof MMRs at the time of this header |
Kernel Merkle Root | BlockHash | [u8;32] | MMR root of the transaction kernels |
Kernel MMR Size | u64 | u64 | Number of leaves in the kernel MMR |
Input Merkle Root | BlockHash | [u8;32] | Merkle Root of the transaction inputs in this block |
Total Kernel Offset | BlindingFactor | [u8;32] | Sum of kernel offsets for all transaction kernels in this block |
Total Script Offset | BlindingFactor | [u8;32] | Sum of script offsets for all transaction kernels in this block |
Nonce | u64 | u64 | Nonce increment used to mine this block |
Pow | ProofOfWork | See Proof Of Work | Proof of Work information |
[u8;32]
indicates an array of 32 unsigned 8-bit integers
Proof Of Work
Field | Abstract Type | Data Type | Description |
---|---|---|---|
Proof of Work Algorithm | PowAlgorithm | u8 | The algorithm used to mine this block ((Monero or SHA3)) |
Proof of Work Data | Vec<u8> | u8 | Supplemental proof of work data. For example for Sha3, this would be empty (only the block header is required), but for Monero merge mining we need the Monero block header and RandomX seed hash |
Block Body
Field | Abstract Type | Data Type | Description |
---|---|---|---|
Sorted | bool | bool | True if sorted |
Transaction Inputs | Vec<TransactionInput> | TransactionInput | List of inputs spent |
Transaction Outputs | Vec<TransactionOutput> | TransactionOutput | List of outputs produced |
Transaction Kernels | Vec<TransactionKernel> | TransactionKernel | Kernels contain the excesses and their signatures for the transactions |
A further breakdown of the body fields is described below:
TransactionInput
Field | Abstract Type | Data Type | Description |
---|---|---|---|
Version | TransactionInputVersion | u16 | The features of the output being spent. We will check maturity for all outputs. |
Spent Output | SpentOutput | See SpentOutput | Either the hash of TransactionOutput that this Input is spending or its data |
Input Data | ExecutionStack | Vec<u8> | The script input data, maximum size is 512 |
Script Signature | CommitmentAndPublicKeySignature | See CommitmentAndPublicKeySignature | A signature signing the script and all other transaction input metadata with the script private key |
SpentOutput
Field | Abstract Type | Data Type | Description |
---|---|---|---|
OutputHash | HashOutput | [u8;32] | The features of the output being spent. We will check maturity for all outputs. |
Version | TransactionOutputVersion | u8 | The TransactionOutput version |
Features | OutputFeatures | See OutputFeatures | Options for the output's structure or use |
Commitment | PedersenCommitment | [u8;32] | The commitment referencing the output being spent. |
Script | TariScript | Vec<u8> | The serialised script, maximum size is 512 |
Sender Offset Public Key | CommitmentAndPublicKeySignature | [u8;32] | The Tari script sender offset public key |
Covenant | Vec<CovenantToken> | See CovenantToken | A future-based contract detailing input and output metadata |
Encrypted Value | EncryptedValue | [u8;24] | The encrypted value of the value commitment |
Minimum Value Promise | MicroTari | u64 (See Value encryption) | The minimum value promise embedded in the range proof |
CommitmentAndPublicKeySignature
Field | Abstract Type | Data Type | Description |
---|---|---|---|
Public Nonce | PedersenCommitment | [u8;32] | The public (Pedersen) commitment nonce created with the two random nonces |
u | SecretKey | [u8;32] | The first publicly known private key of the signature signing with the value |
v | SecretKey | [u8;32] | The second publicly known private key of the signature signing with the blinding factor |
Public Key | PedersenCommitment | [u8;32] | The public nonce of the Schnorr signature |
a | SecretKey | [u8;32] | The publicly known private key of the Schnorr signature |
Find out more about CommitmentAndPublicKey signatures:
- Simple Schnorr Signature with Pedersen Commitment as Key
- A New and Efficient Signature on Commitment Values
- Tari Commitment and Public Key Signature
OutputFeatures
Field | Abstract Type | Data Type | Description |
---|---|---|---|
Version | OutputFeaturesVersion | u8 | The OutputFeatures version |
Output Type | OutputType | u8 | The type of output |
Maturity | u64 | u64 | The block height at which the output can be spent |
Metadata | Vec<u8> | u8 | The block height at which the output can be spent |
Side-chain Features | SideChainFeatures | none | Not implemented |
CovenantToken
Field | Abstract Type | Data Type | Description |
---|---|---|---|
Filter | CovenantFilter | See CovenantFilter | The covenant filter |
Arg | CovenantArg | See CovenantArg | The covenant argument(s) |
CovenantFilter
Field | Abstract Type | Data Type | Description |
---|---|---|---|
Identity | IdentityFilter | none | The Identity |
And | AndFilter | none | And operation |
Or | OrFilter | none | Or operation |
Xor | XorFilter | none | Xor operation |
Not | NotFilter | none | Not operation |
Output Hash Equal | OutputHashEqFilter | none | Output hash to be equal to |
Fields Preserved | FieldsPreservedFilter | none | Fields to be preserved |
Field Equal | FieldEqFilter | none | Field to be equal to |
Fields Hashed Equal | FieldsHashedEqFilter | none | Fields hashed to be equal to |
Absolute Height | AbsoluteHeightFilter | none | Absolute height |
CovenantArg
Field | Abstract Type | Data Type | Description |
---|---|---|---|
Hash | FixedHash | [u8;32] | Future hash value |
PublicKey | PublicKey | [u8;32] | Future public key |
Commitment | PedersenCommitment | [u8;32] | Future commitment referencing the output being spent. |
Tari Script | TariScript | Vec<u8> | Future serialised script, maximum size is 512 |
Covenant | Vec<CovenantToken> | See CovenantToken | Future covenant |
Output Type | OutputType | u8 | Future type of output |
Uint | Uint | u64 | Future value |
Output Field | OutputField | See OutputField | Future set of output fields |
OutputFields | Vec<OutputField> | See OutputField | Future set of output fields |
Bytes | Vec<u8> | u8 | Future raw data |
OutputField
Field | Abstract Type | Data Type | Description |
---|---|---|---|
Commitment | u8 | u8 | Commitment byte code |
Script | u8 | u8 | Script byte code |
Sender Offset Public Key | u8 | u8 | SenderOffsetPublicKey byte code |
Covenant | u8 | u8 | Covenant byte code |
Features | u8 | u8 | Features byte code |
Features Output Type | u8 | u8 | FeaturesOutputType byte code |
Features Maturity | u8 | u8 | FeaturesMaturity byte code |
Features Metadata | u8 | u8 | FeaturesMetadata byte code |
Features Side Chain Features | u8 | u8 | FeaturesSideChainFeatures byte code |
TransactionOutput
Field | Abstract Type | Data Type | Description |
---|---|---|---|
Version | TransactionOutputVersion | u8 | The TransactionOutput version |
Features | OutputFeatures | See OutputFeatures | Options for the output's structure or use |
Commitment | PedersenCommitment | [u8;32] | The homomorphic commitment representing the output amount |
Proof | RangeProof | Vec<u8> | A proof that the commitment is in the right range |
Script | TariScript | Vec<u8> | The script that will be executed when spending this output |
Sender Offset Public Key | PublicKey | [u8;32] | The Tari script sender offset public key |
Metadata Signature | CommitmentAndPublicKeySignature | See CommitmentAndPublicKeySignature | A signature signing all transaction output metadata with the script offset private key and spending key |
Covenant | Vec<CovenantToken> | See CovenantToken | A future-based contract detailing input and output metadata |
Encrypted Value | EncryptedValue | [u8;24] | The encrypted value of the value commitment |
Minimum Value Promise | MicroTari | u64 (See Value encryption) | The minimum value promise embedded in the range proof |
TransactionKernel
Field | Abstract Type | Data Type | Description |
---|---|---|---|
Version | TransactionKernelVersion | u8 | The TransactionKernel version |
Features | KernelFeatures | u8 | Options for a kernel's structure or use |
Fee | MicroTari | u64 | Fee originally included in the transaction this proof is for. |
Lock Height | u64 | u64 | This kernel is not valid earlier than this height. The max maturity of all inputs to this transaction |
Excess | PedersenCommitment | [u8;32] | Remainder of the sum of all transaction commitments (minus an offset). If the transaction is well-formed, amounts plus fee will sum to zero, and the excess is a valid public key |
Excess Signature | RistrettoSchnorr | See RistrettoSchnorr | An aggregated signature of the metadata in this kernel, signed by the individual excess values and the offset excess of the sender |
Burn Commitment | PedersenCommitment | [u8;32] | This is an optional field that must be set if the transaction contains a burned output |
RistrettoSchnorr
Field | Abstract Type | Data Type | Description |
---|---|---|---|
Public nonce | PublicKey | [u8;32] | The public nonce of the Schnorr signature |
Signature | SecretKey | [u8;32] | The signature of the Schnorr signature |
New Block Template
The new block template is used in constructing a new partial block, allowing a miner to add the coinbase UTXO and as a final step for the Base node to add the MMR roots to the header.
Field | Abstract Type | Data Type | Description |
---|---|---|---|
Header | NewBlockHeaderTemplate | See New Block Header Template | The Tari protocol version number, used for soft/hard forks |
Body | AggregateBody | See Block Body | Height of this block since the genesis block |
Target Difficulty | Difficulty | u64 | The minimum difficulty required to satisfy the Proof of Work for the block |
Reward | MicroTari | u64 | The value of the emission for the coinbase output for the block |
Total Fees | MicroTari | u64 | The sum of all transaction fees in this block |
New Block Header Template
Field | Abstract Type | Data Type | Description |
---|---|---|---|
Version | u16 | u16 | The Tari protocol version number, used for soft/hard forks |
Height | u64 | u64 | Height of this block since the genesis block |
Previous Hash | BlockHash | [u8;32] | Hash of the previous block in the chain |
Total Kernel Offset | BlindingFactor | [u8;32] | Total accumulated sum of kernel offsets since genesis block. We can derive the kernel offset sum for this block from the total kernel offset of the previous block header. |
Total Script Offset | BlindingFactor | [u8;32] | Sum of script offsets for all transaction kernels in this block |
Pow | ProofOfWork | See Proof Of Work | Proof of Work information |
RFC-0170/NetworkCommunicationProtocol
The Tari Communication Network and Network Communication Protocol
Maintainer(s): Stringhandler
License
Copyright 2018 The Tari Development Community
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Language
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as shown here.
Disclaimer
The purpose of this document and its content is for information purposes only and may be subject to change or update without notice.
This document may include preliminary concepts that may or may not be in the process of being developed by the Tari community. The release of this document is intended solely for review and discussion by the community regarding the technological merits of the potential system outlined herein.
Goals
This document will introduce the Tari communication network and the communication protocol used to select, establish and maintain connections between peers on the network. Communication Nodes and Communication Clients will be introduced and their required functionality will be proposed.
Related RFCs
Description
Assumptions
- A communication channel can be established between two peers once their online communication addresses are known to each other.
Abstract
The backbone of the Tari communication network consists of a large number of nodes that maintain peer connections between each other. These nodes forward and propagate encrypted and unencrypted data messages through the network such as joining requests, discovery requests, transactions and completed blocks. Network clients, not responsible for maintaining the network, are able to create ad hoc connections with nodes on the network to perform joining and discovery requests. The majority of communication between clients and nodes will be performed using direct Peer-to-peer (P2P) communication once the discovery process was used to obtain the online communication addresses of peers. Where possible the efficient Kademlia based directed forwarding of encrypted data messages can be used to perform quick node discovery and joining of clients and nodes on the Tari communication network. Where messages are of importance to a wide variety of entities on the network, Gossip protocol based message propagation can be performed to distribute the message to the entire network.
Overview
The Tari communication network is a variant of a Kademlia network that allows for fast discovery of nodes, with an added ability to perform Gossip protocol based broadcasting of data messages to the entire network. The majority of the communication required on the Base Layer and Digital Asset Network (DAN) will be performed via direct P2P communication between known clients and nodes. Alternatively, the Tari communication network can be used for broadcasting joining requests, discovery requests and propagating data messages such as completed blocks, transactions and data messages that are of interest to a large part of the Tari communication network.
The Tari communication network consists of a number of different entities that need to communicate in a distributed and ad-hoc manner. The primary entities that need to communicate are Validator Nodes (VN), Base Nodes (BN), and Wallets (W). Here are some examples of different communication tasks that need to be performed by these entities on the Tari Communication network:
- Base Nodes on the Base Layer need to propagate completed blocks and transactions to other Base Nodes using Gossip protocol based broadcasting.
- Wallets need to communicate and negotiate with other Wallets to create transactions. They also need the ability to submit transactions to the mempool of Base Nodes.
- Validator Nodes need to communicate with other Validator Nodes to perform consensus. Note that in future, the Validator Nodes may run on a different network.
Here is an overview communication matrix that show which source entities SHOULD initiate communication with destination entities on the Tari Communication network:
Destination (across) Source (down) | Validator Node | Base Node | Wallet |
---|---|---|---|
Validator Node | Yes | No | No |
Base Node | No | Yes | No |
Wallet | No | Yes | Yes |
Communication Nodes and Communication Clients
To simplify the description of the Tari communication network, the different entities with similar behaviour were grouped into two groups: Communication Nodes and Communication Clients.
- Validator Nodes and Base Nodes are Communication Nodes (CN).
- Wallets are Communication Clients (CC).
CNs form the core communication infrastructure of the Tari communication network and are responsible for maintaining the Tari communication network by receiving, forwarding and distributing joining requests, discovery requests, data messages and routing information. CCs are different from CNs in that they do not maintain the network and they are not responsible for propagating any joining requests, discovery requests, data messages and routing information. They do make use of the network to submit their own joining requests and perform discovery request of other specific CNs and CCs when they need to communicate with them. Once a CC has discovered the CC or CN they want to communicate with, they will establish a direct P2P channel with them. The Tari communication network is unaware of this direct P2P communication once discovery is completed.
The different entity types MUST be grouped into the different communication node types as follows:
Entity Type | Communication Node Type |
---|---|
Validator Node | Communication Node |
Base Node | Communication Node |
Wallet | Communication Client |
Unique identification of Communication Nodes and Communication Clients
In the Tari communication network, each CN or CC makes use of a node ID to determine their position in the network. This node ID can be derived from the CNs or CCs identification public key. The method used to obtain a node ID will either enhance or limit the trustworthiness of that entity when propagating messages through them on the Tari communication network.
The similarity or distance between different node IDs can be calculated by performing the Hamming distance between the bits of the two node ID numbers. The Hamming distance can be implemented as an Exclusive OR (XOR) between the bits of the numbers and the summation of the resulting true bits. CCs and/or CNs that have similar node IDs, that produce a small Hamming distance, are located in similar regions of the Tari communication network. This does not mean that their geographic locations are near each other, but rather that their location in the network is similar. A thresholding scheme can be applied to the Hamming distance to ensure that only neighboring CNs with similar node IDs are allowed to share and propagate specific information. As an example, only routing table information that contains similar node IDs to the requesting CCs or CNs node ID should be shared with them. Limiting the sharing of routing table information makes it more difficult to map the entire Tari communication network.
Note that Mining Workers are excluded from the Tari communication network. A Mining Server will have a local or remote connection with a Base Node. They do not need to make use of the communication network and they are not responsible for propagating any messages on the network. The parent Base Node will perform any communication tasks on the Tari communication network on their behalf.
Online Communication Address, Peer Address and Routing Table
Each CC and CN on the Tari communication network will have identification cryptographic keys, a node ID and an online communication address. The online communication address SHOULD be either an IPv4, IPv6, Or Tor (Base32) address and can be stored using the network address type as follows:
Description | Data type | Comments |
---|---|---|
address type | uint4 | Specify if IPv4/IPv6/Tor |
address | char array | IPv4, IPv6, Tor (Base32) address |
port | uint16 | port number |
Tari uses the Multiaddr format for addresses.
A Tor address can be used when anonymity is important for a CC or CN. The IPv4 and IPv6 address types do not provide any privacy features but do provide increased bandwidth.
Each CC or CN has a local lookup table that contains the online communication addresses of all CCs and CNs on the Tari communication network known to that CC or CN. When a CC or CN wants to join the Tari communication network, the online communication address of at least one other CN that is part of the network needs to be known. The online communication address of the initial CN can either be manually provided or a bootstrapped list of "reliable" and persistent CNs can be provided with the Validator Node, Base Node or Wallet software. The new CC or CN can then request additional peer contact information of other CNs from the initial peers to extend their own routing table.
The routing table consists of a list of peer addresses that link node IDs, public identification keys and online communication addresses of each known CC and CN.
The Peer Address stored in the routing table MAY be implemented as follows:
Description | Data type | Comments |
---|---|---|
network address | network_address | The online communication address of the CC or CN |
node_ID | node_ID | Registration Assigned for VN, Self selected for BN, W and TW |
public_key | public_key | The public key of the identification cryptographic key of the CC or CN |
node_type | node_type | VN, BN, W or TW |
linked asset IDs | list of asset IDs | Asset IDs can be used as an address on Tari network similar to a node ID |
last_connection | timestamp | Time of last successful connection with peer |
update_timestamp | timestamp | A timestamp for the last peer address update |
When a new CC or CN wants to join the Tari communication network they need to submit a joining request to the rest of the network. The joining request contains the peer address of the new CC or CN. Each CN that receives the joining request can decide if they want to add the new CCs or CNs contact information to their local routing table. When a CN, that received the joining request, has a similar node ID to the new CC or CN then that node must add the peer address to their routing table. All CNs with similar node IDs to the new CC or CN should have a copy of the new peer address in their routing tables.
To limit potential attacks, only one registration for a specific node type with the same online communication address can be stored in the routing table of a CN. This restriction will limit Bad Actors from spinning up multiple CNs on a single computer.
Joining the Network using a Joining Request
A new CC or CN needs to register their peer address on the Tari communication network. This is achieved by submitting a network joining request to a subset of CNs selected from the routing table of the new CC or CN. These peers will forward the joining request, with the peer address, to the rest of the network until CNs with similar node IDs have been reached. CNs with similar node IDs will then add the new peer address of the new node to their routing table, allowing for fast discovery of the new CC or CN.
Other CCs and CNs will then be able to retrieve the new CCs or CNs peer address by submitting discovery requests. Once the peer address of the desired CC or CN has been discovered then a direct P2P communication channel can be established between the two parties for any future communication. After discovery, the rest of the Tari communication network will be unaware of any further communication between the two parties.
Sending Data Messages and Discovery Requests
The majority of all communication on the Tari communication network will be performed using direct P2P channels established between different CCs and CNs once they are aware of the peer addresses of each other that contain their online communication addresses. Message propagation on the network will typically consist only of joining and discovery requests where a CC or CN wants to join the network or retrieve the peer address of another CC or CN so that a direct P2P channel can be established.
Messages can be transmitted in this network in either an unencrypted or encrypted form. Typically messages that have been sent in unencrypted form are of interest to a number of CNs on the network and should be propagated so that every CN that is interested in that data message obtains a copy. Block and Transaction propagation are examples of data messages where multiple entities on the Tari communication network are interested in that data message; this requires propagation through the entire Tari communication network in unencrypted form.
Encrypted data messages make use of the source and destinations identification cryptographic keys to construct a shared secret with which the message can be encoded and decoded. This ensures that only the two parties are able to decode the data message as it is propagated through the communication network. This mechanism can be used to perform private discovery requests, where the online communication address of the source node is encrypted and propagated through the network until it reached the destination node. Private discovery requests can only be performed if both parties are online at the same time. Encryption of the data message ensures that only the destination node is able to view the online address of the source node as the data message moves through the network. Once the destination node receives and decrypts the data message, that node is then able to establish a P2P communication channel with the source node for any further communication.
Propagation of completely private discovery request, hidden as an encrypted data message, can be performed as a broadcast through the entire network using the Gossip protocol. Propagation of public discovery requests can be performed using more efficient directed propagation using the Kademlia protocol. As encrypted message with visible destinations tend to not be of interest to the rest of the network, directed propagation using the Kademlia protocol to forward these messages to the correct parties are preferred. Privacy of a CCs online address, whom is sending a transaction to a Base Node, may be enhanced if the transaction is encrypted and sent to a Base Node with a node ID that is not the closest to the CCs node ID. This should prevent linking a transaction to the originating online address.
This same encryption technique can be used to send encrypted messages to a single node or a group of nodes, where the group of nodes have shared identification keys. A Validation Committee is an example of a group of CNs that have shared identification keys for the committee. The shared identification keys ensure that all members of that committee are able to receive and decrypt data messages that were sent to the committee.
Maintaining connections with peers
CCs and CNs establish and maintain connections with peers differently. CCs only create a few short-lived ad hoc channels and CNs create and maintain long-lived channels with a number of peers.
If a CC is unaware of a destination CNs or CCs online communication address then the address first needs to be obtained using a discovery request. When a CC already knows the communication address of the CC or CN that he wants to communicate with, then a direct P2P channel can be established between the two peers for the duration of the communication task. The communication channel can then be closed as soon as the communication task has been completed.
CNs consisting of VNs and BNs typically attempt to maintain communication channels with a large number of peers. The distribution of peers (VNs vs BNs) that a single CN keeps communication channels open with can change depending on the type of node. A CN that is also a BN should maintain more peer connections with other BNs, but should also have some connections with other VNs.
A CN that is also a VN should maintain more peer connections with other VNs, but also have some connections with BNs. CNs that are part of Validator Node committees should attempt to maintain permanent connections with the other members of the committee to ensure that quick consensus can be achieved.
To maintain connections with peers, the following process can be performed. Discover peers using discovery requests, and add their details to the local routing table. The CN can decide how the peer connections should be selected from the routing table by either:
- manually selecting a subset,
- automatically selecting a random subset or
- selecting a subset of neighbouring nodes with similar node IDs.
Functionality Required of Communication Nodes
- It MUST select a cryptographic key pair used for identification on the Tari Communication network.
- A CN MAY request the peer addresses of CNs with similar node IDs from other CNs to extend their local routing table.
- If a CN is a BN, then a node ID MUST be derived from the nodes identification public key.
- A new CN MUST submit a joining request to the Tari communication network so that the nodes peer address can be added to the routing table of neighbouring peers in the network.
- If a CN receives a new joining request with a similar node ID (within a network selected threshold), then the peer address specified in the joining request MUST be added to its local routing table.
- When a CN receives an encrypted message, the node MUST attempt to open the message. It MUST authenticate the encryption before trying to decrypt it.
- When a CN receives an encrypted message that the node is unable to open, and the destination node ID is known then the CN MUST forward it to all connected peers that have node IDs that are closer to the destination.
- When a CN receives an encrypted message that the node is unable to open and the destination node is unknown then the CN MUST forward the message to all connected peers.
- A CN MUST have the ability to verify the content of unencrypted messages to limit the propagation of spam messages.
- If an unencrypted message is received by the CN with a unspecified destination node ID, then the node MUST verify the content of the message and forward the message to all connected peers.
- If an unencrypted message is received by the CN with an specified destination node ID, then the node MUST verify the content of the message and forward the message to all connected peers that have closer node IDs.
- A CN MUST have the ability to select a set of peer connections from its routing table.
- Connections with the selected set of peers MUST be maintained by the CN.
- A CN MUST have a mechanism to construct encrypted and unencrypted joining requests, discovery requests or data messages.
- A CN MUST construct and provide a list of peer addresses from its routing table that is similar to a requested node ID so that other CCs and CNs can extend their routing tables.
- A CN MUST keep its routing table up to date by removing unreachable peer addresses and adding newly received addresses.
- It MUST have a mechanism to determine if a node ID was obtained through registration or was derived from an identification public key.
- A CN MUST calculate the similarity between different node IDs by calculating the Hamming distance between the bits of the two node ID numbers.
Functionality Required of Communication Clients
- It MUST select a cryptographic key pair used for identification on the Tari Communication network.
- It MUST have a mechanism to derive a node ID from the self-selected identification public key.
- A CC must have the ability to construct a peer address that links its identification public key, node ID and an online communication address.
- A new CC MUST broadcast a joining request with its peer address to the Tari communication network so that CNs with similar node IDs can add the peer address of the new CC to their routing tables.
- A CC MAY request the peer addresses of CNs with similar node IDs from other CNs to extend their local routing table.
- A CC MUST have a mechanism to construct encrypted and unencrypted joining and discovery requests.
- A CC MUST maintain a small persistent routing table of Tari Communication network peers with which ad hoc connections can be established.
- As the CC becomes aware of other CNs and CCs on the communication network, the CC SHOULD extend its local routing table by including the newly discovered CCs or CNs contact information.
- Peers from the CCs routing table that have been unreachable for a number of attempts SHOULD be removed from the its routing table.
- A CC MUST calculate the similarity between different node IDs by calculating the Hamming distance between the bits of the two node ID numbers.
Change Log
Date | Change | Author |
---|---|---|
11 Nov 2022 | Update, removed registration of Validator Nodes | Stringhandler |
RFC-0171/MessageSerialization
Message Serialization
Maintainer(s): Cayle Sharrock Stanley Bondi
Licence
Copyright 2019 The Tari Development Community
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Language
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as shown here.
Disclaimer
This document and its content are intended for information purposes only and may be subject to change or update without notice.
This document may include preliminary concepts that may or may not be in the process of being developed by the Tari community. The release of this document is intended solely for review and discussion by the community of the technological merits of the potential system outlined herein.
Goals
The aim of this Request for Comment (RFC) is to describe the message serialization formats for message payloads used in the Tari network.
Related Requests for Comment
RFC-0710: Tari Communication Network and Network Communication Protocol
Description
One way of interpreting the Tari network is that it is a large peer-to-peer messaging application. The entities chatting on the network include:
- Wallets
- Base nodes
- Validator nodes
- Other client applications (e.g. chat)
The types of messages that these entities send might include:
- Text messages
- Transaction messages
- Block propagation messages
- Asset creation instructions
- Asset state change instructions
- State Checkpoint messages
For successful communication to occur, the following needs to happen:
- The message is translated from its memory storage format into a standard payload format that will be transported over the wire.
- The communication module wraps the payload into a message format, which may entail any/all of
- adding a message header to describe the type of payload;
- encrypting the message;
- signing the message;
- adding destination/recipient metadata.
- The communication module then sends the message over the wire.
- The recipient receives the message and unwraps it, possibly performing any/all of the following:
- decryption;
- verifying signatures;
- extracting the payload;
- passing the serialized payload to modules that are interested in that particular message type.
- The message is deserialized into the correct data structure for use by the receiving software
This document only covers the first and last steps, i.e. serializing data from in-memory objects to a format that can be transmitted over the wire. The other steps are handled by the Tari communication protocol.
In addition to machine-to-machine communication, we also standardize on human-to-machine communication. Use cases for this include:
- Handcrafting instructions or transactions. The ideal format here is a very human-readable format.
- Copying transactions or instructions from cold wallets. The ideal format here is a compact but easy-to-copy format.
- Peer-to-peer text messaging. This is just a special case of what has already been described, with the message
structure containing a unicode
message_text
field.
When sending a message from a human to the network, the following happens:
- The message is deserialized into the native structure.
- Additional validation can be performed.
- The usual machine-to-machine process is followed, as described above.
Binary Serialization Formats
The ideal properties for binary serialization formats are:
- widely used across multiple platforms and languages, but with excellent Rust support;
- compact binary representation; and
- serialization "Just Works"(TM) with little or no additional coding overhead.
Several candidates fulfill these properties to some degree.
bincode
- Pros:
- Fast and compact
- Serde support
- Cons:
- (Almost) any type changes result in incompatibilities
- language support outside of rust is limited
Message Pack
- Pros:
- Compact
- Fast
- Multiple language support
- Good Rust/Serde support
- Native byte encoding (compared to JSON)
- Cons:
- No metadata support
- Self-describing overhead
MessagePack has almost the exact same characteristics as JSON, without the syntactical overhead. It is also self-describing, which can be a pro and a con. Like JSON, field names are encoded as strings which adds significant overhead over p2p comms. However, when used in conjunction with msgpack-schemas this overhead is removed and binary representations become characteristically similar to protobuf.
Protobuf
Protobuf is a widely-used binary serialization format that was developed by Google and has excellent Rust support. The Protobuf byte format encodes tag numbers as varints that map to a known schema fields.
- Pros:
- Compact
- Fast
- Multiple language support
- Good Rust/Serde support
- Some schema changes are backward compatible
- Cons
- Schema must be defined for each message type
- Does not fit into the serde ecosystem, meaning it is hard to swap out later.
In the latest protoV3 spec, all fields are optional. which forces the implementation to check for the presence of required data and allows for. This means that you can change your schema significantly and there is a
It's fairly easy to reason about backwards-compatibility for schema changes once you understand protobuf encoding. Essentially, since all fields in protov3 are optional, a message will usually be able to successfully decode even if message tags are added/changed/removed. It therefore depends on the application whether changes are backward-compatible.
Generally, the following rules apply:
- you should not change existing field types to a non-compatible type. For example, changing a
uint32
to auint64
is fine, but changing auint32
to astring
is not. - you should not change existing tag numbers should not be changed, field names may change as needed since they are not included in the byte format.
- you may delete optional or repeated fields
- you may add new optional or repeated fields as long as you use a new field number
Cap'n Proto
Similar to Protobuf, but claims to be much faster. Rust is supported.
Hand-rolled Serialization
Hintjens recommends using hand-rolled serialization for bulk messaging. While Pieter usually offers sage advice, I'm going to argue against using custom serializers at this stage for the following reasons:
- We're unlikely to improve hugely over existing serialization formats.
- Since Serde does 95% of our work for us, there's a significant development overhead (and new bugs) involved with a hand-rolled solution.
- We'd have to write de/serializers for every language that wants Tari bindings; whereas every major language has a protobuf implementation.
Tari message formats
Wire message format
The decision was taken to use Protobuf encoding for messages on the Tari peer-to-peer wire protocol, as it ticks these boxes:
- it is possible to modify message schemas in future versions without breaking network communication with previous versions of node software,
- de/encoding is compact and fast, and
- it has great Rust support through the prost crate.
Other serialization formats
For human-readable formats, it makes little sense to deviate from JSON. For copy-paste semantics, the extra compression that Base64 offers over raw hex or Base58 makes it attractive.
The standard binary representation used in databases (e.g. blockchain storage, wallets) will make use of bincode
.
In these cases, a straightforward #[derive(Deserialize, Serialize)]
is all that is required to implement de/encoding
for the data structure.
However, other structures might need fine-tuning, or hand-written serialization procedures. To capture both use cases,
it is proposed that a MessageFormat
trait be defined:
#![allow(unused)] fn main() { pub trait MessageFormat: Sized { fn to_binary(&self) -> Result<Vec<u8>, MessageFormatError>; fn to_json(&self) -> Result<String, MessageFormatError>; fn to_base64(&self) -> Result<String, MessageFormatError>; fn from_binary(msg: &[u8]) -> Result<Self, MessageFormatError>; fn from_json(msg: &str) -> Result<Self, MessageFormatError>; fn from_base64(msg: &str) -> Result<Self, MessageFormatError>; } }
This trait will have default implementations to cover most use cases (e.g. a simple call through to serde_json
). Serde
also offers significant ability to tweak how a given struct will be serialized through the use of
attributes.
Change Log
Date | Change | Author |
---|---|---|
29 Mar 2019 | First draft | CjS77 |
24 Jul 2019 | Technical editing | anselld |
13 Oct 2022 | Update | sdbondi |
26 Oct 2022 | Stabilise RFC | CjS77 |
RFC-0172/PeerToPeerMessaging
Peer to Peer Messaging Protocol
Maintainer(s): Stanley Bondi, Cayle Sharrock, Stringhandler
Licence
Copyright 2019 The Tari Development Community
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Language
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as shown here.
Disclaimer
This document and its content are intended for information purposes only and may be subject to change or update without notice.
This document may include preliminary concepts that may or may not be in the process of being developed by the Tari community. The release of this document is intended solely for review and discussion by the community of the technological merits of the potential system outlined herein.
Goals
The aim of this Request for Comment (RFC) is to describe the peer-to-peer messaging protocol for communication nodes and communication clients on the Tari network.
Related Requests for Comment
Description
Assumptions
- All wire messages are de/serialized as per RFC-0171: Message Serialisation.
- All peer's public identities are based on Ristretto prime order elliptic curves.
Broad Requirements
Tari network peer communication must facilitate secure, private and efficient communication between peers. Broadly, a communication node or communication client MUST be capable of:
- bidirectional communication between multiple connected peers;
- encrypted, authenticated over-the-wire communication;
- understanding and constructing Tari messages;
- gracefully reestablishing dropped connections; and (optionally)
- either:
- communicating to a SOCKS5 proxy (e.g. connections over Tor).
- or have a static public IPv4 address.
Additionally, communication nodes MUST be capable of performing the following tasks:
- maintaining a list of known and available peers in the form of a peer list;
- forwarding directed messages to neighbouring peers; and
- broadcasting messages to neighbouring peers.
Overall Architectural Design
The Tari communication layer has a modular design to allow for the various communicating nodes and clients to use the same infrastructure code.
Peer connection state is monitored by the ConnectionManager
component. The ConnectionManager
emits events to allow
other components to subscribe to connection state changes.
ConnectionManager
- manages peer connections and connection state monitoring.PeerConnection
- manages the sending and receiving of messages for a single peer connection.NetAddress
- multiaddr describing the public address and transport for a peer-to-peer connection.Messaging Protocol
- defines the Tari wire message format and message types.Connection Multiplexer
- allows multiple substreams to be established over a single transport-level connection.
NetAddress
A NetAddress
is a publicly-accessible address for a peer. A peer may have one or more NetAddress
es.
A good NetAddress
format should:
- have an efficient binary representation;
- have a human-readable representation with a simple syntax;
- support many transport protocols; and
- be self-describing
For these reasons, we select the multiaddr format for all peer-to-peer addresses.
For example,
/ip4/123.123.123.123/tcp/12345
- IPv4 address with TCP transport on port 12345./onion3/abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrst:12345
- Tor onion address
Supported Transports
The following transports are supported by the Tari communication layer:
- TCP/IP - A publicly accessible IPv4 address.
- SOCKS5 - Allows a SOCKS5 proxy to be configured for inbound and outbound connections.
- Tor - A specialisation of the SOCKS5 transport that facilitates connections over the Tor network.
Establishing a Connection
Every participating communication node SHOULD listen on at least one of the supported transports, accessible from a public address, to allow remote peers to establish peer connections.
A peer wishing to establish a peer connection should attempt a connection to one of the remote peer's public NetAddresses using the transport described in the NetAddress.
The peer that is initiating the outbound connection is referred to as the initiator and the peer that accepts the inbound connection is referred to as the responder for the remainder of this section.
We describe the following socket upgrade procedures for an encrypted peer-to-peer connection on the Tari network.
- The Wire Mode Byte
The wire mode indicates the intention of the initiator. It is up to the application domain to dictate what byte is acceptable however a common configuration is to use the wire mode to indicate which network (mainnet, testnet etc) the initiator is attempting to use allowing the responder to accept/reject the connection early on in the connection procedure.
The initiator MUST send a single byte indicating the wire mode within WIRE_MODE_TIMEOUT
.
The responder SHOULD to accept or reject (close) the connection based on the initiator's wire mode byte.
The responder SHOULD reject (close) the connection if no byte is received within WIRE_MODE_TIMEOUT
.
Once the byte is sent, the initiator may immediately proceed to the next procedure.
- The Noise Protocol
The Noise Protocol framework is a set of related crypto protocols that support mutual authentication and ephemeral encryption key exchange amongst other features.
We list the following characteristics and requirements for encrypted peer-to-peer connections on the Tari network:
- Mutual authentication of the initiator and responder;
- the responder need not know the initiator's public identity prior to the connection;
- the public identity of each participant is hidden to any observer;
- forward secrecy; and,
- for efficiency, has a minimal round trip time.
For these reasons we select the single round-trip Noise_IX_25519_ChaChaPoly_BLAKE2b
protocol, that is, the Noise IX
handshake pattern using Curve25519 for ephemeral and static identities, ChaChaPoly encryption and a HKDF using BLAKE2b.
If successful, an authenticated encrypted socket connection is established between the peers.
- Identity Exchange
At this point the initiator and responder are aware of each other's public identity keys, however, some additional information is required to fully "introduce" the participants to each other.
Both the initiator and responder simultaneously transmit a message containing their up-to-date public addresses, the peer feature flags, protocols supported by the peer, a timestamp of the last time these details changed and a signature that signs the public addresses, feature flags and update timestamp. Peers may store and share these details with other peers, who can check the authenticity of the provided information by verifying the signature.
- Multiplexing
Now that these procedures are complete, we have an active PeerConnection. There is no explicit protocol message required to initiate multiplexing as both sides implicitly agree to send yamux protocol messages. Light-weight Yamux substreams are opened lazily as/when required by the application.
Multiplexing allows a single socket connection to be used simultaneously by multiple components as if each had their own dedicated channel, in a very similar way that your browser can perform many HTTP2 requests over a single server connection. In yamux, these dedicated channels are called substreams. The details of the yamux protocol are out of scope for this RFC.
Protocol Negotiation
To begin any protocol, an initiator MUST open a new yamux substream and begin protocol negotiation.
Protocol negotiation ensures that both sides of the exchange are speaking the same language.
Protocols are identified by a unique string identifier given by the author of the protocol.
A protocol name can technically be any string, but it is defined in the Tari protocol as t/{protocol-ident}/{version}
,
where t
is short for Tari, for example the messaging protocol is t/msg/0.1
.
To begin, the initiator MUST send a protocol negotiation message consisting of 1 length byte, 1 bitflag byte and the protocol identifier string. The length byte MUST be equal to the length of the protocol identifier.
The bitflags are defined as follows:
0x01
-OPTIMISTIC
0x02
-TERMINATE
0x04
-PROTOCOL_NOT_SUPPORTED
(response)0x08 - 0x128
- Future use (ignored)
If the OPTIMISTIC
flag is set, the initiator considers the negotiation complete as it is optimistic that the responder
supports it. It can assume this because the peer gave a list of supported protocols in the Identity Exchange
procedure. If the responder does not support the protocol, it can simply close the substream.
In general, peers will use OPTIMISTIC
negatation and never wait for a response, as they have a full list of supported
protocols. However, if the initiator wishes to negotiate a protocol not in the protocol list, it may leave the OPTIMISTIC
flag unset in the initial message.
If the responder supports the protocol, it SHOULD respond with the name of the supported protocol and all flags unset and
immediately proceed with the agreed upon protocol.
If not, it SHOULD respond with the PROTOCOL_NOT_SUPPORTED
flag set and an empty protocol name and wait for more messages.
The initiator MAY send another protocol negotiation message or close the substream.
A responder MAY set the TERMINATE
flag at any time and close the substream. In practise, this is used to indicate to the
initiator that it has exceeded the maximum number of protocol negotiation queries (5) and should give up.
Peer
A single peer that can communicate on the Tari network.
Fields include:
public_key
- The Ristretto public key identity of the peer.addresses
- a list of NetAddresses associated with the peer, perhaps accompanied by some bookkeeping metadata, such as preferred address;peer_features
- bitflags with the following flagsMESSAGE_PROPAGATION = 0x01
- peer is able to propagate/route messagesDHT_STORE_FORWARD = 0x02
- peer provides message storage and can respond toSafRequestMessages
- A
COMMUNICATION_NODE
is defined as0x03
(MESSAGE_PROPAGATION | DHT_STORE_FORWARD
) - A
COMMUNICATION_CLIENT
is defined as0x00
last_seen
- a timestamp of the last time a message has been sent/received from this peer;banned_until
- an optional timestamp indicating the peer is banned;offline_at
- an optional timestamp indicating at which time a peer was marked as offline due to multiple failed attempts to contact the peer.
A peer may also contain reputation metrics (e.g. rejected_message_count, avg_latency) to be used to decide if a peer should be banned. This mechanism is yet to be decided.
PeerManager
The PeerManager is responsible for managing the list of peers with which the node has previously interacted. This list is called a routing table and is made up of Peers.
The PeerManager can
- add a peer to the routing table;
- search for a peer given a node ID, public key or NetAddress;
- delete a peer from the list;
- persist the peer list using a storage backend;
- restore the peer list from the storage backend;
- maintain lightweight views of peers, using a filter criterion, e.g. a list of peers that have been banned, i.e. a denylist; and
- prune the routing table based on a filter criterion, e.g. last date seen.
General-purpose Messaging Protocol
The messaging protocol is a simple fire-and-forget protocol where arbitrary messages can be sent between peers.
If Alice wants to send a message to Bob, she will open a new yamux substream and negotiate the
t/msg/0.1
protocol. If Bob wants to send a message to Alice, he will do the same. This means that two substreams (one per direction)
are open for bi-directional message sending as required.
Message frames are length delimited (see Tokio's LengthDelimitedCodec). At this level, no structure apart from the length-delimited framing is imposed on the message protocol allowing that to be fully determined by domain-level components.
Tari DHT and Base-Layer Messaging Protocol
The following illustrates the structure of a Tari message:
+----------------------------------------+
| DhtEnvelope |
| +----------------------------------+ |
| | DhtHeader | |
| +----------------------------------+ |
| +----------------------------------+ |
| | EnvelopeBody | |
| | (multipart, optionally encrypted)| |
| | +------------------------------+ | |
| | | +-----------------------+ | | |
| | | | 1. MessageHeader | | | |
| | | +-----------------------+ | | |
| | | +-----------------------+ | | |
| | | | 2. MessageBody | | | |
| | | +-----------------------+ | | |
| | +------------------------------+ | |
| +----------------------------------+ |
+----------------------------------------+
Each Tari message is wrapped in a DhtEnvelope
which contains a DhtHeader
and an EnvelopeBody
.
The DhtHeader
is a protobuf message with these fields:
-
version: u32
The major message header version. A peer MAY discard the message if the version is not supported.
-
message_signature: 64 bytes
The raw representation of a Schnorr signature committing to:
- sender public key
- signature public nonce
- and Blake2b hash of:
- "comms.dht.v1.message_signature"
- version
- destination
- msg_type
- flags
- expiry
- ephemeral_public_key
- body
This is required if the
ENCRYPTED
flag is set. -
ephemeral_public_key: 32 bytes
Ephemeral public key component of the ECDH shared key. MUST be specified if the ENCRYPTED flag is set.
-
dht_message_type: i32
Enumeration of the type of message.
- None = 0 - Domain-level message
- Join = 1 - Join/Announce
- Discovery = 2 - Discovery request
- DiscoveryResponse = 3 - Response to a discovery request
- SafRequestMessages = 20 - Request stored messages from a node
- SafStoredMessages = 21 - Stored messages response
-
flags: u32
- bitflags0x01 - ENCRYPTED
-
message_tag: u64
- Message trace ID. This can be omitted or any value and is used for debug tracing. -
expires: Option<prost_types::Timestamp>
Expiry timestamp for the message, if any. If specified any peer receiving the message after this time MAY discard it.
-
destination: Option<dht_header::Destination>
Enumeration of the message destination:
UNKNOWN = 0
- the destination was not specified, this indicates that the message is destined for the receiver.PUBLICKEY(XXXXX) = 1
- destination is the specified public key. This MUST be provided when theENCRYPTED
flag is set.
Inbound Message Validation
The following validation rules MUST be applied to all incoming messages:
- If
ENCRYPTED
is set- The
destination
MUST bePUBLICKEY(XXXXX)
- The
ephemeral_public_key
MUST be specified - The
message_signature
MUST be non-empty - If able to decrypt the message signature:
- the signature MUST be valid
- the destination public key MUST match the local public key
- The
- If the
ENCRYPTED
flag is not set, indicating a cleartext message- The
message_signature
MAY be specified. If it is, it MUST be valid. - Other fields relating to encryption e.g.
ephemeral_public_key
MAY be set but SHOULD be ignored.
- The
If any of these rules fail the message SHOULD be discarded.
Outbound Messaging
The protocol provides for the following outbound message broadcast strategies:
Direct(Identity)
- Send the message directly to the destination peer.Flood(exclude)
- Send to all connected peers excludingexclude
peers. If no peers are connected, no messages are sent.Random(n, exclude)
- Send to a random set of peers of size n that are Communication Nodes, excludingexclude
peers.ClosestNodes({node_id, exclude, connected_only})
- Send to all n nearest Communication Nodes to the given node_id.DirectOrClosestNodes({node_id, exclude, connected_only})
- Send directly to destination if connected but otherwise send to all n nearest Communication NodesBroadcast(excludes)
- Send to a random set of connected peers, excludingexcludes
peers. The number of peers selected at most equal topropagation_factor
.SelectedPeers(peers)
- Send to the specified peers.Propagate(NodeDestination, Vec<NodeId>)
- Propagate to a set of connected peers closer to the destination. The number of peers selected at most equal topropagation_factor
.
A peer's node_id is defined as the 13-byte variable-length Blake2b hash of the public key. To determine if a peer identity is "closer" to another peer we compare the XOR distance between peers as proposed by the kademlia paper.
DHT Messages
Join
Announces a peer's availability to the network. A routing node SHOULD propagate this message closer to the destination. As it travels through the network, the peer information is stored in the peer list. Peers close to the newly joined node MAY attempt to dial the node on receipt of this message.
Discovery
An encrypted discovery request containing the sender's contact details. A routing node SHOULD propagate this message closer to the destination.
The destination peer will attempt to contact the sender and send a DiscoveryResponse
message to reciprocate with its peer information.
DiscoveryResponse
Sent in response to a Discovery
message.
SafRequestMessages
/SafStoredMessages
Request and response messages for stored messages destined for the requester.
EnvelopeBody
The EnvelopeBody
is the "payload" of the message and consists of an arbitrary number of ordered opaque BLOBs.
It may be encrypted for a particular destination. The contents of these BLOBs are decided by domain-level requirements.
The Tari protocol inserts a MessageHeader
at index 0 and MessageBody
at index 1.
A zero-sized encoding of EnvelopeBody
is permitted as that is a valid proto3 encoding. When applying message encryption,
the body MUST be padded and, therefore, a message SHOULD be discarded if the encoded EnvelopeBody
is zero-sized.
MessageHeader
Every Tari message MUST have a payload header containing the following fields at index 0 in the EnvelopeBody
:
Name | Type | Description |
---|---|---|
message_type | u8 | An enumeration of the message type of the body. Refer to message types below. |
nonce | u32 | The optional message nonce. |
MessageTypes are represented as an unsigned eight-bit integer denoting the expected contents of the MessageBody
.
Category | Name | Value | Description |
---|---|---|---|
Network | PingPong | 1 | A PongPong message. |
Blockchain | NewTransaction | 65 | Transaction submitted by a wallet or propagated by a base node. |
Blockchain | NewBlock | 66 | Block propagated by a base node. |
Wallet | SenderPartialTransaction | 67 | A partial MimbleWimble transaction submitted by a sender wallet to the receiver. |
Wallet | ReceiverPartialTransactionReply | 68 | Reply to SenderPartialTransaction submitted by a receiver wallet to the sender. |
Blockchain | BaseNodeRequest | 69 | Base node request message. |
Blockchain | BaseNodeResponse | 70 | Base node response in reply to a BaseNodeRequest message. |
Blockchain | MempoolRequest | 71 | Base node mempool request message. |
Blockchain | MempoolResponse | 72 | Base node response in reply to a MempoolRequest message. |
Wallet | TransactionFinalized | 73 | Finalized transaction message sent by a sender to receiver wallet. |
Wallet | TransactionCancelled | 74 | A courtesy message sent by a wallet to inform the other that the transaction is cancelled. |
All other message types are reserved for future use.
Message Encryption
Encrypted messages may be routed across the Tari network such that only the destination node is able to decipher the contents of the message. An encrypted message reveals to recipient but keeps the sender and contents private.
To route an encrypted message, the following requirements MUST be met:
- The destination public key MUST be specified.
- The
message_signature
MUST be non-empty and SHOULD be encrypted. - The
ephemeral_public_key
MUST be a valid Ristretto public key. - The
EnvelopeBody
MUST be non-empty, as message padding (described below) is required. - If the message
expiry
is specified, a routing node MAY discard the message if the expiry time has passed.
A message is encrypted using the following procedure:
- The
DhtEnvelopBody
is containing theMessageHeader
andMessageBody
is serialized using [protobuf]. - A CSRNG is used to generate the cipher nonce and this is prepended onto the message.
- The plaintext message is padded with '0x00' to a multiple of 6000 bytes.
- The message encryption key is generated as follows:
- Key material
dh_key
is generated by Diffie-Hellman of the recipient public key and the ephemeral private key. - The final message key is constructed:
message_key = Blake2b("comms.dht.v1.key_message" || dh_key)
to produce a 32-byte key.
- Key material
- The message Schnorr signature is generated as follows:
- A domain-separated Blake2b hash is generated with the challenge
message_challenge = Blake2b("comms.dht.v1.challenge" || protocol_version || destination || dht_message_type || le_bytes(flags) || expiry || ephemeral_public_key || message_body)
. - The signer signs the hashed challenge
"comms.dht.v1.message_signature" || signer_public_key || public_nonce || message_challenge
with the sender secret key. - The signature is serialized and encrypted using the [ChaCha20Poly1305] AEAD cipher and the same
dh_key
constructed earlier.- The final signature key is constructed:
Blake2b("comms.dht.v1.key_signature" || dh_key)
to produce a 32-byte key.
- The final signature key is constructed:
- A domain-separated Blake2b hash is generated with the challenge
- The
DhtEnvelopBody
is encrypted using the [ChaCha20Poly1305] AEAD cipher andmessage_key
. - The final message is a
DhtEnvelope
containing the plaintextDhtHeader
and encryptedDhtEnvelopeBody
.
Message Routing
On receipt of a valid message with destination set to PUBLIC_KEY(xxxx)
, a node SHOULD forward a message either
directly to the peer, if able, or closer to the peer as per the XOR metric. If the message is invalid, the node
SHOULD discard it.
Store and Forward
Sometimes it may be desirable for messages to be sent without a destination node/client being online. This is especially important for a modern chat/messaging application as well as interactive Mimblewimble transactions.
Each communication node SHOULD allocate some disk space for storage of messages for offline recipients.
A sender sends a message destined for a particular public identity to its closest peers, which forward the message
to their closest peers, and so on. A peer is considered close enough by finding the farthest peer from the n
closest
online and available peers to the storage node and comparing that to the XOR distance of the message destination.
Eventually, the message will reach nodes that either know the destination or are very close to the destination. These nodes SHOULD store the message in some pending message storage for the destination. The maximum number of buckets and the size of each bucket SHOULD be sufficiently large as to be unlikely to overflow, but not so large as to approach disk space problems. Individual messages should be small and responsibilities for storage spread over the entire network.
On receipt of a valid message with destination set to PUBLIC_KEY(xxxx)
, and if the peer is sufficiently close to the destination,
a node SHOULD store the message for a time and return it later to the peer in response to a SafRequestMessages
message.
If the [DhtEnvelopeBody] is encrypted, the type and contents of the message remain private.
Message Deduplication
A peer propagating/routing a message may receive the same message after propagation from another peer as there is no way for routing node to know which peers have seen the message before. To prevent infinite message propagation, message contents should be hashed and stored in a dedup cache. On receiving a message, if the message hash is found, the message SHOULD be discarded and not propagated further.
Change Log
Date | Change | Author |
---|---|---|
13 Jun 2022 | Moved from tari repo | sdbondi |
9 Nov 2022 | Removed I2P and ZeroMQ | stringhandler |
17 Jan 2023 | Implementation parity updates | sdbondi |
25 Jan 2023 | Clarify empty body rules | sdbondi |
RFC-0173/Versioning
Versioning
Maintainer(s): Stringhandler
Licence
Copyright 2021 The Tari Development Community
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Language
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as shown here.
Disclaimer
This document and its content are intended for information purposes only and may be subject to change or update without notice.
This document may include preliminary concepts that may or may not be in the process of being developed by the Tari community. The release of this document is intended solely for review and discussion by the community of the technological merits of the potential system outlined herein.
Goals
The aim of this Request for Comment (RFC) is to describe the various types of versioning that nodes on the Tari network will use during interaction with other nodes.
Related Requests for Comment
- RFC-0710: Tari Communication Network and Network Communication Protocol
- RFC-0171: MessageSerialization
Description
In a decentralized system the set of nodes on the network will run a variety of software versions as time goes on. Some of these versions will be compatible and others not. For example, if a crucial consensus change is added during a hard fork event. Furthermore, there will be multiple networks running Tari code, i.e. Mainnet vs Testnet. Versioning refers to the strategies we will use for nodes to determine if they can communicate.
Tari will contain three different versioning schemes:
- WireMode is the first byte a peer sends when connecting to another peer, used to identify the network and/or protocol bytes that follow
- P2P message versions that will accompany every P2P message,
- Consensus rules versions that will be exchanged on connection and are included in each block header.
WireMode byte
In the Bitcoin P2P protocol messages are preceded by 4 magic values or bytes. These values are used to delimit when a new message starts in a byte stream and also are used to indicate which of the Bitcoin networks the node is speaking on, such as TestNet or Mainnet.
Tari message packets are encapsulated using the Noise protocol, so we do not need the delimiting functionality of these bytes. Once we have a Noise socket we are able to send/receive bytes as with any other socket, but those bytes are encrypted over the wire. Using that socket we send yamux packets and messaging/rpc messages are length-delimited.
Tari includes a single WireMode byte at the beginning of every connection session. This byte indicates which network a node is communicating on, so that if the counterparty is on a different network it can reject this connection cheaply without having to perform any further operations, like completing the Noise protocol handshake.
The following is the current mapping of the WireMode byte:
pub enum Network {
MainNet = 0x00,
LocalNet = 0x10,
Ridcully = 0x21,
Stibbons = 0x22,
Weatherwax = 0xa3,
Igor = 0x24,
Dibbler = 0x25,
Esmeralda = 0x26,
}
// As well as the special wiremode for local liveness checks
const LIVENESS_WIRE_MODE: u8 = 0xa6;
P2P message version
Peer to Peer messages on the Tari network are encapsulated into message envelopes. The body of message envelopes are defined, serialized and deserialized using Protobuf. These messages will only be updated by adding new fields to the Protobuf definitions, never removing fields. This is done in order to preserve backwards compatibility where newer nodes can still communicate with older nodes.
The P2P messaging protocol will see many changes in its lifetime. Some will be minor changes that are fully backwards
compatible and some changes will be breaking where older nodes will not be able to communicate with newer nodes. In
order to document these two classes of changes each P2P message header will contain a version
field that will use
a two-part semantic versioning scheme with the format of major.minor
integer versions. The minor
version will be
incremented whenever there is any change. The major
version be incremented when there is a breaking change made to
the P2P protocol. Each integer can be stored separately.
Consensus version
The final aspect of the Tari system that will be versioned are the Consensus rules. These rules will change as the network matures. Changes to consensus rules can be achieved using either a Soft fork or Hard fork. Soft forks are where new consensus rules are added that older nodes will see as valid (thus backwards compatible) but newer nodes will reject blocks from older nodes that are not aware of the new consensus rules. A hard fork means that the new consensus rules are not backwards compatible and so only nodes that know about the new rules will be able to produce and validate new transactions and blocks.
The consensus version will be used by a node to determine if it can interact with another node successfully or not. A
list of fork versions will be maintained within the code. When a connection is started with a new node the two nodes
will exchange Version
messages detailing the consensus version they are each running and the block height at which
they are currently operating.
Both nodes will need to reply with a Version Acknowledge
message to confirm that they are
compatible with the counterparty's version. It is possible for a newer node to downgrade its protocol to speak to an
older node so this must be decided during this handshake process. Only once the acknowledgments have been exchanged can
further messages be exchanged by the parties. This is the method currently employed on the
Bitcoin network
For example, if we have two nodes, Node A and Node B, where Node A is ahead of Node B in version and block height. During the handshake Node B will not recognize Node A's version but should wait for Node A to reject or confirm the connection because Node A could potentially downgrade their version to match Node B's. Node A will speak to Node B if and only if Node A recognizes Node B's version and Node B's block height is in the correct range for its advertised version according to Node A's fork version list.
Tari Block Headers contain a version
field which will be used to indicate the version of consensus rules that are
used in the construction and validation of this block. Consensus rules versions will only consist of breaking changes
and as such will be represented with a single incremented integer. This coupled with the internal list of fork versions,
that includes the height at which they came into effect, will be used to validate whether the consensus rules specified
in the header are valid for that block's height.
Change Log
Date | Change | Author |
---|---|---|
26 Oct 2022 | Stabilise RFC | CjS77 |
RFC-0174/Chat Metadata
Versioning
Maintainer(s): brianp
Licence
Copyright 2023 The Tari Development Community
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Language
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as shown here.
Disclaimer
This document and its content are intended for information purposes only and may be subject to change or update without notice.
This document may include preliminary concepts that may or may not be in the process of being developed by the Tari community. The release of this document is intended solely for review and discussion by the community of the technological merits of the potential system outlined herein.
Goals
The aim of this Request for Comment (RFC) is to describe the various types of versioning that nodes on the Tari network will use during interaction with other nodes.
Related Requests for Comment
Description
The chat protocol incorporates metadata within messages to support various message types, such as Replies, TokenRequests, Gifs, and Links. This metadata-driven approach enhances the technical flexibility of chat, allowing for the structured handling of distinct content and interactions within the messaging framework.
Chat metadata is transmitted simply as a length checked byte array allowing for flexbility of future content. Content is unvalidated in the protocol and requires clients to ensure standardized formatting for specific types.
Types & Formats
Reply:
MetadataType int: 0
Data format: String message_id
A reply metadata should be a single String element matching the UUID of the message being replied to.
Example: 06703dbfeabc43b98ceff16de2e104bc
TokenRequest:
MetadataType int: 1
Data format: double micro_minotari_amount
A double (reaL) number representing the request amount in MicroMinoTari.
Example: 14000.87
Gif:
MetadataType int: 2
Data format: String giphy_url
A url to a giphy hosted image
Example: https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExNjN0dmR2aWNjbTluNGZ3ZHlubHNqajIwcmlqazdtYXExNWp4aG94NSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/VIPdgcooFJHtC/giphy.gif
Links:
MetadataType int: 3
Data format: String uri
A uri in string format
Example: https://www.tari.com/
Change Log
Date | Change | Author |
---|---|---|
21 Dec 2023 | Proposal draft | brianp |
RFC-0181/BulletproofsPlus
Bulletproofs+ range proving
Maintainer(s): Aaron Feickert
Licence
Copyright 2022 The Tari Development Community
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Language
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as shown here.
Disclaimer
This document and its content are intended for information purposes only and may be subject to change or update without notice.
This document may include preliminary concepts that may or may not be in the process of being developed by the Tari community. The release of this document is intended solely for review and discussion by the community of the technological merits of the potential system outlined herein.
Goals
This Request for Comment (RFC) describes Tari-specific implementation details for Bulletproofs+ range proving and verifying, in addition to giving an outline of comparative performance.
Related Requests for Comment
Introduction
The Tari implementation of the Bulletproofs+ range proving system makes several changes and optimizations that we describe here. In particular, it supports the following useful features:
- Commitments can be extended; that is, they can commit to a value using multiple masks.
- Range proofs can be aggregated; that is, a single range proof can assert valid range for multiple commitments in an efficient way.
- A set of arbitrary range proofs can be verified in a batch; that is, the verifier can check the validity of all proofs in the set at once in an efficient way.
- The prover can assert a nonzero minimum value bound to a commitment.
- The prover can delegate to certain verifiers the ability to recover the masks used for the extended commitment in a non-aggregated proof.
The Bulletproofs+ preprint does not address extended commitments, as it only defines its range proving system using Pedersen commitments. However, a later preprint for Zarcanum updates the algorithms and security proofs to accommodate one additional mask, and the reasoning extends generally. Aggregation of range assertions using Pedersen commitments is described in the Bulletproofs+ preprint, and the Zarcanum preprint describes the corresponding changes for extended commitments. Batch verification is described only informally in the Bulletproofs+ preprint, and in an incomplete fashion. Minimum value assertion is not addressed in the preprint. An approach to mask and value recovery was used by Grin for the Bulletproofs range proving system, implemented as described by the deprecated RFC-0180, and can be modified to support Bulletproofs+ range proofs with extended commitments.
Notation
To reduce confusion in our description and more closely match implementation libraries, we use additive notation and uppercase letters for group elements, and otherwise generally assume notation from the preprints. Denote the commitment value generator by $G_c$ and the commitment mask generator vector by $\vec{H}_c$. Because the preprint uses the notation $A$ differently in the weighted inner product and range proving protocols, we rename it to $A'$ in the weighted inner product protocol.
We assume that a range proof aggregates $m$ range assertions, each of which is to an $n$-bit range. We further assume that each value commitment uses $p$ masks; that is, a standard Pedersen commitment would have $p = 1$.
A specific definition of note relates to that of the vector $\vec{d}$ introduced in the preprint. This vector is defined as \[ \vec{d} = \sum_{j=0}^{m-1} z^{2(j+1)} \vec{d}_j \tag{1} \] where each \[ \vec{d}_j = (\underbrace{0,\ldots,0}_{jn}, \vec{2}^n, \underbrace{0,\ldots,0}_{(m-j-1)n}) \tag{2} \] contains only powers of two. In particular, this means we can express individual elements of $\vec{d}$ as $d_{jn+i} = z^{2(j+1)} 2^i$ for $0 \leq i < n$ and $0 \leq j < m$.
Finally, we note one additional unfortunate notation change that applies to the implementation. Both the Bulletproofs+ and Zarcanum preprints use $G$ as the commitment value generator, and either $H$ or $\vec{H}_c$ (in our notation) for masking. However, in the Tari protocol (as in other similar protocols), this notation is switched! This is because of how generators are defined and used elsewhere in the protocol and elliptic curve library implementation. The only resulting change is a switch in generator notation in the Tari implementation: $H$ for the value component generator, and $\vec{G}_c$ (in our notation) for masking.
Minimum value assertion
We first briefly note how to achieve minimum value assertion. Let $0 \leq v_{\text{min}} \leq v \leq v_{\text{max}}$, where $v_{\text{min}}$ is a minimum value specified by the prover, $v$ is the value bound to the prover's commitment $V$, and $v_{\text{max}}$ is a globally-fixed maximum value. When generating the proof, the prover uses $v - v_{\text{min}}$ as the value witness, and additionally binds $v_{\text{min}}$ into the Fiat-Shamir transcript. When verifying the proof, the verifier uses $V - v_{\text{min}} G_c$ as the commitment (but binds the original commitment $V$ in the transcript). This asserts that $v_{\text{min}} \leq v \leq v_{\text{min}} + v_{\text{max}}$. While this approach modifies the upper bound allowed for value binding, it does not pose problems in practice, as the intent of range proving is to ensure no overflow when performing balance computations elsewhere in the Tari protocol.
Extended commitments and aggregation
We now describe how to reduce verification of a single aggregated range proof using extended commitments to a single multiscalar multiplication operation. A partial approach is described in the Bulletproofs+ preprint. The single multiscalar multiplication used to verify an aggregated range proof (given in Section 6.1 of the Bulletproofs+ preprint) can be written more explicitly in our case by accounting for the extra steps used to support extended commitments, and by noting that the $P$ input term to the weighted inner product argument (given in Figure 1 of the Bulletproofs+ preprint and Figure D.1 of the Zarcanum preprint) is replaced by the term $\widehat{A}$ defined in the overall range proving protocol (given in Figure 3 of the Bulletproofs+ preprint and Figure D.3 of the Zarcanum preprint).
Suppose we index the inner product generator vectors $\vec{G}$ and $\vec{H}$ using $i$, the inner product recursion generator vectors $\vec{L}$ and $\vec{R}$ using $j$, the aggregated commitment vector $\vec{V}$ by $k$, and the extended commitment mask generator vector $\vec{H}_c$ by $l$. We assume indexing starts at zero unless otherwise noted. Single aggregated proof verification reduces (by suitable modification of the equation given in Section 6.1 of the Bulletproofs+ preprint) to checking that the following equation holds: \[ \sum_{i=0}^{mn-1} (r'es_i) G_i + \sum_{i=0}^{mn-1} (s'es_i') H_i + \sum_{l=0}^{p-1} \delta_l' H_{c,l} = e^2 \widehat{A} + \sum_{j=0}^{\operatorname{lg}(mn)-1} (e^2e_j^2) L_j + \sum_{j=0}^{\operatorname{lg}(mn)-1} (e^2e_j^{-2}) R_j + e A' + B \] But we also have (from suitable modification of the definition given in Figure 3 of the Bulletproofs+ preprint) that \[ \widehat{A} = A - \sum_{i=0}^{mn-1} z G_i + \sum_{i=0}^{mn-1} (z + d_iy^{mn-i}) H_i + x G_c + y^{mn+1}\sum_{k=0}^{m-1} z^{2(k+1)} (V_k - v_{\text{min},k} G_c) \] defined by the range proving system outside of the inner product argument. Here \[ \begin{align*} x &= \langle \vec{1}^{mn}, \overrightarrow{y}^{mn} \rangle z - \langle \vec{1}^{mn}, \vec{d} \rangle y^{mn+1}z - \langle \vec{1}^{mn}, \overrightarrow{y}^{mn} \rangle z^2 \\ &= z\sum_{i=1}^{mn} y^i - y^{mn+1}z\sum_{i=0}^{mn-1}d_i - z^2\sum_{i=1}^{mn} y^i \tag{3} \end{align*} \] is a scalar defined entirely in terms of constants and challenge values from the proof. Grouping terms, we find that a single aggregated range proof can be verified by checking that the following equation holds: \[ \begin{multline*} \sum_{i=0}^{mn-1} (r'es_i + e^2z) G_i + \sum_{i=0}^{mn-1} (s'es_i' - e^2(z + d_iy^{mn-i})) H_i + \left( r'ys' - e^2x + e^2y^{mn+1}\sum_{k=0}^{m-1} z^{2(k+1)}v_{\text{min},k} \right) G_c \\ + \sum_{l=0}^{p-1} \delta_l' H_{c,i} - \sum_{k=0}^{m-1} (y^{mn+1}z^{2(k+1)}e^2) V_k - e^2 A - \sum_{j=0}^{\operatorname{lg}(mn)-1} (e^2e_j^2) L_j - \sum_{j=0}^{\operatorname{lg}(mn)-1} (e^2e_j^{-2}) R_j - e A' - B = 0 \tag{4} \end{multline*} \]
Batch verification
To verify a batch of proofs, we apply a separate random multiplicative scalar weight $w \neq 0$ to each proof's verification equation, form a linear combination of these equations, and group like terms. Because each equation receives a separate random weight, successful evaluation of the resulting linear combination means that each constituent equation holds with high probability, and therefore that all proofs in the set are valid. If the linear combination evaluation fails, at least one included proof is invalid. The verifier must then test each proof in turn, or use a more efficient approach like binary search to identify each failure. This follows the general approach informally discussed in Section 6.1 of the Bulletproofs+ preprint.
The reason for this rather convoluted algebra is twofold. First, grouping like terms means that each unique generator used across a batch is only evaluated in the resulting multiscalar multiplication once; since the generators $\vec{G}, \vec{H}, G_c, \vec{H}_c$ are globally fixed, this provides significant efficiency improvement. Second, the use of algorithms (like those of Straus and Pippenger and others) to evaluate the multiscalar multiplication scale slightly sublinearly, such that it is generally beneficial to minimize the number of multiscalar multiplications for a given set of generators. This means our approach to batch verification is effectively optimal.
Designated mask recovery
It is possible for the prover to perform careful modifications to a non-aggregated range proof in order to allow a designated verifier to recover the masks used in the corresponding extended commitment. The construction we describe here does not affect the verification process for non-designated verifiers. Note that this construction requires a non-aggregated proof that contains a range assertion for only a single commitment. Unlike the approach used initially in RFC-0180, it is not possible to embed additional data (like the commitment value) into a Bulletproofs+ range proof.
The general approach is that the prover and designated verifier share a common nonce seed. The prover uses this value to determinstically derive and replace certain nonces used in the proof. During the verification process, the designated verifier performs the same deterministic derivation and is able to extract the commitment masks from the proof. Because the resulting proof is still special honest-verifier zero knowledge, as long as the nonce seed is sampled uniformly at random, a non-designated verifier is not able to gain any information about the masks.
After sampling a nonce seed, the prover passes it through an appropriate set of domain-separated hash functions with scalar output to generate the following nonces used in the proof: \[ \{\eta_l\}, \{\delta_l\}, \{\alpha_l\}, \{d_{L,j,l}\}, \{d_{R,j,l}\} \] Here, as before, $0 \leq l < p$ is indexed over the number of masks used in the extended commitment, and $0 \leq j < \operatorname{lg}(mn)$ is indexed over the weighted inner product argument rounds. Let $\{\gamma_l\}$ be the masks used in the non-aggregated proof.
By doing this, the prover effectively defines the proof element set $\{\delta_l'\}$ as follows: \[ \delta_l' = \eta_l + \delta_le + e^2 \left( \alpha_l + \gamma_ly^{n+1}z^2 + \sum_{j=0}^{\operatorname{lg}(mn)-1}(e_j^2d_{L,j,l} + e_j^{-2}d_{R,j,l}) \right) \]
When verifying the proof, the designated verifier uses the nonce seed to perform the same nonce derivation as the prover. It then computes the mask set $\{\gamma_l\}$ as follows: \[ \gamma_l = \left( (\delta_l' - \eta_l - \delta_le)e^{-2} - \alpha_l - \sum_{j=0}^{\operatorname{lg}(mn)-1}(e_j^2d_{L,j,l} + e_j^{-2}d_{R,j,l}) \right) y^{-(n+1)}z^{-2} \] The recovered masks must then be checked against the extended commitment once the value is separately communicated to the verifier. Otherwise, if the verifier uses a different nonce seed than the prover did (or if the prover otherwise did not derive the nonces using a nonce seed at all), it will recover incorrect masks. If the verifier is able to construct the extended commitment from the value and recovered masks, the recovery succeeds; otherwise, the recovery fails.
Sum optimization
From Equation (3), the verifier must compute $\sum_i d_i$. Because the vector $\vec{d}$ contains $mn$ elements by Equations (1) and (2), computing the sum naively is a slow process. The implementation takes advantage of the fact that this sum can be expressed in terms of a partial sum of a geometric series to compute it much more efficiently; we describe this here.
We first recall the following identity for the partial sum of a geometric series for $r \neq 0$: \[ \sum_{k=0}^{n-1} r^k = \frac{1 - r^n}{1 - r} \]
Next, we note that from Equation (2), we have \[ \sum_{i=0}^{mn-1} (d_j)_i = \sum_{k=0}^{n-1} 2^k \] for all $0 \leq j < m$.
Given these facts, we can express the required sum of the elements of $\vec{d}$ as follows: \[ \begin{align*} \langle \vec{1}^{mn}, \vec{d} \rangle &= \sum_{i=0}^{mn-1} d_i \\ &= \sum_{i=0}^{mn-1} \left( \sum_{j=0}^{m-1} z^{2(j+1)} (d_j)_i \right) \\ &= \sum_{j=0}^{m-1} z^{2(j+1)} \left( \sum_{i=0}^{mn-1} (d_j)_i \right) \\ &= \sum_{j=0}^{m-1} z^{2(j+1)} \left( \sum_{k=0}^{n-1} 2^k \right) \\ &= (2^n - 1) \sum_{j=0}^{m-1} z^{2(j+1)} \end{align*} \] This requires a sum of only $m$ even powers of $z$, which can be computed iteratively.
Comparative performance
We now compare Bulletproofs+ performance to that of the Bulletproofs range proving system.
Size
Bulletproofs+ range proofs, like Bulletproofs, scale logarithmically in size. In each case, a proof consists of the following:
Proving system | Group elements | Scalars |
---|---|---|
Bulletproofs | $2 \operatorname{lg}(nm) + 4$ | $5$ |
Bulletproofs+ | $2 \operatorname{lg}(nm) + 3$ | $p + 2$ |
That is, both the range bit length $n$ and aggregation factor $m$ contribute logarithmically to the proof size. In the case of the Tari-specific implementation of Bulletproofs+, the number of masks contributes linearly to the proof size.
Regardless of the bit length $n$ or aggregation factor $m$ used, a single-mask ($p = 1$) Bulletproofs+ range proof saves $1$ group element and $2$ scalars over the equivalent Bulletproofs range proof. For the Ristretto-based Tari implementation, this amounts to $96$ bytes after group element and scalar encoding.
We also note that while it is possible to encode an extra scalar of data in a Bulletproofs non-aggregated range proof (in addition to the mask) using nonce-based recovery techniques, this is not possible with Bulletproofs+ range proofs.
Verification efficiency
Like in Bulletproofs+, it is possible to reduce verification of a batch of Bulletproofs range proofs to a single multiscalar multiplication operation.
To compare this efficiency, we count unique generators used in the multiscalar multiplication operation in both cases. It is the case that scalar-only operations differ greatly between the two systems, but these operations are much faster than those involving group elements.
Let $b$ be the batch size of such a verification; that is, the number of proofs to be verified together. This means single-proof verification has $b = 1$.
Proving system | Operation size |
---|---|
Bulletproofs | $b[2\operatorname{lg}(mn) + m + 4] + 2mn + 2$ |
Bulletproofs+ | $b[2\operatorname{lg}(mn) + m + 3] + 2mn + p + 1$ |
Verification in Bulletproofs+ is slightly faster than in Bulletproofs in theory.
In practice, verification of a single 64-bit range proof in the Tari Bulletproofs+ implementation is comparable to an updated fork of the Dalek Bulletproofs implementation, though verification of an aggregated proof is notably slower.
Proving efficiency
It is more challenging to compare proving efficiency, since generation of a range proof does not reduce cleanly to a single multiscalar multiplication evaluation for either Bulletproofs or Bulletproofs+ range proofs. However, we note that the overall complexity between the inner-product arguments in the proving systems is similar in terms of group operations; outside of these inner-product arguments, Bulletproofs+ requires fewer group operations. Overall efficiency is likely to depend on specific optimizations.
In practice, generation of a single 64-bit range proof in the Tari Bulletproofs+ implementation is about 10% faster than in the Dalek Bulletproofs updated fork, with similar performance for aggregated proofs.
Changelog
Date | Change | Author |
---|---|---|
7 Dec 2022 | First draft | Aaron |
13 Jan 2022 | Performance updates | brianp |
20 Jul 2023 | Sum optimization | Aaron |
31 Jul 2023 | Notation and efficiency | Aaron |
3 Aug 2023 | Proving efficiency note | Aaron |
RFC-0182/CommitmentSignatures
Commitment and public key signatures
Maintainer(s): Aaron Feickert
Licence
Copyright 2023 The Tari Development Community
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Language
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as shown here.
Disclaimer
This document and its content are intended for information purposes only and may be subject to change or update without notice.
This document may include preliminary concepts that may or may not be in the process of being developed by the Tari community. The release of this document is intended solely for review and discussion by the community of the technological merits of the potential system outlined herein.
Goals
This Request for Comment (RFC) describes signatures relating to commitments and public keys that are useful for Tari transaction authorization.
Related Requests for Comment
Introduction
A commitment and public key signature (CAPK signature) is used in Tari protocols as part of transaction authorization. Given a commitment $C = aH + xG$ and public key $P = yG$, a CAPK signature asserts knowledge of the openings $(a,x)$ and $y$ in zero knowledge. Optionally, the value $a$ may be disclosed as part of the signature.
Structurally, a CAPK signature is a conjunction of Schnorr-type representation proofs for $C$ and $P$. While it is defined as an interactive protocol, the (strong) Fiat-Shamir technique is used to make it non-interactive and bind arbitrary message data, effectively transforming the proof into a signature.
Value-hiding protocol
We first describe the version of the protocol that does not reveal the commitment value. It is a sigma protocol for the following relation, where $G$ and $H$ are fixed group generators with no efficiently-computable discrete logarithm relationship: \[ \left\{ C, P ; (a, x, y) | C = aH + xG, P = yG \right\} \] The interactive form of this protocol proceeds as follows:
- The prover samples scalar nonces $r_a, r_x, r_y$ uniformly at random.
- The prover computes ephemeral values $C_{eph} = r_a H + r_x G$ and $P_{eph} = r_y G$, and sends these values to the verifier.
- The verifier samples a nonzero scalar challenge $e$ uniformly at random, and sends it to the prover.
- The prover computes $u_a = r_a + ea$ and $u_x = r_x + ex$ and $u_y = r_y + ey$, and sends these values to the verifier.
- The verifier accepts the proof if and only if $u_a H + u_x G = C_{eph} + eC$ and $u_y G = P_{eph} + eP$.
The strong Fiat-Shamir technique can transform this into a non-interactive protocol. To do so, the prover and verifier both use a domain-separated cryptographic hash function to compute the challenge $e$, carefully binding $C$, $P$, $C_{eph}$, $P_{eph}$, and any arbitrary message data. In this non-interactive format, the public statement data is the tuple $(C, P)$ and the proof data is the tuple $(C_{eph}, P_{eph}, u_a, u_x, u_y)$.
Verification can be made more efficient by reducing to a single linear combination evaluation. To do this, the verifier samples a nonzero scalar weight $w$ uniformly at random (not using Fiat-Shamir!) and accepts the proof if and only if the following holds: \[ u_a H + (u_x + wu_y)G - C_{eph} - wP_{eph} - eC - weP = 0 \]
Security proof
We require that the (interactive) protocol be correct, special sound, and special honest-verifier zero knowledge. While the proof technique is standard, we present it here for completeness.
Correctness follows immediately by inspection.
To show the protocol is special sound, consider a rewinding argument with two distinct challenges $e \neq e'$ on the same statement $(C, P)$ and initial transcript $(C_{eph}, P_{eph})$. We must produce extracted witnesses $a, x, y$ consistent with the statement. Suppose the responses on these transcripts are $(u_a, u_x, u_y)$ and $(u_a', u_x', u_y')$, respectively. The first verification equation applied to both transcripts yields $(u_a - u_a')H + (u_x - u_x')G = (e - e')C$, from which we obtain witness extractions \[ a = \frac{u_a - u_a'}{e - e'} \] and \[ x = \frac{u_x - u_x'}{e - e'} \] such that $C = aH + xG$, as required. Similarly, the second verification equation yields $(u_y - u_y')G = (e - e')P$, so \[ y = \frac{u_y - u_y'}{e - e'} \] is the remaining extracted witness, such that $P = yG$. This shows the protocol is 2-special sound.
Finally, we show the protocol is special honest-verifier zero knowledge. This requires us to simulate, for an arbitrary statement and challenge, a transcript distributed identically to that of a real proof. Fix a statement $(C, P)$ and sample a challenge $e \neq 0$ uniformly at random. Then, sample $u_a, u_x, u_y$ uniformly at random. We then set $C_{eph} = u_a H + u_x G - eC$ and $P_{eph} = u_y G - eP$. The resulting transcript is valid by construction. Further, all proof elements are uniformly distributed at random in both the simulation and in real proofs. This shows the protocol is special honest-verifier zero knowledge.
Value-revealing protocol
We now describe a modified version of the protocol that reveals the commitment value. While this protocol can be made more efficient than we list here (discussed later), this design is intended to be more closely compatible with the value-hiding protocol for easier implementation. It is a sigma protocol for the following relation, where $G$ and $H$ are fixed group generators with no efficiently-computable discrete logarithm relationship: \[ \left\{ C, P, a ; (x, y) | C = aH + xG, P = yG \right\} \] The interactive form of this protocol proceeds as follows:
- The prover samples scalar nonces $r_x, r_y$ uniformly at random.
- The prover computes ephemeral values $C_{eph} = r_x G$ and $P_{eph} = r_y G$, and sends these values to the verifier.
- The verifier samples a nonzero scalar challenge $e$ uniformly at random, and sends it to the prover.
- The prover computes $u_a = ea$ and $u_x = r_x + ex$ and $u_y = r_y + ey$, and sends these values to the verifier.
- The verifier accepts the proof if and only if $u_a = ea$ and $u_a H + u_x G = C_{eph} + eC$ and $u_y G = P_{eph} + eP$.
As in the value-hiding protocol, the strong Fiat-Shamir technique can transform this into a non-interactive protocol. Crucially, in this version of the protocol, the prover and verifier must also bind the value $a$ into the challenge. That is, they both use a domain-separated cryptographic hash function to compute the challenge $e$, carefully binding $C$, $P$, $a$, $C_{eph}$, $P_{eph}$, and any arbitrary message data. In this non-interactive format, the public statement data is the tuple $(C, P, a)$ and the proof data is the tuple $(C_{eph}, P_{eph}, u_a, u_x, u_y)$.
The same weighting technique as above may be used to combine the second and third verification equations here. However, the first verification equation must still be checked.
Security proof
We require that the (interactive) protocol be correct, special sound, and special honest-verifier zero knowledge. While the proof technique is standard, we present it here for completeness.
Correctness follows immediately by inspection.
To show the protocol is special sound, consider a rewinding argument with two distinct challenges $e \neq e'$ on the same statement $(C, P, a)$ and initial transcript $(C_{eph}, P_{eph})$. We must produce extracted witnesses $x, y$ consistent with the statement. Suppose the responses on these transcripts are $(u_a, u_x, u_y)$ and $(u_a', u_x', u_y')$, respectively. The first verification equation gives that $u_a = ea$ and $u_a' = e'a$. The second verification equation applied to both transcripts then yields $(e - e')aH + (u_x - u_x')G = (e - e')C$, from which we obtain witness extraction \[ x = \frac{u_x - u_x'}{e - e'} \] such that $C = aH + xG$, as required. Similarly, the third verification equation yields $(u_y - u_y')G = (e - e')P$, so \[ y = \frac{u_y - u_y'}{e - e'} \] is the remaining extracted witness, such that $P = yG$. This shows the protocol is 2-special sound.
Finally, we show the protocol is special honest-verifier zero knowledge. This requires us to simulate, for an arbitrary statement and challenge, a transcript distributed identically to that of a real proof. Fix a statement $(C, P)$ and sample a challenge $e \neq 0$ uniformly at random. Then, fix $u_a = ea$ and sample $u_x, u_y$ uniformly at random. We then set $C_{eph} = u_a H + u_x G - eC$ and $P_{eph} = u_y G - eP$. The resulting transcript is valid by construction. Further, all proof elements are either fixed by verification or uniformly distributed at random in both the simulation and in real proofs. This shows the protocol is special honest-verifier zero knowledge.
Simplification
This protocol may be simplified further. To do so, the prover does not compute $u_a$ at all, instead sending only $u_x$ and $u_y$ as its post-challenge responses. To account for this, the verifier accepts the proof if and only if $eaH + u_x G = C_{eph} + eC$ and $u_y G = P_{eph} + eP$.
However, this alters the proof format and verification, which may be undesirable for a more general implementation.
RFC-0190/Mempool
Maintainer(s): Stanley Bondi, SW van Heerden
The Mempool for Unconfirmed Transactions on the Tari Base Layer
License
Copyright 2018 The Tari Development Community
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Language
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as shown here.
Disclaimer
The purpose of this document and its content is for information purposes only and may be subject to change or update without notice.
This document may include preliminary concepts that may or may not be in the process of being developed by the Tari community. The release of this document is intended solely for review and discussion by the community regarding the technological merits of the potential system outlined herein.
Goals
This document will introduce the Tari base layer Mempool that consists of an Unconfirmed Pool and Reorg Pool. The Mempool is used for storing and managing unconfirmed transactions.
Related RFCs
Description
Assumptions
- Each base node is connected to a number of peers that maintain their own copies of the Mempool.
Abstract
The Mempool is responsible for managing, verifying and maintaining all unconfirmed transactions that have not yet been included in a block and added to the Tari blockchain. It is also responsible for propagating valid transactions and sharing the Mempool state with connected peers. An overview of the required functionality for the Mempool and each of its component pools will be provided.
Overview
Every base node maintains a transaction pool called Mempool
that consists of two separate pools: the Unconfirmed Pool and Reorg Pool.
These two pools have different tasks and work together to form the Mempool used for maintaining unconfirmed transactions.
This is the role descriptions for each component pool:
Unconfirmed Pool
: contains all unconfirmed transactions that have been verified, have passed all checks, that only spend valid UTXOs and don't have any time-lock restrictions.Reorg Pool
: stores a backup of all transactions that have recently been included into blocks, in case a blockchain reorganization occurs and these transactions have to be restored back to the Unconfirmed Pool, so that they can be included in future blocks.
Prioritizing Unconfirmed Transactions
The maximum storage capacity used for storing unconfirmed transactions by the Mempool and each of its component pools
can be configured. When a new transaction is received and the storage capacity limit is reached, then
transactions are prioritized by ordering their total fee per gram
of all UTXOs used and transaction age, in that order.
The transactions of the least priority are discarded to make room for higher priority transactions.
The transaction priority metric has the following behavior:
- transactions with higher fee per gram SHOULD be prioritized over lower fee per gram transactions.
- older transactions in the mempool SHOULD be prioritized over newer ones.
Memory Pool State: Syncing and Updating
On the initial startup, the complete state of the unconfirmed pool is pulled from the connected peers. Typically, Mempool doesn't persist its state, but can be configured to do so. If the state is locally present, then only the missing, unconfirmed transactions are synced from the peers, otherwise, the full state is requested. The validity and priority of transactions are computed as they are being downloaded from the connected peers. If the base node undergoes a re-org, then the missing state is again pulled from the peers.
The functional bahavior required for the Mempool's synchronization are the following:
- All verified transactions MUST be propagated to neighboring peers.
- Unverified or invalid transactions MUST NOT be propagated to peers.
- Verified transactions that were discarded due to low priority levels MUST be propagated to peers.
- Duplicate transactions MUST NOT be propagated to peers.
- Mempool MUST have an interface, allowing peers to query and download its state, partially and in full.
- Mempool MUST accept all transactions received from peers but MAY decide to discard low-priority transactions.
- Mempool MUST allow wallets to track payments by monitoring that a particular transaction has been added to the Mempool.
- Mempool MAY choose:
- to discard its state on restart and then download the full state from its peers or
- to store its state using persistent storage to reduce communication bandwidth required when reinitializing the pool after a restart.
Unconfirmed Pool
This Mempool component consists of transactions that have been received, verified and have passed all the checks, but not yet included in the blocks. These transactions are ready to be used to construct new blocks for the Tari blockchain.
Functional behavior required of the Unconfirmed Pool:
- It MUST verify that incoming transactions spend only existing UTXOs.
- It MUST ensure that incoming transactions don't have a processing time-lock or has a time-lock that has expired.
- It MUST ensure that all time-locks of the UTXOs that will be spent by the transaction have expired.
- Transactions that have been used to construct new blocks MUST be removed from the Unconfirmed Pool and added to the Reorg Pool.
Reorg Pool
The Reorg Pool consists of transactions that have recently been added to blocks, resulting in their removal from the Unconfirmed Pool. When a potential blockchain reorganization occurs that invalidates previously assembled blocks, the transactions used to construct these discarded blocks can be moved back into the Unconfirmed Pool. This ensures that high-priority transactions are not lost during reorganization and can be included in future blocks. The Reorg Pool is an internal, isolated Mempool component and cannot be accessed or queried from outside.
Functional behavior required of the Reorg Pool:
- Copies of the transactions used recently in blocks MUST be stored in the Reorg Pool.
- Transactions in the Reorg Pool MAY be discarded after a set expiration threshold has been reached.
- When reorganization is detected, all affected transactions found in the Reorg Pool MUST be moved back to the Unconfirmed Pool and removed from the Reorg Pool.
Mempool
The Mempool is responsible for the internal transaction management and synchronization with the peers. New transactions must pass all verification steps to make it into the Unconfirmed Pool and further be propagated to peers.
Functional behavior required of the Mempool:
- If the received transaction already exists in the Mempool, then it MUST be discarded.
- If multiple transactions contain the same UTXO, then only the highest priority transaction MUST be kept and the rest (having the said UTXO included) MUST be discarded.
- If the storage capacity limit is reached, then new incoming transactions SHOULD be prioritized according to a set number of rules.
- Transactions of the least priority MUST be discarded to make room for higher priority incoming transactions.
- Transactions with its computed priority being lower than the minimum set threshold MUST be discarded.
- The Mempool MUST verify that incoming transactions do not have duplicate outputs.
- The Mempool MUST verify that only matured coinbase outputs can be spent.
- Each component pool MAY have the storage capacity configured and adjusted.
- The memory pool SHOULD have a mechanism to estimate fee categories from the current Mempool state. For example, the transaction fee can be estimated, ensuring that new transactions will be properly prioritized, to be added into new blocks in a timely manner.
Functional behavior required for the allocation of incoming transactions in the component pools:
- Verified transactions that have passed all checks such as spending of valid UTXOs and expired time-locks MUST be placed in the Unconfirmed Pool.
- Incoming transactions with time-locks, prohibiting them from being included in new blocks SHOULD be discarded.
- Newly received, verified transactions attempting to spend a UTXO that does not yet exist MUST be discarded.
- Transactions that have been added to blocks and were removed from the Unconfirmed Pool SHOULD be added to the Reorg Pool.
Tari-specific Base Layer Extensions
This section covers RFCs that describe extensions to the Mimblewimble protocol that enable key functionality for the Tari Base layer token and Digital Asset network.
- RFC-0201: TariScript
- RFC-0202: TariScript Opcodes
- RFC-0203: Stealth Addresses
- RFC-0230: Time related transactions
- RFC-0240: Atomic swaps
- RFC-0250: Covenants
RFC-0201/TariScript
TariScript
Maintainer(s): Cayle Sharrock, Stringhandler
Licence
Copyright 2020 The Tari Development Community
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Language
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as shown here.
Disclaimer
This document and its content are intended for information purposes only and may be subject to change or update without notice.
This document may include preliminary concepts that may or may not be in the process of being developed by the Tari community. The release of this document is intended solely for review and discussion by the community of the technological merits of the potential system outlined herein.
Goals
This Request for Comment (RFC) presents a proposal for introducing TariScript into the Tari base layer protocol. Tari Script aims to provide a general mechanism for enabling further extensions such as side-chains, the DAN, one-sided payments and atomic swaps.
Related Requests for Comment
- RFC-0182: Commitment and public key signatures
- RFC-0200: Base Layer Extensions
- RFC-0202: TariScript Opcodes
- RFC-0204: TariScript Examples
- RFC-0250: Covenants
$$ \newcommand{\script}{\alpha} % utxo script \newcommand{\input}{ \theta } \newcommand{\cat}{\Vert} \newcommand{\so}{\gamma} % script offset \newcommand{\hash}[1]{\mathrm{H}\bigl({#1}\bigr)} $$
Introduction
It is hopefully clear to anyone reading these RFCs that the ambitions of the Tari project extend beyond a Mimblewimble-clone-coin. It should also be fairly clear that vanilla Mimblewimble does not have the feature set to provide functionality such as:
- One-sided payments
- Multiparty side-chain peg-outs and peg-ins
- Generalised smart contracts
Extensions to Mimblewimble have been proposed for most of these features, for example, David Burkett's one-sided payment proposal for LiteCoin (LIP-004) and this project's HTLC RFC.
Some smart contract features are possible, or partly possible in vanilla Mimblewimble using Scriptless script, such as
- Atomic swaps
- Hash time-locked contracts
Tari implemented a scripting language similar to Bitcoin script, called TariScript, under a single set of (relatively minor) modifications and additions to the Mimblewimble protocol, which achieved collapsing all of these use cases.
Scripting on Mimblewimble
Other than Beam, none of the existing Mimblewimble projects have employed a scripting language.
Grin styles itself as a "Minimal implementation of the Mimblewimble protocol", so one might infer that this status is unlikely to change soon.
Beam does have a smart contract protocol, which allows users to execute arbitrary code (shaders) in a sandboxed Beam VM and have the results of that code interact with transactions.
Mimblewimble coin is a fork of Grin and "considers the protocol ossified".
Litecoin has included Mimblewimble as a side-chain through MWEB. As of 2022, there appears to be no plans to include general scripting into the protocol.
Scriptless scripts
Scriptless script is a wonderfully elegant technology and the inclusion of TariScript does not preclude the use of Scriptless scripts in Tari. However, scriptless scripts have some disadvantages:
- They are often difficult to reason about, with the result that the development of features based on scriptless scripts is essentially in the hands of a very select group of cryptographers and developers.
- The use case set is impressive considering that the "scripts" are essentially signature wrangling, but is still somewhat limited.
- Every feature must be written and implemented separately using the specific and specialised protocol designed for that feature. That is, it cannot be used as a dynamic scripting framework on a running blockchain.
TariScript - a brief motivation
The essential idea of TariScript is as follows:
Given a standard Tari UTXO, we add additional restrictions on whether that UTXO can be included as a valid input in a transaction.
As long as those conditions are suitably committed to, are not malleable throughout the existence of the UTXO, and one can prove that the script came from the UTXO owner, then these conditions are not that different to the requirement of having range proofs attached to UTXOs, which require that the value of Tari commitments is non-negative.
This argument is independent of the nature of the additional restrictions. Specifically, if these restrictions are manifested as a script that provides additional constraints over whether a UTXO may be spent, the same arguments apply.
This means that in a very hand-wavy sort of way, there ought to be no reason that TariScript is not workable.
Note that range proofs can be discarded after a UTXO is spent. This entails that the global security guarantees of Mimblewimble is not that every transaction in history was valid from an inflation perspective, but that the net effect of all transactions leads to zero spurious inflation. This sounds worse than it is, since locally, every individual transaction is checked for validity at the time of inclusion in the blockchain.
If it somehow happened that two illegal transactions made it into the blockchain (perhaps due to a bug), and the two cancelled each other out such that the global coin supply was still correct, one would never know this when doing a chain synchronisation in pruned mode.
But if there was a steady inflation bug due to invalid range proofs making it into the blockchain, a pruned mode sync would still detect that something was awry, because the global coin supply balance acts as another check.
With TariScript, once the script has been pruned away, and then there is a re-org to an earlier point on the chain, then there's no way to ensure that the script was honoured unless you run an archival node.
This is broadly in keeping with the Mimblewimble security guarantees that, in pruned-mode synchronisation, individual transactions are not necessarily verified during chain synchronisation.
However, the guarantee that no additional coins are created or destroyed remains intact.
Put another way, the blockchain relies on the network at the time to enforce the TariScript spending rules. This means that the scheme may be susceptible to certain horizon attacks.
Incidentally, a single honest archival node would be able to detect any fraud on the same chain and provide a simple proof that a transaction did not honour the redeem script.
Additional requirements
The assumptions that broadly equate scripting with range proofs in the above argument are:
- The script must be committed to the blockchain.
- The script must not be malleable in any way without invalidating the transaction. This restriction extends to all participants, including the UTXO owner.
- We must be able to prove that the UTXO originator provides the script and no one else.
- The scripts and their redeeming inputs must be stored on the blockchain. In particular, the input data must not be malleable.
Preventing Cut-through
A major issue with many Mimblewimble extension schemes is that miners are able to cut-through UTXOs if an output is spent in the same block it was created. This makes it so that the intervening UTXO never existed; along with any checks and balances carried in that UTXO. It's also impossible to prove without additional information that cut-through even occurred (though one may suspect, since the "one" transaction would contribute two kernels to the block).
In particular, cut-through is devastating for an idea like TariScript which relies on conditions present in the UTXO being enforced. For example, say there is a UTXO in the mempool that everyone knows the blinding factor to, but is restricted to a single public key via the TariScript. A malicious user can spend the UTXO in a zero-conf transaction, and send the cut-through transaction to the mempool. Since the miner only sees the resulting aggregate transaction, it cannot know that there was a TariScript on the removed UTXO. The solution to this problem is described later in this RFC.
In contrast, range proofs are still valid if they are cut-through, because the resulting UTXOs must have valid range proofs.
Protocol additions
Please refer to Notation, which provides important pre-knowledge for the remainder of the report.
At a high level, TariScript works as follows:
- The spending script \((\script)\) is recorded in the transaction UTXO.
- Although scripts are included on the UTXO, they are only executed when the UTXO is spent, and in most cases, will require additional input data to be provided at this time.
- The script input data is recorded in the transaction inputs.
- When validating a transaction, the script is executed using the script input data.
- After the script \((\script)\) is executed, the execution stack must contain exactly one value that will be interpreted as the script public key \((K_{S})\).
- The script public key and commitment must match the script signature on the input, which prevents malleability of the data in the input.
- To prevent a script from being removed from a UTXO, a new field sender offset public key \((K_{O})\) has been added.
- The sender offset private keys \((k_{O})\) and script private keys \((k_{S})\) are used in conjunction to create a script offset \((\so)\), which are used in the consensus balance to prevent a number of attacks.
NOTE: One can prove ownership of a UTXO by demonstrating knowledge of both the commitment blinding factor \((k\)), and the script private key \((k_{S})\) for a valid script input.
UTXO data commitments
The script, as well as other UTXO metadata, such as the output features are signed for with the sender offset private key to prevent malleability. As we will describe later, the notion of a script offset is introduced to prevent cut-through and forces the preservation of these commitments until they are recorded into the blockchain.
Transaction output
The definition of a Tari transaction output is:
pub struct TransactionOutput {
/// The transaction output version
version: TransactionOutputVersion,
/// Options for an output's structure or use
features: OutputFeatures,
/// The homomorphic commitment representing the output amount
commitment: Commitment,
/// A proof that the commitment is in the right range
proof: RangeProof,
/// The serialised script
script: Vec<u8>,
/// The sender offset pubkey, K_O
sender_offset_public_key: PublicKey
/// UTXO signature signing the transaction output data and the homomorphic commitment with a combination
/// of the homomorphic commitment private values (amount and blinding factor) and the sender offset private key.
metadata_signature: CommitmentAndPublicKeySignature,
/// The covenant that will be executed when spending this output
covenant: Covenant,
/// The encrypted commitment value.
encrypted_value: EncryptedValue,
/// The minimum value of the commitment that is proven by the range proof
minimum_value_promise: MicroTari,
}
The metadata signature is a CAPK signature (as described in RFC-0182) signed with the commitment value, \( v_i \), known by the sender and receiver, the spending key, \( k_i \), known by the receiver and the sender offset private key, \(k_{Oi}\), known by the sender. (Note that \( k_{Oi} \) should be treated as a nonce.) The CAPK signature is effectively an aggregated CAPK signature between the sender and receiver, and the challenge consists of all the transaction output metadata, effectively forming a contract between the sender and receiver, making all those values non-malleable and ensuring only the sender and receiver can enter into this contract.
For purposes of this RFC, we denote the metadata signature terms as follows:
- \( R_{MRi} \) is the ephemeral commitment,
- \( R_{MSi} \) is the ephemeral public key,
- \( a_{MRi} \) and \( b_{MRi} \) are the first and second commitment signature scalars,
- \( b_{MSi} \) is the public key signature scalar.
Sender:
The sender's ephemeral public key is:
$$ \begin{aligned} R_{MSi} &= r_{MSi_b} \cdot G \end{aligned} \tag{3} $$
The sender sends \( (K_{Oi}, R_{MSi}) \) along with the other partial transaction information \( (\script_i, F_i) \) to the receiver, who now has all the required information to calculate the final challenge.
Reciver:
The commitment definition is unchanged:
$$ \begin{aligned} C_i = v_i \cdot H + k_i \cdot G \end{aligned} \tag{4} $$
The receiver's ephemeral commitment is:
$$ \begin{aligned} R_{MRi} &= r_{MRi_a} \cdot H + r_{MRi_b} \cdot G \end{aligned} \tag{5} $$
The final challenge is:
$$ \begin{aligned} e &= \hash{ R_{MSi} \cat R_{MRi} \cat \script_i \cat F_i \cat K_{Oi} \cat C_i \cat \pi_i \cat \varphi_i \cat \vartheta_i } \\ \end{aligned} \tag{6} $$
The receiver can now calculate their portion of the aggregated CAPK signature as:
$$ \begin{aligned} a_{MRi} &= r_{MRi_a} + e \cdot v_{i} \\ b_{MRi} &= r_{MRi_b} + e \cdot k_i \end{aligned} \tag{7} $$
The receiver sends \( s_{MRi} = (a_{MRi}, b_{MRi}, R_{MRi} ) \) along with the other partial transaction information \( (C_i) \) to the sender.
Sender:
The sender starts by calculating the final challenge \( e \) (6) and then completes their part of the aggregated CAPK signature.
$$ \begin{aligned} b_{MSi} &= r_{MSi_b} + e \cdot k_{Oi} \end{aligned} \tag{8} $$
The final CAPK signature is combined as follows:
$$ \begin{aligned} s_{Mi} = (a_{MRi}, b_{MRi}, R_{MRi}, b_{MSi}, R_{MSi} ) \end{aligned} \tag{9} $$
Verifier:
This is verified by the following:
$$ \begin{aligned} a_{MRi} \cdot H + b_{MRi} \cdot G &\overset{?}{=} R_{MRi} + e \cdot C \\ b_{MSi} \cdot G &\overset{?}{=} R_{MSi} + e \cdot K_{Oi} \end{aligned} \tag{10} $$
Note that:
- The UTXO has a positive value \( v \) like any normal UTXO.
- The script and the output features can no longer be changed by the miner or any other party. This includes the sender and receiver; they would need to cooperate to enter into a new contract to change any metadata, otherwise, the metadata signature will be invalidated.
- We provide the complete script on the output.
Transaction input
In standard Mimblewimble, an input is the same as an output sans range proof. The range proof doesn't need to be checked again when spending inputs, so it is dropped.
The definition of a Tari transaction input is:
pub struct TransactionInput {
/// The transaction input version
version: TransactionInputVersion,
/// The output that will be spent that this input is referencing
spent_output: SpentOutput {
/// The transaction output version
version: TransactionOutputVersion,
/// Options for an output's structure or use
features: OutputFeatures,
/// The homomorphic Pedersen commitment representing the output amount
commitment: Commitment,
/// The serialised script
script: Vec<u8>,
/// The sender offset pubkey, K_O
sender_offset_public_key: PublicKey
/// The covenant that will be executed when spending this output
covenant: Covenant,
/// The encrypted commitment value.
encrypted_value: EncryptedValue,
/// The minimum value of the commitment that is proven by the range proof
minimum_value_promise: MicroTari,
}
/// The script input data, if any
input_data: Vec<u8>,
/// Signature signing the script, input data, [script public key], and the homomorphic commitment with a combination
/// of the homomorphic commitment private values (amount and blinding factor) and the [script private key].
script_signature: CommitmentAndPubKeySignature,
}
The script signature is a CAPK signature using a combination of the output commitment private values \( (v_i \, , \, k_i )\) and script private key \(k_{Si}\) to prove ownership thereof. It signs the script, the script input, script public key, and the commitment.
For purposes of this RFC, we denote the script signature terms as follows:
- \( R_{SCi} \) is the ephemeral commitment,
- \( R_{SPi} \) is the ephemeral public key,
- \( a_{SCi} \) and \( b_{SCi} \) are the first and second commitment signature scalars,
- \( b_{SPi} \) is the public key signature scalar.
Sender:
The script signature is given by
$$ \begin{aligned} s_{Si} = (a_{SCi}, b_{SCi}, R_{SCi}, b_{SPi}, R_{SPi} ) \end{aligned} \tag{11} $$
where
$$ \begin{aligned} R_{SCi} &= r_{SCi_a} \cdot H + r_{SCi_b} \cdot G \\ a_{SCi} &= r_{SCi_a} + e \cdot v_i \\ b_{SCi} &= r_{SCi_b} + e \cdot k_i \\ R_{SPi} &= r_{SPi_b} \cdot G \\ b_{SPi} &= r_{SPi_b} + e \cdot k_{Si} \\ \end{aligned} \tag{12} $$
with the challenge being
$$ \begin{aligned} e &= \hash{ R_{SCi} \cat R_{SPi} \cat \alpha_i \cat \input_i \cat K_{Si} \cat C_i} \\ \end{aligned} \tag{13} $$
Verifier:
This is verified by the following:
$$ \begin{aligned} a_{SCi} \cdot H + b_{SCi} \cdot G &\overset{?}{=} R_{SCi} + e \cdot C \\ b_{SPi} \cdot G &\overset{?}{=} R_{SPi} + e \cdot K_{Si} \end{aligned} \tag{14} $$
The script public key \(K_{Si}\) needed for the script signature verification is not stored with the TransactionInput, but obtained by executing the script with the provided input data. Because this signature is signed with the script private key \(k_{Si}\), it ensures that only the owner can provide the input data \(\input_i\) to the TransactionInput.
Script Offset
For every transaction, an accompanying script offset \( \so \) needs to be provided. This is there to prove that every
script public key \( K_{Sj} \) and every sender offset public key \( K_{Oi} \) supplied with the UTXOs are the
correct ones. The sender will know and provide sender offset private keys \(k_{Oi} \) and script private keys
\(k_{Si} \); these are combined to create the script offset \( \so \), which is calculated as follows:
$$ \begin{aligned} \so = \sum_j\mathrm{k_{Sj}} - \sum_i\mathrm{k_{Oi}} \; \text{for each input}, j,\, \text{and each output}, i \end{aligned} \tag{15} $$
Verification of (15) will entail:
$$ \begin{aligned} \so \cdot G = \sum_j\mathrm{K_{Sj}} - \sum_i\mathrm{K_{Oi}} \; \text{for each input}, j,\, \text{and each output}, i \end{aligned} \tag{16} $$
We modify the transactions to be:
pub struct Transaction {
...
/// A scalar offset that links outputs and inputs to prevent cut-through, enforcing the correct application of
/// the output script.
pub script_offset: BlindingFactor,
}
All script offsets (\(\so\)) from (15) contained in a block are summed together to create a total script offset (17) so that algorithm (15) still holds for a block.
$$ \begin{aligned} \so_{total} = \sum_k\mathrm{\so_{k}}\; \text{for every transaction}, k \end{aligned} \tag{17} $$
Verification of (17) will entail:
$$ \begin{aligned} \so_{total} \cdot G = \sum_j\mathrm{K_{Sj}} - \sum_i\mathrm{K_{Oi}} \; \text{for each input}, j,\, \text{and each output}, i \end{aligned} \tag{18} $$
As can be seen, all information required to verify (17) is contained in a block's inputs and outputs. One important distinction to make is that the Coinbase output in a coinbase transaction does not count toward the script offset. This is because the Coinbase UTXO already has special rules accompanying it and it has no input, thus we cannot generate a script offset \( \so \). The coinbase output can allow any script \(\script_i\) and sender offset public key \( K_{Oi} \) as long as it does not break any of the rules in RFC 120 and the script is honored at spend. If the coinbase is used as an input, it is treated exactly the same as any other input.
We modify Blockheaders to be:
pub struct BlockHeader {
...
/// Sum of script offsets for all kernels in this block.
pub total_script_offset: Scalar,
}
This notion of the script offset \(\so\) means that no third party can remove any input or output from a
transaction or the block, as that will invalidate the script offset balance equation, either (16) or (18) depending on
whether the scope is a transaction or block. It is important to know that this also stops
cut‑through so that we can verify all spent UTXO scripts. Because the script private key and
sender offset private key are not publicly known, it's impossible to create a new script offset.
Certain scripts may allow more than one valid set of input data. Users might be led to believe that this will allow a third party to change the script keypair \((k_{Si}\),\(K_{Si})\). If an attacker can change the \(K_{Si}\) keys of the input then he can take control of the \(K_{Oi}\) as well, allowing the attacker to change the metadata of the UTXO including the script. But as shown in Script offset security, this is not possible.
If equation (16) or (18) balances then we know that each included input and output in the transaction or block has its correct script public key and sender offset public key. Signatures (9) & (11) are checked independently from script offset verification (16) and (18), and looked at in isolation those could verify correctly but can still be signed by fake keys. When doing verification in (16) and (18) you know that the signatures and the message/metadata signed by the private keys can be trusted.
Consensus
TariScript does not impact the Mimblewimble balance for blocks and transactions, however, an additional consensus rule for transaction and block validation is required.
Verify that for every valid transaction or block:
- The metadata signature \( s_{Mi} \) is valid for every output.
- The script executes successfully using the given input script data.
- The result of the script is a valid script public key, \( K_S \).
- The script signature, \( s_{Si} \), is valid for every input.
- The script offset is valid for every transaction and block.
Preventing Cut-through with the Script Offset
Earlier, we described that cut-through must be prevented; this is achieved by the script offset. It mathematically links all inputs and outputs of all the transactions in a block and that tallied up creates the script offset. Providing the script offset requires knowledge of keys that miners do not possess; thus they are unable to produce the necessary script offset when attempting to perform cut-through on a pair of transactions.
Let's show by example how the script offset stops cut-through, where Alice spends to Bob who spends to Carol. Ignoring fees, we have:
$$ C_a \Rightarrow C_b \Rightarrow C_c $$
For these two transactions, the total script offset is calculated as follows:
$$ \begin{aligned} \so_1 = k_{Sa} - k_{Ob}\\ \so_2 = k_{Sb} - k_{Oc}\\ \end{aligned} \tag{19} $$
$$ \begin{aligned} \so_t = \so_1 + \so_2 = (k_{Sa} + k_{Sb}) - (k_{Ob} + k_{Oc})\\ \end{aligned} \tag{20} $$
In standard Mimblewimble cut-through can be applied to get:
$$ C_a \Rightarrow C_c $$
After cut-through the total script offset becomes:
$$ \begin{aligned} \so'_t = k_{Sa} - k_{Oc}\\ \end{aligned} \tag{21} $$
As we can see:
$$ \begin{aligned} \so_t\ \neq \so'_t \\ \end{aligned} \tag{22} $$
A third party cannot generate a new script offset as only the original owner can provide the script private key \(k_{Sa}\) to create a new script offset.
Script offset security
If all the inputs in a transaction or a block contain scripts such as just NOP
or CompareHeight
commands, then the
hypothesis is that it is possible to recreate a false script offset. Let's show by example why this is not possible. In
this Example we have Alice who pays Bob with no change output:
$$ C_a \Rightarrow C_b $$
Alice has an output \(C_{a}\) which contains a script that only has a NOP
command in it. This means that the
script \( \script_a \) will immediately exit on execution leaving the entire input data \( \input_a \)on the
stack. She sends all the required information to Bob as per the standard mw transaction, who
creates an output \(C_{b}\). Because of the NOP
script \( \script_a \), Bob can change the script public key
\( K_{Sa}\) contained in the input data. Bob can now use his own \(k'_{Sa}\) as the script private key. He
replaces the sender offset public key with his own \(K'_{Ob}\) allowing him to change the script
\( \script_b \) and generate a new signature as in (9). Bob can now generate a new script offset with
\(\so' = k'_{Sa} - k'_{Ob} \). Up to this point, it all seems valid. No one can detect that Bob changed the script
to \( \script_b \).
But what Bob also needs to do is generate the signature in (13). For this signature, Bob needs to know \(k_{Sa}, k_a, v_a\). Because Bob created a fake script private key, and there is no change in this transaction, he does know the script private key and the value. But Bob does not know the blinding factor \(k_a\) of Alice's commitment and thus cannot complete the signature in (13). Only the rightful owner of the commitment, which in Mimblewimble terms is the person who knows \( k_a, v_a\), and can generate the signature in (13).
Script lock key generation
At face value, it looks like the burden for wallets has tripled, since each UTXO owner has to remember three private keys, the spend key, \( k_i \), the sender offset key \( k_{O} \), and the script key \( k_{S} \). In practice, the script key will often be a static key associated with the user's node or wallet. Even if it is not, the script and sender offset keys can be deterministically derived from the spend key. For example, \( k_{S} \) could be \( \hash{ k_i \cat \alpha} \).
Blockchain bloat
The most obvious drawback to TariScript is the effect it has on blockchain size. UTXOs are substantially larger, with the addition of the script, metadata signature, script signature, and a public key to every output.
These can eventually be pruned but will increase storage and bandwidth requirements.
The input size of a block is much bigger than in standard Mimblewimble, whereas it would only be a commitment and output features. In Tari, each input includes a script, input_data, the script signature, and an extra public key. This could be compacted by just broadcasting input hashes along with the missing script input data and signature, instead of the full input in a transaction message, but this will still be larger than standard Mimblewimble inputs.
In Tari, every header is also bigger as it includes an extra blinding factor that cannot be pruned away.
Fodder for chain analysis
Another potential drawback of TariScript is the additional information that is handed to entities wishing to perform chain analysis. Having scripts attached to outputs will often clearly mark the purpose of that UTXO. Users may wish to re-spend outputs into vanilla, default UTXOs in a mixing transaction to disassociate Tari funds from a particular script.
Notation
Where possible, the "usual" notation is used to denote terms commonly found in cryptocurrency literature. Lowercase characters are used as private keys, while uppercase characters are used as public keys. New terms introduced by TariScript are assigned Greek lowercase letters in most cases.
Symbol | Definition |
---|---|
\( \script_i \) | An output script for output i, serialised to binary. |
\( F_i \) | Output features for UTXO i. |
\( f_t \) | Transaction fee for transaction t. |
\( (k_{Oi}, K_{Oi}) \) | The private - public keypair for the UTXO sender offset key. Note that \( k_{Oi} \) should be treated as a nonce. |
\( (k_{Si}, K_{Si}) \) | The private - public keypair for the script key. The script, \( \script_i \) resolves to \( K_S \) after completing execution. |
\( \so_t \) | The script offset for transaction t, see (15) |
\( C_i \) | A Pedersen commitment to a value \( v_i \), see (4) |
\( \input_i \) | The serialised input for script \( \script_i \) |
\( \pi_i \) | The covenant for UTXO i. |
\( \varphi_i \) | The encrypted value for UTXO i. |
\( \vartheta_i \) | The minimum value promise for UTXO i. |
\( s_{Si} \) | A script signature for output \( i \), see (11 - 13). Additionally, the capital letter subscripts, C and P refer to the ephemeral commitment and ephemeral public key portions respectively (example \( s_{SCi}, s_{SPi} \)) . |
\( s_{Mi} \) | A metadata signature for output \( i \), see (3 - 10). Additional the capital letter subscripts, R and S refer to a UTXO receiver and sender respectively (exmple \( s_{MRi}, s_{MSi} \)) . |
Credits
Thanks to David Burkett for proposing a method to prevent cut-through and willingness to discuss ideas.
Change log
Date | Change | Author |
---|---|---|
17 Aug 2020 | First draft | CjS77 |
11 Feb 2021 | Major update | CjS77, SWvheerden, philipr-za |
26 Apr 2021 | Clarify one sided payment rules | SWvheerden |
31 May 2021 | Including full script in transaction outputs | philipr-za |
04 Jun 2021 | Remove beta range-proof calculation | SWvheerden |
22 Jun 2021 | Change script_signature type to ComSig | hansieodendaal |
30 Jun 2021 | Clarify Tari Script nomenclature | hansieodendaal |
06 Oct 2022 | Minor improvements in legibility | stringhandler |
11 Nov 2022 | Update ComAndPubSig and move out examples | stringhandler |
22 Nov 2022 | Added metadata_signature and script_signature math | hansieodendaal |
06 Apr 2023 | Grammar and spelling changes | SWvheerden |
RFC-0202/TariScriptOpcodes
TariScript Opcodes
Maintainer(s): Cayle Sharrock
Licence
Copyright 2020 The Tari Development Community
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Language
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as shown here.
Disclaimer
This document and its content are intended for information purposes only and may be subject to change or update without notice.
This document may include preliminary concepts that may or may not be in the process of being developed by the Tari community. The release of this document is intended solely for review and discussion by the community of the technological merits of the potential system outlined herein.
Goals
This Request for Comment (RFC) defines the opcodes that make up the TariScript scripting language and provides some examples and applicaitons.
Related Requests for Comment
Introduction
TariScript semantics
The proposal for TariScript is straightforward. It is based on Bitcoin script and inherits most of its ideas.
The main properties of TariScript are
- The scripting language is stack-based. At redeem time, the UTXO spender must supply an input stack. The script runs by operating on the stack contents.
- If an error occurs during execution, the script fails.
- After the script completes, it is successful if and only if it has not aborted, and there is exactly a single element on the stack. The script fails if the stack is empty, or contains more than one element, or aborts early.
- It is not Turing complete, so there are no loops or timing functions.
- The opcodes enforce type safety. e.g. A public key cannot be added to an integer scalar. Errors of this kind MUST cause the script to fail. The Rust implementation of TariScript automatically applies the type safety rules.
Failure modes
Bitcoin transactions can "fail" in two main ways: Either there is a genuine error in the locking or unlocking script; or a wallet broadcasts a Non-standard transaction to a non-mining node. To be more precise, Bitcoin core nodes only accept a small subset of valid transaction scripts that are deemed Standard transactions. To succesfully produce a non-standard Bitcoin transaction, one has to submit it directly to a miner that accepts non-standard transactions.
It's interesting to note that only 0.02% of transactions mined in Bitcoin before block 550,000 were non-standard, and it appears that the vast majority of these were in fact unintentional, leading to loss of funds (see Non-standard transaction).
The present RFC proposes that TariScript not identify a subset of transactions as "standard". However, some transactions might be invalid in and of- themselves (e.g. invalid signature, invalid script), while others may be invalid because of the execution context (e.g. a lock time has not expired).
All Tari nodes MUST reject the former type of invalid transaction; and SHOULD reject the latter. In these instances, it is the wallets' responsibility to wait until transactions are valid before broadcasting them.
Rejected transactions are simply silently dropped.
This policy discourages spamming on the network and promotes responsible behaviour by wallets.
The full list of Error codes is given below.
Constraints
- The maximum length of a script when serialised is 1,024 bytes.
- The maximum length of a script's input is 1,024 bytes.
- The maximum stack height is 255.
Opcode versions
Base layer core consensus constants are linked to block height for each network, be it testnet, stagenet or mainnet, and are backwards compatible, meaning a base node running updated consensus constants will also be able to validate the blockchain for the previous version up to its last effective block height.
Opcode versioning is contained within the consensus constants and is used to determine which opcodes are effective
from which block height. As an example, OpcodeVersion::V0
could be effective from the genesis block,
OpcodeVersion::V1
may contain two additional opcodes and could be effective from height 1234, whereas
OpcodeVersion::V2
may deprecate three other opcodes and be effective from height 21743.
Opcodes
TariScript opcodes range from 0 to 255 and are represented as a single unsigned byte. The opcode set is limited to allow for the applications specified in this RFC, but can be expanded in the future.
Block height checks
All these opcodes test the current block height (or, if running the script as part of a transaction validation, the next earliest block height) against a given value.
CheckHeightVerify(height)
Pops the top of the stack as height
. Compare the current block height to height
.
- Fails with
IncompatibleTypes
if u64 is not a valid 64-bit unsigned integer. - Fails with
VerifyFailed
if the block height <height
.
CheckHeight(height)
Pops the top of the stack as height
. Pushes the value of (the current tip height - height
) to the stack. In
other words, the top of the stack will hold the height difference between height
and the current height.
If the chain has progressed beyond height
, the value is positive; and negative if the chain has yet to
reach height
.
- Fails with
IncompatibleTypes
if u64 is not a valid 64-bit unsigned integer. - Fails with
StackOverflow
if the stack would exceed the max stack height.
CompareHeightVerify
Pops the top of the stack as height
and compares it to the current block height.
- Fails with
InvalidInput
if there is not a valid integer value on top of the stack. - Fails with
StackUnderflow
if the stack is empty. - Fails with
VerifyFailed
if the block height <height
.
CompareHeight
Pops the top of the stack as height
, then pushes the value of (height
- the current height) to the stack.
In other words, this opcode replaces the top of the stack with the difference between height
and the
current height.
- Fails with
InvalidInput
if there is not a valid integer value on top of the stack. - Fails with
StackUnderflow
if the stack is empty.
Stack manipulation
NoOp
No op. Does nothing. Never fails.
PushZero
Pushes a zero onto the stack. This is a very common opcode and has the same effect as PushInt(0)
but is more
compact. PushZero
can also be interpreted as PushFalse
, although no such opcode exists.
- Fails with
StackOverflow
if the stack would exceed the max stack height.
PushOne
Pushes a one onto the stack. This is a very common opcode and has the same effect as PushInt(1)
but is more
compact. PushOne
can also be interpreted as PushTrue
, although no such opcode exists.
- Fails with
StackOverflow
if the stack would exceed the max stack height.
PushHash(HashValue)
Pushes the associated 32-byte value onto the stack.
- Fails with
IncompatibleTypes
if HashValue is not a valid 32 byte sequence. - Fails with
StackOverflow
if the stack would exceed the max stack height.
PushInt(val)
Pushes the associated 64-bit signed integer (val
) onto the stack.
- Fails with
IncompatibleTypes
ifval
is not a valid 64-bit signed integer. - Fails with
StackOverflow
if the stack would exceed the max stack height.
PushPubKey(PublicKey)
Pushes the associated 32-byte value onto the stack. It will be interpreted as a public key or a commitment.
- Fails with
IncompatibleTypes
if PublicKey is not a valid 32 byte RistrettoPublicKey sequence. - Fails with
StackOverflow
if the stack would exceed the max stack height.
Drop
Drops the top stack item.
- Fails with
StackUnderflow
if the stack is empty.
Dup
Duplicates the top stack item.
- Fails with
StackUnderflow
if the stack is empty. - Fails with
StackOverflow
if the stack would exceed the max stack height.
RevRot
Reverse rotation. The top stack item moves into 3rd place, e.g. abc => bca.
- Fails with
StackUnderflow
if the stack has fewer than three items.
Math operations
GeZero
Pops the top stack element as val
. If val
is greater than or equal to zero, push a 1 to the stack,
otherwise push 0.
- Fails with
StackUnderflow
if the stack is empty. - Fails with
InvalidInput
ifval
is not an integer.
GtZero
Pops the top stack element as val
. If val
is strictly greater than zero, push a 1 to the stack, otherwise push 0.
- Fails with
StackUnderflow
if the stack is empty. - Fails with
InvalidInput
if the item is not an integer.
LeZero
Pops the top stack element as val
. If val
is less than or equal to zero, push a 1 to the stack, otherwise push 0.
- Fails with
StackUnderflow
if the stack is empty. - Fails with
InvalidInput
if the item is not an integer.
LtZero
Pops the top stack element as val
. If val
is strictly less than zero, push a 1 to the stack, otherwise push 0.
- Fails with
StackUnderflow
if the stack is empty. - Fails with
InvalidInput
if the items is not an integer.
Add
Pops two items from the stack and pushes their sum to the stack.
- Fails with
StackUnderflow
if the stack has fewer than two items. - Fails with
InvalidInput
if the items cannot be added to each other (e.g. an integer and public key).
Sub
Pops two items from the stack and pushes the second minus the top to the stack.
- Fails with
StackUnderflow
if the stack has fewer than two items. - Fails with
InvalidInput
if the items cannot be subtracted from each other (e.g. an integer and public key).
Equal
Pops the top two items from the stack, and pushes 1 to the stack if the inputs are exactly equal, 0 otherwise. A 0 is also pushed if the values cannot be compared (e.g. integer and pubkey).
- Fails with
StackUnderflow
if the stack has fewer than two items.
EqualVerify
Pops the top two items from the stack, and compares their values.
- Fails with
StackUnderflow
if the stack has fewer than two items. - Fails with
VerifyFailed
if the top two stack elements are not equal.
Boolean logic
Or(n)
Pops n
+ 1 items from the stack. If the last item matches at least one of the first n
items, push 1 onto the stack, otherwise push 0 onto the stack.
- Fails with
StackUnderflow
if the stack has fewer thann
+ 1 items. - Fails with
InvalidInput
ifn
is not a valid 8-bit unsigned integer.
OrVerify(n)
Pops n
+ 1 items from the stack. If the last item matches at least one of the first n items,
continue.
- Fails with
StackUnderflow
if the stack has fewer thann
+ 1 items. - Fails with
VerifyFailed
the last item does not match at least one of the firstn
items. - Fails with
InvalidInput
ifn
is not a valid 8-bit unsigned integer.
Cryptographic operations
HashBlake256
Pops the top element, hash it with the Blake256 hash function and push the result to the stack.
- Fails with
StackUnderflow
if the stack is empty. - Fails with
InvalidInput
if the input is not a valid 32 byte hash value.
HashSha256
Pops the top element, hash it with the SHA256 hash function and push the result to the stack.
- Fails with
StackUnderflow
if the stack is empty. - Fails with
InvalidInput
if the input is not a valid 32 byte hash value.
HashSha3
Pops the top element, hash it with the SHA-3 hash function and push the result to the stack.
- Fails with
StackUnderflow
if the stack is empty. - Fails with
InvalidInput
if the input is not a valid 32 byte hash value.
CheckSig(Message)
Pops the public key and then the signature from the stack. If signature validation using the 32-byte message and public key succeeds , push 1 to the stack, otherwise push 0.
- Fails with
IncompatibleTypes
if Message is not a valid 32-byte sequence. - Fails with
StackUnderflow
if the stack has fewer than 2 items. - Fails with
InvalidInput
if the top stack element is not a PublicKey. - Fails with
InvalidInput
if the second stack element is not a Signature.
CheckSigVerify(Message)
Identical to CheckSig
, except that nothing is pushed to the stack if the signature is valid.
In addition to the failures mentioned:
- Fails with
VerifyFailed
if the signature is invalid.
CheckMultiSig(m, n, Vec, Message)
Pops exactly m
signatures from the stack. The multiple signature validation will not succeed if the m
signatures are not unique or if Vec
- Fails with
IncompatibleTypes
if eitherm
orn
is not a valid 8-bit unsigned integer, if Veccontains an invalid public key or if Message is not a valid 32-byte sequence. - Fails with
ValueExceedsBounds
ifm
== 0 or ifn
== 0 or ifm
>n
or ifn
>MAX_MULTISIG_LIMIT
(32) or if the number of public keys provided !=n
. - Fails with
StackUnderflow
if the stack has fewer than m items. - Fails with
IncompatibleTypes
if any of the m signatures from the stack is not a valid signature. - Fails with
InvalidInput
if each of the top m elements is not a Signature.
CheckMultiSigVerify(m, n, Vec, Message)
Identical to CheckMultiSig
, except that nothing is pushed to the stack if the
multiple signature validation is either valid or invalid.
In addition to the failures mentioned:
- Fails with
VerifyFailed
if any signature is invalid.
CheckMultiSigVerifyAggregatePubKey(m, n, public keys, Msg)
Identical to CheckMultiSig
, except that the aggregate of the public keys is
pushed to the stack if multiple signature validation succeeds.
In addition to the failures mentioned:
- Fails with
VerifyFailed
if any signature is invalid.
ToRistrettoPoint
Pops the top element from the stack (either a scalar or a hash), parses it canonically as a Ristretto secret key if possible, computes the corresponding Ristretto public key, and pushes this value to the stack.
- Fails with
StackUnderflow
if the stack is empty. - Fails with
IncompatibleTypes
if the stack item is not either a scalar or a hash. - Fails with
InvalidInput
if the stack item cannot be canonically parsed as a Ristretto secret key.
Miscellaneous
Return
This opcode does nothing except that it always fails.
- Fails with
Return
.
If-then-else
Pops the top element of the stack into pred
. If pred
is 1, the instructions between IfThen
and Else
are
executed. If pred
is 0, instructions are popped until Else
or EndIf
is encountered. If Else
is
encountered, instructions are executed until EndIf
is reached. EndIf
is a marker opcode and a no-op.
- Fails with
StackUnderflow
if the stack is empty. - Fails with
InvalidInput
if pred is anything other than 0 or 1. - Fails with the corresponding failure code if any instruction during execution of the clause causes a failure.
Else
Marks the beginning of the Else
branch.
EndIf
Marks the end of the IfThen
statement.
Serialisation
TariScript and the execution stack are serialised into byte strings using a simple linear parser. Since all opcodes are
a single byte, it's very easy to read and write script byte strings. If an opcode has a parameter associated with it,
e.g. PushHash
then it is equally known how many bytes following the opcode will contain the parameter.
The script input data is serialised in an analogous manner. The first byte in a stream indicates the type of data in the bytes that follow. The length of each type is fixed and known a priori. The next n bytes read represent the data type.
As input data elements are read in, they are pushed onto the stack. This means that the last input element will typically be operated on first!
The types of input parameters that are accepted are:
Type | Range / Value |
---|---|
Number | 64-bit signed integer |
Hash | 32-byte hash value |
Scalar | 32-byte scalar value |
Commitment | 32-byte homomorphic commitment (Pedersen commitment ) |
PublicKey | 32-byte Ristretto public key |
Signature | 64-byte Ristretto Schnorr signature (32-byte nonce + 32-byte signature) |
Example scripts
Anyone can spend
The simplest script is an empty script, or a script with a single NoOp
opcode. When faced with this script, the
spender can supply any pubkey in her script input for which she knows the private key. The script will execute, leaving
that public key as the result, and the transaction script validation will pass.
One-sided transactions
One-sided transactions lock the input to a predetermined public key provided by the recipient; essentially the same method that Bitcoin uses. The simplest form of this is to simply post the new owner's public key as the script:
PushPubkey(P_B)
To spend this output, Bob provides an empty input stack. After execution, the stack contains his public key.
An equivalent script to Bitcoin's P2PKH would be:
Dup HashBlake256 PushHash(PKH) EqualVerify
To spend this, Bob provides his public key as script input. To illustrate the execution process, we show the script running on the left, and resulting stack on the right:
Initial script | Initial Stack |
---|---|
Dup | Bob's Pubkey |
HashBlake256 | |
PushHash(PKH) | |
EqualVerify |
Copy Bob's pubkey:
Dup | |
---|---|
HashBlake256 | Bob's Pubkey |
PushHash(PKH) | Bob's Pubkey |
EqualVerify |
Hash the public key:
HashBlake256 | |
---|---|
PushHash(PKH) | H(Bob's Pubkey) |
EqualVerify | Bob's Pubkey |
Push the expected hash to the stack:
PushHash(PKH) | |
---|---|
EqualVerify | PKH |
H(Bob's Pubkey) | |
Bob's Pubkey |
Is PKH
equal to the hash of Bob's public key?
EqualVerify | |
---|---|
Bob's Pubkey |
The script has completed without errors, and Bob's public key remains on the stack.
Multiparty Time-locked contract
Alice sends some Tari to Bob. If he doesn't spend it within a certain timeframe (up till block 4000), then she is also able to spend it back to herself.
The spender provides their public key as input to the script.
Dup PushPubkey(P_b) CheckHeight(4000) GeZero IFTHEN PushPubkey(P_a) OrVerify(2) ELSE EqualVerify ENDIF
Let's run through this script assuming it's block 3990 and Bob is spending the UTXO. We'll only print the stack this time:
Initial Stack |
---|
Bob's pubkey |
Dup
:
Stack |
---|
Bob's pubkey |
Bob's pubkey |
PushPubkey(P_b)
:
Stack |
---|
P_b |
Bob's pubkey |
Bob's pubkey |
CheckHeight(4000)
. The block height is 3990, so 3990 - 4000
is pushed to the stack:
Stack |
---|
-10 |
P_b |
Bob's pubkey |
Bob's pubkey |
GeZero
pushes a 1 if the top stack element is positive or zero:
Stack |
---|
0 |
P_b |
Bob's pubkey |
Bob's pubkey |
IFTHEN
compares the top of the stack to 1. It is not a match, so it will execute the ELSE
branch:
Stack |
---|
P_b |
Bob's pubkey |
Bob's pubkey |
EqualVerify
checks that P_b
is equal to Bob's pubkey:
Stack |
---|
Bob's pubkey |
The ENDIF
is a no-op, so the stack contains Bob's public key, meaning Bob must sign to spend this transaction.
Similarly, if it is after block 4000, say block 4005, and Alice or Bob tries to spend the UTXO, the sequence is:
Initial Stack |
---|
Alice or Bob's pubkey |
Dup
and PushPubkey(P_b)
as before:
Stack |
---|
P_b |
Alice or Bob's pubkey |
Alice or Bob's pubkey |
CheckHeight(4000)
calculates 4005 - 4000)
and pushes 5 to the stack:
Stack |
---|
5 |
P_b |
Alice or Bob's pubkey |
Alice or Bob's pubkey |
GeZero
pops the 5 and pushes a 1 to the stack:
Stack |
---|
1 |
P_b |
Alice or Bob's pubkey |
Alice or Bob's pubkey |
The top of the stack is 1, so IFTHEN
executes the first branch, PushPubkey(P_a)
:
Stack |
---|
P_a |
P_b |
Alice or Bob's pubkey |
Alice or Bob's pubkey |
OrVerify(2)
compares the 3rd element, Alice's pubkey, with the 2 top items that were popped. There is a match, so the
script continues.
Stack |
---|
Alice or Bob's pubkey |
If the script executes successfully, then either Alice's or Bob's public key is left on the stack, meaning only Alice or Bob can spend the output.
Error codes
Code | Description |
---|---|
Return | The script failed with an explicit Return |
StackOverflow | The stack exceeded 255 elements during script execution |
NonUnitLengthStack | The script completed execution with a stack size other than one |
StackUnderflow | Tried to pop an element off an empty stack |
IncompatibleTypes | An operand was applied to incompatible types |
ValueExceedsBounds | A script opcode resulted in a value that exceeded the maximum or minimum value |
InvalidOpcode | The script encountered an invalid opcode |
MissingOpcode | The script is missing closing opcodes (Else or EndIf) |
InvalidSignature | The script contained an invalid signature |
InvalidInput | The serialised stack contained invalid input |
InvalidData | The script contained invalid data |
VerifyFailed | A verification opcode failed, aborting the script immediately |
InvalidDigest | as_hash requires a Digest function that returns at least 32 bytes |
Credits
Thanks to @philipr-za and @SWvheerden for their input and contributions to this RFC.
Change Log
Date | Change | Author |
---|---|---|
17 Aug 2020 | First draft | CjS77 |
11 Feb 2021 | Tari script proposal v3 | CjS77 |
16 Feb 2021 | Update TariScript OpCodes | CjS77 |
08 Mar 2021 | Update RFC docs | delta1 |
12 Nov 2021 | Add CheckMultiSig/Verify | delta1 |
30 Jun 2021 | Add missing OP_CHECKMULTISIG/VERIFY and update error codes | sdbondi |
11 Jan 2022 | Add ToRistrettoPoint opcode to TariScript | SWvheerden |
27 Sep 2022 | Add aggregate signatures to transaction inputs and outputs | hansieodendaal |
28 Sep 2022 | Minor update to reflect implementation | sdbondi |
11 Nov 2022 | Update for code review/audit | hansieodendaal |
20 Nov 2023 | Update ToRistrettoPoint documentation | AaronFeickert |
RFC-0204/TariScriptExamples
$$ \newcommand{\script}{\alpha} % utxo script \newcommand{\input}{ \theta } \newcommand{\cat}{\Vert} \newcommand{\so}{\gamma} % script offset \newcommand{\hash}[1]{\mathrm{H}\bigl({#1}\bigr)} $$
TariScript Examples
Maintainer(s): Cayle Sharrock
Licence
Copyright 2020 The Tari Development Community
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Language
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as shown here.
Disclaimer
This document and its content are intended for information purposes only and may be subject to change or update without notice.
This document may include preliminary concepts that may or may not be in the process of being developed by the Tari community. The release of this document is intended solely for review and discussion by the community of the technological merits of the potential system outlined herein.
TariScript Examples
Standard MW transaction
For this use case we have Alice who sends Bob some Tari. Bob's wallet is online and is able to countersign the transaction.
Alice creates a new transaction spending \( C_a \) to a new output containing the commitment \( C_b \) (ignoring fees for now).
To spend \( C_a \), she provides:
- An input that contains \( C_a \).
- The script input, \( \input_a \).
- A valid script signature, \( s_{Si} \) as per (13),(14) proving that she owns the commitment \( C_a \), knows the private key, \( k_{Sa} \), corresponding to \( K_{Sa} \), the public key left on the stack after executing \( \script_a \) with \( \input_a \).
- A sender offset public key, \( K_{Ob} \).
- The sender portion of the public nonce, \( R_{MSi} )\, as per (10).
- The script offset, \( \so\) with: $$ \begin{aligned} \so = k_{Sa} - k_{Ob} \end{aligned} \tag{20} $$
Alice sends the usual first round data to Bob, but now because of TariScript also includes \( K_{Ob} \) and
\( R_{MSi} \). Bob can then complete his side of the transaction as per the [standard Mimblewimble protocol]
providing the commitment \(C_b\), its public blinding factor, its rangeproof and the partial transaction signature.
In addition, Bob also needs to provide a partial metadata signature as per (5) where he commits to all the transaction
output metadata with a commitment signature. Because Alice is creating the transaction, she can suggest the script
\( \script_b \) to use for Bob's output, similar to a [bitcoin transaction], but Bob can choose a different script
\(\script_b\). However, in most cases the parties will agree on using something akin to a NOP
script
\(\script_b\). Bob has to return this consistent set of information back to Alice.
Alice verifies the information received back from Bob, check if she agrees with the script \( \script_b \) Bob signed, and calculates her portion of the metadata signature \( s_{Mb} \) with:
$$ \begin{aligned} s_{Mb} = r_{mb} + k_{Ob} \hash{ \script_b \cat F_b \cat R_{Mb} } \end{aligned} \tag{21} $$
Alice then constructs the final aggregated metadata signature \(s_{Mb}\) as per (12) and replaces Bob's partial metadata signature in Bob's TransactionOutput.
She completes the transaction as per [standard Mimblewimble protocol] and also adds the script offset \( \so \), after which she sends the final transaction to Bob and broadcasts it to the network.
Transaction validation
Base nodes validate the transaction as follows:
- They check that the usual Mimblewimble balance holds by summing inputs and outputs and validating against the excess signature. This check does not change nor do the other validation rules, such as confirming that all inputs are in the UTXO set etc.
- The metadata signature \(s_{Ma}\) on Bob's output,
- The input script must execute successfully using the provided input data; and the script result must be a valid public key,
- The script signature on Alice's input is valid by checking:
$$ \begin{aligned} a_{Sa} \cdot H + b_{Sa} \cdot G = R_{Sa} + (C_a + K_{Sa})* \hash{ R_{Sa} \cat \alpha_a \cat \input_a \cat K_{Sa} \cat C_a} \end{aligned} \tag{22} $$
- The script offset is verified by checking that the balance holds:
$$ \begin{aligned} \so \cdot{G} = K_{Sa} - K_{Ob} \end{aligned} \tag{23} $$
Finally, when Bob spends this output, he will use \( K_{Sb} \) as his script input and sign it with his script private key \( k_{Sb} \). He will choose a new sender offset public key \( K_{Oc} \) to give to the recipient, and he will construct the script offset, \( \so_b \) as follows:
$$ \begin{aligned} \so_b = k_{Sb} - k_{Oc} \end{aligned} \tag{24} $$
One sided payment
In this example, Alice pays Bob, who is not available to countersign the transaction, so Alice initiates a one-sided payment,
$$ C_a \Rightarrow C_b $$
Once again, transaction fees are ignored to simplify the illustration.
Alice owns \( C_a \) and provides the required script to spend the UTXO as was described in the previous cases.
Alice needs a public key from Bob, \( K_{Sb} \) to complete the one-sided transaction. This key can be obtained out-of-band, and might typically be Bob's wallet public key on the Tari network.
Bob requires the value \( v_b \) and blinding factor \( k_b \) to claim his payment, but he needs to be able to claim it without asking Alice for them.
This information can be obtained by using Diffie-Hellman and Bulletproof rewinding. If the blinding factor \( k_b \) was calculated with Diffie-Hellman using the sender offset keypair, (\( k_{Ob} \),\( K_{Ob} \)) as the sender keypair and the script keypair, \( (k_{Sb} \),\( K_{Sb}) \) as the receiver keypair, the blinding factor \( k_b \) can be securely calculated without communication.
Alice uses Bob's public key to create a shared secret, \( k_b \) for the output commitment, \( C_b \), using Diffie-Hellman key exchange.
Alice calculates \( k_b \) as
$$ \begin{aligned} k_b = k_{Ob} * K_{Sb} \end{aligned} \tag{25} $$
Next Alice uses Bulletproof rewinding, see RFC 180, to encrypt the value \( v_b \) into the the Bulletproof for the commitment \( C_b \). For this she uses \( k_{rewind} = \hash{k_{b}} \) as the rewind_key and \( k_{blinding} = \hash{\hash{k_{b}}} \) as the blinding key.
Alice knows the script-redeeming private key \( k_{Sa}\) for the transaction input.
Alice will create the entire transaction, including generating a new sender offset keypair and calculating the script offset,
$$ \begin{aligned} \so = k_{Sa} - k_{Ob} \end{aligned} \tag{26} $$
She also provides a script that locks the output to Bob's public key, PushPubkey(K_Sb)
.
This will only be spendable if the spender can provide a valid signature as input that demonstrates proof
of knowledge of \( k_{Sb}\) as well as the value and blinding factor of the output \(C_b\). Although Alice knowns
the value and blinding factor of the output \(C_b\) only Bob knows \( k_{Sb}\).
Any base node can now verify that the transaction is complete, verify the signature on the script, and verify the script offset.
For Bob to claim his commitment he will scan the blockchain for a known script because he knowns that the script will
be PushPubkey(K_Sb)
. In this case, the script is analogous to an address in Bitcoin or Monero. Bob's wallet can scan
the blockchain looking for scripts that he would know how to resolve.
When Bob's wallet spots a known script, he requires the blinding factor, \( k_b \) and the value \( v_b \). First he uses Diffie-Hellman to calculate \( k_b \).
Bob calculates \( k_b \) as
$$ \begin{aligned} k_b = K_{Ob} * k_{Sb} \end{aligned} \tag{27} $$
Next Bob's wallet calculates \( k_{rewind} \), using \( k_{rewind} = \hash{k_{b}}\) and (\( k_{blinding} = \hash{\hash{k_{b}}} \), using those to rewind the Bulletproof to get the value \( v_b \).
Because Bob's wallet already knowns the script private key \( k_{Sb} \), he now knows all the values required to spend the commitment \( C_b \)
For Bob's part, when he discovers one-sided payments to himself, he should spend them to new outputs using a traditional transaction to thwart any potential horizon attacks in the future.
To summarise, the information required for one-sided transactions are as follows:
Transaction input | Symbols | Knowledge |
---|---|---|
commitment | \( C_a = k_a \cdot G + v \cdot H \) | Alice knows the blinding factor and value. |
features | \( F_a \) | Public |
script | \( \alpha_a \) | Public |
script input | \( \input_a \) | Public |
script signature | \( s_{Sa} \) | Alice knows \( k_{Sa},\, r_{Sa} \) and \( k_{a},\, v_{a} \) of the commitment \(C_a\). |
sender offset public key | \( K_{Oa} \) | Not used in this transaction. |
Transaction output | Symbols | Knowledge |
---|---|---|
commitment | \( C_b = k_b \cdot G + v \cdot H \) | Alice and Bob know the blinding factor and value. |
features | \( F_b \) | Public |
script | \( \script_b \) | Script is public; only Bob knows the correct script input. |
range proof | Alice and Bob know opening parameters. | |
sender offset public key | \( K_{Ob} \) | Alice knows \( k_{Ob} \). |
metadata signature | \( s_{Mb} \) | Alice knows \( k_{Ob} \), \( (k_{b},\, v) \) and the metadata. |
HTLC-like script
In this use case we have a script that controls where it can be spent. The script is out of scope for this example, but has the following rules:
- Alice can spend the UTXO unilaterally after block n, or
- Alice and Bob can spend it together.
This would be typically what a lightning-type channel requires.
Alice owns the commitment \( C_a \). She and Bob work together to create \( C_s\). But we don't yet know who can spend the newly created \( C_s\) and under what conditions this will be.
$$ C_a \Rightarrow C_s \Rightarrow C_x $$
Alice owns \( C_a\), so she knows the blinding factor \( k_a\) and the correct input for the script's spending conditions. Alice also generates the sender offset keypair, \( (k_{Os}, K_{Os} )\).
Now Alice and Bob proceed with the standard transaction flow.
Alice ensures that the sender offset public key \( K_{Os}\) is part of the output metadata that contains commitment \( C_s\). Alice will fill in the script with her \( k_{Sa}\) to unlock the commitment \( C_a\). Because Alice owns \( C_a\) she needs to construct \( \so\) with:
$$ \begin{aligned} \so = k_{Sa} - k_{Os} \end{aligned} \tag{28} $$
The blinding factor, \( k_s\) can be generated using a Diffie-Hellman construction. The commitment \( C_s\) needs to be constructed with the script that Bob agrees on. Until it is mined, Alice could modify the script via double-spend and thus Bob must wait until the transaction is confirmed before accepting the conditions of the smart contract between Alice and himself.
Once the UTXO is mined, both Alice and Bob possess all the knowledge required to spend the \( C_s \) UTXO. It's only the conditions of the script that will discriminate between the two.
The spending case of either Alice or Bob claiming the commitment \( C_s\) follows the same flow described in the previous examples, with the sender proving knowledge of \( k_{Ss}\) and "unlocking" the spending script.
The case of Alice and Bob spending \( C_s \) together to a new multiparty commitment requires some elaboration.
Assume that Alice and Bob want to spend \( C_s \) co-operatively. This involves the script being executed in such a way that the resulting public key on the stack is the sum of Alice and Bob's individual script keys, \( k_{SsA} \) and \( k_{SaB} \).
The script input needs to be signed by this aggregate key, and so Alice and Bob must each supply a partial signature following the usual Schnorr aggregate mechanics, but one person needs to add in the signature of the blinding factor and value.
In an analogous fashion, Alice and Bob also generate an aggregate sender offset private key \( k_{Ox}\), each using their own \( k_{OxA} \) and \( k_{OxB}\).
To be specific, Alice calculates her portion from
$$ \begin{aligned} \so_A = k_{SsA} - k_{OxA} \end{aligned} \tag{29} $$
Bob will construct his part of the \( \so\) with:
$$ \begin{aligned} \so_B = k_{SsB} - k_{OxB} \end{aligned} \tag{30} $$
And the aggregate \( \so\) is then:
$$ \begin{aligned} \so = \so_A + \so_B \end{aligned} \tag{31} $$
Notice that in this case, both \( K_{Ss} \) and \( K_{Ox}\) are aggregate keys.
Notice also that because the script resolves to an aggregate key \( K_s\) neither Alice nor Bob can claim the commitment \( C_s\) without the other party's key. If either party tries to cheat by editing the input, the script validation will fail.
If either party tries to cheat by creating a new output, the script offset will not validate correctly as it locks the output of the transaction.
A base node validating the transaction will also not be able to tell this is an aggregate transaction as all keys are aggregated Schnorr signatures. But it will be able to validate that the script input is correctly signed, thus the output public key is correct and that the \( \so\) is correctly calculated, meaning that the commitment \( C_x\) is the correct UTXO for the transaction.
To summarise, the information required for creating a multiparty UTXO is as follows:
Transaction input | Symbols | Knowledge |
---|---|---|
commitment | \( C_a = k_a \cdot G + v \cdot H \) | Alice knows the blinding factor and value. |
features | \( F_a \) | Public |
script | \( \alpha_a \) | Public |
script input | \( \input_a \) | Public |
script signature | \( s_{Sa} \) | Alice knows \( k_{Sa},\, r_{Sa} \) and \( k_{a},\, v_{a} \) of the commitment \(C_a\). |
sender offset public key | \( K_{Oa} \) | Not used in this transaction. |
Transaction output | Symbols | Knowledge |
---|---|---|
commitment | \( C_s = k_s \cdot G + v \cdot H \) | Alice and Bob know the blinding factor and value. |
features | \( F_s \) | Public |
script | \( \script_s \) | Script is public; Alice and Bob only knows their part of the correct script input. |
range proof | Alice and Bob know opening parameters. | |
sender offset public key | \( K_{Os} = K_{OsA} + K_{OsB}\) | Alice knows \( k_{OsA} \), Bob knows \( k_{OsB} \), neither party knows \( k_{Os} \). |
metadata signature | \( (a_{Ms} , b_{Ms} , R_{Ms}) \) | Alice knows \( k_{OsA} \), Bob knows \( k_{OsB} \), both parties know \( (k_{s},\, v) \). Neither party knows \( k_{Os}\). |
When spending the multi-party input:
Transaction input | Symbols | Knowledge |
---|---|---|
commitment | \( C_s = k_s \cdot G + v_s \cdot H \) | Alice and Bob know the blinding factor and value. |
features | \( F_s \) | Public |
script | \( \alpha_s \) | Public |
script input | \( \input_s \) | Public |
script signature | \( (a_{Ss} ,b_{Ss} , R_{Ss}) \) | Alice knows \( (k_{SsA},\, r_{SsA}) \), Bob knows \( (k_{SsB},\, r_{SsB}) \), both parties know \( (k_{s},\, v_{s}) \), neither party knows \( k_{Ss}\). |
sender offset public key | \( K_{Os} \) | As above, Alice and Bob each know part of the sender offset key. |
Multi-party considerations
Multi-party in this context refers to n-of-n
parties creating a single combined transaction output and m-of-n
parties spending a single combined transaction input. We have some options to do this:
- using the m-of-n script TariScript without sharding the spending key;
- combination of the m-of-n script TariScript and sharding the spending key;
- sharding the spending key combined with the NoOp script TariScript;
If the spending key \( k_i \) is sharded for any of these options the commitment definition changes to:
$$ \begin{aligned} C_i = v_i \cdot H + \sum_\psi (k_{i_\psi} \cdot G) \; \; \; \text{ for each receiver party } \psi \end{aligned} \tag{1b} $$
The sender-receiver interaction can be categorized as follows, however, for simplicity we can assume that each multi-party side will have a single party acting as the dealer:
- Multi-party senders can create the single output and send it to a single receiver.
- A single sender can create the single output and send it to multi-party receivers.
- Multi-party senders can create the single output and send it to multi-party receivers.
The multi-party impact on the transaction output, transaction input and script offset is discussed below.
Multi-party transaction output
If multiple senders and receiver parties need to create an aggregate metadata_signature
for a single multi-party
transaction output, there are two secrets that warrant our attention; the script offset private key \( k_{Oi} \)
controlled by the senders and the spending key \( k_i \) controlled by the receivers. Depending on the protocol
design, one or both secrets may be sharded amongst all parties.
Sharding the script offset private key:
If the script offset private key \( k_{Oi} \) is sharded, the aggregate sender terms in (10) and (11) collected by the sender's dealer change to:
$$ \begin{aligned} R_{MSi} &= \sum_\omega (r_{{MSi_b}_\omega} \cdot G) \; \; \; \text{ for each sender party } \omega \end{aligned} \tag{10b} $$
$$ \begin{aligned} a_{MSi} &= 0 \\ b_{MSi} &= \sum_\omega (r_{{MSi_b}_\omega} + e \cdot k_{{Oi}_\omega}) \; \; \; \text{ for each sender party } \omega \end{aligned} \tag{11b} $$
Sharding the spending key:
If the spending key \( k_i \) is sharded, the receiver's dealer needs to collect shards and combine them. The aggregate receiver terms in (3) and (5) collected by the receiver's dealer change to:
$$ \begin{aligned} R_{MRi} &= r_{MRi_a} \cdot H + \sum_\psi ( r_{{MRi_b}_\psi} \cdot G ) \; \; \; \text{ for each receiver party } \psi \end{aligned} \tag{3b} $$
$$ \begin{aligned} a_{MRi} &= r_{MRi_a} + e(v_{i}) \\ b_{MRi} &= \sum_\psi ( r_{{MRi_b}_\psi} + e \cdot k_{i_\psi} ) \; \; \; \text{ for each receiver party } \psi \end{aligned} \tag{5b} $$
Multi-party transaction input
If multiple senders need to create an aggregate script_signature
for a multi-party transaction input, again, there are
two secrets that warrant our attention, the script private key \( k_{Si} \) and the spending key \( k_i \).
Depending on the protocol design, one or both secrets may be sharded amongst all parties, with some limitations:
- Sharding the script private key will always be applicable for an m-of-n script TariScript.
- Sharding the script private key will never be applicable for a NoOp script TariScript.
Sharding only the script private key:
If only the script private key \( k_{Si} \) will be sharded the aggregate terms in (14) collected by the sender's dealer change to:
$$ \begin{aligned} R_{Si} &= r_{Si_a} \cdot H + \sum_\omega ( r_{{Si_b}_\omega} \cdot G ) \; \; \; \text{ for each sender party } \omega \\ a_{Si} &= r_{Si_a} + e(v_{i}) \\ b_{Si} &= \sum_\omega ( r_{{Si_b}_\omega} + e \cdot k_{{Si}_\omega} ) + e \cdot k_i \; \; \; \text{ for each sender party } \omega \\ e &= \hash{ R_{Si} \cat \alpha_i \cat \input_i \cat K_{Si} \cat C_i} \\ \end{aligned} \tag{14b} $$
Sharding the script private key and the spending key:
If both the script private key \( k_{Si} \) and the spending key \( k_i \) will be sharded the aggregate terms in (14) collected by the sender's dealer change to:
$$ \begin{aligned} R_{Si} &= r_{Si_a} \cdot H + \sum_\omega ( r_{{Si_b}_\omega} \cdot G ) \; \; \; \text{ for each sender party } \omega \\ a_{Si} &= r_{Si_a} + e(v_{i}) \\ b_{Si} &= \sum_\omega ( r_{{Si_b}_\omega} + e \cdot (k_{{Si}_\omega} + k_{i_\omega}) ) \; \; \; \text{ for each sender party } \omega \\ e &= \hash{ R_{Si} \cat \alpha_i \cat \input_i \cat K_{Si} \cat C_i} \\ \end{aligned} \tag{14c} $$
It is worth noting that m-of-n
treatment of the script private key \( k_{Si} \) and the spending key \( k_i \)
differs slightly. Only m
of the original n
parties need to create the script input, and the script will resolve to
the correct \( K_{Si} \), but all n
parties need to be present to recreate the original \( k_i \). This can be
done with Pedersen Verifiable Secret Sharing (PVSS), similar to
this example.
The dealer will reconstruct the \( k_{i_j} \) shards of the missing parties and add them to their shard before
combining.
Sharding only the spending key:
If only the spending key \( k_i \) will be sharded the aggregate terms in (14) collected by the sender's dealer change to:
$$ \begin{aligned} R_{Si} &= r_{Si_a} \cdot H + \sum_\omega ( r_{{Si_b}_\omega} \cdot G ) \; \; \; \text{ for each sender party } \omega \\ a_{Si} &= r_{Si_a} + e(v_{i}) \\ b_{Si} &= \sum_\omega ( r_{{Si_b}_\omega} + e \cdot k_{i_\omega} ) + e \cdot k_{Si} \; \; \; \text{ for each sender party } \omega \\ e &= \hash{ R_{Si} \cat \alpha_i \cat \input_i \cat K_{Si} \cat C_i} \\ \end{aligned} \tag{14d} $$
Multi-party script offset
For multiple senders, aggregate terms are collected by the sender's dealer and (16) changes according to which of the secrets have been sharded:
Sharding only the script private key:
$$ \begin{aligned} \so = \sum_\omega \left( \sum_j\mathrm{k_{Sj}} \right) _\omega - \sum_i\mathrm{k_{Oi}} \; \; \text{for each input}, j,\, \text{and each output}, i \; \text{ for each sender party } \omega \end{aligned} \tag{16b} $$
Sharding only the script offset private key:
$$ \begin{aligned} \so = \sum_j\mathrm{k_{Sj}} - \sum_\omega \left( \sum_i\mathrm{k_{Oi}} \right) _\omega \; \; \text{for each input}, j,\, \text{and each output}, i \; \text{ for each sender party } \omega \end{aligned} \tag{16c} $$
Sharding the script private key and the script offset private key:
$$ \begin{aligned} \so = \sum_\omega \left( \sum_j\mathrm{k_{Sj}} - \sum_i\mathrm{k_{Oi}} \right) _\omega \; \; \text{for each input}, j,\, \text{and each output}, i \; \text{ for each sender party } \omega \end{aligned} \tag{16d} $$
Date | Change | Author |
---|---|---|
11 Nov 2022 | Move out of RFC 0201 | stringhandler |
RFC-0203/Stealth addresses
Stealth addresses
Maintainer(s): Stringhandler
Licence
Copyright 2022 The Tari Development Community
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Language
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as shown here.
Disclaimer
This document and its content are intended for information purposes only and may be subject to change or update without notice.
This document may include preliminary concepts that may or may not be in the process of being developed by the Tari community. The release of this document is intended solely for review and discussion by the community of the technological merits of the potential system outlined herein.
Goals
This Request for Comment (RFC) presents a design for a one-time (stealth) address protocol useful for one-sided payments to improve recipient privacy for payments on the Tari base layer.
Related Requests for Comment
Introduction
The Tari protocol extends the Mimblewimble protocol to include scripting in the form of TariScript. One of the first features implemented using TariScript was one-sided payments. These are payments to a recipient that do not require an interactive negotiation in the same way a standard Mimblewimble transaction does. One of the main downsides of the current implementation of one-sided payments is that the script key used is the public key of the recipient's wallet. This public key is embedded in the TariScript of the UTXO created by the sender. The issue is that it becomes very easy for a third party to scan the blockchain to look for one-sided transaction outputs being sent to a given wallet. In order to alleviate this privacy leak, this RFC proposes the use of one-time (stealth) addresses to be used as the script key when sending a one-sided payment.
Background
Stealth addresses were first proposed on the Bitcoin Talk forum by user Bytecoin. The concept was further refined by Peter Todd, using a design similar to the BIP-32 style of address generation. In this approach, the sender can use an ephemeral public key (derived from a nonce) to perform a non-interactive Diffie-Hellman exchange with the recipient's public key, and use this to derive a one-time public key to which only the recipient can derive the corresponding private key. This reduces on-chain linkability (but, importantly, does not eliminate it).
The approach was further extended in the CryptoNote whitepaper to support a dual-key design, whereby a separate scanning key is also used in the one-time address construction; this enables identification of outputs, but requires the spending key to derive the private key required to spend the output.
It is important to note that while one-time addresses are not algebraically linkable, it is possible to observe transactions that consume multiple such outputs and infer common ownership of them.
For use in Tari, single-key one-time addresses are supported.
One-time (stealth) addresses
Single-key one-time stealth addresses require only that a recipient possess a private key \( a \) and corresponding public key \( A = a \cdot G \), and distribute the public key out of band to receive one-sided payments in a non-interactive manner.
The protocol that a sender will use to make a payment to the recipient is as follows:
- Generate a random nonce \( r \) and use it to produce an ephemeral public key \( R = r \cdot G \).
- Compute a Diffie-Hellman exchange to obtain the shared secret \( c = H( r \cdot A ) \), where \( H \) is a cryptographic hash function.
- Include \( K_S = c \cdot G + A \) as the last public key in a one-sided payment script in a transaction.
- Include \( R \) in the script for use by the recipient, but
DROP
it so that it is not used in script execution. This changes the script for a one-sided payment fromPushPubkey(K_S)
toPushPubkey(R) Drop PushPubkey(K_S)
.
To identify one-sided payments, the recipient scans the blockchain for outputs containing a one-sided payment script. It then does the following to test for ownership:
- Extract the ephemeral public key \( R \) from the script.
- Compute a Diffie-Hellman exchange to obtain the shared secret \( c = H( a \cdot R ) \).
- Compute \( K_S' = c \cdot G + A \). If \( K_S' = K_S \) is included in the script, the recipient can produce the required script signature using the corresponding one-time private key \( c + a \).
Implementation notes
- Stealth addresses were included in the Tari Console Wallet as of version v0.35.0 (2022-08-11).
- The FFI (used in Aurora) uses stealth addresses by default as of libwallet-v0.35.0 (2022-08-11).
Change Log
Date | Change | Author |
---|---|---|
01 Jun 2022 | First draft | philip-za |
26 Oct 2022 | Stabilise RFC | CjS77 |
RFC-0205/Hardware transactions
Hardware transactions
Maintainer(s): SW van Heerden
Licence
Copyright 2023 The Tari Development Community
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Language
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as shown here.
Disclaimer
This document and its content are intended for information purposes only and may be subject to change or update without notice.
This document may include preliminary concepts that may or may not be in the process of being developed by the Tari community. The release of this document is intended solely for review and discussion by the community of the technological merits of the potential system outlined herein.
Goals
This Request for Comment (RFC) presents a design for a hardware wallet with MimbleWimble
Related Requests for Comment
Introduction
Hardware wallets are secure physical devices used to protect crypto assets and funds by requiring user interaction from the device. These devices are used to sign for transactions by keeping all the secrets from the transactions on the hardware device. If the host machines that run the wallets are compromised by malware the secrets from the transactions are still safe as they are not kept on the machine as with regular wallets. The devices are very low power and only feature a very limited processing power. MimbleWimble has intensive crypto operations that are in some instances very slow or not able to run on the hardware wallet at all. This RFC describes a way to get around these limitations to have fully functional secure hardware wallet integration.
Background
Vanilla Mimblewimble only has a single secret per UTXO, the blinding factor (\( k_i \) ). The Tari protocol extends the Mimblewimble protocol to include scripting in the form of TariScript. This adds a second secret per UTXO called the script_key (\( k_s \) ). In practical terms this means that while vanilla Mimblewimble only requires that a wallet wishing to spend an UTXO, prove knowledge of (\( k_i \) ) by producing the kernel signature, this is not sufficient for Tari. A Tari wallet must also prove knowledge of the script key (\( k_s \) ), by producing the script signature.
Requirements
To properly implement hardware wallets we need the following requirements to be met:
- No UTXO can be spent without a user physically approving the transaction on the hardware wallet.
- Users need to verify transaction properties when signing for transactions.
- The user must be able to receive transactions without having to authorize them on the hardware wallet.
- The user must be able to receive transactions without having the Hardware wallet attached.
- The hardware device implementation should comply with the best practices and/or security recommendations of the provider.
Implementation
Entities
Normal transactions have only a single entity, the wallet which controls all secrets and transactions. But with hardware wallets, we need to define two distinct entities:
- Signer: This is the entity that keeps the secrets for the transactions and approves them, aka the hardware wallet.
- Helper: This entity is the program that helps the signer construct the transaction, send it over the network and scan the network, aka wallet.
Process Overview
By splitting the ownership of the UTXO's secrets by assigning knowledge of only the script key (\( k_s \) ) to the signer, we can lift much of the heavy cryptography like bulletproof creation to the helper device by exposing (\( k_i \) ) to it. By looking at how one-sided-stealth transactions are created, we can construct the script key in such a way that the helper can calculate the public script key, but cannot calculate the private script key.
All hardware wallet created UTXOs will contain a script PushPubkey(K_S)
. The key (\( k_s \) ) is created as follows:
$$
\begin{align}
k_S &= H(k_i) + a \\
K_S &= H(k_i) \cdot G + A
\end{align}
$$
The blinding factor (\( k_i \) ) is used as a random nonce when creating the script key. This means the helper can create the public key without the signer present, and the signer can then at a later stage create the private key from the nonce. The key pair (\( a, A \) ) is the master key pair from the signer. The private key (\( a \) ) is kept secret by the signer at all times.
Initialization
Adding a hardware wallet to a wallet (helper) we need to ensure that all keys are only derived from a single seed phrase provided by the hardware wallet.
Helper asks signer for master helper key (\( k_H \) ). This key is derived from the signer seed phrase.
Transaction receiving
When a transaction is received the helper constructs the new UTXO with its Rangeproof. Choosing a new ( \( k_i \) ) for the UTXO, it calculates a new \( K_S \). It attaches the script PushPubkey(K_S)
to output.
Transaction sending
When the user wants to send a transaction, the helper retrieves the desired UTXO. The helper asks the signer to sign the transaction. The signer calculates \( k_s \) to sign the transaction. The signer creates a random nonce \( k_O \) to use for the script_offset. It produces the metadata signature with \( k_O \), and supplies the script_offset to the helper. The helper can attach the correct signatures to the UTXOs and ship the transaction.
Receiving normal one-sided transaction
This can be done by the helper asking the signer for a public key. And advertising this public key as the destination public key for a 1-sided transaction. The helper can scan the blockchain for this public key.
Receiving one-sided-stealth
Not yet possible with this this design.
Output recovery
When creating outputs the wallet encrypts the blinding factor \(k_i \) and value \( v \) with \( k_H \). This is encrypted using extended-nonce AEAD using a random nonce and authenticated decryption. Because the key \( k_H \) is calculated from the seed phrase of the signer, this will be the same each time. The helper can try to decrypt each scanned output, when it is successful it knows it has found its own output. The helper can validate that the commitment is correct using the blinding factor \(k_i \) and value \( v \). It can also validate (\( K_S)\) corresponds to (\( k_i, A \) )
Security
Because the script key is required for spending, it is the only key that needs to be kept secret. The following table explains what an attacker can do upon learning a key from the helper
key | Worst case scenario |
---|---|
\( k_H \) | Can view all transactions and values made by the wallet |
\( k_i \) | Can try and brute force the value of the transaction |
The keys ( \( a, k_S, k_O \) ) are only known to the signer, and should never leave the hardware wallet.
Change Log
Date | Change | Author |
---|---|---|
17 May 2023 | First draft | swvheerden |
RFC-0250/Covenants
Covenants
Maintainer(s): Stanley Bondi
Licence
Copyright 2022 The Tari Development Community
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Language
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as shown here.
Disclaimer
This document and its content are intended for information purposes only and may be subject to change or update without notice.
This document may include preliminary concepts that may or may not be in the process of being developed by the Tari community. The release of this document is intended solely for review and discussion by the community of the technological merits of the potential system outlined herein.
Goals
This Request for Comment (RFC) presents a proposal for introducing covenants into the Tari base layer protocol. Tari Covenants aims to provide restrictions on the future spending of subsequent transactions to enable a number of powerful use-cases, such as
- vaults
- side-chain checkpointing transactions,
- commission on NFT transfers, and
- many others not thought of here.
Related Requests for Comment
Introduction
The Tari protocol already provides programmable consensus, through TariScript, that restricts whether a UTXO may be included as an input to a transaction (a.k.a spent). The scope of information within TariScript is inherently limited, by the TariScript Opcodes and the input data provided by a spender. Once the requirements of the script are met, a spender may generate UTXOs of their choosing, within the constraints of MimbleWimble.
This RFC expands the capabilities of Tari protocol by adding additional requirements, called covenants that allow the owner(s) of a UTXO to control the composition of a subsequent transaction.
Covenants are not a new idea and have been proposed and implemented in various forms by others.
For example,
- Bitcoin-NG covenants put forward the
CheckOutputVerify
script opcode. - Handshake has implemented covenants to add the UTXO state of their auctioning process.
- Elements Covenants
Covenants in MimbleWimble
In blockchains like Bitcoin, a block contains discrete transactions containing inputs and outputs. A covenant in Bitcoin would be able to interrogate those outputs belonging to the input to ensure that they adhere to rules.
In MimbleWimble, the body of a block and transaction can be expressed in an identical data structure. This
is indeed the case in the Tari codebase, which defines a structure called AggregateBody
containing inputs
and outputs (and kernels) for transactions and blocks. This is innate to MimbleWimble, so even if we were
to put a "box" around these inputs/outputs there is nothing to stop someone from including inputs and
outputs from other boxes as long as balance is maintained.
This results in an interesting dilemma: how do we allow rules that dictate how future outputs look only armed with the knowledge that the rule must apply to one or more outputs?
In this RFC, we detail a covenant scheme that allows the UTXO originator to express a filter that must be satisfied for a subsequent spending transaction to be considered valid.
Assumptions
The following assumptions are made:
- Duplicate commitments within a block are disallowed by consensus prior to covenant execution,
- all outputs in the output set are valid, and
- all inputs are valid spends, save for covenant checks.
Protocol modifications
Modifications to the existing protocol and consensus are as follows:
- the covenant is recorded in the transaction UTXO,
- the covenant is committed to in the output and input hashes to prevent malleability,
- transactions with covenants entering the mempool MUST be validated, and
- each covenant in a block must be validated before being included in the block chain.
Transaction input and output changes
A covenant
field would need to be added to the TransactionOutput
and TransactionInput
structs
and committed to in their hashes.
Covenant definition
We define a clear notation for covenants that mirrors the miniscript project.
Execution Context and Scope
Covenants execute within a limited read-only context and scope. This is both to reduce complexity (and therefore the possibility of bugs) and maintain reasonable performance.
A covenant's context is limited to:
- an immutable reference to the current input,
- a vector of immutable references to outputs in the current block/transaction (called the output set),
- the current input's mined height, and
- the current block height.
Each output's covenant is executed with this context, filtering on the output set and returning the result. The output set given to each covenant at execution MUST be the same set for all covenants and MUST never be influenced by other covenants. The stateless and immutable nature of this scheme has the benefit of being able to execute covenants in parallel.
A covenant passes if at least one output in the set is matched. Allowing more than one output to match allows for covenants that restrict the characteristics of multiple outputs. A covenant that matches zero outputs fails which invalidates the transaction/block.
If a covenant is empty (zero bytes) the identity
operation is implied and therefore, no actual execution need occur.
Argument types
enum CovenantArg {
// byte code: 0x01
// data size: 32 bytes
Hash([u8; 32]),
// byte code: 0x02
// data size: 32 bytes
PublicKey(RistrettoPublicKey),
// byte code: 0x03
// data size: 32 bytes
Commitment(PedersonCommitment),
// byte code: 0x04
// data size: variable
TariScript(TariScript),
// byte code: 0x05
// data size: <= 4096 bytes
Covenant(Covenant),
// byte cide: 0x06
// data size: variable
Uint(u64),
// byte cide: 0x07
// data size: variable
OutputField(OutputField),
// byte cide: 0x08
// data size: variable
OutputFields(OutputFields),
// byte cide: 0x09
// data size: variable
Bytes(Vec<u8>),
// byte cide: 0x0a
// data size: 1 bytes
OutputType(OutputType),
}
Output field tags
Fields from each output in the output set may be brought into a covenant filter. The available fields are defined as follows:
Tag Name | Byte Code | Returns |
---|---|---|
field::commitment | 0x00 | output.commitment |
field::script | 0x01 | output.script |
field::sender_offset_public_key | 0x02 | output.sender_offset_public_key |
field::covenant | 0x03 | output.covenant |
field::features | 0x04 | output.features |
field::features_output_type | 0x05 | output.features.output_type |
field::features_maturity | 0x06 | output.features.maturity |
field::features_metadata | 0x07 | output.features.metadata |
field::features_sidechain_features | 0x08 | output.features.sidechain_features |
Each field tag returns a consensus encoded byte representation of the value contained in the field.
How those bytes are interpreted depends on the covenant. For instance, filter_fields_hashed_eq
will
concatenate the bytes and hash the result whereas filter_field_eq
will interpret the bytes as a
little-endian 64-bit unsigned integer.
Set operations
identity()
The output set is returned unaltered. This rule is implicit for an empty (0 byte) covenant.
op_byte: 0x20
args: []
and(A, B)
The intersection (\(A \cap B\)) of the resulting output set for covenant rules \(A\) and \(B\).
op_byte: 0x21
args: [Covenant, Covenant]
or(A, B)
The union (\(A \cup B\)) of the resulting output set for covenant rules \(A\) and \(B\).
op_byte: 0x22
args: [Covenant, Covenant]
xor(A, B)
The symmetric difference (\(A \triangle B\)) of the resulting output set for covenant rules \(A\) and \(B\). This is, outputs that match either \(A\) or \(B\) but not both.
op_byte: 0x23
args: [Covenant, Covenant]
not(A)
Returns the compliment of A
. That is, all the elements of A
are removed from the
resultant output set.
op_byte: 0x24
args: [Covenant]
Filters
filter_output_hash_eq(hash)
Filters for a single output that matches the hash. This filter only returns zero or one outputs.
op_byte: 0x30
args: [Hash]
filter_fields_preserved(fields)
Filter for outputs where all given fields in the input are preserved in the output.
op_byte: 0x31
args: [Fields]
filter_fields_hashed_eq(fields, hash)
op_byte: 0x32
args: [Fields, VarInt]
filter_field_eq(field, int)
Filters for outputs whose field value matches the given integer value. If the given field cannot be cast to an unsigned 64-bit integer, the transaction/block is rejected.
op_byte: 0x33
args: [Field, VarInt]
filter_absolute_height(height)
Checks the block height that the current UTXO (i.e. the current input) is greater than or
equal to the HEIGHT
block height. If so, the identity()
is returned.
op_byte: 0x34
args: [VarInt]
Encoding / Decoding
Covenants can be encoded to/decoded from bytes as a token stream. Each token is consumed and interpreted serially before being executed.
For instance,
xor(
filter_output_hash_eq(Hash(0e0411c70df0ea4243a363fcbf161ebe6e2c1f074faf1c6a316a386823c3753c)),
filter_relative_height(10),
)
is represented in hex bytes as 23 30 01 a8b3f48e39449e89f7ff699b3eb2b080a2479b09a600a19d8ba48d765fe5d47d 35 07 0a
.
Let's unpack that as follows:
23 // xor - consume two covenant args
30 // filter_output_hash_eq - consume a hash arg
01 // 32-byte hash
a8b3f48e39449e89f7ff699b3eb2b080a2479b09a600a19d8ba48d765fe5d47d // data
// end filter_output_hash_eq
35 // 2nd covenant - filter_absolute_height
07 // varint
0A // 10
// end varint, filter_absolute_height, xor
Some functions can take any number of arguments, such as filter_fields_hashed_eq
which defines the Fields
type.
This type is encoded first by its byte code 34
followed by a varint encoded number that indicates the number
of field identifiers to consume. To mitigate misuse, the maximum allowed arguments are limited.
Covenant Validation
A covenant and therefore the block/transaction MUST be regarded as invalid if:
- an unrecognised bytecode is encountered
- the end of the byte stream is reached unexpectedly
- there are bytes remaining on the stream after interpreting
- an invalid argument type is encountered
- the
Fields
type encounters more than 9 arguments (i.e. the number of fields tags available) - the depth of the calls exceeds 16.
Consensus changes
The covenant is executed once all other validations, including TariScript, are complete. This ensures that invalid transactions in a block cannot influence the results.
Considerations
Complexity
This introduces additional validation complexity. We avoid stacks, loops, and conditionals (covenants are basically one conditional), there are overheads both in terms of complexity and performance as a trade-off for the power given by covenants.
The worst case complexity for covenant validation is O(num_inputs*num_outputs)
, although as mentioned above
validation for each input can be executed in parallel. To compensate for the additional workload the network
encounters, use of covenants should incur heavily-weighted fees to discourage needlessly using them.
Cut-through
The same arguments made in the TariScript RFC for the need to prevent cut-through apply to covenants.
Chain analysis
The same arguments made in the TariScript RFC apply.
Security
As all outputs in a block are in the scope of an input to be checked, any unrelated/malicious output in a block could pass an unrelated covenant rule if given the chance. A secure covenant is one that uniquely identifies one or more outputs.
Examples
Now or never
Spend by block 10 or burn
not(filter_absolute_height(10))
Note, this covenant may be valid when submitted to the mempool, but invalid by the time it is put in a block for the miner.
Side-chain checkpointing
and(
filter_field_eq(field::feature_flags, 16) // SIDECHAIN CHECKPOINT = 16
filter_fields_preserved([field::features, field::covenant, field::script])
)
Restrict spending to a particular commitment if not spent by block 100
or(
not(filter_absolute_height(100)),
filter_fields_hashed_eq([field::commmitment], Hash(xxxx))
)
Output must preserve covenant, features and script or be burnt
xor(
filter_fields_preserved([field::features, field::covenant, field::script]),
and(
filter_field_eq(field::features_flags, 128), // FLAG_BURN = 128
filter_fields_hashed_eq([field::commitment, field::script], Hash(...)),
),
)
Commission for NFT transfer
// Must be different outputs
xor(
and(
// Relavant input fields preserved in subsequent output
filter_fields_preserved([fields::features, fields::covenant, fields::script]),
// The spender must obtain the covenent for the subsequent output
filter_fields_hashed_eq([fields::covenant], Hash(xxxx)),
),
// The spender must obtain and submit the output that matches this hash
filter_output_hash_eq(Hash(xxxx)),
)
Other potential covenants
filter_script_eq(script)
filter_covenant_eq(covenant)
filter_script_match(<pattern>)
filter_covenant_match(<pattern>)
Change Log
Date | Change | Author |
---|---|---|
17 Oct 2021 | First draft | sbondi |
08 Oct 2022 | Stable update | brianp |
RFC-0303/DanOverview
Digital Assets Network
Maintainer(s): Cayle Sharrock,S W van Heerden
Licence
Copyright 2022 The Tari Development Community
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Language
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as shown here.
Disclaimer
This document and its content are intended for information purposes only and may be subject to change or update without notice.
This document may include preliminary concepts that may or may not be in the process of being developed by the Tari community. The release of this document is intended solely for review and discussion by the community of the technological merits of the potential system outlined herein.
Goals
The aim of this Request for Comment (RFC) is to describe the key elements of the Tari second layer, also known as the Digital Assets Network (DAN).
Related Requests for Comment
Description
The Tari DAN is based on a sharded BFT consensus mechanism called Cerberus.
One particular note is that Tari has chosen Hotstuff as the base BFT consensus algorithm over pBFT mentioned in the paper.
The core idea of Cerberus is that instead of dividing work up between validator nodes according to the contracts they are managing (as per Tari DANv1, Polkadot, Avalanche, etc.), Cerberus distributes nodes evenly over a set of shard addresses. Any time an instruction modifies the state of a contract, it will affect one or more shard addresses, and only those nodes that are responsible for covering those addresses will reach consensus on the correct state changes.
This means that nodes have to be prepared to execute instructions on any contract in the network. This does create a data synchronisation burden, but the added benefit of a highly scalable, decentralised DAN significantly outweighs this trade-off.
Key actors
There are several components on both the Tari Digital Assets Network (DAN) and base layer that interoperate to collectively enable scalable smart contracts on Tari.
These components include:
- Minotari base layer - Enforces Tari monetary policy and plays the role of global registrar.
- Templates - Reusable smart contract components.
- Contracts - Self-contained pieces of code that describe the behaviour of a smart contract. They are compiled and executed in the Tari VM.
- Validator Nodes - VNs validate smart contracts and earn fees for doing so.
- Cerberus consensus engine - highly scalable, high-speed sharded BFT consensus engine.
- Tari Virtual Machine - Runs smart contracts in a secure sandbox.
- Tari - the token that fuels the Tari Network a.k.a DAN.
The remainder of this document describes these elements in a little more detail and how they relate to each other.
The Minotari Base Layer
Obviously, the most important role of the Minotari base layer (formerly, Tari base layer) is to issue and secure the base Tari token.
As it relates to the DAN, the base layer also serves as an immutable global registry for several key pieces of data:
- It maintains the register of all validator nodes.
- It provides the only means of minting more [Tari] into the DAN economy.
- It maintains the register of all DAN contract templates.
The base layer also forces the DAN to make progress in the case of a Byzantine stoppage.
Templates
Templates are parameterised smart contracts. Templates are intended to be well-tested, secure, reusable components for building and running smart contracts on the DAN.
For example, an NFT template would allow a user to populate a few fields, such as name, number of tokens, media locations, and then launch a new NFT series without having to write any actual code.
Templates are stored and managed on the base layer. You can think of the Tari base layer as a type of git for smart contracts. Templates will also have version control features and a smooth upgrade path for existing contracts.
Contracts
Tari smart contracts are the meat of the Tari ecosystem. Usually, a smart contract will be comprised of one or more Tari templates, glue code, and initialisation code.
The contracts are always executed in the Tari Virtual machines. The input and output of every contract instruction is validated by validator nodes that reach consensus using the Cerberus consensus engine.
Validator Nodes
Validator Nodes (VNs) execute and reach consensus on DAN contract instructions. VNs must register on the base layer and lock up funds (the registration deposit) in order to participate in the DAN.
With this in place, every base node has an up-to-the-minute list of all active validator nodes and their metadata. The registration deposit also serves as a Sybil prevention mechanism.
Validator nodes are the bridge between the consensus layer and the Tari Virtual Machine (TVM).
VNs are required to re-register periodically as a proof-of-liveness mechanism.
Validator nodes must be able to
- interpret the instructions they receive from clients, identifying the contract code that the instruction refers,
- retrieve and deserialize the relevant input state,
- compute the output state that result from applying the contract logic to the input state, and
- reach consensus with its peers.
Steps 1 - 3 are carried out in the Tari Virtual Machine (TVM). Step 4 is achieved by communicating with peers via the DAN consensus layer.
Tari exists at the Validator node level, and VNs earn fees, in Tari, for each instruction -- in aggregate -- that it aids in getting finalised.
DAN consensus layer
The Cerberus BFT consensus algorithm runs on the consensus layer. This layer is completely ignorant of the semantics of DAN smart contracts.
This layer only cares that:
- only VNs that have registered on the base layer are participating in consensus.
- VNs are self-organising into VN committees and are carrying out the rules of Cerberus correctly.
In particular, the consensus layer has no idea whether an instruction's output is correct. If two-thirds (plus one) of the committee agree on the results, then consensus has been reached and the consensus layer is happy.
For example, if consensus decides that 2 + 2 = 5, then for the purposes of this contract, that is the case.
The Tari Virtual Machine
The TVM is a WASM-based virtual machine designed to run Tari contracts. Contracts are composed of one or more Tari templates, glue code and a state schema. Tari Labs provides a Rust implementation for TVM contracts, but in principle, other languages could implement the specification as well.
The TVM is able to
- load a contract.
- provide a list of methods that the contract exposes.
- Execute calls on the contract.
- Initiate retrieval and persistence of the state of the contract. The state itself is not stored in the VM, but by Indexers.
Tari and the turbine model
Tari is used to power the DAN's economic engine. Tari is minted via a one-way perpetual peg as described in RFC-0320. Briefly, Minotari are burnt to create Tari, which are used to pay for the execution of instructions on the DAN. A portion of instruction fees are burnt with every instruction to provide a constant source of demand for Tari in the DAN.
There is no peg out back to the base layer for Tari. The reason for this is explained in RFC-0320. Tari holders wishing to convert back to Tari will be able to perform a submarine swap with a Tari seller. We also anticipate that exchanges will list the Tari-Minotari pair to enable easy conversion of Tari back to Minotari.
Change Log
Date | Change | Author |
---|---|---|
23 Oct 2023 | Thaum -> Tari | CjS77 |
1 Nov 2022 | High-level overview | CjS77 |
26 Oct 2022 | First outline | SWvHeerden |
RFC-313/VN Registration
Maintainer(s): stringhandler, SW van heerden and sdbondi
Licence
Copyright 2022 The Tari Development Community
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Language
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as shown here.
Disclaimer
This document and its content are intended for information purposes only and may be subject to change or update without notice.
This document may include preliminary concepts that may or may not be in the process of being developed by the Tari community. The release of this document is intended solely for review and discussion by the community of the technological merits of the potential system outlined herein.
Goals
The goal of this RFC is to outline the DAN validator node registration requirements and define a set of procedures that allow permissionless participation in the DAN. This includes defining the interaction between the validator and base node, new base layer validations, and validator shard key allocation.
Overview
Building on the RFC-0303, we define the mechanism that allows DAN validators to register on the DAN network.
Each validator requires a connection to a trusted base-layer node that provides a canonical view of the blockchain. The blockchain serves as a shared logical clock for the DAN.
In this RFC, we will show that the validators can leverage the strong liveness guarantees of proof-of-work while providing a scheme that mitigates the effects of weak safety guarantees (namely, reorgs) on the base layer.
Requirements and Definitions
- To participate in DAN BFT consensus, a validator node MUST be registered on the Layer 1 Tari blockchain.
- Each registration expires after a number of epochs, at which point the validator may no longer participate in DAN consensus.
- A validator MAY re-register before or after the expiration epoch is reached to allow continued participation in DAN consensus.
- A validator registration MUST be submitted as a base layer
ValidatorNodeRegistration
UTXO signed by theVN_Public_Key
- A validator MUST be assigned a deterministic but randomized
VN_Shard_Key
that can be verified at any epoch by other validators by inspecting the base layer. - The
VN_Shard_Key
MUST be periodically reassigned/shuffled to prevent prolonged control over a particular shard space. - A validator MUST be able to generate a Merkle proof that it is registered for the epoch that it is currently participating.
- The validator set for any given epoch MUST be unambiguous between validators or anyone observing the base layer chain.
- Base layer reorgs MUST NOT negatively affect the DAN layer.
We define the following validator variables:
Symbol | Name | Description |
---|---|---|
$V_i$ | VN_Public_Key | The $i$th public validator node key |
$S_i$ | VN_Shard_Key | The $i$th 256-bit VN shard key. |
$\epsilon_i$ | Epoch | The $i$th epoch. An epoch is EpochLength blocks. |
An epoch $\epsilon$ is defined by the base layer block height $h$, where $\epsilon_i = \lfloor \frac{h}{\text{EpochLength}} \rfloor$, and spans all blocks from the start of the epoch up to but excluding the start of the next epoch.
Base layer consensus constants (all values TBD):
Name | Value | Description |
---|---|---|
EpochLength | 60 Blocks (~2h) | The number of blocks in an epoch |
VNRegistrationValidityPeriod | 20 Epochs (~40hrs) | The number of epochs that a validator node registration is valid |
VNRegDepositAmount | TBD Tari | The minimum amount that must be spent to create a valid ValidatorNodeRegistration UTXO |
VNRegLockHeight | 10 Epochs | The lock height that must be set on every ValidatorNodeRegistration UTXO |
VNShardShuffleInterval | 100 Epochs | The interval that a validator node shard key is shuffled |
Validator node consensus constants:
Name | Value | Description |
---|---|---|
VNConfirmationPeriod | 1000 Blocks |
Validator Registration
A validator node operator wishing to participate in DAN consensus MUST generate a Ristretto keypair <VN_Public_Key, VN_Secret_Key>
that serves as a stable DAN identity and a signing key for L2 consensus messages.
The VN_Public_Key
MUST be registered on the base layer by submitting a ValidatorNodeRegistration
UTXO which allows the validator to participate in DAN consensus for VNRegistrationValidityPeriod
.
The published ValidatorNodeRegistration
UTXO has these requirements:
- MUST contain a valid Schnorr signature that proves knowledge of the
VN_Secret_Key
- The signature challenge is defined as $e = H(P \mathbin\Vert R \mathbin\Vert m)$
- $R$ is a public nonce, $P$ is the public
VN_Public_Key
- $m$ is the UTXO commitment
- the UTXO's
minimum_value
must be at leastVNRegDepositAmount
to mitigate spam/Sybil attacks, - the UTXO lock-height must be set to
VNRegLockHeight
. - A script which burns the validator node registration funds if the validator does not reclaim it for a long period after the lock height expires.
OP_PUSH_INT(N) OP_COMPARE_HEIGHT OP_LTE_ZERO OP_IF_THEN
NOP
OP_ELSE
OP_RETURN
OP_END_IF
By submitting this UTXO the validator node operator is committing to providing a highly-available node from the next epoch
after the validator node registration was submitted to VNRegistrationValidityPeriod
epochs after that.
A validator node operator MAY re-register their VN_Public_Key
before the VNRegistrationValidityPeriod
epoch is reached, OPTIONALLY
spending the previous ValidatorNodeRegistration
UTXO. If the previous ValidatorNodeRegistration
UTXO has not expired and
a new ValidatorNodeRegistration
UTXO is submitted, the new ValidatorNodeRegistration
UTXO supersedes the previous one.
The validator node may implement auto re-registration to ensure that the validator node continues to be included in the current VN set without constant manual intervention.
A validator MAY deregister by spending their ValidatorNodeRegistration
UTXO. This will remove the validator from the current VN set
in the next epoch.
Base-layer consensus
The base layer performs the following additional validations for ValidatorNodeRegistration
UTXOs:
- The
VN_Registration_Signature
MUST be valid for the givenVN_Public_Key
and challenge - The
minimum_value
field MUST be at leastVNRegDepositAmount
. The existingminumum_value
validation ensures the committed value is correct.
Additionally, we introduce a new block header field validator_node_mr
that contains a Merkle root committing to all validator
Vn_Shard_Key
s in the current epoch.
The validator_node_mr
needs to be recalculated at every EpochSize
blocks to account for departing and arriving nodes.
The validator_node_mr
MUST remain unchanged for blocks between epochs, that is, blocks that are not multiples of EpochSize
.
A validator generates a Merkle proof that proves its
VN_Shard_Key
is included in the validator set for any given epoch. This proof is provided in layer 2 Quorum Certificates.
The validator_node_mr
is calculated for each block as follows:
- if the current block height is a multiple of
EpochSize
- then fetch the VN set for the epoch
- build a merkle tree from the VN set, each node is $H(V_i \mathbin\Vert S_i)$
- otherwise, fetch the previous block's
validator_node_mr
and return it
Epoch transitions
The DAN BFT consensus protocol relies on a shared and consistent "source of truth" from the base layer chain that defines the current epoch and validator set as well as templates.
As briefly mentioned in the overview, any PoW base layer chain is prone to reorgs. The question arises, how do we achieve a shared, consistent view of the chain when the data can disappear from underneath you?
We define a VNConfirmationPeriod
as is a network-wide constant that specifies the number confirmations (blocks) that a block must have
before a validator will recognise it as final. The chosen value for VNConfirmationPeriod
must be large enough to make reorgs beyond that
point practically impossible. Validators simply ignore base layer reorgs with a depth less than VNConfirmationPeriod
deep as the
data they have extracted is still valid. This means that the point of finality is not always VNConfirmationPeriod
blocks away from the
tip and is non-decreasing/monotonic which effectively negates reorg "noise" from the chain tip.
However, this does not address base layer latency delays where a single huge block or multi-block reorgs may take seconds to be received and processed by all base nodes. Moreover, the validators may poll the base layer only every few seconds, further increasing the latency for validators to become aware of the state. This means a single strict validator epoch change-over point will almost always cause liveness failures at and after the epoch transition.
To address this, we define a VNEpochGracePeriod
where both the previous and current epoch are accepted. This value, in blocks, must allow enough
time for all validators to become aware of the base layer state for the epoch.
To illustrate, consider the following view of a base layer chain. We mark 3 views of the chain.
(a) (b) (c)
| | | {noisy}
------ | --x------------ | --x------------ | --x------------ | --x------------ | --- .... ------> tip
| ϵ10 | | ϵ11 | ϵ12 | ϵ13 ϵ14
V_1 V_2 V_3 V_4 V_5 V_6
Key:
x - Epoch transition point
ϵn - Epoch n
V_n - Validator registrations
Point (a)
- the validator node set is incomplete for epoch 12.
- the active epoch is 11.
- the validator MUST reject instructions for epoch 12.
Point (b) - the start of epoch 12:
- the validator node set is final for epoch 12.
- the active epoch remains at 11. This is because validators may not have reached epoch 12/point (b) and therefore will only accept epoch 11.
- a validator MUST accept instructions from epoch 11 and 12.
- a validator may receive a leader proposal for epoch 11 and 12, however a well-behaved validator MUST only vote for one of these proposals.
Point (c) - the transition point for epoch 12:
- the active epoch is now 12
- the validator MUST reject instructions from 11.
- it is assumed at this point that almost all (at least $2f + 1$) nodes will accept epoch 12
Validator Node Set Definition
The function $\text{get_vn_set}(\epsilon_\text{start}, \epsilon_\text{end}) \rightarrow \vec{S}$ that returns an ordered
vector $\vec{S}$ of VN_Shard_Key
s that are registered for the epoch $\epsilon_n$. The validator node set is ordered by VN_Shard_Key
.
Data Indexes
The following additional indexes are recommended to allow efficient retrieval of the VN set, shard key mappings and to produce a valid validator_node_mr
:
- $I_\text{primary} = \{ (h_i, V_i, C_i) \rightarrow (V_i, S_i) \}$
- $I_\text{shard} = \{ (V_i, h_i, C_i) \rightarrow S_i \}$
- $C_i$ is the $i$th UTXO commitment
Database index $I_\text{primary}$ that maintains a mapping from the next epoch after the registration to all the
<VN_Public_Key, VN_Shard_Key>
tuples for all ValidatorNodeRegistration
UTXOs. This allows efficient retrieval
from a particular height onwards, optionally for a particular validator node public key.
The index entry is not removed whenever the ValidatorNodeRegistration
UTXO is spent or expires. This is to allow
state to be rewound for reorgs.
If a validator adds two or more validator registration UTXOS in the same block, the index will order them by commitment, that is,
the same canonical ordering as the Tari block body. The get_vn_set
function MUST return the last registration
public key and shard key tuple only, according to this canonical ordering.
Algorithm
The function $\text{get_vn_set}$ is defined as follows:
- Iterate on index $I_\text{primary}$, starting from where the key is between (inclusive) the equivalent block height for
$\epsilon_\text{start}$ and $\epsilon_\text{end}$:
- Add to the set, and
- if the validator node public key is already in the set, remove the previous entry.
For this example, we say that there have been no registrations prior to V_1
; we define
VNRegistrationValidityPeriod = 2 epochs
.
(a) (b) (c)
| | | {noisy}
------ | --x------------ | --x------------ | --x------------ | --x------------ | --- .... ------> tip
| ϵ10 | | ϵ11 | ϵ12 | | ϵ13 ϵ14
V_1 V_2 V_3 V_4 V_5 V_2 V_6
Key:
x - Epoch transition point
ϵn - Epoch n
V_n - Validator Node Registration UTXO n
- Point (a) $\text{get_vn_set}(\epsilon_11) -> [V_1, V_2, V_3]$
- Point (b):
- In: $[V_2, V_3, V_4, V_5]$, out: $[V_1]$
- $\text{get_vn_set}(\epsilon_12) -> [V_2, V_3, V_4, V_5]$
- Point (c):
- In: $[V_6]$, out: $[]$
- $\text{get_vn_set}(\epsilon_13) -> [V_2, V_4, V_5, V_6]$
Shard Key and Shuffling
The VN_Shard_Key
is a deterministic 256-bit random number that is assigned to a validator node by the base layer for a given epoch, and
maps onto the 256-bit shard space.
The DAN network needs to agree on and maintain a mapping between each participant's VN_Public_Key
and the corresponding VN_Shard_Key
for the
current epoch.
Over time, an adversary may gain excessive control over a particular shard space. To mitigate this, we introduce a shuffling mechanism that
periodically and randomly reassigns VN_Shard_Key
s within the network.
We define the function $\text{generate_shard_key}(V_n, \eta) \rightarrow S$ that generates the VN_Shard_Key
from the inputs.
$S = H_\text{shard}(V_n \mathbin\Vert \eta)$ where $H_\text{shard}$ is a domain-separated Blake256 hash function, $V_n$ is the public VN_Public_Key
and $\eta$ is some entropy.
And we define the function $\text{derive_shard_key}(S_{n-1}, V_n, \epsilon_n, \hat{B}) \rightarrow S$ that deterministically derives the VN_Shard_Key
for
epoch $\epsilon_n$ from the public VN_Public_Key
$V_n$, $\hat{B}$ the block hash at height $\epsilon_n * \text{EpochSize} - 1$ (the block before the epoch block).
The function $\text{derive_shard_key}$ is defined as follows:
- Given:
- $S_{n-1}$ the previous
VN_Shard_Key
- $V$ the
VN_Public_Key
- $\epsilon_n$ the epoch number to generate a
VN_Shard_Key
for - $\hat{B}$ the previous block hash
- $S_{n-1}$ the previous
- If the previous shard key $S_{n-1}$ is not null,
- Check $(V + \epsilon_n) \bmod \text{ShufflePeriod} == 0$.
- If true, generate a new shard key using $\text{generate_shard_key}(V, \hat{B})$.
- If false, return $S_{n-1}$.
- If the previous shard key $S_{n-1}$ is null,
- generate a new shard key using $\text{generate_shard_key}(V, \hat{B})$.
Only a random fraction of validators will be re-assigned shard keys per epoch and that fraction will not be shuffled again
for VNShardShuffleInterval
epochs. Although the exact number of validators that shuffle per epoch varies, on average
the VNShardShuffleInterval
should aim to shuffle around 5% of the network at every epoch. This is to ensure that the
number of shuffling validators is much less than $\frac{1}{3}$ of the validator set, as this could break liveness and safety
guarantees.
The prev_shard_key
is the last VN_Shard_Key
that was assigned to the validator node within the VNRegistrationValidityPeriod
.
Should VNRegistrationValidityPeriod
elapse without a renewed registration, a new VN_Shard_Key
is assigned.
This means that a validator may be assigned a different VN_Shard_Key
after each VNRegistrationValidityPeriod
,
sooner than VNShardShuffleInterval
. The validator has nothing to gain from this, on the contrary, they will have
to re-sync their state and spend time not participating in the network, losing out on fees.
Change Log
Date | Change | Author |
---|---|---|
12 Oct 2022 | First outline | SWvHeerden |
08 Nov 2022 | Updates | sdbondi |
18 Nov 2022 | Implementation updates | sdbondi |
RFC-0320/TurbineModel
The DAN peg-in mechanism, or Turbine model
Maintainer(s): Cayle Sharrock
Licence
Copyright 2022 The Tari Development Community
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Language
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as shown here.
Disclaimer
This document and its content are intended for information purposes only and may be subject to change or update without notice.
This document may include preliminary concepts that may or may not be in the process of being developed by the Tari community. The release of this document is intended solely for review and discussion by the community of the technological merits of the potential system outlined herein.
Goals
Tari is used to power the DAN's economic engine.
This RFC describes the motivation and mechanism of the Minotari to DAN peg-in mechanism.
Related Requests for Comment
Description
Side-chains are related to their parent chains via a pegging mechanism. In general, peg-in transactions (transferring value to the side-chain) are straightforward. One locks value up on the parent chain, which can be referenced by the side-chains. However, the reverse transaction is fraught with difficulty, since the parent chain must know almost nothing about the transaction particulars of the side chain. We know this is the case, otherwise the entire side-chain + parent-chain system are forced to work in lock-step and the two chains are really just one larger, more complicated chain.
Peg outs are particularly difficult if the participants change on the side-chain (as opposed to say, payment channels, like the Lightning Network, where the same parties peg-in and -out).
There are several proposals to develop a reliable two-way peg, including space-chains, drive-chains and federated side-chains, like elements. All of them have particular trade-offs and difficulties.
For Tari, we propose a slightly different approach: A one-way peg with persistent 2nd-layer burn.
Because the operating principle is quite similar to that of a gas turbine, we call this approach the turbine model. Fuel (Minotari) is fed into the turbine, which is burnt (also burnt :)) which produces a hot, motive gas (Tari) that drives the engine (the DAN). The exhaust gas is ejected from the rear of the turbine (a portion of the instruction fees are burnt).
An aside - the monetary policy trilemma
The monetary policy trilemma states that you cannot control all 3 of these things simultaneously:
- exchange rate
- monetary policy (i.e. minting and burning to control supply)
- flow of capital (money leaving or entering the system)
Let's briefly consider the trilemma from the point of view of the DAN.
Monetary policy
We are effectively forced to control monetary policy. Or put another way, we can't let people freely mint their own Tari. Unfortunately, even though we have "chosen" to control supply, it's difficult for us to control the Tari supply in practice. In the physical world, the money supply is typically managed by a central bank, with the emphasis on "central".
The designers of a decentralised monetary system have precious few levers available to control supply and no simple ones.
Capital flow
Allowing free capital flow would require a reliable and efficient peg-out mechanism from the DAN back to the base layer. However, I argue that this is an Achilles heel. Any peg-out system you can devise that is coupled with a burn-type peg-in mechanism is an existential threat to the base layer.
Why? Because the peg-out necessarily requires creation of coins on the base layer by trusting some mechanism external to it. Note that we're not bringing UTXOs that were pegged-in back into circulation (in this case they would not be burned, merely locked-up as in a traditional peg). Therefore, the base layer accounting simply has to accept these mints as valid.
Consequently, any bug whatsoever on the DAN related to the minting process' authenticity could lead to undetectable inflation on the base layer.
A central axiom of side-chain design, if there is such a thing, is that the side-chain should pose zero risk to the security of the base layer. For this reason, the burn mechanism effectively excludes the possibility of peg ins.
So essentially, we cannot allow the free flow of capital either.
Exchange rate
Since we have already picked two legs of the trilemma, we cannot do anything about the third, and must allow the change rate to float.
The turbine model
This is actually not as terrible as it sounds. The trilemma doesn't force you to sit at the vertex of the monetary policy triangle. If you allow partial freedoms in supply and capital flow, then the exchange rate will move, but will tend to remain range-bound.
Although capital flow here is not free, it's not completely restricted either:
- Peg-ins are completely unrestricted.
- Submarine-swaps allow people to remove money from the system on the micro-level (albeit not on the macro level).
And the money supply can be tuned, if not controlled. This all leads to the proposal of a new mechanism, the turbine model:
The DAN Tari supply is increased by user peg-in deposits, and any other mechanism that we may want to enforce, such as asset issuer financing.
To prevent the eventual collapse of the Tari price to zero, there must be an exhaust mechanism that continually removes Tari from the system..
The simplest exhaust mechanism is to simply burn a fraction of Tari fees from every transaction! These can be very low. Presumably, over the long-run the burn rate should approximately match the Minotari blockchain tail emission.
The exhaust places a permanent upward pressure on the Tari exchange rate; but it will never exceed 1:1 with Minotari, since any premium will be immediately arbitraged away. This is because anyone can always burn as much Minotari as they wish and mint Tari at a 1:1 ratio on the DAN and then sell them for a risk-free profit. This action will increase the supply of Tari and drive the price back down to parity.
If the exhaust is temporarily insufficient to hold the peg, the Tari price will drop below 1 XTR. This will immediately shut off deposits because submarine swaps will a be cheaper route to obtaining Tari than burning Minotari (which are always 1:1). Since the exhausts upward price pressure is a constant force, the Tari price will eventually approach 1:1 again.
Over time, we expect this mechanism to provide a somewhat stable peg between Tari and Minotari with the Tari price occasionally dropping below parity and possibly remaining there for some time. As secondary markets for the Tari-Minotari pair matures, this event will immediately create a bid on Tari, since -- in the absence of catastrophic failure -- speculators know that the Tari price will eventually return to parity, causing upward pressure to come into play quickly and efficiently.
Change Log
Date | Change | Author |
---|---|---|
23 Nov 2023 | Thaum -> Tari | CjS77 |
1 Nov 2022 | First draft | CjS77 |
RFC-0350/TariVM
The Tari Virtual Machine
Maintainer(s): Cayle Sharrock
Licence
Copyright 2023 The Tari Development Community
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Language
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as shown here.
Disclaimer
This document and its content are intended for information purposes only and may be subject to change or update without notice.
This document may include preliminary concepts that may or may not be in the process of being developed by the Tari community. The release of this document is intended solely for review and discussion by the community of the technological merits of the potential system outlined herein.
Goals
This RFC describes the design goals and rationale for the Tari Virtual Machine (TVM).
Related Requests for Comment
Description
The Consensus Layer for the Tari network, described in RFC-0305, is responsible for distributed and trust-minimised decision-making in the Tari digital assets network (DAN).
The consensus layer is blind to any notions of smart contracts, NFTs or stablecoins. It merely enforces that decision made by honest nodes are propagated to the rest of the network.
Business logic is encapsulated in the Tari Logic layer.
The relationship between the logic and the consensus layers is best illustrated by the transaction flow, from client to resolution. This flow is diagrammed in Figure 1.
The client creates a transaction using a transaction manifest. The manifest collates every contract, function call and log entry that the client wants to execute into a single bundle. The manifest is then submitted to an indexer that will collect all the input state required for the transaction pre-emptively. This is a deviation from the Cerberus and Chainspace papers and allows the indexer to perform a dry-run of the transaction before submitting it to a validator node.
The advantage of this is that the client can be notified of any errors before submitting the transaction to the network and incurring fees.
On the other hand, the indexer commits to the given set of inputs when submitting the transaction to the validator node. If the indexer is mistaken, or lagging in its updates, it is possible that the transaction will be aborted due to attempting to use expired inputs.
However, this is not expected to happen often, and assuming everything is in order, the validator node will confirm the correct version of all the input states by collecting all local and foreign substates involved in the transaction.
If these are satisfactory and match what was provided by the indexer, the validator node will pass the transaction manifest data along with the input state to the Tari Engine for execution.
The Tari Engine will determine which WASM modules are required to perform this work, load them into a WASM runtime, and execute the functions. The Tari engine makes use of several services to help it in this task, including the template manager, the Tari SDK, and Tari runtime wrapper.
The execution result (whether successful or not) is returned to the validator node, which then compares the results with its peers via Hotstuff consensus to achieve agreement on the final result. If consensus is achieved, the affected substates are updated (see RFC-330) and the result is relayed to the indexer who passes it on to the client.
A key point here is that the consensus layer delegates all business logic to the logic layer. However, only the consensus layer has the ability to make changes to the state of the network, after reaching consensus.
flowchart LR
Cl((Client)) -------> |tx
'Transfer JPG to Bob'| Ix1
Ix1 -.-> |returns result| Cl
subgraph Indexer
TM --> Ix1([Indexer])
Ix1 <-.-> |reads| SS1[(Substates)]
Ix1 <-.-> |requests| VN1[[VNC]]
Ix1 <-.-> |dry run| VM1[[TariVM]]
end
Ix1 -->|submits with\nlocked inputs| C
subgraph Logic Layer
T[Template manager]
W[Wasm compiler]
ABI[Contract interface]
E[Tari engine]
RT[WASM Runtime]
SDK[Tari SDK]
E <--> RT
end
C --> |calls| E
E --> |returns new substates| C
subgraph Consensus Layer
C[Validator node]
C <-.-> |reads/writes| SS[(Local substates)]
C <-.-> |foreign state\nrequests| VNC[[VNC]]
EM[Epoch management]
VNCm[Validator committee\n management]
HS[Cerberus-Hotstuff]
end
The Tari Logic layer comprises several submodules:
- The Tari engine. The Tari engine is responsible for the transaction execution process and fee disbursements.
- The Template manager. The template manager polls the Minotari base layer looking for template registration transactions.
- The Tari runtime. The Tari runtime wraps a WASM virtual machine that executes the compiled contract code. The runtime is able to calculate the total compute requirements for every instruction, which determines the transaction fee.
- The contract interface (ABI). This is a list of functions, their arguments and return values that a particular contract is able to execute. The ABI is generated when the contract template is compiled.
- The transaction manifest. This is a high-level set of instructions that a client application generates to achieve some user goal.
The Tari engine
The Tari engine is responsible for the transaction execution process and fee disbursements. It communicates with
other actors in the Tari network via JSON-RPC. Transactions can be submitted to be executed via the
submit_transaction
procedure call.
A transaction contains the following information:
- A list of input substate that will be downed (spent).
- A list of input substates that are used as references, but their state is not altered.
- The list of instructions to execute (call method, claim funds, emit logs etc.).
- Signatures
- Network metadata
Transaction execution proceeds via the following high-level flow:
- The engine determines the total fee for the transaction by charging for every operation executed within the WASM runtime, as per the fee schedule.
- The engine initializes a WASM runtime, which executes the instructions embedded in the transaction. For each instruction, a [template provider] will attempt to provide the WASM, or other compatible binary, to execute the instruction with the given input parameters. See also the base node scanner. For now, only WASM modules are supported, but in future, additional runtimes could be supported by the Tari Engine, including Zero-Knowledge contracts, or the EVM.
- The result of execution -- the execution status, and the set of outputs -- is passed back to the consensus layer.
Note that outside of the vanishingly small chance of an output substate collision, even a failed execution attempt is
a 'positive' (i.e.
COMMIT
ted) result in terms of consensus, as long as the super-majority of nodes agree that "failure" is the consensus result!
Template manager
The Tari engine maintains a service that scans the Minotari chain every few minutes that among other things, looks for template registration transactions.
When a new template is registered, the template manager will locate the registered WASM module, validate it, and store it in the local database.
The template manager abstracts away issues such as template versioning, whether the module is stored in IPFS, or a centralised repository. It can also request binaries from a peer to reduce the load on external services.
In future, the template manager might also be able to retrieve source code, and make use of reproducible builds to compile audited source code and add it to the local WASM repository.
The Tari runtime
The Tari runtime is a wrapper that provides common functionality for executing arbitrary smart contracts in WASM
modules. Functionality includes:
- calling a function,
- calling a method,
- emitting a log entry,
- pushing an object into the workspace.
In combination with a contract's ABI, the runtime is able to execute almost any contract code.
Tari uses the wasmer runtime to actually load and execute Web Assembly inside a secure, sandboxed environment.
The contract interface (ABI)
Rust is strongly-typed, yet we need to be able to call an unlimited variety of functions and methods from arbitrary contracts in a unified, consistent way. This is where the ABI comes in. It defines all the public methods and their arguments that a contract exposes.
The ABI is generated when a contract template is compiled from Rust source.
The transaction manifest
When a client wants to interact with the DAN, it is often the case that she wants to invoke multiple functions across multiple contracts simultaneously. For example, Alice may want to buy a monkey NFT from Bob. Her transaction might lock funds in a cryptographic escrow (in the Tari contract) until she has proof that the NFT has landed in her NFT account (which is in a different contract).
The transaction manifest collects all the information necessary to achieve this goal, including the contract(s) function(s) to call, their arguments, fee information and all the necessary signature and witness data to authorise the transaction. The transaction manifest is compiled into an abstract syntax tree (AST) that can be consumed by a validator node.
Internally, A manifest is simply a list of manifest intents that are bundled together into an atomic whole.
An intent is typically one of the following:
- A template invocation or component invocation, indicating that the user wants to execute a function on a contract, supplying the necessary input arguments.
- A log entry, providing the log level and message.
Why Web Assembly?
The Smart contract execution environment is incredibly hostile. The runtime is effectively tasked to run arbitrary code on a global system that has potentially billions of dollars of value at stake.
It is therefore critical that the execution environment does not affect state in the broader network that it is not entitled to, but also, the code cannot be allowed to jailbreak the execution environment of the validator node itself and wreak havoc on the host system.
Thus, the runtime environment should
- be strictly sandboxed,
- support multiple concurrent VM without being a resource hog,
- have no access to the host system internals, including disk storage, camera, microphones, etc.
- be ephemeral and support rapid cold starts.
Given these onerous requirements, rather than reinvent the wheel, we considered several existing solutions, including
- WebAssembly (WASM)
- Extended Berkeley Packet Filter (eBPF)
- Full virtualisation, such as KVM or VmWare
Full virtualisation was quickly discarded as being too heavy-weight. Validator nodes will be required to swap out contracts constantly, and so cold-starting a runtime environment must be as lightweight and as fast as possible.
Surprisingly, there were very few options remaining, with eBPF and Wasm being the most mature and closest to our needs. In fact both runtimes are used in smart-contract execution environments today. WASM is used in Stellar, Near, Cosmos, Polkadot, Radix and a host of others. eBPF is used in Solana.
We chose WASM for the following reasons:
- WASM is a mature, well-supported standard. It is supported by the major browsers, which means that trillion-dollar companies like Google, Microsoft and Apple have a vested interest in its success.
- WASM was designed from the ground-up to be a highly performant, lightweight, sandboxed runtime.
- There is fantastic tooling to convert Rust, C, Go, and [insert favorite language here] into WASM.
- WASM can run almost anywhere, but especially in the browser. Thus, the path to running Dapps safely in the browser is likely much easier.
On the other hand, eBPF was originally design as a packet-filter to protect networks from malicious data packets. It has since been extended to support a full-blown virtual machine, but it still feels a little like a square peg being banged into a round hole.
Ultimately, it was a fairly straightforward decision to go forward with WASM.
Independently, Stellar reached the same conclusion, for much the same reasons.
In contrast, while Polkadot does use WASM, there is an active discussion to replace Wasm with RISC-V. This VM is being designed explicitly for smart-contract environments and is worth watching.
For the time-being, WebAssembly is a pretty clear winner.
Change Log
Date | Change | Author |
---|---|---|
20 Dec 2023 | First draft | CjS77 |
RFC-0305/Consensus
The Tari Network Consensus Layer
Maintainer(s): Cayle Sharrock,stringhandler
Licence
Copyright 2022 The Tari Development Community
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Language
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as shown here.
Disclaimer
This document and its content are intended for information purposes only and may be subject to change or update without notice.
This document may include preliminary concepts that may or may not be in the process of being developed by the Tari community. The release of this document is intended solely for review and discussion by the community of the technological merits of the potential system outlined herein.
Goals
This Request for Comment (RFC) describe the consensus mechanism known as Cerberus as it is implemented in Tari. Tari implements the Cerberus variant known as Pessimistic Cerberus, for the most part, with Hotstuff BFT replacing pBFT as described in the Cerberus paper.
This RFC serves to document any deviations from the academic paper as well as finer-grained details of the implementation.
Related Requests for Comment
Introduction
The Tari DAN is based on a sharded BFT consensus mechanism called Cerberus.
One particular note is that Tari has chosen Hotstuff as the base BFT consensus algorithm over pBFT mentioned in the paper.
The core idea of Cerberus is that instead of dividing work up between validator nodes according to the contracts they are managing (as per Tari DANv1, Polkadot, Avalanche, etc.), Cerberus distributes nodes evenly over a set of state slots. Any time an instruction modifies the state of a contract, it will affect one or more state slot, and only those nodes that are responsible for covering those addresses will reach consensus on the correct state changes.
This means that nodes have to be prepared to execute instructions on any contract in the network. This does create a data synchronisation burden, but the added benefit of a highly scalable, decentralised DAN significantly outweighs this trade-off.
The consensus layer is logic agnostic
The first key point to make about the Cerberus layer is that it is logic agnostic. The consensus layer does not know anything about Tari, about digital assets, or smart contracts. It has one job:
Ensure that a super-majority of participating nodes agree on the state transition for every Tari transaction.
Defining the consensus layer in this way allows us to separate the concerns of the consensus layer from the concerns of the smart contract layer. This is important because it reduces the attack surface of the consensus layer, and allows us to develop the consensus layer in isolation from the smart contract, or "business logic" layer.
To be clear, if 67% percent of nodes decide that $1 + 1 = 3$ then that is the truth as far as the consensus layer is concerned.
This job can be subdivided into several smaller, co-ordinated tasks:
- Deterministic distribution of validator nodes across the state space, to form validator committees.
- Periodically re-distributing validator nodes across the state space to reduce the likelihood and opportunity for collusion.
- Efficient transmission of consensus messages to the rest of the network.
- Identifying and removing malicious nodes from the network.
- Correct identification of nodes participating in cross-shard consensus.
- Requesting and responding to state requests from other nodes.
- Reaching consensus on the state transition for a given transaction.
- Effective leader rollover in the case of a faulty leader.
- Guaranteeing liveness in the face of a Byzantine stoppage.
Distribution of validator nodes
Validator node selection and distribution is described in RFC-314. RFC-314 also covers the periodic re-distribution of validator nodes across the state space.
Efficient transmission of consensus messages to the rest of the network
The Tari communications layer is used to transmit consensus messages to the rest of the network. The Comms layer is described in RFC-170 and related sub-RFCs.
- Describe differences in configuration between the Tari and Minotari networks.
- Describe how VNC members find each other and how they keep in touch.
- Describe how banning or other sanctioning behaviour works.
- How client messages are propagated and routed to the correct nodes in the network.
- How consensus messages are communicated across the network.
Identifying and removing malicious nodes from the network
In the current proposal, malicious nodes are not actively removed from the network. Instead, they can be banned by peers, as described above, and then de-registered as validator nodes at an epoch transition.
This is still an indirect punishment, since a substantial deposit is required to register as a validator node. After de-registration, the deposit is locked up for a significant period (3-6 months). Therefore, a serial offender running bad validator nodes will incur a significant opportunity cost over time.
However, the community is open to other proposals, both game-theoretic and technical, for dealing with malicious nodes.
Many proof-of-stake systems utilise "slashing" to punish non-cooperative nodes. Slashing mechanisms sound good at first, but in fact, there are many edge cases that can result in honest-but-poorly-configured nodes being punished. We are somewhat sceptical that slashing will achieve their intended goals.
Slashing introduces
significant additional complexity,
including the need for additional tuning parameters, the need for 'watchtowers' to police the VN
set's behaviour (which is a centralising force), the need for trustless fraud-proofs (a non-trivial problem), and
the fact that software bugs don't follow the rules of economic game-theory (in other words, they're not rational).
Furthermore, slashing is less relevant in a BFT process where safety and liveness is guaranteed as long as 67% of the committee is honest. The motivation for punishing malicious nodes in Tari is essentially two-fold:
- to reduce the chance that a critical mass of 1/3 malicious nodes accumulate on the network.
- to deter nodes from colluding to try and achieve 33% (to break liveness) or 67% (to break safety).
One alternative to slashing os to make all VN deposits non-refundable. Therefore, a malicious node will implicitly have their deposit slashed once they are banned. Banning can also be made temporary, depending on the offense. Honest nodes will need to run for a period of time before they become profitable, akin to an apprenticeship, or 'paying your dues'. VN fees would be increased to compensate for this mechanism.
Overall, this strategy is very similar to slashing, but is simpler to implement and police.
Another option is to make use of the auditability and fraud-proof properties of Cerberus (See Section V.B of the Chainspace paper). This would allow retroactive punitive actions against malicious nodes, and in particular, colluding nodes that act together to subvert an entire validator node committee. This is an avenue worth exploring, since it's quite clear from the experience of incumbent proof-of-stake networks, controlling hundreds of billions of dollars of value, that essentially all slashing events are due to configuration errors or intentional bugs, rather than intentional attempts to bring the network down.
Identification of nodes participating in cross-shard consensus.
Every validator node is registered on the base layer. Therefore, anyone with a synchronised Minotari node will be in possession of the current set of validator nodes running the Tari network.
The rules for assigning a given validator node (with its public key) to a Tari shard are deterministic and described in RFC-314.
It therefore follows that every validator node must also run a Minotari node (or connect to one that they trust). This will provide all the information that they need to determine which VNs are part of every committee and therefore which nodes to contact when participating in cross-shard consensus.
Requesting and responding to state requests from other nodes.
State requests come from two primary sources:
- Other validator nodes requesting state that they need to process an instruction. They will typically request this state from peers in the braided consensus group as part of a consensus round, although there are opportunities to optimise this process through caching and pre-fetching via an Indexer.
- Clients (wallets, dApp users etc.) will usually request state from an Indexer that is following the history of a set of contracts on interest. Indexers are a trusted party. Users wanting to operate in a trustless environment will need to run their own indexer. Indexers are described in RFC-331.
Reaching consensus on the state transition for a given transaction.
Tari uses Cerberus in conjunction with HotStuff BFT to achieve consensus on substate transitions. This process is described in detail in RFC-330.
Effective leader rollover in the case of a faulty leader.
Leader rollover is also covered in RFC-330.
Guaranteeing liveness in the face of a Byzantine stoppage.
A liveness break will only occur if at least a third of nodes in a single VNC are actively or passively colluding to prevent consensus being reached. Successive leader rollovers will have failed to resolve the issue, and the transaction will become stuck.
Eventually, the entire network will stop functioning even though the network is sharded, because probabilistically, every contract will eventually produce a state change that required the Byzantine committee to be part of the consensus.
Therefore, it's critical that liveness can be forced relatively quickly and efficiently.
The basic strategy is that enough nodes vote to force an epoch change. Nodes need to provide proof of recent activity in order to participate in the new epoch. Nodes that cannot provide proof will be banned and de-registered as validator nodes.
The epoch change causes a validator node shuffle, and any remaining nodes that may have been preparing to collude will be assigned new shards.
Change Log
Date | Change | Author |
---|---|---|
16 Dec 2023 | Second draft | CjS77 |
30 Oct 2023 | First draft | CjS77 |
RFC-314/VNC Selection
Validator node committee selection
Maintainer(s): stringhandler and SW van heerden
Licence
Copyright 2022 The Tari Development Community
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Language
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as shown here.
Disclaimer
This document and its content are intended for information purposes only and may be subject to change or update without notice.
This document may include preliminary concepts that may or may not be in the process of being developed by the Tari community. The release of this document is intended solely for review and discussion by the community of the technological merits of the potential system outlined herein.
Goals
The goal of this RFC is to describe the process for allocating Validator Nodes (VNs) to Validator Node Committees (VNCs)
Intro
Validator nodes will have to group themselves into Validator Node Committees (VNC) for transactions/shard processing. Committees will be formed for each new transaction/shard processing. This committee needs to be determined pseudo-randomly, and we use the VN_Key as this changes periodically and is pseudorandom. Each VN will be allocated a unique and individual shard space to serve in as a VNC member.
Requirements
In order to ensure that VNCs are selected securely and can operate successfully, we pin down the following requirements:
- A percentage of the nodes must change to a new shard space every epoch (e.g. exactly 25%)
- A VN must not be able to determine its own shard space location ahead of time, i.e. the VN only knows it's new location once the block with the new epoch is mined.
- It must be easy, e.g. 𝓞(log n), to calculate the VN set. (You should have to replay all shuffles to calculate the current VN set).
- A VN must be able to be part of a committee for a certain period to allow time to sync state, before being penalised (if applicable) for not participating in consensus.
- Open Question: Should there be a limit (e.g. 10%) in the number of nodes that can join per epoch
VN-key expiry
Each new VN will get a VN-key on registration. This key will expire on some pseudorandom height. This is calculated as follows:
$$ \begin{aligned} \text{Expire height} = \text{(Current block height)} + \text{(min expire height)} + \text{new VN-pubkey } MOD \text{ ( max expire height)} \end{aligned} $$
Every time a new VN-Key is assigned a new expiry date is calculated. Because the [miner]s calculate the VN-key, they also calculate the new VN-key every time it expires.
Process of choosing committees when an instruction needs to be processed
For each instruction, the substates involved in this instruction MUST be known before they can be processed.
For each involved substate, the address of the substate is mapped to a shard. For each shard, the VN committee is
constructed from COMMITTEE_SIZE/2
VNs to the left and right of the shard,
in the [VNKey Merkle tree].
Thus, a single instruction will have a maximum N * COMMITTEE_SIZE
validator nodes processing it, when N is the number
of involved substates.
Committee Creation
Because we have the base layer where each VN needs to publish a registration transaction, we can get base_node and
miners to keep track of all active VNs. We represent all VNs in a (balanced) Merkle tree with the VN_keys as leaves. We
declare a constant COMMITTEE_SIZE
which can be changed in the consensus constants.
For the sake of simplicity, COMMITTEE_SIZE
MUST be an even positive integer.
As per the Cerberus algorithm, validator nodes are responsible for managing sub-states, rather than contract semantics. A given instruction may involve dozens of sub-states, meaning that there are potentially dozens of non-overlapping committees that are required to reach a braided consensus.
Each committee is determined independently. For each sub-state, a committee is formed by taking the
first COMMITTEE_SIZE / 2
VN_keys to the left and COMMITTEE_SIZE / 2
VN_Keys to the right of the sub-state's shard
address in the merkle tree.
This makes it very easy to determine what shard space a VN needs to serve, which states the VN needs to sync from peers, and who has them. Because the second layer has a delayed view of the network. VNs can also know beforehand and prep to ensure they are ready when new block heights appear.
An important edge case here that is implied but not listed, is that if the whole network is less or equal to
the COMMITTEE_SIZE
, then the whole network participates in the VNC.
Committee proofs
We construct the balanced Merkle tree from all active VN registration transactions and prune away all inactive ones.
Because the base nodes have to keep track
of the entire unspent UTXO set, it becomes easy for them to track and validate all active VN registration UTXOs. This
means we can keep base nodes
responsible for validating and constructing the Merkle tree. We commit this Merkle tree per block as a Merkle root
inside the block's header as the validator_node_set_root
.
This Merkle root in the header always lags by one block, meaning that the Merkle root is for the state of the VN's before the start of the block it's mined in. When a VN registers, that new VN key will only appear inside the Merkle tree in the next block. The reason for this is that header_hash is used in the the calculation process of the VN_key.
Option 2 for Committee proofs
The VN_key needs some random entropy that's not minable to ensure that a VN cannot choose its VN_key. This random
entropy is currently the block hash of the
block the VN registration was mined in. Hence the reason for the Merkle root lagging by one block. If we change this
entropy value be another source of
randomness, such as the utxo_merkle_root
, then we don't have to do the lag by 1, and it can all be calculated in the
single block.
Leader selection
The VNC leader should not be decided beforehand and must be chosen pseudorandomly only when a tx is published. We also need to select an order of leaders in the case, the first leader is offline/and or not responsive.
$$ \begin{aligned} vn_position_hash(vn, tx) = Hash(vn.signing_key || hash(tx)) \end{aligned} $$
This will give a sortable list we use to order the VNs for leader selection.
Change Log
Date | Change | Author |
---|---|---|
11 Oct 2022 | First outline | SWvHeerden |
RFC-0321/ProcessingForeignProposals
Processing Foreign Proposals
Maintainer(s): stringhandler
Licence
Copyright 2022 The Tari Development Community
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Language
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as shown here.
Disclaimer
This document and its content are intended for information purposes only and may be subject to change or update without notice.
This document may include preliminary concepts that may or may not be in the process of being developed by the Tari community. The release of this document is intended solely for review and discussion by the community of the technological merits of the potential system outlined herein.
Goals
This RFC describes the process of distributing and processing foreign proposals in the Tari DAN Cerberus Model
Across the entire network transactions must be processed or time out. When a transaction is started on a shard, it locks up substates, preventing other transactions from completing. Therefore if a transaction is started on a shard, it should complete or be aborted in a timely manner to release the resources.
Related Requests for Comment
None
Glossary
- Block - A second layer block, consisting of ordered commands
- Command - Command can either be Prepare, LocalPrepared, Accept, and moves a transaction into that state.
Description
To solve the above problems, we'll use reliable broadcast between shards and process foreign evidence in order.
In a local shard committee, the proposed block must include a reliable broadcast counter for each other shard. If the proposal includes transactions that involve other shards, this counter must be incremented. At the beginning of each epoch, all reliable broadcast counters must be reset.
When the proposed block becomes committed locally (i.e. it has a chain of 3 QCs validating it), the block must be broadcast to each involved shard that was incremented, along with evidence of being committeed (The chain of QCs must be included).
To ensure this, f+1
nodes in the local committee will forward this committed block to each relevant committee, along with a 3 chain of QC's proving it was committeed.
As a local committee member, when I receive a foreign proposal, if it is valid I will queue up a special command ForeignProposal(number, QC_Hash) that I must propose when I am next leader (if it has not been proposed already). I also should request all transaction hashes that I have not seen from involved_shards for each transaction in the proposal, and add them to my mempool for execution.
When processing transactions from a foreign, there are two methodologies we can try.
- Strict ordering
- Relaxed ordering
Strict ordering
In strict ordering, before transactions in the N+1
th foreign proposal for a shard, all transactions in the N
th foreign proposal for that shard must be sequenced into the local chain as either a ABORT(reason = Timeout) or a LOCALPREPARE(TxId, ForeignShardId).
This means that if a transaction is going to timeout, it will hold up all transactions in future proposals. While timeouts are expected to be rare when at least one honest node is able to provide the transaction, this approach could lead to
really long finalization times, slowing down all cross shard transactions. In addition, there may be potential for deadlocks, where state is locked for a long time while transactions wait to timeout.
Relaxed ordering
In relaxed ordering, transactions from foreign proposals can be processed in any order, but transactions must still timeout if they are not processed after a certain number of blocks from the FOREIGN_PROPOSAL command. This could lead to some strange behaviour where a transaction can be aborted due to double spends, even though the double spend happens much later in one shard. This however could happen even with strict ordering if the transactions arrive at different times.
Given the above, we shall use relaxed ordering unless future development reveals other problems.
NOTE: ForeignProposal commands can be proposed in between a previous ForeignProposal and LocalPrepare/Timeout commands, but commands from the ForeignProposal must only be proposed after all transactions in the first ForeignProposal have been sequenced. The TIMEOUT_TIME block is counted from the height where the ForeignProposal is sequenced.
ForeignProposal commands must appear in strict ascending order in the blockchain, but do not have to be in sequential blocks. In other words, for shard s, the block containing ForeignProposal(s, 1) must have a height lower than ForeignProposal(s, 2). Also, if a chain contains ForeignProposal(s, 1) and ForeignProposal(s, 3), then it must also contain ForeignProposal(s, 2).
If a node receives a foreign proposal (not the command), and it has not received the previous foreign proposal, then it should ask the committee to provide it to them.
Change Log
Date | Change | Author |
---|---|---|
17 Nov 2023 | First draft | stringhandler |
RFC-0325/DanEpochManagement
Epochs and time management
Maintainer(s): SW van Heerden
Licence
Copyright 2019 The Tari Development Community
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Language
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as shown here.
Disclaimer
This document and its content are intended for information purposes only and may be subject to change or update without notice.
This document may include preliminary concepts that may or may not be in the process of being developed by the Tari community. The release of this document is intended solely for review and discussion by the community of the technological merits of the potential system outlined herein.
Goals
The aim of this Request for Comment (RFC) is to describe the role of Epochs and time management on the DAN.
Related Requests for Comment
Motivation
For stability and security in the VNCs we have the following requirements:
- We need to know who the valid and active VNs are.
- VNCs need to be periodically shuffled to prevent shard targeting attacks.
- VNC members cannot be swapped on a whim and need to be stable to allow members to vote on and process instructions.
- Chain re-organisations on the Minotari chain should not have an effect on the VNC distribution.
- When swapping VNs from one [shard] to another, the VN has enough time to sync the required state before the swapping takes effect.
- When swapping VNs from one [shard] to another, the VNC must retain enough members so as to keep functioning.
DAN Lag
Because the Minotari chain influences the DAN layer and is used as a timing mechanism for the DAN, we need to ensure that the DAN has a stable view of the Minotari.
The Minotari is built on Proof-of-Work and this means that chain might undergo re-organisation. We need to ensure
that re-orgs do cause a VNC reshuffle. We introduce a concept called DAN Lag
which is an offset between when a
VNC change is recorded on the Minotari chain and when it takes effect.
For example, if we define DAN Lag
as 720 blocks, or a day. Then when a VN registers and that transaction is
mined in the Minotari at height 1000 then only at height 1720 will the DAN Layer recognise the VN as being
registered.
This DAN Lag
allows the Minotari chain to have small re-orgs of less than the DAN Lag
without it having any
effect on the DAN Layer.
An added benefit of this is that the DAN Layer knows all changes in advance of when it will happen, so when a VN is
swapped to a new shard space it will give it the DAN Lag
period to sync the required state.
DAN Grace Time
Minotari nodes are decentralized. Thus, we don't have a single point of view of the state of the chain.
The DAN operates in ms timeframes, while the Minotari chain runs at the minute timeframe, averaging 2 minutes per block. These blocks also take a few seconds to process and propagate through the network.
This means that for some VNs the Minotari block height might be 999, while for others it might be 1000. The practical problem for this is that we cannot base consensus decisions on the block height if the VNs cannot come to some consensus as to what the Minotari height is.
We define DAN Grace Time
or DGT as the number of blocks in the past or future of the VN's own current block height,
that it will accept. This is very similar to the FTL concept concerning the timestamp in the block header.
In example of this would be, that we have VN_1
whose base node is on height 999 and VN_2
whose base node is on
height 1000. We have set the DGT as 1.
An instruction has specified that from height 1000 the VNC that must process it contain both VN_1
and VN_2
, but
before that it is only VN_1
. From this it can be seen that VN_1
thinks only it must be in the VNC but VN_2
thinks they both need to be. But using the DGT of 1 block, VN_1
will accept VN_2
as part of the VNC
because it is withing the DGT period and it assumes that its own base node is simply lagging 1 block.
The DGT has the additional benefit of allowing a VN to finish processing an instruction after a block height has been reached.
With the example mentioned above if VN_1
has to stop processing an instruction at height 1000, but it started the
instruction at height 999. When height 1000 comes
along it can still continue to process the instruction till height 1001.
VN Epoch
We define VN Epoch
as the time period a VN must serve in a single [shard] before being moved to a different
[shard]. The purpose of this shuffling is to prevent having a single VNC cover the same shard for an extended
period of time and thus reduce the risk of VN collusion.
The shuffling algorithm can be linked to the VN registration hash. This provides a random and uniformly distributed seed that allows us to shuffle the VNs around the shard space in a deterministic way. The algorithm can also be constructed such that a minority of nodes are shuffled at every epoch, while still maintaining equal-sized VNCs.
Summary
VNs must re-register periodically. This is to ensure that the VNs are still active and to prevent a VN from registering once and then never being removed from the VN registry. A new shard key is generated each time that a VN re-registers.
(Open question: Is the re-registration fee cheaper than the initial registration fee? Is it zero?)
VNs that miss their re-registration deadline will automatically be de-registered. (Open question: Is the registration deposit lost in this case?) Therefore it is always possible to maintain a list of recently active VNs.
The DAN Lag
gives VNs enough time to sync their new shard's state.
VN are shuffled periodically, but a minority of nodes are shuffled every epoch.
Tuning the values
We need to ensure that all the consensus values defined in the RFC needs to be tuned with the following in mind:
DAN Lag
The DAN Lag
must be long enough that small frequent re-orgs dont have an effect on the DAN layer. This must also be
long enough to give any new VN ample time to download any required state for [shard] it will cover.
DAN Grace Time
This must be only a block or two. This is just to handle the edge case of what happens if a VN's node has not yet seen a new height, or the height changes while processing an instruction
VN Epoch
This must be long enough so that VNs don't flood the network with sync requests and are able to spend most of their
time processing instructions.
It must clearly be longer than the DAN Lag
.
It must be short enough that we don't allow VNs to collude and carry out sharding attacks.
The epoch must also be short enough that we can effectively remove inactive VNs from the VN registry.
Change Log
Date | Change | Author |
---|---|---|
19 Oct 2022 | First outline | SWvHeerden |
RFC-0330/Cerberus
The Tari Cerberus-Hotsuff Consensus Algorithm
Maintainer(s): Cayle Sharrock,stringhandler
Licence
Copyright 2022 The Tari Development Community
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Language
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as shown here.
Disclaimer
This document and its content are intended for information purposes only and may be subject to change or update without notice.
This document may include preliminary concepts that may or may not be in the process of being developed by the Tari community. The release of this document is intended solely for review and discussion by the community of the technological merits of the potential system outlined herein.
Goals
This Request for Comment (RFC) describe the consensus mechanism known as Cerberus as it is implemented in Tari. Tari implements the Cerberus variant known as Optimistic Cerberus, for the most part, with Hotstuff BFT replacing pBFT as described in the Cerberus paper.
This RFC serves to document any deviations from the academic paper as well as finer-grained details of the implementation.
Related Requests for Comment
Introduction
The Tari DAN is based on a sharded BFT consensus mechanism called Cerberus.
One particular note is that Tari has chosen Hotstuff as the base BFT consensus algorithm over pBFT mentioned in the paper.
The core idea of Cerberus is that instead of dividing work up between validator nodes according to the contracts they are managing (as per Tari DANv1, Polkadot, Avalanche, etc.), Cerberus distributes nodes evenly over a set of state slots, called substates. Any time an instruction modifies the state of a contract, it will affect one or more substates, and only those nodes that are responsible for covering those addresses will reach consensus on the correct state changes.
This means that nodes have to be prepared to execute instructions on any contract in the network. This does create a data synchronisation burden, but the added benefit of a highly scalable, decentralised DAN significantly outweighs this trade-off.
Shards, substates and state addresses
The central idea of Cerberus is that all possible state objects are assigned a unique address, deterministically. Know the provenance of the state, know the address 1. The state space is incredibly large, with 2^256 possible substate addresses; which is way more than the number of atoms in our galaxy. The chance of any two pieces of state ever trying to occupy the same substate is vanishingly small.
The state space is also evenly divided into contiguous sections, called shards. Each shard covers a set of non-overlapping substate addresses and the full set of shards covers the entire state space.
Each validator node registered on the base layer is randomly assigned a shard to cover. The number of shards depends on the total number of validator nodes in the network.
Collectively, all the nodes covering the same shard are known as a validator (node) committee (VNC).
Broadly speaking, the shard-assignment algorithm will try to arrange things in a way that every shard has the same number of validator nodes covering it.
The number of nodes in a VNC is set system-wide. The final number has not been determined yet, but it will be a value, 3n+1, where n is an integer between 8 and 33, giving a committee size of between 25 and 100 nodes.
As nodes continue to join the network, the target committee size stays fixed, whereas the shard size will shrink. This is what will allow the Tari network to scale to achieve thousands of transactions per second.
Every substate slot can only be used once. The substate lifecycle is
Empty
. No state has ever been stored in this slot.Up
. A transaction output has resulted in some object being stored in this substate slot.Down
. The state in this slot has changed. We mark the substate as 'down' to indicate that the state is no longer valid. Once a substate is down, it can never be used again 2.
This is a simplification to convey the general idea. The address derivation procedure is explained in full below.
It's possible that substates could be reset, decades in the future, if substate address collisions become a risk. For now, we treat all down substates as permanently unusable.
Braided consensus
A question that naturally arises whenever sharded distributed networks are discussed is, what happens when cross-shard communication happens. With Cerberus, the procedure is that affected shards come together to form a temporary Hotstuff consensus group, and reach agreement on the correct outcome of the instruction.
A correct outcome is one of:
Abort
: The instruction was invalid, and any state changes are rolled back such that the instruction never happened.Commit
: All input substates for the instruction will be set toDown
, and at least one new substate will be marked toUp
(fromEmpty
).
Achieving this outcome entails a fairly complicated dance between the participating nodes3:
- When nodes receive an instruction that affects contract state, the nodes determine the input substates that will
be consumed in the instruction. This substates MUST currently all be in an
Up
state. If any input state isEmpty
, orDown
, the nodes can immediately voteAbort
on the instruction. - Assuming all input states are valid, nodes will then pledge these substates, effectively marking them as pending
Down
. - Then we have cross-shard exchange. Every leader for the round will forward the instruction and the pledged states to all other nodes in the wider consensus group.
- Nodes wait until they have received the transaction and pledges from all the other committee leaders. Otherwise they time-out, and ???.
- Once this is complete, and all pledges have been received, nodes decide within their local committee whether to
Commit
orAbort
. This procedure proceeds via Hotstuff consensus rules and takes several rounds of communication between the local leader and the committee members.
For a more formal treatment, refer to Pessimistic-Cerberus in the Cerberus paper.
Cerberus consensus - a diagram
Transaction processing for Pessimistic Cerberus follows the following broad algorithm:
- A client broadcasts a transaction to multiple validator nodes, ensuring that at least one node from every shard that covers the inputs for the transaction receives the transaction. In practice, this can be achieved by communicating with a single node, and the node shoulders the responsibility of broadcasting the transaction to the rest of the network, including to every node in the node's local VNC.
- When a transaction is received by a validator node, it checks to see if at least one input for the transaction is in the node's shard space. If not, it may ignore the transaction. Otherwise, it forwards the message to the current round leader.
- Soon, every round leader for the shards that contain affected inputs will have received the transaction.
- The affected shards then begin the local consensus phase. This consists of a full Hotstuff consensus chain with the leader proposing a new block containing the transaction, and the committee members voting on the block.
- At the same time, all local inputs (the subset of transaction inputs covered by the local shard) are marked as
Pledged
. If any input is already marked asPledged
, the transaction immediately resolves asAbort
. This step prevents double-spending of inputs across concurrent transactions in separate shards. Note that if a double-spend is attempted by submitting the two transactions to different shards, then both transactions will be aborted, since Cerberus does not have a way to determine which transaction was 'first'. - If every input is in a single shard, then the local consensus is sufficient to finalise the outcome of the transaction (proceeding to execution phase as described below), and the result can be broadcast to the client.
- Otherwise, the leader of the round broadcasts the transaction, some metadata, and the local input state to the other shards leaders involved in the transaction. Notice that up until this point, execution of the transaction is impossible in a multi-shard transaction because no node has all the input state it needs to run the transaction instruction. The local consensus phase is solely to determine the validity of the transaction from an input and double-spend perspective.
- When every shard leader has received a message from every other shard leader participating the transaction, and
none of the messages received was
Abort
, then the shards can begin the global consensus phase. - Round leaders transmit the received messages to the rest of their VNC.
- Each VN checks that the state received from each foreign shard corresponds to the inputs in the transaction. If
not, the VN can immediately vote
Abort
. - At this point, the shard leaders have all the state they need to execute the transaction. Execution is handed off
to the TariVM which returns a new set of state objects as output. It is important to note that if a transaction
execution returns an error (because someone tried to spend more than they have, for example), then this _does
not lead to an
Abort
decision!` - Each shard executes the transaction independently, and another Hotstuff consensus chain is produced to achieve
consensus on the resulting output set. If the transaction is
Abort
, then all pledged inputs are rolled back. Otherwise, the decision isCommit
, and the pledged inputs are marked asDown
. Any output objects that belong in the current shard can be marked asUp
, and the transaction result is broadcast to the client as well as other shards that need to mark new substates asUp
as a result of the transaction output.
A mermaid flow diagram of the above process is shown below:
flowchart TD
A[Client] --> B([Broadcast transaction])
B --> C{Any tx inputs in my shard?}
C --> |Yes| D[Forward to leader]
C --> |No| E[Ignore transaction]
D -.-> L
L[Leader] ==> BL([ Broadcast message to VNC ])
BL -.-> |PREPARE| LN[VNC nodes]
subgraph Local_Consensus
LN --> G{Any inputs already pledged?}
G --> |Yes.
Vote PREPARED_ABORT | LC
G --> |No| I[Pledge inputs]
I --> |Vote PREPARED_COMMIT| LC[[Consensus on pledges]]
LC --> LCD{Consensus on PREPARED?}
LCD --> |No| LocalStall[Local liveness break!]
end
LCD --> |Yes| Pledge[Pledge inputs]
Pledge --> SS{ Single shard tx? }
SS --> |Yes| EX1[[Execute transaction]]
EX1 --> SSC[[Consensus on result]]
SSC --> GCD
ssCommit --> |No| Abort[Abort!]
ssCommit --> |Yes| createSubstates[[Substate creation]]
SS -.-> |No| BLC([ Send LOCAL_PREPARED_*
& local state to other Leaders ])
BLC -.-> Leaders
Leaders -.-> |Forward to VNC| Fwd[Validator node]
subgraph Global_consensus
Fwd --> wait{Are ANY messages
LOCAL_PREPARED_ABORT or
timed out? }
wait --> |Yes
Vote SOMEPREPARED_ABORT| globalConsensus
wait --> |No| EX[[Execute transaction]]
EX --> |Vote ACCEPT_*| globalConsensus[[Intershard Consensus on result]]
end
globalConsensus --> GCD{Consensus on ACCEPT_*?}
GCD --> |No.| Stall[Abandon block!]
GCD--> |Yes| ssCommit{Decision == ACCEPT_COMMIT?}
The process above describes PCerberus in general, with some modifications from Chainspace. There are a few details that need some additional explanation. In particular, this includes substate address derivation and state synchronisation.
Substate address derivation
A substate contains two pieces of information:
- The value of the substate object,
- The version number of the substate object.
The substate address is the universal location of the substate in the 256-bit state space. Most substate addresses are derived from a hash of their id, the provenance of which depends on the substate value type, and their version.
The substate value depends on the type of data the value represents. It is exactly one of the following:
- Component - A component is an instantiation of a contract template.
- Resource - A resource represents a token. Tokens can be fungible, non-fungible, or confidential. The
Resource
substate does not store the tokens themselves, but serves as the global identifier for the resource. The tokens themselves are kept inVaults
, orNonFungible
substates. - Vault - Resources are stored in Vaults. Vaults provide generalised functionality for depositing and withdrawing their resources from the vaults into Buckets.
- NonFungible - A substate representing a singular non-fungible item. Non-fungible items are always associated with
their associated non-fungible
Resource
. - NonFungibleIndex - A substate that holds a reference to another substate.
- UnclaimedConfidentialOutput - A substate representing funds that were burnt on the Minotari layer and are yet to be claimed in the Tari network.
- TransactionReceipt - A substate recording the result of a transaction.
- FeeClaim - To prevent a proliferation of dust-like value transfers for every transaction due to fees, a fee claim is generated instead that allows VNs to aggregate fees and claim them in a single batched transaction at a later time. Fee claims remain in the up state forever to prevent double claims.
Substate ids are domain-separated hashes of their identifying data, which depends on substate type as follows:
- Component - Component addresses are derived from the hash of the component's contract template id and a component id. The component id is typically a hash of the origin transaction's hash and a counter.
- Resource - Generally a unique, random 256-bit integer, derived from the hash of the transaction hash and a counter. Some ids for special resources are hard-coded.
- Vault - Vault ids are a unique, random 256-bit integer, derived from the hash of the transaction hash and a counter.
- NonFungible - The id of a non-fungible item is derived from the item's resource address, and its id. The id depends on the specifics of the NFT, and could be an integer, a string, a hash, or a uuid.
- NonFungibleIndex - Non-fungible index ids are derived from the resource they are pointing to and an index offset.
- UnclaimedConfidentialOutput - The UCO id is derived from the burn commitment on the Minotari layer.
- TransactionReceipt - The id of a transaction receipt is the hash of the associated transaction.
- FeeClaim - The id of a fee claim is derived from the epoch number and the validator's public key.
State synchronisation
Change Log
Date | Change | Author |
---|---|---|
17 Dec 2023 | Second draft | CjS77 |
30 Oct 2023 | First draft | CjS77 |
RFC-0304/Consensus
The Tari Network Consensus Layer
Maintainer(s): stringhandler, Stanley Bondi
Licence
Copyright 2022 The Tari Development Community
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Language
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as shown here.
Disclaimer
This document and its content are intended for information purposes only and may be subject to change or update without notice.
This document may include preliminary concepts that may or may not be in the process of being developed by the Tari community. The release of this document is intended solely for review and discussion by the community of the technological merits of the potential system outlined herein.
Goals
This Request for Comment (RFC) describes operation of Tari network Indexers. Indexers are a key actor in providing rapid, up-to-date and accurate information about the state of Tari contracts to client applications.
Related Requests for Comment
Introduction
Since the Tari network is designed to scale to hundreds of thousands of contracts, and millions of transactions per hours, having a global state tracking system, like Etherscan, is neither feasible nor advisable. Instead, client applications (wallets, ticket apps, exchange front-ends etc) will run an Indexer that follows the state of a finite set of contracts of interest all across the shard-space.
indexer_lib
in the DAN source
code.
The indexer application has additional functionality (such as a copy of the Tari engine for executing dry runs of transactions) that is outside the domain of what the indexer is responsible for: maintaining a database of contract state, and delivering that state to client applications.
You can think of Validator nodes as staying in position and managing the state of a fixed set of addresses, possibly having to operate instructions for thousands of different contracts, while Indexers are constantly hopping around the shard-space, following the progress of a fixed set of contracts wherever their state goes.
Figure 1 illustrates this dynamic:
The indexer library work via three inter-oparting modules: substate scanning, substate decoding, and the substate cache.
Substate scanning
Given a substate address, the indexer will obtain the state for that substate, if it exists, using the following algorithm:
- If the substate is in the cache, retrieve the cached value. Query the network for the next version, and if it does not exist, return the cached version.
- If the cache misses, or the network indicates that the cache is stale because the next version does exist then:
- If the next substate version's state is
UP
, update the cache and return the substate. - Otherwise, if the next substate version's state is
DOWN
, query the network until the current version is found. - If the cache misses, and the network indicates that the substate does not exist, then the substate address is not valid.
- If the next substate version's state is
- After retrieving the latest version from the network, update the cache before returning the substate.
Substate decoding
The substate decoding module decodes the state at a given substate address, and collects any referenced substate addresses contained within. This is done recursively until all substates have been decoded.
This is the primary mechanism for discovering the state of a contract, and allowing it to solely track updates to the state of contracts the indexer is interested in. This is a key aspect of Tari's scalability. Without this, we would fall back into the trap of having a global state machine, which axiomatically, does not scale.
Substate cache
The substate cache reduces the amount of network traffic by storing the state of substates of interest. When a request for a substate is made, the cache is checked first. If the substate is not in the cache, the network is queried for the substate, potentially making many queries to find the latest version of the substate.
If the substate is in the cache, the cache may be checked to see if the substate is stale. Sometimes (e.g. for transaction dry runs), we may simply be optimistic and accept the cached value as the latest version.
For consensus, the approach is more conservative and the network is consulted to verify the freshness of the cached value.
The current implementation of the substate cache uses a local file-based cache and is highly performant and designed with concurrency in mind.
Change Log
Date | Change | Author |
---|---|---|
20 Dec 2023 | First draft | CjS77 |
23 Oct 2023 | Placeholder text | CjS77 |
Proposals
RFC documents in this section are not part of the Tari specification, and for this reason are not placed in the preceding sections. However, they describe useful applications of the Tari protocol that may be built on top of the core technology.
RFCs in this section will remain in status until they are implemented in some fashion in a wallet, standalone application, or in the RPC protocols, at which point they can be marked as .
If the underlying protocol changes to the point that an application is no longer implementable in its current state, it must be updated to or else deprecated and moved to the Deprecated RFCs chapter.
RFC-0123/ReplayAttacks
Mitigating One-sided payment replay attacks
Maintainer(s): Cayle Sharrock and S W van heerden
Licence
Copyright 2021 The Tari Development Community
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Language
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as shown here.
Disclaimer
This document and its content are intended for information purposes only and may be subject to change or update without notice.
This document may include preliminary concepts that may or may not be in the process of being developed by the Tari community. The release of this document is intended solely for review and discussion by the community of the technological merits of the potential system outlined herein.
Goals
The aim of this Request for Comment (RFC) is to describe ways we can block replay attacks related to one-sided payments using TariScript.
Related Requests for Comment
Replay attack
Replay attacks are "replaying" old messages to deceive the receiver about the message's authenticity. With TariScript, a vulnerability exists where a replay attack can occur under certain conditions, even with the current consensus rules.
For this attack to work, we need Alice and Charlie to collude to steal some of Bob's funds:
- Alice sends a one-sided transaction to Bob.
- Bob spends this UTXO to Charlie.
- Bob has to spend this and only this UTXO alone to Charlie with zero change.
- Alice sends a new one-sided transaction to Bob, creating the exact same output as before
- Alice shares the Blinding factor of the UTXO with Charlie
- Charlie can now claim this UTXO by replaying his old transaction
- Charlie has the signatures to spend the scripts, sign for the changes, etc.
- Because the previous transaction contains no other inputs, Charlie only has to provide signatures for this one UTXO.
- Because there is no change UTXO, Charlie has the keys for all the outputs in the transactions and can thus add another transaction or input /output to make sure the kernel excess signature is unique.
This does not work if Bob includes another UTXO in the transaction to Charlie due to the script offset. Although Charlie has the blinding factor, for the one UTXO, he does not have the script offset. Charlie can create a new kernel signature unique for blockchain consensus with the blinding factor. Still, because the script offset needs to balance as well, and he does not know the private keys for this, he needs to use this as is, meaning he needs to use an exact copy of the transaction. If the transaction includes any UTXO that he does not know the blinding factor of, he cannot create a new kernel excess signature. Meaning it won't pass consensus rules.
Solutions
This is a very niche attack that will only be useful under certain circumstances, but never less still needs to be addressed.
Sign with chain information
If we require as part of the script signature challenge that we sign the mined block height of that UTXO, it will ensure that Charlie cannot replay the signatures that Bob provided on the Input to spend the output, as each duplicate commitment will have its own block height. This is ensured as we currently have a limit that a commitment must be unique in the unspent set.
Advantages
- Does not require any more on-chain information
Disadvantages
- Reorged transactions cannot be put back in if the inputs are now spent at different heights
Enforce global commitment uniqueness
Alice cannot send the same one-sided UTXO to Bob if we require the commitment to be globally unique. This does mean that pruned nodes needs to track the spent TXO set's commitment and the UTXO set.
advantages
- Safely reorg transactions
disadvantages
- Pruned node needs to save extra data about the spent set.
- Syncing pruned nodes need to provide extra info to ensure that the downloaded list of commitments is correct
- Without requiring extra information in the header, pruned nodes need to download the entire TXO set and compare this to the output_mmr root.
Change Log
Date | Change | Author |
---|---|---|
2022-10-19 | Minor editorial changes | CjS77 |
2022-10-10 | First outline | SWvheerden |
RFC-0141/SparseMerkleTrees
Sparse Merkle Trees: A mutable data structure for TXO commitments
Maintainer(s): CjS77
Licence
Copyright 2023 The Tari Development Community
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Language
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as shown here.
Disclaimer
This document and its content are intended for information purposes only and may be subject to change or update without notice.
This document may include preliminary concepts that may or may not be in the process of being developed by the Tari community. The release of this document is intended solely for review and discussion by the community of the technological merits of the potential system outlined herein.
Goals
This Request for Comment (RFC) proposes replacing the current Mutable Merkle Mountain Range (MMMR) data structure used for tracking the commitment to the UTXO set, with a Sparse Merkle tree (SMT).
Related Requests for Comment
Description
Sparse Merkle trees
A sparse Merkle tree (SMT) is a Merkle-type structure, except the contained data is indexed, and each datapoint is placed at the leaf that corresponds to that datapoint’s index. Empty nodes are represented by a predefined "null" value.
Since every empty node has the same hash, and the tree is sparse, it is possible to prune the tree in a way such that only the non-zero leaf nodes and placeholders marking the empty sub-trees are stored. Therefore, SMTs are relatively compact.
A major feature of SMTs is that they are truly mutable. The current UTXO Merkle root in Tari is calculated using a Merkle Mountain Range (MMR). This has some drawbacks:
- MMRs are immutable data structures, and therefore as a workaround (some would say, hack), a bitmap is appended to the MMR to mark the spent outputs. In Tari's implementation, a roaring bitmap is used, which takes advantage of compression, but even so, it is still fairly large and will grow indefinitely.
- The Merkle tree must keep a record of all TXOs forever, and mark them as they are spent. The blockchain cannot prune STXOs from the set.
- The root is path-dependent. Let's say that the UTXO merkle root currently has a value
R1
. When you add a UTXO to the set, to giving a new Merkle rootR2
, say, and then immediately remove the UTXO, the Merkle root will now be someR3
and notR1
as you might expect. This path dependence also extends to the order of adding UTXOs. Adding UTXOA
thenB
yields a different root toB
thenA
.
SMTs are true mutable data structures and do not have these drawbacks.
- No tracking bitmap is needed. When a UTXO is spent, it can be deleted from the tree.
- It is possible to prune STXOs from the UTXO set to calculate the Merkle root.
- Adding and removing UTXOs in any order will always yield the same Merkle root. Adding and then deleting a UTXO from the set will result in the same Merkle root as before the UTXO was added.
Inclusion and exclusion proofs
Inclusion proofs for the current MMMR structure are possible but clunky, since the entire bitmap state must be included with the Merkle tree proof.
Exclusion proofs are not possible in the current MMMR implementation, unless an output happens to be a spent output. In this "STXO proof", the form of the proof is identical to the inclusion proof, with the verifier checking that the bit corresponding to the TXO is set, rather than unset.
SMTs support inclusion and exclusion proofs, and they are both succinct, O(log n), representations of the tree.
Space savings
In terms of space, the SMT is more efficient than MMMRs and the advantage grows with time.
For an SMT, to calculate the root of the UTXO set, all you need is the UTXO set itself, assuming the commitment is used as the tree index.
Consider some representative numbers:
Let's assume there are 1,000,000 UTXOs, with another 2,000,000 UTXOs having being spent over the lifetime of the project. A busy blockchain might achieve this level of traffic in a few days.
If each commitment-UTXO hash pair is 64 bytes, you need serialize 64MB to recreate the Merkle root for the SMT.
For the MMMR, even though you only need the UTXO hash, you need all 3,000,000 values (96MB) plus approximately 1MB for every million hashes in a bitmap to indicate which hashes have been deleted (3 MB) for a total of 99MB.
This only gets worse with time. Over a period of a year, a busy blockchain might have 100,000,000 spent transaction outputs. However, the UTXO set will grow far more slowly, and perhaps only 10x in size to 10 million outputs.
The SMT requires serialising 640MB of data to recreate the root, whereas the MMMR now requires 3.6GB of data.
Implementation
The proposed implementation assumes that a key-value store for the data exists. The Merkle tree is only concerned with the index and the value hash, as opposed to the value itself.
When constructing a new tree, a hashing algorithm is specified. As indicated, the "values" provided to the tree must already be a hash, and should have been generated from a different hashing algorithm to the one driving the tree, in order to prevent second pre-image attacks.
To insert a new leaf, the key is used to derive a path through the tree. Starting with the most significant bit, you move down the left branch if the bit is zero, or take the right branch if the bit is equal to one. Once a terminal node is reached, the node is replaced with a new sub-tree with the existing terminal node and the new leaf node forming the children of the last branch node in the sub-tree. The depth of the sub-tree is determined by the number of matching bits of the respective keys of the two nodes.
To delete a node, the procedure above is reversed. This entails that a significant portion of the tree may be pruned when deleting a node in a highly sparse region of the tree.
The null hashes representing the empty sub-trees are treated identically to the leaf nodes.
Thus branch hashes are calculated in the usual way, inter alia, H_branch = H(Branch marker, H_left, H_right)
,
irrespective of whether the left or right nodes are empty or not.
Domain separation SHOULD be used to distinguish branch nodes from leaf nodes. This also mitigates second pre-image attacks if the advice above is not followed and the values are hashed with the same algorithm as the tree.
For leaf node hashes, the key MUST be included in the hash. This prevents leaf node spoofing of the pruned tree.
Therefore, leaf node hashes are of the form H_leaf = H(Leaf marker, H_key, H_value)
.
A proof of concept implementation has been written and submitted for review in PR #5457. The examples that follow assume this implementation.
Example
Let's create a SMT with four nodes.
If we insert the nodes at
- A: 01001111 (79 in decimal)
- B: 01011111 (95 in decimal)
- C: 11100000 (224 in decimal)
- D: 11110000 (240 in decimal)
you will notice that the first two diverge at the fourth bit, while the first and last pairs differ at the first bit. This results in a SMT that looks like this:
┌──────┐ ┌─────┤ root ├─────┐ │ └──────┘ │ ┌┴┐0 ┌┴┐1 ┌──┤ ├──┐ ┌──┤ ├───┐ │ └─┘ │ │ └─┘ │ ┌┴┐00 ┌┴┐01 ┌┴┐10 ┌┴┐11 │0│ ┌──┤ ├──┐ │0│ ┌─┤ ├─┐ └─┘ │ └─┘ │ └─┘ │ └─┘ │ ┌┴┐010 ┌┴┐011 110┌┴┐ ┌┴┐111 ┌─┤ ├─┐ │0│ │0│ ┌─┤ ├─┐ │ └─┘ │ └─┘ └─┘ │ └─┘ │ ┌┴┐ ┌┴┐ ┌┴┐ ┌┴┐ │A│ │B│ │D│ │C│ └─┘ └─┘ └─┘ └─┘
Figure 1: An example sparse Merkle tree.
The merkle root is calculated by hashing nodes in the familiar way.
Of note is that when we delete all the nodes, the SMT hash is 000...
, as expected. The MMMR
will never have a hash that's the same after adding, and then deleting the same node because of the bitmap tracking
deleted entries.
So even though the SMT is slower, it is still fast enough. The bandwidth savings are substantial and the privacy benefits are significant.
Benchmarks
Details
The SMT implementation is faster than MutableMMR
up to around 10,000 nodes, and then the average depth starts to
affect it. By 1,000,000 nodes, it is significantly slower than MMR.
This makes sense since MMR is basically O(1) for inserts, while SMT is O(log(n)).
A rudimentary benchmark test yielded the following results
Starting: SMT: Inserting 1000000 keys
Finished: SMT: Inserting 1000000 keys - 1.921310493s
Starting: SMT: Calculating root hash
Tree size: 1000000. Root hash: 3e42ca40df366db52464c19b6ba71428976a56d7b120bc3c882fc29bf05dc1d7
Finished: SMT: Calculating root hash - 644.226062ms
Starting: SMT: Deleting 500000 keys
Finished: SMT: Deleting 500000 keys - 863.873761ms
Starting: SMT: Calculating root hash
Tree size: 500000. Root hash: 2a7b51f114a17c229f1067feb4ba5b6aad975689160a5eab0d90f89a3bcf09f8
Finished: SMT: Calculating root hash - 207.30907ms
Starting: SMT: Deleting another 500000 keys
Finished: SMT: Deleting another 500000 keys - 850.606501ms
Starting: SMT: Calculating root hash
Tree size: 0. Root hash: 0000000000000000000000000000000000000000000000000000000000000000
Finished: SMT: Calculating root hash - 3.892µs
Starting: MMR: Inserting 1000000 keys
Finished: MMR: Inserting 1000000 keys - 741.641704ms
Starting: SMT: Calculating root hash
Tree size: 1000000. Root hash: da6135ccaabf146024cae1b0e7ad6ba7e9dad79724fb9199b721d4cd243ba999
Finished: SMT: Calculating root hash - 8.649µs
Starting: MMR: Deleting 500000 keys
Finished: MMR: Deleting 500000 keys - 6.525858ms
Starting: SMT: Calculating root hash
Tree size: 500000. Root hash: fd60e168f27acba374109de9b8231e7252f0cfdf385f87dbfd92873d4956c995
Finished: SMT: Calculating root hash - 50.276µs
Starting: MMR: Deleting another 500000 keys
Finished: MMR: Deleting another 500000 keys - 6.862469ms
Starting: SMT: Calculating root hash
Tree size: 0. Root hash: 5d70f3177a0b46ea1b853c58d5e3f7d6e78cbc4149d71592bb6cea63d50ed96c
Finished: SMT: Calculating root hash - 93.618µs
The SMT is taking 1.92s to insert 1 mil nodes, or 1.9us per node on average on a 2018 Intel i9 Macbook Pro.
This is still sufficiently fast for our purposes and the benefits of having a truly mutable data structure, succinct inclusion and exclusion proofs, and significant serialisation savings far outweigh the performance costs.
Specification
Define the following constants:
Name | Type | Value |
---|---|---|
EMPTY_NODE_HASH | bytes | [0; 32] |
LEAF_PREFIX | bytes | b"V" |
BRANCH_PREFIX | bytes | b"B" |
KEY_LENGTH_BYTES | integer | 32 |
Node types
Each Node
in the tree is either, Empty
, a Leaf
, or a Branch
. Every node in the tree is associated with a
hash function, H
that has a digest output length of KEY_LENGTH_BYTES
.
Leaf nodes store the key and value in their data property. Leaf nodes are immutable, and MAY cache the hash value for efficiency.
Branch nodes contain two Node
instances, referring to the left and right child nodes respectively. Branch nodes
MAY store additional data, such as the height of the node in the tree, the key prefix and the node's hash value, for
performance and efficiency purposes.
Default empty nodes always have a constant hash, EMPTY_NODE_HASH
.
In summary, the nodes are defined as:
pub struct LeafNode<H> {
key: NodeKey,
hash: NodeHash,
value: ValueHash,
hash_type: PhantomData<H>,
}
pub struct EmptyNode {}
impl EmptyNode {
pub fn hash(&self) -> &'static NodeHash {
&EMPTY_NODE_HASH
}
}
pub struct BranchNode<H> {
// The height of the branch. It is also the number of bits that all keys below this branch share.
height: usize,
// Only the first `height` bits of the key are relevant for this branch.
key: NodeKey,
hash: NodeHash,
// Flag to indicate that the tree hash changed somewhere below this branch. and that the hash should be
// recalculated.
is_hash_stale: bool,
left: Box<Node<H>>,
right: Box<Node<H>>,
hash_type: PhantomData<H>,
}
Leaf node values
This specification outsources hashing the value data to an external service. 'Value' data in terms of the
specification refers to the hash of the value data. The hashing algorithm used to hash the data SHOULD be different
from H
, to prevent second preimage attacks.
The digest length of the value hashes does not need to be KEY_LENGTH_BYTES
, but it MUST be a constant predefined
length.
Node hashes
Empty nodes
As described above, empty nodes always return EMPTY_NODE_HASH
as the hash value.
Leaf nodes
The definition of a leaf node's hash is
H::digest(LEAF_PREFIX || KEY(32-bytes) || VALUE_HASH)
The key MUST be included in the hash. Imagine every leaf node has the same value. If the key was not hashed, there would be many different tree structures that would yield the same tree root. Specifically, any tree could replace a leaf node with a different leaf node with the same key prefix corresponding to the height of the original leaf node without changing the root hash.
Branch nodes
The definition of a branch node's hash is
H::digest(BRANCH_PREFIX || height || key_prefix || left_child_hash || right_child_hash)
where
height
is the height of the branch in the tree, where the root node is height 0.key_prefix
is the common prefix that the key of every descendent node of this branch will begin with.key_prefix
isKEY_LENGTH_BYTES
long, and every bit after the prefix MUST be set to zero. This means that key prefixes are not unique. For example, every key prefix for the left-most path down the tree will always have a prefix of[0; 32]
. The height parameter helps disambiguate this.left_child_hash
andright_child_hash
are the hashes of the left and right child noes respectively, and have lengthKEY_LENGTH_BYTES
.
Tree structure
The Merkle tree is built on top of an underlying dataset consisting of a set of (key, value) tuples.
The key fixes the position of each dataset element in the tree: starting from the root, each digit in the binary
expansion indicates whether we should follow the left child (next digit is 0) or the right child (next digit is 1),
see Figure 1. The length of the key (in bytes) is a fixed constant of the tree, KEY_LENGTH_BYTES
, larger than 0.
Rather than explicitly creating a full tree, we simulate it by inserting only non-zero leaves into the tree whenever a new key-value pair is added to the dataset, using the two optimizations:
- Each subtree with exactly one non-empty leaf is replaced by the leaf itself.
- Each subtree containing only empty nodes is replaced by a constant node with hash value equal to
EMPTY_HASH
.
Root Hash Calculation
The Merkle root of a dataset is computed as follows:
- The Merkle root of an empty dataset is set to the constant value
EMPTY_HASH
. - The Merkle root of a dataset with a single element is set to the leaf hash of that element.
- Otherwise, the Merkle root is the hash of the branch node occupying the root position. The child hashes are calculated recursively using the definitions above.
Adding or updating a key-value pair
Adding a new node or updating an existing one follows the same logic. Therefore, a single function, upsert
is
defined. The borrow semantics of Rust requires a slightly different approach to managing the tree than one might
take in other languages (e.g. LIP39).
pub fn upsert(&mut self, key: NodeKey, value: ValueHash) -> Result<UpdateResult, SMTError> {
let new_leaf = LeafNode::new(key, value);
if self.is_empty() {
self.root = Node::Leaf(new_leaf);
return Ok(UpdateResult::Inserted);
} else if self.root.is_leaf() {
return self.upsert_root(new_leaf);
}
// Traverse the tree until we find either an empty node or a leaf node.
let mut terminal_branch = self.find_terminal_branch(new_leaf.key())?;
let result = terminal_branch.insert_or_update_leaf(new_leaf)?;
Ok(result)
}
/// Look at the height-th most significant bit and returns Left of it is a zero and Right if it is a one
fn traverse_direction(height: usize, child: &NodeKey) -> TraverseDirection {...}
// Finds the branch node above the terminal node. The case of an empty or leaf root node must be handled elsewhere
fn find_terminal_branch(&mut self, child_key: &NodeKey) -> Result<TerminalBranch<'_, H>, SMTError> {
let mut parent_node = &mut self.root;
let mut empty_siblings = Vec::new();
if !parent_node.is_branch() {
return Err(SMTError::UnexpectedNodeType);
}
let mut done = false;
let mut traverse_dir = TraverseDirection::Left;
while !done {
let branch = parent_node.as_branch_mut().unwrap();
traverse_dir = traverse_direction(branch.height(), child_key)?;
let next = match traverse_dir {
TraverseDirection::Left => {
empty_siblings.push(branch.right().is_empty());
branch.left()
},
TraverseDirection::Right => {
empty_siblings.push(branch.left().is_empty());
branch.right()
},
};
if next.is_branch() {
parent_node = match traverse_dir {
TraverseDirection::Left => parent_node.as_branch_mut().unwrap().left_mut(),
TraverseDirection::Right => parent_node.as_branch_mut().unwrap().right_mut(),
};
} else {
done = true;
}
}
let terminal = TerminalBranch {
parent: parent_node,
direction: traverse_dir,
empty_siblings,
};
Ok(terminal)
}
struct TerminalBranch<'a, H> {
parent: &'a mut Node<H>,
direction: TraverseDirection,
empty_siblings: Vec<bool>,
}
impl<'a, H: Digest<OutputSize = U32>> TerminalBranch<'a, H> {
/// Returns the terminal node of the branch
pub fn terminal(&self) -> &Node<H> {
let branch = self.parent.as_branch().unwrap();
branch.child(self.direction)
}
// When inserting a new leaf node, there might be a slew of branch nodes to create depending on where the keys
// of the existing leaf and new leaf node diverge. E.g. if a leaf node of key `1101` is being inserted into a
// tree with a single leaf node of key `1100` then we must create branches at `1...`, `11..`, and `110.` with
// the leaf nodes `1100` and `1101` being the left and right branches at height 4 respectively.
//
// This function handles this case, as well the simple update case, and the simple insert case, where the target
// node is empty.
fn insert_or_update_leaf(&mut self, leaf: LeafNode<H>) -> Result<UpdateResult, SMTError> {
let branch = self.parent.as_branch_mut().ok_or(SMTError::UnexpectedNodeType)?;
let height = branch.height();
let terminal = branch.child_mut(self.direction);
match terminal {
Empty(_) => {
let _ = [Set terminal to the new leaf (Insert)]
Ok(UpdateResult::Inserted)
},
Leaf(old_leaf) if old_leaf.key() == leaf.key() => {
let old_value = [Replace of leaf with new leaf (Update)]
Ok(UpdateResult::Updated(old_value))
},
Leaf(_) => {
let branch = // Create a new sub-tree with the old and new leaf being children of a branch at the height
// of the common key-prefixes
Ok(UpdateResult::Inserted)
},
_ => unreachable!(),
}
}
Removing a Leaf Node
A certain key-value pair can be removed from the tree by deleting the corresponding leaf node and rearranging the
affected nodes in the tree. The following protocol can be used to remove a key k
from the tree.
/// Attempts to delete the value at the location `key`. If the tree contains the key, the deleted value hash is
/// returned. Otherwise, `KeyNotFound` is returned.
pub fn delete(&mut self, key: &NodeKey) -> Result<DeleteResult, SMTError> {
if self.is_empty() {
return Ok(DeleteResult::KeyNotFound);
}
if self.root.is_leaf() {
return self.delete_root(key);
}
let mut path = self.find_terminal_branch(key)?;
let result = match path.classify_deletion(key)? {
PathClassifier::KeyDoesNotExist => DeleteResult::KeyNotFound,
PathClassifier::TerminalBranch => {
let deleted = // prune tree placing sibling at correct place upstream
DeleteResult::Deleted(deleted)
},
PathClassifier::NonTerminalBranch => {
let deleted_hash = path.delete()?;
DeleteResult::Deleted(deleted_hash)
},
};
Ok(result)
}
Proof Construction
Proofs are constructed in a straightforward manner. Unlike other SMT implementations (e.g. LIP39), if a
sibling node is empty, then EMPTY_NODE_HASH
is included as the sibling hash, rather than building a bitmap of
non-empty siblings.
Both inclusion and exclusion proofs use a common algorithm, build_proof_candidate
for traversing the tree to the
desired proof key,
collecting hashes of every sibling node. The terminal node for where the proof key should reside is also noted:
pub struct ExclusionProof<H> {
siblings: Vec<NodeHash>,
// The terminal node of the tree proof, or `None` if the the node is `Empty`.
leaf: Option<LeafNode<H>>,
phantom: std::marker::PhantomData<H>,
}
impl<H: Digest<OutputSize = U32>> SparseMerkleTree<H> {
/// Construct the data structures needed to generate the Merkle proofs. Although this function returns a struct
/// of type `ExclusionProof` it is not really a valid (exclusion) proof. The constructors do additional
/// validation before passing the structure on. For this reason, this method is `private` outside of the module.
pub(crate) fn build_proof_candidate(&self, key: &NodeKey) -> Result<ExclusionProof<H>, SMTError> {
let mut siblings = Vec::new();
let mut current_node = &self.root;
while current_node.is_branch() {
let branch = current_node.as_branch().unwrap();
let dir = traverse_direction(branch.height(), key)?;
current_node = match dir {
TraverseDirection::Left => {
siblings.push(branch.right().hash().clone());
branch.left()
},
TraverseDirection::Right => {
siblings.push(branch.left().hash().clone());
branch.right()
},
};
}
let leaf = current_node.as_leaf().cloned();
let candidate = ExclusionProof::new(siblings, leaf);
Ok(candidate)
}
}
Inclusion proof
An inclusion proof is valid if the terminal node found in build_proof_candidate
matches the key and value provided
in the proof request. Equivalently, the leaf node's hash must match the hash of a new leaf node generated with the
key and value given in the proof request.
The final proof consists of the vector of sibling hashes.
pub struct InclusionProof<H> {
siblings: Vec<NodeHash>,
phantom: std::marker::PhantomData<H>,
}
impl<H: Digest<OutputSize = U32>> InclusionProof<H> {
/// Generates an inclusion proof for the given key and value hash from the given tree. If the key does not exist in
/// tree, or the key does exist, but the value hash does not match, then `from_tree` will return a
/// `NonViableProof` error.
pub fn from_tree(tree: &SparseMerkleTree<H>, key: &NodeKey, value_hash: &ValueHash) -> Result<Self, SMTError> {
let proof = tree.build_proof_candidate(key)?;
match proof.leaf {
Some(leaf) => {
let node_hash = LeafNode::<H>::hash_value(key, value_hash);
if leaf.hash() != &node_hash {
return Err(SMTError::NonViableProof);
}
},
None => return Err(SMTError::NonViableProof),
}
Ok(Self::new(proof.siblings))
}
}
Exclusion proof
An exclusion proof request only requires a key value. A proof is valid if the leaf node returned by
build_proof_candidate
does not have the same key as the proof request.
The proof consists of the sibling hashes and a copy of the terminal leaf node.
impl<H: Digest<OutputSize = U32>> ExclusionProof<H> {
/// Generates an exclusion proof for the given key from the given tree. If the key exists in the tree then
/// `from_tree` will return a `NonViableProof` error.
pub fn from_tree(tree: &SparseMerkleTree<H>, key: &NodeKey) -> Result<Self, SMTError> {
let proof = tree.build_proof_candidate(key)?;
// If the keys match, then we cannot provide an exclusion proof, since the key *is* in the tree
if let Some(leaf) = &proof.leaf {
if leaf.key() == key {
return Err(SMTError::NonViableProof);
}
}
Ok(proof)
}
Proof Verification
To check an exclusion proof, the Verifier calls the ExclusionProof::validate(&self, keys, root)
function. This
function is not a method of the tree, and can be run just by holding the Merkle root.
The function reconstructs the tree using the expected key and places the leaf node provided in the proof at the terminal position. It then calculates the root hash.
Validation succeeds if the calculated root hash matches the given root hash, and the leaf node is empty, or the existing leaf node has a different key to the expected key.
pub fn validate(&self, expected_key: &NodeKey, expected_root: &NodeHash) -> bool {
let leaf_hash = match &self.leaf {
Some(leaf) => leaf.hash().clone(),
None => (EmptyNode {}).hash().clone(),
};
let root = self.calculate_root_hash(expected_key, leaf_hash);
// For exclusion proof, roots must match AND existing leaf must be empty, or keys must not match
root == *expected_root &&
match &self.leaf {
Some(leaf) => leaf.key() != expected_key,
None => true,
}
}
Verifying inclusion proofs is similar, except that the terminal leaf node will be constructed from the key and value hash provided by the verifier.
pub fn validate(&self, expected_key: &NodeKey, expected_value: &ValueHash, expected_root: &NodeHash) -> bool {
// calculate expected leaf node hash
let leaf_hash = LeafNode::<H>::hash_value(expected_key, expected_value);
let calculated_root = self.calculate_root_hash(expected_key, leaf_hash);
calculated_root == *expected_root
}
Backwards Compatibility
If the UTXO Merkle root is replaced by a sparse Merkle tree, this change would require a hard fork, since it fundamentally alters how the UTXO Merkle root is calculated.
References
- Dahlberg et. al., "Efficient Sparse Merkle Trees", SMT
- A. Ricottone, "LIP-0039: Introduce sparse Merkle trees", LIP39
Change Log
Date | Change | Author |
---|---|---|
10 Jul 2023 | First draft | CjS77 |
RFC-0153/StagedWalletSecurity
Staged Wallet Security
Maintainer(s): Cayle Sharrock
Licence
Copyright 2021 The Tari Development Community
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Language
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as shown here.
Disclaimer
This document and its content are intended for information purposes only and may be subject to change or update without notice.
This document may include preliminary concepts that may or may not be in the process of being developed by the Tari community. The release of this document is intended solely for review and discussion by the community of the technological merits of the potential system outlined herein.
Goals
This Request for Comment (RFC) aims to describe Tari's ergonomic approach to securing funds in a hot wallet. The focus is on mobile wallets, but the strategy described here is equally applicable to console or desktop wallets.
Related Requests for Comment
Description
Rationale
A major UX hurdle when users first interact with a crypto wallet is the friction they experience with the first user experience.
A common theme: I want to play with some new wallet X that I saw advertised somewhere, so I download it and run it. But first I get several screens that
- ask me to review my seed phrase,
- ask me to write down my seed phrase,
- prevent typical "skip this" tricks like taking a screenshot,
- ask to confirm if I've written down my seed phrase,
- force me to write a test, either by supplying a random sample of my seed phrase, or by getting me to type in the whole thing.
After all this, I play with the wallet a bit, and then typically, I uninstall it.
The goal of this RFC is to get the user playing with the wallet as quickly as possible. Without sacrificing security whatsoever.
A staged approach
This RFC proposes a smart, staged approach to wallet security. One that maximises user experience without compromising safety.
Each step enforces more stringent security protocols on the user than the previous step.
The user moves from one step to another based on criteria that
- the user configures based on her preferences, or
- uses sane predefined defaults.
The criteria are generally based on the value of the wallet balance.
Once a user moves to a stage, the wallet does not move to a lower stage if the requirements for the stage are no longer met.
Users may also jump to any more advanced stage from their wallet settings / configuration at any time.
Stage zero - zero balance
When the user has a zero balance, there's no risk in letting them skip securing their wallet.
Therefore, Tari wallets SHOULD just skip the whole seed phrase ritual and let the user jump right into the action.
Stage 1a - a reminder to write down your seed phrase
Once the user's balance exceeds the MINIMUM_STAGE_ONE_BALANCE
, they will be prompted to review and write down their
seed phrase. The MINIMUM_STAGE_ONE_BALANCE
is any non-zero balance by default.
After the transaction that causes the balance to exceed MINIMUM_STAGE_ONE_BALANCE
is confirmed, the user is presented
with a friendly message:
You now have _real_ money in your wallet. If you accidentally delete your wallet app or lose
your device, your funds are lost, and there is no way to recover them unless you have safely kept a copy of your
`seed phrase` safe somewhere. Click 'Ok' to review and save the phrase now, or 'Do it later' to do it at a more
convenient time.
If the user elects not to save the phrase, the message pops up again periodically. Once per day, or when the balance increases -- whichever is less frequent -- is sufficient without being too intrusive.
Stage 1b - simple wallet backups
Users are used to storing their data in the cloud. Although this practice is frowned upon by crypto purists, for small balances (the type you often keep in a hot wallet), using secure cloud storage for wallet backups is a fair compromise between keeping the keys safe from attackers and protecting users from themselves.
The simple wallet backup saves the spending keys and values of the user's wallet to a personal cloud space (e.g. Google Drive, Apple iCloud, Dropbox).
This solution does not require any additional input from the user besides providing authorisation to store in the cloud. This can be done using the standard APIs and Authentication flows that each cloud provider publishes for their platform.
In particular, we do not ask for a password to encrypt the commitment data. The consequence is that anyone who gains access to this data -- by stealing the user's cloud credentials -- could steal the user's funds.
Therefore, the threshold for moving from this stage to Stage 2, STAGE_TWO_THRESHOLD_BALANCE
is relatively low;
somewhere in the region of \$10 to \$50.
The seed phrase MUST NOT be stored on the cloud in Stage 1b. Doing so would result in all future funds of the user being lost if the backup were ever compromised. Since the backup is unencrypted in Stage 1b, we store the minimum amount of data needed to recover the funds and limit the potential loss of funds in case of a breach to just that found in the commitments in the backup, which should not be more than \$50.
Therefore, stage 1b backups are really just exporting and importing of UTXO data. The consequence of this is that restoring from a 1b backup does not restore the emoji id. On the other hand, you can easily import into any other wallet (e.g. into another wallet on another device owned by the same user).
Backups MUST be authorised by the user when the first cloud backup is made and SHOULD be automatically updated after each transaction is confirmed.
Wallet authors MAY choose to exclude Stage 1b from the staged security protocol.
As usual, the user MUST be able to configure STAGE_TWO_THRESHOLD_BALANCE
to suit their particular needs.
When this threshold is reached, the user SHOULD be prompted with a call to back-up their data:
Your wallet now holds a fair amount of value, which you probably don't want to lose. Unlike a physical wallet's
notes, it's possible to make a copy of your wallet's contents that you can use to recover your funds in case you
lose them.
You should think about making a copy of your wallet's coins, which we will keep up date date after every transaction.
If you have a copy of your seed phrase saved somewhere safe, and don't want to back up your coins into the cloud, you
can skip this step.
The user can pick between backup my coins
or I have my seed phrase. Skip backups for now
.
If the user chooses to skip the backup, do not prompt them for stage 1b backups again.
Stage 2 - full wallet backups
Once a user has a significant balance (over STAGE_TWO_THRESHOLD_BALANCE
), Stage 2 is active. Stage 2 entails a full,
encrypted backup of the user's wallet to the cloud. The user needs to provide a password to perform and secure the encryption.
This makes the user's fund safer while at rest in the cloud. It also introduces an additional point of failure: the user can forget their wallet's encryption password.
Stage 1b and 2 are similar in functionality but different in scope (Stage 2 allows us to store all the wallet metadata, rather than just the commitments). For this reason, Stage 1b is optional.
Backups MUST be authorised by the user when the first cloud backup is made and SHOULD be automatically updated after each transaction.
When migrating from Stage 1 to Stage 2, the Stage 1b backups SHOULD be deleted.
Once the stage 2 threshold is reached, the user is again presented with a call-to-action:
😎💰💰💰😎
It's awesome that you're using [this Tari wallet] so much!
You're a high-roller, so it's time to add another layer of security
to your wallet backups.
Your funds will be safer if we encrypt your cloud backups with an
additional password of your own choosing. You need to make sure that
this password is very string and that YOU DO NOT FORGET it!
(Whatever happens, you can always restore from your seed phrase, so
make sure you're still keeping this safe and secreted away).
As an added benefit, password-encrypted backups allow us to backup your
transaction history as well!
The user can choose to Upgrade my backups
, do this later
, check my backup settings
.
The latter option takes the user to the wallet settings, where they can configure the various thresholds and manually decide which backup strategy they want to pursue.
They should be prompted periodically (once per day is sufficient) if they choose to defer the upgrade.
Stage 3 - Sweep to cold wallet
Pending hardware wallet support
Above a given limit -- user-defined, or the default MAX_HOT_WALLET_BALANCE
, the user should be prompted to transfer
funds into a cold wallet. The amount to sweep can be calculated as MAX_HOT_WALLET_BALANCE
- SAFE_HOT_WALLET_BALANCE
.
If the user ignores the prompt, they SHOULD be reminded one week later. From the second prompt onward, users SHOULD be given
an option to re-configure the values for MAX_HOT_WALLET_BALANCE
and SAFE_HOT_WALLET_BALANCE
.
Assuming one-sided payments are live, the user SHOULD be able to configure a COLD_WALLET_ADDRESS
in the wallet.
For security reasons, a user SHOULD be asked for their 2FA confirmation, if it is configured, before broadcasting the sweep transaction to the blockchain.
A suitable prompt for the user once the MAX_HOT_WALLET_BALANCE
threshold is reached is:
🐋🐋🐋🐋🐋🐋🐋🐋 ❗❗❗ Whale Alert ❗❗❗ 🐋🐋🐋🐋🐋🐋🐋🐋
Your mobile wallet holds way more funds than would normally be
considered safe to walk around with in your pocket.
You should really consider sweeping most of your balance into a
cold- or hardware wallet.
Possible actions from this prompt include:
Set up my cold wallet address
, if the cold wallet address has not been configured.Transfer funds to my cold wallet
, if the cold wallet has been set up. The usual transaction confirmation flow SHOULD be followed here, and a stealth one-sided payment SHOULD be the default transaction type.I'll deal with this myself
, which takes the user to their backup settings.
To avoid spamming the user, do not fire this prompt more than once every three days.
Security hygiene
- From stage 1 onwards Users should be asked periodically whether they still have their seed phrase written down. Once every two months is sufficient.
Change Log
Date | Change | Author |
---|---|---|
2022-11-10 | Initial stable | Adrian Truszczyński |
2022-12-12 | Update user messages | CjS77 |
2022-12-20 | Fix typo | CjS77 |
RFC-0154/DeepLinksConvention
Deep links structure convention.
Maintainer(s): Adrian Truszczyński
Licence
Copyright 2022 The Tari Development Community
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Language
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as shown here.
Disclaimer
This document and its content are intended for information purposes only and may be subject to change or update without notice.
This document may include preliminary concepts that may or may not be in the process of being developed by the Tari community. The release of this document is intended solely for review and discussion by the community regarding the technological merits of the potential system outlined herein.
Goals
The aim of this Request for Comment (RFC) is to specify the deep links structure used in the Tari Aurora project. The primary motivation is to create a simple, human-readable, and scalable way to structure deep links used by the Tari Aurora clients.
Description
Deep links are the URIs with hierarchical components sequence. We can use this sequence to pass and handle data in a standardized and predictable way. To do that, we need to pass three components to the target client: the scheme, command, and data.
Scheme
The scheme is used to address the client, which will handle the command and data components. In the Tari Aurora project, we're using the tari
scheme to open the wallet app and execute the command.
Command
The command is a path string used to pass information about the action that should be performed by the client. The handler uses this command to determine how to deserialize the data, before passing it to the command function. To support multiple networks the first path component should be the name of corresponding network. Before proceeding with the command, the client should first check that the active account is pointed to the correct network, or show an error if there is a mismatch.
For simple actions, the command can be defined as a single phrase. For more complex actions, the command should be defined as a multi-path where the second path component should be the name of the action group.
Examples:
Simple Actions:
mainnet/user_profile
testnet/login
Complex Actions:
mainnet/payments/send
testnet/payments/request
Data
The data component is an optional string of key-value pairs used by the parser/decoder to deserialize the data, which the client passes to the command function. The data component should be formatted in the same way as a URL query. The sub-component should have a ?
prefix, the key-value pairs should be separated by &
, and every key should be separated from the value by =
character.
The Structure
Combining all three components, they will form a deep link with a structure presented below:
{scheme}://{network_name}/{command}?{data}
Examples:
tari://mainnet/profile
tari://mainnet/profile/username
tari://testnet/payments/send?amount=1.23&pubKey=01234556789abcde
tari://nextnet/contacts/list[0][alias]=MrTari&list[0][tariAddress]=01234556789abcde&list[1][alias]=AdamSmith&list[1][tariAddress]=edcba9876543210
Deeplinks in use:
{network_name}/transactions/send
The data contains transaction information used in the send tokens process.
Value Name | Value Type | Note |
---|---|---|
tariAddress | String | Receiver's Tari Address (base58) |
amount | UInt64? | The amount in micro Tari |
note | String? | Note passed with transaction |
{network_name}/base_nodes/add
The data contains a custom base node configuration. This deep link adds a new base node configuration to the pool and switches to the added base node.
Value Name | Value Type | Note |
---|---|---|
name | String | The name of the base node |
peer | String | Base node's public link and onion address combined together |
{network_name}/contacts
List of contacts. This deep link is used to share multiple Tari contacts with another user.
Value Name | Value Type | Note |
---|---|---|
list | Array<Contact> | List of Contact objects |
Contact
object
Value Name | Value Type | Note |
---|---|---|
alias | String? | Contact's name/alias |
tariAddress | String | Contact's Tari Address (base58) |
{network_name}/profile
User's profile card. It is used to initiate the transaction with a predefined receiver address and presented alias.
Value Name | Value Type | Note |
---|---|---|
alias | String | User's name/alias |
tariAddress | String | User's Tari Address (base58) |
Change Log
Date | Change | Author |
---|---|---|
19 Apr 2023 | First stable, replace public key with tari address | SWvHeerden |
06 Jun 2023 | Added descriptions for /contacts and /profile deep links | Adrian Truszczyński |
17 Jul 2024 | Updated documentation for transactions/send , /contacts , and /profile deep links | Adrian Truszczyński |
RFC-0230/Time-related Transactions
Time-related Transactions
Maintainer(s): S W van Heerden
Licence
Copyright 2019 The Tari Development Community
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Language
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as shown here.
Disclaimer
This document and its content are intended for information purposes only and may be subject to change or update without notice.
This document may include preliminary concepts that may or may not be in the process of being developed by the Tari community. The release of this document is intended solely for review and discussion by the community of the technological merits of the potential system outlined herein.
Goals
The aim of this Request for Comment (RFC) is to describe a few extensions to Mimblewimble to allow time-related transactions.
Related Requests for Comment
Description
Time-locked UTXOs
Time-locked Unspent Transaction Outputs (UTXOs) can be accomplished by adding a feature flag to a UTXO and a lock height, also referred to as the output's maturity. This allows a consensus limit on after which height the output can be spent.
This requires that users constructing a transaction:
- MUST include a feature flag of their UTXO; and
- MUST include a lock height in their UTXO.
This adds the following requirement for a base node:
- A base node MUST NOT allow a UTXO to be spent if the current head has not already exceeded the UTXO's lock height.
This also adds the following requirement for a base node:
- A base node MUST reject any block that contains a UTXO with a lock height not already past the current head.
Time-locked Contracts
In standard Mimblewimble, time-locked contracts can be accomplished by modifying the kernel of each transaction to include a lock height. This limits how early in the blockchain lifetime the specific transaction can be included in a block. This approach is used in a traditional Mimblewimble construction that does not implement any kind of scripting. This has two disadvantages. Firstly, the spending condition is very primitive and cannot be linked to other conditions. Secondly, it bloats the kernel, which is a component of the transaction that cannot be pruned.
However, with TariScript it becomes possible to express spending conditions like a time-lock as part of a UTXO's script.
The CheckHeightVerify(height)
TariScript Op code allows a time-lock check to be incorporated into a script. The following is a simple
example of a plain time-lock script that prevents an output from being spent before the chain reaches height 4000:
CheckHeightVerify(4000)
Hashed Time-locked Contract
Hashed time-locked contracts (HTLC) are a way of reserving funds that can only be spent if a hash pre-image can be provided or if a specified amount of time has passed. The hash pre-image is a secret that can be revealed under the right conditions to enable spending of the UTXO before the time-lock is reached. The secret can be directly exchanged between the parties or revealed to the other party by spending an output that makes use of an adaptor signature.
HTLCs enable a number of interesting transaction constructions. For example, Atomic Swaps and Payment Channels like those in the Lightning Network.
The following is an example of an HTLC script. In this script, Alice sends some Tari to Bob that he can spend using the
private key of P_b
if he can provide the pre-image to the SHA256 hash output (HASH256{pre_image}
) specified in the script
by Alice. If Bob has not spent this UTXO before the chain reaches height 5000 then Alice will be able to spend the output
using the private key of P_a
.
HashSha256
PushHash(HASH256{pre_image})
Equal
IFTHEN
PushPubkey(P_b)
ELSE
CheckHeightVerify(5000)
PushPubkey(P_a)
ENDIF
A more detailed analysis of the execution of this kind of script can be found at Time-locked Contact
Change Log
Date | Change | Author |
---|---|---|
14 Apr 2019 | First draft | SWvheerden |
19 Dec 2021 | TariScript updates | philipr |
31 Oct 2022 | Stable update | brianp |
RFC-0240/Atomic Swap
Maintainer(s): S W van Heerden
Licence
Copyright 2021 The Tari Development Community
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Language
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as shown here.
Disclaimer
This document and its content are intended for information purposes only and may be subject to change or update without notice.
This document may include preliminary concepts that may or may not be in the process of being developed by the Tari community. The release of this document is intended solely for review and discussion by the community of the technological merits of the potential system outlined herein.
Goals
This Request for Comment (RFC) aims to describe how Atomic swaps will be created between two parties on different blockchains.
Related Requests for Comment
$$ \newcommand{\preimage}{\phi} % pre image \newcommand{\hash}[1]{\mathrm{H}\bigl({#1}\bigr)} $$
Description
Atomic swaps are atomic transactions that allow users to exchange different crypto assets and or coins without using a central exchange and or trusting each other. Trading coins or assets this way makes it much more private and secure to do and swap because no third party is required to be secure. Atomic swaps work on the principle of using Hashed Time Lock Contracts(HTLC). In short, it requires some hash pre-image to unlock the contract, or the time-lock can be used to reclaim the funds.
In a cross-chain Atomic swap, both users lock up the funds to be exchanged on their respective chains in an HTLC-type contract. However, the two contracts’ pre-image, or spending secret, is the same, but only one party knows the correct pre-image. When the first HTLC contract is spent, this publicly reveals the pre-image for the other party to spend the second HTLC. If the first HTLC is never spent, the second transaction's time-lock will allow the user to respend the funds back to themselves after the time lock has passed.
BTC - XTR AtomicSwap
Overview
BTC uses a scripting language for smart contracts on transactions which enables atomic swaps on the BTC chain. Traditionally Mimblewimble coins do not implement scripts, which makes Atomic swaps harder to implement but not impossible. Grin has implemented atomic swaps using a version of a 2-of-2 multi-signature transaction mimblewimble atomic swaps. Fortunately, Tari does have scripting with TariScript, which works a lot like BTC scripts, making the implementation simpler. Because of the scripting similarities, the scripts to both HTLCs will look very similar, and we only need to ensure that we use the same hash function in both.
To do an Atomic swap from BTC to XTR, we need four wallets, two BTC wallets, and two XTR wallets, one wallet per person, per coin.
As an example, Alice wants to trade some of her XTR for Bob's BTC. Alice and Bob need to agree on an amount of XTR and BTC to swap. Once an agreement is reached, the swap is executed in the following steps:
-
Alice chooses a set of random bytes, \( \preimage \), as the pre-image and hashes it with SHA256. She then sends
-
the hash of the pre-image, \( \hash{\preimage} \), to Bob along with her BTC address.
-
Bob sends her a public version of his script key, \( K_{Sb} \), for use in the XTR transaction, which we can refer to as Bob's script address.
-
Alice creates a one-sided XTR transaction with an HTLC contract requiring \( \preimage \) as the input, which will either payout to Bob's script address or her script address, \( K_{Sa} \), after a particular "time" has elapsed (block height has been reached).
-
Bob waits for this transaction to be mined. When it is mined, he verifies that the UTXO spending script expects a comparison of \( \hash{\preimage} \) as the first instruction, and that his public script key, \( K_{Sb} \), will be the final value remaining after executing the script. He has the private script key, \( k_{Sb} \), to enable him to produce a signature to claim the funds if he can get hold of the expected pre-image input value, \( \preimage \). He also verifies that the UTXO has a sufficiently long time-lock to give him time to claim the transaction.
-
Upon verification, Bob creates a Segwit HTLC BTC transaction with the same \( \hash{\preimage} \), which will spend
-
to Alice's BTC address she gave him. It is essential to note that the time lock for this HTLC has to expire before
-
the time lock of the XTR HTLC that Alice created.
-
Alice checks the Bitcoin blockchain, and upon seeing that the transaction is mined, she claims the transaction, but,
-
for her to do so, she has to make public what \( \preimage \) is as she has to use it as the witness of the claiming transaction.
-
Bob sees that his BTC is spent, and looks at the witness to get \( \preimage \). Bob can then use \( \preimage \) to claim the XTR transaction.
BTC - HTLC script
Here is the required BTC script that Bob publishes:
OP_IF
OP_SHA256 <HASH256{pre_image}> OP_EQUALVERIFY
<Alice BTC address> OP_CHECKSIG
OP_ELSE
<relative locktime>
OP_CHECKSEQUENCEVERIFY
OP_DROP
<Bob BTC address> OP_CHECKSIG
OP_ENDIF
relative locktime is a time sequence in which Alice chooses to lock up the funds to give Bob time to claim this.
XTR - HTLC script
Here is the required XTR script that Alice publishes:
HashSha256 PushHash(HASH256{pre_image}) Equal
IFTHEN
PushPubkey(K_{Sb})
ELSE
CheckHeightVerify(height)
PushPubkey(K_{Sa})
ENDIF
(\( K_{Sb} \)) is the public key of the script key pair that Bob chooses to claim this transaction if Alice backs out. height is an absolute block height that Bob chooses to lock up the funds to give Alice time to claim the funds.
XTR - XMR swap
The Tari - Monero atomic swap involved a bit more detail than just a simple script and is explained in RFC-0241: XTR - XMR swap
Notation
Where possible, the "usual" notation is used to denote terms commonly found in cryptocurrency literature. Lower case characters are used as private keys, while uppercase characters are used as public keys. New terms introduced here are assigned greek lowercase letters in most cases. Some terms used here are noted down in TariScript.
Name | Symbol | Definition |
---|---|---|
Pre-image | \( \preimage \) | The random byte data used for the pre-image of the hash |
|
Change Log
Date | Change | Author |
---|---|---|
17 Oct 2022 | First outline | SWvHeerden |
RFC-0241/XMR Atomic Swap
Maintainer(s): S W van Heerden
Licence
Copyright 2021 The Tari Development Community
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Language
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as shown here.
Disclaimer
This document and its content are intended for information purposes only and may be subject to change or update without notice.
This document may include preliminary concepts that may or may not be in the process of being developed by the Tari community. The release of this document is intended solely for review and discussion by the community of the technological merits of the potential system outlined herein.
Goals
This Request for Comment (RFC) aims to describe how an Atomic swap between Tari and Monero will be created.
Related Requests for Comment
$$ \newcommand{\script}{\alpha} % utxo script \newcommand{\input}{ \theta } \newcommand{\cat}{\Vert} \newcommand{\so}{\gamma} % script offset \newcommand{\hash}[1]{\mathrm{H}\bigl({#1}\bigr)} $$
Comments
Any comments, changes or questions to this PR can be made in one of the following ways:
- Join the discussion on the Github discussion page.
- Create a new PR on the Tari project Github pull requests.
- Create a new issue on the Tari project Github issues.
Description
Doing atomic swaps with Monero is more complicated and requires a cryptographic dance to complete as Monero does not implement any form of HTLC's or the like. This means that when doing an atomic swap with Monero, most of the logic will have to be implemented on the Tari side. Atomic swaps between Monero and Bitcoin have been implemented by the Farcaster project and the Comit team. Due to the how TariScript works, we have a few advantages over Bitcoin script regarding adaptor signatures, as the script key was explicitly designed with scriptless scripts in mind.
Method
The primary, happy path outline of a Tari - Monero atomic swap is described here, and more detail will follow. We assume that Alice wants to trade her XTR for Bob's XMR.
- Negotiation - Both parties negotiate the value and other details of the Monero and Tari UTXO's.
- Commitment - Both parties commit to the keys, nonces, inputs, and outputs to use for the transaction.
- XTR payment - Alice makes the XTR payment to a UTXO containing a "special" script described below.
- XMR Payment - The Monero payment is made to a multiparty scriptless script UTXO.
- Claim XTR - Bob redeems the XTR, and in doing so, reveals the XMR private key to Alice only.
- Claim XMR - Alice may claim the XMR using the revealed key.
Please take note of the notation used in TariScript and specifically notation used on the signatures on the transaction inputs and on the signatures on the transaction outputs. We will note other notations in the Notation section.
TL;DR
The scheme revolves around Alice, who wants to exchange her Tari for Bob's Monero. Because they don't trust each other, they have to commit some information to do the exchange. And if something goes wrong here, we want to ensure that we can refund both parties either in Monero or Tari.
How this works is that Alice and Bob create a shared output on both chains. The Monero output is a simple aggregate key to unlock the UTXO, while multiple keys are needed to unlock the Tari UTXO. An aggregate key locks this Monero UTXO that neither Alice nor Bob knows, but they both know half of the key. The current Tari block height determines the unlocking key for the Tari UTXO.
The process is started by Alice and Bob exchanging and committing to some information. Alice is the first to publish a transaction, which creates the Tari UTXO. If Bob is happy that the Tari UTXO has been mined and verifies all the information, he will publish a transaction to create the Monero UTXO.
The TariScript script on the UTXO ensures that they will have to reveal their portion of the Monero key when either Alice or Bob spends this. This disclosure allows the other party to claim the Monero by being the only one to own the complete Monero aggregate key.
We can visualize the happy path flow with the image below.
The script will ensure that at any point in time, at least someone can claim the Tari UTXO, and if that person does so, the other party can claim the Monero UTXO by looking at the spending data. It has two lock heights, determining who can claim the Tari UTXO if the happy path fails. Before the first lock height, only Bob can claim the Tari; we call this the swap transaction.
If Bob disappears after Alice has posted the Tari UTXO, Alice can claim the Tari after the first lock height and before the second lock height; we call this the refund transaction. It ensures that Alice can reclaim her Tari if Bob disappears, and if Bob reappears, he can reclaim his Monero.
That leaves us with the scenario where Alice disappears after Bob posts the Monero transaction, in which case we need to protect Bob. After the second lock height, only Bob can claim the Tari; we call this the lapse transaction. The lapse transaction will reveal Bob's Monero key so that if Alice reappears, she can claim the Monero.
The image below details the time flow of the Tari transactions spending the Tari UTXO.
Heights, Security, and other considerations
We need to consider a few things for this to be secure, as there are possible scenarios that can reduce the security in the atomic swap.
When looking at the two lock heights, the first lock height should be sufficiently large enough to give ample time for Alice to post the Tari UTXO transaction and for it to be mined with a safe number of confirmations, and for Bob to post the Monero transaction and for it to be mined with a safe number of confirmations. The second lock height should give ample time for Alice after the first lock height to re-claim her Tari. Larger heights here might make refunds slower, but it should be safer in giving more time to finalize this.
Allowing both to claim the Tari after the second lock height is, on face value, a safer option. This can be done by enabling either party to claim the script with the lapse transaction. The counterparty can then claim the Monero. However, this will open up an attack vector to enable either party to claim the Monero while claiming the Tari. Either party could trivially pull off such a scheme by performing a front-running attack and having a bit of luck. The counterparty monitors all broadcast transactions to base nodes. Upon identifying the lapse transaction, they do two things; in quick succession, broadcast their lapse transaction and the transaction to claim the Monero, both with sufficiently high fees. Base nodes will prefer to mine transactions with the higher fees, and thus the counterparty can walk away with both the Tari and the Monero.
It is also possible to prevent the transaction from being mined after being submitted to the mempool. This can be caused by a combination of a too busy network, not enough fees, or a too-small period in the time locks. When one of these atomic swap transactions gets published to a mempool, we effectively already have all the details exposed. For the atomic swaps, it means we already revealed part of the Monero key, although the actual Tari transaction has not been mined. But this is true for any HTLC or like script on any blockchain. But in the odd chance that this does happen whereby the fees are too little and time locks not enough, it should be possible to do a child-pays-for-parent transaction to bump up the fees on the transaction to get it mined and confirmed.
Key construction
Using multi-signatures with Schnorr signatures, we need to ensure that the keys are constructed so that key cancellation attacks are not possible. To do this, we create new keys from the chosen public keys \(K_a'\) and \(K_b'\)
$$ \begin{aligned} K_a &= \hash{\hash{K_a' \cat K_b'} \cat K_a' } * K_a' \\ k_a &= \hash{\hash{K_a' \cat K_b'} \cat K_a' } * k_a' \\ K_b &= \hash{\hash{K_a' \cat K_b'} \cat K_b' } * K_b' \\ k_b &= \hash{\hash{K_a' \cat K_b'} \cat K_b' } * k_b' \\ \end{aligned} \tag{1} $$
Key equivalence
Monero uses Ed25519, while Tari uses Ristretto as its curve of choice. While Ristretto and Ed25519 both works on Curve25519 and the Edward points are the same, they differ when the points are encoded. In practice, this means the following:
$$ \begin{aligned} Xm &= x \cdot G_m \\ X &= x \cdot G \\ X &\neq X_m \\ \end{aligned} \tag{2} $$
To use public keys across different implementations or curves, we must prove that the same private key created the "different" public keys. Because we exchange encoded points, we need some way of proving they are the same. Jepsen designed a proof to prove Discrete Logarithm Equality (DLEQ) between two generator groups. But the proof here is not created for Elliptic Curve Cryptography (ECC) but can be adapted to ECC by:
$$ \begin{aligned} e &= \hash{K_1 \cat K_2 \cat R_{1} \cat R_{2}} \\ s &= r + e(k) \\ R_{1} &= r \cdot G \\ R_{2} &= r \cdot G_m \\ K_{1} &= k \cdot G \\ K_{2} &= k \cdot G_m \\ \end{aligned} \tag{3} $$
The verification is then: $$ \begin{aligned} e &= \hash{K_1 \cat K_2 \cat R_{1} \cat R_{2}} \\ s \cdot G &= R_{1} + e(K_1) \\ s \cdot G_m &= R_{2} + e(K_2) \\ \end{aligned} \tag{4} $$
But this method has a problem with ECC keys as they are always used as \(mod(n)\) where n is the group size. With ECC, we cannot always use the method; it can only be used when the two curves are of the same group size \(n\), or \(s < n\). If this is not the case, a bit comparison needs to be created to prove this. But luckily, we use Ristretto and Ed25519, both on Curve25519, because it’s the same curve, same generator point, and just different encodings. We can use this proof to know even though the encoded public keys do not match, they still share a private key
Key security
The risk of publicly exposing part of the Monero private key is still secure because of how ECC works. We can add two secret keys together and share the public version of both. And at the same time, we know that no one can calculate the secret key with just one part.
$$ \begin{aligned} (k_a + k_b) \cdot G &= k_a \cdot G + k_b \cdot G\\ (k_a + k_b) \cdot G &= K_a + K_b \\ (k_a + k_b) \cdot G &= K \\ \end{aligned} \tag{5} $$
We know that \(K\), \(K_a\), \(K_b\) are public. While \(k\), \(k_a\), \(k_b\) are all private.
But if we expose \(k_b\), we can try to do the following: $$ \begin{aligned} (k_a + k_b) \cdot G &= K_a + K_b\\ k_a \cdot G &= (K_a + K_b - k_b \cdot G) \\ k_a \cdot G &= K_a \\ \end{aligned} \tag{6} $$
However, this is the Elliptic-Curve Discrete Logarithm Problem, and there is no easy solution to solve this on current computer hardware. Thus this is still secure even though we leaked part of the secret key \(k\).
Method 1
Detail
This method relies purely on TariScript to enforce the exposure of the private Monero aggregate keys. Based on Point Time Lock Contracts, the script forces the spending party to supply their Monero
private key part as input data to the script, evaluated via the operation ToRistrettoPoint
. This TariScript operation will
publicly reveal part of the aggregated Monero private key, but this is still secure: see Key security.
The simplicity of this method lies therein that the spending party creates all transactions on their own. Bob requires a pre-image from Alice to complete the swap transaction; Alice needs to verify that Bob published the Monero transaction and that everything is complete as they have agreed. If she is happy, she will provide Bob with the pre-image to claim the Tari UTXO.
TariScript
The Script used for the Tari UTXO is as follows:
ToRistrettoPoint
CheckHeight(height_1)
LtZero
IFTHEN
PushPubkey(X_b)
EqualVerify
HashSha256
PushHash(HASH256{pre_image})
EqualVerify
PushPubkey(K_{Sb})
Else
CheckHeight(height_2)
LtZero
IFTHEN
PushPubkey(X_a)
EqualVerify
PushPubkey(K_{Sa})
Else
PushPubkey(X_b)
EqualVerify
PushPubkey(K_{Sb})
ENDIF
ENDIF
Before height_1
, Bob can claim the Tari UTXO by supplying pre_image
and his private Monero key part x_b
. After
height_1
but before height_2
, Alice can claim the Tari UTXO by supplying her private Monero key part x_a
. After
height_2
, Bob can claim the Tari UTXO by providing his private Monero key part x_b
.
Negotiation
Alice and Bob have to negotiate the exchange rate and the amount exchanged in the atomic swap. They also need to decide how the two UTXO's will look on the blockchain. To accomplish this, the following needs to be finalized:
- Amount of Tari to swap for the amount of Monero
- Monero public key parts \(Xm_a\), \(Xm_b\) ,and its aggregate form \(Xm\)
- DLEQ proof of \(Xm_a\) and \(X_a\)
- Tari script key parts \(K_{Sa}\), \(K_{Sb}\)
- The TariScript to be used in the Tari UTXO
- The blinding factor \(k_i\) for the Tari UTXO, which can be a Diffie-Hellman between their Tari addresses.
Key selection
Using (1), we create the Monero keys as they are multi-party aggregate keys. The Monero key parts for Alice and Bob is constructed as follows:
$$ \begin{aligned} Xm_a' &= xm_a' \cdot G_m \\ Xm_b' &= xm_b' \cdot G_m \\ xm_a &= \hash{\hash{Xm_a' \cat Xm_b'} \cat Xm_a' } * xm_a' \\ xm_b &= \hash{\hash{Xm_a' \cat Xm_b'} \cat Xm_b' } * xm_b' \\ xm_a &= x_a \\ xm_b &= x_b \\ Xm_a &= \hash{\hash{Xm_a' \cat Xm_b'} \cat Xm_a' } * Xm_a' \\ Xm_b &= \hash{\hash{Xm_a' \cat Xm_b'} \cat Xm_b' } * Xm_b' \\ xm &= xm_a + xm_b + k_i \\ Xm &= Xm_a + Xm_b + k_i \cdot G_m\\ x &= x_a + x_b + k_i \\ X &= X_a + X_b + k_i \cdot G\\ \end{aligned} \tag{7} $$
Commitment phase
This phase allows Alice and Bob to commit to using their keys.
Starting values
Alice needs to provide Bob with the following:
- Script public key: \( K_{Sa}\)
- Monero public key \( Xm_a'\) with Ristretto encoding
Bob needs to provide Alice with the following:
- Script public key: \( K_{Sb}\)
- Monero public key \( Xm_b'\) with Ristretto encoding
Using the above equations in (7), Alice and Bob can calculate \(Xm\), \(Xm_a\), \(Xm_b\)
DLEQ proof
Alice needs to provide Bob with:
- Monero public key \(X_a\) encoding on Ristretto
- DLEQ proof for \(Xm_a\) and \(X_a\): \((R_{ZTa}, R_{ZMa}, s_{Za})\)
$$ \begin{aligned} e &= \hash{X_a \cat Xm_a \cat R_{ZTa} \cat R_{ZMa}} \\ s_{Za} &= r + e(x_a) \\ R_{ZTa} &= r \cdot G \\ R_{ZMa} &= r \cdot G_m \\ \end{aligned} \tag{8} $$
Bob needs to provide Alice with:
- Monero public key \(X_b\) encoding on Ristretto
- DLEQ proof for \(Xm_b\) and \(X_b\): \((R_{ZTb}, R_{ZMb}, s_{Zb})\)
$$ \begin{aligned} e &= \hash{X_b \cat Xm_b \cat R_{ZTb} \cat R_{ZMb}} \\ s_{Za} &= r + e(x_b) \\ R_{ZTa} &= r \cdot G \\ R_{ZMa} &= r \cdot G_m \\ \end{aligned} \tag{9} $$
Alice needs to verify Bob's DLEQ proof with:
$$ \begin{aligned} e &= \hash{X_b \cat Xm_b \cat R_{ZTb} \cat R_{ZMb}} \\ s_{Zb} \cdot G &= R_{ZTb} + e(X_b) \\ s_{Zb} \cdot G_m &= R_{ZMb} + e(Xm_b) \\ \end{aligned} \tag{10} $$
Bob needs to verify Alice's DLEQ proof with:
$$ \begin{aligned} e &= \hash{X_a \cat Xm_a \cat R_{ZTa} \cat R_{ZMa}} \\ s_{Za} \cdot G &= R_{ZTa} + e(X_a) \\ s_{Za} \cdot G_m &= R_{ZMa} + e(Xm_a) \\ \end{aligned} \tag{11} $$
XTR payment
Alice will construct the Tari UTXO with the correct script and publish the containing transaction to the blockchain, knowing that she can reclaim her Tari if Bob vanishes or tries to break the agreement. This is done with standard Mimblewimble rules and signatures.
XMR payment
When Bob sees that the Tari UTXO that Alice created is mined on the Tari blockchain with the correct script, Bob can publish the Monero transaction containing the Monero UTXO with the aggregate key \(Xm = Xm_a + Xm_b + k_i \cdot G_m \).
Claim XTR
When Alice sees that the Monero UTXO that Bob created is mined on the Monero blockchain containing the correct aggregate
key \(Xm\), she can provide Bob with the required pre_image
to spend the Tari UTXO. She does not have the
missing key \(xm_b \) to claim the Monero yet, but it will be revealed when Bob claims the Tari.
Bob can now supply the pre_image
and his Monero private key as transaction input to unlock the script.
Claim XMR
Alice can now see that Bob spent the Tari UTXO, and by examining the input_data
required to satisfy the script, she
can learn Bob's secret Monero key. Although this private key \( xm_b \) is now public knowledge, her part of the Monero spend key
is still private, and thus only she knows the complete Monero spend key. She can use this knowledge to claim the Monero
UTXO.
The refund
If something goes wrong and Bob never publishes the Monero or disappears, Alice needs to wait for the lock height
height_1
to pass. This will allow her to reclaim her Tari, but in doing so, she needs to publish her Monero secret key
as input to the script to unlock the Tari. When Bob comes back online, he can use this public knowledge to reclaim his
Monero, as only he knows both parts of the Monero UTXO spend key.
The lapse transaction
If something goes wrong and Alice never gives Bob the required pre_image
, Bob needs to wait for the lock height
height_2
to pass. This will allow him to claim the Tari he wanted all along, but in doing so, he needs to publish
his Monero secret key as input to the script to unlock the Tari. When Alice comes back online, she can use this public
knowledge to claim the Monero she wanted all along as only she now knows both parts of the Monero UTXO spend key.
Method 2
Detail
This method is based on work by the Farcaster project and the Comit team. It utilizes adapter signatures and multi-party commitment signatures to ensure that the spending party leaks their private Monero key part. Because all keys are aggregates keys, we need to ensure that the refund and lapse transactions are negotiated and signed before Alice publishes the Tari UTXO. This will allow either Alice or Bob to claim the refund and lapse transactions, respectively, without the other party being online.
TariScript
The Script used for the Tari UTXO is as follows:
CheckHeight(height_1)
LtZero
IFTHEN
PushPubkey(K_{Ss})
Else
CheckHeight(height_2)
LtZero
IFTHEN
PushPubkey(K_{Sr})
Else
PushPubkey(K_{Sl})
ENDIF
ENDIF
Before height_1
, Bob can claim the Tari UTXO if Alice gives him the correct signature to complete the transaction.
After height_1
but before height_2
, Alice can claim the Tari UTXO. After height_2,
Bob can claim the Tari UTXO.
Negotiation
Alice and Bob have to negotiate the exchange rate and the amount exchanged in the atomic swap. They also need to decide how the two UTXO's will look on the blockchain. To accomplish this, the following needs to be finalized:
- Amount of Tari to swap for the amount of Monero
- Monero public key parts \(Xm_a\), \(Xm_b\), and its aggregate form \(X\)
- Tari script key parts \(K_{Ssa}\), \(K_{Ssb}\), and its aggregate form \(K_{Ss}\) for the swap transaction
- Tari script key parts \(K_{Sra}\), \(K_{Srb}\), and its aggregate form \(K_{Sr}\) for the refund transaction
- Tari script key parts \(K_{Sla}\), \(K_{Slb}\), and its aggregate form \(K_{Sl}\) for the lapse transaction
- Tari sender offset key parts \(K_{Osa}\), \(K_{Osb}\), and its aggregate form \(K_{Os}\) for the swap transaction
- Tari sender offset key parts \(K_{Ora}\), \(K_{Orb}\), and its aggregate form \(K_{Or}\) for the refund transaction
- Tari sender offset key parts \(K_{Ola}\), \(K_{Olb}\), and its aggregate form \(K_{Ol}\) for the lapse transaction
- All of the nonces used in the script signature creation and Metadata signature for the swap, refund, and lapse transactions
- The script offset used in both the swap, refund, and lapse transactions
- The TariScript to be used in the Tari UTXO
- The blinding factor \(k_i\) for the Tari UTXO, which can be a Diffie-Hellman between their addresses.
Key selection
Using (1), we create the Monero keys as they are multi-party aggregate keys.
The script key parts for Alice and Bob is constructed as follows:
$$ \begin{aligned} k_{Ssa} &= \hash{\hash{K_{Ssa}' \cat K_{Ssb}'} \cat K_{Ssa}' } * k_{Ssa}' \\ k_{Ssb} &= \hash{\hash{K_{Ssa}' \cat K_{Ssb}'} \cat K_{Ssb}' } * k_{Ssb}' \\ k_{Ss} &= k_{Ssa} + k_{sb} \\ k_{Sra} &= \hash{\hash{K_{Sra}' \cat K_{Srb}'} \cat K_{Sra}' } * k_{Sra}' \\ k_{Srb} &= \hash{\hash{K_{Sra}' \cat K_{Srb}'} \cat K_{Srb}' } * k_{Srb}' \\ k_{Sr} &= k_{Sra} + k_{Srb} \\ k_{Sla} &= \hash{\hash{K_{Sla}' \cat K_{Slb}'} \cat K_{Sla}' } * k_{Sla}' \\ k_{Slb} &= \hash{\hash{K_{Sla}' \cat K_{Slb}'} \cat K_{Slb}' } * k_{Slb}' \\ k_{Sl} &= k_{Sla} + k_{Slb} \\ \end{aligned} \tag{12} $$
The sender offset key parts for Alice and Bob is constructed as follows:
$$ \begin{aligned} k_{Osa} &= \hash{\hash{K_{Osa}' \cat K_{Osb}'} \cat K_{Osa}' } * k_{Osa}' \\ k_{Osb} &= \hash{\hash{K_{Osa}' \cat K_{Osb}'} \cat K_{Osb}' } * k_{Osb}' \\ k_{Os} &= k_{Osa} + k_{Ssb} \\ k_{Ora} &= \hash{\hash{K_{Ora}' \cat K_{Orb}'} \cat K_{Ora}' } * k_{Ora}' \\ k_{Orb} &= \hash{\hash{K_{Ora}' \cat K_{Orb}'} \cat K_{Orb}' } * k_{Orb}' \\ k_{Or} &= k_{Ora} + k_{Srb} \\ k_{Ola} &= \hash{\hash{K_{Ola}' \cat K_{Olb}'} \cat K_{Ola}' } * k_{Ola}' \\ k_{Olb} &= \hash{\hash{K_{Ola}' \cat K_{Olb}'} \cat K_{Olb}' } * k_{Olb}' \\ k_{Ol} &= k_{Ola} + k_{Slb} \\ \end{aligned} \tag{13} $$
The Monero key parts for Alice and Bob is constructed as follows:
$$ \begin{aligned} xm_a' &= x_a' \\ xm_b' &= x_b' \\ Xm_a' &= x_a' \cdot G_m \\ Xm_b' &= x_b' \cdot G_m \\ X_a' &= x_a' \cdot G \\ X_b' &= x_b' \cdot G \\ x_a &= \hash{\hash{X_a' \cat X_b'} \cat X_a' } * x_a' \\ x_b &= \hash{\hash{X_a' \cat X_b'} \cat X_b' } * x_b' \\ x &= x_a + x_b + k_i \\ Xm &= Xm_a + Xm_b + k_i \cdot G_m \\ X &= X_a + X_b + k_i \cdot G\\ \end{aligned} \tag{14} $$
Commitment phase
Similar to method 1, this phase allows Alice and Bob to commit to using their keys and requires more than one round to complete. Some of the information that needs to be committed depends on previous knowledge.
Starting values
Alice needs to provide Bob with the following:
- Output commitment \(C_r\) of the refund transaction's output
- Output features \( F_r\) of the refund transaction's output
- Output script \( \script_r\) of the refund transaction's output
- Output commitment \(C_l\) of the lapse transaction's output
- Output features \( F_l\) of the lapse transaction's output
- Output script \( \script_l\) of the lapse transaction's output
- Public keys: \( K_{Ssa}'\), \( K_{Sra}'\), \( K_{Sla}'\), \( K_{Osa}'\), \( K_{Ora}'\), \( K_{Ola}'\), \( X_a'\)
- Nonces: \( R_{Ssa}\), \( R_{Sra}\), \( R_{Sla}\), \( R_{Msa}\), \( R_{Mra}\), \( R_{Mla}\)
Bob needs to provide Alice with the following:
- Output commitment \(C_s\) of the swap transaction's output
- Output features \( F_s\) of the swap transaction's output
- Output script \( \script_s\) of the swap transaction's output
- Public keys: \( K_{Ssb}'\), \( K_{Srb}'\), \( K_{Slb}'\), \( K_{Osb}'\), \( K_{Orb}'\), \( K_{Olb}'\), \( X_b'\)
- Nonces: \( R_{Ssb}\), \( R_{Srb}\), \( R_{Slb}\), \( R_{Msb}\), \( R_{Mrb}\), \( R_{Mlb}\)
After Alice and Bob have exchanged the variables, they start trading calculated values.
Construct adaptor signatures and DLEQ proofs
Alice needs to provide Bob with the following values:
- Adaptor signature part \(b_{Sra}'\) for \(b_{Sra}\)
- Signature part \(a_{Sra}\)
- Monero public key \(X_a\) encoded with Ristretto
- Monero public key \(Xm_a\) encoded with ed25519
- DLEQ proof for \(Xm_a\) and \(X_a\): \((R_{ZTa}, R_{ZMa}, s_{Za})\)
Alice constructs the adaptor signature of the input script signature parts for the refund transaction ( \(a_{Sra}\) and \(b_{Sra}'\) ) with:
$$ \begin{aligned} a_{Sra} &= r_{Sra_a} + e_r(v_{i}) \\ b_{Sra}' &= r_{Sra_b} + e_r(k_{Sra}+k_i) \\ e_r &= \hash{ (R_{Sr} + (X_a)) \cat \alpha_r \cat \input_r \cat (K_{Sra} + K_{Srb}) \cat C_i} \\ R_{Sr} &= r_{Sra_a} \cdot H + r_{Sra_b} \cdot G + R_{Srb} \\ X_a &= x_a \cdot G \\ \end{aligned} \tag{15} $$
Alice constructs the DLEQ proof:
$$ \begin{aligned} e &= \hash{X_a \cat Xm_a \cat R_{ZTa} \cat R_{ZMa}} \\ s_{Za} &= r + e(x_a) \\ R_{ZTa} &= r \cdot G \\ R_{ZMa} &= r \cdot G_m \\ \end{aligned} \tag{16} $$
Bob needs to provide Alice with the following values:
- Adaptor signature part \(b_{Ssb}'\) for \(b_{Ssb}\)
- Signature part \(a_{Ssb}\)
- Adaptor signature part \(b_{Slb}'\) for \(b_{Slb}\)
- Signature part \(a_{Slb}\)
- Monero public key \(X_b\) encoded with Ristretto
- Monero public key \(Xm_b\) encoded with ed25519
- DLEQ proof for \(Xm_b\) and \(X_b\): \((R_{ZTb}, R_{ZMb}, s_{Zb})\)
Bob constructs the adaptor signatures of the input script signature parts for the swap transaction ( \(a_{Ssb}\), \(b_{Ssb}'\) ) and the lapse transaction ( \(a_{Slb}\) and \(b_{Slb}'\) ) with
$$ \begin{aligned} a_{Ssb} &= r_{Ssb_a} + e_s(v_{i}) \\ b_{Ssb}' &= r_{Ssb_b} + e_s(k_{Ssb}+k_i) \\ e_s &= \hash{ (R_{Sr} + (X_b)) \cat \alpha_i \cat \input_i \cat (K_{Ssa} + K_{Ssb}) \cat C_i} \\ R_{Ss} &= r_{Ssb_a} \cdot H + r_{Ssb_b} \cdot G + R_{Ssa} \\ a_{Slb} &= r_{Slb_a} + e_l(v_{i}) \\ b_{Slb}' &= r_{Slb_b} + e_l(k_{Slb}+k_i) \\ e_l &= \hash{ (R_{Sl} + (X_b)) \cat \alpha_i \cat \input_i \cat (K_{Sla} + K_{Slb}) \cat C_i} \\ R_{Sl} &= r_{Slb_a} \cdot H + r_{Slb_b} \cdot G + R_{Sla} \\ X_b &= x_b \cdot G \\ \end{aligned} \tag{17} $$
Bob constructs the DLEQ proof:
$$ \begin{aligned} e &= \hash{X_b \cat Xm_b \cat R_{ZTb} \cat R_{ZMb}} \\ s_{Zb} &= r + e(x_b) \\ R_{ZTb} &= r \cdot G \\ R_{ZMb} &= r \cdot G_m \\ \end{aligned} \tag{18} $$
Verify adaptor signatures and DLEQ proofs
Alice needs to verify Bob's adaptor signatures with:
$$ \begin{aligned} a_{Ssb} \cdot H + b_{Ssb}' \cdot G &= R_{Ssb} + (C_i+K_{Ssb})*e_s \\ a_{Slb} \cdot H + b_{Slb}' \cdot G &= R_{Slb} + (C_i+K_{Slb})*e_l \\ \end{aligned} \tag{19} $$
Alice needs to verify Bob's Monero public keys using the zero-knowledge proof:
$$ \begin{aligned} e &= \hash{X_b \cat Xm_b \cat R_{ZTb} \cat R_{ZMb}} \\ s_{Zb} \cdot G &= R_{ZTb} + e(X_b) \\ s_{Zb} \cdot G_m &= R_{ZMb} + e(Xm_b) \\ \end{aligned} \tag{20} $$
Bob needs to verify Alice's adaptor signature with:
$$ \begin{aligned} a_{Sra} \cdot H + b_{Sra}' \cdot G &= R_{Sra} + (C_i+K_{Sra})*e_r \\ \end{aligned} \tag{21} $$
Bob needs to verify Alice's Monero public keys using the zero-knowledge proof:
$$ \begin{aligned} e &= \hash{X_a \cat XM_a \cat R_{ZTa} \cat R_{ZMa}} \\ s_{Za} \cdot G &= R_{ZTa} + e(X_a) \\ s_{Za} \cdot G_m &= R_{ZMa} + e(Xm_a) \\ \end{aligned} \tag{22} $$
Swap out refund and lapse transactions
If Alice and Bob are happy with the verification, they need to swap out refund and lapse transactions.
Alice needs to provide Bob with the following:
- Input script signature for lapse transaction (\( (a_{Sla}, b_{Sla}), R_{Sla}\) )
- Output metadata signature for lapse transaction (\( b_{Mla}, R_{Mla}\) )
- Script offset for lapse transaction \( \so_{la} \)
Alice constructs for the lapse transaction signatures. $$ \begin{aligned} a_{Sla} &= r_{Sla_a} + e_l(v_{i}) \\ b_{Sla} &= r_{Sla_b} + e_l(k_{Sla}) \\ e_l &= \hash{ (R_{Sl} + (X_b)) \cat \alpha_i \cat \input_i \cat (K_{Sla} + K_{Slb}) \cat C_i} \\ R_{Sl} &= r_{Sla_a} \cdot H + r_{Sla_b} \cdot G + R_{Slb}\\ b_{Mla} &= r_{Mla_b} + e(k_{Ola}) \\ R_{Mla} &= b_{Mla} \cdot G \\ e &= \hash{ (R_{Mla} + R_{Mlb}) \cat \script_l \cat F_l \cat (K_{Ola} + K_{Olb}) \cat C_l} \\ \so_{la} &= k_{Sla} - k_{Ola} \\ \end{aligned} \tag{23} $$
Bob needs to provide Alice with the following:
- Input script signature for refund transaction (\( (a_{Srb}, b_{Srb}), R_{Srb}\) )
- Output metadata signature for refund transaction (\( b_{Mrb}, R_{Mrb}\) )
- Script offset for refund transaction \( \so_{rb} \)
Bob constructs for the refund transaction signatures. $$ \begin{aligned} a_{Srb} &= r_{Srb_a} + e_r(v_{i}) \\ b_{Srb} &= r_{Srb_b} + e_r(k_{Srb}) \\ e_r &= \hash{ (R_{Sr} + (X_a)) \cat \alpha_i \cat \input_i \cat (K_{Sra} + K_{Srb}) \cat C_i} \\ R_{Sl} &= r_{Srb_a} \cdot H + r_{Srb_b} \cdot G + R_{Sra}\\ b_{Mrb} &= r_{Mrb_b} + e(k_{Orb}) \\ R_{Mrb} &= b_{Mrb} \cdot G \\ e &= \hash{ (R_{Mra} + R_{Mrb}) \cat \script_r \cat F_r \cat (K_{Ora} + K_{Orb}) \cat C_r} \\ \so_{rb} &= k_{Srb} - k_{Orb} \\ \end{aligned} \tag{24} $$
Although the script validation on output \(C_i\) will not pass due to the lock height, both Alice and Bob need to verify that the total aggregated signatures and script offset for the refund and lapse transaction are valid should they need to publish them at a future date without the other party’s presence.
XTR payment
If Alice and Bob are happy with all the committed values, Alice will construct the Tari UTXO with the correct script and publish the containing transaction to the blockchain. Because Bob already gave her the required signatures for his part of the refund transaction, Alice can easily compute the required aggregated signatures by adding the parts together. She has all the knowledge to spend this after the lock expires.
XMR Payment
When Bob sees that the Tari UTXO that Alice created is mined on the Tari blockchain with the correct script, he can go ahead and publish the Monero UTXO with the aggregate key \(Xm = Xm_a + Xm_b \).
Claim XTR
When Alice sees that the Monero UTXO that Bob created is mined on the Monero blockchain containing the correct aggregate key \(Xm\), she can provide Bob with the following allowing him to spend the Tari UTXO:
- Script signature for the swap transaction \((a_{Ssa}\, b_{Ssa}), R_{Ssa}\)
- Metadata signature for swap transaction \((b_{Msa}, R_{Msa})\)
- Script offset for swap transaction \( \so_{sa} \)
She does not have the missing key \(x_b \) to claim the Monero yet, but it will be revealed when Bob claims the Tari.
Alice constructs for the swap transaction. $$ \begin{aligned} a_{Ssa} &= r_{Ssa_a} + e_s(v_{i}) \\ b_{Ssa} &= r_{Ssa_b} + e_s(k_{Ssa}) \\ e_s &= \hash{ (R_{Ss} + (X_b)) \cat \alpha_i \cat \input_i \cat (K_{Ssa} + K_{Ssb}) \cat C_i} \\ R_{Ss} &= r_{Ssa_a} \cdot H + r_{Ssa_b} \cdot G + R_{Ssb}\\ b_{Msa} &= r_{Msa_b} + e(k_{Osa}) \\ R_{Msa} &= b_{Msa} \cdot G \\ e &= \hash{ (R_{Msa} + R_{Msb}) \cat \script_s \cat F_s \cat (K_{Osa} + K_{Osb}) \cat C_s} \\ \so_{sa} &= k_{Ssa} - k_{Osa} \\ \end{aligned} \tag{25} $$
Bob constructs the swap transaction. $$ \begin{aligned} a_{Ssb} &= r_{Ssb_a} + e_s(v_{i}) \\ b_{Ssb} &= r_{Ssb_b} + x_b + e_s(k_{Ssb} + k_i) \\ e_s &= \hash{ (R_{Ss} + (X_b)) \cat \alpha_i \cat \input_i \cat (K_{Ssa} + K_{Ssb}) \cat C_i} \\ a_{Ss} &= a_{Ssa} + a_{Ssb} \\ b_{Ss} &= b_{Ssa} + b_{Ssb} \\ R_{Ss} &= r_{Ssa_b} \cdot H + r_{Ssb_b} \cdot G + R_{Ssa}\\ a_{Msb} &= r_{Msb_a} + e(v_{s}) \\ b_{Msb} &= r_{Msb_b} + e(k_{Osb}+k_s) \\ R_{Msb} &= a_{Msb} \cdot H + b_{Msb} \cdot G \\ e &= \hash{ (R_{Msa} + R_{Msb}) \cat \script_s \cat F_s \cat (K_{Osa} + K_{Osb}) \cat C_s} \\ R_{Ms} &= R_{Msa} + R_{Msb} \\ \so_{sb} &= k_{Ssb} - k_{Osb} \\ \so_{s} &= \so_{sa} +\so_{sb} \\ \end{aligned} \tag{26} $$
Bob's transaction now has all the required signatures to complete the transaction. He will then publish the transaction.
Claim XMR
Because Bob has now published the transaction on the Tari blockchain, Alice can calculate the missing Monero key \(x_b\) as follows:
$$ \begin{aligned} b_{Ss} &= b_{Ssa} + b_{Ssb} \\ b_{Ss} - b_{Ssa} &= b_{Ssb} \\ b_{Ssb} &= r_{Ssb_b} + x_b + e_s(k_{Ssb} + k_i) \\ b_{Ssb} - b_{Ssb}' &= r_{Ssb_b} + x_b + e_s(k_{Ssb} + k_i) -(r_{Ssb_b} + e_s(k_{Ssb}+k_i))\\ b_{Ssb} - b_{Ssb}' &= x_b \\ \end{aligned} \tag{27} $$
With \(x_b\) in hand, she can calculate \(X = x_a + x_b\), and with this, she claims the Monero.
The refund
If something goes wrong and Bob never publishes the Monero, Alice needs to wait for the lock height
height_1
to pass. This will allow her to create the refund transaction to reclaim her Tari.
Alice constructs the refund transaction with
$$ \begin{aligned} a_{Sra} &= r_{Sra_a} + e_s(v_{i}) \\ b_{Sra} &= r_{Sra_b} + x_a + e_s(k_{Sra} + k_i) \\ e_r &= \hash{ (R_{Sr} + (X_a)) \cat \alpha_i \cat \input_i \cat (K_{Sra} + K_{Srb}) \cat C_i} \\ a_{Sr} &= a_{Sra} + a_{Srb} \\ b_{Sr} &= b_{Sra} + b_{Srb} \\ R_{Sr} &= r_{Sra_a} \cdot H + r_{Sra_b} \cdot G + R_{Srb}\\ a_{Mra} &= r_{Mra_a} + e(v_{r}) \\ b_{Mra} &= r_{Mra_b} + e(k_{Ora}+k_r) \\ R_{Mra} &= a_{Mra} \cdot H + b_{Mra} \cdot G \\ e &= \hash{ (R_{Mra} + R_{Mrb}) \cat \script_s \cat F_s \cat (K_{Ora} + K_{Orb}) \cat C_r} \\ R_{Mr} &= R_{Mra} + R_{Mrb} \\ \so_{ra} &= k_{Sra} - k_{Ora} \\ \so_{r} &= \so_{ra} +\so_{rb} \\ \end{aligned} \tag{28} $$
This allows Alice to claim back her Tari, but it also exposes her Monero key \(x_a\) This means if Bob did publish the Monero UTXO, he could calculate \(X\) using: $$ \begin{aligned} b_{Sr} &= b_{Sra} + b_{Srb} \\ b_{Sr} - b_{Sra} &= b_{Sra} \\ b_{Sra} &= r_{Sra_b} + x_a + e_r(k_{Sra} + k_i) \\ b_{Sra} - b_{Sra}' &= r_{Sra_b} + x_a + e_r(k_{Sra} + k_i) -(r_{Sra_b} + e_r(k_{Sra}+k_i))\\ b_{Sra} - b_{Sra}' &= x_a \\ \end{aligned} \tag{29} $$
The lapse transaction
If something goes wrong and Alice never publishes her refund transaction, Bob needs to wait for the
lock height height_2
to pass. This will allow him to create the lapse transaction to claim the Tari.
Bob constructs the lapse transaction with $$ \begin{aligned} a_{Slb} &= r_{Slb_a} + e_l(v_{i}) \\ b_{Slb} &= r_{Slb_b} + x_b + e_l(k_{Slb} + k_i) \\ e_l &= \hash{ (R_{Sl} + (X_b)) \cat \alpha_i \cat \input_i \cat (K_{Sla} + K_{Slb}) \cat C_i} \\ a_{Sl} &= a_{Sla} + a_{Slb} \\ b_{Sl} &= b_{Sla} + b_{Slb} \\ R_{Sl} &= r_{Slb_a} \cdot H + r_{Slb_b} \cdot G + R_{Sla}\\ a_{Mlb} &= r_{Mlb_a} + e(v_{l}) \\ b_{Mlb} &= r_{Mlb_b} + e(k_{Olb}+k_l) \\ R_{Mlb} &= a_{Mlb} \cdot H + b_{Mlb} \cdot G \\ e &= \hash{ (R_{Mla} + R_{Mlb}) \cat \script_l \cat F_l \cat (K_{Ola} + K_{Olb}) \cat C_l} \\ R_{Ml} &= R_{Mla} + R_{Mlb} \\ \so_{lb} &= k_{Slb} - k_{Olb} \\ \so_{r} &= \so_{la} +\so_{lb} \\ \end{aligned} \tag{30} $$
This allows Bob to claim the Tari he originally wanted, but it also exposes his Monero key \(x_b\) This means if Alice ever comes back online, she can calculate \(X\) and claim the Monero she wanted all along using: $$ \begin{aligned} b_{Sl} &= b_{Slb} + b_{Slb} \\ b_{Sl} - b_{Slb} &= b_{Slb} \\ b_{Slb} &= r_{Slb_b} + x_a + e_r(k_{Slb} + k_i) \\ b_{Slb} - b_{Slb}' &= r_{Slb_b} + x_b + e_r(k_{Slb} + k_i) -(r_{Slb_b} + e_r(k_{Slb}+k_i))\\ b_{Slb} - b_{Slb}' &= x_b \\ \end{aligned} \tag{31} $$
Notation
Where possible, the "usual" notation is used to denote terms commonly found in cryptocurrency literature. Lower case characters are used as private keys, while uppercase characters are used as public keys. New terms introduced here are assigned greek lowercase letters in most cases. Some terms used here are noted down in TariScript.
Name | Symbol | Definition |
---|---|---|
subscript s | \( _s \) | The swap transaction |
subscript r | \( _r \) | The refund transaction |
subscript l | \( _l \) | The lapse transaction |
subscript a | \( _a \) | Belongs to Alice |
subscript b | \( _b \) | Belongs to Bob |
Monero key | \( X \) | Aggregate Monero public key encoded with Ristretto |
Alice's Monero key | \( X_a \) | Alice's partial Monero public key encoded with Ristretto |
Bob's Monero key | \( X_b \) | Bob's partial Monero public key encoded with Ristretto |
Monero key | \( Xm \) | Aggregate Monero public key encoded with Ed25519 |
Alice's Monero key | \( Xm_a \) | Alice's partial Monero public key encoded with Ed25519 |
Bob's Monero key | \( Xm_b \) | Bob's partial Monero public key encoded with Ed25519 |
Script key | \( K_s \) | The script key of the utxo |
Alice's Script key | \( K_sa \) | Alice's partial script key |
Bob's Script key | \( K_sb \) | Bob's partial script key |
Alice's adaptor signature | \( b'_{Sa} \) | Alice's adaptor signature for the signature \( b_{Sa} \) of the script_signature of the utxo |
Bob's adaptor signature | \( b'_{Sb} \) | Bob's adaptor signature for the \( b_{Sb} \) of the script_signature of the utxo |
Ristretto G generator | \(k \cdot G \) | Value k over Curve25519 G generator encoded with Ristretto |
Ristretto H generator | \(k \cdot H \) | Value k over Tari H generator encoded with Ristretto |
ed25519 G generator | \(k \cdot G_m \) | Value k over Curve25519 G generator encoded with Ed25519 |
RFC-310/Submarine Swap
Maintainer(s): S W van Heerden
Licence
Copyright 2021 The Tari Development Community
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Language
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as shown here.
Disclaimer
This document and its content are intended for information purposes only and may be subject to change or update without notice.
This document may include preliminary concepts that may or may not be in the process of being developed by the Tari community. The release of this document is intended solely for review and discussion by the community of the technological merits of the potential system outlined herein.
Goals
This Request for Comment (RFC) aims to describe how an Atomic swap between Tari and Minotari will be created.
Related Requests for Comment
$$ \newcommand{\script}{\alpha} % utxo script \newcommand{\input}{ \theta } \newcommand{\cat}{\Vert} \newcommand{\so}{\gamma} % script offset \newcommand{\hash}[1]{\mathrm{H}\bigl({#1}\bigr)} $$
Comments
Any comments, changes or questions to this PR can be made in one of the following ways:
- Create a new PR on the Tari project Github pull requests.
- Create a new issue on the Tari project Github issues.
Description
To be able to exchange Tari and Minotari without the use of some centralized exchange service, we need to do Submarine swaps or Atomic swaps between the two. We want to keep Tari as bare bones as possible with if possible just a commitment and perhaps a range proof, this means that we will not have access to smart contract features typically required for doing submarine swaps. This does not mean it is not possible to do atomic swaps with non-smart contract coins, look at RFC-0241: AtomicSwap XMR to see how this is done with Minotari and Monero.
Method
The primary, happy path outline of a Tari - Minotari submarine swap is described here, and more detail will follow. We assume that Alice wants to trade her Minotari for Bob's Tari.
- Negotiation - Both parties negotiate the value and other details of the Tari and Minotari commitments.
- Commitment - Both parties commit to the keys, nonces, inputs, and outputs to use for the transaction.
- Minotari payment - Alice makes the Minotari payment to a UTXO containing a "special" script described below.
- Tari Payment - The Tari payment is made to a multiparty scriptless script commitment.
- Claim Minotari - Bob redeems the Minotari, and in doing so, reveals the Tari private key to Alice only.
- Claim Tari - Alice may claim the Tari using the revealed key.
Please take note of the notation used in TariScript and specifically notation used on the signatures on the transaction inputs and on the signatures on the transaction outputs. We will note other notations in the Notation section.
TL;DR
The scheme revolves around Alice, who wants to exchange her Minotari for Bob's Tari. Because they don't trust each other, they have to commit some information to do the exchange. And if something goes wrong here, we want to ensure that we can refund both parties either in Tari or Minotari.
How this works is that Alice and Bob create a shared output on both chains. The Tari output is a simple aggregate key to unlock the commitment, while multiple keys are needed to unlock the Minotari UTXO. An aggregate key locks this Tari commitment that neither Alice nor Bob knows, but they both know half of the key. The current Minotari block height determines the unlocking key for the Minotari UTXO.
The process is started by Alice and Bob exchanging and committing to some information. Alice is the first to publish a transaction, which creates the Minotari UTXO. If Bob is happy that the Minotari UTXO has been mined and verifies all the information, he will publish a transaction to create the Tari commitment.
The TariScript script on the UTXO ensures that they will have to reveal their portion of the Tari key when either Alice or Bob spends this. This disclosure allows the other party to claim the Tari by being the only one to own the complete Tari aggregate key.
The script will ensure that at any point in time, at least someone can claim the Minotari UTXO, and if that person does so, the other party can claim the Tari commitment by looking at the spending data. It has two lock heights, determining who can claim the Minotari UTXO if the happy path fails. Before the first lock height, only Bob can claim the Tari; we call this the swap transaction.
If Bob disappears after Alice has posted the Minotari UTXO, Alice can claim the Minotari after the first lock height and before the second lock height; we call this the refund transaction. It ensures that Alice can reclaim her Minotari if Bob disappears, and if Bob reappears, he can reclaim his Tari.
That leaves us with the scenario where Alice disappears after Bob posts the Tari transaction, in which case we need to protect Bob. After the second lock height, only Bob can claim the Minotari; we call this the lapse transaction. The lapse transaction will reveal Bob's Tari key so that if Alice reappears, she can claim the Tari.
Heights, Security, and other considerations
We need to consider a few things for this to be secure, as there are possible scenarios that can reduce the security in the atomic swap.
When looking at the two lock heights, the first lock height should be sufficiently large enough to give ample time for Alice to post the Tari UTXO transaction and for it to be mined with a safe number of confirmations, and for Bob to post the Tari transaction and for it to be mined with a safe number of confirmations. The second lock height should give ample time for Alice after the first lock height to re-claim her Minotari. Larger heights here might make refunds slower, but it should be safer in giving more time to finalize this.
Allowing both to claim the Minotari after the second lock height is, on face value, a safer option. This can be done by enabling either party to claim the script with the lapse transaction. The counterparty can then claim the Tari. However, this will open up an attack vector to enable either party to claim the Tari while claiming the Minotari. Either party could trivially pull off such a scheme by performing a front-running attack and having a bit of luck. The counterparty monitors all broadcast transactions to base nodes. Upon identifying the lapse transaction, they do two things; in quick succession, broadcast their lapse transaction and the transaction to claim the Tari, both with sufficiently high fees. Base nodes will prefer to mine transactions with the higher fees, and thus the counterparty can walk away with both the Minotari and the Tari.
It is also possible to prevent the transaction from being mined after being submitted to the mempool. This can be caused by a combination of a too busy network, not enough fees, or a too-small period in the time locks. When one of these atomic swap transactions gets published to a mempool, we effectively already have all the details exposed. For the atomic swaps, it means we already revealed part of the Tari key, although the actual Minotari transaction has not been mined. But this is true for any HTLC or like script on any blockchain. But in the odd chance that this does happen whereby the fees are too little and time locks not enough, it should be possible to do a child-pays-for-parent transaction to bump up the fees on the transaction to get it mined and confirmed.
Key construction
Using multi-signatures with Schnorr signatures, we need to ensure that the keys are constructed so that key cancellation attacks are not possible. To do this, we create new keys from the chosen public keys \(K_a'\) and \(K_b'\)
$$ \begin{aligned} K_a &= \hash{\hash{K_a' \cat K_b'} \cat K_a' } * K_a' \\ k_a &= \hash{\hash{K_a' \cat K_b'} \cat K_a' } * k_a' \\ K_b &= \hash{\hash{K_a' \cat K_b'} \cat K_b' } * K_b' \\ k_b &= \hash{\hash{K_a' \cat K_b'} \cat K_b' } * k_b' \\ \end{aligned} \tag{1} $$
Key security
The risk of publicly exposing part of the Tari private key is still secure because of how ECC works. We can add two secret keys together and share the public version of both. And at the same time, we know that no one can calculate the secret key with just one part.
$$ \begin{aligned} (k_a + k_b) \cdot G &= k_a \cdot G + k_b \cdot G\\ (k_a + k_b) \cdot G &= K_a + K_b \\ (k_a + k_b) \cdot G &= K \\ \end{aligned} \tag{5} $$
We know that \(K\), \(K_a\), \(K_b\) are public. While \(k\), \(k_a\), \(k_b\) are all private.
But if we expose \(k_b\), we can try to do the following: $$ \begin{aligned} (k_a + k_b) \cdot G &= K_a + K_b\\ k_a \cdot G &= (K_a + K_b - k_b \cdot G) \\ k_a \cdot G &= K_a \\ \end{aligned} \tag{6} $$
However, this is the Elliptic-Curve Discrete Logarithm Problem, and there is no easy solution to solve this on current computer hardware. Thus this is still secure even though we leaked part of the secret key \(k\).
Method
Detail
We rely purely on TariScript to enforce the exposure of the private Tari aggregate keys. Based on Point Time Lock Contracts,
the script forces the spending party to supply their Tari private key part as input data to the script, evaluated via the operation ToRistrettoPoint
. This TariScript
operation will publicly reveal part of the aggregated Tari private key, but this is still secure: see Key security.
The simplicity of this method lies therein that the spending party creates all transactions on their own. Bob requires a pre-image from Alice to complete the swap transaction; Alice needs to verify that Bob published the Tari transaction and that everything is complete as they have agreed. If she is happy, she will provide Bob with the pre-image to claim the Tari UTXO.
TariScript
The Script used for the Tari UTXO is as follows:
ToRistrettoPoint
CheckHeight(height_1)
LtZero
IFTHEN
PushPubkey(X_b)
EqualVerify
HashSha256
PushHash(HASH256{pre_image})
EqualVerify
PushPubkey(K_{Sb})
Else
CheckHeight(height_2)
LtZero
IFTHEN
PushPubkey(X_a)
EqualVerify
PushPubkey(K_{Sa})
Else
PushPubkey(X_b)
EqualVerify
PushPubkey(K_{Sb})
ENDIF
ENDIF
Before height_1
, Bob can claim the Minotari UTXO by supplying pre_image
and his private Tari key part x_b
. After
height_1
but before height_2
, Alice can claim the Minotari UTXO by supplying her private Tari key part x_a
. After
height_2
, Bob can claim the Minotari UTXO by providing his private Tari key part x_b
.
Negotiation
Alice and Bob have to negotiate the exchange rate and the amount exchanged in the atomic swap. They also need to decide how the two UTXO's will look on the blockchain. To accomplish this, the following needs to be finalized:
- Amount of Minotari to swap for the amount of Tari
- Tari public key parts \(X_a\), \(X_b\) ,and its aggregate form \(X\)
- Minotari script key parts \(K_{Sa}\), \(K_{Sb}\)
- The TariScript to be used in the Minotari UTXO
- The blinding factor \(k_i\) for the Minotari UTXO, which can be a Diffie-Hellman between their Tari network addresses.
Key selection
Using (1), we create the Tari keys as they are multi-party aggregate keys. The Tari key parts for Alice and Bob is constructed as follows:
$$ \begin{aligned} X_a' &= x_a' \cdot G \\ X_b' &= x_b' \cdot G \\ x_a &= \hash{\hash{X_a' \cat X_b'} \cat X_a' } * x_a' \\ x_b &= \hash{\hash{X_a' \cat X_b'} \cat X_b' } * x_b' \\ x_a &= x_a \\ x_b &= x_b \\ X_a &= \hash{\hash{X_a' \cat X_b'} \cat X_a' } * X_a' \\ X_b &= \hash{\hash{X_a' \cat X_b'} \cat X_b' } * X_b' \\ x &= x_a + x_b + k_i \\ X &= X_a + X_b + k_i \cdot G_m\\ x &= x_a + x_b + k_i \\ X &= X_a + X_b + k_i \cdot G\\ \end{aligned} \tag{7} $$
Commitment phase
This phase allows Alice and Bob to commit to using their keys.
Starting values
Alice needs to provide Bob with the following:
- Script public key: \( K_{Sa}\)
- Tari public key \( X_a'\)
Bob needs to provide Alice with the following:
- Script public key: \( K_{Sb}\)
- Tari public key \( X_b'\)
Using the above equations in (7), Alice and Bob can calculate \(X\), \(X_a\), \(X_b\)
Minotari payment
Alice will construct the Minotari UTXO with the correct script and publish the containing transaction to the blockchain, knowing that she can reclaim her Minotari if Bob vanishes or tries to break the agreement. This is done with standard Mimblewimble rules and signatures.
Tari payment
When Bob sees that the Minotari UTXO that Alice created is mined on the Minotari blockchain with the correct script, Bob can publish the Tari transaction containing the Tari commitment with the aggregate key \(X = X_a + X_b + k_i \cdot G \).
Claim Minotari
When Alice sees that the Tari commitment that Bob created is confirmed on the second layer containing the correct aggregate
key \(X\), she can provide Bob with the required pre_image
to spend the Minotari UTXO. She does not have the
missing key \(x_b \) to claim the Tari yet, but it will be revealed when Bob claims the Minotari.
Bob can now supply the pre_image
and his Tari private key as transaction input to unlock the script.
Claim Tari
Alice can now see that Bob spent the Minotari UTXO, and by examining the input_data
required to satisfy the script, she
can learn Bob's secret Tari key. Although this private key \( x_b \) is now public knowledge, her part of the Tari spend key
is still private, and thus only she knows the complete Tari spend key. She can use this knowledge to claim the Tari commitment.
The refund
If something goes wrong and Bob never publishes the Tari or disappears, Alice needs to wait for the lock height
height_1
to pass. This will allow her to reclaim her Minotari, but in doing so, she needs to publish her Tari secret key
as input to the script to unlock the Minotari. When Bob comes back online, he can use this public knowledge to reclaim his
Tari, as only he knows both parts of the Tari commitment spend key.
The lapse transaction
If something goes wrong and Alice never gives Bob the required pre_image
, Bob needs to wait for the lock height
height_2
to pass. This will allow him to claim the Minotari he wanted all along, but in doing so, he needs to publish
his Tari secret key as input to the script to unlock the Minotari. When Alice comes back online, she can use this public
knowledge to claim the Tari she wanted all along as only she now knows both parts of the Tari commitment spend key.
Notation
Where possible, the "usual" notation is used to denote terms commonly found in cryptocurrency literature. Lower case characters are used as private keys, while uppercase characters are used as public keys. New terms introduced here are assigned greek lowercase letters in most cases. Some terms used here are noted down in TariScript.
Name | Symbol | Definition |
---|---|---|
subscript s | \( _s \) | The swap transaction |
subscript r | \( _r \) | The refund transaction |
subscript l | \( _l \) | The lapse transaction |
subscript a | \( _a \) | Belongs to Alice |
subscript b | \( _b \) | Belongs to Bob |
Tari key | \( X \) | Aggregate Tari public key |
Alice's Tari key | \( X_a \) | Alice's partial Tari public key |
Bob's Tari key | \( X_b \) | Bob's partial Tari public key |
Script key | \( K_s \) | The script key of the utxo |
Alice's Script key | \( K_sa \) | Alice's partial script key |
Bob's Script key | \( K_sb \) | Bob's partial script key |
Alice's adaptor signature | \( b'_{Sa} \) | Alice's adaptor signature for the signature \( b_{Sa} \) of the script_signature of the utxo |
Bob's adaptor signature | \( b'_{Sb} \) | Bob's adaptor signature for the \( b_{Sb} \) of the script_signature of the utxo |
Ristretto G generator | \(k \cdot G \) | Value k over Curve25519 G generator encoded with Ristretto |
Change Log
Date | Change | Author |
---|---|---|
23 Oct 2023 | Thaum -> Tari | CjS77 |
15 Nov 2022 | First outline | SWvHeerden |
RFC-0388 Bearer Tokens
A Scheme for Granting the Bearer Permissions on Second Layer Assets
Maintainer(s): @mikethetike
Licence
Copyright 2022 The Tari Development Community
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Language
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as shown here.
Disclaimer
This document and its content are intended for information purposes only and may be subject to change or update without notice.
This document may include preliminary concepts that may or may not be in the process of being developed by the Tari community. The release of this document is intended solely for review and discussion by the community regarding the technological merits of the potential system outlined herein.
Goals
Many dapps and web3 enabled applications require the ability to spend or interact with the assets that a user owns on their behalf. Ethereum and many other blockchains achieve this in a stateful manner by invoking methods such as 'approve' and 'approve_all' in ERC20 and ERC721. Being stateful, the user must spend fees in order to add or revoke permissions. When fees are high, or due to simplistic user interfaces, a user will often grant a much higher level of permission than is required. For example, the user may approve a service to transfer all of their funds, or all of their NFTs.
The scheme proposed by this RFC is a stateless token that the bearer can use to invoke methods on the DAN layer.
Related Requests for Comment
Description
This scheme is inspired by Macaroons in that it allows the bearer of a token to delegate a more restrictive token to another bearer.
Structure
Auth Token
Field | Type | Description |
---|---|---|
root_nonce | bytes(32) (optional) | a base ID used to revoke permissions(see below) |
expires_at_height | u64 (optional) | the sidechain height at which this token expires. Timestamps are not reliable in blockchains, so height is used in this case |
granted_to | pubkey | the public key of the grantee. Any bearer using this token will need to prove knowledge of the private key |
scopes | string[] | A list of scopes granted to this scope |
caveats | CaveatExpression[] | An ordered list of caveats |
based_on | Token (optional) | If this token is derived from another token, it should be present here |
issuer | PubKey | the issuer of this token |
issuer_sig | Signature | a signature signed by issuer of the challenge Hash(root_nonce + granted_to + scopes + caveats + expires_at_height + based_on + issuer ) |
Caveat Expression
Caveat Expression = <Field> <operator> <Argument>
Field: An arbitrary string that will be interpreted by the code Operator: OneOf("eq", "le", "lt","ge", "gt") Argument: A constant value, to be interpreted by the code
Examples
amount lt 1000
token_id eq 4759
Delegation
A bearer of a token may grant another identity a more specific token, provided that the scopes and caveats are more restrictive.
Validation
The root_nonce, if specified must match the root nonce on record for the issuer.
Open question: should root nonce just be a special caveat?
Before starting execution of the instruction, the list of auth tokens must be validated to ensure that each token is more restrictive than the last.
When executing instructions, caveats MUST be checked if they are relevant to the resources being acted upon.
For example, if a function requires a scope transfer
, the most specific AuthToken must have that scope present.
Revoking Tokens
Each contract SHOULD store a root nonce for each identity in the contract. To revoke a set of tokens, an identity owner may change their root nonce. This will revoke all tokens based on this root.
Example 1: Invoking an instruction
In this example, Alice wants to allow Bob to spend 100 of a fungible asset called WARI
from her account.
Let's assume the transfer function looks like this:
fn transfer(amount: u64, from: PublicKey, to: PublicKey)
{
requires_scope("transfer", from);
// ...
}
Alice creates a token:
/* bob's token */
{
"scopes": ["transfer"],
"granted_to": "<Bob's Public Key>",
"caveats": [
"amount le 100"
],
"issuer": "<Alice's Public Key>",
"issuer_sig": "<sig>"
}
Note: The
root_nonce
andexpires_at_height
are missing here, but it would have been better for Alice to include these. Also, this token allows Bob to spend up to 100 at a time, but does not restrict Bob from using this token again
Bob can now create the instruction and submit it to the validator node. The validator node checks that the instruction signature
matches the public key in granted_to
and also checks that the amount
parameter is less than or equal to 100. Finally, the
validator node checks that the scope includes 'transfer', and Alice's public key (specified in from
) matches the auth token's issuer signature.
Example 2: Delegation
Let's continue the example, but in this case Bob wants to allow Carol to spend the funds.
In this case, Bob creates a token for Carol with the following:
/* carol's token */
{
"scopes": ["transfer"],
"granted_to": "<Carol's Public Key",
"caveats": [
"amount le 90"
],
"issuer": "<Bob's Public Key>",
"issuer_sig": "<sig>",
"based_on": {
/* bob's token */
}
}
Carol can now create a set of instructions, attach the token and sign it with her public key.
{
"instructions": [
{
"method": "transfer",
"amount": "80",
"from": "<alice's pub key>",
"to": "<carol's pub key>"
},
{
"method": "transfer",
"amount": "10",
"from": "<alice's pub key>",
"to": "<bob's pub key>"
}
],
"authority": {
"bearer": {/* carol's token */},
"sig": "<sig with carol's pub key>"
}
}
When the validator node receives a set of instructions with this token, it must process each token recursively, with respect to the token it is based on.
RFC-8001/MultiPartyTransactions
Time related transactions
Maintainer(s): SW van heerden
License
Copyright 2019 The Tari Development Community
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Language
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as shown here.
Disclaimer
The purpose of this document and its content is for information purposes only and may be subject to change or update without notice.
This document may include preliminary concepts that may or may not be in the process of being developed by the Tari community. The release of this document is intended solely for review and discussion by the community regarding the technological merits of the potential system outlined herein.
Goals
This document describes a few extension to MimbleWimble to allow multi-party UTXOs.
Related RFCs
Description
Multi Party UTXO
Normal MimbleWimble does not have the concept of a multisig UTXO. The UTXO is a commitment C(v,r) = r·G + v·H
with
the value blinded. However, the blinding factor r
can be composed of multiple blinding factors where r = r1 + r2 + ... + rn
, as Pedersen commitments are linear.
The output commitment can then be constructed as C(v,r) = r1·G + r2·G + ... + rn·G + v·H = (r1 + r2 + ... + rn)·G + v·H
.
This can be exploited for multiple users where each participant has their own ri
and keeps their private blinding factor
hidden and only provides their public blinding factor.
The base layer is oblivious as to how the commitment and related signature were constructed.
To open such commitments (in order to spend it) only the n-of-n blinding factor r
is required, and not the original
aggregated signature that was used to sign the transaction. The parties that wants to open the commitment needs to
collaborate to produce the n-of-n blinding factor r
.
RFC-8002/TransactionProtocol
Transaction Protocol
Maintainer(s): Cayle Sharrock
Licence
Copyright 2019 The Tari Development Community
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Language
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as shown here.
Disclaimer
This document and its content are intended for information purposes only and may be subject to change or update without notice.
This document may include preliminary concepts that may or may not be in the process of being developed by the Tari community. The release of this document is intended solely for review and discussion by the community of the technological merits of the potential system outlined herein.
Goals
This Request for Comment (RFC) describes the transaction protocol for peer-to-peer Tari payments using the Mimblewimble protocol. It also considers some attacks that may be launched against the protocol and offers some discussion around those attacks and potential alternatives to the protocol.
The goal is to describe a transaction protocol that:
- permits multiple recipients;
- preserves privacy regarding how many parties are involved in the transaction; and
- is secure against all reasonable attacks.
Related Requests for Comment
Description
The Tari base layer is built using Mimblewimble, which requires that all parties involved in a Tari transfer must interact to construct a valid Mimblewimble transaction.
A valid transaction involves:
- a set of one or more inputs being spent by the Sender;
- a set of zero or more outputs being sent to the Sender;
- a set of recipients, each of whom MUST construct exactly one output; and
- a set of partial Schnorr signatures which, when aggregated, validates the transaction construction and indicates every party's satisfaction with the terms.
The Issue with Multiple Recipients
Each party involved in a Tari transaction must produce a partial signature, signing the same challenge. This challenge is defined as
$$ e = H(\Sigma R_i \Vert \Sigma P_i \Vert m) $$
where \(R_i\) are public nonces generated by each party for this signature; \(P_i\) are the public Spending Keys; and m is the additional metadata for the transaction. \(\Sigma P_i\) is the value of the (pre-offset) excess that is stored in the transaction kernel.
Notice that every signing party needs to know the sum of all the nonces and public spending keys. This suggests that every party knows how many parties are involved in the transaction, which is not an ideal privacy scenario. It would be preferable if a secure scheme could be found where each recipient interacts only with the sender and does not need to calculate these sums themselves.
Unfortunately, as discussed below, it seems that using any known scheme, it's not possible to satisfy this privacy goal while achieving the desired security level.
The Issue with Multiple Senders
To increase privacy, the public excess values are offset by a constant random value. The choice of this value, as well as fee selection, can only be set once per transaction. The privilege of selecting these values is generally bestowed on the sender, since the sender pays the fee. Allowing multiple sending parties (or equivalently, allowing recipients to provide inputs) would require a negotiation round to set the fee and offset before the transaction could be constructed. This is a complication we don't want to deal with, and so all schemes presented here allow exactly one sender.
Two-party Transactions
Two-party transactions are fairly straightforward and are described in detail by Tari Labs University (TLU). (Refer to Mimblewimble Transaction.)
It is proposed that Tari implement this single-round two-party transaction scheme as a special case to support both online two-party transactions as well as "offline" transactions such as via email, text message and carrier pigeon.
Multiple-recipient Transaction Scheme
** Legend **
Symbol | Meaning |
---|---|
tx_id | Transaction identifier |
amt_i | Amount sent to i-th recipient |
Rs, Ri | Public nonce |
Xs, Pi | Public excess/key |
m | Message metadata |
C_i | Commitment |
RP_i | Range proof |
[..] | Vector of data |
Transaction ID
The scheme above makes use of a tx_id
field in every peer-to-peer message. Since all messages are stateless and
asynchronous, peers need some way of figuring out which message refers to which transaction. The transaction ID fulfils
this role.
The ID does not appear on the blockchain in any manner; is purely used to disambiguate Tari transaction messages and can be discarded after the transaction is broadcast to the network.
The tx_id
is unique for every receiver so that any observers of the communication will not be able to group receivers
together (however, the communication should be over secure channels in general).
The format of the transaction ID is a four-byte, little-endian integer (u64) and is calculated as
H(Rs||i)[0..4]
where i
is the i-th recipient in the transaction. The sender can use the tx_id
as a hash map key to identify and
differentiate recipients.
Replay Attacks
If any party can be convinced to sign a different message with the same nonce, its private keys will be lost. One way of achieving this would be if a virtual machine could be "snapshotted" or otherwise cloned at any point between sharing the public nonce and signing the message. Both copies of the victim's machine will now continue, unaware that there's a copy participating in a signature round. What then happens is:
$$ \begin{align} &\text{Clone A} & &\text{Clone B} \\ e_1 &= H(r_1 \Vert r_s \Vert \dots) & e_2 &= H(r_1 \Vert r_s^* \Vert \dots) \\ s_1 &= r_1 + e_1 \cdot k_1 & s_2 &= r_1 + e_2 \cdot k_1 \\ \end{align} $$
The attacker receives both signatures and trivially calculates the secret key:
$$ \begin{align} \Delta s &= s_1 - s_2 \\ &= k_1(e_1 - e_2) = k_1\Delta e \\ \Rightarrow k_1 &= \frac{\Delta s}{\Delta e} \end{align} $$
We've demonstrated this with the attacker changing their nonce, but literally any alteration to the challenge will provide a new challenge \(e_2\), enabling the attack.
What can we do about this? In fact, it's not possible to eliminate this attack at all! The reason sits with the proof that the Schnorr scheme works as a zero-knowledge protocol; the demonstration of this proof is precisely the attack we're trying to avoid [GOL19]. If we could eliminate this attack, we'd need to come up with a completely different way of proving the zero-knowledge property.
So we can't stop it, but we can make it as tricky as possible for the attacker to trick the receiver into replaying the signature. MuSig does this by requiring parties to share the hash of their nonces beforehand. At its extreme: in the two-party, single-round scheme, for example, the attacker would need to be able to control the victim's machine code execution (like running a debugger), at which point one might think the attacker could read the private key directly from memory anyway.
Rogue Key Attacks
Rogue Key attacks are another type of attack that can occur in multi-signature schemes.
In this case, the attacker has the freedom to choose a key or nonce after the victim has already disclosed theirs. This may allow the attacker to forge a valid signature on behalf of the victim. A recent paper, [DRI19], suggests that any Schnorr-based, two-round multi-signature scheme is vulnerable to a rogue key attack.
How this might apply in an insecure two-round Tari multi-signature scheme is as follows: A receiver sends their public nonce; output commitment and range proof; and public spending key to the sender, but then decides to cancel the transaction by refusing to provide a signature and sending an "Abort" message to the sender instead. The sender could, if they wanted, forge the 2-of-2 signature using this rogue key attack and broadcast the transaction anyway.
Note: This attack is not applicable in the one-round, two-party scheme, since the receiver returns their information in an all-or-nothing manner. However, the receiver could attempt to forge a signature, since they have the Sender's public nonce, but there's nothing they can really do with this signature; they certainly cannot broadcast a transaction with it because they don't have any of the transaction data at this stage.
We avoid rogue-key attacks in the Tari multi-recipient scheme by employing three rounds. In the first round, parties share a hash of their public nonces, which each party can later use to verify that no nonces were changed after the actual public nonces were shared.
RFC-8003/TariUseCases
Digital Asset Framework
Maintainer(s): CjS77
Licence
Copyright 2019 The Tari Development Community
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Language
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as shown here.
Disclaimer
This document and its content are intended for information purposes only and may be subject to change or update without notice.
This document may include preliminary concepts that may or may not be in the process of being developed by the Tari community. The release of this document is intended solely for review and discussion by the community of the technological merits of the potential system outlined herein.
Goals
There will be many types of digital assets that can be issued on Tari. The aim of this Request for Comment (RFC) is to help potential asset issuers identify use cases for Tari that lead to the design of new types of digital assets that may or may not exist in their ecosystems today.
Related Requests for Comment
Description
Tari digital assets may exist on the Tari Digital Assets Network (DAN), are perceived to have value and can be owned, with the associated digital data being classified as intangible, personal property.
Types
Digital assets may have the following high-level classification scheme:
- Symbolic
- Insignia
- Mascots
- Event-driven or historical, e.g. a rivalry or a highly temporal event
- Artefacts or objects
- Legendary
- Rare
- High demand
- Artistic
- Historical
- Utility
- Tickets
- In-game items
- Points
- Currency
- Full or fractional representation
- Bearer instruments/access token
- Chat stickers
- Persona
- Personality trait(s)
- Emotion(s)
- Statistics
- Superpower(s)
- Storyline(s)
- Relationship(s)
- Avatar
Behaviours
Digital asset tokens may influence the following behavioural types:
- Advance purchase
- Experience enhancement
- Sharing
- Loyalty
- Reward for performance
- Tipping
- Donating to a charity
- Collecting
- Trading
- Building/combining
Attributes
Digital assets have many different properties, which may be one or more of the following:
- Digital assets can be interactive:
- Easter eggs;
- media
- dynamic, e.g. imagine if assets were similar to sounds, visualizations or other kinds of "demos" or "gifs")
- Game mechanics;
- Evolutionary.
- Easter eggs;
- Digital assets can be combined to create super assets.
- Digital assets can be attribute(s) of another digital asset, e.g. wheels of a vehicle or a VIP ticket has two drink cards.
- Digital assets can have contingencies, e.g. ownership of a digital asset is contingent on ownership of a different digital asset. Using this digital asset is contingent on holding it for a particular duration, etc.
- Digital assets can have utility, e.g. be useful.
- Digital assets can be used across platforms, e.g. a digital asset for a game could be used as avatars in a social network.
- Digital assets can have history.
- Digital assets can have user-generated tags and/or metadata.
Interactions
Digital asset owners may have the following interactions with the DAN and/or other people:
- Digital asset owners can attest that they have ownership over their assets at time
t
. - Digital asset owners may attest ownership to an individual, to a group of friends or to the entire world.
Rules
Rules are the governance of how digital assets may be used or transferred, as defined by the asset issuer:
- Royalty fees. Digital asset issuers can set a royalty that charges a fee every time the digital asset is transferred between parties. The fee as defined by the issuer can be fixed or dynamic, or follow a complex formula, and value is granted to the issuer(s) and/or other entities.
- Contingency. Digital asset ownership/interaction may be contingent/dependent on another asset.
- Timing controls. Digital assets can only be transferred or used at particular times.
- Sharing. Digital assets can be shared with others or even co-owned.
- Privacy. Ownership of a digital asset can be changed from private to public.
- Upgradability/versioning. Digital assets can be upgraded and/or versioned.
- Redeemability. Digital assets can be used once or multiple times.
Examples
Some examples of how different types of digital assets with different attributes, rules and interactions may be manifested are provided here:
Crystal Skull of Akador
- Is rare, it is 1 of 5.
- Is legendary:
- https://indianajones.fandom.com/wiki/Crystal_Skull_of_Akator;
- press reports that this artefact could be worth $X.
- Drives collectability.
- Drives advance purchase, e.g. if you are one of the first 100,000 people to buy tickets to Indiana Jones World, you have a chance of winning this 1 of 5 artefact.
- Has superpowers and utility, e.g. if you have this item while visiting Indiana Jones World, you get to skip the line three times.
- Is a contingency for another asset, e.g. if you collect this item, two Sankara stones and the Cross of Coronado, you
can buy the ark of the covenant:
- Ark of the covenant is rare. It is 1 of 1.
- Ark of the covenant is legendary.
- Ark of the covenant gives you lifetime access to Indiana Jones World.
- Ark of the covenant has rules; 20% of the resale price goes to Indiana Jones World.
AB de Villiers' bat
- Is not rare, it is 1 of 100,000.
- Is legendary - https://www.youtube.com/watch?v=HK6B2da3DPA.
- Drives collectability, it is part of a series of bats from famous batsmen.
- Can be combined with other assets, e.g. be one of the first 10 people to combine six bats to turn this asset into a
One Day International (ODI) century bat:
- ODI century bats are rare, they are 1 of 10;
- ODI century bats are legendary.
- Has no superpowers.
- Has no utility.
- Has no rules.
OVO Owl x Supreme
- Is rare, it is 1 of 200.
- Is legendary:
- https://www.supremenewyork.com;
- https://us.octobersveryown.com.
- Has a game mechanic:
- Every time it’s transferred, it may become a golden ticket that grants you access to any Drake show.
- If it becomes a golden ticket and is transferred, it loses its golden ticket superpower.
- Has utility:
- Unlocks exclusive media content feat. Drake hosted by OVO SOUND.
- May become a golden ticket that grants you access to any Drake show.
- Has rules - every time its transferred Supreme and OVO SOUND receive 25% of the transaction value.
RFC-0385/StableCoin
Privacy-enabled Stablecoin contract design
Maintainer(s): Cayle Sharrock, Aaron Feickert
Licence
Copyright 2022 The Tari Development Community
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:RFC-0303_DanOverview.md
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Language
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as shown here.
Disclaimer
This document and its content are intended for information purposes only and may be subject to change or update without notice.
This document may include preliminary concepts that may or may not be in the process of being developed by the Tari community. The release of this document is intended solely for review and discussion by the community of the technological merits of the potential system outlined herein.
Goals
This Request for Comment (RFC) describes the a possible manifestation of a privacy-preserving stablecoin on the Tari Digital Assets Network (DAN).
Related Requests for Comment
Evaluation of existing stablecoins
The top two stablecoins by issuance, or "total value locked" (TVL) are Tether USD (USDT, under various contracts) and USD Coin (USDC). As of August 2023, these two coins accounted for 87% of the total stablecoin market.
Both coins are fully collateralised and the peg is maintained by the centralised issuer.
Although Tether is under scrutiny by authorities, both stablecoins have been in operation for several years. One might reasonably assume that the intersection of the feature set of the two coins' contracts represent a minimal set of requirements for legal operation.
What follows is a brief summary of the features of the two coins.
Tether USD (USDT)
The Tether USD ERC-20 contract is deployed at address
0xdac17f958d2ee523a2206206994597c13d831ec7
.
The contract code for this contract is presented in Appendix A. As of August
2023, USDT 39B was help in this contract.
The contract has the following key features:
Administration
The following monetary functions can only be called by the contract owner:
issue(amount)
- issues new tokens to the contract owner's account.redeem(amount)
- redeems tokens from the contract owner's account.setParams(...)
- Allows owner to set or change fees for transfers. Currently set to zero.
The owner has access to the following fraud/AML functions:
addBlackList(address)
- Adds an address to the blacklist. Blacklisted addresses are not allowed to send tokens (but they can receive them).removeBlackList(address)
- Removes an address from the blacklist.destroyBlackFunds(address)
- Destroys all tokens in the blacklisted address, reducing the total supply.
Finally, the owner has access to the following contract management functions:
deprecate(address)
- Deprecates the contract and supplies the upgraded contract address.pause
- pauses the entire contract, preventing any transfers.unpause
- unpauses the contract.transferOwnership(address)
- transfers ownership of the contract to a new address.
Account owners
The Tether contract records balances through a simple map of standard wallet addresses to amount. Any ethereum address is eligible to hold a USDT balance by virtue of the ERC-20 contract.
Account owners (ie the address matching the transaction sender
) have the following abilities:
transfer(to, amount)
- transfers tokens to another address. Fees get sent to the owner's account.transferFrom(from, to, value)
- allows a 3rd party to transfer tokens from thefrom
account to theto
account. The 3rd party must have been authorised by thefrom
account to do so usingapprove
and amount must be less than or equal toallowance(from, sender)
.approve(spender, amount)
- authorises a 3rd party to transfer tokens from the owner's account to another account. The 3rd party must calltransferFrom
to perform the transfer.
Public read-only functions
The following functions are available to the public:
totalSupply
- returns the total supply of minted tokens. The unit is in millionths of a USD.balanceOf(address)
- returns the balance of the given address.allowance(owner, spender)
- returns the amount of tokens that the spender is allowed to spend on behalf of the owner.getBlackListStatus(address)
- returns whether the given address is blacklisted.getOwner
- returns the owner of the contract.
Circle USD (USDC)
The primary Circle USD contract address is
0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48
.
This is a proxy contract that relays calls to a secondary contract. This is ostensibly done to allow transparent upgrades to the contract, but it does imply additional risk, since the contract code that actually runs and secures your funds is not actually immutable anymore.
The current proxied contract is presented in Appendix B. As of August 2023,
USDC 24B was held in this contract.
It is an ERC-20 contract like Tether, but adds the ability to carry out signature-based operations. Fees are not supported in this contract.
The contract has the following key features:
Administration
The owner has access to the following contract management functions:
updatePauser(address)
- gives thePause
role to a new address.updateBlacklister(address)
- gives theBlacklist
role to a new address.transferOwnership(address)
- transfers ownership of the contract to a new address.updateMasterMinter(address)
- gives theMasterMinter
role to a new address.updateRescuer(address)
- gives theRescuer
role to a new address.
The address with the Pauser
role has access to the following functions:
pause
- pauses the entire contract, preventing any transfers. Caller must have thePauser
role.unpause
- unpauses the contract. Caller must have thePauser
role.
The address with the Blacklist
role has access to the following functions:
blacklist(address)
- Adds an address to the blacklist. Blacklisted addresses are not allowed to send tokens (but they can receive them).unBlacklist(address)
- Removes an address from the blacklist. Caller must have theBlacklist
role.
The address with the MasterMinter
role has access to the following functions:
configureMinter(address, amount)
- Allows theMasterMinter
to add a new minter. The minter is allowed to mint up to the given amount of tokens. New minters have theMint
role.removeMinter(address)
- Removes a minter. The address will no longer be able to mint tokens.
Addresses with the Mint
role have access to the following functions:
mint(address, amount)
- Mints tokens to the given address. The amount must be less than or equal to the amount that the minter is allowed to mint. Unlike in Tether, USDC mints can be injected directly into arbitrary accounts.burn(amount)
- Burns tokens from the minter's address. Minter must not be blacklisted.
The address with the Rescuer
role has access to the following function:
rescueERC20(contract, address, amount)
- Unconditionally transfersamount
funds from the contract toaddress
. This is ostensibly a backdoor that allows the owner to recover funds in the event of a bug.
Account owners
The Tether contract records balances through a simple map of standard wallet addresses to amount. Any ethereum address is eligible to hold a USDT balance by virtue of the ERC-20 contract.
Account owners (ie the address matching the transaction sender
) have the following abilities:
transfer(to, amount)
- transfers tokens to another address. Fees get sent to the owner's account. Neither party may be blacklisted.transferFrom(from, to, value)
- allows a 3rd party to transfer tokens from thefrom
account to theto
account. The 3rd party must have been authorised by thefrom
account to do so usingapprove
and amount must be less than or equal toallowance(from, sender)
. Neitherfrom
,to
or thesender
may be blacklisted.approve(spender, amount)
- authorises a 3rd party to transfer tokens from the owner's account to another account. The 3rd party must calltransferFrom
to perform the transfer. Neither the 3rd party or the authorising account may be blacklisted.in(de)creaseAllowance(spender, amount)
- increases (decreases) the amount that the spender is allowed to spend on behalf of the owner. Neither the 3rd party or the authorising account may be blacklisted.transferWithAuthorization(to, value, authParams..)
- transfers tokens to another address based on the signature provided. This allows clients to batch transfers and save on gas, or services to pay gas on behalf of clients.cancelAuthorization(authParams..)
- cancels a pending transferWithAuthorization.permit(owner, spender, value, ...)
- Similar toapprove
but authorisation is provided by a bearer signature.
Public read-only functions
The following functions are available to the public:
totalSupply
- returns the total supply of minted tokens. The unit is in millionths of a USD.balanceOf(address)
- returns the balance of the given address.allowance(owner, spender)
- returns the amount of tokens that the spender is allowed to spend on behalf of the owner.isBlacklisted(address)
- returns whether the given address is blacklisted.getOwner
- returns the owner of the contract.minterAllowance(address)
- returns the amount of tokens that the given address is allowed to mint.isMinter(address)
- returns whether the given address is a minter.
Feature Comparison of Tether and Circle USD
Feature | Tether | Circle USD | Minimal requirements |
---|---|---|---|
Contract type | ERC-20 | ERC-20 | |
Minting | Yes (owner only) | Yes (multiple minters) | Yes |
Burning | Yes (owner only) | Yes (multiple minters) | Yes |
Minting to arbitrary account | No | Yes | No |
Blacklisting | Yes | Yes | Yes |
Blacklisted account can send | No | No | No |
Blacklisted account can receive | Yes | No | Yes |
Blacklisted account can be destroyed | Yes | No | No |
Take funds from arbitrary account | No | No | No |
Fees | Yes (currently zero) | No | No |
Contract upgrade | Yes (via linked list) | Yes (via proxy) | N/A |
Contract pause | Yes | Yes | N/A |
Description
Key assumptions and requirements
- The stablecoin is account-based.
- Issuance and redemptions of the stablecoin tokens are performed by a centralised entity, the
issuer
. The stability of the token and its peg are completely dependent on the issuer's ability to maintain the peg. The issuer is required to act responsibly and issue and redeem tokens in a timely manner in order to engender confidence in the coin and maintain the peg. - Aside from the administrator privileges conferred on the
issuer
by the stablecoin contract, the coin is operated in a decentralised manner, and transfers are facilitated by the Tari network, and are not processed by any centralised entity, including theissuer
. - The
issuer
has the following "administrator" powers:- Create and authorise new accounts.
- Issue new tokens. The new tokens are credited to the
issuer
's account. The transactions are in the clear so that anyone can verify the total circulating supply of the stablecoin. - Redeem (burn) existing tokens. The burnt tokens are debited from the
issuer
's account. These transactions are in the clear. - Have access to the full list of account ids.
- Blacklist an account. Blacklisted accounts are not allowed to send, or receive, tokens.
- Remove an account from the blacklist.
- Account-holders have the following abilities:
- View their own balance.
- If their account is not blacklisted, transfer funds to any other (non-blacklisted) account.
- General users:
- Cannot see the balance of any account (other than their own).
- Can see the total supply of tokens in circulation.
- Can apply for a new account by interacting with the
issuer
.
- The possibility of the issuer charging a fee for transfers is not considered in this design.
- The issuer can view the balance of account holders.
- The issuer cannot unilaterally spend or seize funds from any account holder.
Validator nodes validate all stablecoin transactions. In particular:
- Validator nodes cannot determine the value of any transaction.
- However, they are able to, and MUST verify that
- no coins are created or destroyed in the transaction, i.e. the sum of the parties' balances before and after the transfer are equal,
- the transfer value is positive,
- the sender has a positive balance after the transaction,
- the sender has authorised the transaction,
- the sending party holds a valid account,
- the sending party is not on the blacklist,
- the receiving party holds a valid account,
- the receiving party is not on the blacklist.
- If any of the conditions in 2 are not met, the transaction is invalid and the validator node MUST reject the transaction.
Implementation
The broad strategy is as follows:
- The issuer mints stablecoin tokens in equal quantity to the amount of fiat currency deposited with the issuer. These mint transactions are in the clear and anyone can ascertain the total circulating supply of the stablecoin.
- Issuers can transfer tokens to any account. These transfers are also in the clear.
- Transfers between account holders are confidential.
- The issuer can carry out confidential transfers by first transferring tokens from the cleartext issuer account to another, standard account controlled by the issuer, and then performing a confidential transfer from there to the final destination account.
- Balances are stored in Pedersen commitments, the masks of which are known only to account holders.
- Balances are also verifiably encrypted to the issuer.
- Users create a new account by interacting with the issuer. The issuer provides an account id that can be compared against a blacklist for the purposes of determining whether the account is valid or not. The issuer may choose to conduct a KYC procedure out-of-band as part of the account creation process.
- The full list of accounts form part of a whitelist. Only the issuer can modify the whitelist.
- A blacklist, which only the issuer can modify, comprises a list of accounts that may no longer participate in stablecoin transactions.
- Spending authority rests solely with the account owner and required knowledge of the account private key.
- Transfers are done in two-steps, via the issuance of an e-cheque by the sender, followed by the claiming of the e-cheque by the recipient.
The remainder of this section describes the implementation in more detail.
Mathematical furniture
As a point of notation, we sometimes use superscripts in mathematical notation in this note; unless otherwise indicated, this is symbolic and not to be interpreted as indicating exponentiation.
Hash functions and ciphers
We require the use of multiple cryptographic hash functions, which must be sampled independently. It is possible to do this by carefully applying domain separation to a single cryptographic hash function. We further require that when data is provided as input to such a hash function, it is done safely in a manner that is canonical and cannot induce collisions. We use a comma notation to indicate multiple input values, as in $H(a, b, c, \ldots)$.
We also require the use of a key-committing AEAD (authenticated encryption with additional data) construction. It is possible to extend an arbitrary AEAD design in this manner by including a safe cryptographic hash of a derived key.
Let $H_{\text{AEAD}}$ be a cryptographic hash function whose output is the AEAD key space, suitable for $H_ {\text{AEAD}}$ to operate as a key derivation function for the AEAD.
Proving systems
The design will require several zero-knowledge proving systems that allow validators to assert correctness of operations without revealing protected data.
Verifiable encryption
We require the use of a verifiable ElGamal encryption proving system that asserts the value bound to a Pedersen commitment matches the value encrypted to a given public key. This will be used to assert that the issuer can decrypt account balances without knowing the opening to the account's balance commitment.
The proving relation is
$\{ (C, E, R, P); (v, m, r) | C = vG + mH, E = vG + rP, R = rG \}$.
- The prover samples $x_v, x_m, x_r$ uniformly at random.
- It computes $C' = x_v G + x_m H$, $E' = x_v G + x_r P$, and $R' = x_r G$ and sends them to the verifier.
- The verifier samples nonzero $e$ uniformly at random and sends it to the prover.
- The prover computes $s_v = ev + x_v$, $s_m = em + x_m$, and $s_r = er + x_r$ and sends them to the verifier.
- The verifier accepts the proof if and only if $eC + C' = s_v G + s_m H$, $eE + E' = s_v G + s_r P$, and $eR + R' = s_r G$.
The proof can be made non-interactive using the Fiat-Shamir technique. It is a sigma protocol for the relation that is complete, 2-special sound, and special honest-verifier zero knowledge.
Value equality
We require the use of a value equality proving system that asserts two Pedersen commitments bind to the same value. This will be used to assert that commitments used in transfers retain balance.
The proving relation is
$\{ (C_1, C_2); (v, m_1, m_2) | C_1 = vG + m_1 H, C_2 = vG + m_2 H \}$.
- The prover samples $x_v, x_{m_1}, x_{m_2}$ uniformly at random.
- It computes $C_1' = x_v G + x_{m_1} H$ and $C_2' = x_v G + x_{m_2} H$ and sends them to the verifier.
- The verifier samples nonzero $e$ uniformly at random and sends it to the prover.
- The prover computes $s_v = ev + x_v$, $s_{m_1} = em_1 + x_{m_1}$, and $s_{m_2} = em_2 + x_{m_2}$ and sends them to the verifier.
- The verifier accepts the proof if and only if $eC_1 + C_1' = s_v G + s_{m_1} H$ and $eC_2 + C_2' = s_v G + s_{m_2} H$.
The proof can be made non-interactive using the Fiat-Shamir technique. It is a sigma protocol for the relation that is complete, 2-special sound, and special honest-verifier zero knowledge.
Schnorr representation
We require the use of a Schnorr representation proving system that asserts knowledge of a discrete logarithm. This will be used to sign messages, as well as to assert that a Pedersen commitment binds to a given value.
The proving relation is
$\{ P; p | P = pG \}$.
- The prover samples $x_p$ uniformly at random.
- It computes $P' = x_p G$ and sends it to the verifier.
- The verifier samples nonzero $e$ uniformly at random and sends it to the prover.
- The prover computes $s_p = ep + x_p$ and sends it to the verifier.
- The verifier accepts the proof if and only if $eP + P' = s_p G$.
The proof can be made non-interactive using the Fiat-Shamir technique. It is a sigma protocol for the relation that is complete, 2-special sound, and special honest-verifier zero knowledge.
To use this proving system to assert that a commitment $C$ binds to a given value $v$, we set $P = C - vG$ and use the Schnorr representation proving system on this statement using the generator $H$ instead of $G$, and being careful to bind $v$ into the Fiat-Shamir transcript.
Commitment range
We require the use of a commitment range proving system that asserts that all Pedersen commitments in a set bind to values in a specified range. This will be used to prevent balance underflow and overflow that would inflate supply. We assume the intended range is $[0, 2^n)$ for some globally-fixed $n$; in practice, $n = 64$ is typically used (since it is often the case that $n$ must itself be a power of two).
The proving relation is
$\{ (C_j)_{j=0}^{m-1}; (v_j, m_j)_{j=0}^{m-1} : C_j = v_j G + m_j H, v_j \in [0, 2^n) \forall j \}$.
The popular and efficient Bulletproofs and Bulletproofs+ range proving systems may be used for this purpose.
Design
We now describe the stablecoin design.
Issuer
The stablecoin is instantiated by defining the issuer.
The issuer samples its secret key $p$ uniformly at random, and computes the corresponding public key $P = pG$. It produces a Schnorr representation proof $\Pi_P$ using statement $P$ and witness $p$. It sets up the stablecoin as follows:
- Public key: $P$
- Public key proof: $\Pi_P$
Validators check this operation by verifying $\Pi_P$.
The issuer also sets up an account for itself using the structure described below for users.
Users
A user who wishes to open an account interacts with the issuer via a side channel. The user samples its secret key $k$ uniformly at random, and computes the corresponding public key $K = kG$. It produces a Schnorr representation proof $\Pi_K$ using statement $K$ and witness $k$. The issuer checks that $K$ has not been used in any other approved account, and verifies $\Pi_K$.
If the issuer approves the account, it signs $K$ by generating a Schnorr representation proof $\Pi_{P, K}$ using statement $P$ and witness $p$, binding $K$ into the Fiat-Shamir transcript.
The issuer sets up the account structure as follows:
- Public key: $K$
- Public key proof: $\Pi_K$
- Issuer proof: $\Pi_{P, K}$
- State nonce: 0
- Balance commitment: 0 (identity group element)
- Issuer-encrypted balance: None
- User-encrypted balance: None
- Pending e-cheques: None
- The state nonce is a value used to track changes to the account and avoid replaying messages.
- The issuer-encrypted balance field will be populated with a verifiable encryption of the balance that can be decrypted by the issuer.
- The user-encrypted balance field will be populated with an authenticated encryption of the value and mask corresponding to the balance commitment that can be decrypted by the user for account recovery purposes.
- The pending e-cheques field will be populated with e-cheques representing transfers that are destined for the account, but that the user has neither approved nor rejected.
Validators check this operation by verifying $\Pi_K$ and $\Pi_{P, K}$, asserting that $K$ does not appear in any other account, and asserting the other constant values are as expected.
Cheques
When a user wishes to transfer funds to another user, it does so by generating an e-cheque. Once validators check the e-cheque, it is added to the recipient's pending e-cheques list and the sender's account is updated.
The e-cheque remains until the recipient approves or rejects it, or until the e-cheque becomes abandoned, as described later.
Suppose the sender wishes to transfer value $v^\Delta$ to a recipient.
- Let $K_s = k_s G$ and $K_r$ be the sender and recipient keys, respectively.
- Let $C = vG + mH$ be the sender balance commitment.
- Let $i$ be the sender state nonce.
The sender does the following:
- Samples a scalar $m_s^\Delta$ uniformly at random and uses it to generate a commitment $C_s^\Delta = v^\Delta G + m_s^\Delta H$ to the transfer value.
- Samples a scalar $m^\Delta$ uniformly at random and uses it to generate a commitment $C^\Delta = v^\Delta G + m^\Delta H$ to the transfer value.
- Generates a proof of value equality $\Pi_\Delta$ on the statement $(C_s^\Delta, C^\Delta)$ and witness $(v^\Delta, m_s^\Delta, m^\Delta)$.
- Samples a scalar $r$ uniformly at random, and computes $R = rG$.
- Generates an AEAD key $H_{\text{AEAD}}(r K_r)$ and uses it to encrypt the tuple $(v^\Delta, m^\Delta)$, producing authenticated ciphertext $c$.
- Sets $E = (v - v^\Delta) G + rP$ as an ElGamal encryption of its new balance, and generates a verifiable encryption proof $\Pi_{\text{enc}}$ on the statement $(C - C_s^\Delta, E, R, P)$ and witness $(v - v^\Delta, m - m_s^\Delta, r)$.
- Generates a range proof $\Pi_{\text{range}}$ on the statement $\{ C_s^\Delta, C - C_s^\Delta \}$ and witness $\{ ( v^\Delta, m_s^\Delta), (v - v^\Delta, m - m_s^\Delta) \}$.
- Generates an AEAD key $H_{\text{AEAD}}(k_s)$ and uses it to encrypt the tuple $(v - v^\Delta, m - m_s^\Delta)$, producing authenticated ciphertext $c_s$.
- Sets the e-cheque to be the tuple $t = (K_s, K_r, C, i, C_s^\Delta, C^\Delta, E, R, \Pi_\Delta, \Pi_{\text{enc}}, \Pi_ {\text{range}}, c_s, c_{sr})$.
- Signs the e-cheque by generating a Schnorr representation proof $\Pi_t$ using statement $K_s$ and witness $k_s$, binding $t$ into the Fiat-Shamir transcript.
Prior to accepting the e-cheque as valid, validators perform the following checks:
- Assert that neither $K_s$ nor $K_r$ appear on the blacklist.
- Verify the proof $\Pi_t$.
- Look up the sender's account using $K_s$ and assert that $C$ and $i$ match the corresponding values in the account.
- Verify the proofs $\Pi_\Delta$, $\Pi_{\text{enc}}$, and $\Pi_{\text{range}}$.
If these checks pass, validators add the e-cheque to the recipient's pending e-cheques list, and update the sender's account as follows:
- Increment the state nonce.
- Set the balance commitment to $C - C_s^\Delta$.
- Set the issuer-encrypted balance to the tuple $(E, R)$.
- Set the user-encrypted balance to $c_s$.
When the recipient sees the e-cheque, it can either endorse or void it.
Endorsement means the recipient intends to accept the funds and wishes to have its account updated accordingly. Voiding means the recipient does not intend to accept the funds and wishes for the sender to be able to claim them back. Prior to making this determination, the recipient does the following:
- Generates an AEAD key $H_{\text{AEAD}}(k_r R)$ and uses it to authenticate and decrypt $c$, producing the tuple $( v^\Delta, m^\Delta)$.
- Checks that $C^\Delta = v^\Delta G + m^\Delta H$.
If these checks pass, it may choose to endorse or void the e-cheque. If they fail, it must void the e-cheque.
Voiding an e-cheque
Suppose the recipient wishes to void an e-cheque $t$.
It does the following:
- Sets the voiding to be the tuple $t_{\text{void}} = (t)$.
- Signs the voiding by generating a Schnorr representation proof $\Pi_t$ using statement $K_r$ and witness $k_r$, binding $t_{\text{void}}$ into the Fiat-Shamir transcript.
Prior to accepting the voiding as valid, validators perform the following checks:
- Assert that neither $K_s$ nor $K_r$ appear on the blacklist.
- Verify the proof $\Pi_t$.
If these checks pass, validators annotate $t$ in the recipient's pending e-cheques list to indicate the voiding.
Endorsing an e-cheque
Suppose the recipient wishes to endorse an e-cheque $t$ with $C^\Delta = v^\Delta G + m^\Delta H$ from its pending e-cheques list.
Now let $C = vG + mH$ be the recipient balance commitment.
Let $i$ be the recipient state nonce.
The recipient does the following:
- Samples a scalar $m_r^\Delta$ uniformly at random, and uses it to generate a commitment $C_r^\Delta = v^\Delta G + m_r^\Delta H$ to the transfer value.
- Generates a proof of value equality $\Pi_\Delta$ on the statement $(C_r^\Delta, C^\Delta)$ and witness $(v^\Delta, m_r^\Delta, m^\Delta)$.
- Samples a scalar $r$ uniformly at random, and computes $R = rG$.
- Sets $E = (v + v^\Delta) G + rP$ as an ElGamal encryption of its new balance, and generates a verifiable encryption proof $\Pi_{\text{enc}}$ on the statement $(C + C_r^\Delta, E, R, P)$ and witness $(v + v^\Delta, m + m_r^\Delta, r)$.
- Generates an AEAD key $H_{\text{AEAD}}(k_r)$ and uses it to encrypt the tuple $(v + v^\Delta, m + m_r^\Delta)$, producing authenticated ciphertext $c_r$.
- Sets the endorsement to be the tuple $t_{\text{end}} = (t, C, i, C_r^\Delta, E, R, \Pi_\Delta, \Pi_{\text{enc}}, c_r)$.
- Signs the endorsement by generating a Schnorr representation proof $\Pi_t$ using statement $K_r$ and witness $k_r$, binding $t_{\text{end}}$ into the Fiat-Shamir transcript.
It is also possible for the original sender of an e-cheque to endorse it as well. This can arise in two cases:
- The recipient of the e-cheque has voided it.
- The recipient of the e-cheque has neither accepted nor voided it, and a protocol-specified period of time has passed.
The process is the same as above, with the sender now playing the role of the recipient.
Prior to accepting the endorsement as valid, validators perform the following checks:
- Assert that neither $K_s$ nor $K_r$ appear on the blacklist.
- Verify the proof $\Pi_t$.
- Look up the recipient's account using $K_r$ and assert that $C$ and $i$ match the corresponding values in the account, and that $t$ appears in the pending e-cheques list.
- Verify the proofs $\Pi_\Delta$ and $\Pi_{\text{enc}}$.
If these checks pass, validators remove the e-cheque $t$ from the recipient's pending e-cheques list, and update the recipient's account as follows:
- Increment the state nonce.
- Set the balance commitment to $C + C_r^\Delta$.
- Set the issuer-encrypted balance to the tuple $(E, R)$.
- Set the user-encrypted balance to $c_r$.
Issuer balance visibility
The issuer can privately view any user's balance at any time using ElGamal decryption. Suppose it wishes to view the balance of a user whose issuer-encrypted balance is $(E, R)$.
It does the following:
- Sets $V = E - pR$.
- Finds $v$ such that $V = vG$; this is the user's balance.
Because the search space for balances is limited, the issuer can optimize this process. For example, it could produce a lookup table mapping $vG \mapsto v$ for reasonable values $v$, or simply use brute force on the search space.
User account recovery
If the user loses access to their account balance, they can recover the opening to their balance commitment to regain access, provided they still hold the private key $k$.
Suppose such a user with key $k$ queries validators for its account's user-encrypted balance.
It does the following:
- Generates an AEAD key $H_{\text{AEAD}}(k)$ and uses it to authenticate and decrypt the user-encrypted balance, producing the tuple $(v, m)$; this is the opening to their balance commitment.
Issuer transfers
When transferring funds from the issuer to a user, or from a user to the issuer, it is required that the value be publicly visible for transparency purposes. However, we wish to reuse as much of the existing design as possible, in order to simplify the design and reduce engineering risk.
If the issuer wishes to transfer funds to a user, it produces an e-cheque with the following modifications:
- It sets $r = 0$.
- It uses a zero key to produce $c_s$.
When validating such an e-cheque, validators additionally do the following:
- Assert that $R = 0$.
- Assert that $c_s$ decrypts using a zero key, and that the resulting opening is valid.
- Decrypt $c_s$ and assert the resulting opening is valid.
- Decrypt $c$ using a zero key and assert the resulting opening is valid.
If a user wishes to transfer funds to the issuer, it produces an e-cheque with the following modifications:
- It sets $r = 0$.
When validating such an e-cheque, validators additionally do the following:
- Assert that $R = 0$.
- Decrypt $c$ using a zero key and asserting the resulting opening is valid.
If the issuer wishes to accept transfer funds from a user, it produces an endorsement with the following modifications:
- It uses a zero key to produce $c_r$.
When validating such an endorsement, validators additionally do the following:
- Assert that $c_r$ decrypts using a zero key, and that the resulting opening is valid.
This design allows for transparent analysis of the issuer's balance and e-cheques.
Final notes
There are several variations of this contract that could be implemented.
For example, removing the encrypted balance fields would make user balances opaque to the issuer as well, while also simplifying the design considerably.
The transfer process can be augmented to include dummy inputs and outputs, using a strategy similar to that used in Lelantus Spark. This would obfuscate the parties in a transfer, dramatically improving privacy.
The use of e-cheques as the primary value transfer vehicle opens up many possibilities for adding additional margin of error to on-chain financial transactions:
- For example, e-cheques could have a holding time associated with them, to allow parties to validate payments out-of-band.
- They could have additional claim constraints, which would simplify escrow contracts and swap contracts while improving security.
Appendix A - Tether USD contract
/**
*Submitted for verification at Etherscan.io on 2017-11-28
*/
pragma solidity ^0.4.17;
/**
* @title SafeMath
* @dev Math operations with safety checks that throw on error
*/
library SafeMath {
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
uint256 c = a * b;
assert(c / a == b);
return c;
}
function div(uint256 a, uint256 b) internal pure returns (uint256) {
// assert(b > 0); // Solidity automatically throws when dividing by 0
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
assert(b <= a);
return a - b;
}
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
assert(c >= a);
return c;
}
}
/**
* @title Ownable
* @dev The Ownable contract has an owner address, and provides basic authorization control
* functions, this simplifies the implementation of "user permissions".
*/
contract Ownable {
address public owner;
/**
* @dev The Ownable constructor sets the original `owner` of the contract to the sender
* account.
*/
function Ownable() public {
owner = msg.sender;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
/**
* @dev Allows the current owner to transfer control of the contract to a newOwner.
* @param newOwner The address to transfer ownership to.
*/
function transferOwnership(address newOwner) public onlyOwner {
if (newOwner != address(0)) {
owner = newOwner;
}
}
}
/**
* @title ERC20Basic
* @dev Simpler version of ERC20 interface
* @dev see https://github.com/ethereum/EIPs/issues/20
*/
contract ERC20Basic {
uint public _totalSupply;
function totalSupply() public constant returns (uint);
function balanceOf(address who) public constant returns (uint);
function transfer(address to, uint value) public;
event Transfer(address indexed from, address indexed to, uint value);
}
/**
* @title ERC20 interface
* @dev see https://github.com/ethereum/EIPs/issues/20
*/
contract ERC20 is ERC20Basic {
function allowance(address owner, address spender) public constant returns (uint);
function transferFrom(address from, address to, uint value) public;
function approve(address spender, uint value) public;
event Approval(address indexed owner, address indexed spender, uint value);
}
/**
* @title Basic token
* @dev Basic version of StandardToken, with no allowances.
*/
contract BasicToken is Ownable, ERC20Basic {
using SafeMath for uint;
mapping(address => uint) public balances;
// additional variables for use if transaction fees ever became necessary
uint public basisPointsRate = 0;
uint public maximumFee = 0;
/**
* @dev Fix for the ERC20 short address attack.
*/
modifier onlyPayloadSize(uint size) {
require(!(msg.data.length < size + 4));
_;
}
/**
* @dev transfer token for a specified address
* @param _to The address to transfer to.
* @param _value The amount to be transferred.
*/
function transfer(address _to, uint _value) public onlyPayloadSize(2 * 32) {
uint fee = (_value.mul(basisPointsRate)).div(10000);
if (fee > maximumFee) {
fee = maximumFee;
}
uint sendAmount = _value.sub(fee);
balances[msg.sender] = balances[msg.sender].sub(_value);
balances[_to] = balances[_to].add(sendAmount);
if (fee > 0) {
balances[owner] = balances[owner].add(fee);
Transfer(msg.sender, owner, fee);
}
Transfer(msg.sender, _to, sendAmount);
}
/**
* @dev Gets the balance of the specified address.
* @param _owner The address to query the the balance of.
* @return An uint representing the amount owned by the passed address.
*/
function balanceOf(address _owner) public constant returns (uint balance) {
return balances[_owner];
}
}
/**
* @title Standard ERC20 token
*
* @dev Implementation of the basic standard token.
* @dev https://github.com/ethereum/EIPs/issues/20
* @dev Based oncode by FirstBlood: https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol
*/
contract StandardToken is BasicToken, ERC20 {
mapping (address => mapping (address => uint)) public allowed;
uint public constant MAX_UINT = 2**256 - 1;
/**
* @dev Transfer tokens from one address to another
* @param _from address The address which you want to send tokens from
* @param _to address The address which you want to transfer to
* @param _value uint the amount of tokens to be transferred
*/
function transferFrom(address _from, address _to, uint _value) public onlyPayloadSize(3 * 32) {
var _allowance = allowed[_from][msg.sender];
// Check is not needed because sub(_allowance, _value) will already throw if this condition is not met
// if (_value > _allowance) throw;
uint fee = (_value.mul(basisPointsRate)).div(10000);
if (fee > maximumFee) {
fee = maximumFee;
}
if (_allowance < MAX_UINT) {
allowed[_from][msg.sender] = _allowance.sub(_value);
}
uint sendAmount = _value.sub(fee);
balances[_from] = balances[_from].sub(_value);
balances[_to] = balances[_to].add(sendAmount);
if (fee > 0) {
balances[owner] = balances[owner].add(fee);
Transfer(_from, owner, fee);
}
Transfer(_from, _to, sendAmount);
}
/**
* @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender.
* @param _spender The address which will spend the funds.
* @param _value The amount of tokens to be spent.
*/
function approve(address _spender, uint _value) public onlyPayloadSize(2 * 32) {
// To change the approve amount you first have to reduce the addresses`
// allowance to zero by calling `approve(_spender, 0)` if it is not
// already 0 to mitigate the race condition described here:
// https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
require(!((_value != 0) && (allowed[msg.sender][_spender] != 0)));
allowed[msg.sender][_spender] = _value;
Approval(msg.sender, _spender, _value);
}
/**
* @dev Function to check the amount of tokens than an owner allowed to a spender.
* @param _owner address The address which owns the funds.
* @param _spender address The address which will spend the funds.
* @return A uint specifying the amount of tokens still available for the spender.
*/
function allowance(address _owner, address _spender) public constant returns (uint remaining) {
return allowed[_owner][_spender];
}
}
/**
* @title Pausable
* @dev Base contract which allows children to implement an emergency stop mechanism.
*/
contract Pausable is Ownable {
event Pause();
event Unpause();
bool public paused = false;
/**
* @dev Modifier to make a function callable only when the contract is not paused.
*/
modifier whenNotPaused() {
require(!paused);
_;
}
/**
* @dev Modifier to make a function callable only when the contract is paused.
*/
modifier whenPaused() {
require(paused);
_;
}
/**
* @dev called by the owner to pause, triggers stopped state
*/
function pause() onlyOwner whenNotPaused public {
paused = true;
Pause();
}
/**
* @dev called by the owner to unpause, returns to normal state
*/
function unpause() onlyOwner whenPaused public {
paused = false;
Unpause();
}
}
contract BlackList is Ownable, BasicToken {
/////// Getters to allow the same blacklist to be used also by other contracts (including upgraded Tether) ///////
function getBlackListStatus(address _maker) external constant returns (bool) {
return isBlackListed[_maker];
}
function getOwner() external constant returns (address) {
return owner;
}
mapping (address => bool) public isBlackListed;
function addBlackList (address _evilUser) public onlyOwner {
isBlackListed[_evilUser] = true;
AddedBlackList(_evilUser);
}
function removeBlackList (address _clearedUser) public onlyOwner {
isBlackListed[_clearedUser] = false;
RemovedBlackList(_clearedUser);
}
function destroyBlackFunds (address _blackListedUser) public onlyOwner {
require(isBlackListed[_blackListedUser]);
uint dirtyFunds = balanceOf(_blackListedUser);
balances[_blackListedUser] = 0;
_totalSupply -= dirtyFunds;
DestroyedBlackFunds(_blackListedUser, dirtyFunds);
}
event DestroyedBlackFunds(address _blackListedUser, uint _balance);
event AddedBlackList(address _user);
event RemovedBlackList(address _user);
}
contract UpgradedStandardToken is StandardToken{
// those methods are called by the legacy contract
// and they must ensure msg.sender to be the contract address
function transferByLegacy(address from, address to, uint value) public;
function transferFromByLegacy(address sender, address from, address spender, uint value) public;
function approveByLegacy(address from, address spender, uint value) public;
}
contract TetherToken is Pausable, StandardToken, BlackList {
string public name;
string public symbol;
uint public decimals;
address public upgradedAddress;
bool public deprecated;
// The contract can be initialized with a number of tokens
// All the tokens are deposited to the owner address
//
// @param _balance Initial supply of the contract
// @param _name Token Name
// @param _symbol Token symbol
// @param _decimals Token decimals
function TetherToken(uint _initialSupply, string _name, string _symbol, uint _decimals) public {
_totalSupply = _initialSupply;
name = _name;
symbol = _symbol;
decimals = _decimals;
balances[owner] = _initialSupply;
deprecated = false;
}
// Forward ERC20 methods to upgraded contract if this one is deprecated
function transfer(address _to, uint _value) public whenNotPaused {
require(!isBlackListed[msg.sender]);
if (deprecated) {
return UpgradedStandardToken(upgradedAddress).transferByLegacy(msg.sender, _to, _value);
} else {
return super.transfer(_to, _value);
}
}
// Forward ERC20 methods to upgraded contract if this one is deprecated
function transferFrom(address _from, address _to, uint _value) public whenNotPaused {
require(!isBlackListed[_from]);
if (deprecated) {
return UpgradedStandardToken(upgradedAddress).transferFromByLegacy(msg.sender, _from, _to, _value);
} else {
return super.transferFrom(_from, _to, _value);
}
}
// Forward ERC20 methods to upgraded contract if this one is deprecated
function balanceOf(address who) public constant returns (uint) {
if (deprecated) {
return UpgradedStandardToken(upgradedAddress).balanceOf(who);
} else {
return super.balanceOf(who);
}
}
// Forward ERC20 methods to upgraded contract if this one is deprecated
function approve(address _spender, uint _value) public onlyPayloadSize(2 * 32) {
if (deprecated) {
return UpgradedStandardToken(upgradedAddress).approveByLegacy(msg.sender, _spender, _value);
} else {
return super.approve(_spender, _value);
}
}
// Forward ERC20 methods to upgraded contract if this one is deprecated
function allowance(address _owner, address _spender) public constant returns (uint remaining) {
if (deprecated) {
return StandardToken(upgradedAddress).allowance(_owner, _spender);
} else {
return super.allowance(_owner, _spender);
}
}
// deprecate current contract in favour of a new one
function deprecate(address _upgradedAddress) public onlyOwner {
deprecated = true;
upgradedAddress = _upgradedAddress;
Deprecate(_upgradedAddress);
}
// deprecate current contract if favour of a new one
function totalSupply() public constant returns (uint) {
if (deprecated) {
return StandardToken(upgradedAddress).totalSupply();
} else {
return _totalSupply;
}
}
// Issue a new amount of tokens
// these tokens are deposited into the owner address
//
// @param _amount Number of tokens to be issued
function issue(uint amount) public onlyOwner {
require(_totalSupply + amount > _totalSupply);
require(balances[owner] + amount > balances[owner]);
balances[owner] += amount;
_totalSupply += amount;
Issue(amount);
}
// Redeem tokens.
// These tokens are withdrawn from the owner address
// if the balance must be enough to cover the redeem
// or the call will fail.
// @param _amount Number of tokens to be issued
function redeem(uint amount) public onlyOwner {
require(_totalSupply >= amount);
require(balances[owner] >= amount);
_totalSupply -= amount;
balances[owner] -= amount;
Redeem(amount);
}
function setParams(uint newBasisPoints, uint newMaxFee) public onlyOwner {
// Ensure transparency by hardcoding limit beyond which fees can never be added
require(newBasisPoints < 20);
require(newMaxFee < 50);
basisPointsRate = newBasisPoints;
maximumFee = newMaxFee.mul(10**decimals);
Params(basisPointsRate, maximumFee);
}
// Called when new token are issued
event Issue(uint amount);
// Called when tokens are redeemed
event Redeem(uint amount);
// Called when contract is deprecated
event Deprecate(address newAddress);
// Called if contract ever adds fees
event Params(uint feeBasisPoints, uint maxFee);
}
Appendix B - Circle USD contract
The Circle USD contract runs as a proxy contract. Therefore the code that is actually active can be changed at any
time. The following is the contract code that was active as of 25 August 2023, deployed to address
0xa2327a938febf5fec13bacfb16ae10ecbc4cbdcf
.
/**
*Submitted for verification at Etherscan.io on 2021-04-17
*/
// File: @openzeppelin/contracts/math/SafeMath.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
/**
* @dev Wrappers over Solidity's arithmetic operations with added overflow
* checks.
*
* Arithmetic operations in Solidity wrap on overflow. This can easily result
* in bugs, because programmers usually assume that an overflow raises an
* error, which is the standard behavior in high level programming languages.
* `SafeMath` restores this intuition by reverting the transaction when an
* operation overflows.
*
* Using this library instead of the unchecked operations eliminates an entire
* class of bugs, so it's recommended to use it always.
*/
library SafeMath {
/**
* @dev Returns the addition of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `+` operator.
*
* Requirements:
*
* - Addition cannot overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a, "SafeMath: addition overflow");
return c;
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting on
* overflow (when the result is negative).
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
*
* - Subtraction cannot overflow.
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
return sub(a, b, "SafeMath: subtraction overflow");
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting with custom message on
* overflow (when the result is negative).
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
*
* - Subtraction cannot overflow.
*/
function sub(
uint256 a,
uint256 b,
string memory errorMessage
) internal pure returns (uint256) {
require(b <= a, errorMessage);
uint256 c = a - b;
return c;
}
/**
* @dev Returns the multiplication of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `*` operator.
*
* Requirements:
*
* - Multiplication cannot overflow.
*/
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
if (a == 0) {
return 0;
}
uint256 c = a * b;
require(c / a == b, "SafeMath: multiplication overflow");
return c;
}
/**
* @dev Returns the integer division of two unsigned integers. Reverts on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
return div(a, b, "SafeMath: division by zero");
}
/**
* @dev Returns the integer division of two unsigned integers. Reverts with custom message on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function div(
uint256 a,
uint256 b,
string memory errorMessage
) internal pure returns (uint256) {
require(b > 0, errorMessage);
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* Reverts when dividing by zero.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
return mod(a, b, "SafeMath: modulo by zero");
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* Reverts with custom message when dividing by zero.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function mod(
uint256 a,
uint256 b,
string memory errorMessage
) internal pure returns (uint256) {
require(b != 0, errorMessage);
return a % b;
}
}
// File: @openzeppelin/contracts/token/ERC20/IERC20.sol
pragma solidity ^0.6.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `recipient`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address recipient, uint256 amount)
external
returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender)
external
view
returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `sender` to `recipient` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(
address sender,
address recipient,
uint256 amount
) external returns (bool);
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(
address indexed owner,
address indexed spender,
uint256 value
);
}
// File: contracts/v1/AbstractFiatTokenV1.sol
/**
* Copyright (c) 2018-2020 CENTRE SECZ
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
pragma solidity 0.6.12;
abstract contract AbstractFiatTokenV1 is IERC20 {
function _approve(
address owner,
address spender,
uint256 value
) internal virtual;
function _transfer(
address from,
address to,
uint256 value
) internal virtual;
}
// File: contracts/v1/Ownable.sol
/**
* Copyright (c) 2018 zOS Global Limited.
* Copyright (c) 2018-2020 CENTRE SECZ
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
pragma solidity 0.6.12;
/**
* @notice The Ownable contract has an owner address, and provides basic
* authorization control functions
* @dev Forked from https://github.com/OpenZeppelin/openzeppelin-labs/blob/3887ab77b8adafba4a26ace002f3a684c1a3388b/upgradeability_ownership/contracts/ownership/Ownable.sol
* Modifications:
* 1. Consolidate OwnableStorage into this contract (7/13/18)
* 2. Reformat, conform to Solidity 0.6 syntax, and add error messages (5/13/20)
* 3. Make public functions external (5/27/20)
*/
contract Ownable {
// Owner of the contract
address private _owner;
/**
* @dev Event to show ownership has been transferred
* @param previousOwner representing the address of the previous owner
* @param newOwner representing the address of the new owner
*/
event OwnershipTransferred(address previousOwner, address newOwner);
/**
* @dev The constructor sets the original owner of the contract to the sender account.
*/
constructor() public {
setOwner(msg.sender);
}
/**
* @dev Tells the address of the owner
* @return the address of the owner
*/
function owner() external view returns (address) {
return _owner;
}
/**
* @dev Sets a new owner address
*/
function setOwner(address newOwner) internal {
_owner = newOwner;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(msg.sender == _owner, "Ownable: caller is not the owner");
_;
}
/**
* @dev Allows the current owner to transfer control of the contract to a newOwner.
* @param newOwner The address to transfer ownership to.
*/
function transferOwnership(address newOwner) external onlyOwner {
require(
newOwner != address(0),
"Ownable: new owner is the zero address"
);
emit OwnershipTransferred(_owner, newOwner);
setOwner(newOwner);
}
}
// File: contracts/v1/Pausable.sol
/**
* Copyright (c) 2016 Smart Contract Solutions, Inc.
* Copyright (c) 2018-2020 CENTRE SECZ0
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
pragma solidity 0.6.12;
/**
* @notice Base contract which allows children to implement an emergency stop
* mechanism
* @dev Forked from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/feb665136c0dae9912e08397c1a21c4af3651ef3/contracts/lifecycle/Pausable.sol
* Modifications:
* 1. Added pauser role, switched pause/unpause to be onlyPauser (6/14/2018)
* 2. Removed whenNotPause/whenPaused from pause/unpause (6/14/2018)
* 3. Removed whenPaused (6/14/2018)
* 4. Switches ownable library to use ZeppelinOS (7/12/18)
* 5. Remove constructor (7/13/18)
* 6. Reformat, conform to Solidity 0.6 syntax and add error messages (5/13/20)
* 7. Make public functions external (5/27/20)
*/
contract Pausable is Ownable {
event Pause();
event Unpause();
event PauserChanged(address indexed newAddress);
address public pauser;
bool public paused = false;
/**
* @dev Modifier to make a function callable only when the contract is not paused.
*/
modifier whenNotPaused() {
require(!paused, "Pausable: paused");
_;
}
/**
* @dev throws if called by any account other than the pauser
*/
modifier onlyPauser() {
require(msg.sender == pauser, "Pausable: caller is not the pauser");
_;
}
/**
* @dev called by the owner to pause, triggers stopped state
*/
function pause() external onlyPauser {
paused = true;
emit Pause();
}
/**
* @dev called by the owner to unpause, returns to normal state
*/
function unpause() external onlyPauser {
paused = false;
emit Unpause();
}
/**
* @dev update the pauser role
*/
function updatePauser(address _newPauser) external onlyOwner {
require(
_newPauser != address(0),
"Pausable: new pauser is the zero address"
);
pauser = _newPauser;
emit PauserChanged(pauser);
}
}
// File: contracts/v1/Blacklistable.sol
/**
* Copyright (c) 2018-2020 CENTRE SECZ
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
pragma solidity 0.6.12;
/**
* @title Blacklistable Token
* @dev Allows accounts to be blacklisted by a "blacklister" role
*/
contract Blacklistable is Ownable {
address public blacklister;
mapping(address => bool) internal blacklisted;
event Blacklisted(address indexed _account);
event UnBlacklisted(address indexed _account);
event BlacklisterChanged(address indexed newBlacklister);
/**
* @dev Throws if called by any account other than the blacklister
*/
modifier onlyBlacklister() {
require(
msg.sender == blacklister,
"Blacklistable: caller is not the blacklister"
);
_;
}
/**
* @dev Throws if argument account is blacklisted
* @param _account The address to check
*/
modifier notBlacklisted(address _account) {
require(
!blacklisted[_account],
"Blacklistable: account is blacklisted"
);
_;
}
/**
* @dev Checks if account is blacklisted
* @param _account The address to check
*/
function isBlacklisted(address _account) external view returns (bool) {
return blacklisted[_account];
}
/**
* @dev Adds account to blacklist
* @param _account The address to blacklist
*/
function blacklist(address _account) external onlyBlacklister {
blacklisted[_account] = true;
emit Blacklisted(_account);
}
/**
* @dev Removes account from blacklist
* @param _account The address to remove from the blacklist
*/
function unBlacklist(address _account) external onlyBlacklister {
blacklisted[_account] = false;
emit UnBlacklisted(_account);
}
function updateBlacklister(address _newBlacklister) external onlyOwner {
require(
_newBlacklister != address(0),
"Blacklistable: new blacklister is the zero address"
);
blacklister = _newBlacklister;
emit BlacklisterChanged(blacklister);
}
}
// File: contracts/v1/FiatTokenV1.sol
/**
*
* Copyright (c) 2018-2020 CENTRE SECZ
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
pragma solidity 0.6.12;
/**
* @title FiatToken
* @dev ERC20 Token backed by fiat reserves
*/
contract FiatTokenV1 is AbstractFiatTokenV1, Ownable, Pausable, Blacklistable {
using SafeMath for uint256;
string public name;
string public symbol;
uint8 public decimals;
string public currency;
address public masterMinter;
bool internal initialized;
mapping(address => uint256) internal balances;
mapping(address => mapping(address => uint256)) internal allowed;
uint256 internal totalSupply_ = 0;
mapping(address => bool) internal minters;
mapping(address => uint256) internal minterAllowed;
event Mint(address indexed minter, address indexed to, uint256 amount);
event Burn(address indexed burner, uint256 amount);
event MinterConfigured(address indexed minter, uint256 minterAllowedAmount);
event MinterRemoved(address indexed oldMinter);
event MasterMinterChanged(address indexed newMasterMinter);
function initialize(
string memory tokenName,
string memory tokenSymbol,
string memory tokenCurrency,
uint8 tokenDecimals,
address newMasterMinter,
address newPauser,
address newBlacklister,
address newOwner
) public {
require(!initialized, "FiatToken: contract is already initialized");
require(
newMasterMinter != address(0),
"FiatToken: new masterMinter is the zero address"
);
require(
newPauser != address(0),
"FiatToken: new pauser is the zero address"
);
require(
newBlacklister != address(0),
"FiatToken: new blacklister is the zero address"
);
require(
newOwner != address(0),
"FiatToken: new owner is the zero address"
);
name = tokenName;
symbol = tokenSymbol;
currency = tokenCurrency;
decimals = tokenDecimals;
masterMinter = newMasterMinter;
pauser = newPauser;
blacklister = newBlacklister;
setOwner(newOwner);
initialized = true;
}
/**
* @dev Throws if called by any account other than a minter
*/
modifier onlyMinters() {
require(minters[msg.sender], "FiatToken: caller is not a minter");
_;
}
/**
* @dev Function to mint tokens
* @param _to The address that will receive the minted tokens.
* @param _amount The amount of tokens to mint. Must be less than or equal
* to the minterAllowance of the caller.
* @return A boolean that indicates if the operation was successful.
*/
function mint(address _to, uint256 _amount)
external
whenNotPaused
onlyMinters
notBlacklisted(msg.sender)
notBlacklisted(_to)
returns (bool)
{
require(_to != address(0), "FiatToken: mint to the zero address");
require(_amount > 0, "FiatToken: mint amount not greater than 0");
uint256 mintingAllowedAmount = minterAllowed[msg.sender];
require(
_amount <= mintingAllowedAmount,
"FiatToken: mint amount exceeds minterAllowance"
);
totalSupply_ = totalSupply_.add(_amount);
balances[_to] = balances[_to].add(_amount);
minterAllowed[msg.sender] = mintingAllowedAmount.sub(_amount);
emit Mint(msg.sender, _to, _amount);
emit Transfer(address(0), _to, _amount);
return true;
}
/**
* @dev Throws if called by any account other than the masterMinter
*/
modifier onlyMasterMinter() {
require(
msg.sender == masterMinter,
"FiatToken: caller is not the masterMinter"
);
_;
}
/**
* @dev Get minter allowance for an account
* @param minter The address of the minter
*/
function minterAllowance(address minter) external view returns (uint256) {
return minterAllowed[minter];
}
/**
* @dev Checks if account is a minter
* @param account The address to check
*/
function isMinter(address account) external view returns (bool) {
return minters[account];
}
/**
* @notice Amount of remaining tokens spender is allowed to transfer on
* behalf of the token owner
* @param owner Token owner's address
* @param spender Spender's address
* @return Allowance amount
*/
function allowance(address owner, address spender)
external
override
view
returns (uint256)
{
return allowed[owner][spender];
}
/**
* @dev Get totalSupply of token
*/
function totalSupply() external override view returns (uint256) {
return totalSupply_;
}
/**
* @dev Get token balance of an account
* @param account address The account
*/
function balanceOf(address account)
external
override
view
returns (uint256)
{
return balances[account];
}
/**
* @notice Set spender's allowance over the caller's tokens to be a given
* value.
* @param spender Spender's address
* @param value Allowance amount
* @return True if successful
*/
function approve(address spender, uint256 value)
external
override
whenNotPaused
notBlacklisted(msg.sender)
notBlacklisted(spender)
returns (bool)
{
_approve(msg.sender, spender, value);
return true;
}
/**
* @dev Internal function to set allowance
* @param owner Token owner's address
* @param spender Spender's address
* @param value Allowance amount
*/
function _approve(
address owner,
address spender,
uint256 value
) internal override {
require(owner != address(0), "ERC20: approve from the zero address");
require(spender != address(0), "ERC20: approve to the zero address");
allowed[owner][spender] = value;
emit Approval(owner, spender, value);
}
/**
* @notice Transfer tokens by spending allowance
* @param from Payer's address
* @param to Payee's address
* @param value Transfer amount
* @return True if successful
*/
function transferFrom(
address from,
address to,
uint256 value
)
external
override
whenNotPaused
notBlacklisted(msg.sender)
notBlacklisted(from)
notBlacklisted(to)
returns (bool)
{
require(
value <= allowed[from][msg.sender],
"ERC20: transfer amount exceeds allowance"
);
_transfer(from, to, value);
allowed[from][msg.sender] = allowed[from][msg.sender].sub(value);
return true;
}
/**
* @notice Transfer tokens from the caller
* @param to Payee's address
* @param value Transfer amount
* @return True if successful
*/
function transfer(address to, uint256 value)
external
override
whenNotPaused
notBlacklisted(msg.sender)
notBlacklisted(to)
returns (bool)
{
_transfer(msg.sender, to, value);
return true;
}
/**
* @notice Internal function to process transfers
* @param from Payer's address
* @param to Payee's address
* @param value Transfer amount
*/
function _transfer(
address from,
address to,
uint256 value
) internal override {
require(from != address(0), "ERC20: transfer from the zero address");
require(to != address(0), "ERC20: transfer to the zero address");
require(
value <= balances[from],
"ERC20: transfer amount exceeds balance"
);
balances[from] = balances[from].sub(value);
balances[to] = balances[to].add(value);
emit Transfer(from, to, value);
}
/**
* @dev Function to add/update a new minter
* @param minter The address of the minter
* @param minterAllowedAmount The minting amount allowed for the minter
* @return True if the operation was successful.
*/
function configureMinter(address minter, uint256 minterAllowedAmount)
external
whenNotPaused
onlyMasterMinter
returns (bool)
{
minters[minter] = true;
minterAllowed[minter] = minterAllowedAmount;
emit MinterConfigured(minter, minterAllowedAmount);
return true;
}
/**
* @dev Function to remove a minter
* @param minter The address of the minter to remove
* @return True if the operation was successful.
*/
function removeMinter(address minter)
external
onlyMasterMinter
returns (bool)
{
minters[minter] = false;
minterAllowed[minter] = 0;
emit MinterRemoved(minter);
return true;
}
/**
* @dev allows a minter to burn some of its own tokens
* Validates that caller is a minter and that sender is not blacklisted
* amount is less than or equal to the minter's account balance
* @param _amount uint256 the amount of tokens to be burned
*/
function burn(uint256 _amount)
external
whenNotPaused
onlyMinters
notBlacklisted(msg.sender)
{
uint256 balance = balances[msg.sender];
require(_amount > 0, "FiatToken: burn amount not greater than 0");
require(balance >= _amount, "FiatToken: burn amount exceeds balance");
totalSupply_ = totalSupply_.sub(_amount);
balances[msg.sender] = balance.sub(_amount);
emit Burn(msg.sender, _amount);
emit Transfer(msg.sender, address(0), _amount);
}
function updateMasterMinter(address _newMasterMinter) external onlyOwner {
require(
_newMasterMinter != address(0),
"FiatToken: new masterMinter is the zero address"
);
masterMinter = _newMasterMinter;
emit MasterMinterChanged(masterMinter);
}
}
// File: @openzeppelin/contracts/utils/Address.sol
pragma solidity ^0.6.2;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
* ====
*/
function isContract(address account) internal view returns (bool) {
// According to EIP-1052, 0x0 is the value returned for not-yet created accounts
// and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned
// for accounts without code, i.e. `keccak256('')`
bytes32 codehash;
bytes32 accountHash
= 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
// solhint-disable-next-line no-inline-assembly
assembly {
codehash := extcodehash(account)
}
return (codehash != accountHash && codehash != 0x0);
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(
address(this).balance >= amount,
"Address: insufficient balance"
);
// solhint-disable-next-line avoid-low-level-calls, avoid-call-value
(bool success, ) = recipient.call{ value: amount }("");
require(
success,
"Address: unable to send value, recipient may have reverted"
);
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain`call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason, it is bubbled up by this
* function (like regular Solidity function calls).
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data)
internal
returns (bytes memory)
{
return functionCall(target, data, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return _functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value
) internal returns (bytes memory) {
return
functionCallWithValue(
target,
data,
value,
"Address: low-level call with value failed"
);
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
require(
address(this).balance >= value,
"Address: insufficient balance for call"
);
return _functionCallWithValue(target, data, value, errorMessage);
}
function _functionCallWithValue(
address target,
bytes memory data,
uint256 weiValue,
string memory errorMessage
) private returns (bytes memory) {
require(isContract(target), "Address: call to non-contract");
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory returndata) = target.call{
value: weiValue
}(data);
if (success) {
return returndata;
} else {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
// solhint-disable-next-line no-inline-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
}
// File: @openzeppelin/contracts/token/ERC20/SafeERC20.sol
pragma solidity ^0.6.0;
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
using SafeMath for uint256;
using Address for address;
function safeTransfer(
IERC20 token,
address to,
uint256 value
) internal {
_callOptionalReturn(
token,
abi.encodeWithSelector(token.transfer.selector, to, value)
);
}
function safeTransferFrom(
IERC20 token,
address from,
address to,
uint256 value
) internal {
_callOptionalReturn(
token,
abi.encodeWithSelector(token.transferFrom.selector, from, to, value)
);
}
/**
* @dev Deprecated. This function has issues similar to the ones found in
* {IERC20-approve}, and its usage is discouraged.
*
* Whenever possible, use {safeIncreaseAllowance} and
* {safeDecreaseAllowance} instead.
*/
function safeApprove(
IERC20 token,
address spender,
uint256 value
) internal {
// safeApprove should only be called when setting an initial allowance,
// or when resetting it to zero. To increase and decrease it, use
// 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
// solhint-disable-next-line max-line-length
require(
(value == 0) || (token.allowance(address(this), spender) == 0),
"SafeERC20: approve from non-zero to non-zero allowance"
);
_callOptionalReturn(
token,
abi.encodeWithSelector(token.approve.selector, spender, value)
);
}
function safeIncreaseAllowance(
IERC20 token,
address spender,
uint256 value
) internal {
uint256 newAllowance = token.allowance(address(this), spender).add(
value
);
_callOptionalReturn(
token,
abi.encodeWithSelector(
token.approve.selector,
spender,
newAllowance
)
);
}
function safeDecreaseAllowance(
IERC20 token,
address spender,
uint256 value
) internal {
uint256 newAllowance = token.allowance(address(this), spender).sub(
value,
"SafeERC20: decreased allowance below zero"
);
_callOptionalReturn(
token,
abi.encodeWithSelector(
token.approve.selector,
spender,
newAllowance
)
);
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that
// the target address contains contract code and also asserts for success in the low-level call.
bytes memory returndata = address(token).functionCall(
data,
"SafeERC20: low-level call failed"
);
if (returndata.length > 0) {
// Return data is optional
// solhint-disable-next-line max-line-length
require(
abi.decode(returndata, (bool)),
"SafeERC20: ERC20 operation did not succeed"
);
}
}
}
// File: contracts/v1.1/Rescuable.sol
/**
* Copyright (c) 2018-2020 CENTRE SECZ
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
pragma solidity 0.6.12;
contract Rescuable is Ownable {
using SafeERC20 for IERC20;
address private _rescuer;
event RescuerChanged(address indexed newRescuer);
/**
* @notice Returns current rescuer
* @return Rescuer's address
*/
function rescuer() external view returns (address) {
return _rescuer;
}
/**
* @notice Revert if called by any account other than the rescuer.
*/
modifier onlyRescuer() {
require(msg.sender == _rescuer, "Rescuable: caller is not the rescuer");
_;
}
/**
* @notice Rescue ERC20 tokens locked up in this contract.
* @param tokenContract ERC20 token contract address
* @param to Recipient address
* @param amount Amount to withdraw
*/
function rescueERC20(
IERC20 tokenContract,
address to,
uint256 amount
) external onlyRescuer {
tokenContract.safeTransfer(to, amount);
}
/**
* @notice Assign the rescuer role to a given address.
* @param newRescuer New rescuer's address
*/
function updateRescuer(address newRescuer) external onlyOwner {
require(
newRescuer != address(0),
"Rescuable: new rescuer is the zero address"
);
_rescuer = newRescuer;
emit RescuerChanged(newRescuer);
}
}
// File: contracts/v1.1/FiatTokenV1_1.sol
/**
* Copyright (c) 2018-2020 CENTRE SECZ
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
pragma solidity 0.6.12;
/**
* @title FiatTokenV1_1
* @dev ERC20 Token backed by fiat reserves
*/
contract FiatTokenV1_1 is FiatTokenV1, Rescuable {
}
// File: contracts/v2/AbstractFiatTokenV2.sol
/**
* Copyright (c) 2018-2020 CENTRE SECZ
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
pragma solidity 0.6.12;
abstract contract AbstractFiatTokenV2 is AbstractFiatTokenV1 {
function _increaseAllowance(
address owner,
address spender,
uint256 increment
) internal virtual;
function _decreaseAllowance(
address owner,
address spender,
uint256 decrement
) internal virtual;
}
// File: contracts/util/ECRecover.sol
/**
* Copyright (c) 2016-2019 zOS Global Limited
* Copyright (c) 2018-2020 CENTRE SECZ
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
pragma solidity 0.6.12;
/**
* @title ECRecover
* @notice A library that provides a safe ECDSA recovery function
*/
library ECRecover {
/**
* @notice Recover signer's address from a signed message
* @dev Adapted from: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/65e4ffde586ec89af3b7e9140bdc9235d1254853/contracts/cryptography/ECDSA.sol
* Modifications: Accept v, r, and s as separate arguments
* @param digest Keccak-256 hash digest of the signed message
* @param v v of the signature
* @param r r of the signature
* @param s s of the signature
* @return Signer address
*/
function recover(
bytes32 digest,
uint8 v,
bytes32 r,
bytes32 s
) internal pure returns (address) {
// EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
// unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
// the valid range for s in (281): 0 < s < secp256k1n ÷ 2 + 1, and for v in (282): v ∈ {27, 28}. Most
// signatures from current libraries generate a unique signature with an s-value in the lower half order.
//
// If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
// with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
// vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
// these malleable signatures as well.
if (
uint256(s) >
0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0
) {
revert("ECRecover: invalid signature 's' value");
}
if (v != 27 && v != 28) {
revert("ECRecover: invalid signature 'v' value");
}
// If the signature is valid (and not malleable), return the signer address
address signer = ecrecover(digest, v, r, s);
require(signer != address(0), "ECRecover: invalid signature");
return signer;
}
}
// File: contracts/util/EIP712.sol
/**
* Copyright (c) 2018-2020 CENTRE SECZ
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
pragma solidity 0.6.12;
/**
* @title EIP712
* @notice A library that provides EIP712 helper functions
*/
library EIP712 {
/**
* @notice Make EIP712 domain separator
* @param name Contract name
* @param version Contract version
* @return Domain separator
*/
function makeDomainSeparator(string memory name, string memory version)
internal
view
returns (bytes32)
{
uint256 chainId;
assembly {
chainId := chainid()
}
return
keccak256(
abi.encode(
// keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")
0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f,
keccak256(bytes(name)),
keccak256(bytes(version)),
chainId,
address(this)
)
);
}
/**
* @notice Recover signer's address from a EIP712 signature
* @param domainSeparator Domain separator
* @param v v of the signature
* @param r r of the signature
* @param s s of the signature
* @param typeHashAndData Type hash concatenated with data
* @return Signer's address
*/
function recover(
bytes32 domainSeparator,
uint8 v,
bytes32 r,
bytes32 s,
bytes memory typeHashAndData
) internal pure returns (address) {
bytes32 digest = keccak256(
abi.encodePacked(
"\x19\x01",
domainSeparator,
keccak256(typeHashAndData)
)
);
return ECRecover.recover(digest, v, r, s);
}
}
// File: contracts/v2/EIP712Domain.sol
/**
* Copyright (c) 2018-2020 CENTRE SECZ
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
pragma solidity 0.6.12;
/**
* @title EIP712 Domain
*/
contract EIP712Domain {
/**
* @dev EIP712 Domain Separator
*/
bytes32 public DOMAIN_SEPARATOR;
}
// File: contracts/v2/EIP3009.sol
/**
* Copyright (c) 2018-2020 CENTRE SECZ
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
pragma solidity 0.6.12;
/**
* @title EIP-3009
* @notice Provide internal implementation for gas-abstracted transfers
* @dev Contracts that inherit from this must wrap these with publicly
* accessible functions, optionally adding modifiers where necessary
*/
abstract contract EIP3009 is AbstractFiatTokenV2, EIP712Domain {
// keccak256("TransferWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce)")
bytes32
public constant TRANSFER_WITH_AUTHORIZATION_TYPEHASH = 0x7c7c6cdb67a18743f49ec6fa9b35f50d52ed05cbed4cc592e13b44501c1a2267;
// keccak256("ReceiveWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce)")
bytes32
public constant RECEIVE_WITH_AUTHORIZATION_TYPEHASH = 0xd099cc98ef71107a616c4f0f941f04c322d8e254fe26b3c6668db87aae413de8;
// keccak256("CancelAuthorization(address authorizer,bytes32 nonce)")
bytes32
public constant CANCEL_AUTHORIZATION_TYPEHASH = 0x158b0a9edf7a828aad02f63cd515c68ef2f50ba807396f6d12842833a1597429;
/**
* @dev authorizer address => nonce => bool (true if nonce is used)
*/
mapping(address => mapping(bytes32 => bool)) private _authorizationStates;
event AuthorizationUsed(address indexed authorizer, bytes32 indexed nonce);
event AuthorizationCanceled(
address indexed authorizer,
bytes32 indexed nonce
);
/**
* @notice Returns the state of an authorization
* @dev Nonces are randomly generated 32-byte data unique to the
* authorizer's address
* @param authorizer Authorizer's address
* @param nonce Nonce of the authorization
* @return True if the nonce is used
*/
function authorizationState(address authorizer, bytes32 nonce)
external
view
returns (bool)
{
return _authorizationStates[authorizer][nonce];
}
/**
* @notice Execute a transfer with a signed authorization
* @param from Payer's address (Authorizer)
* @param to Payee's address
* @param value Amount to be transferred
* @param validAfter The time after which this is valid (unix time)
* @param validBefore The time before which this is valid (unix time)
* @param nonce Unique nonce
* @param v v of the signature
* @param r r of the signature
* @param s s of the signature
*/
function _transferWithAuthorization(
address from,
address to,
uint256 value,
uint256 validAfter,
uint256 validBefore,
bytes32 nonce,
uint8 v,
bytes32 r,
bytes32 s
) internal {
_requireValidAuthorization(from, nonce, validAfter, validBefore);
bytes memory data = abi.encode(
TRANSFER_WITH_AUTHORIZATION_TYPEHASH,
from,
to,
value,
validAfter,
validBefore,
nonce
);
require(
EIP712.recover(DOMAIN_SEPARATOR, v, r, s, data) == from,
"FiatTokenV2: invalid signature"
);
_markAuthorizationAsUsed(from, nonce);
_transfer(from, to, value);
}
/**
* @notice Receive a transfer with a signed authorization from the payer
* @dev This has an additional check to ensure that the payee's address
* matches the caller of this function to prevent front-running attacks.
* @param from Payer's address (Authorizer)
* @param to Payee's address
* @param value Amount to be transferred
* @param validAfter The time after which this is valid (unix time)
* @param validBefore The time before which this is valid (unix time)
* @param nonce Unique nonce
* @param v v of the signature
* @param r r of the signature
* @param s s of the signature
*/
function _receiveWithAuthorization(
address from,
address to,
uint256 value,
uint256 validAfter,
uint256 validBefore,
bytes32 nonce,
uint8 v,
bytes32 r,
bytes32 s
) internal {
require(to == msg.sender, "FiatTokenV2: caller must be the payee");
_requireValidAuthorization(from, nonce, validAfter, validBefore);
bytes memory data = abi.encode(
RECEIVE_WITH_AUTHORIZATION_TYPEHASH,
from,
to,
value,
validAfter,
validBefore,
nonce
);
require(
EIP712.recover(DOMAIN_SEPARATOR, v, r, s, data) == from,
"FiatTokenV2: invalid signature"
);
_markAuthorizationAsUsed(from, nonce);
_transfer(from, to, value);
}
/**
* @notice Attempt to cancel an authorization
* @param authorizer Authorizer's address
* @param nonce Nonce of the authorization
* @param v v of the signature
* @param r r of the signature
* @param s s of the signature
*/
function _cancelAuthorization(
address authorizer,
bytes32 nonce,
uint8 v,
bytes32 r,
bytes32 s
) internal {
_requireUnusedAuthorization(authorizer, nonce);
bytes memory data = abi.encode(
CANCEL_AUTHORIZATION_TYPEHASH,
authorizer,
nonce
);
require(
EIP712.recover(DOMAIN_SEPARATOR, v, r, s, data) == authorizer,
"FiatTokenV2: invalid signature"
);
_authorizationStates[authorizer][nonce] = true;
emit AuthorizationCanceled(authorizer, nonce);
}
/**
* @notice Check that an authorization is unused
* @param authorizer Authorizer's address
* @param nonce Nonce of the authorization
*/
function _requireUnusedAuthorization(address authorizer, bytes32 nonce)
private
view
{
require(
!_authorizationStates[authorizer][nonce],
"FiatTokenV2: authorization is used or canceled"
);
}
/**
* @notice Check that authorization is valid
* @param authorizer Authorizer's address
* @param nonce Nonce of the authorization
* @param validAfter The time after which this is valid (unix time)
* @param validBefore The time before which this is valid (unix time)
*/
function _requireValidAuthorization(
address authorizer,
bytes32 nonce,
uint256 validAfter,
uint256 validBefore
) private view {
require(
now > validAfter,
"FiatTokenV2: authorization is not yet valid"
);
require(now < validBefore, "FiatTokenV2: authorization is expired");
_requireUnusedAuthorization(authorizer, nonce);
}
/**
* @notice Mark an authorization as used
* @param authorizer Authorizer's address
* @param nonce Nonce of the authorization
*/
function _markAuthorizationAsUsed(address authorizer, bytes32 nonce)
private
{
_authorizationStates[authorizer][nonce] = true;
emit AuthorizationUsed(authorizer, nonce);
}
}
// File: contracts/v2/EIP2612.sol
/**
* Copyright (c) 2018-2020 CENTRE SECZ
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
pragma solidity 0.6.12;
/**
* @title EIP-2612
* @notice Provide internal implementation for gas-abstracted approvals
*/
abstract contract EIP2612 is AbstractFiatTokenV2, EIP712Domain {
// keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)")
bytes32
public constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;
mapping(address => uint256) private _permitNonces;
/**
* @notice Nonces for permit
* @param owner Token owner's address (Authorizer)
* @return Next nonce
*/
function nonces(address owner) external view returns (uint256) {
return _permitNonces[owner];
}
/**
* @notice Verify a signed approval permit and execute if valid
* @param owner Token owner's address (Authorizer)
* @param spender Spender's address
* @param value Amount of allowance
* @param deadline The time at which this expires (unix time)
* @param v v of the signature
* @param r r of the signature
* @param s s of the signature
*/
function _permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) internal {
require(deadline >= now, "FiatTokenV2: permit is expired");
bytes memory data = abi.encode(
PERMIT_TYPEHASH,
owner,
spender,
value,
_permitNonces[owner]++,
deadline
);
require(
EIP712.recover(DOMAIN_SEPARATOR, v, r, s, data) == owner,
"EIP2612: invalid signature"
);
_approve(owner, spender, value);
}
}
// File: contracts/v2/FiatTokenV2.sol
/**
* Copyright (c) 2018-2020 CENTRE SECZ
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
pragma solidity 0.6.12;
/**
* @title FiatToken V2
* @notice ERC20 Token backed by fiat reserves, version 2
*/
contract FiatTokenV2 is FiatTokenV1_1, EIP3009, EIP2612 {
uint8 internal _initializedVersion;
/**
* @notice Initialize v2
* @param newName New token name
*/
function initializeV2(string calldata newName) external {
// solhint-disable-next-line reason-string
require(initialized && _initializedVersion == 0);
name = newName;
DOMAIN_SEPARATOR = EIP712.makeDomainSeparator(newName, "2");
_initializedVersion = 1;
}
/**
* @notice Increase the allowance by a given increment
* @param spender Spender's address
* @param increment Amount of increase in allowance
* @return True if successful
*/
function increaseAllowance(address spender, uint256 increment)
external
whenNotPaused
notBlacklisted(msg.sender)
notBlacklisted(spender)
returns (bool)
{
_increaseAllowance(msg.sender, spender, increment);
return true;
}
/**
* @notice Decrease the allowance by a given decrement
* @param spender Spender's address
* @param decrement Amount of decrease in allowance
* @return True if successful
*/
function decreaseAllowance(address spender, uint256 decrement)
external
whenNotPaused
notBlacklisted(msg.sender)
notBlacklisted(spender)
returns (bool)
{
_decreaseAllowance(msg.sender, spender, decrement);
return true;
}
/**
* @notice Execute a transfer with a signed authorization
* @param from Payer's address (Authorizer)
* @param to Payee's address
* @param value Amount to be transferred
* @param validAfter The time after which this is valid (unix time)
* @param validBefore The time before which this is valid (unix time)
* @param nonce Unique nonce
* @param v v of the signature
* @param r r of the signature
* @param s s of the signature
*/
function transferWithAuthorization(
address from,
address to,
uint256 value,
uint256 validAfter,
uint256 validBefore,
bytes32 nonce,
uint8 v,
bytes32 r,
bytes32 s
) external whenNotPaused notBlacklisted(from) notBlacklisted(to) {
_transferWithAuthorization(
from,
to,
value,
validAfter,
validBefore,
nonce,
v,
r,
s
);
}
/**
* @notice Receive a transfer with a signed authorization from the payer
* @dev This has an additional check to ensure that the payee's address
* matches the caller of this function to prevent front-running attacks.
* @param from Payer's address (Authorizer)
* @param to Payee's address
* @param value Amount to be transferred
* @param validAfter The time after which this is valid (unix time)
* @param validBefore The time before which this is valid (unix time)
* @param nonce Unique nonce
* @param v v of the signature
* @param r r of the signature
* @param s s of the signature
*/
function receiveWithAuthorization(
address from,
address to,
uint256 value,
uint256 validAfter,
uint256 validBefore,
bytes32 nonce,
uint8 v,
bytes32 r,
bytes32 s
) external whenNotPaused notBlacklisted(from) notBlacklisted(to) {
_receiveWithAuthorization(
from,
to,
value,
validAfter,
validBefore,
nonce,
v,
r,
s
);
}
/**
* @notice Attempt to cancel an authorization
* @dev Works only if the authorization is not yet used.
* @param authorizer Authorizer's address
* @param nonce Nonce of the authorization
* @param v v of the signature
* @param r r of the signature
* @param s s of the signature
*/
function cancelAuthorization(
address authorizer,
bytes32 nonce,
uint8 v,
bytes32 r,
bytes32 s
) external whenNotPaused {
_cancelAuthorization(authorizer, nonce, v, r, s);
}
/**
* @notice Update allowance with a signed permit
* @param owner Token owner's address (Authorizer)
* @param spender Spender's address
* @param value Amount of allowance
* @param deadline Expiration time, seconds since the epoch
* @param v v of the signature
* @param r r of the signature
* @param s s of the signature
*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external whenNotPaused notBlacklisted(owner) notBlacklisted(spender) {
_permit(owner, spender, value, deadline, v, r, s);
}
/**
* @notice Internal function to increase the allowance by a given increment
* @param owner Token owner's address
* @param spender Spender's address
* @param increment Amount of increase
*/
function _increaseAllowance(
address owner,
address spender,
uint256 increment
) internal override {
_approve(owner, spender, allowed[owner][spender].add(increment));
}
/**
* @notice Internal function to decrease the allowance by a given decrement
* @param owner Token owner's address
* @param spender Spender's address
* @param decrement Amount of decrease
*/
function _decreaseAllowance(
address owner,
address spender,
uint256 decrement
) internal override {
_approve(
owner,
spender,
allowed[owner][spender].sub(
decrement,
"ERC20: decreased allowance below zero"
)
);
}
}
// File: contracts/v2/FiatTokenV2_1.sol
/**
* Copyright (c) 2018-2020 CENTRE SECZ
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
pragma solidity 0.6.12;
// solhint-disable func-name-mixedcase
/**
* @title FiatToken V2.1
* @notice ERC20 Token backed by fiat reserves, version 2.1
*/
contract FiatTokenV2_1 is FiatTokenV2 {
/**
* @notice Initialize v2.1
* @param lostAndFound The address to which the locked funds are sent
*/
function initializeV2_1(address lostAndFound) external {
// solhint-disable-next-line reason-string
require(_initializedVersion == 1);
uint256 lockedAmount = balances[address(this)];
if (lockedAmount > 0) {
_transfer(address(this), lostAndFound, lockedAmount);
}
blacklisted[address(this)] = true;
_initializedVersion = 2;
}
/**
* @notice Version string for the EIP712 domain separator
* @return Version string
*/
function version() external view returns (string memory) {
return "2";
}
}
RFC-0323/TariThrottle
The Tari throttle, or Layer 2 burn rate controller
Maintainer(s): Cayle Sharrock
Licence
Copyright 2022 The Tari Development Community
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Language
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as shown here.
Disclaimer
This document and its content are intended for information purposes only and may be subject to change or update without notice.
This document may include preliminary concepts that may or may not be in the process of being developed by the Tari community. The release of this document is intended solely for review and discussion by the community of the technological merits of the potential system outlined herein.
Goals
This RFC provides an introductory exploratory analysis into the mechanisms behind a proposed Tari throttle: a Layer 2 controller for the L2 fee burn rate to control the Tari circulating supply.
Related Requests for Comment
Table of Contents
- Summary
- PID controllers
- Tari throttle
Summary
TariThrottle is a simple process controller designed to modulate the layer two burn rate in order to achieve two goals:
- Primarily, keep the emission and burn rate roughly balanced (ensuring the long-term sustainability of Tari), and
- Secondarily, to maintain the total circulating supply at a target value (satisfying an implicit assumption in cryptocurrencies that token supplies are finite).
A proof-of-concept controller has been implemented and tested in a simulation environment (repository). As the results below attest, the controller logic is sufficient to achieve these goals, even under highly volatile layer two fee conditions.
However, the controller achieves the goals at the expense of a rapidly changing layer two burn rate, which may be detrimental to the sustainability of validator nodes.
At the risk of the tail wagging the dog, the primary conclusion of this study is that the TariThrottle controller should likely not aim to maintain a supply target, but instead to ensure a sustainable layer two ecosystem, to whit:
- maintain a constant demand gradient so that under normal circumstances there is always a demand for new Tari and thus Minotari are constantly being burnt to satisfy this demand,
- marginal Validator Nodes are able to operate at or near break-even rates, while maintaining a healthy reserve of capacity for surge demand,
- minimum transaction fees remain below $0.01 in today's money, and
- the supply of Minotari is sustainable over the long-term.
Therefore, the conclusion of this study is not to abandon the original targets of the TariThrottle completely, but to adjust the priority of the primary goal (a sustainable long-term balance), and make it subservient to the primary goal of ensuring a constant demand gradient.
A modified Tari throttle model that seeks to achieve these aims is outside of the scope of this RFC and is left for a follow-up study.
PID controllers
TariThrottle is based on a simple PID controller design.
PID controllers are a type of control system that uses feedback to maintain a system in a desired state. They are widely used in industrial control systems.
The PID controller has three components:
- Proportional: This component is proportional to the error between the setpoint and the current value. It is the primary component of the controller and is used to drive the system towards the setpoint.
- Integral: This component is proportional to the integral of the error over time. It is used to eliminate steady-state error.
- Derivative: This component is proportional to the rate of change of the error. It is used to reduce overshoot and oscillation.
Tari throttle
The burn-sim
repository was used to generate the results in this exploratory study.
The primary module in the repository is the TariThrottle
struct.
This struct holds the following data:
- The controller parameters,
- The output variable,
burn_rate
. The burn rate is defined as the fraction of fees collected on the layer 2 that are burnt as exhaust (see the turbine model), and - The three functions that describe how the controller responds to the input variables.
Controller parameters
The controller parameters are used to tune the controller behaviour. They give operators the ability to fine-tune the behaviour of the controller without having to change the underlying code.
Specifically, the controller parameters are:
kp
: determines the weight of the net burn component of the controller. The net burn component is the difference between the emission and the current rate of L2 token burn. When emission equals burn, the total supply of Tari will remain constant.ki
: determines the weight of the integral component of the controller. This is calculated as the difference between the current total supply and the target supply. If the total circulating supply is at the target value, then this term will be zero.kd
: determines the weight of the derivative component of the controller. This is currently not used, since the controller is able to adequately control supply with just the proportional and integral terms.target_supply
: the target circulating supply of Tari.trigger_at
: the block height at which the controller logic becomes active.max
: the maximum burn rate that the controller will allow.min
: the minimum burn rate that the controller will allow. The maximum and minimum are important parameters to prevent extreme burn rates that could be detrimental to the sustainability of the network.
Controller inputs
The controller operates over periods
. A period is the number of blocks over which the controller will operate
without being able to change the burn rate. In practice, this is determined by the epoch length of the Layer two.
The total quantity of fees collected across the entire network is only known at the end of every epoch. This is a key input into the controller, and therefore we are limited to updating the burn rate at the end of each epoch as well.
For this study, the period length is set to 720 blocks, or roughly one day.
Fee models
Since we don't know what the Tari fees will be in the future, we can only carry out simulations based on various scenarios of fee growth. For example, we can model a low fee environment, an exponential fee growth environment, a highly volatile fee environment, and so on.
In this study, we looked at the following fee models:
Sigmoidal
: a sigmoidal growth model, which looks like an exponential growth model initially, but then levels off as the fees approach a maximum value. This is the most likely pattern to appear in practice, since it incorporates the idea that as fees become very high, the minimum transaction fee can be reduced so that total fee revenue grows sustainably, even as the network usage (in terms of transactions per second) continues to grow.Exponential
: a simple exponential growth model. This model assumes a constant annual growth rate in network fees.Sinusoidal
: a highly volatile fee environment, where the fees oscillate between a minimum and maximum value. This is the not likely scenario to appear in practice, but it is useful to test the robustness of the controller logic.
Emission model
The second input to the controller is the number of tokens emitted over the preceding period. This is straightforward to model, since the emission curve is known a priori. The currently proposed Minotari mainnet emission curve parameters were used to generate the emission inputs for this study, including a 30% premine and a 1% tail emission inflation rate.
Miscellaneous parameters
The trigger_at
parameter was generally set to start the controller after the first year of operation, when we
expect the layer two to go live on mainnet.
The target_supply
parameter was set to 15, 18 and 21 billion Tari to determine the effect of different supply
targets.
The initital_value
was set to min
to allow the circulating supply to approach the target supply as quickly as
possible.
The min
and max
parameters were set to 5% and 50% fee burn rates respectively.
THe period
was set to 720 blocks, or roughly one day. The epoch length for Tari has not been finalised yet, but it
should not be wildly different from this value.
Integer-based division
Typical PID controllers use floating-point math in their control algorithms. However, the TariThrottle controller will be run on a distributed set of machines that may be operating under different models for floating-point operations, since the IEEE leaves some aspects of floating-point math unspecified.
This is not permissible in Tari, and therefore an integer-based approach to control is implemented in the tari-sim
repository.
Simply put, this involves scaling the error values by a constant to convert them to integers of roughly the same
order of magnitude, (via error_i_scale
). The control parameters are also integers, expressed as "parts per million"
so that a value of 500,000 corresponds to 0.5 for example, while 10 corresponds to 0.00001. This give sufficient
granularity to the control parameters to allow for effective tuning of the controller.
The simulations
Controller parameters
A preliminary batch of simulations was run to set the controller parameters to values that roughly achieve the stated goal above. These simulations are omitted for brevity, but the result indicate that the following parameter range provide a good balance between robustness and responsiveness of the Tari throttle:
kp
= 0.0001 - 0.0003. A value closer to 100ppm gives more weight to achieving the target supply, while a value closer to 300ppm gives more weight to the net burn rate. The simulations run scenarios for aki
values of 100, 200 and 300 ppm.ki
= -0.00035. This value is negative, since if we're above the target supply, we must increase the burn rate, and vice versa.kd
= 0. This value is not used in the current implementation, since the controller is able to adequately control supply with just the proportional and integral terms.
Target supply
The simulations were run for target circulating supplies of 15, 18 and 21 billion Tari.
Target values other than the quoted "initial supply" of 21 billion Tari were used, because it is actually quite difficult to achieve the 21 billion target in practice. This is because the earliest it is possible to even reach this value (at ZERO burn rate) is after about 15 years.
This offers very little flexibility to the control logic, and could easily introduce instability into the layer two ecosystem.
For this reason, simulations were run with alternative target supplies of 15 and 18 billion Tari to compare how the controller reacts with more scope to adjust the burn rate.
Fee models
Five different fee models scenarios were employed:
- "Strong growth, tapering fees". This scenario describes strong fee growth in the network, reaching 5,000,000 Tari per day around 6 years after the launch of the layer 2. At a minimum fee of 0.01 Tari per transaction, this corresponds to a network activity of around 5,800 tx/s, or roughly double the average activity of the Visa network. The 'tapering fees' part of the scenario refers to the fact that this scenario does allow the network to continue to grow, but that the total fee revenue grows at a modest 100,000 XTR/d per year. This would be achieved by reducing the minimum transaction fee. This also matches the expectation that the price of XMR increases with network activity, and so reducing the transaction fee maintains sub-penny transaction fees in nominal USD terms.
- "Slower growth, tapering fees". This scenario is identical to "Strong growth, tapering fees", but with a lower maximum fee rate of 1,000,000 Tari per day.
- "25% annual fee growth". This scenario describes a network that grows at 25% per year, without compensating by decreasing the minimum transaction fee.
- "10% annual fee growth". This scenario describes a network that grows at 10% per year, without compensating by decreasing the minimum transaction fee.
- "Unstable fees, low frequency". This scenario describes a network that has volatile fees, oscillating between 0 and 500,000 XTR per day over a 90-day period. This is not a realistic scenario, but it is useful to test the robustness of the controller logic.
- "Unstable fees, high frequency". This scenario describes a network that has extremely volatile fees, oscillating between 0 and 1,000,000 XTR per day over a 14-day period. This is not a very realistic scenario, but it is useful to test the robustness of the controller logic under extremely volatile conditions.
Results
A simulation was run for every combination of the variations in kp
, target_supply
, and fee_model
. The results
of all 50+ simulation runs are given below.
21 billion Tari target supply
All of the following simulations were run with a target supply of 21 billion Tari.
10% annual fee growth (low fee scenario)
Figure 1. Supply target: 21 billion XTR. 10% annual fee growth fee model. kp
= 0.000100.
Figure 2. Supply target: 21 billion XTR. 10% annual fee growth fee model. kp
= 0.000200.
Figure 3. Supply target: 21 billion XTR. 10% annual fee growth fee model. kp
= 0.000300.
10% annual fee growth (high fee scenario)
Figure 4. Supply target: 21 billion XTR. 25% annual fee growth fee model. kp
= 0.000100.
Figure 5. Supply target: 21 billion XTR. 25% annual fee growth fee model. kp
= 0.000300.
Slower growth, tapering fees
Figure 6. Supply target: 21 billion XTR. Slower growth, tapering fees fee model. kp
= 0.000100.
Figure 7. Supply target: 21 billion XTR. Slower growth, tapering fees fee model. kp
= 0.000200.
Figure 8. Supply target: 21 billion XTR. Slower growth, tapering fees fee model. kp
= 0.000300.
Strong growth, tapering fees
Figure 9. Supply target: 21 billion XTR. Strong growth, tapering fees fee model. kp
= 0.000100.
Figure 10. Supply target: 21 billion XTR. Strong growth, tapering fees fee model. kp
= 0.000200.
Figure 11. Supply target: 21 billion XTR. Strong growth, tapering fees fee model. kp
= 0.000300.
Unstable fees, high frequency
Figure 12. Supply target: 21 billion XTR. Unstable fees, high frequency fee model. kp
= 0.000100.
Figure 13. Supply target: 21 billion XTR. Unstable fees, high frequency fee model. kp
= 0.000300.
Unstable fees, low frequency
Figure 14. Supply target: 21 billion XTR. Unstable fees, low frequency fee model. kp
= 0.000100.
Figure 15. Supply target: 21 billion XTR. Unstable fees, low frequency fee model. kp
= 0.000200.
Figure 16. Supply target: 21 billion XTR. Unstable fees, low frequency fee model. kp
= 0.000300.
18 billion Tari target supply
All of the following simulations were run with a target supply of 18 billion Tari.
10% annual fee growth (low fee scenario)
Figure 17. Supply target: 18 billion XTR. 10% annual fee growth fee model. kp
= 0.000100.
Figure 18. Supply target: 18 billion XTR. 10% annual fee growth fee model. kp
= 0.000200.
Figure 19. Supply target: 18 billion XTR. 10% annual fee growth fee model. kp
= 0.000300.
10% annual fee growth (high fee scenario)
Figure 20. Supply target: 18 billion XTR. 25% annual fee growth fee model. kp
= 0.000100.
Figure 21. Supply target: 18 billion XTR. 25% annual fee growth fee model. kp
= 0.000200.
Figure 22. Supply target: 18 billion XTR. 25% annual fee growth fee model. kp
= 0.000300.
Slower growth, tapering fees
Figure 23. Supply target: 18 billion XTR. Slower growth, tapering fees fee model. kp
= 0.000100.
Figure 24. Supply target: 18 billion XTR. Slower growth, tapering fees fee model. kp
= 0.000200.
Figure 25. Supply target: 18 billion XTR. Slower growth, tapering fees fee model. kp
= 0.000300.
Strong growth, tapering fees
Figure 26. Supply target: 18 billion XTR. Strong growth, tapering fees fee model. kp
= 0.000100.
Figure 27. Supply target: 18 billion XTR. Strong growth, tapering fees fee model. kp
= 0.000200.
Figure 28. Supply target: 18 billion XTR. Strong growth, tapering fees fee model. kp
= 0.000300.
Unstable fees, high frequency
Figure 29. Supply target: 18 billion XTR. Unstable fees, high frequency fee model. kp
= 0.000100.
Figure 30. Supply target: 18 billion XTR. Unstable fees, high frequency fee model. kp
= 0.000200.
Figure 31. Supply target: 18 billion XTR. Unstable fees, high frequency fee model. kp
= 0.000300.
Unstable fees, low frequency
Figure 32. Supply target: 18 billion XTR. Unstable fees, low frequency fee model. kp
= 0.000100.
Figure 33. Supply target: 18 billion XTR. Unstable fees, low frequency fee model. kp
= 0.000200.
Figure 34. Supply target: 18 billion XTR. Unstable fees, low frequency fee model. kp
= 0.000300.
15 billion Tari target supply
All of the following simulations were run with a target supply of 15 billion Tari.
10% annual fee growth (low fee scenario)
Figure 35. Supply target: 15 billion XTR. 10% annual fee growth fee model. kp
= 0.000100.
Figure 36. Supply target: 15 billion XTR. 10% annual fee growth fee model. kp
= 0.000200.
Figure 37. Supply target: 15 billion XTR. 10% annual fee growth fee model. kp
= 0.000300.
10% annual fee growth (high fee scenario)
Figure 38. Supply target: 15 billion XTR. 25% annual fee growth fee model. kp
= 0.000100.
Figure 39. Supply target: 15 billion XTR. 25% annual fee growth fee model. kp
= 0.000300.
Slower growth, tapering fees
Figure 40. Supply target: 15 billion XTR. Slower growth, tapering fees fee model. kp
= 0.000100.
Figure 41. Supply target: 15 billion XTR. Slower growth, tapering fees fee model. kp
= 0.000200.
Figure 42. Supply target: 15 billion XTR. Slower growth, tapering fees fee model. kp
= 0.000300.
Strong growth, tapering fees
Figure 43. Supply target: 15 billion XTR. Strong growth, tapering fees fee model. kp
= 0.000100.
Figure 44. Supply target: 15 billion XTR. Strong growth, tapering fees fee model. kp
= 0.000200.
Figure 45. Supply target: 15 billion XTR. Strong growth, tapering fees fee model. kp
= 0.000300.
Unstable fees, high frequency
Figure 46. Supply target: 15 billion XTR. Unstable fees, high frequency fee model. kp
= 0.000100.
Figure 47. Supply target: 15 billion XTR. Unstable fees, high frequency fee model. kp
= 0.000200.
Figure 48. Supply target: 15 billion XTR. Unstable fees, high frequency fee model. kp
= 0.000300.
Unstable fees, low frequency
Figure 49. Supply target: 15 billion XTR. Unstable fees, low frequency fee model. kp
= 0.000100.
Figure 50. Supply target: 15 billion XTR. Unstable fees, low frequency fee model. kp
= 0.000200.
Figure 51. Supply target: 15 billion XTR. Unstable fees, low frequency fee model. kp
= 0.000300.
Discussion
Low fees growth
Figures 1-3, 17-19, and 35-37 show the results of the simulations for the 10% annual fee growth scenario. None of
these scenarios produce sufficient fees to reduce the circulating supply to the target within the 33-year simulation
timeframe. The common feature of these simulations is that the controller hits the maximum burn rate of 50% and stays
there for the remainder of the simulation. Increasing the max
burn rate to a higher value might succeed in achieving
the target supply, but this might mean that validator nodes cannot operate at break-even rates.
Ultimately a poor growth in fee revenue -- in this model, never exceeding 30 tx/s over 30 years of operation, represents a failure mode, not just of the controller, but of the network as a whole, since Tari adoption has never taken off, and there's nothing a controller can do to fix that.
High fees growth
At the other end of the spectrum, if Tari achieves runaway success, and the minimum transaction fee is never reduced to compensate, then the controller will be able to maintain the target circulating supply of Tari during the 33-year simulation timeframe.
This is shown in Figures 4 and 5. In Figures 20-21, and 38-39, the tapering of supply only begins towards the end of the simulation period.
However, it should be noted that indefinite fee growth is not sustainable from a token supply point of view, unless the inflation rate of the emission curve is increased to compensate. This is not evident in these charts, but if the simulation were to continue for several more years, the fee growth would eventually set the burn rate to its minimum of 5% and the total circulating supply would eventually fall to zero.
So, a few things to note about this scenario:
- It's very unlikely to happen in practice that 25% network growth is achieved consistently for 30-40 years. Therefore, this scenario is largely illustrative of the controller's ability to handle extreme fee growth. And within fairly wide limits, we demonstrate that the throttle manages this very well.
- The minimum burn rate of 5% could be reduced further without negatively impacting validator node revenues (in fact reducing the burn rate is beneficial for validator nodes).
- In practice, it is not unreasonable to expect that the price of Tari in nominal USD terms would increase as the network usage increases, and so the minimum transaction fee would have to be reduced to maintain sub-penny transaction fees -- keeping the Tari network cheap to use for the average user. This entails that the total fee revenue would taper off, even as the network continues to grow. The Sigmoidal fee model is a better representation of this eventuality.
Unstable fee revenue
Figures 12-16, 29-34, and 46-51 show the controller's response to various scenarios under which the fee revenue is oscillating wildly. Under some scenarios, notably Figures 12 and 13, the target supply cannot be achieved even with the controller at minimum burn rates. This is symptomatic of the point made previously that a target supply of 21 billion offers very little room for the controller to maneuver.
In Figures 14-16, 32-34 and 46-51, the controller is able to maintain the supply target with very small amounts of oscillation, by synchronising the fee oscillation with an oscillating burn rate.
While this demonstrates that the throttle is able to achieve its design goals, one must question whether the wildly oscillating burn rates needed to achieve these goals are healthy for the network.
To maintain a constant supply, as the fees increase, the burn rate decreases to try and match the emission (which is roughly constant over the oscillation period). From a validator node perspective, the portion of fees paid to them increases as fees increase. That sounds great, but when fees decrease, they receive a smaller percentage of the shrinking pool of fees, and so they are doubly disadvantaged. Marginal validator nodes will be hit twice as hard, and be forced off the network due to unfavourable economics, reducing the overall network capacity. When fees pick up again, additional capacity will come online, but the lag between the increased usage and capacity could well lead to a degradation in the user experience.
Slower growth with tapering fees
The previous scenarios provide valuable insights into the controller's ability to handle extremes in network demand in terms of fee growth patterns. However, these scenarios are not likely to play out in reality, at least not for extended multi-decade periods.
The scenarios represented in the "tapering fees" series are more indicative of the long-term macro behaviour of the Tari fee growth. The two specific scenarios in this category reflect an initial exponential growth period followed by a more sustainable linear fee growth rate, corresponding to reducing the minimum transaction fee over time while the network continues to grow.
In the "Slower growth" variant, the fee revenue grows from zero to 1,000,000 XTR per day over 5 years, and then increases by 100,000 XTR/d per year after that.
Figures 6-8 are all quite similar. These all use 21 billion XTR as a target and differ only in the weighting applied to trying to achieve the target supply, vs. balancing burn rate and emission.
The slow growth rate allows the supply to reach the target value of 21 billion in about 20 years, at which point the burn rate adjusts slightly to between 15% and 20% to maintain the target supply.
Figures 23-25 shows what happens with a target supply of 18 billion XTR. Here the target is reached much sooner, after around 9 years of mainnet operation. In these simulations, the effect of the controller is more pronounced, since an additional 3 billion XTR must be burned to maintain the target supply compared to the scenario with a 21 billion XTR target.
Figures 40-42 show the scenario with a target supply of 15 billion XTR. The lower target requires several years of
max
burn with the supply overshooting the target before being pulled back onto the target.
Strong growth with tapering fees
The "Strong growth" variant of the tapering fees scenario assumes a much higher adoption rate, with fees growing to 5,000,000 XTR/d over 3 years, and then growing by 100,000 XTR/d per year after that.
Figures 9-11 show the results of the simulations with a target supply of 21 billion XTR. Here the controller cannot
meet the target supply since it would require a burn rate of under 5%. This is illustrated by the purple lines not
moving off the min
burn rate level for the entirety of the simulation period.
Figures 26-28 show the results of the simulations with a target supply of 18 billion XTR. Here the controller is
able to meet the target supply, but only just. The burn rate rises only slightly above the min
burn rate level
and is essentially "just holding on".
Finally, Figures 43-45 tell a similar but slightly more exaggerated version of the story.
In practice, if the strong growth scenario were to play out, several interventions would be necessary to give the
controller room to maneuver. These would include reducing the min
burn rate, and reucing the minimum transaction
fee to maintain sub-penny transaction fees.
Conclusions
This study shows that the TariThrottle controller is able to maintain a dual mandate of maintaining a target circulating supply of Tari as well as a steady net burn rate over a wide range of fee growth scenarios.
Some scenarios are unlikely to play out as described in practice. But they give valuable insights into how the controller would respond in stressful situations, short-term shocks and deviations, and offer an indication of the robustness of the controller algorithm.
The most common instances of the controller failing to achieve its target was when the fee growth was so low that the maximum burn rate was less than the tail emission.
Under these conditions, the tokens that are emitted exceed the maximum that can be burnt. In practice, this mode represents a failure of the Tari ecosystem as a whole and a limiting controller would not be the greatest concern at this point.
However, the biggest conclusion to draw from this study is that the stated dual mandate is ill-advised.
There are several scenarios where maintaining a target supply is detrimental to the sustainability of validator nodes and to the ability of the network to provide surge capacity during sudden high periods of network demand.
The oscillating fee revenue scenarios are particularly illuminating, since they demonstrate that as fees are falling, the controller increases the burn rate to try and match the emission. This results in a double loss of revenue for validator nodes, since they're receiving a smaller slice of a shrinking pie. This will force them offline sooner than they otherwise would have. When fees recover, it may take some time for those nodes to come back online, and as a result, users experience lags in the network.
Many scenarios illustrate that the controller can effectively do nothing for between 5 and 15 years while it waits for the circulating supply to reach its target. This effectively means that the controller is powerless to do anything to help with validator node profitability or excessive network fees during this period, because its control mandate is misaligned with the health of the network.
It's clear that some additional studies are needed. In particular, we need to identify and maximise for the health of the network, rather than a fixed supply target. Sometimes these two goals will be aligned, but it behooves us to remember which variables represent the tail, and which represent the dog.
As a minimum, the following factors should be considered, since they directly relate to the health of the ecosystem, in that they incentivise validation nodes to remain online and keep the Tari network affordable:
- There must be a near-constant driving force to burn Minotari and redeem them as Tari, similar to how a kite must always have tension in the string for the operator to be able to fly the kite. Brief periods of slack, corresponding to the price of Tari dropping below 1 XTR, are acceptable, since the operator can draw the slack in to regain tension (by continuing to burn Tari with no new redemptions), but if the situation persists, the kite will crash.
- Marginal validator nodes must be able to operate at break-even rates at approximately 50% utilisation. Marginal VNs are those that can spin up very quickly to meet surge demand, but are more expensive to run because of this. Typically, VNs running on spot EC2 instances fall into this category.
- Long-term validator nodes must be able to operate at break-even rates at approximately 20% utilisation. These nodes are those that are always online, and are typically running on reserved EC2 instances or even cheaper server infrastructure. The 20% value means that they will stay online all the time, whilst keeping 5x surge capacity in reserve.
- The supply of Minotari never goes to zero.
- The minimum transaction fee should be under 1c in USD terms.
A controller model that incorporates these target conditions will be the subject of future work.
Deprecated RFC
The following documents are either obsolete, or have been superseded by newer RFC documents.
RFC-0010/CodeStructure
Tari Code Structure and Organization
Maintainer(s): Cayle Sharrock
Licence
Copyright 2018 The Tari Development Community
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Language
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as shown here.
Disclaimer
This document and its content are intended for information purposes only and may be subject to change or update without notice.
This document may include preliminary concepts that may or may not be in the process of being developed by the Tari community. The release of this document is intended solely for review and discussion by the community of the technological merits of the potential system outlined herein.
Goals
The aim of this Request for Comment (RFC) is to describe and explain the Tari codebase layout.
Related Requests for Comment
None.
Description
The code follows a Domain-driven Design (DDD) layout, with top-level directories falling into infrastructure, domain and application layers.
Infrastructure Layer
The infrastructure layer provides a set of crates that have general infrastructural utility. The rest of the Tari codebase can make use of these crates to obtain persistence, communication and cryptographic services. The infrastructure layer doesn't know anything about blockchains, transactions or digital assets.
We recommend that code in this layer generalizes infrastructure services behind abstraction layers as much as is reasonable, so that specific implementations can be swapped out with relative ease.
Domain Layer
The domain layer houses the Tari "business logic". All protocol-related concepts and procedures are defined and implemented here.
This means that any and all terms defined in the Glossary will have a software implementation here, and only here. They can be used in the application layer, but must be implemented in the domain layer.
The domain layer can make use of crates in the infrastructure layer to achieve its goals.
Application Layer
In the application layer, applications build on top of the domain layer to produce the executable software that is deployed as part of the Tari network.
As an example, the following base layer applications may be developed as part of the Tari protocol release:
- A base node executable (tari_base_node)
- A Command Line Interface (CLI) wallet for the Tari cryptocurrency (tari_console_wallet)
- A standalone miner (tari_miner)
- A mining proxy to enable merge mining Monero (tari_merge_mining_proxy)
- An Application Programming Interface (API) server for the base node (REST, gRPC, etc.)
Code Layout
- Tari Protocol
Github: tari-project/tari
The Tari Protocol code repository is a Rust workspace consisting of multiple packages. A package is a set of crates, which is the source code of a binary or library.
The source code is organized into the following directories.
-
applications
contains crates for all the application-layer executables that form part of the Tari codebase.tari_base_node
- the Base Node applicationtari_console_wallet
- the CLI Wallet applicationtari_miner
- the SHA3 Miner (CPU)tari_merge_mining_proxy
- the Merge Mining Proxytari_explorer
- a local web based block explorer
-
base_layer
is the fundamental domain-layer directory and contains multiple packages and crates.core
- core classes and traits, such as Transactions, Blocks, and consensus, mempool, and blockchain database code;key_manager
- construction and storage of key derivations and mnemonic seed phrases;mmr
- an independent implementation of a Merkle Mountain Range;p2p
- the block and transaction propagation module;service_framework
- asynchronous service stack builder;wallet
- a wallet library including services and storage classes to create Tari wallets;wallet_ffi
- a Foreign Function Interface (FFI) library to create Tari wallets in other programming languages;
-
comms
is the networking and messaging subsystem, used across the base layer and applications; -
infrastructure
contains application-layer code and is not Tari-specific. It holds the following crates:derive
- a crate to containderive(...)
macros;shutdown
- a convenient way for threads to let each other know to stop working;storage
- data persistence services, including a Lightning Memory-mapped Database (LMDB) persistence implementation;
-
other utility and test libraries.
- Tari Cryptography
Github: tari-project/tari-crypto
Tari Crypto was refactored into its own crate, for ease of use and integration across different projects. It includes all cryptographic services, including a Curve25519 implementation.
Change Log
Date | Description | Author |
---|---|---|
21 Dec 2018 | First draft | CjS77 |
14 Jan 2022 | Deprecated in favour of README | CjS77 |
RFC-0121/Consensus Encoding
Consensus Encoding
Maintainer(s): Stanley Bondi
Licence
Copyright 2022 The Tari Development Community
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Language
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as shown here.
Disclaimer
This document and its content are intended for information purposes only and may be subject to change or update without notice.
This document may include preliminary concepts that may or may not be in the process of being developed by the Tari community. The release of this document is intended solely for review and discussion by the community of the technological merits of the potential system outlined herein.
Goals
The aim of this Request for Comment (RFC) is to describe the encoding used for various consensus-critical data types, as well as the construction of hash pre-images and signature challenges used in base-layer consensus.
Related Requests for Comment
Description
A Tari base node must validate each block containing a block header as well as set of transaction inputs, transaction outputs and transaction kernels, each containing a number of fields pertinent to their function within the [base layer]. The data contained within these structures needs to be consistently encoded (represented as bytes) across platforms and implementations so that the network can agree on a single correct state.
This RFC defines the low-level specification for how these data types MUST be encoded to construct a valid hash and signature on the Tari network.
Consensus Encoding
The primary goal of consensus encoding is to provide a consistent data format that is committed to in hashes and signatures.
Consensus encoding defines what "raw" data is included in the encoding, the order in which it should appear and the length for variable length elements. To keep encoding as simple as possible, no type information, field names etc. are catered for in the format as this is always statically known. This is particularly appropriate for hashes and signatures where many fields must be consistently represented and concatenated together.
The rest of this section defines some encodings for common primitives used in the Tari codebase.
Unsigned integer encoding
Varint encoding is used for integer fields greater than 1 byte. Describing varint is out of scope for this RFC but there are many resources online to understand this fairly basic encoding. The only rule we apply is that the encoding has a limit of 10 bytes, a little more than what is required to store a 64-bit integer.
Dynamically-sized vec encoding
This type refers to a contiguous block of data of any length. Because the size is dynamic, the size is included in the encoding.
|len(data)| data for type | data for type | ...
Fixed size arrays
If the size of the array is constant (static). The length is omitted and the data is encoded.
| data for type | ...
Optional or nullable encoding
An optional field starts with a 0x00 byte to indicate the value is not provided (None
, null
, nil
etc) or a 0x01 byte
to indicate that the value is provided followed by the encoding of the value.
| 0 or 1 | encoding for type |
Ristretto Keys
RistrettoPublicKey
and RistrettoPrivateKey
types defined in the tari_crypto
crate both have 32-byte canonical formats
and are encoded as a 32-byte fixed array.
The tari_crypto
Rust crate provides an FFI interface that allows
generating of the canonical byte formats in any language that supports FFI.
Commitment
A commitment is a RistrettoPublicKey and so has identical encoding.
Schnorr Signature
See the TLU on Schnorr Signatures
A Schnorr signature tuple is <R, s>
where R
is a RistrettoPublicKey and s
is a the signature scalar wrapped in RistrettoPrivateKey.
The encoding is fixed at 64-bytes:
| 32-byte public key | 32-byte scalar |
Signature
A signature tuple consists of a <R, s>
where R
is the public nonce and s
is the signature scalar.
The encoding is fixed at 64-bytes:
| 32-byte commitment (R) | 32-byte scalar (s) |
Commitment Signature
A commitment signature tuple consists of a <R, u, v>
where R
is the Pederson commitment \(r_u.G + r_v.H\)
for the signature scalars u
and v
.
The encoding is fixed at 96-bytes:
| 32-byte commitment (R) | 32-byte scalar (u) | 32-byte scalar (v) |
Example
Given the following data and types:
{
// Type: Fixed array of 5 bytes
short_id: [1,2,3,4,5],
// Type: variable length bytes
name: Buffer.from("Case"),
// Type: unsigned integer
age: 40,
// Type: struct
details: {
// Type: variable length bytes
kind: Buffer.from("Hacker"),
},
// Type: nullable varint
dob: null
}
Encoded (hex) as follows:
short id | len | name | age | len | kind | null? | dob |
---|---|---|---|---|---|---|---|
0102030405 | 04 | 43617365 | 28 | 05 | 4861636b6572 | 00 |
Note that nested structs are flattened and the order must be preserved to allow decoding.
The 00
null byte is important so that for e.g. the kind
bytes cannot be manipulated to
produce the same encoding as non-null dob
.
Block Header
The block hash pre-image is constructed by first constructing the merge mining hash. Each encoding is concatenated in order as follows:
version
- 1 byteheight
- varintprev_hash
- fixed 32-bytestimestamp
- varintinput_mr
- fixed 32-bytesoutput_mr
- fixed 32-bytesoutput_mmr_size
- varintwitness_mr
- fixed 32-byteskernel_mr
- fixed 32-byteskernel_mmr_size
- `varinttotal_kernel_offset
- 32-byte Scalar, see RistrettoPrivateKeytotal_script_offset
- 32-byte Scalar, see RistrettoPrivateKey
This pre-image is hashed and block hash is constructed, in order, as follows:
merge_mining_hash
- As abovepow_algo
- enumeration of types of PoW as a single unsigned byte, whereMonero = 0x00
andSha3 = 0x01
pow_data
- raw variable bytes (no length varint)nonce
- the PoW nonce,u64
converted to a fixed 8-byte array (little endian)
Output Features
pub struct OutputFeatures {
pub version: OutputFeaturesVersion,
pub maturity: u64,
pub flags: OutputFlags,
pub metadata: Vec<u8>,
pub unique_id: Option<Vec<u8>>,
pub parent_public_key: Option<PublicKey>,
pub asset: Option<AssetOutputFeatures>,
pub mint_non_fungible: Option<MintNonFungibleFeatures>,
pub sidechain_checkpoint: Option<SideChainCheckpointFeatures>,
}
Output features consensus encoding is defined as follows (in order):
version
- 1 unsigned byte. This should always be0x00
but is reserved for future proofing.maturity
- varintflags
- 1 unsigned bytemetadata
- dynamic vectorunique_id
- nullable + dynamic vectorparent_public_key
- nullable + 32-byte compressed public keyasset
- nullable + AssetOutputFeaturesmint_non_fungible
- nullable + MintNonFungibleFeaturessidechain_checkpoint
- nullable + SideChainCheckpointFeatures
AssetOutputFeatures
public_key
- RistrettoPublicKeytemplate_ids
- dynamic vector + varinttemplate_parameters
- dynamic vector
MintNonFungibleFeatures
asset_public_key
- RistrettoPublicKeyasset_owner_commitment
- RistrettoPublicKey
SideChainCheckpointFeatures
merkle_root
- fixed sized arraycommittee
- dynamic vector + RistrettoPublicKey
Transaction Output
pub struct TransactionOutput {
pub version: TransactionInputVersion,
pub features: OutputFeatures,
pub commitment: Commitment,
pub proof: RangeProof,
pub script: TariScript,
pub sender_offset_public_key: PublicKey,
pub metadata_signature: ComSignature,
pub covenant: Covenant,
}
The canonical output hash is appended to the output Merkle tree and commits to the common data between an output
and the input spending that output i.e. output_hash = Hash(version | features | commitment | script | covenant)
.
The encoding is defined as follows:
version
- 1 bytefeatures
- OutputFeaturescommitment
- RistrettoPublicKeyscript
- byte length as varint + TariScriptcovenant
- byte length as varint + Covenant
Witness hash
The witness hash is appended to the witness Merkle tree.
proof
- Raw proof bytes encoded using dynamic vector encodingmetadata_signature
- [CommitmentSignature]
Metadata signature challenge
See Metadata Signature for details.
public_commitment_nonce
- RistrettoPublicKeyscript
- byte length as varint + TariScriptfeatures
- OutputFeaturessender_offset_public_key
- RistrettoPublicKeycommitment
- RistrettoPublicKeycovenant
- byte length as varint + Covenant
Transaction Input
The following struct represents the full transaction input data for reference. The actual input struct does not duplicate the output data to optimise storage and transmission of the input.
pub struct TransactionInput {
pub version: u8,
pub input_data: ExecutionStack,
pub script_signature: ComSignature,
pub output_version: TransactionOutputVersion,
pub features: OutputFeatures,
pub commitment: Commitment,
pub script: TariScript,
pub sender_offset_public_key: PublicKey,
pub covenant: Covenant,
}
The transaction input canonical hash pre-image is constructed as follows:
input_version
- 1 byteoutput_hash
- See [TransactionOutput]sender_offset_public_key
- RistrettoPublicKeyinput_data
- TariScript Stackscript_signature
- [CommitmentSignature]
Transaction Kernel
The following struct represents the full transaction input data for reference. The actual input struct does not duplicate the output data to optimise storage and transmission of the input.
pub struct TransactionKernel {
pub version: TransactionKernelVersion,
pub features: KernelFeatures,
pub fee: MicroTari,
pub lock_height: u64,
pub excess: Commitment,
pub excess_sig: Signature,
}
The transaction kernel is encoded as follows:
input_version
- 1 bytefeatures
- OutputFeaturesfee
- RistrettoPublicKeylock_height
- TariScript Stackexcess
- Commitmentexcess_sig
- [Signature]
The canonical hash pre-image is constructed from this encoding.
Script Challenge
For details see RFC-0201_TariScript.md.
The script challenge is constructed as follows:
nonce_commitment
- Commitmentscript
- TariScriptinput_data
- TariScript Stackscript_public_key
- RistrettoPublicKeycommitment
- Commitment
RFC-0130/Mining
Full-node Mining on Tari Base Layer
Maintainer(s): Stringhandler
Licence
Copyright 2018 The Tari Development Community
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Language
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as shown here.
Disclaimer
This document and its content are intended for information purposes only and may be subject to change or update without notice.
This document may include preliminary concepts that may or may not be in the process of being developed by the Tari community. The release of this document is intended solely for review and discussion by the community of the technological merits of the potential system outlined herein.
Goals
The aim of this Request for Comment (RFC) is to provide a brief overview of the Tari merged mining process and introduce the primary functionality required of the Mining Server and Mining Worker.
Related Requests for Comment
Description
Assumptions
- The Tari blockchain will be merged mined with Monero.
- The Tari Base Layer has a network of Base Nodes that verify and propagate valid transactions and blocks.
Abstract
The process of merged mining Tari with Monero on the Tari Base Layer is performed by Mining Servers and Mining Workers. Mining Servers are responsible for constructing new blocks by bundling transactions from the mempool of a connected Base Node. They then distribute Proof-of-Work (PoW) tasks to Mining Workers in an attempt to solve newly created blocks. Solved solutions and shares are sent by the Mining Workers to the Mining Server, which in turn verifies the solution and distributes the newly created blocks to the Base Node and Monero Node for inclusion in their respective blockchains.
Merged Mining on Tari Base Layer
This document is divided into three parts:
- A brief overview of the merged mining process and the interactions between the Base Node, Mining Server and Mining Worker.
- The primary functionality required of the Mining Server.
- The primary functionality required of the Mining Worker.
Overview of Tari Merged Mining Process using Mining Servers and Mining Workers
Mining on the Tari Base Layer consists of three primary entities: the Base Nodes, Mining Servers and Mining Workers. A description of the Base Node is provided in RFC-0110/Base Nodes. A Mining Server is connected locally or remotely to a Tari Base Node and a Monero Node, and is responsible for constructing Tari and Monero Blocks from their respective mempools. The Mining Server should retrieve transactions from the mempool of the connected Base Node and assemble a new Tari block by bundling transactions together.
Mining servers may re-verify transactions before including them in a new Tari block, but this enforcement of verification and transaction rules such as signatures and timelocks is the responsibility of the connected Base Node. Mining Servers are responsible for cut-through, as this is required for scalability and privacy.
To enable merged mining of Tari with Monero, both a Tari and a Monero block need to be created and linked. First, a new Tari block is created and then the block header hash of the new Tari block is included in the coinbase transaction of the new Monero block. Once a new merged mined Monero block has been constructed, PoW tasks can be sent to the connected Mining Workers, which will attempt to solve the block by performing the latest released version of the PoW algorithm selected by Monero.
Assuming the Tari difficulty is less than the Monero difficulty, miners get rewarded for solving the PoW at any difficulty above the Tari difficulty. If the block is solved above the Tari difficulty, a new Tari block is mined. If the difficulty is also greater than the Monero difficulty, a new Monero block is mined as well. In either event, the header for the candidate Monero block is included in the Tari block header.
If the PoW solution was sufficient to meet the difficult level of both the Tari and Monero blockchains, then the individual blocks for each blockchain can be sent from the Mining Server to the Base Node and Monero Node to be added to the respective blockchains.
Every Tari block must include the solved Monero block's information (block header hash, Merkle tree branch and hash of the coinbase transaction) in the PoW summary section of the Tari block header. If the PoW solution found by the Mining Workers only solved the problem at the Tari difficulty, the Monero block can be discarded.
This process will ensure that the Tari difficulty remains independent. Adjusting the difficulty will ensure that the Tari block times are preserved. Also, the Tari block time can be less than, equal to or greater than the Monero block times. A more detailed description of the merged mining process between a Primary and Auxiliary blockchain is provided in the Merged Mining TLU report.
Functionality Required by Tari Mining Server
- The Tari blockchain MUST have the ability to be merged mined with Monero.
- The Tari Mining Server:
- MUST maintain a local or remote connection with a Base Node and a Monero Node.
- MUST have a mechanism to construct a new Tari and Monero block by selecting transactions from the different Tari and Monero mempools that need to be included in the different blocks.
- MUST apply cut-through when mining Tari transactions from the mempool and only add the excess to the list of new Tari block transactions.
- MAY have a configurable transaction selection mechanism for the block construction process.
- MAY have the ability to re-verify transactions before including them in a new Tari block.
- MUST have the ability to include the block header hash of the new Tari block in the coinbase section of a newly created Monero block to enable merged mining.
- MUST be able to include the Monero block header hash, Merkle tree branch and hash of the coinbase transaction of the Monero block into the PoW summary field of the new Tari block header.
- MUST have the ability to transmit and distribute PoW tasks for the newly created Monero block, which contains the Tari block information, to connected Mining Workers.
- MUST verify PoW solutions received from Mining Workers and MUST reject and discard invalid solutions or solutions that do not meet the minimum required difficulty.
- MAY keep track of mining share contributions of the connected Mining Workers.
- MUST submit completed Tari blocks to the Tari Base Node.
- MUST submit completed Monero blocks to the Monero Network.
Functionality Required by Tari Mining Worker
The Tari Mining Worker:
- MUST maintain a local or remote connection to a Mining Server.
- MUST have the ability to receive PoW tasks from the connected Mining Server.
- MUST have the ability to perform the latest released version of Monero's PoW algorithm on the received PoW tasks.
- MUST attempt to solve the PoW task at the difficulty specified by the Mining Server.
- MUST submit completed shares to the connected Mining Server.
RFC-0152/EmojiId
Emoji Id specification
Maintainer(s):Cayle Sharrock
Licence
Copyright 2022. The Tari Development Community
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Language
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as shown here.
Disclaimer
This document and its content are intended for information purposes only and may be subject to change or update without notice.
This document may include preliminary concepts that may or may not be in the process of being developed by the Tari community. The release of this document is intended solely for review and discussion by the community regarding the technological merits of the potential system outlined herein.
Goals
This document describes the specification for Emoji Ids. Emoji Ids are encoded node ids used for humans to verify peer node addresses easily and for machines to verify that the address is being used in the correct context.
Related Requests for Comment
None
Description
Tari Communication Nodes are identified on the network via their Node ID; which in turn are derived from the node's public key. Both the node id and public key are simple large integer numbers.
The most common practice for human beings to copy large numbers in cryptocurrency software is scanning a QR code or copying and pasting a value from one application to another. These numbers are typically encoded using hexadecimal or Base58 encoding. The user will then typically scan (parts) of the string by eye to ensure that the value was transferred correctly.
For Tari, we propose encoding values, the node ID in particular and masking the network identifier, for Tari, using emojis. The advantages of this approach are:
- Emoji are more easily identifiable; and, if selected carefully, less prone to identification errors (e.g., mistaking an O for a 0).
- The alphabet can be considerably larger than hexadecimal (16) or Base58 (58), resulting in shorter character sequences in the encoding.
- Should be be able to detect if the address used belongs to the correct network.
The specification
Emoji map
An emoji alphabet of 256 characters is selected. Each emoji is assigned a unique index from 0 to 255 inclusive. The list of selected emojis is:
🦋 | 📟 | 🌈 | 🌊 | 🎯 | 🐋 | 🌙 | 🤔 | 🌕 | ⭐ | 🎋 | 🌰 | 🌴 | 🌵 | 🌲 | 🌸 |
🌹 | 🌻 | 🌽 | 🍀 | 🍁 | 🍄 | 🥑 | 🍆 | 🍇 | 🍈 | 🍉 | 🍊 | 🍋 | 🍌 | 🍍 | 🍎 |
🍐 | 🍑 | 🍒 | 🍓 | 🍔 | 🍕 | 🍗 | 🍚 | 🍞 | 🍟 | 🥝 | 🍣 | 🍦 | 🍩 | 🍪 | 🍫 |
🍬 | 🍭 | 🍯 | 🥐 | 🍳 | 🥄 | 🍵 | 🍶 | 🍷 | 🍸 | 🍾 | 🍺 | 🍼 | 🎀 | 🎁 | 🎂 |
🎃 | 🤖 | 🎈 | 🎉 | 🎒 | 🎓 | 🎠 | 🎡 | 🎢 | 🎣 | 🎤 | 🎥 | 🎧 | 🎨 | 🎩 | 🎪 |
🎬 | 🎭 | 🎮 | 🎰 | 🎱 | 🎲 | 🎳 | 🎵 | 🎷 | 🎸 | 🎹 | 🎺 | 🎻 | 🎼 | 🎽 | 🎾 |
🎿 | 🏀 | 🏁 | 🏆 | 🏈 | ⚽ | 🏠 | 🏥 | 🏦 | 🏭 | 🏰 | 🐀 | 🐉 | 🐊 | 🐌 | 🐍 |
🦁 | 🐐 | 🐑 | 🐔 | 🙈 | 🐗 | 🐘 | 🐙 | 🐚 | 🐛 | 🐜 | 🐝 | 🐞 | 🐢 | 🐣 | 🐨 |
🦀 | 🐪 | 🐬 | 🐭 | 🐮 | 🐯 | 🐰 | 🦆 | 🦂 | 🐴 | 🐵 | 🐶 | 🐷 | 🐸 | 🐺 | 🐻 |
🐼 | 🐽 | 🐾 | 👀 | 👅 | 👑 | 👒 | 🧢 | 💅 | 👕 | 👖 | 👗 | 👘 | 👙 | 💃 | 👛 |
👞 | 👟 | 👠 | 🥊 | 👢 | 👣 | 🤡 | 👻 | 👽 | 👾 | 🤠 | 👃 | 💄 | 💈 | 💉 | 💊 |
💋 | 👂 | 💍 | 💎 | 💐 | 💔 | 🔒 | 🧩 | 💡 | 💣 | 💤 | 💦 | 💨 | 💩 | ➕ | 💯 |
💰 | 💳 | 💵 | 💺 | 💻 | 💼 | 📈 | 📜 | 📌 | 📎 | 📖 | 📿 | 📡 | ⏰ | 📱 | 📷 |
🔋 | 🔌 | 🚰 | 🔑 | 🔔 | 🔥 | 🔦 | 🔧 | 🔨 | 🔩 | 🔪 | 🔫 | 🔬 | 🔭 | 🔮 | 🔱 |
🗽 | 😂 | 😇 | 😈 | 🤑 | 😍 | 😎 | 😱 | 😷 | 🤢 | 👍 | 👶 | 🚀 | 🚁 | 🚂 | 🚚 |
🚑 | 🚒 | 🚓 | 🛵 | 🚗 | 🚜 | 🚢 | 🚦 | 🚧 | 🚨 | 🚪 | 🚫 | 🚲 | 🚽 | 🚿 | 🧲 |
The emoji have been selected such that:
- Similar-looking emoji are excluded from the map. For example, neither 😁 or 😄 should be included. Similarly, the Irish and Côte d'Ivoire flags look very similar, and both should be excluded.
- Modified emoji (skin tones, gender modifiers) are excluded. Only the "base" emoji are considered.
The selection of an alphabet with 256 symbols means there is a direct mapping between bytes and emoji.
Encoding
The emoji ID is calculated from a node public key B
(serialized as 32 bytes) and a network identifier N
(serialized as 8 bits) as follows:
- Use the DammSum algorithm with
k = 8
andm = 32
to compute an 8-bit checksumC
usingB
as input. - Compute the masked checksum
C' = C XOR N
. - Encode
B
into an emoji string using the emoji map. - Encode
C'
into an emoji character using the emoji map. - Concatenate
B
andC'
as the emoji ID.
The result is 33 emoji characters.
Decoding
The node public key is obtained from an emoji ID and a network identifier N
(serialized to 8 bits) as follows:
- Assert that the emoji ID contains exactly 33 valid emoji characters from the emoji alphabet. If not, return an error.
- Decode the emoji ID as an emoji string by mapping each emoji character to a byte value using the emoji map, producing
33 bytes. Let
B
be the first 32 bytes andC'
be the last byte. - Compute the unmasked checksum
C = C' XOR N
. - Use the DammSum validation algorithm on
B
to assert thatC
is the correct checksum. If not, return an error. - Attempt to deserialize
B
as a public key. If this fails, return an error. If it succeeds, return the public key.
Checksum effectiveness
It is important to note that masking the checksum reduces its effectiveness.
Namely, if an emoji ID is presented with a different network identifier, and if there is a transmission error, it is possible for the result to decode in a seemingly valid way with a valid checksum after unmasking.
If both conditions occur randomly, the likelihood of this occurring is n / 256
for n
possible network identifiers.
Since emoji ID will typically be copied digitally and therefore not particularly subject to transmission errors, so it seems unlikely for these conditions to coincide in practice.
Change Log
Date | Change | Author |
---|---|---|
2022-11-10 | Initial stable | SWvHeerden |
2022-11-11 | Algorithm improvements | AaronFeickert |
RFC-0180: Bulletproof range proof rewinding
Bulletproof range proof rewinding
Maintainer(s): Hansie Odendaal
Licence
Copyright 2020 The Tari Development Community
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Language
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as shown here.
Disclaimer
This document and its content are intended for information purposes only and may be subject to change or update without notice.
This document may include preliminary concepts that may or may not be in the process of being developed by the Tari community. The release of this document is intended solely for review and discussion by the community of the technological merits of the potential system outlined herein.
Goals
This Request for Comment (RFC) presents a proposal for Bulletproof range proof rewinding in the Tari blockchain to enable advanced usages like wallet recovery and one‑sided payments.
Related Requests for Comment
Introduction
We use dalek-cryptography/bulletproofs
in the Tari project and have a need to do wallet recovery from seed values and
also to recover the value in the value commitment from the Unspent Transaction Output (UTXO). Pull requests
PR#340 for the dalek-cryptography/bulletproofs
crate
and PR#6 for the zkcrypto/bulletproofs
crate were submitted to add
Bulletproofs rewinding functionality to the Bulletproofs crate as a user option.
The methodology presented here is closely modelled on Grin's solution as discussed here, but using two private keys instead of one.
Rewind Scheme
Bulletproofs per say are not be discussed in this RFC, only how the rewinding scheme works. Readers who require background information on Bulletproofs can read the excellent documentation created by the Dalek team here. Important to note is that Dalek only implemented the aggregated Multiparty Computation Protocol (MCP) for range proofs and that proving a single range proof is handled a special case.
Constructing a rewindable Bulletproof range proof
Our scheme is discussed with reference to the Party and Dealer's algorithm and using notation defined here.
In this scheme three additional parameters are introduced when creating a range proof for a Pedersen commitment (termed value commitment by Dalek because it is a commitment to the value of the token):
- Private rewind key: $ r_{key} $
- Private blinding key: $ b_{key} $
- Twenty three (23) bytes proof message: $ p_{msg} $.
The 23 bytes worth of proof message can be any message a user wants to embed within the proof. Internally the two private keys, in combination with the value commitment, are converted into two rewind nonces and two blinding nonces:
- Rewind nonce 1: $ r_{n1} = \text{H}( \ \text{H}(r_{key} \cdot \widetilde{B}) \ || \ V_{(j)} \ ) $
- Rewind nonce 2: $ r_{n2} = \text{H}( \ \text{H}(b_{key} \cdot \widetilde{B}) \ || \ V_{(j)} \ ) $
- Blinding nonce 1: $ b_{n1} = \text{H}( \ \text{H}(r_{key}) \ || \ V_{(j)} \ ) $
- Blinding nonce 2: $ b_{n2} = \text{H}( \ \text{H}(b_{key}) \ || \ V_{(j)} \ ) $
These four values are seen as nonces due to the fact that each value commitment is unique, whereas the $ r_{key} $ and $ b_{key} $ can be used over and over without leaking any information.
The value $ v_{(j)} $ is an 8 byte word, and $ p_{msg} $ is a 23 byte word. The bytes of these two words can be concatenated to form a 32 byte word and when XORed with $ r_{n2} $ , it can be used to embed the value and proof message. $ r_{n2} $ is modified as follows:
$$ \begin{aligned} r^\backprime_{n2} = r_{n2} \ \mathbin{\oplus} \ (v_{(j)_{\ bytes \ 1..8}} \ || \ p_{msg_{\ bytes \ 9..31}} ) \end{aligned} \tag{1} $$
Consider the start of the protocol where each party $ j $ computes three commitments: to the value $ v_{(j)} $, to the bits of that value $ \mathbf{a}_{L, (j)}, \mathbf{a}_{R, (j)} $, and to the per-bit blinding factors $ \mathbf{s}_{L, (j)}, \mathbf{s}_{R, (j)} $:
$$ \begin{aligned} V_{(j)} &\gets \operatorname{Com}(v_{(j)}, {\widetilde{v}_{(j)}}) && = v_{(j)} \cdot B + {\widetilde{v}_{(j)}} \cdot {\widetilde{B}} \\ A_{(j)} &\gets \operatorname{Com}({\mathbf{a}}_{L, (j)}, {\mathbf{a}}_{R, (j)}) && = {\langle {\mathbf{a}}_{L, (j)}, {\mathbf{G}_{(j)}} \rangle} + {\langle {\mathbf{a}}_{R, (j)}, {\mathbf{H}_{(j)}} \rangle} + {\widetilde{a}_{(j)}} {\widetilde{B}} \\ S_{(j)} &\gets \operatorname{Com}({\mathbf{s}}_{L, (j)}, {\mathbf{s}}_{R, (j)}) && = {\langle {\mathbf{s}}_{L, (j)}, {\mathbf{G}_{(j)}} \rangle} + {\langle {\mathbf{s}}_{R, (j)}, {\mathbf{H}_{(j)}} \rangle} + {\widetilde{s}_{(j)}} {\widetilde{B}} \\ \end{aligned} \tag{2} $$
where $ \widetilde{v}_{(j)}, \widetilde{a}_{(j)}, \widetilde{s}_{(j)} $ are sampled randomly from $ {\mathbb Z_p} $. (Note that $ \widetilde{v}_{(j)} $ is the blinding factor of the value commitment.)
In our scheme:
- blinding factor $ {\widetilde{a}_{(j)}} $ is replaced by $ r_{n1} $
- blinding factor $ {\widetilde{s}_{(j)}} $ is replaced by $ r^\backprime_{n2} $
Consider where the party commits to the terms $ t_{1, (j)}, t_{2, (j)} $:
$$ \begin{aligned} T_{1, (j)} &\gets \operatorname{Com}(t_{1, (j)}, {\tilde{t}_{(j1}}) && = t_{1, (j)} \cdot B + {\tilde{t}_{1, (j)}} \cdot {\widetilde{B}} \\ T_{2, (j)} &\gets \operatorname{Com}(t_{2, (j)}, {\tilde{t}_{2, (j)}}) && = t_{2, (j)} \cdot B + {\tilde{t}_{2, (j)}} \cdot {\widetilde{B}} \end{aligned} \tag{3} $$
where $ \tilde{t}_{1, (j)}, \tilde{t}_{2, (j)} $ are sampled randomly from $ {\mathbb Z_p} $.
In our scheme:
- blinding factor $ \tilde{t}_{1, (j)} $ is replaced by $ b_{n1} $
- blinding factor $ \tilde{t}_{2, (j)} $ is replaced by $ b_{n2} $
The synthetic blinding factors calculation below is key, as it will be used to extract the data when playing the Bulletproof in reverse:
$$ \begin{aligned} {\tilde{t}}_{(j)}(x) &\gets z^{2} {\tilde{v}}_{(j)} + x {\tilde{t}}_{1, (j)} + x^{2} {\tilde{t}}_{2, (j)} \\ \end{aligned} \tag{4} $$
$$ \begin{aligned} \tilde{e}_{(j)} &\gets {\widetilde{a}}_{(j)} + x {\widetilde{s}}_{(j)} \end{aligned} \tag{5} $$
In the end, the complete range proof consists of these elements:
$$ \begin{aligned} \lbrace A, S, T_1, T_2, t(x), {\tilde{t}}(x), \tilde{e}, L_k, R_k, \dots, L_1, R_1, a, b \rbrace \end{aligned} \tag{6} $$
Note: This scheme has been improved in what has been presented in by Grin after being commented on by Dalek, by not using the same rewind nonce for $ {\widetilde{a}_{(j)}} $ and $ {\widetilde{s}_{(j)}} $ nor the same blinding nonce for $ \tilde{t}_{1, (j)} $ and $ \tilde{t}_{2, (j)} $.
Extracting data
Note the presence of $ {\tilde{t}}_{(j)} $ and $ \tilde{e} $ in (6). The Dalek Bulletproofs are constructed using Merlin Transcripts to automate the Fiat-Shamir transform, so that non-interactive protocols can be implemented as if they were interactive. The prover adds each step of the Bulletproof range proof creation to the protocol transcript, so the verifier has to do the same.
The extraction procress starts by adding the values $ A $ and $ S $ are to the protocol transcript to obtain challenge scalars $ z $ and $ x $ from the transcript.
There after, $ {\widetilde{s}_{(j)}} $ is extracted from (5) by replacing $ {\widetilde{a}_{(j)}} $ with $ r_{n1} $ :
$$ \begin{aligned} {\widetilde{s}}_{(j)} = ( \tilde{e}_{(j)} - r_{n1} ) \cdot \frac{1}x \end{aligned} \tag{7} $$
Next, the value and proof message are extracted from $ {\widetilde{s}_{(j)}} $ when XORed with $ r_{n2} $ :
$$ \begin{aligned} v_{(j)} &= ( r_{n2} \ \mathbin{\oplus} \ {\widetilde{s}}_{(j)} ) | _{\ bytes \ 1..8} \\ p_{msg} &= ( r_{n2} \ \mathbin{\oplus} \ {\widetilde{s}}_{(j)} ) | _{\ bytes \ 9..31} \end{aligned} \tag{8} $$
Finally, the blinding factor is extracted from (4):
$$ \begin{aligned} \widetilde{v} = \frac{1}{z^2} \cdot ( {\tilde{t}}(x) - x \cdot \tilde{t}_{1, (j)} - x^2 \cdot \tilde{t}_{2, (j)} ) \end{aligned} \tag{9} $$
Some notes on usage and use cases
Rewinding a Bulletproof can take place according to one or both of these steps:
- Peak value only: Using this step returns the value and proof message only, but returning garbage data if the wrong rewind nonces are provided, or,
- Rewind fully: Using this step returns the value, blinding factor and proof message, returning an error if the wrong rewind and blinding nonces are provided. Note that this step is independent from peaking the value only, thus do not have ot be preceded by it. If many range proofs need to be scanned to uncover those that belong to a particuler wallet, peaking the value only before fully rewinfing it will provide a performance benefit.
The main use case has to do with wallet recovery. A user would normally have a backup of their unique wallet seed words somewhere, but could more easily lose their entire wallet without having made any backups or only having old backups. If a wallet can derive one or more sets of private keys from the seed words and use them in every UTXO construction as proposed, it can enable wallet recovery using Bulletproof rewinding.
A secondary use case would be for trusted 3rd parties to identify spending, by only having access to the public rewind key and the embedded proof message. The public rewind keys can be shared with a 3rd party out of band. The owner and/or delegated 3rd party can then use these keys in conjunction with a specific value commitment to calculate candidate rewind nonces for its proof. The returned proof message from the peak value only rewind step can be used to narrow down the probability that the particular proof belongs to a specific collection. In this mode the owner alone will be able to use both sets of pub-pvt key pairs in conjunction with a specific value commitment to calculate candidate rewind and blinding nonces for its proof. The rewind fully step will reveal the details of the value commitment and proof message if successful.
The use for this protocol, as opposed to simply revealing the original value along with the blinding factor to whoever wants the plain value, is to protect the UTXO. In Mimblewimble, if the value commitment can be opened, it can be spent without the owners knowledge.
The proof message is private or can be shared with a trusted 3rd party in the same way one would share the public rewind keys, but not common public knowledge. It is totally arbitrary, but known data, to enable identifying beyond a doubt if the returned value $ v_{(j)} $ is from a specific collection of value commitments $ V_{(j)} $.
Implementation
Using the Application Programmers Interface (API) it is possible to:
- create a rewindable Zero-knowledge (ZK) proof with up to 23 bytes of additional embedded proof message $ p_{msg} $ ;
- extract the value $ v_{(j)} $ and 23 bytes proof messsage $ p_{msg} $ only;
- extract the value $ v_{(j)} $ , blinding factor $ \widetilde{v} $ and 23 bytes proof messsage $ p_{msg} $ .
Credits
- @jaspervdm for his improved bulletproof rewind scheme, used as precurser.
- @cathieyun for provifing valuable feedback to improve this scheme.
RFC-0300/DAN
Digital Assets Network
Maintainer(s): Cayle Sharrock
Licence
Copyright 2018 The Tari Development Community
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Language
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as shown here.
Disclaimer
This document and its content are intended for information purposes only and may be subject to change or update without notice.
This document may include preliminary concepts that may or may not be in the process of being developed by the Tari community. The release of this document is intended solely for review and discussion by the community of the technological merits of the potential system outlined herein.
Goals
The aim of this Request for Comment (RFC) is to describe the key features of the Tari second layer, also known as the Digital Assets Network (DAN)
Related Requests for Comment
Description
Abstract
Digital Assets (DAs) are managed by committees of special nodes called Validator Nodes (VNs):
- VNs manage digital asset state change and ensure that the rules of the asset contracts are enforced.
- VNs form a peer-to-peer communication network that together defines the Tari DAN.
- VNs register themselves on the base layer and commit collateral to prevent Sybil attacks.
- Scalability is achieved by sacrificing decentralization. Not all VNs manage every asset. Assets are managed by subsets of the DAN, called VN committees. These committees reach consensus on DA state amongst themselves.
- VNs earn fees for their efforts.
- DA contracts are not Turing complete, but are instantiated by Asset Issuers (AIs) using DigitalAssetTemplates that are defined in the DAN protocol code.
Digital Assets
- DA contracts are not Turing complete, but are selected from a set of DigitalAssetTemplates that govern the behaviour of each contract type. For example, there could be a Single-use Token template for simple ticketing systems, a Coupon template for loyalty programmes, and so on.
- The template system is intended to be highly flexible and additional templates can be added to the protocol periodically.
- Asset issuers can link a Registered Asset Issuer Domain (RAID) ID in an OpenAlias TXT public Domain Name System (DNS) record to a Fully Qualified Domain Name (FQDN) that they own. This is to help disambiguate similar contracts and improve the signal-to-noise ratio from scam or copycat contracts.
An AI will issue a DA by constructing a contract from one of the supported set of DigitalAssetTemplates. The AI will choose how large the committee of VNs will be for this DA, and have the option to nominate Trusted Nodes to be part of the VN committee for the DA. Any remaining spots on the committee will be filled by permissionless VNs that are selected according to a CommitteeSelectionStrategy. This is a strategy that an AI will use to select from the set of potential candidate VNs that nominated themselves for a position on the committee when the AI broadcast a public call for VNs during the asset creation process. For the VNs to accept the appointment to the committee, they will need to put up the specified collateral.
RFC-0301/NamespaceRegistration
Namespace Registration on Base Layer
Maintainer(s): Hansie Odendaal
Licence
Copyright 2019 The Tari Development Community
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Language
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as shown here.
Disclaimer
This document and its content are intended for information purposes only and may be subject to change or update without notice.
This document may include preliminary concepts that may or may not be in the process of being developed by the Tari community. The release of this document is intended solely for review and discussion by the community of the technological merits of the potential system outlined herein.
Goals
The aim of this Request for Comment (RFC) is to describe and specify the process for creating and linking an asset issuer specified domain name with a digital asset on the Digital Assets Network (DAN).
Related Requests for Comment
Description
Background
Alternative Approaches
In order to easily differentiate digital assets in the DAN, apart from some unique unpronounceable character string, a human-readable identifier (domain name) is required. It is perceived that shorter names will have higher value due to branding and marketability. The question is, how this can be managed elegantly? It is also undesirable if, for example, the real Disney is forced to use the long versioned "disney.com-goofy-is-my-asset-yes" because some fake Disneys claimed "goofy" and "disney.com-goofy" and everything in-between.
One method to curb name space squatting is to register names on the base layer layer with a domain name registration transaction. Let us call such a name a Registered Asset Issuer Name (RAIN). To make registering RAINs difficult enough to prevent spamming the network, a certain amount of Tari coins must be committed in a burn (permanently destroy) or a time-locked pay-to-self type transaction. Lots of management overhead will be associated with such a scheme, even if domainless assets are allowed. However, it would be impossible to stop someone from registering, say, a "disney.com" RAIN if they did not own the real "disney.com" Fully Qualified Domain Name (FQDN).
Another approach would be to make use of the public Domain Name System (DNS) and to link the FQDNs that are already registered, to the digital assets in the DAN, making use of OpenAlias text (TXT) DNS records on an FQDN. Let us call this a Registered Asset Issuer Domain (RAID) TXT record. If we hash a public key and FQDN pair, it will provide us with a unique RAID_ID (RAID Identification). The RAID_ID will serve a similar purpose in the DAN as a Top Level Domain (TLD) in a DNS, as all digital assets belonging to a specific asset issuer could then be grouped under the FQDN by inference. To make this scheme more elaborate, but potentially unnecessary, all such RAID_IDs could also be registered on the base layer, similar to the RAIN scheme.
If the standard Mimblewimble protocol is followed, a new output feature can be defined to cater for a RAID tuple
(RAID_ID, PubKey)
that is linked to a specific Unspent Transaction Output (UTXO). The RAID_ID
could be based on
a Base58Check variant applied to Hash256(PubKey || FQDN)
. If the
amount of Tari coins associated with the RAID tuple transaction is burned, (RAID_ID, PubKey)
will forever be present
on the blockchain and can increase blockchain bloat. On the other hand, if those Tari coins are spent back to their
owner with a specific time lock, it will be possible to spend and prune that UTXO later on. While that UTXO remains
unspent, the RAID tuple will be valid, but when all or part of it is spent, the RAID tuple will disappear from the
blockchain. Such a UTXO will thus be "coloured" while unspent, as it will have different properties to a normal UTXO. It
will also be possible to recreate the original RAID tuple by registering it using the original Hash256(PubKey || FQDN)
.
Thinking about the make-up of the RAID_ID
, it is evident that it can easily be calculated on the fly using the public
key and FQDN, the values of which will always be known. The biggest advantage of having the RAID tuple on the base
layer is that of embedded consensus, where it will be validated (as no duplicates can be allowed) and mined before it
can be used. However, this comes at the cost of more complex code, a more elaborate asset registration process and
higher asset registration fees.
This Request for Comment
This RFC explores the creation and use of RAID TXT records to link asset issuer-specified domain names with digital assets on the DAN, without RAID_IDs being registered on the base layer.
Requirements
OpenAlias TXT DNS Records
An OpenAlias TXT DNS record [1] on an FQDN is a single string and starts with "oa1:<name>" field followed by a number of key-value pairs. Standard (optional) key values are: "recipient_address"; "recipient_name"; "tx_description"; "tx_amount"; "tx_payment_id"; "address_signature"; and "checksum". Additional key values may also be defined. Only entities with write access to a specific DNS record will be able to create the required TXT DNS record entries.
TXT DNS records are limited to multiple strings of size 255, and as the User Datagram Protocol (UDP) size is 512 bytes, a TXT DNS record that exceeds that limit is less optimal [2, Sections 3.3 and 3.4]. Some hosting sites also place limitations on TXT DNS record string lengths and concatenation of multiple strings as per [2]. The basic idea of this specification is to make the implementation robust and flexible at the same time.
Req - Integration with public DNS records MUST be used to ensure valid ownership of an FQDN that needs to be linked to a digital asset on the DAN.
Req - The total size of the OpenAlias TXT DNS record SHOULD NOT exceed 255 characters.
Req - The total size of the OpenAlias TXT DNS record MUST NOT exceed 512 characters.
Req - The OpenAlias TXT DNS record implementation MUST make provision to interpret entries that are made up of more than one string as defined in [2].
Req - The OpenAlias TXT DNS record SHOULD adhere to the formatting requirements as specified in [1].
Req - The OpenAlias TXT DNS record MUST be constructed as follows:
OpenAlias TXT DNS Record Field | OpenAlias TXT DNS Record Data |
---|---|
oa1:<name> | "oa1:tari" |
pk | <256 bit public key, in hexadecimal format (64 characters), that is converted into a Base58 encoded string (44 characters)> |
raid_id | <RAID_ID (refer to RAID_ID) (15 characters)> |
nonce | <256 bit public nonce, in hexadecimal format (64 characters), that is converted into a Base58 encoded string (44 characters)> |
sig | <Asset issuer's 256 bit Schnorr signature for the RAID_ID (refer to RAID_ID), in hexadecimal format (64 characters), that is converted into a Base58 encoded string (44 characters)> |
desc | <Optional RAID description>; ASCII String; Up to 48 characters for the condensed version (using only one string) and up to 235 characters (when spanning two strings). |
crc | <CRC-32 checksum of the entire record, up to but excluding the checksum key-value pair (starting at "oa1:tari" and ending at the last ";" before the checksum key-value pair) in hexadecimal format (eight characters)> |
Examples: Two example OpenAlias TXT DNS records are shown; the first is a condensed version and the second spans two strings:
RAID_ID:
public key = ca469346d7643336c19155fdf5c6500a5232525ce4eba7e4db757639159e9861
FQDN = disney.com
-> id = RYqMMuSmBZFQkgp
base58 encodings:
public key = ca469346d7643336c19155fdf5c6500a5232525ce4eba7e4db757639159e9861
-> base58 = EcbmnM6PLosBzpyCcBz1TikpNXRKcucpm73ez6xYfLtg
public nonce = fc2c5fce596338f43f70dc0ce14659fdfea1ba3e588a7c6fa79957fc70aa1b4b
-> base58 = 5ctFNnCfBrP99rT1AFmj1WPyMD8uAdNUTESHhLoV3KBZ
signature = 7dc54ec98da2350b0c8ed0561537517ac6f93a37f08a34482824e7df3514ce0d
-> base58 = 9TxTorviyTJAaVJ4eY4AQPixwLb6SDL4dieHff6MFUha
OpenAlias TXT DNS record (condensed: 212 characters):
IN TXT = "oa1:tari pk=EcbmnM6PLosBzpyCcBz1TikpNXRKcucpm73ez6xYfLtg;id=RYqMMuSmBZFQkgp;
nonce=5ctFNnCfBrP99rT1AFmj1WPyMD8uAdNUTESHhLoV3KBZ;sig=9TxTorviyTJAaVJ4eY4AQPixwLb6SDL4dieHff6MFUha;
desc=Cartoon characters;crc=176BE80C"
OpenAlias TXT DNS record (spanning two strings: string 1 = 179 characters and string 2 = 250 characters):
IN TXT = "oa1:tari pk=EcbmnM6PLosBzpyCcBz1TikpNXRKcucpm73ez6xYfLtg; id=RYqMMuSmBZFQkgp;
nonce=5ctFNnCfBrP99rT1AFmj1WPyMD8uAdNUTESHhLoV3KBZ; sig=9TxTorviyTJAaVJ4eY4AQPixwLb6SDL4dieHff6MFUha; "
"desc=Cartoon characters: Mickey Mouse\; Minnie Mouse\; Goofy\; Donald Duck\; Pluto\; Daisy Duck\;
Scrooge McDuck\; Launchpad McQuack\; Huey, Dewey and Louie\; Bambi\; Thumper\; Flower\; Faline\;
Tinker Bell\; Peter Pan and Captain Hook.; crc=54465902"
RAID_ID
Because the RAID_ID
does not exist as an entity on the base layer or in the DAN, it cannot be owned or
transferred, but can only be verified as part of the OpenAlias TXT DNS record [1] verification. If an asset creator
chooses not to link a RAID_ID
and FQDN, a default network-assigned RAID_ID
will be used in the digital asset
registration process.
Req - A default RAID_ID
MUST be used where it will not be linked to an FQDN, e.g. it MAY be derived
from a default input string "No FQDN"
.
Req - An FQDN-linked (non-default) RAID_ID
MUST be derived from the concatenation PubKey || <FQDN>
.
Req - All concatenations of inputs for any hash algorithm MUST be done without adding any spaces.
Req - The hash algorithm MUST be Blake2b
using a 32 byte digest size, unless otherwise specified.
Req - Deriving a RAID_ID
MUST be calculated as follows:
- Inputs for all hashing algorithms used to calculate the
RAID_ID
MUST be lower-case characters. - Stage 1 - MUST select the input string to use (either
"No FQDN"
orPubKey || <FQDN>
).- Example: Mimblewimble public key
ca469346d7643336c19155fdf5c6500a5232525ce4eba7e4db757639159e9861
and FQDNdisney.com
are used here, resulting inca469346d7643336c19155fdf5c6500a5232525ce4eba7e4db757639159e9861disney.com
.
- Example: Mimblewimble public key
- Stage 2 - MUST perform
Blake2b
hashing on the result of stage 1 using a 10 byte digest size.- Example: In hexadecimal representation
ff517a1387153cc38009
or binary representation\xffQz\x13\x87\x15<\xc3\x80\t
.
- Example: In hexadecimal representation
- Stage 3 - MUST concatenate the
RAID_ID
identifier byte,0x62
, with the result of stage 2.- Example:
62ff517a1387153cc38009
`.
- Example:
- Stage 4 - MUST convert the result of stage 3 from a byte string into a
Base58
encoded string. This will result in a 15 character string starting withR
.- Example: The resulting
RAID_ID
will beRYqMMuSmBZFQkgp
.
- Example: The resulting
Req - A valid RAID_ID
signature MUST be a 256 bit Schnorr signature defined as s = PvtNonce + e·PvtKey
with
the challenge e
being e = Blake2b(PubNonce || PubKey || RAID_ID)
.
Sequence of Events
The sequence of events leading up to digital asset registration is perceived as follows:
-
The asset issuer will decide if the default
RAID_ID
or aRAID_ID
that is linked to an FQDN must be used for asset registration. (Note: A single linked (RAID_ID
, FQDN) tuple may be associated with multiple digital assets from the same asset issuer.) -
Req - If a default
RAID_ID
is required:- The asset issuer MUST use the default
RAID_ID
(refer to RAID_ID). - The asset issuer MUST NOT sign the
RAID_ID
.
- The asset issuer MUST use the default
-
Req - If a linked (
RAID_ID
, FQDN) tuple is required:- The asset issuer MUST create a
RAID_ID
. - The asset issuer MUST sign the
RAID_ID
as specified (refer to RAID_ID). - The asset issuer MUST create a valid TXT DNS record (refer to OpenAlias TXT DNS Records).
- The asset issuer MUST create a
-
Req - Validator Nodes (VNs) MUST only allow a valid
RAID_ID
to be used in the digital asset registration process. -
Req - VNs MUST verify the OpenAlias TXT DNS record if a linked (
RAID_ID
, FQDN) tuple is used:- Verify that all fields have been completed as per the specification (refer to OpenAlias TXT DNS Records).
- Verify that the
RAID_ID
can be calculated from information provided in the TXT DNS record and the FQDN of the public DNS record it is in. - Verify that the asset issuer's
RAID_ID
signature is valid. - Verify the checksum.
Confidentiality and Security
To prevent client lookups from leaking, OpenAlias recommends making use of DNSCrypt, resolution via DNSCrypt-compatible resolvers that support Domain Name System Security Extensions (DNSSEC) and without DNS requests being logged.
Req - Token Wallets (TWs) and VNs SHOULD implement the following confidentiality and security measures when dealing with OpenAlias TXT DNS records:
- All queries SHOULD make use of the DNSCrypt protocol.
- Resolution SHOULD be forced via DNSCrypt-compatible resolvers that:
- support DNSSEC;
- do not log DNS requests.
- The DNSSEC trust chain validity:
- SHOULD be verified;
- SHOULD be reported back to the asset issuer.
References
[1] Crate Openalias [online]. Available: https://docs.rs/openalias/0.2.0/openalias/index.html. Date accessed: 2019-03-05.
[2] RFC 7208: Sender Policy Framework (SPF) for Authorizing Use of Domains in Email, Version 1 [online]. Available: https://tools.ietf.org/html/rfc7208. Date accessed: 2019-03-06.
RFC-0302/ValidatorNode
Validator Nodes
Maintainer(s): Cayle Sharrock
Licence
Copyright 2018 The Tari Development Community
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Language
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as shown here.
Disclaimer
This document and its content are intended for information purposes only and may be subject to change or update without notice.
This document may include preliminary concepts that may or may not be in the process of being developed by the Tari community. The release of this document is intended solely for review and discussion by the community of the technological merits of the potential system outlined herein.
Goals
The aim of this Request for Comment (RFC) is to describe the responsibilities of Validator Nodes (VNs) on the Digital Asset Network (DAN).
Related Requests for Comment
- RFCD-0322: Validator Node Registration
- RFCD-0304: Validator Node Committee Selection
- RFCD-0340: VN Consensus Overview
Description
Abstract
Validator Nodes form the basis of the second-layer DAN. All actions on this network take place by interacting with VNs. Some examples of actions that VNs will facilitate are:
- issuing a Digital Asset (DA);
- querying the state of a DA and its constituent tokens; and
- issuing an instruction to change the state of a DA or tokens.
VNs will also perform archival functions for the assets they manage. The lifetime of these archives and the fee structure for this function are still being discussed.
Registration
VNs register themselves on the Base Layer using a special transaction type.
Validator node registration is described in RFC-0322.
Execution of Instructions
VNs are expected to manage the state of DAs on behalf of DA issuers. They receive fees as reward for doing this.
- DAs consist of an initial state plus a set of state transition rules. These rules are set by the Tari protocol, but will usually provide parameters that must be specified by the Asset Issuer.
- The set of VNs that participate in managing state of a specific DA is called a Committee. A committee is selected during the asset issuance process and membership of the committee can be updated at Checkpoints.
- The VN is responsible for ensuring that every state change in a DA conforms to the contract's rules.
- VNs accept DA Instructions from clients and peers. Instructions allow for creating, updating, expiring and archiving DAs on the DAN.
- VNs provide additional collateral, called AssetCollateral, when accepting an offer to manage an asset, which is stored in a multi-signature (multi-sig) Unspent Transaction Output (UTXO) on the base layer. This collateral can be taken from the VN if it is proven that the VN engaged in malicious behaviour.
- VNs participate in fraud-proof validations in the event of consensus disputes (which could result in the malicious VN's collateral being slashed).
- DA metadata (e.g. large images) is managed by VNs. The large data itself will not be stored on the VNs, but in an external location, and a hash of the data can be stored. Whether the data is considered part of the state (and thus checkpointed) or out of state depends on the type of DA contract employed.
Fees
Fees will be paid to VNs based on the amount of work they did during a checkpoint period. The fees will be paid from a fee pool, which will be collected and reside in a UTXO that is accessible by the committee. The exact mechanism for the payment of the fees by the committee and who pays the various types of fees is still under discussion.
Checkpoints
VNs periodically post checkpoint summaries to the base layer for each asset that they are managing. The checkpoints will form an immutable record of the DA state on the base layer. There will be two types of checkpoints:
-
An Opening Checkpoint (OC) will:
- specify the members of the VN committee;
- lock up the collateral for the committee members for this checkpoint period; and
- collect the fee pool for this checkpoint period from the Asset Issuer into a multi-sig UTXO under the control of the committee. This can be a top-up of the fees or a whole new fee pool.
-
A Closing Checkpoint (CC) will:
- summarize the DA state on the base layer;
- release the fee payouts;
- release the collateral for the committee members for this checkpoint period; and
- allow for committee members to resign from the committee.
After a DA is issued, there will immediately be an OC. After a checkpoint period there will then be a CC, followed immediately by an OC for the next period. We will call this set of checkpoints an Intermediate checkpoint, which could be a compressed combination of an OC and CC. This will continue until the end of the asset's lifetime, when there will be a final CC that will be followed by the retirement of the asset.
Consensus
Committees of VNs will use a ConsensusStrategy to manage the process of:
- propagating signed instructions between members of the committee; and
- determining when the threshold has been reached for an instruction to be considered valid.
Part of the Consensus Strategy will be mechanisms for detecting actions by Bad Actors. The nature of the enforcement actions that can be taken against bad actors is still to be decided.
Network Communication
The VNs will communicate using a Peer-to-Peer (P2P) network. To facilitate this, the VNs must perform the following functions:
- VNs MUST maintain a list of peers, including which assets each peer is managing.
- VNs MUST relay instructions to members of the committee that are managing the relevant asset.
- VNs MUST respond to requests for information about DAs that they manage on the DAN.
- VNs and clients can advertise public keys to facilitate P2P communication encryption.
RFC-0304/VNCommittees
Validator Node Committee Selection
Maintainer(s): Stringhandler
Licence
Copyright 2019 The Tari Development Community
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Language
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as shown here.
Disclaimer
This document and its content are intended for information purposes only and may be subject to change or update without notice.
This document may include preliminary concepts that may or may not be in the process of being developed by the Tari community. The release of this document is intended solely for review and discussion by the community of the technological merits of the potential system outlined herein.
Goals
The aim of this Request for Comment (RFC) is to describe the process an Asset Issuer (AI) will go through in order to select the committee of Validator Nodes (VNs) that will serve a given Digital Asset (DA).
Related Requests for Comment
Description
Abstract
Digital Assets (DAs) are managed by committees of nodes called Validator Nodes (VNs), as described in RFC-0300 and RFC-0302. During the asset creation process, described in RFC-0341, the Asset Issuer (AI) MUST select a committee of VNs to manage their asset. This process consists of the following steps:
- Candidate VNs MUST be nominated to be considered for selection by the AI.
- The AI MUST employ a CommitteeSelectionStrategy to select VNs from the set of nominated candidates.
- The AI MUST make an offer of committee membership to the selected VNs.
- Selected VNs MAY accept the offer to become part of the committee by posting the required AssetCollateral.
Nomination
The first step in assembling a committee is to nominate candidate VNs. As described in
RFC-0311, an asset can be created with two possible committee_modes
- CREATOR_NOMINATION
or PUBLIC_NOMINATION
:
- In
CREATOR_NOMINATION
mode, the AI nominates candidate committee members directly. The AI will have a list of permissioned Trusted Nodes that they want to act as the committee. The AI will contact the candidate VNs directly to inform them of their nomination. - In
PUBLIC_NOMINATION
mode, the AI does not have a list of Trusted Nodes and wants to source unknown VNs from the network. In this case, the AI broadcasts a public call for nomination to the Tari network using the peer-to-peer messaging protocol described in RFC-0172. This call for nomination contains all the details of the asset. VNs that want to participate will then nominate themselves by contacting the AI.
Selection
Once the AI has received a list of nominated VNs, it must make a selection, assuming enough VNs were nominated to populate the committee. The AI will employ some CommitteeSelectionStrategy in order to select the committee from the candidate VNs that have been nominated. This strategy might aim for a perfectly random selection, or perhaps it will consider some metrics about the candidate VNs, such as the length of their VN registrations. These metrics might indicate that they are reliable and have not been denylisted for poor or malicious performance.
A consideration when selecting a committee in PUBLIC_NOMINATION
mode will be the size of the pool of nominated VNs.
The size of this pool relative to the size of the committee to be selected will be linked to a risk profile. If the pool
contains very few candidates, then it will be much easier for an attacker to have nominated their own nodes in order to
obtain a majority membership of the committee. For example, if the AI is selecting a committee of 10 members using a uniformly
random selection strategy and only 12 public nominations are received, an attacker only requires control of six VNs to
achieve a majority position in the committee. In contrast, if 100 nominations are received and the AI performs a
uniformly random selection, an attacker would need to control more than 50 of the nominated nodes in order to achieve a
majority position in the committee.
Offer Acceptance
Once the selection has been made by the AI, the selected VNs will be informed and they will be made an offer of membership. If the VNs are still inclined to join the committee, they will accept the offer by posting the AssetCollateral required by the asset to the base layer during the initial Checkpoint transaction built to commence the operation of the asset.
RFC-0306/DANTemplateRegistration
Digital Asset Network (DAN) Template Registration
Maintainer(s): Stanley Bondi
Licence
Copyright 2019 The Tari Development Community
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Language
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as shown here.
Disclaimer
This document and its content are intended for information purposes only and may be subject to change or update without notice.
This document may include preliminary concepts that may or may not be in the process of being developed by the Tari community. The release of this document is intended solely for review and discussion by the community of the technological merits of the potential system outlined herein.
Goals
The aim of this Request for Comment (RFC) is to describe the layer 2 code template registration procedure. Code templates are composable and reusable building blocks that define state and operations on any given side-chain contract.
Registering these on the base layer accomplishes these goals:
- Provides a public register of code templates with an immutable notary (the Tari base layer).
- Provides a source of truth that Validator Nodes and potentially others can use to ensure the correct code templates are being run.
- Provides reusable building-blocks available to anyone wishing to build side-chain contracts.
- Provides other metadata, such as versioning and authorship.
This registration scheme seeks to enable template indexing services and possibly marketplaces for template authors.
Related Requests for Comment
Description
A template author registers a template on the Base layer creating a special UTXO on the base layer. The registration transaction requires the spending of a certain minimum deposit amount of Tari coin, in addition to weighted UTXO fees, to discourage spam.
It is helpful to describe what a template is and how it relates to a running side-chain contract. Rather than being a fully-fledged smart-contract, templates define single-concern state and state-transition functions that can be composed with other templates and run, by a set of layer 2 validator nodes.
This RFC is primarily concerned with the mechanism for making these templates available to other parties wishing to build contracts and run them. The most important aspect of this is to allow any validator node to verify that they are running the same code as other members of the same committee.
Template Registration UTXO fields
A base-layer template registration UTXO MUST have the TEMPLATE_REGISTRATION
output flag set and contain the following data:
Name | Type | Description |
---|---|---|
author_public_key | ECC public key | Public key of the author |
author_signature | Schnorr signature | Signature that signs remaining fields |
template_id | 256-bit hash | Hash of TEMPLATE_REGISTRATION fields (see below) |
template_name | String (255) | A descriptive name for the template |
template_version | Varint | Code version as a single number |
build_info | BuildInfo struct | Information on the binary build |
binary_checksum | SHA2 checksum | A SHA2 checksum of the WASM binary. |
binary_address | Multiaddr | A multiaddr pointing to the WASM module binary download. This may be an HTTP, ONION, p2p, or IPFS address. Maximum byte length: 255 |
We define the template_id
as a hash of author_public_key
, template_name
, template_version
, build_info
, binary_checksum
, and binary_address
.
The author_signature
is a Schnorr proof that commits to the template_id
.
The base node acts as a notary for this data, it is not responsible for the validity of the template fields. However, it must not allow malformed/invalid data to be committed to the blockchain.
Therefore, some additional base-layer consensus rules are required for a TEMPLATE_REGISTRATION
UTXO:
- A base node MUST validate the
author_signature
; and - the
template_id
MUST be unique within the unspent set.
To reference a template within a contract:
- the contract includes a reference to the specific template registration
template_id
. - Alternative: the
TEMPLATE_REGISTRATION
information is copied into the contract definition UTXO.
A base node SHOULD NOT check that the binary_address
points to a valid template binary.
** BuildInfo struct **
Name | Type | Description |
---|---|---|
build_image | Url | A docker build environment used to build the binary |
repo_link | Url | A public link to the source code repository |
commit_hash | SHA2 hash | The commit hash of the code used to build the binary |
The build_image
field SHOULD contain a link to a publicly-accessible docker image that contains the exact build
environment used to build the binary. The build environment refers to the specific compiler and OS used. Typically,
this will be a docker image with a specific version of the rust compiler, LLVM and the wasm-unknown-unknown
target
provided by the Tari community.
Anyone wishing to execute the template MAY build the binary themselves and compare checksums. It's worth noting that identical build environments do not guarantee deterministic builds. If you're curious about the kinds of issues encountered with deterministic builds using the rust language, read this post.
Obtaining the Template Binary
The contract definition specifies the [binary_checksum] for each template. Once the validator node has been assigned a contract via the contract definition, the validator node performs the following actions to obtain a template:
- It scans the blockchain for a template matching the
binary_checksum
; - it downloads the WASM binary and verifies the checksum and stores the binary and associated registration UTXO metadata;
- a validator node operator may choose to build the binary from source as per
BuildInfo
and use the resulting binary;
In the event that a new validator is added to [contract constitution], but the original template registration is unavailable, the validator node SHOULD make the template available to new committee members. New committee members SHOULD confirm the checksum with a 2/3 majority of the validator committee to ensure that the correct copy is received.
Spending the Template Registration UTXO
These cases apply to spending the template registration UTXO:
- The author MAY spend into another template registration UTXO
- This effectively withdraws (yanks) the previous version of the template.
- The template name and author SHOULD be identical. This MAY be enforced by covenant.
- The template version number MUST be incremented by one from the version number in the spent input.
- It is RECOMMENDED that live contracts upgrade their definitions to run the new template.
- The author MAY spend to a "vanilla" UTXO to reclaim their deposit.
- This effectively withdraws (yanks) the template.
- The template MAY remain in use on existing contracts. In fact, anyone may now re-register the template.
Alternatives:
- Consensus rule that prevents spending of the template while used by other contracts
- The contract definition may have copy the template binary_checksum etc. Validator nodes may mirror the code for new VNs
Upgrading a Template
If a validator node detects an update to the contract definition that includes a template update, the validator node
- MUST fetch the new template(s) as per the previous procedure.
RFC-0311/AssetTemplates
Digital Asset Templates
Maintainer(s): Cayle Sharrock
Licence
Copyright 2019 The Tari Development Community
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Language
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as shown here.
Disclaimer
This document and its content are intended for information purposes only and may be subject to change or update without notice.
This document may include preliminary concepts that may or may not be in the process of being developed by the Tari community. The release of this document is intended solely for review and discussion by the community of the technological merits of the potential system outlined herein.
Goals
The aim of this Request for Comment (RFC) is to describe the Tari Digital Asset templating system for smart contract definition.
The term “smart contracts” in this document is used to refer to a set of rules enforced by computers. These smart contracts are not Turing complete, such as those executed by the Ethereum Virtual Machine (VM).
Related Requests for Comment
- RFCD-0300: The Digital Assets Network
- RFCD-0301: Namespace Registration
- RFCD-0340: Validator Node Consensus
- RFCD-0345: Asset Life cycle
Description
Motivation
The reasons for issuing assets on Tari under a templating system, rather than a scripting language (whether Turing complete or not), are manifold:
- A scripting language, irrespective of how simple it is, limits the target market for asset issuers to developers, or people who pay developers.
- The market doesn’t want general smart contracts. This is evidenced by the fact that the vast majority of Ethereum transactions go through ERC-20 or ERC-721 contracts, which are literally contract templates.
- The attack surface for smart contracts is reduced considerably, to the node software itself.
- Bugs can be fixed for all contracts simultaneously by using a template versioning system. Existing assets can opt in to fixes by migrating assets to a new version of the contract.
- Contracts will have better Quality Assurance (QA), since more eyes are looking at fewer contract code sets.
- Transmission, storage and processing of contracts will be more efficient, as one only has to deal with the parameters, and not the logic of the contract. Furthermore, the cost for users is usually lower, since there's no need to add friction or extra costs to contract execution (e.g. Ethereum gas) to work around the halting problem.
Implementation
Assets are created on the Tari network by issuing a create_asset
instruction from a wallet or client, and broadcasting
it to the Tari Digital Assets Network (DAN).
The instruction is in JSON format and MUST contain the following fields:
Name | Type | Description |
---|---|---|
Asset Description | ||
issuer | PubKey | The public key of the creator of the asset. Refer to issuer. |
name | string[64] | The name or identifier for the asset. Refer to Name and Description. |
description | string[216] | A short description of the asset - with name, fits in a tweet. Refer to Name and Description. |
raid_id | string[15] | The Registered Asset Issuer Domain (RAID_ID) for the asset. |
fqdn | string[*] | The Fully Qualified Domain Name (FQDN) corresponding to the raid_id . Up to 255 characters in length; or "No_FQDN" to use the default. |
public_nonce | PubKey | Public nonce part of the creator signature. |
template_id | u64 | The template descriptor. Refer to Template ID. |
asset_expiry | u64 | A timestamp or block height after which the asset will automatically expire. Zero for arbitrarily long-lived assets. |
Validation Committee Selection | ||
committee_mode | u8 | The validation committee nomination mode, either CREATOR_NOMINATION (0) or PUBLIC_NOMINATION (1). |
committee_parameters | Object | Refer to Committee Parameters. |
asset_creation_fee | u64 | The fee the issuer is paying, in microTari, for the asset creation process. |
commitment | u256 | A time-locked commitment for the asset creation fee. |
initial_state_hash | u256 | The hash of the canonical serialization of the initial template state (of the template-specific data). |
initial_state_length | u64 | Size in bytes of initial state. |
Template-specific Data | Object | Template-specific metadata can be defined in this section. |
Signatures | ||
metadata_hash | u256 | A hash of the previous three sections' data, in canonical format (m ). |
creator_sig | u256 | A digital signature of the message H(R ‖ P ‖ RAID_ID ‖ m) , using the asset creator’s private key corresponding to the issuer Public Key Hash (PKH). |
commitment_sig | u256 | A signature proving the issuer is able to spend the commitment to the asset fee. |
Committee Parameters
If committee_mode
is CREATOR_NOMINATION
, the committee_parameters
object is:
Name | Type | Description |
---|---|---|
trusted_node_set | Array of PKH | See below. |
Only the nodes in the trusted node set will be allowed to execute instructions for this asset.
If committee_mode
is PUBLIC_NOMINATION
, the committee_parameters
object is:
Name | Type | Description |
---|---|---|
node_threshold | u32 | The required number of Validator Nodes (VNs) that must register to execute instructions for this asset. |
minimum_collateral | u64 | The minimum amount of Tari a VN must put up in collateral in order to execute instructions for this asset. |
node_selection_strategy | u32 | The selection strategy to employ allowing nodes to register to manage this asset. |
Issuer
Anyone can create new assets on the Tari network from their Tari Collections client. The client will provide the Public Key Hash (PKH) and sign the instruction. The client needn’t use the same private key each time.
Name and Description
These fields are purely for information purposes. They do not need to be unique and do not act as an asset ID.
RAID ID
The RAID_ID is a 15-character string that associates the asset issuer with a registered Internet domain name on the Domain Name System (DNS).
If it is likely that a digital asset issuer will be issuing many assets on the Tari Network (hundreds or thousands),
the issuer should strongly consider using a registered domain (e.g. acme.com
). This is
done via OpenAlias on the domain owner's DNS record, as described in RFC-0301. A RAID prevents spoofing of assets from
copycats or other malicious actors. It also simplifies asset discovery.
Assets from issuers that do not have a RAID are all grouped under the default RAID.
RAID owners must provide a valid signature proving that they own the given domain when creating assets.
Fully Qualified Domain Name
The Fully Qualified Domain Name (FQDN) that corresponds to the raid_id
or the string "NO FQDN"
to use the default RAID ID.
Validator Nodes (VNs) will calculate and check that the RAID ID is valid when
validating the instruction signature.
Public Nonce
A single-use public nonce to be used in the asset signature.
Asset Identification
Assets are identified by a 64-character string that uniquely identifies an asset on the network:
Bytes | Description |
---|---|
8 | Template type (hex) |
4 | Template version (hex) |
4 | Feature flags (hex) |
15 | RAID identifier (Base58) |
1 | A period character, . |
32 | Hex representation of the metadata_hash field |
This allows assets to be deterministically identified from their initial state. Two different creation instructions
leading to the same hash refer to the same single asset, by definition. VNs maintain an index of assets and
their committees, and so can determine whether a given asset already exists; and MUST reject any create_asset
instruction for an existing asset.
Template ID
Tari uses templates to define the behaviour for its smart contracts. The template ID refers to the type of digital asset being created.
Note: Integer values are given in little-endian format, i.e. the least significant bit is first.
The template number is a 64-bit unsigned integer and has the following format, with 0 representing the least significant bit:
Bit Range | Description |
---|---|
0 - 31 | Template type (0 - 4,294,967,295) |
32 - 47 | Template version (0 - 65,535) |
48 | Beta Mode flag |
49 | Confidentiality flag |
50 - 63 | Reserved (must be 0) |
The lowest 32 bits refer to the canonical smart contract type, i.e. the qualitative types of contracts the network supports. Many assets can be issued from a single template.
Template types below 65,536 (216) are public, community-developed templates. All VNs MUST implement and be able to interpret instructions related to these templates.
Template types 65,536 and above are opt-in or proprietary templates. There is no guarantee that any given VN will be able to manage assets on these templates. Part of the committee selection and confirmation process for new assets will be an attestation by VNs that they are willing and able to manage the asset under the designated template rules.
A global registry of opt-in template types will be necessary to prevent collisions (public templates existence will be evident from the Validator Node source code), possibly implemented as a special transaction type on the base layer, which is perfectly suited for hosting such a registry. The details of this will be covered in a separate proposal.
Examples of template types may be:
Template Type | Asset |
---|---|
1 | Simple single-use tokens |
2 | Simple coupons |
20 | ERC-20-compatible |
... | ... |
120 | Collectible cards |
144 | In-game items |
721 | ERC-721-compatible |
... | ... |
65,537 | Acme In game items |
723,342 | CryptoKitties v8 |
The template ID may also set one or more feature flags to indicate that the contract is:
- Experimental, or in testing phase (bit 48).
- Confidential. The definition of confidential assets and their implementation had not been finalized at the time of writing.
Wallets/client apps SHOULD have settings to allow, or otherwise completely ignore, asset types on the network that have certain feature flags enabled. For instance, most consumer wallets should never interact with templates that have the “Beta mode” bit set. Only developers' wallets should ever even see that such assets exist.
Asset Expiry
Asset issuers can set a future expiry date or block height, after which the asset will expire and nodes will be free to expunge any/all state relating to the asset from memory after a fixed grace period. The grace period is to allow interested parties (e.g. the issuer) to take a snapshot of the final state of the contract if they wish (e.g. proving that you had a ticket for that epic World Cup final game, even after the asset no longer exists on the DAN).
Nodes will publish a final checkpoint on the base layer soon after expiry and before purging an asset.
The expiry_date is a Unix epoch, representing the number of seconds since 1 January 1970 00:00:00 UTC if the value is greater than 1,500,000,000; or a block height if it is less than that value (with 1 min blocks this scheme is valid until the year 4870).
Expiry times should not be considered exact, since nodes don’t share the same clocks and block heights, and time proxies become more inaccurate the further out you go (since height in the future is dependent on hash rate).
Signature Validation
Validator nodes will verify the creator_sig
for every create_asset
instruction before propagating the instruction to
the network. The process is as follows:
-
The VN MUST calculate the metadata hash by hashing the canonical representation of all the data in the first three sections of the
create_asset
instruction. -
The VN MUST compare this calculated value to the value given in the
metadata_hash
field. If they do not match, the VN MUST drop the instruction and STOP. -
The VN MUST calculate the RAID ID from the
fqdn
andissuer
fields as specified in RFC-0301. -
The VN MUST compare the calculated RAID ID with the value given in the
raid_id
field. If they do not match, the VN MUST drop the instruction and STOP. -
If the
fqdn
is `"No FQDN", then skip to step 9. -
The VN MUST Look up the OpenAlias TXT record at the domain given in
fqdn
. If the record does not exist, then the VN MUST drop the instruction and STOP. -
The VN MUST check that each of the public key and RAID ID in the TXT record match the values in the
create_asset
instruction. If any values do not match, the VN MUST then drop the instruction and STOP. -
The VN MUST validate the registration signature in the TXT record, using the TXT record's nonce, the issuer's public key and the RAID ID. If the signature does not verify, the VN MUST drop the instruction and STOP.
-
The VN MUST validate the signature in the
creator_sig
field against the challenge built up from the issuer's public key, the nonce given inpublic_nonce
field, theraid_id
field and themetadata_hash
field.
If step 9 passes, then the VN has proven that the create_asset
contains a valid RAID ID, and that if a non-default
FQDN was provided, the owner of that domain provided the create_asset
instruction. In this case, the VN SHOULD
propagate the instruction to the network.
RFC-0312/DANSpecification
High level Digital Asset Network Specification
Maintainer(s): Cayle Sharrock
Licence
Copyright 2022 The Tari Development Community
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Language
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as shown here.
Disclaimer
This document and its content are intended for information purposes only and may be subject to change or update without notice.
This document may include preliminary concepts that may or may not be in the process of being developed by the Tari community. The release of this document is intended solely for review and discussion by the community of the technological merits of the potential system outlined herein.
Goals
This document describes the high-level, or informal specification for how digital assets are created, managed, secured, and wound- down on the Tari digital asset network (DAN).
The document covers, among other things:
- The relationship of side-chains to digital assets and contract,
- Required characteristics of side-chains,
- Peg-in and peg-out mechanisms,
- Digital asset template minimum requirements,
- Validator node requirements,
- Checkpoint and refund mechanisms,
- Failure mode strategies.
This RFC covers a lot of ground. Therefore the intent is not to provide a detailed, code-ready specification for the entire DAN infrastructure; those are left to other RFCs; but to establish a foundation onto which the rest of the DAN specifications can be built.
This RFC supersedes and deprecates several older RFCs:
- RFCD-0300: Digital Assets Network
- RFCD-0301: Namespace Registration
- RFCD-0302: Validator Nodes
- RFCD-0304: Validator Node committee selection
- RFCD-0345: Asset Life cycle
Several RFC documents are in the process of being revised in order to fit into this proposed framework:
Motivation
There are many ways to skin a cat. The philosophy guiding the approach in the RFC is one that permits scaling of the network to handle in the region of 1 billion messages per day (network-wide) and 1 million digital assets with near real-time user experience on asset state retrieval, updating and transfer, on a sufficiently decentralised and private basis.
The definition of sufficient here is subjective, and part of the design philosophy of Tari is that we leave it up to the user to determine what that means, keeping in mind that there is always a trade-off between decentralisation, performance, and cost.
For some assets, decentralisation and censorship resistance will be paramount, and users will be willing to live with a more laggy experience. Gamers in a Web 3.0-MMORPG on the other hand, want cheap, fast transactions with verifiable ownership, and therefore will generally need to sacrifice decentralisation for that.
The goal of the DAN is for asset issuers to be able to configure the side-chain for their project to suit their particular needs.
Description
There are several key actors that participate in Tari Digital Asset Network:
- A tari [contract] is a piece of code that establishes the relationship and rules of engagement between one or more digital assets. This includes ownership rules, transfer rules and state change rules.
- The Asset issuer is the entity that defines a contract and brings it into existence.
- Validator nodes manage the contract on behalf of the asset issuer by executing instructions on a Tari side-chain.
- [Users] interact with contracts and may own, transfer or execute state change instructions against the contract by submitting instructions via the Tari comms network to the relevant validator node committee.
The role of the Layer 1 base chain
The Tari Overview RFC describes the role of the base layer. In summary, the base layer
- maintains the integrity of the Tari cryptocurrency token, and
- maintains registers of the side-chains,
- and facilitates the version control and reproducible execution environments for contract templates.
It does not know about or care about what happens in the side chains as long as the Tari consensus, side-chain and validator node rules are kept.
It is helpful to view the base layer blocks and transactions as an immutable, append-only document which allows us to model the tables and foreign relationships of a traditional database. The rows are represented by the UTXOs and we can infer which table the row belongs to by inspecting the output features of the UTXO.
Whereas a standard RDMS manages access control and permissions via policy, we must also take care to ensure proper access control via consensus rules, lock scripts, covenants, signatures and kernels.
Top-level requirements for side-chains
The guiding principle of Tari contracts are that they are managed on a dedicated side-chain. One side-chain, one contract. Other RFCs will discuss ways to overcome the apparent limitations this rule implies, including inter-contract interactions and asset hibernation.
Asset issuer <-> Validator node agreements
The fundamental relationship of Tari contracts is between the asset issuer and the validator node(s) that manage the contract's side-chain. This relationship is somewhat adversarial by nature: Issuers want high quality service at the lowest possible price; Validators want to be compensated for their services and under some circumstances may want to cheat on contracts for their own gain.
Tari seeks to address this in the lightest way possible by requiring the absolute minimum in terms of base layer governance while providing options for side-chain governance that suits the needs of the parties involved.
For example, an asset issuer that wants to issue a highly decentralised, censorship-resistant high-value contract on a side-chain would likely seek to recruit dozens of validator nodes and run a proof-of-stake consensus model with a confidential asset specification.
In contrast, an asset issuer that wants to participate in the Tari ecosystem, but is not interested in decentralisation could run their own validator node; with no consensus, or staking, or validator node compensation contracts -- these would be unnecessary; and provide a high performance, real-time contract. Games with realistic embedded economics would follow this model, as well as early on in the transition from tradFi to deFi.
A set of Validator nodes that manage the same contract is called the validator node committee for the contract.
The Asset issuer
The asset issuer, otherwise known as the contract owner, is the entity that publishes a contract definition transaction.
The contract definition transaction defines the "what" of the contract. It specifies the complete specification of the code that will run, the execution environment it must be run under, as well as the initialisation parameters for all the contract template constructors.
The contract definition allows validator nodes to be confident that they are running a byte-for-byte equivalent code base with the exact same interpretation of that code as its peers without having to collaborate with any other nodes to confirm this.
In most cases, a contract definition will comprise several well-reviewed and secure templates to define the operation of the contract.
The asset issuer will also draft and publish the contract constitution. The constitution defines how a contract is run, and defines the conditions under which the terms of the constitution can be changed.
The Constitution Committee
The asset issuer will in the creation of the contract constitution nominate a key or set of keys to "own" the asset and control all things related to how the contract is run. They are known as the constitution committee (CC)
The CC has the power to change anything inside of the contract constitution. In many cases, the CC will simply be the asset issuer. However, allowing the CC to differ from the asset issuer enables a number of other use-cases such as a DAO, a nominated list of keys, etc.
The role of validator nodes
- Validator nodes SHOULD diligently and accurately process all instructions related to the contract.
- The committee SHOULD reach consensus on every instruction related to the contract. This specification does NOT dictate how this consensus is reached. If the committee contains one member, then consensus is trivial, and does not require any complicated consensus algorithms. A standard web-based application stack will suffice in most cases. Larger committees can choose from any manner of consensus algorithms, including PBFT, HotStuff, proof-of-stake or proof-of-work.
OPEN QUESTION: The asset issuer has no in-band way to know how the VNs are reaching consensus. Even out-of-band, there could be one server and a bunch of proxies that merely relay messages. Only proof of work (because it is permissionless) and proof of stake (maybe?) work around this problem.
- TODO - research how Polygon and other multichain networks solve this problem.
The Tari base layer does not get involved in governance issues beyond those mechanics that are defined in contract constitutions. However, many asset issuers may want to include mechanisms that, for example, require a Tari stake to act as a validator node. Validator nodes may also desire a compensation mechanism so that they get paid for managing the contract. These mechanisms form part of the contract itself, and are opaque to the machinery of the base layer, side-chain and associated peg transactions.
Validator nodes MAY have to stake Tari for each contract they validate. Asset issuers will determine the nature and amount of stake required as part of the contract constitution. The contract stake is variable on a contract-to-contract basis so that an efficient market between asset issuers and validator nodes can develop. This market is not defined on the Tari blockchain at all and would be best implemented as a DAO on the DAN itself.
Similarly, it has been suggested in the past that Validator Nodes should post hardware benchmarks when registering. The problem with this requirement is that it is fairly trivial to game. We cannot enforce that the machine that posted the benchmark is the same as the one that is running validations.
A better approach is to leave this to the market. A reputation contract can be built, on Tari, of course, that periodically and randomly asks Validator Nodes to perform cryptographically signed benchmarks in exchange for performance certificates. Nodes can voluntarily sign up for such a service and use the certificates as a form of credential. Nodes that do not sign up may have trouble finding contracts to validate and might have to lower their price to get work.
Tari contracts are template-based, and so many contracts may wish to include contract templates that add any or all of the following governance functions to the side-chain contract:
- Validator node staking.
- Validator node slashing.
- A Validator node proof-of-participation certificate template. Poorly performing validator nodes may receive reduced compensation, be fined, or even ejected from the committee at a checkpoint.
- A fee model template. The asset issuer could provide a guaranteed pool of funds from which the committee will be paid at every checkpoint.
This list is far from complete, but should convey the idea that:
- Tari contracts SHOULD be highly modular and composable, with each template performing exactly ONE highly specific task, and doing it very well.
- The base layer and peg transactions know the absolute minimum about the assets on the chain. However, they provide all the information necessary for the contract templates and side-chains to function efficiently.
The contract lifecycle
Every contract MUST be governed by one, and only one, Tari side-chain. A contract MAY define one or more digital assets. This contract can be very simple or highly complex.
The lifecycle of a contract proceeds via these steps:
- The asset issuer publishes a contract definition transaction.
- The asset issuer publishes a contract constitution transaction.
- Once this transaction is published, we enter the acceptance period.
- Each validator node that will be managing the contract publishes a contract acceptance transaction. The group of validator nodes that manages the contract is called the Validator Node Committee (VNC).
- Once the acceptance period has expired, the side-chain initialization period begins.
- The VNC jointly publishes a side-chain initialization transaction.
- At this point, the contract is considered live, and users can safely interact with the contract on the side-chain. Technically, users do not have to wait until this point. The VNC COULD start processing transactions optimistically as soon as the constitution is published, and print the zero-th and first checkpoints once they are mined on the base layer. However, this is not generally recommended.
- The VNC periodically publishes a checkpoint transaction.
- Failure to do so can lead to the contract being abandoned.
- The CC MAY shut the contract down by publishing a dissolution transaction.
The following sections will discuss each of these steps in more detail.
Contract instantiation
Steps 1 - 6 in the contract lifecycle are part of the contract instantiation process. Instantiation is a multi-step process and is ideally represented as a finite-state machine that reacts to transactions published on chain that contain outputs containing specific output features. The combination of output features and FSM allows nodes to accurately track the progress of potentially thousands of contracts in a safe and decentralised manner.
The contract definition transaction
It bears repeating that every contract is governed by one, and only one, Tari side-chain. A contract MAY define one or more digital assets. These assets' behaviour is captured in templates and are highly composable. This allows the contract to be very simple or highly complex, and be handled with the same contract handling machinery.
- Every contract MUST be registered on the base layer.
- Contracts MUST be registered by publishing a
contract definition
transaction. - Asset issuers MUST stake a small amount of Tari in order to publish a new contract.
- Exactly ONE output MUST have a
ContractSpecification
output feature. - The contract specification UTXO MUST include a covenant that only permits it to be spent to a
new
ContractSpecification
UTXO or as an unencumbered UTXO in aContractDeregistration
transaction.
Note: The latter is desirable because it tidies up the UTXO set. But this transaction MUST NOT be published before contract has been dissolved (see [contract dissolution]).
- The
ContractSpecification
UTXO MUST hold at least theMINIMUM_OWNER_COLLATERAL
in Tari. The amount is hard-coded into consensus rules and is a nominal amount to prevent spam, and encourages asset owners to tidy up after themselves if a contract winds down. Initially,MINIMUM_OWNER_COLLATERAL
is set at 200 Tari, but MAY be changed across network upgrades.
Implementation note:
Assuming the collateral is represented by the UTXO commitment $C = kG + vH$, the minimum requirement is verified by
having the range-proof commit to $(k, v - v_\mathrm{min})$ rather than the usual $(k, v)$. Note that this change
requires us to modify the
TransactionOutput
definition to include a minimum_value_commitment
field, defaulting to zero, to capture this extra
information.
- The
ContractSpecification
UTXO MUST also include:- The contract description,
- the asset issuer record
- the contract definition, as described below.
Contract description
The contract description is a simple metadata record that provides context for the contract. The record includes:
- The contract id --
<u256 hash>
. This is immutable for the life of the contract and is calculated asH(contract_name || contract specification hash || Initial data hash || Runtime data hash)
. - A contract name --
utf-8 char[32]
(UTF-8 string) 32 bytes. This is for informational purposes only, so it shouldn't be too long, but not too short that it's not useful (this isn't DOS 3.1 after all). 32 bytes is the same length as a public key or hash, so feels like a reasonable compromise.
Asset issuer record
The asset issuer record identifies the asset issuer as the initial owner and publisher of the contract. The following fields are required:
- the asset issuer's public key, also known as the owner public key,
<PublicKey>
.
Contract definition
The following information must be captured as part of the contract definition
in the ContractSpecification
UTXO of
the contract definition transaction:
- the full contract specification in a compact serialised format,
- the initialisation arguments for the contract, in a compact serialisation format,
- the runtime specification.
This data tells validator nodes exactly what code will be running, and the data needed to initialise that code.
Asset templates will have a strictly defined interface that includes a constructor, or initialisation method. The parameters that these constructors accept is what determines the initial data.
The runtime specification includes, for example, the version of the runtime and any meta-parameters that the runtime accepts.
These three pieces of data are necessary AND sufficient to enable any validator node to start running the contract and execute instructions on it, knowing that any other validator node running the same contract will determine exactly the same state changes for every instruction it receives.
The contract constitution
Following the contract definition transaction,the asset issuer MUST publish a contract constitution transaction in order for the contract initialisation process to proceed.
This transaction defines the "how" and "who" of the digital asset's management.
It contains the "contract terms" for the management of the contract.
Exactly ONE UTXO MUST include the ContractConstitution
output feature flag. The contract constitution UTXO contains
the following:
- It MUST include the contract id. The contract definition transaction SHOULD be mined prior to publication of the constitution transaction, but it strictly is not necessary if VNs are able to access the contract specification in some other way.
- It MUST include a list of public keys of the proposed CC;
- It MUST include a list of public keys of the proposed VNC;
- It MUST include an expiry timestamp before which all VNs must sign and agree to these terms (the acceptance period);
- It MAY include quorum conditions for acceptance of this proposal (default to 100% of VN signatures required);
- If the conditions will unequivocally pass, the acceptance period MAY be shortcut.
- The UTXO MUST only be spendable by a multisig of the quorum of VNs performing side-chain initialisation. (e.g. a 3 of 5 threshold signature).
- It MUST include the side-chain metadata record:
- The consensus algorithm to be used
- checkpoint quorum requirements
- It MUST include the following Checkpoint Parameters Record
- minimum checkpoint frequency,
- minimum quarantine period.
- It MAY include a
RequirementsForConstitutionChange
record. It omitted, the checkpoint parameters and side-chain metadata records are immutable via covenant.- How and when the Constitution UTXO can change.
- Quorum required by the CC,
- Proposal period.
- How and when the Checkpoint Parameters record can change.
- How and when the side-chain metadata record can change.
- It SHOULD include a list of emergency public keys that have signing power if the contract is abandoned.
If both the acceptance period and side-chain initialization period elapses without quorum, the CC MAY spend
theContractConstitution
UTXO back to himself to recover his funds.
In this case, the asset issuer MAY try and publish a new contract constitution.
Contract constitutions for proof-of-work side-chains
Miners are joining and leaving PoW chains all the time. It is impractical to require a full constitution change cycle to execute every time this happens, the chain would never make progress!
To work around this, the constitution actually defines a set of proxy- or observer-nodes that perform the role of running a full node on the side chain and publishing the required [checkpoint transaction]s onto the Tari base chain. The observer node(s) are then technically the VNC. Issuers could place additional safeguards in the contract definition and constitution to keep the VNC honest. Conceivably, even Monero or Bitcoin itself could be attached as a side-chain to Tari in this manner.
The contract acceptance transaction
The entities that are nominated as members of a VNC for a new contract MUST cryptographically [acknowledge and agree] to manage the contract. This happens by virtue of the contract acceptance transactions.
- Each potential VNC member MUST publish a contract acceptance transaction committing the required stake. The UTXO is also an explicit agreement to manage the contract.
- Exactly ONE UTXO MUST have the output feature
ContractAcceptance
. - The UTXO MUST contain a time lock, that prevents the VN spending the UTXO before the acceptance period
- side-chain initialization period has lapsed.
- The output MUST include the contract id.
A contract acceptance transaction MUST be rejected if
- contract id does not exist (the contract definition has not been mined)
- the signing public key was not nominated in the relevant contract constitution
- the deposit is insufficient
The side-chain initialization period
Once the acceptance period has expired, side-chain initialization period begins.
At this point, VNs that have accepted the contract must
- allocate resources
- Setup whatever is needed to run the contract
- Set up consensus with their peer VNs (e.g. hotstuff)
- Initialise the contract and run the constructors
- Reach consensus on the initial state.
- Prepare the side-chain initialization transaction.
all before the side-chain initialization period expires.
The side-chain initialization transaction
Side-chains MUST be marked as initiated by virtue of a side-chain initialization transaction.
- Once the acceptance period has expired, side-chain initialization period begins.
- At this point, there MUST be a quorum of acceptance transactions from validator nodes.
- The validator node committee MUST collaborate to produce, sign and broadcast the initialisation transaction by spending the initial Contract Constitution transaction into the zero-th checkpoint transaction.
- The initialisation transaction MUST spend all the [contract acceptance transactions] for the contract.
- Base layer consensus MUST confirm that the spending rules and covenants have been observed, and that the checkpoint contains the correct covenants and output flags.
- There is a minimum [side-chain deposit] that MUST be included in the peg-in UTXO. A single aggregated UTXO containing at least $$ m D $$ Tari, where m is the number of VNs and D is the deposit required.
- This transaction also acts as the zero-th checkpoint for the contract. As such, it requires all the checkpoint information.
- The state commitment is the merklish root of the state after running the code initialisation using the [initial data] provided in the contract definition transaction.
Contract execution
The goal of the DAN is to allow many, if not millions, of instructions to be processed on the side-chain with little or no impact on the size of the base layer.
The only requirements that the base layer will enforce during contract execution are those specified in the contract constitution.
The base layer will check and enforce these requirements at checkpoints.
Checkpoint transactions
The roles of the checkpoint transaction:
- Present proof of liveness
- Allows authorised entities to make changes to the committee
- Summarise contract state
- Summarise contract logs / events
Implementation Note: In the discussion of Tari account contract templates below, we need a mechanism for proving
that the side-chain state corresponds to what someone is claiming with respect to a valid base layer transaction. But
since our policy is one that the base layer never knows anything about what goes on in side-chains, this poses a
challenge. One possible solution to this would be to add a MERKLE_PROOF
opcode to TariScript that could validate a
base layer transaction based on a checkpoint merkle root combined with a merkle proof that a VNC has given to a user.
Validator node committees MUST periodically sign and broadcast a checkpoint transaction.
The transaction signature MUST satisfy the requirements laid out for checkpoint transactions defined in the contract constitution.
- The checkpoint transaction MUST spend the previous checkpoint transaction for this contract. Consensus will guarantee that only one checkpoint UTXO exists for every contract on the base layer. This is guaranteed by virtue of a covenant. The contract id must equal the contract id of the checkpoint being spent.
- The checkpoint transaction MUST contain exactly ONE UTXO with the
Checkpoint
output feature.
The Checkpoint
output feature adheres to the following:
- It MUST reference the contract id.
- It MUST contain a commitment to the current contract state. This is typically some sort of Merklish root.
- It MAY have a URI to off-chain state or merkle tree
- It MUST contain a checkpoint number, strictly increasing by 1 from the previous checkpoint.
- It MUST strictly copy over the constitution rules from the previous checkpoint, OR
- It MUST contain valid signatures according to the constitution allowing the rules to be changed, along with the relevant parts of the contract constitution change pipeline.
If a valid checkpoint is not posted within the maximum allowed timeframe, the contract is abandoned. This COULD lead to penalties and stake slashing if enabled within the contract specification.
Changes to the constitution
Changes to the contract constitution can happen at any time through the [constitution amendment] process. This also applies to changes to the VNC. Only the CC may make changes to thecontract constitution.
- The rules over how members are added or removed are defined in the contract constitution.
- At the minimum, there's a proposal step, a validation step, an acceptance step, and an activation step. Therefore changes take place over at least a 4-checkpoint time span.
- If a VN leaves a committee their [side-chain deposit] MAY be refunded to them.
- If a new VN joins the committee they must provide the [side-chain deposit] at their activation step.
- In the proposal step, any authorised CC may make a change proposal, within the limits defined by the change rules in the contract constitution
- Before activation, VNC members MAY submit an acceptance transaction that registers their willingness to validate the contract. If no acceptance is submitted within the
acceptance_period
the validator is assumed to be uninterested in running the contract and will not form part of the finalized contract committee.
Contract abandonment
This is the state where VNC and the Asset Owner(s) have abandoned the contract.
If a contract misses one or more checkpoints, nodes can mark it as VNC abandoned
. This is not formally marked on the
blockchain, (since something was NOT done on-chain), but nodes will be able to test for abandoned state.
If a contract has not seen any new constitution amendment for a checkpoint period after it has been marked as VNC abandoned
,
it is marked as abandoned
.
The contract constitution SHOULD provide a set of emergency pubkeys that are able to
- perform a peg-out
- do all governancy things
- rescue funds and state
Implementation note: We could add an IS_ABANDONED
opcode (sugar for height since last checkpoint) to test for
abandonment.
If a contract is abandoned, the emergency key MAY spend the last checkpoint into a QUARANTINED
state. A contract MUST
stay in QUARANTINED
state for at least one month.
The contract can leave the quarantined state in one of two ways:
-
The current VNC MAY reinstate the contract operation by publishing the missing checkpoint(s), and committing to any remedial actions as specified in the contract constitution, e.g. paying a fine, etc.
-
The quarantine period lapses, at which point the emergency key holder(s) have full administrative power over the contract. This means that they have to issue a new constitution to assign a new VNC, peg-out and shut down the contract, or whatever.
OPEN QUESTION: Do we want to allow an additional fall back of everyone spend after years in
abandoned
state?
Contract dissolution
Contract templates
Template code registration and versioning
The code template implementations MUST be registered on the base layer.
The reason for this is that it allows Validator Nodes to know unequivocally that they are all running the same code and can expect the same output for the same input.
Template registration also allows us to implement a secure and trust-minimised upgrade mechanism for templates.
Potentially, we could even introduce a mechanism wherein template developers get paid for people using their template.
Template registration UTXO would contain:
- A link to the code (git commit or IPFS)
- The type of code (source or binary blob)
- A hash of the source code / blob
- Version info.
- [Execution engine] requirements (similar to solc pragma)
There's a clear upgrade path, since there's a code-chain from one version of a contract template to the next.
User account balance representation in side-chains
Tari uses the UTXO model in its ledger accounting. On the other hand Tari side-chains SHOULD use an account-based system to track balances and state.
The reasons for this are:
- An account-based approach leads to fewer outputs on peg-out transactions. There is roughly a 1:1 ratio of users to
balances in an account-based system. On the other hand there are O(n) UTXOs in an output-based system where
n
are the number of transactions carried out on the side-chain. When a side-chain wants to shut down, they must record a new output on the base layer for every account or output (as the case may be) that they track in the peg-out transaction( s). It should be self-evident that account-based systems are far more scalable in the vast majority of use-cases. - Following on from this, Accounts scale better for micro-payment applications, where hundreds or thousands of tiny payments flow between the same two parties.
- Many DAN applications will want to track state (such as NFTs) as well as currency balances. Account-based ledgers make this type of application far simpler.
Pedersen commitments and account-based ledgers
Standard Pedersen commitments are essentially useless in account-based ledgers.
The reason being that since the spending keys would be common to all transactions involving a given account, it is
trivial to use the accounting rules to cancel out the k.G
terms from transactions and to use a pre-image attack to
unblind all the values.
The specific protocol of user accounts in the side-chain is decided by the asset issuer.
Options include:
Fully trusted
In this configuration, the side-chain is controlled by a single validator node, perhaps a server running an RDMS. The validator node has full visibility into the state of the side chain at all times. It may or may not share this state with the public. If it does not, then the situation is analogous to current Web 2.0 server applications.
Decentralised and federated
In this configuration, a distributed set of validator nodes maintain the side-chain state. The set of nodes are fixed. If consensus between nodes is achieved using a mechanism such as HotStuff BFT, very high throughputs can be achieved.
Decentralised and censorship resistant
In this configuration, the side-chain could itself be a proof-of-work blockchain. This offers maximum decentralisation and censorship resistance. However, throughput will be lower.
Confidentiality
As mentioned above, Pedersen commitments are not suitable for account-based ledgers. However, the Zether protocol was expressly designed to provide confidentiality in a smart-contract context. It can be combined with any of the above schemes. Zether can also be extended to provide privacy by including a ring-signature scheme for transfers.
Key template discussions
A majority of contracts will want to implement on or more of the following features:
- A financial bridge from the base layer and user accounts,
- A fee or compensation mechanism for the VNC,
- Inter-contract communications
These are complex topics and there are entire blockchain systems where this functionality is built into the fabric of the design. Tari’s modular approach naturally means that the functionality will be delegated into templates and instantiated where necessary and desired by asset issuers.
This also means that Tari offers additional flexibility for issuers and users while the ecosystem is better positioned to respond to changes in demand and new smart contract patterns.
For this RFC, we limit the conversation to a very broad description of how the templates could be implemented, but will leave specifics to RFCs that are more focussed on the topic.
Funding, withdrawals and deposits
Deposits and withdrawals go via a smart contract template using the bridge model.
Very high level flow
- Send Tari via One-sided payment to an address defined by the template. (Could have a
DEPOSIT
output feature if required) - The VNC sees this, and then issues / prints / mints the equivalent value on side-chain according to the side-chain protocol.
- Equivalent coins change hands many times. The account template maintains an accurate balance of all users’ accounts, with the VNC reaching consensus on value transfer instructions according to the consensus algorithm in force.
- A User requests a withdrawal.
- The VNC debits the user’s account and "burns" equivalent coins on the side chain.
- The VNC broadcasts a standard one-side Tari transaction to the user’s benefit.
- Optionally, the template functionality facilitating proofs of reserve, i.e. that locked funds are of equivalent value to minted funds.
Note that this model is not trustless from a base-layer point of view. Users are trusting the side chain, and VNC to not steal their funds. Therefore one may want to encourage the deployment of PoW or PoS side-chains when executing contracts that handle large amounts of value.
Possible variants
- Users deposit and get a refund transaction to hold onto.
- The refund tx gets updated every time the balance changes. ala Lightning.
- Proof of burn tied to proof of spend.
- Atomic swaps to force issue of token on side-chain in (1.) above.
We could implement any/all of these variants in different templates.
Validator node fees
2 Template models:
- Model A - Centrally funded
- Fees are drawn from a single account (typically funded by asset issuer)
- Eligible instructions are defined in the template constructor.
- Model B - User funded
- Requires an account template
- Fees are supplied with an instruction
- Eligible instructions are defined in the template constructor.
- Instructions that are not covered by the model MAY be rejected by the VNC
Validator Node Instructions
What does an instruction look like? Note: Solana instructions contain
- ProgramId
- Vec of accounts that the instruction will interact with (plus whether they're mutable and have signer auth)
- a blob that the program will deserialise. So, no inherently accessible API
Requires:
- Contract ID
- Vec of method calls: (this is different to how Solana does it/ Maybe some discussion on pros&cons is worthwhile. If we
go WASM, the API is available via reflection)
- Method ID (template::method)
- Method arguments
- Authorization
- signed token-based (Macaroons / JWTish)
Now the VNs have everything they need to execute the Instruction. They execute the instruction. The update the state tree. Return of the call is a "diff" of some sort, which gets appended to the "OP Log" document, and the new state root hash.
The VNC SHOULD reach consensus on this result.
Then you move onto the next instruction.
-
Where do instructions get submitted?
- The [peg-in transaction] contains the pubkeys of each member of the VNC; or a checkpoint transaction.
- ergo, a client app knows the pubkeys of the VNC at all times.
- A client can send an Instruction to ANY VNC member via comms
-
VNs MUST maintain a mempool of instructions
-
VNs SHOULD share instructions with its peer committee members
-
Ordering of instructions.
- (In Hotstuff) The leader selects the next instruction(s) to run.
- The leader MAY batch instructions in a single consensus round.
- For account-based side-chains, Instructions SHOULD contain a nonce??? (Might not be workable)
- For account-based side-chains, Instructions COULD have a dependency field that forces ordering of selected
instructions.
- Potentially, an accumulator is a way to do this. An instruction provides a list of instruction hashes, and the instruction can be included ONLY IF ALL hashes have been recorded.
- Instructions MUST not be executed more than once, even if resubmitted. Suggests some sort of salt/entropy/nonce so that the same execution steps could be run without being interpreted as the same instruction. (e.g. micro-transactions).
Inter-contract interactions
Possible routes for this:
Atomic transactions
- Provide a proof that a conditional instruction on one chain has been executed,
- Execute on this chain, which reveals some fact that the other chain can use to finalise the instruction on the other chain.
- Rolls back if 2nd party does not follow through.
Advantages:
- Does work.
Disadvantages
- Slow
- Need to get data from other chain.
- Might hold up entire chain for extended periods.
Observer protocol
Implement a set of APIs in a template for reading the event log from the VNC directly or query the "read-only" contract.
Pros:
- Fast
- Permissionless in one-way applications
- Can check that results are signed by the VNC quorum
Cons:
- Rely on contracts implementing the protocol
- Instructions that require both chains' state to update is harder using this method.
Micro-payments
Bundle accounts template into smart contract
- The bundled "wrapped" Tari is used in micropayments.
- Users top up or withdraw Tari into the micropayment accounts using a bride or one of the methods described above.
Async-await analogue
- Contract A is a digital assets contract.
- Contract B is a payments contract.
- A and Bob have a monetary account on B, and Bob wants access to the assets on A.
- Bob authorises A to debit his account on B for a certain amount / under certain conditions OR
- Bob authorises the invoice produced by A for a discrete payment.
- A submits a payment instruction to B to withdraw the amount, co-signed by Bob (or he did a pre-auth).
- A "awaits" the result of the payment, and once successful, releases the asset OR
- the instruction times out and Bob does not receive the asset and the instruction concludes.
Pros:
- Can work in general, not just micro-payments
- Can be fast.
- Doesn't block progress in the face of obstructive agents.
Cons:
- Complex (handling collusion, "proof-of-delivery")
- time-outs can lock up funds for long periods.
- Relies on chains publishing events.
- Contract B is a trusted party from the PoV of Bob / A (e.g. Bob & B collude to lie about account updates in order to defraud A)
Change Log
- 06-04-2022: First draft
RFC-0322/VNRegistration
Validator Node Registration
Maintainer(s): Cayle Sharrock
Licence
Copyright 2019 The Tari Development Community
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Language
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as shown here.
Disclaimer
This document and its content are intended for information purposes only and may be subject to change or update without notice.
This document may include preliminary concepts that may or may not be in the process of being developed by the Tari community. The release of this document is intended solely for review and discussion by the community of the technological merits of the potential system outlined herein.
Goals
The aim of this Request for Comment (RFC) is to describe Validator Node (VN) registration. Registration accomplishes two goals:
- Provides a register of Validator Nodes with an authority (the Tari base layer).
- Offers Sybil resistance against gaining probabilistic majority of any given publicly nominated VN committee.
Related Requests for Comment
- RFCD-0302: Validator Nodes
- RFCD-0304: Validator Node Committee Selection
- RFCD-0170: The Tari Communication Network and Network Communication Protocol
- RFCD-0341: Asset Registration
- RFC-0200: Base Layer Extensions
Description
VNs register themselves on the Base layer using a special transaction type. The registration transaction type requires the spending of a certain minimum amount of Tari coin, the (Registration Deposit), which has a time lock on the output for a minimum amount of time (Registration Term), as well as some metadata, such as the VN's public key.
The Node ID is calculated after registration to prevent mining of VN public keys that can be used to manipulate routing on the Distributed Hash Table (DHT).
Once a VN's Registration Term has expired, so will this specific VN registration. The Unspent Transaction Output (UTXO) time lock will have elapsed so the Registration Deposit can be reclaimed and a new VN registration needs to be performed. This automatic registration expiry will ensure that the VN registry stays up to date with active VN registrations, and inactive registrations will naturally be removed.
Requiring nodes to register themselves serves two purposes:
- makes VN Sybil attacks expensive; and
- provides an authoritative "central-but-not-centralized" registry of VNs from the base layer.
Node ID
The VN ID can be calculated deterministically after the VN registration transaction is mined. This ensures that VNs are randomly distributed over the DHT network.
VN IDs MUST be calculated as follows:
NodeId = Hash( pk || h || kh )
Where
Field | Description |
---|---|
pk | The VN's DHT public key |
h | The block height of the block in which the registration transaction was mined |
kh | The hash of the registration transaction's kernel |
Base Nodes SHOULD maintain a cached list of VNs and MUST return the Node ID in response to a
get_validator_node_id
request.
Validator Node Registration
A VN MUST register on the base layer before it can join any Distributed Area Network (DAN) committees. Registration happens by virtue of a VN registration transaction.
VN registrations are valid for the Registration Term.
The registration term is set at SIX months.
A VN registration transaction is a special transaction.
- The transaction MUST have EXACTLY ONE UTXO with the
VN_Deposit
flag set. - This UTXO MUST also:
- set a time lock for AT LEAST the Registration Term (or equivalent block periods);
- provide the value of the UTXO in the signature metadata; and
- provide the public key for the spending key for the output in the signature metadata.
- The value of this output MUST be equal to or greater than the Registration Deposit.
- The UTXO MUST store:
- the value of the VN deposit UTXO as a u64; and
- the value of the public key for the spending key for the output as 32 bytes in little-endian order.
- The
KernelFeatures
bit flag MUST have theVN_Registration
flag set. - The kernel MUST also store the VN's DHT public key as 32 bytes in little-endian order.
Validator Node Registration Renewal
If a VN owner does not renew the registration before the Registration Term has expired, the registration will lapse and the VN will no longer be allowed to participate in any committees.
The number of consecutive renewals MAY increase the VN's reputation score.
A VN may only renew a registration in the TWO-WEEK period prior to the current term expiring.
A VN renewal transaction is a special transaction:
- The transaction MUST have EXACTLY ONE UTXO with the
VN_Deposit
flag set. - The transaction MUST spend the previous VN deposit UTXO for this VN.
- This UTXO MUST also:
- set a time lock for AT LEAST six months (or equivalent block periods);
- provide the value of the transaction in the signature metadata; and
- provide the public key for the spending key for the output in the signature metadata.
- This UTXO MUST also store:
- The value of the VN deposit UTXO as a u64.
- The value of the public key for the spending key for the output as 32 bytes in little-endian order;
- The VN's Node ID. This can be validated by following the Renewal transaction kernel chain.
- The kernel hash of this transaction's kernel.
- A counter indicating that this is the n-th consecutive renewal. This counter will be confirmed by nodes and miners. The first renewal will have a counter value of one.
- The previous VN deposit UTXO MUST NOT be spendable in a standard transaction (i.e. its time lock has not expired).
- The previous VN deposit UTXO MUST expire within the next TWO WEEKS.
- The transaction MAY provide additional inputs to cover transaction fees and increases in the Registration Deposit.
- The transaction kernel MUST have the
VN_Renewal
bit flag set. - The transaction kernel MUST also store the hash of the previous renewal transaction kernel, or the registration kernel, if this is the first renewal.
One will notice that a VN's Node ID does not change as a result of a renewal transaction. Rather, every renewal adds to a chain linking back to the original registration transaction. It may be desirable to establish a long chain of renewals, in order to offer evidence of longevity and improve a VN's reputation.
RFC-0340/VNConsensusOverview
Validator node consensus algorithm
Maintainer(s): Cayle Sharrock
License
Copyright 2019. The Tari Development Community
Validator node consensus algorithm
Maintainer(s): Cayle Sharrock
License
Copyright 2019. The Tari Development Community
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Language
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as shown here.
Disclaimer
The purpose of this document and its content is for information purposes only and may be subject to change or update without notice.
This document may include preliminary concepts that may or may not be in the process of being developed by the Tari community. The release of this document is intended solely for review and discussion by the community regarding the technological merits of the potential system outlined herein.
Goals
This document describes at a high level how smart contract state is managed on the Tari Digital Assets Network.
Related RFCs
- RFCD-0300: The Tari Digital Assets Network
- RFCD-0302: Validator Nodes
- RFCD-0304: Validator Node committee selection
- RFCD-0341: Asset Registration
Description
Overview
The primary problem under consideration here is for multiple machines running the same program (in the form of a Tari smart contract) to maintain agreement on what the state of the program is, often under adverse conditions, including unreliable network communication, malicious third parties, or even malicious peers running the smart contract.
In computer science terms, the problem is referred to as State Machine Replication, or SMR. If we want our honest machines (referred to as replicas in SMR parlance) to reach agreement in the face of arbitrary failures, then we talk about our system being Byzantine Fault Tolerant.
Tari Asset committees are chosen by the asset issuer according to RFC-0304. The committees form a fixed set of replicas, at the very least from checkpoint to checkpoint, and will typically be limited in size, usually less than ten, and almost always under 100. Note: These numbers are highly speculative based on an intuitive guess about the main use cases for Tari DAs, where we have
- many 1-3-sized committees where the asset issuer and the VN committee are the same entity,
- semi-decentralised assets of ±4-10 where speed trumps censorship-resistance,
- a small number of 50-100 VNs where censorship-resistance trumps speed.
Because nodes cannot join and leave the committees at will, robust yet slow and expensive consensus approaches such as Nakamoto consensus can be dropped in favour of something more performant.
There is a good survey of consensus mechanisms on Tari Labs University.
From the point of view of a DAN committee, the ideal consensus algorithm is one that
- Allows a high number of transactions per second, and doesn't have unnecessary pauses (i.e. a partially synchronous or asynchronous model).
- Is Byzantine Fault tolerant.
- Is relatively efficient from a network communication point of view (number of messages passed per state agreement).
- Is relatively simple to implement (to reduce the bug and vulnerability surface in implementations).
A summary of some of the most well-known BFT algorithms is presented in this table.
A close reading of the algorithms presented suggest that LinBFT, which is based on HotStuff BFT provide the best trade-offs for the goals that a DAN committee is trying to achieve:
- The algorithm is optimistic, i.e. as soon as quorum is reached on a particular state, the committee can move onto the next one. There is no need to wait for the "timeout" period as we do in e.g. Tendermint. This allows instructions to be executed almost as quickly as they are received.
- The algorithm is efficient in communication, requiring O(n) messages per state agreement in most practical cases. This is compared to e.g. PBFT which requires O(n4) messages.
- The algorithm is modular and relatively simple to implement.
Potential drawbacks to using HotStuff include:
- Each round required the election of a leader. Having a leader dramatically simplifies the consensus algorithm; it allows a linear number of messages to be sent between the leader and the other replicas in order to agree on the current state; and it allows a strict ordering to be established on instructions without having to resort to e.g. proof of work. However, if the choice of leader is deterministic, attackers can identify and potentially DDOS the leader for a given round, causing the algorithm to time out. There are ways to mitigate this attack for a specific round, as suggested in the LinBFT paper, such as using Verifiable Random Functions, but DDOSing a single replica means that, on average, the algorithm will time out every 1/n rounds.
- The attack described above only pauses progress in Hotstuff for the timeout period. In similar protocols, e.g. Tendermint it can be shown to delay progress indefinitely.
Given these trade-offs, there is strong evidence to suggest that HotStuff BFT, when implemented on the Tari DAN will provide BFT security guarantees with liveness performance in the sub-second scale and throughput on the order of thousands of instructions per second, if the benchmarks presented in the HotStuff paper are representative.
Implementation
The HotStuff BFT algorithm provides a detailed description of the consensus algorithm. Only a summary of it is presented here. To reduce confusion, we adopt the HotStuff nomenclature to describe state changes, rounds and function names where appropriate.
Every proposed state change, as a result of replicas receiving instructions from clients is called a view. There is a
function that every node can call that will tell it which replica will be the leader for a given
view. Every view goes through three phases (Prepare
, PreCommit
, Commit
) before final consensus is reached. Once a
view reaches the Commit
phase, it is finalised and will never be reversed.
As part of their normal operation, every replica broadcasts instructions it receives for its contract to its peers. These instructions are stored in a replica's instruction mempool.
When the leader selection function designates a replica as leader for the next view, it will try
and execute all the instructions it currently has in its mempool to update the state for the next view. Following this
it compiles a tuple of <valid-instructions, rejected-instructions, new-state>. This tuple represents the CMD
structure described in HotStuff.
In parallel with this, the leader expects a set of NewView
messages from the other replicas, indicating that the other
replicas know that this replica is the leader for the next view.
Once a super-majority of these messages have been received, the leader composes a proposal for the next state by adding a new node to the state history graph (I'm calling it a state history graph to avoid naming confusion, but it's really a blockchain). It composes a message containing the new proposal, and broadcasts it to the other replicas.
Replicas, on receipt of the proposal, decide whether the proposal is valid, both from a protocol point of view (i.e. did the leader provide a well-formed proposal) as well as whether they agree on the new state (e.g. by executing the instructions as given and comparing the resulting state with that of the proposal). If there is agreement, they vote on the proposal by signing it, and sending their partial signature back to the leader.
When the leader has received a super-majority of votes, it sends a message back to the replicas with the (aggregated) set of signatures.
Replicas can validate this signature and provide another partial signature indicating that they've received the first aggregated signature for the proposal.
At this point, all replicas know that enough other replicas have received the proposal and are in agreement that it is valid.
In Tendermint, replicas would now wait for the timeout period to make sure that the proposal wasn't going to be superseded before finalising the proposal. But there is an attack described in the HotStuff paper that could stall progress at this point.
The HotStuff protocol prevents this by having a final round of confirmations with the leader. This prevents the stalling attack and also lets replicas finalise the proposal immediately on receipt of the final confirmation from the leader. This lets HotStuff proceed at "network" speed, rather than with a heartbeat dictated by the BFT synchronicity parameter.
Although there are 4 round trips of communication between replicas and the leader, the number of messages sent are O(n). It's also possible to stagger and layer these rounds on top of each other, so that there are always four voting rounds happening simultaneously, rather than waiting for one to complete in its entirety before moving onto the next one. Further details are given in the HotStuff paper.
Forks and byzantine failures
The summary of the HotStuff protocol given above describes the "Happy Path", when there are no breakdowns in communication, or when the leader is an honest node. In cases where the leader is unavailable, the protocol will time out, the current view will be abandoned, and all replicas will move onto the next view.
If a leader is not honest, replicas will reject its proposal, and move onto the next view.
If there is a temporary network partition, the chain may fork (up to a depth of three), but the protocol guarantees safety via the voting mechanism, and the chain will reconcile once the partition resolves.
Leader selection
HotStuff leaves the leader selection algorithm to the application. Usually, a round-robin approach is suggested for its simplicity. However, this requires the replicas to reliably self-order themselves before starting with SMR, which is a non-trivial exercise in byzantine conditions.
For Tari DAN committees, the following algorithm is proposed:
- Every replica knows the Node ID of every other replica in the committee.
- For a given view number, the Node ID with the closest XOR distance to the hash of the view number will be the leader for that view, where the hash function provides a uniformly random value of the same length as the Node ID.
Quorum Certificate
A Quorum certificate, or QC is proof that a super-majority of replicas have agreed on a given state. In particular, a QC consists of
- The type of QC (depending on the phase in which the HotStuff pipeline the QC was signed),
- The view number for the QC
- A reference to the node in the state tree being ratified,
- A signature from a super-majority of replicas.
Tari-specific considerations
As soon as a state is finalised, replicas can inform clients as to the result of instructions they have submitted (in the affirmative or negative). Given that HotStuff proceeds optimistically, and finalisation happens after 4 rounds of communication, it's anticipated that clients can receive a final response from the validator committee in under 500 ms for reasonably-sized committees (this value is speculation at present and will be updated once exploratory experiments have been carried out).
The Tari communication platform was designed to handle peer-to-peer messaging of the type described in HotStuff, and therefore the protocol implementation should be relatively straightforward.
The "state" agreed upon by the VN committee will not only include the smart-contract state, but instruction fee allocations and periodic checkpoints onto the base layer.
Checkpoints onto the base layer achieve several goals:
- Offers a proof-of-work backstop against "evil committees". Without proof of work, there's nothing stopping an evil committee (one that controls a super-majority of replicas) from rewriting history. Proof-of-work is the only reliable and practical method that currently exists to make it expensive to change the history of a chain of records. Tari gives us a "best of both worlds" scenario wherein an evil committee would have to rewrite the base layer history (which does use proof-of-work) before they could rewrite the digital asset history (which does not).
- They allow the asset issuer to authorise changes in the VN committee replica set.
- It allows asset owners to have an immutable proof of asset ownership long after the VN committee has dissolved after the useful end-of-life of a smart contract.
- Provides a means for an asset issuer to resurrect a smart contract long after the original contract has terminated.
When Validator Nodes run smart contracts, they should be run in a separate thread so that if a smart contract crashes, it does not bring the consensus algorithm down with it.
Furthermore, VNs should be able to quickly revert state to at least four views back in order to handle temporary forks. Nodes should also be able to initialise/resume a smart contract (e.g. from a crash) given a state, view number, and view history.
This implies that VNs, in addition to passing around HotStuff BFT messages, will expose additional APIs in order to
- allow lagging replicas to catch up in the execution state.
- Provide information to (authorised) clients regarding the most recent finalised state of the smart contract via a read-only API.
- Accept smart-contract instructions from clients and forward these onto the other replicas in the VN committee.
RFC-0341: Asset Registration
Asset Registration Process
Maintainer(s): Stringhandler
Licence
Copyright 2019 The Tari Development Community
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Language
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as shown here.
Disclaimer
This document and its content are intended for information purposes only and may be subject to change or update without notice.
This document may include preliminary concepts that may or may not be in the process of being developed by the Tari community. The release of this document is intended solely for review and discussion by the community of the technological merits of the potential system outlined herein.
Goals
The aim of this Request for Comment (RFC) is to describe the process in which an Asset Issuer (AI) will need to engage to register a Digital Asset (DA) and commence its operation on the Digital Asset Network (DAN).
Related Requests for Comment
- RFCD-0311: Digital Asset Templates
- RFCD-0302: Validator Nodes
- RFCD-0304: Validator Node Committee Selection
Description
Abstract
This document will describe the process through which an AI will go in order to:
- register a DA on the base layer;
- assemble a committee of Validator Nodes (VNs); and
- commence operation of the DA on the DAN.
Asset Creation Instruction
The first step in registering and commencing the operation of an asset is that the AI MUST issue an asset creation transaction to the base layer.
This transaction will be time-locked for the length of the desired nomination period. This ensures that this transaction
cannot be spent until the nomination period has elapsed so that it is present during the entire nomination process. The
value of the transaction will be the asset_creation_fee
described in RFC-0311. The AI
will spend the transaction back to themselves, but locking this fee up at this stage achieves two goals:
-
Firstly, it makes it expensive to spam the network with asset creation transactions that a malicious AI does not intend to complete.
-
Secondly, it proves to the VNs that participate in the nomination process that the AI does indeed have the funds required to commence operation of the asset once the committee has been selected.
If the asset registration process fails, e.g. if there are not enough available VNs for the committee, then the AI can refund the fee to themselves after the time lock expires.
The transaction will contain the following extra metadata to facilitate the registration process:
-
The value of the transaction in clear text and the public spending key of the commitment so that it can be verified by third parties. A third party can verify the value of the commitment by using the information in (1) and (2) below, to calculate (3):
- The output commitment is $ C = k \cdot G + v \cdot H $.
- $ v $ and $ k \cdot G $ are provided in the metadata.
- A verifier can calculate $ C - k \cdot G = v \cdot H $ and verify this value by multiplying the clear text $ v $ by $ H $ themselves.
-
A commitment (hash) to the asset parameters as defined by a DigitalAssetTemplate described in RFC-0311. This template will define all the parameters of the asset that the AI intends to register, including information the VNs need to know, such as what AssetCollateral is required to be part of the committee.
Once this transaction has been confirmed to the required depth on the blockchain, the nomination phase can begin.
Nomination Phase
The next step in registering an asset is for the AI to select a committee of VNs to manage the asset. The process to do
this is described in RFC-0304. This process lasts as long as the time lock on the asset
creation transaction described above. The VNs have until that time lock elapses to nominate themselves (in the case of
an asset being registered using the committee_mode::PUBLIC_NOMINATION
parameter in the DigitalAssetTemplate).
Asset Commencement
Once the nomination phase is complete and the AI has selected a committee as described in RFC-0304,
the chosen committee and AI are ready to commit their asset_creation_fee
and AssetCollaterals to commence the
operation of the asset. This is done by the AI and the committee members collaborating to build the initial Checkpoint
of the asset. When this Checkpoint transaction is published to the base layer, the digital asset will be live on
the DAN. The Checkpoint transaction is described in RFC_0220.
RFC-0345/AssetLifeCycle
Asset Life Cycle
Maintainer(s): Cayle Sharrock
Licence
Copyright 2019. The Tari Development Community.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Language
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as shown here.
Disclaimer
This document and its content are intended for information purposes only and may be subject to change or update without notice.
This document may include preliminary concepts that may or may not be in the process of being developed by the Tari community. The release of this document is intended solely for review and discussion by the community regarding the technological merits of the potential system outlined herein.
Goals
Related Requests for Comment
- RFCD-0300: The Digital Assets Network
- RFCD-0301: Namespace Registration
- RFCD-0311 Asset templates
- RFCD-0340: Validator Node Consensus
Description
Introduction
Tari digital assets are created on the Digital Assets Network, managed by a Validator Node (VN) committee, see RFC-0340, and are operated under the rules of the template that governs the asset.
A given version of a template provides an immutable definition of the type of information (the state) that is managed during the lifetime of the asset. All the rules that govern reading and updating of the asset state, and rules regarding any transfers of tokens that may be defined as part of the asset are governed by the template code.
This immutability is what allows VN committees to reach rapid consensus about what an asset’s state looks
like after instructions are executed.
However, immutability is a major problem when faced with typical software and business development challenges such as software bugs, or changes in legal and operational requirements.
The Tari network is designed to accommodate these requirements by offering a migration path for assets from one version of a template to another version of a template.
Asset migration
To carry out a successful migration, the following requirements must be met:
- The validator node committee for the asset must support the migration path. This entails that every VN in the
committee has a
MigrateAsset
class from thetemplate_type.version
combination of the existing asset to thetemplate_type.version
of the new asset. - The original asset issuer provides a valid
migrate_asset
instruction to the DAN.- The asset issuer MUST provide any additional state that exists in the new template version and not the original.
- The original asset SHOULD be marked as
retired
. If so, thesuperseded_by
field in the old asset will carry the new asset id once the new asset has been confirmed. We recommend retiring the old asset because all the keys that indicate ownership of tokens will be copied over; effectively re-using them; which can damage privacy. - A policy is provided to determine the course of action to follow if any state from the old asset is illegal under
the new template rules (e.g. If a new rule requires
token.age
to be > 21; what happens to any tokens where this condition fails?)
As part of the migration,
- An entirely new asset is created with the full state of the old asset copied over into the new asset; supplemented with any additional state required in the new template.
- A state validation run is performed; and any invalid state from the old asset is modified according to the migration policy.
- Step 2 is repeated until a valid initial state is produced.
- If Step 2 has run
STATE_VALIDATION_LIMIT
times and the initial state is still not valid, the migration instruction is rejected; the migrate_asset instruction will advise what should be done with the original asset in this case: either allow the original asset to continue as before, or retire it. - Once a valid initial state is produced, a new
create_asset
instruction is generated from the initial state and themigrate_asset
instruction. Typically the same VN committee will be used for the new asset, but this needn’t be the case.
Once a successful migration has completed, any instructions to the old asset can return a simple asset_migrated
response with the new asset ID, which will allow clients and wallets to seamlessly update their records.
Retiring Assets
Retiring an asset follows the same procedure as when as asset reaches its natural end-of-life: A final checkpoint is posted to the base layer and a grace period is given to allow DAN nodes and clients to take a snapshot of the final state if desired.
Resurrecting assets
It’s unreasonable to expect VNs to hold onto large chunks of state for assets that are effectively dead (e.g. ticket stubs long after the event is over). For this reason, assets are allowed to expire after which VNs can forget about the state and use that storage for something else.
However, it may be that interest in an asset resurfaces long after the asset expires (nostalgia being the multi-billion
dollar industry it is today). The resurrect_asset
instruction provides a mechanism to bring an asset back to life.
To resurrect an asset, the following conditions must be met:
- The asset must have expired.
- It must not be currently active (i.e. it hasn’t already been resurrected).
- An asset issuer (not necessarily the original asset issuer) must provide funding for the new lifetime of the asset.
- The asset issuer needs to have a copy of the state corresponding to the final asset checkpoint of the original asset.
- The new asset issuer transmits a
resurrect_asset
instruction to the network. This instruction is identical to the originalcreate_asset
instruction with the following exceptions:- The “initial state” merkle root must be equal to the final state checkpoint merkle root.
- The asset owner public key will be provided by the new asset issuer.
- Third parties can interrogate the new committee asking them to provide a Merkle Proof for pieces of state that the third party (perhaps former asset owners) knows about. This can mitigate fraud attempts where parties can attempt to resurrect assets without actually having a copy of the smart contract state. If enough random state proofs are requested, or a single proof of enough random pieces of state, we can be confident that the asset resurrection is legitimate.
The VN committee for the resurrected asset need not bear any relation to the original VN committee. Once confirmed, the resurrected asset operates exactly like any other asset. An asset can expire and be resurrected multiple times (sequentially).
RFC-0360/NFTInvoices
NFT sale via Mimblwimble Invoice
Maintainer(s): Stringhandler
Licence
Copyright 2021 The Tari Development Community
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Language
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as shown here.
Disclaimer
This document and its content are intended for information purposes only and may be subject to change or update without notice.
This document may include preliminary concepts that may or may not be in the process of being developed by the Tari community. The release of this document is intended solely for review and discussion by the community of the technological merits of the potential system outlined herein.
Goals
The aim of this Request for Comment (RFC) is to describe a formulation of a Mimblewimble transaction negotiation process to allow for the sale of an Non Fungible Token (NFT) by a Seller to a Buyer. The Seller will initiate the transaction negotiation by specifying which [NFT] containing UTXO they are offering for sale and how much Tari they expect in return. Having the party that will receive the funds specify the funds they wish to receive is termed an Invoice. The Buyer will then provide their payment input and resulting change output to the transaction and provide a partial signature. The final step will be the Seller completing the final signature to produce a complete transaction.
The process must be completely atomic. At no point should the data included in the negotiation be malleable by the other party or allow for one party to change the elements provided by the counterparty without making the transaction invalid.
Related Requests for Comment
$$ \newcommand{\hash}[1]{\mathrm{H}\bigl({#1}\bigr)} $$
Description
The standard interactive Mimblewimble transaction is usually initiated by a party, the Sender, that is going to send funds to a Receiver. The Sender selects a number of input UTXOs from which the sent funds and fees will come from, they build a change UTXO to receive any remaining funds and calculate the excess of these inputs and change output. The Receiver is then told how many Tari they will receive and provides the aggregated public excess and public nonce of the Sender's portion of the transaction signature. The Receiver builds a commitment with the specified value and their private spending key and then provides their partial signature for their UTXO and the excess. This gets sent back to the Sender who then completes the transaction by providing their partial signature and aggregating it with the Receiver's partial signature.
In the standard Mimblewimble negotiation there are two main elements that make the process trustless for the Sender and Receiver. Firstly, for the Sender, whose inputs are being spent, has the final say in whether to complete the transaction by producing the final signature. For the Receiver there is little risk because they have not provided any inputs at all and are just receiving funds, so they have nothing to lose if the process is compromised. Furthermore, their partial signature means that the Sender cannot change anything about the Receiver's output in the final stage of negotiation.
When it comes to the sale of a token using a Mimblewimble style transaction the risk profile for both the Seller and the Buyer is a bit more complicated.
- The Seller has to prove that they own a given token UTXO and provide some partial signature committing to the terms of the sale without the Buyer being able to use that data to build a new transaction transferring ownership of the token with different terms to what the Seller specified.
- The Buyer has to provide inputs into the transaction before the finalization step in such a way that those inputs are committed but not at risk of the Seller being able to claim the inputs without completing the agreed upon transaction in full.
So the ultimate goal must be a negotiation process where the Seller and Buyer can interact completely trustlessly to build this transaction to the agreed terms. This must be done while each party's contributions to the negotiation are not malleable by the other party and the entire process must be atomic. It either results in a valid transaction built as both parties expect or not at all.
Transaction Negotiation
Assumptions:
- The UTXOs containing the token data have a value of zero (This has not been decided yet in the RFCs regarding token UTXOs).
- The Buyer is going to pay the fees.
- The Buyer will select a single input for payment purposes whose value will cover the requested payment and fees.
- In the following notation a Transaction Input/Output will be represented by \( C_x \) and when used in equations will represent a Pederson Commitment but when sent to the counterparty will include all the addition metadata as described in RFC-0201: TariScript
In the following equations the capital letter subscripts, S and B refer to a token Seller and Buyer respectively.
The transaction that is constructed by this process will need to produce an output metadata signature for the Transaction Output the Buyer will receive the token in and also the Transaction Output the Seller will receive their payment in. These signatures will be aggregated Commitment Signatures that are described in detail in RFC-0201: TariScript.
RFC-0201: TariScript is written from the perspective of a standard Mimblewimble transaction where only the Sender has Transaction Inputs and the Receiver only has a single Transaction Output. The case this RFC discusses both parties receive a Transaction Output and both parties supply a Transaction Input. This results in the requirement of an extra round of communication between the parties. However, the negotiation described below allows for a transaction to be built that can be validated exactly the same was as is described in RFC-0201: TariScript.
Negotiation
Round 1
The Seller initiates the negotiation by collecting and calculating the following:
Terms | Description |
---|---|
\( C_{St} = 0 \cdot H + k_{St} \cdot G \) | Select the Seller's token Transaction Input to be offered |
\( C_{Sp} = v_p \cdot H + k_{Sp} \cdot G \) | Choose a value to be requested from Buyer and select a spending key for the payment Transaction Output to be received |
\( R_{Sk} = r_{Sk} \cdot G \) | Choose a excess signature nonce |
\( R_{SMSt} = r_{SMSt_a} \cdot H + r_{SMSt_b} \cdot G \) | Choose a Seller commitment signature nonce for the Buyer's token output |
\( K_{SO} = k_{SO} \cdot G \) | Choose a Seller Offset to be used in the Buyer's received token output metadata signature |
\( x_S = k_{Sp} - k_{St} \) | Seller's private Excess |
\( X_S = x_S \cdot G \) | Seller's public Excess |
\( s_{St} \) | A Commitment Signature using \( C_{St} \) signing the message \( e = (X_S \Vert v_p \Vert R_S) \) |
The Seller now sends the following to the Buyer
Items | Description |
---|---|
\( C_{St} \) | The Seller's token Transaction Input to be offered |
\( C_{Sp} \) | Seller's payment Transaction Output to be received |
\( v_p \) | The value the Seller is requesting as part of this offer |
\( R_{Sk} \) | Seller's public excess signature nonce |
\( R_{SMSt}\) | Seller's public metadata signature nonce for the Buyer's token UTXO |
\( K_{SO}\) | Seller Offset public key |
\( s_{St} \) | A Commitment Signature using \( C_{St} \) signing the message \( e = (X_S \Vert v_p \Vert R_s) \) |
Round 2
The commitment signature is provided to the Buyer to show that the Seller does indeed own the token UTXO \( C_{St} \). The Buyer's wallet should verify on the base layer that this UTXO is for the token they are being offered.
The Buyer will construct the Seller's public Excess, \( X_s \) from the provided components: \( X_s = C_{Sp} - C_{St} - v_p \cdot H \) This operation confirms that the only commitments that form part of the Seller's excess are the token input and a commitment with the value of the requested payment. To ensure that there is no malleability in the Seller's payment output the Buyer will also provide the Seller with an offset public key so that the Seller can produce an output metadata signature.
The Buyer will now calculate/choose the following:
Terms | Description |
---|---|
\( C_{Bp} = v_{Bp} \cdot H + k_{Bp} \cdot G \) | Buyer's selected Transaction Input from which the payment is drawn |
\( C_{Bc} = (v_{Bp} - v_p - \text{fees}) \cdot H + k_{Bc} \cdot G \) | Buyer's change Transaction Output accounting for the amount to pay to seller and the fees |
\( C_{Bt} = 0 \cdot H + k_{Bt} \cdot G \) | Buyer's token Transaction Output that he will own if the transaction is completed |
\( x_B = k_{Bt} + k_{Bc} - k_{Bp} \) | Buyers's private Excess |
\( X_B = x_B \cdot G \) | Buyers's public Excess |
\( R_B = r_B \cdot G \) | Choose an excess signature nonce |
\( s_{B} = r_B + e_k x_B \) | A partial excess signature signing the message \( e_k = (R_S + R_B \Vert X_S + X_B \Vert \text{metadata} \Vert \text{fees} ) \) |
\( R_{BMSt} = r_{BMSt_a} \cdot H + r_{BMSt_b} \cdot G \) | Choose a Buyer commitment signature nonce for the Buyer's token output |
\( e_{Bt} = \hash{ (R_{SMSt} + R_{BMSt}) \Vert \alpha_{Bt} \Vert F_{Bt} \Vert K_{SO} \Vert C_{Bt}} \) | Buyer's token output metadata signature challenge where \( \alpha_{Bt} \) is the Buyer's token UTXO script and \( F_{Bt} \) is the Buyer's token UTXO output features |
\( s_{BMSt} = (a_{BMSt}, b_{BMSt}, R_{BMSt} ) \) | A partial metadata commitment signature for \( C_{Bt} \) signing message \( e_{Bt} \) |
\( R_{BMSp} = r_{BMSp_a} \cdot H + r_{BMSp_b} \cdot G \) | Buyer's public metadata signature nonce for use by the Seller to calculate their payment output metadata signature |
\( K_{BO} = k_{BO} \cdot G \) | Choose a Buyer Offset to be used in the Sellers's received payment output metadata signature |
The Buyer will then return the following to the Seller:
Items | Description |
---|---|
\( C_{Bp} \) | Buyer's Transaction Input that the payment will come from |
\( C_{Bc} \) | Buyer's change Transaction Output |
\( C_{Bt} \) | Buyer's token Transaction Input that will be received if the sale is completed |
\( R_B \) | Buyers's public Excess |
\( \text{fees & metadata} \) | The fees and Mimblewimble transaction metadata |
\( s_{B} \) | The Buyer's partial excess signature |
\( R_{SMSt}\) | Buyer's public metadata signature nonce for use in the Seller's payment output metadata signature |
\( K_{BO}\) | Buyer Offset to be used in the Sellers's received payment output metadata signature |
\( s_{BMSt} \) | A partial metadata commitment signature for \( C_{Bt} \) |
Round 3
The Seller can calculate the Buyer's excess as follows:
\( X_B = C_{Bt} + C_{Bc} - C_{Bp} + (v_p + \text{fees}) \cdot H \)
The Seller will now calculate/choose the following:
Terms | Description |
---|---|
\( s_{S} = r_S + e x_S \) | Sellers's partial excess signature |
\( s = s_S + s_B, R = R_S + R_B \) | Seller's aggregates the excess signatures to produce the final excess signature |
\( b_{SMSt} = r_{SMSt_b} + e_{Bt}(k_SO) \) | Seller's partial metadata signature for the Buyer's token output |
\( s_{MSt} = (a_{BMSt}, b_{SMSt} + b_{BMSt}, R_{SMSt} + R_{BMSt} \) | Aggregated metadata signature for Buyer's token Transaction Output |
\( R_{SMS} = r_{SMSp_a} \cdot H + r_{SMSp_b} \cdot G \) | Choose a Seller commitment signature nonce for the Seller's payment output |
\( e_{Sp} = \hash{ (R_{SMSpt} + R_{BMSp}) \Vert \alpha_{Sp} \Vert F_{Sp} \Vert K_{BO} \Vert C_{Sp}} \) | where \( \alpha_{Sp} \) is the Sellers's payment UTXO script and \( F_{Sp} \) is the Seller's payment UTXO output features |
\( s_{SMSp} = (a_{SMSp}, b_{SMSp}, R_{SMSp} ) \) | A partial metadata commitment signature for \( C_{Sp} \) signing message \( e_{Sp} \) |
\( \gamma_S = k_Sst - k_SO \) | The Seller's portion of the Script Offset constructed using the script key from \(C_St\), \( k_Sst \), and the private Seller Offset |
The Seller can almost fully complete the transaction construction now. However, while the Seller can complete their portion of the final script offset they cannot complete the entire offset because the Buyer also has input's in the transaction. This means we need one extra round of communication where the Seller returns the almost complete transaction to the Buyer who will complete there portion of the final script offset to complete the transaction.
The Seller sends the following back to the Buyer:
Items | Description |
---|---|
\( C_{St} \) | Seller's token Transaction Input |
\( C_{Sp} \) | Seller's payment Transaction Output |
\( C_{Bp} \) | Buyer's Transaction Input that the payment will come from |
\( C_{Bc} \) | Buyer's change Transaction Output |
\( C_{Bt} \) | Buyer's token Transaction Output that will be received if the sale is completed |
\( X_S + X_B \) | Public Excess |
\( s \) | Aggregated Excess Signature |
\( s_{MSt} \) | Aggregated metadata signature for Buyer's token Transaction Output |
\( s_{SMSp} \) | Partial metadata commitment signature for \( C_{Sp} \) |
\( \gamma_S \) | The Seller's portion of the Script Offset |
Round 4
The final step will be for the Buyer to complete their portion of the metadata signature for the Seller's payment Transaction Output and complete their portion of the Script Offset.
Terms | Description |
---|---|
\( b_{BMSp} = r_{BMSp_b} + e_{Sp}(k_BO) \) | Buyers's partial metadata signature for the Seller's payment output |
\( s_{MSp} = (a_{SMSp}, b_{SMSp} + b_{BMSp}, R_{SMSp} + R_{BMSp} \) | Aggregated metadata signature for Seller's payment Transaction Output |
\( \gamma_B = k_Bsp - k_BO \) | The Seller's portion of the Script Offset constructed using the script key from \(C_Bp\), \( k_Bsp \), and the private Buyer Offset |
\( \gamma = \gamma_B + \gamma_S \) | The final script offset for the completed transaction. |
The Buyer's change Transaction Output will be constructed fully by them including the metadata signature which will be included in the final \( \gamma \) value.
Validation
A Base Node will validate this transaction in the exact same way as described in RFC-0201: TariScript.
In addition to the standard Mimblewimble and TariScript validation operations the consensus rules required for assets and
tokens must also be applied. The most important is to confirm that the unique_id
of the token being sold in this transaction
exists only once as an input and once as an output and the metadata is correctly transfered.
Security concerns
For this negotiation to be secure it must not be possible for the following to occur:
- For the Buyer to use the information provided by the Seller during the first round to construct a valid transaction spending the Seller's token UTXO without the requested payment being fulfilled.
- For the Seller to take the information provided by the Buyer in the second round and construct a valid transaction where the ownership of the token is not transferred even if the Mimblewimble arithmetic balances out.
These points should both be taken care of by the aggregated excess signature and the respective output [metadata signatures]. These signatures prevent the counterparty from changing anything about the Transaction Outputs without the cooperation of the other party.
RFC-0500/PaymentChannels
Payment channels
Maintainer(s): Cayle Sharrock
Licence
Copyright 2019. The Tari Development Community
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Language
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as shown here.
Disclaimer
This document and its content are intended for information purposes only and may be subject to change or update without notice.
This document may include preliminary concepts that may or may not be in the process of being developed by the Tari community. The release of this document is intended solely for review and discussion by the community regarding the technological merits of the potential system outlined herein.
Goals
Related Requests for Comment
Description
Introduction
The base layer is slow. The DAN is fast. However, every DAN instruction that results in a state change has a fee (i.e.
base layer transaction) associated with it.
To bridge the speed gap between the two layers, a payment channel solution is required.
This document provides the high-level overview of how this is done.
The Clacks
The Clacks is a multi-party side-channel off-chain scalability proposal for Tari. The essential idea behind the Clacks is:
- Users give control of some Tari to a Clacks Committee.
- The committee creates an off-chain UTXO for the user(s). This is called the peg-in transaction.
- Users can transact amongst each other without those transactions touching the base layer. This allows a very high throughput of transactions and instant finality. However, the locus of trust move significantly towards the Clacks committee. In other words, base layer transactions are slow, but do not require users to trust anyone. Clacks transactions are fast, but requires some level of trust in the entity or entities controlling the user's funds.
- Users can send off-chain Tari to any other users, including users that have not made deposits into the Clacks committee.
- Users can request a withdrawal for their Tari at any time. At predefined intervals, the Clacks committee will process those withdrawal requests and give the Tari UTXO control back to the user.
Users also have a pre-signed, time-locked transaction that returns the user's fund back to them. This refund transaction can be used in case the Clacks committee stops processing withdrawals and provides insurance for users against having their funds locked up forever.
The peg-in, peg-out cycle are represented by standard Tari transactions. This means that full nodes verify that no funds have been created or destroyed over the course of the cycle. They do not check that the "balances" associated with users are what those users would expect in an honestly run side-chain.
However, if the side-chain is run as a standard mimblewimble process, any third party running a full node and that access to the opening and closing channel balances, can in fact verify that the committee has operated honestly.
-
Tracking issue: assigned during WIP phase
Maintainer(s):
Licence
Copyright
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of this document must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Language
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (covering RFC2119 and RFC8174) when, and only when, they appear in all capitals, as shown here.
Disclaimer
This document and its content are intended for information purposes only and may be subject to change or update without notice.
This document may include preliminary concepts that may or may not be in the process of being developed by the Tari community. The release of this document is intended solely for review and discussion by the community regarding the technological merits of the potential system outlined herein.
Goals
Related Requests for Comment
Description
Change Log
Date | Change | Author |
---|---|---|
dd mmm YYYY | Short desc | Author |
Tari Network Terminology
Below are a list of terms and their definitions that are used throughout the Tari code and documentation. Let's use this glossary to disambiguate ideas, and work towards a ubiquitous language for this project.
Archive node
This is a full history base node. It will keep a complete history of every transaction ever received and it will not implement pruning.
AssetCollateral
The amount of tari coin that a Validator Node must put up on the base layer in order to become part of an asset committee.
Asset Issuer
An entity that creates digital assets on the Tari DAN. The Asset Issuer will specify the parameters of the contract template that defines the rules that govern the asset and the number and nature of its constituent tokens on issuance. The Asset Issuer will, generally, be the initial owner of the tokens.
Bad Actor
A participant that acts maliciously or negligently to the detriment of the network or another participant.
Base layer
The Tari Base layer is a merge-mined blockchain secured by proof-of-work. The base layer is primarily responsible for the emission of new Tari, for securing and managing Tari coin transfers.
Base Node
A full Tari node running on the base layer. It's primary role is validating and propagating Tari coin transactions and blocks to the rest of the network.
Block
A collection of transactions and associated metadata recorded as a single entity in the Tari blockchain. The ordering of Tari transactions is set purely by the block height of the block they are recorded in.
Block Header
A data structure that validates the information contained in a block.
Block Body
A data structure containing the transaction inputs, outputs, and kernels that make up the block.
Block reward
The amount of Tari created by the coinbase transaction in every block. The block reward is set by the emission schedule.
Blockchain
A sequence of tari blocks. Each block contains a hash of the previous valid block. Thus the blocks form a chain with the property that changing anything in a block other than the head block requires rewriting the entire blockchain from that point on.
Blockchain state
The complete state of the blockchain at a specific block height. This means a pruned utxo set, a complete set of kernels and headers up to that block height from the genesis block.
BroadcastStrategy
A strategy for propagating messages amongst nodes in a peer-to-peer network. Example implementations of
BroadcastStrategy
include the Gossip protocol and flood fill.
Chain Reorganization
A chain reorganization occurs after a chain split occurs on the network, which commonly occurs due to network latency and connectivity issues. When a chain split occurs one chain will have the higher accumulated proof-of-work, this chain is considered the best chain. Nodes on the poorer chain will need to rewind and resync their chains to best chain. In this process transaction in the mempool could become orphaned or invalid.
Checkpoint
A hash of the state of a Digital Asset that is recorded on the base layer.
Coinbase transaction
The first transaction in every Tari block yields a Block Reward according to the Tari emission Schedule and is awarded to the miner that performed the Proof of Work for the block.
Coinbase extra
An arbitrary 64 bytes can be included with the coinbase utxo that can be used to store any data. This can be used to identify the pool that mined the block for example or a Merkle root used when merge mining with Tari as the parent chain.
Committee
A group of Validator Nodes that are responsible for managing the state of a specific Digital Asset. A committee is selected during asset issuance and can be updated at Checkpoints.
CommitteeSelectionStrategy
A strategy for an Asset Issuer to select candidates for the committee from the available registered Validator Nodes who responded to the nomination call for that asset.
ConsensusStrategy
The approach that will be taken for a committee to reach consensus on the validity of instructions that are performed on a given Digital Asset.
Commitment
A commitment is a cryptographic primitive that allows one to commit to a chosen value while keeping it hidden from others, with the ability to reveal the committed value later. Commitments are designed so that one cannot change the value or statement after they have committed to it.
Commitment and Public Key Signature
A mathematical assertion of knowledge of the opening of a commitment and the private key corresponding to a public key, and which is bound to a message to produce a signature. In the context of Tari protocols, it is used to construct a metadata signature and script signature for transactions.
Specifically, it is a Schnorr-type conjunction proof that uses the Fiat-Shamir technique for message binding.
Communication Node
A Communication Node is either a Validator Node or Base Node that is part of the Tari communication network. It maintains the network and is responsible for forwarding and propagating joining requests, discovery requests and data messages on the communication network.
Communication Client
A Communication Client is a Wallet or Asset Manager that makes use of the Tari communication network to send joining and discovery requests. A Communication Client does not maintain the communication network and is not responsible for forwarding or propagating any requests or data messages.
Creator Nomination Mode
An asset runs in creator nomination mode when every validator node in a validator committee is a Trusted Node that was directly nominated by the Asset Issuer.
Current head
The last block of the base layer that represents the latest valid block. This block must be from the longest proof-of-work chain to be the current head.
Cut-Through
Cut-through is the process where outputs spent within a single block may be removed without breaking the standard MimbleWimble
validation rules. Simplistically, Alice -> Bob -> Carol
may be "cut-through" to Alice -> Carol
. Bob's commitments may be removed.
On Tari, for reasons described in RFC-0201_TariScript, cut-through is prevented from ever happening.
Digital asset
Digital assets (DAs) are the sets or collections of native digital tokens (both fungible and non-fungible) that are created by asset issuers on the Tari 2nd layer. For example, a promoter might create a DA for a music concert event. The event is the digital asset, and the tickets for the event are digital asset tokens.
Digital Asset Network
The Tari second layer. All digital asset interactions are managed on the Tari Digital Assets Network (DAN). These interactions (defined in instructions) are processed and validated by Validator Nodes.
DigitalAssetTemplate
A DigitalAssetTemplate is one of a set of contract types supported by the DAN. These contracts are non-turing complete and consist of rigid rule-sets with parameters that can be set by Asset Issuers.
Digital asset tokens
Digital asset tokens (or often, just "tokens") are the finite set of digital entities associated with a given digital asset. Depending on the DA created, tokens can represent tickets, in-game items, collectibles or loyalty points. They are bound to the digital asset that created them.
Hashed Time Locked Contract
A time locked contract that only pays out after a certain criteria has been met or refunds the originator if a certain period has expired.
Emission schedule
An explicit formula as a function of the block height, h, that determines the block reward for the hth block.
Instructions
Instructions are the digital asset network equivalent of transactions. Instructions are issued by asset issuers and client applications and are relayed by the DAN to the validator nodes that are managing the associated digital asset.
Mempool
The mempool consists of the unconfirmed pool and reorg pool, and is responsible for managing unconfirmed transactions that have not yet been included in the longest proof-of-work chain. Miners usually draw verified transactions from the mempool to build up transaction blocks.
Metadata Signature
The metadata signature is a commitment and public key signature, attached to a transaction output and signed with a combination of the homomorphic commitment private values \( (v_i \, , \, k_i )\), the spending key known only to the receiver, and sender offset private key \(k_{Oi}\) known only to the sender. This prevents malleability of the UTXO metadata.
Script Signature
The script signature is an aggregated Commitment Signature ("ComSig") signature, attached to a transaction input and signed with a combination of the homomorphic commitment private values \( (v_i \, , \, k_i )\), the spending key known only to the sender, and script private key \(k_{Si}\) known only to the sender. This ensures that the script is valid and that the input data has not changed.
Mimblewimble
Mimblewimble is a privacy-centric cryptocurrency protocol. It was dropped in the Bitcoin Developers chatroom by an anonymous author and has since been refined by several authors, including Andrew Poelstra.
Mining Server
A Mining Server is responsible for constructing new blocks by bundling transactions from the mempool of a connected Base Node. It also distributes Proof-of-Work tasks to Mining Workers and verifies PoW solutions.
Mining Worker
A Mining Worker is responsible for performing Proof-of-Work tasks received from its parent Mining Server.
Multisig
Multi-signatures (Multisigs) are also known as N-of-M signatures, this means that a minimum of N number of the M peers need to agree before a transaction can be spent. N and M can be equal; which is a special case and is often referred to as an N-of-N Multisig.
Node ID
A node ID is a unique identifier that specifies the location of a communication node or communication client in the Tari communication network. The node ID can either be obtained from registration on the Base Layer or can be derived from the public identification key of a communication node or communication client.
Non-fungible Token (NFT)
A Non-fungible token is a specific instance of a token issued as part of a digital asset. It is another name for a [digital asset token]. NFTs are contained within specially marked UTXOs on the Tari Base Layer.
Orphan Pool
The orphan pool is part of the mempool and manages all transactions that have been verified but attempt to spend UTXOs that do not exist or haven't been created yet.
Pending Pool
The pending pool is part of the mempool and manages all transactions that have a time-lock restriction on when it can be processed or attempts to spend UTXOs with time-locks.
Pruned Node
This is a pruned history base node. It uses cryptography of mimblewimble to allow the removal of spent inputs and outputs beyond the pruninghorizon.
It can still validate the integrity of the blockchain i.e. no coins were destroyed or created beyond what is allowed by consensus rules. A sufficient number of blocks back from the tip should be configured because reorgs are no longer possible beyond that horizon.
Pruning Horizon
This is a local setting for each node to help reduce syncing time and bandwidth. This is the number of blocks from the chain tip beyond which a chain will be pruned.
Public Nomination Mode
An asset runs in public nomination mode when the Asset Issuer broadcasts a call for nominations to the network and VNs from the network nominate themselves as candidates to become members of the committee for the asset. The Asset Issuer will then employ the CommitteeSelectionStrategy to select the committee from the list of available candidates.
Range proof
A mathematical demonstration that a value inside a commitment (i.e. it is hidden) lies within a certain range. For Mimblewimble, range proofs are used to prove that outputs are positive values.
Registration Deposit
An amount of tari coin that is locked up on the base layer when a Validator Node is registered. In order to make Sybil attacks expensive and to provide an authorative base layer registry of validator nodes they will need to lock up a amount of Tari Coin on the Base Layer using a registration transaction to begin acting as a VN on the DAN.
Registration Term
The minimum amount of time that a VN registration lasts, the Registration Deposit can only be released after this minimum period has elapsed.
Reorg Pool
The reorg pool is part of the mempool and stores all transactions that have recently been included in blocks in case a blockchain reorganization occurs and the transactions need to be restored to the transaction pool.
Script Keypair
The script private - public keypair, \((k_{Si}\),\(K_{Si})\), is used in TariScript to unlock and execute the script associated with an output. Afterwards the execution stack must contain exactly one value that must be equal to the script public key.
Script Offset
The script offset provides a proof that every script public key \( K_{Si} \) and sender offset public key \( K_{Oi} \) provided for the a transaction's inputs and outputs are correct.
Sender Offset Keypair
The sender offset private - public keypair, (\( k_{Oi} \),\( K_{Oi} \)), is used by the sender of an output to lock all its metadata by virtue of a metadata signature.
Spending Key
A private spending key is a private key that permits spending of a UTXO. It is also sometimes referred to as a Blinding Factor, since is Tari (and Mimblewimble) outputs, the value of a UTXO is blinded by the spending key:
$$ C = v.H + k.G $$
The public key, \(P = k.G\) is known as the public spending key.
SynchronisationState
The current synchronisation state of a Base Node. This can either be
starting
- The node has freshly started up and is still waiting for first round of chain_metadata responses from its neighbours on which to base its next state change.header_sync
- The node is in the process of synchronising headers with chosen sync peer.horizon_sync
- The node is in the process of syncing blocks from the tip to its [pruning horizon]block_sync
- The node is in the process of syncing all blocks back to the genesis blocklistening
- The node has completed its syncing strategy and will continue to listen for new blocks and monitor its neighbours to detect if it falls behind.
SynchronisationStrategy
The generalised approach for a Base Node to obtain the current state of the blockchain from the peer-to-peer network. Specific implementations may differ based on different trade-offs and constraints with respect to bandwidth, local network conditions etc.
Tari Coin
The base layer token. Tari coins are released according to the emission schedule on the Tari base layer blockchain in coinbase transactions.
TariScript
Tari uses a scripting system for transactions, not unlike Bitcoin's scripting system, called TariScript. It is also simple, stack-based, processed from left to right, not Turing-complete, with no loops. It is a list of instructions linked in a non malleable way to each output, specifying its conditions of spending.
Total Accumulated difficulty
The Accumulated difficulty of the chain is used to compare chain tips. Every block has a total accumulated difficulty that is calculated as the sum of all achieved difficulties of Sha3 blocks multiplied by he sum of all achieved difficulties of RandomX blocks. This is represented as an u128.
Transaction
Transactions are activities recorded on the Tari blockchain running on the base layer. Transactions always involve a transfer of Tari coins. A mimblewimble transaction body consists of one or more blinded inputs and outputs.
Transaction Pool
The transaction pool is part of the mempool and manages all transactions that have been verified, that spend valid UTXOs and don't have any time-lock restrictions.
Trusted Node
A permissioned Validator Node nominated by an Asset Issuer that will form part of the committee for that Digital Asset.
Token Wallet
A Tari Token Wallet is responsible for managing Digital assets and Tokens, and for constructing and negotiating instructions for transferring and receiving Assets and Tokens on the Digital Asset Network.
Transaction Weight
The weight of a transaction / block measured in "grams". See Block / Transaction weight for more details.
Unspent transaction outputs
An unspent transaction output (UTXO) is a discrete number of Tari that are available to be spent. The sum of all UTXOs represents all the Tari currently in circulation. In addition, the sum of all UTXO values equals the sum of the block rewards for all blocks up to the current block height.
UTXO values are hidden by their commitments. Only the owner of the UTXO and (presumably) the creator of the UTXO (either a Coinbase transaction or previous spender) know the value of the UTXO.
Transaction Kernel
A piece of data that is always kept as part of the blockchain and never pruned away. This contains the excess signature and serves as proof that the parties transacting know the blinding factors of their commitments used in the transaction.
Validator Node
Validator nodes (VNs) make up the Tari second layer, or Digital Asset Network. VNs are responsible for creating and updating digital assets living on the Tari network.
Validator Node committee
Validator nodes (VNs) validate and execute instructions on the Digital Assets Network (DAN). They are also responsible for maintaining state in DAN assets.
Wallet
A Tari Wallet is responsible for managing key pairs, and for constructing and negotiating transactions for transferring and receiving tari coins on the Base Layer.
Disclaimer
This document is subject to the disclaimer.