Awesome
š„·š½ Dark Safe š¦
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.
- I knew I did not want to store all the leaves of some
n
depth merkle tree, nor do hash path and leaf index computation off-chain. Also... Merkle trees are boring š«š³
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:
- double signing
- under signing
- non-signer signatures
A TLDR Of How It works
š·š½āāļø Setup
- An admin selects up to 8 Ethereum EOA
addresses
as signers on the safe and a signingthreshold
- (Note: to prevent brute-force attacks decoding who the signers are, add at least one fresh EOA as a signer).
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).- Consider those combinations as "roots" and Lagrange Interpolate a polynomial that passes through all those points where
y=0
. - Take the Pedersen hash of the polynomial as a suscinct commitment to the polynomial.
- Deploy a new instance of DarkSafe, via the ModuleProxyFactory passing the
polynomialHash
and thepolynomial
as initialization params.- The contract will emit the
polynomial
andpolynomialHash
in aSignersRotated()
event as decentralized, succinct data stucture to represent the signer set and threshold.
- The contract will emit the
āļø Signing
- Sign over a SafeMessageHash with an EOA Private Key (via
eth_personalSign
). - Pass your signature to the next signer.
- 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.
- Have some relayer call the _execute function, passing only the safe TX data, and the valid noir proof.
- š Transaction is executed š
See it in action
yarn && yarn build --debug
Run tests
yarn && yarn build
cd circuits/ && nargo prove
forge test
Notes
-
Check out DRY - a cool merkle tree implementation with FaceID by some noir OGs
-
This project is just for fun, demonstrating a relatively efficient and elegant usecase for Noir and shouldn't be used in production unless we work together on this and get it audited
-
Interpolating a polynomial over the K choose N of the signer set is not secure enough for me to be comfortable. It is not impossible to brute force k choose n up to 8 over all the Ethereum addresses and compute f(x) to try and brute-force find out who's on the safe.
Some possible solutions are:
- Always spin up a fresh EOA to add as a signer, it's important this account has never made an Ethereum transaction on any chain.
- Refactor the code to accept a bit of randomness: an
r
value to hash together with eachroot
. This makes it impossible to brute force. Ther
value can be as simple as a knownpassword
has to at least be known by the prover.
Massive Thanks to...
the boiz
noir guys