Skip to main content

tzap_core/
root_auth.rs

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(&params.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(&params.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}