diff --git a/README.md b/README.md index 0e65795..0eec9f9 100644 --- a/README.md +++ b/README.md @@ -24,8 +24,6 @@ This work was spawned from the Mina Grant Starter Program and aims to provide a - AES-128 encryption - Static and dynamic message sizes -- Counter Mode (CTR) -- Galois Counter Mode (GCM) (coming soon) ## Installation and Quick Start @@ -77,6 +75,14 @@ npm run build node ./build/test/circuitSummary.js ``` +## Code Breakdown +The main entrypoint of the code is contained within `src/implementations/IterativeAES128.ts` which has the following implemented. + - `IterativeAes128` is responsible for verifying that a cipher has been encrypted using AES with an arbitrary key and message + - `IterativeAes128MessagePublic` is responsible for verifying that a cipher **and** a message have been encrypted using AES with an arbitrary key. + - `computeIterativeAes128Encryption()` which can be inlined within circuits in order to proof AES encryption. + +Additionally, `Byte16` is used to represent 256-bit numbers and is commonly used as inputs to functions and circuits. + ## Circuit Breakdown ### AES128 Iterative Summary @@ -90,13 +96,6 @@ node ./build/test/circuitSummary.js | Rot64 | 4800 | | RangeCheck0 | 4800 | -### Core: -- Implementing block mode: **Counter Mode (CTR)**. - -### Optional: - -- User authentication block mode: **Galois Counter Mode (GCM)**. - # Contributing Everyone is welcome to contribute, file an issue or submit a pull request if you think there is something worth mentioning. diff --git a/src/implementations/AES128CTR.ts b/src/implementations/AES128CTR.ts deleted file mode 100644 index b23a874..0000000 --- a/src/implementations/AES128CTR.ts +++ /dev/null @@ -1,123 +0,0 @@ -import { Struct, SelfProof, ZkProgram, Field, Poseidon } from "o1js"; -import { Byte16 } from "../primitives/Bytes.js"; -import { computeIterativeAes128Encryption } from "./IterativeAES128.js"; - -/** - * Public input for the AES-128 CTR mode verification circuit. - * - * @property cipher - The ciphertext produced by encrypting a single block. - * @property iv - The initialization vector. This value must be randomly generated for each proof. - */ -class AES128CTRPublicInput extends Struct({ - cipher: Byte16, - iv: Field, // Randomly generated IV that can be publicly disclosed. -}) {} - -/** - * Public output for the AES-128 CTR mode verification circuit. - * - * @property counter - The current counter value (starting from 1). - * @property keyHash - The Poseidon hash of the key used in the encryption. - */ -class AES128CTRPublicOutput extends Struct({ - counter: Field, - keyHash: Field, -}) {} - -/** - * Computes the cipher for a single block in CTR mode. - * - * @param iv_plus_ctr - The sum of the IV and counter. - * @param key - The 128-bit key. - * @param message - The plaintext message block. - * @returns The ciphertext produced by XORing the plaintext with the key stream. - * - * The key stream is computed by applying an iterative AES-128 encryption on the IV+counter. - */ -export function computeCipher( - iv_plus_ctr: Field, - key: Byte16, - message: Byte16, -): Byte16 { - const curr_key: Byte16 = computeIterativeAes128Encryption( - Byte16.fromField(iv_plus_ctr), - key, - ); - return Byte16.xor(message, curr_key); -} - -/** - * ZK program for verifying AES-128 CTR mode encryption using recursive proofs. - * - * It supports both a base case (for a single block) and an inductive step (for multiple blocks). - * In addition to verifying the encryption, each proof computes and returns the Poseidon hash of the key. - * In the inductive case, the key hash is compared with the previous proof's hash to enforce consistency. - */ -const Aes128Ctr = ZkProgram({ - name: "aes-verify-iterative", - publicInput: AES128CTRPublicInput, - publicOutput: AES128CTRPublicOutput, - - methods: { - // Base case: Verify a single block encryption. - base: { - // Private inputs: plaintext message and key. - privateInputs: [Byte16, Byte16], - - async method(input: AES128CTRPublicInput, message: Byte16, key: Byte16) { - const cipher = computeCipher(input.iv, key, message); - cipher.assertEquals(input.cipher); - const keyHash = Poseidon.hash([key.toField()]); - - return { - publicOutput: new AES128CTRPublicOutput({ - counter: Field(1), - keyHash, - }), - }; - }, - }, - - // Inductive step: Verify subsequent block encryptions. - inductive: { - // Private inputs: - // - A recursive proof of the previous block's encryption. - // - The plaintext message for the current block. - // - The key for the current block. - privateInputs: [ - SelfProof, - Byte16, - Byte16, - ], - - async method( - input: AES128CTRPublicInput, - previousProof: SelfProof, - message: Byte16, - key: Byte16, - ) { - const currentKeyHash = Poseidon.hash([key.toField()]); - currentKeyHash.assertEquals(previousProof.publicOutput.keyHash); - previousProof.verify(); - input.iv.assertEquals(previousProof.publicInput.iv); - - const cipher = computeCipher( - input.iv.add(previousProof.publicOutput.counter), - key, - message, - ); - cipher.assertEquals(input.cipher); - - const newCounter = previousProof.publicOutput.counter.add(Field(1)); - return { - publicOutput: new AES128CTRPublicOutput({ - counter: newCounter, - keyHash: currentKeyHash, - }), - }; - }, - }, - }, -}); - -export { Aes128Ctr, AES128CTRPublicInput }; diff --git a/src/implementations/IterativeAES128.ts b/src/implementations/IterativeAES128.ts index 81156a2..08ebc86 100644 --- a/src/implementations/IterativeAES128.ts +++ b/src/implementations/IterativeAES128.ts @@ -67,6 +67,31 @@ const IterativeAes128 = ZkProgram({ }, }); +class IterativeAES128MessagePublicInput extends Struct({ + cipher: Byte16, + message: Byte16, +}) {} + +/** + * A zkProgram that verifies a proof that a message and cipher are conncted via AES-128 using the given key. + */ +const IterativeAes128MessagePublic = ZkProgram({ + name: "aes-verify-iterative-decrypt", + publicInput: IterativeAES128MessagePublicInput, + + methods: { + verifyAES128: { + privateInputs: [Byte16], + + async method(input: IterativeAES128MessagePublicInput, key: Byte16) { + const message = input.message; + const state = computeIterativeAes128Encryption(message, key); + state.assertEquals(input.cipher); + }, + }, + }, +}); + /** * Generates a proof that the given message was encrypted with AES-128 using the given key. * The key must be in hex form. @@ -77,7 +102,6 @@ const IterativeAes128 = ZkProgram({ * @throws If the message is not 16 characters long or the key is not 32 characters long * @throws If the proof generation fails */ -// NO TEST NOW AS IT WILL CHANGE SOON async function generateIterativeAes128Proof( message: string, keyHex: string, // Should we allow non hex strings? @@ -107,4 +131,6 @@ export { generateIterativeAes128Proof, IterativeAes128, IterativeAES128PublicInput, + IterativeAes128MessagePublic, + IterativeAES128MessagePublicInput, }; diff --git a/src/implementations/IterativeAES128CTR.ts b/src/implementations/IterativeAES128CTR.ts deleted file mode 100644 index 500bef7..0000000 --- a/src/implementations/IterativeAES128CTR.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { Proof, Field, ZkProgram, Struct } from "o1js"; -import { computeIterativeAes128Encryption } from "./IterativeAES128.js"; -import { Byte16 } from "../primitives/Bytes.js"; - -class AES128HelperPublicInput extends Struct({ - cipher: Byte16, - message: Byte16, -}) {} -/** - * A zkProgram that verifies a proof that a message was encrypted with AES-128 using the given key AND DISCLOSES THE MESSAGE. - * This one should only be used for counter mode only AS IT DISCLOSES THE MESSAGE. - */ -const CtrModeIterativeAes128Helper = ZkProgram({ - name: "aes-verify-iterative", - publicInput: AES128HelperPublicInput, - - methods: { - verifyAES128: { - privateInputs: [Byte16], - - async method(input: AES128HelperPublicInput, key: Byte16) { - const state = computeIterativeAes128Encryption(input.message, key); - state.assertEquals(input.cipher); - }, - }, - }, -}); - -function verifyIterativeCounterMode( - proofs: Proof[], - ciphers: Byte16[], - messages: Byte16[], - iv: Field, -): void { - proofs.forEach((proof, index) => { - proof.verify(); - const ctrCipher = proof.publicInput.cipher; - const ctrInput = proof.publicInput.message; - const message = messages[index]; - const counter = Field(index); - - ctrInput.assertEquals(Byte16.fromField(iv.add(counter))); - message.assertEquals(Byte16.xor(ciphers[index], ctrCipher)); - }); -} - -export { - CtrModeIterativeAes128Helper, - AES128HelperPublicInput, - verifyIterativeCounterMode, -}; diff --git a/src/index.ts b/src/index.ts index 28cc455..b4d547a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,16 @@ import { IterativeAes128, IterativeAES128PublicInput, + IterativeAes128MessagePublic, + IterativeAES128MessagePublicInput, } from "./implementations/IterativeAES128"; import { Byte16 } from "./primitives/Bytes.js"; -export { IterativeAes128, IterativeAES128PublicInput, Byte16 }; +export { + IterativeAes128, + IterativeAES128PublicInput, + Byte16, + IterativeAes128MessagePublic, + IterativeAES128MessagePublicInput, +}; export { generateIterativeAes128Proof as generateAes128Proof } from "./implementations/IterativeAES128"; diff --git a/test/implementations/verifyAES128.test.ts b/test/implementations/verifyAES128.test.ts index 1e90634..8371593 100644 --- a/test/implementations/verifyAES128.test.ts +++ b/test/implementations/verifyAES128.test.ts @@ -2,6 +2,8 @@ import { computeIterativeAes128Encryption, IterativeAes128, IterativeAES128PublicInput as AESPublicInput, + IterativeAes128MessagePublic, + IterativeAES128MessagePublicInput, } from "../../src/implementations/IterativeAES128.js"; import { encryptAES128 } from "../../src/utils/crypto.js"; import { Byte16 } from "../../src/primitives/Bytes.js"; @@ -53,3 +55,24 @@ describe("Iterative AES128 Encryption", () => { }, ); }); + +describe("Iterative AES128 Decryption", () => { + (RUN_ZK_TESTS ? it : it.skip)( + "should verify the proof using the zkProgram", + async () => { + const { verificationKey } = await IterativeAes128MessagePublic.compile(); + const { plaintext, key } = testVectorToByte16(testVector1); + const cipher = Byte16.fromHex(getCipherText(testVector1)); + const input = new IterativeAES128MessagePublicInput({ + cipher, + message: plaintext, + }); + const { proof } = await IterativeAes128MessagePublic.verifyAES128( + input, + key, + ); + const isValid = await verify(proof, verificationKey); + expect(isValid).toBe(true); + }, + ); +});