Added Paillier and ECDSA split signing demo.
This commit is contained in:
parent
e8fba132cf
commit
77debc1d4f
4 changed files with 534 additions and 0 deletions
50
demo/demo.css
Normal file
50
demo/demo.css
Normal file
|
@ -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;
|
||||
}
|
158
demo/split-key.html
Executable file
158
demo/split-key.html
Executable file
|
@ -0,0 +1,158 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Two-party ECDSA signature generation</title>
|
||||
<link rel="stylesheet" type="text/css" href="demo.css"/>
|
||||
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
jQuery(function ($) {
|
||||
var worker = new Worker("split-key.js");
|
||||
|
||||
worker.onmessage = function (event) {
|
||||
var data = event.data;
|
||||
|
||||
switch (data.cmd) {
|
||||
case "ff":
|
||||
$("#"+data.field).val(data.value);
|
||||
break;
|
||||
case "log":
|
||||
if (console && "function" === typeof console.log) {
|
||||
console.log.apply(console, data.args);
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
worker.onerror = function (error) {
|
||||
console.error(error);
|
||||
};
|
||||
|
||||
worker.postMessage("start");
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Two-party ECDSA signature generation</h1>
|
||||
<p><strong>Initialization</strong></p>
|
||||
<div class="alice">
|
||||
<p>Alice starts out with her share of the private key d<sub>1</sub></p>
|
||||
<div>
|
||||
<label for="d1">d<sub>1</sub>=</label>
|
||||
<input id="d1" type="text" readonly="readonly"/>
|
||||
</div>
|
||||
<p>And a Paillier keypair pk/sk</p>
|
||||
<div>
|
||||
<label for="p1_n">n=</label>
|
||||
<input id="p1_n" type="text" readonly="readonly"/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="p1_g">g=</label>
|
||||
<input id="p1_g" type="text" readonly="readonly"/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="p1_l">λ=</label>
|
||||
<input id="p1_l" type="text" readonly="readonly"/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="p1_m">μ=</label>
|
||||
<input id="p1_m" type="text" readonly="readonly"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bob">
|
||||
<p>Bob starts out with his share d<sub>2</sub> of the private key d</p>
|
||||
<div>
|
||||
<label for="d2">d<sub>2</sub>=</label>
|
||||
<input id="d2" type="text" readonly="readonly"/>
|
||||
</div>
|
||||
</div>
|
||||
<p><strong>Protocol</strong></p>
|
||||
<div class="alice">
|
||||
<p>First Alice generates her share of the one-time secret k<sub>1</sub></p>
|
||||
<div>
|
||||
<label for="k1">k<sub>1</sub>=</label>
|
||||
<input id="k1" type="text" readonly="readonly"/>
|
||||
</div>
|
||||
<p>And its inverse z<sub>1</sub> = (k<sub>1</sub>)<sup>-1</sup> mod n</p>
|
||||
<div>
|
||||
<label for="z1">z<sub>1</sub>=</label>
|
||||
<input id="z1" type="text" readonly="readonly"/>
|
||||
</div>
|
||||
<p>She also calculates Q<sub>1</sub> = k<sub>1</sub>G</p>
|
||||
<div>
|
||||
<label for="q1">Q<sub>1</sub>=</label>
|
||||
<input id="q1" type="text" readonly="readonly"/>
|
||||
</div>
|
||||
<p>She then encrypts z<sub>1</sub> with her Paillier secret to create α = E<sub>pk</sub>(z<sub>1</sub>)</p>
|
||||
<div>
|
||||
<label for="alpha">α=</label>
|
||||
<input id="alpha" type="text" readonly="readonly"/>
|
||||
</div>
|
||||
<p>And β = E<sub>pk</sub>(d<sub>1</sub>z<sub>1</sub> mod n)</p>
|
||||
<div>
|
||||
<label for="beta">β=</label>
|
||||
<input id="beta" type="text" readonly="readonly"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="messageright"><div class="arrow"></div>
|
||||
Q<sub>1</sub>, α, β, message, e, pk
|
||||
</div>
|
||||
<div class="bob">
|
||||
<p>Bob validates Q<sub>1</sub> by ensuring that
|
||||
<ol>
|
||||
<li>Q<sub>1</sub> ≠ O</li>
|
||||
<li>x<sub>Q<sub>1</sub></sub> and y<sub>Q<sub>1</sub></sub> are in the interval [1,n - 1]</li>
|
||||
<li>y<sub>Q<sub>1</sub></sub><sup>2</sup> ≡ x<sub>Q<sub>1</sub></sub><sup>3</sup> + ax<sub>Q<sub>1</sub></sub> + b (mod p)</li>
|
||||
<li>nQ<sub>1</sub> = O</li>
|
||||
</ol></p>
|
||||
<p>And verifies the message to be signed</p>
|
||||
<p>He then generates his share k<sub>2</sub> of the private one-time value k</p>
|
||||
<div>
|
||||
<label for="k2">k<sub>2</sub>=</label>
|
||||
<input id="k2" type="text" readonly="readonly"/>
|
||||
</div>
|
||||
<p>And its inverse z<sub>2</sub> = (k<sub>2</sub>)<sup>-1</sup> mod n</p>
|
||||
<div>
|
||||
<label for="z2">z<sub>2</sub>=</label>
|
||||
<input id="z2" type="text" readonly="readonly"/>
|
||||
</div>
|
||||
<p>He can calculate r = x<sub>Q</sub> where Q(x<sub>Q</sub>, y<sub>Q</sub>) = k<sub>2</sub>Q<sub>1</sub></p>
|
||||
<div>
|
||||
<label for="r">r=</label>
|
||||
<input id="r" type="text" readonly="readonly"/>
|
||||
</div>
|
||||
<p>And Q<sub>2</sub> = k<sub>2</sub>G</p>
|
||||
<div>
|
||||
<label for="q2">Q<sub>2</sub>=</label>
|
||||
<input id="q2" type="text" readonly="readonly"/>
|
||||
</div>
|
||||
<p>Bob prepares a random value c to use for blinding<p>
|
||||
<div>
|
||||
<label for="c">c=</label>
|
||||
<input id="c" type="text" readonly="readonly"/>
|
||||
</div>
|
||||
<p>Finally he calculates σ = (α ×<sub>pk</sub> z<sub>2</sub>e) +<sub>pk</sub> (β ×<sub>pk</sub> z<sub>2</sub>d<sub>2</sub>r) +<sub>pk</sub> E<sub>pk</sub>(cn)</p>
|
||||
<div>
|
||||
<label for="sigma">σ=</label>
|
||||
<input id="sigma" type="text" readonly="readonly"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="messageleft"><div class="arrow"></div>
|
||||
Q<sub>2</sub>, r, σ
|
||||
</div>
|
||||
<div class="alice">
|
||||
<p>Alice confirms Q<sub>2</sub> is a valid public point
|
||||
<ol>
|
||||
<li>Q<sub>2</sub> ≠ O</li>
|
||||
<li>x<sub>Q<sub>2</sub></sub> and y<sub>Q<sub>2</sub></sub> are in the interval [1,n - 1]</li>
|
||||
<li>y<sub>Q<sub>2</sub></sub><sup>2</sup> ≡ x<sub>Q<sub>2</sub></sub><sup>3</sup> + ax<sub>Q<sub>2</sub></sub> + b (mod p)</li>
|
||||
<li>nQ<sub>2</sub> = O</li>
|
||||
</ol></p>
|
||||
<p>She now calculates r = x<sub>Q</sub> where Q = k<sub>1</sub>Q<sub>2</sub> and matches it against what Bob claimed</p>
|
||||
<p>She decrypts σ to receive s = D<sub>sk</sub>(σ)</p>
|
||||
<div>
|
||||
<label for="s">s=</label>
|
||||
<input id="s" type="text" readonly="readonly"/>
|
||||
</div>
|
||||
<p>She verifies the signature using r and the combined public key before publishing.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
229
demo/split-key.js
Normal file
229
demo/split-key.js
Normal file
|
@ -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));
|
||||
};
|
97
src/paillier.js
Normal file
97
src/paillier.js
Normal file
|
@ -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;
|
||||
})();
|
Loading…
Reference in a new issue