Verifying Reports

Refer to the Kaleido Relay whitepaper the full architectural specification.

Download the latest report

Access your Public Ethereum Tether dashboard and click the Download Last Report hyperlink.

Tether Activity Summary

The report is returned as one or more JSON objects (where each object correlates to an individual node’s signed state proof) and contains the following information:

  • Node ID hash(es)
  • Block number
  • Block hash
  • Checksum(s)
  • v(s), r(s), s(s)

The values for r and s are the normal output for an ECDSA signature, with v serving as a recovery ID for cryptographic derivation of the signature’s corresponding account address. The nuances of elliptical curves and the associated signing algorithms are beyond the scope of this documentation, however, the following article – A (Relatively Easy to Understand) Primer on Elliptic Curve Cryptography – is a good place to start if you’re interested in learning more about the underlying constructs of ECDSA. For those in search of a deeper understanding behind the core primitives of elliptic curve cryptography, this article by Vitalik Buterin is an excellent resource.

Assume an environment with three nodes. For example:

Node List

A downloaded state report for this environment would look similar to the following:

[
    {
        "nodeIdHash": "0x1a0c5087e279cb9941a2661da10821a0784f1e9dcbbc44add1912ac5a2f9f9b3",
        "blockNumber": "101169",
        "blockHash": "0xea8d2cca7c58263602860b723ec9da5cc5f8a6f22197e22b90fa42a8af928433",
        "checksum": "0xd95117e40a74ed7f72e9def25ad53ac5927683a3b7c7a2efba5045e37ad4f9fc",
        "v": "28",
        "r": "0x32357a9cfb30821b1071a2d2bdd59ee5444250ff51bbc77fb57408d3c142bb8e",
        "s": "0x46de6bad76ead4ad2af7e77415b6422c1c6affaf4343ec548b846e265dbe2940"
    },
    {
        "nodeIdHash": "0xcc814cf744fffb78797fe0dc6666e13734741d17e1deb0e2098f29d007343426",
        "blockNumber": "101169",
        "blockHash": "0xea8d2cca7c58263602860b723ec9da5cc5f8a6f22197e22b90fa42a8af928433",
        "checksum": "0x235bffbde1db601db7a4d2145faf2019c4e7ed801b9a7d43fea8bc5b6d0d5c29",
        "v": "28",
        "r": "0x43b9a1f18a98de5feba9848197c4a7cfd8718080e4fd54a683d9f9eb4c2a4210",
        "s": "0x7cefa80e2994a4520a5f78eb0480bfd4b4b146d3d5c011bf3fc4ba2f40c9875a"
    },
    {
        "nodeIdHash": "0x407f869854e60ab38465a5ba26afe528793f23454b931d4d5548b1b0753cb543",
        "blockNumber": "101169",
        "blockHash": "0xea8d2cca7c58263602860b723ec9da5cc5f8a6f22197e22b90fa42a8af928433",
        "checksum": "0x63503425bc60bad29862db73696b0171b7f05f9e3f06495951e8168d151c0efd",
        "v": "27",
        "r": "0x8b7e00d0c71fee10fb9d6a7b3e26a3a4c918c6721b06e72ed4be6ac0cb8b0c3f",
        "s": "0x0c2f87ee7fe50992b3b837d5d74bd8d58aa1bec1c40c794139e431d5228ae4b7"
    }
]

The report will contain a unique JSON object for each node in the environment, with the individual objects represented in the same order as the nodes on the environmental screen. For example, node 1 maps to the first JSON object with a nodeIdHash value of 0x1a0c5087e279cb9941a2661da10821a0784f1e9dcbbc44add1912ac5a2f9f9b3. Whereas node 2 maps to the second object with a nodeIdHash value of 0xcc814cf744fffb78797fe0dc6666e13734741d17e1deb0e2098f29d007343426 and so on, until all of the nodes in the environment are accounted for.

Verify using the node details helper panel

Navigate to the environment running your tether service. Expand the dropdown next to the node whose report you wish to verify and click Node Details. Scroll to the bottom of the screen and click the Upload Report Details button

Note the verification all happens in your browser

Tether Verify Report Upload

Use the Node ID Hash as an identifier and locate the corresponding JSON object for your node. Next, populate the required arguments for Block Number, Block Hash, Signature-V, Signature-R and Signature-S. With the above state report as a frame of reference, a properly configured upload panel for node 1 would look as follows:

Tether Report Details

The report is verified by first stringifying the values for Node ID, Node ID Hash, Block Number and Block Hash. This string and the values for the three pieces of the digital signature – v, r and s – are passed as arguments to the eth.accounts.recover method in order to cryptographically derive the address of the signing account. If the ensuing result matches the Node Signing Address, then the report is considered verified.

Tether Report Verified Successfully

Programmatically Verify a State Report

The following section explains how to programmatically verify a downloaded state report using secp256k1 ECDSA cryptographic recovery maths. Note that this is the same mechanism used behind the scenes by Kaleido to verify the node’s signing address against the supplied arguments.

Before starting, ensure that you have Node.js and its accompanying package manger installed on your machine.

node -v && npm -v

If you don’t have them, they can be downloaded here.

For the sake of consistency, we will once again leverage the JSON object for node 1 as the frame of reference for this exercise:

{
    "nodeIdHash": "0x1a0c5087e279cb9941a2661da10821a0784f1e9dcbbc44add1912ac5a2f9f9b3",
    "blockNumber": "101169",
    "blockHash": "0xea8d2cca7c58263602860b723ec9da5cc5f8a6f22197e22b90fa42a8af928433",
    "checksum": "0xd95117e40a74ed7f72e9def25ad53ac5927683a3b7c7a2efba5045e37ad4f9fc",
    "v": "28",
    "r": "0x32357a9cfb30821b1071a2d2bdd59ee5444250ff51bbc77fb57408d3c142bb8e",
    "s": "0x46de6bad76ead4ad2af7e77415b6422c1c6affaf4343ec548b846e265dbe2940"
}

First, navigate to a working directory on your local machine and initialize a node project. For example:

cd $HOME/mylocaltests && npm init

We aren’t concerned with any dependencies or alternate metadata in the package.json, so press ENTER until the basic file is generated.

Next, install the web3 modules. Our program needs access to the Ethereum APIs in order to perform the cryptographic recovery of the account address. More on this at the conclusion.

npm i web3

Now, create a javascript file named index.js at the root of your directory. You’ll notice that, by default, this file is specified in the package.json as the main entry point for the application.

Populate the program with the following template code:

let Web3 = require('web3')
let web3 = new Web3()

let payload = {
    "nodeId": "zzzgnlszeq",
    "nodeIdHash": "0x1a0c5087e279cb9941a2661da10821a0784f1e9dcbbc44add1912ac5a2f9f9b3",
    "blockNumber": 101169,
    "blockHash": "0xea8d2cca7c58263602860b723ec9da5cc5f8a6f22197e22b90fa42a8af928433"
}

let msg = JSON.stringify(payload)

let result = web3.eth.accounts.recover(
    msg,
    '0x1c',
    '0x32357a9cfb30821b1071a2d2bdd59ee5444250ff51bbc77fb57408d3c142bb8e',
    '0x46de6bad76ead4ad2af7e77415b6422c1c6affaf4343ec548b846e265dbe2940')

console.log(result)

With the exception of nodeId, all of these values are extracted from the node’s state report JSON object. The 10 character string for nodeId is withheld from the publicly pinned transaction because it serves as the only uniquely identifiable piece of user information. All of the other values are either indecipherable or meaningless to any user attempting to glean state report details from the publicly callable smart contract.

The nodeId string can be easily ascertained by accessing the environmental panel in the Kaleido console and viewing the Node Details screen for the targeted node. Ensure that this page also displays the same nodeIdHash value that you see in the state report. If you’d like to manually verify the hashed representation of the Node ID, you can feed it to a SHA256 algorithm in your terminal. For example:

echo -n "zzzgnlszeq" | shasum -a 256

This returns:

1a0c5087e279cb9941a2661da10821a0784f1e9dcbbc44add1912ac5a2f9f9b3

Which, when prepended with 0x for proper hex representation, leaves us with the same value displayed in the state report.

Go ahead and run the program with the sample values:

node .

It will return us 0xAcA169A8418d3902E01DcC91C04a7161524a02Dc, the exact string for Node Signing Address displayed in the screenshots above.

Use a Different Report

To verify your own unique report, edit all of the placeholder values in the program and make sure to:

  • Specify the block number as an integer and not a string; do NOT include quotations
  • Pass the four arguments to the recover method exactly as follows: eth.accounts.recover(msg, v, r, s)
  • Specify v, the recovery ID, in hexadecimal representation. 27 is 0x1b and 28 is 0x1c

Under the covers the stringified JSON object defined as variable msg will be enveloped with \x19Ethereum Signed Message:\n" + message.length + message and hashed using Keccak256. The outputted value is the same as the checksum string in the state report and is ultimately what will be signed using the node’s private key. You can generate the same checksum string for your report by passing the stringfied JSON object to the eth.accounts.hashMessage method and logging the output.

Refer to the web3 API documentation for additional details around the hash, sign and recover methods.