#Receiving DynamicProof as Public Input causes ZKProgram to fail to compile

17 messages · Page 1 of 1 (latest)

gilded sun
#

Hi, I'm trying to verify a proof from a circuit within another circuit. If I understand correctly, then DynamicProofs allow exactly that. However, when I try to compile the circuit, I receive an error that I am now allowed to use DynamicProofs directly despite having already created a class which extends Dynamic Proof.

Relevant code:

// Compile the verification key for AES128CTR
const { verificationKey: vk_data } = await AES128Ctr.compile();
const vk = new VerificationKey(vk_data);

class SideLoadedAESProof extends DynamicProof<
  AES128CTRPublicInput,
  AES128CtrPublicOutput
> {
  publicInput: AES128CTRPublicInput;
  publicOutput: AES128CtrPublicOutput;
  maxProofsVerified: 1;
}

const AES128CTR256 = ZkProgram({
  name: "aes-verify-ctr-256",
  publicInput: SideLoadedAESProof,

  methods: {
    verifyAESCTR256: {
      privateInputs: [],

      async method(proof: SideLoadedAESProof) {
        proof.verify(vk);
        proof.publicInput.ctr.assertEquals(NUM_BLOCKS - 1);

        // // Verify the ciphers
        // const cipher_internal_hash = Poseidon.hashPacked(
        //   Provable.Array(Field, NUM_BLOCKS),
        //   [
        //     Poseidon.hashPacked(Byte16, input.ciphers[0]),
        //     Poseidon.hashPacked(Byte16, input.ciphers[1]),
        //   ],
        // );

        // cipher_internal_hash.assertEquals(proof.publicOutput.cipher_hash);
      },
    },
  },
});

Error:

You cannot use the `Proof` class directly. Instead, define a subclass:
    class MyProof extends Proof<PublicInput, PublicOutput> { ... }

I am currently using version 2.20 of o1js

untold mesa
#

Can you share the rest of your code so we can reproduce the issue?

#

I guess specifically AES128Ctr and the types AES128CTRPublicInput and AES128CtrPublicOutput.

gilded sun
#
class AES128CTRPublicInput extends Struct({
  cipher: Byte16,
  iv: Byte16,
  key_hash: Field,
  ctr: Field,
}) {}

class AES128CtrPublicOutput extends Struct({
  cipher_hash: Field,
}) {}
#
const AES128Ctr = ZkProgram({
  name: "aes-verify-ctr-base",
  publicInput: AES128CTRPublicInput,
  publicOutput: AES128CtrPublicOutput,

  methods: {
    // base case for a singleton block
    base: {
      privateInputs: [Byte16, Byte16],

      async method(input: AES128CTRPublicInput, message: Byte16, key: Byte16) {
        // Assert that the key hash is correct
        Poseidon.hashPacked(Byte16, key).assertEquals(input.key_hash);
        // Assert that ctr = 0
        Field(0).assertEquals(input.ctr);

        // ctr = 0, so iv passed as is
        const cipher = computeCipher(input.iv, key, message);
        cipher.assertEquals(input.cipher);

        // TODO: Check whether we need a separator within our hash?
        const output = new AES128CtrPublicOutput({
          cipher_hash: Poseidon.hashPacked(Byte16, cipher),
        });

        return { publicOutput: output };
      },
    },
#
inductive: {
      privateInputs: [
        SelfProof<AES128CTRPublicInput, AES128CtrPublicOutput>,
        Byte16,
        Byte16,
      ],
      async method(
        input: AES128CTRPublicInput,
        previousProof: SelfProof<AES128CTRPublicInput, AES128CtrPublicOutput>,
        message: Byte16,
        key: Byte16,
      ) {
        previousProof.verify();

        // Perform necessary assertions, including matches iv, key_hashes, correct counter, and correct cipher
        input.iv.assertEquals(previousProof.publicInput.iv);
        // This ensures that key is the same for all blocks
        input.key_hash.assertEquals(previousProof.publicInput.key_hash);
        Poseidon.hashPacked(Byte16, key).assertEquals(input.key_hash);

        // This ensures that counter is correclty incremented
        input.ctr.assertEquals(previousProof.publicInput.ctr.add(Field(1)));

        // TODO: Implement addition of the IV and the counter in Byte16 to reduce constraints
        const cipher = computeCipher(
          Byte16.fromField(input.iv.toField().add(input.ctr)),
          key,
          message,
        );
        cipher.assertEquals(input.cipher);

        // TODO: Check whether we need a separator within our hash?
        const output = new AES128CtrPublicOutput({
          cipher_hash: Poseidon.hashPacked(Provable.Array(Field, 2), [
            previousProof.publicOutput.cipher_hash,
            Poseidon.hashPacked(Byte16, cipher),
          ]),
        });
        return { publicOutput: output };
      },
    },
  },
});
#

sorry discord doesn't handle the formatting well and doesn't allow me to output the entire zkProgram in one message

#

Byte16 is a Struct wrapping a 2D Field Array and computeCipher returns a Byte16

woven onyx
stoic tendon
#

Proofs aren't supposed to be public inputs, that's why it's not working - you'll have to use private inputs for that

#

We should represent this more clearly in either the public input type or at least throw an error message

gilded sun
#

I have now tried modifying the code as so:

class AES128CTR256PublicInput extends Struct({
  proof_input: AES128CTRPublicInput,
  proof_output: AES128CtrPublicOutput,
  ciphers: Provable.Array(Byte16, NUM_BLOCKS),
}) {}

// Compile the verification key for AES128CTR
const { verificationKey: vk_data } = await AES128Ctr.compile();
const vk = new VerificationKey(vk_data);

const AES128CTR256 = ZkProgram({
  name: "aes-verify-ctr-256",
  publicInput: AES128CTR256PublicInput,

  methods: {
    verifyAESCTR256: {
      privateInputs: [SideLoadedAESProof],

      async method(input: AES128CTR256PublicInput, proof: SideLoadedAESProof) {
        proof.verify(vk);
        proof.publicInput.ctr.assertEquals(NUM_BLOCKS - 1);

        // Assert that the proof input and output are consistent
        proof.publicOutput.assertEquals(input.proof_output);
        proof.publicInput.assertEquals(input.proof_input);

        // Verify the ciphers
        const cipher_internal_hash = Poseidon.hashPacked(
          Provable.Array(Field, NUM_BLOCKS),
          [
            Poseidon.hashPacked(Byte16, input.ciphers[0]),
            Poseidon.hashPacked(Byte16, input.ciphers[1]),
          ],
        );

        cipher_internal_hash.assertEquals(proof.publicOutput.cipher_hash);
      },
    },
  },
});
gilded sun
#

@woven onyx , the server does not allow me posting links, so I have DM'd

granite fable
gilded sun
gilded sun
#

Okay, I've figured out the problem. SideLoadedAESProof needs to be defined as so:

class SideLoadedAESProof extends DynamicProof<
  AES128CTRPublicInput,
  AES128CtrPublicOutput
> {
  static publicInputType = AES128CTRPublicInput;
  static publicOutputType = AES128CtrPublicOutput;
  static maxProofsVerified = 1 as const;
}