Langkah 7: Verifikasi dokumen dalam buku besar - 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.

Langkah 7: Verifikasi dokumen dalam buku besar

Dengan Amazon QLDB, Anda dapat memverifikasi integritas dokumen secara efisien dalam jurnal buku besar Anda dengan menggunakan hashing kriptografi dengan SHA-256. Untuk mempelajari lebih lanjut tentang cara kerja verifikasi dan hashing kriptografi di QLDB, lihat. Verifikasi data di Amazon QLDB

Pada langkah ini, Anda memverifikasi revisi dokumen dalam VehicleRegistration tabel di vehicle-registration buku besar Anda. Pertama, Anda meminta intisari, yang dikembalikan sebagai file output dan bertindak sebagai tanda tangan dari seluruh riwayat perubahan buku besar Anda. Kemudian, Anda meminta bukti untuk revisi relatif terhadap intisari itu. Dengan menggunakan bukti ini, integritas revisi Anda diverifikasi jika semua pemeriksaan validasi lulus.

Untuk memverifikasi revisi dokumen
  1. Tinjau .ts file berikut, yang berisi objek QLDB yang diperlukan untuk verifikasi.

    1. BlockAddress.ts

      /* * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: MIT-0 * * Permission is hereby granted, free of charge, to any person obtaining a copy of this * software and associated documentation files (the "Software"), to deal in the Software * without restriction, including without limitation the rights to use, copy, modify, * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ import { ValueHolder } from "aws-sdk/clients/qldb"; import { dom, IonTypes } from "ion-js"; export class BlockAddress { _strandId: string; _sequenceNo: number; constructor(strandId: string, sequenceNo: number) { this._strandId = strandId; this._sequenceNo = sequenceNo; } } /** * Convert a block address from an Ion value into a ValueHolder. * Shape of the ValueHolder must be: {'IonText': "{strandId: <"strandId">, sequenceNo: <sequenceNo>}"} * @param value The Ion value that contains the block address values to convert. * @returns The ValueHolder that contains the strandId and sequenceNo. */ export function blockAddressToValueHolder(value: dom.Value): ValueHolder { const blockAddressValue : dom.Value = getBlockAddressValue(value); const strandId: string = getStrandId(blockAddressValue); const sequenceNo: number = getSequenceNo(blockAddressValue); const valueHolder: string = `{strandId: "${strandId}", sequenceNo: ${sequenceNo}}`; const blockAddress: ValueHolder = {IonText: valueHolder}; return blockAddress; } /** * Helper method that to get the Metadata ID. * @param value The Ion value. * @returns The Metadata ID. */ export function getMetadataId(value: dom.Value): string { const metaDataId: dom.Value = value.get("id"); if (metaDataId === null) { throw new Error(`Expected field name id, but not found.`); } return metaDataId.stringValue(); } /** * Helper method to get the Sequence No. * @param value The Ion value. * @returns The Sequence No. */ export function getSequenceNo(value : dom.Value): number { const sequenceNo: dom.Value = value.get("sequenceNo"); if (sequenceNo === null) { throw new Error(`Expected field name sequenceNo, but not found.`); } return sequenceNo.numberValue(); } /** * Helper method to get the Strand ID. * @param value The Ion value. * @returns The Strand ID. */ export function getStrandId(value: dom.Value): string { const strandId: dom.Value = value.get("strandId"); if (strandId === null) { throw new Error(`Expected field name strandId, but not found.`); } return strandId.stringValue(); } export function getBlockAddressValue(value: dom.Value) : dom.Value { const type = value.getType(); if (type !== IonTypes.STRUCT) { throw new Error(`Unexpected format: expected struct, but got IonType: ${type.name}`); } const blockAddress: dom.Value = value.get("blockAddress"); if (blockAddress == null) { throw new Error(`Expected field name blockAddress, but not found.`); } return blockAddress; }
    2. Verifier.ts

      /* * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: MIT-0 * * Permission is hereby granted, free of charge, to any person obtaining a copy of this * software and associated documentation files (the "Software"), to deal in the Software * without restriction, including without limitation the rights to use, copy, modify, * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ import { Digest, ValueHolder } from "aws-sdk/clients/qldb"; import { createHash } from "crypto"; import { dom, toBase64 } from "ion-js"; import { getBlobValue } from "./Util"; const HASH_LENGTH: number = 32; const UPPER_BOUND: number = 8; /** * Build the candidate digest representing the entire ledger from the Proof hashes. * @param proof The Proof object. * @param leafHash The revision hash to pair with the first hash in the Proof hashes list. * @returns The calculated root hash. */ function buildCandidateDigest(proof: ValueHolder, leafHash: Uint8Array): Uint8Array { const parsedProof: Uint8Array[] = parseProof(proof); const rootHash: Uint8Array = calculateRootHashFromInternalHash(parsedProof, leafHash); return rootHash; } /** * Combine the internal hashes and the leaf hash until only one root hash remains. * @param internalHashes An array of hash values. * @param leafHash The revision hash to pair with the first hash in the Proof hashes list. * @returns The root hash constructed by combining internal hashes. */ function calculateRootHashFromInternalHash(internalHashes: Uint8Array[], leafHash: Uint8Array): Uint8Array { const rootHash: Uint8Array = internalHashes.reduce(joinHashesPairwise, leafHash); return rootHash; } /** * Compare two hash values by converting each Uint8Array byte, which is unsigned by default, * into a signed byte, assuming they are little endian. * @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. */ function compareHashValues(hash1: Uint8Array, hash2: Uint8Array): number { if (hash1.length !== HASH_LENGTH || hash2.length !== HASH_LENGTH) { throw new Error("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 array 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; } /** * Flip a single random bit in the given hash value. * This method is intended to be used for purpose of demonstrating the QLDB verification features only. * @param original The hash value to alter. * @returns The altered hash with a single random bit changed. */ export function flipRandomBit(original: any): Uint8Array { if (original.length === 0) { throw new Error("Array cannot be empty!"); } const bytePos: number = Math.floor(Math.random() * original.length); const bitShift: number = Math.floor(Math.random() * UPPER_BOUND); const alteredHash: Uint8Array = original; alteredHash[bytePos] = alteredHash[bytePos] ^ (1 << bitShift); return alteredHash; } /** * Take two hash values, sort them, concatenate them, and generate a new hash value from the concatenated values. * @param h1 Byte array containing one of the hashes to compare. * @param h2 Byte array containing one of the hashes to compare. * @returns The concatenated array of hashes. */ export function joinHashesPairwise(h1: Uint8Array, h2: Uint8Array): Uint8Array { if (h1.length === 0) { return h2; } if (h2.length === 0) { return h1; } let concat: Uint8Array; if (compareHashValues(h1, h2) < 0) { concat = concatenate(h1, h2); } else { concat = concatenate(h2, h1); } const hash = createHash('sha256'); hash.update(concat); const newDigest: Uint8Array = hash.digest(); return newDigest; } /** * Parse the Block object returned by QLDB and retrieve block hash. * @param valueHolder A structure containing an Ion string value. * @returns The block hash. */ export function parseBlock(valueHolder: ValueHolder): Uint8Array { const block: dom.Value = dom.load(valueHolder.IonText); const blockHash: Uint8Array = getBlobValue(block, "blockHash"); return blockHash; } /** * Parse the Proof object returned by QLDB into an iterator. * The Proof object returned by QLDB is a dictionary like the following: * {'IonText': '[{{<hash>}},{{<hash>}}]'} * @param valueHolder A structure containing an Ion string value. * @returns A list of hash values. */ function parseProof(valueHolder: ValueHolder): Uint8Array[] { const proofs : dom.Value = dom.load(valueHolder.IonText); return proofs.elements().map(proof => proof.uInt8ArrayValue()); } /** * Verify document revision against the provided digest. * @param documentHash The SHA-256 value representing the document revision to be verified. * @param digest The SHA-256 hash value representing the ledger digest. * @param proof The Proof object retrieved from GetRevision.getRevision. * @returns If the document revision verifies against the ledger digest. */ export function verifyDocument(documentHash: Uint8Array, digest: Digest, proof: ValueHolder): boolean { const candidateDigest = buildCandidateDigest(proof, documentHash); return (toBase64(<Uint8Array> digest) === toBase64(candidateDigest)); }
  2. Gunakan dua .ts program (GetDigest.tsdanGetRevision.ts) untuk melakukan langkah-langkah berikut:

    • Minta intisari baru dari vehicle-registration buku besar.

    • Minta bukti untuk setiap revisi dokumen dengan VIN 1N4AL11D75C109151 dari VehicleRegistration tabel.

    • Verifikasi revisi menggunakan intisari dan bukti yang dikembalikan dengan menghitung ulang intisari.

    GetDigest.tsProgram ini berisi kode berikut.

    /* * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: MIT-0 * * Permission is hereby granted, free of charge, to any person obtaining a copy of this * software and associated documentation files (the "Software"), to deal in the Software * without restriction, including without limitation the rights to use, copy, modify, * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ import { QLDB } from "aws-sdk"; import { GetDigestRequest, GetDigestResponse } from "aws-sdk/clients/qldb"; import { LEDGER_NAME } from "./qldb/Constants"; import { error, log } from "./qldb/LogUtil"; import { digestResponseToString } from "./qldb/Util"; /** * Get the digest of a ledger's journal. * @param ledgerName Name of the ledger to operate on. * @param qldbClient The QLDB control plane client to use. * @returns Promise which fulfills with a GetDigestResponse. */ export async function getDigestResult(ledgerName: string, qldbClient: QLDB): Promise<GetDigestResponse> { const request: GetDigestRequest = { Name: ledgerName }; const result: GetDigestResponse = await qldbClient.getDigest(request).promise(); return result; } /** * This is an example for retrieving the digest of a particular ledger. * @returns Promise which fulfills with void. */ const main = async function(): Promise<void> { try { const qldbClient: QLDB = new QLDB(); log(`Retrieving the current digest for ledger: ${LEDGER_NAME}.`); const digest: GetDigestResponse = await getDigestResult(LEDGER_NAME, qldbClient); log(`Success. Ledger digest: \n${digestResponseToString(digest)}.`); } catch (e) { error(`Unable to get a ledger digest: ${e}`); } } if (require.main === module) { main(); }
    catatan

    Gunakan getDigest fungsi untuk meminta intisari yang mencakup ujung jurnal saat ini di buku besar Anda. Tip jurnal mengacu pada blok komitmen terbaru pada saat QLDB menerima permintaan Anda.

    GetRevision.tsProgram ini berisi kode berikut.

    /* * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: MIT-0 * * Permission is hereby granted, free of charge, to any person obtaining a copy of this * software and associated documentation files (the "Software"), to deal in the Software * without restriction, including without limitation the rights to use, copy, modify, * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ import { QldbDriver, TransactionExecutor } from "amazon-qldb-driver-nodejs"; import { QLDB } from "aws-sdk"; import { Digest, GetDigestResponse, GetRevisionRequest, GetRevisionResponse, ValueHolder } from "aws-sdk/clients/qldb"; import { dom, toBase64 } from "ion-js"; import { getQldbDriver } from "./ConnectToLedger"; import { getDigestResult } from './GetDigest'; import { VEHICLE_REGISTRATION } from "./model/SampleData" import { blockAddressToValueHolder, getMetadataId } from './qldb/BlockAddress'; import { LEDGER_NAME } from './qldb/Constants'; import { error, log } from "./qldb/LogUtil"; import { getBlobValue, valueHolderToString } from "./qldb/Util"; import { flipRandomBit, verifyDocument } from "./qldb/Verifier"; /** * Get the revision data object for a specified document ID and block address. * Also returns a proof of the specified revision for verification. * @param ledgerName Name of the ledger containing the document to query. * @param documentId Unique ID for the document to be verified, contained in the committed view of the document. * @param blockAddress The location of the block to request. * @param digestTipAddress The latest block location covered by the digest. * @param qldbClient The QLDB control plane client to use. * @returns Promise which fulfills with a GetRevisionResponse. */ async function getRevision( ledgerName: string, documentId: string, blockAddress: ValueHolder, digestTipAddress: ValueHolder, qldbClient: QLDB ): Promise<GetRevisionResponse> { const request: GetRevisionRequest = { Name: ledgerName, BlockAddress: blockAddress, DocumentId: documentId, DigestTipAddress: digestTipAddress }; const result: GetRevisionResponse = await qldbClient.getRevision(request).promise(); return result; } /** * Query the table metadata for a particular vehicle for verification. * @param txn The {@linkcode TransactionExecutor} for lambda execute. * @param vin VIN to query the table metadata of a specific registration with. * @returns Promise which fulfills with a list of Ion values that contains the results of the query. */ export async function lookupRegistrationForVin(txn: TransactionExecutor, vin: string): Promise<dom.Value[]> { log(`Querying the 'VehicleRegistration' table for VIN: ${vin}...`); let resultList: dom.Value[]; const query: string = "SELECT blockAddress, metadata.id FROM _ql_committed_VehicleRegistration WHERE data.VIN = ?"; await txn.execute(query, vin).then(function(result) { resultList = result.getResultList(); }); return resultList; } /** * Verify each version of the registration for the given VIN. * @param txn The {@linkcode TransactionExecutor} for lambda execute. * @param ledgerName The ledger to get the digest from. * @param vin VIN to query the revision history of a specific registration with. * @param qldbClient The QLDB control plane client to use. * @returns Promise which fulfills with void. * @throws Error: When verification fails. */ export async function verifyRegistration( txn: TransactionExecutor, ledgerName: string, vin: string, qldbClient: QLDB ): Promise<void> { log(`Let's verify the registration with VIN = ${vin}, in ledger = ${ledgerName}.`); const digest: GetDigestResponse = await getDigestResult(ledgerName, qldbClient); const digestBytes: Digest = digest.Digest; const digestTipAddress: ValueHolder = digest.DigestTipAddress; log( `Got a ledger digest: digest tip address = \n${valueHolderToString(digestTipAddress)}, digest = \n${toBase64(<Uint8Array> digestBytes)}.` ); log(`Querying the registration with VIN = ${vin} to verify each version of the registration...`); const resultList: dom.Value[] = await lookupRegistrationForVin(txn, vin); log("Getting a proof for the document."); for (const result of resultList) { const blockAddress: ValueHolder = blockAddressToValueHolder(result); const documentId: string = getMetadataId(result); const revisionResponse: GetRevisionResponse = await getRevision( ledgerName, documentId, blockAddress, digestTipAddress, qldbClient ); const revision: dom.Value = dom.load(revisionResponse.Revision.IonText); const documentHash: Uint8Array = getBlobValue(revision, "hash"); const proof: ValueHolder = revisionResponse.Proof; log(`Got back a proof: ${valueHolderToString(proof)}.`); let verified: boolean = verifyDocument(documentHash, digestBytes, proof); if (!verified) { throw new Error("Document revision is not verified."); } else { log("Success! The document is verified."); } const alteredDocumentHash: Uint8Array = flipRandomBit(documentHash); log( `Flipping one bit in the document's hash and assert that the document is NOT verified. The altered document hash is: ${toBase64(alteredDocumentHash)}` ); verified = verifyDocument(alteredDocumentHash, digestBytes, proof); if (verified) { throw new Error("Expected altered document hash to not be verified against digest."); } else { log("Success! As expected flipping a bit in the document hash causes verification to fail."); } log(`Finished verifying the registration with VIN = ${vin} in ledger = ${ledgerName}.`); } } /** * Verify the integrity of a document revision in a QLDB ledger. * @returns Promise which fulfills with void. */ const main = async function(): Promise<void> { try { const qldbClient: QLDB = new QLDB(); const qldbDriver: QldbDriver = getQldbDriver(); const registration = VEHICLE_REGISTRATION[0]; const vin: string = registration.VIN; await qldbDriver.executeLambda(async (txn: TransactionExecutor) => { await verifyRegistration(txn, LEDGER_NAME, vin, qldbClient); }); } catch (e) { error(`Unable to verify revision: ${e}`); } } if (require.main === module) { main(); }
    catatan

    Setelah getRevision fungsi mengembalikan bukti untuk revisi dokumen yang ditentukan, program ini menggunakan API sisi klien untuk memverifikasi revisi tersebut.

  3. Untuk menjalankan program transpiled, masukkan perintah berikut.

    node dist/GetRevision.js

Jika Anda tidak perlu lagi menggunakan vehicle-registration buku besar, lanjutkan keLangkah 8 (opsional): Bersihkan sumber daya.