Skip to content

Commit

Permalink
chore(avm-simulator): sha256 -> sha256compression
Browse files Browse the repository at this point in the history
  • Loading branch information
dbanks12 committed Apr 26, 2024
1 parent c5f51f4 commit 10a8ba2
Show file tree
Hide file tree
Showing 10 changed files with 264 additions and 70 deletions.
1 change: 1 addition & 0 deletions avm-transpiler/src/instructions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pub const ALL_DIRECT: u8 = 0b00000000;
pub const ZEROTH_OPERAND_INDIRECT: u8 = 0b00000001;
pub const FIRST_OPERAND_INDIRECT: u8 = 0b00000010;
pub const SECOND_OPERAND_INDIRECT: u8 = 0b00000100;
pub const THIRD_OPERAND_INDIRECT: u8 = 0b00001000;

/// A simple representation of an AVM instruction for the purpose
/// of generating an AVM bytecode from Brillig.
Expand Down
4 changes: 2 additions & 2 deletions avm-transpiler/src/opcodes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ pub enum AvmOpcode {
// Gadgets
KECCAK,
POSEIDON2,
SHA256, // temp - may be removed, but alot of contracts rely on it
SHA256COMPRESSION,
PEDERSEN, // temp - may be removed, but alot of contracts rely on it
}

Expand Down Expand Up @@ -157,7 +157,7 @@ impl AvmOpcode {
// Gadgets
AvmOpcode::KECCAK => "KECCAK",
AvmOpcode::POSEIDON2 => "POSEIDON2",
AvmOpcode::SHA256 => "SHA256 ",
AvmOpcode::SHA256COMPRESSION => "SHA256COMPRESSION",
AvmOpcode::PEDERSEN => "PEDERSEN",
}
}
Expand Down
39 changes: 38 additions & 1 deletion avm-transpiler/src/transpile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use acvm::FieldElement;

use crate::instructions::{
AvmInstruction, AvmOperand, AvmTypeTag, ALL_DIRECT, FIRST_OPERAND_INDIRECT,
SECOND_OPERAND_INDIRECT, ZEROTH_OPERAND_INDIRECT,
SECOND_OPERAND_INDIRECT, THIRD_OPERAND_INDIRECT, ZEROTH_OPERAND_INDIRECT,
};
use crate::opcodes::AvmOpcode;
use crate::utils::{dbg_print_avm_program, dbg_print_brillig_program};
Expand Down Expand Up @@ -854,6 +854,43 @@ fn generate_mov_instruction(indirect: Option<u8>, source: u32, dest: u32) -> Avm
/// (array goes in -> field element comes out)
fn handle_black_box_function(avm_instrs: &mut Vec<AvmInstruction>, operation: &BlackBoxOp) {
match operation {
BlackBoxOp::Sha256Compression {
input,
hash_values,
output,
} => {
let inputs_offset = input.pointer.0;
let inputs_size_offset = input.size.0;
let hash_values_offset = hash_values.pointer.0;
let hash_values_size_offset = hash_values.size.0;
let dst_offset = output.pointer.0;
//assert_eq!(output.size, 32, "SHA256 output size must be 32!");

avm_instrs.push(AvmInstruction {
opcode: AvmOpcode::SHA256COMPRESSION,
indirect: Some(
ZEROTH_OPERAND_INDIRECT | FIRST_OPERAND_INDIRECT | THIRD_OPERAND_INDIRECT,
),
operands: vec![
AvmOperand::U32 {
value: dst_offset as u32,
},
AvmOperand::U32 {
value: hash_values_offset as u32,
},
AvmOperand::U32 {
value: hash_values_size_offset as u32,
},
AvmOperand::U32 {
value: inputs_offset as u32,
},
AvmOperand::U32 {
value: inputs_size_offset as u32,
},
],
..Default::default()
});
}
BlackBoxOp::PedersenHash {
inputs,
domain_separator,
Expand Down
129 changes: 126 additions & 3 deletions yarn-project/foundation/src/crypto/sha256/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,134 @@ import { Fr } from '../../fields/fields.js';
import { truncateAndPad } from '../../serialize/free_funcs.js';
import { type Bufferable, serializeToBuffer } from '../../serialize/serialize.js';

export const sha256 = (data: Buffer) => Buffer.from(hash.sha256().update(data).digest());
export function sha256(data: Buffer) {
return Buffer.from(hash.sha256().update(data).digest());
}

export const sha256Trunc = (data: Buffer) => truncateAndPad(sha256(data));
export function sha256Trunc(data: Buffer) {
return truncateAndPad(sha256(data));
}

export const sha256ToField = (data: Bufferable[]) => {
export function sha256ToField(data: Bufferable[]) {
const buffer = serializeToBuffer(data);
return Fr.fromBuffer(sha256Trunc(buffer));
};

/**
* The "SHA256 Compression" operation (component operation of SHA256 "Hash").
* WARNING: modifies `state` in place (and also returns it)
*
* This algorithm is extracted from the hash.js package
* and modified to take in an initial state to operate on.
*/
export function sha256Compression(state: Uint32Array, inputs: Uint32Array): Uint32Array {
// assert lengths 8 and 16
const W = new Array(64);
const k = [
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98,
0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786,
0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8,
0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819,
0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a,
0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7,
0xc67178f2,
];
let i = 0;
for (i = 0; i < 16; i++) {
W[i] = inputs[i];
}
for (i = 16; i < W.length; i++) {
W[i] = sum32_4(
W[i - 16],
W[i - 7],
g0_256(W[i - 15]), // Rot17, Rot18, Sh3
g1_256(W[i - 2]), //ROt17, Rot19, Sh10
);
}

let a = state[0];
let b = state[1];
let c = state[2];
let d = state[3];
let e = state[4];
let f = state[5];
let g = state[6];
let h = state[7];

for (let i = 0; i < 64; i++) {
const T1 = sum32_5(
h,
s1_256(e), // Rot6, Rot11, Rot25
ch32(e, f, g),
k[i],
W[i],
);

const T2 = sum32(
s0_256(a), // Rot2, Rot13, Rot22
maj32(a, b, c),
);
h = g;
g = f;
f = e;
e = sum32(d, T1);
d = c;
c = b;
b = a;
a = sum32(T1, T2);
}

state[0] = sum32(state[0], a);
state[1] = sum32(state[1], b);
state[2] = sum32(state[2], c);
state[3] = sum32(state[3], d);
state[4] = sum32(state[4], e);
state[5] = sum32(state[5], f);
state[6] = sum32(state[6], g);
state[7] = sum32(state[7], h);
return state;
};

// SHA256 HELPER FUNCTIONS (from hash.js package)

function rotr32(w: number, b: number) {
return (w >>> b) | (w << (32 - b));
}

function sum32(a: number, b: number) {
return (a + b) >>> 0;
}

function sum32_4(a: number, b: number, c: number, d: number) {
return (a + b + c + d) >>> 0;
}

function sum32_5(a: number, b: number, c: number, d: number, e: number) {
return (a + b + c + d + e) >>> 0;
}

function ch32(x: number, y: number, z: number) {
return (x & y) ^ (~x & z);
}

function maj32(x: number, y: number, z: number) {
return (x & y) ^ (x & z) ^ (y & z);
}

function s0_256(x: number) {
return rotr32(x, 2) ^ rotr32(x, 13) ^ rotr32(x, 22);
}

function s1_256(x: number) {
return rotr32(x, 6) ^ rotr32(x, 11) ^ rotr32(x, 25);
}

function g0_256(x: number) {
return rotr32(x, 7) ^ rotr32(x, 18) ^ (x >>> 3);
}

function g1_256(x: number) {
return rotr32(x, 17) ^ rotr32(x, 19) ^ (x >>> 10);
}

2 changes: 1 addition & 1 deletion yarn-project/simulator/src/avm/avm_gas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ export const GasCosts: Record<Opcode, Gas | typeof DynamicGasCost> = {
// Gadgets
[Opcode.KECCAK]: TemporaryDefaultGasCost,
[Opcode.POSEIDON2]: TemporaryDefaultGasCost,
[Opcode.SHA256]: TemporaryDefaultGasCost, // temp - may be removed, but alot of contracts rely on i: TemporaryDefaultGasCost,
[Opcode.SHA256COMPRESSION]: TemporaryDefaultGasCost,
[Opcode.PEDERSEN]: TemporaryDefaultGasCost, // temp - may be removed, but alot of contracts rely on i: TemporaryDefaultGasCost,t
};

Expand Down
6 changes: 5 additions & 1 deletion yarn-project/simulator/src/avm/fixtures/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { type CommitmentsDB, type PublicContractsDB, type PublicStateDB } from '
import { AvmContext } from '../avm_context.js';
import { AvmContextInputs, AvmExecutionEnvironment } from '../avm_execution_environment.js';
import { AvmMachineState } from '../avm_machine_state.js';
import { Field, Uint8 } from '../avm_memory_types.js';
import { Field, Uint32, Uint8 } from '../avm_memory_types.js';
import { HostStorage } from '../journal/host_storage.js';
import { AvmPersistableStateManager } from '../journal/journal.js';

Expand Down Expand Up @@ -124,6 +124,10 @@ export function randomMemoryBytes(length: number): Uint8[] {
return [...Array(length)].map(_ => new Uint8(Math.floor(Math.random() * 255)));
}

export function randomMemoryUint32s(length: number): Uint32[] {
return [...Array(length)].map(_ => new Uint32(Math.floor(Math.random() * 0xFFFFFFFF)));
}

export function randomMemoryFields(length: number): Field[] {
return [...Array(length)].map(_ => new Field(Fr.random()));
}
110 changes: 66 additions & 44 deletions yarn-project/simulator/src/avm/opcodes/hashing.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { keccak256, pedersenHash, sha256 } from '@aztec/foundation/crypto';
import { keccak256, pedersenHash, sha256Compression } from '@aztec/foundation/crypto';

import { type AvmContext } from '../avm_context.js';
import { Field, type Uint8, Uint32 } from '../avm_memory_types.js';
import { initContext, randomMemoryBytes, randomMemoryFields } from '../fixtures/index.js';
import { initContext, randomMemoryBytes, randomMemoryFields, randomMemoryUint32s } from '../fixtures/index.js';
import { Addressing, AddressingMode } from './addressing_mode.js';
import { Keccak, Pedersen, Poseidon2, Sha256 } from './hashing.js';
import { Keccak, Pedersen, Poseidon2, Sha256Compression } from './hashing.js';

describe('Hashing Opcodes', () => {
let context: AvmContext;
Expand Down Expand Up @@ -136,72 +136,94 @@ describe('Hashing Opcodes', () => {
});
});

describe('Sha256', () => {
describe('Sha256Compression', () => {
it('Should (de)serialize correctly', () => {
const buf = Buffer.from([
Sha256.opcode, // opcode
Sha256Compression.opcode, // opcode
1, // indirect
...Buffer.from('12345678', 'hex'), // dstOffset
...Buffer.from('23456789', 'hex'), // messageOffset
...Buffer.from('3456789a', 'hex'), // messageSizeOffset
...Buffer.from('23456789', 'hex'), // stateOffset
...Buffer.from('3456789a', 'hex'), // stateSizeOffset
...Buffer.from('456789ab', 'hex'), // messageOffset
...Buffer.from('56789abc', 'hex'), // messageSizeOffset
]);
const inst = new Sha256(
const inst = new Sha256Compression(
/*indirect=*/ 1,
/*dstOffset=*/ 0x12345678,
/*messageOffset=*/ 0x23456789,
/*messageSizeOffset=*/ 0x3456789a,
/*stateOffset=*/ 0x23456789,
/*stateSizeOffset=*/ 0x3456789a,
/*messageOffset=*/ 0x456789ab,
/*messageSizeOffset=*/ 0x56789abc,
);

expect(Sha256.deserialize(buf)).toEqual(inst);
expect(Sha256Compression.deserialize(buf)).toEqual(inst);
expect(inst.serialize()).toEqual(buf);
});

it('Should hash correctly - direct', async () => {
const args = randomMemoryBytes(10);
const state = randomMemoryUint32s(8);
const stateArray = Uint32Array.from(state.map(byte => byte.toNumber()));
const message = randomMemoryUint32s(16);
const messageArray = Uint32Array.from(message.map(byte => byte.toNumber()));
const indirect = 0;
const messageOffset = 0;
const messageSizeOffset = 15;
const dstOffset = 20;
context.machineState.memory.set(messageSizeOffset, new Uint32(args.length));
context.machineState.memory.setSlice(messageOffset, args);

await new Sha256(indirect, dstOffset, messageOffset, messageSizeOffset).execute(context);

const resultBuffer = Buffer.concat(
context.machineState.memory.getSliceAs<Uint8>(dstOffset, 32).map(byte => byte.toBuffer()),
);
const inputBuffer = Buffer.concat(args.map(byte => byte.toBuffer()));
const expectedHash = sha256(inputBuffer);
expect(resultBuffer).toEqual(expectedHash);
const stateOffset = 0;
const stateSizeOffset = 100;
const messageOffset = 200;
const messageSizeOffset = 300;
const outputOffset = 300;
context.machineState.memory.set(stateSizeOffset, new Uint32(state.length));
context.machineState.memory.setSlice(stateOffset, state);
context.machineState.memory.set(messageSizeOffset, new Uint32(message.length));
context.machineState.memory.setSlice(messageOffset, message);

await new Sha256Compression(indirect, outputOffset, stateOffset, stateSizeOffset, messageOffset, messageSizeOffset).execute(context);

const output = context.machineState.memory.getSliceAs<Uint32>(outputOffset, 8);
const outputArray = Uint32Array.from(output.map(word => word.toNumber()));

const expectedOutput = sha256Compression(stateArray, messageArray);
expect(outputArray).toEqual(expectedOutput);
});

it('Should hash correctly - indirect', async () => {
const args = randomMemoryBytes(10);
const state = randomMemoryUint32s(8);
const stateArray = Uint32Array.from(state.map(byte => byte.toNumber()));
const message = randomMemoryUint32s(16);
const messageArray = Uint32Array.from(message.map(byte => byte.toNumber()));
const indirect = new Addressing([
/*dstOffset=*/ AddressingMode.INDIRECT,
/*stateOffset*/ AddressingMode.INDIRECT,
/*stateSizeOffset*/ AddressingMode.INDIRECT,
/*messageOffset*/ AddressingMode.INDIRECT,
/*messageSizeOffset*/ AddressingMode.INDIRECT,
]).toWire();
const messageOffset = 0;
const messageOffsetReal = 10;
const messageSizeOffset = 1;
const messageSizeOffsetReal = 100;
const dstOffset = 2;
const dstOffsetReal = 30;
context.machineState.memory.set(messageOffset, new Uint32(messageOffsetReal));
context.machineState.memory.set(dstOffset, new Uint32(dstOffsetReal));
const stateOffset = 0;
const stateOffsetReal = 10;
const stateSizeOffset = 1;
const stateSizeOffsetReal = 100;
const messageOffset = 2;
const messageOffsetReal = 200;
const messageSizeOffset = 3;
const messageSizeOffsetReal = 300;
const outputOffset = 4;
const outputOffsetReal = 400;
context.machineState.memory.set(stateSizeOffset, new Uint32(stateSizeOffsetReal));
context.machineState.memory.set(stateSizeOffsetReal, new Uint32(state.length));
context.machineState.memory.set(stateOffset, new Uint32(stateOffsetReal));
context.machineState.memory.setSlice(stateOffsetReal, state);
context.machineState.memory.set(messageSizeOffset, new Uint32(messageSizeOffsetReal));
context.machineState.memory.set(messageSizeOffsetReal, new Uint32(args.length));
context.machineState.memory.setSlice(messageOffsetReal, args);
context.machineState.memory.set(messageSizeOffsetReal, new Uint32(message.length));
context.machineState.memory.set(messageOffset, new Uint32(messageOffsetReal));
context.machineState.memory.setSlice(messageOffsetReal, message);
context.machineState.memory.set(outputOffset, new Uint32(outputOffsetReal));

await new Sha256(indirect, dstOffset, messageOffset, messageSizeOffset).execute(context);
await new Sha256Compression(indirect, outputOffset, stateOffset, stateSizeOffset, messageOffset, messageSizeOffset).execute(context);

const resultBuffer = Buffer.concat(
context.machineState.memory.getSliceAs<Uint8>(dstOffsetReal, 32).map(byte => byte.toBuffer()),
);
const inputBuffer = Buffer.concat(args.map(byte => byte.toBuffer()));
const expectedHash = sha256(inputBuffer);
expect(resultBuffer).toEqual(expectedHash);
const output = context.machineState.memory.getSliceAs<Uint32>(outputOffsetReal, 8);
const outputArray = Uint32Array.from(output.map(word => word.toNumber()));

const expectedOutput = sha256Compression(stateArray, messageArray);
expect(outputArray).toEqual(expectedOutput);
});
});

Expand Down
Loading

0 comments on commit 10a8ba2

Please sign in to comment.