Skip to content

Conversation

@a1denvalu3
Copy link
Collaborator

@a1denvalu3 a1denvalu3 commented Oct 7, 2025

Keyset ID Version 2 Implementation

Warning

This PR bumps Nutshell's version to 0.19.0

Overview

This PR implements the Keyset ID Version 2 specification, which changes how keyset IDs are derived and introduces support for short keyset IDs. This change improves determinism, security, and functionality of keysets while maintaining backward compatibility.

Key Changes

1. New Keyset ID Derivation

  • Version-based ID derivation:

    • Version < 0.15.0: Base64 IDs (legacy format, 12 chars)
    • Version 0.15.0-0.18.1: V1 IDs (prefix 00, 16 chars)
    • Version ≥ 0.19.0: V2 IDs (prefix 01, 66 chars)
  • V2 Keyset ID calculation:

    • Prefixes 01 to indicate V2 format
    • Includes unit information in the hash (unit:{unit_name})
    • Optionally includes expiration timestamp (final_expiry:{timestamp})
    • Uses full SHA256 hash (32 bytes) instead of truncated hash
    • Properly sorted keys for consistent results

2. Short Keyset ID Support

  • Implemented short V2 keyset IDs (16 chars) for token efficiency
  • Added ID expansion functionality to map short→full IDs
  • Updated token redemption code to handle V2 short IDs
  • Added comprehensive detection and validation of different ID formats

3. Keyset Rotation Improvements

  • Enhanced rotate_next_keyset to support the final_expiry parameter
  • Ensures V1 keysets rotate properly into V2 keysets based on version
  • Maintains backward compatibility with existing keysets

4. DB Fetching Rearrangement

  • Improved keyset fetching from DB to use derivation path instead of ID
  • Added proper support for final_expiry field in keyset database tables
  • Updated DB schema to store and retrieve the new field
  • Fixed CRUD operations to handle V2 keyset format

5. Version-based Secret Derivation

  • Implemented keyset version-based secret derivation mechanism (NUT-13)
  • Different derivation methods are used based on keyset version:
    • Base64 keysets (pre-0.15.0) and V1 keysets (00): BIP32 derivation path
    • V2 keysets (01): HMAC-SHA256 with specific domain separation
  • HMAC-SHA256 derivation for V2 keysets:
    • Uses formula: message = "Cashu_KDF_HMAC_SHA256" || keyset_id || counter
    • Secret = HMAC-SHA256(seed, message || 0x00)
    • Blinding factor = HMAC-SHA256(seed, message || 0x01)
  • Ensures deterministic secret generation while improving security

Technical Details

Keyset ID Derivation Changes

The V2 keyset ID derivation now includes:

  1. Sorting public keys by amount in ascending order
  2. Concatenating all public key bytes without separators
  3. Adding unit:{unit_name} to the byte array
  4. Optionally adding final_expiry:{timestamp} if provided
  5. Computing SHA256 hash of the entire byte array
  6. Prefixing with 01 to indicate V2 format

This creates a more robust, deterministic ID that properly includes all relevant keyset information, unlike previous versions that only used public keys.

Secret Derivation Implementation

The wallet now detects keyset version and uses the appropriate secret derivation method:

  1. For V2 keysets (version 01):

    # Derive message components
    keyset_id_bytes = bytes.fromhex(keyset_id)
    counter_bytes = counter.to_bytes(8, byteorder="big", signed=False)
    base = b"Cashu_KDF_HMAC_SHA256" + keyset_id_bytes + counter_bytes
    
    # Derive secret and blinding factor
    secret = hmac.new(seed, base + b"\x00", hashlib.sha256).digest()
    r = hmac.new(seed, base + b"\x01", hashlib.sha256).digest()
  2. For V1 keysets (version 00) and Base64 keysets:

    # Convert keyset ID to integer
    keyest_id_int = int.from_bytes(bytes.fromhex(keyset_id), "big") % (2**31 - 1)
    
    # Generate BIP32 derivation paths
    token_derivation_path = f"m/129372'/0'/{keyest_id_int}'/{counter}'"
    secret_derivation_path = f"{token_derivation_path}/0"
    r_derivation_path = f"{token_derivation_path}/1"
    
    # Derive keys using BIP32
    secret = bip32.get_privkey_from_path(secret_derivation_path)
    r = bip32.get_privkey_from_path(r_derivation_path)

The secret derivation mechanism is critical for wallet restoration and token recovery. This change ensures compatibility with all keyset formats while introducing a more robust derivation method for V2 keysets.

Rotate-Next-Keyset Behavior

When rotate_next_keyset is called:

  1. It finds the highest counter keyset for the specified unit
  2. Creates a new derivation path, increasing the counter by one
  3. Generates a new keyset with the latest ID v2 format
  4. The new final_expiry parameter is passed through to the new keyset
  5. The old keyset is deactivated

Keyset Fetching Rearrangement

Previously, keysets were fetched from DB by their ID, which could cause issues when trying to load a keyset whose ID calculation might have changed. Now:

  1. Keysets are primarily fetched by derivation path in the activate_keyset method
  2. This allows proper loading of keysets regardless of ID format changes
  3. New keysets are created only when no matching derivation path is found

Backward Compatibility

  • All existing keysets continue to work correctly
  • Older version tokens can be redeemed without issues
  • Automatic detection of different keyset ID formats
  • V2 short IDs can be expanded to full IDs for operations

Testing

  • Comprehensive test suite added for all new functionality
  • Test vectors for all keyset ID formats and secret derivation
  • Tests for rotation behavior between V1 and V2 keysets
  • Validation of proper short ID expansion
  • Verified secret derivation matches NUT-13 specification

@a1denvalu3 a1denvalu3 marked this pull request as draft October 8, 2025 16:50
@a1denvalu3 a1denvalu3 closed this Oct 9, 2025
@a1denvalu3 a1denvalu3 reopened this Oct 10, 2025
@a1denvalu3 a1denvalu3 marked this pull request as ready for review October 10, 2025 12:53
@codecov
Copy link

codecov bot commented Oct 13, 2025

Codecov Report

❌ Patch coverage is 52.09581% with 80 lines in your changes missing coverage. Please review.
✅ Project coverage is 52.30%. Comparing base (516cee2) to head (04bbecb).
⚠️ Report is 10 commits behind head on main.

Files with missing lines Patch % Lines
cashu/wallet/keyset_manager.py 23.33% 23 Missing ⚠️
cashu/mint/management_rpc/protos/management_pb2.py 0.00% 11 Missing ⚠️
cashu/wallet/proofs.py 16.66% 10 Missing ⚠️
cashu/wallet/helpers.py 0.00% 9 Missing ⚠️
cashu/wallet/wallet_deprecated.py 0.00% 9 Missing ⚠️
cashu/wallet/secrets.py 73.91% 6 Missing ⚠️
cashu/mint/management_rpc/cli/cli.py 0.00% 5 Missing ⚠️
cashu/core/crypto/keys.py 93.54% 2 Missing ⚠️
cashu/mint/management_rpc/management_rpc.py 0.00% 2 Missing ⚠️
cashu/wallet/transactions.py 83.33% 2 Missing ⚠️
... and 1 more
Additional details and impacted files
@@             Coverage Diff             @@
##             main     #798       +/-   ##
===========================================
- Coverage   64.71%   52.30%   -12.42%     
===========================================
  Files          91       90        -1     
  Lines       10900    10673      -227     
===========================================
- Hits         7054     5582     -1472     
- Misses       3846     5091     +1245     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@callebtc callebtc self-requested a review October 19, 2025 11:50
@a1denvalu3 a1denvalu3 added this to the 0.18.0 milestone Oct 20, 2025
@a1denvalu3 a1denvalu3 modified the milestones: 0.18.0, 0.18.1 Nov 1, 2025
@a1denvalu3 a1denvalu3 requested a review from asmogo November 1, 2025 18:42
@a1denvalu3 a1denvalu3 modified the milestones: 0.18.1, 0.18.2 Nov 5, 2025
@a1denvalu3 a1denvalu3 self-assigned this Nov 7, 2025
@a1denvalu3 a1denvalu3 added mint About the Nutshell mint ready Reviewed, tested, ready to merge labels Nov 7, 2025
@a1denvalu3 a1denvalu3 modified the milestones: 0.18.2, 0.19.0 Nov 8, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

mint About the Nutshell mint ready Reviewed, tested, ready to merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant