Tutorial: Memverifikasi data menggunakan AWS SDK - Amazon Quantum Ledger Database (Amazon QLDB)

Terjemahan disediakan oleh mesin penerjemah. Jika konten terjemahan yang diberikan bertentangan dengan versi bahasa Inggris aslinya, utamakan versi bahasa Inggris.

Tutorial: Memverifikasi data menggunakan AWS SDK

Dalam tutorial ini, Anda memverifikasi hash revisi dokumen dan hash blok jurnal di buku besar Amazon QLDB dengan menggunakan QLDB API melalui SDK. AWS Anda juga menggunakan driver QLDB untuk menanyakan revisi dokumen.

Pertimbangkan contoh di mana Anda memiliki revisi dokumen yang berisi data untuk kendaraan dengan nomor identifikasi kendaraan (VIN) dari. KM8SRDHF6EU074761 Revisi dokumen ada dalam VehicleRegistration tabel yang ada di buku besar bernama. vehicle-registration Misalkan Anda ingin memverifikasi integritas revisi dokumen untuk kendaraan ini dan blok jurnal yang berisi revisi.

catatan

Untuk posting AWS blog terperinci yang membahas nilai verifikasi kriptografi dalam konteks kasus penggunaan yang realistis, lihat Verifikasi kriptografi dunia nyata dengan Amazon QLDB.

Prasyarat

Sebelum memulai, pastikan Anda melakukan hal berikut:

  1. Siapkan driver QLDB untuk bahasa pilihan Anda dengan menyelesaikan prasyarat masing-masing di bawah. Memulai dengan driver Amazon QLDB Ini termasuk mendaftar AWS, memberikan akses terprogram untuk pengembangan, dan mengonfigurasi lingkungan pengembangan Anda.

  2. Ikuti langkah 1-2 Memulai dengan konsol Amazon QLDB untuk membuat buku besar bernama vehicle-registration dan memuatnya dengan data sampel yang telah ditentukan.

Selanjutnya, tinjau langkah-langkah berikut untuk mempelajari cara kerja verifikasi, lalu jalankan contoh kode lengkap dari awal hingga akhir.

Langkah 1: Minta intisari

Sebelum Anda dapat memverifikasi data, Anda harus terlebih dahulu meminta intisari dari buku besar Anda vehicle-registration untuk digunakan nanti.

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

Langkah 2: Kueri revisi dokumen

Gunakan driver QLDB untuk menanyakan alamat blok, hash, dan ID dokumen yang terkait dengan 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)

Langkah 3: Minta bukti untuk revisi

Ulangi hasil kueri dan gunakan setiap alamat blok dan ID dokumen bersama dengan nama buku besar untuk mengirimkan permintaan. GetRevision Untuk mendapatkan bukti revisi, Anda juga harus memberikan alamat tip dari intisari yang disimpan sebelumnya. Operasi API ini mengembalikan objek yang menyertakan revisi dokumen dan bukti revisi.

Untuk informasi tentang struktur revisi dan isinya, lihatMelakukan Kueri Metadata Dokumen.

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)

Kemudian, ambil bukti untuk revisi yang diminta.

QLDB API mengembalikan bukti sebagai representasi string dari daftar urutan hash node. Untuk mengonversi string ini menjadi daftar representasi biner dari hash node, Anda dapat menggunakan pembaca Ion dari perpustakaan Amazon Ion. Untuk informasi selengkapnya tentang penggunaan pustaka Ion, lihat Buku Masak Amazon Ion.

Java

Dalam contoh ini, Anda gunakan IonReader untuk melakukan konversi biner.

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

Dalam contoh ini, Anda gunakan IonLoader untuk memuat bukti ke dalam datagram Ion.

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

Dalam contoh ini, Anda menggunakan pembaca Ion untuk mengonversi bukti menjadi biner dan untuk mengulangi daftar hash simpul bukti.

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

Dalam contoh ini, Anda menggunakan load fungsi untuk melakukan konversi biner.

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

Dalam contoh ini, Anda menggunakan loads fungsi untuk melakukan konversi biner.

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

Langkah 4: Hitung ulang intisari dari revisi

Gunakan daftar hash bukti untuk menghitung ulang intisari, dimulai dengan hash revisi. Selama intisari yang disimpan sebelumnya diketahui dan dipercaya di luar QLDB, integritas revisi dokumen terbukti jika hash intisari yang dihitung ulang cocok dengan hash intisari yang disimpan.

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

Langkah 5: Minta bukti untuk blok jurnal

Selanjutnya, Anda memverifikasi blok jurnal yang berisi revisi dokumen.

Gunakan alamat blokir dan alamat tip dari intisari yang Anda simpan di Langkah 1 untuk mengirimkan GetBlock permintaan. Mirip dengan GetRevision permintaan di Langkah 2, Anda harus kembali memberikan alamat tip dari intisari yang disimpan untuk mendapatkan bukti untuk blok tersebut. Operasi API ini mengembalikan objek yang menyertakan blok dan bukti untuk blok tersebut.

Untuk informasi tentang struktur blok jurnal dan isinya, lihatIsi jurnal di 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)

Kemudian, ambil hash blok dan bukti dari hasilnya.

Java

Dalam contoh ini, Anda gunakan IonLoader untuk memuat objek blok ke dalam IonDatagram wadah.

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

Anda juga menggunakan IonLoader untuk memuat bukti ke dalamIonDatagram.

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

Dalam contoh ini, Anda gunakan IonLoader untuk memuat blok dan bukti ke dalam datagram Ion untuk masing-masing.

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

Dalam contoh ini, Anda menggunakan pembaca Ion untuk mengonversi bukti menjadi biner dan untuk mengulangi daftar hash simpul bukti.

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

Dalam contoh ini, Anda menggunakan load fungsi untuk mengonversi blok dan bukti menjadi biner.

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

Dalam contoh ini, Anda menggunakan loads fungsi untuk mengonversi blok dan bukti menjadi biner.

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)

Langkah 6: Hitung ulang intisari dari blok

Gunakan daftar hash bukti untuk menghitung ulang intisari, dimulai dengan hash blok. Selama intisari yang disimpan sebelumnya diketahui dan dipercaya di luar QLDB, integritas blok terbukti jika hash intisari yang dihitung ulang cocok dengan hash intisari yang disimpan.

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

Contoh kode sebelumnya menggunakan dot fungsi berikut saat menghitung ulang intisari. Fungsi ini mengambil masukan dari dua hash, mengurutkannya, menggabungkan mereka, dan kemudian mengembalikan hash dari array gabungan.

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

Jalankan contoh kode lengkap

Jalankan contoh kode lengkap sebagai berikut untuk melakukan semua langkah sebelumnya dari awal hingga akhir.

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