Skip to content

Commit c8141eb

Browse files
Update SHA-256 VM chip to support SHA-512 and SHA-384
1 parent 0f9d07a commit c8141eb

File tree

18 files changed

+2187
-92
lines changed

18 files changed

+2187
-92
lines changed

extensions/sha2/circuit/Cargo.toml

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
[package]
2-
name = "openvm-sha256-circuit"
2+
name = "openvm-sha2-circuit"
33
version.workspace = true
44
authors.workspace = true
55
edition.workspace = true
6-
description = "OpenVM circuit extension for sha256"
6+
description = "OpenVM circuit extension for SHA-2"
77

88
[dependencies]
99
openvm-stark-backend = { workspace = true }
@@ -13,16 +13,16 @@ openvm-circuit-primitives-derive = { workspace = true }
1313
openvm-circuit-derive = { workspace = true }
1414
openvm-circuit = { workspace = true }
1515
openvm-instructions = { workspace = true }
16-
openvm-sha256-transpiler = { workspace = true }
16+
openvm-sha2-transpiler = { workspace = true }
1717
openvm-rv32im-circuit = { workspace = true }
18-
openvm-sha256-air = { workspace = true }
18+
openvm-sha2-air = { workspace = true }
1919

2020
derive-new.workspace = true
2121
derive_more = { workspace = true, features = ["from"] }
2222
rand.workspace = true
2323
serde.workspace = true
2424
sha2 = { version = "0.10", default-features = false }
25-
strum = { workspace = true }
25+
ndarray = { workspace = true, default-features = false }
2626

2727
[dev-dependencies]
2828
openvm-stark-sdk = { workspace = true }
@@ -37,3 +37,6 @@ mimalloc = ["openvm-circuit/mimalloc"]
3737
jemalloc = ["openvm-circuit/jemalloc"]
3838
jemalloc-prof = ["openvm-circuit/jemalloc-prof"]
3939
nightly-features = ["openvm-circuit/nightly-features"]
40+
41+
[package.metadata.cargo-shear]
42+
ignored = ["ndarray"]

extensions/sha2/circuit/README.md

Lines changed: 39 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,43 @@
1-
# SHA256 VM Extension
1+
# SHA-2 VM Extension
22

3-
This crate contains the circuit for the SHA256 VM extension.
3+
This crate contains circuits for the SHA-2 family of hash functions.
4+
We support SHA-256, SHA-512, and SHA-384.
45

5-
## SHA-256 Algorithm Summary
6+
## SHA-2 Algorithms Summary
67

7-
See the [FIPS standard](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf), in particular, section 6.2 for reference.
8+
The SHA-256, SHA-512, and SHA-384 algorithms are similar in structure.
9+
We will first describe the SHA-256 algorithm, and then describe the differences between the three algorithms.
10+
11+
See the [FIPS standard](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf) for reference. In particular, sections 6.2, 6.4, and 6.5.
812

913
In short the SHA-256 algorithm works as follows.
1014
1. Pad the message to 512 bits and split it into 512-bit 'blocks'.
11-
2. Initialize a hash state consisting of eight 32-bit words.
15+
2. Initialize a hash state consisting of eight 32-bit words to a specific constant value.
1216
3. For each block,
13-
1. split the message into 16 32-bit words and produce 48 more 'message schedule' words based on them.
14-
2. apply 64 'rounds' to update the hash state based on the message schedule.
15-
3. add the previous block's final hash state to the current hash state (modulo `2^32`).
17+
1. split the message into 16 32-bit words and produce 48 more words based on them. The 16 message words together with the 48 additional words are called the 'message schedule'.
18+
2. apply a scrambling function 64 times to the hash state to update it based on the message schedule. We call each update a 'round'.
19+
3. add the previous block's final hash state to the current hash state (modulo $2^{32}$).
1620
4. The output is the final hash state
1721

22+
The differences with the SHA-512 algorithm are that:
23+
- SHA-512 uses 64-bit words, 1024-bit blocks, performs 80 rounds, and produces a 512-bit output.
24+
- all the arithmetic is done modulo $2^{64}$.
25+
- the initial hash state is different.
26+
27+
The SHA-384 algorithm is a truncation of the SHA-512 output to 384 bits, and the only difference is that the initial hash state is different.
28+
1829
## Design Overview
1930

20-
This chip produces an AIR that consists of 17 rows for each block (512 bits) in the message, and no more rows.
21-
The first 16 rows of each block are called 'round rows', and each of them represents four rounds of the SHA-256 algorithm.
22-
Each row constrains updates to the working variables on each round, and it also constrains the message schedule words based on previous rounds.
23-
The final row is called a 'digest row' and it produces a final hash for the block, computed as the sum of the working variables and the previous block's final hash.
31+
We reuse the same AIR code to produce circuits for all three algorithms.
32+
To achieve this, we parameterize the AIR by constants (such as the word size, number of rounds, and block size) that are specific to each algorithm.
33+
34+
This chip produces an AIR that consists of $R+1$ rows for each block of the message, and no more rows
35+
(for SHA-256, $R = 16$ and for SHA-512 and SHA-384, $R = 20$).
36+
The first $R$ rows of each block are called 'round rows', and each of them constrains four rounds of the hash algorithm.
37+
Each row constrains updates to the working variables on each round, and also constrains the message schedule words based on previous rounds.
38+
The final row of each block is called a 'digest row' and it produces a final hash for the block, computed as the sum of the working variables and the previous block's final hash.
2439

25-
Note that this chip only supports messages of length less than `2^29` bytes.
40+
Note that this chip only supports messages of length less than $2^{29}$ bytes.
2641

2742
### Storing working variables
2843

@@ -50,7 +65,7 @@ Since we can reliably constrain values from four rounds ago, we can build up `in
5065

5166
The last block of every message should have the `is_last_block` flag set to `1`.
5267
Note that `is_last_block` is not constrained to be true for the last block of every message, instead it *defines* what the last block of a message is.
53-
For instance, if we produce an air with 10 blocks and only the last block has `is_last_block = 1` then the constraints will interpret it as a single message of length 10 blocks.
68+
For instance, if we produce a trace with 10 blocks and only the last block has `is_last_block = 1` then the constraints will interpret it as a single message of length 10 blocks.
5469
If, however, we set `is_last_block` to true for the 6th block, the trace will be interpreted as hashing two messages, each of length 5 blocks.
5570

5671
Note that we do constrain, however, that the very last block of the trace has `is_last_block = 1`.
@@ -63,11 +78,11 @@ We use this trick in several places in this chip.
6378

6479
### Block index counter variables
6580

66-
There are two "block index" counter variables in each row of the air named `global_block_idx` and `local_block_idx`.
67-
Both of these variables take on the same value on all 17 rows in a block.
81+
There are two "block index" counter variables in each row named `global_block_idx` and `local_block_idx`.
82+
Both of these variables take on the same value on all $R+1$ rows in a block.
6883

6984
The `global_block_idx` is the index of the block in the entire trace.
70-
The very first 17 rows in the trace will have `global_block_idx = 1` and the counter will increment by 1 between blocks.
85+
The very first block in the trace will have `global_block_idx = 1` on each row and the counter will increment by 1 between blocks.
7186
The padding rows will all have `global_block_idx = 0`.
7287
The `global_block_idx` is used in interaction constraints to constrain the value of `hash` between blocks.
7388

@@ -79,15 +94,16 @@ The `local_block_idx` is used to calculate the length of the message processed s
7994

8095
### VM air vs SubAir
8196

82-
The SHA-256 VM extension chip uses the `Sha256Air` SubAir to help constrain the SHA-256 hash.
83-
The VM extension air constrains the correctness of the SHA message padding, while the SubAir adds all other constraints related to the hash algorithm.
84-
The VM extension air also constrains memory reads and writes.
97+
The SHA-2 VM extension chip uses the `Sha2Air` SubAir to help constrain the appropriate SHA-2 hash algorithm.
98+
The SubAir is also parameterized by the specific SHA-2 variant's constants.
99+
The VM extension AIR constrains the correctness of the message padding, while the SubAir adds all other constraints related to the hash algorithm.
100+
The VM extension AIR also constrains memory reads and writes.
85101

86102
### A gotcha about padding rows
87103

88104
There are two senses of the word padding used in the context of this chip and this can be confusing.
89-
First, we use padding to refer to the extra bits added to the message that is input to the SHA-256 algorithm in order to make the input's length a multiple of 512 bits.
90-
So, we may use the term 'padding rows' to refer to round rows that correspond to the padded bits of a message (as in `Sha256VmAir::eval_padding_row`).
105+
First, we use padding to refer to the extra bits added to the message that is input to the hash algorithm in order to make the input's length a multiple of the block size.
106+
So, we may use the term 'padding rows' to refer to round rows that correspond to the padded bits of a message (as in `Sha2VmAir::eval_padding_row`).
91107
Second, the dummy rows that are added to the trace to make the trace height a power of 2 are also called padding rows (see the `is_padding_row` flag).
92108
In the SubAir, padding row probably means dummy row.
93-
In the VM air, it probably refers to SHA-256 padding.
109+
In the VM air, it probably refers to the message padding.

extensions/sha2/circuit/src/extension.rs

Lines changed: 35 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,15 @@ use openvm_rv32im_circuit::{
1313
Rv32I, Rv32IExecutor, Rv32IPeriphery, Rv32Io, Rv32IoExecutor, Rv32IoPeriphery, Rv32M,
1414
Rv32MExecutor, Rv32MPeriphery,
1515
};
16-
use openvm_sha256_transpiler::Rv32Sha256Opcode;
16+
use openvm_sha2_air::{Sha256Config, Sha384Config, Sha512Config};
17+
use openvm_sha2_transpiler::Rv32Sha2Opcode;
1718
use openvm_stark_backend::p3_field::PrimeField32;
1819
use serde::{Deserialize, Serialize};
19-
use strum::IntoEnumIterator;
2020

2121
use crate::*;
2222

2323
#[derive(Clone, Debug, VmConfig, derive_new::new, Serialize, Deserialize)]
24-
pub struct Sha256Rv32Config {
24+
pub struct Sha2Rv32Config {
2525
#[system]
2626
pub system: SystemConfig,
2727
#[extension]
@@ -31,38 +31,40 @@ pub struct Sha256Rv32Config {
3131
#[extension]
3232
pub io: Rv32Io,
3333
#[extension]
34-
pub sha256: Sha256,
34+
pub sha2: Sha2,
3535
}
3636

37-
impl Default for Sha256Rv32Config {
37+
impl Default for Sha2Rv32Config {
3838
fn default() -> Self {
3939
Self {
4040
system: SystemConfig::default().with_continuations(),
4141
rv32i: Rv32I,
4242
rv32m: Rv32M::default(),
4343
io: Rv32Io,
44-
sha256: Sha256,
44+
sha2: Sha2,
4545
}
4646
}
4747
}
4848

4949
#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize)]
50-
pub struct Sha256;
50+
pub struct Sha2;
5151

5252
#[derive(ChipUsageGetter, Chip, InstructionExecutor, From, AnyEnum)]
53-
pub enum Sha256Executor<F: PrimeField32> {
54-
Sha256(Sha256VmChip<F>),
53+
pub enum Sha2Executor<F: PrimeField32> {
54+
Sha256(Sha2VmChip<F, Sha256Config>),
55+
Sha512(Sha2VmChip<F, Sha512Config>),
56+
Sha384(Sha2VmChip<F, Sha384Config>),
5557
}
5658

5759
#[derive(From, ChipUsageGetter, Chip, AnyEnum)]
58-
pub enum Sha256Periphery<F: PrimeField32> {
60+
pub enum Sha2Periphery<F: PrimeField32> {
5961
BitwiseOperationLookup(SharedBitwiseOperationLookupChip<8>),
6062
Phantom(PhantomChip<F>),
6163
}
6264

63-
impl<F: PrimeField32> VmExtension<F> for Sha256 {
64-
type Executor = Sha256Executor<F>;
65-
type Periphery = Sha256Periphery<F>;
65+
impl<F: PrimeField32> VmExtension<F> for Sha2 {
66+
type Executor = Sha2Executor<F>;
67+
type Periphery = Sha2Periphery<F>;
6668

6769
fn build(
6870
&self,
@@ -81,18 +83,32 @@ impl<F: PrimeField32> VmExtension<F> for Sha256 {
8183
chip
8284
};
8385

84-
let sha256_chip = Sha256VmChip::new(
86+
let sha256_chip = Sha2VmChip::<F, Sha256Config>::new(
87+
builder.system_port(),
88+
builder.system_config().memory_config.pointer_max_bits,
89+
bitwise_lu_chip.clone(),
90+
builder.new_bus_idx(),
91+
builder.system_base().offline_memory(),
92+
);
93+
inventory.add_executor(sha256_chip, vec![Rv32Sha2Opcode::SHA256.global_opcode()])?;
94+
95+
let sha512_chip = Sha2VmChip::<F, Sha512Config>::new(
96+
builder.system_port(),
97+
builder.system_config().memory_config.pointer_max_bits,
98+
bitwise_lu_chip.clone(),
99+
builder.new_bus_idx(),
100+
builder.system_base().offline_memory(),
101+
);
102+
inventory.add_executor(sha512_chip, vec![Rv32Sha2Opcode::SHA512.global_opcode()])?;
103+
104+
let sha384_chip = Sha2VmChip::<F, Sha384Config>::new(
85105
builder.system_port(),
86106
builder.system_config().memory_config.pointer_max_bits,
87107
bitwise_lu_chip,
88108
builder.new_bus_idx(),
89-
Rv32Sha256Opcode::CLASS_OFFSET,
90109
builder.system_base().offline_memory(),
91110
);
92-
inventory.add_executor(
93-
sha256_chip,
94-
Rv32Sha256Opcode::iter().map(|x| x.global_opcode()),
95-
)?;
111+
inventory.add_executor(sha384_chip, vec![Rv32Sha2Opcode::SHA384.global_opcode()])?;
96112

97113
Ok(inventory)
98114
}

extensions/sha2/circuit/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
mod sha256_chip;
2-
pub use sha256_chip::*;
1+
mod sha2_chip;
2+
pub use sha2_chip::*;
33

44
mod extension;
55
pub use extension::*;

0 commit comments

Comments
 (0)