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",
},
}
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 here.
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 instancestringified Trustee public keys
trustee threshold
stringified Validator public keys
validator threshold
PostDataRequest
: posts a data requesta token
PostTrusteeResponse
: posts a Trustee responseTrustee response payload
Trustee's digital signature on 1
PostValidatorResponse
: posts a Validator responseValidator response payload
Validator's digital signature on 1
A piece of block data may contain one of these functions' invocations and their parameters.
Extracting the transactions
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 protobufjs
for decoding.
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!
Last updated
Was this helpful?