PEG API Auditing

As of block 222270, PegNet switched from using the bootstrap formula for the value of PEG to using the market value, calculated from the three exchanges that list the token: CiteX, ViteX, and VineX. For miners to be able to oraclize this data, we needed to add APIs to the system. Every asset in PegNet has at least two separate APIs to pull data from and we did not want to make an exception for PEG. CoinGecko was the only existing API for the price of PEG, so two entities in the PegNet community stepped up: Factoshi and Factomizewith the goal of being phased out as other data aggregators come online.. The APIs of Factoshi and Factomize are the focus of this blog and the auditing tool.

As Factomize operates PegNetMarketCap.com and some who work for Factomize own pAssets, acting as one of the providers for the value of PEG presents a potential conflict of interest. For that reason, we felt it beneficial to create a tool that helps others keep us accountable. The way it works is simple:

  • We publish price data from our PegNet API every minute on the blockchain
  • Third parties can compare the values they receive from our API and see if it diverges from what we publish
  • Analysts can compare historical trends to spot manipulation

The Data

All of our entries are written to the chain 843dbee7a49a9b9510d399759fbce24b1f700268c94508085abce352d70ed1f6, at the rate of one every minute. Each entry should be validated by third parties before being included in analysis.

ExtIDBytesDescription
010The Unix timestamp is a string of digits representing the time that the
API endpoints were accessed. This timestamp should be within
1-2 minutes of the Entry’s timestamp.
132The ed25519 public key that signed this Entry.
264The ed25519 signature of this entry. The signature covers the following:
(32-byte chain id | 10-byte ExtID[0] | the Entry's contents)

At the time of writing, the public key belonging to PegNetMarketCap is: 90a5ad85e62dbc535f98c424429a3ea6e285538231ab1324136403cbdc459ae1. This key is also published in the footer of pegnetmarketcap.com.

The content itself is encoded in plaintext JSON, with the keys “PNMC” and “Factoshi” containing the full response from the respective APIs:

Note: since these responses are JSON themselves, the entry contains nested JSON which you will have to parse as well.

Interpreting the Data

This is by far the most challenging aspect: what does manipulated data look like? The first thing that should be mentioned is that PNMC’s and Factoshi’s API implements are different and independent of each other. That means that it’s possible for their numbers to diverge but both still be valid, meaning not manipulated.

Scenario A: Individual Manipulation

If PNMC were to serve different values to different clients, then some miners could be invalidated to favor others. This is fairly easy to detect by miners themselves:

  1. Query the API and note the price
  2. Wait for the audit entry for that minute and compare the two values
  3. If the value in 1 is significantly different from the value in 2, the API selectively returned bad data

Alternatively, you can also query the prices of all APIs at the start of a mining block, then compare the price of PEG in all of the OPRs. Since the values in the OPRs have to come from one of the three APIs, there shouldn’t be a fourth outlier in there. That would indicate either one of the APIs serving bad data or one of the miners attempting to submit bad values.

Scenario B: Complete Manipulation

If, for example, Exchange A has a PEG price of 0.003 USD, Exchange B has 0.0029, and Exchange C has 0.0035, the APIs should be a price that is within that range. If an API, for example, returns 0.0028 or 0.0036 it would be fairly suspicious. Suspicious does not necessarily indicate manipulation; the API could be using a rolling average that slightly lags behind rapid price jumps.

However, if you track these differences over a longer period of time and consistently see values that are outside of an acceptable margin of error, the API may be driving prices up or down.

Scenario C: Exchange Manipulation

If the API reports odd or unexpected numbers, it could also indicate that data feeds they read from are being manipulated. Since PNMC and Factoshi have different implementations, this may not always be true, but if both APIs exhibit similar manipulations, the root cause is likely from the data feeds themselves.

Please note: These manipulations are not limited to PNMC or Factoshi. Any of the APIs or exchanges can potentially engage in manipulation to raise or lower prices on PegNet. Due to our direct involvement in the PegNet community, we want to be as transparent as possible about our data.

PNMC vs Factoshi

Using the data gathered over time, we have the data to compare the price of PEG determined by these two APIs:

If you want to gather this data yourself, there is an export example in the examples directory. The program downloads the entire chain and then prints out a comma-separated list of entries in the form of “time, PNMC price, Factoshi price”. It uses the open node to download the data, which may take several minutes. If you change the example to point to a local node, it is significantly faster.

The program output used to create the above graph is available for download: https://gist.github.com/WhoSoup/1f789b2103d6750f101dbf77149e3dff

Example Verification

Let’s take the entry 3c715dba3a70a0cbcc6b10dee73dbcb3f09141f776a63457679db85d821e6780 as an example for how to verify the content. It is not feasible to calculate the signature by hand, so here’s a brief code snippet written in Golang:

    chain, _ := hex.DecodeString("843dbee7a49a9b9510d399759fbce24b1f700268c94508085abce352d70ed1f6")
    timestamp := []byte("1576061423")
    content := []byte(`{"Factoshi":"{\"price\":0.0034144444,\"updated_at\":1576061416,\"quote\":\"USD\",\"base\":\"PEG\"}","PNMC":"{\"ticker_symbol\":\"PEG\",\"exchange_price\":\"0.00341280\",\"exchange_price_dateline\":1576061415}"}`)
    pubkey, _ := hex.DecodeString("90a5ad85e62dbc535f98c424429a3ea6e285538231ab1324136403cbdc459ae1")
    signature, _ := hex.DecodeString("7c7b9273681ecac228c6548e341163d972d3e13215b97f9a3ca8f4f4b1c4cd272a00464afec32b2de0f34c95620857f521485594d530720b6afa46b942b05804")

    // build the data for the signature
    data := chain
    data = append(data, timestamp...)
    data = append(data, content...)

    // verify the signature
    fmt.Println("Signature valid?", ed25519.Verify(pubkey, data, signature))

    // verify the time
    fmt.Println("Local timezone:", time.Unix(1576061423, 0))
    fmt.Println("           UTC:", time.Unix(1576061423, 0).UTC())

If you want to run it yourself, the example works in the golang playground: https://play.golang.org/p/5YixoBEjW7k. The source code is also available in the repository: https://github.com/WhoSoup/pnmc-auditor/blob/master/examples/verify/verify.go

The output will be something like:

Signature valid? true
Local timezone: 2019-12-11 11:50:23 +0100 CET
           UTC: 2019-12-11 10:50:23 +0000 UTC

At this point, you know the signature is valid and was signed with PNMC’s public key. All that’s left is checking the explorer to see when the entry was submitted:


Wednesday, December 11, 2019, 11:51 (UTC+0100)

That is within 37 seconds of the entry’s timestamp and therefore well inside the window.

Example Comparison

Let’s consider the block height 222520 and look at the Audit Chain EBlock for that height: 61337adc9e34ab051d32da7c307a1ef731f1daf06f794798b2e46550c850ce64. It contains ten entries, including the one in the example above. We know from the way PegNet works that miners pull prices near the beginning of the block. Let’s compare the first entry in the eblock (60d9b61f7b4133e1d4f12f507391427d2c4fc920d678accb7f983ad9ea9d9e8d) with the data from pegnet.

The audit entry contains the following prices:

  • Factoshi: 0.0033699656
  • PNMC: 0.00335991

We can query the prices from pegnetd directly, via pegnetd get rates 222520:

{"PEG":"0.00335991","pADA":"0.03689946","pBNB":"15.00111626","pBRL":"0.24119482","pCAD":"0.7553897","pCHF":"1.01509445","pCNY":"0.14203944","pDASH":"50.52701026","pDCR":"20.00270541","pETH":"146.43893541","pEUR":"1.10802096","pFCT":"2.80600488","pGBP":"1.31450955","pHKD":"0.12800409","pINR":"0.01410924","pJPY":"0.00920175","pKRW":"0.00083683","pLTC":"44.62245885","pMXN":"0.05196829","pPHP":"0.01967516","pRVN":"0.02607645","pSGD":"0.73538604","pUSD":"1","pXAG":"16.68947561","pXAU":"1466.27565982","pXBC":"208.31591893","pXBT":"7269.33948649","pXLM":"0.05370935","pXMR":"53.5554196","pZEC":"31.08442599"}

The price for PEG matches the one in the audit trail, so we know that the winning miner that block used PNMC’s API.

If we compare the last entry (the one used in the example above) in the EBlock, we get the following:

  • Factoshi: 0.0034144444
  • PNMC: 0.00341280
  • Rate of block 222521: 0.003412

It’s very close but not exact to PNMC’s because there is a 1-minute difference between that entry and the data that miners pull.

The App

For those who don’t trust us to self-audit, you are welcome to start your own audit chain. The more the merrier! The code is open source on GitHub with pre-built binaries available.

Before you start it, you will need:

  • To create a new chain on the factom protocol
  • A funded EC Address
  • An FA address you want to use to sign

The app looks for config.ini in the working directory or the PATH. The configuration options are:

  • paying: This is an EC address key that is used to pay for the entries written to a chain
  • signing: This is an FA address key that is used to cryptographically sign the entries. The public key will be published along with the signature
  • chain: This is the chain to write entries to. It has to be created separately
  • factomd: This points to the open node. If you want to use a different node, change this
  • interval: This is the time in seconds that it will write entries. A 60-second interval will cost 1440 EC a day.

To run, just start the executable.

Conclusion

While it’s not the perfect situation, we hope that this data will provide a solid foundation of trust in the accuracy of the PNMC (and Factoshi) data. Please note that Factoshi will likely be the first to retire its API as other aggregators come online so the comparison won’t always be available but PNMC will continue to publish its data to Factom. If you have questions about the inner workings, the code, or the data, please feel free to reach out to me (Who#3791) on on the PegNet Discord.