Schreibtransaktionsbelege von Azure Confidential Ledger überprüfen

Die Empfangsbestätigung einer Schreibtransaktion im Azure Confidential Ledger stellt einen kryptografischen Merkle-Nachweis dar, dass die zugehörige Schreibtransaktion vom CCF-Netzwerk global bestätigt wurde. Benutzer von Azure Confidential Ledger können zu jedem Zeitpunkt eine Empfangsbestätigung für eine bestätigte Schreibtransaktion erhalten, um zu überprüfen, ob der entsprechende Schreibvorgang erfolgreich im unveränderlichen Ledger erfasst wurde.

Weitere Informationen zu Empfangsbestätigungen für Schreibtransaktionen in Azure Confidential Ledger finden Sie im entsprechenden Artikel.

Schritte zur Belegverifizierung

Eine Schreibtransaktionsbestätigung kann anhand einer bestimmten Gruppe von Schritten überprüft werden, die in den folgenden Unterabschnitten beschrieben sind. Die gleichen Schritte sind in der CCF-Dokumentation beschrieben.

Blattknotenberechnung

Der erste Schritt besteht darin, den SHA-256-Hash des Blattknotens in der Merkle-Struktur zu berechnen, der der zugesicherten Transaktion entspricht. Ein Blattknoten besteht aus der geordneten Verkettung der folgenden Felder, die unter leafComponents in einem Beleg des Azure Confidential Ledger zu finden sind:

  1. writeSetDigest
  2. SHA-256-Digest von commitEvidence
  3. claimsDigest Felder

Diese Werte müssen als Bytearrays verkettet werden: Beide writeSetDigest und claimsDigest müssten aus Zeichenfolgen hexadezimaler Ziffern in Bytearrays konvertiert werden. Andererseits kann der Hash von commitEvidence (als Bytearray) durch Anwenden der SHA-256-Hashfunktion über die UTF-8-codierte commitEvidence Zeichenfolge abgerufen werden.

Der Hash-Digest des Blattknotens kann auf ähnliche Weise berechnet werden, indem die SHA-256-Hashfunktion auf die Ergebnisverkettung der resultierenden Bytes angewendet wird.

Berechnung des Stammknotens

Der zweite Schritt besteht darin, den SHA-256-Hash des Stammes der Merkle-Struktur zum Zeitpunkt des Commits der Transaktion zu berechnen. Die Berechnung erfolgt durch iteratives Verketten und Hashen des Ergebnisses der vorherigen Iteration, beginnend mit dem Blattknoten-Hash, der im vorherigen Schritt berechnet wurde, und den geordneten Knoten-Hashes, die im proof-Feld eines Belegs angegeben sind. Die proof Liste wird als sortierte Liste bereitgestellt, und ihre Elemente müssen in der angegebenen Reihenfolge durchlaufen werden.

Die Verkettung muss für die Bytedarstellung unter Berücksichtigung der relativen Reihenfolge durchgeführt werden, die in den bereitgestellten Objekten im Feld proof angegeben ist (entweder left oder right).

  • Wenn der Schlüssel des aktuellen Elements proof in ist left, sollte das Ergebnis der vorherigen Iteration an den aktuellen Elementwert angefügt werden.
  • Wenn der Schlüssel des aktuellen Elements in proofright ist, sollte das Ergebnis der vorherigen Iteration dem aktuellen Elementwert vorangestellt werden.

Nach jeder Verkettung muss die SHA-256-Funktion angewendet werden, um die Eingabe für die nächste Iteration abzurufen. Dieser Vorgang folgt den Standardschritten zum Berechnen des Stammknotens einer Merkle-Struktur mit den erforderlichen Knoten für die Berechnung.

Überprüfen Sie die Signatur über den Stammknoten

Der dritte Schritt besteht darin, zu überprüfen, ob die über den Stammknotenhash erstellte kryptografische Signatur mithilfe des Signaturknotenzertifikats in der Bestätigung gültig ist. Der Überprüfungsprozess folgt den Standardschritten für die Überprüfung digitaler Signaturen für Nachrichten, die mit dem Elliptic Curve Digital Signature Algorithm (ECDSA) signiert wurden. Genauer gesagt sind die Schritte:

  1. Decodieren Sie die Base64-Zeichenfolge signature in ein Bytearray.
  2. Extrahieren Sie den öffentlichen ECDSA-Schlüssel aus dem Signaturknotenzertifikat cert.
  3. Stellen Sie sicher, dass die Signatur über dem Stamm der Merkle-Struktur (berechnet mit den Anweisungen im vorherigen Unterabschnitt) mithilfe des extrahierten öffentlichen Schlüssels aus dem vorherigen Schritt authentifiziert ist. Dieser Schritt entspricht effektiv einem standardmäßigen Überprüfungsprozess für digitale Signaturen mithilfe von ECDSA. Es gibt viele Bibliotheken in den am häufigsten verwendeten Programmiersprachen, mit denen eine ECDSA-Signatur mithilfe eines Zertifikats für öffentliche Schlüssel über einige Daten überprüft werden kann (z. B. die cryptographiebibliothek für Python).

Überprüfen der Unterstützung des Signaturknotenzertifikats

Zusätzlich zum vorherigen Schritt muss auch verifiziert werden, dass das Zertifikat des signierenden Knotens durch das aktuelle Ledger-Zertifikat genehmigt (d. h. signiert) ist. Dieser Schritt hängt nicht von den anderen drei vorherigen Schritten ab und kann unabhängig von den anderen ausgeführt werden.

Es ist möglich, dass sich die aktuelle Dienstidentität, die den Beleg ausgestellt hat, von der Identität unterscheidet, die den Signaturknoten unterstützt hat (z. B. aufgrund einer Zertifikatverlängerung). In diesem Fall ist es erforderlich, die Vertrauenskette der Zertifikate vom Signaturknotenzertifikat (d. h. das cert Feld im Beleg) bis zur vertrauenswürdigen Stammzertifizierungsstelle (CA) (d. h. dem aktuellen Dienstidentitätszertifikat) über andere vorherige Dienstidentitäten (d. h. das serviceEndorsements Listenfeld im Beleg) zu überprüfen. Die serviceEndorsements Liste wird als sortierte Liste von der ältesten bis zur neuesten Dienstidentität bereitgestellt.

Die Bestätigung des Zertifikats muss für die gesamte Kette überprüft werden und folgt dem genau gleichen digitalen Signaturüberprüfungsprozess, der im vorherigen Unterabschnitt beschrieben ist. Es gibt beliebte open-source-Kryptografiebibliotheken (z. B. OpenSSL), die in der Regel verwendet werden können, um einen Zertifikat-Bestätigungsschritt auszuführen.

Verifizieren des Digest von Anwendungs-Claims

Als optionaler Schritt ist es möglich, für den Fall, dass Anwendungs-Claims einer Bestätigung angefügt sind, den Claims-Digest aus den offengelegten Claims (nach einem bestimmten Algorithmus) zu berechnen und zu verifizieren, dass der Digest mit dem in dem Payload enthaltenen claimsDigest übereinstimmt. Um den Digest aus den zur Verfügung gestellten Claim-Objekten zu berechnen, muss jedes Anwendungs-Claim-Objekt in der Liste durchlaufen werden und sein kind-Feld überprüft werden.

Wenn das Anspruchsobjekt der Art LedgerEntryist, sollte die Ledger-Sammlungs-ID (collectionId) und der Inhalt (contents) des Anspruchs extrahiert und verwendet werden, um ihre HMAC-Digests mithilfe des geheimen Schlüssels (secretKey) zu berechnen, der im Anspruchsobjekt angegeben ist. Diese beiden Digests werden dann verkettet, und der SHA-256-Hash der Verkettung wird berechnet. Das Protokoll (protocol) und der resultierende Anspruchsdatendigest werden dann verkettet, und ein anderer SHA-256-Hash der Verkettung wird berechnet, um den endgültigen Digest abzurufen.

Wenn das Anspruchsobjekt der Art ClaimDigestist, sollte der Anspruchsdigest (value) extrahiert, mit dem Protokoll (protocol) verkettet werden, und der SHA-256-Hash der Verkettung wird berechnet, um den endgültigen Digest abzurufen.

Nach dem Berechnen jedes einzelnen Anspruchsdigests ist es erforderlich, alle berechneten Digests aus jedem Anwendungsanspruchsobjekt zu verketten (in derselben Reihenfolge, in der sie im Beleg angezeigt werden). Der Verkettung sollte dann die Anzahl der verarbeiteten Claims vorangestellt werden. Der SHA-256-Hash der vorangegangenen Verkettung ergibt den endgültigen Digest der Claims, der mit dem im Receipt-Objekt vorhandenen claimsDigest übereinstimmen sollte.

Mehr Ressourcen

Weitere Informationen zum Inhalt eines Schreibtransaktionsbelegs in Azure Confidential Ledger sowie Erläuterungen zu den einzelnen Feldern finden Sie im entsprechenden Artikel. Die CCF-Dokumentation enthält auch weitere Informationen zur Bestätigung des Belegs und zu anderen verwandten Ressourcen unter den folgenden Links:

Überprüfen der Belege für Schreibtransaktionen

Belegverifizierungsprogramme

Die Clientbibliothek für das vertrauliche Azure-Hauptbuch für Python stellt Hilfsfunktionen bereit, um Empfangsbestätigungen von Schreibtransaktionen zu überprüfen und den Claims-Digest aus einer Liste von Anwendungs-Claims zu berechnen. Weitere Informationen zur Verwendung des Data Plane SDK und der belegspezifischen Dienstprogramme finden Sie im Abschnitt this section und dieser Beispielcode.

Einrichtung und Voraussetzungen

Zu Referenzzwecken stellen wir Python-Beispielcode bereit, um Belege für Schreibtransaktionen in Azure Confidential Ledger gemäß den im vorherigen Abschnitt beschriebenen Schritten vollständig zu verifizieren.

Um den vollständigen Überprüfungsalgorithmus auszuführen, sind das aktuelle Dienstnetzwerkzertifikat und eine Schreibtransaktionsbestätigung aus einer ausgeführten Azure vertraulichen Ledgerressource erforderlich. Ausführliche Informationen zum Abrufen einer Schreibtransaktionsbestätigung und des Dienstzertifikats aus einer Azure vertraulichen Hauptbuchinstanz finden Sie in diesem Artikel.

Exemplarische Vorgehensweise mit Code

Der folgende Code kann verwendet werden, um die erforderlichen Objekte zu initialisieren und den Bestätigungsalgorithmus auszuführen. Ein separates Hilfsprogramm (verify_receipt) wird verwendet, um den vollständigen Überprüfungsalgorithmus auszuführen, und akzeptiert den Inhalt des receipt Felds in einer GET_RECEIPT Antwort als Wörterbuch und das Dienstzertifikat als einfache Zeichenfolge. Die Funktion löst eine Ausnahme aus, wenn der Beleg nicht gültig ist oder ein Fehler während der Verarbeitung aufgetreten ist.

Es wird davon ausgegangen, dass sowohl der Beleg als auch das Dienstzertifikat aus Dateien geladen werden können. Stellen Sie sicher, dass Sie sowohl die Konstanten service_certificate_file_name als auch receipt_file_name mit den entsprechenden Dateinamen des Dienstzertifikats und des Belegs aktualisieren, die Sie überprüfen möchten.

import json 

# Constants
service_certificate_file_name = "<service-certificate-file>"
receipt_file_name = "<receipt-file>"

# Use the receipt and the service identity to verify the receipt content 
with open(service_certificate_file_name, "r") as service_certificate_file, open( 
    receipt_file_name, "r" 
) as receipt_file: 

    # Load relevant files content 
    receipt = json.loads(receipt_file.read())["receipt"] 
    service_certificate_cert = service_certificate_file.read() 

    try: 
        verify_receipt(receipt, service_certificate_cert) 
        print("Receipt verification succeeded") 

    except Exception as e: 
        print("Receipt verification failed") 

        # Raise caught exception to look at the error stack
        raise e 

Da der Überprüfungsprozess einige kryptografische und Hashinggrundtypen erfordert, werden die folgenden Bibliotheken verwendet, um die Berechnung zu vereinfachen.

  • Die CCF-Python-Bibliothek: Das Modul stellt eine Reihe von Tools zur Bestätigung des Belegs bereit.
  • Die Python Kryptografiebibliothek: eine weit verbreitete Bibliothek, die verschiedene kryptografische Algorithmen und Grundtypen enthält.
  • Das modul hashlib, Teil der Python Standardbibliothek: ein Modul, das eine allgemeine Schnittstelle für gängige Hashingalgorithmen bereitstellt.
from ccf.receipt import verify, check_endorsements, root 
from cryptography.x509 import load_pem_x509_certificate, Certificate 
from hashlib import sha256 
from typing import Dict, List, Any 

Innerhalb der verify_receipt Funktion überprüfen wir, ob der angegebene Beleg gültig ist und alle erforderlichen Felder enthält.

# Check that all the fields are present in the receipt 
assert "cert" in receipt 
assert "leafComponents" in receipt 
assert "claimsDigest" in receipt["leafComponents"] 
assert "commitEvidence" in receipt["leafComponents"] 
assert "writeSetDigest" in receipt["leafComponents"] 
assert "proof" in receipt 
assert "signature" in receipt 

Wir initialisieren die Variablen, die im restlichen Programm verwendet werden sollen.

# Set the variables 
node_cert_pem = receipt["cert"] 
claims_digest_hex = receipt["leafComponents"]["claimsDigest"] 
commit_evidence_str = receipt["leafComponents"]["commitEvidence"] 
write_set_digest_hex = receipt["leafComponents"]["writeSetDigest"] 
proof_list = receipt["proof"] 
service_endorsements_certs_pem = receipt.get("serviceEndorsements", [])
root_node_signature = receipt["signature"] 

Wir können die PEM-Zertifikate für die Dienstidentität, den Signaturknoten und die Bestätigungen von Zertifikaten aus früheren Dienstidentitäten mithilfe der Kryptografiebibliothek laden.

# Load service and node PEM certificates 
service_cert = load_pem_x509_certificate(service_cert_pem.encode()) 
node_cert = load_pem_x509_certificate(node_cert_pem.encode()) 

# Load service endorsements PEM certificates 
service_endorsements_certs = [ 
    load_pem_x509_certificate(pem.encode()) 
    for pem in service_endorsements_certs_pem 
] 

Der erste Schritt des Überprüfungsprozesses besteht darin, den Digest des Blattknotens zu berechnen.

# Compute leaf of the Merkle Tree corresponding to our transaction 
leaf_node_hex = compute_leaf_node( 
    claims_digest_hex, commit_evidence_str, write_set_digest_hex 
)

Die compute_leaf_node-Funktion akzeptiert als Parameter die Blattkomponenten des Belegs (das claimsDigest, das commitEvidence und das writeSetDigest) und gibt den Blattknotenhash in hexadezimaler Form zurück.

Wie zuvor beschrieben, berechnen wir den Digest von commitEvidence (mit der SHA-256-Funktion hashlib ). Anschließend konvertieren wir sowohl writeSetDigest als auch claimsDigest in Bytearrays. Schließlich verketten wir die drei Arrays und verarbeiten das Ergebnis mithilfe der SHA256-Funktion.

def compute_leaf_node( 
    claims_digest_hex: str, commit_evidence_str: str, write_set_digest_hex: str 
) -> str: 
    """Function to compute the leaf node associated to a transaction 
    given its claims digest, commit evidence, and write set digest.""" 

    # Digest commit evidence string 
    commit_evidence_digest = sha256(commit_evidence_str.encode()).digest() 

    # Convert write set digest to bytes 
    write_set_digest = bytes.fromhex(write_set_digest_hex) 

    # Convert claims digest to bytes 
    claims_digest = bytes.fromhex(claims_digest_hex) 

    # Create leaf node by hashing the concatenation of its three components 
    # as bytes objects in the following order: 
    # 1. write_set_digest 
    # 2. commit_evidence_digest 
    # 3. claims_digest 
    leaf_node_digest = sha256( 
        write_set_digest + commit_evidence_digest + claims_digest 
    ).digest() 

    # Convert the result into a string of hexadecimal digits 
    return leaf_node_digest.hex() 

Nach dem Berechnen des Blatts können wir die Wurzel des Merkle-Baumes berechnen.

# Compute root of the Merkle Tree 
root_node = root(leaf_node_hex, proof_list) 

Wir verwenden die Funktion root, die als Teil der CCF-Python-Bibliothek bereitgestellt wird. Die Funktion verkettet das Ergebnis der vorherigen Iteration nacheinander mit einem neuen Element aus proof, verarbeitet die Verkettung und wiederholt dann den Schritt für jedes Element in proof mit dem zuvor berechneten Digest. Die Verkettung muss die Reihenfolge der Knoten im Merkle-Baum respektieren, um sicherzustellen, dass die Wurzel richtig komputiert wird.

def root(leaf: str, proof: List[dict]): 
    """ 
    Recompute root of Merkle tree from a leaf and a proof of the form: 
    [{"left": digest}, {"right": digest}, ...] 
    """ 

    current = bytes.fromhex(leaf) 

    for n in proof: 
        if "left" in n: 
            current = sha256(bytes.fromhex(n["left"]) + current).digest() 
        else: 
            current = sha256(current + bytes.fromhex(n["right"])).digest() 
    return current.hex() 

Nach dem Berechnen des Stammknotenhashs können wir die Signatur überprüfen, die im Beleg über dem Stamm enthalten ist, um zu überprüfen, ob die Signatur korrekt ist.

# Verify signature of the signing node over the root of the tree 
verify(root_node, root_node_signature, node_cert) 

Ebenso stellt die CCF-Bibliothek eine Funktion verify bereit, um diese Überprüfung durchzuführen. Wir verwenden den öffentlichen ECDSA-Schlüssel des Signaturknotenzertifikats, um die Signatur über den Stamm des Baums zu überprüfen.

def verify(root: str, signature: str, cert: Certificate):
    """ 
    Verify signature over root of Merkle Tree 
    """ 

    sig = base64.b64decode(signature) 
    pk = cert.public_key() 
    assert isinstance(pk, ec.EllipticCurvePublicKey) 
    pk.verify( 
        sig, 
        bytes.fromhex(root), 
        ec.ECDSA(utils.Prehashed(hashes.SHA256())), 
    )

Der letzte Schritt der Quittungsüberprüfung besteht darin, das Zertifikat zu validieren, das verwendet wurde, um die Wurzel der Merkle-Struktur zu signieren.

# Verify node certificate is endorsed by the service certificates through endorsements 
check_endorsements(node_cert, service_cert, service_endorsements_certs) 

Ebenso können wir das CCF-Hilfsprogramm check_endorsements verwenden, um zu überprüfen, ob die Dienstidentität den Signaturknoten unterstützt. Die Zertifikatkette könnte aus früheren Dienstzertifikaten bestehen, daher sollten wir überprüfen, ob die Bestätigung transitiv angewendet wird, wenn serviceEndorsements es sich nicht um eine leere Liste handelt.

def check_endorsement(endorsee: Certificate, endorser: Certificate): 
    """ 
    Check endorser has endorsed endorsee 
    """ 

    digest_algo = endorsee.signature_hash_algorithm 
    assert digest_algo 
    digester = hashes.Hash(digest_algo) 
    digester.update(endorsee.tbs_certificate_bytes) 
    digest = digester.finalize() 
    endorser_pk = endorser.public_key() 
    assert isinstance(endorser_pk, ec.EllipticCurvePublicKey) 
    endorser_pk.verify( 
        endorsee.signature, digest, ec.ECDSA(utils.Prehashed(digest_algo)) 
    ) 

def check_endorsements( 
    node_cert: Certificate, service_cert: Certificate, endorsements: List[Certificate] 
): 
    """ 
    Check a node certificate is endorsed by a service certificate, transitively through a list of endorsements. 
    """ 

    cert_i = node_cert 
    for endorsement in endorsements: 
        check_endorsement(cert_i, endorsement) 
        cert_i = endorsement 
    check_endorsement(cert_i, service_cert) 

Alternativ können wir das Zertifikat auch mithilfe der OpenSSL-Bibliothek mit einer ähnlichen Methode überprüfen.

from OpenSSL.crypto import ( 
    X509, 
    X509Store, 
    X509StoreContext, 
)

def verify_openssl_certificate( 
    node_cert: Certificate, 
    service_cert: Certificate, 
    service_endorsements_certs: List[Certificate], 
) -> None: 
    """Verify that the given node certificate is a valid OpenSSL certificate through 
    the service certificate and a list of endorsements certificates.""" 

    store = X509Store() 

    # pyopenssl does not support X509_V_FLAG_NO_CHECK_TIME. For recovery of expired 
    # services and historical receipts, we want to ignore the validity time. 0x200000 
    # is the bitmask for this option in more recent versions of OpenSSL. 
    X509_V_FLAG_NO_CHECK_TIME = 0x200000 
    store.set_flags(X509_V_FLAG_NO_CHECK_TIME) 

    # Add service certificate to the X.509 store 
    store.add_cert(X509.from_cryptography(service_cert)) 

    # Prepare X.509 endorsement certificates 
    certs_chain = [X509.from_cryptography(cert) for cert in service_endorsements_certs] 

    # Prepare X.509 node certificate 
    node_cert_pem = X509.from_cryptography(node_cert) 

    # Create X.509 store context and verify its certificate 
    ctx = X509StoreContext(store, node_cert_pem, certs_chain) 
    ctx.verify_certificate() 

Beispielcode

Der vollständige Beispielcode, der im Code-Durchgang verwendet wird, wird bereitgestellt.

Hauptprogramm

import json 

# Use the receipt and the service identity to verify the receipt content 
with open("network_certificate.pem", "r") as service_certificate_file, open( 
    "receipt.json", "r" 
) as receipt_file: 

    # Load relevant files content 
    receipt = json.loads(receipt_file.read())["receipt"]
    service_certificate_cert = service_certificate_file.read()

    try: 
        verify_receipt(receipt, service_certificate_cert) 
        print("Receipt verification succeeded") 

    except Exception as e: 
        print("Receipt verification failed") 

        # Raise caught exception to look at the error stack 
        raise e 

Bestätigung des Belegs

from cryptography.x509 import load_pem_x509_certificate, Certificate 
from hashlib import sha256 
from typing import Dict, List, Any 

from OpenSSL.crypto import ( 
    X509, 
    X509Store, 
    X509StoreContext, 
) 

from ccf.receipt import root, verify, check_endorsements 

def verify_receipt(receipt: Dict[str, Any], service_cert_pem: str) -> None: 
    """Function to verify that a given write transaction receipt is valid based 
    on its content and the service certificate. 
    Throws an exception if the verification fails.""" 

    # Check that all the fields are present in the receipt 
    assert "cert" in receipt 
    assert "leafComponents" in receipt 
    assert "claimsDigest" in receipt["leafComponents"] 
    assert "commitEvidence" in receipt["leafComponents"] 
    assert "writeSetDigest" in receipt["leafComponents"] 
    assert "proof" in receipt 
    assert "signature" in receipt 

    # Set the variables 
    node_cert_pem = receipt["cert"] 
    claims_digest_hex = receipt["leafComponents"]["claimsDigest"] 
    commit_evidence_str = receipt["leafComponents"]["commitEvidence"] 

    write_set_digest_hex = receipt["leafComponents"]["writeSetDigest"] 
    proof_list = receipt["proof"] 
    service_endorsements_certs_pem = receipt.get("serviceEndorsements", [])
    root_node_signature = receipt["signature"] 

    # Load service and node PEM certificates
    service_cert = load_pem_x509_certificate(service_cert_pem.encode()) 
    node_cert = load_pem_x509_certificate(node_cert_pem.encode()) 

    # Load service endorsements PEM certificates
    service_endorsements_certs = [ 
        load_pem_x509_certificate(pem.encode()) 
        for pem in service_endorsements_certs_pem 
    ] 

    # Compute leaf of the Merkle Tree 
    leaf_node_hex = compute_leaf_node( 
        claims_digest_hex, commit_evidence_str, write_set_digest_hex 
    ) 

    # Compute root of the Merkle Tree
    root_node = root(leaf_node_hex, proof_list) 

    # Verify signature of the signing node over the root of the tree
    verify(root_node, root_node_signature, node_cert) 

    # Verify node certificate is endorsed by the service certificates through endorsements
    check_endorsements(node_cert, service_cert, service_endorsements_certs) 

    # Alternative: Verify node certificate is endorsed by the service certificates through endorsements 
    verify_openssl_certificate(node_cert, service_cert, service_endorsements_certs) 

def compute_leaf_node( 
    claims_digest_hex: str, commit_evidence_str: str, write_set_digest_hex: str 
) -> str: 
    """Function to compute the leaf node associated to a transaction 
    given its claims digest, commit evidence, and write set digest.""" 

    # Digest commit evidence string
    commit_evidence_digest = sha256(commit_evidence_str.encode()).digest() 

    # Convert write set digest to bytes
    write_set_digest = bytes.fromhex(write_set_digest_hex) 

    # Convert claims digest to bytes
    claims_digest = bytes.fromhex(claims_digest_hex) 

    # Create leaf node by hashing the concatenation of its three components 
    # as bytes objects in the following order: 
    # 1. write_set_digest 
    # 2. commit_evidence_digest 
    # 3. claims_digest 
    leaf_node_digest = sha256( 
        write_set_digest + commit_evidence_digest + claims_digest 
    ).digest() 

    # Convert the result into a string of hexadecimal digits 
    return leaf_node_digest.hex() 

def verify_openssl_certificate( 
    node_cert: Certificate, 
    service_cert: Certificate, 
    service_endorsements_certs: List[Certificate], 
) -> None: 
    """Verify that the given node certificate is a valid OpenSSL certificate through 
    the service certificate and a list of endorsements certificates.""" 

    store = X509Store() 

    # pyopenssl does not support X509_V_FLAG_NO_CHECK_TIME. For recovery of expired 
    # services and historical receipts, we want to ignore the validity time. 0x200000 
    # is the bitmask for this option in more recent versions of OpenSSL. 
    X509_V_FLAG_NO_CHECK_TIME = 0x200000 
    store.set_flags(X509_V_FLAG_NO_CHECK_TIME) 

    # Add service certificate to the X.509 store
    store.add_cert(X509.from_cryptography(service_cert)) 

    # Prepare X.509 endorsement certificates
    certs_chain = [X509.from_cryptography(cert) for cert in service_endorsements_certs] 

    # Prepare X.509 node certificate
    node_cert_pem = X509.from_cryptography(node_cert) 

    # Create X.509 store context and verify its certificate
    ctx = X509StoreContext(store, node_cert_pem, certs_chain) 
    ctx.verify_certificate() 

Nächste Schritte