Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dgca/938c7ce758b9b7ab9f797bdf077bc6f9 to your computer and use it in GitHub Desktop.
Save dgca/938c7ce758b9b7ab9f797bdf077bc6f9 to your computer and use it in GitHub Desktop.
Exploring Zero-Knowledge Proofs for Verifying Function Outputs in JavaScript

ChatGPT Convo: Exploring Zero-Knowledge Proofs for Verifying Function Outputs in JavaScript

@me:

The following is a question about whether or not it is possible to do something in Javascript. If it is possible please explain how one could achieve this:

Say I have a pure function that takes some input, does some synchronous computation, and produces an output. Is it possible to verify that a potential result was the result of that function without running the function myself?


@ChatGPT:

In general, you cannot verify that a potential result was produced by a specific function without running the function yourself. However, there are some scenarios where you can achieve this using cryptographic techniques.

One such technique is using a commitment scheme, which allows a party to commit to a value (in this case, the input of the function) without revealing it. The other party can later verify that the committed value was indeed used in the computation.

Here's a high-level description of a simple commitment scheme using a cryptographic hash function, such as SHA-256, in JavaScript:

  1. The party who wants to commit to a value x computes a hash of x concatenated with a random nonce r: commitment = hash(x || r). They share the commitment with the other party, but not the nonce or the value x.
  2. The party who wants to verify the commitment later receives the original value x and the nonce r from the committing party.
  3. The verifier computes the hash of the received x and r: hash(x || r), and compares it to the original commitment. If they match, the verifier can be sure that the received value x was indeed the value committed to earlier.

Now, let's see how this can be used to verify that a result was produced by a function without running the function yourself:

  1. The party who runs the function computes the output and a commitment to the input: output = f(input) and commitment = hash(input || nonce).
  2. They share the output and the commitment with the other party.
  3. When the verifier wants to check that the output corresponds to the function applied to the committed input, they ask the other party to reveal the input and nonce.
  4. The verifier checks that the commitment matches the received input and nonce: hash(input || nonce) === commitment.
  5. If the commitment is valid, the verifier can now run the function on the received input: output_verified = f(input).
  6. They compare the received output to the computed output: output === output_verified.

If the outputs match and the commitment is valid, the verifier can be sure that the output was indeed produced by the function applied to the committed input.

Here's an example using JavaScript and the crypto module:

const crypto = require('crypto');

function f(input) {
  // Your pure function here
}

function createCommitment(input, nonce) {
  const hash = crypto.createHash('sha256');
  hash.update(input + nonce);
  return hash.digest('hex');
}

const input = 'some input';
const nonce = crypto.randomBytes(16).toString('hex');
const output = f(input);
const commitment = createCommitment(input, nonce);

// Share output and commitment with the verifier
// ...

// Later, reveal input and nonce to the verifier
// ...

// The verifier checks the commitment and function result
const commitment_verified = createCommitment(input, nonce);
const output_verified = f(input);

if (commitment_verified === commitment && output_verified === output) {
  console.log('The output is indeed produced by the function applied to the committed input');
} else {
  console.log('The output is not valid or the commitment is not valid');
}

Keep in mind that this approach has some limitations:

  • It requires the verifier to trust that the hash function used in the commitment scheme is secure and collision-resistant.
  • The verifier still needs to run the function once on the revealed input to verify the output, so it doesn't completely avoid running the function. However, it allows the verifier to defer running the function until they are ready to check the output.

@me:

In the above example, wouldn't the verifier be able to verify simply by running the function and comparing it against the previous result? It seems like the commitment hash is unnecessary.


@ChatGPT:

You're correct. In the example provided, the commitment scheme appears unnecessary, as the verifier could simply run the function and compare the result. The commitment scheme would be more useful in situations where the input must be kept secret until a certain condition is met or a specific event occurs. The scheme would allow the verifier to confirm that the input they eventually receive was the same as the one initially used to generate the output.

However, if there is no need for the input to be kept secret, the commitment scheme would indeed be redundant. In that case, the process would be simplified:

  1. The party who runs the function computes the output: output = f(input).
  2. They share the input and output with the other party.
  3. The verifier runs the function on the received input: output_verified = f(input).
  4. They compare the received output to the computed output: output === output_verified.

If the outputs match, the verifier can be sure that the output was indeed produced by the function applied to the given input.

Here's an updated example without the commitment scheme:

function f(input) {
  // Your pure function here
}

const input = 'some input';
const output = f(input);

// Share input and output with the verifier
// ...

// The verifier checks the function result
const output_verified = f(input);

if (output_verified === output) {
  console.log('The output is indeed produced by the function applied to the given input');
} else {
  console.log('The output is not valid');
}

In this case, as you pointed out, the verifier can confirm the output's validity simply by running the function and comparing the result.


@me:

I'm trying to figure out if it's possible to verify that a function indeed produced a certain result without running the function. Would it be possible to do that with zero knowledge proofs?


@me:

Yes, zero-knowledge proofs (ZKP) are a cryptographic technique that can help achieve this. Zero-knowledge proofs allow one party (the prover) to convince another party (the verifier) that a given statement is true, without revealing any information about the statement itself, apart from its validity. In the context of your question, the prover could use a zero-knowledge proof to demonstrate that they know an input to the function that, when executed, yields the provided output, without revealing the input itself or having the verifier run the function.

There are various types of zero-knowledge proof systems, such as zk-SNARKs (Zero-Knowledge Succinct Non-Interactive Argument of Knowledge) and zk-STARKs (Zero-Knowledge Scalable Transparent ARgument of Knowledge). Both zk-SNARKs and zk-STARKs are non-interactive, meaning that the proof consists of a single message sent from the prover to the verifier, and they don't need to engage in a back-and-forth conversation.

Here's a high-level overview of how you could use zero-knowledge proofs to achieve your goal:

  1. The prover generates a zero-knowledge proof that they know the input to the function that produces the given output. The proof is generated using a specific ZKP system (e.g., zk-SNARKs or zk-STARKs).
  2. The prover shares the proof with the verifier.
  3. The verifier checks the validity of the proof using the verification algorithm of the chosen ZKP system. If the proof is valid, the verifier can be sure that the prover knows the input that yields the given output when the function is executed, without knowing the input or running the function themselves.

To implement zero-knowledge proofs in practice, you would need to use existing libraries or tools that support the desired ZKP system. Unfortunately, most ZKP libraries are not implemented in JavaScript, and they usually require specific knowledge of cryptography and the chosen ZKP system.

Here are a few libraries that might be helpful:

  • For zk-SNARKs:

    • snarkjs - A JavaScript library for zk-SNARKs. It provides a high-level API and can be used in Node.js and browser environments.
    • libsnark - A C++ library for zk-SNARKs. You would need to create JavaScript bindings or use it with WebAssembly to use it in a JavaScript environment.
  • For zk-STARKs:

    • StarkWare's Cairo - A high-level language for writing zk-STARK-based programs. You would need to write a Cairo program for your function and use the provided tools to generate and verify proofs.

Keep in mind that working with zero-knowledge proofs can be complex, and it might be challenging to implement them correctly without a solid understanding of the underlying cryptographic concepts.


I think you might misunderstand my goal. I need the prover to prove that the output of the function is the result of actually running the function with a given set of inputs. Not that they know the inputs.

E.g. say we have Alice who wants a specific function to be run, and Bob who will run the function and give Alice the result. Bob could be a good person and run the task as described, but he could also be a bad actor and give Alice the incorrect result.

We want Alice to be able to verify that Bob did indeed run the correct function with the correct inputs without Alice needing to run the function herself.


@ChatGPT:

I apologize for the misunderstanding. In this case, you can still use zero-knowledge proofs to achieve your goal. The prover (Bob) would have to generate a proof that demonstrates that the provided output is indeed the result of running the function with the given set of inputs, without revealing any information about the internal computation.

Here's a high-level overview of how this could work:

  1. The function and the input are public: Alice and Bob both know the function and the input that should be used in the computation.
  2. Bob runs the function: He computes the output using the function and the provided input.
  3. Generate the proof: Bob generates a zero-knowledge proof using a ZKP system like zk-SNARKs or zk-STARKs, proving that the provided output is the result of running the function with the given set of inputs. He does this without revealing any information about the internal computation.
  4. Share the output and the proof: Bob shares the output and the generated proof with Alice.
  5. Verify the proof: Alice checks the validity of the proof using the verification algorithm provided by the chosen ZKP system. If the proof is valid, Alice can be confident that the output is indeed the result of running the function with the given inputs. This process does not require Alice to run the function herself.

To implement this solution, you would need to express the function as a constraint system or a similar representation that can be used with the chosen ZKP system (e.g., zk-SNARKs or zk-STARKs). This might involve writing a program in a domain-specific language like Cairo (for zk-STARKs) or using a library that supports the desired ZKP system, as mentioned in my previous response.

Keep in mind that zero-knowledge proofs can be complex and may not be the most efficient solution depending on the specifics of your problem. Additionally, expressing the function as a constraint system or a similar representation might be non-trivial, depending on the function itself.


What's a constraint system?


@ChatGPT:

A constraint system is a mathematical representation of a set of constraints (conditions or rules) that must be satisfied by a set of variables. Constraint systems are used to model problems in various fields, such as optimization, scheduling, cryptography, and more. In the context of zero-knowledge proofs, constraint systems are used to express a computation or function in a way that can be efficiently encoded and verified with the chosen ZKP system.

There are several types of constraint systems, such as:

  1. Linear constraint systems: In a linear constraint system, constraints are expressed as linear equations involving the variables. For example, a simple linear constraint system could have two variables, x and y, and two constraints: x + y = 5 and x - y = 1. The solution to this system is the pair of values (x = 3, y = 2) that satisfy both constraints.

  2. Quadratic constraint systems: In a quadratic constraint system, constraints are expressed as quadratic equations involving the variables. For example, a quadratic constraint system could have two variables, x and y, and one constraint: x^2 + y^2 = 1. The solution to this system is a set of points (x, y) that lie on the unit circle.

  3. Boolean or rank-1 constraint systems (R1CS): In an R1CS, constraints are expressed as a set of quadratic equations in the form a * b = c, where a, b, and c are linear combinations of the variables. R1CS is particularly useful in the context of zk-SNARKs, as it can efficiently represent a wide range of computations and is well-suited for the underlying cryptography.

In zero-knowledge proofs, the function or computation you want to prove knowledge of is represented as a constraint system. The prover demonstrates that they know a set of variable assignments that satisfy all constraints in the system, without revealing the actual variable values. The verifier checks the proof's validity, ensuring that the constraints are satisfied without learning any information about the variable assignments.

To use a constraint system with a ZKP system, you would need to:

  1. Express the function or computation as a constraint system: This process can be complex and depends on the specific function and the chosen constraint system representation.

  2. Generate a proof: The prover generates a zero-knowledge proof that demonstrates their knowledge of a set of variable assignments that satisfy the constraints, using the chosen ZKP system.

  3. Verify the proof: The verifier checks the proof's validity using the verification algorithm provided by the chosen ZKP system, ensuring that the constraints are satisfied without learning any information about the variable assignments.

In summary, a constraint system is a mathematical representation of a set of constraints that must be satisfied by a set of variables. It is used in zero-knowledge proofs to express a computation or function in a way that can be efficiently encoded and verified by the chosen ZKP system.


Dope, thanks :)


@ChatGPT:

You're welcome! I'm glad I could help. If you have any more questions or need further clarification on anything, feel free to ask. Good luck with your project! 😊

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment