@nori-zk/proof-conversion
Version:
Verifying zkVM proofs inside o1js circuits, to generate Mina compatible proof
187 lines • 8.37 kB
JavaScript
import { Field, Provable, VerificationKey, Poseidon, UInt8, Bytes, Gadgets, ZkProgram, Struct, Undefined, UInt32, FeatureFlags, DynamicProof, } from 'o1js';
import { bytesToWord, wordToBytes, } from '../sha/utils.js';
import { Bytes32 } from './verify_blobstream.js';
import { BlobInclusionInput, } from './verify_blob_inclusion.js';
class Bytes29 extends Bytes(29) {
}
class Bytes64 extends Bytes(64) {
}
const BLOB_SIZE = 212;
const Bytes289Padding = [
UInt8.from(0x80n),
UInt8.from(0),
UInt8.from(0),
UInt8.from(0),
UInt8.from(0),
UInt8.from(0),
UInt8.from(0),
UInt8.from(0),
UInt8.from(0),
UInt8.from(0),
UInt8.from(0),
UInt8.from(0),
UInt8.from(0),
UInt8.from(0),
UInt8.from(0),
UInt8.from(0),
UInt8.from(0),
UInt8.from(0),
UInt8.from(0),
UInt8.from(0),
UInt8.from(0),
UInt8.from(0),
UInt8.from(0),
UInt8.from(0),
UInt8.from(0),
UInt8.from(0),
UInt8.from(0),
UInt8.from(0),
UInt8.from(0),
UInt8.from(0x09n),
UInt8.from(0x08n),
];
class BatcherInput extends Struct({
currentStateHash: Field,
index: Field,
namespace: Bytes29.provable,
currentRollingHash: Provable.Array(Field, 2),
batcherVkHash: Field,
blobInclusionVkHash: Field,
}) {
}
class BatcherOutput extends Struct({
initialStateHash: Field,
currentStateHash: Field,
currentRollingHash: Provable.Array(Field, 2),
dataCommitment: Bytes32.provable,
}) {
}
const NUM_PROOFS = 5;
function bytesToField(bs) {
return bs.reduce((acc, byte, idx) => {
const shift = 1n << BigInt(8 * idx);
return acc.add(byte.value.mul(shift));
}, Field.from(0));
}
function halfHashToField(nums) {
return nums.reduce((acc, byte, idx) => {
const shift = 1n << BigInt(32 * idx);
return acc.add(byte.value.mul(shift));
}, Field.from(0));
}
function hashToFields(hash) {
let firstHalf = halfHashToField(hash.slice(0, 4));
let secondHalf = halfHashToField(hash.slice(4, 8));
return [firstHalf, secondHalf];
}
function fieldToHalfHash(f) {
let nums = Provable.witness(Provable.Array(UInt32, 4), () => {
let w = f.toBigInt();
return Array.from({ length: 4 }, (_, k) => UInt32.from((w >> BigInt(32 * k)) & 0xffffffffn));
});
// check decomposition
halfHashToField(nums).assertEquals(f);
return nums;
}
function fieldsToHash(f) {
return fieldToHalfHash(f[0]).concat(fieldToHalfHash(f[1]));
}
class BatcherDynamicProof extends DynamicProof {
}
BatcherDynamicProof.publicInputType = BatcherInput;
BatcherDynamicProof.publicOutputType = BatcherOutput;
BatcherDynamicProof.maxProofsVerified = 2;
BatcherDynamicProof.featureFlags = FeatureFlags.allMaybe;
class BlobInclusionDynamicProof extends DynamicProof {
}
BlobInclusionDynamicProof.publicInputType = BlobInclusionInput;
BlobInclusionDynamicProof.publicOutputType = Undefined;
BlobInclusionDynamicProof.maxProofsVerified = 1;
BlobInclusionDynamicProof.featureFlags = FeatureFlags.allMaybe;
const batcherVerifier = ZkProgram({
name: 'batcherVerifier',
publicInput: BatcherInput,
publicOutput: BatcherOutput,
methods: {
compute: {
privateInputs: [
BatcherDynamicProof,
VerificationKey,
BlobInclusionDynamicProof,
VerificationKey,
Field,
Bytes64.provable,
],
async method(input, previousProof, batcherVk, blobInclusionProof, blobInclusionVk, counter, incrementByBytes) {
const isFirst = input.index.equals(Field(0));
const isNotFirst = isFirst.not();
Provable.assertEqualIf(isNotFirst, Field, input.index, previousProof.publicInput.index.add(Field(1)));
previousProof.verifyIf(batcherVk, isNotFirst);
Provable.assertEqualIf(isNotFirst, Field, input.batcherVkHash, batcherVk.hash);
Provable.assertEqualIf(isNotFirst, Field, previousProof.publicInput.batcherVkHash, batcherVk.hash);
const isLast = input.index.equals(Field(NUM_PROOFS - 1));
input.currentStateHash.assertEquals(Poseidon.hashPacked(Field, counter));
let namespaceBytes = [];
namespaceBytes = namespaceBytes.concat([
UInt8.from(29n),
...Array(7).fill(UInt8.from(0)),
]);
namespaceBytes = namespaceBytes.concat(input.namespace.bytes);
namespaceBytes = namespaceBytes.concat([
UInt8.from(BLOB_SIZE),
...Array(7).fill(UInt8.from(0)),
]);
namespaceBytes = namespaceBytes.concat(incrementByBytes.bytes.slice(45));
let lastBlockBytes = incrementByBytes.bytes.slice(0, 33);
lastBlockBytes = lastBlockBytes.concat(Bytes289Padding);
let bytesToHash = Provable.if(isFirst, Bytes64.provable, Bytes64.from(namespaceBytes), Provable.if(isLast, Bytes64.provable, Bytes64.from(lastBlockBytes), incrementByBytes));
// Provable.asProver(() => {
// console.log(`bytes to hash ${input.index.toBigInt()}: ${Buffer.from(bytesToHash.toBytes()).toString('hex')}`);
// });
const dataCommitment = incrementByBytes.bytes.slice(1, 33);
const chunks = [];
for (let i = 0; i < bytesToHash.length; i += 4) {
const chunk = UInt32.Unsafe.fromField(bytesToWord(bytesToHash.bytes.slice(i, i + 4).reverse()));
chunks.push(chunk);
}
let initialStateFields = hashToFields(Gadgets.SHA256.initialState);
const currentH = Provable.if(isFirst, Provable.Array(Field, 2), initialStateFields, input.currentRollingHash);
let W = Gadgets.SHA256.createMessageSchedule(chunks);
let H = fieldsToHash(currentH);
H = Gadgets.SHA256.compression(H, W);
const numToAdd = bytesToField(incrementByBytes.bytes.slice(0, 20));
// Provable.asProver(() => {
// console.log(`num to add in circuit ${input.index.toBigInt()}: ${numToAdd.toBigInt()}`);
// });
const newCounter = Provable.if(isNotFirst.and(isLast.not()), counter.add(numToAdd), counter);
const currentStateHash = Poseidon.hashPacked(Field, newCounter);
blobInclusionProof.verifyIf(blobInclusionVk, isLast);
Provable.assertEqualIf(isLast, Field, input.blobInclusionVkHash, blobInclusionVk.hash);
const currentRollingHash = hashToFields(H);
const currentRollingHashBytes = H.flatMap((x) => wordToBytes(x.value, 4).reverse());
// Provable.asProver(() => {
// console.log(`current H ${input.index.toBigInt()}: ${H.map(x=>x.toBigint())}`);
// console.log(`current rolling hash bytes ${input.index.toBigInt()}: ${Buffer.from(currentRollingHashBytes.map(x=>x.toNumber())).toString('hex')}`);
// });
Provable.assertEqualIf(isLast, Bytes32.provable, blobInclusionProof.publicInput.digest, Bytes32.from(currentRollingHashBytes));
/*return new BatcherOutput({
initialStateHash: Provable.if(isFirst, Field, input.currentStateHash, previousProof.publicOutput.initialStateHash),
currentStateHash,
currentRollingHash,
dataCommitment: Bytes32.from(dataCommitment),
});*/
return {
publicOutput: new BatcherOutput({
initialStateHash: Provable.if(isFirst, Field, input.currentStateHash, previousProof.publicOutput.initialStateHash),
currentStateHash,
currentRollingHash,
dataCommitment: Bytes32.from(dataCommitment),
}),
};
},
},
},
});
const BatcherProof = ZkProgram.Proof(batcherVerifier);
export { batcherVerifier, BatcherProof, BatcherInput, BatcherOutput, Bytes29, Bytes64, BatcherDynamicProof, BlobInclusionDynamicProof, bytesToField, };
//# sourceMappingURL=batcher.js.map