Repo Structure
βββ 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
A zodiac-compatible Safe module that shields the ethereum addresses of up to 8 authorized "dark signers".
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.
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 π«π³
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
- 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
- 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 π
yarn && yarn build --debug
yarn && yarn build
cd circuits/ && nargo prove
forge test
-
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.
the boiz
noir guys