From b4062d56d1bb7600dba61e8d7f46bca307260a72 Mon Sep 17 00:00:00 2001
From: Daniel Cousens <github@dcousens.com>
Date: Sat, 10 Dec 2016 13:52:08 +1100
Subject: [PATCH] stealth: add dual key example

---
 test/integration/stealth.js | 55 +++++++++++++++++++++++++++++++++----
 1 file changed, 50 insertions(+), 5 deletions(-)

diff --git a/test/integration/stealth.js b/test/integration/stealth.js
index 7808d54..5c0fa9e 100644
--- a/test/integration/stealth.js
+++ b/test/integration/stealth.js
@@ -12,10 +12,8 @@ var n = secp256k1.n
 // vG = (dG * sha256(e * dG)G)
 function stealthSend (e, Q) {
   var eQ = Q.multiply(e) // shared secret
-
   var c = bigi.fromBuffer(bitcoin.crypto.sha256(eQ.getEncoded()))
   var cG = G.multiply(c)
-
   var vG = new bitcoin.ECPair(null, Q.add(cG))
 
   return vG
@@ -24,7 +22,6 @@ function stealthSend (e, Q) {
 // v = (d + sha256(eG * d))
 function stealthReceive (d, eG) {
   var eQ = eG.multiply(d) // shared secret
-
   var c = bigi.fromBuffer(bitcoin.crypto.sha256(eQ.getEncoded()))
   var v = new bitcoin.ECPair(d.add(c).mod(n))
 
@@ -40,6 +37,34 @@ function stealthRecoverLeaked (v, e, Q) {
   return d
 }
 
+// vG = (rG * (dG * sha256(e * dG)G))
+function stealthDualSend (e, R, Q) {
+  var eQ = Q.multiply(e) // shared secret
+  var c = bigi.fromBuffer(bitcoin.crypto.sha256(eQ.getEncoded()))
+  var cG = G.multiply(c)
+  var vG = new bitcoin.ECPair(null, R.add(Q.add(cG)))
+
+  return vG
+}
+
+// vG = (rG * (d + sha256(eG * d))G)
+function stealthDualScan (d, R, eG) {
+  var eQ = eG.multiply(d) // shared secret
+  var c = bigi.fromBuffer(bitcoin.crypto.sha256(eQ.getEncoded()))
+  var vG = new bitcoin.ECPair(null, R.add(G.multiply(d.add(c).mod(n))))
+
+  return vG
+}
+
+// v = (r + d + sha256(eG * d))
+function stealthDualReceive (d, r, eG) {
+  var eQ = eG.multiply(d) // shared secret
+  var c = bigi.fromBuffer(bitcoin.crypto.sha256(eQ.getEncoded()))
+  var v = new bitcoin.ECPair(r.add(d).add(c).mod(n))
+
+  return v
+}
+
 describe('bitcoinjs-lib (crypto)', function () {
   it('can generate a single-key stealth address', function () {
     // XXX: should be randomly generated, see next test for example
@@ -93,6 +118,26 @@ describe('bitcoinjs-lib (crypto)', function () {
     assert.equal(leaked.toWIF(), recipient.toWIF())
   })
 
-  // TODO
-  it.skip('can generate a dual-key stealth address', function () {})
+  it('can generate a dual-key stealth address (randomly)', function () {
+    // XXX: should be randomly generated, see next test for example
+    var recipient = bitcoin.ECPair.fromWIF('5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss') // private to recipient
+    var scan = bitcoin.ECPair.fromWIF('L5DkCk3xLLoGKncqKsWQTdaPSR4V8gzc14WVghysQGkdryRudjBM') // private to scanner/recipient
+    var nonce = bitcoin.ECPair.fromWIF('KxVqB96pxbw1pokzQrZkQbLfVBjjHFfp2mFfEp8wuEyGenLFJhM9') // private to sender
+
+    // ... recipient reveals public key(s) (recipient.Q, scan.Q) to sender
+    var forSender = stealthDualSend(nonce.d, recipient.Q, scan.Q)
+    assert.throws(function () { forSender.toWIF() }, /Error: Missing private key/)
+
+    // ... sender reveals nonce public key (nonce.Q) to scanner
+    var forScanner = stealthDualScan(scan.d, recipient.Q, nonce.Q)
+    assert.throws(function () { forScanner.toWIF() }, /Error: Missing private key/)
+
+    // ... scanner reveals relevant transaction + nonce public key (nonce.Q) to recipient
+    var forRecipient = stealthDualReceive(scan.d, recipient.d, nonce.Q)
+    assert.doesNotThrow(function () { forRecipient.toWIF() })
+
+    // scanner, sender and recipient, all derived same address
+    assert.equal(forSender.getAddress(), forScanner.getAddress())
+    assert.equal(forSender.getAddress(), forRecipient.getAddress())
+  })
 })