

Las traducciones son generadas a través de traducción automática. En caso de conflicto entre la traducción y la version original de inglés, prevalecerá la version en inglés.

# Paso 7: verificar un documento en un libro mayor
Paso 7: verificar un documento

**importante**  
Aviso de fin del soporte: los clientes actuales podrán utilizar Amazon QLDB hasta que finalice el soporte, el 31 de julio de 2025. Para obtener más información, consulte [Migración de un registro de Amazon QLDB a Amazon Aurora](https://aws.amazon.com/blogs/database/migrate-an-amazon-qldb-ledger-to-amazon-aurora-postgresql/) PostgreSQL.

Con Amazon QLDB, puede verificar de manera eficiente la integridad de un documento del diario de su libro mayor mediante el uso de hash criptográfico con SHA-256. Para obtener más información sobre cómo funcionan la verificación y el hash criptográfico en QLDB, consulte [Verificación de datos en Amazon QLDB](verification.md).

En este paso, verificará la revisión de un documento en la tabla `VehicleRegistration` del libro mayor `vehicle-registration`. En primer lugar, solicite un resumen, que se devuelve como un archivo de salida y actúa como firma de todo el historial de cambios del libro mayor. A continuación, solicita una prueba de la revisión relativa a ese resumen. Con esta prueba, se verifica la integridad de la revisión si se aprueban todas las comprobaciones de validación.

**Verificación de la revisión de un documento**

1. Revise los siguientes archivos `.ts`, que contienen los objetos QLDB necesarios para la verificación.

   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;
      }
      ```

   1. `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));
      }
      ```

1. Utilice dos programas `.ts` (`GetDigest.ts` y `GetRevision.ts`) para realizar los siguientes pasos:
   + Solicite un nuevo resumen del libro mayor `vehicle-registration`.
   + Solicite una prueba de cada revisión del documento con el VIN `1N4AL11D75C109151` de la tabla `VehicleRegistration`.
   + Verifique las revisiones utilizando el resumen devuelto y compruébelo recalculando el resumen.

   El programa `GetDigest.ts` contiene el siguiente código.

   ```
   /*
    * 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();
   }
   ```
**nota**  
Utilice la función `getDigest` para solicitar un resumen que incluya el *tip* actual del diario del libro mayor. La sugerencia del diario hace referencia al último bloque confirmado en el momento en que QLDB recibe su solicitud.

   El programa `GetRevision.ts` contiene el siguiente código.

   ```
   /*
    * 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();
   }
   ```
**nota**  
Una vez que la función `getRevision` devuelve una prueba de la revisión del documento especificado, este programa utiliza una API del cliente para verificar esa revisión.

1. Para ejecutar el programa transpilado, introduzca el siguiente comando.

   ```
   node dist/GetRevision.js
   ```

Si ya no necesita usar el libro mayor `vehicle-registration`, continúe con [Paso 8 (opcional): limpiar recursos](getting-started.nodejs.step-8.md).