Skip to content

Commit 613acca

Browse files
authored
fix: allow to use blake3 to hash PV (#2236)
1 parent 0f00b16 commit 613acca

File tree

15 files changed

+329
-87
lines changed

15 files changed

+329
-87
lines changed

.github/workflows/pr.yml

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -152,15 +152,6 @@ jobs:
152152
RUSTFLAGS: -Copt-level=3 -Coverflow-checks=y -Cdebuginfo=0 -C target-cpu=native
153153
RUST_BACKTRACE: 1
154154

155-
- name: Run cargo test with Blake3 PV hashing
156-
uses: actions-rs/cargo@v1
157-
with:
158-
command: test
159-
args: --release --package sp1-verifier -F blake3
160-
env:
161-
RUSTFLAGS: -Copt-level=3 -Coverflow-checks=y -Cdebuginfo=0 -C target-cpu=native
162-
RUST_BACKTRACE: 1
163-
164155
lint:
165156
name: Formatting & Clippy
166157
runs-on: [runs-on, runner=16cpu-linux-x64, disk=large, "run-id=${{ github.run_id }}"]

Cargo.lock

Lines changed: 45 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ serde = "1.0.204"
103103
serde_json = "1.0.132"
104104
tracing = "0.1.40"
105105
tracing-subscriber = "0.3.18"
106-
blake3 = "1.6.1"
106+
blake3 = { version = "1.6.1", default-features = false }
107107

108108
[workspace.metadata.typos]
109109
default.extend-ignore-re = [

crates/primitives/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ categories = { workspace = true }
1111

1212
[dependencies]
1313
bincode = "1.3.3"
14+
blake3 = { workspace = true }
15+
cfg-if = "1.0.0"
1416
hex = "0.4.3"
1517
lazy_static = "1.5.0"
1618
num-bigint = { version = "0.4.6", default-features = false }

crates/primitives/src/io.rs

Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -52,24 +52,34 @@ impl SP1PublicValues {
5252
self.buffer.write_slice(slice);
5353
}
5454

55-
/// Hash the public values.
55+
/// Hash the public values using SHA256.
5656
pub fn hash(&self) -> Vec<u8> {
57-
let mut hasher = Sha256::new();
58-
hasher.update(self.buffer.data.as_slice());
59-
hasher.finalize().to_vec()
57+
sha256_hash(self.buffer.data.as_slice())
6058
}
6159

62-
/// Hash the public values, mask the top 3 bits and return a BigUint. Matches the implementation
63-
/// of `hashPublicValues` in the Solidity verifier.
60+
/// Hash the public values using Blake3.
61+
pub fn blake3_hash(&self) -> Vec<u8> {
62+
blake3_hash(self.buffer.data.as_slice())
63+
}
64+
65+
/// Hash the public values using SHA256, mask the top 3 bits and return a BigUint.
66+
/// Matches the implementation of `hashPublicValues` in the Solidity verifier.
6467
///
6568
/// ```solidity
6669
/// sha256(publicValues) & bytes32(uint256((1 << 253) - 1));
6770
/// ```
6871
pub fn hash_bn254(&self) -> BigUint {
72+
self.hash_bn254_with_fn(sha256_hash)
73+
}
74+
75+
/// Hash the public values using the provided `hasher` function, mask the top 3 bits and
76+
/// return a BigUint.
77+
pub fn hash_bn254_with_fn<F>(&self, hasher: F) -> BigUint
78+
where
79+
F: Fn(&[u8]) -> Vec<u8>,
80+
{
6981
// Hash the public values.
70-
let mut hasher = Sha256::new();
71-
hasher.update(self.buffer.data.as_slice());
72-
let mut hash = hasher.finalize();
82+
let mut hash = hasher(self.buffer.data.as_slice());
7383

7484
// Mask the top 3 bits.
7585
hash[0] &= 0b00011111;
@@ -85,6 +95,18 @@ impl AsRef<[u8]> for SP1PublicValues {
8595
}
8696
}
8797

98+
/// Hash the input using SHA256.
99+
pub fn sha256_hash(input: &[u8]) -> Vec<u8> {
100+
let mut hasher = Sha256::new();
101+
hasher.update(input);
102+
hasher.finalize().to_vec()
103+
}
104+
105+
/// Hash the input using Blake3.
106+
pub fn blake3_hash(input: &[u8]) -> Vec<u8> {
107+
blake3::hash(input).as_bytes().to_vec()
108+
}
109+
88110
#[cfg(test)]
89111
mod tests {
90112
use super::*;
@@ -103,4 +125,19 @@ mod tests {
103125

104126
assert_eq!(hash, expected_hash_biguint);
105127
}
128+
129+
#[test]
130+
fn test_hash_public_values_blake3() {
131+
let test_hex = "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef";
132+
let test_bytes = hex::decode(test_hex).unwrap();
133+
134+
let mut public_values = SP1PublicValues::new();
135+
public_values.write_slice(&test_bytes);
136+
let hash = public_values.hash_bn254_with_fn(blake3_hash);
137+
138+
let expected_hash = "1639647ab9e42519f0cbdcf343033482b018c0c1ed48f8367f32381c60913447";
139+
let expected_hash_biguint = BigUint::from_bytes_be(&hex::decode(expected_hash).unwrap());
140+
141+
assert_eq!(hash, expected_hash_biguint);
142+
}
106143
}

crates/prover/src/verify.rs

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ use p3_baby_bear::BabyBear;
66
use p3_field::{AbstractField, PrimeField};
77
use sp1_core_executor::{subproof::SubproofVerifier, SP1ReduceProof};
88
use sp1_core_machine::cpu::MAX_CPU_LOG_DEGREE;
9-
use sp1_primitives::{consts::WORD_SIZE, io::SP1PublicValues};
9+
use sp1_primitives::{
10+
consts::WORD_SIZE,
11+
io::{blake3_hash, SP1PublicValues},
12+
};
1013

1114
use sp1_recursion_circuit::machine::RootPublicValues;
1215
use sp1_recursion_core::{air::RecursionPublicValues, stark::BabyBearPoseidon2Outer};
@@ -448,10 +451,7 @@ pub fn verify_plonk_bn254_public_inputs(
448451
return Err(PlonkVerificationError::InvalidVerificationKey.into());
449452
}
450453

451-
let public_values_hash = public_values.hash_bn254();
452-
if public_values_hash != expected_public_values_hash {
453-
return Err(PlonkVerificationError::InvalidPublicValues.into());
454-
}
454+
verify_public_values(public_values, expected_public_values_hash)?;
455455

456456
Ok(())
457457
}
@@ -471,9 +471,32 @@ pub fn verify_groth16_bn254_public_inputs(
471471
return Err(Groth16VerificationError::InvalidVerificationKey.into());
472472
}
473473

474-
let public_values_hash = public_values.hash_bn254();
475-
if public_values_hash != expected_public_values_hash {
476-
return Err(Groth16VerificationError::InvalidPublicValues.into());
474+
verify_public_values(public_values, expected_public_values_hash)?;
475+
476+
Ok(())
477+
}
478+
479+
/// In SP1, a proof's public values can either be hashed with SHA2 or Blake3. In SP1 V4, there is no
480+
/// metadata attached to the proof about which hasher function was used for public values hashing.
481+
/// Instead, when verifying the proof, the public values are hashed with SHA2 and Blake3, and
482+
/// if either matches the `expected_public_values_hash`, the verification is successful.
483+
///
484+
/// The security for this verification in SP1 V4 derives from the fact that both SHA2 and Blake3 are
485+
/// designed to be collision resistant. It is computationally infeasible to find an input i1 for
486+
/// SHA256 and an input i2 for Blake3 that the same hash value. Doing so would require breaking both
487+
/// algorithms simultaneously.
488+
fn verify_public_values(
489+
public_values: &SP1PublicValues,
490+
expected_public_values_hash: BigUint,
491+
) -> Result<()> {
492+
// First, check if the public values are hashed with SHA256. If that fails, attempt hashing with
493+
// Blake3. If neither match, return an error.
494+
let sha256_public_values_hash = public_values.hash_bn254();
495+
if sha256_public_values_hash != expected_public_values_hash {
496+
let blake3_public_values_hash = public_values.hash_bn254_with_fn(blake3_hash);
497+
if blake3_public_values_hash != expected_public_values_hash {
498+
return Err(Groth16VerificationError::InvalidPublicValues.into());
499+
}
477500
}
478501

479502
Ok(())

crates/sdk/src/prover.rs

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,15 @@ pub enum SP1VerificationError {
8585
Other(anyhow::Error),
8686
}
8787

88+
/// In SP1, a proof's public values can either be hashed with SHA2 or Blake3. In SP1 V4, there is no
89+
/// metadata attached to the proof about which hasher function was used for public values hashing.
90+
/// Instead, when verifying the proof, the public values are hashed with SHA2 and Blake3, and
91+
/// if either matches the `expected_public_values_hash`, the verification is successful.
92+
///
93+
/// The security for this verification in SP1 V4 derives from the fact that both SHA2 and Blake3 are
94+
/// designed to be collision resistant. It is computationally infeasible to find an input i1 for
95+
/// SHA256 and an input i2 for Blake3 that the same hash value. Doing so would require breaking both
96+
/// algorithms simultaneously.
8897
pub(crate) fn verify_proof<C: SP1ProverComponents>(
8998
prover: &SP1Prover<C>,
9099
version: &str,
@@ -109,10 +118,12 @@ pub(crate) fn verify_proof<C: SP1ProverComponents>(
109118
.collect_vec();
110119

111120
// Make sure the committed value digest matches the public values hash.
112-
for (a, b) in committed_value_digest_bytes.iter().zip_eq(bundle.public_values.hash()) {
113-
if *a != b {
114-
return Err(SP1VerificationError::InvalidPublicValues);
115-
}
121+
// It is computationally infeasible to find two distinct inputs, one processed with
122+
// SHA256 and the other with Blake3, that yield the same hash value.
123+
if committed_value_digest_bytes != bundle.public_values.hash() &&
124+
committed_value_digest_bytes != bundle.public_values.blake3_hash()
125+
{
126+
return Err(SP1VerificationError::InvalidPublicValues);
116127
}
117128

118129
// Verify the core proof.
@@ -132,10 +143,12 @@ pub(crate) fn verify_proof<C: SP1ProverComponents>(
132143
.collect_vec();
133144

134145
// Make sure the committed value digest matches the public values hash.
135-
for (a, b) in committed_value_digest_bytes.iter().zip_eq(bundle.public_values.hash()) {
136-
if *a != b {
137-
return Err(SP1VerificationError::InvalidPublicValues);
138-
}
146+
// It is computationally infeasible to find two distinct inputs, one processed with
147+
// SHA256 and the other with Blake3, that yield the same hash value.
148+
if committed_value_digest_bytes != bundle.public_values.hash() &&
149+
committed_value_digest_bytes != bundle.public_values.blake3_hash()
150+
{
151+
return Err(SP1VerificationError::InvalidPublicValues);
139152
}
140153

141154
prover.verify_compressed(proof, vkey).map_err(SP1VerificationError::Recursion)

crates/test-artifacts/programs/Cargo.lock

Lines changed: 21 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/test-artifacts/programs/fibonacci-blake3/Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/verifier/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ sha2 = { version = "0.10.8", default-features = false }
1515
thiserror = { version = "2", default-features = false }
1616
hex = { version = "0.4.3", default-features = false, features = ["alloc"] }
1717
lazy_static = { version = "1.5.0", default-features = false }
18-
blake3 = { workspace = true, optional = true }
18+
blake3 = { workspace = true }
1919
cfg-if = "1.0.0"
2020

2121
# arkworks
@@ -32,12 +32,12 @@ num-bigint = "0.4.6"
3232
num-traits = "0.2.19"
3333
cfg-if = "1.0.0"
3434
serial_test = "3.2.0"
35+
rstest = "0.25.0"
3536

3637
[features]
3738
default = ["std"]
3839
std = ["thiserror/std"]
3940
ark = ["ark-bn254", "ark-serialize", "ark-ff", "ark-groth16", "ark-ec"]
40-
blake3 = ["dep:blake3"]
4141

4242
[lints]
4343
workspace = true

0 commit comments

Comments
 (0)