From 4f7153586989d3a347d381db5cfad6ff36a896ea Mon Sep 17 00:00:00 2001 From: "Ryan X. Charles" Date: Tue, 23 Sep 2014 16:54:52 -0700 Subject: [PATCH] StealthTx For spotting transactions to which you have the stealth key (or at least the scan key) and creating transactions to a stealth address. So far it is only partially working - you can see if a transaction is a stealth transaction (or at least one of a limited kind of stealth transactions), and you can see that you do not have the stealth key to spend one of these transactions. However, I have not yet tested whether you can see a stealth transaction that you actually have the key to. Also, it is not yet easy to spend to a stealth address. --- lib/expmt/stealthtx.js | 69 ++++++++++++++++++++++++++++++++++++++++++ test/stealthtx.js | 68 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 137 insertions(+) create mode 100644 lib/expmt/stealthtx.js create mode 100644 test/stealthtx.js diff --git a/lib/expmt/stealthtx.js b/lib/expmt/stealthtx.js new file mode 100644 index 0000000..0133166 --- /dev/null +++ b/lib/expmt/stealthtx.js @@ -0,0 +1,69 @@ +var StealthAddress = require('./stealthaddress'); +var StealthKey = require('./stealthkey'); +var Transaction = require('../transaction'); +var Pubkey = require('../pubkey'); + +var StealthTx = function StealthTx(tx, sa, sk) { + if (!(this instanceof StealthTx)) + return new StealthTx(tx, sa, sk); + if (tx instanceof Transaction) { + this.tx = tx; + this.sa = sa; + this.sk = sk; + } else if (tx) { + var obj = tx; + this.set(obj); + } +}; + +StealthTx.prototype.set = function(obj) { + this.sk = obj.sk || this.sk; + this.sa = obj.sa || this.sa; + this.tx = obj.tx || this.tx; + return this; +}; + +StealthTx.prototype.isForMe = function() { + if (!this.notMine()) + return true; + else + return false; +}; + +StealthTx.prototype.notMine = function() { + var err; + if (err = this.notStealth()) + return "Not stealth: " + err; + var txopbuf = this.tx.txouts[0].script.chunks[1].buf; + var parsed = StealthTx.parseOpReturnData(txopbuf); + var pubkey = parsed.pubkey; + var pubkeyhashbuf = this.tx.txouts[1].script.chunks[2].buf; + var sk = this.sk; + if (sk.isForMe(pubkey, pubkeyhashbuf)) { + return false; + } else { + return "StealthTx not mine"; + } +}; + +//For now, we only support a very limited variety of stealth tx +StealthTx.prototype.notStealth = function() { + var txouts = this.tx.txouts; + if (!(txouts.length >= 2)) + return "Not enough txouts"; + if (!txouts[0].script.isOpReturn()) + return "First txout is not OP_RETURN"; + if (!txouts[1].script.isPubkeyhashOut()) + return "Second txout is not pubkeyhash"; + return false; +}; + +StealthTx.parseOpReturnData = function(buf) { + var parsed = {}; + parsed.version = buf[0]; + parsed.noncebuf = buf.slice(1, 5); + parsed.pubkey = Pubkey().fromBuffer(buf.slice(5, 5 + 33)); + return parsed; +}; + +module.exports = StealthTx; diff --git a/test/stealthtx.js b/test/stealthtx.js new file mode 100644 index 0000000..f00d73e --- /dev/null +++ b/test/stealthtx.js @@ -0,0 +1,68 @@ +var should = require('chai').should(); +var Txout = require('../lib/txout'); +var Stealthkey = require('../lib/expmt/stealthkey'); +var StealthTx = require('../lib/expmt/stealthtx'); +var Transaction = require('../lib/transaction'); +var Varint = require('../lib/varint'); + +describe('StealthTx', function() { + + var txhex = '0100000001c828ccce36eca04f96321ad488528af86c7598e67157c4f8e2f462a9e0e3af5f010000006a47304402204525eef6a56cc57fb184e53efdfdc1086d5265da21480d55c2184536440a64f70220349cdc6c66a8507dde0d172fe64aeb57ae56e014b50315f615086a6b85c5424e012102c0633ddb6bf2a8686e2ba4ce8026c94e1e27ef12e73f8fed6d6d2b97199f9b74ffffffff020000000000000000286a2606deadbeef0365b5a5b0ba059666e907b0b5e07b37fdb162d1399ed829315491fe1f30c87b3f905f0100000000001976a9142042d5e7ef9e82346419fbfe7df5ae52fe4bea3c88ac00000000'; + var txbuf = new Buffer(txhex, 'hex'); + var txidhex = '66da969fff214c329e27062beaf3baf20ed035801559b31f3e868c2de4cdfc5b'; + var tx = Transaction(txbuf); + + it('should make a new StealthTx', function() { + var stx = new StealthTx(); + should.exist(stx); + stx = StealthTx(); + should.exist(stx); + }); + + describe('#isForMe', function() { + + it('should return false for this known tx and random stealthkey', function() { + var sk = Stealthkey().fromRandom(); + var stx = StealthTx().set({sk: sk, tx: tx}); + stx.isForMe().should.equal(false); + }); + + }); + + describe('#notMine', function() { + + it('should return true for this known tx and random stealthkey', function() { + var sk = Stealthkey().fromRandom(); + var stx = StealthTx().set({sk: sk, tx: tx}); + stx.notMine().should.equal("StealthTx not mine"); + }); + + }); + + describe('#notStealth', function() { + + it('should know this is a stealth tx', function() { + var stx = StealthTx().set({tx: tx}); + stx.notStealth().should.equal(false); + }); + + it('should know this is not a stealth tx', function() { + var tx2 = Transaction(tx); + tx2.txouts.pop(); + tx2.txoutsvi = Varint(1); + var stx = StealthTx().set({tx: tx2}); + stx.notStealth().should.equal("Not enough txouts"); + }); + + }); + + describe('@parseOpReturnData', function() { + var txout = tx.txouts[0]; + var buf = txout.script.chunks[1].buf; + var parsed = StealthTx.parseOpReturnData(buf); + (typeof parsed.version).should.equal('number'); + parsed.noncebuf.length.should.be.above(0); + parsed.pubkey.toBuffer().length.should.equal(33); + }); + +});