HSMs Synchronization

In Secubit’s architecture, multiple HSMs may be deployed across different secure facilities or datacenters. These HSMs must always agree on the current Merkle root to guarantee consistency of wallet data and policies. The synchronization process ensures that updates are cryptographically validated before any root change is applied, preventing unauthorized modifications or state divergence.

The protocol works as follows:

  1. HMAC Generation
    Each HSM derives a synchronization proof by concatenating the old root hash with the new root hash and computing an HMAC-SHA256 over this data using a shared sync key. The sync key is provisioned securely into all HSMs during initialization and never leaves hardware.

  2. Cross-Validation
    When an update is proposed, HSM 1 generates a sync object containing the old root hash, the new root hash, and its HMAC. This object is sent to HSM 2, which recomputes the HMAC locally and checks for equality. If the HMACs differ, the update is rejected.

  3. Current State Check
    In addition to HMAC validation, HSM 2 checks whether the provided old root hash matches its currently stored root hash. This prevents replay attacks or desynchronization if one HSM has already advanced.

  4. Update Process
    Only if both checks succeed—the HMAC matches and the current root matches the expected old root—does HSM 2 accept the update and replace its root hash with the new root hash. If either validation fails, the update is discarded.

flowchart
    subgraph HSM1["HSM 1"]
        RH_OLD1("old_root_hash")
        RH_NEW1("new_root_hash")        
        SK1("🔑 sync_key")

        RH_OLD1 --> C1
        RH_NEW1 --> C1
        C1(["concat"])

        C1 --> H1
        SK1 --> H1
        H1(["hmac256"])
        H1 --> HMAC1
        HMAC1("hmac")
    end
    
    subgraph SO["sync_object"]
        RH_OLD_SO("old_root_hash")
        RH_NEW_SO("new_root_hash")
        HMAC_SO("hmac")
    end
    style SO fill:none

    RH_OLD1 --> RH_OLD_SO
    RH_NEW1 --> RH_NEW_SO
    HMAC1 --> HMAC_SO

    subgraph HSM2["HSM 2"]
        RH_OLD_SO --> C2
        RH_NEW_SO --> C2
        C2(["concat"])

        SK2("🔑 sync_key")

        C2 --> H2
        SK2 --> H2
        H2(["hmac256"])
        H2 --> HMAC2

        HMAC2("hmac")
        HMAC2 --> EH2
        HMAC_SO --> EH2
        EH2{"equal?"}

        RH2("root_hash")
        RH_OLD_SO --> ER2
        RH2 --> ER2
        ER2{"equal?"}

        ER2 --> U2
        EH2 --> U2
        U2[["update root_hash to new_root_hash"]]
    end

Security Guarantees

  • Data integrity
    Every update binds both the old root hash and the new root hash into the HMAC, ensuring that no forged or altered update can be applied undetected.

  • Replay resistance
    The HSM validates that the provided old root hash matches its current state, preventing an attacker from replaying a previously valid synchronization object.

  • Shared secret protection
    The synchronization key used for HMAC is provisioned securely inside each HSM and never leaves the hardware boundary, making it impossible to forge updates from outside.

  • Consistency across HSMs
    Updates are accepted only when both the HMAC check and the root equality check succeed, guaranteeing that all HSMs converge on the same new root hash.