From 96df77429f8d07030c020ff8fca46f381e8e6561 Mon Sep 17 00:00:00 2001 From: "Ryan X. Charles" Date: Mon, 22 Sep 2014 18:23:10 -0700 Subject: [PATCH] add support for Dark Wallet-style addresses These functions are prefixed DW which stands for Dark Wallet. The code for the Dark Wallet address format can be found here: https://github.com/darkwallet/darkwallet/blob/develop/js/util/stealth.js Note that I deliberately support only the simplest possible format, which is where there is only one payload pubkey and the prefix is blank. I should now go back and replace my old toString, fromString, toBuffer, fromBuffer functions with these Dark Wallet versions, since they are much more well-thought out than mine. --- lib/expmt/stealthaddress.js | 65 +++++++++++++++++++++++++++++++++++++ test/stealthaddress.js | 51 +++++++++++++++++++++++++++++ 2 files changed, 116 insertions(+) diff --git a/lib/expmt/stealthaddress.js b/lib/expmt/stealthaddress.js index 66ae9c631..1551488e0 100644 --- a/lib/expmt/stealthaddress.js +++ b/lib/expmt/stealthaddress.js @@ -2,6 +2,8 @@ var Stealthkey = require('./stealthkey'); var Base58check = require('../base58check'); var Pubkey = require('../pubkey'); var KDF = require('../kdf'); +var BufferWriter = require('../bufferwriter'); +var BufferReader = require('../bufferreader'); var StealthAddress = function StealthAddress(addrstr) { if (!(this instanceof StealthAddress)) @@ -20,6 +22,9 @@ var StealthAddress = function StealthAddress(addrstr) { } }; +StealthAddress.mainver = 42; +StealthAddress.testver = 43; + StealthAddress.prototype.set = function(obj) { this.payloadPubkey = obj.payloadPubkey || this.payloadPubkey; this.scanPubkey = obj.scanPubkey || this.scanPubkey; @@ -56,6 +61,29 @@ StealthAddress.prototype.fromBuffer = function(buf) { return this; }; +StealthAddress.prototype.fromDWBuffer = function(buf) { + var parsed = StealthAddress.parseDWBuffer(buf); + if ((parsed.version !== StealthAddress.mainver) && (parsed.version !== StealthAddress.testver)) + throw new Error('Invalid version'); + if (parsed.options !== 0) + throw new Error('Invalid options'); + if (!parsed.scanPubkey) + throw new Error('Invalid scanPubkey'); + if (parsed.payloadPubkeys.length !== 1) + throw new Error('Must have exactly one payloadPubkey'); + if (parsed.nSigs !== 1) + throw new Error('Must require exactly one signature'); + if (parsed.prefix.toString() !== "") + throw new Error('Only blank prefixes supported'); + this.scanPubkey = parsed.scanPubkey; + this.payloadPubkey = parsed.payloadPubkeys[0]; + return this; +}; + +StealthAddress.prototype.fromDWString = function(str) { + return this.fromDWBuffer(Base58check(str).toBuffer()); +}; + StealthAddress.prototype.fromString = function(str) { var buf = Base58check.decode(str); this.fromBuffer(buf); @@ -86,6 +114,27 @@ StealthAddress.prototype.toBuffer = function() { return Buffer.concat([pBuf, sBuf]); }; +StealthAddress.prototype.toDWBuffer = function(networkstr) { + if (networkstr === 'testnet') + var version = StealthAddress.testver; + else + var version = StealthAddress.mainver; + var bw = new BufferWriter(); + bw.writeUInt8(version); + bw.writeUInt8(0); //options + bw.write(this.scanPubkey.toDER(true)); + bw.writeUInt8(1); //number of payload keys - we only support 1 (not multisig) + bw.write(this.payloadPubkey.toDER(true)); + bw.writeUInt8(1); //number of signatures - we only support 1 (not multisig) + bw.writeUInt8(0); //prefix length - we do not support prefix yet + var buf = bw.concat(); + return buf; +}; + +StealthAddress.prototype.toDWString = function(networkstr) { + return Base58check(this.toDWBuffer(networkstr)).toString(); +}; + StealthAddress.prototype.toString = function() { var buf = this.toBuffer(); var b58 = Base58check.encode(buf); @@ -93,4 +142,20 @@ StealthAddress.prototype.toString = function() { return b58; }; +StealthAddress.parseDWBuffer = function(buf) { + var br = new BufferReader(buf); + var parsed = {}; + parsed.version = br.readUInt8(); + parsed.options = br.readUInt8(); + parsed.scanPubkey = Pubkey().fromBuffer(br.read(33)); + parsed.nPayloadPubkeys = br.readUInt8(); + parsed.payloadPubkeys = []; + for (var i = 0; i < parsed.nPayloadPubkeys; i++) + parsed.payloadPubkeys.push(Pubkey().fromBuffer(br.read(33))); + parsed.nSigs = br.readUInt8(); + parsed.nPrefix = br.readUInt8(); + parsed.prefix = br.read(parsed.nPrefix / 8); + return parsed; +}; + module.exports = StealthAddress; diff --git a/test/stealthaddress.js b/test/stealthaddress.js index 8ab30a9a3..8d32289a9 100644 --- a/test/stealthaddress.js +++ b/test/stealthaddress.js @@ -26,6 +26,7 @@ describe('StealthAddress', function() { senderKeypair.privkey2pubkey(); var addressString = '9dDbC9FzZ74r8njQkXD6W27gtrxLiWaeFPHxeo1fynQRXPicqxVt7u95ozbwoVVMXyrzaHKN9owsteg63FgwDfrxWx82SAW'; + var dwhex = '2a0002697763d7e9becb0c180083738c32c05b0e2fee26d6278020c06bbb04d5f66b32010362408459041e0473298af3824dbabe4d2b7f846825ed4d1c2e2c670c07cb275d0100'; it('should make a new stealth address', function() { var sa = new StealthAddress(); @@ -69,6 +70,22 @@ describe('StealthAddress', function() { }); + describe('#fromDWBuffer', function() { + + it('should parse this DW buffer', function() { + StealthAddress().fromDWBuffer(new Buffer(dwhex, 'hex')).toDWBuffer().toString('hex').should.equal(dwhex); + }); + + }); + + describe('#fromDWString', function() { + + it('should parse this DW buffer', function() { + StealthAddress().fromDWString(Base58check(new Buffer(dwhex, 'hex')).toString()).toDWBuffer().toString('hex').should.equal(dwhex); + }); + + }); + describe('#fromString', function() { it('should give a stealthkey address with the right pubkeys', function() { @@ -126,6 +143,24 @@ describe('StealthAddress', function() { }); + describe('#toDWBuffer', function() { + + it('should return this known address buffer', function() { + var buf = Base58check.decode(addressString); + StealthAddress().fromBuffer(buf).toDWBuffer().toString('hex').should.equal(dwhex); + }); + + }); + + describe('#toDWString', function() { + + it('should return this known address buffer', function() { + var buf = Base58check.decode(addressString); + StealthAddress().fromBuffer(buf).toDWString().should.equal(Base58check(new Buffer(dwhex, 'hex')).toString()); + }); + + }); + describe('#toString', function() { it('should return this known address string', function() { @@ -134,4 +169,20 @@ describe('StealthAddress', function() { }); + describe('@parseDWBuffer', function() { + + it('should parse this known DW buffer', function() { + var buf = new Buffer(dwhex, 'hex'); + var parsed = StealthAddress.parseDWBuffer(buf); + parsed.version.should.equal(42); + parsed.options.should.equal(0); + parsed.scanPubkey.toString().should.equal('02697763d7e9becb0c180083738c32c05b0e2fee26d6278020c06bbb04d5f66b32'); + parsed.nPayloadPubkeys.should.equal(1); + parsed.payloadPubkeys[0].toString().should.equal('0362408459041e0473298af3824dbabe4d2b7f846825ed4d1c2e2c670c07cb275d'); + parsed.nSigs.should.equal(1); + parsed.prefix.toString().should.equal(''); + }); + + }); + });