Tutorial: verifica dei dati utilizzando un AWS SDK - Database Amazon Quantum Ledger (Amazon) QLDB

Le traduzioni sono generate tramite traduzione automatica. In caso di conflitto tra il contenuto di una traduzione e la versione originale in Inglese, quest'ultima prevarrà.

Tutorial: verifica dei dati utilizzando un AWS SDK

Importante

Avviso di fine del supporto: i clienti esistenti potranno utilizzare Amazon QLDB fino alla fine del supporto il 31/07/2025. Per ulteriori dettagli, consulta Migrare un Amazon QLDB Ledger ad Amazon Aurora Postgre. SQL

In questo tutorial, verifichi un hash di revisione del documento e un hash del blocco del diario in un QLDB registro Amazon utilizzando il comando tramite un. QLDB API AWS SDK Utilizzi il QLDB driver anche per interrogare la revisione del documento.

Si consideri un esempio in cui si dispone di una revisione del documento che contiene i dati di un veicolo con un numero di identificazione del veicolo (VIN) di. KM8SRDHF6EU074761 La revisione del documento si trova in una VehicleRegistration tabella che si trova in un registro denominato. vehicle-registration Supponiamo di voler verificare l'integrità sia della revisione del documento per questo veicolo sia del blocco del giornale che contiene la revisione.

Nota

Per un post dettagliato AWS sul blog che illustra il valore della verifica crittografica nel contesto di un caso d'uso realistico, consulta la verifica crittografica nel mondo reale con Amazon. QLDB

Prerequisiti

Prima di iniziare, assicurati di fare quanto segue:

  1. Configura il QLDB driver per una lingua a tua scelta completando i rispettivi prerequisiti indicati Guida introduttiva al QLDB driver Amazon di seguito. Ciò include la registrazione AWS, la concessione dell'accesso programmatico per lo sviluppo e la configurazione dell'ambiente di sviluppo.

  2. Segui i passaggi da 1 Guida introduttiva alla QLDB console Amazon a 2 per creare un registro denominato vehicle-registration e caricarlo con dati di esempio predefiniti.

Successivamente, esamina i passaggi seguenti per scoprire come funziona la verifica, quindi esegui l'esempio di codice completo dall'inizio alla fine.

Passaggio 1: richiedi un riassunto

Prima di poter verificare i dati, devi prima richiedere un riepilogo dal registro vehicle-registration per utilizzarlo in un secondo momento.

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

Passaggio 2: interrogare la revisione del documento

Usa il QLDB driver per interrogare gli indirizzi di blocco, gli hash e IDs i documenti associati a. 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)

Fase 3: Richiedere una bozza della revisione

Esegui un'iterazione dei risultati della query e utilizza ogni indirizzo di blocco e ID del documento insieme al nome del libro mastro per inviare una richiesta. GetRevision Per ottenere una bozza della revisione, devi anche fornire l'indirizzo del suggerimento contenuto nel digest salvato in precedenza. Questa API operazione restituisce un oggetto che include la revisione del documento e la bozza della revisione.

Per informazioni sulla struttura di revisione e sul relativo contenuto, vedere. Interrogazione dei metadati dei documenti

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)

Recuperate quindi la bozza della revisione richiesta.

QLDBAPIRestituisce la dimostrazione come rappresentazione in formato stringa dell'elenco ordinato degli hash dei nodi. Per convertire questa stringa in un elenco della rappresentazione binaria degli hash del nodo, puoi utilizzare un lettore di ioni dalla libreria Amazon Ion. Per ulteriori informazioni sull'uso della libreria Ion, consulta Amazon Ion Cookbook.

Java

In questo esempio, si utilizza IonReader per eseguire la conversione binaria.

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 questo esempio, si utilizza IonLoader per caricare la bozza in un datagramma ionico.

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

In questo esempio, si utilizza un lettore Ion per convertire la bozza in binario e per scorrere l'elenco degli hash dei nodi della dimostrazione.

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 questo esempio, si utilizza la load funzione per eseguire la conversione binaria.

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

In questo esempio, si utilizza la loads funzione per eseguire la conversione binaria.

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

Fase 4: Ricalcola il digest dalla revisione

Usa l'elenco di hash del proof per ricalcolare il digest, iniziando dall'hash della revisione. Fintanto che il digest salvato in precedenza è noto e affidabile all'esterno diQLDB, l'integrità della revisione del documento è dimostrata se l'hash del digest ricalcolato corrisponde all'hash del digest salvato.

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

Fase 5: Richiedere una bozza per il blocco Journal

Successivamente, si verifica il blocco del diario che contiene la revisione del documento.

Utilizza l'indirizzo di blocco e l'indirizzo tip del digest che hai salvato nel passaggio 1 per inviare una GetBlock richiesta. Analogamente alla GetRevision richiesta nella Fase 2, devi fornire nuovamente l'indirizzo del suggerimento dal digest salvato per ottenere una prova dell'esistenza del blocco. Questa API operazione restituisce un oggetto che include il blocco e la bozza del blocco.

Per informazioni sulla struttura dei blocchi del journal e sul relativo contenuto, vedereContenuti del diario in 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)

Quindi, recupera l'hash del blocco e la dimostrazione dal risultato.

Java

In questo esempio, si utilizza IonLoader per caricare l'oggetto blocco in un IonDatagram contenitore.

String blockText = getBlockResult.getBlock().getIonText(); IonDatagram datagram = SYSTEM.getLoader().load(blockText); ionStruct = (IonStruct)datagram.get(0); final byte[] blockHash = ((IonBlob)ionStruct.get("blockHash")).getBytes();

Utilizzate anche IonLoader per caricare la bozza in un fileIonDatagram.

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 questo esempio, si utilizza IonLoader per caricare il blocco e la bozza in un datagramma ionico per ciascuno.

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 questo esempio, utilizzate un lettore Ion per convertire la bozza in binario e per scorrere l'elenco degli hash dei nodi della bozza.

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 questo esempio, si utilizza la load funzione per convertire il blocco e la dimostrazione in binario.

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

In questo esempio, si utilizza la loads funzione per convertire il blocco e la dimostrazione in binario.

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)

Fase 6: Ricalcola il digest dal blocco

Usa l'elenco di hash del proof per ricalcolare il digest, iniziando dall'hash del blocco. Finché il digest salvato in precedenza è noto e affidabile all'esternoQLDB, l'integrità del blocco viene dimostrata se l'hash del digest ricalcolato corrisponde all'hash del digest salvato.

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

Gli esempi di codice precedenti utilizzano la seguente funzione per ricalcolare il digestdot. Questa funzione accetta l'input di due hash, li ordina, li concatena e quindi restituisce l'hash dell'array concatenato.

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

Esegui l'esempio completo di codice

Eseguite l'esempio di codice completo come segue per eseguire tutti i passaggi precedenti dall'inizio alla fine.

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