

Die vorliegende Übersetzung wurde maschinell erstellt. Im Falle eines Konflikts oder eines Widerspruchs zwischen dieser übersetzten Fassung und der englischen Fassung (einschließlich infolge von Verzögerungen bei der Übersetzung) ist die englische Fassung maßgeblich.

# Datenüberprüfung in Amazon QLDB
<a name="verification"></a>

**Wichtig**  
Hinweis zum Ende des Supports: Bestandskunden können Amazon QLDB bis zum Ende des Supports am 31.07.2025 nutzen. Weitere Informationen finden Sie unter [Migrieren eines Amazon QLDB-Ledgers zu Amazon](https://aws.amazon.com/blogs/database/migrate-an-amazon-qldb-ledger-to-amazon-aurora-postgresql/) Aurora PostgreSQL.

Mit Amazon QLDB können Sie darauf vertrauen, dass der Verlauf der Änderungen an Ihren Anwendungsdaten korrekt ist. QLDB verwendet ein unveränderliches Transaktionsprotokoll, ein sogenanntes *Journal*, für die Datenspeicherung. Das Journal verfolgt jede Änderung an Ihren übermittelten Daten und führt einen vollständigen und überprüfbaren Verlauf der Änderungen im Laufe der Zeit.

*QLDB verwendet die SHA-256-Hash-Funktion mit einem Merkle-Tree-basierten Modell, um eine kryptografische Darstellung Ihres Journals zu generieren, die als Digest bezeichnet wird.* Der Digest dient als eindeutige Signatur des gesamten Änderungsverlaufs Ihrer Daten zu einem bestimmten Zeitpunkt. Sie verwenden den Digest, um die Integrität der Änderungen an Ihrem Dokument in Bezug auf diese Signatur zu überprüfen.

**Topics**
+ [Welche Art von Daten können Sie in QLDB verifizieren?](#verification.structure)
+ [Was bedeutet Datenintegrität?](#verification.integrity)
+ [Wie funktioniert die Überprüfung?](#verification.how-it-works)
+ [Beispiel für eine Überprüfung](#verification.example)
+ [Wie wirkt sich die Schwärzung von Daten auf die Überprüfung aus?](#verification.redaction)
+ [Erste Schritte mit der Überprüfung](#verification.getting-started)
+ [Schritt 1: Einen Digest in QLDB anfordern](verification.digest.md)
+ [Schritt 2: Verifizierung Ihrer Daten in QLDB](verification.verify.md)
+ [Ergebnisse der Überprüfung](verification.results.md)
+ [Tutorial: Daten mit einem AWS SDK verifizieren](verification.tutorial-block-hash.md)
+ [Häufige Fehler bei der Überprüfung](verification.errors.md)

## Welche Art von Daten können Sie in QLDB verifizieren?
<a name="verification.structure"></a>

In QLDB hat jedes Ledger genau ein Journal. Ein Journal kann mehrere *Strähnen* umfassen. Dabei handelt es sich um Partitionen des Journals.

**Anmerkung**  
QLDB unterstützt derzeit nur Zeitschriften mit einem einzigen Strang.

Ein *Block* ist ein Objekt, das bei einer Transaktion in der Journalsträhne festgeschrieben wird. Dieser Block enthält Objekte vom Typ *Eintrag*, die die Dokumentversionen wiedergeben, die aufgrund der Transaktion entstanden sind. Sie können entweder eine einzelne Revision oder einen ganzen Journalblock in QLDB überprüfen.

Das folgende Diagramm veranschaulicht diese Journalstruktur.

![\[Das Amazon QLDB-Journalstrukturdiagramm zeigt eine Reihe von Hash-verketteten Blöcken, die einen Strang bilden, sowie die Sequenznummer und den Hash jedes Blocks.\]](http://docs.aws.amazon.com/de_de/qldb/latest/developerguide/images/verification/journal-structure.png)


Das Diagramm zeigt, dass Transaktionen in das Journal als Blöcke übergeben werden, die Dokumentrevisionseinträge enthalten. Sie zeigt auch, dass jeder Block mit nachfolgenden Blöcken Hash-verkettet ist und eine Sequenznummer aufweist, um seine Adresse innerhalb der Strähne anzugeben.

Hinweise zum Dateninhalt in einem Block finden Sie unter [Journalinhalte in Amazon QLDB](journal-contents.md).

## Was bedeutet Datenintegrität?
<a name="verification.integrity"></a>

Datenintegrität in QLDB bedeutet, dass das Journal Ihres Ledgers tatsächlich unveränderlich ist. Mit anderen Worten: Ihre Daten (bzw. alle Dokumentversionen) befinden sich in einem Zustand, in dem die folgenden Bedingungen erfüllt sind:

1. Sie befinden sich an derselben Position im Journal, an der sie ursprünglich geschrieben wurden.

1. Sie wurden seit dem ursprünglichen Schreibvorgang nicht verändert.

## Wie funktioniert die Überprüfung?
<a name="verification.how-it-works"></a>

Um zu verstehen, wie die Überprüfung in Amazon QLDB funktioniert, können Sie das Konzept in vier grundlegende Komponenten unterteilen.
+ [Hashing](#verification.how-it-works.hashing)
+ [Digest](#verification.how-it-works.digest)
+ [Merkle-Baum](#verification.how-it-works.merkle-tree)
+ [Nachweis](#verification.how-it-works.proof)

### Hashing
<a name="verification.how-it-works.hashing"></a>

QLDB verwendet die kryptografische SHA-256-Hash-Funktion, um 256-Bit-Hashwerte zu erstellen. Ein Hash fungiert als eine eindeutige Signatur mit fester Länge für eine beliebige Anzahl von Eingabedaten. Wenn Sie einen Teil der Eingabe ändern — auch nur ein einzelnes Zeichen oder Bit —, ändert sich der Ausgabe-Hash vollständig.

Das folgende Diagramm zeigt, dass die SHA-256-Hash-Funktion völlig eindeutige Hashwerte für zwei QLDB-Dokumente erzeugt, die sich nur um eine einzige Ziffer unterscheiden.

![\[Diagramm, das zeigt, dass die kryptografische SHA-256-Hash-Funktion völlig eindeutige Hashwerte für zwei QLDB-Dokumente erzeugt, die sich nur um eine einzige Ziffer unterscheiden.\]](http://docs.aws.amazon.com/de_de/qldb/latest/developerguide/images/sha256.png)


Die SHA-256-Hash-Funktion ist eine Möglichkeit, was bedeutet, dass es mathematisch nicht möglich ist, die Eingabe zu berechnen, wenn eine Ausgabe gegeben wird. Das folgende Diagramm zeigt, dass es nicht möglich ist, das QLDB-Eingabedokument zu berechnen, wenn ein Ausgabe-Hashwert angegeben wird.

![\[Diagramm, das zeigt, dass es nicht möglich ist, das QLDB-Eingabedokument zu berechnen, wenn ein Ausgabe-Hashwert angegeben wird.\]](http://docs.aws.amazon.com/de_de/qldb/latest/developerguide/images/sha256-one-way.png)


Die folgenden Dateneingaben werden zu Überprüfungszwecken in QLDB gehasht:
+ Dokumentversionen
+ PartiQL-Anweisungen
+ Revisionseinträge
+ Journalblöcke

### Digest
<a name="verification.how-it-works.digest"></a>

Ein *Digest* ist eine kryptografische Darstellung des gesamten Journals Ihres Ledgers zu einem Zeitpunkt. Das Journal kann nur angehängt werden (Append-only) und Journalblöcke werden ähnlich wie Blockchains sequenziert und Hash-verkettet.

Sie können jederzeit eine Zusammenfassung für ein Hauptbuch anfordern. QLDB generiert den Digest und gibt ihn als sichere Ausgabedatei an Sie zurück. Anschließend verwenden Sie diesen Digest, um die Integrität von Dokumentüberarbeitungen zu überprüfen, die zu einem früheren Zeitpunkt festgeschrieben wurden. Wenn Sie Hashes neu berechnen, indem Sie mit einer Revision beginnen und mit dem Digest enden, weisen Sie nach, dass Ihre Daten zwischenzeitlich nicht verändert wurden.

### Merkle-Baum
<a name="verification.how-it-works.merkle-tree"></a>

Je größer Ihr Hauptbuch wird, desto ineffizienter wird es, die gesamte Hash-Kette des Journals zur Überprüfung neu zu berechnen. QLDB verwendet ein Merkle-Baummodell, um diese Ineffizienz zu beheben.

Ein *Hash-Baum* ist eine Baumdatenstruktur, in der jeder Blattknoten ein Hash eines Datenblocks darstellt. Jeder Nicht-Blattknoten ist ein Hash seiner untergeordneten Knoten. Ein Merkle-Baum, der häufig in Blockchains verwendet wird, hilft Ihnen dabei, große Datensätze mithilfe eines prüfungssicheren Mechanismus effizient zu verifizieren. Weitere Informationen zu Hash-Bäumen finden Sie auf der [Wikipedia-Seite zu Hash-Bäumen](https://en.wikipedia.org/wiki/Merkle_tree). Weitere Informationen zu Hash-Prüfnachweisen und einen Beispiel-Anwendungsfall finden Sie im Artikel [How Log Proofs Work](https://www.certificate-transparency.org/log-proofs-work) auf der Website von Certificate Transparency.

Die QLDB-Implementierung des Merkle-Baums basiert auf der vollständigen Hash-Kette eines Journals. In diesem Modell sind die Blattknoten die Menge aller einzelnen Dokumentrevisions-Hashes. Der Stammknoten stellt den Digest des gesamten Journals ab einem Zeitpunkt dar.

Mithilfe eines Hash-Baum-Prüfnachweises können Sie eine Revision verifizieren, indem Sie nur eine kleine Teilmenge des Revisionsverlaufs Ihres Ledgers überprüfen. Sie tun dies, indem Sie den Baum von einem bestimmten Blattknoten (Revision) bis zu seiner Stamm (Digest) durchlaufen. Entlang dieses Durchlaufpfads werden Geschwisterpaare von Knoten rekursiv gehasht, um ihren übergeordneten Hash zu berechnen, bis Sie mit dem Digest enden. Dieser Durchlauf weist eine zeitliche Komplexität von `log(n)`-Knoten in der Struktur auf.

### Nachweis
<a name="verification.how-it-works.proof"></a>

Ein *Beweis* ist die geordnete Liste von Knoten-Hashes, die QLDB für einen bestimmten Digest und eine bestimmte Dokumentrevision zurückgibt. Er besteht aus den Hashes, die von einem Hash-Baummodell benötigt werden, um den gegebenen Blattknoten-Hash (eine Revision) mit dem Stamm-Hash (dem Digest) zu verketten.

Das Ändern von übergebenen Daten zwischen einer Revision und einem Digest unterbricht die Hash-Kette Ihres Journals und macht es unmöglich, einen Nachweis zu generieren.

## Beispiel für eine Überprüfung
<a name="verification.example"></a>

Das folgende Diagramm veranschaulicht das Amazon QLDB-Hash-Tree-Modell. Es zeigt einen Satz von Block-Hashes, die zum obersten Stammknoten aufgerollt werden, der den Digest einer Journalsträhne darstellt. In einem Ledger mit einem einsträhnigen Journal stellt dieser Stammknoten den Digest des gesamten Ledgers dar.

![\[Amazon QLDB-Hashbaumdiagramm für eine Reihe von Block-Hashes in einem Journalstrang.\]](http://docs.aws.amazon.com/de_de/qldb/latest/developerguide/images/verification/hash-tree.png)


Angenommen, Knoten **A** ist der Block, der die Dokumentrevision enthält, deren Hash-Wert Sie verifizieren möchten. **Die folgenden Knoten stellen die geordnete Liste der Hashes dar, die QLDB in Ihrem Beweis bereitstellt: **B**, **E, G.**** **Diese Hashes sind erforderlich, um den Digest aus Hash A neu zu berechnen.**

Gehen Sie wie folgt vor, um den Digest neu zu berechnen:

1. **Beginnen Sie mit Hash **A** und verketten Sie ihn mit Hash B.** **Hashen Sie dann das Ergebnis, um D zu berechnen.**

1. Verwenden Sie **D** und **E**, um **F** zu berechnen.

1. Verwenden Sie **F** und **G**, um den Digest zu berechnen.

Die Verifizierung ist erfolgreich, wenn der neu berechnete Digest dem erwarteten Wert entspricht. Bei einem Revisions-Hash und einem Digest ist es nicht möglich, die Hashes in einem Nachweis zurückzuentwickeln. Daher beweist diese Übung, dass Ihre Revision tatsächlich an dieser Stelle des Journals im Verhältnis zum Digest geschrieben wurde.

## Wie wirkt sich die Schwärzung von Daten auf die Überprüfung aus?
<a name="verification.redaction"></a>

In Amazon QLDB löscht eine `DELETE` Anweisung ein Dokument nur logisch, indem eine neue Version erstellt wird, die es als gelöscht markiert. QLDB unterstützt auch einen *Datenschwärzungsvorgang*, mit dem Sie inaktive Dokumentrevisionen in der Historie einer Tabelle dauerhaft löschen können.

Bei der Schwärzung werden nur die Benutzerdaten in der angegebenen Revision gelöscht, und die Journalsequenz und die Metadaten des Dokuments bleiben unverändert. Nachdem eine Revision geschwärzt wurde, werden die Benutzerdaten in der Revision (dargestellt durch die `data` Struktur) durch ein neues Feld ersetzt. `dataHash` Der Wert dieses Feldes ist der [Amazon Ion-Hash](ion.md) der entfernten `data` Struktur. Weitere Informationen und ein Beispiel für einen Schwärzungsvorgang finden Sie unter[Revisionen von Dokumenten redigieren](working.redaction.md).

Dadurch behält das Ledger seine allgemeine Datenintegrität bei und bleibt über die bestehenden API-Verifizierungsoperationen kryptografisch überprüfbar. Sie können diese API-Operationen weiterhin wie erwartet verwenden, um einen Digest ([GetDigest](https://docs.aws.amazon.com/qldb/latest/developerguide/API_GetDigest.html)), einen Nachweis ([GetBlock](https://docs.aws.amazon.com/qldb/latest/developerguide/API_GetBlock.html)oder [GetRevision](https://docs.aws.amazon.com/qldb/latest/developerguide/API_GetRevision.html)) anzufordern und dann Ihren Bestätigungsalgorithmus mit den zurückgegebenen Objekten auszuführen.

### Einen Revisions-Hash neu berechnen
<a name="verification.redaction.recalc-hash"></a>

Wenn Sie die Revision eines einzelnen Dokuments überprüfen möchten, indem Sie den Hashwert neu berechnen, müssen Sie unter bestimmten Bedingungen überprüfen, ob die Revision geschwärzt wurde. Wenn die Revision geschwärzt wurde, können Sie den Hashwert verwenden, der in dem Feld angegeben ist. `dataHash` Wenn es nicht redigiert wurde, können Sie den Hash mithilfe des Felds neu berechnen. `data`

Durch diese bedingte Prüfung können Sie redigierte Versionen identifizieren und die entsprechenden Maßnahmen ergreifen. Sie können beispielsweise Datenmanipulationsereignisse zu Überwachungszwecken protokollieren.

## Erste Schritte mit der Überprüfung
<a name="verification.getting-started"></a>

Bevor Sie Daten überprüfen können, müssen Sie vom Ledger einen Digest anfordern und für später speichern. Jede Dokumentversion, die vor dem neuesten im Digest enthaltenen Block festgeschrieben wurde, ist für die Überprüfung anhand des Digests qualifiziert.

Anschließend fordern Sie von Amazon QLDB einen Nachweis für eine qualifizierte Version an, die Sie verifizieren möchten. Mit diesem Nachweis rufen Sie eine clientseitige API zur Neuberechnung des Digest-Hashes beginnend mit dem Hash Ihrer Version auf. Solange der zuvor gespeicherte Digest *außerhalb von QLDB bekannt und vertrauenswürdig* ist, ist die Integrität Ihres Dokuments bewiesen, wenn Ihr neu berechneter Digest-Hash mit dem gespeicherten Digest-Hash übereinstimmt.

**Wichtig**  
Konkret beweisen Sie, dass die Revision des Dokuments zwischen dem Zeitpunkt, an dem Sie diesen Digest gespeichert haben, und dem Zeitpunkt, an dem Sie die Überprüfung durchführen, nicht geändert wurde. Sie können eine Zusammenfassung anfordern und speichern, sobald eine Revision, die Sie später überprüfen möchten, in das Journal übernommen wurde.
Als bewährte Methode empfehlen wir, dass Sie regelmäßig Zusammenfassungen anfordern und diese nicht im Hauptbuch speichern. Legen Sie anhand der Häufigkeit fest, mit der Sie Überarbeitungen in Ihrem Hauptbuch vornehmen, wie häufig Sie Überarbeitungen anfordern.  
Einen ausführlichen AWS Blogbeitrag, in dem der Wert der kryptografischen Überprüfung im Kontext eines realistischen Anwendungsfalls erörtert wird, finden Sie unter [Kryptografische Verifizierung in der realen Welt mit](https://aws.amazon.com/blogs/database/real-world-cryptographic-verification-with-amazon-qldb/) Amazon QLDB.

 step-by-stepAnleitungen dazu, wie Sie einen Digest aus Ihrem Ledger anfordern und anschließend Ihre Daten verifizieren können, finden Sie im Folgenden:
+ [Schritt 1: Einen Digest in QLDB anfordern](verification.digest.md)
+ [Schritt 2: Verifizierung Ihrer Daten in QLDB](verification.verify.md)

# Schritt 1: Einen Digest in QLDB anfordern
<a name="verification.digest"></a>

**Wichtig**  
Hinweis zum Ende des Supports: Bestandskunden können Amazon QLDB bis zum Ende des Supports am 31.07.2025 nutzen. Weitere Informationen finden Sie unter [Migrieren eines Amazon QLDB-Ledgers zu Amazon](https://aws.amazon.com/blogs/database/migrate-an-amazon-qldb-ledger-to-amazon-aurora-postgresql/) Aurora PostgreSQL.

Amazon QLDB bietet eine API, mit der Sie eine Zusammenfassung anfordern können, die den aktuellen *Tipp* des Journals in Ihrem Hauptbuch abdeckt. Der Tipp des Journals bezieht sich auf den letzten festgeschriebenen Block zu dem Zeitpunkt, zu dem QLDB Ihre Anfrage erhält. Sie können das AWS-Managementkonsole, ein AWS SDK oder das AWS Command Line Interface (AWS CLI) verwenden, um eine Zusammenfassung zu erhalten.

**Topics**
+ [AWS-Managementkonsole](#verification.digest.con)
+ [QLDB-API](#verification.digest.api)

## AWS-Managementkonsole
<a name="verification.digest.con"></a>

Gehen Sie wie folgt vor, um über die QLDB-Konsole einen Digest anzufordern.

**So fordern Sie einen Digest an (Konsole)**

1. [Melden Sie sich bei der AWS-Managementkonsole an und öffnen Sie die Amazon QLDB-Konsole unter https://console.aws.amazon.com /qldb.](https://console.aws.amazon.com/qldb)

1. Wählen Sie im Navigationsbereich **Ledgers** aus.

1. Wählen Sie in der Liste der Ledgers den Ledger-Namen, für den Sie einen Digest anfordern möchten.

1. Wählen Sie **Get digest (Digest abrufen)**. Dasm Dialogfeld **Get digest (Digest abrufen)** zeigt die folgenden Digest-Details:
   + **Digest** — Der SHA-256-Hashwert des Digests, den Sie angefordert haben.
   + **Adresse des Digest-Tipps — Die** letzte Blockposition im Journal, auf die sich der von Ihnen angeforderte Digest bezieht. Eine Adresse hat die folgenden zwei Felder:
     + `strandId`— Die eindeutige ID des Journalstrangs, der den Block enthält.
     + `sequenceNo`— Die Indexnummer, die die Position des Blocks innerhalb des Strangs angibt.
   + **Ledger** — Der Ledger-Name, für den Sie einen Digest angefordert haben.
   + **Datum** — Der Zeitstempel, zu dem Sie den Digest angefordert haben.

1. Überprüfen Sie die Digest-Informationen. Wählen Sie dann **Speichern**. Sie können den Standard-Dateinamen behalten oder einen neuen Namen eingeben.
**Anmerkung**  
Möglicherweise stellen Sie fest, dass sich Ihre Digest-Hash-Adresswerte ändern, auch wenn Sie keine Daten in Ihrem Ledger ändern. Dies liegt daran, dass die Konsole den Systemkatalog des Ledgers jedes Mal abruft, wenn Sie eine Abfrage im *PartiQL-Editor* ausführen. Hierbei handelt es sich um eine Read-Transaktion, die an das Journal übertragen wird. Dies führt dazu, dass sich die neueste Block-Adresse ändert.

   Dieser Schritt speichert eine Klartext-Datei mit Inhalten im [Amazon Ion](ion.md)-Format. Die Datei verfügt über die Dateinamenerweiterung `.ion.txt` und enthält alle Digest-Informationen, die im vorherigen Dialogfeld aufgelistet wurden. Im Folgenden finden Sie ein Beispiel für die Inhalte einer Digest-Datei. Die Reihenfolge der Felder kann je nach Browser variieren.

   ```
   {
     "digest": "42zaJOfV8iGutVGNaIuzQWhD5Xb/5B9lScHnvxPXm9E=",
     "digestTipAddress": "{strandId:\"BlFTjlSXze9BIh1KOszcE3\",sequenceNo:73}",
     "ledger": "my-ledger",
     "date": "2019-04-17T16:57:26.749Z"
   }
   ```

1. Speichern Sie diese Datei an einem Ort, an dem Sie später darauf zugreifen können. Später können Sie diese Datei verwenden, um die Revision eines Dokuments anhand dieser Datei zu überprüfen.
**Wichtig**  
Die Dokumentrevision, die Sie zu einem späteren Zeitpunkt überprüfen, muss von dem von Ihnen gespeicherten Digest abgedeckt werden. Das bedeutet, dass die Sequenznummer der Adresse des Dokuments kleiner oder gleich der Sequenznummer der **Digest tip address (Digest-Tip-Adresse)** sein muss.

## QLDB-API
<a name="verification.digest.api"></a>

Sie können auch einen Digest aus Ihrem Ledger anfordern, indem Sie die Amazon QLDB-API mit einem AWS SDK oder dem verwenden. AWS CLI Die QLDB-API bietet den folgenden Vorgang für die Verwendung durch Anwendungsprogramme:
+ [GetDigest](https://docs.aws.amazon.com/qldb/latest/developerguide/API_GetDigest.html)— Gibt den Digest eines Ledgers für den letzten festgeschriebenen Block im Journal zurück. Die Antwort enthält einen 256-Bit-Hashwert und eine Blockadresse.

*Informationen zum Anfordern eines Digests mithilfe von finden Sie in der AWS CLI Befehlsreferenz unter dem Befehl [get-digest](https://docs.aws.amazon.com/cli/latest/reference/qldb/get-digest.html).AWS CLI *

### Beispielanwendung
<a name="verification.digest.api.sample"></a>

[Java-Codebeispiele finden Sie im GitHub Repository aws-samples/ -java. amazon-qldb-dmv-sample](https://github.com/aws-samples/amazon-qldb-dmv-sample-java) Anweisungen zum Herunterladen und Installieren dieser Beispielanwendung finden Sie unter [Installation der Amazon QLDB-Java-Beispielanwendung](sample-app.java.md). Bevor Sie eine Zusammenfassung anfordern, stellen Sie sicher, dass Sie die Schritte 1—3 unter So erstellen Sie ein Musterbuch und laden [Java-Lernprogramm](getting-started.java.tutorial.md) es mit Beispieldaten.

Der Tutorial-Code in der Klasse [GetDigest](https://github.com/aws-samples/amazon-qldb-dmv-sample-java/blob/master/src/main/java/software/amazon/qldb/tutorial/GetDigest.java) bietet ein Beispiel für die Anforderung eines Digests aus dem Beispiel-Ledger `vehicle-registration`.

Zum Überprüfen einer Dokumentrevision mithilfe des gespeicherten Digests fahren Sie mit [Schritt 2: Verifizierung Ihrer Daten in QLDB](verification.verify.md) fort.

# Schritt 2: Verifizierung Ihrer Daten in QLDB
<a name="verification.verify"></a>

**Wichtig**  
Hinweis zum Ende des Supports: Bestandskunden können Amazon QLDB bis zum Ende des Supports am 31.07.2025 nutzen. Weitere Informationen finden Sie unter [Migrieren eines Amazon QLDB-Ledgers zu Amazon](https://aws.amazon.com/blogs/database/migrate-an-amazon-qldb-ledger-to-amazon-aurora-postgresql/) Aurora PostgreSQL.

Amazon QLDB bietet eine API, um einen Nachweis für eine bestimmte Dokument-ID und den zugehörigen Block anzufordern. Sie müssen zudem die Tip-Adresse eines zuvor gespeicherten Digests angeben, wie in [Schritt 1: Einen Digest in QLDB anfordern](verification.digest.md) beschrieben. Sie können das AWS-Managementkonsole, ein AWS SDK oder das verwenden, um einen AWS CLI Nachweis zu erhalten.

Anschließend können Sie den von QLDB zurückgegebenen Nachweis verwenden, um die Dokumentrevision mithilfe einer clientseitigen API mit dem gespeicherten Digest zu überprüfen. Dadurch haben Sie die Kontrolle über den zum Überprüfen der Daten verwendeten Algorithmus.

**Topics**
+ [AWS-Managementkonsole](#verification.verify.con)
+ [QLDB-API](#verification.verify.api)

## AWS-Managementkonsole
<a name="verification.verify.con"></a>

In diesem Abschnitt werden die Schritte zur Überprüfung einer Dokumentrevision anhand eines zuvor gespeicherten Digests mithilfe der Amazon QLDB-Konsole beschrieben.

Stellen Sie vor dem Starten sicher, dass Sie die Schritte in [Schritt 1: Einen Digest in QLDB anfordern](verification.digest.md) befolgen. Die Überprüfung erfordert einen zuvor gespeicherten Digest, der die Revision abdeckt, die Sie überprüfen möchten.

**So überprüfen Sie eine Dokumentrevision (Konsole)**

1. [Öffnen Sie die Amazon QLDB-Konsole unter https://console.aws.amazon.com /qldb.](https://console.aws.amazon.com/qldb)

1. Führen Sie zunächst eine Abfrage Ihres Ledgers für die `id` und die `blockAddress` der Revision durch, die Sie verifizieren möchten. Diese Felder sind in den Metadaten des Dokuments enthalten. Sie können diese Abfrage in der *bestätigten Ansicht* vornehmen.

   Das Dokument `id` ist eine vom System zugewiesene eindeutige ID-Zeichenfolge. Das `blockAddress` ist eine Ionen-Struktur, die den Blockspeicherort angibt, an dem die Revision übertragen wurde.

   Wählen Sie im Navigationsbereich **PartiQL-Editor** aus.

1. Wählen Sie den Namen des Ledgers, in dem Sie eine Revision überprüfen möchten.

1. Geben Sie im Abfrage-Editor-Fenster eine `SELECT`-Anweisung in der folgenden Syntax ein und wählen Sie dann **Run (Ausführen)**.

   ```
   SELECT metadata.id, blockAddress FROM _ql_committed_table_name
   WHERE criteria
   ```

   Die folgende Abfrage gibt beispielsweise ein Dokument aus der `VehicleRegistration` Tabelle im Beispielbuch zurück, das in erstellt wurde. [Erste Schritte mit der Amazon QLDB-Konsole](getting-started.md)

   ```
   SELECT r.metadata.id, r.blockAddress FROM _ql_committed_VehicleRegistration AS r 
   WHERE r.data.VIN = 'KM8SRDHF6EU074761'
   ```

1. Kopieren und speichern Sie die `id`- und `blockAddress`-Werte, die Ihre Abfrage zurückgibt. Achten Sie darauf, dass Sie die doppelten Anführungszeichen für das `id`-Feld auslassen. In [Amazon Ion](ion.md) werden Zeichenfolge-Datentypen in doppelte Anführungszeichen gesetzt. Beispielsweise müssen Sie nur den alphanumerischen Text im folgenden Codeausschnitt kopieren.

   `"LtMNJYNjSwzBLgf7sLifrG"`

1. Nachdem Sie eine Dokumentrevision ausgewählt haben, können Sie damit beginnen, sie zu überprüfen.

   Wählen Sie im Navigationsbereich die Option **Verification (Überprüfung)** aus.

1. Geben Sie auf dem Formular **Verify document (Dokument überprüfen)** unter **Specify the document that you want to verify (Das Dokument angeben, das Sie überprüfen möchten)** die folgenden Eingabeparameter ein:
   + **Ledger** — Das Ledger, in dem Sie eine Revision verifizieren möchten.
   + **Blockadresse** — Der von Ihrer Abfrage in Schritt 4 zurückgegebene `blockAddress` Wert.
   + **Dokument-ID** — Der von Ihrer Abfrage in Schritt 4 zurückgegebene `id` Wert.

1. Wählen Sie unter **Specify the document that you want to verify (Das Dokument angeben, das Sie überprüfen möchten)** den Digest aus, den Sie zuvor gespeichert haben, indem Sie **Choose digest (Digest auswählen)** auswählen. Wenn die Datei gültig ist, werden alle Digest-Felder auf Ihrer Konsole automatisch gefüllt. Sie können die folgenden Werte auch manuell direkt aus Ihrer Digest-Datei kopieren und einfügen:
   + **Digest** — Der `digest` Wert aus Ihrer Digest-Datei.
   + **Adresse des Digest-Tipps** — Der `digestTipAddress` Wert aus Ihrer Digest-Datei.

1. Überprüfen Sie Ihre Dokument- und Digest-Eingabeparameter und wählen Sie anschließend **Verify (Überprüfen)** aus.

   Die Konsole automatisiert zwei Schritte für Sie:

   1. Fordern Sie von QLDB einen Nachweis für Ihr angegebenes Dokument an.

   1. Verwenden Sie den von QLDB zurückgegebenen Nachweis, um eine clientseitige API aufzurufen, die Ihre Dokumentenrevision anhand des bereitgestellten Digest überprüft. Um diesen Überprüfungsalgorithmus zu untersuchen, laden Sie das Codebeispiel im folgenden Abschnitt [QLDB-API](#verification.verify.api) herunter.

   Die Konsole zeigt die Ergebnisse Ihrer Anforderung in der Karte **Verification results (Überprüfungsergebnisse)** an. Weitere Informationen finden Sie unter [Ergebnisse der Überprüfung](verification.results.md).

## QLDB-API
<a name="verification.verify.api"></a>

Sie können eine Dokumentrevision auch überprüfen, indem Sie die Amazon QLDB-API mit einem AWS SDK oder dem verwenden. AWS CLI Die QLDB-API bietet die folgenden Operationen zur Verwendung durch Anwendungsprogramme:
+ `GetDigest`— Gibt den Digest eines Ledgers für den letzten festgeschriebenen Block im Journal zurück. Die Antwort enthält einen 256-Bit-Hashwert und eine Blockadresse.
+ `GetBlock`— Gibt ein Blockobjekt an einer angegebenen Adresse in einem Journal zurück. Gibt auch einen Nachweis für den angegebenen Block zur Überprüfung zurück, wenn `DigestTipAddress` bereitgestellt wird.
+ `GetRevision`— Gibt ein Revisionsdatenobjekt für eine angegebene Dokument-ID und Blockadresse zurück. Gibt auch einen Nachweis der angegebenen Revision zur Überprüfung zurück, falls `DigestTipAddress` bereitgestellt wird.

Eine vollständige Beschreibung dieser API-Vorgänge finden Sie unter [Amazon QLDB API-Referenz](api-reference.md).

Informationen zur Überprüfung von Daten mithilfe von finden Sie in der [AWS CLI Befehlsreferenz. AWS CLI](https://docs.aws.amazon.com/cli/latest/reference/qldb/index.html)

### Beispielanwendung
<a name="verification.verify.api.sample"></a>

Java-Codebeispiele finden Sie im GitHub Repository [amazon-qldb-dmv-sampleaws-samples/](https://github.com/aws-samples/amazon-qldb-dmv-sample-java) -java. Anweisungen zum Herunterladen und Installieren dieser Beispielanwendung finden Sie unter [Installation der Amazon QLDB-Java-Beispielanwendung](sample-app.java.md). Bevor Sie eine Überprüfung durchführen, stellen Sie sicher, dass Sie die Schritte 1—3 unter So erstellen Sie ein Musterbuch und laden es mit Beispieldaten. [Java-Lernprogramm](getting-started.java.tutorial.md)

Der Tutorial-Code in der Klasse [GetRevision](https://github.com/aws-samples/amazon-qldb-dmv-sample-java/blob/master/src/main/java/software/amazon/qldb/tutorial/GetRevision.java) bietet ein Beispiel für das Anfordern eines Nachweises für eine Dokumentrevision und das anschließende Überprüfen dieser Revision. Diese Klasse führt die folgenden Schritte aus:

1. Fordert einen neuen Digest aus dem Beispiel-Ledger `vehicle-registration` an.

1. Fordert einen Nachweis für eine Beispiel-Dokumentrevision aus der Tabelle `VehicleRegistration` im Ledger `vehicle-registration` an.

1. Überprüft die Beispiel-Revision mithilfe des zurückgegebenen Digests und des Nachweises.

# Ergebnisse der Überprüfung
<a name="verification.results"></a>

**Wichtig**  
Hinweis zum Ende des Supports: Bestandskunden können Amazon QLDB bis zum Ende des Supports am 31.07.2025 nutzen. Weitere Informationen finden Sie unter [Migrieren eines Amazon QLDB-Ledgers zu Amazon](https://aws.amazon.com/blogs/database/migrate-an-amazon-qldb-ledger-to-amazon-aurora-postgresql/) Aurora PostgreSQL.

In diesem Abschnitt werden die Ergebnisse beschrieben, die von einer Amazon QLDB-Datenverifizierungsanfrage auf dem zurückgegeben wurden. AWS-Managementkonsole Detaillierte Anweisungen zum Übermitteln einer Überprüfungsanfrage finden Sie unter [Schritt 2: Verifizierung Ihrer Daten in QLDB](verification.verify.md).

Auf der **Bestätigungsseite** der QLDB-Konsole werden die Ergebnisse Ihrer Anfrage auf der Karte mit den **Überprüfungsergebnissen** angezeigt. Auf der Registerkarte „**Nachweis**“ wird der Inhalt des von QLDB für die von Ihnen angegebene Dokumentrevision und Zusammenfassung zurückgesandten Proofs angezeigt. Er enthält folgende Details:
+ **Revisions-Hash** — Der SHA-256-Wert, der die Dokumentenrevision, die Sie überprüfen, eindeutig darstellt.
+ **Proof-Hashes** — Die von QLDB bereitgestellte geordnete Liste von Hashes, die zur Neuberechnung des angegebenen Digest verwendet werden. Die Konsole beginnt mit dem **Revision hash (Revisions-Hash)** und kombiniert ihn der Reihe nach mit jedem Nachweis-Hash, bis sie mit einem neu berechneten Digest endet.

  Die Liste ist standardmäßig ausgeblendet. Sie können sie erweitern, um die Hash-Werte anzuzeigen. Optional können Sie die Hash-Berechnungen selbst testen, indem Sie die in [Verwenden Sie einen Nachweis, um Ihren Digest neu zu berechnen](#verification.results.recalc) beschriebenen Schritte ausführen.
+ **Berechneter Digest** **— Der Hash, der sich aus der Reihe von Hash-Berechnungen ergab, die für den **Revisions-Hash** durchgeführt wurden.** Wenn dieser Wert dem zuvor gespeicherten **Digest** entspricht, ist die Überprüfung erfolgreich.

Auf der Registerkarte **Block** wird der Inhalt des Blocks angezeigt, der die Revision enthält, die Sie überprüfen. Er enthält folgende Details:
+ **Transaktions-ID** — Die eindeutige ID der Transaktion, die diesen Block festgeschrieben hat.
+ **Transaktionszeit** — Der Zeitstempel, zu dem dieser Block an den Strang übergeben wurde.
+ **Block-Hash** — Der SHA-256-Wert, der diesen Block und seinen gesamten Inhalt eindeutig repräsentiert.
+ **Blockadresse** — Der Ort im Journal Ihres Ledgers, an dem dieser Block festgeschrieben wurde. Eine Adresse hat die folgenden zwei Felder:
  + **Strang-ID** — Die eindeutige ID des Journalstrangs, der diesen Block enthält.
  + **Sequenznummer** — Die Indexnummer, die die Position dieses Blocks innerhalb des Strangs angibt.
+ **Statements** — Die PartiQL-Anweisungen, die ausgeführt wurden, um Einträge in diesem Block festzuschreiben.
**Anmerkung**  
Wenn Sie parametrisierte Anweisungen programmgesteuert ausführen, werden sie in Ihren Journalblöcken mit Bind-Parametern anstelle der Literaldaten aufgezeichnet. Sie können beispielsweise die folgende Anweisung in einem Journal-Block anzeigen, wobei das Fragezeichen (`?`) ein Variablenplatzhalter für den Dokumentinhalt ist.  

  ```
  INSERT INTO Vehicle ?
  ```
+ **Dokumenteinträge** — Die Dokumentüberarbeitungen, die in diesem Block übernommen wurden.

Wenn Ihre Anfrage die Dokumentrevision nicht überprüfen konnte, finden Sie unter [Häufige Fehler bei der Überprüfung](verification.errors.md) weitere Informationen zu möglichen Ursachen.

## Verwenden Sie einen Nachweis, um Ihren Digest neu zu berechnen
<a name="verification.results.recalc"></a>

Nachdem QLDB einen Nachweis für Ihre Anfrage zur Dokumentenverifizierung zurückgesendet hat, können Sie versuchen, die Hash-Berechnungen selbst durchzuführen. In diesem Abschnitt werden die allgemeinen Schritte zur Neuberechnung Ihres Digest anhand des bereitgestellten Nachweises beschrieben.

Koppeln Sie zunächst Ihren **Revision hash (Revisions-Hash)** mit dem ersten Hash in der Liste **Proof hashes (Nachweis-Hashes)**. Führen Sie dann die folgenden Schritte aus:

1. Sortieren Sie die zwei Hashes. Vergleichen Sie die Hashes anhand ihrer *signierten* Bytewerte in Little-Endian-Reihenfolge.

1. Verketten Sie die beiden Hashes in sortierter Reihenfolge.

1. Führen Sie für das verkettete Paar einen Hash mit einem SHA-256-Hash-Generator durch.

1. Kombinieren Sie Ihren neuen Hash mit dem nächsten Hash im Proof und wiederholen Sie die Schritte 1—3. Nach dem Verarbeiten des letzten Nachweises ist Ihr neuer Hash der neu berechnete Digest.

Wenn Ihr neu berechneter Digest mit dem zuvor gespeicherten Digest übereinstimmt, ist das Dokument erfolgreich überprüft.

Ein step-by-step Tutorial mit Codebeispielen, die diese Überprüfungsschritte demonstrieren, finden Sie unter. [Tutorial: Daten mit einem AWS SDK verifizieren](verification.tutorial-block-hash.md)

# Tutorial: Daten mit einem AWS SDK verifizieren
<a name="verification.tutorial-block-hash"></a>

**Wichtig**  
Hinweis zum Ende des Supports: Bestandskunden können Amazon QLDB bis zum Ende des Supports am 31.07.2025 nutzen. Weitere Informationen finden Sie unter [Migrieren eines Amazon QLDB-Ledgers zu Amazon](https://aws.amazon.com/blogs/database/migrate-an-amazon-qldb-ledger-to-amazon-aurora-postgresql/) Aurora PostgreSQL.

In diesem Tutorial verifizieren Sie einen Dokumentrevisions-Hash und einen Journal-Block-Hash in einem Amazon QLDB-Ledger, indem Sie die QLDB-API über ein SDK verwenden. AWS Sie verwenden den QLDB-Treiber auch, um die Dokumentrevision abzufragen.

Stellen Sie sich ein Beispiel vor, bei dem Sie über eine Dokumentenrevision verfügen, die Daten für ein Fahrzeug mit der Fahrzeugidentifikationsnummer (VIN) von enthält. `KM8SRDHF6EU074761` Die Version des Dokuments befindet sich in einer `VehicleRegistration` Tabelle, die sich in einem Hauptbuch mit dem Namen `vehicle-registration` befindet. Angenommen, Sie möchten die Integrität sowohl der Dokumentenversion für dieses Fahrzeug als auch des Journalblocks, der die Revision enthält, überprüfen.

**Anmerkung**  
Einen ausführlichen AWS Blogbeitrag, in dem der Wert der kryptografischen Überprüfung im Kontext eines realistischen Anwendungsfalls erörtert wird, finden Sie unter [Kryptografische Verifizierung in der realen Welt mit](https://aws.amazon.com/blogs/database/real-world-cryptographic-verification-with-amazon-qldb/) Amazon QLDB.

**Topics**
+ [Voraussetzungen](#verification.tutorial.prereqs)
+ [Schritt 1: Fordern Sie eine Zusammenfassung an](#verification.tutorial.step-1)
+ [Schritt 2: Fragen Sie die Version des Dokuments ab](#verification.tutorial.step-2)
+ [Schritt 3: Fordern Sie einen Nachweis für die Revision an](#verification.tutorial.step-3)
+ [Schritt 4: Berechnen Sie den Digest aus der Revision neu](#verification.tutorial.step-4)
+ [Schritt 5: Fordern Sie einen Nachweis für den Journalblock an](#verification.tutorial.step-5)
+ [Schritt 6: Berechne den Digest aus dem Block neu](#verification.tutorial.step-6)
+ [Führen Sie das vollständige Codebeispiel aus](#verification.tutorial.full)

## Voraussetzungen
<a name="verification.tutorial.prereqs"></a>

Bevor Sie beginnen, stellen Sie sicher, dass die folgende Voraussetzung erfüllt ist:

1. Richten Sie den QLDB-Treiber für eine Sprache Ihrer Wahl ein, indem Sie die entsprechenden Voraussetzungen unter erfüllen. [Erste Schritte mit dem Amazon QLDB-Treiber](getting-started-driver.md) Dazu gehören die Registrierung für AWS, die Gewährung des programmatischen Zugriffs für die Entwicklung und die Konfiguration Ihrer Entwicklungsumgebung.

1. Folgen Sie den Schritten 1—2 unter[Erste Schritte mit der Amazon QLDB-Konsole](getting-started.md), um ein Ledger mit einem Namen zu erstellen `vehicle-registration` und es mit vordefinierten Beispieldaten zu laden.

Sehen Sie sich als Nächstes die folgenden Schritte an, um zu erfahren, wie die Überprüfung funktioniert, und führen Sie dann das vollständige Codebeispiel von Anfang bis Ende aus.

## Schritt 1: Fordern Sie eine Zusammenfassung an
<a name="verification.tutorial.step-1"></a>

Bevor Sie Daten verifizieren können, müssen Sie zunächst eine Zusammenfassung aus Ihrem Hauptbuch `vehicle-registration` anfordern, die Sie später verwenden können.

------
#### [ Java ]

```
// Get a digest
GetDigestRequest digestRequest = new GetDigestRequest().withName(ledgerName);
GetDigestResult digestResult = client.getDigest(digestRequest);

java.nio.ByteBuffer digest = digestResult.getDigest();

// expectedDigest is the buffer we will use later to compare against our calculated digest
byte[] expectedDigest = new byte[digest.remaining()];
digest.get(expectedDigest);
```

------
#### [ .NET ]

```
// Get a digest
GetDigestRequest getDigestRequest = new GetDigestRequest
{
    Name = ledgerName
};
GetDigestResponse getDigestResponse = client.GetDigestAsync(getDigestRequest).Result;

// expectedDigest is the buffer we will use later to compare against our calculated digest
MemoryStream digest = getDigestResponse.Digest;
byte[] expectedDigest = digest.ToArray();
```

------
#### [ Go ]

```
// Get a digest
currentLedgerName := ledgerName
input := qldb.GetDigestInput{Name: &currentLedgerName}
digestOutput, err := client.GetDigest(&input)
if err != nil {
    panic(err)
}

// expectedDigest is the buffer we will later use to compare against our calculated digest
expectedDigest := digestOutput.Digest
```

------
#### [ Node.js ]

```
// Get a digest
const getDigestRequest: GetDigestRequest = {
    Name: ledgerName
};
const getDigestResponse: GetDigestResponse = await qldbClient.getDigest(getDigestRequest).promise();

// expectedDigest is the buffer we will later use to compare against our calculated digest
const expectedDigest: Uint8Array = <Uint8Array>getDigestResponse.Digest;
```

------
#### [ Python ]

```
# Get a digest
get_digest_response = qldb_client.get_digest(Name=ledger_name)

# expected_digest is the buffer we will later use to compare against our calculated digest
expected_digest = get_digest_response.get('Digest')
digest_tip_address = get_digest_response.get('DigestTipAddress')
```

------

## Schritt 2: Fragen Sie die Version des Dokuments ab
<a name="verification.tutorial.step-2"></a>

Verwenden Sie den QLDB-Treiber, um die Blockadressen, Hashes und Dokumente abzufragen, IDs die mit VIN verknüpft sind. `KM8SRDHF6EU074761`

------
#### [ Java ]

```
// Retrieve info for the given vin's document revisions
Result result = driver.execute(txn -> {
    final String query = String.format("SELECT blockAddress, hash, metadata.id FROM _ql_committed_%s WHERE data.VIN = '%s'", tableName, vin);
    return txn.execute(query);
});
```

------
#### [ .NET ]

```
// Retrieve info for the given vin's document revisions
var result = driver.Execute(txn => {
    string query = $"SELECT blockAddress, hash, metadata.id FROM _ql_committed_{tableName} WHERE data.VIN = '{vin}'";
    return txn.Execute(query);
});
```

------
#### [ Go ]

```
// Retrieve info for the given vin's document revisions
result, err := driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) {
    statement := fmt.Sprintf(
            "SELECT blockAddress, hash, metadata.id FROM _ql_committed_%s WHERE data.VIN = '%s'",
            tableName,
            vin)
    result, err := txn.Execute(statement)
    if err != nil {
        return nil, err
    }

    results := make([]map[string]interface{}, 0)

    // Convert the result set into a map
    for result.Next(txn) {
        var doc map[string]interface{}
        err := ion.Unmarshal(result.GetCurrentData(), &doc)
        if err != nil {
            return nil, err
        }
        results = append(results, doc)
    }
    return results, nil
})
if err != nil {
    panic(err)
}
resultSlice := result.([]map[string]interface{})
```

------
#### [ Node.js ]

```
const result: dom.Value[] = await driver.executeLambda(async (txn: TransactionExecutor): Promise<dom.Value[]> => {
    const query: string = `SELECT blockAddress, hash, metadata.id FROM _ql_committed_${tableName} WHERE data.VIN = '${vin}'`;
    const queryResult: Result = await txn.execute(query);
    return queryResult.getResultList();
});
```

------
#### [ Python ]

```
def query_doc_revision(txn):
    query = "SELECT blockAddress, hash, metadata.id FROM _ql_committed_{} WHERE data.VIN = '{}'".format(table_name, vin)
    return txn.execute_statement(query)

# Retrieve info for the given vin's document revisions
result = qldb_driver.execute_lambda(query_doc_revision)
```

------

## Schritt 3: Fordern Sie einen Nachweis für die Revision an
<a name="verification.tutorial.step-3"></a>

Gehen Sie die Abfrageergebnisse nacheinander durch und verwenden Sie jede Blockadresse und Dokument-ID zusammen mit dem Ledger-Namen, um eine `GetRevision` Anfrage einzureichen. *Um einen Nachweis für die Revision zu erhalten, müssen Sie auch die Tipp-Adresse aus dem zuvor gespeicherten Digest angeben.* Dieser API-Vorgang gibt ein Objekt zurück, das die Revision des Dokuments und den Nachweis für die Revision enthält.

Hinweise zur Revisionsstruktur und ihrem Inhalt finden Sie unter[Metadaten von Dokumenten werden abgefragt](working.metadata.md).

------
#### [ Java ]

```
for (IonValue ionValue : result) {
    IonStruct ionStruct = (IonStruct)ionValue;

    // Get the requested fields
    IonValue blockAddress = ionStruct.get("blockAddress");
    IonBlob hash = (IonBlob)ionStruct.get("hash");
    String metadataId = ((IonString)ionStruct.get("id")).stringValue();

    System.out.printf("Verifying document revision for id '%s'%n", metadataId);

    String blockAddressText = blockAddress.toString();

    // Submit a request for the revision
    GetRevisionRequest revisionRequest = new GetRevisionRequest()
            .withName(ledgerName)
            .withBlockAddress(new ValueHolder().withIonText(blockAddressText))
            .withDocumentId(metadataId)
            .withDigestTipAddress(digestResult.getDigestTipAddress());

    // Get a result back
    GetRevisionResult revisionResult = client.getRevision(revisionRequest);

    ...
}
```

------
#### [ .NET ]

```
foreach (IIonValue ionValue in result)
{
    IIonStruct ionStruct = ionValue;

    // Get the requested fields
    IIonValue blockAddress = ionStruct.GetField("blockAddress");
    IIonBlob hash = ionStruct.GetField("hash");
    String metadataId = ionStruct.GetField("id").StringValue;

    Console.WriteLine($"Verifying document revision for id '{metadataId}'");

    // Use an Ion Reader to convert block address to text
    IIonReader reader = IonReaderBuilder.Build(blockAddress);
    StringWriter sw = new StringWriter();
    IIonWriter textWriter = IonTextWriterBuilder.Build(sw);
    textWriter.WriteValues(reader);
    string blockAddressText = sw.ToString();

    // Submit a request for the revision
    GetRevisionRequest revisionRequest = new GetRevisionRequest
    {
        Name = ledgerName,
        BlockAddress = new ValueHolder
        {
            IonText = blockAddressText
        },
        DocumentId = metadataId,
        DigestTipAddress = getDigestResponse.DigestTipAddress
    };
    
    // Get a response back
    GetRevisionResponse revisionResponse = client.GetRevisionAsync(revisionRequest).Result;

    ...
}
```

------
#### [ Go ]

```
for _, value := range resultSlice {
    // Get the requested fields
    ionBlockAddress, err := ion.MarshalText(value["blockAddress"])
    if err != nil {
        panic(err)
    }
    blockAddress := string(ionBlockAddress)
    metadataId := value["id"].(string)
    documentHash := value["hash"].([]byte)

    fmt.Printf("Verifying document revision for id '%s'\n", metadataId)

    // Submit a request for the revision
    revisionInput := qldb.GetRevisionInput{
        BlockAddress:     &qldb.ValueHolder{IonText: &blockAddress},
        DigestTipAddress: digestOutput.DigestTipAddress,
        DocumentId:       &metadataId,
        Name:             &currentLedgerName,
    }

    // Get a result back
    revisionOutput, err := client.GetRevision(&revisionInput)
    if err != nil {
        panic(err)
    }

    ...
}
```

------
#### [ Node.js ]

```
for (let value of result) {
    // Get the requested fields
    const blockAddress: dom.Value = value.get("blockAddress");
    const hash: dom.Value = value.get("hash");
    const metadataId: string = value.get("id").stringValue();

    console.log(`Verifying document revision for id '${metadataId}'`);

    // Submit a request for the revision
    const revisionRequest: GetRevisionRequest = {
        Name: ledgerName,
        BlockAddress: {
            IonText: dumpText(blockAddress)
        },
        DocumentId: metadataId,
        DigestTipAddress: getDigestResponse.DigestTipAddress
    };

    // Get a response back
    const revisionResponse: GetRevisionResponse = await qldbClient.getRevision(revisionRequest).promise();

    ...
}
```

------
#### [ Python ]

```
for value in result:
    # Get the requested fields
    block_address = value['blockAddress']
    document_hash = value['hash']
    metadata_id = value['id']

    print("Verifying document revision for id '{}'".format(metadata_id))

    # Submit a request for the revision and get a result back
    proof_response = qldb_client.get_revision(Name=ledger_name,
                                              BlockAddress=block_address_to_dictionary(block_address),
                                              DocumentId=metadata_id,
                                              DigestTipAddress=digest_tip_address)
```

------

Rufen Sie dann den Nachweis für die angeforderte Revision ab.

Die QLDB-API gibt den Beweis als Zeichenkettendarstellung der geordneten Liste von Knoten-Hashes zurück. Um diese Zeichenfolge in eine Liste der binären Repräsentation der Knoten-Hashes umzuwandeln, können Sie einen Ion-Reader aus der Amazon Ion-Bibliothek verwenden. Weitere Informationen zur Verwendung der Ion-Bibliothek finden Sie im [Amazon Ion Cookbook](http://amzn.github.io/ion-docs/guides/cookbook.html#reading-and-writing-ion-data).

------
#### [ Java ]

In diesem Beispiel verwenden Sie, `IonReader` um die Binärkonvertierung durchzuführen.

```
String proofText = revisionResult.getProof().getIonText();

// Take the proof and convert it to a list of byte arrays
List<byte[]> internalHashes = new ArrayList<>();
IonReader reader = SYSTEM.newReader(proofText);
reader.next();
reader.stepIn();
while (reader.next() != null) {
    internalHashes.add(reader.newBytes());
}
```

------
#### [ .NET ]

In diesem Beispiel verwenden Sie, `IonLoader` um den Beweis in ein Ion-Datagramm zu laden.

```
string proofText = revisionResponse.Proof.IonText;
IIonDatagram proofValue = IonLoader.Default.Load(proofText);
```

------
#### [ Go ]

In diesem Beispiel verwenden Sie einen Ion-Reader, um den Beweis in eine Binärdatei umzuwandeln und die Node-Hash-Liste des Beweises durchzugehen.

```
proofText := revisionOutput.Proof.IonText

// Use ion.Reader to iterate over the proof's node hashes
reader := ion.NewReaderString(*proofText)
// Enter the struct containing node hashes
reader.Next()
if err := reader.StepIn(); err != nil {
    panic(err)
}
```

------
#### [ Node.js ]

In diesem Beispiel verwenden Sie die `load` Funktion, um die Binärkonvertierung durchzuführen.

```
let proofValue: dom.Value = load(revisionResponse.Proof.IonText);
```

------
#### [ Python ]

In diesem Beispiel verwenden Sie die `loads` Funktion, um die Binärkonvertierung durchzuführen.

```
proof_text = proof_response.get('Proof').get('IonText')
proof_hashes = loads(proof_text)
```

------

## Schritt 4: Berechnen Sie den Digest aus der Revision neu
<a name="verification.tutorial.step-4"></a>

Verwenden Sie die Hash-Liste des Proofs, um den Digest neu zu berechnen, beginnend mit dem Revisions-Hash. Solange der zuvor gespeicherte Digest außerhalb von QLDB bekannt und vertrauenswürdig ist, ist die Integrität der Dokumentenrevision bewiesen, wenn der neu berechnete Digest-Hash mit dem gespeicherten Digest-Hash übereinstimmt.

------
#### [ Java ]

```
// Calculate digest
byte[] calculatedDigest = internalHashes.stream().reduce(hash.getBytes(), BlockHashVerification::dot);

boolean verified = Arrays.equals(expectedDigest, calculatedDigest);

if (verified) {
    System.out.printf("Successfully verified document revision for id '%s'!%n", metadataId);
} else {
    System.out.printf("Document revision for id '%s' verification failed!%n", metadataId);
    return;
}
```

------
#### [ .NET ]

```
byte[] documentHash = hash.Bytes().ToArray();
foreach (IIonValue proofHash in proofValue.GetElementAt(0))
{
    // Calculate the digest
    documentHash = Dot(documentHash, proofHash.Bytes().ToArray());
}

bool verified = expectedDigest.SequenceEqual(documentHash);

if (verified)
{
    Console.WriteLine($"Successfully verified document revision for id '{metadataId}'!");
}
else
{
    Console.WriteLine($"Document revision for id '{metadataId}' verification failed!");
    return;
}
```

------
#### [ Go ]

```
// Going through nodes and calculate digest
for reader.Next() {
    val, _ := reader.ByteValue()
    documentHash, err = dot(documentHash, val)
}

// Compare documentHash with the expected digest
verified := reflect.DeepEqual(documentHash, expectedDigest)

if verified {
    fmt.Printf("Successfully verified document revision for id '%s'!\n", metadataId)
} else {
    fmt.Printf("Document revision for id '%s' verification failed!\n", metadataId)
    return
}
```

------
#### [ Node.js ]

```
let documentHash: Uint8Array = hash.uInt8ArrayValue();
proofValue.elements().forEach((proofHash: dom.Value) => {
    // Calculate the digest
    documentHash = dot(documentHash, proofHash.uInt8ArrayValue());
});

let verified: boolean = isEqual(expectedDigest, documentHash);

if (verified) {
    console.log(`Successfully verified document revision for id '${metadataId}'!`);
} else {
    console.log(`Document revision for id '${metadataId}' verification failed!`);
    return;
}
```

------
#### [ Python ]

```
# Calculate digest
calculated_digest = reduce(dot, proof_hashes, document_hash)

verified = calculated_digest == expected_digest
if verified:
    print("Successfully verified document revision for id '{}'!".format(metadata_id))
else:
    print("Document revision for id '{}' verification failed!".format(metadata_id))
```

------

## Schritt 5: Fordern Sie einen Nachweis für den Journalblock an
<a name="verification.tutorial.step-5"></a>

Als Nächstes überprüfen Sie den Journalblock, der die Dokumentrevision enthält.

Verwenden Sie die Blockadresse und die Tipp-Adresse aus dem Digest, die Sie in [Schritt 1](#verification.tutorial.step-1) gespeichert haben, um eine `GetBlock` Anfrage einzureichen. Ähnlich wie bei der `GetRevision` Anfrage in [Schritt 2](#verification.tutorial.step-2) *müssen Sie erneut die Tipp-Adresse aus dem gespeicherten Digest angeben, um einen Nachweis für die Sperre zu erhalten*. Diese API-Operation gibt ein Objekt zurück, das den Block und den Nachweis für den Block enthält.

Hinweise zur Journalblockstruktur und ihrem Inhalt finden Sie unter[Journalinhalte in Amazon QLDB](journal-contents.md).

------
#### [ Java ]

```
// Submit a request for the block
GetBlockRequest getBlockRequest = new GetBlockRequest()
        .withName(ledgerName)
        .withBlockAddress(new ValueHolder().withIonText(blockAddressText))
        .withDigestTipAddress(digestResult.getDigestTipAddress());

// Get a result back
GetBlockResult getBlockResult = client.getBlock(getBlockRequest);
```

------
#### [ .NET ]

```
// Submit a request for the block
GetBlockRequest getBlockRequest = new GetBlockRequest
{
    Name = ledgerName,
    BlockAddress = new ValueHolder
    {
        IonText = blockAddressText
    },
    DigestTipAddress = getDigestResponse.DigestTipAddress
};

// Get a response back
GetBlockResponse getBlockResponse = client.GetBlockAsync(getBlockRequest).Result;
```

------
#### [ Go ]

```
// Submit a request for the block
blockInput := qldb.GetBlockInput{
    Name:             &currentLedgerName,
    BlockAddress:     &qldb.ValueHolder{IonText: &blockAddress},
    DigestTipAddress: digestOutput.DigestTipAddress,
}

// Get a result back
blockOutput, err := client.GetBlock(&blockInput)
if err != nil {
    panic(err)
}
```

------
#### [ Node.js ]

```
// Submit a request for the block
const getBlockRequest: GetBlockRequest = {
    Name: ledgerName,
    BlockAddress: {
        IonText: dumpText(blockAddress)
    },
    DigestTipAddress: getDigestResponse.DigestTipAddress
};

// Get a response back
const getBlockResponse: GetBlockResponse = await qldbClient.getBlock(getBlockRequest).promise();
```

------
#### [ Python ]

```
def block_address_to_dictionary(ion_dict):
"""
Convert a block address from IonPyDict into a dictionary.
Shape of the dictionary must be: {'IonText': "{strandId: <"strandId">, sequenceNo: <sequenceNo>}"}

:type ion_dict: :py:class:`amazon.ion.simple_types.IonPyDict`/str
:param ion_dict: The block address value to convert.

:rtype: dict
:return: The converted dict.
"""
block_address = {'IonText': {}}
if not isinstance(ion_dict, str):
    py_dict = '{{strandId: "{}", sequenceNo:{}}}'.format(ion_dict['strandId'], ion_dict['sequenceNo'])
    ion_dict = py_dict
block_address['IonText'] = ion_dict
return block_address

# Submit a request for the block and get a result back
block_response = qldb_client.get_block(Name=ledger_name, BlockAddress=block_address_to_dictionary(block_address),
                                       DigestTipAddress=digest_tip_address)
```

------

Rufen Sie dann den Block-Hash und den Beweis aus dem Ergebnis ab.

------
#### [ Java ]

In diesem Beispiel verwenden Sie, `IonLoader` um das Blockobjekt in einen `IonDatagram` Container zu laden.

```
String blockText = getBlockResult.getBlock().getIonText();

IonDatagram datagram = SYSTEM.getLoader().load(blockText);
ionStruct = (IonStruct)datagram.get(0);

final byte[] blockHash = ((IonBlob)ionStruct.get("blockHash")).getBytes();
```

Sie verwenden auch`IonLoader`, um den Beweis in einen zu laden`IonDatagram`.

```
proofText = getBlockResult.getProof().getIonText();

// Take the proof and create a list of hash binary data
datagram = SYSTEM.getLoader().load(proofText);
ListIterator<IonValue> listIter = ((IonList)datagram.iterator().next()).listIterator();

internalHashes.clear();
while (listIter.hasNext()) {
    internalHashes.add(((IonBlob)listIter.next()).getBytes());
}
```

------
#### [ .NET ]

In diesem Beispiel laden Sie `IonLoader` den Block und den Beweis jeweils in ein Ionen-Datagramm.

```
string blockText = getBlockResponse.Block.IonText;
IIonDatagram blockValue = IonLoader.Default.Load(blockText);

// blockValue is a IonDatagram, and the first value is an IonStruct containing the blockHash
byte[] blockHash = blockValue.GetElementAt(0).GetField("blockHash").Bytes().ToArray();

proofText = getBlockResponse.Proof.IonText;
proofValue = IonLoader.Default.Load(proofText);
```

------
#### [ Go ]

In diesem Beispiel verwenden Sie einen Ion-Reader, um den Beweis in eine Binärdatei umzuwandeln und die Node-Hashes des Proofs durchzugehen.

```
proofText = blockOutput.Proof.IonText

block := new(map[string]interface{})
err = ion.UnmarshalString(*blockOutput.Block.IonText, block)
if err != nil {
    panic(err)
}

blockHash := (*block)["blockHash"].([]byte)

// Use ion.Reader to iterate over the proof's node hashes
reader = ion.NewReaderString(*proofText)
// Enter the struct containing node hashes
reader.Next()
if err := reader.StepIn(); err != nil {
    panic(err)
}
```

------
#### [ Node.js ]

In diesem Beispiel verwenden Sie die `load` Funktion, um den Block und den Beweis in eine Binärdatei umzuwandeln.

```
const blockValue: dom.Value = load(getBlockResponse.Block.IonText)
let blockHash: Uint8Array = blockValue.get("blockHash").uInt8ArrayValue();

proofValue = load(getBlockResponse.Proof.IonText);
```

------
#### [ Python ]

In diesem Beispiel verwenden Sie die `loads` Funktion, um den Block und den Beweis in eine Binärdatei umzuwandeln.

```
block_text = block_response.get('Block').get('IonText')
block = loads(block_text)

block_hash = block.get('blockHash')

proof_text = block_response.get('Proof').get('IonText')
proof_hashes = loads(proof_text)
```

------

## Schritt 6: Berechne den Digest aus dem Block neu
<a name="verification.tutorial.step-6"></a>

Verwenden Sie die Hash-Liste des Proofs, um den Digest neu zu berechnen, beginnend mit dem Block-Hash. Solange der zuvor gespeicherte Digest außerhalb von QLDB bekannt und vertrauenswürdig ist, ist die Integrität des Blocks bewiesen, wenn der neu berechnete Digest-Hash mit dem gespeicherten Digest-Hash übereinstimmt.

------
#### [ Java ]

```
// Calculate digest
calculatedDigest = internalHashes.stream().reduce(blockHash, BlockHashVerification::dot);

verified = Arrays.equals(expectedDigest, calculatedDigest);

if (verified) {
    System.out.printf("Block address '%s' successfully verified!%n", blockAddressText);
} else {
    System.out.printf("Block address '%s' verification failed!%n", blockAddressText);
}
```

------
#### [ .NET ]

```
foreach (IIonValue proofHash in proofValue.GetElementAt(0))
{
    // Calculate the digest
    blockHash = Dot(blockHash, proofHash.Bytes().ToArray());
}

verified = expectedDigest.SequenceEqual(blockHash);

if (verified)
{
    Console.WriteLine($"Block address '{blockAddressText}' successfully verified!");
}
else
{
    Console.WriteLine($"Block address '{blockAddressText}' verification failed!");
}
```

------
#### [ Go ]

```
// Going through nodes and calculate digest
for reader.Next() {
    val, err := reader.ByteValue()
    if err != nil {
        panic(err)
    }
    blockHash, err = dot(blockHash, val)
}

// Compare blockHash with the expected digest
verified = reflect.DeepEqual(blockHash, expectedDigest)

if verified {
    fmt.Printf("Block address '%s' successfully verified!\n", blockAddress)
} else {
    fmt.Printf("Block address '%s' verification failed!\n", blockAddress)
    return
}
```

------
#### [ Node.js ]

```
proofValue.elements().forEach((proofHash: dom.Value) => {
    // Calculate the digest
    blockHash = dot(blockHash, proofHash.uInt8ArrayValue());
});

verified = isEqual(expectedDigest, blockHash);

if (verified) {
    console.log(`Block address '${dumpText(blockAddress)}' successfully verified!`);
} else {
    console.log(`Block address '${dumpText(blockAddress)}' verification failed!`);
}
```

------
#### [ Python ]

```
# Calculate digest
calculated_digest = reduce(dot, proof_hashes, block_hash)

verified = calculated_digest == expected_digest
if verified:
    print("Block address '{}' successfully verified!".format(dumps(block_address,
                                                                   binary=False,
                                                                   omit_version_marker=True)))
else:
    print("Block address '{}' verification failed!".format(block_address))
```

------

In den vorherigen Codebeispielen wird bei der Neuberechnung des Digest die folgende Funktion verwendet. `dot` Diese Funktion nimmt die Eingabe von zwei Hashes entgegen, sortiert sie, verkettet sie und gibt dann den Hash des verketteten Arrays zurück.

------
#### [ Java ]

```
/**
 * Takes two hashes, sorts them, concatenates them, and then returns the
 * hash of the concatenated array.
 *
 * @param h1
 *              Byte array containing one of the hashes to compare.
 * @param h2
 *              Byte array containing one of the hashes to compare.
 * @return the concatenated array of hashes.
 */
public static byte[] dot(final byte[] h1, final byte[] h2) {
    if (h1.length != HASH_LENGTH || h2.length != HASH_LENGTH) {
        throw new IllegalArgumentException("Invalid hash.");
    }

    int byteEqual = 0;
    for (int i = h1.length - 1; i >= 0; i--) {
        byteEqual = Byte.compare(h1[i], h2[i]);
        if (byteEqual != 0) {
            break;
        }
    }

    byte[] concatenated = new byte[h1.length + h2.length];
    if (byteEqual < 0) {
        System.arraycopy(h1, 0, concatenated, 0, h1.length);
        System.arraycopy(h2, 0, concatenated, h1.length, h2.length);
    } else {
        System.arraycopy(h2, 0, concatenated, 0, h2.length);
        System.arraycopy(h1, 0, concatenated, h2.length, h1.length);
    }

    MessageDigest messageDigest;
    try {
        messageDigest = MessageDigest.getInstance("SHA-256");
    } catch (NoSuchAlgorithmException e) {
        throw new IllegalStateException("SHA-256 message digest is unavailable", e);
    }

    messageDigest.update(concatenated);
    return messageDigest.digest();
}
```

------
#### [ .NET ]

```
/// <summary>
/// Takes two hashes, sorts them, concatenates them, and then returns the
/// hash of the concatenated array.
/// </summary>
/// <param name="h1">Byte array containing one of the hashes to compare.</param>
/// <param name="h2">Byte array containing one of the hashes to compare.</param>
/// <returns>The concatenated array of hashes.</returns>
private static byte[] Dot(byte[] h1, byte[] h2)
{
    if (h1.Length == 0)
    {
        return h2;
    }

    if (h2.Length == 0)
    {
        return h1;
    }

    HashAlgorithm hashAlgorithm = HashAlgorithm.Create("SHA256");
    HashComparer comparer = new HashComparer();
    if (comparer.Compare(h1, h2) < 0)
    {
        return hashAlgorithm.ComputeHash(h1.Concat(h2).ToArray());
    }
    else
    {
        return hashAlgorithm.ComputeHash(h2.Concat(h1).ToArray());
    }
}

private class HashComparer : IComparer<byte[]>
{
    private static readonly int HASH_LENGTH = 32;

    public int Compare(byte[] h1, byte[] h2)
    {
        if (h1.Length != HASH_LENGTH || h2.Length != HASH_LENGTH)
        {
            throw new ArgumentException("Invalid hash");
        }

        for (var i = h1.Length - 1; i >= 0; i--)
        {
            var byteEqual = (sbyte)h1[i] - (sbyte)h2[i];
            if (byteEqual != 0)
            {
                return byteEqual;
            }
        }

        return 0;
    }
}
```

------
#### [ Go ]

```
// Takes two hashes, sorts them, concatenates them, and then returns the hash of the concatenated array.
func dot(h1, h2 []byte) ([]byte, error) {
    compare, err := hashComparator(h1, h2)
    if err != nil {
        return nil, err
    }

    var concatenated []byte
    if compare < 0 {
        concatenated = append(h1, h2...)
    } else {
        concatenated = append(h2, h1...)
    }

    newHash := sha256.Sum256(concatenated)
    return newHash[:], nil
}

func hashComparator(h1 []byte, h2 []byte) (int16, error) {
    if len(h1) != hashLength || len(h2) != hashLength {
        return 0, errors.New("invalid hash")
    }
    for i := range h1 {
        // Reverse index for little endianness
        index := hashLength - 1 - i

        // Handle byte being unsigned and overflow
        h1Int := int16(h1[index])
        h2Int := int16(h2[index])
        if h1Int > 127 {
            h1Int = 0 - (256 - h1Int)
        }
        if h2Int > 127 {
            h2Int = 0 - (256 - h2Int)
        }

        difference := h1Int - h2Int
        if difference != 0 {
            return difference, nil
        }
    }
    return 0, nil
}
```

------
#### [ Node.js ]

```
/**
 * Takes two hashes, sorts them, concatenates them, and calculates a digest based on the concatenated hash.
 * @param h1 Byte array containing one of the hashes to compare.
 * @param h2 Byte array containing one of the hashes to compare.
 * @returns The digest calculated from the concatenated hash values.
 */
function dot(h1: Uint8Array, h2: Uint8Array): Uint8Array {
    if (h1.length === 0) {
        return h2;
    }
    if (h2.length === 0) {
        return h1;
    }

    const newHashLib = createHash("sha256");

    let concatenated: Uint8Array;
    if (hashComparator(h1, h2) < 0) {
        concatenated = concatenate(h1, h2);
    } else {
        concatenated = concatenate(h2, h1);
    }
    newHashLib.update(concatenated);
    return newHashLib.digest();
}

/**
 * Compares two hashes by their **signed** byte values in little-endian order.
 * @param hash1 The hash value to compare.
 * @param hash2 The hash value to compare.
 * @returns Zero if the hash values are equal, otherwise return the difference of the first pair of non-matching
 *          bytes.
 * @throws RangeError When the hash is not the correct hash size.
 */
function hashComparator(hash1: Uint8Array, hash2: Uint8Array): number {
    if (hash1.length !== HASH_SIZE || hash2.length !== HASH_SIZE) {
        throw new RangeError("Invalid hash.");
    }
    for (let i = hash1.length-1; i >= 0; i--) {
        const difference: number = (hash1[i]<<24 >>24) - (hash2[i]<<24 >>24);
        if (difference !== 0) {
            return difference;
        }
    }
    return 0;
}

/**
 * Helper method that concatenates two Uint8Array.
 * @param arrays List of arrays to concatenate, in the order provided.
 * @returns The concatenated array.
 */
function concatenate(...arrays: Uint8Array[]): Uint8Array {
    let totalLength = 0;
    for (const arr of arrays) {
        totalLength += arr.length;
    }
    const result = new Uint8Array(totalLength);
    let offset = 0;
    for (const arr of arrays) {
        result.set(arr, offset);
        offset += arr.length;
    }
    return result;
}

/**
 * Helper method that checks for equality between two Uint8Array.
 * @param expected Byte array containing one of the hashes to compare.
 * @param actual Byte array containing one of the hashes to compare.
 * @returns Boolean indicating equality between the two Uint8Array.
 */
function isEqual(expected: Uint8Array, actual: Uint8Array): boolean {
    if (expected === actual) return true;
    if (expected == null || actual == null) return false;
    if (expected.length !== actual.length) return false;

    for (let i = 0; i < expected.length; i++) {
        if (expected[i] !== actual[i]) {
            return false;
        }
    }
    return true;
}
```

------
#### [ Python ]

```
def dot(hash1, hash2):
    """
    Takes two hashes, sorts them, concatenates them, and then returns the
    hash of the concatenated array.

    :type hash1: bytes
    :param hash1: The hash value to compare.

    :type hash2: bytes
    :param hash2: The hash value to compare.

    :rtype: bytes
    :return: The new hash value generated from concatenated hash values.
    """
    if len(hash1) != hash_length or len(hash2) != hash_length:
        raise ValueError('Illegal hash.')

    hash_array1 = array('b', hash1)
    hash_array2 = array('b', hash2)

    difference = 0
    for i in range(len(hash_array1) - 1, -1, -1):
        difference = hash_array1[i] - hash_array2[i]
        if difference != 0:
            break

    if difference < 0:
        concatenated = hash1 + hash2
    else:
        concatenated = hash2 + hash1

    new_hash_lib = sha256()
    new_hash_lib.update(concatenated)
    new_digest = new_hash_lib.digest()
    return new_digest
```

------

## Führen Sie das vollständige Codebeispiel aus
<a name="verification.tutorial.full"></a>

Führen Sie das vollständige Codebeispiel wie folgt aus, um alle vorherigen Schritte von Anfang bis Ende durchzuführen.

------
#### [ Java ]

```
import com.amazon.ion.IonBlob;
import com.amazon.ion.IonDatagram;
import com.amazon.ion.IonList;
import com.amazon.ion.IonReader;
import com.amazon.ion.IonString;
import com.amazon.ion.IonStruct;
import com.amazon.ion.IonSystem;
import com.amazon.ion.IonValue;
import com.amazon.ion.system.IonSystemBuilder;
import com.amazonaws.services.qldb.AmazonQLDB;
import com.amazonaws.services.qldb.AmazonQLDBClientBuilder;
import com.amazonaws.services.qldb.model.GetBlockRequest;
import com.amazonaws.services.qldb.model.GetBlockResult;
import com.amazonaws.services.qldb.model.GetDigestRequest;
import com.amazonaws.services.qldb.model.GetDigestResult;
import com.amazonaws.services.qldb.model.GetRevisionRequest;
import com.amazonaws.services.qldb.model.GetRevisionResult;
import com.amazonaws.services.qldb.model.ValueHolder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.ListIterator;

import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.qldbsession.QldbSessionClient;
import software.amazon.awssdk.services.qldbsession.QldbSessionClientBuilder;
import software.amazon.qldb.QldbDriver;
import software.amazon.qldb.Result;

public class BlockHashVerification {
    private static final IonSystem SYSTEM = IonSystemBuilder.standard().build();
    private static final QldbDriver driver = createQldbDriver();
    private static final AmazonQLDB client = AmazonQLDBClientBuilder.standard().build();
    private static final String region = "us-east-1";
    private static final String ledgerName = "vehicle-registration";
    private static final String tableName = "VehicleRegistration";
    private static final String vin = "KM8SRDHF6EU074761";
    private static final int HASH_LENGTH = 32;

    /**
     * Create a pooled driver for creating sessions.
     *
     * @return The pooled driver for creating sessions.
     */
    public static QldbDriver createQldbDriver() {
        QldbSessionClientBuilder sessionClientBuilder = QldbSessionClient.builder();
        sessionClientBuilder.region(Region.of(region));

        return QldbDriver.builder()
                .ledger(ledgerName)
                .sessionClientBuilder(sessionClientBuilder)
                .build();
    }

    /**
     * Takes two hashes, sorts them, concatenates them, and then returns the
     * hash of the concatenated array.
     *
     * @param h1
     *              Byte array containing one of the hashes to compare.
     * @param h2
     *              Byte array containing one of the hashes to compare.
     * @return the concatenated array of hashes.
     */
    public static byte[] dot(final byte[] h1, final byte[] h2) {
        if (h1.length != HASH_LENGTH || h2.length != HASH_LENGTH) {
            throw new IllegalArgumentException("Invalid hash.");
        }

        int byteEqual = 0;
        for (int i = h1.length - 1; i >= 0; i--) {
            byteEqual = Byte.compare(h1[i], h2[i]);
            if (byteEqual != 0) {
                break;
            }
        }

        byte[] concatenated = new byte[h1.length + h2.length];
        if (byteEqual < 0) {
            System.arraycopy(h1, 0, concatenated, 0, h1.length);
            System.arraycopy(h2, 0, concatenated, h1.length, h2.length);
        } else {
            System.arraycopy(h2, 0, concatenated, 0, h2.length);
            System.arraycopy(h1, 0, concatenated, h2.length, h1.length);
        }

        MessageDigest messageDigest;
        try {
            messageDigest = MessageDigest.getInstance("SHA-256");
        } catch (NoSuchAlgorithmException e) {
            throw new IllegalStateException("SHA-256 message digest is unavailable", e);
        }

        messageDigest.update(concatenated);
        return messageDigest.digest();
    }

    public static void main(String[] args) {
        // Get a digest
        GetDigestRequest digestRequest = new GetDigestRequest().withName(ledgerName);
        GetDigestResult digestResult = client.getDigest(digestRequest);

        java.nio.ByteBuffer digest = digestResult.getDigest();

        // expectedDigest is the buffer we will use later to compare against our calculated digest
        byte[] expectedDigest = new byte[digest.remaining()];
        digest.get(expectedDigest);

        // Retrieve info for the given vin's document revisions
        Result result = driver.execute(txn -> {
            final String query = String.format("SELECT blockAddress, hash, metadata.id FROM _ql_committed_%s WHERE data.VIN = '%s'", tableName, vin);
            return txn.execute(query);
        });

        System.out.printf("Verifying document revisions for vin '%s' in table '%s' in ledger '%s'%n", vin, tableName, ledgerName);

        for (IonValue ionValue : result) {
            IonStruct ionStruct = (IonStruct)ionValue;

            // Get the requested fields
            IonValue blockAddress = ionStruct.get("blockAddress");
            IonBlob hash = (IonBlob)ionStruct.get("hash");
            String metadataId = ((IonString)ionStruct.get("id")).stringValue();

            System.out.printf("Verifying document revision for id '%s'%n", metadataId);

            String blockAddressText = blockAddress.toString();

            // Submit a request for the revision
            GetRevisionRequest revisionRequest = new GetRevisionRequest()
                    .withName(ledgerName)
                    .withBlockAddress(new ValueHolder().withIonText(blockAddressText))
                    .withDocumentId(metadataId)
                    .withDigestTipAddress(digestResult.getDigestTipAddress());

            // Get a result back
            GetRevisionResult revisionResult = client.getRevision(revisionRequest);

            String proofText = revisionResult.getProof().getIonText();

            // Take the proof and convert it to a list of byte arrays
            List<byte[]> internalHashes = new ArrayList<>();
            IonReader reader = SYSTEM.newReader(proofText);
            reader.next();
            reader.stepIn();
            while (reader.next() != null) {
                internalHashes.add(reader.newBytes());
            }

            // Calculate digest
            byte[] calculatedDigest = internalHashes.stream().reduce(hash.getBytes(), BlockHashVerification::dot);

            boolean verified = Arrays.equals(expectedDigest, calculatedDigest);

            if (verified) {
                System.out.printf("Successfully verified document revision for id '%s'!%n", metadataId);
            } else {
                System.out.printf("Document revision for id '%s' verification failed!%n", metadataId);
                return;
            }

            // Submit a request for the block
            GetBlockRequest getBlockRequest = new GetBlockRequest()
                    .withName(ledgerName)
                    .withBlockAddress(new ValueHolder().withIonText(blockAddressText))
                    .withDigestTipAddress(digestResult.getDigestTipAddress());

            // Get a result back
            GetBlockResult getBlockResult = client.getBlock(getBlockRequest);

            String blockText = getBlockResult.getBlock().getIonText();

            IonDatagram datagram = SYSTEM.getLoader().load(blockText);
            ionStruct = (IonStruct)datagram.get(0);

            final byte[] blockHash = ((IonBlob)ionStruct.get("blockHash")).getBytes();

            proofText = getBlockResult.getProof().getIonText();

            // Take the proof and create a list of hash binary data
            datagram = SYSTEM.getLoader().load(proofText);
            ListIterator<IonValue> listIter = ((IonList)datagram.iterator().next()).listIterator();

            internalHashes.clear();
            while (listIter.hasNext()) {
                internalHashes.add(((IonBlob)listIter.next()).getBytes());
            }

            // Calculate digest
            calculatedDigest = internalHashes.stream().reduce(blockHash, BlockHashVerification::dot);

            verified = Arrays.equals(expectedDigest, calculatedDigest);

            if (verified) {
                System.out.printf("Block address '%s' successfully verified!%n", blockAddressText);
            } else {
                System.out.printf("Block address '%s' verification failed!%n", blockAddressText);
            }
        }
    }
}
```

------
#### [ .NET ]

```
using Amazon.IonDotnet;
using Amazon.IonDotnet.Builders;
using Amazon.IonDotnet.Tree;
using Amazon.QLDB;
using Amazon.QLDB.Driver;
using Amazon.QLDB.Model;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;

namespace BlockHashVerification
{
    class BlockHashVerification
    {
        private static readonly string ledgerName = "vehicle-registration";
        private static readonly string tableName = "VehicleRegistration";
        private static readonly string vin = "KM8SRDHF6EU074761";
        private static readonly IQldbDriver driver = QldbDriver.Builder().WithLedger(ledgerName).Build();
        private static readonly IAmazonQLDB client = new AmazonQLDBClient();

        /// <summary>
        /// Takes two hashes, sorts them, concatenates them, and then returns the
        /// hash of the concatenated array.
        /// </summary>
        /// <param name="h1">Byte array containing one of the hashes to compare.</param>
        /// <param name="h2">Byte array containing one of the hashes to compare.</param>
        /// <returns>The concatenated array of hashes.</returns>
        private static byte[] Dot(byte[] h1, byte[] h2)
        {
            if (h1.Length == 0)
            {
                return h2;
            }

            if (h2.Length == 0)
            {
                return h1;
            }

            HashAlgorithm hashAlgorithm = HashAlgorithm.Create("SHA256");
            HashComparer comparer = new HashComparer();
            if (comparer.Compare(h1, h2) < 0)
            {
                return hashAlgorithm.ComputeHash(h1.Concat(h2).ToArray());
            }
            else
            {
                return hashAlgorithm.ComputeHash(h2.Concat(h1).ToArray());
            }
        }

        private class HashComparer : IComparer<byte[]>
        {
            private static readonly int HASH_LENGTH = 32;

            public int Compare(byte[] h1, byte[] h2)
            {
                if (h1.Length != HASH_LENGTH || h2.Length != HASH_LENGTH)
                {
                    throw new ArgumentException("Invalid hash");
                }

                for (var i = h1.Length - 1; i >= 0; i--)
                {
                    var byteEqual = (sbyte)h1[i] - (sbyte)h2[i];
                    if (byteEqual != 0)
                    {
                        return byteEqual;
                    }
                }

                return 0;
            }
        }

        static void Main()
        {
            // Get a digest
            GetDigestRequest getDigestRequest = new GetDigestRequest
            {
                Name = ledgerName
            };
            GetDigestResponse getDigestResponse = client.GetDigestAsync(getDigestRequest).Result;

            // expectedDigest is the buffer we will use later to compare against our calculated digest
            MemoryStream digest = getDigestResponse.Digest;
            byte[] expectedDigest = digest.ToArray();

            // Retrieve info for the given vin's document revisions
            var result = driver.Execute(txn => {
                string query = $"SELECT blockAddress, hash, metadata.id FROM _ql_committed_{tableName} WHERE data.VIN = '{vin}'";
                return txn.Execute(query);
            });

            Console.WriteLine($"Verifying document revisions for vin '{vin}' in table '{tableName}' in ledger '{ledgerName}'");

            foreach (IIonValue ionValue in result)
            {
                IIonStruct ionStruct = ionValue;

                // Get the requested fields
                IIonValue blockAddress = ionStruct.GetField("blockAddress");
                IIonBlob hash = ionStruct.GetField("hash");
                String metadataId = ionStruct.GetField("id").StringValue;

                Console.WriteLine($"Verifying document revision for id '{metadataId}'");

                // Use an Ion Reader to convert block address to text
                IIonReader reader = IonReaderBuilder.Build(blockAddress);
                StringWriter sw = new StringWriter();
                IIonWriter textWriter = IonTextWriterBuilder.Build(sw);
                textWriter.WriteValues(reader);
                string blockAddressText = sw.ToString();

                // Submit a request for the revision
                GetRevisionRequest revisionRequest = new GetRevisionRequest
                {
                    Name = ledgerName,
                    BlockAddress = new ValueHolder
                    {
                        IonText = blockAddressText
                    },
                    DocumentId = metadataId,
                    DigestTipAddress = getDigestResponse.DigestTipAddress
                };
                
                // Get a response back
                GetRevisionResponse revisionResponse = client.GetRevisionAsync(revisionRequest).Result;

                string proofText = revisionResponse.Proof.IonText;
                IIonDatagram proofValue = IonLoader.Default.Load(proofText);

                byte[] documentHash = hash.Bytes().ToArray();
                foreach (IIonValue proofHash in proofValue.GetElementAt(0))
                {
                    // Calculate the digest
                    documentHash = Dot(documentHash, proofHash.Bytes().ToArray());
                }

                bool verified = expectedDigest.SequenceEqual(documentHash);

                if (verified)
                {
                    Console.WriteLine($"Successfully verified document revision for id '{metadataId}'!");
                }
                else
                {
                    Console.WriteLine($"Document revision for id '{metadataId}' verification failed!");
                    return;
                }

                // Submit a request for the block
                GetBlockRequest getBlockRequest = new GetBlockRequest
                {
                    Name = ledgerName,
                    BlockAddress = new ValueHolder
                    {
                        IonText = blockAddressText
                    },
                    DigestTipAddress = getDigestResponse.DigestTipAddress
                };

                // Get a response back
                GetBlockResponse getBlockResponse = client.GetBlockAsync(getBlockRequest).Result;

                string blockText = getBlockResponse.Block.IonText;
                IIonDatagram blockValue = IonLoader.Default.Load(blockText);

                // blockValue is a IonDatagram, and the first value is an IonStruct containing the blockHash
                byte[] blockHash = blockValue.GetElementAt(0).GetField("blockHash").Bytes().ToArray();

                proofText = getBlockResponse.Proof.IonText;
                proofValue = IonLoader.Default.Load(proofText);

                foreach (IIonValue proofHash in proofValue.GetElementAt(0))
                {
                    // Calculate the digest
                    blockHash = Dot(blockHash, proofHash.Bytes().ToArray());
                }

                verified = expectedDigest.SequenceEqual(blockHash);

                if (verified)
                {
                    Console.WriteLine($"Block address '{blockAddressText}' successfully verified!");
                }
                else
                {
                    Console.WriteLine($"Block address '{blockAddressText}' verification failed!");
                }
            }
        }
    }
}
```

------
#### [ Go ]

```
package main

import (
    "context"
    "crypto/sha256"
    "errors"
    "fmt"
    "reflect"

    "github.com/amzn/ion-go/ion"
    "github.com/aws/aws-sdk-go/aws"
    AWSSession "github.com/aws/aws-sdk-go/aws/session"
    "github.com/aws/aws-sdk-go/service/qldb"
    "github.com/aws/aws-sdk-go/service/qldbsession"
    "github.com/awslabs/amazon-qldb-driver-go/qldbdriver"
)

const (
    hashLength = 32
    ledgerName = "vehicle-registration"
    tableName  = "VehicleRegistration"
    vin        = "KM8SRDHF6EU074761"
)

// Takes two hashes, sorts them, concatenates them, and then returns the hash of the concatenated array.
func dot(h1, h2 []byte) ([]byte, error) {
    compare, err := hashComparator(h1, h2)
    if err != nil {
        return nil, err
    }

    var concatenated []byte
    if compare < 0 {
        concatenated = append(h1, h2...)
    } else {
        concatenated = append(h2, h1...)
    }

    newHash := sha256.Sum256(concatenated)
    return newHash[:], nil
}

func hashComparator(h1 []byte, h2 []byte) (int16, error) {
    if len(h1) != hashLength || len(h2) != hashLength {
        return 0, errors.New("invalid hash")
    }
    for i := range h1 {
        // Reverse index for little endianness
        index := hashLength - 1 - i

        // Handle byte being unsigned and overflow
        h1Int := int16(h1[index])
        h2Int := int16(h2[index])
        if h1Int > 127 {
            h1Int = 0 - (256 - h1Int)
        }
        if h2Int > 127 {
            h2Int = 0 - (256 - h2Int)
        }

        difference := h1Int - h2Int
        if difference != 0 {
            return difference, nil
        }
    }
    return 0, nil
}

func main() {
    driverSession := AWSSession.Must(AWSSession.NewSession(aws.NewConfig()))
    qldbSession := qldbsession.New(driverSession)
    driver, err := qldbdriver.New(ledgerName, qldbSession, func(options *qldbdriver.DriverOptions) {})
    if err != nil {
        panic(err)
    }
    client := qldb.New(driverSession)

    // Get a digest
    currentLedgerName := ledgerName
    input := qldb.GetDigestInput{Name: &currentLedgerName}
    digestOutput, err := client.GetDigest(&input)
    if err != nil {
        panic(err)
    }

    // expectedDigest is the buffer we will later use to compare against our calculated digest
    expectedDigest := digestOutput.Digest

    // Retrieve info for the given vin's document revisions
    result, err := driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) {
        statement := fmt.Sprintf(
                "SELECT blockAddress, hash, metadata.id FROM _ql_committed_%s WHERE data.VIN = '%s'",
                tableName,
                vin)
        result, err := txn.Execute(statement)
        if err != nil {
            return nil, err
        }

        results := make([]map[string]interface{}, 0)

        // Convert the result set into a map
        for result.Next(txn) {
            var doc map[string]interface{}
            err := ion.Unmarshal(result.GetCurrentData(), &doc)
            if err != nil {
                return nil, err
            }
            results = append(results, doc)
        }
        return results, nil
    })
    if err != nil {
        panic(err)
    }
    resultSlice := result.([]map[string]interface{})

    fmt.Printf("Verifying document revisions for vin '%s' in table '%s' in ledger '%s'\n", vin, tableName, ledgerName)

    for _, value := range resultSlice {
        // Get the requested fields
        ionBlockAddress, err := ion.MarshalText(value["blockAddress"])
        if err != nil {
            panic(err)
        }
        blockAddress := string(ionBlockAddress)
        metadataId := value["id"].(string)
        documentHash := value["hash"].([]byte)

        fmt.Printf("Verifying document revision for id '%s'\n", metadataId)

        // Submit a request for the revision
        revisionInput := qldb.GetRevisionInput{
            BlockAddress:     &qldb.ValueHolder{IonText: &blockAddress},
            DigestTipAddress: digestOutput.DigestTipAddress,
            DocumentId:       &metadataId,
            Name:             &currentLedgerName,
        }

        // Get a result back
        revisionOutput, err := client.GetRevision(&revisionInput)
        if err != nil {
            panic(err)
        }

        proofText := revisionOutput.Proof.IonText

        // Use ion.Reader to iterate over the proof's node hashes
        reader := ion.NewReaderString(*proofText)
        // Enter the struct containing node hashes
        reader.Next()
        if err := reader.StepIn(); err != nil {
            panic(err)
        }

        // Going through nodes and calculate digest
        for reader.Next() {
            val, _ := reader.ByteValue()
            documentHash, err = dot(documentHash, val)
        }

        // Compare documentHash with the expected digest
        verified := reflect.DeepEqual(documentHash, expectedDigest)

        if verified {
            fmt.Printf("Successfully verified document revision for id '%s'!\n", metadataId)
        } else {
            fmt.Printf("Document revision for id '%s' verification failed!\n", metadataId)
            return
        }

        // Submit a request for the block
        blockInput := qldb.GetBlockInput{
            Name:             &currentLedgerName,
            BlockAddress:     &qldb.ValueHolder{IonText: &blockAddress},
            DigestTipAddress: digestOutput.DigestTipAddress,
        }

        // Get a result back
        blockOutput, err := client.GetBlock(&blockInput)
        if err != nil {
            panic(err)
        }

        proofText = blockOutput.Proof.IonText

        block := new(map[string]interface{})
        err = ion.UnmarshalString(*blockOutput.Block.IonText, block)
        if err != nil {
            panic(err)
        }

        blockHash := (*block)["blockHash"].([]byte)

        // Use ion.Reader to iterate over the proof's node hashes
        reader = ion.NewReaderString(*proofText)
        // Enter the struct containing node hashes
        reader.Next()
        if err := reader.StepIn(); err != nil {
            panic(err)
        }

        // Going through nodes and calculate digest
        for reader.Next() {
            val, err := reader.ByteValue()
            if err != nil {
                panic(err)
            }
            blockHash, err = dot(blockHash, val)
        }

        // Compare blockHash with the expected digest
        verified = reflect.DeepEqual(blockHash, expectedDigest)

        if verified {
            fmt.Printf("Block address '%s' successfully verified!\n", blockAddress)
        } else {
            fmt.Printf("Block address '%s' verification failed!\n", blockAddress)
            return
        }
    }
}
```

------
#### [ Node.js ]

```
import { QldbDriver, Result, TransactionExecutor} from "amazon-qldb-driver-nodejs";
import { QLDB } from "aws-sdk"
import { GetBlockRequest, GetBlockResponse, GetDigestRequest, GetDigestResponse, GetRevisionRequest, GetRevisionResponse } from "aws-sdk/clients/qldb";
import { createHash } from "crypto";
import { dom, dumpText, load } from "ion-js"

const ledgerName: string = "vehicle-registration";
const tableName: string = "VehicleRegistration";
const vin: string = "KM8SRDHF6EU074761";
const driver: QldbDriver = new QldbDriver(ledgerName);
const qldbClient: QLDB = new QLDB();
const HASH_SIZE = 32;

/**
 * Takes two hashes, sorts them, concatenates them, and calculates a digest based on the concatenated hash.
 * @param h1 Byte array containing one of the hashes to compare.
 * @param h2 Byte array containing one of the hashes to compare.
 * @returns The digest calculated from the concatenated hash values.
 */
function dot(h1: Uint8Array, h2: Uint8Array): Uint8Array {
    if (h1.length === 0) {
        return h2;
    }
    if (h2.length === 0) {
        return h1;
    }

    const newHashLib = createHash("sha256");

    let concatenated: Uint8Array;
    if (hashComparator(h1, h2) < 0) {
        concatenated = concatenate(h1, h2);
    } else {
        concatenated = concatenate(h2, h1);
    }
    newHashLib.update(concatenated);
    return newHashLib.digest();
}

/**
 * Compares two hashes by their **signed** byte values in little-endian order.
 * @param hash1 The hash value to compare.
 * @param hash2 The hash value to compare.
 * @returns Zero if the hash values are equal, otherwise return the difference of the first pair of non-matching
 *          bytes.
 * @throws RangeError When the hash is not the correct hash size.
 */
function hashComparator(hash1: Uint8Array, hash2: Uint8Array): number {
    if (hash1.length !== HASH_SIZE || hash2.length !== HASH_SIZE) {
        throw new RangeError("Invalid hash.");
    }
    for (let i = hash1.length-1; i >= 0; i--) {
        const difference: number = (hash1[i]<<24 >>24) - (hash2[i]<<24 >>24);
        if (difference !== 0) {
            return difference;
        }
    }
    return 0;
}

/**
 * Helper method that concatenates two Uint8Array.
 * @param arrays List of arrays to concatenate, in the order provided.
 * @returns The concatenated array.
 */
function concatenate(...arrays: Uint8Array[]): Uint8Array {
    let totalLength = 0;
    for (const arr of arrays) {
        totalLength += arr.length;
    }
    const result = new Uint8Array(totalLength);
    let offset = 0;
    for (const arr of arrays) {
        result.set(arr, offset);
        offset += arr.length;
    }
    return result;
}

/**
 * Helper method that checks for equality between two Uint8Array.
 * @param expected Byte array containing one of the hashes to compare.
 * @param actual Byte array containing one of the hashes to compare.
 * @returns Boolean indicating equality between the two Uint8Array.
 */
function isEqual(expected: Uint8Array, actual: Uint8Array): boolean {
    if (expected === actual) return true;
    if (expected == null || actual == null) return false;
    if (expected.length !== actual.length) return false;

    for (let i = 0; i < expected.length; i++) {
        if (expected[i] !== actual[i]) {
            return false;
        }
    }
    return true;
}

const main = async function (): Promise<void> {
    // Get a digest
    const getDigestRequest: GetDigestRequest = {
        Name: ledgerName
    };
    const getDigestResponse: GetDigestResponse = await qldbClient.getDigest(getDigestRequest).promise();

    // expectedDigest is the buffer we will later use to compare against our calculated digest
    const expectedDigest: Uint8Array = <Uint8Array>getDigestResponse.Digest;

    const result: dom.Value[] = await driver.executeLambda(async (txn: TransactionExecutor): Promise<dom.Value[]> => {
        const query: string = `SELECT blockAddress, hash, metadata.id FROM _ql_committed_${tableName} WHERE data.VIN = '${vin}'`;
        const queryResult: Result = await txn.execute(query);
        return queryResult.getResultList();
    });

    console.log(`Verifying document revisions for vin '${vin}' in table '${tableName}' in ledger '${ledgerName}'`);

    for (let value of result) {
        // Get the requested fields
        const blockAddress: dom.Value = value.get("blockAddress");
        const hash: dom.Value = value.get("hash");
        const metadataId: string = value.get("id").stringValue();

        console.log(`Verifying document revision for id '${metadataId}'`);

        // Submit a request for the revision
        const revisionRequest: GetRevisionRequest = {
            Name: ledgerName,
            BlockAddress: {
                IonText: dumpText(blockAddress)
            },
            DocumentId: metadataId,
            DigestTipAddress: getDigestResponse.DigestTipAddress
        };

        // Get a response back
        const revisionResponse: GetRevisionResponse = await qldbClient.getRevision(revisionRequest).promise();

        let proofValue: dom.Value = load(revisionResponse.Proof.IonText);

        let documentHash: Uint8Array = hash.uInt8ArrayValue();
        proofValue.elements().forEach((proofHash: dom.Value) => {
            // Calculate the digest
            documentHash = dot(documentHash, proofHash.uInt8ArrayValue());
        });

        let verified: boolean = isEqual(expectedDigest, documentHash);

        if (verified) {
            console.log(`Successfully verified document revision for id '${metadataId}'!`);
        } else {
            console.log(`Document revision for id '${metadataId}' verification failed!`);
            return;
        }

        // Submit a request for the block
        const getBlockRequest: GetBlockRequest = {
            Name: ledgerName,
            BlockAddress: {
                IonText: dumpText(blockAddress)
            },
            DigestTipAddress: getDigestResponse.DigestTipAddress
        };

        // Get a response back
        const getBlockResponse: GetBlockResponse = await qldbClient.getBlock(getBlockRequest).promise();

        const blockValue: dom.Value = load(getBlockResponse.Block.IonText)
        let blockHash: Uint8Array = blockValue.get("blockHash").uInt8ArrayValue();

        proofValue = load(getBlockResponse.Proof.IonText);

        proofValue.elements().forEach((proofHash: dom.Value) => {
            // Calculate the digest
            blockHash = dot(blockHash, proofHash.uInt8ArrayValue());
        });

        verified = isEqual(expectedDigest, blockHash);

        if (verified) {
            console.log(`Block address '${dumpText(blockAddress)}' successfully verified!`);
        } else {
            console.log(`Block address '${dumpText(blockAddress)}' verification failed!`);
        }
    }
};

if (require.main === module) {
    main();
}
```

------
#### [ Python ]

```
from amazon.ion.simpleion import dumps, loads
from array import array
from boto3 import client
from functools import reduce
from hashlib import sha256
from pyqldb.driver.qldb_driver import QldbDriver

ledger_name = 'vehicle-registration'
table_name = 'VehicleRegistration'
vin = 'KM8SRDHF6EU074761'
qldb_client = client('qldb')
hash_length = 32


def query_doc_revision(txn):
    query = "SELECT blockAddress, hash, metadata.id FROM _ql_committed_{} WHERE data.VIN = '{}'".format(table_name, vin)
    return txn.execute_statement(query)


def block_address_to_dictionary(ion_dict):
    """
    Convert a block address from IonPyDict into a dictionary.
    Shape of the dictionary must be: {'IonText': "{strandId: <"strandId">, sequenceNo: <sequenceNo>}"}

    :type ion_dict: :py:class:`amazon.ion.simple_types.IonPyDict`/str
    :param ion_dict: The block address value to convert.

    :rtype: dict
    :return: The converted dict.
    """
    block_address = {'IonText': {}}
    if not isinstance(ion_dict, str):
        py_dict = '{{strandId: "{}", sequenceNo:{}}}'.format(ion_dict['strandId'], ion_dict['sequenceNo'])
        ion_dict = py_dict
    block_address['IonText'] = ion_dict
    return block_address


def dot(hash1, hash2):
    """
    Takes two hashes, sorts them, concatenates them, and then returns the
    hash of the concatenated array.

    :type hash1: bytes
    :param hash1: The hash value to compare.

    :type hash2: bytes
    :param hash2: The hash value to compare.

    :rtype: bytes
    :return: The new hash value generated from concatenated hash values.
    """
    if len(hash1) != hash_length or len(hash2) != hash_length:
        raise ValueError('Illegal hash.')

    hash_array1 = array('b', hash1)
    hash_array2 = array('b', hash2)

    difference = 0
    for i in range(len(hash_array1) - 1, -1, -1):
        difference = hash_array1[i] - hash_array2[i]
        if difference != 0:
            break

    if difference < 0:
        concatenated = hash1 + hash2
    else:
        concatenated = hash2 + hash1

    new_hash_lib = sha256()
    new_hash_lib.update(concatenated)
    new_digest = new_hash_lib.digest()
    return new_digest


# Get a digest
get_digest_response = qldb_client.get_digest(Name=ledger_name)

# expected_digest is the buffer we will later use to compare against our calculated digest
expected_digest = get_digest_response.get('Digest')
digest_tip_address = get_digest_response.get('DigestTipAddress')

qldb_driver = QldbDriver(ledger_name=ledger_name)

# Retrieve info for the given vin's document revisions
result = qldb_driver.execute_lambda(query_doc_revision)

print("Verifying document revisions for vin '{}' in table '{}' in ledger '{}'".format(vin, table_name, ledger_name))

for value in result:
    # Get the requested fields
    block_address = value['blockAddress']
    document_hash = value['hash']
    metadata_id = value['id']

    print("Verifying document revision for id '{}'".format(metadata_id))

    # Submit a request for the revision and get a result back
    proof_response = qldb_client.get_revision(Name=ledger_name,
                                              BlockAddress=block_address_to_dictionary(block_address),
                                              DocumentId=metadata_id,
                                              DigestTipAddress=digest_tip_address)

    proof_text = proof_response.get('Proof').get('IonText')
    proof_hashes = loads(proof_text)

    # Calculate digest
    calculated_digest = reduce(dot, proof_hashes, document_hash)

    verified = calculated_digest == expected_digest
    if verified:
        print("Successfully verified document revision for id '{}'!".format(metadata_id))
    else:
        print("Document revision for id '{}' verification failed!".format(metadata_id))

    # Submit a request for the block and get a result back
    block_response = qldb_client.get_block(Name=ledger_name, BlockAddress=block_address_to_dictionary(block_address),
                                           DigestTipAddress=digest_tip_address)

    block_text = block_response.get('Block').get('IonText')
    block = loads(block_text)

    block_hash = block.get('blockHash')

    proof_text = block_response.get('Proof').get('IonText')
    proof_hashes = loads(proof_text)

    # Calculate digest
    calculated_digest = reduce(dot, proof_hashes, block_hash)

    verified = calculated_digest == expected_digest
    if verified:
        print("Block address '{}' successfully verified!".format(dumps(block_address,
                                                                       binary=False,
                                                                       omit_version_marker=True)))
    else:
        print("Block address '{}' verification failed!".format(block_address))
```

------

# Häufige Fehler bei der Überprüfung
<a name="verification.errors"></a>

**Wichtig**  
Hinweis zum Ende des Supports: Bestandskunden können Amazon QLDB bis zum Ende des Supports am 31.07.2025 nutzen. Weitere Informationen finden Sie unter [Migrieren eines Amazon QLDB-Ledgers zu Amazon](https://aws.amazon.com/blogs/database/migrate-an-amazon-qldb-ledger-to-amazon-aurora-postgresql/) Aurora PostgreSQL.

In diesem Abschnitt werden Laufzeitfehler beschrieben, die von Amazon QLDB bei Überprüfungsanfragen ausgelöst werden.

Im Folgenden finden Sie eine Liste mit allgemeinen Ausnahmen, die vom Service zurückgegeben werden. Jede Ausnahme enthält die spezifische Fehlermeldung, gefolgt von den API-Vorgängen, durch die sie ausgelöst werden kann, einer kurzen Beschreibung und Vorschlägen für mögliche Lösungen.<a name="verification.errors.varlist"></a>

**IllegalArgumentException**  
Nachricht: Der angegebene Ion-Wert ist ungültig und kann nicht analysiert werden.  
API-Operationen: `GetDigest, GetBlock, GetRevision`  
Stellen Sie sicher, dass Sie einen gültigen [Amazon Ion](ion.md)-Wert bereitstellen, bevor Sie Ihre Anfrage erneut stellen.

**IllegalArgumentException**  
Meldung: Die angegebene Blockadresse ist ungültig.  
API-Operationen: `GetDigest, GetBlock, GetRevision`  
Stellen Sie sicher, dass Sie eine gültige Block-Adresse bereitstellen, bevor Sie Ihre Anfrage erneut stellen. Ein Block-Adresse ist eine Amazon Ion-Struktur, die über zwei Felder verfügt: `strandId` und `sequenceNo`.  
Beispiel: `{strandId:"BlFTjlSXze9BIh1KOszcE3",sequenceNo:14}`

**IllegalArgumentException**  
Nachricht: Die Sequenznummer der angegebenen Digest-Tip-Adresse liegt außerhalb des zuletzt übermittelten Datensatzes des Strangs.  
API-Operationen: `GetDigest, GetBlock, GetRevision`  
Die Digest-Tip-Adresse, die Sie angeben, muss über eine Sequenznummer verfügen, die kleiner oder gleich der Sequenznummer des zuletzt übertragenen Datensatzes der Journal-Strähne ist. Bevor Sie Ihre Anforderung wiederholen, stellen Sie sicher, dass Sie eine Digest-Tip-Adresse mit einer gültigen Sequenznummer bereitstellen.

**IllegalArgumentException**  
Meldung: The Strand ID of the provided block address is not valid.  
API-Operationen: `GetDigest, GetBlock, GetRevision`  
Die von Ihnen angegebene Block-Adresse muss eine Strähnen-ID aufweisen, die der Strähnen-ID des Journals entspricht. Bevor Sie Ihre Anforderung wiederholen, stellen Sie sicher, dass Sie eine Block-Adresse mit einer gültigen Strähnen-ID bereitstellen.

**IllegalArgumentException**  
Nachricht: Die Sequenznummer der angegebenen Blockadresse liegt außerhalb des letzten festgeschriebenen Datensatzes des Strangs.  
API-Operationen: `GetBlock, GetRevision`  
Die Block-Adresse, die Sie angeben, muss über eine Sequenznummer verfügen, die kleiner oder gleich der Sequenznummer des zuletzt übertragenen Datensatzes der Strähne ist. Bevor Sie Ihre Anforderung wiederholen, stellen Sie sicher, dass Sie eine Block-Adresse mit einer gültigen Sequenznummer.

**IllegalArgumentException**  
Nachricht: Die Strähnen-ID der bereitgestellten Block-Adresse muss der Strähnen-ID der bereitgestellten Digest-Tip-Adresse entsprechen.  
API-Operationen: `GetBlock, GetRevision`  
Sie können ein Dokument nur überprüfen oder blockieren, wenn es in derselben Journal-Strähne vorhanden ist wie das von Ihnen angegebene Digest-Journal.

**IllegalArgumentException**  
Nachricht: Die Sequenznummer der bereitgestellten Block-Adresse darf nicht größer sein als die Sequenznummer der bereitgestellten Digest-Tip-Adresse.  
API-Operationen: `GetBlock, GetRevision`  
Sie können eine Dokumentrevision nur überprüfen oder blockieren, wenn sie von dem von Ihnen bereitgestellten Digest abgedeckt wird. Dies bedeutet, dass sie vor der Digest-Tip-Adresse an das Journal übertragen wurde.

**IllegalArgumentException**  
Nachricht: Die angegebene Dokument-ID wurde im Block an der angegebenen Block-Adresse nicht gefunden.  
API-Betrieb: `GetRevision`  
Die Dokument-ID, die Sie angeben, muss in der von Ihnen angegebenen Block-Adresse vorhanden sein. Bevor Sie Ihre Anforderung wiederholen, stellen Sie sicher, dass diese beiden Parameter konsistent sind.