📌
PAD-Dev-Docs
  • Introduction
  • How PAD Works
  • Applying for a new PAD Instance
  • Setting up a Trustee
  • Code Samples for Encryptor and Decryptor
    • Node.js
    • React Native
  • Code Samples for Auditor
    • Node.js
  • API Description
    • Authentication
    • Operator
    • Encryptor
    • Decryptor
    • Trustee
    • Validator
    • Auditor
  • Building a Find Me App With PAD
  • Glossary
Powered by GitBook
On this page
  • Computing digest of a ledger
  • Computing hash of a block
  • Computing data hash
  • Extracting transactions from block data
  • Functions and parameters
  • Extracting the transactions
  • Examples of transactions and parameters

Was this helpful?

  1. Code Samples for Auditor

Node.js

The Auditor is a cautious observer of the ledger that routinely checks the ledger's integrity.

For example, instead of requesting transaction-by-transaction (a transaction in PAD means either a data request or a Trustee/Validator response), an Auditor would request the ledger as raw blocks, which can be used to verify the integrity of the ledger. Trustee attestations allow the Auditor to confirm they have been provided with the same ledger used by the Trustees.

Computing digest of a ledger

The Auditor computes a succinct representation of the ledger, which can then be tested for consistency against the Trustee attestations. Since the ledger forms a blockchain, we define this succinct representation as the hash of the most recent block in the blockchain.

Occasionally, an Auditor may want to check on the transaction details. In the following sections, we will show how an Auditor can:

  • Compute hash of a block

  • Compute hash of a block's data section

  • Extract transactions from a block's data

Computing hash of a block

A block is composed of a header and a data section. The block header section contains the block number, previous block's hash, and the hash of the current block's data section.

Example

{
    "header": {
        "number": "1",
        "previousHash": "0ede53fd38d632f3a7d849f8ba8f70c851d772b53cecbc9d858fe7c8af03a858",
        "dataHash": "ee967360a911deb8f6bd77cbb49b334e1837c006c9b6cb2d59d8acd41964a6ac",
    },
}
const asn1 = require('asn1.js');

const cryptoUtil = require('./cryptoUtil');

/**
 * @typedef {Object} Header
 * @property {string|number} [number]
 * @property {string|Buffer} [previousHash]
 * @property {string|Buffer} dataHash
 */
/**
 * @param {Header} header
 * @return {Buffer}
 */
function computeBlockHash(header) {
  const body = function() {
    this.seq().obj(
      this.key('number').int(),
      this.key('previousHash').octstr(),
      this.key('dataHash').octstr(),
    );
  };
  const headerAsn1 = asn1.define('headerAsn1', body);
  let {number, previousHash, dataHash} = header;
  if (previousHash == null) {
    previousHash = '';
  }
  if (typeof(number) !== 'number') {
    number = parseInt(number);
  }
  if (!Buffer.isBuffer(previousHash)) {
    previousHash = Buffer.from(previousHash, 'hex');
  }
  if (!Buffer.isBuffer(dataHash)) {
    dataHash = Buffer.from(dataHash, 'hex');
  }
  const encoded = headerAsn1.encode({
    number,
    previousHash,
    dataHash,
  });
  const hash = cryptoUtil.hash(encoded);
  return hash;
}

const header1 = {
  dataHash: "af34032c92ef85b976db007fa339293253bc4e58f144cf648c6ffcd5a1150791",
};
console.log(computeBlockHash(header1).toString('hex'));
// 1c2cf6ed047ab1d35b2ed3bfbba376d99626db2213632dd4b955ceb4c05f3ba8

const header2 = {
    number: 1,
    previousHash: "1c2cf6ed047ab1d35b2ed3bfbba376d99626db2213632dd4b955ceb4c05f3ba8",
    dataHash: "cf8289074798c7e8e1d267f0c0fb83acde339fb3007ff5246fb6745a94d55883",
};
console.log(computeBlockHash(header2).toString('hex'));
// 1af3275c9db7305fc85a3ded00a7829b5d6a99deacd35ed48cd31b79c8689275

Computing data hash

When a user has utilised the data section of a block (for example, he/she extracted a data request and used it for some business logic), it is crucial for him/her to check its consistency against the dataHash entry in the block header.

The data section is an array of base64-encoded strings. Its hash is defined as the hash on the concatenation of these binary data. Below is a sample script for computing the data hash of a block:

const cryptoUtil = require('./cryptoUtil');

/**
 * @param {string[]} data
 * @return {Buffer}
 */
function computeDataHash(data) {
  const dataArray = data.map((d) => {
    return Buffer.from(d, 'base64');
  });
  return cryptoUtil.hash(...dataArray);
}

const block = {
  header: {
    dataHash: "4a148a98c3f5216c480bed070e267186751da47d3782a4282b2c383879097c80",
  },
  data: {
    data: ["datum1==", "datum2=="], // fake data
  },
};

const dataHashHex = block.header.dataHash;
const dataHash = Buffer.from(dataHashHex, 'hex');

const data = block.data.data;
console.log(computeDataHash(data).equals(dataHash)); // true

Extracting transactions from block data

Functions and parameters

There are 4 smart contract functions that write on the ledger. Their parameters are as follows:

  • Init: writes metadata of the instance

    1. stringified Trustee public keys

    2. trustee threshold

    3. stringified Validator public keys

    4. validator threshold

  • PostDataRequest: posts a data request

    1. a token

  • PostTrusteeResponse: posts a Trustee response

    1. Trustee response payload

    2. Trustee's digital signature on 1

  • PostValidatorResponse: posts a Validator response

    1. Validator response payload

    2. Validator's digital signature on 1

A piece of block data may contain one of these functions' invocations and their parameters.

Extracting the transactions

const {Type, Field} = require('protobufjs');

const protoChaincodeInput = new Type('ChaincodeInput')
    .add(new Field('args', 1, 'string', 'repeated'));

const protoChaincodeSpec = new Type('ChaincodeSpec')
    .add(new Field('input', 3, 'ChaincodeInput')).add(protoChaincodeInput);

const protoChaincodeProposalPayloadInput = new Type('ChaincodeProposalPayloadInput')
    .add(new Field('chaincodeSpec', 1, 'ChaincodeSpec')).add(protoChaincodeSpec);

const protoChaincodeProposalPayload = new Type('ChaincodeProposalPayload')
    .add(new Field('input', 1, 'ChaincodeProposalPayloadInput')).add(protoChaincodeProposalPayloadInput);

const protoChaincodeActionPayload = new Type('ChaincodeActionPayload')
    .add(new Field('chaincodeProposalPayload', 1, 'ChaincodeProposalPayload')).add(protoChaincodeProposalPayload);

const protoTransactionAction = new Type('TransactionAction')
    .add(new Field('payload', 2, 'ChaincodeActionPayload')).add(protoChaincodeActionPayload);

const protoTransaction = new Type('Transaction')
    .add(new Field('actions', 1, 'TransactionAction', 'repeated')).add(protoTransactionAction);

const protoPayload = new Type('Payload')
    .add(new Field('data', 2, 'Transaction')).add(protoTransaction);

const protoEnvelope = new Type('Envelope')
    .add(new Field('payload', 1, 'Payload')).add(protoPayload);

function getTransactions(bytes) {
  const txs = [];
  const envelope = protoEnvelope.decode(bytes);
  const actions = envelope.toJSON().payload.data.actions;
  for (const action of actions) {
    txs.push(action.payload.chaincodeProposalPayload
        .input.chaincodeSpec.input.args);
  }
  return txs;
}

const block = {
  header: {
    // ...
  },
  data: {
    data: [
      'CpsDEpgDCpUDEpIDCo8DCowDCokDGoYDChNQb3N0VHJ1c3RlZVJlc3BvbnNlCrIBeyJ0eXBlIjoidHJ1c3RlZV9yZXNwb25zZSIsInBhZE5hbWUiOiJteS1wYWQtMS4wIiwidHJ1c3RlZUlkIjoidHJ1c3RlZTEiLCJ0b2tlbiI6ImYxMzQxZmM5MDU5Zjg1N2MyMThmNTEwNzcxZTJjODlmIiwidHJ1c3RlZVNoYXJlIjoiQWtGdVdiQUcxU2NjbDY5UlduMnhIU2lRUGRsaFlvbVpuRnF5SE81bFFEZGwifQq5AXsic2lnbmVyTWV0YWRhdGEiOnsiaWQiOiJ0cnVzdGVlMSIsImZ1bGxOYW1lIjoiVHJ1c3RlZS0xIiwicm9sZSI6IlRydXN0ZWUifSwicGF5bG9hZCI6Ik1FWUNJUUNCMHJFa2FvZGR6VWQ0eXVJUmFuTlZ5ZVlDK21JZmxPSm5Mdm82RnF0QXRBSWhBSys3cWxtenJnZ1diRTEzVDJvcUErNS9wSUdiZmluZFl3OEFTbU1xV21vNSJ9',
      'CkESPwo9EjsKOQo3CjUaMwoPUG9zdERhdGFSZXF1ZXN0CiAyMWJmNTdkZDUyYTNiODEzMjhmZTEzOGFjNGU4ZWQxYg==',
    ],
  },
}
const blockData = block.data.data;

const data0B64 = blockData[0];
const data0 = Buffer.from(data0B64, 'base64');

console.log(getTransactions(data0));
// [
//   [
//     'PostTrusteeResponse',
//     '{"type":"trustee_response","padName":"my-pad-1.0","trusteeId":"trustee1","token":"f1341fc9059f857c218f510771e2c89f","trusteeShare":"AkFuWbAG1Sccl69RWn2xHSiQPdlhYomZnFqyHO5lQDdl"}',
//     '{"signerMetadata":{"id":"trustee1","fullName":"Trustee-1","role":"Trustee"},"payload":"MEYCIQCB0rEkaoddzUd4yuIRanNVyeYC+mIflOJnLvo6FqtAtAIhAK+7qlmzrggWbE13T2oqA+5/pIGbfindYw8ASmMqWmo5"}'
//   ]
// ]

const data1B64 = blockData[1];
const data1 = Buffer.from(base1B64, 'base64');

console.log(getTransactions(data1));
// [
//   [
//     'PostTrusteeResponse',
//     '21bf57dd52a3b81328fe138ac4e8ed1b'
//   ]
// ]

Examples of transactions and parameters

Coming soon!

PreviousCode Samples for AuditorNextAPI Description

Last updated 2 years ago

Was this helpful?

When we say "hash" of a block, we mean the hash of its header section. Below is a Node.js example of how to compute the hash given a JSON representation of the header. The dependency cryptoUtil is defined .

It is possible to extract the invoked smart contract functions and parameters from block data. The resulting value is an array of arrays of strings where the first strings in the inner arrays are the functions and the succeeding strings are the parameters. Since the blocks are protocol-buffer-encoded, we need to use a library for decoding.

here
protobufjs