Home

Awesome

šŸ„·šŸ½ Dark Safe šŸ¦

Deep Dive Video šŸ“ŗ

deep dive video

<details> <summary>Repo Structure</summary>
ā”œā”€ā”€ circuits # contains the noir code
ā”‚   ā”œā”€ā”€ src
ā”‚   ā”‚   ā””ā”€ā”€ main.nr # the DarkSafe Noir circuit
ā”‚   ā”œā”€ā”€ Prover.toml # contains all the private inputs / data
ā”‚   ā””ā”€ā”€ Verifier.toml # contains all the public inputs / data
ā”œā”€ā”€ contracts
ā”‚   ā”œā”€ā”€ DarkSafe.sol # the Zodiac Module to be installed on the safe
ā”‚   ā””ā”€ā”€ Verifier.sol # the `nargo` cli generated contract used to verify the a generated proof
ā”œā”€ā”€ scripts
ā”‚   ā””ā”€ā”€ build.ts # A CLI tool to help generate the input data
</details>

What

A zodiac-compatible Safe module that shields the ethereum addresses of up to 8 authorized "dark signers".

Background

The idea for this project started back at devcon 6 - where I saw Noir had efficient keccak256 and secp256k1 signature verification circuits.

I spent the plane ride home wondering if Noir could enable Gnosis Safes with a shielded set of signers, removing the attack vector of publically stored signer addresses.

This would allow for anonymous onchain orgs, where signers could coordinate to execute transactions.

Problem

Using this project as a research playground, I wanted to find an ~ elegant ~ data structure that represented the set of valid sigers and the signing threshold in a single hash.

Solution

Thanks to some great help from @autoparallel and @0xjepsen, I ended up representing valid signer sets (including signing threshold) into a polynomial.

This polynomial is emitted in an event onchain as a reverse encoded array, of 32 byte coefficiencts, with the array index representing the degree of the x value's exponent. For example:

# Polynomial in array form: index represents exponent degree.
[42, 1, 9, 145, 1]

# Polynomial in standard algebraic form
Polynomial = x^4 + 145x^3 + 9x^2 + x + 42
              šŸ‘†šŸ¼                   šŸ‘†šŸ¼   šŸ‘†šŸ¼
# (implied) (1x^4)             (1x^1) (42x^0)

This polynomial represents all the valid combinations of the signers.

Just like Safe, it protects against:

A TLDR Of How It works

šŸ‘·šŸ½ā€ā™‚ļø Setup

  1. An admin selects up to 8 Ethereum EOA addresses as signers on the safe and a signing threshold
    • (Note: to prevent brute-force attacks decoding who the signers are, add at least one fresh EOA as a signer).
  2. K choose N over the signer set and the threshold to find all possible additive combinations of the Ethereum addresses (remember, an eth address is just a number, so you can use addition to express a combination of multiple addresses).
  3. Consider those combinations as "roots" and Lagrange Interpolate a polynomial that passes through all those points where y=0.
  4. Take the Pedersen hash of the polynomial as a suscinct commitment to the polynomial.
  5. Deploy a new instance of DarkSafe, via the ModuleProxyFactory passing the polynomialHash and the polynomial as initialization params.
    • The contract will emit the polynomial and polynomialHash in a SignersRotated() event as decentralized, succinct data stucture to represent the signer set and threshold.

āœļø Signing

  1. Sign over a SafeMessageHash with an EOA Private Key (via eth_personalSign).
  2. Pass your signature to the next signer.
  3. Repeat steps 1+2 until a valid signer until a valid proof can be generated via the Noir circuit.
    • This keeps other signers anonymous on a "need-to-know" basis. In other words, not all signers need to be known at proof generation.
  4. Have some relayer call the _execute function, passing only the safe TX data, and the valid noir proof.
  5. šŸƒ Transaction is executed šŸƒ

See it in action

yarn && yarn build --debug

Run tests

yarn && yarn build

cd circuits/ && nargo prove

forge test

Notes

Some possible solutions are:

Massive Thanks to...

the boiz

noir guys