1use sha2::{Digest, Sha256};
2
3use crate::format::{
4 root_auth_spec_id_for_revision, AeadAlgo, BlockKind, CompressionAlgo, FecAlgo, FormatError,
5 KdfAlgo, FORMAT_VERSION, VOLUME_FORMAT_REV, VOLUME_FORMAT_REV_45,
6};
7
8const ROOT_AUTH_DESCRIPTOR_DOMAIN: &[u8] = b"tzap-root-auth-descriptor-v1\0";
9const ARCHIVE_ROOT_DOMAIN_V45: &[u8] = b"tzap-archive-root-v45\0";
10const CRYPTO_HEADER_PRE_HMAC_DOMAIN_V45: &[u8] = b"tzap-crypto-header-pre-hmac-v45\0";
11const MANIFEST_FOOTER_GLOBAL_PRE_HMAC_DOMAIN_V45: &[u8] =
12 b"tzap-manifest-footer-global-pre-hmac-v45\0";
13const CRITICAL_METADATA_DOMAIN_V45: &[u8] = b"tzap-critical-metadata-v45\0";
14const INDEX_ROOT_DOMAIN_V45: &[u8] = b"tzap-index-root-v45\0";
15const FEC_LAYOUT_DOMAIN_V45: &[u8] = b"tzap-fec-layout-v45\0";
16const DATA_BLOCK_MERKLE_DOMAIN_V45: &[u8] = b"tzap-data-block-merkle-v45\0";
17const EMPTY_MERKLE_DOMAIN: &[u8] = b"tzap-empty-merkle-tree-v1\0";
18const SIGNER_IDENTITY_DOMAIN: &[u8] = b"tzap-signer-identity-v1\0";
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub struct FecLayoutObjectRow {
22 pub object_class: u8,
23 pub present: bool,
24 pub object_id: u64,
25 pub first_block_index: u64,
26 pub data_block_count: u32,
27 pub parity_block_count: u32,
28 pub encrypted_size: u32,
29 pub plain_size: u32,
30}
31
32#[derive(Debug, Clone, PartialEq, Eq)]
33pub struct DataBlockMerkleLeaf {
34 pub block_index: u64,
35 pub kind: BlockKind,
36 pub flags: u8,
37 pub payload: Vec<u8>,
38}
39
40#[derive(Debug, Clone, Copy)]
41pub struct CriticalMetadataDigestInputs<'a> {
42 pub archive_uuid: [u8; 16],
43 pub session_id: [u8; 16],
44 pub format_version: u16,
45 pub volume_format_rev: u16,
46 pub stripe_width: u32,
47 pub total_volumes: u32,
48 pub compression_algo: CompressionAlgo,
49 pub aead_algo: AeadAlgo,
50 pub fec_algo: FecAlgo,
51 pub kdf_algo: KdfAlgo,
52 pub crypto_header_pre_hmac_bytes: &'a [u8],
53 pub chunk_size: u32,
54 pub envelope_target_size: u32,
55 pub block_size: u32,
56 pub fec_data_shards: u16,
57 pub fec_parity_shards: u16,
58 pub index_fec_data_shards: u16,
59 pub index_fec_parity_shards: u16,
60 pub index_root_fec_data_shards: u16,
61 pub index_root_fec_parity_shards: u16,
62 pub volume_loss_tolerance: u8,
63 pub bit_rot_buffer_pct: u8,
64 pub has_dictionary: u8,
65 pub manifest_footer_global_pre_hmac_bytes: &'a [u8],
66 pub index_root_first_block: u64,
67 pub index_root_data_block_count: u32,
68 pub index_root_parity_block_count: u32,
69 pub index_root_encrypted_size: u32,
70 pub index_root_decompressed_size: u32,
71 pub root_auth_descriptor_digest: [u8; 32],
72}
73
74#[derive(Debug, Clone, Copy)]
75pub struct ArchiveRootInputs {
76 pub archive_uuid: [u8; 16],
77 pub session_id: [u8; 16],
78 pub format_version: u16,
79 pub volume_format_rev: u16,
80 pub compression_algo: CompressionAlgo,
81 pub aead_algo: AeadAlgo,
82 pub fec_algo: FecAlgo,
83 pub kdf_algo: KdfAlgo,
84 pub critical_metadata_digest: [u8; 32],
85 pub index_digest: [u8; 32],
86 pub fec_layout_digest: [u8; 32],
87 pub total_data_block_count: u64,
88 pub data_block_merkle_root: [u8; 32],
89 pub root_auth_descriptor_digest: [u8; 32],
90 pub signer_identity_digest: [u8; 32],
91}
92
93#[derive(Debug, Clone, Copy)]
94struct RootAuthRevisionParams {
95 root_auth_spec_id: [u8; 24],
96 archive_root_domain: &'static [u8],
97 crypto_header_pre_hmac_domain: &'static [u8],
98 manifest_footer_global_pre_hmac_domain: &'static [u8],
99 critical_metadata_domain: &'static [u8],
100 index_root_domain: &'static [u8],
101 fec_layout_domain: &'static [u8],
102 data_block_merkle_domain: &'static [u8],
103}
104
105fn root_auth_revision_params(
106 format_version: u16,
107 volume_format_rev: u16,
108) -> Result<RootAuthRevisionParams, FormatError> {
109 let root_auth_spec_id = root_auth_spec_id_for_revision(format_version, volume_format_rev)?;
110 if volume_format_rev != VOLUME_FORMAT_REV_45 {
111 return Err(FormatError::UnsupportedVolumeFormatRevision {
112 format_version,
113 volume_format_rev,
114 reader_max_supported_revision: VOLUME_FORMAT_REV_45,
115 });
116 }
117 Ok(RootAuthRevisionParams {
118 root_auth_spec_id,
119 archive_root_domain: ARCHIVE_ROOT_DOMAIN_V45,
120 crypto_header_pre_hmac_domain: CRYPTO_HEADER_PRE_HMAC_DOMAIN_V45,
121 manifest_footer_global_pre_hmac_domain: MANIFEST_FOOTER_GLOBAL_PRE_HMAC_DOMAIN_V45,
122 critical_metadata_domain: CRITICAL_METADATA_DOMAIN_V45,
123 index_root_domain: INDEX_ROOT_DOMAIN_V45,
124 fec_layout_domain: FEC_LAYOUT_DOMAIN_V45,
125 data_block_merkle_domain: DATA_BLOCK_MERKLE_DOMAIN_V45,
126 })
127}
128
129pub fn root_auth_descriptor_digest(
130 authenticator_id: u16,
131 signer_identity_type: u16,
132 signer_identity_bytes: &[u8],
133 authenticator_value_length: u32,
134 footer_length: u32,
135) -> Result<[u8; 32], FormatError> {
136 root_auth_descriptor_digest_for_revision(
137 FORMAT_VERSION,
138 VOLUME_FORMAT_REV,
139 authenticator_id,
140 signer_identity_type,
141 signer_identity_bytes,
142 authenticator_value_length,
143 footer_length,
144 )
145}
146
147pub fn root_auth_descriptor_digest_for_revision(
148 format_version: u16,
149 volume_format_rev: u16,
150 authenticator_id: u16,
151 signer_identity_type: u16,
152 signer_identity_bytes: &[u8],
153 authenticator_value_length: u32,
154 footer_length: u32,
155) -> Result<[u8; 32], FormatError> {
156 let params = root_auth_revision_params(format_version, volume_format_rev)?;
157 let signer_identity_length = u32::try_from(signer_identity_bytes.len())
158 .map_err(|_| FormatError::InvalidArchive("root-auth signer identity length overflow"))?;
159 let signer_identity_hash = sha256_bytes(signer_identity_bytes);
160 let mut bytes = Vec::new();
161 bytes.extend_from_slice(ROOT_AUTH_DESCRIPTOR_DOMAIN);
162 bytes.extend_from_slice(¶ms.root_auth_spec_id);
163 push_u16(&mut bytes, authenticator_id);
164 push_u16(&mut bytes, signer_identity_type);
165 push_u32(&mut bytes, signer_identity_length);
166 bytes.extend_from_slice(&signer_identity_hash);
167 push_u32(&mut bytes, authenticator_value_length);
168 push_u32(&mut bytes, footer_length);
169 Ok(sha256_bytes(&bytes))
170}
171
172pub fn signer_identity_digest(
173 signer_identity_type: u16,
174 signer_identity_bytes: &[u8],
175) -> Result<[u8; 32], FormatError> {
176 let signer_identity_length = u32::try_from(signer_identity_bytes.len())
177 .map_err(|_| FormatError::InvalidArchive("root-auth signer identity length overflow"))?;
178 let mut bytes = Vec::new();
179 bytes.extend_from_slice(SIGNER_IDENTITY_DOMAIN);
180 push_u16(&mut bytes, signer_identity_type);
181 push_u32(&mut bytes, signer_identity_length);
182 bytes.extend_from_slice(signer_identity_bytes);
183 Ok(sha256_bytes(&bytes))
184}
185
186pub fn critical_metadata_digest(
187 inputs: CriticalMetadataDigestInputs<'_>,
188) -> Result<[u8; 32], FormatError> {
189 let params = root_auth_revision_params(inputs.format_version, inputs.volume_format_rev)?;
190 let crypto_header_pre_hmac_digest = len_prefixed_digest(
191 params.crypto_header_pre_hmac_domain,
192 inputs.crypto_header_pre_hmac_bytes,
193 )?;
194 let manifest_footer_global_pre_hmac_digest = len_prefixed_digest(
195 params.manifest_footer_global_pre_hmac_domain,
196 inputs.manifest_footer_global_pre_hmac_bytes,
197 )?;
198
199 let mut bytes = Vec::new();
200 bytes.extend_from_slice(params.critical_metadata_domain);
201 bytes.extend_from_slice(&inputs.archive_uuid);
202 bytes.extend_from_slice(&inputs.session_id);
203 push_u16(&mut bytes, inputs.format_version);
204 push_u16(&mut bytes, inputs.volume_format_rev);
205 push_u32(&mut bytes, inputs.stripe_width);
206 push_u32(&mut bytes, inputs.total_volumes);
207 push_u16(&mut bytes, inputs.compression_algo as u16);
208 push_u16(&mut bytes, inputs.aead_algo as u16);
209 push_u16(&mut bytes, inputs.fec_algo as u16);
210 push_u16(&mut bytes, inputs.kdf_algo as u16);
211 bytes.extend_from_slice(&crypto_header_pre_hmac_digest);
212 push_u32(&mut bytes, inputs.chunk_size);
213 push_u32(&mut bytes, inputs.envelope_target_size);
214 push_u32(&mut bytes, inputs.block_size);
215 push_u16(&mut bytes, inputs.fec_data_shards);
216 push_u16(&mut bytes, inputs.fec_parity_shards);
217 push_u16(&mut bytes, inputs.index_fec_data_shards);
218 push_u16(&mut bytes, inputs.index_fec_parity_shards);
219 push_u16(&mut bytes, inputs.index_root_fec_data_shards);
220 push_u16(&mut bytes, inputs.index_root_fec_parity_shards);
221 bytes.push(inputs.volume_loss_tolerance);
222 bytes.push(inputs.bit_rot_buffer_pct);
223 bytes.push(inputs.has_dictionary);
224 bytes.extend_from_slice(&manifest_footer_global_pre_hmac_digest);
225 push_u64(&mut bytes, inputs.index_root_first_block);
226 push_u32(&mut bytes, inputs.index_root_data_block_count);
227 push_u32(&mut bytes, inputs.index_root_parity_block_count);
228 push_u32(&mut bytes, inputs.index_root_encrypted_size);
229 push_u32(&mut bytes, inputs.index_root_decompressed_size);
230 bytes.extend_from_slice(&inputs.root_auth_descriptor_digest);
231 Ok(sha256_bytes(&bytes))
232}
233
234pub fn index_digest(index_root_plaintext: &[u8]) -> [u8; 32] {
235 index_digest_for_revision(FORMAT_VERSION, VOLUME_FORMAT_REV, index_root_plaintext)
236 .expect("default RootAuth revision is supported")
237}
238
239pub fn index_digest_for_revision(
240 format_version: u16,
241 volume_format_rev: u16,
242 index_root_plaintext: &[u8],
243) -> Result<[u8; 32], FormatError> {
244 let params = root_auth_revision_params(format_version, volume_format_rev)?;
245 let mut bytes = Vec::new();
246 bytes.extend_from_slice(params.index_root_domain);
247 bytes.extend_from_slice(index_root_plaintext);
248 Ok(sha256_bytes(&bytes))
249}
250
251pub fn fec_layout_digest(rows: &[FecLayoutObjectRow]) -> Result<[u8; 32], FormatError> {
252 fec_layout_digest_for_revision(FORMAT_VERSION, VOLUME_FORMAT_REV, rows)
253}
254
255pub fn fec_layout_digest_for_revision(
256 format_version: u16,
257 volume_format_rev: u16,
258 rows: &[FecLayoutObjectRow],
259) -> Result<[u8; 32], FormatError> {
260 let params = root_auth_revision_params(format_version, volume_format_rev)?;
261 let row_count = u32::try_from(rows.len())
262 .map_err(|_| FormatError::InvalidArchive("root-auth FEC row count overflow"))?;
263 let mut bytes = Vec::new();
264 bytes.extend_from_slice(params.fec_layout_domain);
265 push_u32(&mut bytes, row_count);
266 for row in rows {
267 bytes.push(row.object_class);
268 bytes.push(if row.present { 1 } else { 0 });
269 push_u16(&mut bytes, 0);
270 push_u64(&mut bytes, row.object_id);
271 push_u64(&mut bytes, row.first_block_index);
272 push_u32(&mut bytes, row.data_block_count);
273 push_u32(&mut bytes, row.parity_block_count);
274 push_u32(&mut bytes, row.encrypted_size);
275 push_u32(&mut bytes, row.plain_size);
276 }
277 Ok(sha256_bytes(&bytes))
278}
279
280pub fn data_block_merkle_root(leaves: &[DataBlockMerkleLeaf]) -> [u8; 32] {
281 data_block_merkle_root_for_revision(FORMAT_VERSION, VOLUME_FORMAT_REV, leaves)
282 .expect("default RootAuth revision is supported")
283}
284
285pub fn data_block_merkle_root_for_revision(
286 format_version: u16,
287 volume_format_rev: u16,
288 leaves: &[DataBlockMerkleLeaf],
289) -> Result<[u8; 32], FormatError> {
290 let params = root_auth_revision_params(format_version, volume_format_rev)?;
291 if leaves.is_empty() {
292 return Ok(empty_data_block_merkle_root(params));
293 }
294
295 let leaf_hashes = leaves
296 .iter()
297 .map(|leaf| {
298 data_block_merkle_leaf_hash_with_params(
299 params,
300 leaf.block_index,
301 leaf.kind,
302 leaf.flags,
303 &leaf.payload,
304 )
305 })
306 .collect::<Vec<_>>();
307 Ok(data_block_merkle_root_from_leaf_hashes_with_params(
308 params,
309 &leaf_hashes,
310 ))
311}
312
313pub fn data_block_merkle_leaf_hash(
314 block_index: u64,
315 kind: BlockKind,
316 flags: u8,
317 payload: &[u8],
318) -> [u8; 32] {
319 data_block_merkle_leaf_hash_for_revision(
320 FORMAT_VERSION,
321 VOLUME_FORMAT_REV,
322 block_index,
323 kind,
324 flags,
325 payload,
326 )
327 .expect("default RootAuth revision is supported")
328}
329
330pub fn data_block_merkle_leaf_hash_for_revision(
331 format_version: u16,
332 volume_format_rev: u16,
333 block_index: u64,
334 kind: BlockKind,
335 flags: u8,
336 payload: &[u8],
337) -> Result<[u8; 32], FormatError> {
338 let params = root_auth_revision_params(format_version, volume_format_rev)?;
339 Ok(data_block_merkle_leaf_hash_with_params(
340 params,
341 block_index,
342 kind,
343 flags,
344 payload,
345 ))
346}
347
348fn data_block_merkle_leaf_hash_with_params(
349 params: RootAuthRevisionParams,
350 block_index: u64,
351 kind: BlockKind,
352 flags: u8,
353 payload: &[u8],
354) -> [u8; 32] {
355 let mut leaf_payload = Vec::with_capacity(10 + payload.len());
356 push_u64(&mut leaf_payload, block_index);
357 leaf_payload.push(kind as u8);
358 leaf_payload.push(flags);
359 leaf_payload.extend_from_slice(payload);
360
361 let mut bytes = Vec::new();
362 bytes.push(0x00);
363 bytes.extend_from_slice(params.data_block_merkle_domain);
364 bytes.extend_from_slice(&leaf_payload);
365 sha256_bytes(&bytes)
366}
367
368pub fn data_block_merkle_root_from_leaf_hashes(leaf_hashes: &[[u8; 32]]) -> [u8; 32] {
369 data_block_merkle_root_from_leaf_hashes_for_revision(
370 FORMAT_VERSION,
371 VOLUME_FORMAT_REV,
372 leaf_hashes,
373 )
374 .expect("default RootAuth revision is supported")
375}
376
377pub fn data_block_merkle_root_from_leaf_hashes_for_revision(
378 format_version: u16,
379 volume_format_rev: u16,
380 leaf_hashes: &[[u8; 32]],
381) -> Result<[u8; 32], FormatError> {
382 let params = root_auth_revision_params(format_version, volume_format_rev)?;
383 Ok(data_block_merkle_root_from_leaf_hashes_with_params(
384 params,
385 leaf_hashes,
386 ))
387}
388
389fn data_block_merkle_root_from_leaf_hashes_with_params(
390 params: RootAuthRevisionParams,
391 leaf_hashes: &[[u8; 32]],
392) -> [u8; 32] {
393 if leaf_hashes.is_empty() {
394 return empty_data_block_merkle_root(params);
395 }
396
397 let mut level = leaf_hashes.to_vec();
398
399 while level.len() > 1 {
400 let mut next = Vec::with_capacity(level.len().div_ceil(2));
401 let mut idx = 0usize;
402 while idx < level.len() {
403 if idx + 1 == level.len() {
404 next.push(level[idx]);
405 } else {
406 let mut bytes = Vec::new();
407 bytes.push(0x01);
408 bytes.extend_from_slice(params.data_block_merkle_domain);
409 bytes.extend_from_slice(&level[idx]);
410 bytes.extend_from_slice(&level[idx + 1]);
411 next.push(sha256_bytes(&bytes));
412 }
413 idx += 2;
414 }
415 level = next;
416 }
417 level[0]
418}
419
420fn empty_data_block_merkle_root(params: RootAuthRevisionParams) -> [u8; 32] {
421 let mut bytes = Vec::new();
422 bytes.extend_from_slice(EMPTY_MERKLE_DOMAIN);
423 bytes.extend_from_slice(params.data_block_merkle_domain);
424 sha256_bytes(&bytes)
425}
426
427pub fn archive_root(inputs: ArchiveRootInputs) -> [u8; 32] {
428 archive_root_for_revision(inputs).expect("ArchiveRootInputs revision is supported")
429}
430
431pub fn archive_root_for_revision(inputs: ArchiveRootInputs) -> Result<[u8; 32], FormatError> {
432 let params = root_auth_revision_params(inputs.format_version, inputs.volume_format_rev)?;
433 let mut bytes = Vec::new();
434 bytes.extend_from_slice(params.archive_root_domain);
435 bytes.extend_from_slice(¶ms.root_auth_spec_id);
436 bytes.extend_from_slice(&inputs.archive_uuid);
437 bytes.extend_from_slice(&inputs.session_id);
438 push_u16(&mut bytes, inputs.format_version);
439 push_u16(&mut bytes, inputs.volume_format_rev);
440 push_u16(&mut bytes, inputs.compression_algo as u16);
441 push_u16(&mut bytes, inputs.aead_algo as u16);
442 push_u16(&mut bytes, inputs.fec_algo as u16);
443 push_u16(&mut bytes, inputs.kdf_algo as u16);
444 bytes.extend_from_slice(&inputs.critical_metadata_digest);
445 bytes.extend_from_slice(&inputs.index_digest);
446 bytes.extend_from_slice(&inputs.fec_layout_digest);
447 push_u64(&mut bytes, inputs.total_data_block_count);
448 bytes.extend_from_slice(&inputs.data_block_merkle_root);
449 bytes.extend_from_slice(&inputs.root_auth_descriptor_digest);
450 bytes.extend_from_slice(&inputs.signer_identity_digest);
451 Ok(sha256_bytes(&bytes))
452}
453
454fn len_prefixed_digest(domain: &[u8], payload: &[u8]) -> Result<[u8; 32], FormatError> {
455 let length = u32::try_from(payload.len())
456 .map_err(|_| FormatError::InvalidArchive("root-auth digest input length overflow"))?;
457 let mut bytes = Vec::new();
458 bytes.extend_from_slice(domain);
459 push_u32(&mut bytes, length);
460 bytes.extend_from_slice(payload);
461 Ok(sha256_bytes(&bytes))
462}
463
464fn sha256_bytes(bytes: &[u8]) -> [u8; 32] {
465 let digest = Sha256::digest(bytes);
466 let mut out = [0u8; 32];
467 out.copy_from_slice(&digest);
468 out
469}
470
471fn push_u16(bytes: &mut Vec<u8>, value: u16) {
472 bytes.extend_from_slice(&value.to_le_bytes());
473}
474
475fn push_u32(bytes: &mut Vec<u8>, value: u32) {
476 bytes.extend_from_slice(&value.to_le_bytes());
477}
478
479fn push_u64(bytes: &mut Vec<u8>, value: u64) {
480 bytes.extend_from_slice(&value.to_le_bytes());
481}
482
483#[cfg(test)]
484mod tests {
485 use super::*;
486
487 fn sample_archive_inputs(
488 volume_format_rev: u16,
489 kdf_algo: KdfAlgo,
490 critical_metadata_digest: [u8; 32],
491 ) -> ArchiveRootInputs {
492 ArchiveRootInputs {
493 archive_uuid: [1; 16],
494 session_id: [2; 16],
495 format_version: FORMAT_VERSION,
496 volume_format_rev,
497 compression_algo: CompressionAlgo::None,
498 aead_algo: AeadAlgo::None,
499 fec_algo: FecAlgo::None,
500 kdf_algo,
501 critical_metadata_digest,
502 index_digest: [3; 32],
503 fec_layout_digest: [4; 32],
504 total_data_block_count: 2,
505 data_block_merkle_root: [5; 32],
506 root_auth_descriptor_digest: [6; 32],
507 signer_identity_digest: [7; 32],
508 }
509 }
510
511 fn sample_critical_inputs<'a>(
512 volume_format_rev: u16,
513 kdf_algo: KdfAlgo,
514 crypto_header_pre_hmac_bytes: &'a [u8],
515 root_auth_descriptor_digest: [u8; 32],
516 ) -> CriticalMetadataDigestInputs<'a> {
517 CriticalMetadataDigestInputs {
518 archive_uuid: [1; 16],
519 session_id: [2; 16],
520 format_version: FORMAT_VERSION,
521 volume_format_rev,
522 stripe_width: 1,
523 total_volumes: 1,
524 compression_algo: CompressionAlgo::None,
525 aead_algo: AeadAlgo::None,
526 fec_algo: FecAlgo::None,
527 kdf_algo,
528 crypto_header_pre_hmac_bytes,
529 chunk_size: 256 * 1024,
530 envelope_target_size: 1024 * 1024,
531 block_size: 64 * 1024,
532 fec_data_shards: 1,
533 fec_parity_shards: 0,
534 index_fec_data_shards: 1,
535 index_fec_parity_shards: 0,
536 index_root_fec_data_shards: 1,
537 index_root_fec_parity_shards: 0,
538 volume_loss_tolerance: 0,
539 bit_rot_buffer_pct: 0,
540 has_dictionary: 0,
541 manifest_footer_global_pre_hmac_bytes: b"manifest-footer-pre-hmac",
542 index_root_first_block: 0,
543 index_root_data_block_count: 1,
544 index_root_parity_block_count: 0,
545 index_root_encrypted_size: 4096,
546 index_root_decompressed_size: 256,
547 root_auth_descriptor_digest,
548 }
549 }
550
551 #[test]
552 fn root_auth_wrappers_use_current_revision_domains() {
553 let descriptor = root_auth_descriptor_digest(1, 1, b"identity", 64, 512).unwrap();
554 assert_eq!(
555 descriptor,
556 root_auth_descriptor_digest_for_revision(
557 FORMAT_VERSION,
558 VOLUME_FORMAT_REV,
559 1,
560 1,
561 b"identity",
562 64,
563 512,
564 )
565 .unwrap()
566 );
567
568 assert_eq!(
569 index_digest(b"index-root"),
570 index_digest_for_revision(FORMAT_VERSION, VOLUME_FORMAT_REV, b"index-root").unwrap()
571 );
572 assert_eq!(
573 data_block_merkle_root_from_leaf_hashes(&[[9; 32], [10; 32]]),
574 data_block_merkle_root_from_leaf_hashes_for_revision(
575 FORMAT_VERSION,
576 VOLUME_FORMAT_REV,
577 &[[9; 32], [10; 32]],
578 )
579 .unwrap()
580 );
581
582 let inputs = sample_archive_inputs(VOLUME_FORMAT_REV, KdfAlgo::None, [8; 32]);
583 assert_eq!(
584 archive_root(inputs),
585 archive_root_for_revision(inputs).unwrap()
586 );
587 }
588
589 #[test]
590 fn archive_root_rejects_legacy_revision_43() {
591 assert_eq!(
592 archive_root_for_revision(sample_archive_inputs(43, KdfAlgo::None, [8; 32]))
593 .unwrap_err(),
594 FormatError::UnsupportedVolumeFormatRevision {
595 format_version: FORMAT_VERSION,
596 volume_format_rev: 43,
597 reader_max_supported_revision: VOLUME_FORMAT_REV_45,
598 }
599 );
600 archive_root_for_revision(sample_archive_inputs(
601 VOLUME_FORMAT_REV_45,
602 KdfAlgo::None,
603 [8; 32],
604 ))
605 .unwrap();
606 }
607
608 #[test]
609 fn v45_recipient_wrap_archive_root_commits_keywrap_table_digest() {
610 let descriptor = root_auth_descriptor_digest_for_revision(
611 FORMAT_VERSION,
612 VOLUME_FORMAT_REV_45,
613 1,
614 1,
615 b"identity",
616 64,
617 512,
618 )
619 .unwrap();
620 let critical_a = critical_metadata_digest(sample_critical_inputs(
621 VOLUME_FORMAT_REV_45,
622 KdfAlgo::RecipientWrap,
623 b"crypto-header-pre-hmac keywrap-table-digest A",
624 descriptor,
625 ))
626 .unwrap();
627 let critical_b = critical_metadata_digest(sample_critical_inputs(
628 VOLUME_FORMAT_REV_45,
629 KdfAlgo::RecipientWrap,
630 b"crypto-header-pre-hmac keywrap-table-digest B",
631 descriptor,
632 ))
633 .unwrap();
634
635 assert_ne!(critical_a, critical_b);
636 assert_ne!(
637 archive_root_for_revision(sample_archive_inputs(
638 VOLUME_FORMAT_REV_45,
639 KdfAlgo::RecipientWrap,
640 critical_a,
641 ))
642 .unwrap(),
643 archive_root_for_revision(sample_archive_inputs(
644 VOLUME_FORMAT_REV_45,
645 KdfAlgo::RecipientWrap,
646 critical_b,
647 ))
648 .unwrap(),
649 );
650 }
651}