Draft prototype for the EXODUS verifier in solidity.
Go to file
Uncle Fatso 92a8ce4b44
update readme based on the latest code changes
Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
2025-10-28 21:32:06 +03:00
lib initial commit 2025-10-07 21:12:32 +03:00
src extend tests 2025-10-28 21:31:05 +03:00
test extend tests 2025-10-28 21:31:05 +03:00
.gitignore initial commit 2025-10-07 21:12:32 +03:00
.gitmodules initial commit 2025-10-07 21:12:32 +03:00
foundry.toml initial commit; double, addition, division are already optimized 2025-10-12 16:25:36 +03:00
raw_vectors.json initial commit; double, addition, division are already optimized 2025-10-12 16:25:36 +03:00
README.md update readme based on the latest code changes 2025-10-28 21:32:06 +03:00

EXODUS - EXchange Of Digital Uniformed Signatures

Overview

This module optimizes a hot spot in the verification formula used for missingsigner recovery. The target expression is:

k*P + l*Q + d*M

which requires three scalar multiplications and two point additions. Naive ellipticcurve routines make this very gasexpensive; this work reduces cost while maintaining correctness.

For full background and protocol details see the project wiki.

Goals

  • Reduce gas for the targeted combination of scalar multiplications and additions.
  • Keep implementation compact and auditable for onchain use.
  • Maintain correctness and safety for cryptographic operations.

Design choices

  • Use Projective coordinates (not Jacobian) to cut down on the number of mulmod/addmod operations where possible while retaining simple formulas for point addition and doubling.
  • Perform the final conversion to affine coordinates with an optimized Extended Euclidean Algorithm implemented in inline assembly to reduce gas compared with highlevel inversion routines.
  • Benchmark against the Jacobian implementation from the witnet ellipticcurvesolidity project as a reference.

Rationale

  • Projective coordinates: fewer modular multiplications in the common path, making point operations cheaper on average.
  • Assembly Extended Euclidean Algorithm for inversion: this algorithm in optimized inline assembly typically yields lower gas for single inversions compared with repeated mulmod exponentiation or other higherlevel approaches.
  • Comparing to a wellmaintained Jacobian implementation gives a meaningful baseline for gas and correctness.

Gas Usage

Jacobian is the original implementation used as a reference implementation, while Projective is optimized one.

╭----------------------------------------+-----------------+---------+---------+---------+---------╮
| src/MathTester.sol:MathTester Contract |                 |         |         |         |         |
+==================================================================================================+
| Deployment Cost                        | Deployment Size |         |         |         |         |
|----------------------------------------+-----------------+---------+---------+---------+---------|
| 3247879                                | 14807           |         |         |         |         |
|----------------------------------------+-----------------+---------+---------+---------+---------|
|                                        |                 |         |         |         |         |
|----------------------------------------+-----------------+---------+---------+---------+---------|
| Function Name                          | Min             | Avg     | Median  | Max     | # Calls |
|----------------------------------------+-----------------+---------+---------+---------+---------|
| addGhost                               | 1450            | 1450    | 1450    | 1450    | 44      |
|----------------------------------------+-----------------+---------+---------+---------+---------|
| addJacobian                            | 1976            | 1976    | 1976    | 1976    | 88      |
|----------------------------------------+-----------------+---------+---------+---------+---------|
| addMixedGhost                          | 1084            | 1084    | 1084    | 1084    | 44      |
|----------------------------------------+-----------------+---------+---------+---------+---------|
| addMixedProjective                     | 1098            | 1098    | 1098    | 1098    | 44      |
|----------------------------------------+-----------------+---------+---------+---------+---------|
| addProjective                          | 1252            | 1252    | 1252    | 1252    | 44      |
|----------------------------------------+-----------------+---------+---------+---------+---------|
| doubleGhost                            | 732             | 732     | 732     | 732     | 45      |
|----------------------------------------+-----------------+---------+---------+---------+---------|
| doubleJacobian                         | 1101            | 1101    | 1101    | 1101    | 45      |
|----------------------------------------+-----------------+---------+---------+---------+---------|
| doubleProjective                       | 710             | 710     | 710     | 710     | 45      |
|----------------------------------------+-----------------+---------+---------+---------+---------|
| isOnCurve                              | 335             | 335     | 335     | 335     | 306     |
|----------------------------------------+-----------------+---------+---------+---------+---------|
| isOnCurveGhost                         | 863             | 863     | 863     | 863     | 306     |
|----------------------------------------+-----------------+---------+---------+---------+---------|
| mulPairEc                              | 103705          | 731500  | 1058710 | 1270392 | 44      |
|----------------------------------------+-----------------+---------+---------+---------+---------|
| mulPairGhost                           | 4241            | 178420  | 310213  | 337584  | 44      |
|----------------------------------------+-----------------+---------+---------+---------+---------|
| mulPairProjective                      | 5275            | 182401  | 327577  | 361377  | 44      |
|----------------------------------------+-----------------+---------+---------+---------+---------|
| mulQuartetEc                           | 308036          | 1561245 | 1982575 | 2672715 | 42      |
|----------------------------------------+-----------------+---------+---------+---------+---------|
| mulQuartetProjective                   | 12468           | 206689  | 346026  | 396570  | 42      |
|----------------------------------------+-----------------+---------+---------+---------+---------|
| mulSingleEc                            | 2093            | 327159  | 408874  | 600125  | 44      |
|----------------------------------------+-----------------+---------+---------+---------+---------|
| mulSingleGhost                         | 1803            | 113386  | 148753  | 219187  | 44      |
|----------------------------------------+-----------------+---------+---------+---------+---------|
| mulSingleProjective                    | 1916            | 114289  | 149838  | 220860  | 44      |
|----------------------------------------+-----------------+---------+---------+---------+---------|
| mulTripletEc                           | 205416          | 1140277 | 1546137 | 1958683 | 43      |
|----------------------------------------+-----------------+---------+---------+---------+---------|
| mulTripletProjective                   | 6636            | 199868  | 346618  | 397506  | 43      |
|----------------------------------------+-----------------+---------+---------+---------+---------|
| toAffineGhost                          | 11853           | 13845   | 13925   | 15527   | 176     |
|----------------------------------------+-----------------+---------+---------+---------+---------|
| toAffineJacobian                       | 40395           | 47860   | 47979   | 53983   | 133     |
|----------------------------------------+-----------------+---------+---------+---------+---------|
| toAffineProjective                     | 11165           | 13918   | 13900   | 16214   | 351     |
╰----------------------------------------+-----------------+---------+---------+---------+---------╯

╭-----------------------------------------------------+-----------------+--------+--------+--------+---------╮
| test/GhostVerifier.t.sol:GhostVerifierImpl Contract |                 |        |        |        |         |
+============================================================================================================+
| Deployment Cost                                     | Deployment Size |        |        |        |         |
|-----------------------------------------------------+-----------------+--------+--------+--------+---------|
| 1871747                                             | 8463            |        |        |        |         |
|-----------------------------------------------------+-----------------+--------+--------+--------+---------|
|                                                     |                 |        |        |        |         |
|-----------------------------------------------------+-----------------+--------+--------+--------+---------|
| Function Name                                       | Min             | Avg    | Median | Max    | # Calls |
|-----------------------------------------------------+-----------------+--------+--------+--------+---------|
| setPubkey                                           | 111552          | 111565 | 111564 | 111576 | 9       |
|-----------------------------------------------------+-----------------+--------+--------+--------+---------|
| verify                                              | 611046          | 680989 | 648023 | 955747 | 9       |
╰-----------------------------------------------------+-----------------+--------+--------+--------+---------╯

The verify function consumes a maximum of 955,747 gas units when processing 1,000 signers with 300 missing signatures. Assuming an ETH price of $4,200 and gas cost of 2 gwei:

  • test_verify_5_of_5_works() (gas: 735686 = $6.18) - Baseline (100%)
  • test_verify_67_of_100() (gas: 812318 = $6.82) - 10% increase
  • test_verify_700_of_1000() (gas: 1118143 = $9.39) - 51% increase

The gas cost scales reasonably across different numbers of signers, making the solution practical for real-world use cases.

Compiler Inconsistency Note

We observed unexpected behavior in the Solidity compiler. Despite identical code in ECMathProjective and GhostEllipticCurves libraries, gas costs vary for critical elliptic curve operations:

  • doubleProjective and addProjective show inconsistent gas costs between implementations
  • mulSingleGhost unexpectedly consumes less gas than mulSingleProjective

These variations occur even with clean compilation runs, suggesting compiler-level optimizations that behave unpredictably with identical code in different library contexts. The root cause remains unclear.

Implementation Details

Final implementation formula is:

s*G + sum_i^n (e*ai*(Pi) + b*R1m + d*R2m) = R + e*Hagg
s*G + sum_i^n (e*ai*(Pi)) + sum_i^n (b*R1m) + sum_i^n (b*R2m) = b*R1 + d*R2 + e*Hagg
s*G + e*(Hagg - sum_i^n Hi) = b*(R1 - sum_i^n R1m) + d*(R2 - sum_i^n R2m)
s*G + e*(Hagg - sum_i^n Hi) = b*R1e + d*R2e

where
R1 = R1e + R1m (existing nonce + missing nonce)
R2 = R2e + R2m
Hi = ai*Pi

Nonce Abuse

Our approach enables any participant to submit pairs of (R1e, R1m) and (R2e, R2m), which we use to reconstruct the original nonces R1 and R2. These reconstructed values are essential for computing b and d during the verification process, ultimately deriving the final R required for commitment e.

This design leverages circular dependencies and cryptographic hash functions to ensure verification integrity.

arbitrary data: s, msg, R1e, R1m, R2e, R2m
s*G + e*H` = b*R1e + d*R2e
s*G = b*R1e + d*R2e - e*H`
s*G = target_value (Discrete Log Problem)

However, the hash dependencies create a circular constraint similar to the "chicken and egg" problem:

b = sha256(R1e + R1m, R2e + R2m, H, msg)
d = sha256(msg, H, R2e + R2m, R1e + R1m)
e = sha256(b*(R1e+R1m) + d*(R2e+R2m), H, msg)

b*R1e + d*R2e - s*G = e*H`

For now it looks safe if sha256 or keccak256 hashing algorithm is used.

Missing Aggregation Abuse

We are proposing the to store not only final Hagg point but the hash of all signers (both coordinates for each) in the sequence that they were aggregated. Thus each verify call will have all list of Xi=ai*Pi with indexes of missing signers. Here we are relying on fact that sha256 and keccak256 do not have any known ability to find collisions and resistant to preimage attack.

We propose storing not only the final aggregated point Hagg but also the cryptographic hash of all signers' coordinates in their aggregation sequence. This approach ensures each verify call has access to the complete list of Xi = ai * Pi along with indexes of missing signers.

This design relies on the cryptographic security of sha256 and keccak256, both of which:

  • Have no known practical collision attacks
  • Maintain strong resistance to preimage attacks
  • Provide robust security guarantees for our verification process

Precompile Abuse

The step by step hack is described here.

Contributing

All contributions are welcome — whether it's code, documentation, tests, performance benchmarks, or review. Please submit commits, issues, or pull requests; any help to improve correctness, security, or gas efficiency is greatly appreciated.