diff --git a/demo/demo.css b/demo/demo.css
new file mode 100644
index 0000000..6912bee
--- /dev/null
+++ b/demo/demo.css
@@ -0,0 +1,50 @@
+p {
+ margin: 0.4em 0 0.2em;
+}
+
+input[type=text] {
+ width: 500px;
+}
+
+.alice, .bob {
+ margin: 1em;
+ width: 550px;
+ padding: 10px;
+}
+
+.alice {
+ border: 2px solid grey;
+ border-left-width: 20px;
+}
+
+.bob {
+ border: 2px solid grey;
+ border-right-width: 20px;
+}
+
+.messageleft, .messageright {
+ margin: 1em;
+ background-color: grey;
+ height: 30px;
+ text-align: center;
+ color: #fff;
+ line-height: 30px;
+ width: 590px;
+}
+
+.messageleft .arrow, .messageright .arrow {
+ border-top: 15px solid #fff;
+ border-bottom: 15px solid #fff;
+ width: 0;
+ height: 0;
+}
+
+.messageright .arrow {
+ float: right;
+ border-left: 15px solid grey;
+}
+
+.messageleft .arrow {
+ float: left;
+ border-right: 15px solid grey;
+}
diff --git a/demo/split-key.html b/demo/split-key.html
new file mode 100755
index 0000000..b3b9915
--- /dev/null
+++ b/demo/split-key.html
@@ -0,0 +1,158 @@
+
+
+
+Two-party ECDSA signature generation
+
+
+
+
+
+Two-party ECDSA signature generation
+Initialization
+
+
Alice starts out with her share of the private key d1
+
+
+
+
+
And a Paillier keypair pk/sk
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Bob starts out with his share d2 of the private key d
+
+
+
+
+
+Protocol
+
+
First Alice generates her share of the one-time secret k1
+
+
+
+
+
And its inverse z1 = (k1)-1 mod n
+
+
+
+
+
She also calculates Q1 = k1G
+
+
+
+
+
She then encrypts z1 with her Paillier secret to create α = Epk(z1)
+
+
+
+
+
And β = Epk(d1z1 mod n)
+
+
+
+
+
+
+Q
1, α, β, message, e, pk
+
+
+
Bob validates Q1 by ensuring that
+
+ - Q1 ≠ O
+ - xQ1 and yQ1 are in the interval [1,n - 1]
+ - yQ12 ≡ xQ13 + axQ1 + b (mod p)
+ - nQ1 = O
+
+
And verifies the message to be signed
+
He then generates his share k2 of the private one-time value k
+
+
+
+
+
And its inverse z2 = (k2)-1 mod n
+
+
+
+
+
He can calculate r = xQ where Q(xQ, yQ) = k2Q1
+
+
+
+
+
And Q2 = k2G
+
+
+
+
+
Bob prepares a random value c to use for blinding
+
+
+
+
+
Finally he calculates σ = (α ×pk z2e) +pk (β ×pk z2d2r) +pk Epk(cn)
+
+
+
+
+
+
+
+
Alice confirms Q2 is a valid public point
+
+ - Q2 ≠ O
+ - xQ2 and yQ2 are in the interval [1,n - 1]
+ - yQ22 ≡ xQ23 + axQ2 + b (mod p)
+ - nQ2 = O
+
+
She now calculates r = xQ where Q = k1Q2 and matches it against what Bob claimed
+
She decrypts σ to receive s = Dsk(σ)
+
+
+
+
+
She verifies the signature using r and the combined public key before publishing.
+
+
+
diff --git a/demo/split-key.js b/demo/split-key.js
new file mode 100644
index 0000000..582e1e9
--- /dev/null
+++ b/demo/split-key.js
@@ -0,0 +1,229 @@
+var window = this;
+
+importScripts(
+ "../src/crypto-js/crypto.js",
+ "../src/crypto-js/sha256.js",
+ "../src/jsbn/prng4.js",
+ "../src/jsbn/rng.js",
+ "../src/jsbn/jsbn.js",
+ "../src/jsbn/jsbn2.js",
+
+ "../src/jsbn/ec.js",
+ "../src/jsbn/sec.js",
+ "../src/events/eventemitter.js",
+ "../src/bitcoin.js",
+ "../src/util.js",
+ "../src/base58.js",
+
+ "../src/address.js",
+ "../src/ecdsa.js",
+ "../src/paillier.js"
+);
+
+function hex(value) {
+ if ("function" === typeof value.getEncoded) {
+ return Crypto.util.bytesToHex(value.getEncoded());
+ } else if ("function" === typeof value.toByteArrayUnsigned) {
+ return Crypto.util.bytesToHex(value.toByteArrayUnsigned());
+ } else if (Array.isArray(value)) {
+ return Crypto.util.bytesToHex(value);
+ }
+ return value;
+};
+function ff(field, value) {
+ value = hex(value);
+ postMessage({ "cmd": "ff", "field": field, "value": value });
+};
+
+function log() {
+ postMessage({ "cmd": "log", "args": Array.prototype.slice.apply(arguments) });
+};
+
+self.onmessage = function (event) {
+ var ecparams = getSECCurveByName("secp256k1");
+ var rng = new SecureRandom();
+
+ var G = ecparams.getG();
+ var n = ecparams.getN();
+
+ G.validate();
+
+ var Alice = function (pubShare) {
+ this.d1 = Bitcoin.ECDSA.getBigRandom(n);
+ ff('d1', this.d1);
+
+ this.paillier = Bitcoin.Paillier.generate(n.bitLength()*2+
+ Math.floor(Math.random()*10));
+
+ ff('p1_n', this.paillier.pub.n);
+ ff('p1_g', this.paillier.pub.g);
+ ff('p1_l', this.paillier.l);
+ ff('p1_m', this.paillier.m);
+ };
+ var Bob = function () {
+ this.d2 = Bitcoin.ECDSA.getBigRandom(n);
+ ff('d2', this.d2);
+ };
+
+ Alice.prototype.getPub = function (P) {
+ if (this.pub) return this.pub;
+
+ P.validate();
+
+ return this.pub = P.multiply(this.d1).getEncoded();
+ };
+
+ Bob.prototype.getPubShare = function () {
+ return G.multiply(this.d2);
+ };
+
+ Alice.prototype.step1 = function (message) {
+ var hash = Crypto.SHA256(Crypto.SHA256(message, {asBytes: true}), {asBytes: true});
+ this.e = BigInteger.fromByteArrayUnsigned(hash).mod(n);
+
+ this.k1 = Bitcoin.ECDSA.getBigRandom(n);
+ ff('k1', this.k1);
+
+ this.z1 = this.k1.modInverse(n);
+ ff('z1', this.z1);
+
+ var Q1 = G.multiply(this.k1);
+ ff('q1', Q1);
+
+ var alpha = this.paillier.encrypt(this.z1);
+ var beta = this.paillier.encrypt(this.d1.multiply(this.z1).mod(n));
+
+ ff('alpha', alpha);
+ ff('beta', beta);
+
+ // TODO: Generate a proof that alpha and beta are safe
+
+ return {
+ message: message,
+ e: this.e,
+ Q1: Q1,
+ alpha: alpha,
+ beta: beta,
+ paillier: this.paillier.pub
+ };
+ };
+
+ Bob.prototype.step2 = function (pkg) {
+ // ... In real life we would check that message is a valid transaction and
+ // does what we want.
+
+ // Throws exception on error
+ pkg.Q1.validate();
+
+ var hash = Crypto.SHA256(Crypto.SHA256(message, {asBytes: true}), {asBytes: true});
+ this.e = BigInteger.fromByteArrayUnsigned(hash).mod(n);
+
+ if (!this.e.equals(pkg.e)) {
+ throw new Error('We arrived at different values for e.');
+ }
+
+ this.paillier = pkg.paillier;
+ this.alpha = pkg.alpha;
+ this.beta = pkg.beta;
+
+ this.k2 = Bitcoin.ECDSA.getBigRandom(n);
+ ff('k2', this.k2);
+
+ this.z2 = this.k2.modInverse(n);
+ ff('z2', this.z2);
+
+ var Q2 = G.multiply(this.k2);
+ ff('q2', Q2);
+
+ var Q = pkg.Q1.multiply(this.k2);
+ this.r = Q.getX().toBigInteger().mod(n);
+ ff('r', this.r);
+
+ if (this.r.equals(BigInteger.ZERO)) {
+ throw new Error('r must not be zero.');
+ }
+
+ var c = Bitcoin.ECDSA.getBigRandom(this.paillier.n.divide(n));
+ ff('c', c);
+
+ var p = this.paillier;
+ var s_a = p.multiply(this.alpha, this.e.multiply(this.z2));
+ var s_b = p.multiply(this.beta, this.r.multiply(this.d2).multiply(this.z2));
+ var sigma = p.add(p.addCrypt(s_a, s_b), c.multiply(n));
+ ff('sigma', sigma);
+
+ return {
+ Q2: Q2,
+ r: this.r,
+ sigma: sigma
+ };
+ };
+
+ Alice.prototype.step3 = function (pkg) {
+ pkg.Q2.validate();
+
+ var Q = pkg.Q2.multiply(this.k1);
+ this.r = Q.getX().toBigInteger().mod(n);
+
+ if (!this.r.equals(pkg.r)) {
+ throw new Error('Could not confirm value for r.');
+ }
+
+ if (this.r.equals(BigInteger.ZERO)) {
+ throw new Error('r must not be zero.');
+ }
+
+ var s = this.paillier.decrypt(pkg.sigma).mod(n);
+ ff('s', s);
+
+ var sig = Bitcoin.ECDSA.serializeSig(this.r, s);
+
+ var hash = this.e.toByteArrayUnsigned();
+ if (!Bitcoin.ECDSA.verify(hash, sig, this.getPub())) {
+ throw new Error('Signature failed to verify.');
+ }
+
+ return {
+ r: this.r,
+ s: s
+ };
+ };
+
+ var message = "testmessage";
+
+ var bob = new Bob();
+ var pubShare = bob.getPubShare();
+
+ var alice = new Alice(pubShare);
+ var pub = alice.getPub(pubShare);
+
+ var pkg1 = alice.step1(message);
+ var pkg2 = bob.step2(pkg1);
+ var pkg3 = alice.step3(pkg2);
+
+ var sig = Bitcoin.ECDSA.serializeSig(pkg3.r, pkg3.s);
+
+ var kChk = alice.k1.multiply(bob.k2);
+ var rChk = G.multiply(kChk).getX().toBigInteger();
+ log("r :", hex(pkg3.r));
+ log("r/CHK:", hex(rChk));
+
+ var hash = Crypto.SHA256(Crypto.SHA256(message, {asBytes: true}), {asBytes: true});
+ var eChk = BigInteger.fromByteArrayUnsigned(hash).mod(n);
+ var dChk = alice.d1.multiply(bob.d2);
+ var sChk = kChk.modInverse(n).multiply(eChk.add(dChk.multiply(rChk))).mod(n);
+ log("s :", hex(pkg3.s));
+ log("s/CHK:", hex(sChk));
+
+ var sigChk = Bitcoin.ECDSA.serializeSig(rChk, sChk);
+ log("sig :", hex(sig));
+ log("sig/CHK:", hex(sigChk));
+
+ log("ver :", Bitcoin.ECDSA.verify(hash, sig, pub));
+ log("ver/CHK:", Bitcoin.ECDSA.verify(hash, sigChk, pub));
+ log("ver/CTL:", Bitcoin.ECDSA.verify(hash, Bitcoin.ECDSA.sign(hash, dChk), pub));
+
+ var priv = Bitcoin.ECDSA.getBigRandom(n);
+ pub = G.multiply(priv).getEncoded();
+ log("ver/GEN:", Bitcoin.ECDSA.verify(hash, Bitcoin.ECDSA.sign(hash, priv), pub));
+};
diff --git a/src/paillier.js b/src/paillier.js
new file mode 100644
index 0000000..17fada6
--- /dev/null
+++ b/src/paillier.js
@@ -0,0 +1,97 @@
+Bitcoin.Paillier = (function () {
+ var rng = new SecureRandom();
+ var TWO = BigInteger.valueOf(2);
+
+ var Paillier = {
+ generate: function (bitLength) {
+ var p, q;
+ do {
+ p = new BigInteger(bitLength, 1, rng);
+ q = new BigInteger(bitLength, 1, rng);
+ } while (p.equals(q));
+
+ var n = p.multiply(q);
+
+ // p - 1
+ var p1 = p.subtract(BigInteger.ONE);
+ // q - 1
+ var q1 = q.subtract(BigInteger.ONE);
+
+ var nSq = n.multiply(n);
+
+ // lambda
+ var l = p1.multiply(q1).divide(p1.gcd(q1));
+
+ var coprimeBitLength = n.bitLength() - Math.floor(Math.random()*10);
+
+ var alpha = new BigInteger(coprimeBitLength, 1, rng);
+ var beta = new BigInteger(coprimeBitLength, 1, rng);
+
+ var g = alpha.multiply(n).add(BigInteger.ONE)
+ .multiply(beta.modPow(n,nSq)).mod(nSq);
+
+ // mu
+ var m = g.modPow(l,nSq).mod(nSq)
+ .subtract(BigInteger.ONE).divide(n).modInverse(n);
+
+ return new Paillier.PrivateKey(n,g,l,m,nSq);
+ }
+ };
+
+ Paillier.PublicKey = function (n,g,nSq) {
+ this.n = n;
+ this.g = g;
+ this.nSq = nSq || n.multiply(n);
+ };
+
+ Paillier.PublicKey.prototype.encrypt = function (i, r) {
+ if (!r) {
+ var coprimeBitLength = this.n.bitLength() - Math.floor(Math.random()*10);
+ r = new BigInteger(coprimeBitLength, 1, rng);
+ }
+ return this.g.modPow(i,this.nSq).multiply(r.modPow(this.n,this.nSq))
+ .mod(this.nSq);
+ };
+
+ Paillier.PublicKey.prototype.add = function (c, f) {
+ return c.multiply(this.encrypt(f)).mod(this.nSq);
+ };
+
+ Paillier.PublicKey.prototype.addCrypt = function (c, f) {
+ return c.multiply(f).mod(this.nSq);
+ };
+
+ Paillier.PublicKey.prototype.multiply = function (c, f) {
+ return c.modPow(f, this.nSq);
+ };
+
+ Paillier.PrivateKey = function (n,g,l,m,nSq) {
+ this.l = l;
+ this.m = m;
+ this.n = n;
+ this.nSq = nSq || n.multiply(n);
+ this.pub = new Paillier.PublicKey(n,g,this.nSq);
+ };
+
+ Paillier.PrivateKey.prototype.encrypt = function (m) {
+ return this.pub.encrypt(m);
+ };
+
+ Paillier.PrivateKey.prototype.decrypt = function (c) {
+ return c.modPow(this.l, this.nSq).mod(this.nSq).subtract(BigInteger.ONE)
+ .divide(this.n).multiply(this.m).mod(this.n);
+ };
+
+ Paillier.PrivateKey.prototype.decryptR = function (c, i) {
+ if (!i) {
+ i = this.decrypt(c);
+ }
+ var rn = c.multiply(this.pub.g.modPow(i, this.nSq).modInverse(this.nSq))
+ .mod(this.nSq);
+ var a = this.l.modInverse(this.n).multiply(this.n.subtract(BigInteger.ONE));
+ var e = a.multiply(this.l).add(BigInteger.ONE).divide(this.n);
+ return rn.modPow(e, this.n);
+ };
+
+ return Paillier;
+})();