Skip to content

Commit

Permalink
Logs hashes in L2Block.getCalldataHash (#760)
Browse files Browse the repository at this point in the history
* refactor: updated outdated naming

* chore: updated jest config to avoid warnings

* feat: implementation of `L2Block.computeKernelLogsHash`

* test: reworked testComputeKernelLogsHashNoLogs

* test: testComputeKernelLogsMiddleIterationWithoutLogs Decoder test

* test: `testComputeKernelLogsMiddleIterationWithoutLogs` in l2_block.test.ts

* final touches
  • Loading branch information
benesjan authored Jun 8, 2023
1 parent ca7b916 commit 8ffad9b
Show file tree
Hide file tree
Showing 5 changed files with 152 additions and 25 deletions.
4 changes: 2 additions & 2 deletions l1-contracts/src/core/libraries/Decoder.sol
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ library Decoder {
* Zero values.
*/

// TODO: Uncomment once the logs functionality is added in other places
// TODO #769, relevant issue https://github.com/AztecProtocol/aztec-packages/issues/769
// /**
// * Compute encrypted and unencrypted logs hashes corresponding to the current leaf.
// * Note: `computeKernelLogsHash` will advance offsets by the number of bytes processed.
Expand Down Expand Up @@ -376,7 +376,7 @@ library Decoder {
dstPtr := add(dstPtr, 0xc)
calldatacopy(dstPtr, add(_l2Block.offset, add(contractDataOffset, 0x54)), 0x14)

// TODO: Uncomment once the logs functionality is added in other places
// TODO #769, relevant issue https://github.com/AztecProtocol/aztec-packages/issues/769
// // encryptedLogsHashKernel1
// dstPtr := add(dstPtr, 0x14)
// mstore(dstPtr, mload(add(vars, 0x60))) // `encryptedLogsHashKernel1` starts at 0x60 in `vars`
Expand Down
57 changes: 54 additions & 3 deletions l1-contracts/test/Decoder.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -145,13 +145,21 @@ contract DecoderTest is Test {
}
}

function testComputeKernelLogsHashNoLogs() public {
bytes memory encodedLogs = hex"00000000"; // 4 empty bytes indicating that length of kernel logs is 0
function testComputeKernelLogsIterationWithoutLogs() public {
bytes memory kernelLogsLength = hex"00000004"; // 4 bytes containing value 4
bytes memory iterationLogsLength = hex"00000000"; // 4 empty bytes indicating that length of this iteration's logs is 0
bytes memory encodedLogs = abi.encodePacked(kernelLogsLength, iterationLogsLength);

(bytes32 logsHash, uint256 bytesAdvanced) = helper.computeKernelLogsHash(encodedLogs);

bytes32 kernelPublicInputsLogsHash = bytes32(0);
bytes32 privateCircuitPublicInputsLogsHash = sha256(new bytes(0));

bytes32 referenceLogsHash =
sha256(abi.encodePacked(kernelPublicInputsLogsHash, privateCircuitPublicInputsLogsHash));

assertEq(bytesAdvanced, encodedLogs.length, "Advanced by an incorrect number of bytes");
assertEq(logsHash, bytes32(0), "Logs hash should be 0 when there are no logs");
assertEq(logsHash, referenceLogsHash, "Incorrect logs hash");
}

function testComputeKernelLogs1Iteration() public {
Expand Down Expand Up @@ -207,6 +215,49 @@ contract DecoderTest is Test {
assertEq(logsHash, referenceLogsHashFromIteration2, "Incorrect logs hash");
}

function testComputeKernelLogsMiddleIterationWithoutLogs() public {
// || K_LOGS_LEN | I1_LOGS_LEN | I1_LOGS | I2_LOGS_LEN | I2_LOGS | I3_LOGS_LEN | I3_LOGS ||
// K_LOGS_LEN = 4 + 8 + 4 + 0 + 4 + 20 = 40 (hex"00000028")
// I1_LOGS_LEN = 8 (hex"00000008")
// I1_LOGS = 8 random bytes (hex"aafdc7aa93e78a70")
// I2_LOGS_LEN = 0 (hex"00000000")
// I2_LOGS = 0 bytes (hex"")
// I3_LOGS_LEN = 20 (hex"00000014")
// I3_LOGS = 20 random bytes (hex"97aee30906a86173c86c6d3f108eefc36e7fb014")
bytes memory firstFunctionCallLogs = hex"aafdc7aa93e78a70";
bytes memory secondFunctionCallLogs = hex"";
bytes memory thirdFunctionCallLogs = hex"97aee30906a86173c86c6d3f108eefc36e7fb014";
bytes memory encodedLogs = abi.encodePacked(
hex"0000002800000008",
firstFunctionCallLogs,
hex"00000000",
secondFunctionCallLogs,
hex"00000014",
thirdFunctionCallLogs
);
(bytes32 logsHash, uint256 bytesAdvanced) = helper.computeKernelLogsHash(encodedLogs);

bytes32 referenceLogsHashFromIteration1 =
sha256(abi.encodePacked(bytes32(0), sha256(firstFunctionCallLogs)));

bytes32 privateCircuitPublicInputsLogsHashSecondCall = sha256(secondFunctionCallLogs);

bytes32 referenceLogsHashFromIteration2 = sha256(
abi.encodePacked(
referenceLogsHashFromIteration1, privateCircuitPublicInputsLogsHashSecondCall
)
);

bytes32 privateCircuitPublicInputsLogsHashThirdCall = sha256(thirdFunctionCallLogs);

bytes32 referenceLogsHashFromIteration3 = sha256(
abi.encodePacked(referenceLogsHashFromIteration2, privateCircuitPublicInputsLogsHashThirdCall)
);

assertEq(bytesAdvanced, encodedLogs.length, "Advanced by an incorrect number of bytes");
assertEq(logsHash, referenceLogsHashFromIteration3, "Incorrect logs hash");
}

// Tests https://github.com/AztecProtocol/aztec-packages/issues/730 is handled correctly
function testLogsDontGetInterpretedAsMessages() public {
(,,,,, bytes32[] memory l1ToL2Msgs) = helper.decode(block_with_no_l1_l2_msgs_and_with_logs);
Expand Down
8 changes: 1 addition & 7 deletions yarn-project/types/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,7 @@
"^(\\.{1,2}/.*)\\.js$": "$1"
},
"testRegex": "./src/.*\\.test\\.ts$",
"rootDir": "./src",
"globals": {
"ts-jest": {
"useESM": true,
"tsconfig": "../tsconfig.json"
}
}
"rootDir": "./src"
},
"dependencies": {
"@aztec/circuits.js": "workspace:^",
Expand Down
46 changes: 46 additions & 0 deletions yarn-project/types/src/l2_block.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,50 @@ describe('L2Block', () => {

expect(recovered).toEqual(block);
});

// TS equivalent of `testComputeKernelLogsIterationWithoutLogs` in `Decoder.t.sol`
it('correctly computes kernel logs hash when there are no logs', () => {
// The following 2 values are copied from `testComputeKernelLogsIterationWithoutLogs` in `Decoder.t.sol`
const encodedLogs = Buffer.from('0000000400000000', 'hex');
const referenceLogsHash = Buffer.from('1c9ecec90e28d2461650418635878a5c91e49f47586ecf75f2b0cbb94e897112', 'hex');

const logsHash = L2Block.computeKernelLogsHash(encodedLogs);
expect(logsHash).toEqual(referenceLogsHash);
});

// TS equivalent of `testComputeKernelLogs1Iteration` in `Decoder.t.sol`
it('correctly computes kernel logs hash when are logs from 1 iteration', () => {
// The following 2 values are copied from `testComputeKernelLogs1Iteration` in `Decoder.t.sol`
const encodedLogs = Buffer.from('0000000c00000008aafdc7aa93e78a70', 'hex');
const referenceLogsHash = Buffer.from('8fabfa6cd5f3590246c5e8b82371ad9a0cc1bb34a031b761697295f5ecda418a', 'hex');

const logsHash = L2Block.computeKernelLogsHash(encodedLogs);
expect(logsHash).toEqual(referenceLogsHash);
});

// TS equivalent of `testComputeKernelLogs2Iterations` in `Decoder.t.sol`
it('correctly computes kernel logs hash when are logs from 2 iterations', () => {
// The following 2 values are copied from `testComputeKernelLogs2Iterations` in `Decoder.t.sol`
const encodedLogs = Buffer.from(
'0000002400000008aafdc7aa93e78a700000001497aee30906a86173c86c6d3f108eefc36e7fb014',
'hex',
);
const referenceLogsHash = Buffer.from('23796d70846c2bfcf5d43172e6078c09bee2a42c51c1f6b02bd00be33154b24e', 'hex');

const logsHash = L2Block.computeKernelLogsHash(encodedLogs);
expect(logsHash).toEqual(referenceLogsHash);
});

// TS equivalent of `testComputeKernelLogsMiddleIterationWithoutLogs` in `Decoder.t.sol`
it('correctly computes kernel logs hash when are logs from 3 iterations (2nd iter. without logs)', () => {
// The following 2 values are copied from `testComputeKernelLogsMiddleIterationWithoutLogs` in `Decoder.t.sol`
const encodedLogs = Buffer.from(
'0000002800000008aafdc7aa93e78a70000000000000001497aee30906a86173c86c6d3f108eefc36e7fb014',
'hex',
);
const referenceLogsHash = Buffer.from('29fab3875a0c31104acd405509861e6afb6ee075cc157170c2d6948fd4f852f3', 'hex');

const logsHash = L2Block.computeKernelLogsHash(encodedLogs);
expect(logsHash).toEqual(referenceLogsHash);
});
});
62 changes: 49 additions & 13 deletions yarn-project/types/src/l2_block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -506,7 +506,6 @@ export class L2Block {
* and inside the circuit, it is part of the public inputs.
* @returns The calldata hash.
*/
// TODO: add newEncryptedLogs to this hash once it's been propagated through circuits.
getCalldataHash() {
const computeRoot = (leafs: Buffer[]): Buffer => {
const layers: Buffer[][] = [leafs];
Expand Down Expand Up @@ -534,36 +533,40 @@ export class L2Block {
const leafs: Buffer[] = [];

for (let i = 0; i < leafCount; i++) {
const commitmentPerBase = KERNEL_NEW_COMMITMENTS_LENGTH * 2;
const nullifierPerBase = KERNEL_NEW_NULLIFIERS_LENGTH * 2;
const publicDataWritesPerBase = KERNEL_PUBLIC_DATA_UPDATE_REQUESTS_LENGTH * 2; // @note why is this constant named differently?
const commitmentsPerBase = KERNEL_NEW_COMMITMENTS_LENGTH * 2;
const nullifiersPerBase = KERNEL_NEW_NULLIFIERS_LENGTH * 2;
const publicDataUpdateRequestsPerBase = KERNEL_PUBLIC_DATA_UPDATE_REQUESTS_LENGTH * 2;
const l2ToL1MsgsPerBase = KERNEL_NEW_L2_TO_L1_MSGS_LENGTH * 2;
const commitmentBuffer = Buffer.concat(
this.newCommitments.slice(i * commitmentPerBase, (i + 1) * commitmentPerBase).map(x => x.toBuffer()),
const commitmentsBuffer = Buffer.concat(
this.newCommitments.slice(i * commitmentsPerBase, (i + 1) * commitmentsPerBase).map(x => x.toBuffer()),
);
const nullifierBuffer = Buffer.concat(
this.newNullifiers.slice(i * nullifierPerBase, (i + 1) * nullifierPerBase).map(x => x.toBuffer()),
const nullifiersBuffer = Buffer.concat(
this.newNullifiers.slice(i * nullifiersPerBase, (i + 1) * nullifiersPerBase).map(x => x.toBuffer()),
);
const dataWritesBuffer = Buffer.concat(
const publicDataUpdateRequestsBuffer = Buffer.concat(
this.newPublicDataWrites
.slice(i * publicDataWritesPerBase, (i + 1) * publicDataWritesPerBase)
.slice(i * publicDataUpdateRequestsPerBase, (i + 1) * publicDataUpdateRequestsPerBase)
.map(x => x.toBuffer()),
);
const newL2ToL1MsgsBuffer = Buffer.concat(
this.newL2ToL1Msgs.slice(i * l2ToL1MsgsPerBase, (i + 1) * l2ToL1MsgsPerBase).map(x => x.toBuffer()),
);

const inputValue = Buffer.concat([
commitmentBuffer,
nullifierBuffer,
dataWritesBuffer,
commitmentsBuffer,
nullifiersBuffer,
publicDataUpdateRequestsBuffer,
newL2ToL1MsgsBuffer,
this.newContracts[i * 2].toBuffer(),
this.newContracts[i * 2 + 1].toBuffer(),
this.newContractData[i * 2].contractAddress.toBuffer(),
this.newContractData[i * 2].portalContractAddress.toBuffer32(),
this.newContractData[i * 2 + 1].contractAddress.toBuffer(),
this.newContractData[i * 2 + 1].portalContractAddress.toBuffer32(),
// The following 2 are encrypted logs hashes from kernel 0 and kernel 1 of base rollup circuit
// TODO #769, relevant issue https://github.com/AztecProtocol/aztec-packages/issues/769
// L2Block.computeKernelLogsHash(this.newEncryptedLogs.dataChunks[i * 2]),
// L2Block.computeKernelLogsHash(this.newEncryptedLogs.dataChunks[i * 2 + 1]),
]);
leafs.push(sha256(inputValue));
}
Expand Down Expand Up @@ -684,4 +687,37 @@ export class L2Block {
`newL1ToL2Messages: ${inspectFrArray(this.newL1ToL2Messages)}`,
].join('\n');
}

/**
* Computes logs hash as is done in the kernel and app circuits.
* @param encodedLogs - Encoded logs to be hashed.
* @returns The hash of the logs.
* Note: This is a TS implementation of `computeKernelLogsHash` function in Decoder.sol. See that function documentation
* for more details.
*/
static computeKernelLogsHash(encodedLogs: Buffer): Buffer {
const reader = new BufferReader(encodedLogs);

let remainingLogsLength = reader.readNumber();
const logsHashes: [Buffer, Buffer] = [Buffer.alloc(32), Buffer.alloc(32)];
let kernelPublicInputsLogsHash = Buffer.alloc(32);

while (remainingLogsLength > 0) {
const iterationLogsLength = reader.readNumber();
const iterationLogs = reader.readBytes(iterationLogsLength);

const privateCircuitPublicInputsLogsHash = sha256(iterationLogs);

logsHashes[0] = kernelPublicInputsLogsHash;
logsHashes[1] = privateCircuitPublicInputsLogsHash;

// Hash logs hash from the public inputs of previous kernel iteration and logs hash from private circuit public inputs
kernelPublicInputsLogsHash = sha256(Buffer.concat(logsHashes));

// Decrease remaining logs length by this iteration's logs length (len(I?_LOGS)) and 4 bytes for I?_LOGS_LEN
remainingLogsLength -= iterationLogsLength + 4;
}

return kernelPublicInputsLogsHash;
}
}

0 comments on commit 8ffad9b

Please sign in to comment.