Skip to content

Commit 930f3f6

Browse files
feat: simplify archive size proof generation for publishers
- Publishers only need store ID - system auto-discovers root hash and size from .dig directory - Include publisher's public key in proof for verification - Auto-discover current root hash from store's Layer 0 - Auto-discover archive file size from filesystem - Maintain full parameter requirement for verifiers (who don't have .dig files) - Update README with simplified publisher workflow vs full verifier workflow
1 parent f638dde commit 930f3f6

File tree

5 files changed

+103
-29
lines changed

5 files changed

+103
-29
lines changed

README.md

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ urn:dig:chia:a3f5c8d9e2b1f4a6c9d8e7f2a5b8c1d4e7f0a3b6c9d2e5f8b1c4d7e0a3b6c9d2/vi
269269
|---------|-------------|---------|
270270
| `proof generate` | Generate merkle proof | `digstore proof generate file.txt --bytes 0-1023` |
271271
| `proof verify` | Verify a merkle proof | `digstore proof verify proof.json --verbose` |
272-
| `proof generate-archive-size` | Generate tamper-proof archive size proof | `digstore proof generate-archive-size STORE_ID ROOT_HASH SIZE` |
272+
| `proof generate-archive-size` | Generate tamper-proof archive size proof | `digstore proof generate-archive-size STORE_ID` |
273273
| `proof verify-archive-size` | Verify archive size proof without file access | `digstore proof verify-archive-size proof.txt STORE_ID ROOT_HASH SIZE` |
274274

275275
### Command Options
@@ -314,8 +314,10 @@ digstore get urn:dig:chia:STORE_ID/file.txt
314314
digstore proof generate file.txt -o proof.json
315315
digstore proof verify proof.json
316316

317-
# Generate archive size proof (tamper-proof, no file download needed)
318-
digstore proof generate-archive-size STORE_ID ROOT_HASH SIZE -o size_proof.txt
317+
# Generate archive size proof (tamper-proof, no file download needed)
318+
# Publisher only needs store ID - system auto-discovers root hash and size
319+
digstore proof generate-archive-size STORE_ID -o size_proof.txt
320+
# Verifier needs all parameters since they don't have the .dig files
319321
digstore proof verify-archive-size --from-file size_proof.txt STORE_ID ROOT_HASH SIZE
320322
```
321323

@@ -351,12 +353,13 @@ digstore staged diff --stat
351353
digstore get "urn:dig:chia:invalid-store/fake.txt" # Returns random data, not error
352354

353355
# Archive size proofs (prove file size without downloading)
354-
digstore proof generate-archive-size 9baeb47a392476fe88266b579bf343f3af5f75c7633e25a722a89a6d7b47a2bd \
355-
e9254d8982b7a15a1dbdac05a99129df67880e37b43e5481dcf667aeca04fd4e \
356-
2544 --verbose --show-compression # Outputs compressed hex proof to stdout
356+
# Publisher: Only needs store ID (auto-discovers root hash, size, includes public key)
357+
digstore proof generate-archive-size bf9dfeba76de1daa2c1674260efbb9a7b5effc251213fd90241b37d547528102 \
358+
--verbose --show-compression # Outputs compressed hex proof to stdout
357359

358-
# Verify size proof (no file access required)
359-
digstore proof verify-archive-size proof.txt STORE_ID ROOT_HASH SIZE --verbose
360+
# Verifier: Needs all parameters (doesn't have .dig files to inspect)
361+
digstore proof verify-archive-size proof.txt bf9dfeba76de1daa2c1674260efbb9a7b5effc251213fd90241b37d547528102 \
362+
beec453fffccaf57d3938d3015e8effda1cf71b027b45b3467052d96e31453b3 2544 --verbose
360363

361364
# List all configuration (encrypted storage is enabled by default)
362365
digstore config --list

src/cli/commands/proof/generate_archive_size.rs

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
//! Generate archive size proof command (moved from prove_archive_size.rs)
22
3+
use crate::config::global_config::{GlobalConfig, ConfigKey, ConfigValue};
34
use crate::core::types::Hash;
45
use crate::proofs::size_proof::ArchiveSizeProof;
6+
use crate::storage::{Store, dig_archive::get_archive_path};
57
use anyhow::Result;
68
use colored::Colorize;
79
use std::io::Write;
@@ -10,8 +12,6 @@ use std::path::PathBuf;
1012
/// Execute the proof generate-archive-size command
1113
pub fn execute(
1214
store_id: String,
13-
root_hash: String,
14-
expected_size: u64,
1515
output: Option<PathBuf>,
1616
format: Option<String>,
1717
verbose: bool,
@@ -20,26 +20,65 @@ pub fn execute(
2020
) -> Result<()> {
2121
println!("{}", "Generating tamper-proof archive size proof...".bright_blue());
2222

23-
// Parse store ID and root hash
23+
// Parse store ID
2424
let store_id_hash = Hash::from_hex(&store_id)
2525
.map_err(|_| anyhow::anyhow!("Invalid store ID format: {}", store_id))?;
26-
let root_hash_hash = Hash::from_hex(&root_hash)
27-
.map_err(|_| anyhow::anyhow!("Invalid root hash format: {}", root_hash))?;
2826

2927
if verbose {
3028
println!(" {} Store ID: {}", "•".cyan(), store_id);
31-
println!(" {} Root Hash: {}", "•".cyan(), root_hash);
32-
println!(" {} Expected Size: {} bytes", "•".cyan(), expected_size);
29+
println!(" {} Auto-discovering parameters from .dig directory...", "•".cyan());
30+
}
31+
32+
// Load global configuration to get publisher's public key
33+
let global_config = GlobalConfig::load().map_err(|e| {
34+
anyhow::anyhow!("Failed to load global configuration: {}. Please set crypto.public_key using 'digstore config crypto.public_key <hex_key>'", e)
35+
})?;
36+
37+
let publisher_public_key = match global_config.get(&ConfigKey::CryptoPublicKey) {
38+
Some(ConfigValue::String(pubkey)) => pubkey,
39+
_ => {
40+
return Err(anyhow::anyhow!(
41+
"Publisher public key not configured. Please set it using:\n digstore config crypto.public_key <32-byte-hex-key>"
42+
));
43+
}
44+
};
45+
46+
if verbose {
47+
println!(" {} Publisher public key: {}...", "•".cyan(), &publisher_public_key[..16]);
48+
}
49+
50+
// Open the store to get current root hash and archive size
51+
let store = Store::open_global(&store_id_hash).map_err(|e| {
52+
anyhow::anyhow!("Failed to open store {}: {}. Ensure the store exists in ~/.dig/", store_id, e)
53+
})?;
54+
55+
let current_root_hash = store.current_root.ok_or_else(|| {
56+
anyhow::anyhow!("Store {} has no commits yet. Please commit some data first.", store_id)
57+
})?;
58+
59+
// Get the actual archive file size
60+
let archive_path = get_archive_path(&store_id_hash)?;
61+
let actual_file_size = std::fs::metadata(&archive_path)
62+
.map_err(|e| anyhow::anyhow!("Failed to get archive file size: {}", e))?
63+
.len();
64+
65+
if verbose {
66+
println!(" {} Current root hash: {}", "•".cyan(), current_root_hash.to_hex());
67+
println!(" {} Archive file size: {} bytes", "•".cyan(), actual_file_size);
3368
println!();
3469
}
3570

36-
// Generate the proof
37-
let proof = match ArchiveSizeProof::generate(&store_id_hash, &root_hash_hash, expected_size) {
38-
Ok(proof) => {
71+
// Generate the proof with auto-discovered parameters
72+
let proof = match ArchiveSizeProof::generate(&store_id_hash, &current_root_hash, actual_file_size) {
73+
Ok(mut proof) => {
74+
// Include publisher's public key in the proof for verification
75+
proof.publisher_public_key = Some(publisher_public_key.clone());
76+
3977
if verbose {
4078
println!(" {} Archive located and verified", "✓".green());
4179
println!(" {} Layer count: {}", "•".cyan(), proof.verified_layer_count);
4280
println!(" {} Calculated size: {} bytes", "•".cyan(), proof.calculated_total_size);
81+
println!(" {} Publisher key included in proof", "•".cyan());
4382
}
4483
proof
4584
}

src/cli/mod.rs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -586,12 +586,8 @@ pub enum ProofCommands {
586586
/// Generate tamper-proof archive size proof
587587
#[command(name = "generate-archive-size")]
588588
GenerateArchiveSize {
589-
/// Store ID (32-byte hex)
589+
/// Store ID (32-byte hex) - system will auto-discover root hash and size
590590
store_id: String,
591-
/// Root hash (32-byte hex)
592-
root_hash: String,
593-
/// Expected size in bytes
594-
expected_size: u64,
595591
/// Output file (default: stdout)
596592
#[arg(short, long)]
597593
output: Option<PathBuf>,

src/main.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -211,15 +211,13 @@ fn main() -> Result<()> {
211211
} => cli::commands::proof::execute_verify(proof, target, root, verbose, from_stdin),
212212
ProofCommands::GenerateArchiveSize {
213213
store_id,
214-
root_hash,
215-
expected_size,
216214
output,
217215
format,
218216
verbose,
219217
show_compression,
220218
json,
221219
} => cli::commands::proof::execute_generate_archive_size(
222-
store_id, root_hash, expected_size, output, format, verbose, show_compression, json
220+
store_id, output, format, verbose, show_compression, json
223221
),
224222
ProofCommands::VerifyArchiveSize {
225223
proof,

src/proofs/size_proof.rs

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ pub struct ArchiveSizeProof {
2727
pub layer_size_tree_root: Hash,
2828
/// Integrity proofs to prevent tampering
2929
pub integrity_proofs: IntegrityProofs,
30+
/// Publisher's public key (included by proof generator, verified by verifier)
31+
pub publisher_public_key: Option<String>,
3032
}
3133

3234
/// Integrity proofs to verify archive structure
@@ -59,6 +61,9 @@ pub struct CompressedSizeProof {
5961
pub proof_path_length: u8, // 1 byte: Number of proof elements
6062
pub proof_path: Vec<ProofElement>, // Variable: Merkle proof path
6163

64+
// Publisher verification (32 bytes)
65+
pub publisher_public_key: [u8; 32], // 32 bytes: Publisher's public key
66+
6267
// Integrity verification (96 bytes)
6368
pub header_hash: [u8; 32], // 32 bytes: Archive header hash
6469
pub index_hash: [u8; 32], // 32 bytes: Layer index hash
@@ -147,6 +152,7 @@ impl ArchiveSizeProof {
147152
layer_sizes,
148153
layer_size_tree_root: layer_size_tree.root(),
149154
integrity_proofs,
155+
publisher_public_key: None, // Will be set by CLI command
150156
})
151157
}
152158

@@ -229,6 +235,16 @@ impl CompressedSizeProof {
229235
}
230236
];
231237

238+
// Convert publisher public key from hex string to bytes
239+
let mut publisher_key_bytes = [0u8; 32];
240+
if let Some(ref pubkey_hex) = proof.publisher_public_key {
241+
if let Ok(pubkey_bytes) = hex::decode(pubkey_hex) {
242+
if pubkey_bytes.len() == 32 {
243+
publisher_key_bytes.copy_from_slice(&pubkey_bytes);
244+
}
245+
}
246+
}
247+
232248
Ok(Self {
233249
version: 1,
234250
store_id: *proof.store_id.as_bytes(),
@@ -238,6 +254,7 @@ impl CompressedSizeProof {
238254
merkle_tree_root: *proof.layer_size_tree_root.as_bytes(),
239255
proof_path_length: proof_path.len() as u8,
240256
proof_path,
257+
publisher_public_key: publisher_key_bytes,
241258
header_hash: *proof.integrity_proofs.archive_header_hash.as_bytes(),
242259
index_hash: *proof.integrity_proofs.layer_index_hash.as_bytes(),
243260
first_layer_hash: *proof.integrity_proofs.first_layer_content_hash.as_bytes(),
@@ -265,7 +282,10 @@ impl CompressedSizeProof {
265282
buffer.push(element.position);
266283
}
267284

268-
// 4. Integrity proofs (96 bytes)
285+
// 4. Publisher public key (32 bytes)
286+
buffer.extend_from_slice(&self.publisher_public_key);
287+
288+
// 5. Integrity proofs (96 bytes)
269289
buffer.extend_from_slice(&self.header_hash);
270290
buffer.extend_from_slice(&self.index_hash);
271291
buffer.extend_from_slice(&self.first_layer_hash);
@@ -284,7 +304,7 @@ impl CompressedSizeProof {
284304
let buffer = zstd::decode_all(&compressed[..])
285305
.map_err(|e| DigstoreError::internal(format!("Decompression failed: {}", e)))?;
286306

287-
if buffer.len() < 110 {
307+
if buffer.len() < 142 { // 110 + 32 for publisher public key
288308
return Err(DigstoreError::internal("Proof too short"));
289309
}
290310

@@ -324,7 +344,16 @@ impl CompressedSizeProof {
324344
offset += 33;
325345
}
326346

327-
// 5. Parse integrity proofs
347+
// 5. Parse publisher public key (32 bytes)
348+
if offset + 32 > buffer.len() {
349+
return Err(DigstoreError::internal("Publisher public key extends beyond buffer"));
350+
}
351+
352+
let mut publisher_public_key = [0u8; 32];
353+
publisher_public_key.copy_from_slice(&buffer[offset..offset + 32]);
354+
offset += 32;
355+
356+
// 6. Parse integrity proofs (96 bytes)
328357
if offset + 96 > buffer.len() {
329358
return Err(DigstoreError::internal("Integrity proofs extend beyond buffer"));
330359
}
@@ -345,6 +374,7 @@ impl CompressedSizeProof {
345374
merkle_tree_root,
346375
proof_path_length,
347376
proof_path,
377+
publisher_public_key,
348378
header_hash,
349379
index_hash,
350380
first_layer_hash,
@@ -356,6 +386,13 @@ impl CompressedSizeProof {
356386
// For simplified verification, create minimal layer sizes that sum to total
357387
let layer_sizes = vec![self.total_size / 2, self.total_size - (self.total_size / 2)]; // Split into 2 layers
358388

389+
// Convert publisher public key back to hex string
390+
let publisher_public_key = if self.publisher_public_key != [0u8; 32] {
391+
Some(hex::encode(&self.publisher_public_key))
392+
} else {
393+
None
394+
};
395+
359396
Ok(ArchiveSizeProof {
360397
store_id: Hash::from_bytes(self.store_id),
361398
root_hash: Hash::from_bytes(self.root_hash),
@@ -370,6 +407,7 @@ impl CompressedSizeProof {
370407
first_layer_content_hash: Hash::from_bytes(self.first_layer_hash),
371408
last_layer_content_hash: Hash::from_bytes(self.first_layer_hash), // Simplified
372409
},
410+
publisher_public_key,
373411
})
374412
}
375413
}

0 commit comments

Comments
 (0)