Tutorial: Verificando dados usando um SDK AWS - Amazon Quantum Ledger Database (Amazon QLDB)

As traduções são geradas por tradução automática. Em caso de conflito entre o conteúdo da tradução e da versão original em inglês, a versão em inglês prevalecerá.

Tutorial: Verificando dados usando um SDK AWS

Neste tutorial, você verifica um hash de revisão de documento e um hash de bloco de diário em um livro contábil do Amazon QLDB usando a API QLDB por meio de um SDK. AWS Você também usa o driver QLDB para consultar a revisão do documento.

Considere um exemplo em que você tem uma revisão de documento que contém dados de um veículo com um número de identificação do veículo (vehicle identification number - VIN) de KM8SRDHF6EU074761. A revisão do documento está em uma tabela VehicleRegistration que está em um ledger chamado vehicle-registration. Suponha que você queira verificar a integridade da revisão do documento desse veículo e do bloco de diário que contém a revisão.

nota

Para uma postagem detalhada AWS no blog que discute o valor da verificação criptográfica no contexto de um caso de uso realista, consulte Verificação criptográfica no mundo real com o Amazon QLDB.

Pré-requisitos

Antes de iniciar, certifique-se de fazer o seguinte:

  1. Configure o driver QLDB para um idioma de sua escolha preenchendo os respectivos pré-requisitos em Conceitos básicos do driver do Amazon QLDB. Isso inclui se inscrever AWS, conceder acesso programático para desenvolvimento e configurar seu ambiente de desenvolvimento.

  2. Siga as etapas 1 a 2 Conceitos básicos do console do Amazon QLDB para criar um ledger chamado vehicle-registration e carregá-lo com dados de amostra predefinidos.

Em seguida, revise as etapas a seguir para saber como a verificação funciona e, em seguida, execute o exemplo de código completo do início ao fim.

Etapa 1: Solicite um resumo

Antes de verificar os dados, você deve primeiro solicitar um resumo do seu ledger vehicle-registration para uso posterior.

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

Etapa 2: Consultar a revisão do documento

Use o driver QLDB para consultar os endereços de blocos, hashes e IDs de documentos associados ao 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)

Etapa 3: Solicitar uma prova da revisão

Repita os resultados da consulta e use cada endereço de bloco e ID do documento junto com o nome do ledger para enviar uma solicitação GetRevision. Para obter uma prova da revisão, você também deve fornecer o endereço da dica do resumo salvo anteriormente. Essa operação de API retorna um objeto que inclui a revisão do documento e a prova da revisão.

Para obter mais informações sobre a estrutura e o conteúdo da revisão, consulte Consultando metadados do documento.

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)

Em seguida, recupere a prova da revisão solicitada.

A API QLDB retorna a prova como uma representação em sequência da lista ordenada de hashes de nós. Para converter essa string em uma lista da representação binária dos hashes dos nós, você pode usar um leitor de íons da biblioteca Amazon Ion. Para obter mais informações sobre o uso da biblioteca Ion, consulte o Cookbook Amazon Ion.

Java

Neste exemplo, você usa IonReader para fazer a conversão binária.

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

Neste exemplo, você usa IonLoader para carregar a prova em um datagrama de íons.

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

Neste exemplo, você usa um leitor de íons para converter a prova em binária e para percorrer a lista de hashes de nós da prova.

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

Neste exemplo, você usa a função load para fazer a conversão binária.

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

Neste exemplo, você usa a função loads para fazer a conversão binária.

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

Etapa 4: Recalcular o resumo da revisão

Use a lista de hashes da prova para recalcular o resumo, começando com o hash da revisão. Desde que o resumo salvo anteriormente seja conhecido e confiável fora do QLDB, a integridade da revisão do documento será comprovada se o hash do resumo recalculado corresponder ao hash do resumo salvo.

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

Etapa 5: Solicitar uma prova para o bloco de diário

Em seguida, você verifica o bloco de diário que contém a revisão do documento.

Use o endereço do bloco e o endereço da dica do resumo que você salvou na Etapa 1 para enviar uma solicitação GetBlock. Semelhante à GetRevision solicitação na Etapa 2, você deve fornecer novamente o endereço da dica do resumo salvo para obter uma prova do bloco. Essa operação de API retorna um objeto que inclui o bloco e a prova do bloco.

Para obter informações sobre a estrutura do bloco de diário e seu conteúdo, consulte Conteúdo do periódico no 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)

Em seguida, recupere o hash do bloco e a prova do resultado.

Java

Neste exemplo, você usa IonLoader para carregar o objeto de bloco em um contêiner IonDatagram.

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

Você também usa IonLoader para carregar a prova em um IonDatagram.

proofText = getBlockResult.getProof().getIonText(); // Take the proof and create a list of hash binary data datagram = SYSTEM.getLoader().load(proofText); ListIterator<IonValue> listIter = ((IonList)datagram.iterator().next()).listIterator(); internalHashes.clear(); while (listIter.hasNext()) { internalHashes.add(((IonBlob)listIter.next()).getBytes()); }
.NET

Neste exemplo, você usa IonLoader para carregar o bloco e a prova em um datagrama de íons para cada um.

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

Neste exemplo, você usa um leitor de íons para converter a prova em binária e para percorrer a lista de hashes de nós da prova.

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

Neste exemplo, você usa a função load para converter o bloco e a prova em binário.

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

Neste exemplo, você usa a função loads para converter o bloco e a prova em binário.

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)

Etapa 6: Recalcular o resumo do bloco

Use a lista de hashes da prova para recalcular o resumo, começando com o hash do bloco. Desde que o resumo salvo anteriormente seja conhecido e confiável fora do QLDB, a integridade do bloco é comprovada se o hash do resumo recalculado corresponder ao hash do resumo salvo.

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

Os exemplos de código anteriores usam a função dot a seguir ao recalcular o resumo. Essa função recebe uma entrada de dois hashes, os classifica, concatena e retorna o hash da matriz concatenada.

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

Execute o exemplo de código completo

Execute o exemplo de código completo da seguinte forma para realizar todas as etapas anteriores do início ao fim.

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