Monitoring the Poloniex Exchange with Factom

The Poloniex Factoid Price Monitoring Chain is used to verify the history of the Factoid price on the Poloniex exchange. Factom makes it possible to write real time trading information into an immutable Entry, making it impossible for Factom or Poloniex to lie in the future about the history of the Factoid price. The First Entry of the Chain has as its first External ID a 32 byte Edwards25519 public key. All valid Entries in the Chain will contain JSON data about the price of Factoids in Bitcoin, and the price of Bitcoin in USD received in response to calls to the Poloniex Public API. The signature of the Entry Content by the private key is written into the first External ID of each Entry, proving the Entry was written by the authorized application. Any application may then read the valid data from the Chain by downloading all of the Entries in the Chain and filtering out any Entries not signed by the correct key, or containing incorrectly formatted data.

All code, unless otherwise noted, is Copyright Factom Foundation and may be used or modified under the terms of the MIT License. https://github.com/FactomProject/Testing/tree/master/examples/go/poloniex

The Poloniex Chain First Entry

The first External ID of the First Entry in the Chain contains the 32 byte Edwards25519 Public Key. The other External IDs are human readable identifiers in plaintext.

factom-cli get firstentry 4bf71c177e71504032ab84023d8afc16e302de970e6be110dac20adbf9a19746
ChainID: 4bf71c177e71504032ab84023d8afc16e302de970e6be110dac20adbf9a19746
ExtID: ʁQ{!=sDeČogUJ
ExtID: Factom
ExtID: Poloniex
ExtID: Factoid
ExtID: FCT_BTC BTC_USDT
Content:
The Poloniex Factoid price monitoring chain will record the FCT_BTC and the BTC_USDT price listed on the Poloniex exchange. The prices are listed by the Poloniex API (https://poloniex.com/support/api) and signed with an edwards25519 key. The hex encoded public key, 0xca81e518e9a5519b7b218b85b13d73447f65c48c9c6f1b67db55a54ab48fc1de is the fist External ID of the First Entry in this Factom Chain.

Writing Entries

Each valid Entry in the Poloniex Chain contains the JSON responses from 2 API calls to poloniex.com listing current bid and ask prices for Factoids denoted in Bitcoin, and the bid and ask prices for Bitcoin denoted in USD. The Entry Content is signed by the Edwards 25519 Private Key and the Signature is written as the first External ID of the Entry.

// Copyright 2015 Factom Foundation
// Use of this source code is governed by the MIT
// license that can be found in the LICENSE file.
package main
import (
    "encoding/hex"
    "encoding/json"
    "io/ioutil"
    "log"
    "net/http"
    "time"
    "github.com/FactomProject/factom"
    ed "github.com/agl/ed25519"
)

The OrderBook data structure contains the JSON responses from Poloniex as well as a timestamp at the time of writing.

A timestamp also exists in the Entry Block containing the hash of the Entry in Factom. The Entry Block timestamp is more reliable and secure, but a timestamp is added here as well to prevent a possible replay attack, where an attacker might submit an exact copy of an earlier Entry along with the valid signature at a later time. Adding the timestamp inside the signed Content makes it possible to check the time against the canonical timestamp from the Entry Block.

type OrderBook struct {
    Timestamp int64
    FCT_BTC json.RawMessage
    BTC_USD json.RawMessage
}

The main program is run by the cron daemon every 10 minutes.

A new instance of type *OrderBook will be created. The timestamp and the JSON returns from Poloniex are written into this instance.

func main() {
    book := new(OrderBook)
    // add the timestamp to the data structure
    book.Timestamp = time.Now().Unix()
    // Get Factoid price
    resp1, err := http.Get("http://poloniex.com/public?command=returnOrderBook¤cyPair=BTC_FCT&depth=4")
    if err != nil {
        log.Fatal(err)
    }
    defer resp1.Body.Close()
    fctdata, err := ioutil.ReadAll(resp1.Body)
    if err != nil {
        log.Fatal(err)
    }
    book.FCT_BTC = fctdata
    // Get BTC_USD price
    resp2, err := http.Get("http://poloniex.com/public?command=returnOrderBook¤cyPair=USDT_BTC&depth=4")
    if err != nil {
        log.Fatal(err)
    }
    defer resp2.Body.Close()
    btcdata, err := ioutil.ReadAll(resp2.Body)
    if err != nil {
        log.Fatal(err)
    }
    book.BTC_USD = btcdata
    // ...

The instance containing the JSON and the timestamp is marshaled into a JSON object (JSON inside JSON), then signed with the Private Key. All Key names have been altered to protect the innocent.

    // ...
    data, err := json.Marshal(book)
    if err != nil {
        log.Fatal(err)
    }
    secKey := new([64]byte)
    if s, err := hex.DecodeString("********************************************************************************************************************************"); err != nil {
        log.Fatal(err)
    } else {
        copy(secKey[:], s)
    }
    sig := ed.Sign(secKey, data)
    // ...

The new *factom.Entry is constructed for the Poloniex Chain, with the Signature as its first External ID and the JSON encoded OrderBook (type []byte) as its Content.

The Entry is committed, and after 10 seconds, is revealed to the Factom Network.

    // ...
    e := factom.NewEntry()
    e.ChainID = "4bf71c177e71504032ab84023d8afc16e302de970e6be110dac20adbf9a19746"
    e.ExtIDs = append(e.ExtIDs, sig[:])
    e.Content = data
    if err := factom.CommitEntry(e, "poloniex"); err != nil {
        log.Fatal(err)
    }
    time.Sleep(10 * time.Second)
    if err := factom.RevealEntry(e); err != nil {
        log.Fatal(err)
    }
}

The Factom Entry will look something like this entry from 2015-10-15 00:00:00 UTC

% factom-cli get entry c384f1f20fae5dbe0d20a886cedec67ba5f2ecaa78ad2f81b8798b25c7ec309f
ChainID: 4bf71c177e71504032ab84023d8afc16e302de970e6be110dac20adbf9a19746
ExtID: tסl3WhN,Tb''zP*0*ErikOw&u/$
Content:
{"Timestamp":1444867202,"FCT_BTC":{"asks":[["0.00043958",98.99517698],["0.00043959",97.65131925],["0.00043987",5.4218476],["0.00044000",454.54545454]],"bids":[["0.00042000",43.76962126],["0.00041541",0.48662714],["0.00041540",100],["0.00041286",0.26659061]],"isFrozen":"0"},"BTC_USD":{"asks":[["252.29997287",0.15777835],["252.29997288",0.002913],["252.96000001",0.24341561],["253.25580001",0.09796616]],"bids":[["251.02606906",0.24713845],["251.02606905",0.00025],["251.00000000",1.79041],["250.51731826",0.00025]],"isFrozen":"0"}}

Reading the Chain

Reading the Poloniex Monitoring Chain is done in two steps.

First all of the Entries in the Chain are downloaded. Second invalid Entries are filtered out of the list.

func getValidEntries() ([]*factom.Entry, error) {
    es, err := factom.GetAllChainEntries(chainid)
    if err != nil {
        return nil, err
    }
    validEntries := make([]*factom.Entry, 0)
    for _, e := range es {
        if poloniexIsValid(e) {
            validEntries = append(validEntries, e)
        }
    }
    return validEntries, nil
}

The Entries are validated by checking the signature with the Public Key. Checking the validity of the timestamp in the Content against the Entry Block timestamp is left as an exercise for the reader.

Notice that no error checking is done on the External ID size or the data it contains. The first 64 bytes of any data in the first External ID is copied into the static array and checked as if it were the signature.

func poloniexIsValid(e *factom.Entry) bool {
    pub := new([32]byte)
    if p, err := hex.DecodeString("ca81e518e9a5519b7b218b85b13d73447f65c48c9c6f1b67db55a54ab48fc1de"); err == nil {
        copy(pub[:], p)
    }
    sig := new([64]byte)
    copy(sig[:], e.ExtIDs[0])
    if !ed.Verify(pub, e.Content, sig) {
        return false
    }
    b := new(OrderBook)
    if err := json.Unmarshal(e.Content, b); err != nil {
        return false
    } else if b.Timestamp == 0 {
        return false
    }
    return true
}

The list of valid Entries can be used by an application to read, parse, and process the JSON-formatted history of the Poloniex API.

The JSON data is passed to a webpage for visualization using the html/template library. The returned JSON data is passed to the d3.js library which generates a line graph displaying in the browser:

Poloniex Factoid Dollar

Since all of the chart’s data has been retrieved from the Factom chain, it is historically verified. There is no need to worry about the data having been retroactively tampered with. The live chart of the Factomized data may be viewed here.

By Michael Beam

Special thanks to Adam Langley who wrote the Golang implementation of the Ed25519 library.

Recent Posts

Factom Inc.’s Growth Triggers Leadership Team Expansion

Factom Inc.’s Growth Triggers Leadership Team Expansion

Posted: November 7, 2017 In Press Releases

Factom Inc.’s Growth Triggers Leadership Team Expansion

Factom Harmony

Factom Harmony

Posted: March 2, 2017 In Press Releases

Factom Harmony is a commercial product for the mortgage industry. It is built on the Factom Apollo data management solution and allows users to store and create permanent mortgage records.

The Great Migration

The Great Migration

Posted: December 30, 2016 In Factom Foundation

Factom Genesis is migrating to the new Factom Federation network. We will be deploying eight federated servers and eight audit servers to the Federation network. This migration will usher in an era of censorship-resistant and global distribution.