Skip to content

Commit 875ed3e

Browse files
authored
Merge pull request #8 from yugocabrio/feat/hash-trait
feat: transcript hash trait
2 parents 521b6bd + f43ad2b commit 875ed3e

6 files changed

Lines changed: 95 additions & 31 deletions

File tree

.github/workflows/ci.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@ jobs:
4949
- name: Run tests (std)
5050
run: cargo test --features std --verbose
5151

52-
- name: Run tests (soroban-bn254-precompile)
53-
run: cargo test --features soroban-bn254-precompile --verbose
52+
- name: Run tests (soroban-precompile)
53+
run: cargo test --features soroban-precompile --verbose
5454

55-
- name: Run tests (std + soroban-bn254-precompile)
56-
run: cargo test --features "std soroban-bn254-precompile" --verbose
55+
- name: Run tests (std + soroban-precompile)
56+
run: cargo test --features "std soroban-precompile" --verbose

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ once_cell = { version = "1.19", default-features = false, features = ["alloc", "
2929
[features]
3030
default = ["alloc", "serde", "serde_json"]
3131
# Soroban BN254 precompile integration switch
32-
soroban-bn254-precompile = []
32+
soroban-precompile = []
3333
std = [
3434
"sha3/std",
3535
"ark-ff/std",

README.md

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -60,13 +60,13 @@ Notes:
6060
- `std`: enables I/O and serde for convenient loading.
6161
- `trace`: prints detailed verifier internals (for debugging); off by default.
6262
- `alloc` (default): required for `no_std` collections.
63-
- `soroban-bn254-precompile`: routes MSM + pairing calls through a backend facade intended for a Soroban BN254 precompile.
63+
- `soroban-precompile`: routes MSM + pairing calls through a backend facade intended for a Soroban host precompile.
6464
For now, it falls back to Arkworks so behavior is unchanged, but gives a stable call site to switch to host calls later.
6565

66-
### Soroban BN254 precompile
67-
- Purpose: Provide a seam to swap the EC hot paths (G1 MSM and pairing) to a Soroban BN254 precompile.
68-
- Enable: `--features soroban-bn254-precompile`. If no backend is registered, it transparently falls back to the Arkworks implementation.
69-
- Scope: Public API remains unchanged (`ec::g1_msm`, `ec::pairing_check`). Register a backend once at startup.
66+
### Soroban precompile
67+
- Purpose: Provide seams to swap the EC hot paths (G1 MSM and pairing) and the transcript hash to Soroban host precompiles.
68+
- Enable: `--features soroban-precompile`. If no backend is registered, it transparently falls back to the Arkworks/Keccak implementations.
69+
- Scope: Public API remains unchanged (`ec::g1_msm`, `ec::pairing_check`, `hash::hash32`). Register backends once during contract initialization.
7070

7171
Backend contract
7272
- Trait: `ec::Bn254Ops` (intended to be `Send + Sync`)
@@ -76,14 +76,19 @@ Backend contract
7676
- Must verify `e(p0, rhs_g2) * e(p1, lhs_g2) == 1` using the fixed G2 constants defined in `ec.rs`.
7777

7878
```
79-
#[cfg(feature = "soroban-bn254-precompile")]
79+
#[cfg(feature = "soroban-precompile")]
8080
{
81-
use ultrahonk_rust_verifier::{ec::{self, Bn254Ops}, types::G1Point, field::Fr};
81+
use ultrahonk_rust_verifier::{
82+
ec::{self, Bn254Ops},
83+
hash::{self, HashOps},
84+
types::G1Point,
85+
field::Fr,
86+
};
8287
use ark_bn254::G1Affine;
8388
8489
// Example backend that calls the Soroban host precompile (pseudo-code)
85-
struct SorobanOps { /* env: soroban_sdk::Env, ... */ }
86-
impl Bn254Ops for SorobanOps {
90+
struct SorobanEcOps { /* env: soroban_sdk::Env, ... */ }
91+
impl Bn254Ops for SorobanEcOps {
8792
fn g1_msm(&self, coms: &[G1Point], scalars: &[Fr]) -> Result<G1Affine, String> {
8893
// host_msm(env, coms, scalars).map_err(|e| e.to_string())
8994
unimplemented!("call Soroban MSM precompile")
@@ -93,8 +98,16 @@ Backend contract
9398
unimplemented!("call Soroban pairing precompile")
9499
}
95100
}
101+
struct SorobanHashOps { /* env, ... */ }
102+
impl HashOps for SorobanHashOps {
103+
fn hash(&self, data: &[u8]) -> [u8; 32] {
104+
// host_poseidon(env, data)
105+
unimplemented!("call Soroban hash precompile")
106+
}
107+
}
96108
97-
ec::set_soroban_bn254_backend(Box::new(SorobanOps { /* env, ... */ }));
109+
ec::set_soroban_bn254_backend(Box::new(SorobanEcOps { /* env, ... */ }));
110+
hash::set_soroban_hash_backend(Box::new(SorobanHashOps { /* env, ... */ }));
98111
}
99112
```
100113

src/ec.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use ark_ec::{pairing::Pairing, CurveGroup, PrimeGroup};
1212
use ark_ff::BigInteger;
1313
use ark_ff::{One, PrimeField, Zero};
1414

15-
#[cfg(feature = "soroban-bn254-precompile")]
15+
#[cfg(feature = "soroban-precompile")]
1616
use once_cell::race::OnceBox;
1717

1818
/// Trait for BN254 operations used by the verifier hot paths.
@@ -158,15 +158,15 @@ impl Bn254Ops for ArkworksOps {
158158

159159
static ARKWORKS: ArkworksOps = ArkworksOps;
160160

161-
#[cfg(feature = "soroban-bn254-precompile")]
161+
#[cfg(feature = "soroban-precompile")]
162162
struct BackendHolder(pub Box<dyn Bn254Ops + Send + Sync>);
163163

164-
#[cfg(feature = "soroban-bn254-precompile")]
164+
#[cfg(feature = "soroban-precompile")]
165165
static BACKEND: OnceBox<BackendHolder> = OnceBox::new();
166166

167167
#[inline(always)]
168168
fn backend() -> &'static dyn Bn254Ops {
169-
#[cfg(feature = "soroban-bn254-precompile")]
169+
#[cfg(feature = "soroban-precompile")]
170170
{
171171
if let Some(b) = BACKEND.get() {
172172
return &*b.0;
@@ -199,13 +199,13 @@ pub mod helpers {
199199
}
200200
}
201201

202-
#[cfg(feature = "soroban-bn254-precompile")]
202+
#[cfg(feature = "soroban-precompile")]
203203
/// Register a custom BN254 backend (Soroban BN254 precompile bridge).
204204
pub fn set_backend(ops: Box<dyn Bn254Ops + Send + Sync>) {
205205
let _ = BACKEND.set(Box::new(BackendHolder(ops)));
206206
}
207207

208-
#[cfg(feature = "soroban-bn254-precompile")]
208+
#[cfg(feature = "soroban-precompile")]
209209
#[inline(always)]
210210
pub fn set_soroban_bn254_backend(ops: Box<dyn Bn254Ops + Send + Sync>) {
211211
set_backend(ops)

src/hash.rs

Lines changed: 59 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,62 @@
11
use sha3::{Digest, Keccak256};
22

3-
/// Compute Keccak-256 hash of the given data, returning a 32-byte array.
4-
pub fn keccak256(data: &[u8]) -> [u8; 32] {
5-
let mut hasher = Keccak256::new();
6-
hasher.update(data);
7-
let result = hasher.finalize();
8-
let mut out = [0u8; 32];
9-
out.copy_from_slice(&result);
10-
out
3+
#[cfg(all(feature = "soroban-precompile", not(feature = "std")))]
4+
use alloc::boxed::Box;
5+
#[cfg(all(feature = "soroban-precompile", feature = "std"))]
6+
use std::boxed::Box;
7+
8+
#[cfg(feature = "soroban-precompile")]
9+
use once_cell::race::OnceBox;
10+
11+
/// Transcript hash backend abstraction trait.
12+
pub trait HashOps: Send + Sync {
13+
fn hash(&self, data: &[u8]) -> [u8; 32];
14+
}
15+
16+
pub struct KeccakBackend;
17+
18+
impl HashOps for KeccakBackend {
19+
#[inline(always)]
20+
fn hash(&self, data: &[u8]) -> [u8; 32] {
21+
let mut hasher = Keccak256::new();
22+
hasher.update(data);
23+
let result = hasher.finalize();
24+
let mut out = [0u8; 32];
25+
out.copy_from_slice(&result);
26+
out
27+
}
28+
}
29+
30+
static KECCAK_BACKEND: KeccakBackend = KeccakBackend;
31+
32+
#[cfg(feature = "soroban-precompile")]
33+
static BACKEND: OnceBox<Box<dyn HashOps>> = OnceBox::new();
34+
35+
#[inline(always)]
36+
fn backend() -> &'static dyn HashOps {
37+
#[cfg(feature = "soroban-precompile")]
38+
{
39+
if let Some(b) = BACKEND.get() {
40+
return &**b;
41+
}
42+
}
43+
&KECCAK_BACKEND
44+
}
45+
46+
/// Compute the active backend hash of the given data
47+
#[inline(always)]
48+
pub fn hash32(data: &[u8]) -> [u8; 32] {
49+
backend().hash(data)
50+
}
51+
52+
#[cfg(feature = "soroban-precompile")]
53+
/// Register a custom hash backend (Soroban precompile bridge).
54+
pub fn set_backend(ops: Box<dyn HashOps>) {
55+
let _ = BACKEND.set(Box::new(ops));
56+
}
57+
58+
#[cfg(feature = "soroban-precompile")]
59+
#[inline(always)]
60+
pub fn set_soroban_hash_backend(ops: Box<dyn HashOps>) {
61+
set_backend(ops)
1162
}

src/transcript.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
use crate::trace;
44
use crate::{
55
field::Fr,
6-
hash::keccak256,
6+
hash::hash32,
77
types::{Proof, RelationParameters, Transcript, CONST_PROOF_SIZE_LOG_N},
88
};
99
use ark_bn254::G1Affine;
@@ -33,7 +33,7 @@ fn split(fr: Fr) -> (Fr, Fr) {
3333

3434
#[inline(always)]
3535
fn hash_to_fr(bytes: &[u8]) -> Fr {
36-
Fr::from_bytes(&keccak256(bytes))
36+
Fr::from_bytes(&hash32(bytes))
3737
}
3838

3939
fn u64_to_be32(x: u64) -> [u8; 32] {

0 commit comments

Comments
 (0)