Tutorial: Überprüfen von Daten mit einem AWS SDK - Amazon Quantum Ledger-Datenbank (AmazonQLDB)

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.

Tutorial: Überprüfen von Daten mit einem AWS SDK

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 Aurora SQL Postgre.

In diesem Tutorial verifizieren Sie einen Revisions-Hash für Dokumente und einen Journal-Block-Hash in einem QLDB Amazon-Ledger, indem Sie QLDB API über eine AWS SDK. Sie verwenden den QLDB Treiber auch, um die Revision des Dokuments 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ältKM8SRDHF6EU074761. 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 Praxis mit Amazon. QLDB

Voraussetzungen

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 QLDB Amazon-Treiber Dazu gehören die Registrierung für AWS, die Gewährung des programmatischen Zugriffs für die Entwicklung und die Konfiguration Ihrer Entwicklungsumgebung.

  2. Folgen Sie den Schritten 1—2 unterErste Schritte mit der QLDB Amazon-Konsole, 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

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

Verwenden Sie den QLDB Treiber, um die zugehörigen Blockadressen, Hashes und IDs Dokumente abzufragen. VIN 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

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. Bei diesem API Vorgang wird ein Objekt zurückgegeben, das die Revision des Dokuments und den Nachweis für die Revision enthält.

Hinweise zur Revisionsstruktur und ihrem Inhalt finden Sie unterMetadaten von Dokumenten werden abgefragt.

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.

Der 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.

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: Den Digest aus der Revision neu berechnen

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 bekannt und vertrauenswürdig ist, ist die Integrität der Dokumentenrevision bewiesenQLDB, 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

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 gespeichert haben, um eine GetBlock Anfrage einzureichen. Ähnlich wie bei der GetRevision Anfrage in Schritt 2 müssen Sie erneut die Tipp-Adresse aus dem gespeicherten Digest angeben, um einen Nachweis für die Sperre zu erhalten. Bei diesem API Vorgang wird ein Objekt zurückgegeben, das den Block und den Nachweis für den Block enthält.

Hinweise zur Struktur des Journalblocks und seinem Inhalt finden Sie unterJournalinhalte bei Amazon QLDB.

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 auchIonLoader, um den Beweis in einen zu ladenIonDatagram.

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

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 bekannt und vertrauenswürdig ist, ist die Integrität des Blocks bewiesenQLDB, 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 dot Funktion verwendet. 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

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))