Skip to content

Commit 364d687

Browse files
Merge pull request #22 from KontorProtocol/file-id-nonce
add nonce to file id and file metadata
2 parents 7d67fa3 + e91c5b8 commit 364d687

30 files changed

Lines changed: 274 additions & 132 deletions

benches/bench_main.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ fn file_preparation(bencher: Bencher, size_kb: usize) {
9696
bencher
9797
.with_inputs(|| generate_test_data(size_kb * 1024, 42))
9898
.bench_values(|data| {
99-
api::prepare_file(black_box(&data), "test.dat").unwrap();
99+
api::prepare_file(black_box(&data), "test.dat", b"").unwrap();
100100
});
101101
}
102102

@@ -118,7 +118,7 @@ mod proving {
118118

119119
for i in 0..num_files {
120120
let data = generate_test_data(size_kb * 1024, 42 + i as u64);
121-
let (prepared, metadata) = api::prepare_file(&data, &format!("f{}", i)).unwrap();
121+
let (prepared, metadata) = api::prepare_file(&data, &format!("f{}", i), b"").unwrap();
122122

123123
ledger.add_file(&metadata).unwrap();
124124

@@ -181,7 +181,7 @@ mod verification {
181181
num_challenges: usize,
182182
) -> (FileLedger, api::Proof, Vec<Challenge>) {
183183
let data = generate_test_data(16 * 1024, 42);
184-
let (prepared, metadata) = api::prepare_file(&data, "v.dat").unwrap();
184+
let (prepared, metadata) = api::prepare_file(&data, "v.dat", b"").unwrap();
185185
let mut ledger = FileLedger::new();
186186
ledger.add_file(&metadata).unwrap();
187187

src/api/mod.rs

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,10 @@
3838
//! };
3939
//! use kontor_crypto::FileLedger;
4040
//!
41-
//! // 1. Prepare the file
41+
//! // 1. Prepare the file (nonce can be user_id, timestamp, or any unique bytes)
4242
//! let my_data = b"This is a test file for the PoR system.";
43-
//! let (prepared_file, metadata) = prepare_file(my_data, "test.dat").unwrap();
43+
//! let nonce = b"user_123"; // User-provided nonce for unique file_id
44+
//! let (prepared_file, metadata) = prepare_file(my_data, "test.dat", nonce).unwrap();
4445
//!
4546
//! // 2. Create ledger and add the file
4647
//! let mut ledger = FileLedger::new();
@@ -103,17 +104,25 @@ use tracing::debug_span;
103104
/// # Arguments
104105
///
105106
/// * `data` - The raw data to be processed
106-
/// * `erasure_config` - Configuration for Reed-Solomon erasure coding
107107
/// * `filename` - Filename for operator UX and integration
108+
/// * `nonce` - A user-provided nonce included in the file_id hash. This enables the
109+
/// same data to have different file_ids (e.g., for different users or uploads).
110+
/// The nonce is stored in the returned `FileMetadata`.
108111
///
109112
/// # Returns
110113
///
111114
/// Returns a tuple of `(PreparedFile, FileMetadata)` where:
112115
/// - `PreparedFile` contains the private Merkle tree for the prover
113116
/// - `FileMetadata` contains the public commitment and reconstruction information
117+
///
118+
/// # ID Generation
119+
///
120+
/// - `object_id = object_<SHA256(data)>` - content-based, not unique, for file discovery
121+
/// - `file_id = file_<SHA256(data || nonce)>` - unique per upload, for storage protocol
114122
pub fn prepare_file(
115123
data: &[u8],
116124
filename: &str,
125+
nonce: &[u8],
117126
) -> Result<(types::PreparedFile, types::FileMetadata)> {
118127
let _span = debug_span!("prepare_file", data_size = data.len(), filename).entered();
119128

@@ -123,10 +132,16 @@ pub fn prepare_file(
123132
});
124133
}
125134

126-
// 1. Calculate file ID
127-
let mut hasher = Sha256::new();
128-
hasher.update(data);
129-
let file_id = format!("{:x}", hasher.finalize());
135+
// 1a. Calculate object ID: object_<SHA256(data)> - content-based, for file discovery
136+
let mut object_hasher = Sha256::new();
137+
object_hasher.update(data);
138+
let object_id = format!("obj_{:x}", object_hasher.finalize());
139+
140+
// 1b. Calculate file ID: file_<SHA256(data || nonce)> - unique per upload
141+
let mut file_hasher = Sha256::new();
142+
file_hasher.update(data);
143+
file_hasher.update(nonce);
144+
let file_id = format!("file_{:x}", file_hasher.finalize());
130145

131146
// 2. Encode file into 31-byte symbols using multi-codeword RS
132147
let all_symbols = crate::erasure::encode_file_symbols(data)?;
@@ -142,7 +157,9 @@ pub fn prepare_file(
142157
// 5. Create metadata (num_data_symbols, num_codewords, total_symbols are derived)
143158
let metadata = types::FileMetadata {
144159
root,
160+
object_id,
145161
file_id: file_id.clone(),
162+
nonce: nonce.to_vec(),
146163
padded_len,
147164
original_size: data.len(),
148165
filename: filename.to_string(),
@@ -186,7 +203,7 @@ pub fn tree_depth_from_metadata(metadata: &types::FileMetadata) -> usize {
186203
/// use kontor_crypto::api::{prepare_file, reconstruct_file};
187204
///
188205
/// let data = b"Hello, world!";
189-
/// let (prepared_file, metadata) = prepare_file(data, "example.dat").unwrap();
206+
/// let (prepared_file, metadata) = prepare_file(data, "example.dat", b"").unwrap();
190207
///
191208
/// // Simulate having some symbols with some missing (for reconstruction testing)
192209
/// // In practice, you'd get these from the prepared_file or from storage

src/api/system.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,10 @@ impl<'a> PorSystem<'a> {
4545
&self,
4646
data: &[u8],
4747
filename: &str,
48+
nonce: &[u8],
4849
) -> Result<(PreparedFile, FileMetadata)> {
4950
// Use the existing prepare_file function from mod.rs
50-
super::prepare_file(data, filename)
51+
super::prepare_file(data, filename, nonce)
5152
}
5253

5354
/// Generate a single compact proof for any set of open Challenges.

src/api/types.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,8 +217,14 @@ impl Clone for PorParams {
217217
pub struct FileMetadata {
218218
/// The Merkle root over all symbols (data + parity)
219219
pub root: FieldElement,
220-
/// SHA256 hash of the original file for identification
220+
/// Content-based identifier: `object_<SHA256(data)>`
221+
/// Not unique - same data produces same object_id. Used for file discovery.
222+
pub object_id: String,
223+
/// Unique file identifier: `file_<SHA256(data || nonce)>`
224+
/// Unique per upload - used in the storage protocol.
221225
pub file_id: String,
226+
/// The deterministic nonce used to derive the file_id (enables same data to have unique IDs)
227+
pub nonce: Vec<u8>,
222228
/// The total number of leaves in the Merkle tree (padded to power of 2)
223229
pub padded_len: usize,
224230
/// Size of original file in bytes (for reconstruction)

src/ledger.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,9 @@ impl FileLedger {
179179
/// use kontor_crypto::api::prepare_file;
180180
/// use kontor_crypto::FileLedger;
181181
///
182-
/// let (prepared, metadata) = prepare_file(b"hello", "test.dat").unwrap();
182+
/// // Deterministic nonce ensures unique file_id (e.g., user_id + timestamp)
183+
/// let nonce = b"user_123_1704067200";
184+
/// let (prepared, metadata) = prepare_file(b"hello", "test.dat", nonce).unwrap();
183185
/// let mut ledger = FileLedger::new();
184186
/// ledger.add_file(&metadata).unwrap();
185187
/// ```

src/lib.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,10 @@
2929
//! use kontor_crypto::{api::{self, PorSystem}, FileLedger, KontorPoRError};
3030
//!
3131
//! // 1. Prepare file with Reed-Solomon erasure coding (fixed 31-byte symbols)
32+
//! // Deterministic nonce ensures unique file_id (e.g., user_id + timestamp)
3233
//! let data = b"Hello, world! This is test data for Nova PoR.";
33-
//! let (prepared, metadata) = api::prepare_file(data, "test.dat")?;
34+
//! let nonce = b"user_alice_1704067200";
35+
//! let (prepared, metadata) = api::prepare_file(data, "test.dat", nonce)?;
3436
//!
3537
//! // 2. Create ledger and add the file
3638
//! let mut ledger = FileLedger::new();
@@ -51,9 +53,9 @@
5153
//! ```rust,no_run
5254
//! use kontor_crypto::{api::{self, PorSystem}, FileLedger, KontorPoRError};
5355
//!
54-
//! // 1. Prepare multiple files
55-
//! let (prepared1, metadata1) = api::prepare_file(b"File 1 content", "file1.dat")?;
56-
//! let (prepared2, metadata2) = api::prepare_file(b"File 2 content", "file2.dat")?;
56+
//! // 1. Prepare multiple files (deterministic nonces ensure unique file_ids)
57+
//! let (prepared1, metadata1) = api::prepare_file(b"File 1 content", "file1.dat", b"upload_001")?;
58+
//! let (prepared2, metadata2) = api::prepare_file(b"File 2 content", "file2.dat", b"upload_002")?;
5759
//!
5860
//! // 2. Build ledger
5961
//! let mut ledger = FileLedger::new();

src/main.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ fn setup_network(
230230
rng.fill_bytes(&mut data);
231231

232232
let (prepared, metadata) =
233-
api::prepare_file(&data, &format!("node_file_{}.dat", i)).unwrap();
233+
api::prepare_file(&data, &format!("node_file_{}.dat", i), b"").unwrap();
234234

235235
let idx = match category {
236236
FileSizeCategory::Small => 0,
@@ -267,7 +267,8 @@ fn setup_network(
267267
let mut data = vec![0u8; size];
268268
rng.fill_bytes(&mut data);
269269

270-
let (_, metadata) = api::prepare_file(&data, &format!("network_file_{}.dat", i)).unwrap();
270+
let (_, metadata) =
271+
api::prepare_file(&data, &format!("network_file_{}.dat", i), b"").unwrap();
271272
ledger.add_file(&metadata).unwrap();
272273
}
273274

src/params.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,9 @@ fn generate_params_for_shape(
6464
.map(|i| {
6565
let metadata = FileMetadata {
6666
root: FieldElement::ZERO,
67+
object_id: format!("obj_dummy{}", i),
6768
file_id: format!("dummy{}", i),
69+
nonce: vec![],
6870
padded_len: if i == 0 {
6971
1 << file_tree_depth // First file at max depth
7072
} else {

tests/api_consistency.rs

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use kontor_crypto::{
33
api::{self, Challenge, FieldElement, FileMetadata, PorSystem},
44
poseidon::{domain_tags, poseidon_hash_tagged},
55
};
6+
use rand::Rng;
67
use std::collections::BTreeMap;
78

89
mod common;
@@ -22,7 +23,7 @@ fn test_state_evolution_across_files() {
2223
for i in 0..3 {
2324
let data = vec![(i * 10) as u8; 50];
2425
let (prepared, metadata) =
25-
api::prepare_file(&data, "test_file.dat").expect("Failed to prepare file");
26+
api::prepare_file(&data, "test_file.dat", b"").expect("Failed to prepare file");
2627

2728
files.insert(metadata.file_id.clone(), prepared);
2829
metadatas.push(metadata);
@@ -92,8 +93,11 @@ fn test_commitments_match_between_api_and_circuit() {
9293
let data1 = vec![1u8; 100];
9394
let data2 = vec![2u8; 150];
9495

95-
let (prep1, meta1) = api::prepare_file(&data1, "test_file.dat").unwrap();
96-
let (prep2, meta2) = api::prepare_file(&data2, "test_file.dat").unwrap();
96+
// Use random nonces for unique file_ids
97+
let nonce1: [u8; 16] = rand::thread_rng().gen();
98+
let nonce2: [u8; 16] = rand::thread_rng().gen();
99+
let (prep1, meta1) = api::prepare_file(&data1, "test_file.dat", &nonce1).unwrap();
100+
let (prep2, meta2) = api::prepare_file(&data2, "test_file.dat", &nonce2).unwrap();
97101

98102
// Create challenges
99103
let seed = FieldElement::from(999u64);
@@ -148,8 +152,8 @@ fn test_prove_fails_without_required_ledger() {
148152
let data1 = vec![10u8; 50];
149153
let data2 = vec![20u8; 60];
150154

151-
let (prep1, meta1) = api::prepare_file(&data1, "test_file.dat").unwrap();
152-
let (prep2, meta2) = api::prepare_file(&data2, "test_file.dat").unwrap();
155+
let (prep1, meta1) = api::prepare_file(&data1, "test_file.dat", b"").unwrap();
156+
let (prep2, meta2) = api::prepare_file(&data2, "test_file.dat", b"").unwrap();
153157

154158
let challenges = vec![
155159
Challenge::new_test(meta1.clone(), 1000, 1, FieldElement::from(42u64)),
@@ -182,7 +186,7 @@ fn test_single_file_ignores_ledger() {
182186

183187
let data = vec![30u8; 70];
184188

185-
let (prepared, metadata) = api::prepare_file(&data, "test_file.dat").unwrap();
189+
let (prepared, metadata) = api::prepare_file(&data, "test_file.dat", b"").unwrap();
186190

187191
let challenge = Challenge::new_test(metadata.clone(), 1000, 1, FieldElement::from(123u64));
188192

@@ -198,7 +202,9 @@ fn test_single_file_ignores_ledger() {
198202
// Add another file to make the ledger different
199203
let other_metadata = FileMetadata {
200204
root: FieldElement::from(999u64),
205+
object_id: "object_other".to_string(),
201206
file_id: "other_file".to_string(),
207+
nonce: vec![],
202208
padded_len: 8, // depth 3
203209
original_size: 100,
204210
filename: "other.dat".to_string(),
@@ -239,7 +245,7 @@ fn test_proof_determinism() {
239245

240246
let data = vec![42u8; 100];
241247

242-
let (prepared, metadata) = api::prepare_file(&data, "test_file.dat").unwrap();
248+
let (prepared, metadata) = api::prepare_file(&data, "test_file.dat", b"").unwrap();
243249
let challenge = Challenge::new_test(metadata.clone(), 1000, 1, FieldElement::from(777u64));
244250

245251
let mut files = BTreeMap::new();

tests/api_functionality.rs

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ fn test_challenge_id_determinism() {
1717
println!("Testing ChallengeID deterministic derivation");
1818

1919
let data = b"Test data for challenge ID determinism";
20-
let (_, metadata) = kontor_crypto::api::prepare_file(data, "test.dat").unwrap();
20+
let (_, metadata) = kontor_crypto::api::prepare_file(data, "test.dat", b"").unwrap();
2121

2222
// Same challenge should produce same ID
2323
let challenge1 = Challenge::new_test(metadata.clone(), 1000, 5, FieldElement::from(42u64));
@@ -67,8 +67,8 @@ fn test_challenge_id_collision_resistance() {
6767
let data1 = b"First test file for collision resistance testing";
6868
let data2 = b"Second test file with different content entirely";
6969

70-
let (_, metadata1) = kontor_crypto::api::prepare_file(data1, "file1.dat").unwrap();
71-
let (_, metadata2) = kontor_crypto::api::prepare_file(data2, "file2.dat").unwrap();
70+
let (_, metadata1) = kontor_crypto::api::prepare_file(data1, "file1.dat", b"").unwrap();
71+
let (_, metadata2) = kontor_crypto::api::prepare_file(data2, "file2.dat", b"").unwrap();
7272

7373
// Same parameters, different files
7474
let challenge1 = Challenge::new_test(metadata1, 1000, 5, FieldElement::from(42u64));
@@ -85,7 +85,8 @@ fn test_challenge_id_collision_resistance() {
8585
for i in 0..100 {
8686
let data = format!("Test file content {}", i);
8787
let (_, metadata) =
88-
kontor_crypto::api::prepare_file(data.as_bytes(), &format!("file{}.dat", i)).unwrap();
88+
kontor_crypto::api::prepare_file(data.as_bytes(), &format!("file{}.dat", i), b"")
89+
.unwrap();
8990
let challenge =
9091
Challenge::new_test(metadata, 1000 + i as u64, 3, FieldElement::from(i as u64));
9192
let id = challenge.id();
@@ -107,7 +108,7 @@ fn test_proof_serialization_roundtrip() {
107108

108109
let data = b"Test data for proof serialization testing with sufficient length";
109110
let (prepared, metadata) =
110-
kontor_crypto::api::prepare_file(data, "serialize_test.dat").unwrap();
111+
kontor_crypto::api::prepare_file(data, "serialize_test.dat", b"").unwrap();
111112

112113
// Create ledger
113114
let mut ledger = FileLedger::new();
@@ -171,8 +172,10 @@ fn test_batch_seed_validation() {
171172
let data1 = b"First file for batch seed testing";
172173
let data2 = b"Second file for batch seed testing";
173174

174-
let (prepared1, metadata1) = kontor_crypto::api::prepare_file(data1, "batch1.dat").unwrap();
175-
let (prepared2, metadata2) = kontor_crypto::api::prepare_file(data2, "batch2.dat").unwrap();
175+
let (prepared1, metadata1) =
176+
kontor_crypto::api::prepare_file(data1, "batch1.dat", b"").unwrap();
177+
let (prepared2, metadata2) =
178+
kontor_crypto::api::prepare_file(data2, "batch2.dat", b"").unwrap();
176179

177180
// Create ledger
178181
let mut ledger = FileLedger::new();
@@ -218,7 +221,8 @@ fn test_porsystem_challenge_id_matching() {
218221
println!("Testing PorSystem challenge ID matching in verify");
219222

220223
let data = b"Test data for challenge ID matching";
221-
let (prepared, metadata) = kontor_crypto::api::prepare_file(data, "id_match_test.dat").unwrap();
224+
let (prepared, metadata) =
225+
kontor_crypto::api::prepare_file(data, "id_match_test.dat", b"").unwrap();
222226

223227
// Create ledger
224228
let mut ledger = FileLedger::new();
@@ -260,7 +264,8 @@ fn test_porsystem_file_not_found() {
260264
println!("Testing PorSystem error handling for missing files");
261265

262266
let data = b"Test data for file not found testing";
263-
let (prepared, metadata) = kontor_crypto::api::prepare_file(data, "missing_test.dat").unwrap();
267+
let (prepared, metadata) =
268+
kontor_crypto::api::prepare_file(data, "missing_test.dat", b"").unwrap();
264269

265270
// Create ledger without adding the file
266271
let ledger = FileLedger::new();
@@ -294,7 +299,7 @@ fn test_porsystem_prepare_file() {
294299
let data = b"Test data for PorSystem prepare_file method testing";
295300

296301
// Test successful preparation
297-
let result = system.prepare_file(data, "porsystem_test.dat");
302+
let result = system.prepare_file(data, "porsystem_test.dat", b"");
298303
assert!(
299304
result.is_ok(),
300305
"PorSystem::prepare_file should succeed with valid inputs"
@@ -322,7 +327,7 @@ fn test_porsystem_prepare_file() {
322327
);
323328

324329
// Test empty data rejection
325-
let empty_result = system.prepare_file(&[], "empty.dat");
330+
let empty_result = system.prepare_file(&[], "empty.dat", b"");
326331
match empty_result {
327332
Err(kontor_crypto::KontorPoRError::EmptyData { operation }) => {
328333
assert_eq!(operation, "prepare_file", "Should report correct operation");
@@ -342,12 +347,12 @@ fn test_porsystem_vs_free_function_equivalence() {
342347

343348
// Test prepare_file equivalence
344349
let (prepared_free, metadata_free) =
345-
kontor_crypto::api::prepare_file(data, "free_func.dat").unwrap();
350+
kontor_crypto::api::prepare_file(data, "free_func.dat", b"").unwrap();
346351

347352
let ledger = FileLedger::new();
348353
let system = PorSystem::new(&ledger);
349354
let (prepared_system, metadata_system) =
350-
system.prepare_file(data, "system_method.dat").unwrap();
355+
system.prepare_file(data, "system_method.dat", b"").unwrap();
351356

352357
// Results should be equivalent (except for filename)
353358
assert_eq!(

0 commit comments

Comments
 (0)