Skip to content

Commit 96b3e5c

Browse files
Update SHA-256 VM chip to support SHA-512 and SHA-384
1 parent a3b73ca commit 96b3e5c

File tree

20 files changed

+1349
-606
lines changed

20 files changed

+1349
-606
lines changed

Cargo.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ openvm-stark-sdk = { git = "https://github.com/openvm-org/stark-backend.git", ta
117117
openvm-sdk = { path = "crates/sdk", default-features = false }
118118
openvm-mod-circuit-builder = { path = "crates/circuits/mod-builder", default-features = false }
119119
openvm-poseidon2-air = { path = "crates/circuits/poseidon2-air", default-features = false }
120-
openvm-sha256-air = { path = "crates/circuits/sha256-air", default-features = false }
120+
openvm-sha2-air = { path = "crates/circuits/sha2-air", default-features = false }
121121
openvm-circuit-primitives = { path = "crates/circuits/primitives", default-features = false }
122122
openvm-circuit-primitives-derive = { path = "crates/circuits/primitives/derive", default-features = false }
123123
openvm = { path = "crates/toolchain/openvm", default-features = false }
@@ -147,9 +147,9 @@ openvm-native-transpiler = { path = "extensions/native/transpiler", default-feat
147147
openvm-keccak256-circuit = { path = "extensions/keccak256/circuit", default-features = false }
148148
openvm-keccak256-transpiler = { path = "extensions/keccak256/transpiler", default-features = false }
149149
openvm-keccak256-guest = { path = "extensions/keccak256/guest", default-features = false }
150-
openvm-sha256-circuit = { path = "extensions/sha256/circuit", default-features = false }
151-
openvm-sha256-transpiler = { path = "extensions/sha256/transpiler", default-features = false }
152-
openvm-sha256-guest = { path = "extensions/sha256/guest", default-features = false }
150+
openvm-sha2-circuit = { path = "extensions/sha2/circuit", default-features = false }
151+
openvm-sha2-transpiler = { path = "extensions/sha2/transpiler", default-features = false }
152+
openvm-sha2-guest = { path = "extensions/sha2/guest", default-features = false }
153153
openvm-bigint-circuit = { path = "extensions/bigint/circuit", default-features = false }
154154
openvm-bigint-transpiler = { path = "extensions/bigint/transpiler", default-features = false }
155155
openvm-bigint-guest = { path = "extensions/bigint/guest", default-features = false }

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
@@ -16,15 +16,15 @@ use openvm_rv32im_circuit::{
1616
Rv32I, Rv32IExecutor, Rv32IPeriphery, Rv32Io, Rv32IoExecutor, Rv32IoPeriphery, Rv32M,
1717
Rv32MExecutor, Rv32MPeriphery,
1818
};
19-
use openvm_sha256_transpiler::Rv32Sha256Opcode;
19+
use openvm_sha2_air::{Sha256Config, Sha384Config, Sha512Config};
20+
use openvm_sha2_transpiler::Rv32Sha2Opcode;
2021
use openvm_stark_backend::p3_field::PrimeField32;
2122
use serde::{Deserialize, Serialize};
22-
use strum::IntoEnumIterator;
2323

2424
use crate::*;
2525

2626
#[derive(Clone, Debug, VmConfig, derive_new::new, Serialize, Deserialize)]
27-
pub struct Sha256Rv32Config {
27+
pub struct Sha2Rv32Config {
2828
#[system]
2929
pub system: SystemConfig,
3030
#[extension]
@@ -34,17 +34,17 @@ pub struct Sha256Rv32Config {
3434
#[extension]
3535
pub io: Rv32Io,
3636
#[extension]
37-
pub sha256: Sha256,
37+
pub sha2: Sha2,
3838
}
3939

40-
impl Default for Sha256Rv32Config {
40+
impl Default for Sha2Rv32Config {
4141
fn default() -> Self {
4242
Self {
4343
system: SystemConfig::default().with_continuations(),
4444
rv32i: Rv32I,
4545
rv32m: Rv32M::default(),
4646
io: Rv32Io,
47-
sha256: Sha256,
47+
sha2: Sha2,
4848
}
4949
}
5050
}
@@ -53,22 +53,24 @@ impl Default for Sha256Rv32Config {
5353
impl InitFileGenerator for Sha256Rv32Config {}
5454

5555
#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize)]
56-
pub struct Sha256;
56+
pub struct Sha2;
5757

5858
#[derive(ChipUsageGetter, Chip, InstructionExecutor, From, AnyEnum)]
59-
pub enum Sha256Executor<F: PrimeField32> {
60-
Sha256(Sha256VmChip<F>),
59+
pub enum Sha2Executor<F: PrimeField32> {
60+
Sha256(Sha2VmChip<F, Sha256Config>),
61+
Sha512(Sha2VmChip<F, Sha512Config>),
62+
Sha384(Sha2VmChip<F, Sha384Config>),
6163
}
6264

6365
#[derive(From, ChipUsageGetter, Chip, AnyEnum)]
64-
pub enum Sha256Periphery<F: PrimeField32> {
66+
pub enum Sha2Periphery<F: PrimeField32> {
6567
BitwiseOperationLookup(SharedBitwiseOperationLookupChip<8>),
6668
Phantom(PhantomChip<F>),
6769
}
6870

69-
impl<F: PrimeField32> VmExtension<F> for Sha256 {
70-
type Executor = Sha256Executor<F>;
71-
type Periphery = Sha256Periphery<F>;
71+
impl<F: PrimeField32> VmExtension<F> for Sha2 {
72+
type Executor = Sha2Executor<F>;
73+
type Periphery = Sha2Periphery<F>;
7274

7375
fn build(
7476
&self,
@@ -87,18 +89,32 @@ impl<F: PrimeField32> VmExtension<F> for Sha256 {
8789
chip
8890
};
8991

90-
let sha256_chip = Sha256VmChip::new(
92+
let sha256_chip = Sha2VmChip::<F, Sha256Config>::new(
93+
builder.system_port(),
94+
builder.system_config().memory_config.pointer_max_bits,
95+
bitwise_lu_chip.clone(),
96+
builder.new_bus_idx(),
97+
builder.system_base().offline_memory(),
98+
);
99+
inventory.add_executor(sha256_chip, vec![Rv32Sha2Opcode::SHA256.global_opcode()])?;
100+
101+
let sha512_chip = Sha2VmChip::<F, Sha512Config>::new(
102+
builder.system_port(),
103+
builder.system_config().memory_config.pointer_max_bits,
104+
bitwise_lu_chip.clone(),
105+
builder.new_bus_idx(),
106+
builder.system_base().offline_memory(),
107+
);
108+
inventory.add_executor(sha512_chip, vec![Rv32Sha2Opcode::SHA512.global_opcode()])?;
109+
110+
let sha384_chip = Sha2VmChip::<F, Sha384Config>::new(
91111
builder.system_port(),
92112
builder.system_config().memory_config.pointer_max_bits,
93113
bitwise_lu_chip,
94114
builder.new_bus_idx(),
95-
Rv32Sha256Opcode::CLASS_OFFSET,
96115
builder.system_base().offline_memory(),
97116
);
98-
inventory.add_executor(
99-
sha256_chip,
100-
Rv32Sha256Opcode::iter().map(|x| x.global_opcode()),
101-
)?;
117+
inventory.add_executor(sha384_chip, vec![Rv32Sha2Opcode::SHA384.global_opcode()])?;
102118

103119
Ok(inventory)
104120
}

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)