Skip to main content

ZkProgram Proofs as Inputs

ZkProgram proofs can be used as inputs to zkApp methods, enabling powerful composition patterns. This allows you to verify complex computations off-chain and use the verified results in your smart contracts.

Basic ZkProgram Integration

Define a ZkProgram and use its proofs in a smart contract:

export const BasicMathProgram = ZkProgram({
name: 'BasicMathProgram',
publicInput: Field,
publicOutput: Field,

methods: {
square: {
privateInputs: [],
async method(input: Field) {
return input.mul(input);
},
},

multiply: {
privateInputs: [Field],
async method(publicInput: Field, privateInput: Field) {
return publicInput.mul(privateInput);
},
},
},
});

export class BasicMathProof extends ZkProgram.Proof(BasicMathProgram) {}

export class ProofVerifierContract extends SmartContract {
@state(Field) verifiedResult = State<Field>();
@state(UInt32) proofCount = State<UInt32>();

init() {
super.init();
this.verifiedResult.set(Field(0));
this.proofCount.set(UInt32.from(0));
}

@method async verifySquareProof(proof: BasicMathProof) {
// Verify the proof is valid
proof.verify();

// Ensure it's a square operation proof
proof.publicOutput.assertEquals(proof.publicInput.mul(proof.publicInput));

// Store the verified result
this.verifiedResult.set(proof.publicOutput);

// Increment proof count
const currentCount = this.proofCount.getAndRequireEquals();
this.proofCount.set(currentCount.add(1));
}

@method async verifyAndAccumulate(proof: BasicMathProof, multiplier: Field) {
// Verify the proof
proof.verify();

// Use the proven result in further computation
const provenResult = proof.publicOutput;
const newResult = provenResult.mul(multiplier);

// Accumulate with existing state
const currentResult = this.verifiedResult.getAndRequireEquals();
this.verifiedResult.set(currentResult.add(newResult));
}
}

Recursive Proofs

Build recursive computations using self-referencing proofs:

export const RecursiveProgram = ZkProgram({
name: 'RecursiveProgram',
publicInput: Field,
publicOutput: Field,

methods: {
baseCase: {
privateInputs: [],
async method(input: Field) {
return input.add(1);
},
},

recursiveCase: {
privateInputs: [SelfProof],
async method(input: Field, previousProof: SelfProof) {
// Verify the previous proof
previousProof.verify();

// Ensure continuity: current input should be previous output + 1
previousProof.publicOutput.add(1).assertEquals(input);

// Continue the sequence
return input.add(1);
},
},
},
});

export class RecursiveProof extends ZkProgram.Proof(RecursiveProgram) {}

export class RecursiveContract extends SmartContract {
@state(Field) finalValue = State<Field>();
@state(UInt32) chainLength = State<UInt32>();

init() {
super.init();
this.finalValue.set(Field(0));
this.chainLength.set(UInt32.from(0));
}

@method async acceptRecursiveProof(proof: RecursiveProof, expectedLength: UInt32) {
// Verify the proof
proof.verify();

// Store the final value from the proof chain
this.finalValue.set(proof.publicOutput);
this.chainLength.set(expectedLength);
}
}

This enables scalable computation, state transitions, and proof compression for arbitrary-length computations.

API Reference

For detailed information about ZkProgram and proofs, see: