From 9c80ef8245b22a795a72027c1562001571a7deb0 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Wed, 17 Jun 2026 15:43:39 -0700 Subject: [PATCH] libfreemkv 0.31.9: ~3x fewer AES ops in the subset-difference PK walk calc_pk_from_dk derived all three children (left/pk/right) at every tree level but used only the one it descended into; the Processing Key only matters at the final node. Derive just the descended child per level + the PK once at the end. Bit-for-bit identical; speeds every DK->MK derivation (disc decryption + unpositioned-DK recovery). 60.8s -> 22.9s on a UHD worst-case recovery scan. --- CHANGELOG.md | 10 ++++++++++ Cargo.toml | 2 +- src/aacs/keys.rs | 24 ++++++++++++------------ 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c5a6e70..76915c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## 0.31.9 (2026-06-17) + +### Performance +- Subset-difference PK walk (`calc_pk_from_dk`): at each tree level derive only + the child actually descended into (not both siblings) and compute the + Processing Key once at the final node — ~3x fewer AES block ops per walk. + Bit-for-bit identical output; speeds every Device-Key → Media-Key derivation + (disc decryption AND unpositioned-DK recovery). + + ## 0.31.8 (2026-06-17) ### Added diff --git a/Cargo.toml b/Cargo.toml index 935c50b..c31a261 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libfreemkv" -version = "0.31.8" +version = "0.31.9" edition = "2024" rust-version = "1.86" license = "AGPL-3.0-only" diff --git a/src/aacs/keys.rs b/src/aacs/keys.rs index e134081..681cf6b 100644 --- a/src/aacs/keys.rs +++ b/src/aacs/keys.rs @@ -616,10 +616,13 @@ pub(super) fn calc_pk_from_dk( v_mask: u32, dev_key_v_mask: u32, ) -> [u8; 16] { - // Initial derivation: left_child = aesg3(dk, 0), pk = aesg3(dk, 1), right_child = aesg3(dk, 2) - let mut left_child = aesg3(dk, 0); - let mut pk = aesg3(dk, 1); - let mut right_child = aesg3(dk, 2); + // Descend from the device node to the record node, following the record's + // `uv` bits. At each level only the child we descend INTO is needed (the + // sibling is computed but never used), and the Processing Key is the + // `aesg3(.,1)` of the FINAL node — so we derive ONE child per level and the + // PK once at the end, instead of left/pk/right at every level. Identical + // result, ~3x fewer block ops. (left child = `aesg3(node,0)`, right = `,2`.) + let mut node = *dk; let mut current_v_mask = dev_key_v_mask; // The subset-difference tree is at most 32 levels deep (u32 mask), so the @@ -642,20 +645,17 @@ pub(super) fn calc_pk_from_dk( } } - let curr_key = if bit_pos < 0 || (uv & (1u32 << bit_pos as u32)) == 0 { - left_child + let inc = if bit_pos < 0 || (uv & (1u32 << bit_pos as u32)) == 0 { + 0 // left child } else { - right_child + 2 // right child }; - - left_child = aesg3(&curr_key, 0); - pk = aesg3(&curr_key, 1); - right_child = aesg3(&curr_key, 2); + node = aesg3(&node, inc); current_v_mask = ((current_v_mask as i32) >> 1) as u32; } - pk + aesg3(&node, 1) } /// Derive Media Key from MKB using device keys (subset-difference tree).