Add EIP-6358: Omniverse Distributed Ledger Technology (O-Token Protocol)

Updates

  • [2023.3.1] The name of this EIP might be changed according to the suggestions
  • [2023.3.9] Submit some updates and add the source codes for trying.
  • [2023.3.20] The updates has been merged. The latest version can be found here

Abstract

The Omniverse Distributed Ledger Technology(O-DLT for short) is a new application-level token feature built over multiple existing L1 public chains, enabling asset-related operations such as transfers and receptions running over different consensus spaces synchronously and equivalently.


O-DLT could be considered as a decentralized global ledger established upon two parts:

  • Abstract Nodes are smart contracts or similar mechanisms running at the application level over existing blockchains. The states recorded by the Abstract Nodes that are deployed on different blockchains respectively could be considered as copies of the global state, and they are ultimately consistent.
  • Synchronizer is an off-chain procedure responsible for carrying published omniverse transactions from the O-DLT smart contracts on one blockchain to the O-DLT smart contracts of the others. They are absolutely trustless, and details could be found in workflow below.

The core meaning of Omniverse is that the legitimacy of all on-chain states and operations can be equivalently verified and consistently recorded over different consensus spaces, regardless of where they were initiated. This could be implemented by Verifiable Fraud Proof and Verifiable State Synchronization (We will provide an implementation based on ZK-Proof, which we call it zk-synchronization. Details can be found here).
O-DLT works at an application level, which means everything related is processed in smart contracts or similar mechanisms, just as the ERC20/ERC721 did.

Motivation

For projects serving multiple chains, it might be useful that the token is able to be accessed anywhere. Although assets-bridges can more or less make it, we don’t think it is enough. And in the process of R&D, we found that the fragmentation of tokens is a common and disturbing problem among chains and L2s.

  • We want our token to be treated as a whole instead of being divided into fragmented parts on different public chains. O-DLT can get it.
  • When one chain breaks down, we don’t want to lose our assets along with it. Assets-bridge paradigm cannot provide a guarantee for this. O-DLT can provide this guarantee even if there’s only one chain that works, for example, we can rely on the stability and robustness of Ethereum.
  • Not just for a certain token, we think the Omniverse Token might be useful for other projects on Ethereum and other chains. O-DLT is actually a new kind of open-source asset paradigm at the application level.

Specification

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 RFC 2119 and RFC 8174.

The word Omniverse in definitions will be substituted with the assigned number.

Omniverse Account

The Omniverse account is RECOMMENDED to be expressed as a public key created by the elliptic curve secp256k1, which has already been supported by Ethereum tech stacks and can be used to generate an Ethereum address directly. For those who have a different address system, a mapping mechanism is RECOMMENDED.

Data Structure

The definations of omniverse transaction data MUST be defined as follows:

/**
 * @notice Omniverse transaction data structure
 * @member nonce: The number of the o-transactions. If the current nonce of an omniverse account is `k`, the valid nonce of this o-account in the next o-transaction is `k+1`.
 * @member chainId: The chain where the o-transaction is initiated
 * @member initiateSC: The contract address from which the o-transaction is first initiated
 * @member from: The Omniverse account which signs the o-transaction
 * @member payload: The encoded bussiness logic data, which is maintained by the developer
 * @member signature: The signature of the above informations.
 */
struct ERC6358TransactionData{
    uint128 nonce;
    uint32 chainId;
    bytes initiateSC;
    bytes from;
    bytes payload;
    bytes signature;
}
  • The data structure ERC6358TransactionData MUST be defined as above.
  • The member nonce MUST be defined as uint128 due to better compatibility for more tech stacks of blockchains.
  • The member chainId MUST be defined as uint32.
  • The member initiateSC MUST be defined as bytes.
  • The member from MUST be defined as bytes.
  • The member payload MUST be defined as bytes. It is encoded from a user-defined data related to the o-transaction. For example:
    • For fungible tokens it is RECOMMENDED as follows:
      /**
      * @notice Fungible token data structure, from which the field `payload` in `ERC6358TransactionData` will be encoded
      *
      * @member op: The operation type
      * NOTE op: 0-31 are reserved values, 32-255 are custom values
      *           op: 0 - omniverse account `from` transfers `amount` tokens to omniverse account `exData`, `from` have at least `amount` tokens
      *           op: 1 - omniverse account `from` mints `amount` tokens to omniverse account `exData`
      *           op: 2 - omniverse account `from` burns `amount` tokens from his own, `from` have at least `amount` tokens
      * @member exData: The operation data. This sector could be empty and is determined by `op`. For example:
                  when `op` is 0 and 1, `exData` stores the omniverse account that receives.
                  when `op` is 2, `exData` is empty.
      * @member amount: The amount of tokens being operated
      */
      struct Fungible {
          uint8 op;
          bytes exData;
          uint256 amount;
      }
      
      • The related raw data for signature in o-transaction is the concatenation of the raw bytes of op, exData, and amount.
    • For non-fungible tokens it is RECOMMENDED as follows:
      /**
      * @notice Non-Fungible token data structure, from which the field `payload` in `ERC6358TransactionData` will be encoded
      *
      * @member op: The operation type
      * NOTE op: 0-31 are reserved values, 32-255 are custom values
      *           op: 0 omniverse account `from` transfers token `tokenId` to omniverse account `exData`, `from` have the token with `tokenId`
      *           op: 1 omniverse account `from` mints token `tokenId` to omniverse account `exData`
      *           op: 2 omniverse account `from` burns token `tokenId`, `from` have the token with `tokenId`
      * @member exData: The operation data. This sector could be empty and is determined by `op`
      *           when `op` is 0 and 1, `exData` stores the omniverse account that receives.
                  when `op` is 2, `exData` is empty.
      * @member tokenId: The tokenId of the non-fungible token being operated
      */
      struct NonFungible {
          uint8 op;
          bytes exData;
          uint256 tokenId;
      }
      
      • The related raw data for signature in o-transaction is the concatenation of the raw bytes of op, exData, and tokenId.
  • The member signature MUST be defined as bytes. It is RECOMMENDED to be created as follows, which could be determined by certain omniverse token developers according to their situations:
    • Concat the sectors in ERC6358TransactionData as below (take Fungible token for example) and calculate the hash with keccak256:
      /**
      * @notice Decode `_data` from bytes to Fungible
      * @return A `Fungible` instance
      */
      function decodeData(bytes memory _data) internal pure returns (Fungible memory) {
          (uint8 op, bytes memory exData, uint256 amount) = abi.decode(_data, (uint8, bytes, uint256));
          return Fungible(op, exData, amount);
      }
      
      /**
      * @notice Get the hash of a transaction
      * @return Hash value of the raw data of an `ERC6358TransactionData` instance
      */
      function getTransactionHash(ERC6358TransactionDatamemory _data) public pure returns (bytes32) {
          Fungible memory fungible = decodeData(_data.payload);
          bytes memory payload = abi.encodePacked(fungible.op, fungible.exData, fungible.amount);
          bytes memory rawData = abi.encodePacked(_data.nonce, _data.chainId, _data.initiateSC, _data.from, payload);
          return keccak256(rawData);
      }
      
    • The signature is about the hash value.

Smart Contract Interface

  • Every ERC-Omniverse Token MUST implement the IERC6358
    /**
    * @notice Interface of the ERC Omniverse-DLT
    */
    interface IERC6358{
        /**
        * @notice Emitted when a o-transaction which has nonce `nonce` and was signed by user `pk` is sent by calling {sendOmniverseTransaction}
        */
        event TransactionSent(bytes pk, uint256 nonce);
    
        /**
        * @notice Sends an omniverse transaction
        * @dev
        * Note: MUST implement the validation of the `_data.signature`
        * Note: A map maintaining the omniverse account and the related transaction nonce is RECOMMENDED  
        * Note: MUST implement the validation of the `_data.nonce` according to the current account nonce
        * Note: MUST implement the validation of the `_data. payload`
        * Note: This interface is just for sending an omniverse transaction, and the execution MUST NOT be within this interface
        * Note: The actual execution of an omniverse transaction is RECOMMENDED to be in another function and MAY be delayed for a time,
        * which is determined all by who publishes an O-DLT token
        * @param _data: the omniverse transaction data with type {ERC6358TransactionData}
        * See more information in the defination of {ERC6358TransactionData}
        *
        * Emit a {TransactionSent} event
        */
        function sendOmniverseTransaction(ERC6358TransactionDatacalldata _data) external;
    
        /**
        * @notice Get the number of omniverse transactions sent by user `_pk`,
        * which is also the valid `nonce` of a new omniverse transactions of user `_pk`
        * @param _pk: Omniverse account to be queried
        * @return The number of omniverse transactions sent by user `_pk`
        */
        function getTransactionCount(bytes memory _pk) external view returns (uint256);
    
        /**
        * @notice Get the transaction data `txData` and timestamp `timestamp` of the user `_use` at a specified nonce `_nonce`
        * @param _user Omniverse account to be queried
        * @param _nonce The nonce to be queried
        * @return Returns the transaction data `txData` and timestamp `timestamp` of the user `_use` at a specified nonce `_nonce`
        */
        function getTransactionData(bytes calldata _user, uint256 _nonce) external view returns (ERC6358TransactionData memory, uint256);
    
        /**
        * @notice Get the chain ID
        * @return Returns the chain ID
        */
        function getChainId() external view returns (uint32);
    }
    
    • The sendOmniverseTransaction function MAY be implemented as public or external
    • The getTransactionCount function MAY be implemented as public or external
    • The getTransactionData function MAY be implemented as public or external
    • The getChainId function MAY be implemented as pure or view
    • The TransactionSent event MUST be emitted when sendOmniverseTransaction function is called
  • Optional Extension: Fungible
    // import "{IERC6358.sol}";
    
    /**
    * @notice Interface of the omniverse fungible token, which inherits {IERC6358}
    */
    interface IERC6358Fungible is IERC6358 {
        /**
        * @notice Get the omniverse balance of a user `_pk`
        * @param _pk Omniverse account to be queried
        * @return Returns the omniverse balance of a user `_pk`
        */
        function omniverseBalanceOf(bytes calldata _pk) external view returns (uint256);
    }
    
    • The omniverseBalanceOf function MAY be implemented as public or external
  • Optional Extension: NonFungible
    import "{IERC6358.sol}";
    
    /**
    * @notice Interface of the omniverse non fungible token, which inherits {IERC6358}
    */
    interface IERC6358NonFungible is IERC6358{
        /**
        * @notice Get the number of tokens in account `_pk`
        * @param _pk Omniverse account to be queried
        * @return Returns the number of tokens in account `_pk`
        */
        function omniverseBalanceOf(bytes calldata _pk) external view returns (uint256);
    
        /**
        * @notice Get the owner of a token `tokenId`
        * @param _tokenId Omniverse token id to be queried
        * @return Returns the owner of a token `tokenId`
        */
        function omniverseOwnerOf(uint256 _tokenId) external view returns (bytes memory);
    }
    
    • The omniverseBalanceOf function MAY be implemented as public or external
    • The omniverseOwnerOf function MAY be implemented as public or external

Rationale

Architecture

  • The implementation of the Omniverse Account is not very hard, and we temporarily choose a common elliptic curve secp256k1 to make it out, which has already been supported by Ethereum tech stacks. For those who don’t support secp256k1 or have a different address system, we can adapt them with a simple mapping mechanism (Flow for example).
  • The Omniverse Transaction guarantees the ultimate consistency of omniverse transactions(o-transaction for short) across all chains. The related data structure is ERC6358TransactionData mentioned above.
    • The nonce is very important, which is the key point to synchronize the states globally.
    • The nonce appears in two places, the one is nonce in o-transaction data as above, and the other is account nonce maintained by on-chain O-DLT smart contracts. The example codes about the account nonce can be found here
    • The nonce in o-transaction data will be verified according to the account nonce managed by on-chain O-DLT smart contracts. Some example codes can be found here.
  • The Omniverse Token could be implemented with the interfaces mentioned above. It can also be used with the combination of ERC20/ERC721. The prototype of the code can be found here
    • The first thing is verifying the signature of the o-transaction data.
    • Then the operation will be added to a pre-execution cache, and wait for a fixed time until is executed. The waiting time will be able to be settled by the deployer, for example, 5 minutes.
    • The off-chain synchronizer will deliver the o-transaction data to other chains. If another o-transaction data with the same nonce and the same sender account is received within the waiting time, and if there’s any content in ERC6358TransactionData difference, a malicious attack happens and the related sender account will be punished.
    • The example code of sendOmniverseTransaction is here
    • and the example code of executing is here.
    • The implementation for Omniverse Non-Fungible Token is almost the same and the defination of the interface can be found here
  • The Omniverse Verification is mainly about the verification of the signature implemented in different tech stacks according to the blockchain. As the signature is unfakeable and non-deniable, malicious attacks could be found deterministicly.
  • The bottom is the off-chain synchronizer. The synchronizer is a very simple off-chain procedure, and it just listens to the Omniverse events happening on-chain and delivers the latest o-transaction events. As everything in the Omniverse paradigm is along with a signature and is verified cryptographically, there’s no need to worry about synchronizers doing malicious things, and I will explain it later. The off-chain part of O-DLT is indeed trust-free. Everyone can launch a synchronizer to get rewards by helping synchronize information.

Features

The O-DLT has the following features:

  • The omniverse token(o-token for short) based on O-DLT is not fragmented into separated parts by the boundary of blockchains but as a whole. If someone has one o-token on Ethereum, he will have an equivalent one on other chains at the same time.
  • The state of the tokens based on O-DLT is synchronous on different chains. If someone sends/receives one token on Ethereum, he will send/receive one token on other chains at the same time.

Workflow

  • Suppose a common user A and the related operation account nonce is $k$.
  • A initiate an omniverse transfer operation on Ethereum by calling omniverse_transfer. The current account nonce of A in the O-DLT smart contracts deployed on Ethereum is $k$ so the valid value of nonce in o-transaction needs to be $k+1$.
  • The O-DLT smart contracts on Ethereum verify the signature of the o-transaction data at an application level. If the verification for the signature and data succeeds, the o-transaction data will be published on the O-DLT smart contracts of the Ethereum side. The verification for the data includes:
    • whether the amount is valid
    • and whether the nonce in o-transaction is 1 larger than the account nonce maintained by the on-chain O-DLT
  • Now, A’s newest submitted nonce in o-transaction on Ethereum is $k+1$, but still $k$ on other chains.
  • The off-chain synchronizers find the newly published o-transaction, and they will find the nonce in o-transaction is larger than the related account nonce on other chains.
  • These synchronizers will rush to deliver this message because whoever submits to the destination chain first will get a reward. There’s no will for independent synchronizers to do evil because they just deliver A’s o-transaction data. (The reward is coming from the service fee or a mining mechanism according to the average number of o-transactions within a fixed time. The strategy of the reward may not be just for the first one but for the first three with a gradual decrease.)
  • Finally, the O-DLT smart contracts deployed on other chains will all receive the o-transaction data, verify the signature and execute it when the waiting time is up. After execution, the underlying account nonce will add 1. Now all the account nonce of account A will be $k+1$, and the state of the balances of the related account will be the same too.

We have provided an intuitive but non-rigorous proof for the ultimate consistency for a better understanding of the synchronization mechanisms.

Reference Implementation

  • An Omniverse Account example: 3092860212ceb90a13e4a288e444b685ae86c63232bcb50a064cb3d25aa2c88a24cd710ea2d553a20b4f2f18d2706b8cc5a9d4ae4a50d475980c2ba83414a796

    • The Omniverse Account is a public key of the elliptic curve secp256k1
    • The related private key of the example is: cdfa0e50d672eb73bc5de00cc0799c70f15c5be6b6fca4a1c82c35c7471125b6
  • Omniverse Fungible Token

  • The implememtation of Omniverse Non-Fungible Token is similiar with the Omniverse Fungible Token.

Security Considerations

Attack Vector Analysis

According to the above, there are two roles:

  • common users who initiate a o-transaction (at the application level)
  • and synchronizers who just carry the o-transaction data if they find differences between different chains.

The two roles might be where the attack happens:

Will the synchronizers cheat?

  • Simply speaking, it’s none of the synchronizer’s business as they cannot create other users’ signatures unless some common users tell him, but at this point, we think it’s a problem with the role common user.

  • The synchronizer has no will and cannot do evil because the transaction data that they deliver is verified by the related signature of others(a common user).

  • The synchronizers will be rewarded as long as they submit valid o-transaction data, and valid only means that the signature and the amount are both valid. This will be detailed and explained later when analyzing the role of common user.

  • The synchronizers will do the delivery once they find differences between different chains:

    • If the current account nonce on one chain is smaller than a published nonce in o-transaction on another chain
    • If the transaction data related to a specific nonce in o-transaction on one chain is different from another published o-transaction data with the same nonce in o-transaction on another chain
  • Conclusion: The synchronizers won’t cheat because there are no benefits and no way for them to do so.

Will the common user cheat?

  • Simply speaking, maybe they will, but fortunately, they can’t succeed.

  • Suppose the current account nonce of a common user A is $k$ on all chains.

  • Common user A initiates an o-transaction on O-DLT smart contracts on Chain P first, in which A transfers 10 o-tokens to an o-account of a common user B. The nonce in o-transaction needs to be $k+1$. After signature and data verification, the o-transaction data(ot-P-ab for short) will be published on O-DLT on Chain P.

  • At the same time, A initiates an o-transaction with the same nonce $k+1$ but different data(transfer 10 o-tokens to another o-account C) on Ethereum. This o-transaction(named ot-E-ac) will pass the verification on O-DLT smart contracts on Ethereum first, and be published.

  • At this point, it seems A finished a double spend attack and the O-DLT states on Chain P and Ethereum are different.

  • Response strategy:

    • As we mentioned above, the synchronizers will deliver ot-P-ab to the O-DLT smart contracts on Ethereum and deliver ot-E-ac to the O-DLT on Chain P because they are different although with the same nonce. The synchronizer who successfully submits the o-transaction first(or in the first few) will be rewarded as the signature is valid.
    • Both the O-DLTs on Chain P and Ethereum will find that A did cheating after they received ot-E-ac and ot-P-ab respectively as the signature of A is non-deniable.
    • We mentioned above that the execution of an o-transaction will not be done immediately and instead there needs to be a fixed waiting time. So the double spend attack caused by A won’t succeed.
    • There will be many synchronizers waiting for delivering o-transactions to get rewards. So although it’s almost impossible that a common user can submit two o-transactions to two chains, none of the synchronizers deliver the o-transactions successfully because of a network problem or something else, we still provide a solution:
      • The synchronizers will connect to several native nodes of every public chain to avoid the malicious native nodes.
      • If it indeed happened that all synchronizers’ network break, the o-transaction will be synchronized when the network recovered. If the waiting time is up and the cheating o-transaction has been executed, we will revert it from where the cheating happens according to the nonce in o-transaction and account nonce.
  • A will be punished(lock his account or something else, and this is about the certain tokenomics determined by developers according to their own situation).

  • Conclusion: The common user maybe cheat but won’t succeed.

Copyright

Copyright and related rights waived via CC0.

Additional Information

We are a team focusing on multi-chain interoperability for years, and we have some experience and open-source contributions in this field. We are always dedicated to improving the convenience and security of the cross-chain experience, and we have proposed the first multi-chain interoperability solution Granted by W3F, with milestones all achieved.
In addition, we will provide a mechanism to make Omniverse Token compatible with current ERC20/ERC721 and native tokens, that is, omniverse tokens could be exchanged on current DEX like Uniswap with others. This is related to an abstract account smart contract mechanism, as it is highly recommended that a single EIP contain a single key proposal or new idea, we won’t describe more details about this point here but still we will provide an implementation of it.