diff --git a/index.js b/index.js index e7bf374d1..8fa81aa60 100644 --- a/index.js +++ b/index.js @@ -18,8 +18,8 @@ privsec.Random = require('./lib/random'); privsec.Signature = require('./lib/signature'); //experimental -//privsec.expmt = {}; -//privsec.expmt.Stealth = require('./lib/expmt/stealth'); +privsec.expmt = {}; +privsec.expmt.Stealth = require('./lib/expmt/stealth'); //dependencies privsec.deps = {}; diff --git a/lib/expmt/stealth.js b/lib/expmt/stealth.js new file mode 100644 index 000000000..87d0431aa --- /dev/null +++ b/lib/expmt/stealth.js @@ -0,0 +1,111 @@ +var Key = require('../key'); +var Privkey = require('../privkey'); +var Pubkey = require('../pubkey'); +var Hash = require('../hash'); +var KDF = require('../kdf'); +var base58check = require('../base58check'); + +var Stealth = function Stealth(payloadKey, scanKey) { + if (!(this instanceof Stealth)) + return new Stealth(payloadKey, scanKey); + + this.payloadKey = payloadKey; + this.scanKey = scanKey; +}; + +Stealth.prototype.fromAddressBuffer = function(buf) { + if (!Buffer.isBuffer(buf) || buf.length !== 66) + throw new Error('stealth: A stealth address must have length 66'); + + var pPubBuf = buf.slice(0, 33); + var sPubBuf = buf.slice(33, 66); + + var payloadPubkey = Pubkey().fromDER(pPubBuf); + this.payloadKey = Key(undefined, payloadPubkey); + var scanPubkey = Pubkey().fromDER(sPubBuf); + this.scanKey = Key(undefined, scanPubkey); + + return this; +}; + +Stealth.prototype.fromAddressString = function(str) { + var buf = base58check.decode(str); + this.fromAddressBuffer(buf); + + return this; +}; + +Stealth.prototype.fromRandom = function() { + this.payloadKey = Key().fromRandom(); + this.scanKey = Key().fromRandom(); + + return this; +}; + +Stealth.prototype.getSharedKeyAsReceiver = function(senderPubkey) { + var sharedSecretPoint = senderPubkey.point.mul(this.scanKey.privkey.bn); + var sharedSecretPubkey = Pubkey(sharedSecretPoint); + var buf = sharedSecretPubkey.toDER(true); + var sharedKey = KDF.sha256hmac2key(buf); + + return sharedKey; +}; + +Stealth.prototype.getSharedKeyAsSender = function(senderKey) { + var sharedSecretPoint = this.scanKey.pubkey.point.mul(senderKey.privkey.bn); + var sharedSecretPubkey = Pubkey(sharedSecretPoint); + var buf = sharedSecretPubkey.toDER(true); + var sharedKey = KDF.sha256hmac2key(buf); + + return sharedKey; +}; + +Stealth.prototype.getReceivePubkeyAsReceiver = function(senderPubkey) { + var sharedKey = this.getSharedKeyAsReceiver(senderPubkey); + var pubkey = Pubkey(this.payloadKey.pubkey.point.add(sharedKey.pubkey.point)); + + return pubkey; +}; + +Stealth.prototype.getReceivePubkeyAsSender = function(senderKey) { + var sharedKey = this.getSharedKeyAsSender(senderKey); + var pubkey = Pubkey(this.payloadKey.pubkey.point.add(sharedKey.pubkey.point)); + + return pubkey; +}; + +Stealth.prototype.getReceiveKey = function(senderPubkey) { + var sharedKey = this.getSharedKeyAsReceiver(senderPubkey); + var privkey = Privkey(this.payloadKey.privkey.bn.add(sharedKey.privkey.bn)); + var key = Key(privkey); + key.privkey2pubkey(); + + return key; +}; + +Stealth.prototype.isForMe = function(senderPubkey, myPossiblePubkeyhash) { + var pubkey = this.getReceivePubkeyAsReceiver(senderPubkey); + var pubkeybuf = pubkey.toDER(true); + var pubkeyhash = Hash.sha256ripemd160(pubkeybuf); + + if (pubkeyhash.toString('hex') === myPossiblePubkeyhash.toString('hex')) + return true; + else + return false; +}; + +Stealth.prototype.toAddressBuffer = function() { + var pBuf = this.payloadKey.pubkey.toDER(true); + var sBuf = this.scanKey.pubkey.toDER(true); + + return Buffer.concat([pBuf, sBuf]); +}; + +Stealth.prototype.toAddressString = function() { + var buf = this.toAddressBuffer(); + var b58 = base58check.encode(buf); + + return b58; +}; + +module.exports = Stealth; diff --git a/test/test.stealth.js b/test/test.stealth.js new file mode 100644 index 000000000..40af9b311 --- /dev/null +++ b/test/test.stealth.js @@ -0,0 +1,177 @@ +var should = require('chai').should(); +var Stealth = require('../lib/expmt/stealth'); +var Key = require('../lib/key'); +var Privkey = require('../lib/privkey'); +var Pubkey = require('../lib/pubkey'); +var BN = require('../lib/bn'); +var Hash = require('../lib/hash'); +var base58check = require('../lib/base58check'); + +describe('stealth', function() { + + var stealth = Stealth(); + stealth.payloadKey = Key(); + stealth.payloadKey.privkey = Privkey(); + stealth.payloadKey.privkey.bn = BN().fromBuffer(Hash.sha256(new Buffer('test 1'))); + stealth.payloadKey.privkey2pubkey(); + stealth.scanKey = Key(); + stealth.scanKey.privkey = Privkey(); + stealth.scanKey.privkey.bn = BN().fromBuffer(Hash.sha256(new Buffer('test 2'))); + stealth.scanKey.privkey2pubkey(); + + var senderKey = Key(); + senderKey.privkey = Privkey(); + senderKey.privkey.bn = BN().fromBuffer(Hash.sha256(new Buffer('test 3'))); + senderKey.privkey2pubkey(); + + var addressString = '9dDbC9FzZ74r8njQkXD6W27gtrxLiWaeFPHxeo1fynQRXPicqxVt7u95ozbwoVVMXyrzaHKN9owsteg63FgwDfrxWx82SAW'; + + it('should create a new stealth', function() { + var stealth = new Stealth(); + should.exist(stealth); + }); + + it('should create a new stealth without using "new"', function() { + var stealth = Stealth(); + should.exist(stealth); + }); + + describe('#fromAddressBuffer', function() { + + it('should give a stealth address with the right pubkeys', function() { + var stealth2 = new Stealth(); + var buf = base58check.decode(addressString); + stealth2.fromAddressBuffer(buf); + stealth2.payloadKey.pubkey.toString().should.equal(stealth.payloadKey.pubkey.toString()); + stealth2.scanKey.pubkey.toString().should.equal(stealth.scanKey.pubkey.toString()); + }); + + }); + + describe('#fromAddressString', function() { + + it('should give a stealth address with the right pubkeys', function() { + var stealth2 = new Stealth(); + stealth2.fromAddressString(addressString); + stealth2.payloadKey.pubkey.toString().should.equal(stealth.payloadKey.pubkey.toString()); + stealth2.scanKey.pubkey.toString().should.equal(stealth.scanKey.pubkey.toString()); + }); + + }); + + describe('#fromRandom', function() { + + it('should create a new stealth from random', function() { + var stealth = Stealth().fromRandom(); + should.exist(stealth.payloadKey.privkey.bn.gt(0)); + should.exist(stealth.scanKey.privkey.bn.gt(0)); + }); + + }); + + describe('#getSharedKeyAsReceiver', function() { + + it('should return a key', function() { + var key = stealth.getSharedKeyAsReceiver(senderKey.pubkey); + (key instanceof Key).should.equal(true); + }); + + }); + + describe('#getSharedKeyAsSender', function() { + + it('should return a key', function() { + var stealth2 = new Stealth(); + stealth2.payloadKey = new Key(); + stealth2.payloadKey.pubkey = stealth.payloadKey.pubkey; + stealth2.scanKey = new Key(); + stealth2.scanKey.pubkey = stealth.scanKey.pubkey; + var key = stealth2.getSharedKeyAsSender(senderKey); + (key instanceof Key).should.equal(true); + }); + + it('should return the same key as getSharedKeyAsReceiver', function() { + var stealth2 = new Stealth(); + stealth2.payloadKey = new Key(); + stealth2.payloadKey.pubkey = stealth.payloadKey.pubkey; + stealth2.scanKey = new Key(); + stealth2.scanKey.pubkey = stealth.scanKey.pubkey; + var key = stealth2.getSharedKeyAsSender(senderKey); + + var key2 = stealth.getSharedKeyAsReceiver(senderKey.pubkey); + key.toString().should.equal(key2.toString()); + }); + + }); + + describe('#getReceivePubkeyAsReceiver', function() { + + it('should return a pubkey', function() { + var pubkey = stealth.getReceivePubkeyAsReceiver(senderKey.pubkey); + (pubkey instanceof Pubkey).should.equal(true); + }); + + }); + + describe('#getReceivePubkeyAsSender', function() { + + it('should return a pubkey', function() { + var pubkey = stealth.getReceivePubkeyAsSender(senderKey); + (pubkey instanceof Pubkey).should.equal(true); + }); + + it('should return the same pubkey as getReceivePubkeyAsReceiver', function() { + var pubkey = stealth.getReceivePubkeyAsSender(senderKey); + var pubkey2 = stealth.getReceivePubkeyAsReceiver(senderKey.pubkey); + pubkey2.toString().should.equal(pubkey.toString()); + }); + + }); + + describe('#getReceiveKey', function() { + + it('should return a key', function() { + var key = stealth.getReceiveKey(senderKey.pubkey); + (key instanceof Key).should.equal(true); + }); + + it('should return a key with the same pubkey as getReceivePubkeyAsReceiver', function() { + var key = stealth.getReceiveKey(senderKey.pubkey); + var pubkey = stealth.getReceivePubkeyAsReceiver(senderKey.pubkey); + key.pubkey.toString().should.equal(pubkey.toString()); + }); + + }); + + describe('#isForMe', function() { + + it('should return true if it (the transaction or message) is for me', function() { + var pubkeyhash = new Buffer('3cb64fa6ee9b3e8754e3e2bd033bf61048604a99', 'hex'); + stealth.isForMe(senderKey.pubkey, pubkeyhash).should.equal(true); + }); + + it('should return false if it (the transaction or message) is not for me', function() { + var pubkeyhash = new Buffer('00b64fa6ee9b3e8754e3e2bd033bf61048604a99', 'hex'); + stealth.isForMe(senderKey.pubkey, pubkeyhash).should.equal(false); + }); + + }); + + describe('#toAddressBuffer', function() { + + it('should return this known address buffer', function() { + var buf = stealth.toAddressBuffer(); + base58check.encode(buf).should.equal(addressString); + }); + + }); + + describe('#toAddressString', function() { + + it('should return this known address string', function() { + stealth.toAddressString().should.equal(addressString); + }); + + }); + +});