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)

+
+ + +
+
+
+Q1, α, β, message, e, pk +
+
+

Bob validates Q1 by ensuring that +

    +
  1. Q1 ≠ O
  2. +
  3. xQ1 and yQ1 are in the interval [1,n - 1]
  4. +
  5. yQ12 ≡ xQ13 + axQ1 + b (mod p)
  6. +
  7. nQ1 = O
  8. +

+

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)

+
+ + +
+
+
+Q2, r, σ +
+
+

Alice confirms Q2 is a valid public point +

    +
  1. Q2 ≠ O
  2. +
  3. xQ2 and yQ2 are in the interval [1,n - 1]
  4. +
  5. yQ22 ≡ xQ23 + axQ2 + b (mod p)
  6. +
  7. nQ2 = O
  8. +

+

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; +})();