diff --git a/bridge/docs/BFT_finality.md b/bridge/docs/BFT_finality.md new file mode 100644 index 0000000..131117b --- /dev/null +++ b/bridge/docs/BFT_finality.md @@ -0,0 +1,182 @@ +# Byzantine Fault Tolerance Finality + +The main purpose of this document is to describe the problems that may have a BFT algorithm and the solutions existing in some the most popular PoS chains. In particular we describe the Finality solution presented by Cosmos, Eth 2.0 and Polkadot. + +## Byzantine Generals Problem + +The Byzantine Generals Problem can be stated as follows. Imagine you are an army distributed at different positions to attack the enemy base. Imagine that the attack only works if x number of the generals of the different distributions in the army agree on the same decision, either attack or retreat. However, you can not communicate your decision in the distance, i.e., the only way to communicate the decision is to send a messenger, but: + +- This messenger could be intercepted by the enemy and never reach the other generals, or worse, the enemy can decide to fake the message. +- One of the generals could be a traitor and therefore send a fake message. + +How do we agree into a consensus with these factors? The latter problem is solved by the so called Byzantine Fault Tolerance algorithms that describe that, as long as no more than a fraction _f_ of the soldiers are dishonest, then the honest generals will eventually reach the same consensus. This sentence might just sound vague, but the truth is it implies two very important conditions in BFT algorithms: safety and liveness. + +
+
+
+Fig. 1: Byzantine General Problem +
+ +Applied to blockchains, message are just the state of the ledger that nodes (generals) need to agree upon. That's why BFT algorithms are so important in our case. + +### Byzantine Fault Tolerance algorithms + +As said before, there are two very important conditions any Byzantine Fault Tolerant algorithm needs to satisfy. These are: + +- __Safety__: If one honest participant agrees on a block, then all the other honest participants need to agree on the same block. That is, honest participants cannot agree on conflicting blocks. +- __Liveness__: Honest participants of the protocol will eventually agree on something. Applied to the blockchain ecosystem, it means the chain will continue growing and that eventually blocks will be finalized. + +In partially synchronous or asynchronous network the number up to which faults can be tolerated is 1/3. The reason is: + +Imagine we have _n_ nodes, from which _f_ are malicious and _h_ are honests. Obviously _n_ _=_ _f+h_. +In order to break safety in an asynchronous system the _f_ number of faulty nodes can divide the honest peers in two sets and try to convince each of them to agree on a different thing. This means that, if the threshold to achieve a consensus is _t_, _t > h/2+f_. + +Now if we want to ensure liveness that means the threshold should lower than that of the number of honest peers. That is, _t < h_. + +The threshold between the two, safety and liveness, is given by ++h > t > h/2 +f +
+ +
+h/2 > f, h > 2f +
+ +That means, a BFT protocol maximizes both conditions at 1/3 number of fault nodes. + +The above means that most of the BFT algorithms guarantee their right functionality for as long as at least 2/3 of the participants have an honest behaviour. Having this in mind, each algorithm has to evaluate how to provide, keeping the validity of the algorithm, the next: + +- **Validators rotation**: the validator set must be dyanamic but always keeping 2/3 of honests. +- **Avaiability**: capability of reaction when (in the worst case scenario) 1/3 of the validator set goes offline. +- **Finality**: ability of reaching for all blocks consensus, guaranteeing chain's continuity. + +In the coming section we will focus on the Finality gadgets that have been implemented in Cosmos, Ethe 2.0 and Polkadot. + +## Finality gadget + +The most popular PoS chains, like Cosmos, Eth 2.0 or Polkadot have BFT algorithm that we will review in this section. By default we assume that any blockchain is either partially synchronous or asynchronous, and as such, we will assume each chain can tolerate up to 1/3 of voting power (either as votes or stake). + +#### Cosmos (Tendermint) + +Cosmos is a PoS system with 100 validators at Genesis and it's expeted the number become around 300 after 10 years. Tendermint, its underlying consensus protocol, sets the number of faulty nodes to be of 1/3 as any other Byzantine Fault Tolerant protocol. These validators do the following: + +- Take turns to propose blocks. At each hight, one validator is selected to propose a block +- If no block is commited, we move to next round (weakly synchronous) +- The voting is divided into two stages: pre-vote and pre-commit +- In order for a block to be commited on chain, 2/3 of the validators have to pre-commit for the same block within the same round +- If the block proposer produced a valid block, validators pre-vote for that block. If the block is invalid or they did not receive it, pre-vote null. +- After the pre-vote phase, validators check whether the block proposed (or null) received more than 2/3. If so, precommit for that block. Else, pre-commit NULL. +- If we do not receive 2/3 pre-commits for one block, a new proposer is selected for the same height. +- Basically, the chain does not advance unless 2/3 agree on the same block. + +
+
+
+Fig. 2: Flow of the validators +
+ +Safety is guaranteed as 2 blocks can not be finalized unless a set of validators does not follow the rules. If they are found to not respect the protocol, their stake is slashed. + +Liveness is guaranteed as long as more than 1/3 of the validators don't go offline. If such thing happens, then the chain halts. Compared to other approaches Tendermint priorizes safety (any block appended is considered final) over liveness. + +Tendermints approach is likely not to be very suitable for Witnet without heavy protocol modification and a good overhead of messages. In the end we would be forcing ARS (Active Reputation Set) members to participate on a multi-round voting that would produce a big amount of overhead. If such an agreement is not reached within the epoch, we would be asking ARS members to vote on a block for which they might not even be part of the ARS anymore. + +#### Eth 2.0 Casper FFG + +In Ethereum 2.0, Casper the Friendly Finalty Gadget is the mechanism in charge of selecting a unique chain to represent the canonical transactions of the ledger. + +In this mechanism a set of validators sign blocks and propose them creating "child" blocks from the previous ones, forming a ever-growing block-tree. + +Rather than dealing with the full block tree, Casper only considers the subtree of checkpoints. A checkpoint is set every time the hight of the block tree is a multiple of 100. Notice that this does not mean a checkpoint is set every 100 block, since at the same hight in the block tree more than one block could coexist. +During each checkpoint, the validators send a vote message. The state of the checkpoint is finalized when 2/3 of validators achieve consensus. + +To join as a new validator a deposit is needed and a *deposit message* is sent. The validator joins the validator set two checkpoints after the deposit is sent. In a similar way a validator leaves. Therefore a dynamic validator set is established. +However, when leaving the validator set, there is a "withdrawal delay", before the deposit is withdrawan. If durind this period, the validator violates any commandment, the deposit is slashed. + +The penalties in this process are executed when a node does satisfies a **slashing condition**. These are: + +- Condition 1: a validator must not publish two distinct votes for the same target height; + +- Condition 2: a validator must not vote within the span of its other votes. + +
+
+
+Fig. 3: Final blocks during checkpoints +
+ + +In the figure above stripes represent the checkpoints and _b1_, _b2_, _b3_ are the results after the consensus is achieved in each cheackpoint. The pink chain would be then the canonical chain. + +#### LMD Ghost + +Casper CBC proposes a Fork-choice-rule named LMD Ghost (latest message driven GHOST). The idea is that when a validator votes over a fork subtree, the last blocks signed by validators are taken into consideration, and the most weighted block is set as valid. + +To simplify, suppose there are 5 validators _A, B, C, D, E_, and each of them votes one per slot. Suppose after 9 slots we get the following tree: + +
+
+
+Fig. 4: Last blocks signed by validators +
+ +where blue blocks are the latest blocks signed by each validator. +To apply the "greedy heaviest observed subtree" (GHOST) fork choice rule a validator starts at the genesis block, then each time there is a fork chooses the side where more of the latest messages support that block's subtree, and keep doing this until reaching a block with no children. + +
+
+
+Fig. 5: Blocks with no children +
+ +The result is the longest chain, in a well-running network, almost all of the time LMD GHOST and the longest chain rule will give the exact same answer. But in more extreme circumstances, this is not always true. + +#### Polkadot (GRANDPA) + +In this section we review the finality gadget in Polkadot. The consensus algorithm that describes it is called GRANDPA (GHOST-based Recursive Ancestor Deriving Prefix Agreement), and it is very similar to LMD-GHOST used in Eth2.0. + +LMD-GHOST basically counts the last message signed by each validator in a chain, and calculates the score of each chain in the case of fork. The idea in GRANDPA is to find a common ancestor which obtains a super-majority, meaning that more than 2/3 of the validators constructed on it. The ancestor having a super-majority is said to be final or to achieve economic finality. + +Unlinke Eth2.0, a block is considered to achieve super finality if it is an ancestor of descendant blocks backed by a super majority. In Eth2.0, LMD-GHOST is used as fork-choice rule, but finality is only achieved if a block is singed by 2/3 of the validator set. + +A slashable behavior is that in which a validator signs a block where block B is an ancestor at a round superior at which B became finalized. + +In reality these are not votes but weighted votes depending on the validator stake. + +
+
+
+Fig. 6: Blocks been added to the canonical chain with GRANDPA +
+ + +In the figure above blocks A, B and C are backed by the all nodes and thus become finalized. D2 is backed by just 55% of the stake, and such does not become finalized, neither D1 as its only backed by 20%. + +Voting happens offchain and does not get appended to any block. However, nodes have proof and are incentivized to show bad behavior since they obtain a reward for it. + + +## BFT Finality in Witnet + +The topic of the finality gadget, i.e., when a block is considered final, is extreamly importat in the Witnet's Block Relay. +With respect to the above solutions, GRANDPA looks like a good candidate for Witnet. In most cases, the header would be finalized in the next set of epochs. But in the case of a fork, since the ARS (Active Reputation Set) might change, the chain would keep growing until those ARS members that got out and did not reach a consensus yet come back to the ARS again and vote for some descendant of the block to be validated. This is not the case of Casper FFG, as the same ARS would be asked to vote in each epoch until they find a 2/3 that second the preceeding blocks. +The details about the Block Relay and how it works can be found [here][Block Relay]. + +## References + +[1] [Block Relay](./block_relay.md) + +[2] [Consensus Compare: Casper vs. Tendermint](https://blog.cosmos.network/consensus-compare-casper-vs-tendermint-6df154ad56ae) + +[3] [Casper the Friendly Finality Gadget](https://www.researchgate.net/publication/320626951_Casper_the_Friendly_Finality_Gadget) + +[4] [A CBC Casper Tutorial](https://vitalik.ca/general/2018/12/05/cbc_casper.html) + +[5] [Byzantine Finality Gadgets](https://github.com/w3f/consensus/blob/master/pdf/grandpa.pdf) + +[Block Relay]: ./block_relay.md diff --git a/bridge/docs/aggregated_signatures.md b/bridge/docs/aggregated_signatures.md new file mode 100644 index 0000000..b029604 --- /dev/null +++ b/bridge/docs/aggregated_signatures.md @@ -0,0 +1,145 @@ +# Signature Aggregation Posibilities + +One of the main drawbacks of interacting with a platform like Ethereum is the associated gas cost to complex operations. Specifically, operations that involve elliptic curve point multiplications and modular inversions, even if they are subsidized, imply significant gas costs when several signatures need to be verified. In the case of the Block Relay, where several signatures signed by ARS members need to be verified, it becomes necessary to find alternative solutions than just iterating over the ecrecover function. + +## ECDSA: The Problem + +ECDSA signature verification is subsidized in Ethereum through the ecrecover function. The main problem arises when several of those verifications need to be performed. The ECDSA signature generation is presented below: + +
+
+
+Fig. 1: Signature generation in ECDSA +
+ +First, one of the drawbacks that we observe is that a random number $k$ is needed. This, in order to avoid trusting a RNG, is usually given by the RFC-6979 standard. The ECDSA signature verification algorithm is presented below: + +
+
+
+Fig. 2: Signature verification in ECDSA +
+ +As we can see every ECDSA signature involves a couple of point multiplications and one inversion, all costly operations. This, as the number of signatures to be verified increases, the associated gas cost becomes unacceptable by a protocol like Witnet. Further, the input if we want to verify a transaction signed n peers is n signatures. + +## Schnorr signatures + +Schnorr signatures main difference with respect to ECDSA are mainly that the output consists of a point in the curve R and a scalar s, instead of two scalars. The signature generation can be observed in the algorithm below: + +
+
+
+Fig. 3: Signature generation in Schnorr +
+ +While the verification can be observed in: + +
+
+
+Fig. 4: Signature verification in Schnorr +
+ +Obviously, the verifier does not know anything from the signers secret key, as he is only given R from which he cannot derive k. From the other side, when multiplying *s * G = (k + H(P, R, M) * pk) * G = k * G + H(P, M, R) * pk * G = R + H(P, M, R) * P* + + +
+
+
+Fig. 5: Schnorr signatures from https://medium.com/cryptoadvance/bls-signatures-better-than-schnorr-5a7fe30ea716 +
+ +Now when we need to verify several signatures (as it would be the case in Witnet if we want to verify that the ARS signed a block header), with ECDSA we would need to verify one by one the signatures. More than that, theoretically for N signatures we need to compute 2N multiplications and N inversions. + +For Schnorr signatures we have a very nice addition property. Indeed note that: + +
+
+
+Eq. 1: Aggregate signatures +
+ +In this case we need to perform some point additions (whose cost is negligible with respect to multiplication) and N+1 multiplications. That is a very good speed up with respect to ECDSA. + +But the good point of these is that we can aggregate signatures, by computing *s= (s1, s2, ..., sN)*, *R= R1, ..., RN* and *P = P1, P2, ..., PN*, this time the signature has to be computed as: + +*si = ki + H(P, R, M) * pki* + +Now in order to verify the signature: + +*s * G = R + H(P, R, M) * P = (s1 + s2... sN) = (R1 + R2+ ..+RN) + H(P, R, M) * (P1+P2+...+PN)* + +The main problem with this kind of construction is that we need to agree first on the participants and then calculate common P, R and S. + +Also we have the Rogue key attack. In this case, if we have two participants A and B and an aggregate public key *P = PA + PB*, Bob can send a false key *P_Bf = PB - PA*. If this is the case, then the aggregated key now becomes Bob public key, meaning Bob can be claiming to be signing for both A and B while in practice he is only signing with his private key. + +Further, we did not mitigate the problem of requiring the random number generator in place. + +## Boneh–Lynn–Shacham (BLS) signatures + +BLS signatures fix some of the issues that we observed with Schnorr signatures, as they do not rely on random number generators and we do not need several communication rounds (or to know the set of participants beforehand). However, there are some concepts we need to introduce, specially curve pairing: + +Imagine we have a funtion $e$ that has the following properties: + +- *e(P, Q) -> n* +- *e(x * P, Q) = e(P, x * cdot Q)* +- *e(P, Q + R) = e(P, Q) * e(P, R)* +- *e(a * P, b * Q) = e(P, a * b * Q) = e(a * b * P, Q) = e(P,Q)(ab)* + +
+
+
+Fig. 6: BLS signatures from https://medium.com/cryptoadvance/bls-signatures-better-than-schnorr-5a7fe30ea716 +
+ +Let's further assume that these functions dont reveal anything about the input scalars. We call this function $e$ the pairing function. For using such functions we need curves that are pairing-friendly. + +Further, let's assume there exists a Hash_to_Curve function, i.e., a function that takes the hash of a message and maps it to a point in the elliptic curve. Such a function is the identical to the one used in VRF. + +The signature is just calculate as the first three steps in the VRF proof generation. In fact, the generation is given by: + +
+
+
+Fig. 7: Signature generation in BLS +
+ + +And the verification + +
+
+
+Fig. 8: Signature verification in BLS +
+ +Basically based on the properties formerly defined: + +*e(P, Hash\_to\_curve(M)) = e(pk * G, Hash\_to\_curve(M)) = e(G, pk * H(M)) = e(G,S)* + +The properties that these kind of signatures have are very attractive. For instance, note that we can aggregate any set of signatures we want: + +*S=S1 + S2 +...+SN* + +*e(G, S) = e(G, S1+S2....+S100) = e(G, S1) * e(G, S2)... * e(G, SN) = e(G, p1 * Hash\_to\_curve(M1)) * e(G, p2 * Hash\_to\_curve(M2))... * e(G, pN * Hash\_to\_curve(MN)) = e(P1, Hash\_to\_curve(M1)) .... * e(PN, Hash\_to\_curve(MN))* + +This implies that we need to calculate 1001 pairing functions in order to verify. Close to the number of costly functions that we need to perform for Schnorr, but pairings are much more costly than multiplications. + +However, if all signers are signing the same message: + +*e(G, P1 * Hash\_to\_curve(M)) .... * e(G, PN * Hash\_to\_curve(M)) = e (G, (P1+P2..+PN) * Hash\_to\_curve(M) = e(G * (P1+P2..+PN), Hash\_to\_curve(M))* + +There are a few downsides from using BLS signatures: + +- The pairing functions have not widely been studied nor attacked, and therefore their security is still to be proven. As of today, we do not know of any attack that can break them. +- The efficiency of pairing functions with respect to ECDSA signatures is rather low. However, for certain cases (like that in which we can aggregate signatures of the same message) we can make use of their properties to achieve a higher efficiency than with regular signatures. + diff --git a/bridge/docs/block_relay.md b/bridge/docs/block_relay.md new file mode 100644 index 0000000..95bec51 --- /dev/null +++ b/bridge/docs/block_relay.md @@ -0,0 +1,187 @@ +# Block Relay in Witnet + +The Witnet Bridge Interface (WBI) is a core component of the Witnet protocol as it enables the communication between the smart contracts in Ethereum and Witnet. In order to trustlessly verify transactions included in a Witnet block, the Ethereum smart contract needs to be able to perform verifications of the so called Proofs of Inclusion (PoI). Assuming all Witnet block headers are available to the WBI, a bridge node simply needs to provide the merkle path that includes the transaction to be verified and that reaches the root in the block header. + +Obviously the aforementioned mechanism works just because we assumed the block header is available in the WBI. How to make this possible with a non PoW/PoS chain is a more challenging concern though. Without loss of generality, we will assume that the WBI has access to the Witnet block headers because they are stored in another contract in Ethereum that is called the **Block Relay**. Essentially, the goal is similar to that of building a Witnet **light client in Ethereum**. As any light client, the block relay contains the headers but performs little to no validations. We will focus thus on how these blocks are reported to the block relay. + +As with the WBI, only bridge nodes have access to both Witnet and Ethereum chains. Thus, bridge nodes should be in charge of reporting valid blocks that will later be used to verify PoIs in the WBI. We observe three main challenges: + +- **Integrity of the Block header**: the reporting protocol needs to guarantee the integrity and validity of the block header reported. From the block relay perspective, this essentially means that a block header does have enough acceptance as declared by a majority of Witnet reputation holders. +- **Economic incentives**: the reporters should be economically incentivized to post correct block headers in the Block Relay contract. +- **Handle chain forks and finality gadget**: the Block Relay should be able to recover from chain forks in which the tip of the chain diverges. + +## Block Header Integrity: the problem + +Recall Witnet does not feature a PoW nor PoS consensus mechanisms, but rather it utilizes cryptographic sortition with biased probabilities depending on past behavior. The trustlessness property is thus more difficult to achieve as we cannot simply look at the chain with the longest amount of work/stake. Note that there are implications of a malicious header being inserted in the block relay severe, as: + +1. She can preconstruct a header including a result of a data request of her interest that was previously posted to the WBI. She can even make up a beneficial Active Reputation Set (ARS) and report it with the header. She reports the block header to the Block Relay contract, which accepts it by majority. +2. She later constructs a PoI for the header she inserted in the block relay, aiming at verifying the result of her interest. +3. The WBI accepts it and stores it as the result to the data request, while the true result remains unreported. + + Simplifying, we need a groups of nodes to agree on the particular block header in a non-synchronous network. This traditionally has been resolved by **Bizantyne Fault Tolerance (BFT)** algorithms, that ensure that if there are no more than *f* number of attackers, the network is going to achieve liveness and safety properties. +## Proposed Solution + +In the end our main problem resides on the fact that we need a set of nodes to agree on a state. We solve this problem, as the majority of the PoS systems, by utilizing a BFT algorithm. A review on the Byzantine Generals Problem and some of the BFT approaches, specially with respect to finality in PoS systems, can be found [here][BFT]. + +### Voting Committee +As with any other BFT approach, the first thing we need to do is define the committee that will reach an agreement. In PoS systems this committee is often referred as the **validator set**. It is clear that only Witnet nodes know the state of the chain, so here are our options: + +- __Bridge nodes__: The main issue when we set the bridge nodes as our committee is that these are easily sybileable. The number of bridge nodes is expected to be substantially lower than the number of nodes in Witnet. Thus, a malicious attacker can just spin up the necessary nodes to take over the entire bridge node set and insert a fake header aiming at reporting a fake result. +- __Witnet known Identities__: Instead, we could let every identity that participated in Witnet vote for the correct chain tip. We foresee some issues with this approach. First, the safety and liveness characteristics are guaranteed as long as 2/3 of the reputation points reside on honest participants, which is a reasonable assumption. However, note that the number of signature messages that need to be exchanged and the number of signatures to be verified in the block relay increase substantially. Additionally, the block relay should retain every single header to check voters participations, without having the possibility of discarding sufficiently old headers. +- __Active Reputation Set__: The third approach is to let the Active Reputation Set (ARS) define the current chain tip. The ARS defines the most recent (N epochs) participants in Witnet. Clearly this is the most difficult to be corrupted by an attacker. First, as with the previous approach, we can assume that 2/3 of the reputation will reside in hands of honest nodes, and it is likely that the ARS will contain nodes with high reputation, thus having an interest on being honest. But further, we implemented collateralization and coin-age countermeasures that harden the possibility (and increase the cost) of colliding 1/3 of malicious actors in the ARS. Thus, it is expected that the integrity of the ARS is guaranteed and therefore the ARS members should be in charge of reporting the chain tip. This ARS is limited with respect to the velocity of the Witnet network, thus making even more difficult and costly to sybil it. + +In order to secure the block relay we should thus trust the committee that is more costly to sybilize, in our case the ARS. Thus, the block header at epoch i-1 defines the committee at epoch i. However, we still need to prove to the block relay that a certain ARS identity voted for some chain tip B. In order to do so, we define __Proof of Membership__. + +### Proof of Membership + +In this section we formally define Proof of Membership, i.e., the ability of a Witnet node to demonstrate it was part of the ARS at a specific point in time. In order to provide this capability, we need the inclusion of the merkle root of the Active Reputation Set (ARS). The leaves are the public keys of those members in the ARS. Remember that the ARS is defined as the participants that have been actively working for the Witnet network, in the form of witnessing or mining, in the last N epochs (where N is a protocol parameter). As such, if a Witnet node needs to prove its membership to the ARS at a specific point in time it needs to provide: + +- A challenge signed with the private key corresponding to the public key the node claims to be associated with. +- The public key itself; +- The merkle path (siblings) that lead to the ARS merkle root at a specific point in time; +- The point in time at which it is claiming to be part of the ARS; + +A proof of membership is composed of σ = (C, Sig( C ), PK, Merklepath, epoch). In practice the challenge can just be the concatenation of the beacon plus the address of the sender. + +In order to verify the validity of the proof of membership, the verifier needs to have access to the block header of the epoch at which the prover is claiming to be part of the ARS. If so, the verifier: + +1. Verifies that the public key and the siblings lead to the merkle root provided +2. Verifies the signature to check that the prover is indeed in possesion of the corresponding private key. + +### Finality in Block Relay +One of the main topics when discussing Bynzatine Fault Tolerance on blockchains is the so called finality gadget, i.e., when can we say a block is final. This topic is extremely important in the block relay, as it defines when transactions reported by bridge nodes can become trusted. A full review of different finality gadgets related to Cosmos, Polkadot and Eth2.0 can be found [here][BFT]. Out of those, the algorithm that suits better for Witnet is Grandpa. The reasons are: +- First, we need to be aware that we might not achieve immediate finality on a particular block header, and as such, that we need to continue growing the chain until we see that a particular ancestor achieves 2/3 of the votes. +- Further, we need to realize that in the case of a non-finalized fork the ARS members in descendants blocks might change. Although the ARS is not expected to change substantially, even in that case, the only consequence would be that the block would be finalized once they come back to the ARS in a future epoch. +- With Grandpa, only the ARS members for the current epoch need to sign the last beacon/block. The rest of the nodes in the network do not need to sign the last beacon, as it would be the case in Casper FFG and Cosmos. +- With Casper FFG we would be enforcing the ARS to vote each epoch until 2/3 are reached, in which case all its ancestors are validated. That means that at each epoch every node in the network needs to cast a vote (as they might have been in the ARS in recent epochs), or in the best case, everybody who was in the ARS in the last N epochs. + +- With Tendermint, ARS members would be stuck until they reach a 2/3 consensus. This, in addition to reproduce the problem we explained with casper FFG, also halts the network if more than 1/3 goes offline. This is not the case in FFG and GRANDPA, as the chain continues growing until 2/3 are achieved in descendants or ancestors. + +GRANPA stands for GHOST-based Recursive Ancestor Deriving Prefix Agreement and the idea is similar to LMD-Ghost in Casper CBC: when having a fork, the last blocks signed by the validators are checked, and the heaviest is considerd the valid one and so the chain in which it is. In GRANDPA a chain signed by sufficient validators consolidates its ancestor. +In particular, suppose there is a fork in the blockchain, the chains continue aggregating blocks (when validators sign those blocks) and by the time one of the chains is signed by 2/3 of the validator set the first block is confirmed. + +In Witnet, validators are the members of the ARS, which, as mentioned before, could change from one epoch to another. Suppose at epoch *t* a block *Bt* is singed by less than the 2/3 of the ARS, then it will be validated when 2/3 of that ARS sign its descendants. Notice that even if the ARS changes, the change is not substancial and the members tend to reappear in the ARS. So as the chain grows at some point at least 2/3 will have vote from epoch *t* and due to safety they will agree on the same chain, validating block *Bt*. More formally: + +
+
+
+Fig. 1: Finality of GRANDPA based on ARS member votes +
+ +Proof: Condition 1. is given by BFT, since at most 1/3 are dishonest. Condition 2. on the other hand is given by the construction of the ARS in which nodes are incentivize to stay on-line. Because of this, due to the fact that members of the ARS are the most reputated nodes we can suppose that at least 2/3 of them will have an honest behaviour and so they will be selected as part of the ARS in the next epochs. + +Let's see an example of how this would work in Witnet. Suppose we have 100 nodes in the ARS at epoch *t*, from who, 30 sign a block ad 20 another one, as in the next figure: + +
+
+
+Fig. 2: Fork in the block relay +
+ +As none of the blocks got 2/3 of the ARS none is set as final and both chains continue growing. In epoch *t*+1 the first chain gets another 10 signatures form the ARS of epoch *t*, reaching 30 signatures while the second chain gets 15 new signatures, 45 signatures in total. As in epoch *t*, none of them gets enough support. +However, in epoch *t*+2, the second chain sums another 25 signatures, reaching 70 signatures of the 100 members of the ARS in epoch *t*, and so more than the 2/3 necessary. This way the second block proposed in epoch *t* is considered valid, as one can see in the figure that follows. This mechanism is repeated for the next block, this time taking into consideration the ARS of epoch *t*+1, that may differ from the ARS in the previous epoch. + +
+
+
+Fig. 3: The bottom chain signatures validate the block at time t as it achieves 2/3s +
+ +### Improving Efficiency through aggregated signatures + +The aforementioned approach has a big drawback: it would take 1000 verifications to verify 1000 ARS members signatures. We always pursue a gas-driven design, i.e., a design that decreases the gas cost in Ethereum. Clearly verifying one by one the signatures imposes a prohibitive gas cost. A different proposal might be that the voting is based on aggregated signatures. [This document][Aggregated-sigs] describes different approaches that can be taken to aggregate signatures. In this case, ARS nodes could just send the last header they know about signed, and the bridge node would just need to aggregate those signatures. Among our posibilities we choose BLS signatures for two main reasons. First, our committee size is varying and therefore it becomes substantially difficult to apply Schnorr. Further, BLS signatures will be utilized by Eth2.0 and are subsidized, thus reducing further the cost. + +
+
+
+Fig. 4: Pre-compiles in Byzantum +
+ +With BLS signatures, it would only take one verification in the block relay contract to verify all the aggregated signatures from the ARS. At this stage, VRFs in the WBI would still be performed with the curve secp256k1, but `LAST_BEACON` messages would be signed with BLS. + +
+
+
+Fig. 5: ARS signatures are aggregated so that a single verification is performed in the block relay +
+ +### Economic Incentive: fee as low as possible + +Another aspect that the block relay needs to cover is the fact that nodes will have an economic incentive to report blocks (any transaction verified in a block reported by them implies a reward), and they should not be able to set the reward as high as to make the data request non-executable. In order to cope with that we propose an inverse auction: the lowest price with respect to the number of signatures provided reported by a bridge node is the one that is set as the fee. However, we still reward all bridge nodes that participated in the correct block header consensus reporting. We apply proportionality as to how deviated the reports were with respect to the minimum. Let's work through an example, in which we assume every bridge node contributed with the same numbre of signatures: + +- Imagine both Alice and Bob report the same correct block header to the block relay. +- Alice sets a report reward fee of 1 ETH, while Bob sets the fee as 9 ETH. +- In this case, 1 ETH is established as the block report reward, of which 0.9 will go to Alice and 0.1 to Bob. + +With that in mind the reward that each node would obtain for someone whose reported block was successfully utilized in a PoI verification is: + +
+
+
+Eq. 1: Reward for each of the block header reporters +
+ +where |X| = x1, x2, ..., xN and each xi is the value proposed by selected node i. + +With this scheme all nodes are incentivized to set the fees as low as possible in favor of price competition. Ideally, all bridge nodes would set it low enough (taking into account the transaction gas and potential benefit derived from the WBI) so that they get an equal share of the rewards. + +## Stages + +We divide the process of relaying blocks in different phases to be implemented in the process of building a decentralized efficient block relay. However, each of the phases will share the API to which bridge nodes connect, aiming at offering the most ergonomic updating process to the bridge nodes. + +### Stage 1: ARS voting + +Stage 1 requires the ARS members to vote for the correct block hash candidate in the block relay. This is, all nodes interested on the healthiness of the system need to run both an Ethereum client and a Witnet node to become witnesses and bridge nodes at the same time. This stage includes the merklelization of the ARS as well as the proof of membership verification. + +In this case, the block hash candidate that received more votes from the ARS is the one that is consolidated. As said before, we expect the ARS members to be those that have higher reputation and in consequence to be those more interested in having a good block relay. + +### Stage 1.5: Weighted ARS voting + +In this stage, the influence on the voting of the block header hash is weighted according to the reputation that ARS members have. However, in order to avoid the situation in which a single identity can heavily influence the voting, we can instead weight votes by the position they hold in the ARS. For instance, one could make sure that the first 25% copes with the remaining 75% of the ARS. This way, in order to influence the voting a potential attacker needs to influence the top 25% or the lower 75% of the ARS. Further, an attacker not only needs to place more than 50% of identities in the ARS but rather she also needs to become either the top reputed entities. + +### Stage 2: collateralized voting (?) + +Additional to previous stages, the voting scheme may include a collateral in order to increase the incentives of being honest. Those behaving dishonestly (i.e. reporting block headers that end up not being canonical) will lose their collateral, which may be distributed among honest reporters or even be burned. + +### Stage 3: BLS signatures + +The third phase can be divided in two main purposes. First, the implementation of BLS signatures in Witnet, such that ARS members can vote for the chain tip and witnet bridge nodes can aggregate signatures. Second, the verification implementation in the Block Relay where a single signature verification is enough to verify the aggregated signatures. In this stage, the chain tip is still decided by majority in the block relay, but a single verification is enough to verify the belonging to the ARS. + +A pre-assessment about potential impacts in the P2P network will be made, more specifically in the `LAST_BEACON` message exchanges among peers. + + +### Stage 4: Implement Finality Gadget + +In this stage the finality gadget is implemented in the Block Relay, such that a block is considered final only if 2/3 of the current ARS voted for it or their descendants. If not, the block relay stores both chain tips until one of them is decided. + +Additionally, the bridge node needs to implement a smart key signing in which all signatures belonging to the ARS of the last non-finalized block or their descendants. + +Further, the bridge node needs to be able to insert penalization transactions in Witnet whenever it sees a slashable behavior, such as voting for two chain tips within the same epoch. The slashing will imply a reduction of reputation points of those that double-voted. + +### Stage 5: Replace VRF by BLS + +[To be completed after assessment] + +As aforementioned, stage 3 requires BLS signatures by Witnet nodes. Taking into account that BLS signatures are deterministic, there is a clear opportunity of using BLSs instead of VRFs for computing Proof of Eligibilities (PoEs). This is possible due to the fact that PoEs have fixed inputs for all participating nodes (e.g. data request identifier and beacon). + +However, before replacing VRF with BLS, it is required to perform a further analysis about the following topics and concerns: + +- Efficiency of BLS compared to VRF, both in Witnet nodes and Ethereum Smart Contracts (EVM). +- Assessment of potential changes in the Witnet, both in the Witnet transaction model (e.g. commit transactions). +- Assess maturity of BLS (e.g. pairing functions) in terms of crypto analysis (e.g. known attacks and vulnerabilities). + +### Stage 6: Evaluate a hard-fork/dispute mechanism to recover + +Things to do: + +- Assess if dispute mechanism if required +- Assess how the Block Relay could recover from a fork + +[Aggregated-sigs]: ./aggregated_signatures.md +[BFT]: ./BFT_finality.md diff --git a/bridge/docs/images/B_general_problem.jpg b/bridge/docs/images/B_general_problem.jpg new file mode 100644 index 0000000..2dbfaa2 Binary files /dev/null and b/bridge/docs/images/B_general_problem.jpg differ diff --git a/bridge/docs/images/CasperFFG.png b/bridge/docs/images/CasperFFG.png new file mode 100644 index 0000000..0ed9bed Binary files /dev/null and b/bridge/docs/images/CasperFFG.png differ diff --git a/bridge/docs/images/agg_sig_eq.png b/bridge/docs/images/agg_sig_eq.png new file mode 100644 index 0000000..dbe09a1 Binary files /dev/null and b/bridge/docs/images/agg_sig_eq.png differ diff --git a/bridge/docs/images/bls.png b/bridge/docs/images/bls.png new file mode 100644 index 0000000..e02d28a Binary files /dev/null and b/bridge/docs/images/bls.png differ diff --git a/bridge/docs/images/bls_generation.png b/bridge/docs/images/bls_generation.png new file mode 100644 index 0000000..4adeb42 Binary files /dev/null and b/bridge/docs/images/bls_generation.png differ diff --git a/bridge/docs/images/bls_graph.png b/bridge/docs/images/bls_graph.png new file mode 100644 index 0000000..6696b96 Binary files /dev/null and b/bridge/docs/images/bls_graph.png differ diff --git a/bridge/docs/images/bls_verification.png b/bridge/docs/images/bls_verification.png new file mode 100644 index 0000000..1fc9431 Binary files /dev/null and b/bridge/docs/images/bls_verification.png differ diff --git a/bridge/docs/images/cosmosBFT.png b/bridge/docs/images/cosmosBFT.png new file mode 100644 index 0000000..93e98ee Binary files /dev/null and b/bridge/docs/images/cosmosBFT.png differ diff --git a/bridge/docs/images/ecdsa_generation.png b/bridge/docs/images/ecdsa_generation.png new file mode 100644 index 0000000..c5c61cc Binary files /dev/null and b/bridge/docs/images/ecdsa_generation.png differ diff --git a/bridge/docs/images/ecdsa_verification.png b/bridge/docs/images/ecdsa_verification.png new file mode 100644 index 0000000..6e59a82 Binary files /dev/null and b/bridge/docs/images/ecdsa_verification.png differ diff --git a/bridge/docs/images/eq_rew.png b/bridge/docs/images/eq_rew.png new file mode 100644 index 0000000..56b0fac Binary files /dev/null and b/bridge/docs/images/eq_rew.png differ diff --git a/bridge/docs/images/fork_grandpa_1.png b/bridge/docs/images/fork_grandpa_1.png new file mode 100644 index 0000000..5bf3cb2 Binary files /dev/null and b/bridge/docs/images/fork_grandpa_1.png differ diff --git a/bridge/docs/images/fork_grandpa_2.png b/bridge/docs/images/fork_grandpa_2.png new file mode 100644 index 0000000..a8abc9e Binary files /dev/null and b/bridge/docs/images/fork_grandpa_2.png differ diff --git a/bridge/docs/images/lmd_ghost1.png b/bridge/docs/images/lmd_ghost1.png new file mode 100644 index 0000000..c10e887 Binary files /dev/null and b/bridge/docs/images/lmd_ghost1.png differ diff --git a/bridge/docs/images/lmd_ghost2.png b/bridge/docs/images/lmd_ghost2.png new file mode 100644 index 0000000..b0274e0 Binary files /dev/null and b/bridge/docs/images/lmd_ghost2.png differ diff --git a/bridge/docs/images/polkadotBFT.png b/bridge/docs/images/polkadotBFT.png new file mode 100644 index 0000000..d8f3994 Binary files /dev/null and b/bridge/docs/images/polkadotBFT.png differ diff --git a/bridge/docs/images/precompiles.png b/bridge/docs/images/precompiles.png new file mode 100644 index 0000000..e317a23 Binary files /dev/null and b/bridge/docs/images/precompiles.png differ diff --git a/bridge/docs/images/safety_liveness.png b/bridge/docs/images/safety_liveness.png new file mode 100644 index 0000000..e57d215 Binary files /dev/null and b/bridge/docs/images/safety_liveness.png differ diff --git a/bridge/docs/images/schnorr_generation.png b/bridge/docs/images/schnorr_generation.png new file mode 100644 index 0000000..04ff9a4 Binary files /dev/null and b/bridge/docs/images/schnorr_generation.png differ diff --git a/bridge/docs/images/schnorr_graph.png b/bridge/docs/images/schnorr_graph.png new file mode 100644 index 0000000..d00bf4d Binary files /dev/null and b/bridge/docs/images/schnorr_graph.png differ diff --git a/bridge/docs/images/schnorr_verification.png b/bridge/docs/images/schnorr_verification.png new file mode 100644 index 0000000..7752464 Binary files /dev/null and b/bridge/docs/images/schnorr_verification.png differ diff --git a/bridge/index.md b/bridge/index.md index 3eb6218..319d897 100644 --- a/bridge/index.md +++ b/bridge/index.md @@ -2,5 +2,4 @@ - [State of the Art](./docs/state_of_the_art.md) - [Witnet Bridge Interface](./docs/WBI.md) -- Block Relay - +- [Block Relay](./docs/block_relay.md) diff --git a/deriveY/deriveY.md b/deriveY/deriveY.md new file mode 100644 index 0000000..c6557f2 --- /dev/null +++ b/deriveY/deriveY.md @@ -0,0 +1,58 @@ +# `deriveY` and quadratic residue + +Suppose we have an elliptic curve *E* over *ZP*, with *P* a prime, given by the equation *y2=x3+ax2+b*. Then knowing *x* there are two results from the equation of the curve, *y* and *-y*. The parity byte of *y*, 0x02 even, 0x03 odd, is enough to know which of the two results is the *y* we need. This is what the `deriveY` function does, revels *y* having as inputs the compressed form of *y* and *x*. +The main part of the function relies on computing the quadratic residue modulus *P*, this is resolving something like *y2 = n mod (P)*. This can be done using different algorithms, the library `EllipticCurve.sol` uses the computation of *n(p + 1)/4*, but this only works for Elliptic Curves having `P = 3 mod(4)`. + +```solidity +pragma solidity ^0.5.0; + +import "./EllipticCurve.sol"; + +contract Secp256k1 is EllipticCurve { + + function deriveY( + uint8 prefix, + uint256 x, + uint256 a, + uint256 b, + uint256 pp) + public pure returns (uint256 y) + { + // x^3 + ax + b + uint256 y2 = addmod(mulmod(x, mulmod(x, x, pp), pp), addmod(mulmod(x, a, pp), b, pp), pp); + uint256 y_ = expMod(y2, (pp + 1) / 4, pp); + // uint256 cmp = yBit ^ y_ & 1; + y = (y_ + prefix) % 2 == 0 ? y_ : pp - y_; + } +} +``` + +## Case `p=1(4)` + +For Elliptic Curves in which `P=1(4)` there are others algorithms that compute the modular square root. In fact they are more general algorithms so they could be implemented for all the Elliptic Curves the library supports, but the computational costs are higher than for the current function `deriveY`. + +The most common algorithms for computing the *n* square root module *P* are the *Tonelli-Shanks* and the *Cipolla*'s algorithm. Because in the `EllipticCurve.sol` library the only curve that satisfies `P= 1 mod 4` is `Secp224r1` we will focus on its characteristics. In this curve the value of *P* is 2224 -296+1, so *P = 1 mod (4)*. +The computational cost of the algortihms depends on the relation between the bit-length *m* of the prime *P* and *S*, being *S* the biggest exponent such that *p-1=Q2S*, where *Q* is odd. More preciscely the *Cipolla*'s algorithm is more efficient than the *Tonelli-Shanks* if and only if *S(S-1)>mP+20*. +For `Secp224r1` we have *S=96* and *m=256*, making the *Cipolla*'s algorithm the best option. More ditails can be found in [*Square Root Modulo P*][link-post2]. + +## Idea of the Cipolla's algorithm + +Suppose we want to compute the modular square root *r2 = n mod(p)*. We first need to know that the *Legendre*'s symbol states if an element is a square or if its not by applying the *Euler*'s criterion which states that an element *a(p-1)/2* is equal to 1 if *a* is quadratic residue modulus *P* and is equal to -1 if it's not. + +There are two main steps in the algorithm: + +1) First we nedd to find an element *a* of *Zp* such that *a2 - n* is not a square. This can easly be done by applying the *Legendre*'s symbol. In fact half of the elements of *ZP* are nonquadratic residue. +2) Second we compute *x= (a + (a2-n)1/2)(p+1)/2* in the field extention *FP(( a2-n)1/2)*. The resulting *x* will satisfy *x2=n mod(p)*. + +After finding *a*, the number of operations required for the algorithm is *4m+2k-4* multiplications, *4m-2* sums, where *m* is the number of digits in the binary representation of *P* and *k* is the number of ones in this representation. + +A code in Python for the algorithm can be found in this [*post*][link-post]. + +## Conclusion + +The computational cost of the algorithm is a big disadvantage for the `deriveY` function for curves having *P = 1 mod 4*, the cumbersome extension field arithmetic needed make the implementation in Solidity difficoult to achive. +As mentioned above, the Elliptic Curve library has been tested for serveral curves and just the `Secp224r1` satisfies this condition, so for now we will keep using the `deriveY` function as it is. Note that `deriveY` is just an auxiliary function, and thus does not limit the functionality of curve arithmetic operations. + + +[link-post]: https://rosettacode.org/wiki/Cipolla%27s_algorithm +[link-post2]:http://www.cmat.edu.uy/~tornaria/pub/Tornaria-2002.pdf \ No newline at end of file diff --git a/reputation/docs/dynamic-threshold.md b/reputation/docs/dynamic-threshold.md new file mode 100644 index 0000000..fac0b89 --- /dev/null +++ b/reputation/docs/dynamic-threshold.md @@ -0,0 +1,92 @@ +# Dynamic Threshold in RePoE +## RePoE +RePoE, Reputation Proof of Eligibility, allows us to decide which witnesses are selected +to solve the data request. This proof utilizes the influence of the node operator, in terms +of its reputation weight, +
+
+
+Fig. 1: Supply and Inflation with constant reward issuance. +
+ +We can therefore expect the token price to decrease at fixed rates in equilibrium, as demand is no longer increasing, while supply is. According to [the equation MV=PQ](https://www.investopedia.com/terms/m/monetaristtheory.asp), as more money enters circulation, we can expect that price (P) increases to equalize the equation. As a result, the data request price increases, and required collateral for the nodes increases. However, the incentives for honest nodes to hold those Wits reduces, allowing an attacker to acquire those Wits at a lower price. +From the miners point of view, fees would slowly increase over time, as the constant number of Wits received are valued at a lower price at equilibrium. This means fees would need to be regularly adjusted to compensate the loss of monetary power introduced by the inflation. +Dogecoin is an example of such emission curve. + +
+
+
+Fig. 2: EOS issuance. +
+ +# Constant Inflation Rate +In contrast with the previous option, in this case we consider changing the reward per block every timeframe to achieve a constant inflation rate in a certain period of time. In this case, the supply growth per unit of time is not constant but rather is an increasing graph. + +
+
+
+Fig. 3: Supply and Inflation with constant inflation issuance. +
+ +In some ways this option reflects the previous, although the depreciation of the token would happen significantly faster. There would be little incentive to join the network in the early stages, and no weighted advantage to being an early adopter. At equilibrium, collaterals would increase, posing uncertainties in both the price of the data requests and the collaterals that need to be specified. +With respect to the miner rewards, this issuance would maintain low fees as the mining reward increases every year. +Networks like EOS implement such an issuance which started with a 5% (from which 80% was allocated to Worker Proposal Fund) inflation rate per year but recently was reduced to 1%. + +
+
+
+Fig. 4: EOS issuance. +
+ +# Decreasing Issuance +This is the curve that Bitcoin (among others) have implemented. In this case, the reward per block gets reduced every x units of time. This means the reward potentially reaches 0 (alongside the inflation). The supply converges to a fixed value dictated by both the time in which the reward gets reduced and the reward per se. + +
+
+
+Fig. 4: Supply and Inflation with decreasing issuance. +
+ +This clearly incentivizes joining the network early on, due to the higher reward. This is key for a network like Witnet, in which we highly want a good number of witnesses to join from the start. Furthermore, at equilibrium (where demand does not increase) prices remain constant, as very little supply is being added per block. This favours predicability of the data request and collateral prices. This is crucial for Witnet, which can only secure data requests whose value is at most the value of the Witnet network. While token prices adjust with, for example, a constant inflation rate, attackers could take the opportunity to attack projects utilizing Witnet. +In this case, as reward for miners plummets, fees would need to compensate them to ensure they remain honest to the network. However, as very little monetary supply is introduced, we can expect fees to remain stable and constant at the equilibrium point. An identical situation would be observed with collaterals; as the price of the data requests remains constant, the data requests do not need to update the corresponding collaterals. + + +# Parametric based on collaterals/requests +Another option would be to make inflation parametric based on the number of requests observed in a certain unit of time or the collateralized amount in the same unit of time. Although this would be ideal, we find that this can be gameable in ways we cannot predict. For instance, one could ask to perform a data request whose collateralization is high in order to take advantage of newly minted Wits to play with the price. +On the other hand, miners could decide not to include transactions related to data requests/collateralization so that the miner reward stays high as much time as possible. Thus, this kind of inflation gives miners quite a lot of incentive to censor transactions, and reduce the level of decentralization (no single entity with enough power to censor/power) in the network. + +# Issuance Combinations +Some projects have decided to implement a mix of the aforementioned schemes. One of the examples is Monero, which has a decreasing issuance supply type until the end of May, 2022. Thereafter, Monero will utilize a constant reward issuance, with a 0.6 XMR constant reward for every 2-minute block, and achieve a <1% inflation (decreasing over time). The main reasoning behind this tail emission is to avoid one of the main critics of the Bitcoin supply: the only-fee scenario. + +
+
+
+Fig. 5: Monero inflation and supply vs Bitcoin. +
+ +This could be interesting for the Wit token. Nobody knows how a only-fee market will behave, but theoretically this could break our assumption of block producers in the event of ties being those with higher reward. Essentially nodes will prefer to mine on those blocks that include less transactions, e.g., those with lesser fees. + +# Conclusions +We reviewed different type of issuances for the Wit token, with their corresponding pros/cons. There seems to be a clear call to adopt a decreasing issuance in order to favour early adoption of the network while still maintaining data request price stability on equilibrium. The impredictability of what would happen on an only-fee blockchain makes us question whether to adopt a Monero-like issuance or stay with the classic Bitcoin-like issuance. + + +# Potential halving acceleration/deceleration +Here lets assume that our curve follows the decreasing issuance model, either combining it with some other model once the issuance has finished or by itself. There are several values we can based on, but we decided to maintain constant the total supply. Based on that, we can specify different values for rewards and halving that converge in: + +
+
+
+Eq 1: Total supply equation +
+ +We have [here](https://github.com/girazoki/tokenomics) scripts that automatically generate values for a given Initial Supply and Total Supply. The following graphs have been generated assuming 2,500,000,000 of total supply and an initial supply of 750,000,000. Here we assume we have a base InitialReward=500 and HalvingPeriod=1,750,000, which we will multiply/divide respectively by a multiplier. + +
+
+
+Fig. 5: Vesting, inflation and supply with constant inflation issuance. +
+ +Overall we observe that the inflation is similar around 5 years so we choose to stay with the multiplier 1 to ensure the inflation is not too high over the first years. \ No newline at end of file diff --git a/utils/ecc_test_vectors/Readme.md b/utils/ecc_test_vectors/Readme.md new file mode 100644 index 0000000..7de6140 --- /dev/null +++ b/utils/ecc_test_vectors/Readme.md @@ -0,0 +1,11 @@ +# Usage + +The best way to use this code is to create a virtual environment and install the dependencies there. + +- sudo apt install virtualenv +- virtualenv -p python3 . +- source bin/activate +- pip install -r requirements.txt +- python generate_test_vectors.py -curve secp256k1 + + The output will be a json with the tests of the curve. Currently we support secp256k1, P256(secp256r1), secp224k1, P224(secp224r1), secp192k1, P192(secp192r1), P384 and P512 diff --git a/utils/ecc_test_vectors/generate_test_vectors.py b/utils/ecc_test_vectors/generate_test_vectors.py new file mode 100644 index 0000000..d6b771b --- /dev/null +++ b/utils/ecc_test_vectors/generate_test_vectors.py @@ -0,0 +1,420 @@ +import importlib +import sys +import argparse +import json +from fastecdsa.point import Point + +# Known endomorphisms of the curves +endomorphism = { + "P256" : { + "lambda" : "", + "beta" : "" + }, + "secp256k1" : { + "lambda" : '0x5363ad4cc05c30e0a5261c028812645a122e22ea20816678df02967c1b23bd72', + "beta" : '0x7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee' + }, + "P192" : { + "lambda" : "", + "beta" : "" + }, + "secp192k1" : { + "lambda" : '0x3d84f26c12238d7b4f3d516613c1759033b1a5800175d0b1', + "beta" : '0xbb85691939b869c1d087f601554b96b80cb4f55b35f433c2' + }, + "P224" : { + "lambda" : "", + "beta" : "" + }, + "secp224k1" : { + "lambda" : '0x60dcd2104c4cbc0be6eeefc2bdd610739ec34e317f9b33046c9e4788', + "beta" : '0xfe0e87005b4e83761908c5131d552a850b3f58b749c37cf5b84d6768' + }, + "P384" : { + "lambda" : "", + "beta" : "" + }, + "P521" : { + "lambda" : "", + "beta" : "" + } +} + +# Data structure +data = {} +data['params'] = {} +data['multiplication'] = {'valid' : [], 'invalid' : [] } +data['addition'] = {'valid' : [], 'invalid' : [] } +data['subtraction'] = {'valid' : [], 'invalid' : [] } +data['mulAddMul'] = {'valid' : [], 'invalid' : [] } +data['decomposeScalar'] = {'valid' : [], 'invalid' : [] } +data['simMul'] = {'valid' : [], 'invalid' : [] } + +# Parse input arguments +# Commands: + # -curve : choose the curve you want to generate the test for +parser = argparse.ArgumentParser(description='Curve for which you want to generate your test vectors') +parser.add_argument('-curve', action="store", choices = ["P256", "secp256k1", "P192", "secp192k1", "P224", "secp224k1"], required=True, type=str, + dest = "curve", + help='Specify the curve to generate the test vectors, e.g., secp256k1 or P256') + +args = parser.parse_args() + +# Load curve module dependent on the given argument +curve_module = importlib.import_module("fastecdsa.curve") +target_curve = getattr(curve_module, args.curve) + +# Test vectors taken from https://chuckbatson.wordpress.com/2014/11/26/secp256k1-test-vectors/ +test_vec = [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 112233445566778899, + 112233445566778899112233445566778899, + 28948022309329048855892746252171976963209391069768726095651290785379540373584, + 57896044618658097711785492504343953926418782139537452191302581570759080747168, + 86844066927987146567678238756515930889628173209306178286953872356138621120752, + 115792089237316195423570985008687907852837564279074904382605163141518161494317, + 115792089237316195423570985008687907852837564279074904382605163141518161494318, + 115792089237316195423570985008687907852837564279074904382605163141518161494319, + 115792089237316195423570985008687907852837564279074904382605163141518161494320, + 115792089237316195423570985008687907852837564279074904382605163141518161494321, + 115792089237316195423570985008687907852837564279074904382605163141518161494322, + 115792089237316195423570985008687907852837564279074904382605163141518161494323, + 115792089237316195423570985008687907852837564279074904382605163141518161494324, + 115792089237316195423570985008687907852837564279074904382605163141518161494325, + 115792089237316195423570985008687907852837564279074904382605163141518161494326, + 115792089237316195423570985008687907852837564279074904382605163141518161494327, + 115792089237316195423570985008687907852837564279074904382605163141518161494328, + 115792089237316195423570985008687907852837564279074904382605163141518161494329, + 115792089237316195423570985008687907852837564279074904382605163141518161494330, + 115792089237316195423570985008687907852837564279074904382605163141518161494331, + 115792089237316195423570985008687907852837564279074904382605163141518161494332, + 115792089237316195423570985008687907852837564279074904382605163141518161494333, + 115792089237316195423570985008687907852837564279074904382605163141518161494334, + 115792089237316195423570985008687907852837564279074904382605163141518161494335, + 115792089237316195423570985008687907852837564279074904382605163141518161494336, + 77059549740374936337596179780007572461065571555507600191520924336939429631266, + 32670510020758816978083085130507043184471273380659243275938904335757337482424 + ] + +# Fix if a is represented as a negative number (some curves do) +if target_curve.a < 0: + target_curve.a = target_curve.p + target_curve.a + +# Write down parameters of the curve +data['params']={ + 'gx' : str(hex(target_curve.gx)), + 'gy' : str(hex(target_curve.gy)), + 'pp' : str(hex(target_curve.p)), + 'nn' : str(hex(target_curve.q)), + 'aa' : str(hex(target_curve.a)), + 'bb' : str(hex(target_curve.b)), + 'lambda' : endomorphism[args.curve]['lambda'], + 'beta' : endomorphism[args.curve]['beta'] +} + +# Generator point +G = Point(target_curve.gx, target_curve.gy, curve=target_curve) + +# Generate multiplication test vectors +for item in test_vec: + R = item * G + data['multiplication']['valid'].append({ + 'description' : "G x" + str(item), + 'input' : { + 'k' : str(item), + 'x' : str(hex(target_curve.gx)), + 'y' : str(hex(target_curve.gy)) + }, + 'output' : { + 'x' : str(hex(R.x)), + 'y' : str(hex(R.y)) + } + }) + +# Generate small scalar addition vectors +for i in range(0, 19): + P = test_vec[i] * G + Q = test_vec[0] * G + Z = test_vec[i+1] * G + data['addition']['valid'].append({ + 'description' : 'small scalar %s xG plus G' % str(i+1), + 'input' : { + 'x1' : str(hex(P.x)), + 'y1' : str(hex(P.y)), + 'x2' : str(hex(Q.x)), + 'y2' : str(hex(Q.y)) + }, + 'output': { + 'x' : str(hex(Z.x)), + 'y' : str(hex(Z.y)) + } + }) + +# Generate big scalar addition vectors +P = test_vec[22] * G +Q = test_vec[23] * G +Z = test_vec[24] * G +data['addition']['valid'].append({ + 'description' : 'big scalar 1 xG plus big scalar 2 xG', + 'input' : { + 'x1' : str(hex(P.x)), + 'y1' : str(hex(P.y)), + 'x2' : str(hex(Q.x)), + 'y2' : str(hex(Q.y)) + }, + 'output': { + 'x' : str(hex(Z.x)), + 'y' : str(hex(Z.y)) + } +}) + +# Generate big scalar plus small scalar addition vectors +for i in range(0, 19): + P = test_vec[25] * G + Q = test_vec[i] * G + Z = test_vec[25+i+1] * G + data['addition']['valid'].append({ + 'description' : 'small scalar %s xG plus big scalar 4 xG' % str(i+1), + 'input' : { + 'x1' : str(hex(P.x)), + 'y1' : str(hex(P.y)), + 'x2' : str(hex(Q.x)), + 'y2' : str(hex(Q.y)) + }, + 'output': { + 'x' : str(hex(Z.x)), + 'y' : str(hex(Z.y)) + } + }) + +# Generate subtraction vectors +for i in range(0, 19): + P = test_vec[i] * G + Q = test_vec[0] * G + Z = test_vec[i+1] * G + data['subtraction']['valid'].append({ + 'description' : 'small scalar %s xG minus G' % str(4+i+1), + 'input' : { + 'x1' : str(hex(Z.x)), + 'y1' : str(hex(Z.y)), + 'x2' : str(hex(Q.x)), + 'y2' : str(hex(Q.y)) + }, + 'output': { + 'x' : str(hex(P.x)), + 'y' : str(hex(P.y)) + } + }) + +if endomorphism[args.curve]['lambda']: + # Generate kP+ lQ vectors + Z = test_vec[24] * G + data['mulAddMul']['valid'].append({ + 'description' : 'big scalar 1 xG plus big scalar 2 xG', + 'input' : { + 'k' : str(test_vec[22]), + 'l' : str(test_vec[23]), + 'px' : str(hex(target_curve.gx)), + 'py' : str(hex(target_curve.gy)), + 'qx' : str(hex(target_curve.gx)), + 'qy' : str(hex(target_curve.gy)) + }, + 'output': { + 'x' : str(hex(Z.x)), + 'y' : str(hex(Z.y)) + } + }) + + for i in range(0, 19): + P = test_vec[25+i] * G + Z = test_vec[25+i+1] * G + data['mulAddMul']['valid'].append({ + 'description' : 'small scalar %s xG plus big scalar 4 xG' % str(i+1), + 'input' : { + 'k' : str(test_vec[25+i]), + 'l' : str(test_vec[0]), + 'px' : str(hex(target_curve.gx)), + 'py' : str(hex(target_curve.gy)), + 'qx' : str(hex(target_curve.gx)), + 'qy' : str(hex(target_curve.gy)) + }, + 'output': { + 'x' : str(hex(Z.x)), + 'y' : str(hex(Z.y)) + } + }) + +# Additional test vectors in case of secp256k1 +if args.curve == 'secp256k1': + + data['decomposeScalar']['valid'].append({ + 'description' : 'scalar decomposition of Big Scalar 16', + 'input' : { + 'k' : '115792089237316195423570985008687907852837564279074904382605163141518161494329' + }, + 'output': { + 'k1' : '-8', + 'k2' : '0' + } + }) + + data['decomposeScalar']['valid'].append({ + 'description' : 'scalar decomposition of Big Scalar 1', + 'input' : { + 'k' : '28948022309329048855892746252171976963209391069768726095651290785379540373584' + }, + 'output': { + 'k1' : '-75853609866811635898812693916901439793', + 'k2' : '-91979353254113275055958955257284867062' + } + }) + + data['decomposeScalar']['valid'].append({ + 'description' : 'scalar decomposition of Big Scalar 2', + 'input' : { + 'k' : '57896044618658097711785492504343953926418782139537452191302581570759080747168' + }, + 'output': { + 'k1' : '216210193282829828426210433195336588662', + 'k2' : '-119455732959019993483332865153036025047' + } + }) + + data['decomposeScalar']['valid'].append({ + 'description' : 'scalar decomposition of Big Scalar 21', + 'input' : { + 'k' : '77059549740374936337596179780007572461065571555507600191520924336939429631266' + }, + 'output': { + 'k1' : '-89243190524605339210527649141408088119', + 'k2' : '-53877858828609620138203152946894934485' + } + }) + + data['decomposeScalar']['valid'].append({ + 'description' : 'scalar decomposition of Big Scalar 22', + 'input' : { + 'k' : '32670510020758816978083085130507043184471273380659243275938904335757337482424' + }, + 'output': { + 'k1' : '-185204247857117235934281322466442848518', + 'k2' : '-7585701889390054782280085152653861472' + } + }) + + data['decomposeScalar']['valid'].append({ + 'description' : 'scalar decomposition of small scalar 1', + 'input' : { + 'k' : '1' + }, + 'output': { + 'k1' : '1', + 'k2' : '0' + } + }) + + data['decomposeScalar']['valid'].append({ + 'description' : 'scalar decomposition of small scalar 5', + 'input' : { + 'k' : '5' + }, + 'output': { + 'k1' : '5 ', + 'k2' : '0' + } + }) + + data['simMul']['valid'].append({ + 'description' : 'simultaneous multiplication of big scalar 21 and small scalar 22 times G', + 'input' : { + 'k1' : '-89243190524605339210527649141408088119', + 'k2' : '-53877858828609620138203152946894934485', + 'l1' : '-185204247857117235934281322466442848518', + 'l2' : '-7585701889390054782280085152653861472', + 'px' : str(hex(target_curve.gx)), + 'py' : str(hex(target_curve.gy)), + 'qx' : '0xc6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5', + 'qy' : '0x1ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a' + }, + 'output': { + 'x' : '0x7635e27fba8e1f779dcfdde1b1eacbe0571fbe39ecf6056d29ba4bd3ef5e22f2', + 'y' : '0x197888e5cec769ac2f1eb65dbcbc0e49c00a8cdf01f8030d8286b68c1933fb18' + } + }) + + data['simMul']['valid'].append({ + 'description' : 'simultaneous multiplication of big scalar 16 and big scalar 1 times G', + 'input' : { + 'k1' : '-8', + 'k2' : '0', + 'l1' : '1', + 'l2' : '0', + 'px' : str(hex(target_curve.gx)), + 'py' : str(hex(target_curve.gy)), + 'qx' : str(hex(target_curve.gx)), + 'qy' : str(hex(target_curve.gy)) + }, + 'output': { + 'x' : '0x5CBDF0646E5DB4EAA398F365F2EA7A0E3D419B7E0330E39CE92BDDEDCAC4F9BC', + 'y' : '0x951435BF45DAA69F5CE8729279E5AB2457EC2F47EC02184A5AF7D9D6F78D9755' + } + }) + + data['simMul']['valid'].append({ + 'description' : 'simultaneous multiplication of big scalar 16 and small scalar 5 times G', + 'input' : { + 'k1' : '-8', + 'k2' : '0', + 'l1' : '5', + 'l2' : '0', + 'px' : str(hex(target_curve.gx)), + 'py' : str(hex(target_curve.gy)), + 'qx' : str(hex(target_curve.gx)), + 'qy' : str(hex(target_curve.gy)) + }, + 'output': { + 'x' : '0xF9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9', + 'y' : '0xC77084F09CD217EBF01CC819D5C80CA99AFF5666CB3DDCE4934602897B4715BD' + } + }) + + data['simMul']['valid'].append({ + 'description' : 'simultaneous multiplication of big scalar 1 and big scalar 2 times G', + 'input' : { + 'k1' : '-75853609866811635898812693916901439793', + 'k2' : '-91979353254113275055958955257284867062', + 'l1' : '216210193282829828426210433195336588662', + 'l2' : '-119455732959019993483332865153036025047', + 'px' : str(hex(target_curve.gx)), + 'py' : str(hex(target_curve.gy)), + 'qx' : str(hex(target_curve.gx)), + 'qy' : str(hex(target_curve.gy)) + }, + 'output': { + 'x' : '0xE24CE4BEEE294AA6350FAA67512B99D388693AE4E7F53D19882A6EA169FC1CE1', + 'y' : '0x8B71E83545FC2B5872589F99D948C03108D36797C4DE363EBD3FF6A9E1A95B10' + } + }) + +# Dump to json +with open(args.curve +'.json', 'w') as outfile: + json.dump(data, outfile, indent=2) diff --git a/utils/ecc_test_vectors/requirements.txt b/utils/ecc_test_vectors/requirements.txt new file mode 100644 index 0000000..b42bc21 --- /dev/null +++ b/utils/ecc_test_vectors/requirements.txt @@ -0,0 +1,3 @@ +fastecdsa==1.7.4 +pkg-resources==0.0.0 +six==1.12.0