Proof of Signature
A Proof of Signature allows a prover to demonstrate, in zero knowledge, that a given message was correctly signed under a specific public key — without revealing the private key.
At first glance, this may seem redundant: a digital signature is already a form of zero-knowledge proof in the informal sense, since it convinces a verifier that the signer possesses the private key without revealing it.
However, embedding signature verification inside a ZK-SNARK brings important advantages:
- it allows signatures to be verified as part of a larger privacy-preserving computation
- it enables efficient on-chain verification of complex authentication logic
- it allows signatures to be composed with other ZK proofs (membership, range, encryption, etc.)
In this chapter, we focus on proving correct ECDSA signatures inside a SNARK.
ZK-Unfriendly Nature of ECDSA
In this chapter we focus on verifying ECDSA over secp256k1 with SHA3 (Keccak), as used in Bitcoin and Ethereum.
However, ECDSA is not ZK-friendly:
- it relies on elliptic curve scalar inversions
- it involves non-linear field operations
- its structure does not align well with SNARK-friendly arithmetic
As a result, proving ECDSA inside a SNARK is computationally expensive.
For reference, generating a single proof on a MacBook M2 Pro takes approximately 2 minutes and 30 seconds.
This makes Proof of Signature primarily a proof of concept, or a building block for systems where performance is acceptable in exchange for strong privacy or composability.
Using the ZK-Toolbox (Developer View)
We expose Proof of Signature via our zk‑toolbox using ECDSA on secp256k1.
Inputs and Outputs
- Public inputs:
msgHash: the message hash (SHA3)signature: the ECDSA signature overmsgHash
- Public output:
pubkey: the public key recovered from the signature
The proof asserts that:
There exists a public key
pubkeysuch thatsignatureis a valid ECDSA signature overmsgHashunderpubkey.
Example
import { ProofOfSignature, getECDSAInputs } from "@prifilabs/zk-toolbox";
import * as secp from '@noble/secp256k1';
import { keccak_256 } from '@noble/hashes/sha3.js';
import { utf8ToBytes } from '@noble/hashes/utils';
// step 1: generate the inputs
const privKey = secp.utils.randomPrivateKey();
const message = "Hello World!";
const msgHash = Buffer.from(keccak_256(utf8ToBytes(message))).toString('hex');
const signature = await secp.signAsync(msgHash, privKey);
const publicInputs = { msgHash, signature };
// step 2: generate the proof
const proofOfSignature = new ProofOfSignature();
const { proof, publicOutputs } = await proofOfSignature.generate({}, publicInputs);
// step 3: verify the output (optional)
const pubkey = publicInputs.signature.recoverPublicKey(Buffer.from(publicInputs.msgHash, 'hex')).toHex();
console.assert(publicOutputs.pubkey === pubkey);
// step 4: verify the proof
const res = await proofOfSignature.verify(proof, publicInputs, publicOutputs);
console.assert(res);
Behind the Scenes: The CIRCOM Circuit
Efficient ZK-ECDSA
We reuse the excellent work from Personae Labs providing an efficient ECDSA implementation for SNARKs.
This circuit does not take raw (r, s) signatures directly but expects a preprocessed form derived from the ECDSA verification equation. Our zk‑toolbox wrapper handles this transformation.
Our Circom Wrapper
Our circuit is a minimal wrapper around Personae’s verifier:
pragma circom 2.0.0;
include "../node_modules/circomlib/circuits/poseidon.circom";
include "./lib/ECDSA/ecdsa_verify_no_precompute.circom";
template ProofOfSignature(n, k) {
// public inputs
signal input s[4];
signal input T[2][4];
signal input U[2][4];
// public outputs
signal output pubKey[2][4];
component verifyECDSA = ECDSAVerifyNoPrecompute(64, 4);
verifyECDSA.s <== s;
verifyECDSA.T <== T;
verifyECDSA.U <== U;
pubKey <== verifyECDSA.pubKey;
}
component main {public [s, T, U]} = ProofOfSignature(64, 4);
Conceptually, the circuit enforces that:
- the ECDSA verification equation holds
- the public key is correctly derived
without revealing any private signing material since none of it is needed to generate the proof.
Conclusion and Applications
Proof of Signature brings cryptographic authentication into the zero-knowledge world.
Rather than merely verifying signatures, we can now reason about them privately, and combine them with other ZK statements.
Example Applications
-
Private authentication
Proving control over an Ethereum or Bitcoin address without revealing which transaction or message was signed. -
ZK-based account abstraction
Proving that a transaction was authorized by a key while hiding balances, recipients, or amounts. -
Decentralized identity (DID)
Proving that a credential was signed by a trusted issuer without revealing the credential contents. -
Privacy-preserving access control
Demonstrating that an action is signed by an authorized entity without exposing the action itself.