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 over msgHash
  • Public output:
    • pubkey: the public key recovered from the signature

The proof asserts that:

There exists a public key pubkey such that signature is a valid ECDSA signature over msgHash under pubkey.

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

  1. Private authentication
    Proving control over an Ethereum or Bitcoin address without revealing which transaction or message was signed.

  2. ZK-based account abstraction
    Proving that a transaction was authorized by a key while hiding balances, recipients, or amounts.

  3. Decentralized identity (DID)
    Proving that a credential was signed by a trusted issuer without revealing the credential contents.

  4. Privacy-preserving access control
    Demonstrating that an action is signed by an authorized entity without exposing the action itself.