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