Skip to content

Commit 2a10dff

Browse files
authored
Merge pull request #2985 from o1-labs/martin/saffron-proof-of-storage-clean-e2e-part-1
Add storage-proof command to cli and e2e
2 parents 33482cb + e99a6c9 commit 2a10dff

File tree

6 files changed

+150
-46
lines changed

6 files changed

+150
-46
lines changed

saffron/src/blob.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use poly_commitment::{commitment::CommitmentCurve, ipa::SRS, PolyComm, SRS as _}
77
use rayon::prelude::*;
88
use serde::{Deserialize, Serialize};
99
use serde_with::serde_as;
10-
use tracing::{debug, instrument};
10+
use tracing::{debug, debug_span, instrument};
1111

1212
// A FieldBlob<F> represents the encoding of a Vec<u8> as a list of polynomials over F,
1313
// where F is a prime field. The polyonomials are represented in the monomial basis.
@@ -43,10 +43,12 @@ impl<G: CommitmentCurve> FieldBlob<G> {
4343
let field_elements = encode_for_domain(&domain, bytes);
4444
let domain_size = domain.size();
4545

46-
let data: Vec<DensePolynomial<G::ScalarField>> = field_elements
47-
.par_iter()
48-
.map(|chunk| Evaluations::from_vec_and_domain(chunk.to_vec(), domain).interpolate())
49-
.collect();
46+
let data: Vec<DensePolynomial<G::ScalarField>> = debug_span!("fft").in_scope(|| {
47+
field_elements
48+
.par_iter()
49+
.map(|chunk| Evaluations::from_vec_and_domain(chunk.to_vec(), domain).interpolate())
50+
.collect()
51+
});
5052

5153
let commitments = commit_to_blob_data(srs, &data);
5254

saffron/src/cli.rs

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,23 @@
11
use clap::{arg, Parser};
2+
use std::{fmt::Display, str::FromStr};
3+
4+
#[derive(Debug, Clone)]
5+
pub struct HexString(pub Vec<u8>);
6+
7+
impl FromStr for HexString {
8+
type Err = hex::FromHexError;
9+
10+
fn from_str(s: &str) -> Result<Self, Self::Err> {
11+
let stripped = s.strip_prefix("0x").unwrap_or(s);
12+
Ok(HexString(hex::decode(stripped)?))
13+
}
14+
}
15+
16+
impl Display for HexString {
17+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
18+
write!(f, "0x{}", hex::encode(&self.0))
19+
}
20+
}
221

322
#[derive(Parser)]
423
pub struct EncodeFileArgs {
@@ -16,8 +35,12 @@ pub struct EncodeFileArgs {
1635
#[arg(long = "srs-filepath", value_name = "SRS_FILEPATH")]
1736
pub srs_cache: Option<String>,
1837

19-
#[arg(long = "assert-commitment", value_name = "COMMITMENT")]
20-
pub assert_commitment: Option<String>,
38+
#[arg(
39+
long = "assert-commitment",
40+
value_name = "COMMITMENT",
41+
help = "hash of commitments (hex encoded)"
42+
)]
43+
pub assert_commitment: Option<HexString>,
2144
}
2245

2346
#[derive(Parser)]
@@ -46,6 +69,27 @@ pub struct ComputeCommitmentArgs {
4669
pub srs_cache: Option<String>,
4770
}
4871

72+
#[derive(Parser)]
73+
pub struct StorageProofArgs {
74+
#[arg(
75+
long,
76+
short = 'i',
77+
value_name = "FILE",
78+
help = "input file (encoded as field elements)"
79+
)]
80+
pub input: String,
81+
82+
#[arg(long = "srs-filepath", value_name = "SRS_FILEPATH")]
83+
pub srs_cache: Option<String>,
84+
85+
#[arg(
86+
long = "challenge",
87+
value_name = "CHALLENGE",
88+
help = "challenge (hex encoded"
89+
)]
90+
pub challenge: HexString,
91+
}
92+
4993
#[derive(Parser)]
5094
#[command(
5195
name = "saffron",
@@ -59,4 +103,6 @@ pub enum Commands {
59103
Decode(DecodeFileArgs),
60104
#[command(name = "compute-commitment")]
61105
ComputeCommitment(ComputeCommitmentArgs),
106+
#[command(name = "storage-proof")]
107+
StorageProof(StorageProofArgs),
62108
}

saffron/src/main.rs

Lines changed: 54 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,27 @@
11
use anyhow::Result;
22
use ark_poly::{EvaluationDomain, Radix2EvaluationDomain};
33
use clap::Parser;
4-
use mina_curves::pasta::{Fp, Vesta};
5-
use poly_commitment::{ipa::SRS, SRS as _};
6-
use saffron::{blob::FieldBlob, cli, commitment, env, utils};
4+
use kimchi::groupmap::GroupMap;
5+
use mina_curves::pasta::{Fp, Vesta, VestaParameters};
6+
use mina_poseidon::{constants::PlonkSpongeConstantsKimchi, sponge::DefaultFqSponge};
7+
use poly_commitment::{commitment::CommitmentCurve, ipa::SRS, SRS as _};
8+
use rand::rngs::OsRng;
9+
use saffron::{
10+
blob::FieldBlob,
11+
cli::{self, HexString},
12+
commitment, env, proof, utils,
13+
};
714
use sha3::{Digest, Sha3_256};
815
use std::{
916
fs::File,
1017
io::{Read, Write},
1118
};
12-
use tracing::debug;
19+
use tracing::{debug, debug_span};
1320

1421
pub const DEFAULT_SRS_SIZE: usize = 1 << 16;
1522

1623
fn get_srs(cache: Option<String>) -> (SRS<Vesta>, Radix2EvaluationDomain<Fp>) {
17-
match cache {
24+
let res = match cache {
1825
Some(cache) => {
1926
let srs = env::get_srs_from_cache(cache);
2027
let domain_fp = Radix2EvaluationDomain::new(srs.size()).unwrap();
@@ -28,11 +35,16 @@ fn get_srs(cache: Option<String>) -> (SRS<Vesta>, Radix2EvaluationDomain<Fp>) {
2835
let domain_size = DEFAULT_SRS_SIZE;
2936
let srs = SRS::create(domain_size);
3037
let domain_fp = Radix2EvaluationDomain::new(srs.size()).unwrap();
31-
srs.get_lagrange_basis(domain_fp);
3238
debug!("SRS created successfully");
3339
(srs, domain_fp)
3440
}
35-
}
41+
};
42+
43+
debug_span!("get_lagrange_basis", basis_size = res.0.size()).in_scope(|| {
44+
res.0.get_lagrange_basis(res.1);
45+
});
46+
47+
res
3648
}
3749

3850
fn decode_file(args: cli::DecodeFileArgs) -> Result<()> {
@@ -66,12 +78,12 @@ fn encode_file(args: cli::EncodeFileArgs) -> Result<()> {
6678
.into_iter()
6779
.for_each(|asserted_commitment| {
6880
let bytes = rmp_serde::to_vec(&blob.commitments).unwrap();
69-
let hash = Sha3_256::new().chain_update(bytes).finalize();
70-
let computed_commitment = hex::encode(hash);
71-
if asserted_commitment != computed_commitment {
81+
let hash = Sha3_256::new().chain_update(bytes).finalize().to_vec();
82+
if asserted_commitment.0 != hash {
7283
panic!(
7384
"commitment hash mismatch: asserted {}, computed {}",
74-
asserted_commitment, computed_commitment
85+
asserted_commitment,
86+
HexString(hash)
7587
);
7688
}
7789
});
@@ -81,16 +93,34 @@ fn encode_file(args: cli::EncodeFileArgs) -> Result<()> {
8193
Ok(())
8294
}
8395

84-
pub fn compute_commitment(args: cli::ComputeCommitmentArgs) -> Result<String> {
96+
pub fn compute_commitment(args: cli::ComputeCommitmentArgs) -> Result<HexString> {
8597
let (srs, domain_fp) = get_srs(args.srs_cache);
8698
let mut file = File::open(args.input)?;
8799
let mut buf = Vec::new();
88100
file.read_to_end(&mut buf)?;
89101
let field_elems = utils::encode_for_domain(&domain_fp, &buf);
90102
let commitments = commitment::commit_to_field_elems(&srs, domain_fp, field_elems);
91103
let bytes = rmp_serde::to_vec(&commitments).unwrap();
92-
let hash = Sha3_256::new().chain_update(bytes).finalize();
93-
Ok(hex::encode(hash))
104+
let hash = Sha3_256::new().chain_update(bytes).finalize().to_vec();
105+
Ok(HexString(hash))
106+
}
107+
108+
pub fn storage_proof(args: cli::StorageProofArgs) -> Result<HexString> {
109+
let file = File::open(args.input)?;
110+
let blob: FieldBlob<Vesta> = rmp_serde::decode::from_read(file)?;
111+
let proof =
112+
{
113+
let (srs, _) = get_srs(args.srs_cache);
114+
let group_map = <Vesta as CommitmentCurve>::Map::setup();
115+
let mut rng = OsRng;
116+
let evaluation_point = utils::encode(&args.challenge.0);
117+
proof::storage_proof::<
118+
Vesta,
119+
DefaultFqSponge<VestaParameters, PlonkSpongeConstantsKimchi>,
120+
>(&srs, &group_map, blob, evaluation_point, &mut rng)
121+
};
122+
let bytes = rmp_serde::to_vec(&proof).unwrap();
123+
Ok(HexString(bytes))
94124
}
95125

96126
pub fn main() -> Result<()> {
@@ -99,15 +129,15 @@ pub fn main() -> Result<()> {
99129
match args {
100130
cli::Commands::Encode(args) => encode_file(args),
101131
cli::Commands::Decode(args) => decode_file(args),
102-
cli::Commands::ComputeCommitment(args) => match compute_commitment(args) {
103-
Ok(c) => {
104-
println!("{}", c);
105-
Ok(())
106-
}
107-
Err(e) => {
108-
eprintln!("{}", e);
109-
Err(e)
110-
}
111-
},
132+
cli::Commands::ComputeCommitment(args) => {
133+
let commitment = compute_commitment(args)?;
134+
println!("{}", commitment);
135+
Ok(())
136+
}
137+
cli::Commands::StorageProof(args) => {
138+
let proof = storage_proof(args)?;
139+
println!("{}", proof);
140+
Ok(())
141+
}
112142
}
113143
}

saffron/src/proof.rs

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,38 @@ use crate::blob::FieldBlob;
22
use ark_ec::AffineRepr;
33
use ark_ff::{One, PrimeField, Zero};
44
use ark_poly::{univariate::DensePolynomial, Polynomial, Radix2EvaluationDomain as D};
5+
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
56
use kimchi::curve::KimchiCurve;
67
use mina_poseidon::FqSponge;
78
use o1_utils::ExtendedDensePolynomial;
89
use poly_commitment::{
9-
commitment::{absorb_commitment, BatchEvaluationProof, Evaluation},
10+
commitment::{absorb_commitment, BatchEvaluationProof, CommitmentCurve, Evaluation},
1011
ipa::{OpeningProof, SRS},
1112
utils::DensePolynomialOrEvaluations,
1213
PolyComm,
1314
};
1415
use rand::rngs::OsRng;
16+
use serde::{Deserialize, Serialize};
17+
use serde_with::serde_as;
1518
use tracing::instrument;
1619

20+
#[serde_as]
21+
#[derive(Debug, Serialize, Deserialize)]
22+
#[serde(bound = "G::ScalarField : CanonicalDeserialize + CanonicalSerialize")]
23+
pub struct StorageProof<G: CommitmentCurve> {
24+
#[serde_as(as = "o1_utils::serialization::SerdeAs")]
25+
pub evaluation: G::ScalarField,
26+
pub opening_proof: OpeningProof<G>,
27+
}
28+
1729
#[instrument(skip_all, level = "debug")]
1830
pub fn storage_proof<G: KimchiCurve, EFqSponge: Clone + FqSponge<G::BaseField, G, G::ScalarField>>(
1931
srs: &SRS<G>,
2032
group_map: &G::Map,
2133
blob: FieldBlob<G>,
2234
evaluation_point: G::ScalarField,
2335
rng: &mut OsRng,
24-
) -> (G::ScalarField, OpeningProof<G>)
36+
) -> StorageProof<G>
2537
where
2638
G::BaseField: PrimeField,
2739
{
@@ -49,7 +61,7 @@ where
4961
sponge.absorb_fr(&[evaluation]);
5062
sponge
5163
};
52-
let proof = srs.open(
64+
let opening_proof = srs.open(
5365
group_map,
5466
&[(
5567
DensePolynomialOrEvaluations::<<G as AffineRepr>::ScalarField, D<G::ScalarField>> ::DensePolynomial(
@@ -65,7 +77,10 @@ where
6577
opening_proof_sponge,
6678
rng,
6779
);
68-
(evaluation, proof)
80+
StorageProof {
81+
evaluation,
82+
opening_proof,
83+
}
6984
}
7085

7186
#[instrument(skip_all, level = "debug")]
@@ -77,15 +92,14 @@ pub fn verify_storage_proof<
7792
group_map: &G::Map,
7893
commitment: PolyComm<G>,
7994
evaluation_point: G::ScalarField,
80-
evaluation: G::ScalarField,
81-
opening_proof: &OpeningProof<G>,
95+
proof: &StorageProof<G>,
8296
rng: &mut OsRng,
8397
) -> bool
8498
where
8599
G::BaseField: PrimeField,
86100
{
87101
let mut opening_proof_sponge = EFqSponge::new(G::other_curve_sponge_params());
88-
opening_proof_sponge.absorb_fr(&[evaluation]);
102+
opening_proof_sponge.absorb_fr(&[proof.evaluation]);
89103

90104
srs.verify(
91105
group_map,
@@ -96,10 +110,10 @@ where
96110
evalscale: G::ScalarField::one(),
97111
evaluations: vec![Evaluation {
98112
commitment,
99-
evaluations: vec![vec![evaluation]],
113+
evaluations: vec![vec![proof.evaluation]],
100114
}],
101-
opening: opening_proof,
102-
combined_inner_product: evaluation,
115+
opening: &proof.opening_proof,
116+
combined_inner_product: proof.evaluation,
103117
}],
104118
rng,
105119
)
@@ -151,16 +165,15 @@ mod tests {
151165
};
152166
let blob = FieldBlob::<Vesta>::encode(&*SRS, *DOMAIN, &data);
153167
let evaluation_point = Fp::rand(&mut rng);
154-
let (evaluation, proof) = storage_proof::<
155-
Vesta,
156-
DefaultFqSponge<VestaParameters, PlonkSpongeConstantsKimchi>,
168+
let proof = storage_proof::<
169+
Vesta, DefaultFqSponge<VestaParameters, PlonkSpongeConstantsKimchi>
170+
157171
>(&*SRS, &*GROUP_MAP, blob, evaluation_point, &mut rng);
158172
let res = verify_storage_proof::<Vesta, DefaultFqSponge<VestaParameters, PlonkSpongeConstantsKimchi>>(
159173
&*SRS,
160174
&*GROUP_MAP,
161175
commitment,
162176
evaluation_point,
163-
evaluation,
164177
&proof,
165178
&mut rng,
166179
);

saffron/src/utils.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use tracing::instrument;
88

99
// For injectivity, you can only use this on inputs of length at most
1010
// 'F::MODULUS_BIT_SIZE / 8', e.g. for Vesta this is 31.
11-
fn encode<Fp: PrimeField>(bytes: &[u8]) -> Fp {
11+
pub fn encode<Fp: PrimeField>(bytes: &[u8]) -> Fp {
1212
Fp::from_be_bytes_mod_order(bytes)
1313
}
1414

saffron/test-encoding.sh

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,19 @@ if ! cargo run --release --bin saffron encode -i "$INPUT_FILE" -o "$ENCODED_FILE
3131
exit 1
3232
fi
3333

34+
# Generate 32-byte random challenge as hex string
35+
echo "Generating random challenge..."
36+
CHALLENGE=$(head -c 32 /dev/urandom | xxd -p -c 32)
37+
echo "Challenge: $CHALLENGE"
38+
39+
# Generate storage proof and capture proof output
40+
echo "Generating storage proof..."
41+
PROOF=$(cargo run --release --bin saffron storage-proof -i "$ENCODED_FILE" --challenge "$CHALLENGE" $SRS_ARG | tee /dev/stderr | tail -n 1)
42+
if [ $? -ne 0 ]; then
43+
echo "Storage proof generation failed"
44+
exit 1
45+
fi
46+
3447
# Run decode
3548
echo "Decoding $ENCODED_FILE to $DECODED_FILE"
3649
if ! cargo run --release --bin saffron decode -i "$ENCODED_FILE" -o "$DECODED_FILE" $SRS_ARG; then

0 commit comments

Comments
 (0)