What the system actually proves
At a high level, the system proves:
“There exists a wallet address, a signature, and a challenge message such that:
They hash to these public commitments, and
The ECDSA signature is valid for that wallet and challenge.”
Critically, the verifier only ever sees Poseidon hashes and a Groth16 proof – never the raw wallet, signature, or UnifiedID.
Public vs private circuit inputs
The WalletOwnership.circom circuit is designed so that:
Public inputs (visible to any verifier, on- or off-chain):
walletAddressHash – Poseidon hash of the 20-byte wallet address
challengeHash – Poseidon hash of the padded 32-byte challenge message
Private inputs (known only to the prover / witness generator):
walletAddress[20] – raw Ethereum address bytes
signature[65] – ECDSA signature bytes (r || s || v)
challengeMessage[32] – padded challenge bytes (contains the UnifiedID text)
Outputs:
signatureHash – Poseidon hash of the 65-byte signature
ownershipProof – Poseidon hash of (walletHash, messageHash, signatureHash)
Circuit logic (WalletOwnership)
Conceptually:
template WalletOwnership() {
signal input walletAddressHash;
signal input challengeHash;
signal input walletAddress[20];
signal input signature[65];
signal input challengeMessage[32];
signal output signatureHash;
signal output ownershipProof;
// Hash the wallet bytes and bind them to the public commitment
component walletHasher = HashBytes(20);
// walletHasher.in[i] <== walletAddress[i];
signal walletHash;
walletHash <== walletHasher.out;
walletHash === walletAddressHash;
// Hash the challenge bytes and bind them to the public commitment
component challengeHasher = HashBytes(32);
// challengeHasher.in[i] <== challengeMessage[i];
signal messageHash;
messageHash <== challengeHasher.out;
messageHash === challengeHash;
// Hash the signature bytes
component signatureHasher = HashBytes(65);
// signatureHasher.in[i] <== signature[i];
signatureHash <== signatureHasher.out;
// Final ownership proof = Poseidon(walletHash, messageHash, signatureHash)
component ownershipHasher = Poseidon(3);
ownershipHasher.inputs[0] <== walletHash;
ownershipHasher.inputs[1] <== messageHash;
ownershipHasher.inputs[2] <== signatureHash;
ownershipProof <== ownershipHasher.out;
}Key point:
Verifiers see only walletAddressHash, challengeHash, signatureHash, ownershipProof and the Groth16 proof.
They never see:
walletAddress[20]
signature[65]
challengeMessage[32] (which contains the UnifiedID)
Last updated