From 7503880a3ff3019e87734b76eccbdba2f031f6c9 Mon Sep 17 00:00:00 2001 From: benesjan Date: Wed, 24 Apr 2024 11:09:55 +0000 Subject: [PATCH 1/3] feat: AES oracle --- .../simulator/src/acvm/oracle/oracle.ts | 19 +++++++++++++++++++ .../simulator/src/acvm/oracle/typed_oracle.ts | 4 ++++ .../src/client/client_execution_context.ts | 7 ++++++- 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/yarn-project/simulator/src/acvm/oracle/oracle.ts b/yarn-project/simulator/src/acvm/oracle/oracle.ts index ac88228ef08..9b91327c5f9 100644 --- a/yarn-project/simulator/src/acvm/oracle/oracle.ts +++ b/yarn-project/simulator/src/acvm/oracle/oracle.ts @@ -4,6 +4,7 @@ import { EventSelector, FunctionSelector } from '@aztec/foundation/abi'; import { AztecAddress } from '@aztec/foundation/aztec-address'; import { Fr, Point } from '@aztec/foundation/fields'; import { createDebugLogger } from '@aztec/foundation/log'; +import { to2Fields } from '@aztec/foundation/serialize'; import { type ACVMField } from '../acvm_types.js'; import { frToBoolean, frToNumber, fromACVMField } from '../deserialize.js'; @@ -378,4 +379,22 @@ export class Oracle { ); return toAcvmEnqueuePublicFunctionResult(enqueuedRequest); } + + encrypt([symmetricKey]: ACVMField[], [initializationVector]: ACVMField[], plaintext: ACVMField[]): ACVMField[] { + // Symmetric key and initialization vector (IV) are 16 bytes and we store them as big endian in Fr + const processedSK = fromACVMField(symmetricKey).toBuffer().subarray(0, 16); + const processedIV = fromACVMField(initializationVector).toBuffer().subarray(0, 16); + // TODO(benesjan): we could save some info here by not including the 2 empty bits at the end of each serialized + // field --> this could be valuable as the ciphertext will go on-chain + const processedPlaintext = Buffer.concat(plaintext.map(fromACVMField).map(f => f.toBuffer())); + const ciphertext = this.typedOracle.encrypt(processedSK, processedIV, processedPlaintext); + // Chunk the ciphertext buffer to 32 bytes and on each chunk call to2Fields function + const ciphertextFields: Fr[] = []; + for (let i = 0; i < ciphertext.length; i += Fr.SIZE_IN_BYTES) { + const chunk = ciphertext.subarray(i, i + Fr.SIZE_IN_BYTES); + ciphertextFields.push(...to2Fields(chunk)); + } + + return ciphertextFields.map(toACVMField); + } } diff --git a/yarn-project/simulator/src/acvm/oracle/typed_oracle.ts b/yarn-project/simulator/src/acvm/oracle/typed_oracle.ts index 70f57233af1..3a0ebe415e3 100644 --- a/yarn-project/simulator/src/acvm/oracle/typed_oracle.ts +++ b/yarn-project/simulator/src/acvm/oracle/typed_oracle.ts @@ -233,4 +233,8 @@ export abstract class TypedOracle { ): Promise { throw new OracleMethodNotAvailableError('enqueuePublicFunctionCall'); } + + encrypt(_symmetricKey: Buffer, _initializationVector: Buffer, _plaintext: Buffer): Buffer { + throw new OracleMethodNotAvailableError('encrypt'); + } } diff --git a/yarn-project/simulator/src/client/client_execution_context.ts b/yarn-project/simulator/src/client/client_execution_context.ts index 0ec755281cc..618cca083ce 100644 --- a/yarn-project/simulator/src/client/client_execution_context.ts +++ b/yarn-project/simulator/src/client/client_execution_context.ts @@ -21,7 +21,7 @@ import { type SideEffect, type TxContext, } from '@aztec/circuits.js'; -import { type Grumpkin } from '@aztec/circuits.js/barretenberg'; +import { Aes128, type Grumpkin } from '@aztec/circuits.js/barretenberg'; import { computePublicDataTreeLeafSlot, computeUniqueNoteHash, siloNoteHash } from '@aztec/circuits.js/hash'; import { type FunctionAbi, type FunctionArtifact, countArgumentsSize } from '@aztec/foundation/abi'; import { type AztecAddress } from '@aztec/foundation/aztec-address'; @@ -522,4 +522,9 @@ export class ClientExecutionContext extends ViewDataOracle { } return values; } + + public override encrypt(symmetricKey: Buffer, initializationVector: Buffer, plaintext: Buffer): Buffer { + const aes128 = new Aes128(); + return aes128.encryptBufferCBC(plaintext, initializationVector, symmetricKey); + } } From 8e7fb2b523f723a8d9bcb48d521e70b847072256 Mon Sep 17 00:00:00 2001 From: benesjan Date: Wed, 24 Apr 2024 15:14:07 +0000 Subject: [PATCH 2/3] WIP --- noir-projects/aztec-nr/aztec/src/oracle.nr | 1 + .../aztec-nr/aztec/src/oracle/encryption.nr | 7 +++++ .../contracts/test_contract/src/main.nr | 8 +++++- .../simulator/src/acvm/oracle/oracle.ts | 26 +++++++------------ .../simulator/src/acvm/oracle/typed_oracle.ts | 2 +- .../src/client/client_execution_context.ts | 4 +-- 6 files changed, 28 insertions(+), 20 deletions(-) create mode 100644 noir-projects/aztec-nr/aztec/src/oracle/encryption.nr diff --git a/noir-projects/aztec-nr/aztec/src/oracle.nr b/noir-projects/aztec-nr/aztec/src/oracle.nr index 57415dc9575..753ef6e930a 100644 --- a/noir-projects/aztec-nr/aztec/src/oracle.nr +++ b/noir-projects/aztec-nr/aztec/src/oracle.nr @@ -4,6 +4,7 @@ mod arguments; mod call_private_function; +mod encryption; mod get_contract_instance; mod get_l1_to_l2_membership_witness; mod get_nullifier_membership_witness; diff --git a/noir-projects/aztec-nr/aztec/src/oracle/encryption.nr b/noir-projects/aztec-nr/aztec/src/oracle/encryption.nr new file mode 100644 index 00000000000..cb655c756ce --- /dev/null +++ b/noir-projects/aztec-nr/aztec/src/oracle/encryption.nr @@ -0,0 +1,7 @@ + +#[oracle(aes128Encrypt)] +pub fn aes128_encrypt_oracle(input: [u8; N], iv: [u8; 16], key: [u8; 16]) -> [u8; N] {} + +unconstrained pub fn aes128_encrypt(input: [u8; N], iv: [u8; 16], key: [u8; 16]) -> [u8; N] { + aes128_encrypt_oracle(input, iv, key) +} diff --git a/noir-projects/noir-contracts/contracts/test_contract/src/main.nr b/noir-projects/noir-contracts/contracts/test_contract/src/main.nr index f7a40cf41a8..107df25dc72 100644 --- a/noir-projects/noir-contracts/contracts/test_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/test_contract/src/main.nr @@ -25,7 +25,7 @@ contract Test { note_getter_options::NoteStatus }, deploy::deploy_contract as aztec_deploy_contract, - oracle::{get_public_key::get_public_key as get_public_key_oracle, unsafe_rand::unsafe_rand} + oracle::{encryption::aes128_encrypt, get_public_key::get_public_key as get_public_key_oracle, unsafe_rand::unsafe_rand} }; use dep::token_portal_content_hash_lib::{get_mint_private_content_hash, get_mint_public_content_hash}; use dep::value_note::value_note::ValueNote; @@ -309,6 +309,12 @@ contract Test { assert(context.version() == version, "Invalid version"); } + #[aztec(private)] + fn encrypt(input: [u8; 64], iv: [u8; 16], key: [u8; 16]) { + let result = aes128_encrypt(input, iv, key); + context.emit_unencrypted_log(result); + } + #[aztec(public)] fn assert_public_global_vars( chain_id: Field, diff --git a/yarn-project/simulator/src/acvm/oracle/oracle.ts b/yarn-project/simulator/src/acvm/oracle/oracle.ts index 9b91327c5f9..f112db7d65a 100644 --- a/yarn-project/simulator/src/acvm/oracle/oracle.ts +++ b/yarn-project/simulator/src/acvm/oracle/oracle.ts @@ -4,7 +4,6 @@ import { EventSelector, FunctionSelector } from '@aztec/foundation/abi'; import { AztecAddress } from '@aztec/foundation/aztec-address'; import { Fr, Point } from '@aztec/foundation/fields'; import { createDebugLogger } from '@aztec/foundation/log'; -import { to2Fields } from '@aztec/foundation/serialize'; import { type ACVMField } from '../acvm_types.js'; import { frToBoolean, frToNumber, fromACVMField } from '../deserialize.js'; @@ -380,21 +379,16 @@ export class Oracle { return toAcvmEnqueuePublicFunctionResult(enqueuedRequest); } - encrypt([symmetricKey]: ACVMField[], [initializationVector]: ACVMField[], plaintext: ACVMField[]): ACVMField[] { - // Symmetric key and initialization vector (IV) are 16 bytes and we store them as big endian in Fr - const processedSK = fromACVMField(symmetricKey).toBuffer().subarray(0, 16); - const processedIV = fromACVMField(initializationVector).toBuffer().subarray(0, 16); - // TODO(benesjan): we could save some info here by not including the 2 empty bits at the end of each serialized - // field --> this could be valuable as the ciphertext will go on-chain - const processedPlaintext = Buffer.concat(plaintext.map(fromACVMField).map(f => f.toBuffer())); - const ciphertext = this.typedOracle.encrypt(processedSK, processedIV, processedPlaintext); - // Chunk the ciphertext buffer to 32 bytes and on each chunk call to2Fields function - const ciphertextFields: Fr[] = []; - for (let i = 0; i < ciphertext.length; i += Fr.SIZE_IN_BYTES) { - const chunk = ciphertext.subarray(i, i + Fr.SIZE_IN_BYTES); - ciphertextFields.push(...to2Fields(chunk)); - } + aes128Encrypt(input: ACVMField[], initializationVector: ACVMField[], key: ACVMField[]): ACVMField[] { + // Convert each field to a number and then to a buffer (1 byte is stored in 1 field) + const processedInput = Buffer.from(input.map(fromACVMField).map(f => f.toNumber())); + const processedIV = Buffer.from(initializationVector.map(fromACVMField).map(f => f.toNumber())); + const processedKey = Buffer.from(key.map(fromACVMField).map(f => f.toNumber())); + + // Encrypt the input + const ciphertext = this.typedOracle.aes128Encrypt(processedInput, processedIV, processedKey); - return ciphertextFields.map(toACVMField); + // Convert each byte of ciphertext to a field and return it + return Array.from(ciphertext).map(byte => toACVMField(byte)); } } diff --git a/yarn-project/simulator/src/acvm/oracle/typed_oracle.ts b/yarn-project/simulator/src/acvm/oracle/typed_oracle.ts index 3a0ebe415e3..0771458a972 100644 --- a/yarn-project/simulator/src/acvm/oracle/typed_oracle.ts +++ b/yarn-project/simulator/src/acvm/oracle/typed_oracle.ts @@ -234,7 +234,7 @@ export abstract class TypedOracle { throw new OracleMethodNotAvailableError('enqueuePublicFunctionCall'); } - encrypt(_symmetricKey: Buffer, _initializationVector: Buffer, _plaintext: Buffer): Buffer { + aes128Encrypt(_input: Buffer, _initializationVector: Buffer, _key: Buffer): Buffer { throw new OracleMethodNotAvailableError('encrypt'); } } diff --git a/yarn-project/simulator/src/client/client_execution_context.ts b/yarn-project/simulator/src/client/client_execution_context.ts index 618cca083ce..0bcb1f07dbb 100644 --- a/yarn-project/simulator/src/client/client_execution_context.ts +++ b/yarn-project/simulator/src/client/client_execution_context.ts @@ -523,8 +523,8 @@ export class ClientExecutionContext extends ViewDataOracle { return values; } - public override encrypt(symmetricKey: Buffer, initializationVector: Buffer, plaintext: Buffer): Buffer { + public override aes128Encrypt(input: Buffer, initializationVector: Buffer, key: Buffer): Buffer { const aes128 = new Aes128(); - return aes128.encryptBufferCBC(plaintext, initializationVector, symmetricKey); + return aes128.encryptBufferCBC(input, initializationVector, key); } } From f74a33f4ec0ab436f91f713c329477d2d5716e33 Mon Sep 17 00:00:00 2001 From: benesjan Date: Wed, 24 Apr 2024 15:24:42 +0000 Subject: [PATCH 3/3] it works --- .../end-to-end/src/e2e_encryption.test.ts | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 yarn-project/end-to-end/src/e2e_encryption.test.ts diff --git a/yarn-project/end-to-end/src/e2e_encryption.test.ts b/yarn-project/end-to-end/src/e2e_encryption.test.ts new file mode 100644 index 00000000000..861a7c573c3 --- /dev/null +++ b/yarn-project/end-to-end/src/e2e_encryption.test.ts @@ -0,0 +1,41 @@ +import { type Wallet } from '@aztec/aztec.js'; +import { Aes128 } from '@aztec/circuits.js/barretenberg'; +import { TestContract } from '@aztec/noir-contracts.js'; + +import { randomBytes } from 'crypto'; + +import { setup } from './fixtures/utils.js'; + +describe('e2e_encryption', () => { + const aes128 = new Aes128(); + + let wallet: Wallet; + let teardown: () => Promise; + + let contract: TestContract; + + beforeAll(async () => { + ({ teardown, wallet } = await setup()); + contract = await TestContract.deploy(wallet).send().deployed(); + }, 25_000); + + afterAll(() => teardown()); + + it('encrypts', async () => { + const input = randomBytes(64); + const iv = randomBytes(16); + const key = randomBytes(16); + + const expectedCiphertext = aes128.encryptBufferCBC(input, iv, key); + + const logs = await contract.methods + .encrypt(Array.from(input), Array.from(iv), Array.from(key)) + .send() + .getUnencryptedLogs(); + // Each byte of encrypted data is in its own field and it's all serialized into a long buffer so we simply extract + // each 32nd byte from the buffer to get the encrypted data + const recoveredCiphertext = logs.logs[0].log.data.filter((_, i) => (i + 1) % 32 === 0); + + expect(recoveredCiphertext).toEqual(expectedCiphertext); + }); +});