Arbitrary Off-chain Security using 0xBitcoin Merged Proof of Work — Part 2 (show me the code)

This is the first simple appendix to the article on 0xBitcoin proof of work security located here. Here we will demonstrate how to build a simple nodeJS REST service and secure the method endpoints with 0xBitcoin Proof of Work.

tldr; source code is here

WTF is difficulty anyway? (skip this if you are 0x1d00ffff)

We toss around the idea of difficulty pretty liberally in the crypto mining world, but a lot of us have no clue what it actually means beyond “if the difficulty is higher it’s, well, more difficult to mine”. This statement is sufficient to most, but if we are going to dive into using hash difficulty as a component to secure external services, we should at least have a rudimentary understanding of what it actually means.

The Bitcoin wiki describes difficulty as such - “Difficulty is a measure of how difficult it is to find a hash below a given target.” Hmm, my grade school English teacher always scolded me whenever I used the word I was defining in its definition. I think we can do better, considering the layman has a hard time conceptualizing what a target means.

In essence, when mining cryptocurrency, difficulty is the ratio of solve time to hashes processed per second (commonly known as hashrate). More simply, difficulty describes how much effort is required for a device to solve a hash within a prescribed timeframe. When 0x1d00ffff ( incidentally, this moniker is the hex number for Bitcoin’s maximum target or difficulty 1, well played, sir) put up his site to calculate 0xBitcoin mining profitability, he bases the calculation on a fairly simple formula:

seconds to solve = 2**22 * difficulty / hashrate

From this we can derive difficulty assuming a target adjustment interval for 0xBitcoin of 10 minutes ( 600 seconds ), so we can say:

difficulty = hashrate * 600 / 2*22

Interesting. If we plug in the hashrate of my GeForce 1060 cards, at 400 mh/s, we would get a “difficulty” of 114440. Inverting the formula like this means we can say that my rig bites a 114440 difficulty portion out of the 0xBitcoin network’s current solution difficulty. ( Not very substantial, considering the current difficulty is ~800 million )

Now that we’ve explored difficulty, let’s take a look at how we can use this magical number to secure endpoints.

Environment setup

This demonstration will require you to have the latest version of npm and node installed on your system. We will be using web3 to connect to 0xBitcoin on Ethereum and Express to stand up our RESTful service.

Setup with npm is simple:

npm i web3 express

Create an Interface to 0xBitcoin

You will start off by creating a lib/0xbitcoin-interface.js file that contains all of the necessary functions to interact with the 0xBitcoin Token contract:

const Web3 = require('web3')
var web3 = new Web3()
const bitcoinJson = require('../abi/0xbitcoin.json')
const BITCOIN_ADDRESS = '0xb6ed7644c69416d67b522e20bc294a9a9b405b31'
web3.setProvider('https://mainnet.infura.io/WugAgm82bU9X5Oh2qltc')
MAX_TARGET = web3.utils.toBN( 2 ).pow( web3.utils.toBN( 234 ) )
module.exports = {
async init() {
this.bitcoin = new web3.eth.Contract(bitcoinJson.abi, BITCOIN_ADDRESS)
},
async validate (publicKey, nonce) {
let challenge = await this.bitcoin.methods.getChallengeNumber().call()
let difficulty = await this.bitcoin.methods.getMiningDifficulty().call()
return this.validateNonce(challenge, publicKey, nonce, difficulty)
},

// validate the nonce
validateNonce(challenge, publicKey, nonce, difficulty) {
var digest = web3.utils.soliditySha3( challenge, publicKey, nonce )
var digestBigNumber = web3.utils.toBN(digest)
var target = this.targetFromDifficulty(difficulty)
if( digestBigNumber.lt(target) ) {
return true
}
return false
},
targetFromDifficulty(difficulty) {
return MAX_TARGET.div( web3.utils.toBN( difficulty) )
},
async getChallengeNumber() {
return await this.bitcoin.methods.getChallengeNumber().call()
}
}

The most notable function in this module is validate(), which grabs the current challenge number and difficulty from the 0xbitcoin contract. Using these variables, we are able to locally validate a potential solution nonce, before sending it off to the contract for token minting.

Create a Simple RESTful Service

Now that we have connectivity with 0xBitcoin, we can start to tie in Proofs of Work to secure restful method calls. Create a new file ‘index.js’ and crack it open with your favourite editor. ( I personally recommend Sublime Text 3).

We will then import the appropriate modules (express, web3 and our 0xbitcoin helper interface), start up the express rest server on port 8080 and initialize it to use fancy json formatting and play friendly with async/await because that’s how gangster javascript developers roll.

const express = require('express')
const Web3 = require('web3')
const web3 = new Web3()
const bitcoin = require('./lib/0xbitcoin-interface')
var app = express()
app.listen(8080, async() => {
console.log('Started Test Service on port 8080')
// initialize 0xbitcoin interface
await bitcoin.init()
})
app.use(express.json())
app.set('json spaces', 2)
app.use(function(err, req, res, next) {
console.error(err.stack)
res.status(500).send(err.stack)
})
// wrap catches for asyn calls
const asyncMiddleware = fn =>
(req, res, next) => {
Promise.resolve(fn(req, res, next))
.catch(next);
}

Now let’s create the REST service endpoint. For this example, since we will be adding payloads to each call, we will be using HTTP POST methods, denoted by ‘app.post( .. ) { … }’ in express. (For those unfamiliar with express, check out the documentation here )

Our initial example will require a miner/validator to submit a nonce, an account address ( origin), and the method payload to the REST service. The first thing we need to do is ensure that this nonce has not already been submitted to our service. In the example we use a simple in memory Set object. (In a production service, this would be much better served with a persistent database.) We then need to call the validate method from our 0xbitcoin helper to see if the nonce submitted by the user is valid on the 0xBitcoin Contract. This particular scenario uses 0xBitcoin’s current difficulty to secure the method; it will fail and throw an error if this validation fails. In other words, this method is secured only with a full 0xbitcoin block solution.

Once we validate that the nonce has provided sufficient Proof of Work, we can now progress to our service-specific logic. This can be anything we want, collating cat images, processing service-specific transactions, even connecting with external data services. The point is, anything after this validation, has been secured by Proof of Work by 0xbitcoin miners, who are compensated via merge mining for providing our service’s endpoint security. We can think of the nonce solution as a security cookie that guarantees that a certain amount of processing, time and electricity has gone into its discovery, thereby financially de-incentivizing bad actors to flood our service endpoint with bogus or manipulative payloads and/or DDOS attacks.

POST method for example 1:

// set to keep track of submitted solutions
// TODO persist this with a db
const submittedSolutions = new Set();
// an arbitrary REST service method that is secured by 0xBitcoin proof of work
// curl -d '{ "nonce":"0xdeadbeef", "origin": "0xaddress", "payload": "{ }"}' -H "Content-Type: application/json" http://127.0.0.1:8080/example1/execute
app.post('/example1/execute', asyncMiddleware( async (request, response, next) => {
// make sure repeated solutions are not submitted
if(submittedSolutions.has(request.body.nonce)){
throw 'Solution has already been submitted'
}
submittedSolutions.add(request.body.nonce)
// merge with 0xbitcoin
let bitcoinMerge = await bitcoin.validate(request.body.origin, request.body.nonce)
if (bitcoinMerge === true){
console.log('0xBitcoin solution found!!')
// TODO submit valid nonce to a pool or directly here
console.log('Submitting to 0xbitcoin or Pool for reward')
// TODO use the service payload to perform service specific logic here
let payload = request.body.payload
console.log('payload is being processed here, secured by 0xbitcoins PoW')
} else {
throw 'Could not validate Proof of Work nonce against 0xBitcoin'
}
}))

Calling this service endpoint method is easy with curl:

curl -d '{ "nonce":"0xdeadbeef", "origin": "0xaddress", "payload": "{}"}' -H "Content-Type: application/json" http://127.0.0.1:8080/example1/execute

That is all fine and dandy, assuming that the network is evenly distributed and everyone is mining fairly and even-handedly. However, as Brandon Grill pointed out yesterday in response to this proposed architecture, there may be an attack vector whereby a large farm could overtake the 0xbitcoin hashrate, and thereby attack security on subsequent services, especially at the beginning of difficulty adjustments.

Difficulty throttling to the rescue!!

So it turns out that this is a viable threat considering the relatively small hashrate of the 0xBitcoin network. So, what can services do to mitigate this? Well, because we are in control of our code, we have control over how hard the Proof of Work has to be to secure our methods. That means we can up the difficulty of our required proofs even before the 0xbitcoin adjustment period. The difficulty has a direct correlation to hashpower, which has a direct correlation to real world money spent on resources (electricity). In other words, higher nonce difficulty = higher service method security. (Inversely, services may not actually require full solutions, but fractional ones, similar to a mining pool shares. So solutions wouldn’t have to be full 0xbitcoin difficulty nonces, but partial nonces that have sufficient difficulty to be secure )

Let’s take a look at how we could implement a throttled solution.

// 0xBitcoin's max target
let MAX_TARGET = web3.utils.toBN( 2 ).pow( web3.utils.toBN( 234 ) )
// the minimum Proof of work difficulty required to secure this service
let SERVICE_MINIMUM_DIFFICULTY = 900000000
// an arbitrary REST service method that is secured by 0xBitcoin proof of work
// curl -d '{ "nonce":"0xdeadbeef", "origin": "0xaddress"}' -H "Content-Type: application/json" http://127.0.0.1:8080/example2/execute
app.post('/example2/execute', asyncMiddleware( async (request, response, next) => {
// make sure repeated solutions are not submitted
if(submittedSolutions.has(request.body.nonce)){
throw 'Solution has already been submitted'
}
submittedSolutions.add(request.body.nonce)
var challenge = await bitcoin.getChallengeNumber(contractAddress)
// validate the nonce against this service's minimum difficulty requirement
if(!validate(challenge, request.body.origin, request.body.nonce, SERVICE_MINIMUM_DIFFICULTY) ) {
throw 'Could not validate Proof of Work nonce against minimum service difficulty of ' + SERVICE_MINIMUM_DIFFICULTY
}
// merge with 0xbitcoin
let bitcoinMerge = await bitcoin.validate(request.body.origin, request.body.nonce)
if (bitcoinMerge === true){
console.log('0xBitcoin solution found!!')
// TODO submit valid nonce to a pool or directly here
console.log('Submitting to 0xbitcoin or Pool for reward')
// TODO use the service payload to perform service specific logic here
let payload = request.body.payload
console.log('payload is being processed here, secured by 0xbitcoins PoW')
}
}))
// validate the nonce
async function validate(challenge, publicKey, nonce, difficulty) {
var digest = web3.utils.soliditySha3( challenge, publicKey, nonce )
var digestBigNumber = web3.utils.toBN(digest)
var target = await bitcoin.targetFromDifficulty(difficulty)
if( digestBigNumber.lt(target) ) {
return true
}
return false
}

So as an extension to example1 we can check that the proof requires a difficulty of 900 million. If not we will eventually reject the call. (Of course even if it doesn’t validate with our service difficulty, it would still merge mine with 0xBitcoin’s current difficulty of ~800m, so let’s get some of that sweet 0xb, even if the method rejects it!!)

Conclusion

These are very rudimentary examples, but they demonstrate the added value of securing an external service using 0xBitcoin’s Proof of Work solutions. It is conceivable that an architecture like this, in communion with a simple consensus platform could operate a complete layer 2 service offering, with the ability to secure randomization agents, decentralized oracles and chain-to-chain value bridges.

You can find the full source code of these examples here.