From 063ce75db689214e7f5b193a8abfc376c3b5f852 Mon Sep 17 00:00:00 2001 From: Christos Porios Date: Mon, 15 May 2017 01:34:01 +0000 Subject: [PATCH 01/20] Test that transactions are being processed --- lib/full_node.js | 12 ++++++++++ lib/index.js | 11 ++-------- lib/trust_is_risk.js | 26 ++++++++++++++++++++++ package.json | 6 +++-- src/full_node.js | 12 ++++++++++ src/index.js | 11 ++-------- src/trust_is_risk.js | 26 ++++++++++++++++++++++ test/full_node.js | 50 ++++++++++++++++++++++++++++++++++++++++++ test/helpers.js | 52 ++++++++++++++++++++++++++++++++++++++++++++ test/test.js | 10 --------- 10 files changed, 186 insertions(+), 30 deletions(-) create mode 100644 lib/full_node.js create mode 100644 lib/trust_is_risk.js create mode 100644 src/full_node.js create mode 100644 src/trust_is_risk.js create mode 100644 test/full_node.js create mode 100644 test/helpers.js delete mode 100644 test/test.js diff --git a/lib/full_node.js b/lib/full_node.js new file mode 100644 index 0000000..947cc83 --- /dev/null +++ b/lib/full_node.js @@ -0,0 +1,12 @@ +// +var bcoin = require('bcoin'); +var TrustIsRisk = require('./trust_is_risk'); + +class FullNode extends bcoin.fullnode { + constructor(options ) { + super(options); + this.trust = new TrustIsRisk(this); + } +} + +module.exports = FullNode; diff --git a/lib/index.js b/lib/index.js index 2b229a1..01c118a 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,12 +1,5 @@ // -var bcoin = require('bcoin'); - -class fullnode extends bcoin.fullnode { - constructor(options ) { - super(options); - } -} - module.exports = { - fullnode + FullNode: require('./full_node'), + TrustIsRisk: require('./trust_is_risk') } diff --git a/lib/trust_is_risk.js b/lib/trust_is_risk.js new file mode 100644 index 0000000..ed2e4e1 --- /dev/null +++ b/lib/trust_is_risk.js @@ -0,0 +1,26 @@ +// +var bcoin = require('bcoin'); + +class TrustIsRisk { + + + constructor(node ) { + this.node = node; + + this.node.on('tx', this.addTX.bind(this)); + } + + addTX(tx ) { + if (!this.isTrustTX(tx)) return false; + // TODO + return true; + } + + isTrustTX(tx ) { + return true; // TODO + } + +} + + +module.exports = TrustIsRisk; diff --git a/package.json b/package.json index 681643a..af50d22 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "lib/index.js", "scripts": { "build": "flow-remove-types -d lib/ src/", - "test": "npm run build && mocha", + "test": "npm run build && mocha -t 5000", "prepublish": "npm run build", "typecheck": "flow check" }, @@ -27,7 +27,9 @@ "devDependencies": { "flow-bin": "^0.45.0", "flow-remove-types": "^1.2.0", - "should": "^11.2.1" + "should": "^11.2.1", + "should-sinon": "0.0.5", + "sinon": "^2.2.0" }, "dependencies": { "bcoin": "^1.0.0-beta.12" diff --git a/src/full_node.js b/src/full_node.js new file mode 100644 index 0000000..2798307 --- /dev/null +++ b/src/full_node.js @@ -0,0 +1,12 @@ +// @flow +var bcoin = require('bcoin'); +var TrustIsRisk = require('./trust_is_risk'); + +class FullNode extends bcoin.fullnode { + constructor(options : Object) { + super(options); + this.trust = new TrustIsRisk(this); + } +} + +module.exports = FullNode; diff --git a/src/index.js b/src/index.js index 2db0e64..f21fb90 100644 --- a/src/index.js +++ b/src/index.js @@ -1,12 +1,5 @@ // @flow -var bcoin = require('bcoin'); - -class fullnode extends bcoin.fullnode { - constructor(options : Object) { - super(options); - } -} - module.exports = { - fullnode + FullNode: require('./full_node'), + TrustIsRisk: require('./trust_is_risk') } diff --git a/src/trust_is_risk.js b/src/trust_is_risk.js new file mode 100644 index 0000000..8be984d --- /dev/null +++ b/src/trust_is_risk.js @@ -0,0 +1,26 @@ +// @flow +var bcoin = require('bcoin'); + +class TrustIsRisk { + node : bcoin.fullnode + + constructor(node : bcoin.fullnode) { + this.node = node; + + this.node.on('tx', this.addTX.bind(this)); + } + + addTX(tx : bcoin.TX) : boolean { + if (!this.isTrustTX(tx)) return false; + // TODO + return true; + } + + isTrustTX(tx : bcoin.TX) : boolean { + return true; // TODO + } + +} + + +module.exports = TrustIsRisk; diff --git a/test/full_node.js b/test/full_node.js new file mode 100644 index 0000000..64ad3b5 --- /dev/null +++ b/test/full_node.js @@ -0,0 +1,50 @@ +var Trust = require('../'); +var bcoin = require('bcoin'); +var testHelpers = require('./helpers'); +var consensus = require('bcoin/lib/protocol/consensus'); +var sinon = require('sinon'); +var should = require('should'); +require('should-sinon'); + +describe('TrustIsRisk.FullNode', () => { + var node = null; + var walletDB = null; + sinon.spy(Trust.TrustIsRisk.prototype, 'addTX'); + + beforeEach(() => testHelpers.getNode().then((n) => { + node = n; + })); + + beforeEach(() => testHelpers.getWalletDB(node).then((w) => { + walletDB = w; + })); + + afterEach(() => walletDB.close()); + afterEach(() => node.close()); + + it('should be a bcoin instance', () => { + should(node).be.an.instanceof(bcoin.fullnode); + }); + + it('should call trust.addTX() on every transaction', async function() { + var sender = await testHelpers.getWallet(walletDB, 'sender'); + var receiver = await testHelpers.getWallet(walletDB, 'receiver'); + + // Produce a block and reward the sender, so that we have a coin to spend. + await testHelpers.mineBlock(node, sender.getAddress('base58')); + + // Make the coin spendable. + consensus.COINBASE_MATURITY = 0; + + await testHelpers.time(100); + await sender.send({ + outputs: [{ + value: 10 * consensus.COIN, + address: receiver.getAddress('base58') + }] + }); + + await testHelpers.time(100); + node.trust.addTX.should.be.calledOnce(); + }); +}); diff --git a/test/helpers.js b/test/helpers.js new file mode 100644 index 0000000..0b2c7fc --- /dev/null +++ b/test/helpers.js @@ -0,0 +1,52 @@ +var TrustIsRisk = require('../'); +var WalletDB = require('bcoin/lib/wallet/walletdb'); +var bcoin = require('bcoin'); + +var testHelpers = { + getNode: async () => { + var node = new TrustIsRisk.FullNode({network: 'regtest', passphrase: 'secret'}); + + await node.open(); + await node.connect(); + node.startSync(); + + return node; + }, + + getWalletDB: async (node) => { + var walletDB = new WalletDB({ + network: 'regtest', + db: 'memory', + client: new bcoin.node.NodeClient(node) + }); + + await walletDB.open(); + await walletDB.connect(); + + return walletDB; + }, + + getWallet: async (walletDB, id) => { + var options = { + id, + passphrase: 'secret', + witness: false, + type: 'pubkeyhash' + }; + + return walletDB.create(options); + }, + + mineBlock: async (node, rewardAddress) => { + var block = await node.miner.mineBlock(node.chain.tip, rewardAddress); + await node.chain.add(block); + }, + + time: async (milliseconds) => { + return new Promise((resolve, reject) => { + setTimeout(resolve, milliseconds); + }); + } +} + +module.exports = testHelpers; diff --git a/test/test.js b/test/test.js deleted file mode 100644 index cd34a7d..0000000 --- a/test/test.js +++ /dev/null @@ -1,10 +0,0 @@ -var TrustIsRisk = require('../'); -var bcoin = require('bcoin'); -var should = require('should'); - -describe('new TrustIsRisk.fullnode', () => { - it('should be a bcoin instance', () => { - var node = new TrustIsRisk.fullnode({}); - should(node).be.an.instanceof(bcoin.fullnode); - }); -}); From c1cce130f2d03eff351f4d83b5f4c0fcc95a6c02 Mon Sep 17 00:00:00 2001 From: Christos Porios Date: Mon, 15 May 2017 14:19:09 +0000 Subject: [PATCH 02/20] Implement trust-increasing transaction parsing --- flow-typed/npm/bcoin_vx.x.x.js | 1688 +------------------------------- lib/full_node.js | 2 + lib/trust_is_risk.js | 75 +- src/full_node.js | 2 + src/trust_is_risk.js | 81 +- test/full_node.js | 2 +- test/trust_is_risk.js | 115 +++ 7 files changed, 297 insertions(+), 1668 deletions(-) create mode 100644 test/trust_is_risk.js diff --git a/flow-typed/npm/bcoin_vx.x.x.js b/flow-typed/npm/bcoin_vx.x.x.js index cd32081..e1db501 100644 --- a/flow-typed/npm/bcoin_vx.x.x.js +++ b/flow-typed/npm/bcoin_vx.x.x.js @@ -1,1670 +1,50 @@ -// flow-typed signature: 52ec7d952e23091ff45f80adada48ac0 -// flow-typed version: <>/bcoin_v^1.0.0-beta.12/flow_v0.45.0 +// This is a work-in-progress attempt to type the bcoin library. -/** - * This is an autogenerated libdef stub for: - * - * 'bcoin' - * - * Fill this stub out by replacing all the `any` types. - * - * Once filled out, we encourage you to share your work with the - * community by sending a pull request to: - * https://github.com/flowtype/flow-typed - */ - -declare module 'bcoin' { - declare module.exports: any; -} - -/** - * We include stubs for each file inside this npm package in case you need to - * require those files directly. Feel free to delete any files that aren't - * needed. - */ -declare module 'bcoin/browser/empty' { - declare module.exports: any; -} - -declare module 'bcoin/browser/index' { - declare module.exports: any; -} - -declare module 'bcoin/browser/server' { - declare module.exports: any; -} - -declare module 'bcoin/browser/transform' { - declare module.exports: any; -} - -declare module 'bcoin/browser/wsproxy' { - declare module.exports: any; -} - -declare module 'bcoin/examples/chain' { - declare module.exports: any; -} - -declare module 'bcoin/examples/client' { - declare module.exports: any; -} - -declare module 'bcoin/examples/miner' { - declare module.exports: any; -} - -declare module 'bcoin/examples/node' { - declare module.exports: any; -} - -declare module 'bcoin/examples/peer' { - declare module.exports: any; -} - -declare module 'bcoin/examples/plugin' { - declare module.exports: any; -} - -declare module 'bcoin/examples/tx' { - declare module.exports: any; -} - -declare module 'bcoin/examples/wallet' { - declare module.exports: any; -} - -declare module 'bcoin/lib/bcoin' { - declare module.exports: any; -} - -declare module 'bcoin/lib/bip70/certs' { - declare module.exports: any; -} - -declare module 'bcoin/lib/bip70/index' { - declare module.exports: any; -} - -declare module 'bcoin/lib/bip70/payment' { - declare module.exports: any; -} - -declare module 'bcoin/lib/bip70/paymentack' { - declare module.exports: any; -} - -declare module 'bcoin/lib/bip70/paymentdetails' { - declare module.exports: any; -} - -declare module 'bcoin/lib/bip70/paymentrequest' { - declare module.exports: any; -} - -declare module 'bcoin/lib/bip70/pk' { - declare module.exports: any; -} - -declare module 'bcoin/lib/bip70/x509' { - declare module.exports: any; -} - -declare module 'bcoin/lib/blockchain/chain' { - declare module.exports: any; -} - -declare module 'bcoin/lib/blockchain/chaindb' { - declare module.exports: any; -} - -declare module 'bcoin/lib/blockchain/chainentry' { - declare module.exports: any; -} - -declare module 'bcoin/lib/blockchain/common' { - declare module.exports: any; -} - -declare module 'bcoin/lib/blockchain/index' { - declare module.exports: any; -} - -declare module 'bcoin/lib/blockchain/layout-browser' { - declare module.exports: any; -} - -declare module 'bcoin/lib/blockchain/layout' { - declare module.exports: any; -} - -declare module 'bcoin/lib/btc/amount' { - declare module.exports: any; -} - -declare module 'bcoin/lib/btc/index' { - declare module.exports: any; -} - -declare module 'bcoin/lib/btc/uri' { - declare module.exports: any; -} - -declare module 'bcoin/lib/coins/coins' { - declare module.exports: any; -} - -declare module 'bcoin/lib/coins/coinview' { - declare module.exports: any; -} - -declare module 'bcoin/lib/coins/compress' { - declare module.exports: any; -} - -declare module 'bcoin/lib/coins/index' { - declare module.exports: any; -} - -declare module 'bcoin/lib/coins/undocoins' { - declare module.exports: any; -} - -declare module 'bcoin/lib/crypto/aes' { - declare module.exports: any; -} - -declare module 'bcoin/lib/crypto/backend-browser' { - declare module.exports: any; -} - -declare module 'bcoin/lib/crypto/backend' { - declare module.exports: any; -} - -declare module 'bcoin/lib/crypto/chachapoly' { - declare module.exports: any; -} - -declare module 'bcoin/lib/crypto/crypto' { - declare module.exports: any; -} - -declare module 'bcoin/lib/crypto/ec-elliptic' { - declare module.exports: any; -} - -declare module 'bcoin/lib/crypto/ec-secp256k1' { - declare module.exports: any; -} - -declare module 'bcoin/lib/crypto/ec' { - declare module.exports: any; -} - -declare module 'bcoin/lib/crypto/index' { - declare module.exports: any; -} - -declare module 'bcoin/lib/crypto/pk-browser' { - declare module.exports: any; -} - -declare module 'bcoin/lib/crypto/pk' { - declare module.exports: any; -} - -declare module 'bcoin/lib/crypto/schnorr' { - declare module.exports: any; -} - -declare module 'bcoin/lib/crypto/scrypt' { - declare module.exports: any; -} - -declare module 'bcoin/lib/crypto/sha256' { - declare module.exports: any; -} - -declare module 'bcoin/lib/crypto/siphash' { - declare module.exports: any; -} - -declare module 'bcoin/lib/db/backends-browser' { - declare module.exports: any; -} - -declare module 'bcoin/lib/db/backends' { - declare module.exports: any; -} - -declare module 'bcoin/lib/db/index' { - declare module.exports: any; -} - -declare module 'bcoin/lib/db/ldb' { - declare module.exports: any; -} - -declare module 'bcoin/lib/db/level' { - declare module.exports: any; -} - -declare module 'bcoin/lib/db/lowlevelup' { - declare module.exports: any; -} - -declare module 'bcoin/lib/db/memdb' { - declare module.exports: any; -} - -declare module 'bcoin/lib/env' { - declare module.exports: any; -} - -declare module 'bcoin/lib/hd/common' { - declare module.exports: any; -} - -declare module 'bcoin/lib/hd/hd' { - declare module.exports: any; -} - -declare module 'bcoin/lib/hd/index' { - declare module.exports: any; -} - -declare module 'bcoin/lib/hd/mnemonic' { - declare module.exports: any; -} - -declare module 'bcoin/lib/hd/private' { - declare module.exports: any; -} - -declare module 'bcoin/lib/hd/public' { - declare module.exports: any; -} - -declare module 'bcoin/lib/hd/wordlist-browser' { - declare module.exports: any; -} - -declare module 'bcoin/lib/hd/wordlist' { - declare module.exports: any; -} - -declare module 'bcoin/lib/hd/words/chinese-simplified' { - declare module.exports: any; -} - -declare module 'bcoin/lib/hd/words/chinese-traditional' { - declare module.exports: any; -} - -declare module 'bcoin/lib/hd/words/english' { - declare module.exports: any; -} - -declare module 'bcoin/lib/hd/words/french' { - declare module.exports: any; -} - -declare module 'bcoin/lib/hd/words/index' { - declare module.exports: any; -} - -declare module 'bcoin/lib/hd/words/italian' { - declare module.exports: any; -} - -declare module 'bcoin/lib/hd/words/japanese' { - declare module.exports: any; -} - -declare module 'bcoin/lib/hd/words/spanish' { - declare module.exports: any; -} - -declare module 'bcoin/lib/http/base' { - declare module.exports: any; -} - -declare module 'bcoin/lib/http/client' { - declare module.exports: any; -} - -declare module 'bcoin/lib/http/index' { - declare module.exports: any; -} - -declare module 'bcoin/lib/http/request' { - declare module.exports: any; -} - -declare module 'bcoin/lib/http/rpc' { - declare module.exports: any; -} - -declare module 'bcoin/lib/http/rpcbase' { - declare module.exports: any; -} - -declare module 'bcoin/lib/http/rpcclient' { - declare module.exports: any; -} - -declare module 'bcoin/lib/http/server' { - declare module.exports: any; -} - -declare module 'bcoin/lib/http/wallet' { - declare module.exports: any; -} - -declare module 'bcoin/lib/mempool/fees' { - declare module.exports: any; -} - -declare module 'bcoin/lib/mempool/index' { - declare module.exports: any; -} - -declare module 'bcoin/lib/mempool/layout-browser' { - declare module.exports: any; -} - -declare module 'bcoin/lib/mempool/layout' { - declare module.exports: any; -} - -declare module 'bcoin/lib/mempool/mempool' { - declare module.exports: any; -} - -declare module 'bcoin/lib/mempool/mempoolentry' { - declare module.exports: any; -} - -declare module 'bcoin/lib/mining/common' { - declare module.exports: any; -} - -declare module 'bcoin/lib/mining/cpuminer' { - declare module.exports: any; -} - -declare module 'bcoin/lib/mining/index' { - declare module.exports: any; -} - -declare module 'bcoin/lib/mining/mine' { - declare module.exports: any; -} - -declare module 'bcoin/lib/mining/miner' { - declare module.exports: any; -} - -declare module 'bcoin/lib/mining/template' { - declare module.exports: any; -} - -declare module 'bcoin/lib/net/bip150' { - declare module.exports: any; -} - -declare module 'bcoin/lib/net/bip151' { - declare module.exports: any; -} - -declare module 'bcoin/lib/net/bip152' { - declare module.exports: any; -} - -declare module 'bcoin/lib/net/common' { - declare module.exports: any; -} - -declare module 'bcoin/lib/net/dns-browser' { - declare module.exports: any; -} - -declare module 'bcoin/lib/net/dns' { - declare module.exports: any; -} - -declare module 'bcoin/lib/net/framer' { - declare module.exports: any; -} - -declare module 'bcoin/lib/net/hostlist' { - declare module.exports: any; -} - -declare module 'bcoin/lib/net/index' { - declare module.exports: any; -} - -declare module 'bcoin/lib/net/packets' { - declare module.exports: any; -} - -declare module 'bcoin/lib/net/parser' { - declare module.exports: any; -} - -declare module 'bcoin/lib/net/peer' { - declare module.exports: any; -} - -declare module 'bcoin/lib/net/pool' { - declare module.exports: any; -} - -declare module 'bcoin/lib/net/proxysocket' { - declare module.exports: any; -} - -declare module 'bcoin/lib/net/seeds/index' { - declare module.exports: any; -} - -declare module 'bcoin/lib/net/seeds/main' { - declare module.exports: any; -} - -declare module 'bcoin/lib/net/seeds/testnet' { - declare module.exports: any; -} - -declare module 'bcoin/lib/net/socks' { - declare module.exports: any; -} - -declare module 'bcoin/lib/net/tcp-browser' { - declare module.exports: any; -} - -declare module 'bcoin/lib/net/tcp' { - declare module.exports: any; -} - -declare module 'bcoin/lib/net/upnp-browser' { - declare module.exports: any; -} - -declare module 'bcoin/lib/net/upnp' { - declare module.exports: any; -} - -declare module 'bcoin/lib/node/config' { - declare module.exports: any; -} - -declare module 'bcoin/lib/node/fullnode' { - declare module.exports: any; -} - -declare module 'bcoin/lib/node/index' { - declare module.exports: any; -} - -declare module 'bcoin/lib/node/logger' { - declare module.exports: any; -} - -declare module 'bcoin/lib/node/node' { - declare module.exports: any; -} - -declare module 'bcoin/lib/node/nodeclient' { - declare module.exports: any; -} - -declare module 'bcoin/lib/node/spvnode' { - declare module.exports: any; -} - -declare module 'bcoin/lib/pkg' { - declare module.exports: any; -} - -declare module 'bcoin/lib/primitives/abstractblock' { - declare module.exports: any; -} - -declare module 'bcoin/lib/primitives/address' { - declare module.exports: any; -} - -declare module 'bcoin/lib/primitives/block' { - declare module.exports: any; -} - -declare module 'bcoin/lib/primitives/coin' { - declare module.exports: any; -} - -declare module 'bcoin/lib/primitives/headers' { - declare module.exports: any; -} - -declare module 'bcoin/lib/primitives/index' { - declare module.exports: any; -} - -declare module 'bcoin/lib/primitives/input' { - declare module.exports: any; -} - -declare module 'bcoin/lib/primitives/invitem' { - declare module.exports: any; -} - -declare module 'bcoin/lib/primitives/keyring' { - declare module.exports: any; -} - -declare module 'bcoin/lib/primitives/memblock' { - declare module.exports: any; -} - -declare module 'bcoin/lib/primitives/merkleblock' { - declare module.exports: any; -} - -declare module 'bcoin/lib/primitives/mtx' { - declare module.exports: any; -} - -declare module 'bcoin/lib/primitives/netaddress' { - declare module.exports: any; -} - -declare module 'bcoin/lib/primitives/outpoint' { - declare module.exports: any; -} - -declare module 'bcoin/lib/primitives/output' { - declare module.exports: any; -} - -declare module 'bcoin/lib/primitives/tx' { - declare module.exports: any; -} - -declare module 'bcoin/lib/primitives/txmeta' { - declare module.exports: any; -} - -declare module 'bcoin/lib/protocol/consensus' { - declare module.exports: any; -} - -declare module 'bcoin/lib/protocol/errors' { - declare module.exports: any; -} - -declare module 'bcoin/lib/protocol/index' { - declare module.exports: any; -} - -declare module 'bcoin/lib/protocol/network' { - declare module.exports: any; -} - -declare module 'bcoin/lib/protocol/networks' { - declare module.exports: any; -} - -declare module 'bcoin/lib/protocol/policy' { - declare module.exports: any; -} - -declare module 'bcoin/lib/protocol/timedata' { - declare module.exports: any; -} - -declare module 'bcoin/lib/script/common' { - declare module.exports: any; -} - -declare module 'bcoin/lib/script/index' { - declare module.exports: any; -} - -declare module 'bcoin/lib/script/opcode' { - declare module.exports: any; -} - -declare module 'bcoin/lib/script/program' { - declare module.exports: any; -} - -declare module 'bcoin/lib/script/script' { - declare module.exports: any; -} - -declare module 'bcoin/lib/script/scriptnum' { - declare module.exports: any; -} - -declare module 'bcoin/lib/script/sigcache' { - declare module.exports: any; -} - -declare module 'bcoin/lib/script/stack' { - declare module.exports: any; -} - -declare module 'bcoin/lib/script/witness' { - declare module.exports: any; -} - -declare module 'bcoin/lib/types' { - declare module.exports: any; -} - -declare module 'bcoin/lib/utils/asn1' { - declare module.exports: any; -} - -declare module 'bcoin/lib/utils/asyncemitter' { - declare module.exports: any; -} - -declare module 'bcoin/lib/utils/asyncobject' { - declare module.exports: any; -} - -declare module 'bcoin/lib/utils/base32' { - declare module.exports: any; -} - -declare module 'bcoin/lib/utils/base58' { - declare module.exports: any; -} - -declare module 'bcoin/lib/utils/bloom' { - declare module.exports: any; -} - -declare module 'bcoin/lib/utils/co' { - declare module.exports: any; -} - -declare module 'bcoin/lib/utils/encoding' { - declare module.exports: any; -} - -declare module 'bcoin/lib/utils/fs' { - declare module.exports: any; -} - -declare module 'bcoin/lib/utils/heap' { - declare module.exports: any; -} - -declare module 'bcoin/lib/utils/index' { - declare module.exports: any; -} - -declare module 'bcoin/lib/utils/ip' { - declare module.exports: any; -} - -declare module 'bcoin/lib/utils/lazy-browser' { - declare module.exports: any; -} - -declare module 'bcoin/lib/utils/lazy' { - declare module.exports: any; -} - -declare module 'bcoin/lib/utils/list' { - declare module.exports: any; -} - -declare module 'bcoin/lib/utils/lock' { - declare module.exports: any; -} - -declare module 'bcoin/lib/utils/lru' { - declare module.exports: any; -} - -declare module 'bcoin/lib/utils/map' { - declare module.exports: any; -} - -declare module 'bcoin/lib/utils/murmur3' { - declare module.exports: any; -} - -declare module 'bcoin/lib/utils/native' { - declare module.exports: any; -} - -declare module 'bcoin/lib/utils/nexttick-browser' { - declare module.exports: any; -} - -declare module 'bcoin/lib/utils/nexttick' { - declare module.exports: any; -} - -declare module 'bcoin/lib/utils/nfkd-browser' { - declare module.exports: any; -} - -declare module 'bcoin/lib/utils/nfkd' { - declare module.exports: any; -} - -declare module 'bcoin/lib/utils/pem' { - declare module.exports: any; -} - -declare module 'bcoin/lib/utils/protobuf' { - declare module.exports: any; -} - -declare module 'bcoin/lib/utils/rbt' { - declare module.exports: any; -} - -declare module 'bcoin/lib/utils/reader' { - declare module.exports: any; -} - -declare module 'bcoin/lib/utils/staticwriter' { - declare module.exports: any; -} - -declare module 'bcoin/lib/utils/util' { - declare module.exports: any; -} - -declare module 'bcoin/lib/utils/validator' { - declare module.exports: any; -} - -declare module 'bcoin/lib/utils/writer' { - declare module.exports: any; -} - -declare module 'bcoin/lib/wallet/account' { - declare module.exports: any; -} - -declare module 'bcoin/lib/wallet/client' { - declare module.exports: any; -} - -declare module 'bcoin/lib/wallet/common' { - declare module.exports: any; -} - -declare module 'bcoin/lib/wallet/http' { - declare module.exports: any; -} - -declare module 'bcoin/lib/wallet/index' { - declare module.exports: any; -} - -declare module 'bcoin/lib/wallet/layout-browser' { - declare module.exports: any; -} - -declare module 'bcoin/lib/wallet/layout' { - declare module.exports: any; -} - -declare module 'bcoin/lib/wallet/masterkey' { - declare module.exports: any; -} - -declare module 'bcoin/lib/wallet/path' { - declare module.exports: any; -} - -declare module 'bcoin/lib/wallet/plugin' { - declare module.exports: any; -} - -declare module 'bcoin/lib/wallet/records' { - declare module.exports: any; -} - -declare module 'bcoin/lib/wallet/rpc' { - declare module.exports: any; -} - -declare module 'bcoin/lib/wallet/server' { - declare module.exports: any; -} - -declare module 'bcoin/lib/wallet/txdb' { - declare module.exports: any; -} - -declare module 'bcoin/lib/wallet/wallet' { - declare module.exports: any; -} - -declare module 'bcoin/lib/wallet/walletdb' { - declare module.exports: any; -} - -declare module 'bcoin/lib/wallet/walletkey' { - declare module.exports: any; -} - -declare module 'bcoin/lib/workers/framer' { - declare module.exports: any; -} - -declare module 'bcoin/lib/workers/index' { - declare module.exports: any; -} - -declare module 'bcoin/lib/workers/jobs' { - declare module.exports: any; -} - -declare module 'bcoin/lib/workers/master' { - declare module.exports: any; -} - -declare module 'bcoin/lib/workers/packets' { - declare module.exports: any; -} - -declare module 'bcoin/lib/workers/parser-client' { - declare module.exports: any; -} - -declare module 'bcoin/lib/workers/parser' { - declare module.exports: any; -} - -declare module 'bcoin/lib/workers/worker-browser' { - declare module.exports: any; -} - -declare module 'bcoin/lib/workers/worker' { - declare module.exports: any; -} - -declare module 'bcoin/lib/workers/workerpool' { - declare module.exports: any; -} - -declare module 'bcoin/migrate/chaindb0to1' { - declare module.exports: any; -} - -declare module 'bcoin/migrate/chaindb1to2' { - declare module.exports: any; -} - -declare module 'bcoin/migrate/coins-old' { - declare module.exports: any; -} - -declare module 'bcoin/migrate/coinview-old' { - declare module.exports: any; -} - -declare module 'bcoin/migrate/compress-old' { - declare module.exports: any; -} - -declare module 'bcoin/migrate/ensure-tip-index' { - declare module.exports: any; -} - -declare module 'bcoin/migrate/walletdb2to3' { - declare module.exports: any; -} - -declare module 'bcoin/migrate/walletdb3to4' { - declare module.exports: any; +declare class bcoin$FullNode { + on(eventName : string, eventHandler : Function) : void; } -declare module 'bcoin/migrate/walletdb4to5' { - declare module.exports: any; +declare class bcoin$Address { + toBase58() : string; + static fromHash(string|Buffer) : bcoin$Address; } -declare module 'bcoin/migrate/walletdb5to6' { - declare module.exports: any; +declare class bcoin$TX { + inputs : bcoin$Input[]; + outputs : bcoin$Output[]; } -declare module 'bcoin/scripts/dump' { - declare module.exports: any; -} +declare class bcoin$Output { + script : bcoin$Script; + value : number; -declare module 'bcoin/scripts/fuzz' { - declare module.exports: any; + getType() : ('pubkeyhash' | 'multisig'); + getAddress() : bcoin$Address; } -declare module 'bcoin/scripts/gen' { - declare module.exports: any; +declare class bcoin$Input { + script : bcoin$Script; + getType() : ('pubkeyhash' | 'multisig'); + getAddress() : bcoin$Address; } -declare module 'bcoin/vendor/setimmediate' { - declare module.exports: any; +declare class bcoin$Script { + get(n : number) : (Buffer); } -declare module 'bcoin/vendor/unorm' { - declare module.exports: any; +declare module 'bcoin' { + declare module.exports: { + fullnode : Class, + script : Class, + primitives : { + Address : Class, + TX : Class, + Output : Class, + Input : Class + }, + crypto : { + hash160(str : (string | Buffer)) : (string | Buffer) + } + } } -// Filename aliases -declare module 'bcoin/browser/empty.js' { - declare module.exports: $Exports<'bcoin/browser/empty'>; -} -declare module 'bcoin/browser/index.js' { - declare module.exports: $Exports<'bcoin/browser/index'>; -} -declare module 'bcoin/browser/server.js' { - declare module.exports: $Exports<'bcoin/browser/server'>; -} -declare module 'bcoin/browser/transform.js' { - declare module.exports: $Exports<'bcoin/browser/transform'>; -} -declare module 'bcoin/browser/wsproxy.js' { - declare module.exports: $Exports<'bcoin/browser/wsproxy'>; -} -declare module 'bcoin/examples/chain.js' { - declare module.exports: $Exports<'bcoin/examples/chain'>; -} -declare module 'bcoin/examples/client.js' { - declare module.exports: $Exports<'bcoin/examples/client'>; -} -declare module 'bcoin/examples/miner.js' { - declare module.exports: $Exports<'bcoin/examples/miner'>; -} -declare module 'bcoin/examples/node.js' { - declare module.exports: $Exports<'bcoin/examples/node'>; -} -declare module 'bcoin/examples/peer.js' { - declare module.exports: $Exports<'bcoin/examples/peer'>; -} -declare module 'bcoin/examples/plugin.js' { - declare module.exports: $Exports<'bcoin/examples/plugin'>; -} -declare module 'bcoin/examples/tx.js' { - declare module.exports: $Exports<'bcoin/examples/tx'>; -} -declare module 'bcoin/examples/wallet.js' { - declare module.exports: $Exports<'bcoin/examples/wallet'>; -} -declare module 'bcoin/lib/bcoin.js' { - declare module.exports: $Exports<'bcoin/lib/bcoin'>; -} -declare module 'bcoin/lib/bip70/certs.js' { - declare module.exports: $Exports<'bcoin/lib/bip70/certs'>; -} -declare module 'bcoin/lib/bip70/index.js' { - declare module.exports: $Exports<'bcoin/lib/bip70/index'>; -} -declare module 'bcoin/lib/bip70/payment.js' { - declare module.exports: $Exports<'bcoin/lib/bip70/payment'>; -} -declare module 'bcoin/lib/bip70/paymentack.js' { - declare module.exports: $Exports<'bcoin/lib/bip70/paymentack'>; -} -declare module 'bcoin/lib/bip70/paymentdetails.js' { - declare module.exports: $Exports<'bcoin/lib/bip70/paymentdetails'>; -} -declare module 'bcoin/lib/bip70/paymentrequest.js' { - declare module.exports: $Exports<'bcoin/lib/bip70/paymentrequest'>; -} -declare module 'bcoin/lib/bip70/pk.js' { - declare module.exports: $Exports<'bcoin/lib/bip70/pk'>; -} -declare module 'bcoin/lib/bip70/x509.js' { - declare module.exports: $Exports<'bcoin/lib/bip70/x509'>; -} -declare module 'bcoin/lib/blockchain/chain.js' { - declare module.exports: $Exports<'bcoin/lib/blockchain/chain'>; -} -declare module 'bcoin/lib/blockchain/chaindb.js' { - declare module.exports: $Exports<'bcoin/lib/blockchain/chaindb'>; -} -declare module 'bcoin/lib/blockchain/chainentry.js' { - declare module.exports: $Exports<'bcoin/lib/blockchain/chainentry'>; -} -declare module 'bcoin/lib/blockchain/common.js' { - declare module.exports: $Exports<'bcoin/lib/blockchain/common'>; -} -declare module 'bcoin/lib/blockchain/index.js' { - declare module.exports: $Exports<'bcoin/lib/blockchain/index'>; -} -declare module 'bcoin/lib/blockchain/layout-browser.js' { - declare module.exports: $Exports<'bcoin/lib/blockchain/layout-browser'>; -} -declare module 'bcoin/lib/blockchain/layout.js' { - declare module.exports: $Exports<'bcoin/lib/blockchain/layout'>; -} -declare module 'bcoin/lib/btc/amount.js' { - declare module.exports: $Exports<'bcoin/lib/btc/amount'>; -} -declare module 'bcoin/lib/btc/index.js' { - declare module.exports: $Exports<'bcoin/lib/btc/index'>; -} -declare module 'bcoin/lib/btc/uri.js' { - declare module.exports: $Exports<'bcoin/lib/btc/uri'>; -} -declare module 'bcoin/lib/coins/coins.js' { - declare module.exports: $Exports<'bcoin/lib/coins/coins'>; -} -declare module 'bcoin/lib/coins/coinview.js' { - declare module.exports: $Exports<'bcoin/lib/coins/coinview'>; -} -declare module 'bcoin/lib/coins/compress.js' { - declare module.exports: $Exports<'bcoin/lib/coins/compress'>; -} -declare module 'bcoin/lib/coins/index.js' { - declare module.exports: $Exports<'bcoin/lib/coins/index'>; -} -declare module 'bcoin/lib/coins/undocoins.js' { - declare module.exports: $Exports<'bcoin/lib/coins/undocoins'>; -} -declare module 'bcoin/lib/crypto/aes.js' { - declare module.exports: $Exports<'bcoin/lib/crypto/aes'>; -} -declare module 'bcoin/lib/crypto/backend-browser.js' { - declare module.exports: $Exports<'bcoin/lib/crypto/backend-browser'>; -} -declare module 'bcoin/lib/crypto/backend.js' { - declare module.exports: $Exports<'bcoin/lib/crypto/backend'>; -} -declare module 'bcoin/lib/crypto/chachapoly.js' { - declare module.exports: $Exports<'bcoin/lib/crypto/chachapoly'>; -} -declare module 'bcoin/lib/crypto/crypto.js' { - declare module.exports: $Exports<'bcoin/lib/crypto/crypto'>; -} -declare module 'bcoin/lib/crypto/ec-elliptic.js' { - declare module.exports: $Exports<'bcoin/lib/crypto/ec-elliptic'>; -} -declare module 'bcoin/lib/crypto/ec-secp256k1.js' { - declare module.exports: $Exports<'bcoin/lib/crypto/ec-secp256k1'>; -} -declare module 'bcoin/lib/crypto/ec.js' { - declare module.exports: $Exports<'bcoin/lib/crypto/ec'>; -} -declare module 'bcoin/lib/crypto/index.js' { - declare module.exports: $Exports<'bcoin/lib/crypto/index'>; -} -declare module 'bcoin/lib/crypto/pk-browser.js' { - declare module.exports: $Exports<'bcoin/lib/crypto/pk-browser'>; -} -declare module 'bcoin/lib/crypto/pk.js' { - declare module.exports: $Exports<'bcoin/lib/crypto/pk'>; -} -declare module 'bcoin/lib/crypto/schnorr.js' { - declare module.exports: $Exports<'bcoin/lib/crypto/schnorr'>; -} -declare module 'bcoin/lib/crypto/scrypt.js' { - declare module.exports: $Exports<'bcoin/lib/crypto/scrypt'>; -} -declare module 'bcoin/lib/crypto/sha256.js' { - declare module.exports: $Exports<'bcoin/lib/crypto/sha256'>; -} -declare module 'bcoin/lib/crypto/siphash.js' { - declare module.exports: $Exports<'bcoin/lib/crypto/siphash'>; -} -declare module 'bcoin/lib/db/backends-browser.js' { - declare module.exports: $Exports<'bcoin/lib/db/backends-browser'>; -} -declare module 'bcoin/lib/db/backends.js' { - declare module.exports: $Exports<'bcoin/lib/db/backends'>; -} -declare module 'bcoin/lib/db/index.js' { - declare module.exports: $Exports<'bcoin/lib/db/index'>; -} -declare module 'bcoin/lib/db/ldb.js' { - declare module.exports: $Exports<'bcoin/lib/db/ldb'>; -} -declare module 'bcoin/lib/db/level.js' { - declare module.exports: $Exports<'bcoin/lib/db/level'>; -} -declare module 'bcoin/lib/db/lowlevelup.js' { - declare module.exports: $Exports<'bcoin/lib/db/lowlevelup'>; -} -declare module 'bcoin/lib/db/memdb.js' { - declare module.exports: $Exports<'bcoin/lib/db/memdb'>; -} -declare module 'bcoin/lib/env.js' { - declare module.exports: $Exports<'bcoin/lib/env'>; -} -declare module 'bcoin/lib/hd/common.js' { - declare module.exports: $Exports<'bcoin/lib/hd/common'>; -} -declare module 'bcoin/lib/hd/hd.js' { - declare module.exports: $Exports<'bcoin/lib/hd/hd'>; -} -declare module 'bcoin/lib/hd/index.js' { - declare module.exports: $Exports<'bcoin/lib/hd/index'>; -} -declare module 'bcoin/lib/hd/mnemonic.js' { - declare module.exports: $Exports<'bcoin/lib/hd/mnemonic'>; -} -declare module 'bcoin/lib/hd/private.js' { - declare module.exports: $Exports<'bcoin/lib/hd/private'>; -} -declare module 'bcoin/lib/hd/public.js' { - declare module.exports: $Exports<'bcoin/lib/hd/public'>; -} -declare module 'bcoin/lib/hd/wordlist-browser.js' { - declare module.exports: $Exports<'bcoin/lib/hd/wordlist-browser'>; -} -declare module 'bcoin/lib/hd/wordlist.js' { - declare module.exports: $Exports<'bcoin/lib/hd/wordlist'>; -} -declare module 'bcoin/lib/hd/words/chinese-simplified.js' { - declare module.exports: $Exports<'bcoin/lib/hd/words/chinese-simplified'>; -} -declare module 'bcoin/lib/hd/words/chinese-traditional.js' { - declare module.exports: $Exports<'bcoin/lib/hd/words/chinese-traditional'>; -} -declare module 'bcoin/lib/hd/words/english.js' { - declare module.exports: $Exports<'bcoin/lib/hd/words/english'>; -} -declare module 'bcoin/lib/hd/words/french.js' { - declare module.exports: $Exports<'bcoin/lib/hd/words/french'>; -} -declare module 'bcoin/lib/hd/words/index.js' { - declare module.exports: $Exports<'bcoin/lib/hd/words/index'>; -} -declare module 'bcoin/lib/hd/words/italian.js' { - declare module.exports: $Exports<'bcoin/lib/hd/words/italian'>; -} -declare module 'bcoin/lib/hd/words/japanese.js' { - declare module.exports: $Exports<'bcoin/lib/hd/words/japanese'>; -} -declare module 'bcoin/lib/hd/words/spanish.js' { - declare module.exports: $Exports<'bcoin/lib/hd/words/spanish'>; -} -declare module 'bcoin/lib/http/base.js' { - declare module.exports: $Exports<'bcoin/lib/http/base'>; -} -declare module 'bcoin/lib/http/client.js' { - declare module.exports: $Exports<'bcoin/lib/http/client'>; -} -declare module 'bcoin/lib/http/index.js' { - declare module.exports: $Exports<'bcoin/lib/http/index'>; -} -declare module 'bcoin/lib/http/request.js' { - declare module.exports: $Exports<'bcoin/lib/http/request'>; -} -declare module 'bcoin/lib/http/rpc.js' { - declare module.exports: $Exports<'bcoin/lib/http/rpc'>; -} -declare module 'bcoin/lib/http/rpcbase.js' { - declare module.exports: $Exports<'bcoin/lib/http/rpcbase'>; -} -declare module 'bcoin/lib/http/rpcclient.js' { - declare module.exports: $Exports<'bcoin/lib/http/rpcclient'>; -} -declare module 'bcoin/lib/http/server.js' { - declare module.exports: $Exports<'bcoin/lib/http/server'>; -} -declare module 'bcoin/lib/http/wallet.js' { - declare module.exports: $Exports<'bcoin/lib/http/wallet'>; -} -declare module 'bcoin/lib/mempool/fees.js' { - declare module.exports: $Exports<'bcoin/lib/mempool/fees'>; -} -declare module 'bcoin/lib/mempool/index.js' { - declare module.exports: $Exports<'bcoin/lib/mempool/index'>; -} -declare module 'bcoin/lib/mempool/layout-browser.js' { - declare module.exports: $Exports<'bcoin/lib/mempool/layout-browser'>; -} -declare module 'bcoin/lib/mempool/layout.js' { - declare module.exports: $Exports<'bcoin/lib/mempool/layout'>; -} -declare module 'bcoin/lib/mempool/mempool.js' { - declare module.exports: $Exports<'bcoin/lib/mempool/mempool'>; -} -declare module 'bcoin/lib/mempool/mempoolentry.js' { - declare module.exports: $Exports<'bcoin/lib/mempool/mempoolentry'>; -} -declare module 'bcoin/lib/mining/common.js' { - declare module.exports: $Exports<'bcoin/lib/mining/common'>; -} -declare module 'bcoin/lib/mining/cpuminer.js' { - declare module.exports: $Exports<'bcoin/lib/mining/cpuminer'>; -} -declare module 'bcoin/lib/mining/index.js' { - declare module.exports: $Exports<'bcoin/lib/mining/index'>; -} -declare module 'bcoin/lib/mining/mine.js' { - declare module.exports: $Exports<'bcoin/lib/mining/mine'>; -} -declare module 'bcoin/lib/mining/miner.js' { - declare module.exports: $Exports<'bcoin/lib/mining/miner'>; -} -declare module 'bcoin/lib/mining/template.js' { - declare module.exports: $Exports<'bcoin/lib/mining/template'>; -} -declare module 'bcoin/lib/net/bip150.js' { - declare module.exports: $Exports<'bcoin/lib/net/bip150'>; -} -declare module 'bcoin/lib/net/bip151.js' { - declare module.exports: $Exports<'bcoin/lib/net/bip151'>; -} -declare module 'bcoin/lib/net/bip152.js' { - declare module.exports: $Exports<'bcoin/lib/net/bip152'>; -} -declare module 'bcoin/lib/net/common.js' { - declare module.exports: $Exports<'bcoin/lib/net/common'>; -} -declare module 'bcoin/lib/net/dns-browser.js' { - declare module.exports: $Exports<'bcoin/lib/net/dns-browser'>; -} -declare module 'bcoin/lib/net/dns.js' { - declare module.exports: $Exports<'bcoin/lib/net/dns'>; -} -declare module 'bcoin/lib/net/framer.js' { - declare module.exports: $Exports<'bcoin/lib/net/framer'>; -} -declare module 'bcoin/lib/net/hostlist.js' { - declare module.exports: $Exports<'bcoin/lib/net/hostlist'>; -} -declare module 'bcoin/lib/net/index.js' { - declare module.exports: $Exports<'bcoin/lib/net/index'>; -} -declare module 'bcoin/lib/net/packets.js' { - declare module.exports: $Exports<'bcoin/lib/net/packets'>; -} -declare module 'bcoin/lib/net/parser.js' { - declare module.exports: $Exports<'bcoin/lib/net/parser'>; -} -declare module 'bcoin/lib/net/peer.js' { - declare module.exports: $Exports<'bcoin/lib/net/peer'>; -} -declare module 'bcoin/lib/net/pool.js' { - declare module.exports: $Exports<'bcoin/lib/net/pool'>; -} -declare module 'bcoin/lib/net/proxysocket.js' { - declare module.exports: $Exports<'bcoin/lib/net/proxysocket'>; -} -declare module 'bcoin/lib/net/seeds/index.js' { - declare module.exports: $Exports<'bcoin/lib/net/seeds/index'>; -} -declare module 'bcoin/lib/net/seeds/main.js' { - declare module.exports: $Exports<'bcoin/lib/net/seeds/main'>; -} -declare module 'bcoin/lib/net/seeds/testnet.js' { - declare module.exports: $Exports<'bcoin/lib/net/seeds/testnet'>; -} -declare module 'bcoin/lib/net/socks.js' { - declare module.exports: $Exports<'bcoin/lib/net/socks'>; -} -declare module 'bcoin/lib/net/tcp-browser.js' { - declare module.exports: $Exports<'bcoin/lib/net/tcp-browser'>; -} -declare module 'bcoin/lib/net/tcp.js' { - declare module.exports: $Exports<'bcoin/lib/net/tcp'>; -} -declare module 'bcoin/lib/net/upnp-browser.js' { - declare module.exports: $Exports<'bcoin/lib/net/upnp-browser'>; -} -declare module 'bcoin/lib/net/upnp.js' { - declare module.exports: $Exports<'bcoin/lib/net/upnp'>; -} -declare module 'bcoin/lib/node/config.js' { - declare module.exports: $Exports<'bcoin/lib/node/config'>; -} -declare module 'bcoin/lib/node/fullnode.js' { - declare module.exports: $Exports<'bcoin/lib/node/fullnode'>; -} -declare module 'bcoin/lib/node/index.js' { - declare module.exports: $Exports<'bcoin/lib/node/index'>; -} -declare module 'bcoin/lib/node/logger.js' { - declare module.exports: $Exports<'bcoin/lib/node/logger'>; -} -declare module 'bcoin/lib/node/node.js' { - declare module.exports: $Exports<'bcoin/lib/node/node'>; -} -declare module 'bcoin/lib/node/nodeclient.js' { - declare module.exports: $Exports<'bcoin/lib/node/nodeclient'>; -} -declare module 'bcoin/lib/node/spvnode.js' { - declare module.exports: $Exports<'bcoin/lib/node/spvnode'>; -} -declare module 'bcoin/lib/pkg.js' { - declare module.exports: $Exports<'bcoin/lib/pkg'>; -} -declare module 'bcoin/lib/primitives/abstractblock.js' { - declare module.exports: $Exports<'bcoin/lib/primitives/abstractblock'>; -} -declare module 'bcoin/lib/primitives/address.js' { - declare module.exports: $Exports<'bcoin/lib/primitives/address'>; -} -declare module 'bcoin/lib/primitives/block.js' { - declare module.exports: $Exports<'bcoin/lib/primitives/block'>; -} -declare module 'bcoin/lib/primitives/coin.js' { - declare module.exports: $Exports<'bcoin/lib/primitives/coin'>; -} -declare module 'bcoin/lib/primitives/headers.js' { - declare module.exports: $Exports<'bcoin/lib/primitives/headers'>; -} -declare module 'bcoin/lib/primitives/index.js' { - declare module.exports: $Exports<'bcoin/lib/primitives/index'>; -} -declare module 'bcoin/lib/primitives/input.js' { - declare module.exports: $Exports<'bcoin/lib/primitives/input'>; -} -declare module 'bcoin/lib/primitives/invitem.js' { - declare module.exports: $Exports<'bcoin/lib/primitives/invitem'>; -} -declare module 'bcoin/lib/primitives/keyring.js' { - declare module.exports: $Exports<'bcoin/lib/primitives/keyring'>; -} -declare module 'bcoin/lib/primitives/memblock.js' { - declare module.exports: $Exports<'bcoin/lib/primitives/memblock'>; -} -declare module 'bcoin/lib/primitives/merkleblock.js' { - declare module.exports: $Exports<'bcoin/lib/primitives/merkleblock'>; -} -declare module 'bcoin/lib/primitives/mtx.js' { - declare module.exports: $Exports<'bcoin/lib/primitives/mtx'>; -} -declare module 'bcoin/lib/primitives/netaddress.js' { - declare module.exports: $Exports<'bcoin/lib/primitives/netaddress'>; -} -declare module 'bcoin/lib/primitives/outpoint.js' { - declare module.exports: $Exports<'bcoin/lib/primitives/outpoint'>; -} -declare module 'bcoin/lib/primitives/output.js' { - declare module.exports: $Exports<'bcoin/lib/primitives/output'>; -} -declare module 'bcoin/lib/primitives/tx.js' { - declare module.exports: $Exports<'bcoin/lib/primitives/tx'>; -} -declare module 'bcoin/lib/primitives/txmeta.js' { - declare module.exports: $Exports<'bcoin/lib/primitives/txmeta'>; -} -declare module 'bcoin/lib/protocol/consensus.js' { - declare module.exports: $Exports<'bcoin/lib/protocol/consensus'>; -} -declare module 'bcoin/lib/protocol/errors.js' { - declare module.exports: $Exports<'bcoin/lib/protocol/errors'>; -} -declare module 'bcoin/lib/protocol/index.js' { - declare module.exports: $Exports<'bcoin/lib/protocol/index'>; -} -declare module 'bcoin/lib/protocol/network.js' { - declare module.exports: $Exports<'bcoin/lib/protocol/network'>; -} -declare module 'bcoin/lib/protocol/networks.js' { - declare module.exports: $Exports<'bcoin/lib/protocol/networks'>; -} -declare module 'bcoin/lib/protocol/policy.js' { - declare module.exports: $Exports<'bcoin/lib/protocol/policy'>; -} -declare module 'bcoin/lib/protocol/timedata.js' { - declare module.exports: $Exports<'bcoin/lib/protocol/timedata'>; -} -declare module 'bcoin/lib/script/common.js' { - declare module.exports: $Exports<'bcoin/lib/script/common'>; -} -declare module 'bcoin/lib/script/index.js' { - declare module.exports: $Exports<'bcoin/lib/script/index'>; -} -declare module 'bcoin/lib/script/opcode.js' { - declare module.exports: $Exports<'bcoin/lib/script/opcode'>; -} -declare module 'bcoin/lib/script/program.js' { - declare module.exports: $Exports<'bcoin/lib/script/program'>; -} -declare module 'bcoin/lib/script/script.js' { - declare module.exports: $Exports<'bcoin/lib/script/script'>; -} -declare module 'bcoin/lib/script/scriptnum.js' { - declare module.exports: $Exports<'bcoin/lib/script/scriptnum'>; -} -declare module 'bcoin/lib/script/sigcache.js' { - declare module.exports: $Exports<'bcoin/lib/script/sigcache'>; -} -declare module 'bcoin/lib/script/stack.js' { - declare module.exports: $Exports<'bcoin/lib/script/stack'>; -} -declare module 'bcoin/lib/script/witness.js' { - declare module.exports: $Exports<'bcoin/lib/script/witness'>; -} -declare module 'bcoin/lib/types.js' { - declare module.exports: $Exports<'bcoin/lib/types'>; -} -declare module 'bcoin/lib/utils/asn1.js' { - declare module.exports: $Exports<'bcoin/lib/utils/asn1'>; -} -declare module 'bcoin/lib/utils/asyncemitter.js' { - declare module.exports: $Exports<'bcoin/lib/utils/asyncemitter'>; -} -declare module 'bcoin/lib/utils/asyncobject.js' { - declare module.exports: $Exports<'bcoin/lib/utils/asyncobject'>; -} -declare module 'bcoin/lib/utils/base32.js' { - declare module.exports: $Exports<'bcoin/lib/utils/base32'>; -} -declare module 'bcoin/lib/utils/base58.js' { - declare module.exports: $Exports<'bcoin/lib/utils/base58'>; -} -declare module 'bcoin/lib/utils/bloom.js' { - declare module.exports: $Exports<'bcoin/lib/utils/bloom'>; -} -declare module 'bcoin/lib/utils/co.js' { - declare module.exports: $Exports<'bcoin/lib/utils/co'>; -} -declare module 'bcoin/lib/utils/encoding.js' { - declare module.exports: $Exports<'bcoin/lib/utils/encoding'>; -} -declare module 'bcoin/lib/utils/fs.js' { - declare module.exports: $Exports<'bcoin/lib/utils/fs'>; -} -declare module 'bcoin/lib/utils/heap.js' { - declare module.exports: $Exports<'bcoin/lib/utils/heap'>; -} -declare module 'bcoin/lib/utils/index.js' { - declare module.exports: $Exports<'bcoin/lib/utils/index'>; -} -declare module 'bcoin/lib/utils/ip.js' { - declare module.exports: $Exports<'bcoin/lib/utils/ip'>; -} -declare module 'bcoin/lib/utils/lazy-browser.js' { - declare module.exports: $Exports<'bcoin/lib/utils/lazy-browser'>; -} -declare module 'bcoin/lib/utils/lazy.js' { - declare module.exports: $Exports<'bcoin/lib/utils/lazy'>; -} -declare module 'bcoin/lib/utils/list.js' { - declare module.exports: $Exports<'bcoin/lib/utils/list'>; -} -declare module 'bcoin/lib/utils/lock.js' { - declare module.exports: $Exports<'bcoin/lib/utils/lock'>; -} -declare module 'bcoin/lib/utils/lru.js' { - declare module.exports: $Exports<'bcoin/lib/utils/lru'>; -} -declare module 'bcoin/lib/utils/map.js' { - declare module.exports: $Exports<'bcoin/lib/utils/map'>; -} -declare module 'bcoin/lib/utils/murmur3.js' { - declare module.exports: $Exports<'bcoin/lib/utils/murmur3'>; -} -declare module 'bcoin/lib/utils/native.js' { - declare module.exports: $Exports<'bcoin/lib/utils/native'>; -} -declare module 'bcoin/lib/utils/nexttick-browser.js' { - declare module.exports: $Exports<'bcoin/lib/utils/nexttick-browser'>; -} -declare module 'bcoin/lib/utils/nexttick.js' { - declare module.exports: $Exports<'bcoin/lib/utils/nexttick'>; -} -declare module 'bcoin/lib/utils/nfkd-browser.js' { - declare module.exports: $Exports<'bcoin/lib/utils/nfkd-browser'>; -} -declare module 'bcoin/lib/utils/nfkd.js' { - declare module.exports: $Exports<'bcoin/lib/utils/nfkd'>; -} -declare module 'bcoin/lib/utils/pem.js' { - declare module.exports: $Exports<'bcoin/lib/utils/pem'>; -} -declare module 'bcoin/lib/utils/protobuf.js' { - declare module.exports: $Exports<'bcoin/lib/utils/protobuf'>; -} -declare module 'bcoin/lib/utils/rbt.js' { - declare module.exports: $Exports<'bcoin/lib/utils/rbt'>; -} -declare module 'bcoin/lib/utils/reader.js' { - declare module.exports: $Exports<'bcoin/lib/utils/reader'>; -} -declare module 'bcoin/lib/utils/staticwriter.js' { - declare module.exports: $Exports<'bcoin/lib/utils/staticwriter'>; -} -declare module 'bcoin/lib/utils/util.js' { - declare module.exports: $Exports<'bcoin/lib/utils/util'>; -} -declare module 'bcoin/lib/utils/validator.js' { - declare module.exports: $Exports<'bcoin/lib/utils/validator'>; -} -declare module 'bcoin/lib/utils/writer.js' { - declare module.exports: $Exports<'bcoin/lib/utils/writer'>; -} -declare module 'bcoin/lib/wallet/account.js' { - declare module.exports: $Exports<'bcoin/lib/wallet/account'>; -} -declare module 'bcoin/lib/wallet/client.js' { - declare module.exports: $Exports<'bcoin/lib/wallet/client'>; -} -declare module 'bcoin/lib/wallet/common.js' { - declare module.exports: $Exports<'bcoin/lib/wallet/common'>; -} -declare module 'bcoin/lib/wallet/http.js' { - declare module.exports: $Exports<'bcoin/lib/wallet/http'>; -} -declare module 'bcoin/lib/wallet/index.js' { - declare module.exports: $Exports<'bcoin/lib/wallet/index'>; -} -declare module 'bcoin/lib/wallet/layout-browser.js' { - declare module.exports: $Exports<'bcoin/lib/wallet/layout-browser'>; -} -declare module 'bcoin/lib/wallet/layout.js' { - declare module.exports: $Exports<'bcoin/lib/wallet/layout'>; -} -declare module 'bcoin/lib/wallet/masterkey.js' { - declare module.exports: $Exports<'bcoin/lib/wallet/masterkey'>; -} -declare module 'bcoin/lib/wallet/path.js' { - declare module.exports: $Exports<'bcoin/lib/wallet/path'>; -} -declare module 'bcoin/lib/wallet/plugin.js' { - declare module.exports: $Exports<'bcoin/lib/wallet/plugin'>; -} -declare module 'bcoin/lib/wallet/records.js' { - declare module.exports: $Exports<'bcoin/lib/wallet/records'>; -} -declare module 'bcoin/lib/wallet/rpc.js' { - declare module.exports: $Exports<'bcoin/lib/wallet/rpc'>; -} -declare module 'bcoin/lib/wallet/server.js' { - declare module.exports: $Exports<'bcoin/lib/wallet/server'>; -} -declare module 'bcoin/lib/wallet/txdb.js' { - declare module.exports: $Exports<'bcoin/lib/wallet/txdb'>; -} -declare module 'bcoin/lib/wallet/wallet.js' { - declare module.exports: $Exports<'bcoin/lib/wallet/wallet'>; -} -declare module 'bcoin/lib/wallet/walletdb.js' { - declare module.exports: $Exports<'bcoin/lib/wallet/walletdb'>; -} -declare module 'bcoin/lib/wallet/walletkey.js' { - declare module.exports: $Exports<'bcoin/lib/wallet/walletkey'>; -} -declare module 'bcoin/lib/workers/framer.js' { - declare module.exports: $Exports<'bcoin/lib/workers/framer'>; -} -declare module 'bcoin/lib/workers/index.js' { - declare module.exports: $Exports<'bcoin/lib/workers/index'>; -} -declare module 'bcoin/lib/workers/jobs.js' { - declare module.exports: $Exports<'bcoin/lib/workers/jobs'>; -} -declare module 'bcoin/lib/workers/master.js' { - declare module.exports: $Exports<'bcoin/lib/workers/master'>; -} -declare module 'bcoin/lib/workers/packets.js' { - declare module.exports: $Exports<'bcoin/lib/workers/packets'>; -} -declare module 'bcoin/lib/workers/parser-client.js' { - declare module.exports: $Exports<'bcoin/lib/workers/parser-client'>; -} -declare module 'bcoin/lib/workers/parser.js' { - declare module.exports: $Exports<'bcoin/lib/workers/parser'>; -} -declare module 'bcoin/lib/workers/worker-browser.js' { - declare module.exports: $Exports<'bcoin/lib/workers/worker-browser'>; -} -declare module 'bcoin/lib/workers/worker.js' { - declare module.exports: $Exports<'bcoin/lib/workers/worker'>; -} -declare module 'bcoin/lib/workers/workerpool.js' { - declare module.exports: $Exports<'bcoin/lib/workers/workerpool'>; -} -declare module 'bcoin/migrate/chaindb0to1.js' { - declare module.exports: $Exports<'bcoin/migrate/chaindb0to1'>; -} -declare module 'bcoin/migrate/chaindb1to2.js' { - declare module.exports: $Exports<'bcoin/migrate/chaindb1to2'>; -} -declare module 'bcoin/migrate/coins-old.js' { - declare module.exports: $Exports<'bcoin/migrate/coins-old'>; -} -declare module 'bcoin/migrate/coinview-old.js' { - declare module.exports: $Exports<'bcoin/migrate/coinview-old'>; -} -declare module 'bcoin/migrate/compress-old.js' { - declare module.exports: $Exports<'bcoin/migrate/compress-old'>; -} -declare module 'bcoin/migrate/ensure-tip-index.js' { - declare module.exports: $Exports<'bcoin/migrate/ensure-tip-index'>; -} -declare module 'bcoin/migrate/walletdb2to3.js' { - declare module.exports: $Exports<'bcoin/migrate/walletdb2to3'>; -} -declare module 'bcoin/migrate/walletdb3to4.js' { - declare module.exports: $Exports<'bcoin/migrate/walletdb3to4'>; -} -declare module 'bcoin/migrate/walletdb4to5.js' { - declare module.exports: $Exports<'bcoin/migrate/walletdb4to5'>; -} -declare module 'bcoin/migrate/walletdb5to6.js' { - declare module.exports: $Exports<'bcoin/migrate/walletdb5to6'>; -} -declare module 'bcoin/scripts/dump.js' { - declare module.exports: $Exports<'bcoin/scripts/dump'>; -} -declare module 'bcoin/scripts/fuzz.js' { - declare module.exports: $Exports<'bcoin/scripts/fuzz'>; -} -declare module 'bcoin/scripts/gen.js' { - declare module.exports: $Exports<'bcoin/scripts/gen'>; -} -declare module 'bcoin/vendor/setimmediate.js' { - declare module.exports: $Exports<'bcoin/vendor/setimmediate'>; -} -declare module 'bcoin/vendor/unorm.js' { - declare module.exports: $Exports<'bcoin/vendor/unorm'>; -} diff --git a/lib/full_node.js b/lib/full_node.js index 947cc83..79b1948 100644 --- a/lib/full_node.js +++ b/lib/full_node.js @@ -3,6 +3,8 @@ var bcoin = require('bcoin'); var TrustIsRisk = require('./trust_is_risk'); class FullNode extends bcoin.fullnode { + + constructor(options ) { super(options); this.trust = new TrustIsRisk(this); diff --git a/lib/trust_is_risk.js b/lib/trust_is_risk.js index ed2e4e1..336a6be 100644 --- a/lib/trust_is_risk.js +++ b/lib/trust_is_risk.js @@ -1,5 +1,14 @@ // var bcoin = require('bcoin'); +var Address = bcoin.primitives.Address; + + // base58 bitcoin address + + + + + + class TrustIsRisk { @@ -11,16 +20,72 @@ class TrustIsRisk { } addTX(tx ) { - if (!this.isTrustTX(tx)) return false; - // TODO + var trustChange = this.parseTXAsTrustChange(tx); + if (trustChange === null) return false; + return true; } - isTrustTX(tx ) { - return true; // TODO + // Parses a transaction as a trust change, or returns null if the + // transaction is not a TIR transaction. + parseTXAsTrustChange(tx ) { + var trustChange = this.parseTXAsTrustIncrease(tx); + if (trustChange === null) { + trustChange = this.parseTXAsTrustDecrease(tx); + } + + return trustChange; } -} + parseTXAsTrustIncrease(tx ) { + if (tx.inputs.length !== 1) return null; + if (tx.inputs[0].getType() !== 'pubkeyhash') return null; + var sender = tx.inputs[0].getAddress(); + + if (tx.outputs.length == 0 || tx.outputs.length > 2) return null; + + var trustChanges = []; + for (var i = 0; i < tx.outputs.length; i++) { + if (this.isChangeOutput(tx.outputs[i], sender)) continue; + + var trustChange = this.parseOutputAsDirectTrust(tx.outputs[i], sender.toBase58()); + if (trustChange === null) return null; + trustChanges.push(trustChange); + } + if (trustChanges.length !== 1) return null; + + return trustChanges[0]; + } + + parseTXAsTrustDecrease(tx ) { + return null; + } + + isChangeOutput(output , sender ) { + return (output.getType() === 'pubkeyhash') + && (output.getAddress().toBase58() === sender.toBase58()); + } + + parseOutputAsDirectTrust(output , sender ) { + if (output.getType() !== 'multisig') return null; + + var addressA = Address.fromHash(bcoin.crypto.hash160(output.script.get(1))).toBase58() + var addressB = Address.fromHash(bcoin.crypto.hash160(output.script.get(2))).toBase58(); + + if (addressA === addressB) return null; + var receiver; + if (addressA === sender) receiver = addressB; + else if (addressB === sender) receiver = addressA; + else return null; + + return { + from: sender, + to: receiver, + amount: Number(output.value) + }; + } + +} module.exports = TrustIsRisk; diff --git a/src/full_node.js b/src/full_node.js index 2798307..f6b31f8 100644 --- a/src/full_node.js +++ b/src/full_node.js @@ -3,6 +3,8 @@ var bcoin = require('bcoin'); var TrustIsRisk = require('./trust_is_risk'); class FullNode extends bcoin.fullnode { + trust : TrustIsRisk + constructor(options : Object) { super(options); this.trust = new TrustIsRisk(this); diff --git a/src/trust_is_risk.js b/src/trust_is_risk.js index 8be984d..43d13b2 100644 --- a/src/trust_is_risk.js +++ b/src/trust_is_risk.js @@ -1,26 +1,91 @@ // @flow var bcoin = require('bcoin'); +var Address = bcoin.primitives.Address; + +type Entity = string; // base58 bitcoin address +type DirectTrust = { + from : Entity, + to: Entity, + amount: number +} +type TrustChange = DirectTrust; class TrustIsRisk { - node : bcoin.fullnode + node : bcoin$FullNode - constructor(node : bcoin.fullnode) { + constructor(node : bcoin$FullNode) { this.node = node; this.node.on('tx', this.addTX.bind(this)); } - addTX(tx : bcoin.TX) : boolean { - if (!this.isTrustTX(tx)) return false; - // TODO + addTX(tx : bcoin$TX) : boolean { + var trustChange = this.parseTXAsTrustChange(tx); + if (trustChange === null) return false; + return true; } - isTrustTX(tx : bcoin.TX) : boolean { - return true; // TODO + // Parses a transaction as a trust change, or returns null if the + // transaction is not a TIR transaction. + parseTXAsTrustChange(tx : bcoin$TX) : ?TrustChange { + var trustChange = this.parseTXAsTrustIncrease(tx); + if (trustChange === null) { + trustChange = this.parseTXAsTrustDecrease(tx); + } + + return trustChange; } -} + parseTXAsTrustIncrease(tx : bcoin$TX) : ?TrustChange { + if (tx.inputs.length !== 1) return null; + if (tx.inputs[0].getType() !== 'pubkeyhash') return null; + var sender = tx.inputs[0].getAddress(); + + if (tx.outputs.length == 0 || tx.outputs.length > 2) return null; + var trustChanges = []; + for (var i = 0; i < tx.outputs.length; i++) { + if (this.isChangeOutput(tx.outputs[i], sender)) continue; + + var trustChange = this.parseOutputAsDirectTrust(tx.outputs[i], sender.toBase58()); + if (trustChange === null) return null; + trustChanges.push(trustChange); + } + if (trustChanges.length !== 1) return null; + + return trustChanges[0]; + } + + parseTXAsTrustDecrease(tx : bcoin$TX) : ?TrustChange { + return null; + } + + isChangeOutput(output : bcoin$Output, sender : bcoin$Address) : boolean { + return (output.getType() === 'pubkeyhash') + && (output.getAddress().toBase58() === sender.toBase58()); + } + + parseOutputAsDirectTrust(output : bcoin$Output, sender : Entity) : ?DirectTrust { + if (output.getType() !== 'multisig') return null; + + var addressA = Address.fromHash(bcoin.crypto.hash160(output.script.get(1))).toBase58() + var addressB = Address.fromHash(bcoin.crypto.hash160(output.script.get(2))).toBase58(); + + if (addressA === addressB) return null; + + var receiver; + if (addressA === sender) receiver = addressB; + else if (addressB === sender) receiver = addressA; + else return null; + + return { + from: sender, + to: receiver, + amount: Number(output.value) + }; + } + +} module.exports = TrustIsRisk; diff --git a/test/full_node.js b/test/full_node.js index 64ad3b5..d6308a8 100644 --- a/test/full_node.js +++ b/test/full_node.js @@ -6,7 +6,7 @@ var sinon = require('sinon'); var should = require('should'); require('should-sinon'); -describe('TrustIsRisk.FullNode', () => { +describe('FullNode', () => { var node = null; var walletDB = null; sinon.spy(Trust.TrustIsRisk.prototype, 'addTX'); diff --git a/test/trust_is_risk.js b/test/trust_is_risk.js new file mode 100644 index 0000000..5c6e95a --- /dev/null +++ b/test/trust_is_risk.js @@ -0,0 +1,115 @@ +var Trust = require('../'); +var bcoin = require('bcoin'); +var testHelpers = require('./helpers'); +var consensus = require('bcoin/lib/protocol/consensus'); +var sinon = require('sinon'); +var should = require('should'); +require('should-sinon'); + +describe('TrustIsRisk', () => { + var inputP2PKH = new bcoin.primitives.Input({ + prevout: { + hash: 'v0pnhphaf4r5wz63j60vnh27s1bftl260qq621y458tn0g4x64u64yqz6d7qi6i8', + index: 0xfffffffa + }, script: bcoin.script.fromString( + // 17P8kCbDBPmqLDCCe9dYwbfiEDaRb5xDYE + "0x47 0x3044022035e32834c6ee4db1696cc06762feca2809d865ca12a3b98c801f3f451341a2570220573bf3ffef55f2651e1563acc0a22f8056222f277f5ddf17dd583d4edd40fa6001 0x21 0x02b8f07a401eca4888039b1898f94db44c43ccc6d3aa8b27e9b6ed7b377b24c083") + }); + + var outputOneOfTwoMultsig = new bcoin.primitives.Output({ + script: bcoin.script.fromString( + // 1/{17P8kCbDBPmqLDCCe9dYwbfiEDaRb5xDYE, 1P6NdQWeZTLrYCpQNbYeXsLeaEjn8h6UFx} + "OP_1 0x21 0x02b8f07a401eca4888039b1898f94db44c43ccc6d3aa8b27e9b6ed7b377b24c083 0x28 0x2437025954568a8273968aa7535dbfc444fd8f8d0f5237cd96ac7234c77810ada53054a3654e669b OP_2 OP_CHECKMULTISIG"), + value: 42 + }); + + var tir = null, mtx = null, changeScript = null; + beforeEach(() => { + tir = new Trust.TrustIsRisk(new bcoin.fullnode({})); + + mtx = new bcoin.primitives.MTX({ + inputs: [ + inputP2PKH + ], + outputs: [ + outputOneOfTwoMultsig + ] + }); + + changeScript = bcoin.script.fromString( + // Pays to 17P8kCbDBPmqLDCCe9dYwbfiEDaRb5xDYE + "OP_DUP OP_HASH160 0x14 0x46005EF459C9E7C37AF8871D25BC39D0EA0534D1 OP_EQUALVERIFY OP_CHECKSIG"); + }); + + describe('.parseTXAsTrustIncrease', () => { + it('correctly parses trust increasing transactions', () => { + var trustChange = tir.parseTXAsTrustIncrease(mtx.toTX()); + + should(trustChange).deepEqual({ + from: "17P8kCbDBPmqLDCCe9dYwbfiEDaRb5xDYE", + to: "1P6NdQWeZTLrYCpQNbYeXsLeaEjn8h6UFx", + amount: 42 + }); + }); + + it('rejects transactions with more than one input', () => { + mtx.inputs.push(inputP2PKH); + var trustChange = tir.parseTXAsTrustIncrease(mtx.toTX()); + + should(trustChange).equal(null); + }); + + it('correctly parses trust increasing transactions with change outputs', () => { + mtx.outputs[0].value -= 10; + mtx.outputs.push(new bcoin.primitives.Output({ + script: changeScript, + value: 10 + })); + var trustChange = tir.parseTXAsTrustIncrease(mtx.toTX()); + + should(trustChange).deepEqual({ + from: "17P8kCbDBPmqLDCCe9dYwbfiEDaRb5xDYE", + to: "1P6NdQWeZTLrYCpQNbYeXsLeaEjn8h6UFx", + amount: 32 + }); + }); + + it('rejects transactions with two change outputs', () => { + mtx.outputs[0].value -= 10; + for (var i = 0; i < 2; i++) { + mtx.outputs.push(new bcoin.primitives.Output({ + script: changeScript, + value: 5 + })); + } + var trustChange = tir.parseTXAsTrustIncrease(mtx.toTX()); + + should(trustChange).equal(null); + }); + + it('rejects transactions with a second output that\'s not a change output', () => { + mtx.outputs[0].value -= 10; + mtx.outputs.push(new bcoin.primitives.Output({ + script: bcoin.script.fromString( + // Pays to 1JDfVQkZxMvRwM3Lc6LkDrpX55Ldk3JqND + "OP_DUP OP_HASH160 0x14 0xBCDF4271C6600E7D02E60F9206A9AD862FFBD4F0 OP_EQUALVERIFY OP_CHECKSIG"), + value: 10 + })); + var trustChange = tir.parseTXAsTrustIncrease(mtx.toTX()); + + should(trustChange).equal(null); + }); + + it('rejects transactions with no trust outputs', () => { + mtx.outputs[0] = new bcoin.primitives.Output({ + script: bcoin.script.fromString( + // Pays to 1JDfVQkZxMvRwM3Lc6LkDrpX55Ldk3JqND + "OP_DUP OP_HASH160 0x14 0xBCDF4271C6600E7D02E60F9206A9AD862FFBD4F0 OP_EQUALVERIFY OP_CHECKSIG"), + value: 10 + }); + var trustChange = tir.parseTXAsTrustIncrease(mtx.toTX()); + + should(trustChange).equal(null); + }); + }); +}); From 00e4c469e76deb37d4faa191f6c1b601bc1edee3 Mon Sep 17 00:00:00 2001 From: Christos Porios Date: Wed, 17 May 2017 19:10:52 +0000 Subject: [PATCH 03/20] Implement TrustIsRisk.prototype.getDirect() --- lib/trust_is_risk.js | 22 +++++++++++++++++++++- src/trust_is_risk.js | 22 +++++++++++++++++++++- test/trust_is_risk.js | 42 ++++++++++++++++++++++++++++++++++-------- 3 files changed, 76 insertions(+), 10 deletions(-) diff --git a/lib/trust_is_risk.js b/lib/trust_is_risk.js index 336a6be..496d37a 100644 --- a/lib/trust_is_risk.js +++ b/lib/trust_is_risk.js @@ -12,16 +12,36 @@ var Address = bcoin.primitives.Address; class TrustIsRisk { + + // Direct trust map + + + + + constructor(node ) { this.node = node; + this.trust = {}; this.node.on('tx', this.addTX.bind(this)); } + getDirect(from , to ) { + if (!this.trust.hasOwnProperty(from)) return 0; + if (!this.trust[from].hasOwnProperty(to)) return 0; + return this.trust[from][to]; + } + addTX(tx ) { var trustChange = this.parseTXAsTrustChange(tx); - if (trustChange === null) return false; + if (!trustChange) return false; + + if (!this.trust.hasOwnProperty(trustChange.from)) this.trust[trustChange.from] = {} + if (!this.trust[trustChange.from].hasOwnProperty(trustChange.to)) { + this.trust[trustChange.from][trustChange.to] = 0 + } + this.trust[trustChange.from][trustChange.to] += trustChange.amount; return true; } diff --git a/src/trust_is_risk.js b/src/trust_is_risk.js index 43d13b2..be4c8af 100644 --- a/src/trust_is_risk.js +++ b/src/trust_is_risk.js @@ -12,16 +12,36 @@ type TrustChange = DirectTrust; class TrustIsRisk { node : bcoin$FullNode + + // Direct trust map + trust : { + [from : Entity] : ({ + [to : Entity] : number + }) + }; constructor(node : bcoin$FullNode) { this.node = node; + this.trust = {}; this.node.on('tx', this.addTX.bind(this)); } + getDirect(from : Entity, to : Entity) : number { + if (!this.trust.hasOwnProperty(from)) return 0; + if (!this.trust[from].hasOwnProperty(to)) return 0; + return this.trust[from][to]; + } + addTX(tx : bcoin$TX) : boolean { var trustChange = this.parseTXAsTrustChange(tx); - if (trustChange === null) return false; + if (!trustChange) return false; + + if (!this.trust.hasOwnProperty(trustChange.from)) this.trust[trustChange.from] = {} + if (!this.trust[trustChange.from].hasOwnProperty(trustChange.to)) { + this.trust[trustChange.from][trustChange.to] = 0 + } + this.trust[trustChange.from][trustChange.to] += trustChange.amount; return true; } diff --git a/test/trust_is_risk.js b/test/trust_is_risk.js index 5c6e95a..a03ef5f 100644 --- a/test/trust_is_risk.js +++ b/test/trust_is_risk.js @@ -7,18 +7,21 @@ var should = require('should'); require('should-sinon'); describe('TrustIsRisk', () => { + var sender = "17P8kCbDBPmqLDCCe9dYwbfiEDaRb5xDYE"; + var receiver = "1P6NdQWeZTLrYCpQNbYeXsLeaEjn8h6UFx"; + var inputP2PKH = new bcoin.primitives.Input({ prevout: { hash: 'v0pnhphaf4r5wz63j60vnh27s1bftl260qq621y458tn0g4x64u64yqz6d7qi6i8', index: 0xfffffffa }, script: bcoin.script.fromString( - // 17P8kCbDBPmqLDCCe9dYwbfiEDaRb5xDYE + // 17P8kCbDBPmqLDCCe9dYwbfiEDaRb5xDYE (sender) "0x47 0x3044022035e32834c6ee4db1696cc06762feca2809d865ca12a3b98c801f3f451341a2570220573bf3ffef55f2651e1563acc0a22f8056222f277f5ddf17dd583d4edd40fa6001 0x21 0x02b8f07a401eca4888039b1898f94db44c43ccc6d3aa8b27e9b6ed7b377b24c083") }); var outputOneOfTwoMultsig = new bcoin.primitives.Output({ script: bcoin.script.fromString( - // 1/{17P8kCbDBPmqLDCCe9dYwbfiEDaRb5xDYE, 1P6NdQWeZTLrYCpQNbYeXsLeaEjn8h6UFx} + // 1/{17P8kCbDBPmqLDCCe9dYwbfiEDaRb5xDYE (sender), 1P6NdQWeZTLrYCpQNbYeXsLeaEjn8h6UFx (receiver)} "OP_1 0x21 0x02b8f07a401eca4888039b1898f94db44c43ccc6d3aa8b27e9b6ed7b377b24c083 0x28 0x2437025954568a8273968aa7535dbfc444fd8f8d0f5237cd96ac7234c77810ada53054a3654e669b OP_2 OP_CHECKMULTISIG"), value: 42 }); @@ -46,8 +49,8 @@ describe('TrustIsRisk', () => { var trustChange = tir.parseTXAsTrustIncrease(mtx.toTX()); should(trustChange).deepEqual({ - from: "17P8kCbDBPmqLDCCe9dYwbfiEDaRb5xDYE", - to: "1P6NdQWeZTLrYCpQNbYeXsLeaEjn8h6UFx", + from: sender, + to: receiver, amount: 42 }); }); @@ -68,8 +71,8 @@ describe('TrustIsRisk', () => { var trustChange = tir.parseTXAsTrustIncrease(mtx.toTX()); should(trustChange).deepEqual({ - from: "17P8kCbDBPmqLDCCe9dYwbfiEDaRb5xDYE", - to: "1P6NdQWeZTLrYCpQNbYeXsLeaEjn8h6UFx", + from: sender, + to: receiver, amount: 32 }); }); @@ -91,7 +94,7 @@ describe('TrustIsRisk', () => { mtx.outputs[0].value -= 10; mtx.outputs.push(new bcoin.primitives.Output({ script: bcoin.script.fromString( - // Pays to 1JDfVQkZxMvRwM3Lc6LkDrpX55Ldk3JqND + // Pays to 1JDfVQkZxMvRwM3Lc6LkDrpX55Ldk3JqND (neither sender or receiver) "OP_DUP OP_HASH160 0x14 0xBCDF4271C6600E7D02E60F9206A9AD862FFBD4F0 OP_EQUALVERIFY OP_CHECKSIG"), value: 10 })); @@ -103,7 +106,7 @@ describe('TrustIsRisk', () => { it('rejects transactions with no trust outputs', () => { mtx.outputs[0] = new bcoin.primitives.Output({ script: bcoin.script.fromString( - // Pays to 1JDfVQkZxMvRwM3Lc6LkDrpX55Ldk3JqND + // Pays to 1JDfVQkZxMvRwM3Lc6LkDrpX55Ldk3JqND (neither sender or receiver) "OP_DUP OP_HASH160 0x14 0xBCDF4271C6600E7D02E60F9206A9AD862FFBD4F0 OP_EQUALVERIFY OP_CHECKSIG"), value: 10 }); @@ -112,4 +115,27 @@ describe('TrustIsRisk', () => { should(trustChange).equal(null); }); }); + + describe('.getDirect()', () => { + it('returns zero for two arbitary parties that do not trust each other', () => { + should(tir.getDirect(sender, receiver)).equal(0); + should(tir.getDirect(receiver, sender)).equal(0); + should(tir.getDirect("1JDfVQkZxMvRwM3Lc6LkDrpX55Ldk3JqND", sender)).equal(0); + should(tir.getDirect(sender, "1JDfVQkZxMvRwM3Lc6LkDrpX55Ldk3JqND")).equal(0); + }); + }); + + describe('.addTX()', () => { + it('correctly increases direct trust when adding a trust-increasing transaction', () => { + mtx.outputs[0].value -= 10; + mtx.outputs.push(new bcoin.primitives.Output({ + script: changeScript, + value: 10 + })); + tir.addTX(mtx.toTX()); + + should(tir.getDirect(sender, receiver)).equal(32); + should(tir.getDirect(receiver, sender)).equal(0); // Trust is not bi-directional + }); + }); }); From 913fd8d3761dc55499ceca6fd6d1ae6c0c12d14c Mon Sep 17 00:00:00 2001 From: Christos Porios Date: Thu, 18 May 2017 18:29:52 +0000 Subject: [PATCH 04/20] Implement trust decreasing transaction parsing --- flow-typed/npm/bcoin_vx.x.x.js | 11 +- lib/trust_is_risk.js | 185 ++++++++++++++++++++------- package.json | 1 + src/trust_is_risk.js | 185 ++++++++++++++++++++------- test/helpers.js | 9 ++ test/trust_is_risk.js | 222 ++++++++++++++++++--------------- 6 files changed, 417 insertions(+), 196 deletions(-) diff --git a/flow-typed/npm/bcoin_vx.x.x.js b/flow-typed/npm/bcoin_vx.x.x.js index e1db501..d07ac45 100644 --- a/flow-typed/npm/bcoin_vx.x.x.js +++ b/flow-typed/npm/bcoin_vx.x.x.js @@ -12,6 +12,8 @@ declare class bcoin$Address { declare class bcoin$TX { inputs : bcoin$Input[]; outputs : bcoin$Output[]; + + hash(enc : ?'hex') : Buffer; } declare class bcoin$Output { @@ -24,6 +26,7 @@ declare class bcoin$Output { declare class bcoin$Input { script : bcoin$Script; + prevout : bcoin$Outpoint; getType() : ('pubkeyhash' | 'multisig'); getAddress() : bcoin$Address; } @@ -32,6 +35,11 @@ declare class bcoin$Script { get(n : number) : (Buffer); } +declare class bcoin$Outpoint { + hash : Buffer; + index : number; +} + declare module 'bcoin' { declare module.exports: { fullnode : Class, @@ -40,7 +48,8 @@ declare module 'bcoin' { Address : Class, TX : Class, Output : Class, - Input : Class + Input : Class, + Outpoint : Class }, crypto : { hash160(str : (string | Buffer)) : (string | Buffer) diff --git a/lib/trust_is_risk.js b/lib/trust_is_risk.js index 496d37a..60a5ac2 100644 --- a/lib/trust_is_risk.js +++ b/lib/trust_is_risk.js @@ -1,92 +1,181 @@ // var bcoin = require('bcoin'); +var assert = require('assert'); var Address = bcoin.primitives.Address; // base58 bitcoin address - + + + + class TrustIsRisk { - // Direct trust map - + - + + + + + constructor(node ) { this.node = node; - this.trust = {}; + this.directTrust = {}; + this.TXToTrust = {}; this.node.on('tx', this.addTX.bind(this)); } - getDirect(from , to ) { - if (!this.trust.hasOwnProperty(from)) return 0; - if (!this.trust[from].hasOwnProperty(to)) return 0; - return this.trust[from][to]; + getDirectTrust(from , to ) { + if (!this.directTrust.hasOwnProperty(from)) return 0; + if (!this.directTrust[from].hasOwnProperty(to)) return 0; + return this.directTrust[from][to]; } + // Attempts to parse a bitcoin transaction as a trust change and adds it to the trust network + // if successful. + // Returns true if the transaction is a TIR transaction and was successfully added to the + // network, false otherwise. + // Throws an error if the transaction was processed earlier. addTX(tx ) { - var trustChange = this.parseTXAsTrustChange(tx); - if (!trustChange) return false; + var txHash = tx.hash().toString('hex'); + if (this.TXToTrust.hasOwnProperty(txHash)) { + throw new Error('Duplicate TX: Transaction with hash ' + txHash + ' has been seen again.'); + } - if (!this.trust.hasOwnProperty(trustChange.from)) this.trust[trustChange.from] = {} - if (!this.trust[trustChange.from].hasOwnProperty(trustChange.to)) { - this.trust[trustChange.from][trustChange.to] = 0 + var trustChanges = this.getTrustChanges(tx); + if (trustChanges.length === 0) return false; + else { + trustChanges.map(this.applyTrustChange.bind(this)); + return true; + } + } + + // Returns a list of trust changes that a transaction causes, which will be one of the following: + // * An empty list (for non-TIR transactions). + // * A list containing a single trust increase (for trust-increasing transactions). + // * A list containing one or more trust decreases (for trust-decreasing transactions). + getTrustChanges(tx ) { + var trustIncrease = this.parseTXAsTrustIncrease(tx); + if (trustIncrease !== null) { + return [trustIncrease]; + } else { + return this.getTrustDecreases(tx); } - this.trust[trustChange.from][trustChange.to] += trustChange.amount; - - return true; } - // Parses a transaction as a trust change, or returns null if the - // transaction is not a TIR transaction. - parseTXAsTrustChange(tx ) { - var trustChange = this.parseTXAsTrustIncrease(tx); - if (trustChange === null) { - trustChange = this.parseTXAsTrustDecrease(tx); + applyTrustChange(trustChange ) { + if (!this.directTrust.hasOwnProperty(trustChange.from)) this.directTrust[trustChange.from] = {}; + if (!this.directTrust[trustChange.from].hasOwnProperty(trustChange.to)) { + this.directTrust[trustChange.from][trustChange.to] = 0 } - return trustChange; + this.directTrust[trustChange.from][trustChange.to] += trustChange.amount; + + if (this.directTrust[trustChange.from][trustChange.to] > 0) { + this.TXToTrust[trustChange.txHash] = { + from: trustChange.from, + to: trustChange.to, + amount: this.directTrust[trustChange.from][trustChange.to], + txHash: trustChange.txHash, + outputIndex: trustChange.outputIndex + }; + } } - parseTXAsTrustIncrease(tx ) { + parseTXAsTrustIncrease(tx ) { if (tx.inputs.length !== 1) return null; - if (tx.inputs[0].getType() !== 'pubkeyhash') return null; - var sender = tx.inputs[0].getAddress(); + if (tx.inputs[0].getType() !== 'pubkeyhash') return null; // TODO: This is unreliable + if (this.TXToTrust[tx.inputs[0].prevout.hash.toString('hex')]) return null; + var sender = tx.inputs[0].getAddress().toBase58(); - if (tx.outputs.length == 0 || tx.outputs.length > 2) return null; + if (tx.outputs.length === 0 || tx.outputs.length > 2) return null; - var trustChanges = []; - for (var i = 0; i < tx.outputs.length; i++) { - if (this.isChangeOutput(tx.outputs[i], sender)) continue; + var trustOutputs = this.searchForDirectTrustOutputs(tx, sender); + if (trustOutputs.length !== 1) return null; - var trustChange = this.parseOutputAsDirectTrust(tx.outputs[i], sender.toBase58()); - if (trustChange === null) return null; - trustChanges.push(trustChange); - } - if (trustChanges.length !== 1) return null; + var changeOutputCount = tx.outputs.filter((o) => this.isChangeOutput(o, sender)).length + if (changeOutputCount + 1 !== tx.outputs.length) return null; - return trustChanges[0]; + return trustOutputs[0]; } - parseTXAsTrustDecrease(tx ) { - return null; + getTrustDecreases(tx ) { + var inputTrusts = this.getInputTrusts(tx.inputs); + return inputTrusts.map(this.getTrustDecrease.bind(this, tx)); + } + + getInputTrusts(inputs ) { + return inputs.map((input) => { + var trust = this.TXToTrust[input.prevout.hash.toString('hex')] + if (trust && trust.outputIndex === input.prevout.index) return trust; + else return null; + }).filter(Boolean); + } + + getTrustDecrease(tx , prevTrust ) { + var txHash = tx.hash().toString('hex'); + var nullifyTrust = { + from: prevTrust.from, + to: prevTrust.to, + amount: -prevTrust.amount, + txHash, + outputIndex: null + }; + + if (tx.inputs.length !== 1) return nullifyTrust; + + var trustOutputs = this.searchForDirectTrustOutputs(tx, prevTrust.from, prevTrust.to); + if (trustOutputs.length != 1) return nullifyTrust; + var nextTrust = trustOutputs[0]; + + assert(nextTrust.from === prevTrust.from); + assert(nextTrust.to === prevTrust.to); + + var trustAmountChange = nextTrust.amount - prevTrust.amount; + assert(trustAmountChange <= 0); + return { + from: nextTrust.from, + to: nextTrust.to, + amount: trustAmountChange, + txHash, + outputIndex: nextTrust.outputIndex + } + } + + // Looks for direct trust outputs that originate from a sender in an array of bitcoin outputs. + // If the recipient parameter is set, it will limit the results only to the outputs being sent to + // the recipient. + searchForDirectTrustOutputs(tx , sender , recipient ) { + var directTrusts = tx.outputs.map((output, outputIndex) => + this.parseOutputAsDirectTrust(tx, outputIndex, sender) + ).filter(Boolean); + + if (recipient) { + directTrusts.filter((trust) => trust.to === recipient); + } + + return directTrusts; } - isChangeOutput(output , sender ) { + isChangeOutput(output , sender ) { return (output.getType() === 'pubkeyhash') - && (output.getAddress().toBase58() === sender.toBase58()); + && (output.getAddress().toBase58() === sender); } - parseOutputAsDirectTrust(output , sender ) { + parseOutputAsDirectTrust(tx , outputIndex , sender ) + { + var txHash = tx.hash().toString('hex'); + var output = tx.outputs[outputIndex]; if (output.getType() !== 'multisig') return null; var addressA = Address.fromHash(bcoin.crypto.hash160(output.script.get(1))).toBase58() @@ -94,15 +183,17 @@ class TrustIsRisk { if (addressA === addressB) return null; - var receiver; - if (addressA === sender) receiver = addressB; - else if (addressB === sender) receiver = addressA; + var recipient; + if (addressA === sender) recipient = addressB; + else if (addressB === sender) recipient = addressA; else return null; return { from: sender, - to: receiver, - amount: Number(output.value) + to: recipient, + amount: Number(output.value), + txHash, + outputIndex }; } diff --git a/package.json b/package.json index af50d22..9928a84 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "devDependencies": { "flow-bin": "^0.45.0", "flow-remove-types": "^1.2.0", + "mocha": "^3.4.2", "should": "^11.2.1", "should-sinon": "0.0.5", "sinon": "^2.2.0" diff --git a/src/trust_is_risk.js b/src/trust_is_risk.js index be4c8af..0babc19 100644 --- a/src/trust_is_risk.js +++ b/src/trust_is_risk.js @@ -1,92 +1,181 @@ // @flow var bcoin = require('bcoin'); +var assert = require('assert'); var Address = bcoin.primitives.Address; type Entity = string; // base58 bitcoin address type DirectTrust = { from : Entity, to: Entity, - amount: number + amount: number, + txHash: string, + outputIndex: ?number, // Not set for nullifying trust changes } type TrustChange = DirectTrust; +type TXHash = string; class TrustIsRisk { node : bcoin$FullNode - // Direct trust map - trust : { + directTrust : { [from : Entity] : ({ [to : Entity] : number }) - }; + } + + TXToTrust : { + [hash : TXHash] : DirectTrust + } constructor(node : bcoin$FullNode) { this.node = node; - this.trust = {}; + this.directTrust = {}; + this.TXToTrust = {}; this.node.on('tx', this.addTX.bind(this)); } - getDirect(from : Entity, to : Entity) : number { - if (!this.trust.hasOwnProperty(from)) return 0; - if (!this.trust[from].hasOwnProperty(to)) return 0; - return this.trust[from][to]; + getDirectTrust(from : Entity, to : Entity) : number { + if (!this.directTrust.hasOwnProperty(from)) return 0; + if (!this.directTrust[from].hasOwnProperty(to)) return 0; + return this.directTrust[from][to]; } + // Attempts to parse a bitcoin transaction as a trust change and adds it to the trust network + // if successful. + // Returns true if the transaction is a TIR transaction and was successfully added to the + // network, false otherwise. + // Throws an error if the transaction was processed earlier. addTX(tx : bcoin$TX) : boolean { - var trustChange = this.parseTXAsTrustChange(tx); - if (!trustChange) return false; + var txHash = tx.hash().toString('hex'); + if (this.TXToTrust.hasOwnProperty(txHash)) { + throw new Error('Duplicate TX: Transaction with hash ' + txHash + ' has been seen again.'); + } - if (!this.trust.hasOwnProperty(trustChange.from)) this.trust[trustChange.from] = {} - if (!this.trust[trustChange.from].hasOwnProperty(trustChange.to)) { - this.trust[trustChange.from][trustChange.to] = 0 + var trustChanges = this.getTrustChanges(tx); + if (trustChanges.length === 0) return false; + else { + trustChanges.map(this.applyTrustChange.bind(this)); + return true; + } + } + + // Returns a list of trust changes that a transaction causes, which will be one of the following: + // * An empty list (for non-TIR transactions). + // * A list containing a single trust increase (for trust-increasing transactions). + // * A list containing one or more trust decreases (for trust-decreasing transactions). + getTrustChanges(tx : bcoin$TX) : TrustChange[] { + var trustIncrease = this.parseTXAsTrustIncrease(tx); + if (trustIncrease !== null) { + return [trustIncrease]; + } else { + return this.getTrustDecreases(tx); } - this.trust[trustChange.from][trustChange.to] += trustChange.amount; - - return true; } - // Parses a transaction as a trust change, or returns null if the - // transaction is not a TIR transaction. - parseTXAsTrustChange(tx : bcoin$TX) : ?TrustChange { - var trustChange = this.parseTXAsTrustIncrease(tx); - if (trustChange === null) { - trustChange = this.parseTXAsTrustDecrease(tx); + applyTrustChange(trustChange : TrustChange) { + if (!this.directTrust.hasOwnProperty(trustChange.from)) this.directTrust[trustChange.from] = {}; + if (!this.directTrust[trustChange.from].hasOwnProperty(trustChange.to)) { + this.directTrust[trustChange.from][trustChange.to] = 0 } - return trustChange; + this.directTrust[trustChange.from][trustChange.to] += trustChange.amount; + + if (this.directTrust[trustChange.from][trustChange.to] > 0) { + this.TXToTrust[trustChange.txHash] = { + from: trustChange.from, + to: trustChange.to, + amount: this.directTrust[trustChange.from][trustChange.to], + txHash: trustChange.txHash, + outputIndex: trustChange.outputIndex + }; + } } - parseTXAsTrustIncrease(tx : bcoin$TX) : ?TrustChange { + parseTXAsTrustIncrease(tx : bcoin$TX) : (TrustChange | null) { if (tx.inputs.length !== 1) return null; - if (tx.inputs[0].getType() !== 'pubkeyhash') return null; - var sender = tx.inputs[0].getAddress(); + if (tx.inputs[0].getType() !== 'pubkeyhash') return null; // TODO: This is unreliable + if (this.TXToTrust[tx.inputs[0].prevout.hash.toString('hex')]) return null; + var sender = tx.inputs[0].getAddress().toBase58(); - if (tx.outputs.length == 0 || tx.outputs.length > 2) return null; + if (tx.outputs.length === 0 || tx.outputs.length > 2) return null; - var trustChanges = []; - for (var i = 0; i < tx.outputs.length; i++) { - if (this.isChangeOutput(tx.outputs[i], sender)) continue; + var trustOutputs = this.searchForDirectTrustOutputs(tx, sender); + if (trustOutputs.length !== 1) return null; - var trustChange = this.parseOutputAsDirectTrust(tx.outputs[i], sender.toBase58()); - if (trustChange === null) return null; - trustChanges.push(trustChange); - } - if (trustChanges.length !== 1) return null; + var changeOutputCount = tx.outputs.filter((o) => this.isChangeOutput(o, sender)).length + if (changeOutputCount + 1 !== tx.outputs.length) return null; - return trustChanges[0]; + return trustOutputs[0]; } - parseTXAsTrustDecrease(tx : bcoin$TX) : ?TrustChange { - return null; + getTrustDecreases(tx : bcoin$TX) : TrustChange[] { + var inputTrusts = this.getInputTrusts(tx.inputs); + return inputTrusts.map(this.getTrustDecrease.bind(this, tx)); + } + + getInputTrusts(inputs : bcoin$Input[]) : DirectTrust[] { + return inputs.map((input) => { + var trust = this.TXToTrust[input.prevout.hash.toString('hex')] + if (trust && trust.outputIndex === input.prevout.index) return trust; + else return null; + }).filter(Boolean); + } + + getTrustDecrease(tx : bcoin$TX, prevTrust : DirectTrust) : TrustChange { + var txHash = tx.hash().toString('hex'); + var nullifyTrust = { + from: prevTrust.from, + to: prevTrust.to, + amount: -prevTrust.amount, + txHash, + outputIndex: null + }; + + if (tx.inputs.length !== 1) return nullifyTrust; + + var trustOutputs = this.searchForDirectTrustOutputs(tx, prevTrust.from, prevTrust.to); + if (trustOutputs.length != 1) return nullifyTrust; + var nextTrust = trustOutputs[0]; + + assert(nextTrust.from === prevTrust.from); + assert(nextTrust.to === prevTrust.to); + + var trustAmountChange = nextTrust.amount - prevTrust.amount; + assert(trustAmountChange <= 0); + return { + from: nextTrust.from, + to: nextTrust.to, + amount: trustAmountChange, + txHash, + outputIndex: nextTrust.outputIndex + } + } + + // Looks for direct trust outputs that originate from a sender in an array of bitcoin outputs. + // If the recipient parameter is set, it will limit the results only to the outputs being sent to + // the recipient. + searchForDirectTrustOutputs(tx : bcoin$TX, sender : Entity, recipient : ?Entity) : DirectTrust[] { + var directTrusts = tx.outputs.map((output, outputIndex) => + this.parseOutputAsDirectTrust(tx, outputIndex, sender) + ).filter(Boolean); + + if (recipient) { + directTrusts.filter((trust) => trust.to === recipient); + } + + return directTrusts; } - isChangeOutput(output : bcoin$Output, sender : bcoin$Address) : boolean { + isChangeOutput(output : bcoin$Output, sender : Entity) : boolean { return (output.getType() === 'pubkeyhash') - && (output.getAddress().toBase58() === sender.toBase58()); + && (output.getAddress().toBase58() === sender); } - parseOutputAsDirectTrust(output : bcoin$Output, sender : Entity) : ?DirectTrust { + parseOutputAsDirectTrust(tx : bcoin$TX, outputIndex : number, sender : Entity) + : (DirectTrust | null) { + var txHash = tx.hash().toString('hex'); + var output = tx.outputs[outputIndex]; if (output.getType() !== 'multisig') return null; var addressA = Address.fromHash(bcoin.crypto.hash160(output.script.get(1))).toBase58() @@ -94,15 +183,17 @@ class TrustIsRisk { if (addressA === addressB) return null; - var receiver; - if (addressA === sender) receiver = addressB; - else if (addressB === sender) receiver = addressA; + var recipient; + if (addressA === sender) recipient = addressB; + else if (addressB === sender) recipient = addressA; else return null; return { from: sender, - to: receiver, - amount: Number(output.value) + to: recipient, + amount: Number(output.value), + txHash, + outputIndex }; } diff --git a/test/helpers.js b/test/helpers.js index 0b2c7fc..2cd68ec 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -46,6 +46,15 @@ var testHelpers = { return new Promise((resolve, reject) => { setTimeout(resolve, milliseconds); }); + }, + + P2PKHOutput: (to, value) => { + var address = bcoin.primitives.Address.fromBase58(to); + var script = bcoin.script.fromString( + `OP_DUP OP_HASH160 0x${Number(address.hash.length).toString(16)} ` + + `0x${address.hash.toString('hex')} OP_EQUALVERIFY OP_CHECKSIG`); + + return new bcoin.primitives.Output({script, value}); } } diff --git a/test/trust_is_risk.js b/test/trust_is_risk.js index a03ef5f..a959f53 100644 --- a/test/trust_is_risk.js +++ b/test/trust_is_risk.js @@ -7,135 +7,155 @@ var should = require('should'); require('should-sinon'); describe('TrustIsRisk', () => { - var sender = "17P8kCbDBPmqLDCCe9dYwbfiEDaRb5xDYE"; - var receiver = "1P6NdQWeZTLrYCpQNbYeXsLeaEjn8h6UFx"; - - var inputP2PKH = new bcoin.primitives.Input({ - prevout: { - hash: 'v0pnhphaf4r5wz63j60vnh27s1bftl260qq621y458tn0g4x64u64yqz6d7qi6i8', - index: 0xfffffffa - }, script: bcoin.script.fromString( - // 17P8kCbDBPmqLDCCe9dYwbfiEDaRb5xDYE (sender) - "0x47 0x3044022035e32834c6ee4db1696cc06762feca2809d865ca12a3b98c801f3f451341a2570220573bf3ffef55f2651e1563acc0a22f8056222f277f5ddf17dd583d4edd40fa6001 0x21 0x02b8f07a401eca4888039b1898f94db44c43ccc6d3aa8b27e9b6ed7b377b24c083") - }); - - var outputOneOfTwoMultsig = new bcoin.primitives.Output({ - script: bcoin.script.fromString( - // 1/{17P8kCbDBPmqLDCCe9dYwbfiEDaRb5xDYE (sender), 1P6NdQWeZTLrYCpQNbYeXsLeaEjn8h6UFx (receiver)} - "OP_1 0x21 0x02b8f07a401eca4888039b1898f94db44c43ccc6d3aa8b27e9b6ed7b377b24c083 0x28 0x2437025954568a8273968aa7535dbfc444fd8f8d0f5237cd96ac7234c77810ada53054a3654e669b OP_2 OP_CHECKMULTISIG"), - value: 42 - }); + var alice = "17P8kCbDBPmqLDCCe9dYwbfiEDaRb5xDYE"; + var bob = "1P6NdQWeZTLrYCpQNbYeXsLeaEjn8h6UFx"; + var charlie = "1JDfVQkZxMvRwM3Lc6LkDrpX55Ldk3JqND"; - var tir = null, mtx = null, changeScript = null; + var inputP2PKH, outputOneOfTwoMultisig, inputOneOfTwoMultisig; + var tir, trustIncreasingMTX, trustDecreasingMTX, trustIncreasingTX; beforeEach(() => { tir = new Trust.TrustIsRisk(new bcoin.fullnode({})); - mtx = new bcoin.primitives.MTX({ + inputP2PKH = new bcoin.primitives.Input({ + prevout: { + hash: 'v0pnhphaf4r5wz63j60vnh27s1bftl260qq621y458tn0g4x64u64yqz6d7qi6i8', + index: 2 + }, + script: bcoin.script.fromString( + // 17P8kCbDBPmqLDCCe9dYwbfiEDaRb5xDYE (alice) + "0x47 0x3044022035e32834c6ee4db1696cc06762feca2809d865ca12a3b98c801f3f451341a2570220573bf3ffef55f2651e1563acc0a22f8056222f277f5ddf17dd583d4edd40fa6001 0x21 0x02b8f07a401eca4888039b1898f94db44c43ccc6d3aa8b27e9b6ed7b377b24c083") + }); + + outputOneOfTwoMultisig = new bcoin.primitives.Output({ + script: bcoin.script.fromString( + // 1/{17P8kCbDBPmqLDCCe9dYwbfiEDaRb5xDYE (alice), 1P6NdQWeZTLrYCpQNbYeXsLeaEjn8h6UFx (bob)} + "OP_1 0x21 0x02b8f07a401eca4888039b1898f94db44c43ccc6d3aa8b27e9b6ed7b377b24c083 0x28 0x2437025954568a8273968aa7535dbfc444fd8f8d0f5237cd96ac7234c77810ada53054a3654e669b OP_2 OP_CHECKMULTISIG"), + value: 42 + }); + + trustIncreasingMTX = new bcoin.primitives.MTX({ inputs: [ inputP2PKH ], outputs: [ - outputOneOfTwoMultsig + outputOneOfTwoMultisig + ] + }); + + trustIncreasingTX = trustIncreasingMTX.toTX(); + inputOneOfTwoMultisig = new bcoin.primitives.Input({ + prevout: { + hash: trustIncreasingTX.hash().toString('hex'), + index: 0 + }, + script: bcoin.script.fromString( + // 17P8kCbDBPmqLDCCe9dYwbfiEDaRb5xDYE + "0x47 0x3044022035e32834c6ee4db1696cc06762feca2809d865ca12a3b98c801f3f451341a2570220573bf3ffef55f2651e1563acc0a22f8056222f277f5ddf17dd583d4edd40fa6001 0x21 0x02b8f07a401eca4888039b1898f94db44c43ccc6d3aa8b27e9b6ed7b377b24c083") + }); + + trustDecreasingMTX = new bcoin.primitives.MTX({ + inputs: [ + inputOneOfTwoMultisig + ], + outputs: [ + Object.assign(outputOneOfTwoMultisig.clone(), {value: 20}), + testHelpers.P2PKHOutput(alice, 22) ] }); - changeScript = bcoin.script.fromString( - // Pays to 17P8kCbDBPmqLDCCe9dYwbfiEDaRb5xDYE - "OP_DUP OP_HASH160 0x14 0x46005EF459C9E7C37AF8871D25BC39D0EA0534D1 OP_EQUALVERIFY OP_CHECKSIG"); }); - describe('.parseTXAsTrustIncrease', () => { - it('correctly parses trust increasing transactions', () => { - var trustChange = tir.parseTXAsTrustIncrease(mtx.toTX()); + describe('.getDirectTrust()', () => { + it('returns zero for two arbitary parties that do not trust each other', () => { + should(tir.getDirectTrust(alice, bob)).equal(0); + should(tir.getDirectTrust(bob, alice)).equal(0); + should(tir.getDirectTrust(charlie, alice)).equal(0); + should(tir.getDirectTrust(alice, charlie)).equal(0); + }); + }); - should(trustChange).deepEqual({ - from: sender, - to: receiver, - amount: 42 + describe('.addTX()', () => { + describe('with a non-TIR transaction', () => { + it('does not change trust', () => { + trustIncreasingMTX.outputs[0] = new bcoin.primitives.Output({ + script: bcoin.script.fromString( + // Pays to 1JDfVQkZxMvRwM3Lc6LkDrpX55Ldk3JqND (neither alice or bob) + "OP_DUP OP_HASH160 0x14 0xBCDF4271C6600E7D02E60F9206A9AD862FFBD4F0 OP_EQUALVERIFY OP_CHECKSIG"), + value: 10 + }); + tir.parseTXAsTrustIncrease(trustIncreasingMTX.toTX()); + + should(tir.getDirectTrust(alice, bob)).equal(0); }); }); - it('rejects transactions with more than one input', () => { - mtx.inputs.push(inputP2PKH); - var trustChange = tir.parseTXAsTrustIncrease(mtx.toTX()); + describe('with a trust increasing transaction', () => { + it('correctly increases trust', () => { + tir.addTX(trustIncreasingTX); - should(trustChange).equal(null); - }); + should(tir.getDirectTrust(alice, bob)).equal(42); + should(tir.getDirectTrust(bob, alice)).equal(0); + }); + + it('which has more than one input does not change trust', () => { + trustIncreasingMTX.inputs.push(inputP2PKH); + tir.addTX(trustIncreasingMTX.toTX()); - it('correctly parses trust increasing transactions with change outputs', () => { - mtx.outputs[0].value -= 10; - mtx.outputs.push(new bcoin.primitives.Output({ - script: changeScript, - value: 10 - })); - var trustChange = tir.parseTXAsTrustIncrease(mtx.toTX()); - - should(trustChange).deepEqual({ - from: sender, - to: receiver, - amount: 32 + should(tir.getDirectTrust(alice, bob)).equal(0); }); - }); - it('rejects transactions with two change outputs', () => { - mtx.outputs[0].value -= 10; - for (var i = 0; i < 2; i++) { - mtx.outputs.push(new bcoin.primitives.Output({ - script: changeScript, - value: 5 - })); - } - var trustChange = tir.parseTXAsTrustIncrease(mtx.toTX()); - - should(trustChange).equal(null); - }); + it('which has a change output correctly increases trust', () => { + trustIncreasingMTX.outputs[0].value -= 10; + trustIncreasingMTX.outputs.push(testHelpers.P2PKHOutput(alice, 10)); + tir.addTX(trustIncreasingMTX.toTX()); - it('rejects transactions with a second output that\'s not a change output', () => { - mtx.outputs[0].value -= 10; - mtx.outputs.push(new bcoin.primitives.Output({ - script: bcoin.script.fromString( - // Pays to 1JDfVQkZxMvRwM3Lc6LkDrpX55Ldk3JqND (neither sender or receiver) - "OP_DUP OP_HASH160 0x14 0xBCDF4271C6600E7D02E60F9206A9AD862FFBD4F0 OP_EQUALVERIFY OP_CHECKSIG"), - value: 10 - })); - var trustChange = tir.parseTXAsTrustIncrease(mtx.toTX()); - - should(trustChange).equal(null); - }); + should(tir.getDirectTrust(alice, bob)).equal(32); + }); + + it('which has two change outputs does not change trust', () => { + trustIncreasingMTX.outputs[0].value -= 10; + for (var i = 0; i < 2; i++) { + trustIncreasingMTX.outputs.push(testHelpers.P2PKHOutput(alice, 5)); + } + tir.addTX(trustIncreasingMTX.toTX()); - it('rejects transactions with no trust outputs', () => { - mtx.outputs[0] = new bcoin.primitives.Output({ - script: bcoin.script.fromString( - // Pays to 1JDfVQkZxMvRwM3Lc6LkDrpX55Ldk3JqND (neither sender or receiver) - "OP_DUP OP_HASH160 0x14 0xBCDF4271C6600E7D02E60F9206A9AD862FFBD4F0 OP_EQUALVERIFY OP_CHECKSIG"), - value: 10 + should(tir.getDirectTrust(alice, bob)).equal(0); }); - var trustChange = tir.parseTXAsTrustIncrease(mtx.toTX()); - should(trustChange).equal(null); - }); - }); + it('which has a second output that is not a change output does not change trust', () => { + trustIncreasingMTX.outputs[0].value -= 10; + trustIncreasingMTX.outputs.push(testHelpers.P2PKHOutput(charlie, 5)); + tir.addTX(trustIncreasingMTX.toTX()); + + should(tir.getDirectTrust(alice, bob)).equal(0); + }); - describe('.getDirect()', () => { - it('returns zero for two arbitary parties that do not trust each other', () => { - should(tir.getDirect(sender, receiver)).equal(0); - should(tir.getDirect(receiver, sender)).equal(0); - should(tir.getDirect("1JDfVQkZxMvRwM3Lc6LkDrpX55Ldk3JqND", sender)).equal(0); - should(tir.getDirect(sender, "1JDfVQkZxMvRwM3Lc6LkDrpX55Ldk3JqND")).equal(0); }); - }); - describe('.addTX()', () => { - it('correctly increases direct trust when adding a trust-increasing transaction', () => { - mtx.outputs[0].value -= 10; - mtx.outputs.push(new bcoin.primitives.Output({ - script: changeScript, - value: 10 - })); - tir.addTX(mtx.toTX()); - - should(tir.getDirect(sender, receiver)).equal(32); - should(tir.getDirect(receiver, sender)).equal(0); // Trust is not bi-directional + describe('with a trust decreasing transaction', () => { + beforeEach(() => { + tir.addTX(trustIncreasingTX); + }); + + it('correctly decreases trust', () => { + tir.addTX(trustDecreasingMTX.toTX()); + should(tir.getDirectTrust(alice, bob)).equal(20); + }); + + it('which has a second input decreases trust to zero', () => { + trustDecreasingMTX.inputs.push(inputP2PKH); + tir.addTX(trustDecreasingMTX.toTX()); + + should(tir.getDirectTrust(alice, bob)).equal(0); + }); + + it('which has more than one trust outputs decreases trust to zero', () => { + trustDecreasingMTX.outputs[0].value -= 15; + trustDecreasingMTX.outputs.push(Object.assign(outputOneOfTwoMultisig.clone(), {value: 5})); + tir.addTX(trustDecreasingMTX.toTX()); + + should(tir.getDirectTrust(alice, bob)).equal(0); + }); }); }); }); From 14b48e5b2591a951d1d10b2b2885cb06d020002c Mon Sep 17 00:00:00 2001 From: Christos Porios Date: Mon, 22 May 2017 23:32:53 +0000 Subject: [PATCH 05/20] Implement MaxFlow-based indirect trust inference --- .../npm/graph-theory-ford-fulkerson_vx.x.x.js | 45 +++ flow-typed/npm/should-sinon_vx.x.x.js | 39 ++ flow-typed/npm/sinon_vx.x.x.js | 375 ++++++++++++++++++ flow-typed/npm/sorted-set_vx.x.x.js | 87 ++++ lib/trust_is_risk.js | 34 +- package.json | 4 +- src/trust_is_risk.js | 34 +- test/graphs/nobodyLikesFrank.json | 24 ++ test/graphs/topcoder.json | 19 + test/helpers.js | 89 ++++- test/trust_is_risk.js | 160 +++++--- 11 files changed, 856 insertions(+), 54 deletions(-) create mode 100644 flow-typed/npm/graph-theory-ford-fulkerson_vx.x.x.js create mode 100644 flow-typed/npm/should-sinon_vx.x.x.js create mode 100644 flow-typed/npm/sinon_vx.x.x.js create mode 100644 flow-typed/npm/sorted-set_vx.x.x.js create mode 100644 test/graphs/nobodyLikesFrank.json create mode 100644 test/graphs/topcoder.json diff --git a/flow-typed/npm/graph-theory-ford-fulkerson_vx.x.x.js b/flow-typed/npm/graph-theory-ford-fulkerson_vx.x.x.js new file mode 100644 index 0000000..983c36c --- /dev/null +++ b/flow-typed/npm/graph-theory-ford-fulkerson_vx.x.x.js @@ -0,0 +1,45 @@ +// flow-typed signature: 2849579ad8dda7785593fce35e7021fd +// flow-typed version: <>/graph-theory-ford-fulkerson_v^1.0.0/flow_v0.45.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'graph-theory-ford-fulkerson' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'graph-theory-ford-fulkerson' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'graph-theory-ford-fulkerson/example/example' { + declare module.exports: any; +} + +declare module 'graph-theory-ford-fulkerson/test/index' { + declare module.exports: any; +} + +// Filename aliases +declare module 'graph-theory-ford-fulkerson/example/example.js' { + declare module.exports: $Exports<'graph-theory-ford-fulkerson/example/example'>; +} +declare module 'graph-theory-ford-fulkerson/index' { + declare module.exports: $Exports<'graph-theory-ford-fulkerson'>; +} +declare module 'graph-theory-ford-fulkerson/index.js' { + declare module.exports: $Exports<'graph-theory-ford-fulkerson'>; +} +declare module 'graph-theory-ford-fulkerson/test/index.js' { + declare module.exports: $Exports<'graph-theory-ford-fulkerson/test/index'>; +} diff --git a/flow-typed/npm/should-sinon_vx.x.x.js b/flow-typed/npm/should-sinon_vx.x.x.js new file mode 100644 index 0000000..6de6924 --- /dev/null +++ b/flow-typed/npm/should-sinon_vx.x.x.js @@ -0,0 +1,39 @@ +// flow-typed signature: 45843e3ce6bf3afc28721ddbe8e0ce2f +// flow-typed version: <>/should-sinon_v0.0.5/flow_v0.45.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'should-sinon' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'should-sinon' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'should-sinon/should-sinon' { + declare module.exports: any; +} + +declare module 'should-sinon/test' { + declare module.exports: any; +} + +// Filename aliases +declare module 'should-sinon/should-sinon.js' { + declare module.exports: $Exports<'should-sinon/should-sinon'>; +} +declare module 'should-sinon/test.js' { + declare module.exports: $Exports<'should-sinon/test'>; +} diff --git a/flow-typed/npm/sinon_vx.x.x.js b/flow-typed/npm/sinon_vx.x.x.js new file mode 100644 index 0000000..c75f639 --- /dev/null +++ b/flow-typed/npm/sinon_vx.x.x.js @@ -0,0 +1,375 @@ +// flow-typed signature: 49be0b929be2f6361f541fdb8e279527 +// flow-typed version: <>/sinon_v^2.2.0/flow_v0.45.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'sinon' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'sinon' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'sinon/lib/sinon' { + declare module.exports: any; +} + +declare module 'sinon/lib/sinon/assert' { + declare module.exports: any; +} + +declare module 'sinon/lib/sinon/behavior' { + declare module.exports: any; +} + +declare module 'sinon/lib/sinon/blob' { + declare module.exports: any; +} + +declare module 'sinon/lib/sinon/call' { + declare module.exports: any; +} + +declare module 'sinon/lib/sinon/collect-own-methods' { + declare module.exports: any; +} + +declare module 'sinon/lib/sinon/collection' { + declare module.exports: any; +} + +declare module 'sinon/lib/sinon/color' { + declare module.exports: any; +} + +declare module 'sinon/lib/sinon/default-behaviors' { + declare module.exports: any; +} + +declare module 'sinon/lib/sinon/match' { + declare module.exports: any; +} + +declare module 'sinon/lib/sinon/mock-expectation' { + declare module.exports: any; +} + +declare module 'sinon/lib/sinon/mock' { + declare module.exports: any; +} + +declare module 'sinon/lib/sinon/sandbox' { + declare module.exports: any; +} + +declare module 'sinon/lib/sinon/spy-formatters' { + declare module.exports: any; +} + +declare module 'sinon/lib/sinon/spy' { + declare module.exports: any; +} + +declare module 'sinon/lib/sinon/stub-descriptor' { + declare module.exports: any; +} + +declare module 'sinon/lib/sinon/stub-entire-object' { + declare module.exports: any; +} + +declare module 'sinon/lib/sinon/stub-non-function-property' { + declare module.exports: any; +} + +declare module 'sinon/lib/sinon/stub' { + declare module.exports: any; +} + +declare module 'sinon/lib/sinon/throw-on-falsy-object' { + declare module.exports: any; +} + +declare module 'sinon/lib/sinon/util/core/called-in-order' { + declare module.exports: any; +} + +declare module 'sinon/lib/sinon/util/core/deep-equal' { + declare module.exports: any; +} + +declare module 'sinon/lib/sinon/util/core/default-config' { + declare module.exports: any; +} + +declare module 'sinon/lib/sinon/util/core/deprecated' { + declare module.exports: any; +} + +declare module 'sinon/lib/sinon/util/core/every' { + declare module.exports: any; +} + +declare module 'sinon/lib/sinon/util/core/extend' { + declare module.exports: any; +} + +declare module 'sinon/lib/sinon/util/core/format' { + declare module.exports: any; +} + +declare module 'sinon/lib/sinon/util/core/function-name' { + declare module.exports: any; +} + +declare module 'sinon/lib/sinon/util/core/function-to-string' { + declare module.exports: any; +} + +declare module 'sinon/lib/sinon/util/core/get-config' { + declare module.exports: any; +} + +declare module 'sinon/lib/sinon/util/core/get-property-descriptor' { + declare module.exports: any; +} + +declare module 'sinon/lib/sinon/util/core/index' { + declare module.exports: any; +} + +declare module 'sinon/lib/sinon/util/core/iterable-to-string' { + declare module.exports: any; +} + +declare module 'sinon/lib/sinon/util/core/log_error' { + declare module.exports: any; +} + +declare module 'sinon/lib/sinon/util/core/order-by-first-call' { + declare module.exports: any; +} + +declare module 'sinon/lib/sinon/util/core/restore' { + declare module.exports: any; +} + +declare module 'sinon/lib/sinon/util/core/times-in-words' { + declare module.exports: any; +} + +declare module 'sinon/lib/sinon/util/core/typeOf' { + declare module.exports: any; +} + +declare module 'sinon/lib/sinon/util/core/value-to-string' { + declare module.exports: any; +} + +declare module 'sinon/lib/sinon/util/core/walk' { + declare module.exports: any; +} + +declare module 'sinon/lib/sinon/util/core/wrap-method' { + declare module.exports: any; +} + +declare module 'sinon/lib/sinon/util/event' { + declare module.exports: any; +} + +declare module 'sinon/lib/sinon/util/fake_server_with_clock' { + declare module.exports: any; +} + +declare module 'sinon/lib/sinon/util/fake_server' { + declare module.exports: any; +} + +declare module 'sinon/lib/sinon/util/fake_timers' { + declare module.exports: any; +} + +declare module 'sinon/lib/sinon/util/fake_xml_http_request' { + declare module.exports: any; +} + +declare module 'sinon/pkg/sinon-2.2.0' { + declare module.exports: any; +} + +declare module 'sinon/pkg/sinon-no-sourcemaps-2.2.0' { + declare module.exports: any; +} + +declare module 'sinon/pkg/sinon-no-sourcemaps' { + declare module.exports: any; +} + +declare module 'sinon/pkg/sinon' { + declare module.exports: any; +} + +// Filename aliases +declare module 'sinon/lib/sinon.js' { + declare module.exports: $Exports<'sinon/lib/sinon'>; +} +declare module 'sinon/lib/sinon/assert.js' { + declare module.exports: $Exports<'sinon/lib/sinon/assert'>; +} +declare module 'sinon/lib/sinon/behavior.js' { + declare module.exports: $Exports<'sinon/lib/sinon/behavior'>; +} +declare module 'sinon/lib/sinon/blob.js' { + declare module.exports: $Exports<'sinon/lib/sinon/blob'>; +} +declare module 'sinon/lib/sinon/call.js' { + declare module.exports: $Exports<'sinon/lib/sinon/call'>; +} +declare module 'sinon/lib/sinon/collect-own-methods.js' { + declare module.exports: $Exports<'sinon/lib/sinon/collect-own-methods'>; +} +declare module 'sinon/lib/sinon/collection.js' { + declare module.exports: $Exports<'sinon/lib/sinon/collection'>; +} +declare module 'sinon/lib/sinon/color.js' { + declare module.exports: $Exports<'sinon/lib/sinon/color'>; +} +declare module 'sinon/lib/sinon/default-behaviors.js' { + declare module.exports: $Exports<'sinon/lib/sinon/default-behaviors'>; +} +declare module 'sinon/lib/sinon/match.js' { + declare module.exports: $Exports<'sinon/lib/sinon/match'>; +} +declare module 'sinon/lib/sinon/mock-expectation.js' { + declare module.exports: $Exports<'sinon/lib/sinon/mock-expectation'>; +} +declare module 'sinon/lib/sinon/mock.js' { + declare module.exports: $Exports<'sinon/lib/sinon/mock'>; +} +declare module 'sinon/lib/sinon/sandbox.js' { + declare module.exports: $Exports<'sinon/lib/sinon/sandbox'>; +} +declare module 'sinon/lib/sinon/spy-formatters.js' { + declare module.exports: $Exports<'sinon/lib/sinon/spy-formatters'>; +} +declare module 'sinon/lib/sinon/spy.js' { + declare module.exports: $Exports<'sinon/lib/sinon/spy'>; +} +declare module 'sinon/lib/sinon/stub-descriptor.js' { + declare module.exports: $Exports<'sinon/lib/sinon/stub-descriptor'>; +} +declare module 'sinon/lib/sinon/stub-entire-object.js' { + declare module.exports: $Exports<'sinon/lib/sinon/stub-entire-object'>; +} +declare module 'sinon/lib/sinon/stub-non-function-property.js' { + declare module.exports: $Exports<'sinon/lib/sinon/stub-non-function-property'>; +} +declare module 'sinon/lib/sinon/stub.js' { + declare module.exports: $Exports<'sinon/lib/sinon/stub'>; +} +declare module 'sinon/lib/sinon/throw-on-falsy-object.js' { + declare module.exports: $Exports<'sinon/lib/sinon/throw-on-falsy-object'>; +} +declare module 'sinon/lib/sinon/util/core/called-in-order.js' { + declare module.exports: $Exports<'sinon/lib/sinon/util/core/called-in-order'>; +} +declare module 'sinon/lib/sinon/util/core/deep-equal.js' { + declare module.exports: $Exports<'sinon/lib/sinon/util/core/deep-equal'>; +} +declare module 'sinon/lib/sinon/util/core/default-config.js' { + declare module.exports: $Exports<'sinon/lib/sinon/util/core/default-config'>; +} +declare module 'sinon/lib/sinon/util/core/deprecated.js' { + declare module.exports: $Exports<'sinon/lib/sinon/util/core/deprecated'>; +} +declare module 'sinon/lib/sinon/util/core/every.js' { + declare module.exports: $Exports<'sinon/lib/sinon/util/core/every'>; +} +declare module 'sinon/lib/sinon/util/core/extend.js' { + declare module.exports: $Exports<'sinon/lib/sinon/util/core/extend'>; +} +declare module 'sinon/lib/sinon/util/core/format.js' { + declare module.exports: $Exports<'sinon/lib/sinon/util/core/format'>; +} +declare module 'sinon/lib/sinon/util/core/function-name.js' { + declare module.exports: $Exports<'sinon/lib/sinon/util/core/function-name'>; +} +declare module 'sinon/lib/sinon/util/core/function-to-string.js' { + declare module.exports: $Exports<'sinon/lib/sinon/util/core/function-to-string'>; +} +declare module 'sinon/lib/sinon/util/core/get-config.js' { + declare module.exports: $Exports<'sinon/lib/sinon/util/core/get-config'>; +} +declare module 'sinon/lib/sinon/util/core/get-property-descriptor.js' { + declare module.exports: $Exports<'sinon/lib/sinon/util/core/get-property-descriptor'>; +} +declare module 'sinon/lib/sinon/util/core/index.js' { + declare module.exports: $Exports<'sinon/lib/sinon/util/core/index'>; +} +declare module 'sinon/lib/sinon/util/core/iterable-to-string.js' { + declare module.exports: $Exports<'sinon/lib/sinon/util/core/iterable-to-string'>; +} +declare module 'sinon/lib/sinon/util/core/log_error.js' { + declare module.exports: $Exports<'sinon/lib/sinon/util/core/log_error'>; +} +declare module 'sinon/lib/sinon/util/core/order-by-first-call.js' { + declare module.exports: $Exports<'sinon/lib/sinon/util/core/order-by-first-call'>; +} +declare module 'sinon/lib/sinon/util/core/restore.js' { + declare module.exports: $Exports<'sinon/lib/sinon/util/core/restore'>; +} +declare module 'sinon/lib/sinon/util/core/times-in-words.js' { + declare module.exports: $Exports<'sinon/lib/sinon/util/core/times-in-words'>; +} +declare module 'sinon/lib/sinon/util/core/typeOf.js' { + declare module.exports: $Exports<'sinon/lib/sinon/util/core/typeOf'>; +} +declare module 'sinon/lib/sinon/util/core/value-to-string.js' { + declare module.exports: $Exports<'sinon/lib/sinon/util/core/value-to-string'>; +} +declare module 'sinon/lib/sinon/util/core/walk.js' { + declare module.exports: $Exports<'sinon/lib/sinon/util/core/walk'>; +} +declare module 'sinon/lib/sinon/util/core/wrap-method.js' { + declare module.exports: $Exports<'sinon/lib/sinon/util/core/wrap-method'>; +} +declare module 'sinon/lib/sinon/util/event.js' { + declare module.exports: $Exports<'sinon/lib/sinon/util/event'>; +} +declare module 'sinon/lib/sinon/util/fake_server_with_clock.js' { + declare module.exports: $Exports<'sinon/lib/sinon/util/fake_server_with_clock'>; +} +declare module 'sinon/lib/sinon/util/fake_server.js' { + declare module.exports: $Exports<'sinon/lib/sinon/util/fake_server'>; +} +declare module 'sinon/lib/sinon/util/fake_timers.js' { + declare module.exports: $Exports<'sinon/lib/sinon/util/fake_timers'>; +} +declare module 'sinon/lib/sinon/util/fake_xml_http_request.js' { + declare module.exports: $Exports<'sinon/lib/sinon/util/fake_xml_http_request'>; +} +declare module 'sinon/pkg/sinon-2.2.0.js' { + declare module.exports: $Exports<'sinon/pkg/sinon-2.2.0'>; +} +declare module 'sinon/pkg/sinon-no-sourcemaps-2.2.0.js' { + declare module.exports: $Exports<'sinon/pkg/sinon-no-sourcemaps-2.2.0'>; +} +declare module 'sinon/pkg/sinon-no-sourcemaps.js' { + declare module.exports: $Exports<'sinon/pkg/sinon-no-sourcemaps'>; +} +declare module 'sinon/pkg/sinon.js' { + declare module.exports: $Exports<'sinon/pkg/sinon'>; +} diff --git a/flow-typed/npm/sorted-set_vx.x.x.js b/flow-typed/npm/sorted-set_vx.x.x.js new file mode 100644 index 0000000..be9ce1e --- /dev/null +++ b/flow-typed/npm/sorted-set_vx.x.x.js @@ -0,0 +1,87 @@ +// flow-typed signature: 49b561be32534814522dc2da04fa8dea +// flow-typed version: <>/sorted-set_v1.x.x/flow_v0.45.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'sorted-set' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'sorted-set' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'sorted-set/examples/distance' { + declare module.exports: any; +} + +declare module 'sorted-set/examples/kademlia' { + declare module.exports: any; +} + +declare module 'sorted-set/examples/ttl' { + declare module.exports: any; +} + +declare module 'sorted-set/lib/intersect-sorted' { + declare module.exports: any; +} + +declare module 'sorted-set/lib/intersect-unique' { + declare module.exports: any; +} + +declare module 'sorted-set/lib/set' { + declare module.exports: any; +} + +declare module 'sorted-set/test/index' { + declare module.exports: any; +} + +declare module 'sorted-set/test/set' { + declare module.exports: any; +} + +// Filename aliases +declare module 'sorted-set/examples/distance.js' { + declare module.exports: $Exports<'sorted-set/examples/distance'>; +} +declare module 'sorted-set/examples/kademlia.js' { + declare module.exports: $Exports<'sorted-set/examples/kademlia'>; +} +declare module 'sorted-set/examples/ttl.js' { + declare module.exports: $Exports<'sorted-set/examples/ttl'>; +} +declare module 'sorted-set/index' { + declare module.exports: $Exports<'sorted-set'>; +} +declare module 'sorted-set/index.js' { + declare module.exports: $Exports<'sorted-set'>; +} +declare module 'sorted-set/lib/intersect-sorted.js' { + declare module.exports: $Exports<'sorted-set/lib/intersect-sorted'>; +} +declare module 'sorted-set/lib/intersect-unique.js' { + declare module.exports: $Exports<'sorted-set/lib/intersect-unique'>; +} +declare module 'sorted-set/lib/set.js' { + declare module.exports: $Exports<'sorted-set/lib/set'>; +} +declare module 'sorted-set/test/index.js' { + declare module.exports: $Exports<'sorted-set/test/index'>; +} +declare module 'sorted-set/test/set.js' { + declare module.exports: $Exports<'sorted-set/test/set'>; +} diff --git a/lib/trust_is_risk.js b/lib/trust_is_risk.js index 60a5ac2..2d810c7 100644 --- a/lib/trust_is_risk.js +++ b/lib/trust_is_risk.js @@ -2,6 +2,9 @@ var bcoin = require('bcoin'); var assert = require('assert'); var Address = bcoin.primitives.Address; +var SortedSet = require('sorted-set'); +var maxFlow = require('graph-theory-ford-fulkerson'); + // base58 bitcoin address @@ -27,15 +30,31 @@ class TrustIsRisk { + + constructor(node ) { this.node = node; this.directTrust = {}; this.TXToTrust = {}; + this.entities = new SortedSet(); this.node.on('tx', this.addTX.bind(this)); } + getTrust(from , to ) { + if (from === to) return Infinity; + + // TODO: Optimize + var graph = this.getGraphWeightMatrix(); + var fromIndex = this.getGraphWeightMatrixIndex(from); + var toIndex = this.getGraphWeightMatrixIndex(to); + + if (fromIndex === -1 || toIndex === -1) return 0; + else return maxFlow(graph, fromIndex, toIndex); + } + getDirectTrust(from , to ) { + if (from === to) return Infinity; if (!this.directTrust.hasOwnProperty(from)) return 0; if (!this.directTrust[from].hasOwnProperty(to)) return 0; return this.directTrust[from][to]; @@ -49,7 +68,7 @@ class TrustIsRisk { addTX(tx ) { var txHash = tx.hash().toString('hex'); if (this.TXToTrust.hasOwnProperty(txHash)) { - throw new Error('Duplicate TX: Transaction with hash ' + txHash + ' has been seen again.'); + throw new Error('Duplicate TX: Transaction with hash ' + txHash + ' has been seen before.'); } var trustChanges = this.getTrustChanges(tx); @@ -90,6 +109,9 @@ class TrustIsRisk { outputIndex: trustChange.outputIndex }; } + + this.entities.add(trustChange.from); + this.entities.add(trustChange.to); } parseTXAsTrustIncrease(tx ) { @@ -197,6 +219,16 @@ class TrustIsRisk { }; } + getGraphWeightMatrix() { + var entitiesArr = this.entities.slice(0, this.entities.length); + return entitiesArr.map((from) => { + return entitiesArr.map((to) => this.getDirectTrust(from, to)); + }); + } + + getGraphWeightMatrixIndex(entity ) { + return this.entities.rank(entity); + } } module.exports = TrustIsRisk; diff --git a/package.json b/package.json index 9928a84..e5bc1ef 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,8 @@ "sinon": "^2.2.0" }, "dependencies": { - "bcoin": "^1.0.0-beta.12" + "bcoin": "^1.0.0-beta.12", + "graph-theory-ford-fulkerson": "^1.0.0", + "sorted-set": "^0.3.0" } } diff --git a/src/trust_is_risk.js b/src/trust_is_risk.js index 0babc19..c74fde1 100644 --- a/src/trust_is_risk.js +++ b/src/trust_is_risk.js @@ -2,6 +2,9 @@ var bcoin = require('bcoin'); var assert = require('assert'); var Address = bcoin.primitives.Address; +var SortedSet = require('sorted-set'); +var maxFlow = require('graph-theory-ford-fulkerson'); + type Entity = string; // base58 bitcoin address type DirectTrust = { @@ -27,15 +30,31 @@ class TrustIsRisk { [hash : TXHash] : DirectTrust } + entities : SortedSet + constructor(node : bcoin$FullNode) { this.node = node; this.directTrust = {}; this.TXToTrust = {}; + this.entities = new SortedSet(); this.node.on('tx', this.addTX.bind(this)); } + getTrust(from : Entity, to : Entity) : number { + if (from === to) return Infinity; + + // TODO: Optimize + var graph = this.getGraphWeightMatrix(); + var fromIndex = this.getGraphWeightMatrixIndex(from); + var toIndex = this.getGraphWeightMatrixIndex(to); + + if (fromIndex === -1 || toIndex === -1) return 0; + else return maxFlow(graph, fromIndex, toIndex); + } + getDirectTrust(from : Entity, to : Entity) : number { + if (from === to) return Infinity; if (!this.directTrust.hasOwnProperty(from)) return 0; if (!this.directTrust[from].hasOwnProperty(to)) return 0; return this.directTrust[from][to]; @@ -49,7 +68,7 @@ class TrustIsRisk { addTX(tx : bcoin$TX) : boolean { var txHash = tx.hash().toString('hex'); if (this.TXToTrust.hasOwnProperty(txHash)) { - throw new Error('Duplicate TX: Transaction with hash ' + txHash + ' has been seen again.'); + throw new Error('Duplicate TX: Transaction with hash ' + txHash + ' has been seen before.'); } var trustChanges = this.getTrustChanges(tx); @@ -90,6 +109,9 @@ class TrustIsRisk { outputIndex: trustChange.outputIndex }; } + + this.entities.add(trustChange.from); + this.entities.add(trustChange.to); } parseTXAsTrustIncrease(tx : bcoin$TX) : (TrustChange | null) { @@ -197,6 +219,16 @@ class TrustIsRisk { }; } + getGraphWeightMatrix() : number[][] { + var entitiesArr = this.entities.slice(0, this.entities.length); + return entitiesArr.map((from) => { + return entitiesArr.map((to) => this.getDirectTrust(from, to)); + }); + } + + getGraphWeightMatrixIndex(entity : Entity) : number { + return this.entities.rank(entity); + } } module.exports = TrustIsRisk; diff --git a/test/graphs/nobodyLikesFrank.json b/test/graphs/nobodyLikesFrank.json new file mode 100644 index 0000000..09865cc --- /dev/null +++ b/test/graphs/nobodyLikesFrank.json @@ -0,0 +1,24 @@ +{ + "alice": { + "bob": 10, + "dave": 3 + }, + "bob": { + "charlie": 1, + "eve": 2, + "dave": 1, + "george": 1 + }, + "charlie": { + "george": 3 + }, + "dave": { + "eve": 10, + "alice": 2 + }, + "eve": {}, + "frank": { + "charlie": 100 + }, + "george": {} +} diff --git a/test/graphs/topcoder.json b/test/graphs/topcoder.json new file mode 100644 index 0000000..f051965 --- /dev/null +++ b/test/graphs/topcoder.json @@ -0,0 +1,19 @@ +{ + "alice": { + "bob": 3, + "dave": 1 + }, + "bob": { + "charlie": 3 + }, + "charlie": { + "frank": 2 + }, + "dave": { + "charlie": 5, + "eve": 4 + }, + "eve": { + "frank": 2 + } +} diff --git a/test/helpers.js b/test/helpers.js index 2cd68ec..40df046 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -1,8 +1,33 @@ var TrustIsRisk = require('../'); var WalletDB = require('bcoin/lib/wallet/walletdb'); var bcoin = require('bcoin'); +var assert = require('assert'); var testHelpers = { + getAddressFixtures: () => { + var names = ['alice', 'bob', 'charlie', 'dave', 'eve', 'frank', 'george']; + var pubkeys = [ + '02b8f07a401eca4888039b1898f94db44c43ccc6d3aa8b27e9b6ed7b377b24c083', + '2437025954568a8273968aa7535dbfc444fd8f8d0f5237cd96ac7234c77810ada53054a3654e669b', + '3BBA2AF9539D09B4FD2BDEA1D3A2CE4BF5D779831B8781EE2ACF9C03378B2AD782', + '19BD8D853FAEFDB9B01E4DE7F6096FF8F5F96D43E6564A5258307334A4AA59F351', + '0503054CF7EBB4E62191AF1D8DE97945178D3F465EE88EF1FB4E80A70CB4A49A84', + '878DFE5B43AC858EA37B3A9EEBA9E244F1848A30F78B2E5AC5B3EBDE81AC7D4516', + '1349A1318B1426E6F724CBFE7ECD2C46008A364A96C4BD20C83FC1C4EBB2EB4A93' + ]; + assert(pubkeys.length === names.length); + + var addr = {}; + for (var i = 0; i < names.length; i++) { + var name = names[i]; + addr[name] = {}; + addr[name].pubkey = Buffer.from(pubkeys[i], 'hex'); + addr[name].base58 = bcoin.primitives.Address.fromHash(bcoin.crypto.hash160(addr[name].pubkey)).toString(); + } + + return addr; + }, + getNode: async () => { var node = new TrustIsRisk.FullNode({network: 'regtest', passphrase: 'secret'}); @@ -48,14 +73,70 @@ var testHelpers = { }); }, - P2PKHOutput: (to, value) => { + bufferToScript: (data) => { + return `0x${Number(data.length).toString(16)} 0x${data.toString('hex')}`; + }, + + getP2PKHOutput: (to, value) => { var address = bcoin.primitives.Address.fromBase58(to); var script = bcoin.script.fromString( - `OP_DUP OP_HASH160 0x${Number(address.hash.length).toString(16)} ` - + `0x${address.hash.toString('hex')} OP_EQUALVERIFY OP_CHECKSIG`); + `OP_DUP OP_HASH160 ${testHelpers.bufferToScript(address.hash)} OP_EQUALVERIFY OP_CHECKSIG`); return new bcoin.primitives.Output({script, value}); + }, + + getP2PKHInput: (pubKey, prevout) => { + if (!prevout) { + prevout = { // Don't care + hash: 'v0pnhphaf4r5wz63j60vnh27s1bftl260qq621y458tn0g4x64u64yqz6d7qi6i8', + index: 2 + }; + } + + return new bcoin.primitives.Input({ + prevout, + script: bcoin.script.fromString( + // Don't care about the signature + "0x47 0x3044022035e32834c6ee4db1696cc06762feca2809d865ca12a3b98c801f3f451341a2570220573bf3ffef55f2651e1563acc0a22f8056222f277f5ddf17dd583d4edd40fa6001 " + + testHelpers.bufferToScript(pubKey)) + }); + }, + + getOneOfTwoMultisigOutput: (pubKeyFrom, pubKeyTo, value) => { + return new bcoin.primitives.Output({ + value, + script: bcoin.script.fromString( + "OP_1 " + + testHelpers.bufferToScript(pubKeyFrom) + " " + + testHelpers.bufferToScript(pubKeyTo) + " " + + "OP_2 OP_CHECKMULTISIG") + }); + }, + + getTrustIncreasingMTX: (pubKeyFrom, pubKeyTo, value) => { + return new bcoin.primitives.MTX({ + inputs: [ + testHelpers.getP2PKHInput(pubKeyFrom) + ], + outputs: [ + testHelpers.getOneOfTwoMultisigOutput(pubKeyFrom, pubKeyTo, value) + ] + }); + }, + + applyGraph: (tir, fileName, addr) => { + var graph = require(fileName); + + for (var from in graph) { + var neighbours = graph[from]; + for (var to in neighbours) { + var value = neighbours[to]; + tir.addTX(testHelpers.getTrustIncreasingMTX(addr[from].pubkey, addr[to].pubkey, value).toTX()); + } + } } -} +}; + + module.exports = testHelpers; diff --git a/test/trust_is_risk.js b/test/trust_is_risk.js index a959f53..cc7cf3b 100644 --- a/test/trust_is_risk.js +++ b/test/trust_is_risk.js @@ -7,43 +7,20 @@ var should = require('should'); require('should-sinon'); describe('TrustIsRisk', () => { - var alice = "17P8kCbDBPmqLDCCe9dYwbfiEDaRb5xDYE"; - var bob = "1P6NdQWeZTLrYCpQNbYeXsLeaEjn8h6UFx"; - var charlie = "1JDfVQkZxMvRwM3Lc6LkDrpX55Ldk3JqND"; + var addr = testHelpers.getAddressFixtures(); + // Add base58 address variables to scope. + for (name in addr) { + eval(`var ${name} = "${addr[name].base58}";`); + } - var inputP2PKH, outputOneOfTwoMultisig, inputOneOfTwoMultisig; var tir, trustIncreasingMTX, trustDecreasingMTX, trustIncreasingTX; beforeEach(() => { tir = new Trust.TrustIsRisk(new bcoin.fullnode({})); - inputP2PKH = new bcoin.primitives.Input({ - prevout: { - hash: 'v0pnhphaf4r5wz63j60vnh27s1bftl260qq621y458tn0g4x64u64yqz6d7qi6i8', - index: 2 - }, - script: bcoin.script.fromString( - // 17P8kCbDBPmqLDCCe9dYwbfiEDaRb5xDYE (alice) - "0x47 0x3044022035e32834c6ee4db1696cc06762feca2809d865ca12a3b98c801f3f451341a2570220573bf3ffef55f2651e1563acc0a22f8056222f277f5ddf17dd583d4edd40fa6001 0x21 0x02b8f07a401eca4888039b1898f94db44c43ccc6d3aa8b27e9b6ed7b377b24c083") - }); - - outputOneOfTwoMultisig = new bcoin.primitives.Output({ - script: bcoin.script.fromString( - // 1/{17P8kCbDBPmqLDCCe9dYwbfiEDaRb5xDYE (alice), 1P6NdQWeZTLrYCpQNbYeXsLeaEjn8h6UFx (bob)} - "OP_1 0x21 0x02b8f07a401eca4888039b1898f94db44c43ccc6d3aa8b27e9b6ed7b377b24c083 0x28 0x2437025954568a8273968aa7535dbfc444fd8f8d0f5237cd96ac7234c77810ada53054a3654e669b OP_2 OP_CHECKMULTISIG"), - value: 42 - }); - - trustIncreasingMTX = new bcoin.primitives.MTX({ - inputs: [ - inputP2PKH - ], - outputs: [ - outputOneOfTwoMultisig - ] - }); - + trustIncreasingMTX = testHelpers.getTrustIncreasingMTX(addr.alice.pubkey, addr.bob.pubkey, 42); trustIncreasingTX = trustIncreasingMTX.toTX(); - inputOneOfTwoMultisig = new bcoin.primitives.Input({ + + var inputOneOfTwoMultisig = new bcoin.primitives.Input({ prevout: { hash: trustIncreasingTX.hash().toString('hex'), index: 0 @@ -58,31 +35,31 @@ describe('TrustIsRisk', () => { inputOneOfTwoMultisig ], outputs: [ - Object.assign(outputOneOfTwoMultisig.clone(), {value: 20}), - testHelpers.P2PKHOutput(alice, 22) + testHelpers.getOneOfTwoMultisigOutput(addr.alice.pubkey, addr.bob.pubkey, 20), + testHelpers.getP2PKHOutput(addr.alice.base58, 22) ] }); - }); describe('.getDirectTrust()', () => { it('returns zero for two arbitary parties that do not trust each other', () => { - should(tir.getDirectTrust(alice, bob)).equal(0); + should(tir.getDirectTrust(addr.alice.base58, bob)).equal(0); should(tir.getDirectTrust(bob, alice)).equal(0); should(tir.getDirectTrust(charlie, alice)).equal(0); should(tir.getDirectTrust(alice, charlie)).equal(0); + should(tir.getDirectTrust(charlie, frank)).equal(0); + }); + + it('returns Infinity for one\'s direct trust to themselves', () => { + should(tir.getDirectTrust(alice, alice)).equal(Infinity); + should(tir.getDirectTrust(bob, bob)).equal(Infinity); }); }); describe('.addTX()', () => { describe('with a non-TIR transaction', () => { it('does not change trust', () => { - trustIncreasingMTX.outputs[0] = new bcoin.primitives.Output({ - script: bcoin.script.fromString( - // Pays to 1JDfVQkZxMvRwM3Lc6LkDrpX55Ldk3JqND (neither alice or bob) - "OP_DUP OP_HASH160 0x14 0xBCDF4271C6600E7D02E60F9206A9AD862FFBD4F0 OP_EQUALVERIFY OP_CHECKSIG"), - value: 10 - }); + trustIncreasingMTX.outputs[0] = testHelpers.getP2PKHOutput(charlie, 50); tir.parseTXAsTrustIncrease(trustIncreasingMTX.toTX()); should(tir.getDirectTrust(alice, bob)).equal(0); @@ -98,7 +75,7 @@ describe('TrustIsRisk', () => { }); it('which has more than one input does not change trust', () => { - trustIncreasingMTX.inputs.push(inputP2PKH); + trustIncreasingMTX.inputs.push(trustIncreasingMTX.inputs[0].clone()); tir.addTX(trustIncreasingMTX.toTX()); should(tir.getDirectTrust(alice, bob)).equal(0); @@ -106,7 +83,7 @@ describe('TrustIsRisk', () => { it('which has a change output correctly increases trust', () => { trustIncreasingMTX.outputs[0].value -= 10; - trustIncreasingMTX.outputs.push(testHelpers.P2PKHOutput(alice, 10)); + trustIncreasingMTX.outputs.push(testHelpers.getP2PKHOutput(alice, 10)); tir.addTX(trustIncreasingMTX.toTX()); should(tir.getDirectTrust(alice, bob)).equal(32); @@ -115,7 +92,7 @@ describe('TrustIsRisk', () => { it('which has two change outputs does not change trust', () => { trustIncreasingMTX.outputs[0].value -= 10; for (var i = 0; i < 2; i++) { - trustIncreasingMTX.outputs.push(testHelpers.P2PKHOutput(alice, 5)); + trustIncreasingMTX.outputs.push(testHelpers.getP2PKHOutput(alice, 5)); } tir.addTX(trustIncreasingMTX.toTX()); @@ -124,7 +101,7 @@ describe('TrustIsRisk', () => { it('which has a second output that is not a change output does not change trust', () => { trustIncreasingMTX.outputs[0].value -= 10; - trustIncreasingMTX.outputs.push(testHelpers.P2PKHOutput(charlie, 5)); + trustIncreasingMTX.outputs.push(testHelpers.getP2PKHOutput(charlie, 5)); tir.addTX(trustIncreasingMTX.toTX()); should(tir.getDirectTrust(alice, bob)).equal(0); @@ -143,7 +120,7 @@ describe('TrustIsRisk', () => { }); it('which has a second input decreases trust to zero', () => { - trustDecreasingMTX.inputs.push(inputP2PKH); + trustDecreasingMTX.inputs.push(testHelpers.getP2PKHInput(addr.alice.pubkey)); tir.addTX(trustDecreasingMTX.toTX()); should(tir.getDirectTrust(alice, bob)).equal(0); @@ -151,11 +128,100 @@ describe('TrustIsRisk', () => { it('which has more than one trust outputs decreases trust to zero', () => { trustDecreasingMTX.outputs[0].value -= 15; - trustDecreasingMTX.outputs.push(Object.assign(outputOneOfTwoMultisig.clone(), {value: 5})); + trustDecreasingMTX.outputs.push( + testHelpers.getOneOfTwoMultisigOutput(addr.alice.pubkey, addr.bob.pubkey, 5)); tir.addTX(trustDecreasingMTX.toTX()); should(tir.getDirectTrust(alice, bob)).equal(0); }); }); + + describe('.getTrust()', () => { + it('returns zero for two arbitary parties that do not trust each other', () => { + should(tir.getTrust(alice, bob)).equal(0); + should(tir.getTrust(bob, alice)).equal(0); + should(tir.getTrust(charlie, alice)).equal(0); + should(tir.getTrust(alice, charlie)).equal(0); + }); + + it('returns Infinity for one\'s trust to themselves', () => { + should(tir.getTrust(alice, alice)).equal(Infinity); + should(tir.getTrust(bob, bob)).equal(Infinity); + }); + + describe('after applying the Nobody Likes Frank graph example', () => { + beforeEach(() => { + testHelpers.applyGraph(tir, './graphs/nobodyLikesFrank.json', addr); + }); + + it('correctly computes trusts', () => { + should(tir.getTrust(alice, alice)).equal(Infinity); + should(tir.getTrust(alice, bob)).equal(10); + should(tir.getTrust(alice, charlie)).equal(1); + should(tir.getTrust(alice, dave)).equal(4); + should(tir.getTrust(alice, eve)).equal(6); + should(tir.getTrust(alice, frank)).equal(0); + should(tir.getTrust(alice, george)).equal(2); + + should(tir.getTrust(bob, alice)).equal(1); + should(tir.getTrust(bob, bob)).equal(Infinity); + should(tir.getTrust(bob, charlie)).equal(1); + should(tir.getTrust(bob, dave)).equal(1); + should(tir.getTrust(bob, eve)).equal(3); + should(tir.getTrust(bob, frank)).equal(0); + should(tir.getTrust(bob, george)).equal(2); + + should(tir.getTrust(charlie, alice)).equal(0); + should(tir.getTrust(charlie, bob)).equal(0); + should(tir.getTrust(charlie, charlie)).equal(Infinity); + should(tir.getTrust(charlie, dave)).equal(0); + should(tir.getTrust(charlie, eve)).equal(0); + should(tir.getTrust(charlie, frank)).equal(0); + should(tir.getTrust(charlie, george)).equal(3); + + should(tir.getTrust(dave, alice)).equal(2); + should(tir.getTrust(dave, bob)).equal(2); + should(tir.getTrust(dave, charlie)).equal(1); + should(tir.getTrust(dave, dave)).equal(Infinity); + should(tir.getTrust(dave, eve)).equal(12); + should(tir.getTrust(dave, frank)).equal(0); + should(tir.getTrust(dave, george)).equal(2); + + should(tir.getTrust(eve, alice)).equal(0); + should(tir.getTrust(eve, bob)).equal(0); + should(tir.getTrust(eve, charlie)).equal(0); + should(tir.getTrust(eve, dave)).equal(0); + should(tir.getTrust(eve, eve)).equal(Infinity); + should(tir.getTrust(eve, frank)).equal(0); + should(tir.getTrust(eve, george)).equal(0); + + should(tir.getTrust(frank, alice)).equal(0); + should(tir.getTrust(frank, bob)).equal(0); + should(tir.getTrust(frank, charlie)).equal(100); + should(tir.getTrust(frank, dave)).equal(0); + should(tir.getTrust(frank, eve)).equal(0); + should(tir.getTrust(frank, frank)).equal(Infinity); + should(tir.getTrust(frank, george)).equal(3); + + should(tir.getTrust(george, alice)).equal(0); + should(tir.getTrust(george, bob)).equal(0); + should(tir.getTrust(george, charlie)).equal(0); + should(tir.getTrust(george, dave)).equal(0); + should(tir.getTrust(george, eve)).equal(0); + should(tir.getTrust(george, frank)).equal(0); + should(tir.getTrust(george, george)).equal(Infinity); + }); + + it('correctly computes trusts when bob trusts frank', () => { + tir.addTX(testHelpers.getTrustIncreasingMTX(addr.bob.pubkey, addr.frank.pubkey, 8).toTX()); + should(tir.getTrust(george, frank)).equal(0); + should(tir.getTrust(alice, frank)).equal(8); + should(tir.getTrust(dave, frank)).equal(2); + should(tir.getTrust(bob, frank)).equal(8); + }); + + // TODO: Decrement direct trusts and test that indirect trusts update correctly + }); + }); }); }); From e7e4f8460489bdbd5241bbfdc7db44f663e5a321 Mon Sep 17 00:00:00 2001 From: Christos Porios Date: Fri, 26 May 2017 00:02:51 +0000 Subject: [PATCH 06/20] Implement .getTrustIncreasingMTX() --- flow-typed/npm/bcoin_vx.x.x.js | 34 +++++++++++++++++++++-- lib/trust_is_risk.js | 37 ++++++++++++++++++++++-- src/trust_is_risk.js | 37 ++++++++++++++++++++++-- test/trust_is_risk.js | 51 ++++++++++++++++++++++++++++++++-- 4 files changed, 150 insertions(+), 9 deletions(-) diff --git a/flow-typed/npm/bcoin_vx.x.x.js b/flow-typed/npm/bcoin_vx.x.x.js index d07ac45..504682f 100644 --- a/flow-typed/npm/bcoin_vx.x.x.js +++ b/flow-typed/npm/bcoin_vx.x.x.js @@ -1,12 +1,16 @@ // This is a work-in-progress attempt to type the bcoin library. +type Hash = (string | Buffer); +type Network = any; + declare class bcoin$FullNode { on(eventName : string, eventHandler : Function) : void; + getTX(hash : Hash) : Promise; } declare class bcoin$Address { toBase58() : string; - static fromHash(string|Buffer) : bcoin$Address; + static fromHash(Hash) : bcoin$Address; } declare class bcoin$TX { @@ -16,6 +20,17 @@ declare class bcoin$TX { hash(enc : ?'hex') : Buffer; } + +declare class bcoin$MTX { + inputs : bcoin$Input[]; + outputs : bcoin$Output[]; + + toTX : bcoin$TX; + template(ring : bcoin$KeyRing) : number; + scriptVector(outputScript : bcoin$Script, inputScript : bcoin$Script, ring : bcoin$KeyRing) : boolean; +} + + declare class bcoin$Output { script : bcoin$Script; value : number; @@ -25,13 +40,19 @@ declare class bcoin$Output { } declare class bcoin$Input { + static fromOutpoint(outpoint : bcoin$Outpoint) : bcoin$Input; + script : bcoin$Script; prevout : bcoin$Outpoint; + getType() : ('pubkeyhash' | 'multisig'); getAddress() : bcoin$Address; } declare class bcoin$Script { + static fromMultisig(m : number, n : number, keys : Buffer[]) : bcoin$Script; + static fromPubkeyhash(hash : Hash) : bcoin$Script; + get(n : number) : (Buffer); } @@ -40,6 +61,11 @@ declare class bcoin$Outpoint { index : number; } +declare class bcoin$KeyRing { + static fromPrivate(key : Buffer, compressed : ?boolean, network : ?Network) : bcoin$KeyRing; + static fromPublic(key : Buffer, network : ?Network) : bcoin$KeyRing; +} + declare module 'bcoin' { declare module.exports: { fullnode : Class, @@ -47,12 +73,14 @@ declare module 'bcoin' { primitives : { Address : Class, TX : Class, + MTX : Class, Output : Class, Input : Class, - Outpoint : Class + Outpoint : Class, + KeyRing: Class }, crypto : { - hash160(str : (string | Buffer)) : (string | Buffer) + hash160(str : (string | Buffer)) : Hash } } } diff --git a/lib/trust_is_risk.js b/lib/trust_is_risk.js index 2d810c7..2b2a5b6 100644 --- a/lib/trust_is_risk.js +++ b/lib/trust_is_risk.js @@ -1,7 +1,12 @@ // var bcoin = require('bcoin'); -var assert = require('assert'); var Address = bcoin.primitives.Address; +var KeyRing = bcoin.primitives.KeyRing; +var MTX = bcoin.primitives.MTX; +var Input = bcoin.primitives.Input; +var Output = bcoin.primitives.Output; +var Outpoint = bcoin.primitives.Outpoint; +var assert = require('assert'); var SortedSet = require('sorted-set'); var maxFlow = require('graph-theory-ford-fulkerson'); @@ -16,6 +21,7 @@ var maxFlow = require('graph-theory-ford-fulkerson'); + class TrustIsRisk { @@ -41,6 +47,33 @@ class TrustIsRisk { this.node.on('tx', this.addTX.bind(this)); } + async getTrustIncreasingMTX(from , to , outpoint , trustAmount ) + { + var prevTX = await this.node.getTX(outpoint.hash); + var prevOutput = prevTX.outputs[outpoint.index]; + + var mtx = new MTX({ + inputs: [ + Input.fromOutpoint(outpoint) + ], + outputs: [ + new Output({ // Trust output + script: bcoin.script.fromMultisig(1, 2, [from, to]), + value: trustAmount + }), + new Output({ // Change output + script: bcoin.script.fromPubkeyhash(bcoin.crypto.hash160(from)), + value: prevOutput.value - trustAmount + }) + ] + }); + + var success = mtx.scriptVector(prevOutput.script, mtx.inputs[0].script, KeyRing.fromPublic(from)); + assert(success); + + return mtx; + } + getTrust(from , to ) { if (from === to) return Infinity; @@ -200,7 +233,7 @@ class TrustIsRisk { var output = tx.outputs[outputIndex]; if (output.getType() !== 'multisig') return null; - var addressA = Address.fromHash(bcoin.crypto.hash160(output.script.get(1))).toBase58() + var addressA = Address.fromHash(bcoin.crypto.hash160(output.script.get(1))).toBase58(); var addressB = Address.fromHash(bcoin.crypto.hash160(output.script.get(2))).toBase58(); if (addressA === addressB) return null; diff --git a/src/trust_is_risk.js b/src/trust_is_risk.js index c74fde1..42dce05 100644 --- a/src/trust_is_risk.js +++ b/src/trust_is_risk.js @@ -1,7 +1,12 @@ // @flow var bcoin = require('bcoin'); -var assert = require('assert'); var Address = bcoin.primitives.Address; +var KeyRing = bcoin.primitives.KeyRing; +var MTX = bcoin.primitives.MTX; +var Input = bcoin.primitives.Input; +var Output = bcoin.primitives.Output; +var Outpoint = bcoin.primitives.Outpoint; +var assert = require('assert'); var SortedSet = require('sorted-set'); var maxFlow = require('graph-theory-ford-fulkerson'); @@ -16,6 +21,7 @@ type DirectTrust = { } type TrustChange = DirectTrust; type TXHash = string; +type PubKey = Buffer; class TrustIsRisk { node : bcoin$FullNode @@ -41,6 +47,33 @@ class TrustIsRisk { this.node.on('tx', this.addTX.bind(this)); } + async getTrustIncreasingMTX(from : PubKey, to : PubKey, outpoint : bcoin$Outpoint, trustAmount : number) + : Promise { + var prevTX = await this.node.getTX(outpoint.hash); + var prevOutput = prevTX.outputs[outpoint.index]; + + var mtx = new MTX({ + inputs: [ + Input.fromOutpoint(outpoint) + ], + outputs: [ + new Output({ // Trust output + script: bcoin.script.fromMultisig(1, 2, [from, to]), + value: trustAmount + }), + new Output({ // Change output + script: bcoin.script.fromPubkeyhash(bcoin.crypto.hash160(from)), + value: prevOutput.value - trustAmount + }) + ] + }); + + var success = mtx.scriptVector(prevOutput.script, mtx.inputs[0].script, KeyRing.fromPublic(from)); + assert(success); + + return mtx; + } + getTrust(from : Entity, to : Entity) : number { if (from === to) return Infinity; @@ -200,7 +233,7 @@ class TrustIsRisk { var output = tx.outputs[outputIndex]; if (output.getType() !== 'multisig') return null; - var addressA = Address.fromHash(bcoin.crypto.hash160(output.script.get(1))).toBase58() + var addressA = Address.fromHash(bcoin.crypto.hash160(output.script.get(1))).toBase58(); var addressB = Address.fromHash(bcoin.crypto.hash160(output.script.get(2))).toBase58(); if (addressA === addressB) return null; diff --git a/test/trust_is_risk.js b/test/trust_is_risk.js index cc7cf3b..88c1d26 100644 --- a/test/trust_is_risk.js +++ b/test/trust_is_risk.js @@ -6,6 +6,8 @@ var sinon = require('sinon'); var should = require('should'); require('should-sinon'); +var Address = bcoin.primitives.Address; + describe('TrustIsRisk', () => { var addr = testHelpers.getAddressFixtures(); // Add base58 address variables to scope. @@ -13,9 +15,10 @@ describe('TrustIsRisk', () => { eval(`var ${name} = "${addr[name].base58}";`); } - var tir, trustIncreasingMTX, trustDecreasingMTX, trustIncreasingTX; + var node, tir, trustIncreasingMTX, trustDecreasingMTX, trustIncreasingTX; beforeEach(() => { - tir = new Trust.TrustIsRisk(new bcoin.fullnode({})); + node = new bcoin.fullnode({}); + tir = new Trust.TrustIsRisk(node); trustIncreasingMTX = testHelpers.getTrustIncreasingMTX(addr.alice.pubkey, addr.bob.pubkey, 42); trustIncreasingTX = trustIncreasingMTX.toTX(); @@ -136,6 +139,50 @@ describe('TrustIsRisk', () => { }); }); + describe('.getTrustIncreasingMTX()', () => { + it('creates valid trust-increasing transactions', async () => { + var getTXStub = sinon.stub(node, 'getTX'); + + var prevOutput = { + hash: 'v1pnhp2af4r5wz63j60vnh27s1bftl260qq621y458tn0g4x64u64yqz6d7qi6i8', + index: 1 + }; + + getTXStub.withArgs(prevOutput.hash).returns(new bcoin.primitives.MTX({ + inputs: [ + testHelpers.getP2PKHInput(addr.alice.pubkey) + ], + outputs: [ + testHelpers.getOneOfTwoMultisigOutput(addr.charlie.pubkey, addr.bob.pubkey, 40), + testHelpers.getP2PKHOutput(alice, 1000), // This is the P2PKH being used + testHelpers.getP2PKHOutput(dave, 200) + ] + }).toTX()); + + var mtx = await tir.getTrustIncreasingMTX(addr.alice.pubkey, addr.bob.pubkey, prevOutput, 100); + + mtx.inputs.length.should.equal(1); + var input = mtx.inputs[0]; + input.script.get(0).should.equal(0); // OP_0, because this is an unsigned bcoin input template. + input.script.get(1).should.equal(addr.alice.pubkey); + + mtx.outputs.length.should.equal(2); + + var trustOutput = mtx.outputs[0]; + trustOutput.getType().should.equal('multisig'); + var addressA = Address.fromHash(bcoin.crypto.hash160(trustOutput.script.get(1))).toBase58(); + var addressB = Address.fromHash(bcoin.crypto.hash160(trustOutput.script.get(2))).toBase58(); + addressA.should.equal(alice); + addressB.should.equal(bob); + trustOutput.value.should.equal(100); + + var changeOutput = mtx.outputs[1]; + changeOutput.getType().should.equal('pubkeyhash'); + changeOutput.getAddress().toBase58().should.equal(alice); + changeOutput.value.should.equal(900); + }); + }); + describe('.getTrust()', () => { it('returns zero for two arbitary parties that do not trust each other', () => { should(tir.getTrust(alice, bob)).equal(0); From 7d44250656d8cb15c4323514029fcb5e9833ad5b Mon Sep 17 00:00:00 2001 From: Christos Porios Date: Fri, 26 May 2017 01:11:54 +0000 Subject: [PATCH 07/20] Use conscise should syntax --- test/full_node.js | 2 +- test/trust_is_risk.js | 28 ++++++++++++++-------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/test/full_node.js b/test/full_node.js index d6308a8..7ade3fb 100644 --- a/test/full_node.js +++ b/test/full_node.js @@ -23,7 +23,7 @@ describe('FullNode', () => { afterEach(() => node.close()); it('should be a bcoin instance', () => { - should(node).be.an.instanceof(bcoin.fullnode); + node.should.be.an.instanceof(bcoin.fullnode); }); it('should call trust.addTX() on every transaction', async function() { diff --git a/test/trust_is_risk.js b/test/trust_is_risk.js index a959f53..885ae45 100644 --- a/test/trust_is_risk.js +++ b/test/trust_is_risk.js @@ -67,10 +67,10 @@ describe('TrustIsRisk', () => { describe('.getDirectTrust()', () => { it('returns zero for two arbitary parties that do not trust each other', () => { - should(tir.getDirectTrust(alice, bob)).equal(0); - should(tir.getDirectTrust(bob, alice)).equal(0); - should(tir.getDirectTrust(charlie, alice)).equal(0); - should(tir.getDirectTrust(alice, charlie)).equal(0); + tir.getDirectTrust(alice, bob).should.equal(0); + tir.getDirectTrust(bob, alice).should.equal(0); + tir.getDirectTrust(charlie, alice).should.equal(0); + tir.getDirectTrust(alice, charlie).should.equal(0); }); }); @@ -85,7 +85,7 @@ describe('TrustIsRisk', () => { }); tir.parseTXAsTrustIncrease(trustIncreasingMTX.toTX()); - should(tir.getDirectTrust(alice, bob)).equal(0); + tir.getDirectTrust(alice, bob).should.equal(0); }); }); @@ -93,15 +93,15 @@ describe('TrustIsRisk', () => { it('correctly increases trust', () => { tir.addTX(trustIncreasingTX); - should(tir.getDirectTrust(alice, bob)).equal(42); - should(tir.getDirectTrust(bob, alice)).equal(0); + tir.getDirectTrust(alice, bob).should.equal(42); + tir.getDirectTrust(bob, alice).should.equal(0); }); it('which has more than one input does not change trust', () => { trustIncreasingMTX.inputs.push(inputP2PKH); tir.addTX(trustIncreasingMTX.toTX()); - should(tir.getDirectTrust(alice, bob)).equal(0); + tir.getDirectTrust(alice, bob).should.equal(0); }); it('which has a change output correctly increases trust', () => { @@ -109,7 +109,7 @@ describe('TrustIsRisk', () => { trustIncreasingMTX.outputs.push(testHelpers.P2PKHOutput(alice, 10)); tir.addTX(trustIncreasingMTX.toTX()); - should(tir.getDirectTrust(alice, bob)).equal(32); + tir.getDirectTrust(alice, bob).should.equal(32); }); it('which has two change outputs does not change trust', () => { @@ -119,7 +119,7 @@ describe('TrustIsRisk', () => { } tir.addTX(trustIncreasingMTX.toTX()); - should(tir.getDirectTrust(alice, bob)).equal(0); + tir.getDirectTrust(alice, bob).should.equal(0); }); it('which has a second output that is not a change output does not change trust', () => { @@ -127,7 +127,7 @@ describe('TrustIsRisk', () => { trustIncreasingMTX.outputs.push(testHelpers.P2PKHOutput(charlie, 5)); tir.addTX(trustIncreasingMTX.toTX()); - should(tir.getDirectTrust(alice, bob)).equal(0); + tir.getDirectTrust(alice, bob).should.equal(0); }); }); @@ -139,14 +139,14 @@ describe('TrustIsRisk', () => { it('correctly decreases trust', () => { tir.addTX(trustDecreasingMTX.toTX()); - should(tir.getDirectTrust(alice, bob)).equal(20); + tir.getDirectTrust(alice, bob).should.equal(20); }); it('which has a second input decreases trust to zero', () => { trustDecreasingMTX.inputs.push(inputP2PKH); tir.addTX(trustDecreasingMTX.toTX()); - should(tir.getDirectTrust(alice, bob)).equal(0); + tir.getDirectTrust(alice, bob).should.equal(0); }); it('which has more than one trust outputs decreases trust to zero', () => { @@ -154,7 +154,7 @@ describe('TrustIsRisk', () => { trustDecreasingMTX.outputs.push(Object.assign(outputOneOfTwoMultisig.clone(), {value: 5})); tir.addTX(trustDecreasingMTX.toTX()); - should(tir.getDirectTrust(alice, bob)).equal(0); + tir.getDirectTrust(alice, bob).should.equal(0); }); }); }); From 23d9a4b3cbe59a9d5da919c0551585e0d8563295 Mon Sep 17 00:00:00 2001 From: Christos Porios Date: Sat, 27 May 2017 19:02:24 +0000 Subject: [PATCH 08/20] Implement .getTrustDecreasingMTXs() --- flow-typed/npm/bcoin_vx.x.x.js | 1 + lib/direct_trust.js | 106 +++++++++++++ lib/helpers.js | 12 ++ lib/trust_db.js | 115 ++++++++++++++ lib/trust_is_risk.js | 258 +++++++++++++++----------------- lib/types.js | 8 + src/direct_trust.js | 106 +++++++++++++ src/helpers.js | 12 ++ src/trust_db.js | 115 ++++++++++++++ src/trust_is_risk.js | 264 +++++++++++++++------------------ src/types.js | 8 + test/trust_is_risk.js | 65 ++++++++ 12 files changed, 787 insertions(+), 283 deletions(-) create mode 100644 lib/direct_trust.js create mode 100644 lib/helpers.js create mode 100644 lib/trust_db.js create mode 100644 lib/types.js create mode 100644 src/direct_trust.js create mode 100644 src/helpers.js create mode 100644 src/trust_db.js create mode 100644 src/types.js diff --git a/flow-typed/npm/bcoin_vx.x.x.js b/flow-typed/npm/bcoin_vx.x.x.js index 504682f..74a3e2e 100644 --- a/flow-typed/npm/bcoin_vx.x.x.js +++ b/flow-typed/npm/bcoin_vx.x.x.js @@ -28,6 +28,7 @@ declare class bcoin$MTX { toTX : bcoin$TX; template(ring : bcoin$KeyRing) : number; scriptVector(outputScript : bcoin$Script, inputScript : bcoin$Script, ring : bcoin$KeyRing) : boolean; + addOutput(output : bcoin$Output) : void; } diff --git a/lib/direct_trust.js b/lib/direct_trust.js new file mode 100644 index 0000000..6545c0b --- /dev/null +++ b/lib/direct_trust.js @@ -0,0 +1,106 @@ +// + +var bcoin = require('bcoin'); +var assert = require('assert'); +var helpers = require('./helpers'); + + + + + + + + + + + + + + +class DirectTrust { + + + + + // Every DT is associated with a transaction output, except for non-standard trust decreasing + // transactions, which reduce trust to zero and are related to a whole transaction and not + // a specific output. + + + + + + + + constructor(options ) { + this.outputIndex = null; + this.script = null; + this.prev = null; + this.next = null; + Object.assign(this, options); + } + + isNull() { + return this.amount === 0; + } + + isIncrease() { + return this.prev === null; + } + + isDecrease() { + return !this.isIncrease(); + } + + isSpent() { + return this.next !== null; + } + + isSpendable() { + return !this.isSpent() && !this.isNull(); + } + + isValid() { + // TODO: Consider removing this function and ensure validity at build time by using the flow + // type system, possibly by creating sub-types like "IncreasingDirectTrust" etc. + var valid = true; + + if ((this.outputIndex === null) !== (this.script === null)) valid = false; + if (this.outputIndex === null && this.isIncrease()) valid = false; + if (this.outputIndex === null && this.amount > 0) valid = false; + if (this.isIncrease() && this.isNull()) valid = false; + if (this.isSpent() && this.isNull()) valid = false; + + return valid; + } + + getFromEntity() { + return helpers.pubKeyToEntity(this.from); + } + + getToEntity() { + return helpers.pubKeyToEntity(this.to); + } + + spend(next ) { + assert(!this.isSpent()); + assert(this.from.equals(next.from) && this.to.equals(next.to)); + assert(next.amount <= this.amount); + + this.next = next; + next.prev = this; + } + + getNullifying(txHash ) { + return new DirectTrust({ + from: this.from, + to: this.to, + amount: 0, + + prev: this, + txHash, + }); + } +} + +module.exports = DirectTrust; diff --git a/lib/helpers.js b/lib/helpers.js new file mode 100644 index 0000000..f7c1443 --- /dev/null +++ b/lib/helpers.js @@ -0,0 +1,12 @@ +// + +var bcoin = require('bcoin'); +var Address = bcoin.primitives.Address; + +var helpers = { + pubKeyToEntity: (key ) => { + return Address.fromHash(bcoin.crypto.hash160(key)).toBase58() + } +}; + +module.exports = helpers; diff --git a/lib/trust_db.js b/lib/trust_db.js new file mode 100644 index 0000000..ccd282b --- /dev/null +++ b/lib/trust_db.js @@ -0,0 +1,115 @@ +// + +var assert = require('assert'); +var SortedSet = require('sorted-set'); +var maxFlow = require('graph-theory-ford-fulkerson'); +var bcoin = require('bcoin'); +var Address = bcoin.primitives.Address; +var KeyRing = bcoin.primitives.KeyRing; +var MTX = bcoin.primitives.MTX; +var Input = bcoin.primitives.Input; +var Output = bcoin.primitives.Output; +var Outpoint = bcoin.primitives.Outpoint; +var DirectTrust = require('./direct_trust'); + +class TrustDB { + + + + + constructor() { + this.directTrusts = new Map(); + this.txToDirectTrust = new Map(); + this.entities = new SortedSet(); + } + + getDirectTrustByTX(txHash ) { + if (!this.isTrustTX(txHash)) return null; + return ((this.txToDirectTrust.get(txHash) ) ); + } + + getDirectTrustByOutpoint(outpoint ) { + var trust = this.txToDirectTrust.get(outpoint.hash.toString('hex')); + if (!trust) return null; + if (trust.outputIndex !== outpoint.index) return null; + return trust; + } + + getDirectTrustAmount(from , to ) { + if (from === to) return Infinity; + + var trusts = this.getSpendableDirectTrusts(from, to); + return trusts.reduce((sum, t) => sum + t.amount, 0); + } + + getSpendableDirectTrusts(from , to ) { + return this.getDirectTrusts(from, to).filter((t) => t.isSpendable()); + } + + getDirectTrusts(from , to ) { + var fromMap = this.directTrusts.get(from); + if (!fromMap) return []; + + var trusts = fromMap.get(to); + if (!trusts) return []; + + return trusts; + } + + getGraphWeightMatrix() { + var entitiesArr = this.getEntities(); + return entitiesArr.map((from) => { + return entitiesArr.map((to) => this.getDirectTrustAmount(from, to)); + }); + } + + getTrustAmount(from , to ) { + // TODO: Optimize + if (from === to) return Infinity; + + var graph = this.getGraphWeightMatrix(); + var fromIndex = this.getEntityIndex(from); + var toIndex = this.getEntityIndex(to); + + if (fromIndex === -1 || toIndex === -1) return 0; + else return maxFlow(graph, fromIndex, toIndex); + } + + getEntities() { + return this.entities.slice(0, this.entities.length); + } + + getEntityIndex(entity ) { + return this.entities.rank(entity); + } + + isTrustTX(txHash ) { + return this.txToDirectTrust.has(txHash); + } + + add(trust ) { + var from = trust.getFromEntity(); + var to = trust.getToEntity(); + assert(from !== to); + + if (!this.directTrusts.has(from)) this.directTrusts.set(from, new Map()); + var fromMap = ((this.directTrusts.get(from) ) ); + + if (!fromMap.has(to)) fromMap.set(to, []); + var trusts = ((fromMap.get(to) ) ); + + if (trust.prev !== null) { + trust.prev.spend(trust); + assert(trust.prev && trust.prev.isValid() && !trust.prev.isSpendable()); + } + + assert(trust.isValid()); + trusts.push(trust); + this.txToDirectTrust.set(trust.txHash, trust); + + this.entities.add(from); + this.entities.add(to); + } +} + +module.exports = TrustDB; diff --git a/lib/trust_is_risk.js b/lib/trust_is_risk.js index 2b2a5b6..278f64a 100644 --- a/lib/trust_is_risk.js +++ b/lib/trust_is_risk.js @@ -1,4 +1,5 @@ // + var bcoin = require('bcoin'); var Address = bcoin.primitives.Address; var KeyRing = bcoin.primitives.KeyRing; @@ -7,90 +8,27 @@ var Input = bcoin.primitives.Input; var Output = bcoin.primitives.Output; var Outpoint = bcoin.primitives.Outpoint; var assert = require('assert'); -var SortedSet = require('sorted-set'); -var maxFlow = require('graph-theory-ford-fulkerson'); - - - // base58 bitcoin address - - - - - - - - - - +var helpers = require('./helpers'); +var TrustDB = require('./trust_db'); +var DirectTrust = require('./direct_trust'); class TrustIsRisk { - - - - - - - - - - - - + constructor(node ) { this.node = node; - this.directTrust = {}; - this.TXToTrust = {}; - this.entities = new SortedSet(); + this.trustDB = new TrustDB(); this.node.on('tx', this.addTX.bind(this)); } - async getTrustIncreasingMTX(from , to , outpoint , trustAmount ) - { - var prevTX = await this.node.getTX(outpoint.hash); - var prevOutput = prevTX.outputs[outpoint.index]; - - var mtx = new MTX({ - inputs: [ - Input.fromOutpoint(outpoint) - ], - outputs: [ - new Output({ // Trust output - script: bcoin.script.fromMultisig(1, 2, [from, to]), - value: trustAmount - }), - new Output({ // Change output - script: bcoin.script.fromPubkeyhash(bcoin.crypto.hash160(from)), - value: prevOutput.value - trustAmount - }) - ] - }); - - var success = mtx.scriptVector(prevOutput.script, mtx.inputs[0].script, KeyRing.fromPublic(from)); - assert(success); - - return mtx; - } - - getTrust(from , to ) { - if (from === to) return Infinity; - - // TODO: Optimize - var graph = this.getGraphWeightMatrix(); - var fromIndex = this.getGraphWeightMatrixIndex(from); - var toIndex = this.getGraphWeightMatrixIndex(to); - - if (fromIndex === -1 || toIndex === -1) return 0; - else return maxFlow(graph, fromIndex, toIndex); + getTrust(from , to ) { + return this.trustDB.getTrustAmount(from, to); } - getDirectTrust(from , to ) { - if (from === to) return Infinity; - if (!this.directTrust.hasOwnProperty(from)) return 0; - if (!this.directTrust[from].hasOwnProperty(to)) return 0; - return this.directTrust[from][to]; + getDirectTrust(from , to ) { + return this.trustDB.getDirectTrustAmount(from, to); } // Attempts to parse a bitcoin transaction as a trust change and adds it to the trust network @@ -100,23 +38,23 @@ class TrustIsRisk { // Throws an error if the transaction was processed earlier. addTX(tx ) { var txHash = tx.hash().toString('hex'); - if (this.TXToTrust.hasOwnProperty(txHash)) { - throw new Error('Duplicate TX: Transaction with hash ' + txHash + ' has been seen before.'); + if (this.trustDB.isTrustTX(txHash)) { + throw new Error(`Transaction already processed: Transaction ${txHash} already carries trust`); } - var trustChanges = this.getTrustChanges(tx); - if (trustChanges.length === 0) return false; + var directTrusts = this.getDirectTrusts(tx); + if (directTrusts.length === 0) return false; else { - trustChanges.map(this.applyTrustChange.bind(this)); + directTrusts.map(this.trustDB.add.bind(this.trustDB)); return true; } } - // Returns a list of trust changes that a transaction causes, which will be one of the following: + // Returns a list of trusts that a transaction contains, which will be one of the following: // * An empty list (for non-TIR transactions). // * A list containing a single trust increase (for trust-increasing transactions). // * A list containing one or more trust decreases (for trust-decreasing transactions). - getTrustChanges(tx ) { + getDirectTrusts(tx ) { var trustIncrease = this.parseTXAsTrustIncrease(tx); if (trustIncrease !== null) { return [trustIncrease]; @@ -125,32 +63,89 @@ class TrustIsRisk { } } - applyTrustChange(trustChange ) { - if (!this.directTrust.hasOwnProperty(trustChange.from)) this.directTrust[trustChange.from] = {}; - if (!this.directTrust[trustChange.from].hasOwnProperty(trustChange.to)) { - this.directTrust[trustChange.from][trustChange.to] = 0 + async getTrustIncreasingMTX(from , to , outpoint , trustAmount ) + { + var prevTX = await this.node.getTX(outpoint.hash); + if (!prevTX) throw new Error('Could not find transaction'); + + var prevOutput = prevTX.outputs[outpoint.index]; + if (!prevOutput) throw new Error('Could not find transaction output'); + + var mtx = new MTX({ + inputs: [ + Input.fromOutpoint(outpoint) + ], + outputs: [ + new Output({ + script: bcoin.script.fromMultisig(1, 2, [from, to]), + value: trustAmount + }) + ] + }); + + var changeAmount = prevOutput.value - trustAmount; + if (changeAmount) { + mtx.addOutput(new Output({ + script: bcoin.script.fromPubkeyhash(bcoin.crypto.hash160(from)), + value: changeAmount + })); } - this.directTrust[trustChange.from][trustChange.to] += trustChange.amount; + var success = mtx.scriptVector(prevOutput.script, mtx.inputs[0].script, KeyRing.fromPublic(from)); + assert(success); + + return mtx; + } + + getTrustDecreasingMTXs(from , to , trustDecreaseAmount ) + { + var fromAddress = Address.fromHash(bcoin.crypto.hash160(from)).toBase58(); + var toAddress = Address.fromHash(bcoin.crypto.hash160(to)).toBase58(); + if (fromAddress === toAddress) throw new Error('Can\'t decrease self-trust'); + + var existingTrustAmount = this.trustDB.getDirectTrustAmount(fromAddress, toAddress); + if (existingTrustAmount < trustDecreaseAmount) throw new Error('Insufficient trust'); + + var directTrusts = this.trustDB.getSpendableDirectTrusts(fromAddress, toAddress); + return directTrusts.map((directTrust) => { + var decrease = Math.min(trustDecreaseAmount, directTrust.amount); + if (decrease === 0) return null; + trustDecreaseAmount -= decrease; + return this.getTrustDecreasingMTX(directTrust, decrease); + }).filter(Boolean); + } + + getTrustDecreasingMTX(directTrust , decreaseAmount ) { + var remainingTrustAmount = directTrust.amount - decreaseAmount; + + var mtx = new MTX({ + inputs: [ + Input.fromOutpoint(new Outpoint(directTrust.txHash, directTrust.outputIndex)) + ], + outputs: [new Output({ + script: bcoin.script.fromPubkeyhash(bcoin.crypto.hash160(directTrust.from)), + value: decreaseAmount + })] + }); - if (this.directTrust[trustChange.from][trustChange.to] > 0) { - this.TXToTrust[trustChange.txHash] = { - from: trustChange.from, - to: trustChange.to, - amount: this.directTrust[trustChange.from][trustChange.to], - txHash: trustChange.txHash, - outputIndex: trustChange.outputIndex - }; + if (remainingTrustAmount > 0) { + mtx.addOutput(new Output({ + script: bcoin.script.fromMultisig(1, 2, [directTrust.from, directTrust.to]), + value: remainingTrustAmount + })); } - this.entities.add(trustChange.from); - this.entities.add(trustChange.to); + var success = mtx.scriptVector(((directTrust.script ) ), + mtx.inputs[0].script, KeyRing.fromPublic(directTrust.from)); + assert(success); + + return mtx; } parseTXAsTrustIncrease(tx ) { if (tx.inputs.length !== 1) return null; if (tx.inputs[0].getType() !== 'pubkeyhash') return null; // TODO: This is unreliable - if (this.TXToTrust[tx.inputs[0].prevout.hash.toString('hex')]) return null; + if (this.trustDB.isTrustTX(tx.inputs[0].prevout.hash.toString('hex'))) return null; var sender = tx.inputs[0].getAddress().toBase58(); if (tx.outputs.length === 0 || tx.outputs.length > 2) return null; @@ -171,7 +166,7 @@ class TrustIsRisk { getInputTrusts(inputs ) { return inputs.map((input) => { - var trust = this.TXToTrust[input.prevout.hash.toString('hex')] + var trust = this.trustDB.getDirectTrustByOutpoint(input.prevout); if (trust && trust.outputIndex === input.prevout.index) return trust; else return null; }).filter(Boolean); @@ -179,35 +174,23 @@ class TrustIsRisk { getTrustDecrease(tx , prevTrust ) { var txHash = tx.hash().toString('hex'); - var nullifyTrust = { - from: prevTrust.from, - to: prevTrust.to, - amount: -prevTrust.amount, - txHash, - outputIndex: null - }; + var nullTrust = prevTrust.getNullifying(txHash); - if (tx.inputs.length !== 1) return nullifyTrust; + if (tx.inputs.length !== 1) return nullTrust; - var trustOutputs = this.searchForDirectTrustOutputs(tx, prevTrust.from, prevTrust.to); - if (trustOutputs.length != 1) return nullifyTrust; + var trustOutputs = this.searchForDirectTrustOutputs(tx, prevTrust.getFromEntity(), + prevTrust.getToEntity()); + if (trustOutputs.length != 1) return nullTrust; var nextTrust = trustOutputs[0]; - assert(nextTrust.from === prevTrust.from); - assert(nextTrust.to === prevTrust.to); + nextTrust.prev = prevTrust; - var trustAmountChange = nextTrust.amount - prevTrust.amount; - assert(trustAmountChange <= 0); - return { - from: nextTrust.from, - to: nextTrust.to, - amount: trustAmountChange, - txHash, - outputIndex: nextTrust.outputIndex - } + assert(nextTrust.amount - prevTrust.amount <= 0); + return nextTrust; } - + // Looks for direct trust outputs that originate from a sender in an array of bitcoin outputs. + // Returns a list of the corresponding DirectTrust objects. // If the recipient parameter is set, it will limit the results only to the outputs being sent to // the recipient. searchForDirectTrustOutputs(tx , sender , recipient ) { @@ -233,35 +216,30 @@ class TrustIsRisk { var output = tx.outputs[outputIndex]; if (output.getType() !== 'multisig') return null; - var addressA = Address.fromHash(bcoin.crypto.hash160(output.script.get(1))).toBase58(); - var addressB = Address.fromHash(bcoin.crypto.hash160(output.script.get(2))).toBase58(); - - if (addressA === addressB) return null; + var entities = [1, 2].map((i) => helpers.pubKeyToEntity(output.script.get(i))); + if (entities[0] === entities[1]) return null; - var recipient; - if (addressA === sender) recipient = addressB; - else if (addressB === sender) recipient = addressA; + var from, to; + if (entities[0] === sender) { + from = output.script.get(1); + to = output.script.get(2); + } + else if (entities[1] === sender) { + from = output.script.get(2); + to = output.script.get(1); + } else return null; - return { - from: sender, - to: recipient, + return new DirectTrust({ + from, + to, amount: Number(output.value), - txHash, - outputIndex - }; - } - getGraphWeightMatrix() { - var entitiesArr = this.entities.slice(0, this.entities.length); - return entitiesArr.map((from) => { - return entitiesArr.map((to) => this.getDirectTrust(from, to)); + txHash, + outputIndex, + script: output.script }); } - - getGraphWeightMatrixIndex(entity ) { - return this.entities.rank(entity); - } } module.exports = TrustIsRisk; diff --git a/lib/types.js b/lib/types.js new file mode 100644 index 0000000..44ee569 --- /dev/null +++ b/lib/types.js @@ -0,0 +1,8 @@ +// + +// base58 bitcoin address: + + + + + diff --git a/src/direct_trust.js b/src/direct_trust.js new file mode 100644 index 0000000..fe3e687 --- /dev/null +++ b/src/direct_trust.js @@ -0,0 +1,106 @@ +// @flow +import type {Entity, TXHash, PubKey} from "./types" +var bcoin = require('bcoin'); +var assert = require('assert'); +var helpers = require('./helpers'); + +type DirectTrustOptions = { + from : PubKey, + to : PubKey, + amount : number, + + txHash : string, + outputIndex? : number, + script? : bcoin$Script, + + prev? : DirectTrust, + next? :DirectTrust +} + +class DirectTrust { + from : PubKey + to : PubKey + amount : number + + // Every DT is associated with a transaction output, except for non-standard trust decreasing + // transactions, which reduce trust to zero and are related to a whole transaction and not + // a specific output. + txHash : string + outputIndex : (number | null) + script : (bcoin$Script | null) + + prev : (DirectTrust | null) + next : (DirectTrust | null) + + constructor(options : DirectTrustOptions) { + this.outputIndex = null; + this.script = null; + this.prev = null; + this.next = null; + Object.assign(this, options); + } + + isNull() { + return this.amount === 0; + } + + isIncrease() : boolean { + return this.prev === null; + } + + isDecrease() : boolean { + return !this.isIncrease(); + } + + isSpent() : boolean { + return this.next !== null; + } + + isSpendable() : boolean { + return !this.isSpent() && !this.isNull(); + } + + isValid() : boolean { + // TODO: Consider removing this function and ensure validity at build time by using the flow + // type system, possibly by creating sub-types like "IncreasingDirectTrust" etc. + var valid = true; + + if ((this.outputIndex === null) !== (this.script === null)) valid = false; + if (this.outputIndex === null && this.isIncrease()) valid = false; + if (this.outputIndex === null && this.amount > 0) valid = false; + if (this.isIncrease() && this.isNull()) valid = false; + if (this.isSpent() && this.isNull()) valid = false; + + return valid; + } + + getFromEntity() : Entity { + return helpers.pubKeyToEntity(this.from); + } + + getToEntity() : Entity { + return helpers.pubKeyToEntity(this.to); + } + + spend(next : DirectTrust) : void { + assert(!this.isSpent()); + assert(this.from.equals(next.from) && this.to.equals(next.to)); + assert(next.amount <= this.amount); + + this.next = next; + next.prev = this; + } + + getNullifying(txHash : string) : DirectTrust { + return new DirectTrust({ + from: this.from, + to: this.to, + amount: 0, + + prev: this, + txHash, + }); + } +} + +module.exports = DirectTrust; diff --git a/src/helpers.js b/src/helpers.js new file mode 100644 index 0000000..fcf67bc --- /dev/null +++ b/src/helpers.js @@ -0,0 +1,12 @@ +// @flow +import type {Entity, TXHash, PubKey} from "./types" +var bcoin = require('bcoin'); +var Address = bcoin.primitives.Address; + +var helpers = { + pubKeyToEntity: (key : PubKey) : Entity => { + return Address.fromHash(bcoin.crypto.hash160(key)).toBase58() + } +}; + +module.exports = helpers; diff --git a/src/trust_db.js b/src/trust_db.js new file mode 100644 index 0000000..bca781c --- /dev/null +++ b/src/trust_db.js @@ -0,0 +1,115 @@ +// @flow +import type {Entity, TXHash, PubKey} from "./types" +var assert = require('assert'); +var SortedSet = require('sorted-set'); +var maxFlow = require('graph-theory-ford-fulkerson'); +var bcoin = require('bcoin'); +var Address = bcoin.primitives.Address; +var KeyRing = bcoin.primitives.KeyRing; +var MTX = bcoin.primitives.MTX; +var Input = bcoin.primitives.Input; +var Output = bcoin.primitives.Output; +var Outpoint = bcoin.primitives.Outpoint; +var DirectTrust = require('./direct_trust'); + +class TrustDB { + directTrusts : Map>> + txToDirectTrust : Map + entities : SortedSet; + + constructor() { + this.directTrusts = new Map(); + this.txToDirectTrust = new Map(); + this.entities = new SortedSet(); + } + + getDirectTrustByTX(txHash : string) : (DirectTrust | null) { + if (!this.isTrustTX(txHash)) return null; + return ((this.txToDirectTrust.get(txHash) : any) : DirectTrust); + } + + getDirectTrustByOutpoint(outpoint : bcoin$Outpoint) : (DirectTrust | null) { + var trust = this.txToDirectTrust.get(outpoint.hash.toString('hex')); + if (!trust) return null; + if (trust.outputIndex !== outpoint.index) return null; + return trust; + } + + getDirectTrustAmount(from : Entity, to : Entity) : number { + if (from === to) return Infinity; + + var trusts = this.getSpendableDirectTrusts(from, to); + return trusts.reduce((sum, t) => sum + t.amount, 0); + } + + getSpendableDirectTrusts(from : Entity, to : Entity) : DirectTrust[] { + return this.getDirectTrusts(from, to).filter((t) => t.isSpendable()); + } + + getDirectTrusts(from : Entity, to : Entity) : DirectTrust[] { + var fromMap = this.directTrusts.get(from); + if (!fromMap) return []; + + var trusts = fromMap.get(to); + if (!trusts) return []; + + return trusts; + } + + getGraphWeightMatrix() : number[][] { + var entitiesArr = this.getEntities(); + return entitiesArr.map((from) => { + return entitiesArr.map((to) => this.getDirectTrustAmount(from, to)); + }); + } + + getTrustAmount(from : Entity, to : Entity) : number { + // TODO: Optimize + if (from === to) return Infinity; + + var graph = this.getGraphWeightMatrix(); + var fromIndex = this.getEntityIndex(from); + var toIndex = this.getEntityIndex(to); + + if (fromIndex === -1 || toIndex === -1) return 0; + else return maxFlow(graph, fromIndex, toIndex); + } + + getEntities() : Entity[] { + return this.entities.slice(0, this.entities.length); + } + + getEntityIndex(entity : Entity) : number { + return this.entities.rank(entity); + } + + isTrustTX(txHash : string) : boolean { + return this.txToDirectTrust.has(txHash); + } + + add(trust : DirectTrust) { + var from = trust.getFromEntity(); + var to = trust.getToEntity(); + assert(from !== to); + + if (!this.directTrusts.has(from)) this.directTrusts.set(from, new Map()); + var fromMap = ((this.directTrusts.get(from) : any) : Map>); + + if (!fromMap.has(to)) fromMap.set(to, []); + var trusts = ((fromMap.get(to) : any) : Array); + + if (trust.prev !== null) { + trust.prev.spend(trust); + assert(trust.prev && trust.prev.isValid() && !trust.prev.isSpendable()); + } + + assert(trust.isValid()); + trusts.push(trust); + this.txToDirectTrust.set(trust.txHash, trust); + + this.entities.add(from); + this.entities.add(to); + } +} + +module.exports = TrustDB; diff --git a/src/trust_is_risk.js b/src/trust_is_risk.js index 42dce05..3f35e59 100644 --- a/src/trust_is_risk.js +++ b/src/trust_is_risk.js @@ -1,4 +1,5 @@ // @flow +import type {Entity, TXHash, PubKey} from "./types" var bcoin = require('bcoin'); var Address = bcoin.primitives.Address; var KeyRing = bcoin.primitives.KeyRing; @@ -7,90 +8,27 @@ var Input = bcoin.primitives.Input; var Output = bcoin.primitives.Output; var Outpoint = bcoin.primitives.Outpoint; var assert = require('assert'); -var SortedSet = require('sorted-set'); -var maxFlow = require('graph-theory-ford-fulkerson'); - - -type Entity = string; // base58 bitcoin address -type DirectTrust = { - from : Entity, - to: Entity, - amount: number, - txHash: string, - outputIndex: ?number, // Not set for nullifying trust changes -} -type TrustChange = DirectTrust; -type TXHash = string; -type PubKey = Buffer; +var helpers = require('./helpers'); +var TrustDB = require('./trust_db'); +var DirectTrust = require('./direct_trust'); class TrustIsRisk { node : bcoin$FullNode - - directTrust : { - [from : Entity] : ({ - [to : Entity] : number - }) - } - - TXToTrust : { - [hash : TXHash] : DirectTrust - } - - entities : SortedSet + trustDB : TrustDB constructor(node : bcoin$FullNode) { this.node = node; - this.directTrust = {}; - this.TXToTrust = {}; - this.entities = new SortedSet(); + this.trustDB = new TrustDB(); this.node.on('tx', this.addTX.bind(this)); } - async getTrustIncreasingMTX(from : PubKey, to : PubKey, outpoint : bcoin$Outpoint, trustAmount : number) - : Promise { - var prevTX = await this.node.getTX(outpoint.hash); - var prevOutput = prevTX.outputs[outpoint.index]; - - var mtx = new MTX({ - inputs: [ - Input.fromOutpoint(outpoint) - ], - outputs: [ - new Output({ // Trust output - script: bcoin.script.fromMultisig(1, 2, [from, to]), - value: trustAmount - }), - new Output({ // Change output - script: bcoin.script.fromPubkeyhash(bcoin.crypto.hash160(from)), - value: prevOutput.value - trustAmount - }) - ] - }); - - var success = mtx.scriptVector(prevOutput.script, mtx.inputs[0].script, KeyRing.fromPublic(from)); - assert(success); - - return mtx; - } - - getTrust(from : Entity, to : Entity) : number { - if (from === to) return Infinity; - - // TODO: Optimize - var graph = this.getGraphWeightMatrix(); - var fromIndex = this.getGraphWeightMatrixIndex(from); - var toIndex = this.getGraphWeightMatrixIndex(to); - - if (fromIndex === -1 || toIndex === -1) return 0; - else return maxFlow(graph, fromIndex, toIndex); + getTrust(from : Entity, to : Entity) { + return this.trustDB.getTrustAmount(from, to); } - getDirectTrust(from : Entity, to : Entity) : number { - if (from === to) return Infinity; - if (!this.directTrust.hasOwnProperty(from)) return 0; - if (!this.directTrust[from].hasOwnProperty(to)) return 0; - return this.directTrust[from][to]; + getDirectTrust(from : Entity, to : Entity) { + return this.trustDB.getDirectTrustAmount(from, to); } // Attempts to parse a bitcoin transaction as a trust change and adds it to the trust network @@ -100,23 +38,23 @@ class TrustIsRisk { // Throws an error if the transaction was processed earlier. addTX(tx : bcoin$TX) : boolean { var txHash = tx.hash().toString('hex'); - if (this.TXToTrust.hasOwnProperty(txHash)) { - throw new Error('Duplicate TX: Transaction with hash ' + txHash + ' has been seen before.'); + if (this.trustDB.isTrustTX(txHash)) { + throw new Error(`Transaction already processed: Transaction ${txHash} already carries trust`); } - var trustChanges = this.getTrustChanges(tx); - if (trustChanges.length === 0) return false; + var directTrusts = this.getDirectTrusts(tx); + if (directTrusts.length === 0) return false; else { - trustChanges.map(this.applyTrustChange.bind(this)); + directTrusts.map(this.trustDB.add.bind(this.trustDB)); return true; } } - // Returns a list of trust changes that a transaction causes, which will be one of the following: + // Returns a list of trusts that a transaction contains, which will be one of the following: // * An empty list (for non-TIR transactions). // * A list containing a single trust increase (for trust-increasing transactions). // * A list containing one or more trust decreases (for trust-decreasing transactions). - getTrustChanges(tx : bcoin$TX) : TrustChange[] { + getDirectTrusts(tx : bcoin$TX) : DirectTrust[] { var trustIncrease = this.parseTXAsTrustIncrease(tx); if (trustIncrease !== null) { return [trustIncrease]; @@ -125,32 +63,89 @@ class TrustIsRisk { } } - applyTrustChange(trustChange : TrustChange) { - if (!this.directTrust.hasOwnProperty(trustChange.from)) this.directTrust[trustChange.from] = {}; - if (!this.directTrust[trustChange.from].hasOwnProperty(trustChange.to)) { - this.directTrust[trustChange.from][trustChange.to] = 0 + async getTrustIncreasingMTX(from : PubKey, to : PubKey, outpoint : bcoin$Outpoint, trustAmount : number) + : Promise { + var prevTX = await this.node.getTX(outpoint.hash); + if (!prevTX) throw new Error('Could not find transaction'); + + var prevOutput = prevTX.outputs[outpoint.index]; + if (!prevOutput) throw new Error('Could not find transaction output'); + + var mtx = new MTX({ + inputs: [ + Input.fromOutpoint(outpoint) + ], + outputs: [ + new Output({ + script: bcoin.script.fromMultisig(1, 2, [from, to]), + value: trustAmount + }) + ] + }); + + var changeAmount = prevOutput.value - trustAmount; + if (changeAmount) { + mtx.addOutput(new Output({ + script: bcoin.script.fromPubkeyhash(bcoin.crypto.hash160(from)), + value: changeAmount + })); } - this.directTrust[trustChange.from][trustChange.to] += trustChange.amount; + var success = mtx.scriptVector(prevOutput.script, mtx.inputs[0].script, KeyRing.fromPublic(from)); + assert(success); + + return mtx; + } + + getTrustDecreasingMTXs(from : PubKey, to : PubKey, trustDecreaseAmount : number) + : bcoin$MTX[] { + var fromAddress = Address.fromHash(bcoin.crypto.hash160(from)).toBase58(); + var toAddress = Address.fromHash(bcoin.crypto.hash160(to)).toBase58(); + if (fromAddress === toAddress) throw new Error('Can\'t decrease self-trust'); + + var existingTrustAmount = this.trustDB.getDirectTrustAmount(fromAddress, toAddress); + if (existingTrustAmount < trustDecreaseAmount) throw new Error('Insufficient trust'); + + var directTrusts = this.trustDB.getSpendableDirectTrusts(fromAddress, toAddress); + return directTrusts.map((directTrust) => { + var decrease = Math.min(trustDecreaseAmount, directTrust.amount); + if (decrease === 0) return null; + trustDecreaseAmount -= decrease; + return this.getTrustDecreasingMTX(directTrust, decrease); + }).filter(Boolean); + } + + getTrustDecreasingMTX(directTrust : DirectTrust, decreaseAmount : number) { + var remainingTrustAmount = directTrust.amount - decreaseAmount; + + var mtx = new MTX({ + inputs: [ + Input.fromOutpoint(new Outpoint(directTrust.txHash, directTrust.outputIndex)) + ], + outputs: [new Output({ + script: bcoin.script.fromPubkeyhash(bcoin.crypto.hash160(directTrust.from)), + value: decreaseAmount + })] + }); - if (this.directTrust[trustChange.from][trustChange.to] > 0) { - this.TXToTrust[trustChange.txHash] = { - from: trustChange.from, - to: trustChange.to, - amount: this.directTrust[trustChange.from][trustChange.to], - txHash: trustChange.txHash, - outputIndex: trustChange.outputIndex - }; + if (remainingTrustAmount > 0) { + mtx.addOutput(new Output({ + script: bcoin.script.fromMultisig(1, 2, [directTrust.from, directTrust.to]), + value: remainingTrustAmount + })); } - this.entities.add(trustChange.from); - this.entities.add(trustChange.to); + var success = mtx.scriptVector(((directTrust.script : any) : bcoin$Script), + mtx.inputs[0].script, KeyRing.fromPublic(directTrust.from)); + assert(success); + + return mtx; } - parseTXAsTrustIncrease(tx : bcoin$TX) : (TrustChange | null) { + parseTXAsTrustIncrease(tx : bcoin$TX) : (DirectTrust | null) { if (tx.inputs.length !== 1) return null; if (tx.inputs[0].getType() !== 'pubkeyhash') return null; // TODO: This is unreliable - if (this.TXToTrust[tx.inputs[0].prevout.hash.toString('hex')]) return null; + if (this.trustDB.isTrustTX(tx.inputs[0].prevout.hash.toString('hex'))) return null; var sender = tx.inputs[0].getAddress().toBase58(); if (tx.outputs.length === 0 || tx.outputs.length > 2) return null; @@ -164,50 +159,38 @@ class TrustIsRisk { return trustOutputs[0]; } - getTrustDecreases(tx : bcoin$TX) : TrustChange[] { + getTrustDecreases(tx : bcoin$TX) : DirectTrust[] { var inputTrusts = this.getInputTrusts(tx.inputs); return inputTrusts.map(this.getTrustDecrease.bind(this, tx)); } getInputTrusts(inputs : bcoin$Input[]) : DirectTrust[] { return inputs.map((input) => { - var trust = this.TXToTrust[input.prevout.hash.toString('hex')] + var trust = this.trustDB.getDirectTrustByOutpoint(input.prevout); if (trust && trust.outputIndex === input.prevout.index) return trust; else return null; }).filter(Boolean); } - getTrustDecrease(tx : bcoin$TX, prevTrust : DirectTrust) : TrustChange { + getTrustDecrease(tx : bcoin$TX, prevTrust : DirectTrust) : DirectTrust { var txHash = tx.hash().toString('hex'); - var nullifyTrust = { - from: prevTrust.from, - to: prevTrust.to, - amount: -prevTrust.amount, - txHash, - outputIndex: null - }; + var nullTrust = prevTrust.getNullifying(txHash); - if (tx.inputs.length !== 1) return nullifyTrust; + if (tx.inputs.length !== 1) return nullTrust; - var trustOutputs = this.searchForDirectTrustOutputs(tx, prevTrust.from, prevTrust.to); - if (trustOutputs.length != 1) return nullifyTrust; + var trustOutputs = this.searchForDirectTrustOutputs(tx, prevTrust.getFromEntity(), + prevTrust.getToEntity()); + if (trustOutputs.length != 1) return nullTrust; var nextTrust = trustOutputs[0]; - assert(nextTrust.from === prevTrust.from); - assert(nextTrust.to === prevTrust.to); + nextTrust.prev = prevTrust; - var trustAmountChange = nextTrust.amount - prevTrust.amount; - assert(trustAmountChange <= 0); - return { - from: nextTrust.from, - to: nextTrust.to, - amount: trustAmountChange, - txHash, - outputIndex: nextTrust.outputIndex - } + assert(nextTrust.amount - prevTrust.amount <= 0); + return nextTrust; } - + // Looks for direct trust outputs that originate from a sender in an array of bitcoin outputs. + // Returns a list of the corresponding DirectTrust objects. // If the recipient parameter is set, it will limit the results only to the outputs being sent to // the recipient. searchForDirectTrustOutputs(tx : bcoin$TX, sender : Entity, recipient : ?Entity) : DirectTrust[] { @@ -233,35 +216,30 @@ class TrustIsRisk { var output = tx.outputs[outputIndex]; if (output.getType() !== 'multisig') return null; - var addressA = Address.fromHash(bcoin.crypto.hash160(output.script.get(1))).toBase58(); - var addressB = Address.fromHash(bcoin.crypto.hash160(output.script.get(2))).toBase58(); - - if (addressA === addressB) return null; + var entities = [1, 2].map((i) => helpers.pubKeyToEntity(output.script.get(i))); + if (entities[0] === entities[1]) return null; - var recipient; - if (addressA === sender) recipient = addressB; - else if (addressB === sender) recipient = addressA; + var from, to; + if (entities[0] === sender) { + from = output.script.get(1); + to = output.script.get(2); + } + else if (entities[1] === sender) { + from = output.script.get(2); + to = output.script.get(1); + } else return null; - return { - from: sender, - to: recipient, + return new DirectTrust({ + from, + to, amount: Number(output.value), - txHash, - outputIndex - }; - } - getGraphWeightMatrix() : number[][] { - var entitiesArr = this.entities.slice(0, this.entities.length); - return entitiesArr.map((from) => { - return entitiesArr.map((to) => this.getDirectTrust(from, to)); + txHash, + outputIndex, + script: output.script }); } - - getGraphWeightMatrixIndex(entity : Entity) : number { - return this.entities.rank(entity); - } } module.exports = TrustIsRisk; diff --git a/src/types.js b/src/types.js new file mode 100644 index 0000000..1dc9d64 --- /dev/null +++ b/src/types.js @@ -0,0 +1,8 @@ +//@flow + +// base58 bitcoin address: +export type Entity = string; + +export type TXHash = string; + +export type PubKey = Buffer; diff --git a/test/trust_is_risk.js b/test/trust_is_risk.js index 88c1d26..bb2b11a 100644 --- a/test/trust_is_risk.js +++ b/test/trust_is_risk.js @@ -110,6 +110,11 @@ describe('TrustIsRisk', () => { should(tir.getDirectTrust(alice, bob)).equal(0); }); + it('which has been processed before throws', () => { + var tx = trustIncreasingMTX.toTX(); + tir.addTX(tx); + should.throws(() => tir.addTX(tx), /already processed/i); + }); }); describe('with a trust decreasing transaction', () => { @@ -183,6 +188,66 @@ describe('TrustIsRisk', () => { }); }); + describe('.getTrustDecreasingMTX()', () => { + var trustTXs = []; + beforeEach(() => { + var tx; + + tx = trustIncreasingTX; + trustTXs.push(tx); + tir.addTX(tx); + + trustIncreasingMTX.outputs[0].value = 100; + tx = trustIncreasingMTX.toTX(); + trustTXs.push(tx); + tir.addTX(tx); + + trustIncreasingMTX.outputs[0].value = 500; + tx = trustIncreasingMTX.toTX(); + trustTXs.push(tx); + tir.addTX(tx); + + // Total trust 642 + }); + + it('creates correct trust decreasing transactions', () => { + // Should spend the entire first transaction and 40 from the second transaction + var mtxs = tir.getTrustDecreasingMTXs(addr.alice.pubkey, addr.bob.pubkey, 82); + mtxs.length.should.equal(2); + var mtx = mtxs[0]; + + mtx.inputs.length.should.equal(1); + mtx.inputs[0].prevout.should.have.properties({ + hash: trustTXs[0].hash().toString('hex'), + index: 0 + }); + + mtx.outputs.length.should.equal(1); // Single P2PKH output + mtx.outputs[0].getType().should.equal('pubkeyhash'); + mtx.outputs[0].getAddress().toBase58().should.equal(alice); + mtx.outputs[0].value.should.equal(42); + + mtx = mtxs[1]; + mtx.outputs.length.should.equal(2); // One P2PKH output and one multisig trust output + mtx.outputs[1].script.toString().should.equal(trustTXs[1].outputs[0].script.toString()); + mtx.outputs[1].value.should.equal(60); + mtx.outputs[0].getType().should.equal('pubkeyhash'); + mtx.outputs[0].getAddress().toBase58().should.equal(alice); + mtx.outputs[0].value.should.equal(40); + }); + + it('throws when trying to decrease self-trust', () => { + should.throws(() => tir.getTrustDecreasingMTXs(addr.alice.pubkey, addr.alice.pubkey, 10) + , /self-trust/i); + }); + + it('throws when there is not enough trust', () => { + should.throws(() => tir.getTrustDecreasingMTXs(addr.alice.pubkey, addr.bob.pubkey, 700) + , /insufficient trust/i); + + }); + }); + describe('.getTrust()', () => { it('returns zero for two arbitary parties that do not trust each other', () => { should(tir.getTrust(alice, bob)).equal(0); From bdee7b066e4ba6a61ff152f1c3968b2488117d43 Mon Sep 17 00:00:00 2001 From: Christos Porios Date: Sat, 27 May 2017 20:04:55 +0000 Subject: [PATCH 09/20] Implement trust stealing --- lib/trust_is_risk.js | 14 ++++++++------ src/trust_is_risk.js | 14 ++++++++------ test/trust_is_risk.js | 33 +++++++++++++++++++++++++++------ 3 files changed, 43 insertions(+), 18 deletions(-) diff --git a/lib/trust_is_risk.js b/lib/trust_is_risk.js index 278f64a..963c337 100644 --- a/lib/trust_is_risk.js +++ b/lib/trust_is_risk.js @@ -97,10 +97,11 @@ class TrustIsRisk { return mtx; } - getTrustDecreasingMTXs(from , to , trustDecreaseAmount ) + getTrustDecreasingMTXs(from , to , trustDecreaseAmount , payTo ) { - var fromAddress = Address.fromHash(bcoin.crypto.hash160(from)).toBase58(); - var toAddress = Address.fromHash(bcoin.crypto.hash160(to)).toBase58(); + var fromAddress = helpers.pubKeyToEntity(from); + var toAddress = helpers.pubKeyToEntity(to); + if (fromAddress === toAddress) throw new Error('Can\'t decrease self-trust'); var existingTrustAmount = this.trustDB.getDirectTrustAmount(fromAddress, toAddress); @@ -111,11 +112,12 @@ class TrustIsRisk { var decrease = Math.min(trustDecreaseAmount, directTrust.amount); if (decrease === 0) return null; trustDecreaseAmount -= decrease; - return this.getTrustDecreasingMTX(directTrust, decrease); + return this.getTrustDecreasingMTX(directTrust, decrease, payTo); }).filter(Boolean); } - getTrustDecreasingMTX(directTrust , decreaseAmount ) { + getTrustDecreasingMTX(directTrust , decreaseAmount , payTo ) { + if (!payTo) payTo = directTrust.getFromEntity(); var remainingTrustAmount = directTrust.amount - decreaseAmount; var mtx = new MTX({ @@ -123,7 +125,7 @@ class TrustIsRisk { Input.fromOutpoint(new Outpoint(directTrust.txHash, directTrust.outputIndex)) ], outputs: [new Output({ - script: bcoin.script.fromPubkeyhash(bcoin.crypto.hash160(directTrust.from)), + script: bcoin.script.fromPubkeyhash(Address.fromBase58(payTo).hash), value: decreaseAmount })] }); diff --git a/src/trust_is_risk.js b/src/trust_is_risk.js index 3f35e59..ac9b1fd 100644 --- a/src/trust_is_risk.js +++ b/src/trust_is_risk.js @@ -97,10 +97,11 @@ class TrustIsRisk { return mtx; } - getTrustDecreasingMTXs(from : PubKey, to : PubKey, trustDecreaseAmount : number) + getTrustDecreasingMTXs(from : PubKey, to : PubKey, trustDecreaseAmount : number, payTo : ?Entity) : bcoin$MTX[] { - var fromAddress = Address.fromHash(bcoin.crypto.hash160(from)).toBase58(); - var toAddress = Address.fromHash(bcoin.crypto.hash160(to)).toBase58(); + var fromAddress = helpers.pubKeyToEntity(from); + var toAddress = helpers.pubKeyToEntity(to); + if (fromAddress === toAddress) throw new Error('Can\'t decrease self-trust'); var existingTrustAmount = this.trustDB.getDirectTrustAmount(fromAddress, toAddress); @@ -111,11 +112,12 @@ class TrustIsRisk { var decrease = Math.min(trustDecreaseAmount, directTrust.amount); if (decrease === 0) return null; trustDecreaseAmount -= decrease; - return this.getTrustDecreasingMTX(directTrust, decrease); + return this.getTrustDecreasingMTX(directTrust, decrease, payTo); }).filter(Boolean); } - getTrustDecreasingMTX(directTrust : DirectTrust, decreaseAmount : number) { + getTrustDecreasingMTX(directTrust : DirectTrust, decreaseAmount : number, payTo : ?Entity) { + if (!payTo) payTo = directTrust.getFromEntity(); var remainingTrustAmount = directTrust.amount - decreaseAmount; var mtx = new MTX({ @@ -123,7 +125,7 @@ class TrustIsRisk { Input.fromOutpoint(new Outpoint(directTrust.txHash, directTrust.outputIndex)) ], outputs: [new Output({ - script: bcoin.script.fromPubkeyhash(bcoin.crypto.hash160(directTrust.from)), + script: bcoin.script.fromPubkeyhash(Address.fromBase58(payTo).hash), value: decreaseAmount })] }); diff --git a/test/trust_is_risk.js b/test/trust_is_risk.js index bb2b11a..48a199d 100644 --- a/test/trust_is_risk.js +++ b/test/trust_is_risk.js @@ -189,9 +189,10 @@ describe('TrustIsRisk', () => { }); describe('.getTrustDecreasingMTX()', () => { - var trustTXs = []; + var trustTXs; beforeEach(() => { var tx; + trustTXs = []; tx = trustIncreasingTX; trustTXs.push(tx); @@ -210,10 +211,13 @@ describe('TrustIsRisk', () => { // Total trust 642 }); - it('creates correct trust decreasing transactions', () => { - // Should spend the entire first transaction and 40 from the second transaction - var mtxs = tir.getTrustDecreasingMTXs(addr.alice.pubkey, addr.bob.pubkey, 82); + // Helper specific to the next couple of tests: + // Checks that mtxs is a list of two trust decreasing transactions. The first one spends the + // entire first trust increasing transaction, and the second spends part of the second. + // Also checks that the reduced trust is sent via P2PKH outputs to the correct recipient. + var checkMTXs = (mtxs, recipient) => { mtxs.length.should.equal(2); + var mtx = mtxs[0]; mtx.inputs.length.should.equal(1); @@ -224,16 +228,33 @@ describe('TrustIsRisk', () => { mtx.outputs.length.should.equal(1); // Single P2PKH output mtx.outputs[0].getType().should.equal('pubkeyhash'); - mtx.outputs[0].getAddress().toBase58().should.equal(alice); + mtx.outputs[0].getAddress().toBase58().should.equal(recipient); mtx.outputs[0].value.should.equal(42); mtx = mtxs[1]; + + mtx.inputs.length.should.equal(1); + mtx.inputs[0].prevout.should.have.properties({ + hash: trustTXs[1].hash().toString('hex'), + index: 0 + }); + mtx.outputs.length.should.equal(2); // One P2PKH output and one multisig trust output mtx.outputs[1].script.toString().should.equal(trustTXs[1].outputs[0].script.toString()); mtx.outputs[1].value.should.equal(60); mtx.outputs[0].getType().should.equal('pubkeyhash'); - mtx.outputs[0].getAddress().toBase58().should.equal(alice); + mtx.outputs[0].getAddress().toBase58().should.equal(recipient); mtx.outputs[0].value.should.equal(40); + }; + + it('creates correct trust decreasing transactions', () => { + var mtxs = tir.getTrustDecreasingMTXs(addr.alice.pubkey, addr.bob.pubkey, 82); + checkMTXs(mtxs, alice); + }); + + it('creates correct trust stealing transactions', () => { + var mtxs = tir.getTrustDecreasingMTXs(addr.alice.pubkey, addr.bob.pubkey, 82, charlie); + checkMTXs(mtxs, charlie); }); it('throws when trying to decrease self-trust', () => { From 885d11fe2b64f73595f3ac2485d362b27a1976ea Mon Sep 17 00:00:00 2001 From: Christos Porios Date: Mon, 29 May 2017 16:01:02 +0000 Subject: [PATCH 10/20] Implement a complete end to end regtest test --- flow-typed/npm/bcoin_vx.x.x.js | 18 +++- lib/direct_trust.js | 10 +- lib/helpers.js | 4 +- lib/trust_db.js | 7 +- lib/trust_is_risk.js | 99 +++++++++++++------ lib/types.js | 2 +- package.json | 2 +- src/direct_trust.js | 10 +- src/helpers.js | 4 +- src/trust_db.js | 7 +- src/trust_is_risk.js | 99 +++++++++++++------ src/types.js | 2 +- test/full_node.js | 156 ++++++++++++++++++++++++++++-- test/graphs/nobodyLikesFrank.json | 2 +- test/helpers.js | 52 +++++----- test/trust_is_risk.js | 101 +++++++++---------- 16 files changed, 401 insertions(+), 174 deletions(-) diff --git a/flow-typed/npm/bcoin_vx.x.x.js b/flow-typed/npm/bcoin_vx.x.x.js index 74a3e2e..87c534d 100644 --- a/flow-typed/npm/bcoin_vx.x.x.js +++ b/flow-typed/npm/bcoin_vx.x.x.js @@ -6,11 +6,14 @@ type Network = any; declare class bcoin$FullNode { on(eventName : string, eventHandler : Function) : void; getTX(hash : Hash) : Promise; + getCoin(hash : Hash, index : number) : bcoin$Coin; } declare class bcoin$Address { toBase58() : string; + hash : Buffer; static fromHash(Hash) : bcoin$Address; + static fromBase58(string) : bcoin$Address; } declare class bcoin$TX { @@ -29,9 +32,11 @@ declare class bcoin$MTX { template(ring : bcoin$KeyRing) : number; scriptVector(outputScript : bcoin$Script, inputScript : bcoin$Script, ring : bcoin$KeyRing) : boolean; addOutput(output : bcoin$Output) : void; + addCoin(coin : bcoin$Coin) : void; + sign(ring : bcoin$KeyRing) : number; + signInput(index : number, coin : bcoin$Coin, keyRing : bcoin$KeyRing) : boolean; } - declare class bcoin$Output { script : bcoin$Script; value : number; @@ -65,6 +70,14 @@ declare class bcoin$Outpoint { declare class bcoin$KeyRing { static fromPrivate(key : Buffer, compressed : ?boolean, network : ?Network) : bcoin$KeyRing; static fromPublic(key : Buffer, network : ?Network) : bcoin$KeyRing; + + getPublicKey() : Buffer; + getPrivateKey() : Buffer; +} + +declare class bcoin$Coin extends bcoin$Output { + script : bcoin$Script; + value : number; } declare module 'bcoin' { @@ -78,7 +91,8 @@ declare module 'bcoin' { Output : Class, Input : Class, Outpoint : Class, - KeyRing: Class + KeyRing : Class, + Coin : Class }, crypto : { hash160(str : (string | Buffer)) : Hash diff --git a/lib/direct_trust.js b/lib/direct_trust.js index 6545c0b..ad676f4 100644 --- a/lib/direct_trust.js +++ b/lib/direct_trust.js @@ -1,12 +1,12 @@ // - + var bcoin = require('bcoin'); var assert = require('assert'); var helpers = require('./helpers'); - - + + @@ -18,8 +18,8 @@ var helpers = require('./helpers'); class DirectTrust { - - + + // Every DT is associated with a transaction output, except for non-standard trust decreasing diff --git a/lib/helpers.js b/lib/helpers.js index f7c1443..27f228c 100644 --- a/lib/helpers.js +++ b/lib/helpers.js @@ -1,10 +1,10 @@ // - + var bcoin = require('bcoin'); var Address = bcoin.primitives.Address; var helpers = { - pubKeyToEntity: (key ) => { + pubKeyToEntity: (key ) => { return Address.fromHash(bcoin.crypto.hash160(key)).toBase58() } }; diff --git a/lib/trust_db.js b/lib/trust_db.js index ccd282b..a19a92d 100644 --- a/lib/trust_db.js +++ b/lib/trust_db.js @@ -1,5 +1,5 @@ // - + var assert = require('assert'); var SortedSet = require('sorted-set'); var maxFlow = require('graph-theory-ford-fulkerson'); @@ -87,6 +87,11 @@ class TrustDB { return this.txToDirectTrust.has(txHash); } + isTrustOutput(txHash , outputIndex ) { + var trust = this.txToDirectTrust.get(txHash); + return trust !== undefined && trust.outputIndex === outputIndex; + } + add(trust ) { var from = trust.getFromEntity(); var to = trust.getToEntity(); diff --git a/lib/trust_is_risk.js b/lib/trust_is_risk.js index 963c337..f994941 100644 --- a/lib/trust_is_risk.js +++ b/lib/trust_is_risk.js @@ -1,5 +1,5 @@ // - + var bcoin = require('bcoin'); var Address = bcoin.primitives.Address; var KeyRing = bcoin.primitives.KeyRing; @@ -7,6 +7,7 @@ var MTX = bcoin.primitives.MTX; var Input = bcoin.primitives.Input; var Output = bcoin.primitives.Output; var Outpoint = bcoin.primitives.Outpoint; +var Coin = bcoin.primitives.Coin; var assert = require('assert'); var helpers = require('./helpers'); var TrustDB = require('./trust_db'); @@ -14,21 +15,21 @@ var DirectTrust = require('./direct_trust'); class TrustIsRisk { - + constructor(node ) { this.node = node; - this.trustDB = new TrustDB(); + this.db = new TrustDB(); this.node.on('tx', this.addTX.bind(this)); } getTrust(from , to ) { - return this.trustDB.getTrustAmount(from, to); + return this.db.getTrustAmount(from, to); } getDirectTrust(from , to ) { - return this.trustDB.getDirectTrustAmount(from, to); + return this.db.getDirectTrustAmount(from, to); } // Attempts to parse a bitcoin transaction as a trust change and adds it to the trust network @@ -38,14 +39,14 @@ class TrustIsRisk { // Throws an error if the transaction was processed earlier. addTX(tx ) { var txHash = tx.hash().toString('hex'); - if (this.trustDB.isTrustTX(txHash)) { + if (this.db.isTrustTX(txHash)) { throw new Error(`Transaction already processed: Transaction ${txHash} already carries trust`); } var directTrusts = this.getDirectTrusts(tx); if (directTrusts.length === 0) return false; else { - directTrusts.map(this.trustDB.add.bind(this.trustDB)); + directTrusts.map(this.db.add.bind(this.db)); return true; } } @@ -63,18 +64,21 @@ class TrustIsRisk { } } - async getTrustIncreasingMTX(from , to , outpoint , trustAmount ) + // Returns a promise resolving to a mutable transaction object, which increases a trust + // relationship by some amount. It will spend the outpoint, which must reference a P2PKH output + // payable to the sender. + // Any satoshis not spent will be returned to the sender, minus the fees, via P2PKH. + async getTrustIncreasingMTX(fromPrivate , to , outpoint , + trustAmount , fee ) { - var prevTX = await this.node.getTX(outpoint.hash); - if (!prevTX) throw new Error('Could not find transaction'); + if (!fee) fee = 1000; // TODO: estimate this + var coin = await this.node.getCoin(outpoint.hash, outpoint.index); + if (!coin) throw new Error('Could not find coin'); - var prevOutput = prevTX.outputs[outpoint.index]; - if (!prevOutput) throw new Error('Could not find transaction output'); + var fromKeyRing = KeyRing.fromPrivate(fromPrivate); + var from = fromKeyRing.getPublicKey(); var mtx = new MTX({ - inputs: [ - Input.fromOutpoint(outpoint) - ], outputs: [ new Output({ script: bcoin.script.fromMultisig(1, 2, [from, to]), @@ -83,7 +87,7 @@ class TrustIsRisk { ] }); - var changeAmount = prevOutput.value - trustAmount; + var changeAmount = coin.value - trustAmount - fee; if (changeAmount) { mtx.addOutput(new Output({ script: bcoin.script.fromPubkeyhash(bcoin.crypto.hash160(from)), @@ -91,45 +95,71 @@ class TrustIsRisk { })); } - var success = mtx.scriptVector(prevOutput.script, mtx.inputs[0].script, KeyRing.fromPublic(from)); + mtx.addCoin(coin); + var success = mtx.scriptVector(coin.script, mtx.inputs[0].script, fromKeyRing); assert(success); + var signedCount = mtx.sign(fromKeyRing); + assert(signedCount === 1); + return mtx; } - getTrustDecreasingMTXs(from , to , trustDecreaseAmount , payTo ) - { - var fromAddress = helpers.pubKeyToEntity(from); - var toAddress = helpers.pubKeyToEntity(to); + // Returns an array of trust-decreasing mutable transaction objects, which reduce a trust + // relationship by the amount specified. The payee will receive the amount deducted minus the + // transaction fees via P2PKH. + // If steal is undefined or set to false, then the `from` key is expected to be a private key and + // the `to` key is expected to be a public key. If steal is set to true, then `from` is expected + // to be a public key and `to` is expected to be a private key. The private key will be used to + // sign the transaction. + getTrustDecreasingMTXs(from , to , trustDecreaseAmount , payee , + steal , fee ) { + if (steal === undefined) steal = false; + + var signingKeyRing, fromKeyRing, toKeyRing; + if (!steal) { + signingKeyRing = KeyRing.fromPrivate(from); + fromKeyRing = KeyRing.fromPrivate(from); + toKeyRing = KeyRing.fromPublic(to); + } else { + signingKeyRing = KeyRing.fromPrivate(to); + fromKeyRing = KeyRing.fromPublic(from); + toKeyRing = KeyRing.fromPrivate(to); + } + + var fromAddress = helpers.pubKeyToEntity(fromKeyRing.getPublicKey()); + var toAddress = helpers.pubKeyToEntity(toKeyRing.getPublicKey()); if (fromAddress === toAddress) throw new Error('Can\'t decrease self-trust'); - var existingTrustAmount = this.trustDB.getDirectTrustAmount(fromAddress, toAddress); + var existingTrustAmount = this.db.getDirectTrustAmount(fromAddress, toAddress); if (existingTrustAmount < trustDecreaseAmount) throw new Error('Insufficient trust'); - var directTrusts = this.trustDB.getSpendableDirectTrusts(fromAddress, toAddress); + var directTrusts = this.db.getSpendableDirectTrusts(fromAddress, toAddress); return directTrusts.map((directTrust) => { var decrease = Math.min(trustDecreaseAmount, directTrust.amount); if (decrease === 0) return null; trustDecreaseAmount -= decrease; - return this.getTrustDecreasingMTX(directTrust, decrease, payTo); + return this.getTrustDecreasingMTX(directTrust, decrease, payee, signingKeyRing, fee); }).filter(Boolean); } - getTrustDecreasingMTX(directTrust , decreaseAmount , payTo ) { - if (!payTo) payTo = directTrust.getFromEntity(); - var remainingTrustAmount = directTrust.amount - decreaseAmount; + getTrustDecreasingMTX(directTrust , decreaseAmount , payee , + signingKeyRing , fee ) { + if (!payee) payee = directTrust.getFromEntity(); + if (!fee) fee = 1000; // TODO: estimate this var mtx = new MTX({ inputs: [ Input.fromOutpoint(new Outpoint(directTrust.txHash, directTrust.outputIndex)) ], outputs: [new Output({ - script: bcoin.script.fromPubkeyhash(Address.fromBase58(payTo).hash), - value: decreaseAmount + script: bcoin.script.fromPubkeyhash(Address.fromBase58(payee).hash), + value: decreaseAmount - fee })] }); + var remainingTrustAmount = directTrust.amount - decreaseAmount; if (remainingTrustAmount > 0) { mtx.addOutput(new Output({ script: bcoin.script.fromMultisig(1, 2, [directTrust.from, directTrust.to]), @@ -141,13 +171,18 @@ class TrustIsRisk { mtx.inputs[0].script, KeyRing.fromPublic(directTrust.from)); assert(success); + success = mtx.signInput(0, new Coin({script: directTrust.script, value: directTrust.amount}), + signingKeyRing); + assert(success); + return mtx; } parseTXAsTrustIncrease(tx ) { if (tx.inputs.length !== 1) return null; - if (tx.inputs[0].getType() !== 'pubkeyhash') return null; // TODO: This is unreliable - if (this.trustDB.isTrustTX(tx.inputs[0].prevout.hash.toString('hex'))) return null; + var input = tx.inputs[0]; + if (input.getType() !== 'pubkeyhash') return null; // TODO: This is unreliable + if (this.db.isTrustOutput(input.prevout.hash.toString('hex'), input.prevout.index)) return null; var sender = tx.inputs[0].getAddress().toBase58(); if (tx.outputs.length === 0 || tx.outputs.length > 2) return null; @@ -168,7 +203,7 @@ class TrustIsRisk { getInputTrusts(inputs ) { return inputs.map((input) => { - var trust = this.trustDB.getDirectTrustByOutpoint(input.prevout); + var trust = this.db.getDirectTrustByOutpoint(input.prevout); if (trust && trust.outputIndex === input.prevout.index) return trust; else return null; }).filter(Boolean); diff --git a/lib/types.js b/lib/types.js index 44ee569..79f17ec 100644 --- a/lib/types.js +++ b/lib/types.js @@ -5,4 +5,4 @@ - + diff --git a/package.json b/package.json index e5bc1ef..6691347 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "lib/index.js", "scripts": { "build": "flow-remove-types -d lib/ src/", - "test": "npm run build && mocha -t 5000", + "test": "npm run build && mocha -t 10000", "prepublish": "npm run build", "typecheck": "flow check" }, diff --git a/src/direct_trust.js b/src/direct_trust.js index fe3e687..153cc83 100644 --- a/src/direct_trust.js +++ b/src/direct_trust.js @@ -1,12 +1,12 @@ // @flow -import type {Entity, TXHash, PubKey} from "./types" +import type {Entity, TXHash, Key} from "./types" var bcoin = require('bcoin'); var assert = require('assert'); var helpers = require('./helpers'); type DirectTrustOptions = { - from : PubKey, - to : PubKey, + from : Key, + to : Key, amount : number, txHash : string, @@ -18,8 +18,8 @@ type DirectTrustOptions = { } class DirectTrust { - from : PubKey - to : PubKey + from : Key + to : Key amount : number // Every DT is associated with a transaction output, except for non-standard trust decreasing diff --git a/src/helpers.js b/src/helpers.js index fcf67bc..7723bff 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -1,10 +1,10 @@ // @flow -import type {Entity, TXHash, PubKey} from "./types" +import type {Entity, TXHash, Key} from "./types" var bcoin = require('bcoin'); var Address = bcoin.primitives.Address; var helpers = { - pubKeyToEntity: (key : PubKey) : Entity => { + pubKeyToEntity: (key : Key) : Entity => { return Address.fromHash(bcoin.crypto.hash160(key)).toBase58() } }; diff --git a/src/trust_db.js b/src/trust_db.js index bca781c..bf610e5 100644 --- a/src/trust_db.js +++ b/src/trust_db.js @@ -1,5 +1,5 @@ // @flow -import type {Entity, TXHash, PubKey} from "./types" +import type {Entity, TXHash, Key} from "./types" var assert = require('assert'); var SortedSet = require('sorted-set'); var maxFlow = require('graph-theory-ford-fulkerson'); @@ -87,6 +87,11 @@ class TrustDB { return this.txToDirectTrust.has(txHash); } + isTrustOutput(txHash : string, outputIndex : number) : boolean { + var trust = this.txToDirectTrust.get(txHash); + return trust !== undefined && trust.outputIndex === outputIndex; + } + add(trust : DirectTrust) { var from = trust.getFromEntity(); var to = trust.getToEntity(); diff --git a/src/trust_is_risk.js b/src/trust_is_risk.js index ac9b1fd..3ce0959 100644 --- a/src/trust_is_risk.js +++ b/src/trust_is_risk.js @@ -1,5 +1,5 @@ // @flow -import type {Entity, TXHash, PubKey} from "./types" +import type {Entity, TXHash, Key} from "./types" var bcoin = require('bcoin'); var Address = bcoin.primitives.Address; var KeyRing = bcoin.primitives.KeyRing; @@ -7,6 +7,7 @@ var MTX = bcoin.primitives.MTX; var Input = bcoin.primitives.Input; var Output = bcoin.primitives.Output; var Outpoint = bcoin.primitives.Outpoint; +var Coin = bcoin.primitives.Coin; var assert = require('assert'); var helpers = require('./helpers'); var TrustDB = require('./trust_db'); @@ -14,21 +15,21 @@ var DirectTrust = require('./direct_trust'); class TrustIsRisk { node : bcoin$FullNode - trustDB : TrustDB + db : TrustDB constructor(node : bcoin$FullNode) { this.node = node; - this.trustDB = new TrustDB(); + this.db = new TrustDB(); this.node.on('tx', this.addTX.bind(this)); } getTrust(from : Entity, to : Entity) { - return this.trustDB.getTrustAmount(from, to); + return this.db.getTrustAmount(from, to); } getDirectTrust(from : Entity, to : Entity) { - return this.trustDB.getDirectTrustAmount(from, to); + return this.db.getDirectTrustAmount(from, to); } // Attempts to parse a bitcoin transaction as a trust change and adds it to the trust network @@ -38,14 +39,14 @@ class TrustIsRisk { // Throws an error if the transaction was processed earlier. addTX(tx : bcoin$TX) : boolean { var txHash = tx.hash().toString('hex'); - if (this.trustDB.isTrustTX(txHash)) { + if (this.db.isTrustTX(txHash)) { throw new Error(`Transaction already processed: Transaction ${txHash} already carries trust`); } var directTrusts = this.getDirectTrusts(tx); if (directTrusts.length === 0) return false; else { - directTrusts.map(this.trustDB.add.bind(this.trustDB)); + directTrusts.map(this.db.add.bind(this.db)); return true; } } @@ -63,18 +64,21 @@ class TrustIsRisk { } } - async getTrustIncreasingMTX(from : PubKey, to : PubKey, outpoint : bcoin$Outpoint, trustAmount : number) + // Returns a promise resolving to a mutable transaction object, which increases a trust + // relationship by some amount. It will spend the outpoint, which must reference a P2PKH output + // payable to the sender. + // Any satoshis not spent will be returned to the sender, minus the fees, via P2PKH. + async getTrustIncreasingMTX(fromPrivate : Key, to : Key, outpoint : bcoin$Outpoint, + trustAmount : number, fee : ?number) : Promise { - var prevTX = await this.node.getTX(outpoint.hash); - if (!prevTX) throw new Error('Could not find transaction'); + if (!fee) fee = 1000; // TODO: estimate this + var coin = await this.node.getCoin(outpoint.hash, outpoint.index); + if (!coin) throw new Error('Could not find coin'); - var prevOutput = prevTX.outputs[outpoint.index]; - if (!prevOutput) throw new Error('Could not find transaction output'); + var fromKeyRing = KeyRing.fromPrivate(fromPrivate); + var from = fromKeyRing.getPublicKey(); var mtx = new MTX({ - inputs: [ - Input.fromOutpoint(outpoint) - ], outputs: [ new Output({ script: bcoin.script.fromMultisig(1, 2, [from, to]), @@ -83,7 +87,7 @@ class TrustIsRisk { ] }); - var changeAmount = prevOutput.value - trustAmount; + var changeAmount = coin.value - trustAmount - fee; if (changeAmount) { mtx.addOutput(new Output({ script: bcoin.script.fromPubkeyhash(bcoin.crypto.hash160(from)), @@ -91,45 +95,71 @@ class TrustIsRisk { })); } - var success = mtx.scriptVector(prevOutput.script, mtx.inputs[0].script, KeyRing.fromPublic(from)); + mtx.addCoin(coin); + var success = mtx.scriptVector(coin.script, mtx.inputs[0].script, fromKeyRing); assert(success); + var signedCount = mtx.sign(fromKeyRing); + assert(signedCount === 1); + return mtx; } - getTrustDecreasingMTXs(from : PubKey, to : PubKey, trustDecreaseAmount : number, payTo : ?Entity) - : bcoin$MTX[] { - var fromAddress = helpers.pubKeyToEntity(from); - var toAddress = helpers.pubKeyToEntity(to); + // Returns an array of trust-decreasing mutable transaction objects, which reduce a trust + // relationship by the amount specified. The payee will receive the amount deducted minus the + // transaction fees via P2PKH. + // If steal is undefined or set to false, then the `from` key is expected to be a private key and + // the `to` key is expected to be a public key. If steal is set to true, then `from` is expected + // to be a public key and `to` is expected to be a private key. The private key will be used to + // sign the transaction. + getTrustDecreasingMTXs(from : Key, to : Key, trustDecreaseAmount : number, payee : ?Entity, + steal : ?boolean, fee : ?number) : bcoin$MTX[] { + if (steal === undefined) steal = false; + + var signingKeyRing, fromKeyRing, toKeyRing; + if (!steal) { + signingKeyRing = KeyRing.fromPrivate(from); + fromKeyRing = KeyRing.fromPrivate(from); + toKeyRing = KeyRing.fromPublic(to); + } else { + signingKeyRing = KeyRing.fromPrivate(to); + fromKeyRing = KeyRing.fromPublic(from); + toKeyRing = KeyRing.fromPrivate(to); + } + + var fromAddress = helpers.pubKeyToEntity(fromKeyRing.getPublicKey()); + var toAddress = helpers.pubKeyToEntity(toKeyRing.getPublicKey()); if (fromAddress === toAddress) throw new Error('Can\'t decrease self-trust'); - var existingTrustAmount = this.trustDB.getDirectTrustAmount(fromAddress, toAddress); + var existingTrustAmount = this.db.getDirectTrustAmount(fromAddress, toAddress); if (existingTrustAmount < trustDecreaseAmount) throw new Error('Insufficient trust'); - var directTrusts = this.trustDB.getSpendableDirectTrusts(fromAddress, toAddress); + var directTrusts = this.db.getSpendableDirectTrusts(fromAddress, toAddress); return directTrusts.map((directTrust) => { var decrease = Math.min(trustDecreaseAmount, directTrust.amount); if (decrease === 0) return null; trustDecreaseAmount -= decrease; - return this.getTrustDecreasingMTX(directTrust, decrease, payTo); + return this.getTrustDecreasingMTX(directTrust, decrease, payee, signingKeyRing, fee); }).filter(Boolean); } - getTrustDecreasingMTX(directTrust : DirectTrust, decreaseAmount : number, payTo : ?Entity) { - if (!payTo) payTo = directTrust.getFromEntity(); - var remainingTrustAmount = directTrust.amount - decreaseAmount; + getTrustDecreasingMTX(directTrust : DirectTrust, decreaseAmount : number, payee : ?Entity, + signingKeyRing : bcoin$KeyRing, fee : ?number) { + if (!payee) payee = directTrust.getFromEntity(); + if (!fee) fee = 1000; // TODO: estimate this var mtx = new MTX({ inputs: [ Input.fromOutpoint(new Outpoint(directTrust.txHash, directTrust.outputIndex)) ], outputs: [new Output({ - script: bcoin.script.fromPubkeyhash(Address.fromBase58(payTo).hash), - value: decreaseAmount + script: bcoin.script.fromPubkeyhash(Address.fromBase58(payee).hash), + value: decreaseAmount - fee })] }); + var remainingTrustAmount = directTrust.amount - decreaseAmount; if (remainingTrustAmount > 0) { mtx.addOutput(new Output({ script: bcoin.script.fromMultisig(1, 2, [directTrust.from, directTrust.to]), @@ -141,13 +171,18 @@ class TrustIsRisk { mtx.inputs[0].script, KeyRing.fromPublic(directTrust.from)); assert(success); + success = mtx.signInput(0, new Coin({script: directTrust.script, value: directTrust.amount}), + signingKeyRing); + assert(success); + return mtx; } parseTXAsTrustIncrease(tx : bcoin$TX) : (DirectTrust | null) { if (tx.inputs.length !== 1) return null; - if (tx.inputs[0].getType() !== 'pubkeyhash') return null; // TODO: This is unreliable - if (this.trustDB.isTrustTX(tx.inputs[0].prevout.hash.toString('hex'))) return null; + var input = tx.inputs[0]; + if (input.getType() !== 'pubkeyhash') return null; // TODO: This is unreliable + if (this.db.isTrustOutput(input.prevout.hash.toString('hex'), input.prevout.index)) return null; var sender = tx.inputs[0].getAddress().toBase58(); if (tx.outputs.length === 0 || tx.outputs.length > 2) return null; @@ -168,7 +203,7 @@ class TrustIsRisk { getInputTrusts(inputs : bcoin$Input[]) : DirectTrust[] { return inputs.map((input) => { - var trust = this.trustDB.getDirectTrustByOutpoint(input.prevout); + var trust = this.db.getDirectTrustByOutpoint(input.prevout); if (trust && trust.outputIndex === input.prevout.index) return trust; else return null; }).filter(Boolean); diff --git a/src/types.js b/src/types.js index 1dc9d64..1bb788b 100644 --- a/src/types.js +++ b/src/types.js @@ -5,4 +5,4 @@ export type Entity = string; export type TXHash = string; -export type PubKey = Buffer; +export type Key = Buffer; diff --git a/test/full_node.js b/test/full_node.js index d6308a8..f8792c5 100644 --- a/test/full_node.js +++ b/test/full_node.js @@ -1,30 +1,36 @@ var Trust = require('../'); +var helpers = require('../lib/helpers.js'); var bcoin = require('bcoin'); +var Script = bcoin.script; +var Address = bcoin.primitives.Address; +var KeyRing = bcoin.primitives.KeyRing; +var MTX = bcoin.primitives.MTX; +var Input = bcoin.primitives.Input; +var Output = bcoin.primitives.Output; +var Outpoint = bcoin.primitives.Outpoint; var testHelpers = require('./helpers'); var consensus = require('bcoin/lib/protocol/consensus'); var sinon = require('sinon'); var should = require('should'); require('should-sinon'); +const COIN = consensus.COIN; + describe('FullNode', () => { var node = null; var walletDB = null; sinon.spy(Trust.TrustIsRisk.prototype, 'addTX'); - beforeEach(() => testHelpers.getNode().then((n) => { + beforeEach('get node', () => testHelpers.getNode().then((n) => { node = n; })); - beforeEach(() => testHelpers.getWalletDB(node).then((w) => { + beforeEach('get walletDB', () => testHelpers.getWalletDB(node).then((w) => { walletDB = w; })); - afterEach(() => walletDB.close()); - afterEach(() => node.close()); - - it('should be a bcoin instance', () => { - should(node).be.an.instanceof(bcoin.fullnode); - }); + afterEach('close walletDB', async () => walletDB.close()); + afterEach('close node', async () => node.close()); it('should call trust.addTX() on every transaction', async function() { var sender = await testHelpers.getWallet(walletDB, 'sender'); @@ -39,7 +45,7 @@ describe('FullNode', () => { await testHelpers.time(100); await sender.send({ outputs: [{ - value: 10 * consensus.COIN, + value: 10 * COIN, address: receiver.getAddress('base58') }] }); @@ -47,4 +53,136 @@ describe('FullNode', () => { await testHelpers.time(100); node.trust.addTX.should.be.calledOnce(); }); + + describe('with the nobodyLikesFrank.json example', () => { + var addresses, rings = {}; + + beforeEach('apply graph transactions', async () => { + addresses = {}; + rings = {}; + + for (var i = 0; i < testHelpers.names.length; i++) { + var name = testHelpers.names[i]; + rings[name] = testHelpers.rings[i]; + addresses[name] = helpers.pubKeyToEntity(rings[name].getPublicKey()); + } + + // Alice mines three blocks, each rewards her with 50 spendable BTC + consensus.COINBASE_MATURITY = 0; + var coinbaseCoinsCount = 3; + var coinbaseHashes = []; + for(var i = 0; i < coinbaseCoinsCount; i++) { + var block = await testHelpers.mineBlock(node, addresses.alice); + coinbaseHashes.push(block.txs[0].hash()); + await testHelpers.time(200); + } + + // Alice sends 20 BTC to everyone (including herself) via P2PKH + var sendAmount = 20; + var outputs = testHelpers.names.map((name) => { + return new Output({ + script: Script.fromPubkeyhash(bcoin.crypto.hash160(rings[name].getPublicKey())), + value: sendAmount * consensus.COIN + }); + }); + + // We have to use a change output, because transaction with too large a fee are considered + // invalid. + var fee = 0.01; + var changeAmount = 50 * coinbaseCoinsCount - sendAmount * testHelpers.names.length - fee; + if (changeAmount >= 0.01) { + outputs.push(new Output({ + script: Script.fromPubkeyhash(bcoin.crypto.hash160(rings.alice.getPublicKey())), + value: changeAmount * consensus.COIN + })); + } + + // Use the coinbase coins as inputs + var coinbaseCoins = await Promise.all(coinbaseHashes.map((hash) => { + return node.getCoin(hash.toString('hex'), 0); + })); + var mtx = new MTX({outputs}); + coinbaseCoins.forEach((coin) => mtx.addCoin(coin)); + + var signedCount = mtx.sign(rings.alice); + signedCount.should.equal(coinbaseCoinsCount); + should(await mtx.verify()); + + var tx = mtx.toTX(); + node.sendTX(tx); + prevout = {}; + testHelpers.names.forEach((name) => { + prevout[name] = { + hash: tx.hash().toString('hex'), + index: testHelpers.names.indexOf(name) + }; + }); + await testHelpers.time(500); + + // Alice mines another block + await testHelpers.mineBlock(node, helpers.pubKeyToEntity(rings.alice.getPublicKey())); + await testHelpers.time(500); + + var graph = require('./graphs/nobodyLikesFrank.json'); + var promises = []; + for (var from in graph) { + var neighbours = graph[from]; + for (var to in neighbours) { + var value = neighbours[to]; + if (!value || value < 1) continue; + + var outpoint = new Outpoint(prevout[from].hash, prevout[from].index); + + var mtx = await node.trust.getTrustIncreasingMTX(rings[from].getPrivateKey(), + rings[to].getPublicKey(), outpoint, value * consensus.COIN); + + should(await mtx.verify()); + + // The change output from this transaction will be used in other transactions from the + // same origin. We therefore need to sleep until the transaction is added to the pool. + var tx = mtx.toTX(); + node.sendTX(tx); + await testHelpers.time(250); + + prevout[from] = {hash: tx.hash().toString('hex'), index: 1}; + } + } + //mtxs.forEach((mtx) => node.sendTX(mtx.toTX())); + await testHelpers.time(500); + + // Alice mines yet another block + await testHelpers.mineBlock(node, helpers.pubKeyToEntity(rings.alice.getPublicKey())); + await testHelpers.time(500); + }); + + it('computes trusts correctly', () => { + for (name in addresses) { // Add addresses to scope + eval(`var ${name} = "${addresses[name]}";`); + } + + should(node.trust.getTrust(alice, alice)).equal(Infinity); + should(node.trust.getTrust(alice, bob)).equal(10 * COIN); + should(node.trust.getTrust(alice, charlie)).equal(1 * COIN); + should(node.trust.getTrust(alice, frank)).equal(0); + should(node.trust.getTrust(alice, eve)).equal(6 * COIN); + + should(node.trust.getTrust(bob, alice)).equal(1 * COIN); + should(node.trust.getTrust(bob, eve)).equal(3 * COIN); + should(node.trust.getTrust(dave, eve)).equal(12 * COIN); + should(node.trust.getTrust(george, eve)).equal(0); + }); + + it('after decreasing some trusts computes trusts correctly', async () => { + var mtxs = node.trust.getTrustDecreasingMTXs(rings.alice.getPrivateKey(), + rings.bob.getPublicKey(), 3 * COIN); + mtxs.length.should.equal(1); + var mtx = mtxs[0]; + + should(await mtx.verify()); + node.sendTX(mtx.toTX()); + + await testHelpers.time(500); + should(node.trust.getTrust(addresses.alice, addresses.bob)).equal(7 * COIN); + }); + }); }); diff --git a/test/graphs/nobodyLikesFrank.json b/test/graphs/nobodyLikesFrank.json index 09865cc..9c246bc 100644 --- a/test/graphs/nobodyLikesFrank.json +++ b/test/graphs/nobodyLikesFrank.json @@ -18,7 +18,7 @@ }, "eve": {}, "frank": { - "charlie": 100 + "charlie": 10 }, "george": {} } diff --git a/test/helpers.js b/test/helpers.js index 40df046..b16a0da 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -1,28 +1,34 @@ var TrustIsRisk = require('../'); var WalletDB = require('bcoin/lib/wallet/walletdb'); var bcoin = require('bcoin'); +var KeyRing = bcoin.primitives.KeyRing; var assert = require('assert'); var testHelpers = { + names: ['alice', 'bob', 'charlie', 'dave', 'eve', 'frank', 'george'], + rings: [ + '02b8f07a401eca4888039b1898f94db44c43ccc6d3aa8b27e9b6ed7b377b24c0', + '2437025954568a8273968aa7535dbfc444fd8f8d0f5237cd96ac7234c77810ad', + '3BBA2AF9539D09B4FD2BDEA1D3A2CE4BF5D779831B8781EE2ACF9C03378B2AD7', + '19BD8D853FAEFDB9B01E4DE7F6096FF8F5F96D43E6564A5258307334A4AA59F3', + '0503054CF7EBB4E62191AF1D8DE97945178D3F465EE88EF1FB4E80A70CB4A49A', + '878DFE5B43AC858EA37B3A9EEBA9E244F1848A30F78B2E5AC5B3EBDE81AC7D45', + '1349A1318B1426E6F724CBFE7ECD2C46008A364A96C4BD20C83FC1C4EBB2EB4A' + ].map((key) => KeyRing.fromPrivate(new Buffer(key, 'hex'))), + getAddressFixtures: () => { - var names = ['alice', 'bob', 'charlie', 'dave', 'eve', 'frank', 'george']; - var pubkeys = [ - '02b8f07a401eca4888039b1898f94db44c43ccc6d3aa8b27e9b6ed7b377b24c083', - '2437025954568a8273968aa7535dbfc444fd8f8d0f5237cd96ac7234c77810ada53054a3654e669b', - '3BBA2AF9539D09B4FD2BDEA1D3A2CE4BF5D779831B8781EE2ACF9C03378B2AD782', - '19BD8D853FAEFDB9B01E4DE7F6096FF8F5F96D43E6564A5258307334A4AA59F351', - '0503054CF7EBB4E62191AF1D8DE97945178D3F465EE88EF1FB4E80A70CB4A49A84', - '878DFE5B43AC858EA37B3A9EEBA9E244F1848A30F78B2E5AC5B3EBDE81AC7D4516', - '1349A1318B1426E6F724CBFE7ECD2C46008A364A96C4BD20C83FC1C4EBB2EB4A93' - ]; - assert(pubkeys.length === names.length); + assert(testHelpers.rings.length === testHelpers.names.length); var addr = {}; - for (var i = 0; i < names.length; i++) { - var name = names[i]; + for (var i = 0; i < testHelpers.names.length; i++) { + var name = testHelpers.names[i]; + var pubKey = testHelpers.rings[i].getPublicKey(); + var privKey = testHelpers.rings[i].getPrivateKey(); + addr[name] = {}; - addr[name].pubkey = Buffer.from(pubkeys[i], 'hex'); - addr[name].base58 = bcoin.primitives.Address.fromHash(bcoin.crypto.hash160(addr[name].pubkey)).toString(); + addr[name].pubKey = pubKey; + addr[name].privKey = privKey; + addr[name].base58 = bcoin.primitives.Address.fromHash(bcoin.crypto.hash160(pubKey)).toString(); } return addr; @@ -65,6 +71,7 @@ var testHelpers = { mineBlock: async (node, rewardAddress) => { var block = await node.miner.mineBlock(node.chain.tip, rewardAddress); await node.chain.add(block); + return node.getBlock(node.chain.tip.hash); }, time: async (milliseconds) => { @@ -79,8 +86,7 @@ var testHelpers = { getP2PKHOutput: (to, value) => { var address = bcoin.primitives.Address.fromBase58(to); - var script = bcoin.script.fromString( - `OP_DUP OP_HASH160 ${testHelpers.bufferToScript(address.hash)} OP_EQUALVERIFY OP_CHECKSIG`); + var script = bcoin.script.fromPubkeyhash(address.hash); return new bcoin.primitives.Output({script, value}); }, @@ -104,12 +110,8 @@ var testHelpers = { getOneOfTwoMultisigOutput: (pubKeyFrom, pubKeyTo, value) => { return new bcoin.primitives.Output({ - value, - script: bcoin.script.fromString( - "OP_1 " - + testHelpers.bufferToScript(pubKeyFrom) + " " - + testHelpers.bufferToScript(pubKeyTo) + " " - + "OP_2 OP_CHECKMULTISIG") + script: bcoin.script.fromMultisig(1, 2, [pubKeyFrom, pubKeyTo]), + value }); }, @@ -131,12 +133,10 @@ var testHelpers = { var neighbours = graph[from]; for (var to in neighbours) { var value = neighbours[to]; - tir.addTX(testHelpers.getTrustIncreasingMTX(addr[from].pubkey, addr[to].pubkey, value).toTX()); + tir.addTX(testHelpers.getTrustIncreasingMTX(addr[from].pubKey, addr[to].pubKey, value).toTX()); } } } }; - - module.exports = testHelpers; diff --git a/test/trust_is_risk.js b/test/trust_is_risk.js index 48a199d..ddb3d36 100644 --- a/test/trust_is_risk.js +++ b/test/trust_is_risk.js @@ -1,12 +1,17 @@ var Trust = require('../'); +var helpers = require('../lib/helpers.js'); var bcoin = require('bcoin'); +var Coin = bcoin.primitives.Coin; +var Address = bcoin.primitives.Address; +var Input = bcoin.primitives.Input; +var MTX = bcoin.primitives.MTX; var testHelpers = require('./helpers'); var consensus = require('bcoin/lib/protocol/consensus'); var sinon = require('sinon'); var should = require('should'); require('should-sinon'); -var Address = bcoin.primitives.Address; +const COIN = bcoin.consensus.COIN; describe('TrustIsRisk', () => { var addr = testHelpers.getAddressFixtures(); @@ -20,10 +25,10 @@ describe('TrustIsRisk', () => { node = new bcoin.fullnode({}); tir = new Trust.TrustIsRisk(node); - trustIncreasingMTX = testHelpers.getTrustIncreasingMTX(addr.alice.pubkey, addr.bob.pubkey, 42); + trustIncreasingMTX = testHelpers.getTrustIncreasingMTX(addr.alice.pubKey, addr.bob.pubKey, 42 * COIN); trustIncreasingTX = trustIncreasingMTX.toTX(); - var inputOneOfTwoMultisig = new bcoin.primitives.Input({ + var inputOneOfTwoMultisig = new Input({ prevout: { hash: trustIncreasingTX.hash().toString('hex'), index: 0 @@ -33,13 +38,13 @@ describe('TrustIsRisk', () => { "0x47 0x3044022035e32834c6ee4db1696cc06762feca2809d865ca12a3b98c801f3f451341a2570220573bf3ffef55f2651e1563acc0a22f8056222f277f5ddf17dd583d4edd40fa6001 0x21 0x02b8f07a401eca4888039b1898f94db44c43ccc6d3aa8b27e9b6ed7b377b24c083") }); - trustDecreasingMTX = new bcoin.primitives.MTX({ + trustDecreasingMTX = new MTX({ inputs: [ inputOneOfTwoMultisig ], outputs: [ - testHelpers.getOneOfTwoMultisigOutput(addr.alice.pubkey, addr.bob.pubkey, 20), - testHelpers.getP2PKHOutput(addr.alice.base58, 22) + testHelpers.getOneOfTwoMultisigOutput(addr.alice.pubKey, addr.bob.pubKey, 20 * COIN), + testHelpers.getP2PKHOutput(addr.alice.base58, 22 * COIN) ] }); }); @@ -62,7 +67,7 @@ describe('TrustIsRisk', () => { describe('.addTX()', () => { describe('with a non-TIR transaction', () => { it('does not change trust', () => { - trustIncreasingMTX.outputs[0] = testHelpers.getP2PKHOutput(charlie, 50); + trustIncreasingMTX.outputs[0] = testHelpers.getP2PKHOutput(charlie, 50 * COIN); tir.parseTXAsTrustIncrease(trustIncreasingMTX.toTX()); should(tir.getDirectTrust(alice, bob)).equal(0); @@ -73,7 +78,7 @@ describe('TrustIsRisk', () => { it('correctly increases trust', () => { tir.addTX(trustIncreasingTX); - should(tir.getDirectTrust(alice, bob)).equal(42); + should(tir.getDirectTrust(alice, bob)).equal(42 * COIN); should(tir.getDirectTrust(bob, alice)).equal(0); }); @@ -85,17 +90,17 @@ describe('TrustIsRisk', () => { }); it('which has a change output correctly increases trust', () => { - trustIncreasingMTX.outputs[0].value -= 10; - trustIncreasingMTX.outputs.push(testHelpers.getP2PKHOutput(alice, 10)); + trustIncreasingMTX.outputs[0].value -= 10 * COIN; + trustIncreasingMTX.outputs.push(testHelpers.getP2PKHOutput(alice, 10 * COIN)); tir.addTX(trustIncreasingMTX.toTX()); - should(tir.getDirectTrust(alice, bob)).equal(32); + should(tir.getDirectTrust(alice, bob)).equal(32 * COIN); }); it('which has two change outputs does not change trust', () => { trustIncreasingMTX.outputs[0].value -= 10; for (var i = 0; i < 2; i++) { - trustIncreasingMTX.outputs.push(testHelpers.getP2PKHOutput(alice, 5)); + trustIncreasingMTX.outputs.push(testHelpers.getP2PKHOutput(alice, 5 * COIN)); } tir.addTX(trustIncreasingMTX.toTX()); @@ -103,8 +108,8 @@ describe('TrustIsRisk', () => { }); it('which has a second output that is not a change output does not change trust', () => { - trustIncreasingMTX.outputs[0].value -= 10; - trustIncreasingMTX.outputs.push(testHelpers.getP2PKHOutput(charlie, 5)); + trustIncreasingMTX.outputs[0].value -= 10 * COIN; + trustIncreasingMTX.outputs.push(testHelpers.getP2PKHOutput(charlie, 5 * COIN)); tir.addTX(trustIncreasingMTX.toTX()); should(tir.getDirectTrust(alice, bob)).equal(0); @@ -112,7 +117,7 @@ describe('TrustIsRisk', () => { it('which has been processed before throws', () => { var tx = trustIncreasingMTX.toTX(); - tir.addTX(tx); + should(tir.addTX(tx)); should.throws(() => tir.addTX(tx), /already processed/i); }); }); @@ -124,20 +129,20 @@ describe('TrustIsRisk', () => { it('correctly decreases trust', () => { tir.addTX(trustDecreasingMTX.toTX()); - should(tir.getDirectTrust(alice, bob)).equal(20); + should(tir.getDirectTrust(alice, bob)).equal(20 * COIN); }); it('which has a second input decreases trust to zero', () => { - trustDecreasingMTX.inputs.push(testHelpers.getP2PKHInput(addr.alice.pubkey)); + trustDecreasingMTX.inputs.push(testHelpers.getP2PKHInput(addr.alice.pubKey)); tir.addTX(trustDecreasingMTX.toTX()); should(tir.getDirectTrust(alice, bob)).equal(0); }); it('which has more than one trust outputs decreases trust to zero', () => { - trustDecreasingMTX.outputs[0].value -= 15; + trustDecreasingMTX.outputs[0].value -= 15 * COIN; trustDecreasingMTX.outputs.push( - testHelpers.getOneOfTwoMultisigOutput(addr.alice.pubkey, addr.bob.pubkey, 5)); + testHelpers.getOneOfTwoMultisigOutput(addr.alice.pubKey, addr.bob.pubKey, 5 * COIN)); tir.addTX(trustDecreasingMTX.toTX()); should(tir.getDirectTrust(alice, bob)).equal(0); @@ -146,45 +151,35 @@ describe('TrustIsRisk', () => { describe('.getTrustIncreasingMTX()', () => { it('creates valid trust-increasing transactions', async () => { - var getTXStub = sinon.stub(node, 'getTX'); + var getTXStub = sinon.stub(node, 'getCoin'); var prevOutput = { hash: 'v1pnhp2af4r5wz63j60vnh27s1bftl260qq621y458tn0g4x64u64yqz6d7qi6i8', index: 1 }; - getTXStub.withArgs(prevOutput.hash).returns(new bcoin.primitives.MTX({ - inputs: [ - testHelpers.getP2PKHInput(addr.alice.pubkey) - ], - outputs: [ - testHelpers.getOneOfTwoMultisigOutput(addr.charlie.pubkey, addr.bob.pubkey, 40), - testHelpers.getP2PKHOutput(alice, 1000), // This is the P2PKH being used - testHelpers.getP2PKHOutput(dave, 200) - ] - }).toTX()); + getTXStub.withArgs(prevOutput.hash).returns(new Coin({ + script: testHelpers.getP2PKHOutput(alice, 1).script, + value: 1000 * COIN + })); - var mtx = await tir.getTrustIncreasingMTX(addr.alice.pubkey, addr.bob.pubkey, prevOutput, 100); + var mtx = await tir.getTrustIncreasingMTX(addr.alice.privKey, addr.bob.pubKey, prevOutput, + 100 * COIN); mtx.inputs.length.should.equal(1); - var input = mtx.inputs[0]; - input.script.get(0).should.equal(0); // OP_0, because this is an unsigned bcoin input template. - input.script.get(1).should.equal(addr.alice.pubkey); mtx.outputs.length.should.equal(2); var trustOutput = mtx.outputs[0]; trustOutput.getType().should.equal('multisig'); - var addressA = Address.fromHash(bcoin.crypto.hash160(trustOutput.script.get(1))).toBase58(); - var addressB = Address.fromHash(bcoin.crypto.hash160(trustOutput.script.get(2))).toBase58(); - addressA.should.equal(alice); - addressB.should.equal(bob); - trustOutput.value.should.equal(100); + [1, 2].map((i) => helpers.pubKeyToEntity(trustOutput.script.get(i))).sort() + .should.deepEqual([alice, bob].sort()); + trustOutput.value.should.equal(100 * COIN); var changeOutput = mtx.outputs[1]; changeOutput.getType().should.equal('pubkeyhash'); changeOutput.getAddress().toBase58().should.equal(alice); - changeOutput.value.should.equal(900); + changeOutput.value.should.equal(900 * COIN - 1000); }); }); @@ -198,17 +193,17 @@ describe('TrustIsRisk', () => { trustTXs.push(tx); tir.addTX(tx); - trustIncreasingMTX.outputs[0].value = 100; + trustIncreasingMTX.outputs[0].value = 100 * COIN; tx = trustIncreasingMTX.toTX(); trustTXs.push(tx); tir.addTX(tx); - trustIncreasingMTX.outputs[0].value = 500; + trustIncreasingMTX.outputs[0].value = 500 * COIN; tx = trustIncreasingMTX.toTX(); trustTXs.push(tx); tir.addTX(tx); - // Total trust 642 + // Total trust 642 BTC }); // Helper specific to the next couple of tests: @@ -229,7 +224,7 @@ describe('TrustIsRisk', () => { mtx.outputs.length.should.equal(1); // Single P2PKH output mtx.outputs[0].getType().should.equal('pubkeyhash'); mtx.outputs[0].getAddress().toBase58().should.equal(recipient); - mtx.outputs[0].value.should.equal(42); + mtx.outputs[0].value.should.equal(42 * COIN - 1000); mtx = mtxs[1]; @@ -241,29 +236,29 @@ describe('TrustIsRisk', () => { mtx.outputs.length.should.equal(2); // One P2PKH output and one multisig trust output mtx.outputs[1].script.toString().should.equal(trustTXs[1].outputs[0].script.toString()); - mtx.outputs[1].value.should.equal(60); + mtx.outputs[1].value.should.equal(60 * COIN); mtx.outputs[0].getType().should.equal('pubkeyhash'); mtx.outputs[0].getAddress().toBase58().should.equal(recipient); - mtx.outputs[0].value.should.equal(40); + mtx.outputs[0].value.should.equal(40 * COIN - 1000); }; it('creates correct trust decreasing transactions', () => { - var mtxs = tir.getTrustDecreasingMTXs(addr.alice.pubkey, addr.bob.pubkey, 82); + var mtxs = tir.getTrustDecreasingMTXs(addr.alice.privKey, addr.bob.pubKey, 82 * COIN); checkMTXs(mtxs, alice); }); it('creates correct trust stealing transactions', () => { - var mtxs = tir.getTrustDecreasingMTXs(addr.alice.pubkey, addr.bob.pubkey, 82, charlie); + var mtxs = tir.getTrustDecreasingMTXs(addr.alice.privKey, addr.bob.pubKey, 82 * COIN, charlie); checkMTXs(mtxs, charlie); }); it('throws when trying to decrease self-trust', () => { - should.throws(() => tir.getTrustDecreasingMTXs(addr.alice.pubkey, addr.alice.pubkey, 10) + should.throws(() => tir.getTrustDecreasingMTXs(addr.alice.privKey, addr.alice.pubKey, 10 * COIN) , /self-trust/i); }); it('throws when there is not enough trust', () => { - should.throws(() => tir.getTrustDecreasingMTXs(addr.alice.pubkey, addr.bob.pubkey, 700) + should.throws(() => tir.getTrustDecreasingMTXs(addr.alice.privKey, addr.bob.pubKey, 700 * COIN) , /insufficient trust/i); }); @@ -330,7 +325,7 @@ describe('TrustIsRisk', () => { should(tir.getTrust(frank, alice)).equal(0); should(tir.getTrust(frank, bob)).equal(0); - should(tir.getTrust(frank, charlie)).equal(100); + should(tir.getTrust(frank, charlie)).equal(10); should(tir.getTrust(frank, dave)).equal(0); should(tir.getTrust(frank, eve)).equal(0); should(tir.getTrust(frank, frank)).equal(Infinity); @@ -346,13 +341,13 @@ describe('TrustIsRisk', () => { }); it('correctly computes trusts when bob trusts frank', () => { - tir.addTX(testHelpers.getTrustIncreasingMTX(addr.bob.pubkey, addr.frank.pubkey, 8).toTX()); + tir.addTX(testHelpers.getTrustIncreasingMTX(addr.bob.pubKey, addr.frank.pubKey, 8).toTX()); should(tir.getTrust(george, frank)).equal(0); should(tir.getTrust(alice, frank)).equal(8); should(tir.getTrust(dave, frank)).equal(2); should(tir.getTrust(bob, frank)).equal(8); }); - + // TODO: Decrement direct trusts and test that indirect trusts update correctly }); }); From eeff57abb107cd34bddc3ff6dbeded06df58856d Mon Sep 17 00:00:00 2001 From: Christos Porios Date: Mon, 29 May 2017 16:49:00 +0000 Subject: [PATCH 11/20] Use ESLint, fix style issues --- .eslintrc.json | 40 +++++++++++++ flow-typed/npm/bcoin_vx.x.x.js | 1 + lib/direct_trust.js | 10 ++-- lib/full_node.js | 4 +- lib/helpers.js | 10 ++-- lib/index.js | 6 +- lib/trust_db.js | 20 +++---- lib/trust_is_risk.js | 44 +++++++------- package.json | 7 ++- src/direct_trust.js | 10 ++-- src/full_node.js | 4 +- src/helpers.js | 10 ++-- src/index.js | 6 +- src/trust_db.js | 20 +++---- src/trust_is_risk.js | 44 +++++++------- test/full_node.js | 102 ++++++++++++++++---------------- test/helpers.js | 40 ++++++------- test/trust_is_risk.js | 104 ++++++++++++++++----------------- 18 files changed, 264 insertions(+), 218 deletions(-) create mode 100644 .eslintrc.json diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..2597f3d --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,40 @@ +{ + "parser": "babel-eslint", + "plugins": [ + "eslint-plugin-flowtype" + ], + "env": { + "es6": true, + "node": true + }, + "extends": [ + "eslint:recommended", + "plugin:flowtype/recommended" + ], + "rules": { + "indent": [ + "error", + 2 + ], + "linebreak-style": [ + "error", + "unix" + ], + "quotes": [ + "error", + "double" + ], + "semi": [ + "error", + "always" + ], + "flowtype/space-before-type-colon": 0, + "no-undef": 0, + "no-unused-vars": 0 + }, + "settings": { + "flowtype": { + "onlyFilesWithFlowAnnotation": true + } + } +} diff --git a/flow-typed/npm/bcoin_vx.x.x.js b/flow-typed/npm/bcoin_vx.x.x.js index 87c534d..c6663cc 100644 --- a/flow-typed/npm/bcoin_vx.x.x.js +++ b/flow-typed/npm/bcoin_vx.x.x.js @@ -100,3 +100,4 @@ declare module 'bcoin' { } } + diff --git a/lib/direct_trust.js b/lib/direct_trust.js index ad676f4..d1da9c8 100644 --- a/lib/direct_trust.js +++ b/lib/direct_trust.js @@ -1,8 +1,8 @@ // - -var bcoin = require('bcoin'); -var assert = require('assert'); -var helpers = require('./helpers'); + + +var assert = require("assert"); +var helpers = require("./helpers"); @@ -14,7 +14,7 @@ var helpers = require('./helpers'); - + class DirectTrust { diff --git a/lib/full_node.js b/lib/full_node.js index 79b1948..3525254 100644 --- a/lib/full_node.js +++ b/lib/full_node.js @@ -1,6 +1,6 @@ // -var bcoin = require('bcoin'); -var TrustIsRisk = require('./trust_is_risk'); +var bcoin = require("bcoin"); +var TrustIsRisk = require("./trust_is_risk"); class FullNode extends bcoin.fullnode { diff --git a/lib/helpers.js b/lib/helpers.js index 27f228c..a345715 100644 --- a/lib/helpers.js +++ b/lib/helpers.js @@ -1,12 +1,12 @@ // - -var bcoin = require('bcoin'); + +var bcoin = require("bcoin"); var Address = bcoin.primitives.Address; var helpers = { - pubKeyToEntity: (key ) => { - return Address.fromHash(bcoin.crypto.hash160(key)).toBase58() - } + pubKeyToEntity: (key ) => { + return Address.fromHash(bcoin.crypto.hash160(key)).toBase58(); + } }; module.exports = helpers; diff --git a/lib/index.js b/lib/index.js index 01c118a..824eb06 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,5 +1,5 @@ // module.exports = { - FullNode: require('./full_node'), - TrustIsRisk: require('./trust_is_risk') -} + FullNode: require("./full_node"), + TrustIsRisk: require("./trust_is_risk") +}; diff --git a/lib/trust_db.js b/lib/trust_db.js index a19a92d..fecf695 100644 --- a/lib/trust_db.js +++ b/lib/trust_db.js @@ -1,16 +1,16 @@ // - -var assert = require('assert'); -var SortedSet = require('sorted-set'); -var maxFlow = require('graph-theory-ford-fulkerson'); -var bcoin = require('bcoin'); + +var assert = require("assert"); +var SortedSet = require("sorted-set"); +var maxFlow = require("graph-theory-ford-fulkerson"); +var bcoin = require("bcoin"); var Address = bcoin.primitives.Address; var KeyRing = bcoin.primitives.KeyRing; var MTX = bcoin.primitives.MTX; var Input = bcoin.primitives.Input; var Output = bcoin.primitives.Output; var Outpoint = bcoin.primitives.Outpoint; -var DirectTrust = require('./direct_trust'); +var DirectTrust = require("./direct_trust"); class TrustDB { @@ -29,7 +29,7 @@ class TrustDB { } getDirectTrustByOutpoint(outpoint ) { - var trust = this.txToDirectTrust.get(outpoint.hash.toString('hex')); + var trust = this.txToDirectTrust.get(outpoint.hash.toString("hex")); if (!trust) return null; if (trust.outputIndex !== outpoint.index) return null; return trust; @@ -87,9 +87,9 @@ class TrustDB { return this.txToDirectTrust.has(txHash); } - isTrustOutput(txHash , outputIndex ) { - var trust = this.txToDirectTrust.get(txHash); - return trust !== undefined && trust.outputIndex === outputIndex; + isTrustOutput(txHash , outputIndex ) { + var trust = this.txToDirectTrust.get(txHash); + return trust !== undefined && trust.outputIndex === outputIndex; } add(trust ) { diff --git a/lib/trust_is_risk.js b/lib/trust_is_risk.js index f994941..7e0b8cb 100644 --- a/lib/trust_is_risk.js +++ b/lib/trust_is_risk.js @@ -1,6 +1,6 @@ // - -var bcoin = require('bcoin'); + +var bcoin = require("bcoin"); var Address = bcoin.primitives.Address; var KeyRing = bcoin.primitives.KeyRing; var MTX = bcoin.primitives.MTX; @@ -8,10 +8,10 @@ var Input = bcoin.primitives.Input; var Output = bcoin.primitives.Output; var Outpoint = bcoin.primitives.Outpoint; var Coin = bcoin.primitives.Coin; -var assert = require('assert'); -var helpers = require('./helpers'); -var TrustDB = require('./trust_db'); -var DirectTrust = require('./direct_trust'); +var assert = require("assert"); +var helpers = require("./helpers"); +var TrustDB = require("./trust_db"); +var DirectTrust = require("./direct_trust"); class TrustIsRisk { @@ -21,7 +21,7 @@ class TrustIsRisk { this.node = node; this.db = new TrustDB(); - this.node.on('tx', this.addTX.bind(this)); + this.node.on("tx", this.addTX.bind(this)); } getTrust(from , to ) { @@ -38,7 +38,7 @@ class TrustIsRisk { // network, false otherwise. // Throws an error if the transaction was processed earlier. addTX(tx ) { - var txHash = tx.hash().toString('hex'); + var txHash = tx.hash().toString("hex"); if (this.db.isTrustTX(txHash)) { throw new Error(`Transaction already processed: Transaction ${txHash} already carries trust`); } @@ -71,9 +71,9 @@ class TrustIsRisk { async getTrustIncreasingMTX(fromPrivate , to , outpoint , trustAmount , fee ) { - if (!fee) fee = 1000; // TODO: estimate this + if (!fee) fee = 1000; // TODO: estimate this var coin = await this.node.getCoin(outpoint.hash, outpoint.index); - if (!coin) throw new Error('Could not find coin'); + if (!coin) throw new Error("Could not find coin"); var fromKeyRing = KeyRing.fromPrivate(fromPrivate); var from = fromKeyRing.getPublicKey(); @@ -95,7 +95,7 @@ class TrustIsRisk { })); } - mtx.addCoin(coin); + mtx.addCoin(coin); var success = mtx.scriptVector(coin.script, mtx.inputs[0].script, fromKeyRing); assert(success); @@ -130,10 +130,10 @@ class TrustIsRisk { var fromAddress = helpers.pubKeyToEntity(fromKeyRing.getPublicKey()); var toAddress = helpers.pubKeyToEntity(toKeyRing.getPublicKey()); - if (fromAddress === toAddress) throw new Error('Can\'t decrease self-trust'); + if (fromAddress === toAddress) throw new Error("Can't decrease self-trust"); var existingTrustAmount = this.db.getDirectTrustAmount(fromAddress, toAddress); - if (existingTrustAmount < trustDecreaseAmount) throw new Error('Insufficient trust'); + if (existingTrustAmount < trustDecreaseAmount) throw new Error("Insufficient trust"); var directTrusts = this.db.getSpendableDirectTrusts(fromAddress, toAddress); return directTrusts.map((directTrust) => { @@ -147,7 +147,7 @@ class TrustIsRisk { getTrustDecreasingMTX(directTrust , decreaseAmount , payee , signingKeyRing , fee ) { if (!payee) payee = directTrust.getFromEntity(); - if (!fee) fee = 1000; // TODO: estimate this + if (!fee) fee = 1000; // TODO: estimate this var mtx = new MTX({ inputs: [ @@ -180,9 +180,9 @@ class TrustIsRisk { parseTXAsTrustIncrease(tx ) { if (tx.inputs.length !== 1) return null; - var input = tx.inputs[0]; - if (input.getType() !== 'pubkeyhash') return null; // TODO: This is unreliable - if (this.db.isTrustOutput(input.prevout.hash.toString('hex'), input.prevout.index)) return null; + var input = tx.inputs[0]; + if (input.getType() !== "pubkeyhash") return null; // TODO: This is unreliable + if (this.db.isTrustOutput(input.prevout.hash.toString("hex"), input.prevout.index)) return null; var sender = tx.inputs[0].getAddress().toBase58(); if (tx.outputs.length === 0 || tx.outputs.length > 2) return null; @@ -190,7 +190,7 @@ class TrustIsRisk { var trustOutputs = this.searchForDirectTrustOutputs(tx, sender); if (trustOutputs.length !== 1) return null; - var changeOutputCount = tx.outputs.filter((o) => this.isChangeOutput(o, sender)).length + var changeOutputCount = tx.outputs.filter((o) => this.isChangeOutput(o, sender)).length; if (changeOutputCount + 1 !== tx.outputs.length) return null; return trustOutputs[0]; @@ -210,7 +210,7 @@ class TrustIsRisk { } getTrustDecrease(tx , prevTrust ) { - var txHash = tx.hash().toString('hex'); + var txHash = tx.hash().toString("hex"); var nullTrust = prevTrust.getNullifying(txHash); if (tx.inputs.length !== 1) return nullTrust; @@ -243,15 +243,15 @@ class TrustIsRisk { } isChangeOutput(output , sender ) { - return (output.getType() === 'pubkeyhash') + return (output.getType() === "pubkeyhash") && (output.getAddress().toBase58() === sender); } parseOutputAsDirectTrust(tx , outputIndex , sender ) { - var txHash = tx.hash().toString('hex'); + var txHash = tx.hash().toString("hex"); var output = tx.outputs[outputIndex]; - if (output.getType() !== 'multisig') return null; + if (output.getType() !== "multisig") return null; var entities = [1, 2].map((i) => helpers.pubKeyToEntity(output.script.get(i))); if (entities[0] === entities[1]) return null; diff --git a/package.json b/package.json index 6691347..c08a511 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,9 @@ "build": "flow-remove-types -d lib/ src/", "test": "npm run build && mocha -t 10000", "prepublish": "npm run build", - "typecheck": "flow check" + "typecheck": "flow check", + "lint": "./node_modules/.bin/eslint --fix src/ test/", + "check": "npm run lint && npm run typecheck && npm run test" }, "repository": { "type": "git", @@ -25,6 +27,9 @@ }, "homepage": "https://github.com/decrypto-org/TrustIsRisk.js#readme", "devDependencies": { + "babel-eslint": "^7.2.3", + "eslint": "^3.19.0", + "eslint-plugin-flowtype": "^2.33.0", "flow-bin": "^0.45.0", "flow-remove-types": "^1.2.0", "mocha": "^3.4.2", diff --git a/src/direct_trust.js b/src/direct_trust.js index 153cc83..70662ed 100644 --- a/src/direct_trust.js +++ b/src/direct_trust.js @@ -1,8 +1,8 @@ // @flow -import type {Entity, TXHash, Key} from "./types" -var bcoin = require('bcoin'); -var assert = require('assert'); -var helpers = require('./helpers'); +import type {Entity, Key} from "./types"; +import type {script} from "bcoin"; +var assert = require("assert"); +var helpers = require("./helpers"); type DirectTrustOptions = { from : Key, @@ -14,7 +14,7 @@ type DirectTrustOptions = { script? : bcoin$Script, prev? : DirectTrust, - next? :DirectTrust + next? : DirectTrust } class DirectTrust { diff --git a/src/full_node.js b/src/full_node.js index f6b31f8..93e664d 100644 --- a/src/full_node.js +++ b/src/full_node.js @@ -1,6 +1,6 @@ // @flow -var bcoin = require('bcoin'); -var TrustIsRisk = require('./trust_is_risk'); +var bcoin = require("bcoin"); +var TrustIsRisk = require("./trust_is_risk"); class FullNode extends bcoin.fullnode { trust : TrustIsRisk diff --git a/src/helpers.js b/src/helpers.js index 7723bff..6416a31 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -1,12 +1,12 @@ // @flow -import type {Entity, TXHash, Key} from "./types" -var bcoin = require('bcoin'); +import type {Entity, TXHash, Key} from "./types"; +var bcoin = require("bcoin"); var Address = bcoin.primitives.Address; var helpers = { - pubKeyToEntity: (key : Key) : Entity => { - return Address.fromHash(bcoin.crypto.hash160(key)).toBase58() - } + pubKeyToEntity: (key : Key) : Entity => { + return Address.fromHash(bcoin.crypto.hash160(key)).toBase58(); + } }; module.exports = helpers; diff --git a/src/index.js b/src/index.js index f21fb90..66a1290 100644 --- a/src/index.js +++ b/src/index.js @@ -1,5 +1,5 @@ // @flow module.exports = { - FullNode: require('./full_node'), - TrustIsRisk: require('./trust_is_risk') -} + FullNode: require("./full_node"), + TrustIsRisk: require("./trust_is_risk") +}; diff --git a/src/trust_db.js b/src/trust_db.js index bf610e5..449f50a 100644 --- a/src/trust_db.js +++ b/src/trust_db.js @@ -1,16 +1,16 @@ // @flow -import type {Entity, TXHash, Key} from "./types" -var assert = require('assert'); -var SortedSet = require('sorted-set'); -var maxFlow = require('graph-theory-ford-fulkerson'); -var bcoin = require('bcoin'); +import type {Entity, TXHash, Key} from "./types"; +var assert = require("assert"); +var SortedSet = require("sorted-set"); +var maxFlow = require("graph-theory-ford-fulkerson"); +var bcoin = require("bcoin"); var Address = bcoin.primitives.Address; var KeyRing = bcoin.primitives.KeyRing; var MTX = bcoin.primitives.MTX; var Input = bcoin.primitives.Input; var Output = bcoin.primitives.Output; var Outpoint = bcoin.primitives.Outpoint; -var DirectTrust = require('./direct_trust'); +var DirectTrust = require("./direct_trust"); class TrustDB { directTrusts : Map>> @@ -29,7 +29,7 @@ class TrustDB { } getDirectTrustByOutpoint(outpoint : bcoin$Outpoint) : (DirectTrust | null) { - var trust = this.txToDirectTrust.get(outpoint.hash.toString('hex')); + var trust = this.txToDirectTrust.get(outpoint.hash.toString("hex")); if (!trust) return null; if (trust.outputIndex !== outpoint.index) return null; return trust; @@ -87,9 +87,9 @@ class TrustDB { return this.txToDirectTrust.has(txHash); } - isTrustOutput(txHash : string, outputIndex : number) : boolean { - var trust = this.txToDirectTrust.get(txHash); - return trust !== undefined && trust.outputIndex === outputIndex; + isTrustOutput(txHash : string, outputIndex : number) : boolean { + var trust = this.txToDirectTrust.get(txHash); + return trust !== undefined && trust.outputIndex === outputIndex; } add(trust : DirectTrust) { diff --git a/src/trust_is_risk.js b/src/trust_is_risk.js index 3ce0959..db3ec16 100644 --- a/src/trust_is_risk.js +++ b/src/trust_is_risk.js @@ -1,6 +1,6 @@ // @flow -import type {Entity, TXHash, Key} from "./types" -var bcoin = require('bcoin'); +import type {Entity, TXHash, Key} from "./types"; +var bcoin = require("bcoin"); var Address = bcoin.primitives.Address; var KeyRing = bcoin.primitives.KeyRing; var MTX = bcoin.primitives.MTX; @@ -8,10 +8,10 @@ var Input = bcoin.primitives.Input; var Output = bcoin.primitives.Output; var Outpoint = bcoin.primitives.Outpoint; var Coin = bcoin.primitives.Coin; -var assert = require('assert'); -var helpers = require('./helpers'); -var TrustDB = require('./trust_db'); -var DirectTrust = require('./direct_trust'); +var assert = require("assert"); +var helpers = require("./helpers"); +var TrustDB = require("./trust_db"); +var DirectTrust = require("./direct_trust"); class TrustIsRisk { node : bcoin$FullNode @@ -21,7 +21,7 @@ class TrustIsRisk { this.node = node; this.db = new TrustDB(); - this.node.on('tx', this.addTX.bind(this)); + this.node.on("tx", this.addTX.bind(this)); } getTrust(from : Entity, to : Entity) { @@ -38,7 +38,7 @@ class TrustIsRisk { // network, false otherwise. // Throws an error if the transaction was processed earlier. addTX(tx : bcoin$TX) : boolean { - var txHash = tx.hash().toString('hex'); + var txHash = tx.hash().toString("hex"); if (this.db.isTrustTX(txHash)) { throw new Error(`Transaction already processed: Transaction ${txHash} already carries trust`); } @@ -71,9 +71,9 @@ class TrustIsRisk { async getTrustIncreasingMTX(fromPrivate : Key, to : Key, outpoint : bcoin$Outpoint, trustAmount : number, fee : ?number) : Promise { - if (!fee) fee = 1000; // TODO: estimate this + if (!fee) fee = 1000; // TODO: estimate this var coin = await this.node.getCoin(outpoint.hash, outpoint.index); - if (!coin) throw new Error('Could not find coin'); + if (!coin) throw new Error("Could not find coin"); var fromKeyRing = KeyRing.fromPrivate(fromPrivate); var from = fromKeyRing.getPublicKey(); @@ -95,7 +95,7 @@ class TrustIsRisk { })); } - mtx.addCoin(coin); + mtx.addCoin(coin); var success = mtx.scriptVector(coin.script, mtx.inputs[0].script, fromKeyRing); assert(success); @@ -130,10 +130,10 @@ class TrustIsRisk { var fromAddress = helpers.pubKeyToEntity(fromKeyRing.getPublicKey()); var toAddress = helpers.pubKeyToEntity(toKeyRing.getPublicKey()); - if (fromAddress === toAddress) throw new Error('Can\'t decrease self-trust'); + if (fromAddress === toAddress) throw new Error("Can't decrease self-trust"); var existingTrustAmount = this.db.getDirectTrustAmount(fromAddress, toAddress); - if (existingTrustAmount < trustDecreaseAmount) throw new Error('Insufficient trust'); + if (existingTrustAmount < trustDecreaseAmount) throw new Error("Insufficient trust"); var directTrusts = this.db.getSpendableDirectTrusts(fromAddress, toAddress); return directTrusts.map((directTrust) => { @@ -147,7 +147,7 @@ class TrustIsRisk { getTrustDecreasingMTX(directTrust : DirectTrust, decreaseAmount : number, payee : ?Entity, signingKeyRing : bcoin$KeyRing, fee : ?number) { if (!payee) payee = directTrust.getFromEntity(); - if (!fee) fee = 1000; // TODO: estimate this + if (!fee) fee = 1000; // TODO: estimate this var mtx = new MTX({ inputs: [ @@ -180,9 +180,9 @@ class TrustIsRisk { parseTXAsTrustIncrease(tx : bcoin$TX) : (DirectTrust | null) { if (tx.inputs.length !== 1) return null; - var input = tx.inputs[0]; - if (input.getType() !== 'pubkeyhash') return null; // TODO: This is unreliable - if (this.db.isTrustOutput(input.prevout.hash.toString('hex'), input.prevout.index)) return null; + var input = tx.inputs[0]; + if (input.getType() !== "pubkeyhash") return null; // TODO: This is unreliable + if (this.db.isTrustOutput(input.prevout.hash.toString("hex"), input.prevout.index)) return null; var sender = tx.inputs[0].getAddress().toBase58(); if (tx.outputs.length === 0 || tx.outputs.length > 2) return null; @@ -190,7 +190,7 @@ class TrustIsRisk { var trustOutputs = this.searchForDirectTrustOutputs(tx, sender); if (trustOutputs.length !== 1) return null; - var changeOutputCount = tx.outputs.filter((o) => this.isChangeOutput(o, sender)).length + var changeOutputCount = tx.outputs.filter((o) => this.isChangeOutput(o, sender)).length; if (changeOutputCount + 1 !== tx.outputs.length) return null; return trustOutputs[0]; @@ -210,7 +210,7 @@ class TrustIsRisk { } getTrustDecrease(tx : bcoin$TX, prevTrust : DirectTrust) : DirectTrust { - var txHash = tx.hash().toString('hex'); + var txHash = tx.hash().toString("hex"); var nullTrust = prevTrust.getNullifying(txHash); if (tx.inputs.length !== 1) return nullTrust; @@ -243,15 +243,15 @@ class TrustIsRisk { } isChangeOutput(output : bcoin$Output, sender : Entity) : boolean { - return (output.getType() === 'pubkeyhash') + return (output.getType() === "pubkeyhash") && (output.getAddress().toBase58() === sender); } parseOutputAsDirectTrust(tx : bcoin$TX, outputIndex : number, sender : Entity) : (DirectTrust | null) { - var txHash = tx.hash().toString('hex'); + var txHash = tx.hash().toString("hex"); var output = tx.outputs[outputIndex]; - if (output.getType() !== 'multisig') return null; + if (output.getType() !== "multisig") return null; var entities = [1, 2].map((i) => helpers.pubKeyToEntity(output.script.get(i))); if (entities[0] === entities[1]) return null; diff --git a/test/full_node.js b/test/full_node.js index f8792c5..9ca89a4 100644 --- a/test/full_node.js +++ b/test/full_node.js @@ -1,6 +1,6 @@ -var Trust = require('../'); -var helpers = require('../lib/helpers.js'); -var bcoin = require('bcoin'); +var Trust = require("../"); +var helpers = require("../lib/helpers.js"); +var bcoin = require("bcoin"); var Script = bcoin.script; var Address = bcoin.primitives.Address; var KeyRing = bcoin.primitives.KeyRing; @@ -8,36 +8,36 @@ var MTX = bcoin.primitives.MTX; var Input = bcoin.primitives.Input; var Output = bcoin.primitives.Output; var Outpoint = bcoin.primitives.Outpoint; -var testHelpers = require('./helpers'); -var consensus = require('bcoin/lib/protocol/consensus'); -var sinon = require('sinon'); -var should = require('should'); -require('should-sinon'); +var testHelpers = require("./helpers"); +var consensus = require("bcoin/lib/protocol/consensus"); +var sinon = require("sinon"); +var should = require("should"); +require("should-sinon"); const COIN = consensus.COIN; -describe('FullNode', () => { +describe("FullNode", () => { var node = null; var walletDB = null; - sinon.spy(Trust.TrustIsRisk.prototype, 'addTX'); + sinon.spy(Trust.TrustIsRisk.prototype, "addTX"); - beforeEach('get node', () => testHelpers.getNode().then((n) => { + beforeEach("get node", () => testHelpers.getNode().then((n) => { node = n; })); - beforeEach('get walletDB', () => testHelpers.getWalletDB(node).then((w) => { + beforeEach("get walletDB", () => testHelpers.getWalletDB(node).then((w) => { walletDB = w; })); - afterEach('close walletDB', async () => walletDB.close()); - afterEach('close node', async () => node.close()); + afterEach("close walletDB", async () => walletDB.close()); + afterEach("close node", async () => node.close()); - it('should call trust.addTX() on every transaction', async function() { - var sender = await testHelpers.getWallet(walletDB, 'sender'); - var receiver = await testHelpers.getWallet(walletDB, 'receiver'); + it("should call trust.addTX() on every transaction", async function() { + var sender = await testHelpers.getWallet(walletDB, "sender"); + var receiver = await testHelpers.getWallet(walletDB, "receiver"); // Produce a block and reward the sender, so that we have a coin to spend. - await testHelpers.mineBlock(node, sender.getAddress('base58')); + await testHelpers.mineBlock(node, sender.getAddress("base58")); // Make the coin spendable. consensus.COINBASE_MATURITY = 0; @@ -46,7 +46,7 @@ describe('FullNode', () => { await sender.send({ outputs: [{ value: 10 * COIN, - address: receiver.getAddress('base58') + address: receiver.getAddress("base58") }] }); @@ -54,14 +54,14 @@ describe('FullNode', () => { node.trust.addTX.should.be.calledOnce(); }); - describe('with the nobodyLikesFrank.json example', () => { + describe("with the nobodyLikesFrank.json example", () => { var addresses, rings = {}; - beforeEach('apply graph transactions', async () => { + beforeEach("apply graph transactions", async () => { addresses = {}; rings = {}; - for (var i = 0; i < testHelpers.names.length; i++) { + for (let i = 0; i < testHelpers.names.length; i++) { var name = testHelpers.names[i]; rings[name] = testHelpers.rings[i]; addresses[name] = helpers.pubKeyToEntity(rings[name].getPublicKey()); @@ -71,7 +71,7 @@ describe('FullNode', () => { consensus.COINBASE_MATURITY = 0; var coinbaseCoinsCount = 3; var coinbaseHashes = []; - for(var i = 0; i < coinbaseCoinsCount; i++) { + for(let i = 0; i < coinbaseCoinsCount; i++) { var block = await testHelpers.mineBlock(node, addresses.alice); coinbaseHashes.push(block.txs[0].hash()); await testHelpers.time(200); @@ -99,7 +99,7 @@ describe('FullNode', () => { // Use the coinbase coins as inputs var coinbaseCoins = await Promise.all(coinbaseHashes.map((hash) => { - return node.getCoin(hash.toString('hex'), 0); + return node.getCoin(hash.toString("hex"), 0); })); var mtx = new MTX({outputs}); coinbaseCoins.forEach((coin) => mtx.addCoin(coin)); @@ -110,41 +110,41 @@ describe('FullNode', () => { var tx = mtx.toTX(); node.sendTX(tx); - prevout = {}; - testHelpers.names.forEach((name) => { - prevout[name] = { - hash: tx.hash().toString('hex'), - index: testHelpers.names.indexOf(name) - }; - }); + prevout = {}; + testHelpers.names.forEach((name) => { + prevout[name] = { + hash: tx.hash().toString("hex"), + index: testHelpers.names.indexOf(name) + }; + }); await testHelpers.time(500); // Alice mines another block await testHelpers.mineBlock(node, helpers.pubKeyToEntity(rings.alice.getPublicKey())); await testHelpers.time(500); - var graph = require('./graphs/nobodyLikesFrank.json'); + var graph = require("./graphs/nobodyLikesFrank.json"); var promises = []; for (var from in graph) { var neighbours = graph[from]; for (var to in neighbours) { var value = neighbours[to]; - if (!value || value < 1) continue; + if (!value || value < 1) continue; - var outpoint = new Outpoint(prevout[from].hash, prevout[from].index); + let outpoint = new Outpoint(prevout[from].hash, prevout[from].index); - var mtx = await node.trust.getTrustIncreasingMTX(rings[from].getPrivateKey(), + let mtx = await node.trust.getTrustIncreasingMTX(rings[from].getPrivateKey(), rings[to].getPublicKey(), outpoint, value * consensus.COIN); - should(await mtx.verify()); + should(await mtx.verify()); // The change output from this transaction will be used in other transactions from the // same origin. We therefore need to sleep until the transaction is added to the pool. - var tx = mtx.toTX(); - node.sendTX(tx); - await testHelpers.time(250); + let tx = mtx.toTX(); + node.sendTX(tx); + await testHelpers.time(250); - prevout[from] = {hash: tx.hash().toString('hex'), index: 1}; + prevout[from] = {hash: tx.hash().toString("hex"), index: 1}; } } //mtxs.forEach((mtx) => node.sendTX(mtx.toTX())); @@ -155,24 +155,24 @@ describe('FullNode', () => { await testHelpers.time(500); }); - it('computes trusts correctly', () => { + it("computes trusts correctly", () => { for (name in addresses) { // Add addresses to scope eval(`var ${name} = "${addresses[name]}";`); } - should(node.trust.getTrust(alice, alice)).equal(Infinity); - should(node.trust.getTrust(alice, bob)).equal(10 * COIN); - should(node.trust.getTrust(alice, charlie)).equal(1 * COIN); - should(node.trust.getTrust(alice, frank)).equal(0); - should(node.trust.getTrust(alice, eve)).equal(6 * COIN); + should(node.trust.getTrust(alice, alice)).equal(Infinity); + should(node.trust.getTrust(alice, bob)).equal(10 * COIN); + should(node.trust.getTrust(alice, charlie)).equal(1 * COIN); + should(node.trust.getTrust(alice, frank)).equal(0); + should(node.trust.getTrust(alice, eve)).equal(6 * COIN); - should(node.trust.getTrust(bob, alice)).equal(1 * COIN); - should(node.trust.getTrust(bob, eve)).equal(3 * COIN); - should(node.trust.getTrust(dave, eve)).equal(12 * COIN); - should(node.trust.getTrust(george, eve)).equal(0); + should(node.trust.getTrust(bob, alice)).equal(1 * COIN); + should(node.trust.getTrust(bob, eve)).equal(3 * COIN); + should(node.trust.getTrust(dave, eve)).equal(12 * COIN); + should(node.trust.getTrust(george, eve)).equal(0); }); - it('after decreasing some trusts computes trusts correctly', async () => { + it("after decreasing some trusts computes trusts correctly", async () => { var mtxs = node.trust.getTrustDecreasingMTXs(rings.alice.getPrivateKey(), rings.bob.getPublicKey(), 3 * COIN); mtxs.length.should.equal(1); diff --git a/test/helpers.js b/test/helpers.js index b16a0da..98b906b 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -1,20 +1,20 @@ -var TrustIsRisk = require('../'); -var WalletDB = require('bcoin/lib/wallet/walletdb'); -var bcoin = require('bcoin'); +var TrustIsRisk = require("../"); +var WalletDB = require("bcoin/lib/wallet/walletdb"); +var bcoin = require("bcoin"); var KeyRing = bcoin.primitives.KeyRing; -var assert = require('assert'); +var assert = require("assert"); var testHelpers = { - names: ['alice', 'bob', 'charlie', 'dave', 'eve', 'frank', 'george'], + names: ["alice", "bob", "charlie", "dave", "eve", "frank", "george"], rings: [ - '02b8f07a401eca4888039b1898f94db44c43ccc6d3aa8b27e9b6ed7b377b24c0', - '2437025954568a8273968aa7535dbfc444fd8f8d0f5237cd96ac7234c77810ad', - '3BBA2AF9539D09B4FD2BDEA1D3A2CE4BF5D779831B8781EE2ACF9C03378B2AD7', - '19BD8D853FAEFDB9B01E4DE7F6096FF8F5F96D43E6564A5258307334A4AA59F3', - '0503054CF7EBB4E62191AF1D8DE97945178D3F465EE88EF1FB4E80A70CB4A49A', - '878DFE5B43AC858EA37B3A9EEBA9E244F1848A30F78B2E5AC5B3EBDE81AC7D45', - '1349A1318B1426E6F724CBFE7ECD2C46008A364A96C4BD20C83FC1C4EBB2EB4A' - ].map((key) => KeyRing.fromPrivate(new Buffer(key, 'hex'))), + "02b8f07a401eca4888039b1898f94db44c43ccc6d3aa8b27e9b6ed7b377b24c0", + "2437025954568a8273968aa7535dbfc444fd8f8d0f5237cd96ac7234c77810ad", + "3BBA2AF9539D09B4FD2BDEA1D3A2CE4BF5D779831B8781EE2ACF9C03378B2AD7", + "19BD8D853FAEFDB9B01E4DE7F6096FF8F5F96D43E6564A5258307334A4AA59F3", + "0503054CF7EBB4E62191AF1D8DE97945178D3F465EE88EF1FB4E80A70CB4A49A", + "878DFE5B43AC858EA37B3A9EEBA9E244F1848A30F78B2E5AC5B3EBDE81AC7D45", + "1349A1318B1426E6F724CBFE7ECD2C46008A364A96C4BD20C83FC1C4EBB2EB4A" + ].map((key) => KeyRing.fromPrivate(new Buffer(key, "hex"))), getAddressFixtures: () => { assert(testHelpers.rings.length === testHelpers.names.length); @@ -35,7 +35,7 @@ var testHelpers = { }, getNode: async () => { - var node = new TrustIsRisk.FullNode({network: 'regtest', passphrase: 'secret'}); + var node = new TrustIsRisk.FullNode({network: "regtest", passphrase: "secret"}); await node.open(); await node.connect(); @@ -46,8 +46,8 @@ var testHelpers = { getWalletDB: async (node) => { var walletDB = new WalletDB({ - network: 'regtest', - db: 'memory', + network: "regtest", + db: "memory", client: new bcoin.node.NodeClient(node) }); @@ -60,9 +60,9 @@ var testHelpers = { getWallet: async (walletDB, id) => { var options = { id, - passphrase: 'secret', + passphrase: "secret", witness: false, - type: 'pubkeyhash' + type: "pubkeyhash" }; return walletDB.create(options); @@ -81,7 +81,7 @@ var testHelpers = { }, bufferToScript: (data) => { - return `0x${Number(data.length).toString(16)} 0x${data.toString('hex')}`; + return `0x${Number(data.length).toString(16)} 0x${data.toString("hex")}`; }, getP2PKHOutput: (to, value) => { @@ -94,7 +94,7 @@ var testHelpers = { getP2PKHInput: (pubKey, prevout) => { if (!prevout) { prevout = { // Don't care - hash: 'v0pnhphaf4r5wz63j60vnh27s1bftl260qq621y458tn0g4x64u64yqz6d7qi6i8', + hash: "v0pnhphaf4r5wz63j60vnh27s1bftl260qq621y458tn0g4x64u64yqz6d7qi6i8", index: 2 }; } diff --git a/test/trust_is_risk.js b/test/trust_is_risk.js index ddb3d36..7c4db6a 100644 --- a/test/trust_is_risk.js +++ b/test/trust_is_risk.js @@ -1,19 +1,19 @@ -var Trust = require('../'); -var helpers = require('../lib/helpers.js'); -var bcoin = require('bcoin'); +var Trust = require("../"); +var helpers = require("../lib/helpers.js"); +var bcoin = require("bcoin"); var Coin = bcoin.primitives.Coin; var Address = bcoin.primitives.Address; var Input = bcoin.primitives.Input; var MTX = bcoin.primitives.MTX; -var testHelpers = require('./helpers'); -var consensus = require('bcoin/lib/protocol/consensus'); -var sinon = require('sinon'); -var should = require('should'); -require('should-sinon'); +var testHelpers = require("./helpers"); +var consensus = require("bcoin/lib/protocol/consensus"); +var sinon = require("sinon"); +var should = require("should"); +require("should-sinon"); const COIN = bcoin.consensus.COIN; -describe('TrustIsRisk', () => { +describe("TrustIsRisk", () => { var addr = testHelpers.getAddressFixtures(); // Add base58 address variables to scope. for (name in addr) { @@ -30,7 +30,7 @@ describe('TrustIsRisk', () => { var inputOneOfTwoMultisig = new Input({ prevout: { - hash: trustIncreasingTX.hash().toString('hex'), + hash: trustIncreasingTX.hash().toString("hex"), index: 0 }, script: bcoin.script.fromString( @@ -49,8 +49,8 @@ describe('TrustIsRisk', () => { }); }); - describe('.getDirectTrust()', () => { - it('returns zero for two arbitary parties that do not trust each other', () => { + describe(".getDirectTrust()", () => { + it("returns zero for two arbitary parties that do not trust each other", () => { should(tir.getDirectTrust(addr.alice.base58, bob)).equal(0); should(tir.getDirectTrust(bob, alice)).equal(0); should(tir.getDirectTrust(charlie, alice)).equal(0); @@ -58,15 +58,15 @@ describe('TrustIsRisk', () => { should(tir.getDirectTrust(charlie, frank)).equal(0); }); - it('returns Infinity for one\'s direct trust to themselves', () => { + it("returns Infinity for one's direct trust to themselves", () => { should(tir.getDirectTrust(alice, alice)).equal(Infinity); should(tir.getDirectTrust(bob, bob)).equal(Infinity); }); }); - describe('.addTX()', () => { - describe('with a non-TIR transaction', () => { - it('does not change trust', () => { + describe(".addTX()", () => { + describe("with a non-TIR transaction", () => { + it("does not change trust", () => { trustIncreasingMTX.outputs[0] = testHelpers.getP2PKHOutput(charlie, 50 * COIN); tir.parseTXAsTrustIncrease(trustIncreasingMTX.toTX()); @@ -74,22 +74,22 @@ describe('TrustIsRisk', () => { }); }); - describe('with a trust increasing transaction', () => { - it('correctly increases trust', () => { + describe("with a trust increasing transaction", () => { + it("correctly increases trust", () => { tir.addTX(trustIncreasingTX); should(tir.getDirectTrust(alice, bob)).equal(42 * COIN); should(tir.getDirectTrust(bob, alice)).equal(0); }); - it('which has more than one input does not change trust', () => { + it("which has more than one input does not change trust", () => { trustIncreasingMTX.inputs.push(trustIncreasingMTX.inputs[0].clone()); tir.addTX(trustIncreasingMTX.toTX()); should(tir.getDirectTrust(alice, bob)).equal(0); }); - it('which has a change output correctly increases trust', () => { + it("which has a change output correctly increases trust", () => { trustIncreasingMTX.outputs[0].value -= 10 * COIN; trustIncreasingMTX.outputs.push(testHelpers.getP2PKHOutput(alice, 10 * COIN)); tir.addTX(trustIncreasingMTX.toTX()); @@ -97,7 +97,7 @@ describe('TrustIsRisk', () => { should(tir.getDirectTrust(alice, bob)).equal(32 * COIN); }); - it('which has two change outputs does not change trust', () => { + it("which has two change outputs does not change trust", () => { trustIncreasingMTX.outputs[0].value -= 10; for (var i = 0; i < 2; i++) { trustIncreasingMTX.outputs.push(testHelpers.getP2PKHOutput(alice, 5 * COIN)); @@ -107,7 +107,7 @@ describe('TrustIsRisk', () => { should(tir.getDirectTrust(alice, bob)).equal(0); }); - it('which has a second output that is not a change output does not change trust', () => { + it("which has a second output that is not a change output does not change trust", () => { trustIncreasingMTX.outputs[0].value -= 10 * COIN; trustIncreasingMTX.outputs.push(testHelpers.getP2PKHOutput(charlie, 5 * COIN)); tir.addTX(trustIncreasingMTX.toTX()); @@ -115,31 +115,31 @@ describe('TrustIsRisk', () => { should(tir.getDirectTrust(alice, bob)).equal(0); }); - it('which has been processed before throws', () => { + it("which has been processed before throws", () => { var tx = trustIncreasingMTX.toTX(); should(tir.addTX(tx)); should.throws(() => tir.addTX(tx), /already processed/i); }); }); - describe('with a trust decreasing transaction', () => { + describe("with a trust decreasing transaction", () => { beforeEach(() => { tir.addTX(trustIncreasingTX); }); - it('correctly decreases trust', () => { + it("correctly decreases trust", () => { tir.addTX(trustDecreasingMTX.toTX()); should(tir.getDirectTrust(alice, bob)).equal(20 * COIN); }); - it('which has a second input decreases trust to zero', () => { + it("which has a second input decreases trust to zero", () => { trustDecreasingMTX.inputs.push(testHelpers.getP2PKHInput(addr.alice.pubKey)); tir.addTX(trustDecreasingMTX.toTX()); should(tir.getDirectTrust(alice, bob)).equal(0); }); - it('which has more than one trust outputs decreases trust to zero', () => { + it("which has more than one trust outputs decreases trust to zero", () => { trustDecreasingMTX.outputs[0].value -= 15 * COIN; trustDecreasingMTX.outputs.push( testHelpers.getOneOfTwoMultisigOutput(addr.alice.pubKey, addr.bob.pubKey, 5 * COIN)); @@ -149,18 +149,18 @@ describe('TrustIsRisk', () => { }); }); - describe('.getTrustIncreasingMTX()', () => { - it('creates valid trust-increasing transactions', async () => { - var getTXStub = sinon.stub(node, 'getCoin'); + describe(".getTrustIncreasingMTX()", () => { + it("creates valid trust-increasing transactions", async () => { + var getTXStub = sinon.stub(node, "getCoin"); var prevOutput = { - hash: 'v1pnhp2af4r5wz63j60vnh27s1bftl260qq621y458tn0g4x64u64yqz6d7qi6i8', + hash: "v1pnhp2af4r5wz63j60vnh27s1bftl260qq621y458tn0g4x64u64yqz6d7qi6i8", index: 1 }; getTXStub.withArgs(prevOutput.hash).returns(new Coin({ - script: testHelpers.getP2PKHOutput(alice, 1).script, - value: 1000 * COIN + script: testHelpers.getP2PKHOutput(alice, 1).script, + value: 1000 * COIN })); var mtx = await tir.getTrustIncreasingMTX(addr.alice.privKey, addr.bob.pubKey, prevOutput, @@ -171,19 +171,19 @@ describe('TrustIsRisk', () => { mtx.outputs.length.should.equal(2); var trustOutput = mtx.outputs[0]; - trustOutput.getType().should.equal('multisig'); + trustOutput.getType().should.equal("multisig"); [1, 2].map((i) => helpers.pubKeyToEntity(trustOutput.script.get(i))).sort() .should.deepEqual([alice, bob].sort()); trustOutput.value.should.equal(100 * COIN); var changeOutput = mtx.outputs[1]; - changeOutput.getType().should.equal('pubkeyhash'); + changeOutput.getType().should.equal("pubkeyhash"); changeOutput.getAddress().toBase58().should.equal(alice); changeOutput.value.should.equal(900 * COIN - 1000); }); }); - describe('.getTrustDecreasingMTX()', () => { + describe(".getTrustDecreasingMTX()", () => { var trustTXs; beforeEach(() => { var tx; @@ -217,12 +217,12 @@ describe('TrustIsRisk', () => { mtx.inputs.length.should.equal(1); mtx.inputs[0].prevout.should.have.properties({ - hash: trustTXs[0].hash().toString('hex'), + hash: trustTXs[0].hash().toString("hex"), index: 0 }); mtx.outputs.length.should.equal(1); // Single P2PKH output - mtx.outputs[0].getType().should.equal('pubkeyhash'); + mtx.outputs[0].getType().should.equal("pubkeyhash"); mtx.outputs[0].getAddress().toBase58().should.equal(recipient); mtx.outputs[0].value.should.equal(42 * COIN - 1000); @@ -230,59 +230,59 @@ describe('TrustIsRisk', () => { mtx.inputs.length.should.equal(1); mtx.inputs[0].prevout.should.have.properties({ - hash: trustTXs[1].hash().toString('hex'), + hash: trustTXs[1].hash().toString("hex"), index: 0 }); mtx.outputs.length.should.equal(2); // One P2PKH output and one multisig trust output mtx.outputs[1].script.toString().should.equal(trustTXs[1].outputs[0].script.toString()); mtx.outputs[1].value.should.equal(60 * COIN); - mtx.outputs[0].getType().should.equal('pubkeyhash'); + mtx.outputs[0].getType().should.equal("pubkeyhash"); mtx.outputs[0].getAddress().toBase58().should.equal(recipient); mtx.outputs[0].value.should.equal(40 * COIN - 1000); }; - it('creates correct trust decreasing transactions', () => { + it("creates correct trust decreasing transactions", () => { var mtxs = tir.getTrustDecreasingMTXs(addr.alice.privKey, addr.bob.pubKey, 82 * COIN); checkMTXs(mtxs, alice); }); - it('creates correct trust stealing transactions', () => { + it("creates correct trust stealing transactions", () => { var mtxs = tir.getTrustDecreasingMTXs(addr.alice.privKey, addr.bob.pubKey, 82 * COIN, charlie); checkMTXs(mtxs, charlie); }); - it('throws when trying to decrease self-trust', () => { + it("throws when trying to decrease self-trust", () => { should.throws(() => tir.getTrustDecreasingMTXs(addr.alice.privKey, addr.alice.pubKey, 10 * COIN) , /self-trust/i); }); - it('throws when there is not enough trust', () => { + it("throws when there is not enough trust", () => { should.throws(() => tir.getTrustDecreasingMTXs(addr.alice.privKey, addr.bob.pubKey, 700 * COIN) , /insufficient trust/i); }); - }); + }); - describe('.getTrust()', () => { - it('returns zero for two arbitary parties that do not trust each other', () => { + describe(".getTrust()", () => { + it("returns zero for two arbitary parties that do not trust each other", () => { should(tir.getTrust(alice, bob)).equal(0); should(tir.getTrust(bob, alice)).equal(0); should(tir.getTrust(charlie, alice)).equal(0); should(tir.getTrust(alice, charlie)).equal(0); }); - it('returns Infinity for one\'s trust to themselves', () => { + it("returns Infinity for one's trust to themselves", () => { should(tir.getTrust(alice, alice)).equal(Infinity); should(tir.getTrust(bob, bob)).equal(Infinity); }); - describe('after applying the Nobody Likes Frank graph example', () => { + describe("after applying the Nobody Likes Frank graph example", () => { beforeEach(() => { - testHelpers.applyGraph(tir, './graphs/nobodyLikesFrank.json', addr); + testHelpers.applyGraph(tir, "./graphs/nobodyLikesFrank.json", addr); }); - it('correctly computes trusts', () => { + it("correctly computes trusts", () => { should(tir.getTrust(alice, alice)).equal(Infinity); should(tir.getTrust(alice, bob)).equal(10); should(tir.getTrust(alice, charlie)).equal(1); @@ -340,7 +340,7 @@ describe('TrustIsRisk', () => { should(tir.getTrust(george, george)).equal(Infinity); }); - it('correctly computes trusts when bob trusts frank', () => { + it("correctly computes trusts when bob trusts frank", () => { tir.addTX(testHelpers.getTrustIncreasingMTX(addr.bob.pubKey, addr.frank.pubKey, 8).toTX()); should(tir.getTrust(george, frank)).equal(0); should(tir.getTrust(alice, frank)).equal(8); From d28d1b66ccf351cb34d96b7e25e89575caacb5c2 Mon Sep 17 00:00:00 2001 From: Christos Porios Date: Mon, 29 May 2017 17:35:04 +0000 Subject: [PATCH 12/20] Refactor: use consistent variable names --- lib/direct_trust.js | 18 ++++---- lib/trust_db.js | 52 ++++++++++----------- lib/trust_is_risk.js | 108 +++++++++++++++++++++---------------------- src/direct_trust.js | 22 ++++----- src/trust_db.js | 52 ++++++++++----------- src/trust_is_risk.js | 108 +++++++++++++++++++++---------------------- test/full_node.js | 16 +++---- test/helpers.js | 24 +++++----- 8 files changed, 200 insertions(+), 200 deletions(-) diff --git a/lib/direct_trust.js b/lib/direct_trust.js index d1da9c8..0656934 100644 --- a/lib/direct_trust.js +++ b/lib/direct_trust.js @@ -5,8 +5,8 @@ var assert = require("assert"); var helpers = require("./helpers"); + - @@ -18,8 +18,8 @@ var helpers = require("./helpers"); class DirectTrust { + - // Every DT is associated with a transaction output, except for non-standard trust decreasing @@ -74,17 +74,17 @@ class DirectTrust { return valid; } - getFromEntity() { - return helpers.pubKeyToEntity(this.from); + getOriginEntity() { + return helpers.pubKeyToEntity(this.origin); } - getToEntity() { - return helpers.pubKeyToEntity(this.to); + getDestEntity() { + return helpers.pubKeyToEntity(this.dest); } spend(next ) { assert(!this.isSpent()); - assert(this.from.equals(next.from) && this.to.equals(next.to)); + assert(this.origin.equals(next.origin) && this.dest.equals(next.dest)); assert(next.amount <= this.amount); this.next = next; @@ -93,8 +93,8 @@ class DirectTrust { getNullifying(txHash ) { return new DirectTrust({ - from: this.from, - to: this.to, + origin: this.origin, + dest: this.dest, amount: 0, prev: this, diff --git a/lib/trust_db.js b/lib/trust_db.js index fecf695..d894ed1 100644 --- a/lib/trust_db.js +++ b/lib/trust_db.js @@ -35,22 +35,22 @@ class TrustDB { return trust; } - getDirectTrustAmount(from , to ) { - if (from === to) return Infinity; + getDirectTrustAmount(origin , dest ) { + if (origin === dest) return Infinity; - var trusts = this.getSpendableDirectTrusts(from, to); + var trusts = this.getSpendableDirectTrusts(origin, dest); return trusts.reduce((sum, t) => sum + t.amount, 0); } - getSpendableDirectTrusts(from , to ) { - return this.getDirectTrusts(from, to).filter((t) => t.isSpendable()); + getSpendableDirectTrusts(origin , dest ) { + return this.getDirectTrusts(origin, dest).filter((t) => t.isSpendable()); } - getDirectTrusts(from , to ) { - var fromMap = this.directTrusts.get(from); - if (!fromMap) return []; + getDirectTrusts(origin , dest ) { + var originMap = this.directTrusts.get(origin); + if (!originMap) return []; - var trusts = fromMap.get(to); + var trusts = originMap.get(dest); if (!trusts) return []; return trusts; @@ -58,21 +58,21 @@ class TrustDB { getGraphWeightMatrix() { var entitiesArr = this.getEntities(); - return entitiesArr.map((from) => { - return entitiesArr.map((to) => this.getDirectTrustAmount(from, to)); + return entitiesArr.map((origin) => { + return entitiesArr.map((dest) => this.getDirectTrustAmount(origin, dest)); }); } - getTrustAmount(from , to ) { + getTrustAmount(origin , dest ) { // TODO: Optimize - if (from === to) return Infinity; + if (origin === dest) return Infinity; var graph = this.getGraphWeightMatrix(); - var fromIndex = this.getEntityIndex(from); - var toIndex = this.getEntityIndex(to); + var originIndex = this.getEntityIndex(origin); + var destIndex = this.getEntityIndex(dest); - if (fromIndex === -1 || toIndex === -1) return 0; - else return maxFlow(graph, fromIndex, toIndex); + if (originIndex === -1 || destIndex === -1) return 0; + else return maxFlow(graph, originIndex, destIndex); } getEntities() { @@ -93,15 +93,15 @@ class TrustDB { } add(trust ) { - var from = trust.getFromEntity(); - var to = trust.getToEntity(); - assert(from !== to); + var origin = trust.getOriginEntity(); + var dest = trust.getDestEntity(); + assert(origin !== dest); - if (!this.directTrusts.has(from)) this.directTrusts.set(from, new Map()); - var fromMap = ((this.directTrusts.get(from) ) ); + if (!this.directTrusts.has(origin)) this.directTrusts.set(origin, new Map()); + var originMap = ((this.directTrusts.get(origin) ) ); - if (!fromMap.has(to)) fromMap.set(to, []); - var trusts = ((fromMap.get(to) ) ); + if (!originMap.has(dest)) originMap.set(dest, []); + var trusts = ((originMap.get(dest) ) ); if (trust.prev !== null) { trust.prev.spend(trust); @@ -112,8 +112,8 @@ class TrustDB { trusts.push(trust); this.txToDirectTrust.set(trust.txHash, trust); - this.entities.add(from); - this.entities.add(to); + this.entities.add(origin); + this.entities.add(dest); } } diff --git a/lib/trust_is_risk.js b/lib/trust_is_risk.js index 7e0b8cb..d13fe7a 100644 --- a/lib/trust_is_risk.js +++ b/lib/trust_is_risk.js @@ -24,12 +24,12 @@ class TrustIsRisk { this.node.on("tx", this.addTX.bind(this)); } - getTrust(from , to ) { - return this.db.getTrustAmount(from, to); + getTrust(origin , dest ) { + return this.db.getTrustAmount(origin, dest); } - getDirectTrust(from , to ) { - return this.db.getDirectTrustAmount(from, to); + getDirectTrust(origin , dest ) { + return this.db.getDirectTrustAmount(origin, dest); } // Attempts to parse a bitcoin transaction as a trust change and adds it to the trust network @@ -66,22 +66,22 @@ class TrustIsRisk { // Returns a promise resolving to a mutable transaction object, which increases a trust // relationship by some amount. It will spend the outpoint, which must reference a P2PKH output - // payable to the sender. - // Any satoshis not spent will be returned to the sender, minus the fees, via P2PKH. - async getTrustIncreasingMTX(fromPrivate , to , outpoint , + // payable to the sender. The origin key must be a private key. Any satoshis not spent will be + // returned to the sender, minus the fees, via P2PKH. + async getTrustIncreasingMTX(origin , dest , outpoint , trustAmount , fee ) { if (!fee) fee = 1000; // TODO: estimate this var coin = await this.node.getCoin(outpoint.hash, outpoint.index); if (!coin) throw new Error("Could not find coin"); - var fromKeyRing = KeyRing.fromPrivate(fromPrivate); - var from = fromKeyRing.getPublicKey(); + var originKeyRing = KeyRing.fromPrivate(origin); + var originPubKey = originKeyRing.getPublicKey(); var mtx = new MTX({ outputs: [ new Output({ - script: bcoin.script.fromMultisig(1, 2, [from, to]), + script: bcoin.script.fromMultisig(1, 2, [originPubKey, dest]), value: trustAmount }) ] @@ -90,16 +90,16 @@ class TrustIsRisk { var changeAmount = coin.value - trustAmount - fee; if (changeAmount) { mtx.addOutput(new Output({ - script: bcoin.script.fromPubkeyhash(bcoin.crypto.hash160(from)), + script: bcoin.script.fromPubkeyhash(bcoin.crypto.hash160(originPubKey)), value: changeAmount })); } mtx.addCoin(coin); - var success = mtx.scriptVector(coin.script, mtx.inputs[0].script, fromKeyRing); + var success = mtx.scriptVector(coin.script, mtx.inputs[0].script, originKeyRing); assert(success); - var signedCount = mtx.sign(fromKeyRing); + var signedCount = mtx.sign(originKeyRing); assert(signedCount === 1); return mtx; @@ -108,34 +108,34 @@ class TrustIsRisk { // Returns an array of trust-decreasing mutable transaction objects, which reduce a trust // relationship by the amount specified. The payee will receive the amount deducted minus the // transaction fees via P2PKH. - // If steal is undefined or set to false, then the `from` key is expected to be a private key and - // the `to` key is expected to be a public key. If steal is set to true, then `from` is expected - // to be a public key and `to` is expected to be a private key. The private key will be used to - // sign the transaction. - getTrustDecreasingMTXs(from , to , trustDecreaseAmount , payee , + // If steal is undefined or set to false, then the `origin` key is expected to be a private key + // and the `dest` key is expected to be a public key. If steal is set to true, then `origin` is + // expected to be a public key and `dest` is expected to be a private key. The private key will be + // used to sign the transaction. + getTrustDecreasingMTXs(origin , dest , trustDecreaseAmount , payee , steal , fee ) { if (steal === undefined) steal = false; - var signingKeyRing, fromKeyRing, toKeyRing; + var signingKeyRing, originKeyRing, destKeyRing; if (!steal) { - signingKeyRing = KeyRing.fromPrivate(from); - fromKeyRing = KeyRing.fromPrivate(from); - toKeyRing = KeyRing.fromPublic(to); + signingKeyRing = KeyRing.fromPrivate(origin); + originKeyRing = KeyRing.fromPrivate(origin); + destKeyRing = KeyRing.fromPublic(dest); } else { - signingKeyRing = KeyRing.fromPrivate(to); - fromKeyRing = KeyRing.fromPublic(from); - toKeyRing = KeyRing.fromPrivate(to); + signingKeyRing = KeyRing.fromPrivate(dest); + originKeyRing = KeyRing.fromPublic(origin); + destKeyRing = KeyRing.fromPrivate(dest); } - var fromAddress = helpers.pubKeyToEntity(fromKeyRing.getPublicKey()); - var toAddress = helpers.pubKeyToEntity(toKeyRing.getPublicKey()); + var originAddress = helpers.pubKeyToEntity(originKeyRing.getPublicKey()); + var destAddress = helpers.pubKeyToEntity(destKeyRing.getPublicKey()); - if (fromAddress === toAddress) throw new Error("Can't decrease self-trust"); + if (originAddress === destAddress) throw new Error("Can't decrease self-trust"); - var existingTrustAmount = this.db.getDirectTrustAmount(fromAddress, toAddress); + var existingTrustAmount = this.db.getDirectTrustAmount(originAddress, destAddress); if (existingTrustAmount < trustDecreaseAmount) throw new Error("Insufficient trust"); - var directTrusts = this.db.getSpendableDirectTrusts(fromAddress, toAddress); + var directTrusts = this.db.getSpendableDirectTrusts(originAddress, destAddress); return directTrusts.map((directTrust) => { var decrease = Math.min(trustDecreaseAmount, directTrust.amount); if (decrease === 0) return null; @@ -146,7 +146,7 @@ class TrustIsRisk { getTrustDecreasingMTX(directTrust , decreaseAmount , payee , signingKeyRing , fee ) { - if (!payee) payee = directTrust.getFromEntity(); + if (!payee) payee = directTrust.getOriginEntity(); if (!fee) fee = 1000; // TODO: estimate this var mtx = new MTX({ @@ -162,13 +162,13 @@ class TrustIsRisk { var remainingTrustAmount = directTrust.amount - decreaseAmount; if (remainingTrustAmount > 0) { mtx.addOutput(new Output({ - script: bcoin.script.fromMultisig(1, 2, [directTrust.from, directTrust.to]), + script: bcoin.script.fromMultisig(1, 2, [directTrust.origin, directTrust.dest]), value: remainingTrustAmount })); } var success = mtx.scriptVector(((directTrust.script ) ), - mtx.inputs[0].script, KeyRing.fromPublic(directTrust.from)); + mtx.inputs[0].script, KeyRing.fromPublic(directTrust.origin)); assert(success); success = mtx.signInput(0, new Coin({script: directTrust.script, value: directTrust.amount}), @@ -183,14 +183,14 @@ class TrustIsRisk { var input = tx.inputs[0]; if (input.getType() !== "pubkeyhash") return null; // TODO: This is unreliable if (this.db.isTrustOutput(input.prevout.hash.toString("hex"), input.prevout.index)) return null; - var sender = tx.inputs[0].getAddress().toBase58(); + var origin = tx.inputs[0].getAddress().toBase58(); if (tx.outputs.length === 0 || tx.outputs.length > 2) return null; - var trustOutputs = this.searchForDirectTrustOutputs(tx, sender); + var trustOutputs = this.searchForDirectTrustOutputs(tx, origin); if (trustOutputs.length !== 1) return null; - var changeOutputCount = tx.outputs.filter((o) => this.isChangeOutput(o, sender)).length; + var changeOutputCount = tx.outputs.filter((o) => this.isChangeOutput(o, origin)).length; if (changeOutputCount + 1 !== tx.outputs.length) return null; return trustOutputs[0]; @@ -215,8 +215,8 @@ class TrustIsRisk { if (tx.inputs.length !== 1) return nullTrust; - var trustOutputs = this.searchForDirectTrustOutputs(tx, prevTrust.getFromEntity(), - prevTrust.getToEntity()); + var trustOutputs = this.searchForDirectTrustOutputs(tx, prevTrust.getOriginEntity(), + prevTrust.getDestEntity()); if (trustOutputs.length != 1) return nullTrust; var nextTrust = trustOutputs[0]; @@ -230,46 +230,46 @@ class TrustIsRisk { // Returns a list of the corresponding DirectTrust objects. // If the recipient parameter is set, it will limit the results only to the outputs being sent to // the recipient. - searchForDirectTrustOutputs(tx , sender , recipient ) { + searchForDirectTrustOutputs(tx , origin , recipient ) { var directTrusts = tx.outputs.map((output, outputIndex) => - this.parseOutputAsDirectTrust(tx, outputIndex, sender) + this.parseOutputAsDirectTrust(tx, outputIndex, origin) ).filter(Boolean); if (recipient) { - directTrusts.filter((trust) => trust.to === recipient); + directTrusts.filter((trust) => trust.dest === recipient); } return directTrusts; } - isChangeOutput(output , sender ) { + isChangeOutput(output , origin ) { return (output.getType() === "pubkeyhash") - && (output.getAddress().toBase58() === sender); + && (output.getAddress().toBase58() === origin); } - parseOutputAsDirectTrust(tx , outputIndex , sender ) + parseOutputAsDirectTrust(tx , outputIndex , origin ) { var txHash = tx.hash().toString("hex"); var output = tx.outputs[outputIndex]; if (output.getType() !== "multisig") return null; - + var entities = [1, 2].map((i) => helpers.pubKeyToEntity(output.script.get(i))); if (entities[0] === entities[1]) return null; - var from, to; - if (entities[0] === sender) { - from = output.script.get(1); - to = output.script.get(2); + var originPubKey, destPubKey; + if (entities[0] === origin) { + originPubKey = output.script.get(1); + destPubKey = output.script.get(2); } - else if (entities[1] === sender) { - from = output.script.get(2); - to = output.script.get(1); + else if (entities[1] === origin) { + originPubKey = output.script.get(2); + destPubKey = output.script.get(1); } else return null; return new DirectTrust({ - from, - to, + origin: originPubKey, + dest: destPubKey, amount: Number(output.value), txHash, diff --git a/src/direct_trust.js b/src/direct_trust.js index 70662ed..15fbae6 100644 --- a/src/direct_trust.js +++ b/src/direct_trust.js @@ -5,8 +5,8 @@ var assert = require("assert"); var helpers = require("./helpers"); type DirectTrustOptions = { - from : Key, - to : Key, + origin : Key, + dest : Key, amount : number, txHash : string, @@ -18,8 +18,8 @@ type DirectTrustOptions = { } class DirectTrust { - from : Key - to : Key + origin : Key + dest : Key amount : number // Every DT is associated with a transaction output, except for non-standard trust decreasing @@ -74,17 +74,17 @@ class DirectTrust { return valid; } - getFromEntity() : Entity { - return helpers.pubKeyToEntity(this.from); + getOriginEntity() : Entity { + return helpers.pubKeyToEntity(this.origin); } - getToEntity() : Entity { - return helpers.pubKeyToEntity(this.to); + getDestEntity() : Entity { + return helpers.pubKeyToEntity(this.dest); } spend(next : DirectTrust) : void { assert(!this.isSpent()); - assert(this.from.equals(next.from) && this.to.equals(next.to)); + assert(this.origin.equals(next.origin) && this.dest.equals(next.dest)); assert(next.amount <= this.amount); this.next = next; @@ -93,8 +93,8 @@ class DirectTrust { getNullifying(txHash : string) : DirectTrust { return new DirectTrust({ - from: this.from, - to: this.to, + origin: this.origin, + dest: this.dest, amount: 0, prev: this, diff --git a/src/trust_db.js b/src/trust_db.js index 449f50a..650616b 100644 --- a/src/trust_db.js +++ b/src/trust_db.js @@ -35,22 +35,22 @@ class TrustDB { return trust; } - getDirectTrustAmount(from : Entity, to : Entity) : number { - if (from === to) return Infinity; + getDirectTrustAmount(origin : Entity, dest : Entity) : number { + if (origin === dest) return Infinity; - var trusts = this.getSpendableDirectTrusts(from, to); + var trusts = this.getSpendableDirectTrusts(origin, dest); return trusts.reduce((sum, t) => sum + t.amount, 0); } - getSpendableDirectTrusts(from : Entity, to : Entity) : DirectTrust[] { - return this.getDirectTrusts(from, to).filter((t) => t.isSpendable()); + getSpendableDirectTrusts(origin : Entity, dest : Entity) : DirectTrust[] { + return this.getDirectTrusts(origin, dest).filter((t) => t.isSpendable()); } - getDirectTrusts(from : Entity, to : Entity) : DirectTrust[] { - var fromMap = this.directTrusts.get(from); - if (!fromMap) return []; + getDirectTrusts(origin : Entity, dest : Entity) : DirectTrust[] { + var originMap = this.directTrusts.get(origin); + if (!originMap) return []; - var trusts = fromMap.get(to); + var trusts = originMap.get(dest); if (!trusts) return []; return trusts; @@ -58,21 +58,21 @@ class TrustDB { getGraphWeightMatrix() : number[][] { var entitiesArr = this.getEntities(); - return entitiesArr.map((from) => { - return entitiesArr.map((to) => this.getDirectTrustAmount(from, to)); + return entitiesArr.map((origin) => { + return entitiesArr.map((dest) => this.getDirectTrustAmount(origin, dest)); }); } - getTrustAmount(from : Entity, to : Entity) : number { + getTrustAmount(origin : Entity, dest : Entity) : number { // TODO: Optimize - if (from === to) return Infinity; + if (origin === dest) return Infinity; var graph = this.getGraphWeightMatrix(); - var fromIndex = this.getEntityIndex(from); - var toIndex = this.getEntityIndex(to); + var originIndex = this.getEntityIndex(origin); + var destIndex = this.getEntityIndex(dest); - if (fromIndex === -1 || toIndex === -1) return 0; - else return maxFlow(graph, fromIndex, toIndex); + if (originIndex === -1 || destIndex === -1) return 0; + else return maxFlow(graph, originIndex, destIndex); } getEntities() : Entity[] { @@ -93,15 +93,15 @@ class TrustDB { } add(trust : DirectTrust) { - var from = trust.getFromEntity(); - var to = trust.getToEntity(); - assert(from !== to); + var origin = trust.getOriginEntity(); + var dest = trust.getDestEntity(); + assert(origin !== dest); - if (!this.directTrusts.has(from)) this.directTrusts.set(from, new Map()); - var fromMap = ((this.directTrusts.get(from) : any) : Map>); + if (!this.directTrusts.has(origin)) this.directTrusts.set(origin, new Map()); + var originMap = ((this.directTrusts.get(origin) : any) : Map>); - if (!fromMap.has(to)) fromMap.set(to, []); - var trusts = ((fromMap.get(to) : any) : Array); + if (!originMap.has(dest)) originMap.set(dest, []); + var trusts = ((originMap.get(dest) : any) : Array); if (trust.prev !== null) { trust.prev.spend(trust); @@ -112,8 +112,8 @@ class TrustDB { trusts.push(trust); this.txToDirectTrust.set(trust.txHash, trust); - this.entities.add(from); - this.entities.add(to); + this.entities.add(origin); + this.entities.add(dest); } } diff --git a/src/trust_is_risk.js b/src/trust_is_risk.js index db3ec16..fc1f8ff 100644 --- a/src/trust_is_risk.js +++ b/src/trust_is_risk.js @@ -24,12 +24,12 @@ class TrustIsRisk { this.node.on("tx", this.addTX.bind(this)); } - getTrust(from : Entity, to : Entity) { - return this.db.getTrustAmount(from, to); + getTrust(origin : Entity, dest : Entity) { + return this.db.getTrustAmount(origin, dest); } - getDirectTrust(from : Entity, to : Entity) { - return this.db.getDirectTrustAmount(from, to); + getDirectTrust(origin : Entity, dest : Entity) { + return this.db.getDirectTrustAmount(origin, dest); } // Attempts to parse a bitcoin transaction as a trust change and adds it to the trust network @@ -66,22 +66,22 @@ class TrustIsRisk { // Returns a promise resolving to a mutable transaction object, which increases a trust // relationship by some amount. It will spend the outpoint, which must reference a P2PKH output - // payable to the sender. - // Any satoshis not spent will be returned to the sender, minus the fees, via P2PKH. - async getTrustIncreasingMTX(fromPrivate : Key, to : Key, outpoint : bcoin$Outpoint, + // payable to the sender. The origin key must be a private key. Any satoshis not spent will be + // returned to the sender, minus the fees, via P2PKH. + async getTrustIncreasingMTX(origin : Key, dest : Key, outpoint : bcoin$Outpoint, trustAmount : number, fee : ?number) : Promise { if (!fee) fee = 1000; // TODO: estimate this var coin = await this.node.getCoin(outpoint.hash, outpoint.index); if (!coin) throw new Error("Could not find coin"); - var fromKeyRing = KeyRing.fromPrivate(fromPrivate); - var from = fromKeyRing.getPublicKey(); + var originKeyRing = KeyRing.fromPrivate(origin); + var originPubKey = originKeyRing.getPublicKey(); var mtx = new MTX({ outputs: [ new Output({ - script: bcoin.script.fromMultisig(1, 2, [from, to]), + script: bcoin.script.fromMultisig(1, 2, [originPubKey, dest]), value: trustAmount }) ] @@ -90,16 +90,16 @@ class TrustIsRisk { var changeAmount = coin.value - trustAmount - fee; if (changeAmount) { mtx.addOutput(new Output({ - script: bcoin.script.fromPubkeyhash(bcoin.crypto.hash160(from)), + script: bcoin.script.fromPubkeyhash(bcoin.crypto.hash160(originPubKey)), value: changeAmount })); } mtx.addCoin(coin); - var success = mtx.scriptVector(coin.script, mtx.inputs[0].script, fromKeyRing); + var success = mtx.scriptVector(coin.script, mtx.inputs[0].script, originKeyRing); assert(success); - var signedCount = mtx.sign(fromKeyRing); + var signedCount = mtx.sign(originKeyRing); assert(signedCount === 1); return mtx; @@ -108,34 +108,34 @@ class TrustIsRisk { // Returns an array of trust-decreasing mutable transaction objects, which reduce a trust // relationship by the amount specified. The payee will receive the amount deducted minus the // transaction fees via P2PKH. - // If steal is undefined or set to false, then the `from` key is expected to be a private key and - // the `to` key is expected to be a public key. If steal is set to true, then `from` is expected - // to be a public key and `to` is expected to be a private key. The private key will be used to - // sign the transaction. - getTrustDecreasingMTXs(from : Key, to : Key, trustDecreaseAmount : number, payee : ?Entity, + // If steal is undefined or set to false, then the `origin` key is expected to be a private key + // and the `dest` key is expected to be a public key. If steal is set to true, then `origin` is + // expected to be a public key and `dest` is expected to be a private key. The private key will be + // used to sign the transaction. + getTrustDecreasingMTXs(origin : Key, dest : Key, trustDecreaseAmount : number, payee : ?Entity, steal : ?boolean, fee : ?number) : bcoin$MTX[] { if (steal === undefined) steal = false; - var signingKeyRing, fromKeyRing, toKeyRing; + var signingKeyRing, originKeyRing, destKeyRing; if (!steal) { - signingKeyRing = KeyRing.fromPrivate(from); - fromKeyRing = KeyRing.fromPrivate(from); - toKeyRing = KeyRing.fromPublic(to); + signingKeyRing = KeyRing.fromPrivate(origin); + originKeyRing = KeyRing.fromPrivate(origin); + destKeyRing = KeyRing.fromPublic(dest); } else { - signingKeyRing = KeyRing.fromPrivate(to); - fromKeyRing = KeyRing.fromPublic(from); - toKeyRing = KeyRing.fromPrivate(to); + signingKeyRing = KeyRing.fromPrivate(dest); + originKeyRing = KeyRing.fromPublic(origin); + destKeyRing = KeyRing.fromPrivate(dest); } - var fromAddress = helpers.pubKeyToEntity(fromKeyRing.getPublicKey()); - var toAddress = helpers.pubKeyToEntity(toKeyRing.getPublicKey()); + var originAddress = helpers.pubKeyToEntity(originKeyRing.getPublicKey()); + var destAddress = helpers.pubKeyToEntity(destKeyRing.getPublicKey()); - if (fromAddress === toAddress) throw new Error("Can't decrease self-trust"); + if (originAddress === destAddress) throw new Error("Can't decrease self-trust"); - var existingTrustAmount = this.db.getDirectTrustAmount(fromAddress, toAddress); + var existingTrustAmount = this.db.getDirectTrustAmount(originAddress, destAddress); if (existingTrustAmount < trustDecreaseAmount) throw new Error("Insufficient trust"); - var directTrusts = this.db.getSpendableDirectTrusts(fromAddress, toAddress); + var directTrusts = this.db.getSpendableDirectTrusts(originAddress, destAddress); return directTrusts.map((directTrust) => { var decrease = Math.min(trustDecreaseAmount, directTrust.amount); if (decrease === 0) return null; @@ -146,7 +146,7 @@ class TrustIsRisk { getTrustDecreasingMTX(directTrust : DirectTrust, decreaseAmount : number, payee : ?Entity, signingKeyRing : bcoin$KeyRing, fee : ?number) { - if (!payee) payee = directTrust.getFromEntity(); + if (!payee) payee = directTrust.getOriginEntity(); if (!fee) fee = 1000; // TODO: estimate this var mtx = new MTX({ @@ -162,13 +162,13 @@ class TrustIsRisk { var remainingTrustAmount = directTrust.amount - decreaseAmount; if (remainingTrustAmount > 0) { mtx.addOutput(new Output({ - script: bcoin.script.fromMultisig(1, 2, [directTrust.from, directTrust.to]), + script: bcoin.script.fromMultisig(1, 2, [directTrust.origin, directTrust.dest]), value: remainingTrustAmount })); } var success = mtx.scriptVector(((directTrust.script : any) : bcoin$Script), - mtx.inputs[0].script, KeyRing.fromPublic(directTrust.from)); + mtx.inputs[0].script, KeyRing.fromPublic(directTrust.origin)); assert(success); success = mtx.signInput(0, new Coin({script: directTrust.script, value: directTrust.amount}), @@ -183,14 +183,14 @@ class TrustIsRisk { var input = tx.inputs[0]; if (input.getType() !== "pubkeyhash") return null; // TODO: This is unreliable if (this.db.isTrustOutput(input.prevout.hash.toString("hex"), input.prevout.index)) return null; - var sender = tx.inputs[0].getAddress().toBase58(); + var origin = tx.inputs[0].getAddress().toBase58(); if (tx.outputs.length === 0 || tx.outputs.length > 2) return null; - var trustOutputs = this.searchForDirectTrustOutputs(tx, sender); + var trustOutputs = this.searchForDirectTrustOutputs(tx, origin); if (trustOutputs.length !== 1) return null; - var changeOutputCount = tx.outputs.filter((o) => this.isChangeOutput(o, sender)).length; + var changeOutputCount = tx.outputs.filter((o) => this.isChangeOutput(o, origin)).length; if (changeOutputCount + 1 !== tx.outputs.length) return null; return trustOutputs[0]; @@ -215,8 +215,8 @@ class TrustIsRisk { if (tx.inputs.length !== 1) return nullTrust; - var trustOutputs = this.searchForDirectTrustOutputs(tx, prevTrust.getFromEntity(), - prevTrust.getToEntity()); + var trustOutputs = this.searchForDirectTrustOutputs(tx, prevTrust.getOriginEntity(), + prevTrust.getDestEntity()); if (trustOutputs.length != 1) return nullTrust; var nextTrust = trustOutputs[0]; @@ -230,46 +230,46 @@ class TrustIsRisk { // Returns a list of the corresponding DirectTrust objects. // If the recipient parameter is set, it will limit the results only to the outputs being sent to // the recipient. - searchForDirectTrustOutputs(tx : bcoin$TX, sender : Entity, recipient : ?Entity) : DirectTrust[] { + searchForDirectTrustOutputs(tx : bcoin$TX, origin : Entity, recipient : ?Entity) : DirectTrust[] { var directTrusts = tx.outputs.map((output, outputIndex) => - this.parseOutputAsDirectTrust(tx, outputIndex, sender) + this.parseOutputAsDirectTrust(tx, outputIndex, origin) ).filter(Boolean); if (recipient) { - directTrusts.filter((trust) => trust.to === recipient); + directTrusts.filter((trust) => trust.dest === recipient); } return directTrusts; } - isChangeOutput(output : bcoin$Output, sender : Entity) : boolean { + isChangeOutput(output : bcoin$Output, origin : Entity) : boolean { return (output.getType() === "pubkeyhash") - && (output.getAddress().toBase58() === sender); + && (output.getAddress().toBase58() === origin); } - parseOutputAsDirectTrust(tx : bcoin$TX, outputIndex : number, sender : Entity) + parseOutputAsDirectTrust(tx : bcoin$TX, outputIndex : number, origin : Entity) : (DirectTrust | null) { var txHash = tx.hash().toString("hex"); var output = tx.outputs[outputIndex]; if (output.getType() !== "multisig") return null; - + var entities = [1, 2].map((i) => helpers.pubKeyToEntity(output.script.get(i))); if (entities[0] === entities[1]) return null; - var from, to; - if (entities[0] === sender) { - from = output.script.get(1); - to = output.script.get(2); + var originPubKey, destPubKey; + if (entities[0] === origin) { + originPubKey = output.script.get(1); + destPubKey = output.script.get(2); } - else if (entities[1] === sender) { - from = output.script.get(2); - to = output.script.get(1); + else if (entities[1] === origin) { + originPubKey = output.script.get(2); + destPubKey = output.script.get(1); } else return null; return new DirectTrust({ - from, - to, + origin: originPubKey, + dest: destPubKey, amount: Number(output.value), txHash, diff --git a/test/full_node.js b/test/full_node.js index 9ca89a4..f7cdea8 100644 --- a/test/full_node.js +++ b/test/full_node.js @@ -125,16 +125,16 @@ describe("FullNode", () => { var graph = require("./graphs/nobodyLikesFrank.json"); var promises = []; - for (var from in graph) { - var neighbours = graph[from]; - for (var to in neighbours) { - var value = neighbours[to]; + for (var origin in graph) { + var neighbours = graph[origin]; + for (var dest in neighbours) { + var value = neighbours[dest]; if (!value || value < 1) continue; - let outpoint = new Outpoint(prevout[from].hash, prevout[from].index); + let outpoint = new Outpoint(prevout[origin].hash, prevout[origin].index); - let mtx = await node.trust.getTrustIncreasingMTX(rings[from].getPrivateKey(), - rings[to].getPublicKey(), outpoint, value * consensus.COIN); + let mtx = await node.trust.getTrustIncreasingMTX(rings[origin].getPrivateKey(), + rings[dest].getPublicKey(), outpoint, value * consensus.COIN); should(await mtx.verify()); @@ -144,7 +144,7 @@ describe("FullNode", () => { node.sendTX(tx); await testHelpers.time(250); - prevout[from] = {hash: tx.hash().toString("hex"), index: 1}; + prevout[origin] = {hash: tx.hash().toString("hex"), index: 1}; } } //mtxs.forEach((mtx) => node.sendTX(mtx.toTX())); diff --git a/test/helpers.js b/test/helpers.js index 98b906b..ba54e80 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -84,8 +84,8 @@ var testHelpers = { return `0x${Number(data.length).toString(16)} 0x${data.toString("hex")}`; }, - getP2PKHOutput: (to, value) => { - var address = bcoin.primitives.Address.fromBase58(to); + getP2PKHOutput: (dest, value) => { + var address = bcoin.primitives.Address.fromBase58(dest); var script = bcoin.script.fromPubkeyhash(address.hash); return new bcoin.primitives.Output({script, value}); @@ -108,20 +108,20 @@ var testHelpers = { }); }, - getOneOfTwoMultisigOutput: (pubKeyFrom, pubKeyTo, value) => { + getOneOfTwoMultisigOutput: (originPubKey, destPubKey, value) => { return new bcoin.primitives.Output({ - script: bcoin.script.fromMultisig(1, 2, [pubKeyFrom, pubKeyTo]), + script: bcoin.script.fromMultisig(1, 2, [originPubKey, destPubKey]), value }); }, - getTrustIncreasingMTX: (pubKeyFrom, pubKeyTo, value) => { + getTrustIncreasingMTX: (originPubKey, destPubKey, value) => { return new bcoin.primitives.MTX({ inputs: [ - testHelpers.getP2PKHInput(pubKeyFrom) + testHelpers.getP2PKHInput(originPubKey) ], outputs: [ - testHelpers.getOneOfTwoMultisigOutput(pubKeyFrom, pubKeyTo, value) + testHelpers.getOneOfTwoMultisigOutput(originPubKey, destPubKey, value) ] }); }, @@ -129,11 +129,11 @@ var testHelpers = { applyGraph: (tir, fileName, addr) => { var graph = require(fileName); - for (var from in graph) { - var neighbours = graph[from]; - for (var to in neighbours) { - var value = neighbours[to]; - tir.addTX(testHelpers.getTrustIncreasingMTX(addr[from].pubKey, addr[to].pubKey, value).toTX()); + for (var origin in graph) { + var neighbours = graph[origin]; + for (var dest in neighbours) { + var value = neighbours[dest]; + tir.addTX(testHelpers.getTrustIncreasingMTX(addr[origin].pubKey, addr[dest].pubKey, value).toTX()); } } } From 22fa3c4882e31def8428597792317b38d273e513 Mon Sep 17 00:00:00 2001 From: Christos Porios Date: Mon, 29 May 2017 17:39:50 +0000 Subject: [PATCH 13/20] Add Travis CI --- .travis.yml | 10 ++++++++++ test/full_node.js | 20 +++++++++++--------- test/helpers.js | 44 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 9 deletions(-) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..58524d2 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,10 @@ +language: node_js +node_js: + - "7" + +install: + - npm install +script: + - npm run lint + - npm run typecheck + - npm test diff --git a/test/full_node.js b/test/full_node.js index f7cdea8..df82206 100644 --- a/test/full_node.js +++ b/test/full_node.js @@ -19,10 +19,12 @@ const COIN = consensus.COIN; describe("FullNode", () => { var node = null; var walletDB = null; + var NodeWatcher = null; sinon.spy(Trust.TrustIsRisk.prototype, "addTX"); beforeEach("get node", () => testHelpers.getNode().then((n) => { node = n; + watcher = new testHelpers.NodeWatcher(node); })); beforeEach("get walletDB", () => testHelpers.getWalletDB(node).then((w) => { @@ -36,21 +38,22 @@ describe("FullNode", () => { var sender = await testHelpers.getWallet(walletDB, "sender"); var receiver = await testHelpers.getWallet(walletDB, "receiver"); + await testHelpers.time(1000); // Produce a block and reward the sender, so that we have a coin to spend. await testHelpers.mineBlock(node, sender.getAddress("base58")); // Make the coin spendable. consensus.COINBASE_MATURITY = 0; - await testHelpers.time(100); + await sender.send({ outputs: [{ value: 10 * COIN, address: receiver.getAddress("base58") }] }); - - await testHelpers.time(100); + await watcher.waitForTX(); + node.trust.addTX.should.be.calledOnce(); }); @@ -74,7 +77,7 @@ describe("FullNode", () => { for(let i = 0; i < coinbaseCoinsCount; i++) { var block = await testHelpers.mineBlock(node, addresses.alice); coinbaseHashes.push(block.txs[0].hash()); - await testHelpers.time(200); + await testHelpers.time(500); } // Alice sends 20 BTC to everyone (including herself) via P2PKH @@ -110,6 +113,8 @@ describe("FullNode", () => { var tx = mtx.toTX(); node.sendTX(tx); + await watcher.waitForTX(); + prevout = {}; testHelpers.names.forEach((name) => { prevout[name] = { @@ -117,7 +122,6 @@ describe("FullNode", () => { index: testHelpers.names.indexOf(name) }; }); - await testHelpers.time(500); // Alice mines another block await testHelpers.mineBlock(node, helpers.pubKeyToEntity(rings.alice.getPublicKey())); @@ -142,13 +146,11 @@ describe("FullNode", () => { // same origin. We therefore need to sleep until the transaction is added to the pool. let tx = mtx.toTX(); node.sendTX(tx); - await testHelpers.time(250); + await watcher.waitForTX(); prevout[origin] = {hash: tx.hash().toString("hex"), index: 1}; } } - //mtxs.forEach((mtx) => node.sendTX(mtx.toTX())); - await testHelpers.time(500); // Alice mines yet another block await testHelpers.mineBlock(node, helpers.pubKeyToEntity(rings.alice.getPublicKey())); @@ -181,7 +183,7 @@ describe("FullNode", () => { should(await mtx.verify()); node.sendTX(mtx.toTX()); - await testHelpers.time(500); + await testHelpers.time(750); should(node.trust.getTrust(addresses.alice, addresses.bob)).equal(7 * COIN); }); }); diff --git a/test/helpers.js b/test/helpers.js index ba54e80..cb19900 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -139,4 +139,48 @@ var testHelpers = { } }; +class NodeWatcher { + constructor(node) { + this.txCount = 0; + this.blockCount = 0; + this.node = node; + this.node.on("tx", this.onTX.bind(this)); + this.node.on("block", this.onBlock.bind(this)); + } + + onTX() { + this.txCount++; + } + + onBlock() { + this.blockCount++; + } + + async waitForBlock(initialCount) { + if (initialCount === undefined) initialCount = this.blockCount; + await new Promise((resolve, reject) => { + var check = (() => { + if (this.blockCount > initialCount) resolve(); + else setTimeout(check, 100); + }).bind(this); + + check(); + }); + } + + async waitForTX(initialCount) { + if (initialCount === undefined) initialCount = this.txCount; + await new Promise((resolve, reject) => { + var check = (() => { + if (this.txCount > initialCount) resolve(); + else setTimeout(check, 100); + }).bind(this); + + check(); + }); + } +} + +testHelpers.NodeWatcher = NodeWatcher; + module.exports = testHelpers; From 0c7d90d2181709320bcf2c8faf100af471557292 Mon Sep 17 00:00:00 2001 From: Christos Porios Date: Mon, 29 May 2017 20:42:26 +0100 Subject: [PATCH 14/20] Fix indentation in .eslintrc --- .eslintrc.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 2597f3d..f8d4494 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,9 +1,9 @@ { - "parser": "babel-eslint", + "parser": "babel-eslint", "plugins": [ "eslint-plugin-flowtype" ], - "env": { + "env": { "es6": true, "node": true }, From 9e2f03c9a90078ccebf915b051f3d536913d07b4 Mon Sep 17 00:00:00 2001 From: Christos Porios Date: Tue, 11 Jul 2017 19:13:48 +0000 Subject: [PATCH 15/20] Code review responses --- .eslintrc.json | 2 +- .travis.yml | 4 +- lib/trust_is_risk.js | 11 +- package.json | 2 +- src/trust_is_risk.js | 11 +- test/full_node.js | 76 +++++---- test/helpers.js | 14 +- test/trust_is_risk.js | 360 +++++++++++++++++++++--------------------- 8 files changed, 243 insertions(+), 237 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 2597f3d..d39279a 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,5 +1,5 @@ { - "parser": "babel-eslint", + "parser": "babel-eslint", "plugins": [ "eslint-plugin-flowtype" ], diff --git a/.travis.yml b/.travis.yml index 58524d2..9af64ac 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,4 @@ node_js: install: - npm install script: - - npm run lint - - npm run typecheck - - npm test + - npm run check diff --git a/lib/trust_is_risk.js b/lib/trust_is_risk.js index d13fe7a..1210bd5 100644 --- a/lib/trust_is_risk.js +++ b/lib/trust_is_risk.js @@ -24,7 +24,7 @@ class TrustIsRisk { this.node.on("tx", this.addTX.bind(this)); } - getTrust(origin , dest ) { + getIndirectTrust(origin , dest ) { return this.db.getTrustAmount(origin, dest); } @@ -46,7 +46,7 @@ class TrustIsRisk { var directTrusts = this.getDirectTrusts(tx); if (directTrusts.length === 0) return false; else { - directTrusts.map(this.db.add.bind(this.db)); + directTrusts.forEach(this.db.add.bind(this.db)); return true; } } @@ -68,12 +68,13 @@ class TrustIsRisk { // relationship by some amount. It will spend the outpoint, which must reference a P2PKH output // payable to the sender. The origin key must be a private key. Any satoshis not spent will be // returned to the sender, minus the fees, via P2PKH. - async getTrustIncreasingMTX(origin , dest , outpoint , + async createTrustIncreasingMTX(origin , dest , outpoint , trustAmount , fee ) { if (!fee) fee = 1000; // TODO: estimate this var coin = await this.node.getCoin(outpoint.hash, outpoint.index); if (!coin) throw new Error("Could not find coin"); + if (origin === dest) throw new Error("Can not increase self-trust."); var originKeyRing = KeyRing.fromPrivate(origin); var originPubKey = originKeyRing.getPublicKey(); @@ -112,7 +113,7 @@ class TrustIsRisk { // and the `dest` key is expected to be a public key. If steal is set to true, then `origin` is // expected to be a public key and `dest` is expected to be a private key. The private key will be // used to sign the transaction. - getTrustDecreasingMTXs(origin , dest , trustDecreaseAmount , payee , + createTrustDecreasingMTXs(origin , dest , trustDecreaseAmount , payee , steal , fee ) { if (steal === undefined) steal = false; @@ -155,7 +156,7 @@ class TrustIsRisk { ], outputs: [new Output({ script: bcoin.script.fromPubkeyhash(Address.fromBase58(payee).hash), - value: decreaseAmount - fee + value: ((decreaseAmount - fee) < 0) ? 0 : (decreaseAmount - fee) })] }); diff --git a/package.json b/package.json index c08a511..3d8c39a 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "blockchain" ], "author": "Decrypto-org", - "license": "ISC", + "license": "MIT", "bugs": { "url": "https://github.com/decrypto-org/TrustIsRisk.js/issues" }, diff --git a/src/trust_is_risk.js b/src/trust_is_risk.js index fc1f8ff..565b6f6 100644 --- a/src/trust_is_risk.js +++ b/src/trust_is_risk.js @@ -24,7 +24,7 @@ class TrustIsRisk { this.node.on("tx", this.addTX.bind(this)); } - getTrust(origin : Entity, dest : Entity) { + getIndirectTrust(origin : Entity, dest : Entity) { return this.db.getTrustAmount(origin, dest); } @@ -46,7 +46,7 @@ class TrustIsRisk { var directTrusts = this.getDirectTrusts(tx); if (directTrusts.length === 0) return false; else { - directTrusts.map(this.db.add.bind(this.db)); + directTrusts.forEach(this.db.add.bind(this.db)); return true; } } @@ -68,12 +68,13 @@ class TrustIsRisk { // relationship by some amount. It will spend the outpoint, which must reference a P2PKH output // payable to the sender. The origin key must be a private key. Any satoshis not spent will be // returned to the sender, minus the fees, via P2PKH. - async getTrustIncreasingMTX(origin : Key, dest : Key, outpoint : bcoin$Outpoint, + async createTrustIncreasingMTX(origin : Key, dest : Key, outpoint : bcoin$Outpoint, trustAmount : number, fee : ?number) : Promise { if (!fee) fee = 1000; // TODO: estimate this var coin = await this.node.getCoin(outpoint.hash, outpoint.index); if (!coin) throw new Error("Could not find coin"); + if (origin === dest) throw new Error("Can not increase self-trust."); var originKeyRing = KeyRing.fromPrivate(origin); var originPubKey = originKeyRing.getPublicKey(); @@ -112,7 +113,7 @@ class TrustIsRisk { // and the `dest` key is expected to be a public key. If steal is set to true, then `origin` is // expected to be a public key and `dest` is expected to be a private key. The private key will be // used to sign the transaction. - getTrustDecreasingMTXs(origin : Key, dest : Key, trustDecreaseAmount : number, payee : ?Entity, + createTrustDecreasingMTXs(origin : Key, dest : Key, trustDecreaseAmount : number, payee : ?Entity, steal : ?boolean, fee : ?number) : bcoin$MTX[] { if (steal === undefined) steal = false; @@ -155,7 +156,7 @@ class TrustIsRisk { ], outputs: [new Output({ script: bcoin.script.fromPubkeyhash(Address.fromBase58(payee).hash), - value: decreaseAmount - fee + value: ((decreaseAmount - fee) < 0) ? 0 : (decreaseAmount - fee) })] }); diff --git a/test/full_node.js b/test/full_node.js index df82206..1cb7245 100644 --- a/test/full_node.js +++ b/test/full_node.js @@ -12,6 +12,7 @@ var testHelpers = require("./helpers"); var consensus = require("bcoin/lib/protocol/consensus"); var sinon = require("sinon"); var should = require("should"); +var assert = require("assert"); require("should-sinon"); const COIN = consensus.COIN; @@ -20,31 +21,32 @@ describe("FullNode", () => { var node = null; var walletDB = null; var NodeWatcher = null; + var watcher = null; sinon.spy(Trust.TrustIsRisk.prototype, "addTX"); - beforeEach("get node", () => testHelpers.getNode().then((n) => { - node = n; + beforeEach("get node", async () => { + node = await testHelpers.getNode(); watcher = new testHelpers.NodeWatcher(node); - })); + }); - beforeEach("get walletDB", () => testHelpers.getWalletDB(node).then((w) => { - walletDB = w; - })); + beforeEach("get walletDB", async () => { + walletDB = await testHelpers.getWalletDB(node); + }); afterEach("close walletDB", async () => walletDB.close()); afterEach("close node", async () => node.close()); it("should call trust.addTX() on every transaction", async function() { - var sender = await testHelpers.getWallet(walletDB, "sender"); - var receiver = await testHelpers.getWallet(walletDB, "receiver"); + var sender = await testHelpers.createWallet(walletDB, "sender"); + var receiver = await testHelpers.createWallet(walletDB, "receiver"); - await testHelpers.time(1000); + await testHelpers.delay(1000); // Produce a block and reward the sender, so that we have a coin to spend. await testHelpers.mineBlock(node, sender.getAddress("base58")); // Make the coin spendable. consensus.COINBASE_MATURITY = 0; - await testHelpers.time(100); + await testHelpers.delay(100); await sender.send({ outputs: [{ @@ -72,27 +74,26 @@ describe("FullNode", () => { // Alice mines three blocks, each rewards her with 50 spendable BTC consensus.COINBASE_MATURITY = 0; - var coinbaseCoinsCount = 3; + var blockCount = 3; var coinbaseHashes = []; - for(let i = 0; i < coinbaseCoinsCount; i++) { + for(let i = 0; i < blockCount; i++) { var block = await testHelpers.mineBlock(node, addresses.alice); coinbaseHashes.push(block.txs[0].hash()); - await testHelpers.time(500); + await testHelpers.delay(500); } // Alice sends 20 BTC to everyone (including herself) via P2PKH var sendAmount = 20; var outputs = testHelpers.names.map((name) => { - return new Output({ - script: Script.fromPubkeyhash(bcoin.crypto.hash160(rings[name].getPublicKey())), - value: sendAmount * consensus.COIN - }); + return testHelpers.getP2PKHOutput( + Address.fromHash(bcoin.crypto.hash160(rings[name].getPublicKey())).toBase58(), + sendAmount * consensus.COIN); }); // We have to use a change output, because transaction with too large a fee are considered // invalid. var fee = 0.01; - var changeAmount = 50 * coinbaseCoinsCount - sendAmount * testHelpers.names.length - fee; + var changeAmount = 50 * blockCount - sendAmount * testHelpers.names.length - fee; if (changeAmount >= 0.01) { outputs.push(new Output({ script: Script.fromPubkeyhash(bcoin.crypto.hash160(rings.alice.getPublicKey())), @@ -108,8 +109,8 @@ describe("FullNode", () => { coinbaseCoins.forEach((coin) => mtx.addCoin(coin)); var signedCount = mtx.sign(rings.alice); - signedCount.should.equal(coinbaseCoinsCount); - should(await mtx.verify()); + assert(signedCount === blockCount); + assert(await mtx.verify()); var tx = mtx.toTX(); node.sendTX(tx); @@ -125,10 +126,9 @@ describe("FullNode", () => { // Alice mines another block await testHelpers.mineBlock(node, helpers.pubKeyToEntity(rings.alice.getPublicKey())); - await testHelpers.time(500); + await testHelpers.delay(500); var graph = require("./graphs/nobodyLikesFrank.json"); - var promises = []; for (var origin in graph) { var neighbours = graph[origin]; for (var dest in neighbours) { @@ -137,13 +137,11 @@ describe("FullNode", () => { let outpoint = new Outpoint(prevout[origin].hash, prevout[origin].index); - let mtx = await node.trust.getTrustIncreasingMTX(rings[origin].getPrivateKey(), + let mtx = await node.trust.createTrustIncreasingMTX(rings[origin].getPrivateKey(), rings[dest].getPublicKey(), outpoint, value * consensus.COIN); - should(await mtx.verify()); + assert(await mtx.verify()); - // The change output from this transaction will be used in other transactions from the - // same origin. We therefore need to sleep until the transaction is added to the pool. let tx = mtx.toTX(); node.sendTX(tx); await watcher.waitForTX(); @@ -154,7 +152,7 @@ describe("FullNode", () => { // Alice mines yet another block await testHelpers.mineBlock(node, helpers.pubKeyToEntity(rings.alice.getPublicKey())); - await testHelpers.time(500); + await testHelpers.delay(500); }); it("computes trusts correctly", () => { @@ -162,20 +160,20 @@ describe("FullNode", () => { eval(`var ${name} = "${addresses[name]}";`); } - should(node.trust.getTrust(alice, alice)).equal(Infinity); - should(node.trust.getTrust(alice, bob)).equal(10 * COIN); - should(node.trust.getTrust(alice, charlie)).equal(1 * COIN); - should(node.trust.getTrust(alice, frank)).equal(0); - should(node.trust.getTrust(alice, eve)).equal(6 * COIN); + should(node.trust.getIndirectTrust(alice, alice)).equal(Infinity); + should(node.trust.getIndirectTrust(alice, bob)).equal(10 * COIN); + should(node.trust.getIndirectTrust(alice, charlie)).equal(1 * COIN); + should(node.trust.getIndirectTrust(alice, frank)).equal(0); + should(node.trust.getIndirectTrust(alice, eve)).equal(6 * COIN); - should(node.trust.getTrust(bob, alice)).equal(1 * COIN); - should(node.trust.getTrust(bob, eve)).equal(3 * COIN); - should(node.trust.getTrust(dave, eve)).equal(12 * COIN); - should(node.trust.getTrust(george, eve)).equal(0); + should(node.trust.getIndirectTrust(bob, alice)).equal(1 * COIN); + should(node.trust.getIndirectTrust(bob, eve)).equal(3 * COIN); + should(node.trust.getIndirectTrust(dave, eve)).equal(12 * COIN); + should(node.trust.getIndirectTrust(george, eve)).equal(0); }); it("after decreasing some trusts computes trusts correctly", async () => { - var mtxs = node.trust.getTrustDecreasingMTXs(rings.alice.getPrivateKey(), + var mtxs = node.trust.createTrustDecreasingMTXs(rings.alice.getPrivateKey(), rings.bob.getPublicKey(), 3 * COIN); mtxs.length.should.equal(1); var mtx = mtxs[0]; @@ -183,8 +181,8 @@ describe("FullNode", () => { should(await mtx.verify()); node.sendTX(mtx.toTX()); - await testHelpers.time(750); - should(node.trust.getTrust(addresses.alice, addresses.bob)).equal(7 * COIN); + await testHelpers.delay(750); + should(node.trust.getIndirectTrust(addresses.alice, addresses.bob)).equal(7 * COIN); }); }); }); diff --git a/test/helpers.js b/test/helpers.js index cb19900..02bcd68 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -7,8 +7,8 @@ var assert = require("assert"); var testHelpers = { names: ["alice", "bob", "charlie", "dave", "eve", "frank", "george"], rings: [ - "02b8f07a401eca4888039b1898f94db44c43ccc6d3aa8b27e9b6ed7b377b24c0", - "2437025954568a8273968aa7535dbfc444fd8f8d0f5237cd96ac7234c77810ad", + "02B8F07A401ECA4888039B1898F94DB44C43CCC6D3AA8B27E9B6ED7B377B24C0", + "2437025954568A8273968AA7535DBFC444FD8F8D0F5237CD96AC7234C77810AD", "3BBA2AF9539D09B4FD2BDEA1D3A2CE4BF5D779831B8781EE2ACF9C03378B2AD7", "19BD8D853FAEFDB9B01E4DE7F6096FF8F5F96D43E6564A5258307334A4AA59F3", "0503054CF7EBB4E62191AF1D8DE97945178D3F465EE88EF1FB4E80A70CB4A49A", @@ -57,7 +57,7 @@ var testHelpers = { return walletDB; }, - getWallet: async (walletDB, id) => { + createWallet: async (walletDB, id) => { var options = { id, passphrase: "secret", @@ -71,10 +71,12 @@ var testHelpers = { mineBlock: async (node, rewardAddress) => { var block = await node.miner.mineBlock(node.chain.tip, rewardAddress); await node.chain.add(block); + // node.chain.tip does not contain all the properties we want, + // so we need to fetch it: return node.getBlock(node.chain.tip.hash); }, - time: async (milliseconds) => { + delay: async (milliseconds) => { return new Promise((resolve, reject) => { setTimeout(resolve, milliseconds); }); @@ -126,14 +128,14 @@ var testHelpers = { }); }, - applyGraph: (tir, fileName, addr) => { + applyGraph: (trust, fileName, addressBook) => { var graph = require(fileName); for (var origin in graph) { var neighbours = graph[origin]; for (var dest in neighbours) { var value = neighbours[dest]; - tir.addTX(testHelpers.getTrustIncreasingMTX(addr[origin].pubKey, addr[dest].pubKey, value).toTX()); + trust.addTX(testHelpers.getTrustIncreasingMTX(addressBook[origin].pubKey, addressBook[dest].pubKey, value).toTX()); } } } diff --git a/test/trust_is_risk.js b/test/trust_is_risk.js index ab2b12a..e0abb54 100644 --- a/test/trust_is_risk.js +++ b/test/trust_is_risk.js @@ -68,9 +68,13 @@ describe("TrustIsRisk", () => { describe("with a non-TIR transaction", () => { it("does not change trust", () => { trustIncreasingMTX.outputs[0] = testHelpers.getP2PKHOutput(charlie, 50 * COIN); - tir.parseTXAsTrustIncrease(trustIncreasingMTX.toTX()); + tir.addTX(trustIncreasingMTX.toTX()); tir.getDirectTrust(alice, bob).should.equal(0); + tir.getDirectTrust(bob, alice).should.equal(0); + tir.getDirectTrust(alice, charlie).should.equal(0); + tir.getDirectTrust(charlie, alice).should.equal(0); + tir.getDirectTrust(charlie, dave).should.equal(0); }); }); @@ -80,6 +84,7 @@ describe("TrustIsRisk", () => { tir.getDirectTrust(alice, bob).should.equal(42 * COIN); tir.getDirectTrust(bob, alice).should.equal(0); + tir.getDirectTrust(charlie, dave).should.equal(0); }); it("which has more than one input does not change trust", () => { @@ -119,6 +124,7 @@ describe("TrustIsRisk", () => { var tx = trustIncreasingMTX.toTX(); should(tir.addTX(tx)); should.throws(() => tir.addTX(tx), /already processed/i); + tir.getDirectTrust(alice, bob).should.equal(42 * COIN); }); }); @@ -148,208 +154,208 @@ describe("TrustIsRisk", () => { tir.getDirectTrust(alice, bob).should.equal(0); }); }); + }); - describe(".getTrustIncreasingMTX()", () => { - it("creates valid trust-increasing transactions", async () => { - var getTXStub = sinon.stub(node, "getCoin"); + describe(".createTrustIncreasingMTX()", () => { + it("creates valid trust-increasing transactions", async () => { + var getTXStub = sinon.stub(node, "getCoin"); - var prevOutput = { - hash: "v1pnhp2af4r5wz63j60vnh27s1bftl260qq621y458tn0g4x64u64yqz6d7qi6i8", - index: 1 - }; + var prevOutput = { + hash: "v1pnhp2af4r5wz63j60vnh27s1bftl260qq621y458tn0g4x64u64yqz6d7qi6i8", + index: 1 + }; - getTXStub.withArgs(prevOutput.hash).returns(new Coin({ - script: testHelpers.getP2PKHOutput(alice, 1).script, - value: 1000 * COIN - })); + getTXStub.withArgs(prevOutput.hash).returns(new Coin({ + script: testHelpers.getP2PKHOutput(alice, 1).script, + value: 1000 * COIN + })); - var mtx = await tir.getTrustIncreasingMTX(addr.alice.privKey, addr.bob.pubKey, prevOutput, - 100 * COIN); + var mtx = await tir.createTrustIncreasingMTX(addr.alice.privKey, addr.bob.pubKey, prevOutput, + 100 * COIN); - mtx.inputs.length.should.equal(1); + mtx.inputs.length.should.equal(1); - mtx.outputs.length.should.equal(2); + mtx.outputs.length.should.equal(2); - var trustOutput = mtx.outputs[0]; - trustOutput.getType().should.equal("multisig"); - [1, 2].map((i) => helpers.pubKeyToEntity(trustOutput.script.get(i))).sort() - .should.deepEqual([alice, bob].sort()); - trustOutput.value.should.equal(100 * COIN); + var trustOutput = mtx.outputs[0]; + trustOutput.getType().should.equal("multisig"); + [1, 2].map((i) => helpers.pubKeyToEntity(trustOutput.script.get(i))).sort() + .should.deepEqual([alice, bob].sort()); + trustOutput.value.should.equal(100 * COIN); - var changeOutput = mtx.outputs[1]; - changeOutput.getType().should.equal("pubkeyhash"); - changeOutput.getAddress().toBase58().should.equal(alice); - changeOutput.value.should.equal(900 * COIN - 1000); - }); + var changeOutput = mtx.outputs[1]; + changeOutput.getType().should.equal("pubkeyhash"); + changeOutput.getAddress().toBase58().should.equal(alice); + changeOutput.value.should.equal(900 * COIN - 1000); }); + }); - describe(".getTrustDecreasingMTX()", () => { - var trustTXs; - beforeEach(() => { - var tx; - trustTXs = []; + describe(".getTrustDecreasingMTX()", () => { + var trustTXs; + beforeEach(() => { + var tx; + trustTXs = []; - tx = trustIncreasingTX; - trustTXs.push(tx); - tir.addTX(tx); + tx = trustIncreasingTX; + trustTXs.push(tx); + tir.addTX(tx); - trustIncreasingMTX.outputs[0].value = 100 * COIN; - tx = trustIncreasingMTX.toTX(); - trustTXs.push(tx); - tir.addTX(tx); + trustIncreasingMTX.outputs[0].value = 100 * COIN; + tx = trustIncreasingMTX.toTX(); + trustTXs.push(tx); + tir.addTX(tx); - trustIncreasingMTX.outputs[0].value = 500 * COIN; - tx = trustIncreasingMTX.toTX(); - trustTXs.push(tx); - tir.addTX(tx); + trustIncreasingMTX.outputs[0].value = 500 * COIN; + tx = trustIncreasingMTX.toTX(); + trustTXs.push(tx); + tir.addTX(tx); - // Total trust 642 BTC - }); + // Total trust 642 BTC + }); - // Helper specific to the next couple of tests: - // Checks that mtxs is a list of two trust decreasing transactions. The first one spends the - // entire first trust increasing transaction, and the second spends part of the second. - // Also checks that the reduced trust is sent via P2PKH outputs to the correct recipient. - var checkMTXs = (mtxs, recipient) => { - mtxs.length.should.equal(2); - - var mtx = mtxs[0]; - - mtx.inputs.length.should.equal(1); - mtx.inputs[0].prevout.should.have.properties({ - hash: trustTXs[0].hash().toString("hex"), - index: 0 - }); - - mtx.outputs.length.should.equal(1); // Single P2PKH output - mtx.outputs[0].getType().should.equal("pubkeyhash"); - mtx.outputs[0].getAddress().toBase58().should.equal(recipient); - mtx.outputs[0].value.should.equal(42 * COIN - 1000); - - mtx = mtxs[1]; - - mtx.inputs.length.should.equal(1); - mtx.inputs[0].prevout.should.have.properties({ - hash: trustTXs[1].hash().toString("hex"), - index: 0 - }); - - mtx.outputs.length.should.equal(2); // One P2PKH output and one multisig trust output - mtx.outputs[1].script.toString().should.equal(trustTXs[1].outputs[0].script.toString()); - mtx.outputs[1].value.should.equal(60 * COIN); - mtx.outputs[0].getType().should.equal("pubkeyhash"); - mtx.outputs[0].getAddress().toBase58().should.equal(recipient); - mtx.outputs[0].value.should.equal(40 * COIN - 1000); - }; + // Helper specific to the next couple of tests: + // Checks that mtxs is a list of two trust decreasing transactions. The first one spends the + // entire first trust increasing transaction, and the second spends part of the second. + // Also checks that the reduced trust is sent via P2PKH outputs to the correct recipient. + var checkMTXs = (mtxs, recipient) => { + mtxs.length.should.equal(2); - it("creates correct trust decreasing transactions", () => { - var mtxs = tir.getTrustDecreasingMTXs(addr.alice.privKey, addr.bob.pubKey, 82 * COIN); - checkMTXs(mtxs, alice); - }); + var mtx = mtxs[0]; - it("creates correct trust stealing transactions", () => { - var mtxs = tir.getTrustDecreasingMTXs(addr.alice.privKey, addr.bob.pubKey, 82 * COIN, charlie); - checkMTXs(mtxs, charlie); + mtx.inputs.length.should.equal(1); + mtx.inputs[0].prevout.should.have.properties({ + hash: trustTXs[0].hash().toString("hex"), + index: 0 }); - it("throws when trying to decrease self-trust", () => { - should.throws(() => tir.getTrustDecreasingMTXs(addr.alice.privKey, addr.alice.pubKey, 10 * COIN) - , /self-trust/i); - }); + mtx.outputs.length.should.equal(1); // Single P2PKH output + mtx.outputs[0].getType().should.equal("pubkeyhash"); + mtx.outputs[0].getAddress().toBase58().should.equal(recipient); + mtx.outputs[0].value.should.equal(42 * COIN - 1000); - it("throws when there is not enough trust", () => { - should.throws(() => tir.getTrustDecreasingMTXs(addr.alice.privKey, addr.bob.pubKey, 700 * COIN) - , /insufficient trust/i); - + mtx = mtxs[1]; + + mtx.inputs.length.should.equal(1); + mtx.inputs[0].prevout.should.have.properties({ + hash: trustTXs[1].hash().toString("hex"), + index: 0 }); + + mtx.outputs.length.should.equal(2); // One P2PKH output and one multisig trust output + mtx.outputs[1].script.toString().should.equal(trustTXs[1].outputs[0].script.toString()); + mtx.outputs[1].value.should.equal(60 * COIN); + mtx.outputs[0].getType().should.equal("pubkeyhash"); + mtx.outputs[0].getAddress().toBase58().should.equal(recipient); + mtx.outputs[0].value.should.equal(40 * COIN - 1000); + }; + + it("creates correct trust decreasing transactions", () => { + var mtxs = tir.createTrustDecreasingMTXs(addr.alice.privKey, addr.bob.pubKey, 82 * COIN); + checkMTXs(mtxs, alice); + }); + + it("creates correct trust stealing transactions", () => { + var mtxs = tir.createTrustDecreasingMTXs(addr.alice.privKey, addr.bob.pubKey, 82 * COIN, charlie); + checkMTXs(mtxs, charlie); + }); + + it("throws when trying to decrease self-trust", () => { + should.throws(() => tir.createTrustDecreasingMTXs(addr.alice.privKey, addr.alice.pubKey, 10 * COIN) + , /self-trust/i); + }); + + it("throws when there is not enough trust", () => { + should.throws(() => tir.createTrustDecreasingMTXs(addr.alice.privKey, addr.bob.pubKey, 700 * COIN) + , /insufficient trust/i); + }); + }); - describe(".getTrust()", () => { - it("returns zero for two arbitary parties that do not trust each other", () => { - should(tir.getTrust(alice, bob)).equal(0); - should(tir.getTrust(bob, alice)).equal(0); - should(tir.getTrust(charlie, alice)).equal(0); - should(tir.getTrust(alice, charlie)).equal(0); - }); - - it("returns Infinity for one's trust to themselves", () => { - should(tir.getTrust(alice, alice)).equal(Infinity); - should(tir.getTrust(bob, bob)).equal(Infinity); + describe(".getIndirectTrust()", () => { + it("returns zero for two arbitary parties that do not trust each other", () => { + should(tir.getIndirectTrust(alice, bob)).equal(0); + should(tir.getIndirectTrust(bob, alice)).equal(0); + should(tir.getIndirectTrust(charlie, alice)).equal(0); + should(tir.getIndirectTrust(alice, charlie)).equal(0); + }); + + it("returns Infinity for one's trust to themselves", () => { + should(tir.getIndirectTrust(alice, alice)).equal(Infinity); + should(tir.getIndirectTrust(bob, bob)).equal(Infinity); + }); + + describe("after applying the Nobody Likes Frank graph example", () => { + beforeEach(() => { + testHelpers.applyGraph(tir, "./graphs/nobodyLikesFrank.json", addr); }); - describe("after applying the Nobody Likes Frank graph example", () => { - beforeEach(() => { - testHelpers.applyGraph(tir, "./graphs/nobodyLikesFrank.json", addr); - }); - - it("correctly computes trusts", () => { - should(tir.getTrust(alice, alice)).equal(Infinity); - should(tir.getTrust(alice, bob)).equal(10); - should(tir.getTrust(alice, charlie)).equal(1); - should(tir.getTrust(alice, dave)).equal(4); - should(tir.getTrust(alice, eve)).equal(6); - should(tir.getTrust(alice, frank)).equal(0); - should(tir.getTrust(alice, george)).equal(2); - - should(tir.getTrust(bob, alice)).equal(1); - should(tir.getTrust(bob, bob)).equal(Infinity); - should(tir.getTrust(bob, charlie)).equal(1); - should(tir.getTrust(bob, dave)).equal(1); - should(tir.getTrust(bob, eve)).equal(3); - should(tir.getTrust(bob, frank)).equal(0); - should(tir.getTrust(bob, george)).equal(2); - - should(tir.getTrust(charlie, alice)).equal(0); - should(tir.getTrust(charlie, bob)).equal(0); - should(tir.getTrust(charlie, charlie)).equal(Infinity); - should(tir.getTrust(charlie, dave)).equal(0); - should(tir.getTrust(charlie, eve)).equal(0); - should(tir.getTrust(charlie, frank)).equal(0); - should(tir.getTrust(charlie, george)).equal(3); - - should(tir.getTrust(dave, alice)).equal(2); - should(tir.getTrust(dave, bob)).equal(2); - should(tir.getTrust(dave, charlie)).equal(1); - should(tir.getTrust(dave, dave)).equal(Infinity); - should(tir.getTrust(dave, eve)).equal(12); - should(tir.getTrust(dave, frank)).equal(0); - should(tir.getTrust(dave, george)).equal(2); - - should(tir.getTrust(eve, alice)).equal(0); - should(tir.getTrust(eve, bob)).equal(0); - should(tir.getTrust(eve, charlie)).equal(0); - should(tir.getTrust(eve, dave)).equal(0); - should(tir.getTrust(eve, eve)).equal(Infinity); - should(tir.getTrust(eve, frank)).equal(0); - should(tir.getTrust(eve, george)).equal(0); - - should(tir.getTrust(frank, alice)).equal(0); - should(tir.getTrust(frank, bob)).equal(0); - should(tir.getTrust(frank, charlie)).equal(10); - should(tir.getTrust(frank, dave)).equal(0); - should(tir.getTrust(frank, eve)).equal(0); - should(tir.getTrust(frank, frank)).equal(Infinity); - should(tir.getTrust(frank, george)).equal(3); - - should(tir.getTrust(george, alice)).equal(0); - should(tir.getTrust(george, bob)).equal(0); - should(tir.getTrust(george, charlie)).equal(0); - should(tir.getTrust(george, dave)).equal(0); - should(tir.getTrust(george, eve)).equal(0); - should(tir.getTrust(george, frank)).equal(0); - should(tir.getTrust(george, george)).equal(Infinity); - }); - - it("correctly computes trusts when bob trusts frank", () => { - tir.addTX(testHelpers.getTrustIncreasingMTX(addr.bob.pubKey, addr.frank.pubKey, 8).toTX()); - should(tir.getTrust(george, frank)).equal(0); - should(tir.getTrust(alice, frank)).equal(8); - should(tir.getTrust(dave, frank)).equal(2); - should(tir.getTrust(bob, frank)).equal(8); - }); - - // TODO: Decrement direct trusts and test that indirect trusts update correctly + it("correctly computes trusts", () => { + should(tir.getIndirectTrust(alice, alice)).equal(Infinity); + should(tir.getIndirectTrust(alice, bob)).equal(10); + should(tir.getIndirectTrust(alice, charlie)).equal(1); + should(tir.getIndirectTrust(alice, dave)).equal(4); + should(tir.getIndirectTrust(alice, eve)).equal(6); + should(tir.getIndirectTrust(alice, frank)).equal(0); + should(tir.getIndirectTrust(alice, george)).equal(2); + + should(tir.getIndirectTrust(bob, alice)).equal(1); + should(tir.getIndirectTrust(bob, bob)).equal(Infinity); + should(tir.getIndirectTrust(bob, charlie)).equal(1); + should(tir.getIndirectTrust(bob, dave)).equal(1); + should(tir.getIndirectTrust(bob, eve)).equal(3); + should(tir.getIndirectTrust(bob, frank)).equal(0); + should(tir.getIndirectTrust(bob, george)).equal(2); + + should(tir.getIndirectTrust(charlie, alice)).equal(0); + should(tir.getIndirectTrust(charlie, bob)).equal(0); + should(tir.getIndirectTrust(charlie, charlie)).equal(Infinity); + should(tir.getIndirectTrust(charlie, dave)).equal(0); + should(tir.getIndirectTrust(charlie, eve)).equal(0); + should(tir.getIndirectTrust(charlie, frank)).equal(0); + should(tir.getIndirectTrust(charlie, george)).equal(3); + + should(tir.getIndirectTrust(dave, alice)).equal(2); + should(tir.getIndirectTrust(dave, bob)).equal(2); + should(tir.getIndirectTrust(dave, charlie)).equal(1); + should(tir.getIndirectTrust(dave, dave)).equal(Infinity); + should(tir.getIndirectTrust(dave, eve)).equal(12); + should(tir.getIndirectTrust(dave, frank)).equal(0); + should(tir.getIndirectTrust(dave, george)).equal(2); + + should(tir.getIndirectTrust(eve, alice)).equal(0); + should(tir.getIndirectTrust(eve, bob)).equal(0); + should(tir.getIndirectTrust(eve, charlie)).equal(0); + should(tir.getIndirectTrust(eve, dave)).equal(0); + should(tir.getIndirectTrust(eve, eve)).equal(Infinity); + should(tir.getIndirectTrust(eve, frank)).equal(0); + should(tir.getIndirectTrust(eve, george)).equal(0); + + should(tir.getIndirectTrust(frank, alice)).equal(0); + should(tir.getIndirectTrust(frank, bob)).equal(0); + should(tir.getIndirectTrust(frank, charlie)).equal(10); + should(tir.getIndirectTrust(frank, dave)).equal(0); + should(tir.getIndirectTrust(frank, eve)).equal(0); + should(tir.getIndirectTrust(frank, frank)).equal(Infinity); + should(tir.getIndirectTrust(frank, george)).equal(3); + + should(tir.getIndirectTrust(george, alice)).equal(0); + should(tir.getIndirectTrust(george, bob)).equal(0); + should(tir.getIndirectTrust(george, charlie)).equal(0); + should(tir.getIndirectTrust(george, dave)).equal(0); + should(tir.getIndirectTrust(george, eve)).equal(0); + should(tir.getIndirectTrust(george, frank)).equal(0); + should(tir.getIndirectTrust(george, george)).equal(Infinity); }); + + it("correctly computes trusts when bob trusts frank", () => { + tir.addTX(testHelpers.getTrustIncreasingMTX(addr.bob.pubKey, addr.frank.pubKey, 8).toTX()); + should(tir.getIndirectTrust(george, frank)).equal(0); + should(tir.getIndirectTrust(alice, frank)).equal(8); + should(tir.getIndirectTrust(dave, frank)).equal(2); + should(tir.getIndirectTrust(bob, frank)).equal(8); + }); + + // TODO: Decrement direct trusts and test that indirect trusts update correctly }); }); }); From d9d073a3f9d8ec0b43050c9fd70754120de01b40 Mon Sep 17 00:00:00 2001 From: Christos Porios Date: Mon, 11 Sep 2017 22:13:29 +0000 Subject: [PATCH 16/20] Move fixtures to separate fixtures file. --- lib/trust_is_risk.js | 1 + src/trust_is_risk.js | 1 + test/fixtures.js | 23 +++++++++++++++++++++++ test/full_node.js | 41 +++++++++++++++++++++++------------------ test/helpers.js | 30 +----------------------------- test/trust_is_risk.js | 18 +++++++++++++++--- 6 files changed, 64 insertions(+), 50 deletions(-) create mode 100644 test/fixtures.js diff --git a/lib/trust_is_risk.js b/lib/trust_is_risk.js index 1210bd5..8bec28d 100644 --- a/lib/trust_is_risk.js +++ b/lib/trust_is_risk.js @@ -89,6 +89,7 @@ class TrustIsRisk { }); var changeAmount = coin.value - trustAmount - fee; + assert(changeAmount >= 0); if (changeAmount) { mtx.addOutput(new Output({ script: bcoin.script.fromPubkeyhash(bcoin.crypto.hash160(originPubKey)), diff --git a/src/trust_is_risk.js b/src/trust_is_risk.js index 565b6f6..07d79c8 100644 --- a/src/trust_is_risk.js +++ b/src/trust_is_risk.js @@ -89,6 +89,7 @@ class TrustIsRisk { }); var changeAmount = coin.value - trustAmount - fee; + assert(changeAmount >= 0); if (changeAmount) { mtx.addOutput(new Output({ script: bcoin.script.fromPubkeyhash(bcoin.crypto.hash160(originPubKey)), diff --git a/test/fixtures.js b/test/fixtures.js new file mode 100644 index 0000000..a11f021 --- /dev/null +++ b/test/fixtures.js @@ -0,0 +1,23 @@ +var bcoin = require("bcoin"); +var KeyRing = bcoin.primitives.KeyRing; + +var privateKeys = { + "alice": "02B8F07A401ECA4888039B1898F94DB44C43CCC6D3AA8B27E9B6ED7B377B24C0", + "bob": "2437025954568A8273968AA7535DBFC444FD8F8D0F5237CD96AC7234C77810AD", + "charlie": "3BBA2AF9539D09B4FD2BDEA1D3A2CE4BF5D779831B8781EE2ACF9C03378B2AD7", + "dave": "19BD8D853FAEFDB9B01E4DE7F6096FF8F5F96D43E6564A5258307334A4AA59F3", + "eve": "0503054CF7EBB4E62191AF1D8DE97945178D3F465EE88EF1FB4E80A70CB4A49A", + "frank": "878DFE5B43AC858EA37B3A9EEBA9E244F1848A30F78B2E5AC5B3EBDE81AC7D45", + "george": "1349A1318B1426E6F724CBFE7ECD2C46008A364A96C4BD20C83FC1C4EBB2EB4A" +}; + +var keyRings = {}; +for (let name in privateKeys) { + let key = privateKeys[name]; + keyRings[name] = KeyRing.fromPrivate(new Buffer(key, "hex")); +} + +module.exports = { + keyRings, + names: Object.keys(keyRings) +}; diff --git a/test/full_node.js b/test/full_node.js index 1cb7245..051c371 100644 --- a/test/full_node.js +++ b/test/full_node.js @@ -13,6 +13,7 @@ var consensus = require("bcoin/lib/protocol/consensus"); var sinon = require("sinon"); var should = require("should"); var assert = require("assert"); +var fixtures = require("./fixtures"); require("should-sinon"); const COIN = consensus.COIN; @@ -64,12 +65,9 @@ describe("FullNode", () => { beforeEach("apply graph transactions", async () => { addresses = {}; - rings = {}; - for (let i = 0; i < testHelpers.names.length; i++) { - var name = testHelpers.names[i]; - rings[name] = testHelpers.rings[i]; - addresses[name] = helpers.pubKeyToEntity(rings[name].getPublicKey()); + for (var [name, keyRing] of Object.entries(fixtures.keyRings)) { + addresses[name] = helpers.pubKeyToEntity(keyRing.getPublicKey()); } // Alice mines three blocks, each rewards her with 50 spendable BTC @@ -84,19 +82,21 @@ describe("FullNode", () => { // Alice sends 20 BTC to everyone (including herself) via P2PKH var sendAmount = 20; - var outputs = testHelpers.names.map((name) => { + var outputs = fixtures.names.map((name) => { return testHelpers.getP2PKHOutput( - Address.fromHash(bcoin.crypto.hash160(rings[name].getPublicKey())).toBase58(), + Address.fromHash(bcoin.crypto.hash160(fixtures.keyRings[name].getPublicKey())) + .toBase58(), sendAmount * consensus.COIN); }); // We have to use a change output, because transaction with too large a fee are considered // invalid. var fee = 0.01; - var changeAmount = 50 * blockCount - sendAmount * testHelpers.names.length - fee; + var changeAmount = 50 * blockCount - sendAmount * fixtures.names.length - fee; if (changeAmount >= 0.01) { outputs.push(new Output({ - script: Script.fromPubkeyhash(bcoin.crypto.hash160(rings.alice.getPublicKey())), + script: Script.fromPubkeyhash(bcoin.crypto.hash160( + fixtures.keyRings.alice.getPublicKey())), value: changeAmount * consensus.COIN })); } @@ -108,7 +108,7 @@ describe("FullNode", () => { var mtx = new MTX({outputs}); coinbaseCoins.forEach((coin) => mtx.addCoin(coin)); - var signedCount = mtx.sign(rings.alice); + var signedCount = mtx.sign(fixtures.keyRings.alice); assert(signedCount === blockCount); assert(await mtx.verify()); @@ -117,15 +117,16 @@ describe("FullNode", () => { await watcher.waitForTX(); prevout = {}; - testHelpers.names.forEach((name) => { + fixtures.names.forEach((name) => { prevout[name] = { hash: tx.hash().toString("hex"), - index: testHelpers.names.indexOf(name) + index: fixtures.names.indexOf(name) }; }); // Alice mines another block - await testHelpers.mineBlock(node, helpers.pubKeyToEntity(rings.alice.getPublicKey())); + await testHelpers.mineBlock(node, helpers.pubKeyToEntity( + fixtures.keyRings.alice.getPublicKey())); await testHelpers.delay(500); var graph = require("./graphs/nobodyLikesFrank.json"); @@ -137,8 +138,11 @@ describe("FullNode", () => { let outpoint = new Outpoint(prevout[origin].hash, prevout[origin].index); - let mtx = await node.trust.createTrustIncreasingMTX(rings[origin].getPrivateKey(), - rings[dest].getPublicKey(), outpoint, value * consensus.COIN); + let mtx = await node.trust.createTrustIncreasingMTX( + fixtures.keyRings[origin].getPrivateKey(), + fixtures.keyRings[dest].getPublicKey(), + outpoint, + value * consensus.COIN); assert(await mtx.verify()); @@ -151,7 +155,8 @@ describe("FullNode", () => { } // Alice mines yet another block - await testHelpers.mineBlock(node, helpers.pubKeyToEntity(rings.alice.getPublicKey())); + await testHelpers.mineBlock(node, helpers.pubKeyToEntity( + fixtures.keyRings.alice.getPublicKey())); await testHelpers.delay(500); }); @@ -173,8 +178,8 @@ describe("FullNode", () => { }); it("after decreasing some trusts computes trusts correctly", async () => { - var mtxs = node.trust.createTrustDecreasingMTXs(rings.alice.getPrivateKey(), - rings.bob.getPublicKey(), 3 * COIN); + var mtxs = node.trust.createTrustDecreasingMTXs(fixtures.keyRings.alice.getPrivateKey(), + fixtures.keyRings.bob.getPublicKey(), 3 * COIN); mtxs.length.should.equal(1); var mtx = mtxs[0]; diff --git a/test/helpers.js b/test/helpers.js index 02bcd68..855c1de 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -1,39 +1,11 @@ var TrustIsRisk = require("../"); var WalletDB = require("bcoin/lib/wallet/walletdb"); var bcoin = require("bcoin"); +var fixtures = require("./fixtures"); var KeyRing = bcoin.primitives.KeyRing; var assert = require("assert"); var testHelpers = { - names: ["alice", "bob", "charlie", "dave", "eve", "frank", "george"], - rings: [ - "02B8F07A401ECA4888039B1898F94DB44C43CCC6D3AA8B27E9B6ED7B377B24C0", - "2437025954568A8273968AA7535DBFC444FD8F8D0F5237CD96AC7234C77810AD", - "3BBA2AF9539D09B4FD2BDEA1D3A2CE4BF5D779831B8781EE2ACF9C03378B2AD7", - "19BD8D853FAEFDB9B01E4DE7F6096FF8F5F96D43E6564A5258307334A4AA59F3", - "0503054CF7EBB4E62191AF1D8DE97945178D3F465EE88EF1FB4E80A70CB4A49A", - "878DFE5B43AC858EA37B3A9EEBA9E244F1848A30F78B2E5AC5B3EBDE81AC7D45", - "1349A1318B1426E6F724CBFE7ECD2C46008A364A96C4BD20C83FC1C4EBB2EB4A" - ].map((key) => KeyRing.fromPrivate(new Buffer(key, "hex"))), - - getAddressFixtures: () => { - assert(testHelpers.rings.length === testHelpers.names.length); - - var addr = {}; - for (var i = 0; i < testHelpers.names.length; i++) { - var name = testHelpers.names[i]; - var pubKey = testHelpers.rings[i].getPublicKey(); - var privKey = testHelpers.rings[i].getPrivateKey(); - - addr[name] = {}; - addr[name].pubKey = pubKey; - addr[name].privKey = privKey; - addr[name].base58 = bcoin.primitives.Address.fromHash(bcoin.crypto.hash160(pubKey)).toString(); - } - - return addr; - }, - getNode: async () => { var node = new TrustIsRisk.FullNode({network: "regtest", passphrase: "secret"}); diff --git a/test/trust_is_risk.js b/test/trust_is_risk.js index e0abb54..a4085fa 100644 --- a/test/trust_is_risk.js +++ b/test/trust_is_risk.js @@ -9,15 +9,27 @@ var testHelpers = require("./helpers"); var consensus = require("bcoin/lib/protocol/consensus"); var sinon = require("sinon"); var should = require("should"); +var fixtures = require("./fixtures"); require("should-sinon"); const COIN = bcoin.consensus.COIN; describe("TrustIsRisk", () => { - var addr = testHelpers.getAddressFixtures(); + var addr = {}; + for (let [name, keyRing] of Object.entries(fixtures.keyRings)) { + var pubKey = keyRing.getPublicKey(); + var privKey = keyRing.getPrivateKey(); + + addr[name] = {}; + addr[name].pubKey = pubKey; + addr[name].privKey = privKey; + addr[name].base58 = bcoin.primitives.Address.fromHash(bcoin.crypto.hash160(pubKey)).toString(); + } + // Add base58 address variables to scope. - for (name in addr) { - eval(`var ${name} = "${addr[name].base58}";`); + for (name in fixtures.keyRings) { + var keyRing = fixtures.keyRings[name]; + eval(`var ${name} = "${bcoin.primitives.Address.fromHash(bcoin.crypto.hash160(keyRing.getPublicKey())).toString()}";`); } var node, tir, trustIncreasingMTX, trustDecreasingMTX, trustIncreasingTX; From 8df2ff57eb88fc64c5421b739734acab72a79222 Mon Sep 17 00:00:00 2001 From: Christos Porios Date: Mon, 11 Sep 2017 22:30:32 +0000 Subject: [PATCH 17/20] Remove compiled files from repo; Minor code review fixes --- .gitignore | 1 + lib/direct_trust.js | 106 ---------------- lib/full_node.js | 14 --- lib/helpers.js | 12 -- lib/index.js | 5 - lib/trust_db.js | 120 ------------------ lib/trust_is_risk.js | 284 ------------------------------------------- lib/types.js | 8 -- src/trust_db.js | 5 - test/full_node.js | 4 + 10 files changed, 5 insertions(+), 554 deletions(-) delete mode 100644 lib/direct_trust.js delete mode 100644 lib/full_node.js delete mode 100644 lib/helpers.js delete mode 100644 lib/index.js delete mode 100644 lib/trust_db.js delete mode 100644 lib/trust_is_risk.js delete mode 100644 lib/types.js diff --git a/.gitignore b/.gitignore index a6ec74b..b4e253f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules/ +lib/ *.swp diff --git a/lib/direct_trust.js b/lib/direct_trust.js deleted file mode 100644 index 0656934..0000000 --- a/lib/direct_trust.js +++ /dev/null @@ -1,106 +0,0 @@ -// - - -var assert = require("assert"); -var helpers = require("./helpers"); - - - - - - - - - - - - - - -class DirectTrust { - - - - - // Every DT is associated with a transaction output, except for non-standard trust decreasing - // transactions, which reduce trust to zero and are related to a whole transaction and not - // a specific output. - - - - - - - - constructor(options ) { - this.outputIndex = null; - this.script = null; - this.prev = null; - this.next = null; - Object.assign(this, options); - } - - isNull() { - return this.amount === 0; - } - - isIncrease() { - return this.prev === null; - } - - isDecrease() { - return !this.isIncrease(); - } - - isSpent() { - return this.next !== null; - } - - isSpendable() { - return !this.isSpent() && !this.isNull(); - } - - isValid() { - // TODO: Consider removing this function and ensure validity at build time by using the flow - // type system, possibly by creating sub-types like "IncreasingDirectTrust" etc. - var valid = true; - - if ((this.outputIndex === null) !== (this.script === null)) valid = false; - if (this.outputIndex === null && this.isIncrease()) valid = false; - if (this.outputIndex === null && this.amount > 0) valid = false; - if (this.isIncrease() && this.isNull()) valid = false; - if (this.isSpent() && this.isNull()) valid = false; - - return valid; - } - - getOriginEntity() { - return helpers.pubKeyToEntity(this.origin); - } - - getDestEntity() { - return helpers.pubKeyToEntity(this.dest); - } - - spend(next ) { - assert(!this.isSpent()); - assert(this.origin.equals(next.origin) && this.dest.equals(next.dest)); - assert(next.amount <= this.amount); - - this.next = next; - next.prev = this; - } - - getNullifying(txHash ) { - return new DirectTrust({ - origin: this.origin, - dest: this.dest, - amount: 0, - - prev: this, - txHash, - }); - } -} - -module.exports = DirectTrust; diff --git a/lib/full_node.js b/lib/full_node.js deleted file mode 100644 index 3525254..0000000 --- a/lib/full_node.js +++ /dev/null @@ -1,14 +0,0 @@ -// -var bcoin = require("bcoin"); -var TrustIsRisk = require("./trust_is_risk"); - -class FullNode extends bcoin.fullnode { - - - constructor(options ) { - super(options); - this.trust = new TrustIsRisk(this); - } -} - -module.exports = FullNode; diff --git a/lib/helpers.js b/lib/helpers.js deleted file mode 100644 index a345715..0000000 --- a/lib/helpers.js +++ /dev/null @@ -1,12 +0,0 @@ -// - -var bcoin = require("bcoin"); -var Address = bcoin.primitives.Address; - -var helpers = { - pubKeyToEntity: (key ) => { - return Address.fromHash(bcoin.crypto.hash160(key)).toBase58(); - } -}; - -module.exports = helpers; diff --git a/lib/index.js b/lib/index.js deleted file mode 100644 index 824eb06..0000000 --- a/lib/index.js +++ /dev/null @@ -1,5 +0,0 @@ -// -module.exports = { - FullNode: require("./full_node"), - TrustIsRisk: require("./trust_is_risk") -}; diff --git a/lib/trust_db.js b/lib/trust_db.js deleted file mode 100644 index d894ed1..0000000 --- a/lib/trust_db.js +++ /dev/null @@ -1,120 +0,0 @@ -// - -var assert = require("assert"); -var SortedSet = require("sorted-set"); -var maxFlow = require("graph-theory-ford-fulkerson"); -var bcoin = require("bcoin"); -var Address = bcoin.primitives.Address; -var KeyRing = bcoin.primitives.KeyRing; -var MTX = bcoin.primitives.MTX; -var Input = bcoin.primitives.Input; -var Output = bcoin.primitives.Output; -var Outpoint = bcoin.primitives.Outpoint; -var DirectTrust = require("./direct_trust"); - -class TrustDB { - - - - - constructor() { - this.directTrusts = new Map(); - this.txToDirectTrust = new Map(); - this.entities = new SortedSet(); - } - - getDirectTrustByTX(txHash ) { - if (!this.isTrustTX(txHash)) return null; - return ((this.txToDirectTrust.get(txHash) ) ); - } - - getDirectTrustByOutpoint(outpoint ) { - var trust = this.txToDirectTrust.get(outpoint.hash.toString("hex")); - if (!trust) return null; - if (trust.outputIndex !== outpoint.index) return null; - return trust; - } - - getDirectTrustAmount(origin , dest ) { - if (origin === dest) return Infinity; - - var trusts = this.getSpendableDirectTrusts(origin, dest); - return trusts.reduce((sum, t) => sum + t.amount, 0); - } - - getSpendableDirectTrusts(origin , dest ) { - return this.getDirectTrusts(origin, dest).filter((t) => t.isSpendable()); - } - - getDirectTrusts(origin , dest ) { - var originMap = this.directTrusts.get(origin); - if (!originMap) return []; - - var trusts = originMap.get(dest); - if (!trusts) return []; - - return trusts; - } - - getGraphWeightMatrix() { - var entitiesArr = this.getEntities(); - return entitiesArr.map((origin) => { - return entitiesArr.map((dest) => this.getDirectTrustAmount(origin, dest)); - }); - } - - getTrustAmount(origin , dest ) { - // TODO: Optimize - if (origin === dest) return Infinity; - - var graph = this.getGraphWeightMatrix(); - var originIndex = this.getEntityIndex(origin); - var destIndex = this.getEntityIndex(dest); - - if (originIndex === -1 || destIndex === -1) return 0; - else return maxFlow(graph, originIndex, destIndex); - } - - getEntities() { - return this.entities.slice(0, this.entities.length); - } - - getEntityIndex(entity ) { - return this.entities.rank(entity); - } - - isTrustTX(txHash ) { - return this.txToDirectTrust.has(txHash); - } - - isTrustOutput(txHash , outputIndex ) { - var trust = this.txToDirectTrust.get(txHash); - return trust !== undefined && trust.outputIndex === outputIndex; - } - - add(trust ) { - var origin = trust.getOriginEntity(); - var dest = trust.getDestEntity(); - assert(origin !== dest); - - if (!this.directTrusts.has(origin)) this.directTrusts.set(origin, new Map()); - var originMap = ((this.directTrusts.get(origin) ) ); - - if (!originMap.has(dest)) originMap.set(dest, []); - var trusts = ((originMap.get(dest) ) ); - - if (trust.prev !== null) { - trust.prev.spend(trust); - assert(trust.prev && trust.prev.isValid() && !trust.prev.isSpendable()); - } - - assert(trust.isValid()); - trusts.push(trust); - this.txToDirectTrust.set(trust.txHash, trust); - - this.entities.add(origin); - this.entities.add(dest); - } -} - -module.exports = TrustDB; diff --git a/lib/trust_is_risk.js b/lib/trust_is_risk.js deleted file mode 100644 index 8bec28d..0000000 --- a/lib/trust_is_risk.js +++ /dev/null @@ -1,284 +0,0 @@ -// - -var bcoin = require("bcoin"); -var Address = bcoin.primitives.Address; -var KeyRing = bcoin.primitives.KeyRing; -var MTX = bcoin.primitives.MTX; -var Input = bcoin.primitives.Input; -var Output = bcoin.primitives.Output; -var Outpoint = bcoin.primitives.Outpoint; -var Coin = bcoin.primitives.Coin; -var assert = require("assert"); -var helpers = require("./helpers"); -var TrustDB = require("./trust_db"); -var DirectTrust = require("./direct_trust"); - -class TrustIsRisk { - - - - constructor(node ) { - this.node = node; - this.db = new TrustDB(); - - this.node.on("tx", this.addTX.bind(this)); - } - - getIndirectTrust(origin , dest ) { - return this.db.getTrustAmount(origin, dest); - } - - getDirectTrust(origin , dest ) { - return this.db.getDirectTrustAmount(origin, dest); - } - - // Attempts to parse a bitcoin transaction as a trust change and adds it to the trust network - // if successful. - // Returns true if the transaction is a TIR transaction and was successfully added to the - // network, false otherwise. - // Throws an error if the transaction was processed earlier. - addTX(tx ) { - var txHash = tx.hash().toString("hex"); - if (this.db.isTrustTX(txHash)) { - throw new Error(`Transaction already processed: Transaction ${txHash} already carries trust`); - } - - var directTrusts = this.getDirectTrusts(tx); - if (directTrusts.length === 0) return false; - else { - directTrusts.forEach(this.db.add.bind(this.db)); - return true; - } - } - - // Returns a list of trusts that a transaction contains, which will be one of the following: - // * An empty list (for non-TIR transactions). - // * A list containing a single trust increase (for trust-increasing transactions). - // * A list containing one or more trust decreases (for trust-decreasing transactions). - getDirectTrusts(tx ) { - var trustIncrease = this.parseTXAsTrustIncrease(tx); - if (trustIncrease !== null) { - return [trustIncrease]; - } else { - return this.getTrustDecreases(tx); - } - } - - // Returns a promise resolving to a mutable transaction object, which increases a trust - // relationship by some amount. It will spend the outpoint, which must reference a P2PKH output - // payable to the sender. The origin key must be a private key. Any satoshis not spent will be - // returned to the sender, minus the fees, via P2PKH. - async createTrustIncreasingMTX(origin , dest , outpoint , - trustAmount , fee ) - { - if (!fee) fee = 1000; // TODO: estimate this - var coin = await this.node.getCoin(outpoint.hash, outpoint.index); - if (!coin) throw new Error("Could not find coin"); - if (origin === dest) throw new Error("Can not increase self-trust."); - - var originKeyRing = KeyRing.fromPrivate(origin); - var originPubKey = originKeyRing.getPublicKey(); - - var mtx = new MTX({ - outputs: [ - new Output({ - script: bcoin.script.fromMultisig(1, 2, [originPubKey, dest]), - value: trustAmount - }) - ] - }); - - var changeAmount = coin.value - trustAmount - fee; - assert(changeAmount >= 0); - if (changeAmount) { - mtx.addOutput(new Output({ - script: bcoin.script.fromPubkeyhash(bcoin.crypto.hash160(originPubKey)), - value: changeAmount - })); - } - - mtx.addCoin(coin); - var success = mtx.scriptVector(coin.script, mtx.inputs[0].script, originKeyRing); - assert(success); - - var signedCount = mtx.sign(originKeyRing); - assert(signedCount === 1); - - return mtx; - } - - // Returns an array of trust-decreasing mutable transaction objects, which reduce a trust - // relationship by the amount specified. The payee will receive the amount deducted minus the - // transaction fees via P2PKH. - // If steal is undefined or set to false, then the `origin` key is expected to be a private key - // and the `dest` key is expected to be a public key. If steal is set to true, then `origin` is - // expected to be a public key and `dest` is expected to be a private key. The private key will be - // used to sign the transaction. - createTrustDecreasingMTXs(origin , dest , trustDecreaseAmount , payee , - steal , fee ) { - if (steal === undefined) steal = false; - - var signingKeyRing, originKeyRing, destKeyRing; - if (!steal) { - signingKeyRing = KeyRing.fromPrivate(origin); - originKeyRing = KeyRing.fromPrivate(origin); - destKeyRing = KeyRing.fromPublic(dest); - } else { - signingKeyRing = KeyRing.fromPrivate(dest); - originKeyRing = KeyRing.fromPublic(origin); - destKeyRing = KeyRing.fromPrivate(dest); - } - - var originAddress = helpers.pubKeyToEntity(originKeyRing.getPublicKey()); - var destAddress = helpers.pubKeyToEntity(destKeyRing.getPublicKey()); - - if (originAddress === destAddress) throw new Error("Can't decrease self-trust"); - - var existingTrustAmount = this.db.getDirectTrustAmount(originAddress, destAddress); - if (existingTrustAmount < trustDecreaseAmount) throw new Error("Insufficient trust"); - - var directTrusts = this.db.getSpendableDirectTrusts(originAddress, destAddress); - return directTrusts.map((directTrust) => { - var decrease = Math.min(trustDecreaseAmount, directTrust.amount); - if (decrease === 0) return null; - trustDecreaseAmount -= decrease; - return this.getTrustDecreasingMTX(directTrust, decrease, payee, signingKeyRing, fee); - }).filter(Boolean); - } - - getTrustDecreasingMTX(directTrust , decreaseAmount , payee , - signingKeyRing , fee ) { - if (!payee) payee = directTrust.getOriginEntity(); - if (!fee) fee = 1000; // TODO: estimate this - - var mtx = new MTX({ - inputs: [ - Input.fromOutpoint(new Outpoint(directTrust.txHash, directTrust.outputIndex)) - ], - outputs: [new Output({ - script: bcoin.script.fromPubkeyhash(Address.fromBase58(payee).hash), - value: ((decreaseAmount - fee) < 0) ? 0 : (decreaseAmount - fee) - })] - }); - - var remainingTrustAmount = directTrust.amount - decreaseAmount; - if (remainingTrustAmount > 0) { - mtx.addOutput(new Output({ - script: bcoin.script.fromMultisig(1, 2, [directTrust.origin, directTrust.dest]), - value: remainingTrustAmount - })); - } - - var success = mtx.scriptVector(((directTrust.script ) ), - mtx.inputs[0].script, KeyRing.fromPublic(directTrust.origin)); - assert(success); - - success = mtx.signInput(0, new Coin({script: directTrust.script, value: directTrust.amount}), - signingKeyRing); - assert(success); - - return mtx; - } - - parseTXAsTrustIncrease(tx ) { - if (tx.inputs.length !== 1) return null; - var input = tx.inputs[0]; - if (input.getType() !== "pubkeyhash") return null; // TODO: This is unreliable - if (this.db.isTrustOutput(input.prevout.hash.toString("hex"), input.prevout.index)) return null; - var origin = tx.inputs[0].getAddress().toBase58(); - - if (tx.outputs.length === 0 || tx.outputs.length > 2) return null; - - var trustOutputs = this.searchForDirectTrustOutputs(tx, origin); - if (trustOutputs.length !== 1) return null; - - var changeOutputCount = tx.outputs.filter((o) => this.isChangeOutput(o, origin)).length; - if (changeOutputCount + 1 !== tx.outputs.length) return null; - - return trustOutputs[0]; - } - - getTrustDecreases(tx ) { - var inputTrusts = this.getInputTrusts(tx.inputs); - return inputTrusts.map(this.getTrustDecrease.bind(this, tx)); - } - - getInputTrusts(inputs ) { - return inputs.map((input) => { - var trust = this.db.getDirectTrustByOutpoint(input.prevout); - if (trust && trust.outputIndex === input.prevout.index) return trust; - else return null; - }).filter(Boolean); - } - - getTrustDecrease(tx , prevTrust ) { - var txHash = tx.hash().toString("hex"); - var nullTrust = prevTrust.getNullifying(txHash); - - if (tx.inputs.length !== 1) return nullTrust; - - var trustOutputs = this.searchForDirectTrustOutputs(tx, prevTrust.getOriginEntity(), - prevTrust.getDestEntity()); - if (trustOutputs.length != 1) return nullTrust; - var nextTrust = trustOutputs[0]; - - nextTrust.prev = prevTrust; - - assert(nextTrust.amount - prevTrust.amount <= 0); - return nextTrust; - } - - // Looks for direct trust outputs that originate from a sender in an array of bitcoin outputs. - // Returns a list of the corresponding DirectTrust objects. - // If the recipient parameter is set, it will limit the results only to the outputs being sent to - // the recipient. - searchForDirectTrustOutputs(tx , origin , recipient ) { - var directTrusts = tx.outputs.map((output, outputIndex) => - this.parseOutputAsDirectTrust(tx, outputIndex, origin) - ).filter(Boolean); - - if (recipient) { - directTrusts.filter((trust) => trust.dest === recipient); - } - - return directTrusts; - } - - isChangeOutput(output , origin ) { - return (output.getType() === "pubkeyhash") - && (output.getAddress().toBase58() === origin); - } - - parseOutputAsDirectTrust(tx , outputIndex , origin ) - { - var txHash = tx.hash().toString("hex"); - var output = tx.outputs[outputIndex]; - if (output.getType() !== "multisig") return null; - - var entities = [1, 2].map((i) => helpers.pubKeyToEntity(output.script.get(i))); - if (entities[0] === entities[1]) return null; - - var originPubKey, destPubKey; - if (entities[0] === origin) { - originPubKey = output.script.get(1); - destPubKey = output.script.get(2); - } - else if (entities[1] === origin) { - originPubKey = output.script.get(2); - destPubKey = output.script.get(1); - } - else return null; - - return new DirectTrust({ - origin: originPubKey, - dest: destPubKey, - amount: Number(output.value), - - txHash, - outputIndex, - script: output.script - }); - } -} - -module.exports = TrustIsRisk; diff --git a/lib/types.js b/lib/types.js deleted file mode 100644 index 79f17ec..0000000 --- a/lib/types.js +++ /dev/null @@ -1,8 +0,0 @@ -// - -// base58 bitcoin address: - - - - - diff --git a/src/trust_db.js b/src/trust_db.js index 650616b..0af2541 100644 --- a/src/trust_db.js +++ b/src/trust_db.js @@ -23,11 +23,6 @@ class TrustDB { this.entities = new SortedSet(); } - getDirectTrustByTX(txHash : string) : (DirectTrust | null) { - if (!this.isTrustTX(txHash)) return null; - return ((this.txToDirectTrust.get(txHash) : any) : DirectTrust); - } - getDirectTrustByOutpoint(outpoint : bcoin$Outpoint) : (DirectTrust | null) { var trust = this.txToDirectTrust.get(outpoint.hash.toString("hex")); if (!trust) return null; diff --git a/test/full_node.js b/test/full_node.js index 051c371..a3260ba 100644 --- a/test/full_node.js +++ b/test/full_node.js @@ -190,4 +190,8 @@ describe("FullNode", () => { should(node.trust.getIndirectTrust(addresses.alice, addresses.bob)).equal(7 * COIN); }); }); + + describe("with the topcoder.json example", () => { + //TODO: Write tests here. + }); }); From 86a512c50ed80f41872a5c91309c77fc1d92831e Mon Sep 17 00:00:00 2001 From: Christos Porios Date: Mon, 11 Sep 2017 23:03:44 +0000 Subject: [PATCH 18/20] More CR responses. --- src/trust_is_risk.js | 9 +++++---- test/trust_is_risk.js | 10 ++++++++++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/trust_is_risk.js b/src/trust_is_risk.js index 07d79c8..367bbee 100644 --- a/src/trust_is_risk.js +++ b/src/trust_is_risk.js @@ -228,17 +228,18 @@ class TrustIsRisk { return nextTrust; } - // Looks for direct trust outputs that originate from a sender in an array of bitcoin outputs. - // Returns a list of the corresponding DirectTrust objects. + // Looks for direct trust outputs that originate from a sender in a transaction. + // Returns an array of the corresponding DirectTrust objects. // If the recipient parameter is set, it will limit the results only to the outputs being sent to // the recipient. searchForDirectTrustOutputs(tx : bcoin$TX, origin : Entity, recipient : ?Entity) : DirectTrust[] { var directTrusts = tx.outputs.map((output, outputIndex) => this.parseOutputAsDirectTrust(tx, outputIndex, origin) - ).filter(Boolean); + ).filter(Boolean); // filter out nulls if (recipient) { - directTrusts.filter((trust) => trust.dest === recipient); + directTrusts = directTrusts.filter((trust) => + helpers.pubKeyToEntity(trust.dest) === recipient); } return directTrusts; diff --git a/test/trust_is_risk.js b/test/trust_is_risk.js index a4085fa..2040784 100644 --- a/test/trust_is_risk.js +++ b/test/trust_is_risk.js @@ -150,6 +150,16 @@ describe("TrustIsRisk", () => { tir.getDirectTrust(alice, bob).should.equal(20 * COIN); }); + it("decreases trust to zero for trust decreasing transactions with a wrong recipient", () => { + // By changing the trust recipient from bob to charlie, we make the transaction a + // nullifying trust transaction. + trustDecreasingMTX.outputs[0] = + testHelpers.getOneOfTwoMultisigOutput(addr.alice.pubKey, addr.charlie.pubKey, 20 * COIN); + + tir.addTX(trustDecreasingMTX.toTX()); + tir.getDirectTrust(alice, bob).should.equal(0); + }); + it("which has a second input decreases trust to zero", () => { trustDecreasingMTX.inputs.push(testHelpers.getP2PKHInput(addr.alice.pubKey)); tir.addTX(trustDecreasingMTX.toTX()); From 8f2511875f0bfe501091048e9edc1bed23b2e9ca Mon Sep 17 00:00:00 2001 From: Christos Porios Date: Mon, 11 Sep 2017 23:17:29 +0000 Subject: [PATCH 19/20] More CR fixes. --- src/direct_trust.js | 15 ++++++--------- src/trust_db.js | 2 +- src/trust_is_risk.js | 6 ++---- 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/src/direct_trust.js b/src/direct_trust.js index 15fbae6..3ed4fcf 100644 --- a/src/direct_trust.js +++ b/src/direct_trust.js @@ -63,15 +63,12 @@ class DirectTrust { isValid() : boolean { // TODO: Consider removing this function and ensure validity at build time by using the flow // type system, possibly by creating sub-types like "IncreasingDirectTrust" etc. - var valid = true; - - if ((this.outputIndex === null) !== (this.script === null)) valid = false; - if (this.outputIndex === null && this.isIncrease()) valid = false; - if (this.outputIndex === null && this.amount > 0) valid = false; - if (this.isIncrease() && this.isNull()) valid = false; - if (this.isSpent() && this.isNull()) valid = false; - - return valid; + if ((this.outputIndex === null) !== (this.script === null)) return false; + if (this.outputIndex === null && this.isIncrease()) return false; + if (this.outputIndex === null && this.amount > 0) return false; + if (this.isIncrease() && this.isNull()) return false; + if (this.isSpent() && this.isNull()) return false; + return true; } getOriginEntity() : Entity { diff --git a/src/trust_db.js b/src/trust_db.js index 0af2541..d41e680 100644 --- a/src/trust_db.js +++ b/src/trust_db.js @@ -71,7 +71,7 @@ class TrustDB { } getEntities() : Entity[] { - return this.entities.slice(0, this.entities.length); + return this.entities.slice(); } getEntityIndex(entity : Entity) : number { diff --git a/src/trust_is_risk.js b/src/trust_is_risk.js index 367bbee..d92a947 100644 --- a/src/trust_is_risk.js +++ b/src/trust_is_risk.js @@ -205,9 +205,7 @@ class TrustIsRisk { getInputTrusts(inputs : bcoin$Input[]) : DirectTrust[] { return inputs.map((input) => { - var trust = this.db.getDirectTrustByOutpoint(input.prevout); - if (trust && trust.outputIndex === input.prevout.index) return trust; - else return null; + return this.db.getDirectTrustByOutpoint(input.prevout); }).filter(Boolean); } @@ -224,7 +222,7 @@ class TrustIsRisk { nextTrust.prev = prevTrust; - assert(nextTrust.amount - prevTrust.amount <= 0); + assert(nextTrust.amount <= prevTrust.amount); return nextTrust; } From ba908f5baa879d86871130dc92ecf8d887312f30 Mon Sep 17 00:00:00 2001 From: Christos Porios Date: Mon, 11 Sep 2017 23:26:40 +0000 Subject: [PATCH 20/20] Fix bcoin version 1.0.0-beta.12 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3d8c39a..116b868 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "sinon": "^2.2.0" }, "dependencies": { - "bcoin": "^1.0.0-beta.12", + "bcoin": "1.0.0-beta.12", "graph-theory-ford-fulkerson": "^1.0.0", "sorted-set": "^0.3.0" }