xelis-project / xelis-blockchain

A private blockDAG with Homomorphic Encryption and Smart Contracts
373 stars 89 forks source link
blockchain blockdag cryptocurrency elgamal homomorphic-encryption privacy ristretto255 rust smart-contracts xelis

XELIS

All rights reserved.

A from scratch blockchain made in Rust and powered by Tokio, using account model. XELIS is based on an event-driven system combined with the native async/await and works with a unique and from scratch p2p system. This allow to be notified on any events happening on the network and to be able to react to them instead of checking periodically for updates.

BlockDAG is enabled to improve the scalability and the security of the network. Homomorphic Encryption using ElGamal is used to provide privacy on transactions (transfered amounts) and balances.

ElGamal cryptosystem was choosen because it's a well known and studied encryption algorithm which has homomorphism features. ElGamal is fast and is used in combination with Ristretto255 curve to provide a good level of security (~128 bits of security). Homomorphic operations available using ElGamal are addition/subtraction between ciphertexts and/or plaintext and multiplication against plaintext value.

Account Model allows to have a more flexible system than UTXO model and to have a better privacy because there is no need to link inputs and outputs, which provide real fungibility. It allows also the fast-sync feature to only download the last state of the blockchain instead of downloading all the history.

Pruning system is also available to reduce the size of the blockchain by removing old blocks and transactions.

We also aims to enabled Smart Contracts support in the future.

We provide different built-in networks:

Acknowledgments

@cchudant:

@deroholic:

Thank you to every people testing actively the code base, honest miners and every future contributors!

Main features

The main features of XELIS are the following:

Objectives

The main objectives of XELIS are:

Others objectives in mind are:

Config

Network

Daemon

Wallet

BlockDAG

XELIS use a blockDAG with following rules:

Topoheight represents how many unique blocks there is in the blockchain ordered by DAG.

A block ordered is a valid and executed one.

Topoheight order is unstable and may change until the blocks are in the stable height.

Longest chain is the one selected by nodes. But for tips branches conflicts, cumulative difficulty is used to select the main chain.

Homomorphic Encryption

Homomorphic Encryption (HE) will allow to add privacy on transactions and accounts by doing computation while staying in encrypted form. Each balances, transaction assets values are in encrypted form and nobody can determine the real value of it except involved parties.

Mining

Mining capabilities of XELIS are a bit differents from others chains because of standards being not implemented. Each job send to a miner is a MinerWork instance in hex format.

The MinerWork is in following format:

The total block work size should be equal to 120 bytes. Header work hash is the immutable part of a block work, its a hash calculated using Blake3 hashing algorithm with the following format as input:

The header work has to be equal to 73 bytes exactly and its hash to 32 bytes.

WARNING: Miner key is not included in the immutable of the block work (aka header work hash). This is done so a block template can be generic and easily updatable for any miner without re-generating a new block template each time.

For pool development, you must verify that the miner public key in a received share is yours as it can be updated.

All hashes are calculated using the Blake3 hashing algorithm except the Proof-Of-Work hash, which use xelis-hash.

POW Hash should be calculated from the MinerWork format and compared against the target difficulty.

NOTE: It is recommended to use the GetWork WebSocket server to be notified of new block work and submit correct work.

Mining jobs from GetWork are only sent when a new block is found or when a new TX is added in mempool. Miners software are recommended to update themselves the block timestamp (or at least every 500ms) for best network difficulty calculation.

Client Protocol

XELIS integrate along with BlockDAG a way to accept multiple times the same TX and only execute it one time. Instead of excluding the whole block because we have a collision with another blockDAG branch for a TX, we just don't execute the TX and keep its hash.

The same TX can be contained in multiple blocks only if:

Also, for more security, user account should only do TXs on the same chain/tip to prevent any orphaned TX. An orphaned TX can happens when two differents TXs (but same owner) with the same nonce are sent in two differents branchs.

During the generation of the DAG order (linking unique topoheight to a block hash), the first block being ordered will execute the TX first.

This feature allows to accept others branch tips even if transactions are the same and prevent more orphans blocks when branches are merged.

Transaction

Transaction types supported:

At this moment, transactions are public and have the following data. Field Type Comment
owner PublicKey Signer of this transaction
data TransactionType Type with data included of this transaction
fee Integer Fees to be paid by the owner for including this TX
nonce Integer Matching nonce of balance to be validated and prevent any replay TX attack
signature Signature Valid signature to prove that the owner validated this TX

Transactions support any registered asset natively.

To prevent any replay attack or double spending, each TX should include a nonce that match the account balance. After each TX, the nonce is incremented by 1.

Integrated Address

Integrated address are base address with custom data integrated. For example, you can integrate in it a unique identifier that will be integrated in the future transaction done using it. Its helpful to determine easily which account to link a transaction with an account/order on service side.

Maximum data allowed is 1KB (same as transaction payload).

Every data is integrated in the transaction payload when using an integrated address.

P2p (Encrypted Network)

All transfered data are using a custom Serializer/Deserializer made by hand to transform a struct representation in raw bytes directly. This serialization is done using the fixed position of each fields and their corresponding bits size.

Before sending a packet, we're encrypting it using ChaCha20-Poly1305 algorithm to prevent network traffic analysis and authenticate each transfered data.

Every data transfered is done through the Packet system which allow easily to read & transfer data and doing the whole serialization itself.

The connection for a new peer (took from the queue or a new incoming connections) is executed through a unique tokio task with the same allocated buffer for handshake. This prevents any DoS attack on creating multiple task and verifying connection.

When the peer is verified and valid, we create him his own tasks. One for reading incoming packets and one for writing packets to him. By separating both directions in two differents task it prevents blocking the communication of opposed side.

For transactions propagation, we keep in cache last N transactions sent or received from a peer to not send the same data twice during propagation.

The daemon also have 3 tokio tasks running:

Pruning Mode

This allows anyone who want to run a light node to reduce the blockchain size by deleting blocks, transactions and versioned balances. The pruned topoheight can only be at a Sync Block and behind at least PRUNE_SAFETY_LIMIT blocks of the top topoheight.

For wallets connected to a pruned node, you can't retrieve transactions history and miner rewards which happened before the pruned topoheight. But your balances are still up-to-date with the chain and if your wallets already synced them, they stay in your wallet database.

The security of the chain is not reduced as all your blocks were already verified by your own node locally.

Fast Sync

Fast sync mode allow you to sync really fast the necessary data only to run a correct and valid version of the chain. For this we request a peer to send us its chain state at a stable point, which include all accounts nonces, assets, balances, top blocks. So in future, when the chain will be really heavy, anyone can still join it by using fast sync system, which is compatible with the pruning mode.

WARNING: You should use fast sync mode only with a trusted peer, because they can send you a potential fake chain.

Boost Sync

This is requesting the full chain to others nodes, but faster. Boost sync mode can be enabled using --allow-boost-sync-mode. This mode use more resources but sync much faster. It is faster because it's requesting blocks to sync in parallel, instead of traditional synchronization that would just request one block, verify it, execute it, repeat. It's not enabled by default to prevent too much load on nodes.

This is the perfect mix between Fast sync and traditional chain sync, to have the full ledger while being faster.

Packets

This parts explains the most importants packets used in XELIS network to communicate over the P2p network.

Key Exchange

Key Exchange is the real first packet to be sent when creating a new connection. This allow to exchange symetric encryption keys between peer to establish an encrypted communication channel over TCP.

Currently, we are using ChaCha20-Poly1305 algorithm to encrypt / decrypt every packets.

This packet can be sent later to rotate the key of a peer. This is currently done every 1 GB of data sent.

We're using two different symetric keys for encryption per Peer. One key is from us, to encrypt our packet, and the other key is to decrypt peer's packets.

Handshake

Handshake packet must be the first packet sent with the blockchain state inside to upgrade a connection to a peer. If valid, the peer will send the same packet with is own blockchain state.

Except at beginning, this packet should never be sent again.

Ping

Ping packet is sent at an regular interval and inform peers of the our blockchain state. Every 15 minutes, the packet can contains up to MAX_LEN sockets addresses (IPv4 or IPv6) to help others nodes to extends theirs peers list.

Chain Sync

We select randomly a peer which is higher in height from the peers list than us and send him a chain request.

The chain request includes last CHAIN_SYNC_REQUEST_MAX_BLOCKS blocks hashes of our chain with theirs topoheight espaced exponentially. This data is used by the select peer to try to find a common point with our chain and his own (block hash must be at same topoheight as other peer). If selected peer found a common point, he add up to CHAIN_SYNC_RESPONSE_MAX_BLOCKS blocks hashes ordered by block height.

Through the "ask and await" request object system, we ask the complete block (block header with transactions included) and add it to chain directly.

Chain sync is requested with a minimum interval of CHAIN_SYNC_DELAY seconds.

Block Propagation

Block propagation packet contains the block header only. Its sent to all peers who have theirs height minus our height less than STABLE_LIMIT. To build the block, we retrieve transactions from mempool. If a transaction is not found in the mempool, we request it from the same peer in order to build it.

Transaction Propagation

Transaction propagation packet contains the hash only to prevent sending the TX. Its also backed by a cache per peer to knows if the transaction was already received from him / send to him.

Storage

All theses data are saved in plaintext.

Tree Key Type Value Type Comment
transactions Hash Transaction Save the whole transaction based on its hash
blocks Hash Block Header Save the block header only based on its hash
blocks_at_height Integer Array of Hash Save all blocks hash at a specific height
extra Bytes No specific type Save the highest topo height, pruned topoheight and TIPS
topo_by_hash Hash Integer Save a block hash at a specific topo height
hash_by_topo Integer Hash Save a topo height for a specific block hash
cumulative_difficulty Hash Integer Save the cumulative difficulty for each block hash
assets Hash Integer Verify if an assets exist and its registration height
rewards Integer Integer Save the block reward
supply Integer Integer Calculated supply (past + block reward) at each block
difficulty Hash Integer Difficulty for each block
tx_blocks Hash Array of Hash All blocks in which this TX hash is included
balances Custom Integer Last topoheight of versioned balance
nonces Public Key Integer Store the highest topoheight of versioned nonce
versioned_balances Custom Versioned Balance Key is composed of topoheight + asset + public key
versioned_nonces Custom Versioned Nonce Key is composed of topoheight + public key

NOTE:

The database engine used is sled. It may changes in future.

Current overhead per block:

At this moment with current implementation, minimal overhead per new account is 208 bytes for keys and 56 bytes for values:

An optimized version could be done to reduce further the disk usage by creating pointers. Instead of saving multiple times the whole Public Key (32 bytes), we create a pointer table to which a u64 value is assigned. And we store this u64 id instead of the whole Public Key, asset..

Wallet

Wallet keep tracks of all your transactions on chain, all your assets you own.

When creating a new wallet, it generate a new random secure "master key" which will be encrypted by a password hashed. This master key allows to change easily the password of your wallet because you only have to save new encrypted version of it.

The master key is also the one which will be able to decrypt/encrypt all your wallet storage.

This way allow to save securely and easily data on any device.

Password hashing algorithm used is Argon2id with a configuration of 15 MB and 16 iterations.

Storage

Wallet implement a fully-encrypted storage system with following features:

Exception for assets list which has its key encrypted to be able to retrieve them later.

Hash algorithm used is Blake3 for keys / tree names. The random salt generated is a 64 bytes length. This simple system prevent someone to read / use the data without the necessary secret key.

Data Type and Value

This protocol allows to transfer data through a custom wallet address called integrated address. It will simply integrate encoded data in the wallet address which can be used to send specific data to the wallet when creating a transaction. Each transaction can reserve up to 1 KB of space (for encrypted data transfering for example).

You can create simple service / communication on chain through wallets while staying anonymous and in encrypted form.

Actually, you can have following values through API:

And these types:

API

Http Server run using Actix Framework and serve the JSON-RPC API and WebSocket.

JSON-RPC

JSON-RPC is available on /json_rpc route on RPC server address that you set (or default one). For a much more detailed API, see the API documentation here.

WebSocket

WebSocket allow JSON-RPC call and any app to be notified when a specific event happens on the daemon. It is running on the same route (/json_rpc) route / RPC server address.

Example to subscribe to a registered event in the WebSocket connection:

{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "subscribe",
    "params": {
        "notify": "new_block"
    }
}

You can notify to several events, just do a request for each event you want. The daemon will send you every events happening as long as you don't unsubscribe or close the WebSocket.

Example to unsubscribe to a specific event:

{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "unsubscribe",
    "params": {
        "notify": "new_block"
    }
}

Daemon

Events availables to subscribe on the daemon API are:

Wallet

Events availables to subscribe on the wallet API are:

XSWD

XSWD (XELIS Secure WebSocket DApp) Protocol is a WebSocket started on unique port 44325 and path /xswd for easy findings from dApps. Its job is to provide an easy to access and secure way to communicate from a desktop/CLI wallet to any dApp (software or in-browser/websites directly).

It's based on the JSON-RPC API and have exact same methods for easy compabitility, the only exception is how verification is done. On a traditional RPC-Server, if authentication is enabled, you must provide a username/password.

XSWD stay open but request a manual action from user to accept the connection of the dApp on the XSWD Server. When accepted, the dApp can requests JSON-RPC methods easily and the user can set/configure a permission for each method. If no permission is found for a request method, it will be prompted/asked to the user for manual verification.

XSWD also have the ability to sends JSON-RPC requests to the daemon directly. For this, set the prefix node. in front of daemon requests, it will not be requested to the user as it's public on-chain data. For wallets RPC methods, set the prefix wallet. which will requests/use the permission set by the user.

DApp can also request to sign the ApplicationData to persist the configured permissions on its side and then provide it when user would reconnect later.

First JSON message from the dApp must be in following format to identify the application:

{
    "id": "0000006b2aec4651b82111816ed599d1b72176c425128c66b2ab945552437dc9",
    "name": "XELIS Example",
    "description": "Description example of up to 255 characters",
    "url": "https://xelis.io",
    "permissions": {}
}

You can also add signature field and provide signed permissions if your dApp requested a signature from wallet in previous connection.

If dApp is accepted by user through XSWD, you will receive the following response:

{
    "id": null,
    "jsonrpc": "2.0",
    "result": true
}

Otherwise, an error like this will be sent and the connection will be closed by the server:

{
    "error": {
        "code": -32603,
        "message": "Invalid JSON format for application data"
    },
    "id": null,
    "jsonrpc": "2.0"
}

How to build

Building this project requires a working Rust (stable) toolchain.

It's expected to be cross-platform and guaranteed to work on Linux, Windows, MacOS platforms.

Build from sub project

Go to one of following folder you want to build from source: xelis_daemon, xelis_miner or xelis_wallet. To build a release (optimized) version: cargo build --release

Build from workspace

To build a specific binary from workspace (parent folder) directly, use the option --bin with xelis_daemon, xelis_miner or xelis_wallet as value. Example: cargo build --release --bin xelis_miner

To build all at once just use cargo build --release

You can also build a debug version (just remove --release option) or run it directly from cargo: cargo run

Build from Docker

To build using Docker, use the following command, using the app build argument to chose which project to build: docker build -t xelis-daemon:master --build-arg app=xelis_daemon .

Funding

XELIS is a community driven project and is not funded by any company or organization. To helps the development, the success and provide a better support of XELIS, we set a dev fee percentage starting at 10% on block reward.

Current dev fee curve is as following: