Skip to content

Commit d6791ac

Browse files
authored
Merge pull request #430 from bane-labs/antimev-doc
antimev: update document and fix a method naming
2 parents 603d6ea + 2cc97ee commit d6791ac

File tree

6 files changed

+156
-6
lines changed

6 files changed

+156
-6
lines changed

antimev/README.md

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
# Neo X Anti-MEV
2+
3+
The anti-mev feature of Neo X is built based-on decentralized key generation, threshold encryption and enhanced dBFT consensus algorithm.
4+
5+
Users can encrypt and send their secret transactions through Envelopes to avoid malicious MEV attacks from miners or other senders in public transaction pool.
6+
7+
## DKG
8+
9+
Neo X empowers a fully decentralized key generation among consensus members. After being elected by Neo X Governance, seven candidates will be scheduled to become the next consensus group of dBFT. But before the formal epoch change, a successful round of DKG is required.
10+
11+
During each round of DKG, there are 3 things required.
12+
13+
1. The next consensus group performs a `Share` to generate `n` distributed secret shares and a global public key, where `n` is the size of Neo X consensus;
14+
2. The current consensus group (if exists) performs a `Reshare` to pass the secret of the last round to this round;
15+
3. (optional) If no more than `f` shares of the last round secret are lost, then left `2f+1` shares perform a `Recover` to complete Step 2.
16+
17+
Since [v0.3.0](https://github.com/bane-labs/go-ethereum/releases/tag/v0.3.0), the DKG module of consensus engine handles the whole process automatically, except setting up the initial anti-mev keystore with a secret passphrase.
18+
19+
### Share
20+
21+
Every participant in `Share` should execute the following steps.
22+
23+
1. Take a random polynomial $f(x)=a_0+a_1 x+a_2 x^2+⋯a_{t-1}x^{t-1}$ as his local secret, where `t` is the threshold, should equal to `2f+1`;
24+
2. Compute $f_1,f_2,...,f_n$ where $f_i=f(i)$ and share them with corresponding participants, where `i` is the index of different participants of `Share`;
25+
3. Accept all $f_i$ from other participants as $f_1(i),f_2(i),...,f_n(i)$, where `i` is the index of receiver, and compute $s_i=\sum f_i$ to get the final secret key.
26+
27+
Meanwhile, the global public key is generated with following steps.
28+
29+
1. Every participant uploads $F(x)=f(x)G_1$ within his PVSS to the KeyManagement contract;
30+
2. KeyManagement contract verifies the validity of each PVSS, and compute $S=\sum_{i=1}^n F_i(0)$ as the global public key;
31+
32+
A well-constructed PVSS should contain following properties.
33+
34+
1. $F(x)=f(x)G_1$ as the commitment of sender's local secret;
35+
2. $rG_1,rG_2$ as a pair of commitment of a random scalar $r$;
36+
3. $F=(F(1),F(2),...,F(n))$ as the commitment share messages;
37+
38+
The KeyManagement verifies the computation of $F(1),F(2),...,F(n)$, and the validity of scalar $r$. Receivers then verify the commitment against the received share message through $e(r_1f(i),g_2)=e(F(i),r_2)$.
39+
40+
To ensure a verifiable message encryption of $f(i)$, zero-knowledge proof will be adopted in the near future.
41+
42+
### Reshare
43+
44+
Every participant in `Reshare` should execute following steps.
45+
46+
1. Regenerate his local secret as $f'(x)=a_0+a'_1 x+a'_2 x^2+⋯a'_{t-1}x^{t-1}$, only keep the same $a_0$;
47+
2. Repeat `Share` Step 2 and 3, but send the sharings to the next consensus group.
48+
49+
The KeyManagement contract ensures each $F(0)=F'(0)$, so that after `Reshare`, the global public key doesn't change, and the original local secrets and secret shares are not leaked.
50+
51+
### Recover
52+
53+
The current consensus group in `Recover` should send all received sharings $f_i$ from lost index `i` to its successor. So that the receiver can recover the original local secrets through [Lagrange interpolation](https://en.wikipedia.org/wiki/Lagrange_polynomial).
54+
55+
`Recover` exposes some (no more than `f`) of the original local secrets, hence `Recover` stage is not allowed until index `i` is confirmed to be absent from `Reshare`.
56+
57+
## TPKE
58+
59+
The DKG procedure held by Governance allows a threshold public key encryption (TPKE) scheme on Neo X, as well as a threshold block signature scheme. Only if at least `2f+1` of Neo X CNs broadcast a piece of a secret/signature, the network can decrypt a transaction or sign a valid block.
60+
61+
Neo X TPKE encodes any secret to BLS12381 $G_1$ for encryption and any message to BLS12381 $G_2$ for signature.
62+
63+
### Encryption
64+
65+
For a secret $msg$, Neo X takes a random $G_1$ point $P$ as the seed for a random AES key, so its ciphertext is $C_1=AES(Hash(P), msg)$.
66+
67+
Neo X also takes a random scalar $r$ to encrypt $P$ as $C_2=P+rS$, where $r$ is a random scalar and $S$ is the global public key. The final encrypted message broadcasted in the network is then $C=(C_1,C_2)$.
68+
69+
To recover the original $msg$, the Neo X consensus needs to decrypt $C_1$ to recover $P$. Each CN should try to compute and share $s_iR$, where $R$ is the commitment of the random scalar $r$ and $s_i$ is the local secret key.
70+
71+
Since the validator index (DKG index) is public in Neo X Governance, these shares then can be aggregated and solved with [Vandermonde matrix](https://en.wikipedia.org/wiki/Vandermonde_matrix).
72+
73+
After recovering the seed $P$, everyone in the network can compute the original $msg$ through AES decryption.
74+
75+
### Signature
76+
77+
For a message $msg$, Neo X encodes it to $G_2$ as $Q=HashToG2(msg)$, then a signature share can be computed as $s_iH$, where $s_i$ is the local secret key.
78+
79+
After collecting enough broadcasted shares, CNs can aggregate and get the final signature with [Vandermonde matrix](https://en.wikipedia.org/wiki/Vandermonde_matrix) in the same way as TPKE decryption.
80+
81+
## Envelope Transaction
82+
83+
Neo X users can only send secret transactions to prevent malicious MEV attacks through Envelope transactions.
84+
85+
### Construction
86+
87+
Only transactions have the following properties can be acknowledged as valid Envelopes in dBFT.
88+
89+
1. The `to` address is Neo X GovReward contract (`0x1212000000000000000000000000000000000003`);
90+
2. The `from` address is the same as the inner secret transaction;
91+
3. The `nonce` is the same as the inner secret transaction;
92+
4. The `gastip` is not lower than Neo X `minGasTipCap` policy plus `envelopeFee` policy, and the inner transaction `gastip` is not lower than this value;
93+
5. The `data` begins with `0xffffffff`, then is followed by a 4-byte DKG epoch index (in BE form), then is followed by a 4-byte inner secret transaction `gaslimit` (in BE form), then is followed by a 32-byte inner secret transaction hash, then is followed by an encoded TPKE ciphertext.
94+
95+
So in a nutshell, Envelopes are always calling the `fallback()` method of Neo X GovReward contract. This method will burn gas based on declared `gaslimit` in `data` to allocate block space in Envelope execution, and it works with `eth_estimateGas` automatically.
96+
97+
Here is an example of the `data` field of an Envelope transaction.
98+
```
99+
| prefix | epoch |gaslimit| inner secret transaction hash | TPKE ciphertext |
100+
| 4-byte | 4-byte | 4-byte | 32-byte | left bytes |
101+
|0xffffffff|00000001|00005208|777bbe0bb1e4c34310c50160c1a22e66bfab8531c54b9bc87739a17eff6fd15a|80f8c8c2...fa6a1810|
102+
```
103+
104+
To send a secret transaction wrapped with an Envelope, we recommend the following steps which should be compatible with most of popular wallets (e.g. Metamask).
105+
106+
1. Construct a secret transaction;
107+
2. Request the wallet to sign this transaction and send it to nodes configured with `--txpool.signaturecache`;
108+
3. Request the wallet to sign the `nonce` of the secret transaction as a message;
109+
4. Use this signature to fetch the signed transaction through `eth_getEncryptedTransaction`;
110+
5. Encrypt the signed transaction with Neo X TPKE;
111+
6. Construct an Envelope transaction with the encrypted data and send it through wallet with the same `nonce`.
112+
113+
It has to be mentioned that the nodes in Step 2 always return an RPC error to prevent the wallet nonce from increasing. Please try Step 3 and 4 to confirm the result or analyze the RPC error message.
114+
115+
### Verification
116+
117+
Envelopes should first pass the mempool verification, otherwise they can't get further decrypted in dBFT.
118+
119+
1. The sender should have enough balance to pay for the block space which the Envelope requires;
120+
2. The `nonce` used by both the secret transaction and its Envelope is valid;
121+
3. The Envelope `gaslimit` should not exceed the `maxEnvelopeGasLimit` policy.
122+
123+
If a user sends a valid Envelope transaction, and the TPKE ciphertext can be decrypted successfully, and the decrypted inner transaction is valid, then the decrypted transaction will replace the Envelope transaction in the block space and get executed, regardless of failed or not.
124+
125+
If a user sends an invalid Envelope transaction, or the TPKE ciphertext can't be decrypted successfully, or the decrypted inner transaction is invalid, then the Envelope transaction will be rejected by mempool or get mined into the next block directly (if Envelope is accepted, then the amount of gas designated for inner transaction execution is burned). The latter means **the user will pay for the block space, but the secret transaction will not be acknowledged**.
126+
127+
It has to be mentioned that there are both gas limit and number limit for Envelope transactions in each block, which is defined by the Neo X `maxEnvelopesPerBlock` policy and `maxEnvelopeGasLimit` policy. So your Envelope transactions may get delayed in mempool when there is traffic.
128+
129+
### RPC API
130+
131+
There are several new and useful RPC APIs for Envelope construction.
132+
133+
1. `eth_getEncryptedTransaction` returns the cached and signed secret transactions. It requires a valid sender signature in parameters, and only works on nodes configured with `--txpool.signaturecache`;
134+
2. `eth_envelopeFee` returns the minimum additional `gastip`/`gasprice` that anti-mev transactions should pay for the service;
135+
3. `eth_maxEnvelopeGasLimit` returns the maximum `gaslimit` that an Envelope can declare for itself.
136+
137+
## Anti-MEV dBFT
138+
139+
To preform an honest transaction ordering and prevent malicious MEV attacks, the dBFT consensus of Neo X has a new period `PreCommit`. Encrypted transactions wrapped with Envelopes are firstly ordered in `PrepareRequest` and `PrepareResponse`, then are decrypted during `PreCommit` stage and included into the proposed block instead of corresponding Envelopes. So that during the final `Commit` stage block signatures are collected for the finalized proposal that includes decrypted transactions.
140+
141+
It is the Envelope transactions that are first proposed in `PrepareRequest`. They are handled in the same way as normal transactions, so Envelopes can appear anywhere in the block space. But since they always pay additional decryption fee in price, most of them will be placed at the front.
142+
143+
After `PrepareResponse`, dBFT has already confirmed a solid `PreBlock` for the next block height, so the transaction order is determined before Envelope decryption. Hence, in anti-mev dBFT, nobody can't audit the content of Envelopes and reorder these transactions based on MEV.
144+
145+
During the new `PreCommit` period, CNs pick out Envelope transactions and broadcast decryption shares for them. So that in the end of `PreCommit` there must be at least `2f+1` decryption shares and Envelope transactions can be replaced by decrypted inner transactions. View change will not happen after here, so that it's impossible for secret transactions to become a target of MEV attack after they got decrypted.
146+
147+
In the final `Commit`, CNs compute and broadcast signature shares for the new block proposal. Only after at least `2f+1` signature shares are collected, the new block can have a valid signature and get acknowledged by the network.

antimev/util.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ func getScaler(size int, threshold int) int {
3434
// searchDLCM tries to find a minimum value of scaler for the given matrix.
3535
func searchDLCM(matrix [][]int, l, pos, offset, size, threshold int) int {
3636
if pos == threshold {
37-
d, coeff := tpke.Feldman(matrix)
37+
d, coeff := tpke.Vandermonde(matrix)
3838
g := d
3939
for i := 0; i < len(coeff); i++ {
4040
g = gcd(g, coeff[i])

contracts/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,9 @@ The current Neo X Policy maintains following parameters. All these policies are
149149
|Minimum Transaction Tip Cap|`minGasTipCap`|Force transaction senders to pay a minimum tip to Neo X Governance|
150150
|Base Fee|`baseFee`|Burn a fixed part of transaction fees instead of following EIP-1559's dynamic evaluation|
151151
|Candidate Limit|`candidateLimit`|Limit the number of candidates in Governance registration and election|
152+
|Envelope Fee|`envelopeFee`|Force envelope transaction senders to pay an extra tip to Neo X Governance|
153+
|Maximum Envelopes Per Block|`maxEnvelopesPerBlock`|Limit the number of envelope transactions in each block|
154+
|Maximum Envelope Gas Limit|`maxEnvelopeGasLimit`|Limit the gas consumption of each envelope transaction|
152155

153156
Since all the policy setters adopt the `needVote` modifier, any policy change requires more than 1/2 of the current Neo X consensus nodes votes to be collected.
154157

crypto/tpke/encryption.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ type workerResult struct {
124124
}
125125

126126
// AggregateAndDecrypt tries to aggregate DecryptionShares and decrypts CipherTexts with verification
127-
// This method takes a batch of ordered CipherTexts, DecryptionShares and a matrix for Feldman
127+
// This method takes a batch of ordered CipherTexts, DecryptionShares and a matrix for Vandermonde
128128
// The size of DecryptionShare array should be len(message)*len(ciphertext)
129129
// Each row of the input matrix should be [1, i, i^2, ..., i^(threshold-1)], i is the dkg key index
130130
// The message amount should be larger than threshold, otherwise the result will be wrong
@@ -139,7 +139,7 @@ func AggregateAndDecrypt(cts []*CipherText, matrix [][]int, shares [][]*Decrypti
139139
}
140140

141141
// Be aware of the integer overflow when the size and threshold of tpke grow big
142-
d, coeff := Feldman(matrix)
142+
d, coeff := Vandermonde(matrix)
143143
d = scaler / d
144144
results := make([]*bls12381.G1Affine, len(cts))
145145
// Compute M=C-d1/d

crypto/tpke/signature.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ func (s *SignatureShare) Bytes() []byte {
8686
}
8787

8888
// AggregateSigShares tries to aggregate SignatureShare to a BLS signature.
89-
// This method takes a slice of [SignatureShare] and a matrix for Feldman.
89+
// This method takes a slice of [SignatureShare] and a matrix for Vandermonde.
9090
// The size of [SignatureShare] slice should be equal to len(message).
9191
// Each row of the input matrix should be [1, i, i^2, ..., i^(threshold-1)], where i
9292
// is the dkg key index. The message amount should be larger than threshold,
@@ -96,7 +96,7 @@ func AggregateSigShares(matrix [][]int, shares []*SignatureShare, scaler int) (*
9696
return nil, ErrTPKELengthMismatch
9797
}
9898
// Be aware of the integer overflow when the size and threshold grow big
99-
d, coeff := Feldman(matrix)
99+
d, coeff := Vandermonde(matrix)
100100
d = scaler / d
101101
// Compute d1
102102
denominator := big.NewInt(int64(abs(d)))

crypto/tpke/util.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ func polyAdd(p1 []*big.Int, p2 []*big.Int) []*big.Int {
207207
}
208208
}
209209

210-
func Feldman(matrix [][]int) (int, []int) {
210+
func Vandermonde(matrix [][]int) (int, []int) {
211211
// Compute D, D1
212212
d, coeff := determinant(matrix, len(matrix))
213213
g := d

0 commit comments

Comments
 (0)