Skip to content

Commit

Permalink
Implement trust decreasing transaction parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
christosporios committed May 26, 2017
1 parent 00e4c46 commit 913fd8d
Show file tree
Hide file tree
Showing 6 changed files with 417 additions and 196 deletions.
11 changes: 10 additions & 1 deletion flow-typed/npm/bcoin_vx.x.x.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -24,6 +26,7 @@ declare class bcoin$Output {

declare class bcoin$Input {
script : bcoin$Script;
prevout : bcoin$Outpoint;
getType() : ('pubkeyhash' | 'multisig');
getAddress() : bcoin$Address;
}
Expand All @@ -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<bcoin$FullNode>,
Expand All @@ -40,7 +48,8 @@ declare module 'bcoin' {
Address : Class<bcoin$Address>,
TX : Class<bcoin$TX>,
Output : Class<bcoin$Output>,
Input : Class<bcoin$Input>
Input : Class<bcoin$Input>,
Outpoint : Class<bcoin$Outpoint>
},
crypto : {
hash160(str : (string | Buffer)) : (string | Buffer)
Expand Down
185 changes: 138 additions & 47 deletions lib/trust_is_risk.js
Original file line number Diff line number Diff line change
@@ -1,108 +1,199 @@
//
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()
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;
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
};
}

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Loading

0 comments on commit 913fd8d

Please sign in to comment.