Qointum is the Quantum-Secure Trustless Smart Money powering Web 3.0. Secure and trustless digital money like Qointum is known as cryptocurrency.
Qointum is trustless because it is decentralized, anyone with a home computer can join the Qointum network to help verify transactions of digital money, and all computers operate using the same rules written in machine code. Qointum is also transparent, anyone can read the machine code rules that govern it and everyone knows the total amount of money in existence, ensuring that money can not be arbitrarily created at the whim of officials behind closed doors. With a Qointum wallet you are in full control, your account can never be frozen by authorities or become unavailable due to bank insolvency.
Today, research laboratories around the world are racing to build the first practical quantum computer, and experimental 16-qubit processors have already been constructed. With cryptocurrencies that use elliptic curve cryptography (ECC) such as Bitcoin, a transaction from an account will expose that account’s public key in the signature, which can be used in theory by a quantum computer to derive the associated private key, giving an attacker control over the account. A significant percentage of Bitcoins are currently held in accounts with exposed public keys and are thus vulnerable to quantum attack. Qointum employs “post-quantum” cryptography that is designed to be secure against quantum computers as well as classical computers, thus future-proofing the security of stored value owned by Qointum users from all theorized cryptographic attacks.
Another challenge facing many cryptocurrencies like Bitcoin is the cost of securing their decentralized networks through “proof-of-work” schemes, which require unbounded amounts of processing and thus consume limitless electricity. The Qointum network provides light-weight decentralized security through Queued Delegated Proof-of-Stake which consumes a limited and negligible amount of electricity, so Qointum users can enjoy a high-frequency value exchange with low transaction fees. Qointum is also ready to handle everyday mobile payments as the network provides instant transaction confirmation by means of a consensus heuristic based on delegate voting.
Qointum is also the first and only cryptocurrency to be scripted in the popular language Python which is used by millions of skilled developers worldwide. It’s truly programmable money capable of smart contracts, which are trustless automated rules that govern relationships between parties. Together with full web browser integration and Qointum’s new web-scalable Entangled Chains technology which can be described as decentralized interconnected databases, Qointum is powering the emerging privacy-centric and cryptocurrency-native Web 3.0.
In order to bootstrap the distributed Qointum network, we, NewGamePlus Inc. (“the Qointum team”), must create tokens (“ℚoins”) that operate the network in the first block (“genesis block”) and distribute them to participants (“stakeholders”). ℚoins will be created and distributed in the genesis block at the launch of the official client (Church’s Calculus).
Stakeholders are rewarded with 5% annual stake interest and benefit from the Qointum Pro License as large-scale developers may acquire qoins to pay the Qointum team for their subscription.
In order to facilitate a quick market turnaround for our crowdfunding backers, we will create tokens (“ℚoinXs”) that operate a temporary pre-release network derived from Bitcoin, these tokens can be later exchanged for ℚoins at a fixed conversion rate. All ℚoinXs that will ever be issued will be created in the genesis block at the launch of the temporary pre-release client “Qointum-X”. Unlike Bitcoin, there will be no mining fees on the Qointum-X network, only transaction fees.
90.1% of qoins will be made available to stakeholders through 3 rounds of crowdfunding with periods that correspond to our client release roadmap: Initial, Alpha and Beta. After the Initial Round, a fixed conversion rate between qoin and qoinX will be determined. At the launch of Qointum-X, backers will receive a corresponding amount of qoinXs to trade on markets. Then later, at the launch of our official client, the state of the Qointum-X blockchain will be captured and only holders of qoinXs will be given the opportunity to claim qoins at the conversion rate. Backers can thus only claim qoins if they do not trade away their qoinXs during the intervening period.
Anyone with selected cryptocurrencies such as Bitcoin (“BTC”), Ethereum or Litecoin may obtain qoins during our crowdfunding rounds. In order to receive qoins and participate in pre-launch releases, backers must agree to the Terms of Crowdfunding and send cryptocurrency to the address displayed on our crowdfunding page. For the Initial Round, the pledge reward is ℚ20000 per 1 BTC, with no limit to the number of qoins available. Due to volatility in the market, pledges denominated in other cryptocurrencies may have a non-fixed reward that is determined daily. The Initial Round ends on Aug-14-2017-00:00-PDT, after which we will no longer accept pledges until the next round. As our thanks to early backers, pledges made before July-17-2017-00:00-PDT and July-31-2017-00:00-PDT will receive qoin bonuses of 75% and 30% respectively. As our thanks to strong backers, backers who pledge a total of over 1 BTC and 0.2 BTC (or equivalent summated over all cryptocurrencies) will receive additional qoin bonuses of 75% and 30% respectively.
9.9% of qoins will be allocated to the Qointum team. These qoins will be allocated incrementally in a manner proportional to the backers so that they become available to the Qointum team as qoinXs during the pre-release period. Also, a number of qoinXs proportional to the amount rewarded in the Initial Round will be held out of circulation in publicly designated addresses to be later distributed during the Alpha and Beta rounds as pledge rewards.
The total number of qoins will be normalized at launch of the official client so that there will be at least ℚ1 billion in circulation, this will factor into the conversion rate between qoinX and qoin at launch. For example, if a total of ℚ1 million are rewarded then backers who hold onto all of their qoinXs would end up claiming 1000x more qoins than originally rewarded due to the normalization.
The Qointum client software is available under the following license types:
We believe that transparency of source code is essential for decentralization. Qointum along with modifications to open source projects used by Qointum can be found in our repositories.
\(
%latex shortcuts
\def\concat{\mathbin{\|}}
\def\hash{\mathcal{H}}
\)
The quantum-secure cryptography required by Qointum consists of two main components: digital signatures for transactions to prove identity and asset ownership; and encryption to communicate privately over the Ensemble messaging network, or store private data on the public blockchain.
For digital signatures Qointum uses the Merkle Signature Scheme which is a signature scheme based on hash trees, and gives practical ~32 byte public key sizes (quantum security analysis – security depends on the hashing algorithm and there are no known practical quantum attacks against hashing algorithms in general). The signature scheme is optimized using a fractal tree representation and the Winternitz One-Time Signature Scheme to reduce signature size down to a practical ~1.7 KB for a public key that can verify up to 1 million signatures.
For hashing in the signature scheme Qointum uses the BLAKE2 256-bit hashing algorithm (detailed security analysis).
For encryption Qointum uses the McEliece cryptosystem which is an asymmetric key encryption scheme (quantum security article, presentation slides, and detailed analysis). The cryptosystem is optimized using quasi-dyadic Goppa codes to reduce public key sizes down to a practical ~4 KB. Also, encryption is supplemented by a Fujisaki-Okamoto transformation using the ChaCha symmetric cipher to ensure that ciphertexts are indistinguishable and therefore secure.
Proof-of-Stake is a decentralized security algorithm where “stakeholders”, individuals or groups, who own the blockchain’s scarce asset (eg. ℚoins) are granted the authority to approve transactions of that asset. The amount of authority granted is proportional to the amount of the asset that a stakeholder owns. A stakeholder proves ownership of an asset amount by using their private key to cryptographically sign a message, this signature can be matched to a public address (a string of bytes) and its associated asset amount found on the blockchain. The security comes from the underlying distribution of the asset as it is spread among many stakeholders, thus many individuals partake in transaction approval, and furthermore they have an economic incentive to properly approve transactions as it preserves the value of their proven asset holdings.
Delegated Proof-of-Stake is an improvement where stakeholders may assign “delegates”, individuals or groups, to approve transactions in their place by voting with their stake. Delegates are represented on the network by a public address (a string of bytes) and prove their delegation by similarly signing a message with their private key. Delegates create blocks of transactions, earn interest for their stakeholders, and collect transaction fees. Delegation may be revoked at any time if their stakeholder does not find their service adequate. This is important for network optimization as delegates, catering to stakeholders, will typically have powerful well-connected servers which can handle higher transaction rates, ultimately providing a better service to Qointum users.
In Qointum’s new “Queued Delegated Proof-of-Stake” (QDPoS), delegates register into a queue scheduled randomly using blockchain entropy. While the scheduling method is random in nature it also ensures a more even round-robin ordering, so it scales well from a few delegates to thousands. The queue is scheduled a number of minutes ahead of time, and queue positions can’t change. This short fixed queue enables network optimization while maintaining security, as opposed to a long fixed order where consecutive delegates would have a greater ability to collude and attack by building their own branch.
Stake assigned to a delegate is held as a security deposit so that game-theoretic incentives can be applied by automatically rewarding or penalizing both stakeholders (who may vote maliciously) and delegates. For example, double block creation by a delegate can be penalized to minimize branching (in addition to loss of voter confidence). Also, short ephemeral registrations by stakeholders can be penalized to minimize block overhead.
The number of delegates is limited by the required security deposit amount. Only a small percentage of qoins are expected to be tied up in security deposits earning interest, as qoins must be kept liquid for operational fees and economic activity. The main chain is expected to be operated by ~100 delegates at any given time, however it can support many more.
Stakeholder registration is performed by publishing blocks. This block-level design (as opposed to transaction-level) allows for operation without any delegate authorization. Eg. a dead branch where all delegates have dropped out due to inactivity can be bootstrapped by registering as its only delegate.
To register as (or assign) a delegate one must provide a unique ID, a method of authorization such as a signable address, and a stake proof transaqoin that extracts a balance of at least \(min\_reg\_stake = ℚ50K\)
. This stake is held as a security deposit for the duration of registration. To be scheduled for block creation a delegate must be assigned at least \(min\_stake = min\_reg\_stake \cdot 50 = ℚ2.5M\)
. This gives an upper bound of \(ℚ1B\ /\ min\_stake = 400\)
possible delegates and growing with inflation. However, only ~25% of qoins are expected to be tied up in security deposits, resulting in ~100 delegates.
Registrants may optionally assign stake to (vote for) a delegate who they trust, and in return earn interest at up to a 5% nominal annual rate. In this way the majority of stakeholders may participate in chain security without always being online. Voters specify the % of interest return to give to the delegate, and the % of fees to receive from the delegate (the delegate must also specify agreeable parameters). Delegates are elected in a process similar to single transferable vote, so a stakeholder may vote for multiple delegates in order of preference. To aid in voting the client tracks delegate performance info.
\(1\ hour \approx 100\ delegates\ /\ 120\ blocks\ per\ hour\)
\(ℚ1B \cdot 5\%\ /\ 1M\ blocks\ per\ year = ℚ50\ reward\ per\ block\ average\)
Registration is performed secretly through a commitment scheme in order to mitigate vote cherry-picking by delegates and collusion among voters/delegates.
The registrant first publishes a registration commitment block consisting of a ℚ250 registration fee payment and the hash of their secret registration block. The commitment is then linked into the blockdag by a delegate, and the registration block is then implicitly scheduled to be linked in 1 round later. The registrant then reveals their block by publishing it at some configurable time up to 90 seconds prior to the scheduled time.
module qointum.stakeholder
authorize_stakeholder(id, auth)
authorize a stakeholder to prove ownership of a block or QTC vote
id
– a unique registration ID
auth
– object passed into the authorization handler
pay_reg_fee(balance)
reg_id(name, sep=’:’)
<name><sep>
, returning <name><sep><checksum>
register_delegate(stake, id, auth_handler, reward_handler, interest_request, fee_offer, about, auth=None)
register a delegate on the blockchain
stake
– A balance of qoinel asset to be held as a security deposit for the duration of registration. Must be at least a minimum amount as specified in the qoinel.
id
– a unique registration ID
auth_handler
– A handler to authorize full access to this registration. The handler takes an argument auth
which is an authorization object and returns true if authorization is successful.
reward_handler
– A handler called by the system periodically to reward the stakeholder for creating blocks, or refund the stake deposit. The handler takes arguments (balance, info)
where balance holds the reward amount of qoinel asset, and info
is a dict of information about the reward.
interest_request
– % of interest return that delegate requests from voters
fee_offer
– % of fees that delegate is offering voters
about
– info for public display
auth
– object passed into the current authorization handler to reclaim this ID if it was previously registered
register_voter(stake, id, auth_handler, reward_handler, votes, max_interest_offer, min_fee_request, auth=None)
register a voter on the blockchain
stake
– see register_delegate
id
– see register_delegate
auth_handler
– see register_delegate
reward_handler
– see register_delegate
votes
– vote for (assign stake to) a list of delegates in order of preference
max_interest_offer
– Max % of interest return that voter is offering delegate. Must agree with the delegate for the vote to be accepted.
min_fee_request
– Min % of fees that voter requests from delegate. Must agree with the delegate for the vote to be accepted.
auth
– see register_delegate
Transaqoin to pay registration fee, in a commitment block:
from qointum.stakeholder import pay_reg_fee from qointum.qointract.accounts import BasicAccount account = BasicAccount('__sign__ Q1JvcReZAHTjaUj4pNG6KLzwqJ7SLtJq5JVaJGGvpeVF9Po8AucML') pay_reg_fee(account.withdraw(250))
Transaqoin to perform action, in a registration block:
from functools import partial from qointum.stakeholder import register_delegate, register_voter, reg_id from qointum.qointract import Qointract from qointum.qointract.accounts import BasicAccount #handler defined below for example #from qointum.qointract.accounts import account_authorized def account_authorized(instance_path, account): return (isinstance(account, Qointract) and account.instance_path == instance_path and account.authorized) #When registering the scope is "main". #When the handlers are later unpickled this transaqoin is imported, #so we must check the scope to avoid registering again in error. if __name__ == '__main__': account = BasicAccount('__sign__ Q1JvcReZAHTjaUj4pNG6KLzwqJ7SLtJq5JVaJGGvpeVF9Po8AucML') stake = account.withdraw(50000) #example of registering a delegate register_delegate(stake, reg_id('Mr. FooBar'), #generates id 'Mr. FooBar:VeZ7' partial(account_authorized, account.instance_path), account.deposit, interest_request=0.1, fee_offer=0.1, about="Mr. FooBar - vote for me! - https://foo.bar", #auth=account ) #example of registering a voter register_voter(stake, reg_id(account.instance_id), partial(account_authorized, account.instance_path), account.deposit, votes=['Mr. FooBar:VeZ7'], max_interest_offer=0.1, min_fee_request=0.1, #auth=account )
Transaqoin to authorize for unregistration, in a registration block:
from qointum.stakeholder import authorize_stakeholder, reg_id from qointum.qointract.accounts import BasicAccount account = BasicAccount('__sign__ Q1JvcReZAHTjaUj4pNG6KLzwqJ7SLtJq5JVaJGGvpeVF9Po8AucML') #example of authorizing a delegate authorize_stakeholder(reg_id('Mr. FooBar'), auth=account) #example of authorizing a voter authorize_stakeholder(reg_id(account.instance_id), auth=account)
Delegates are scheduled in randomized rounds with a block generation rate of 30 seconds. They are queued up to 30 minutes (60 delegates) ahead of time.
\(stake\_weight \cdot (6 - count\ of\ missed\ blocks\ in\ last\ 6\ scheduled)\)
, where each delegate has \(stake\_weight = \lfloor assigned\ stake\ /\ min\_stake \rfloor = usually\ 1\)
\(\log{N}\)
bits of blockchain entropy, where \(N\)
is the total weight of all delegates. If the bit string is not less than \(N\)
then it is shifted right with an entropy bit concatenated on the left and tested again, this is repeated until success. If out of entropy bits (negligible chance \(2^{-256}\)
) then the result is taken as \(bit\ string \mod{N}\)
.\(2 \cdot total\_stake\_weight\)
blocks and ensures that every delegate registered at round start has a chance to participate at least once and at most 4 times. To ensure a more even round-robin ordering, selected delegates are removed from the candidate list for a quarter-round wait period. A delegate’s selection weight is doubled for each quarter-round elapsed since their last scheduling (weights carry into the next round). In the final quarter-round, if the number of yet unselected delegates is equal to the number of block positions left, then only those delegates will be considered for selection. The chance of falling back to such a forced selection given ~100 delegates is roughly \((1 - 2^3\ /\ 50)^{50} \approx 0.02\%\)
per delegate, or \(1 - (1 - 0.02\%)^{100} \approx 2\%\)
per round.Do not miss blocks
If a delegate fails to create a valid block on schedule 3 times successively then they will be forcefully unregistered and their stake (not including assigned stake) will be forfeited to the next delegate.
No double block creating
A delegate will be penalized if they are caught double block creating, that is creating a block on another branch with an equal or later time than a block created on the current branch. A whistleblowing delegate may embed a violating signed block header into their block header, the violator will be forcefully unregistered and their stake (not including assigned stake) will be forfeited to the whistleblower. The whistleblower must also embed the chain of headers that connect the violating block to an ancestor no more than 1 round behind.
\(1\ MB\ block\ /\ 30sec + 3\ KB\ update \approx 40\ KB/sec\)
.Qointum’s blockchain is structured as a “blockdag”, a directed acyclic graph of blocks. A block may have multiple parents and children so that branches may be merged. All branches are broadcast to the network, old or improbable branches are pruned as decided by the consensus algorithm.
Branch merging is a feature planned for a future release
Branch merging is a complex scalability feature, however initially the block generation rate will be set low enough so that consensus will be stable.
Blockchain entropy is derived from the hash of “opened commitments”, that is, revealed secrets where delegates can prove that they selected their secret at an earlier date. Each delegate block gives a new 256-bit random number generated from the opened commitments of previous delegates.
The number will be random unless all delegates collude together, so the scheme requires only one honest delegate. To mitigate collusion, a delegate will be penalized if their commitment is opened early by a whistleblowing delegate, this also encourages secrets to be unguessably random. A delegate could also opt to miss their block and not reveal their secret (eg. to avoid a loss in a lottery draw), however the next number would remain random and such an action would incur a loss of voter confidence.
\(\hash(b_i)\)
and also opens their previously committed secret \(\hash(a_i)\)
by revealing \(a_i\)
. The opened commitments of the current and last round of delegates are then hashed together in sequence \(a = \hash(a \concat a_i)\)
to give the random number \(a\)
.No opening commitments early
A whistleblowing delegate may reveal another delegate’s secret early by embedding it in their block header. The penalty is the same as for double block creation.
The consensus chain is the longest delegate block chain found by the consensus heuristic Maximally Vetted Delegate Chain. This heuristic is efficient as it only requires block headers and a select number of signatures, making it suitable for SSV.
This consensus heuristic follows the actions of delegates starting from the genesis block. Delegates vet for the integrity of a chain by appending blocks, which implicitly vets new delegates so that integrity can be assumed even if older delegates drop out of the chain. Chains with the largest amounts of vetted registered stake are favoured.
\(stake\_weight\)
(usually 1)\(vetting\_factor = prior\ delegate\ score\ at\ reg\ time\ /\ total\_scores\_at\_reg\_time\)
. The registrant’s delegate score is then added to by \(vetting\_factor \cdot (assigned\ stake\ /\ min\_stake)\)
. Thus if all prior delegates append a block then a registrant and their delegate become fully vetted.\(total\_scores\_at\_reg\_time = 0\)
, so the first delegate is immediately fully vetted\(vetting\_factor \cdot (assigned\ stake\ /\ min\_stake)\)
, this amount is also removed from the branch score and \(total\_scores\_at\_reg\_time\)
of future registrants.\(branch\ score\ /\ total\ branch\ scores\)
– a highly vetted branch may be stepped many times before another gets a single step\((5K\ current + 5K\ ephemeral\ /\ year) \cdot 3\ KB\ signature\ \approx 15\ MB + 15\ MB\ /\ year\)
. All current delegates (~100) are verified. Current voters (~5K) are verified with a random 10% (500). Past registrants are verified with a random 10% spread evenly across 1-month periods. The amount of signature data required to verify 1 year is therefore \(100+500+5K \cdot 10\% \approx 3\ MB\)
.\(x\%\)
of stake registered, a smaller percentage of vetted delegates \(x/(x+100)\%\)
could spin off their own branch and attain a similar score by registering and vetting the remaining \(100-x\%\)
of stake using fake balances. The fake branch would be slowed for a day as inactive delegates drop out, so to catch up the attackers could try to regularly miss their consensus branch blocks, however they risk their reputation as their stakeholders could revoke delegation. To remedy a successful attack, a new checkpoint would be included in SSV clients.\(x\%\)
of stake registered, an attacker could compromise \(x\%\)
of unused private keys (through hacking or purchase) of earlier delegates and create a fake branch. New nodes would then accept payment from the compromised historical balances. Such an attack would be rare given the substantial number of keys required which all must share an active block range, and that there is an unregistration fee to penalize unused keys, and that the reputation of delegates is tied to their keys which may stay in use for months or years. To remedy a successful attack, a new checkpoint would be included in all clients.Simplified State Verification (SSV) is a protocol used by light clients such as smartphones and entangled chains which do not have the resources to perform full verification, they instead rely on a weaker form of verification that requires no prior transaction or state knowledge.
The SSV client uses the consensus heuristic to determine the consensus branch, block headers and signatures are checked but the heuristic enables the client to forgo downloading and verifying full blocks. Once the consensus has been decided, the SSV client can then make requests to an SSV-providing node and verify its responses.
Quorum Transaction Confirmation (QTC) provides a trade-off between less security and faster turn-around time. It is explicitly requested in transactions and can be trusted for small transactions of under ℚ10K.
QTC is performed by a quorum composed of the next 20 queued delegates (10 minutes worth). Quorum members vote to confirm consensus on a transaction so that the involved parties can have confidence that the transaction will not be undone. Consensus is unlikely to shift as quorum members are held to their vote by the risk of penalization and loss of voter confidence (similar to double block creation).
No double or false-positive voting
Quorum members will be penalized if they are caught double voting (voting for two different states), or false-positive voting (voting in the majority then creating a block that is not a descendant). The method of penalization is similar to double block creation.
\(20\ delegates \cdot 3\ KB\ vote \cdot 1\ QTC/sec \approx 60\ KB/sec\)
.Checkpoints are block hashes that serve as consensus guideposts for new nodes joining the network. New nodes examine block headers to determine a candidate consensus branch before downloading and verifying full blocks, and a candidate must contain all checkpoints. Any blocks may serve as checkpoints, they are defined in a plaintext JSON file and are acquired from sources that the user trusts (eg. bundled with the client or downloaded from a community website).
Consensus state (registrations, signatures, scores) is included in the storage state, so new nodes only need to download a storage state snapshot corresponding to a checkpoint in order to determine the delegate queue.
Note
The Qointum team will provide a checkpoint file shortly after launch once all initial stakeholders have registered.
Nodes that have been offline and not tracking the consensus branch for a period of weeks to months are susceptible to rare attacks and may require a recent checkpoint. As chains mature through game-theoretic incentives and become operated by reputable long-standing delegates, older checkpoints can be relied upon.
In order to maintain sane node hardware requirements (given the large quantum-secure signatures), the blockchain is pruned and compressed daily. By leveraging the consensus algorithm we target a blockchain size upper bound of 20 GB, growing by 1 GB per year. Nodes have the option of archiving old blockchain data and automatically distributing it to other nodes that demand a higher level of blockchain validation.
There is also qointract storage with its state snapshots and deltas, which varies between chains and typically uses ~20 GB.
\(1\ MB\ block \cdot 20K\ blocks\ /\ week \cdot 1\ week \approx 20\ GB\)
.\(0.2\ KB\ block\ header \cdot 1M\ blocks\ /\ year + 15\ MB\ signatures\ /\ year \approx 0.2\ GB\ /\ year\)
.Sharding is a scalability feature that allows for the transaction processing and state storage of a single chain to be divided among all nodes. The chain qoinel specifies the total number of shards, and delegates specify the number of shards they wish to administer. All delegates participate in the master chain which coordinates transactions and rollbacks, and also participate in their shard chains which are randomly assigned upon registration using blockchain entropy. The storage state is divided into shards by evenly partitioning the space of instance path hashes so that each qointract instance and system state group will reside on a single shard. Delegates then only need to process and verify transactions that read or write to their shard partition, and state from other partitions is embedded into transactions as SSV proofs verifiable against the master chain.
Sharding is a feature planned for a future release
Sharding is a complex scalability feature, however entangled chains already provide scalability as a kind of read-only sharding. Also, much of the machinery developed for entangled chains can be later shared with sharding.
\(5 \cdot 100\ B\ tx + 3\ KB\ update \approx 4\ KB/sec\)
.Qointum transactions are scripted via Transaqoins and Qointracts which are trustless automated rules written in Python that govern relationships between parties. Qointracts are “smart contracts” written by developers and published on the blockchain like reusable persistent code libraries. These contracts are invoked by transaqoins which are “smart transactions” or single-use ephemeral instructions. Qointum is designed so that transactions and contracts can be specified entirely within Python code and then operated on through the OS command-line from the developer’s preferred Python IDE.
Scripts are published as Python bytecode, and the human-readable source code may optionally be included. Whether the source code compiles to its corresponding bytecode is not verified by delegates due to the resource strain, however it can be verified independently through a command-line utility.
Smart transactions/contracts are simply Turing machines, there are many pre-existing languages that are adaptable to our needs and are already adept at interfacing humans with Turing machines.
We must restrict our language choices to those that provide a sandboxed virtual machine that ensures security and determinism when executing untrusted code. Each node must be able execute the same sequence of transactions and result in the same state, without crashing the client or compromising its host device. Also, as a shared-state Turing machine the virtual machine must be able to restrict access to code and data protected by the individual developers and users. Python, specifically its implementation PyPy, can meet these requirements.
Qointum is leveraging Python’s significant network effect – according to the TIOBE Index Python is in the top 5 virtual-machine languages and there are an estimated millions of Python developers world-wide. Qointum is also leveraging millions of man-hours of labour on the core Python language and third-party libraries, allowing us to focus on just developing a cryptocurrency, not a language.
PyPy provides bindings into native and efficient C code, Qointum’s API makes use of this feature when performing computationally intensive cryptographic methods. Also, PyPy provides just-in-time (JIT) compilation of interpreted Python bytecode to native machine code for improved performance.
Transaqoins are Python scripts that specify transactions, they are compiled and published on the blockchain as single-use ephemeral instructions.
Invalid transactions
A transaction is invalid if:
Do not forward invalid transactions
Forwarding transactions where the fee payment can’t be verified against a recent state may result in a ban from peers.
No fee refund for invalid transactions
If an invalid transaction error occurs then the state will be rolled back to just after the fee and deposit payment. In this way delegates are guaranteed payment for processing transactions. However, if the error is handled by a runtime context then a refund on the deposit is still possible.
Nodes prioritize transactions for forwarding and block inclusion by the fee per KB, weighted by the transaction preferences broadcast by each delegate prior to becoming active.
\((base\ fee\ /\ tx\ size + data\ fee) \cdot data\ weight + (runtime\ factor\ /\ tx\ size) \cdot runtime\ weight\)
module qointum.transaqoin
pay_tx_fee(balance, base_fee, runtime_deposit, runtime_factor, data_fee=1)
Pay the transaction fee. Payment must be a balance of qoinel asset.
This function must be called at the start of a transaqoin within a short runtime length of ℚ1, this length allows for the verification of several signatures.
This function also builds a rollback state by committing all active qointracts.
refund_tx_runtime()
Determine the final runtime fee and refund the unused runtime deposit. Returns a balance of qoinel asset.
This function must be called at the end of a transaqoin and the returned refund must be deposited within a short runtime length of ℚ0.1 (suggesting that the receiving account should already be authorized).
This function also commits all active qointracts.
In order to maintain sane node hardware requirements, the Qointum network bounds transaqoin runtime length and memory usage through hard limits and fees. These hard limits and fees are allowed to vary between entangled chains in order to maintain separate economies.
The transaction fee consists of a base fee, a data fee per KB of transaction size, and a runtime factor that applies to the runtime instruction fees. An upfront runtime deposit must also be made since the runtime length of the transaqoin is unknown. The transaqoin specifies the fee and runtime deposit amounts, how to extract the fee and deposit, and how to refund any unused deposit.
Failure to pay transaction fee
A transaction is invalid if:
Unless otherwise noted, all Python bytecode instructions have a runtime fee of µℚ2.5, which is based on a 3 GHz CPU processing ~40 million bytecode instructions per second at a fee of ℚ1 per ~10 CPU-milliseconds.
IMPORT_NAME(namei)
module qointum.utils
compress(data)
uncompress(data)
data
using Snappy. Fee amount grows with the size of data
. Fee amount is pending benchmarking. hash(data)
data
using BLAKE2. Fee amount grows with the size of data
. Fee amount is pending benchmarking. verify_sig(sig)
sig
must be the result of a __sign__ postprocessor command. Fee amount is ℚ0.1.After compiling a transaqoin, the client needs to postprocess the resulting bytecode before it can be published on the blockchain in order to append data such as signatures or other bytecode. The client scans the transaqoin bytecode for string constants of the form __command__ [args…] , and replaces them with the processed result. Arguments are specified in the familiar GNU command-line format.
__package__ [options] <src>
Create a package from all pathnames matching src
which is a glob relative to the current working directory. The result is a byte string of a pickled and compressed tree of directories (dict) and files (byte string). Any .py
modules found in the glob are compiled and the resulting .pyc
is included instead. The package __init__.py
is optional. All directories found in the glob are recursed so that a package may contain subpackages importable via import <package path>.<subpackage>...
. When recursing, dot-prefixed (.
) hidden dirs/files are ignored.
All parties signing the transaction must have a copy of the files being packaged as they must all arrive at the same transaqoin bytecode after compiling and postprocessing its source code.
options
--include-hidden
-h
.
) hidden dirs/files --include-src
-s
.py
module source files --uncompressed
-u
--working-dir=<dir>
-w=<dir>
src
is relative to dir
, the resulting file tree will then have all paths relative to dir
. dir
is itself relative to the current working directory. __sign__ <address>
Sign the transaction using the corresponding private key of the public key address
. The result is a byte string of the form <sign command> <signature>
. If the client wallet does not have the private key then no action is taken.
The client supports multi-party transactions by providing methods to export signatures from transaqoin bytecode and merge signatures into transaqoin bytecode.
The transaction hash is taken prior to appending any signatures, and as signatures include their original commands they can be later replaced with their originals in order to verify the hash. This ensures that the hash can’t be changed in transit (avoiding the “transaction malleability” problem) as it represents all data covered by the signatures.
from qointum.transaqoin import pay_tx_fee, refund_tx_runtime from qointum.qointract.accounts import BasicAccount #authorize a pre-existing account for withdrawal my_account = BasicAccount('__sign__ Q1JvcReZAHTjaUj4pNG6KLzwqJ7SLtJq5JVaJGGvpeVF9Po8AucML') #Withdraw 10 qoins to pay tx fee and recipient. #The returned object is a conserved balance of asset type Qoin. balance = my_account.withdraw(10) #this must be called early within a short runtime length pay_tx_fee(balance, base_fee=1, runtime_deposit=1, runtime_factor=1) #Pay recipient 5 qoins, may be existing account or a new one. #Crediting a balance by an amount transfers the amount into a new balance and returns the new balance. recipient_account = BasicAccount('Q1K3LRJSg3xYB8V2APZB2Ch2YDLPTZ71bVNsQAmwV37cyV5UMgakH') recipient_account.deposit(balance.credit(5)) #Refund unused runtime deposit, must be deposited within a short runtime length. #Also deposit any unused balance. #Debiting a balance transfers the other balance into itself and returns itself. my_account.deposit(balance.debit(refund_tx_runtime())) #printing a qointract outputs a json representation of its key/value store print(my_account) #"qointum.qointract.accounts.BasicAccount:Q1JvcReZAHTjaUj4pNG6KLzwqJ7SLtJq5JVaJGGvpeVF9Po8AucML": { # "balance": "ℚ3.75" #}
Qointracts are Python classes that specify contracts, they are compiled and published on the blockchain as reusable persistent code libraries.
module qointum.qointract
class Qointract(instance_id=None, life_term=timedelta(days=90)) abstract extends Model, AttrProtect
a contract with storage access
ctor
instance_id
– See instance_id. None may be specified for singleton qointracts.life_term
– a timedelta that determines the storage term __sys_init__(instance_id) protected
create_asset(asset) protected final
asset
which is a subclass of Asset. Returns the new asset type which is an instance of asset
. This qointract will govern the new asset type, storing any garbage collected or discarded asset amounts. instance_id final
instance_path final
is_new protected final
pay_storage_fee(amount) protected
qualname final
All qointract data is stored in a LevelDB key/value database and protected through a storage abstraction model in Python.
For SSV we require that the key/value store holds a single root state hash as well as a merkle path of hashes leading to the value to prove it is part of the state. The keys and values must be structured within the database in a way that these hashes can be efficiently updated when a single value is changed. For this purpose we store a radix tree in the database.
To facilitate bringing new full nodes up to speed on the chain, a compressed week-old storage snapshot is maintained as well as hourly state deltas. These snapshots and deltas are automatically distributed to new nodes. To catch up, new nodes simply apply the deltas to the snapshot and then process the remaining transactions over the past hour.
In order to maintain sane node hardware requirements, the Qointum network bounds qointract storage through hard limits and fees. These hard limits and fees are allowed to vary between entangled chains in order to maintain separate economies.
The storage fee is payed by qointract instances on a KB-day basis. Every qointract specifies a life term in days of up to 1 year, and how to extract the storage fee. To ensure a tight upper bound on storage size, the fee must be paid upfront upon storage allocation. At the end of a storage term the fee is distributed to all registered delegates. This effectively locks away qoins for the term allowing for long-term storage discounts while maintaining a fixed upper bound on storage size. Storage is automatically expanded and contracted as expired storage terms are renewed only if necessary.
Failure to pay storage fee
If a qointract fails to pay its storage fee when expanding storage then the transaction is deemed invalid. If a qointract fails to pay its storage fee when automatically renewing an expired storage term then the qointract instance is garbage collected and all stored data is lost.
For the storage fee on the main chain we target a storage size upper bound of 250 GB with a typical usage of ~10 GB as we only expect ~5% of qoins per year will be used for storage. Given 1 billion qoins and a minimum life term of 2 weeks, the maximum storage fee is then ℚ0.3 per KB-day (ℚ2 per KB-week), and we give a 5x scaling discount down to ℚ0.06 per KB-day (ℚ0.4 per KB-week) for a 1 year term. A typical account balance is ~0.1 KB so storage on a 1 year term would cost upfront ℚ2.
\(min\ fee\ \cdot (term\ /\ max\ term)^{-1/2}\)
clamped between the min/max fees. This structure gives a KB-day fee discount to longer terms while maintaining a lower upfront cost for shorter terms. This is the default chain setting and may be changed to any equation in the qoinel.Qointum provides an abstraction layer to the underlying key/value store. This layer conveniently binds code to storage data, protects qointract metadata, and enforces asset conservation.
Qointracts are instantiated with IDs to become qointract instances, each referenced in the key/value store by a unique instance path <qointract>:<instance id>
. Qointracts may require that their instance IDs include a Qointum address (eg. Q1Jvc...
) for authorization, in this way a user may hold a large variety of account types and asset balances under one address.
A qointract is a model, which is a class that defines a data layout in storage. A model holds a set of fields which may in turn hold other models recursively. In this way a key path can be generated that looks like <field>.<field>...
which is appended to the instance data path. A model may also hold field lists/dicts in order to utilize the underlying key/value store to its fullest extent.
Qointract pickle support is disabled by default
Pickling often results in unexpected behaviour as only non-model attributes are preserved and the underlying storage can completely change between saving/loading. After thorough testing one may enable pickle support by setting a class attribute picklable = True
.
<instance path>:data
qointract
- 128 B, instance id
- 128 Bqointract
- Python identifier path with ‘.’ separatorinstance id
- Unicodefield
- Python identifier\(address = \text{'Q'} \concat type \concat base58(data)\)
type
- base58 string1
:\(data = public\ key \concat checksum\)
\(public\ key = merkle\ root\ (32\ B) \concat \log{max\ sigs}\ (1\ B)\)
\(checksum = \hash(public\ key)[:4\ B]\)
\(length = 1 + 1 + \lceil 37\ B \cdot \log{256}\ /\ \log{58} \rceil = 53\ characters\)
Q1JvcReZAHTjaUj4pNG6KLzwqJ7SLtJq5JVaJGGvpeVF9Po8AucML
z
is reserved to extend the type space (eg. z11
, z12
… zz1111
)module qointum.qointract
class Model abstract
module qointum.qointract.fields
class Field(perms=None) abstract
perms
is a (list of) access specifiers to be applied when this is a top-level field of a qointract. class ObjectField(**kwargs) extends Field
Holds any Python object, which is pickled into storage. kwargs
is forwarded to the base class.
An object field resolves to the key <field>
.
class AssetField(**kwargs) extends Field
Holds a balance of any asset type. If no balance is explicitly set into the field then by default it holds an empty balance of the qoinel asset. kwargs
is forwarded to the base class.
All balance objects are tracked in a special list <instance path>:assets.<field paths>
. This list is managed privately by the system so that assets remain conserved.
class ModelField(model, **kwargs) extends Field
Holds an instance of the class model
. This field does not directly store a value, instead its name becomes part of the field path for fields in the held model. kwargs
is forwarded to the base class.
A model field resolves to the keys <field>.<fields of held model>
class FieldList(field, **kwargs) extends Field
Maintains a list of field
in the underlying storage, providing a performance advantage over storing a list
in an ObjectField. kwargs
is forwarded to the base class.
Items resolve to the keys <field>.items.<indices>
, the indices are stored in byte form. List size is also available at <field>.length
.
class FieldDict(field, **kwargs) extends Field
Maintains a dictionary of field
in the underlying storage, providing a performance advantage over storing a dict in an ObjectField. kwargs
is forwarded to the base class.
Keys are available in a list at <field>.keys.<hashes>
, the hashes are stored in byte form. Values resolve to the keys <field>.values.<hashes>
. Dict size is also available at <field>.length
.
class AnyField(**kwargs) extends Field
Automatically selects the field type depending on the object set into it, allowing for heterogeneous lists and dicts, and recursive models like tree nodes. kwargs
is forwarded to the base class.
The disadvantage of the any field is that it must store its field type alongside the value. The value resolves to the key <field>.value
. The field type is pickled at <field>.type
.
from qointum.qointract import Qointract, Model from qointum.qointract.fields import ( ObjectField, AssetField, ModelField, FieldList, FieldDict, AnyField) from qointum.asset import Asset class Account(Qointract): #ObjectField name = ObjectField() #AssetField balance = AssetField() #ModelField class Address(Model): street = ObjectField() city = ObjectField() address = ModelField(Address) #FieldList address_history = FieldList(ModelField(Address)) #FieldDict investments = FieldDict(AssetField()) #AnyField class BitUsd(Asset): pass class BitEur(Asset): pass foreign_currencies = FieldDict(AnyField()) class HeapNode(Model): value = ObjectField() left = AnyField() right = AnyField() exchange_bids = ModelField(HeapNode) exchange_asks = ModelField(HeapNode) def test_fields(self, payment): #ObjectField self.name = "John Smith" #AssetField self.balance.debit(payment) #ModelField city = self.address.city #FieldList self.address_history.append(self.Address("Main Street", "Capital City")) last_street = self.address_history[0].street #FieldDict self.investments['tax free savings'] = self.balance.credit(100) #AnyField self.foreign_currencies['bitusd'] = self.create_asset(self.BitUsd).mint(100) self.foreign_currencies['biteur'] = self.create_asset(self.BitEur).mint(100) self.exchange_bids.value = 42 self.exchange_bids.left = self.HeapNode() self.exchange_bids.left.value = 41 self.exchange_bids.left.right = self.HeapNode() self.exchange_bids.left.right.value = 40
An account is any qointract that is picklable and supports the following methods:
authorized
deposit(balance, info=None)
info
is a dict of information about the deposit request. withdraw(amount, info=None)
amount
of an asset out of the account, returning a new balance that holds the asset amount. info
is a dict of information about the withdraw request.module qointum.qointract.accounts
account_authorized(instance_path, account)
account
is the correct instance and has been authorized (eg. by a signature) class BasicAccount(address, account_id=None, **kwargs) extends Qointract
A simple account holding a balance of qoinel asset. The instance ID is constructed from <address>_<account_id>
. If address
is signed then this account will be authorized. kwargs
is forwarded to the base class.
balance protected
from qointum.qointract import Qointract from qointum.qointract.fields import AssetField from qointum.utils import sig_address, verify_sig class BasicAccount(Qointract): perms = {'super': 'public'} picklable = True _balance = AssetField(perms='protected') @public def __init__(self, address, account_id=None, **kwargs): self.address = sig_address(address) self.account_id = account_id super().__init__('_'.join(filter(None, [self.address, self.account_id])), **kwargs) if verify_sig(address): self.authorize() @public @property def authorized(self): return self.has_perms({'withdraw': 'public'}) @public def deposit(self, balance, info=None): self._balance += balance @protected def authorize(self): self.grant_perms({'balance': 'public', 'withdraw': 'public'}) @protected @property def balance(self): return self._balance.value @protected def withdraw(self, amount, info=None): return self._balance.credit(amount) @protected def pay_storage_fee(self, amount): return self.withdraw(amount)
An asset is a conserved value that can only be created or destroyed in a manner defined by its governing qointract. Assets can be transferred in a conserved manner from one balance object to another across both runtime memory and persistent storage. Qointracts may access their balances through the storage object model or enumerate them in a list.
Transactions must conserve assets
A transaction is invalid if it results in a non-zero balance being garbage collected. Non-zero balances must be explicitly discarded which transfers control of the balance to its governing qointract.
Each chain has its own “qoinel asset” (“ℚoin” on the main chain) accessed via from chain.self.qoinel import Asset
.
module qointum.asset
class Asset abstract extends AttrProtect
An asset type can be created by defining a subclass and then instantiating it from its governing qointract via Qointract.create_asset. The asset should be defined within its governing qointract so that the qointract has access to its protected methods.
An asset type is uniquely defined by its combination of qualname, instance_path, and instance_id.
destroy(balance) protected
discarded protected final
instance_id final
instance_path final
mint(amount) protected
amount
of this asset, returning a balance that holds the new amount prefix
ℚ
results in ℚ3.75
. qualname final
suffix
' FOO'
results in 3.75 FOO
. class balance(asset) extends AttrProtect
Holds an amount of asset and ensures its conservation. The constructor creates an empty zero-value balance of type asset
, which is an instance of Asset. The balance may be debited with another non-empty balance of the same asset type (such as the result of asset.mint) to increase its value, and credited with an amount to decrease its value.
The amount is represented internally as a Fraction of arbitrary precision, qointracts that hold their assets in higher precision have to pay higher storage fees.
Balances support all comparison ops (==
, <
, …) and arithmetic ops (+
, -
, …) with numbers and other balances, however these ops are unable to modify asset amounts, instead the balance is just converted to a number. Balances can also be converted to the standard number types (int
, long
and float
) with some precision loss.
+=(balance)
asset
credit(amount)
amount
out of this balance and returns a new balance holding the amount debit(balance, amount=None)
amount
(or all if none) from balance
into this balance and returns this balance. balance
must have the same asset type as this balance. discard()
value
from qointum.qointract import Qointract from qointum.qointract.fields import AssetField, ObjectField from qointum.qointract.accounts import account_authorized from qointum.asset import Asset class FooCompany(Qointract): perms = {'super': 'public'} class Share(Asset): perms = {'super': 'public'} @public @property def suffix(self): return ' FOO' account = ObjectField() account_path = ObjectField() share = ObjectField() share_pool = AssetField() @public def __init__(self, account=None, **kwargs): super().__init__(**kwargs) #no instance id, FooCompany is a singleton if self.is_new: if not isinstance(account, Qointract) or not account.authorized: raise TypeError("new FooCompany must have authorized account") self.account = account self.account_path = account.instance_path self.share = self.create_asset(self.Share) self.share_pool = self.share.mint(100) self.authorize() else: if account_authorized(self.account_path, account): #the stored account is not accessible because an instance is already active, #so we must use the passed in instance self.account = account self.set_dirty('account', False) self.authorize() @public def buy_share(self, payment): if payment != 10: raise ValueError("share costs ℚ10") if not self.share_pool: raise RuntimeError("no more shares") self.account.deposit(payment) return self.share_pool.credit(1) def authorize(self): self.grant_perms({'maintenance': 'public'}) def maintenance(self): #recover any garbage-collected shares self.share_pool += self.share.discarded def pay_storage_fee(self, amount): return self.account.withdraw(amount)
Attribute permissions allow developers to protect certain code pathways and the data they modify. For example, an account qointract could require that the user provide a valid address signature in order to access the “withdraw qoins” method.
Any class that inherits from AttrProtect (such as Qointract) is provided with contextual access protection for its attributes. Permissions are specified as dict items ’<target>’: ‘<access specifier>’ using the familiar C++ style access specifiers and friend classes. Permissions can also be granted or revoked dynamically at runtime.
The permission target may be set to:
super
(controls access to its inherited attributes)self
(controls access to all attributes)By default all attributes have private access, making them only accessible within the context of their class or any nested / outer classes.
For attributes that reference methods or nested classes, only read/call access can be granted. These attributes can only be changed within the private context or be overridden by subclasses.
public
protected
<class or function>
qointum.qointract.accounts.BasicAccount
) final
The attribute can not be changed after initialization. Once granted this specifier can not be revoked.
If the target is a method then it can not be overridden by a subclass. Private methods are automatically declared as final.
If the target is the current class then it can not be subclassed.
module qointum.qointract
class AttrProtect abstract
any class that inherits from this class is provided with contextual access protection for its attributes
grant_perms(perms) protected
perms
is a dict of permissions ’<target>’: ‘<access specifier>’. has_perms(perms) protected
revoke_perms(perms) protected
In order to publish a qointract on the blockchain a package name must be reserved by registration. This reserved name allows the package to be imported via import chain.self.<package name>
. There is a package registration fee to discourage squatters from reserving a large number of names. The package registration fee is ℚ100 per 3 months.
Package terms are automatically renewed and may be cancelled through unregistration. However, unregistration does not take effect for 2 weeks in order to protect users from unexpected changes, and during this period the original registrant can reclaim their name by providing authorization. Users are warned when importing a package that is undergoing unregistration.
Failure to pay package fee
A package will be unregistered if it fails to pay its fee.
module qointum.package
register_package(name, auth_handler, pay_fee_handler, packager, auth=None)
register a package on the blockchain
name
– a unique package name
auth_handler
– A handler to authorize full access to this package. The handler takes an argument auth
which is an authorization object and returns true if authorization is successful.
pay_fee_handler
– A handler called by the system periodically to collect package fees. The handler takes arguments (amount, info)
where amount
is the required number of qoinel asset, and info
is a dict of information about the fee. The handler must return a payment balance.
packager
– instance of the packager to initialize storage
auth
– object passed into the current authorization handler to reclaim this name if it was previously registered
unregister_package(name, auth)
unregister a package
name
– the package name
auth
– object passed into the authorization handler
The packager is similar to a filesystem, it handles qointract publishing and importing and provides guarantees about how its qointracts are allowed to be changed. The packager is decided upon package registration and can only be changed by re-registering the package which requires a wait period. The registered packager can be imported via a special module path from <package path>.packager import Packager
.
Failure to pay packager storage fee
If the packager fails to pay its storage fee when automatically renewing an expired storage term then the package itself will be unregistered.
Users of a qointract should review its packager and its package __init__.py
The packager has the ability to completely change the underlying qointract at any time, so users should review it and prefer one that guards against nexpected changes such as ImmutablePackager. Users should also review the optional package __init__.py
(if it exists) which is executed upon import.
module qointum.package.packagers
class Packager(**kwargs) abstract extends Qointract
kwargs
is forwarded to the base class. class ImmutablePackager(account=None, **kwargs) extends Packager
This packager ensures that its qointracts can not be changed once published. For new instances account must be valid and authorized. For publishing access account
must be authorized. kwargs
is forwarded to the base class.
isdir(path)
isfile(path)
listdir(path)
read(path)
write(filetree, dirpath=None) protected
Write a tree of files to a directory path. Files are immutable, so this method raises FileExistsError
if a file path already exists. This method is publicly accessible with authorization.
filetree
– a tree of directories (dict) and files (byte string), or the output of a __package__ postprocessor command
dirpath
– directory in which to write, will be created if it doesn’t exist
class MutablePackager(account=None, **kwargs) extends ImmutablePackager
This packager allows its qointracts to be changed at any time using the authorized account. For new instances account
must be valid and authorized. kwargs
is forwarded to the base class.
write(filetree, dirpath=None) protected
from functools import partial from qointum.transaqoin import pay_tx_fee, refund_tx_runtime from qointum.qointract import Qointract from qointum.qointract.accounts import BasicAccount from qointum.package import register_package #handler defined below for example #from qointum.qointract.accounts import account_authorized def account_authorized(instance_path, account): return (isinstance(account, Qointract) and account.instance_path == instance_path and account.authorized) #we could define a custom packager class here #class MyPackager(Packager): ... from qointum.package.packagers import ImmutablePackager #When registering the scope is "main". #When the handlers and packager class are later unpickled this transaqoin is imported, #so we must check the scope to avoid registering again in error. if __name__ == '__main__': account = BasicAccount('__sign__ Q1JvcReZAHTjaUj4pNG6KLzwqJ7SLtJq5JVaJGGvpeVF9Po8AucML') pay_tx_fee(account.withdraw(2), base_fee=1, runtime_deposit=1, runtime_factor=1) register_package('foobar', partial(account_authorized, account.instance_path), account.withdraw, packager=ImmutablePackager(account), #auth=account ) account.deposit(refund_tx_runtime())
from chain.self.foobar.packager import Packager account = BasicAccount('__sign__ Q1JvcReZAHTjaUj4pNG6KLzwqJ7SLtJq5JVaJGGvpeVF9Po8AucML') pay_tx_fee(account.withdraw(2), base_fee=1, runtime_deposit=1, runtime_factor=1) #switch working dir to 'foobar' and create a package from its contents, publish as subpackage 'v1' Packager(account).write('__package__ -w foobar .', 'v1') account.deposit(refund_tx_runtime())
#import our published qointract from chain.self.foobar.v1 import Foo account = BasicAccount('__sign__ Q1JvcReZAHTjaUj4pNG6KLzwqJ7SLtJq5JVaJGGvpeVF9Po8AucML') pay_tx_fee(account.withdraw(2), base_fee=1, runtime_deposit=1, runtime_factor=1) #use our qointract foo = Foo(account) foo.bar('new data') account.deposit(refund_tx_runtime())
import pickle from qointum.package.packagers import Packager from qointum.qointract import Qointract, Model from qointum.qointract.fields import ObjectField, ModelField, FieldDict, AnyField from qointum.qointract.accounts import account_authorized from qointum.utils import uncompress class ImmutablePackager(Packager): perms = {'super': 'public'} @protected class Directory(Model): entries = FieldDict(AnyField()) account = ObjectField(perms='protected') account_path = ObjectField(perms='protected') root = ModelField(Directory, perms='protected') @public def __init__(self, account=None, **kwargs): super().__init__(**kwargs) if self.is_new: if not isinstance(account, Qointract) or not account.authorized: raise TypeError("new packager must have authorized account") self.account = account self.account_path = account.instance_path self.authorize() else: if account_authorized(self.account_path, account): #the stored account is not accessible because an instance is already active, #so we must use the passed in instance self.account = account self.set_dirty('account', False) self.authorize() @public def isdir(self, path): cur = root for name in filter(None, path.split('/')): cur = cur.entries.get(name) if not isinstance(cur, self.Directory): return False return True @public def isfile(self, path): cur = root names = list(filter(None, path.split('/'))) for name in names[:-1]: cur = cur.entries.get(name) if not isinstance(cur, self.Directory): return False file = cur.entries.get(names[-1]) return isinstance(file, bytes) @public def listdir(self, path): cur = root for name in filter(None, path.split('/')): cur = cur.entries.get(name) if not isinstance(cur, self.Directory): raise FileNotFoundError('No such directory: ' + path) return list(cur.entries.keys()) @public def read(self, path): cur = root names = list(filter(None, path.split('/'))) for name in names[:-1]: cur = cur.entries.get(name) if not isinstance(cur, self.Directory): raise FileNotFoundError('No such file: ' + path) file = cur.entries.get(names[-1]) if not isinstance(file, bytes): raise FileNotFoundError('No such file: ' + path) return file @protected def authorize(self): self.grant_perms({'write': 'public'}) @protected def write(self, filetree, dirpath=None): if isinstance(filetree, bytes): filetree = pickle.loads(uncompress(filetree)) cur = root cur_path = [] for name in filter(None, (dirpath or '').split('/')): cur = cur.entries.setdefault(name, self.Directory()) cur_path += name if not isinstance(cur, self.Directory): raise FileExistsError("Unable to create directory: " + '/'.join(cur_path)) self.__write(filetree, cur, cur_path) def __write(self, filetree, cur, cur_path): for name, entry in filetree.items(): if isinstance(entry, dict): next = cur.entries.setdefault(name, self.Directory()) cur_path += name if not isinstance(next, self.Directory): raise FileExistsError("Unable to create directory: " + '/'.join(cur_path)) self.__write(entry, next, cur_path) cur_path.pop() else if isinstance(entry, bytes): if cur.entries.get(name) != None: raise FileExistsError("Unable to create file: " + '/'.join(cur_path + [name])) cur.entries[name] = entry else raise TypeError("Unknown entry type") @protected def pay_storage_fee(self, amount): return self.account.withdraw(amount)
Tasks and signals allow for endogenic transactions, that is transactions generated by internal processes as opposed to exogenic transactions received over the network. Endogenic transactions are treated exactly the same as exogenic transactions, delegates prioritize them by their fees and they are not guaranteed to be included in a block.
Tasks are used to schedule transactions at specific times or periodically, and signals are used to notify listeners about a state change. Sometime after a task is due or a signal has been triggered, at the discretion of the active delegate, the task/listener handler is executed as a transaqoin. Similar to an exogenic transaction, the handler must pay its transaction fee within a short runtime length so that delegates can efficiently verify the payment and prioritize it.
Entangled chains may also listen to signals on their partner chains. The active delegate polls its partner chains using SSV to detect signals, if a listener has been signalled then the delegate provides an SSV proof of the signal along with the listener handler.
The task/listener fee scales the same as the storage fee and has the same min/max life term with fees ranging from ℚ1 - ℚ0.2 per day. A task/listener may be cancelled at any time, and after cancelling the life term is protected where the original owner can reclaim their term by providing their ID and authorization.
Failure to pay task/listener fee
A task/listener will be cancelled if it fails to pay its fee.
Failure to pay task/listener handler tx fee
If a task/listener handler fails to pay its tx fee then the active delegate may include it in their block in which case the task/listener itself will be cancelled and its term fees distributed.
module qointum.task
class crontab(minute=’*‘, hour=’*‘, day_of_week=’*‘, day_of_month=’*‘, month_of_year=’*‘)
provides detailed cron-like scheduling
minute
– A (list of) integers in range [0,59] that represent the minutes of an hour. May also be a crontab pattern (eg. every half hour minute='*/30'
, or on the hour and every two minutes of the last half hour minute='0,30-59/2'
).
hour
– A (list of) integers in range [0,23] that represent the hours of a day. May also be a crontab pattern (eg. every two hours hour='*/2'
, or at midnight and every two office hours hour='0,9-17/2'
).
day_of_week
– A (list of) integers in range [Sunday=0, Saturday=6] that represent the days of a week. May also be a crontab pattern (eg. every weekday day_of_week='mon-fri'
, or every even numbered day day_of_week='*/2'
).
day_of_month
– A (list of) integers in range [1,31] that represent the days of the month. May also be a crontab pattern (eg. every even numbered day day_of_month='*/2'
, or the first and third weeks of the month day_of_month='1-7,15-21'
).
month_of_year
– A (list of) integers in range [1,12] that represent the months of the year. May also be a crontab pattern (eg. the first month of every quarter month_of_year='*/3'
, or every even numbered month month_of_year='*/2'
).
class Task(id=None)
Holds a task ID. If id
is none then one will be generated when schedule is first called.
cancel(auth)
cancel the scheduled task
auth
– object passed into the authorization handler
schedule(handler, auth_handler, pay_fee_handler, period=None, delay=None, life_term=timedelta(days=90), auth=None)
schedule a task for execution
handler
– the function to execute when the task is due, must be a transaqoin (pay tx fees, etc.)
auth_handler
– A handler to authorize full access to this task. The handler takes an argument auth
which is an authorization object and returns true if authorization is successful.
pay_fee_handler
– A handler called by the system periodically to collect task fees. The handler takes arguments (amount, info)
where amount
is the required number of qoinel asset, and info
is a dict of information about the fee. The handler must return a payment balance.
period
– Execute task every period amount of time. If none then the task will execute only once. The recurring period is specified as a timedelta or for finer control a crontab.
delay
– Delay first execution for an amount of time specified as a timedelta. If none then the task will be delayed for the amount of time specified by period
.
life_term
– reserve object for term (determines fees)
auth
– object passed into the current authorization handler to reclaim the ID if it is in use
module qointum.signal
class Listener(id=None)
Holds a listener ID. If id
is none then one will be generated when listen is first called.
listen(signal, instance_id, handler, auth_handler, pay_fee_handler, life_term=timedelta(days=90), auth=None)
execute a function when a signal is triggered
signal
– the signal to listen to, must be a type
instance_id
– the ID of the object to listen to, as provided when triggered
handler
– the function to execute when triggered, must be a transaqoin (pay tx fees, etc.) and accept all args provided by the signal
auth_handler
– A handler to authorize full access to this listener. The handler takes an argument auth
which is an authorization object and returns true if authorization is successful.
pay_fee_handler
– A handler called by the system periodically to collect listener fees. The handler takes arguments (amount, info)
where amount
is the required number of qoinel asset, and info
is a dict of information about the fee. The handler must return a payment balance.
life_term
– reserve object for term (determines fees)
auth
– object passed into the current authorization handler to reclaim the ID if it is in use
unlisten(auth)
stop listening to the current signal
auth
– object passed into the authorization handler
class Signal(instance_id, *args, **kwargs) abstract protected extends AttrProtect
A signal can be triggered to notify listeners about a state change.
A signal can be created by defining an empty subclass. The signal should be defined within the class (eg. a qointract) that requires access to its trigger. The signal should specify an attribute args
as a list of argument names that will be provided to listeners (for self-documentation only). Eg. class foo_done(Signal): args=['name']
. Listeners need access to the signal type object, so the signal should be decorated with the appropriate access specifier (eg. @public
).
A signal can be triggered by instantiating it which notifies listeners of the object with ID instance_id
. args
and kwargs
are forwarded to the listener handlers. Eg. self.foo_done(self.instance_id, 'bar')
.
from functools import partial from datetime import timedelta from random import randrange from qointum.qointract import Qointract, Model from qointum.qointract.fields import ObjectField, ModelField, FieldList from qointum.qointract.accounts import account_authorized from qointum.task import Task, crontab from qointum.signal import Signal from qointum.transaqoin import pay_tx_fee, refund_tx_runtime, RuntimeContext class Lottery(Qointract): perms = {'super': 'public'} account = ObjectField() account_path = ObjectField() task = ObjectField() pot = AssetField() class Ticket(Model): reward_account = ObjectField() tickets = FieldList(ModelField(Ticket)) @public class draw_done(Signal): args=['winner'] @public def __init__(self, instance_id, account=None, **kwargs): super().__init__(instance_id, **kwargs) if self.is_new: if not isinstance(account, Qointract) or not account.authorized: raise TypeError("new lottery must have authorized account") self.account = account self.account_path = account.instance_path self.task = Task() self.authorize() else: if account_authorized(self.account_path, account): #the stored account is not accessible because an instance is already active, #so we must use the passed in instance self.account = account self.set_dirty('account', False) self.authorize() @public def buy_ticket(self, payment, reward_account): if payment != 10: raise ValueError("ticket costs ℚ10") if not isinstance(reward_account, Qointract): raise TypeError("invalid account") if not self.task.scheduled: self.start_draw() #we have a player so start the draw if self.task.due: raise RuntimeError("drawing in progress, please try again later") self.pot += payment self.tickets.append(self.Ticket(reward_account)) def authorize(self): self.grant_perms({'cancel_draw': 'public'}) def start_draw(self): self.task.schedule(self.draw, partial(account_authorized, self.account.instance_path), self.account.withdraw, #period=timedelta(hours=1), #draw every hour #draw every 4 hours and every office hour on weekdays period=crontab(minute=0, hour='*/4,9-17', day_of_week='mon-fri'), delay=timedelta(hours=1), #delay first draw for at least an hour auth=self.account ) def cancel_draw(self): #refund payments for e in self.tickets: refund = self.pot.credit(10) try: with RuntimeContext(max_length=0.1): e.reward_account.deposit(refund) except Exception as e: self.account.deposit(refund) print("problem with account: ", e) del self.tickets[:] self.task.cancel(auth=self.account) def draw(self): #begin task handler, pay tx fee pay_tx_fee(self.account.withdraw(2), base_fee=1, runtime_deposit=1, runtime_factor=1) #reward 70% to winner, 30% to lottery owner winner = self.tickets[randrange(len(self.tickets))] winner_id = "Unknown" prize = self.pot.credit(self.pot * 0.7) try: with RuntimeContext(max_length=0.1): if not isinstance(winner.reward_account, Qointract): raise TypeError("invalid account") winner_id = winner.reward_account.instance_path winner.reward_account.deposit(prize) except Exception as e: self.account.deposit(prize) print("problem with account: ", e) self.account.deposit(self.pot) #restart lottery del self.tickets[:] self.task.cancel(auth=self.account) #trigger signal self.draw_done(self.instance_id, winner_id) #end task handler, refund tx runtime self.account.deposit(refund_tx_runtime()) def pay_storage_fee(self, amount): return self.account.withdraw(amount)
from functools import partial from datetime import datetime from qointum.transaqoin import pay_tx_fee, refund_tx_runtime from qointum.qointract import Qointract from qointum.qointract.accounts import BasicAccount, account_authorized from qointum.signal import Listener from chain.self.lottery import Lottery from chain.self.logger import Logger #we assume there is some logger qointract that just appends log entries to a FieldList def log_draw(account, logger, winner): #begin listener handler, pay tx fee pay_tx_fee(account.withdraw(2), base_fee=1, runtime_deposit=1, runtime_factor=1) #log the winner logger.log('{}: {}'.format(datetime.now(), winner)) #end listener handler, refund tx runtime account.deposit(refund_tx_runtime()) #When setting up the listener the scope is "main". #When the handler is later unpickled this transaqoin is imported, #so we must check the scope to avoid setting up again in error. if __name__ == '__main__': account = BasicAccount('__sign__ Q1JvcReZAHTjaUj4pNG6KLzwqJ7SLtJq5JVaJGGvpeVF9Po8AucML') pay_tx_fee(account.withdraw(2), base_fee=1, runtime_deposit=1, runtime_factor=1) #log the lottery with instance id 'foo' logger = Logger('Lottery_foo', account) listener = Listener() listener.listen(Lottery.draw_done, 'foo', partial(log_draw, account, logger), partial(account_authorized, account.instance_path), account.withdraw, #auth=account ) account.deposit(refund_tx_runtime())
Runtime contexts allow for invalid transaction errors to be localized and handled, and also allow for the runtime length of arbitrary code to be limited. For example, an account deposit method may error because the account can’t pay its storage fees or it gets stuck in an infinite loop, and if left unhandled the entire transaction would be rolled back with tx fees wasted. By wrapping the deposit method in a new runtime context, these errors can be handled.
When an invalid transaction error occurs all changes to qointracts, assets, and storage made within the runtime context are rolled back. The transaction may then continue after handling the error.
A runtime context is created by wrapping a code block in a with
statement that instantiates transaqoin.RuntimeContext
.
None
object.To enter a runtime context all active qointracts must support pickling
All active qointracts must be able to have their state reverted and be reused without unexpected behaviour. Not all qointracts support pickling due to its complexity. If a qointract does not support pickling then if possible move its instantiation into the body of the context block (it will be garbage collected on context exit).
Test qointracts for partial execution cases
Unexpected behaviour may result when a qointract method is run again after erroring out mid-execution due to an imposed runtime length limit. Qointracts must not write to global attributes as they are not restored after an error.
module qointum.transaqoin
class RuntimeContext(max_length=None)
max_length
is the max allowed runtime length of all execution within the context measured in units of the qoinel asset.A transaqoin that uses a runtime context to guard against losing its runtime deposit on error:
from qointum.transaqoin import RuntimeContext my_account = BasicAccount(...) pay_tx_fee(my_account.withdraw(2), base_fee=1, runtime_deposit=1, runtime_factor=1) try: #localize any errors of account load/save and deposit, and limit runtime length with RuntimeContext(max_length=0.1): recipient_account = BasicAccount(...) recipient_account.deposit(my_account.withdraw(10)) except Exception as e: print("problem with account: ", e) my_account.deposit(refund_tx_runtime())
Qointum HTTP (QHTTP) is a next-generation “Web 3.0” protocol that serves entire websites over a decentralized peer-to-peer blockchain network. It is implemented as a web browser plugin and activated for URLs that begin with the protocol identifier qhttp://
.
The website contents are stored on-chain and served via SSV requests distributed over multiple peers. The retrieved HTML / Javascript documents may contain qoinapps which in turn make further SSV requests to dynamically populate the page and provide user interaction.
A Web 3.0 model-view-controller (MVC) Python framework is included in order to handle HTTP requests entirely on the client side. When a user visits a website, their QHTTP client downloads and runs MVC code from the website’s corresponding chain. This MVC code routes any requested URLs to views, which in turn generate HTTP responses containing either HTML rendered from on-chain templates and qointract data, or on-chain media (eg. CSS, images). This framework can be understood as a client-side Web 3.0 variant of Django (the server-side framework that powers this website).
Entangled chains enable Qointum to scale up across the web providing a vast array of services. Entangled chains are like separate databases with connected value, or filters on the Qointum network, nodes can selectively process transactions and store data of only desired services. Through this selective ability, entangled chains can operate with separate transaction and storage fees depending on the economic value of data transferred and stored on each chain.
Each chain is initialized with a qoinel that generates its qoinel asset used for operational fees and specifies a list of entangled chains and other parameters. A chain may bootstrap its qoinel asset from any number of entangled partner chains using SSV.
SSV proofs can be embedded into transactions enabling entangled chains to communicate with each other and exchange value. For example:
Note
The main chain does not initially have any entangled chains, it permits SSV requests from other chains but not transaction SSV proofs due to the resource strain on nodes. However, the main chain qoinel will enable the Qointum community to change the list of entangled chains as “core” chains may become established (such as a redundant storage service).
The active delegate does not perform SSV requests for missing values
The local state can change between the time that a transaction was preprocessed and the time that the active delegate processes it, potentially changing the required set of SSV values. For this reason entangled qointracts must operate in epochs where for each input the corresponding set of SSV values remains unchanged within an epoch.
The chain qoinel is a core kernel composed of founding smart contracts that define the chain, govern its operations, and govern its interactions with entangled chains. The genesis block of each chain contains an initial revision of its qoinel. More specifically, the qoinel governs the creation and destruction of its qoinel asset that is used for operational fees, specifies chain parameters such as stake interest rate, restricts delegate and package registration, and restricts packages imported off entangled chains. The qoinel may include decentralized governance such as polls to allow for portions of the qoinel itself to be updated.
Note
Prospective stakeholders should carefully review a chain’s qoinel before taking a position in its qoinel asset. Distribution of the asset may be complex as it may be bootstrapped from other assets on entangled chains.
Chains have 2 modes of entanglement to partner chains, strong and weak (default).
In strong entanglement when a consensus shift occurs on the partner, nodes always re-verify past transaction SSV proofs (up to 4 hours prior). This securely ties value to the partner, however a misbehaving partner with frequent consensus shifts may invalidate blocks on the chain causing collateral damage.
In weak entanglement nodes only re-verify proofs with an age lower than some limit (default 10 minutes), reducing collateral damage. Proofs older than the age limit are just assumed to be valid and may be re-verified only to warn the user about misbehaving partners.
In order to handle a consensus shift on an entangled partner chain gracefully with minimal collateral damage, the active delegate may additionally create a purge block. Purge blocks cut, squash, and branch the chain in order to remove the invalidated SSV proof and any dependent transactions. In this way the consensus score for the branch (vetting and length) and most transactions are kept intact, as opposed to a trivial rollback where all progress would be lost.
Select an encrypted file to store and generate at least \(24 \cdot 365\)
random suffixes (one for every hour for a year) and hashes \(\hash(file \concat suffix)\)
. Build a merkle tree out of alternating leaves where one leaf is \(\hash(file \concat suffix)\)
and the next leaf is \(null\)
, and publish the merkle root in a qointract while distributing over BitTorrent the file + merkle tree without leaves + partial suffixes. Load up the qointract balance with \(24 \cdot 365\)
qoins and the qointract rewards ℚ1 to anyone who provides a merkle path which includes a missing leaf. Provide enough bits in the partial suffixes so that on average, the network of nodes hashing the file will guess a full suffix once per hour. The qointract keeps track of which leaves have already been guessed, and adjusts the reward according to the hash rate moving average.
The owner must monitor the blockchain in case the hash rate drops to low levels, at that point the owner should retrieve the file via direct download from any available nodes using qointracts that parties update for each MB transferred.
Qointum’s messaging network “Ensemble” is used for private communication that doesn’t require state consensus or long-term data storage. Ensemble is a decentralized peer-to-peer network that provides post-quantum encrypted communication and plausible deniability as to the recipient identity. Ensemble supports asynchronous communication as messages are given a time to live (TTL), so a message sent earlier can be received at any later time. Python and Javascript APIs are provided for developer access to Ensemble.
Nodes connect to random peers and prefer peers who deliver messages on topics of interest, while underperforming peers are dropped. Nodes advertise topics that their peer neighbourhood is interested in, and this advertisement becomes more fuzzy as hop distance to the neighbourhood increases. The more general the topic, the less recipient identity revealed. Routing requires only a logarithmic number of hops with respect to the size of the network, similar to the Kademlia protocol. Ensemble may be accessed from Tor to provide physical routing privacy.
For private communication messages may be optionally encrypted, and to identify the sender messages may be signed. An Ensemble ID consists of an encryption key pair and a signature key pair, messages may be sent to and from IDs. To facilitate recovery of lost IDs, the hierarchical deterministic (HD) wallet holds a tree of IDs generated from a mnemonic seed.
To mitigate spam each message must include a proof of stake, this ensures incentive for an operational service. Due to this asset dependence each chain operates its own Ensemble network.
Each message is assigned a priority which determines the amount of bandwidth and storage allocated to transmit it. The priority is based on peer neighbourhood interest in the topic, the TTL, and the amount of stake proven relative to other messages in queue. Messages with short TTLs are prioritized, so as nodes reach their storage limits longer TTLs are bumped off the network.
Stake proof requires unique asset sources
Proven stake is tracked by its asset sources (ie. asset keys), an asset source can’t be used in more than one proof.
module qointum.ensemble
class Ensemble(chain=None)
Provides access to the Ensemble network on chain
. If chain
is none then the default chain (specified in the client config) is used.
class Message
a message passed to the listener handler
data – the payload
topics – a list of associated topics
from – source ID
to – destination ID
ttl – time to live in seconds
join()
listen(handler, topics=None, to=None)
handler
when a new message arrives from the network with any matching topics
(or if none then accept all topics) and decrypt it using destination ID to
. The handler takes an argument msg
which is the new Message. post(data, topics=None, from=None, to=None, ttl=None)
post a message to the network
data
– the payload
topics
– a list of associated topics
from
– source ID, sign the message using this ID
to
– destination ID, encrypt the message using this ID
ttl
– Time to live in seconds. Nodes will attempt to store the message for this amount of time and prefer shorter ttls.
An Ensemble ID can be advertised as a public payment address (eg. on a website), yet when recorded on the blockchain all payments are made to new addresses that have no discernible link to the advertised address. The payer uses the ID to discreetly request a single-use address from the recipient over Ensemble.
Note
Qointum stealth payments just provide convenient operational security rather than a new cryptographic algorithm.
Stealth payee service, run by the client qointumd
:
from qointum.ensemble import Ensemble from qointum.wallet import Wallet, IdBank my_id = IdBank.get('my web shop') ensemble = Ensemble() def on_message(msg): if not msg.from: return #the payer must include their generated id (encryption key) #creates a quantum-secure address quickly but it can only be signed a small number of times single_use_address = Wallet.create_address(parent='stealth payments', max_sigs=256) ensemble.post(single_use_address, topics=['stealth payments'], from=my_id, to=msg.from, ttl=5) ensemble.listen(on_message, topics=['stealth payments'], to=my_id) ensemble.join()