Skip to content
This repository has been archived by the owner on Mar 28, 2023. It is now read-only.

Bitcoin SPV, Funding Proof and Electrum Client #8

Merged
merged 43 commits into from
Jul 30, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
ac441e2
dapp initial package config
nkuba Jul 23, 2019
18b8a7e
ElectrumX JS client
nkuba Jul 23, 2019
3ef3857
Bitcoin SPV JS implementation
nkuba Jul 23, 2019
8cfd877
Bitcoin transaction parser
nkuba Jul 23, 2019
2651ff8
Script for Funding Proof
nkuba Jul 23, 2019
811af2d
Initial main script
nkuba Jul 23, 2019
38a74a6
Utils scripts
nkuba Jul 23, 2019
be35a6a
Bitcoin TX test data
nkuba Jul 23, 2019
79cede8
Update dependency to electrum-client version
nkuba Jul 23, 2019
faeb58b
Used bcoin libraries for transaction parsing
nkuba Jul 23, 2019
7899a95
Add dependencies to bcoin and bufio
nkuba Jul 23, 2019
95c81ff
Added case for legacy tx to parser test
nkuba Jul 23, 2019
c909fa8
Reorganized structure of the modules
nkuba Jul 24, 2019
c82cc2c
Added .gitignore
nkuba Jul 24, 2019
5eccbea
Public key to address converter
nkuba Jul 24, 2019
15b2517
Add eslint to each package
nkuba Jul 25, 2019
4502cd7
Main package with submodules
nkuba Jul 25, 2019
1700fcf
Refacotr BitcoinSPV to drop class
nkuba Jul 25, 2019
05c262a
Remove unneded file
nkuba Jul 25, 2019
852f091
ChainType -> Network
nkuba Jul 25, 2019
9838f60
Update electrum client after review
nkuba Jul 25, 2019
e22ecc9
Update bitcoin utils exports
nkuba Jul 25, 2019
b69025b
Removed Config script
nkuba Jul 25, 2019
56b1602
Merged libs into one package
nkuba Jul 25, 2019
e25f475
Electrum Config and initialization in BitcoinSPV
nkuba Jul 25, 2019
3b86872
Changed forEach to for loop
nkuba Jul 26, 2019
c101d81
Use bcrypto library for merkle proof verification
nkuba Jul 26, 2019
0a5c26b
Fix trailing comma in package.json
nkuba Jul 26, 2019
f11e0a3
Added testnet parent section to config
nkuba Jul 26, 2019
535c9d5
Remove unused variable
nkuba Jul 26, 2019
bf4e03c
Remove commas
nkuba Jul 29, 2019
129d115
Return from loop in find output for address
nkuba Jul 29, 2019
db7e7f9
Comment about confirmations value
nkuba Jul 29, 2019
7cf4471
Replace dependency electrum-client with electrumjs
nkuba Jul 29, 2019
68b0fe0
Connection details to tbtc electrum server
nkuba Jul 29, 2019
9ea6d93
Scripts stub interfaces
nkuba Jul 29, 2019
cbe5d6c
Updates to funding proof script
nkuba Jul 29, 2019
253756e
Update proof test data
nkuba Jul 29, 2019
c0917b4
rewire dependency for tests
nkuba Jul 29, 2019
d7400c2
Update Address documentation
nkuba Jul 29, 2019
4aa6a47
Docs for Bitcoin SPV
nkuba Jul 29, 2019
5b2860e
Documentation for electrum client
nkuba Jul 29, 2019
2b65fd8
build->built
nkuba Jul 30, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules/
*.code-workspace
17 changes: 17 additions & 0 deletions client/.eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"extends": "eslint-config-keep",
"parserOptions": {
"ecmaVersion": 2017,
"sourceType": "module"
},
"env": {
"es6": true,
"mocha": true
},
"rules": {
"semi": [
2,
"never"
]
}
}
9 changes: 9 additions & 0 deletions client/config/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should break this into testnet and mainnet sections

NB: Testnet electrum servers can be extremely unreliable. we're in the process of standing up our own. If you guys are interested in running one as well, we should coordinate

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We noticed the instability of the testnet electrum servers. We're in the middle of setting up our own instance as well.

CC: @sthompson22, @pdyraga

"electrum": {
"testnet": {
"server": "electrumx-server.tbtc.svc.cluster.local",
"port": 50002,
"protocol": "tls"
}
}
}
2,249 changes: 2,249 additions & 0 deletions client/package-lock.json

Large diffs are not rendered by default.

26 changes: 26 additions & 0 deletions client/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "tbtc-client",
"version": "0.0.1",
"description": "",
"scripts": {
"test": "CONFIG_FILE=${npm_package_config_file} mocha --timeout 5000",
"js:lint": "eslint .",
"js:lint:fix": "eslint --fix ."
},
"config": {
"file": "./config/config.json"
},
"author": "Jakub Nowakowski <[email protected]>",
"license": "ISC",
"dependencies": {
"tbtc-helpers": "file:../lib/tbtc-helpers"
},
"devDependencies": {
"chai": "^4.2.0",
"fs": "0.0.1-security",
"mocha": "^6.2.0",
"rewire": "^4.0.1",
"eslint": "^5.16.0",
"eslint-config-keep": "git+https://github.com/keep-network/eslint-config-keep.git#0.2.0"
}
}
14 changes: 14 additions & 0 deletions client/src/Deposit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// PAGE 1: MAKE A DEPOSIT
liamzebedee marked this conversation as resolved.
Show resolved Hide resolved
async function createDeposit() {
// TODO: Implement:
// Call the deposit factory to get a new deposit
// return the deposit address.
}

// PAGE 2: PUT A BOND
async function initializeDeposit(depositAddress) {
// TODO: Implement:
// 1. Call deposit to create new keep
// 2. Watch for ECDSAKeepCreated event from ECDSAKeepFactory contract
// 3. call get public key
}
92 changes: 92 additions & 0 deletions client/src/FundingProof.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
const BitcoinTxParser = require('tbtc-helpers').BitcoinTxParser
const bitcoinSPV = require('tbtc-helpers').BitcoinSPV
const ElectrumClient = require('tbtc-helpers').ElectrumClient

const fs = require('fs')

/**
* Reads electrum client configuration details from a config file.
* @param {string} configFilePath Path to the configuration file.
* @return {ElectrumClient.Config} Electrum client configuration.
*/
function readElectrumConfig(configFilePath) {
const configFile = fs.readFileSync(configFilePath, 'utf8')
liamzebedee marked this conversation as resolved.
Show resolved Hide resolved
config = JSON.parse(configFile)

return new ElectrumClient.Config(
config.electrum.testnet.server,
config.electrum.testnet.port,
config.electrum.testnet.protocol
)
}

const electrumConfig = readElectrumConfig(process.env.CONFIG_FILE)

/**
* Gets transaction SPV proof from BitcoinSPV.
* @param {string} txID Transaction ID
* @param {number} confirmations Required number of confirmations
*/
async function getTransactionProof(txID, confirmations) {
bitcoinSPV.initialize(electrumConfig)

const spvProof = await bitcoinSPV.getTransactionProof(txID, confirmations)
.catch((err) => {
bitcoinSPV.close()
return Promise.reject(new Error(`failed to get bitcoin spv proof: ${err}`))
})

bitcoinSPV.close()

return {
merkleProof: spvProof.merkleProof,
txInBlockIndex: spvProof.txInBlockIndex,
chainHeaders: spvProof.chainHeaders,
}
}

// PAGE 5: Submit Proof
// 1. Get transaction proof
// 2. Submit proof to tBTC

/**
* Calculates deposit funding proof and submits it to tBTC.
* @param {string} txID Funding transaction ID.
* @param {number} fundingOutputIndex Position of a funding output in the transaction.
*/
async function calculateAndSubmitFundingProof(txID, fundingOutputIndex) {
if (txID.length != 64) {
return Promise.reject(
new Error(`invalid transaction id length [${txID.length}], required: [64]`)
)
}

// TODO: We need to calculate confirmations value in a special way:
// See: https://github.com/keep-network/tbtc-dapp/pull/8#discussion_r307438648
const confirmations = 6

const spvProof = await getTransactionProof(electrumConfig, txID, confirmations)

// 2. Parse transaction to get required details.
const txDetails = await BitcoinTxParser.parse(spvProof.tx)
.cath((err) => {
return Promise.reject(new Error(`failed to parse spv proof: ${err}`))
})

// 3. Submit proof to the contracts
// version: txDetails.version,
// txInVector: txDetails.txInVector,
// txOutVector: txDetails.txOutVector,
// fundingOutputIndex: fundingOutputIndex,
// locktime: txDetails.locktime,
// merkleProof: spvProof.merkleProof,
// txInBlockIndex: spvProof.txInBlockIndex,
// chainHeaders: spvProof.chainHeaders,


// return eth transaction id to later convert it to etherscan link
}

module.exports = {
calculateAndSubmitFundingProof,
}
24 changes: 24 additions & 0 deletions client/src/FundingTransaction.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// PAGE 3: Pay BTC
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getAddress is one I've started implementing.

None of the BTC functions are implemented, only ETH-related ones. But I have started building a little skeleton since there is crossover in the flow.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mentioning this because while it's not necessarily related to the review, it's easier (?) to communicate information here with context than in Flowdock with no code. :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Get address will be implemented in #14

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice one with implementing ETH part!
We can use it to get a public key and then call https://github.com/keep-network/tbtc-dapp/blob/funding/lib/tbtc-helpers/src/Address.js#L18 to convert a public key to a BTC address.

async function getAddress(depositAddress) {
// TODO: Implement:
// 1. Get keep public key from the deposit
// 2. Calculate P2WPKH address from the key
}

// Transition from PAGE 3 to PAGE 4
// 1. Wait for transaction on chain
// 2. Return transaction ID
async function getFundingTransactionID(electrumConfig, bitcoinAddress) {
// TODO: Implement
}

// PAGE 4. WAITING FOR CONFIRMATIONS
async function waitForConfirmations(transactionID) {
// TODO: Implement:
// 1. Wait for required number of confirmations for the transaction
// 2. Monitor confirmations on the chain and return when ready
}

module.exports = {
getAddress, getFundingTransactionID, waitForConfirmations,
}
20 changes: 20 additions & 0 deletions client/test/FundingProofTest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
const rewire = require('rewire')
const FundingProof = rewire('../src/FundingProof')

const fs = require('fs')
const chai = require('chai')
const assert = chai.assert

const TX_ID = '72e7fd57c2adb1ed2305c4247486ff79aec363296f02ec65be141904f80d214e'
const CONFIRMATIONS = 6

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that confirmations is not a reliable indication. We need to add a routine to get new headers until we reach sufficient accumulated difficulty.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I noticed this problem on testnet when blocks get difficulty of 1.

A required accumulated difficulty would be difficulty from the block where the transaction was included multiplied by the factor (now 6), right?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the difficulty 1 thing is an intended feature of testnet, but still annoying to deal with.

A reasonable accumulated difficulty must be determined by the verifier. For TBTC's purposes this is a system parameter that adjusts with the current chain difficulty.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we expect that tBTC will expose the required difficulty so we could get it here to check if the transaction has enough confirmations and we can start proof submission?

Copy link

@prestwich prestwich Jul 26, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this will be available as a view function, or similar

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now we can leave this and add a // TODO with a link to this discussion?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Raised an issue for this: keep-network/tbtc#223


describe('FundingProof', async () => {
it('getTransactionProof', async () => {
const proofFile = fs.readFileSync('./test/data/proof.json', 'utf8')
const expectedResult = JSON.parse(proofFile)

const result = await FundingProof.__get__('getTransactionProof')(TX_ID, CONFIRMATIONS)
liamzebedee marked this conversation as resolved.
Show resolved Hide resolved

assert.deepEqual(result, expectedResult)
})
})
5 changes: 5 additions & 0 deletions client/test/data/proof.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"merkleProof": "4e210df8041914be65ec026f2963c3ae79ff867424c40523edb1adc257fde77252846fd232df9ac2952dbdff1981c904abeae46ff4d4fa70bf2767df5bbb5b8bb9984f0773ca4efc53aba90ef129dac6161b1d86360822a8e46579b0ac7b6353bcdfe49c319a2c576f477fc49a38c6a4c2e942297e2105eb4c098d1038e76702b4744892e0ddd033729ec659613232546d2db8522bca7896d9480cc5ee0de03413a9d78eed747819794321140354f05879f1010951aedb0c62a3866c5e43537a5b7fd1e8290632d71b53e9a18cb3e80570145a5cc3d46ca1ea0c35ceb7db15632f5d72f7ea5eed3a46ce0543102ffffddcf92a951e870862640602dcda0c384037700982aea6e09db84bbd503634409a31830546656841f2cfd11fa7e6ba745df933d20827a26696c8fb73debac281ffb65fd18e7c11ad9a00e059c6cfdc6d29583b7a45472123fac1003384cc60fce2129c8d7364969dfa35021ab26c0b0449",
"txInBlockIndex": 177,
"chainHeaders": "00000020a114bf2d1e930390044cc4e00dd2f490a36dcecb4b6bb702b502000000000000583b7a45472123fac1003384cc60fce2129c8d7364969dfa35021ab26c0b0449bccc2e5dffff001d061013a10000ff3f3210404a744c3170cdd6ad7fc901194c913004faa87f291cd3faac2b00000000ecc1620341eeee4881423cab631e6e4a0b003c05ffc2dfc132a2a902a45df2c573d02e5d148c031af7358d5f00e0ff3fd83c1e679a4766043e3dbc622870e64ba4c2cacfa2f45563210100000000000071d244c45daecf0abf15c5f4e47f12310912918ca56b89c3dfb68103371ae6bf98d42e5d148c031aca5d525e00000020d2a6ad5304a5bbe4948666fd6775dc2cde9c0cef7060a471fe01000000000000597701d1165c140f471f2684f1f6b3e97765ee5492619582af5e6d192895e7d34cd92e5dffff001dbb34c42400000020b9b3fcbb515c899b10bf3889d432ca2782cfad01f9c2cf329fb60e000000000048d580fbe9ccf1cadaffe0e780eab57ea401f6260f38bd459d32cc3eef6cbd33ffd92e5d148c031a3c4277f40000002024af4d64067c20a1ed5cb9fbd432a98fe659e3653378e6b9ed00000000000000fea85a41c80b307f9cdfd22ac52521ba89ea6467769206d89889663cb7742e7358db2e5d148c031a7d30031a0000ff3f6418122efc0ddf2416189b01c0d98ab7e5072fe1e99c3e275401000000000000496c06f87b8d442db7c6bd36ff05e3a7a0edb3e0124d26c61d44c584ba1f8ff86bdc2e5d148c031a411e00ae"
}
17 changes: 17 additions & 0 deletions lib/tbtc-helpers/.eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"extends": "eslint-config-keep",
"parserOptions": {
"ecmaVersion": 2017,
"sourceType": "module"
},
"env": {
"es6": true,
"mocha": true
},
"rules": {
"semi": [
2,
"never"
]
}
}
7 changes: 7 additions & 0 deletions lib/tbtc-helpers/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const BitcoinSPV = require('./src/BitcoinSPV')
const BitcoinTxParser = require('./src/BitcoinTxParser')
const ElectrumClient = require('./src/ElectrumClient')

module.exports = {
BitcoinSPV, BitcoinTxParser, ElectrumClient
}
Loading