ZKP Implementation details

The Circom Circuit powering the application

pragma circom 2.0.0;
include "../../node_modules/circomlib/circuits/poseidon.circom";

template CommitmentHasher(){
    signal input nullifier;
    signal input secret;
    signal input nonce;
    signal output commitment;
    signal output paymentIntent;

    component commitmentHasher  = Poseidon(2);

    component nullifierHasher = Poseidon(2);

    commitmentHasher.inputs[0] <== nullifier;
    commitmentHasher.inputs[1] <== secret;

    commitment <==commitmentHasher.out;

    nullifierHasher.inputs[0] <== nullifier;
    nullifierHasher.inputs[1] <== nonce;

    paymentIntent <== nullifierHasher.out;
}

template DirectDebit(){
    signal input paymentIntent;
    signal input commitmentHash;

    signal input payee;

    // The max amount that can be debited with the proof
    signal input maxDebitAmount;
    
    // The amount of times this proof can be used ti withdraw max amount
    signal input debitTimes;

    // The time that needs to pass before the proof can be used to debit an account again
    signal input debitInterval;

   // Private inputs!
    signal input secret;
    // A nonce for the nullifier so the note is reusable!
    signal input nonce;
    // The nullifier is used to calculate the secret with the commitment
    // And also the payment intent with the nonce!
    signal input nullifier;
    
    //Hidden signals to verify inputs
    signal payeeSquare;
    signal maxDebitAmountSquare;
    signal debitTimesSquare;
    signal debitIntervalSquare;

    // Hashing the commitment and the nullifier
    component commitmentHasher = CommitmentHasher();
    commitmentHasher.nullifier <== nullifier;
    commitmentHasher.secret <== secret;
    commitmentHasher.nonce <== nonce;

    commitmentHasher.paymentIntent === paymentIntent;
    commitmentHasher.commitment === commitmentHash;

    payeeSquare <== payee * payee;
    maxDebitAmountSquare <== maxDebitAmount * maxDebitAmount;
    debitTimesSquare <== debitTimes * debitTimes;
    debitIntervalSquare <==debitInterval * debitInterval;
}

component main {public [paymentIntent,commitmentHash, payee, maxDebitAmount, debitTimes,debitInterval]} = DirectDebit();

The direct debit implementation is powered by this zkp circuit.

Let me explain what is going on, 🤔

Commitment Hasher

The hasher takes a nullifier, a secret and a nonce which are BigIntegers and outputs Poseidon Hashes Commitment and PaymentIntent

  • Nullifier is a random generated number created when the Debit Account is created

  • Secret is a random generated number created when the Debit Account is created

  • Nonce is a random generated number created when a payment intent is created to allow reusing the Nullifier!

  • The Commitment hash is the identifier of the Account. It is the poseidon hash of the nullifier and the secret

  • The PaymentIntent hash is the identifier of the PaymentIntent that is used to nullify it and it's created by hashing the nullifier with the nonce!

Direct Debit Template

The template contains public, private and hidden signals. The public signals need to be available when the proof is verified in the smart contract, the private signals are kept secret and the hidden signals are used to verify parameters of the subscription so they can't be altered! Private Inputs, These are provided only when the ZKP is created

  • Secret, nonce, nullifier

Public Inputs:

  • paymentIntent - Used to verify the creator knows the nullifier and the nonce without revealing it

  • commitment - Used to verify the creator knows the secret and the nullifier that created the commitment hash

  • payee, maxDebitAmount,debitTimes, debitInterval - These are arguments to the smart contract function and they are used to verify they were not tampared with, this is done using hidden signals!

Last updated