27ec74ba8a
The BigInteger class we're using supports negative integers and encodes them with a sign bit. Since in our application we are dealing with unsigned integers only, we need to disable this functionality by creating two wrapper functions toByteArrayUnsigned and fromByteArrayUnsigned.
300 lines
8.7 KiB
JavaScript
300 lines
8.7 KiB
JavaScript
function integerToBytes(i, len) {
|
|
var bytes = i.toByteArrayUnsigned();
|
|
|
|
if (len < bytes.length) {
|
|
bytes = bytes.slice(bytes.length-len);
|
|
} else while (len > bytes.length) {
|
|
bytes.unshift(0);
|
|
}
|
|
|
|
return bytes;
|
|
};
|
|
|
|
ECFieldElementFp.prototype.getByteLength = function () {
|
|
return Math.floor((this.toBigInteger().bitLength() + 7) / 8);
|
|
};
|
|
|
|
ECPointFp.prototype.getEncoded = function (compressed) {
|
|
var x = this.getX().toBigInteger();
|
|
var y = this.getY().toBigInteger();
|
|
|
|
if (compressed) {
|
|
var PC;
|
|
}
|
|
|
|
var len = this.getX().getByteLength();
|
|
|
|
var enc = integerToBytes(x, len);
|
|
|
|
if (compressed) {
|
|
if (y.testBit(0)) {
|
|
enc.unshift(0x02);
|
|
} else {
|
|
enc.unshift(0x03);
|
|
}
|
|
} else {
|
|
enc.unshift(0x04);
|
|
enc = enc.concat(integerToBytes(y, len));
|
|
}
|
|
return enc;
|
|
};
|
|
|
|
ECPointFp.decodeFrom = function (curve, enc) {
|
|
var type = enc.shift();
|
|
|
|
// Extract x and y as byte arrays
|
|
var xBa = enc.slice(0, enc.length/2);
|
|
var yBa = enc.slice(enc.length/2, enc.length);
|
|
|
|
// Prepend zero byte to prevent interpretation as negative integer
|
|
xBa.unshift(0);
|
|
yBa.unshift(0);
|
|
|
|
// Convert to BigIntegers
|
|
var x = new BigInteger(xBa);
|
|
var y = new BigInteger(yBa);
|
|
|
|
// Return point
|
|
return new ECPointFp(curve, curve.fromBigInteger(x), curve.fromBigInteger(y));
|
|
};
|
|
|
|
ECPointFp.prototype.add2D = function (b) {
|
|
if(this.isInfinity()) return b;
|
|
if(b.isInfinity()) return this;
|
|
|
|
if (this.x.equals(b.x)) {
|
|
if (this.y.equals(b.y)) {
|
|
// this = b, i.e. this must be doubled
|
|
return this.twice();
|
|
}
|
|
// this = -b, i.e. the result is the point at infinity
|
|
return this.curve.getInfinity();
|
|
}
|
|
|
|
var x_x = b.x.subtract(this.x);
|
|
var y_y = b.y.subtract(this.y);
|
|
var gamma = y_y.divide(x_x);
|
|
/*console.log("b.y: ", Crypto.util.bytesToHex(b.y.toBigInteger().toByteArrayUnsigned()));
|
|
console.log("this.y: ", Crypto.util.bytesToHex(this.y.toBigInteger().toByteArrayUnsigned()));
|
|
console.log("b.y-this.y: ", Crypto.util.bytesToHex(b.y.subtract(this.y).toBigInteger().toByteArrayUnsigned()));*/
|
|
var x3 = gamma.square().subtract(this.x).subtract(b.x);
|
|
var y3 = gamma.multiply(this.x.subtract(x3)).subtract(this.y);
|
|
|
|
return new ECPointFp(this.curve, x3, y3);
|
|
};
|
|
|
|
ECPointFp.prototype.twice2D = function () {
|
|
if (this.isInfinity()) return this;
|
|
if (this.y.toBigInteger().signum() == 0) {
|
|
// if y1 == 0, then (x1, y1) == (x1, -y1)
|
|
// and hence this = -this and thus 2(x1, y1) == infinity
|
|
return this.curve.getInfinity();
|
|
}
|
|
|
|
var TWO = this.curve.fromBigInteger(BigInteger.valueOf(2));
|
|
var THREE = this.curve.fromBigInteger(BigInteger.valueOf(3));
|
|
var gamma = this.x.square().multiply(THREE).add(this.curve.a).divide(this.y.multiply(TWO));
|
|
|
|
var x3 = gamma.square().subtract(this.x.multiply(TWO));
|
|
var y3 = gamma.multiply(this.x.subtract(x3)).subtract(this.y);
|
|
|
|
return new ECPointFp(this.curve, x3, y3);
|
|
};
|
|
|
|
ECPointFp.prototype.multiply2D = function (k) {
|
|
if(this.isInfinity()) return this;
|
|
if(k.signum() == 0) return this.curve.getInfinity();
|
|
|
|
var e = k;
|
|
var h = e.multiply(new BigInteger("3"));
|
|
|
|
var neg = this.negate();
|
|
var R = this;
|
|
|
|
var i;
|
|
for (i = h.bitLength() - 2; i > 0; --i) {
|
|
R = R.twice();
|
|
|
|
var hBit = h.testBit(i);
|
|
var eBit = e.testBit(i);
|
|
|
|
if (hBit != eBit) {
|
|
R = R.add2D(hBit ? this : neg);
|
|
}
|
|
}
|
|
|
|
return R;
|
|
};
|
|
|
|
function dmp(v) {
|
|
if (!(v instanceof BigInteger)) v = v.toBigInteger();
|
|
return Crypto.util.bytesToHex(v.toByteArrayUnsigned());
|
|
};
|
|
|
|
Bitcoin.ECDSA = (function () {
|
|
var ecparams = getSECCurveByName("secp256k1");
|
|
var rng = new SecureRandom();
|
|
|
|
function implShamirsTrick(P, k, Q, l)
|
|
{
|
|
var m = Math.max(k.bitLength(), l.bitLength());
|
|
var Z = P.add2D(Q);
|
|
var R = P.curve.getInfinity();
|
|
|
|
console.log(P.curve, Q.curve);
|
|
console.log("Px: ", Crypto.util.bytesToHex(P.x.toBigInteger().toByteArrayUnsigned()));
|
|
console.log("Py: ", Crypto.util.bytesToHex(P.y.toBigInteger().toByteArrayUnsigned()));
|
|
console.log("Pz: ", Crypto.util.bytesToHex(P.z.toByteArrayUnsigned()));
|
|
console.log("Qx: ", Crypto.util.bytesToHex(Q.x.toBigInteger().toByteArrayUnsigned()));
|
|
console.log("Qy: ", Crypto.util.bytesToHex(Q.y.toBigInteger().toByteArrayUnsigned()));
|
|
console.log("Qz: ", Crypto.util.bytesToHex(Q.z.toByteArrayUnsigned()));
|
|
console.log("Zx: ", Crypto.util.bytesToHex(Z.x.toBigInteger().toByteArrayUnsigned()));
|
|
console.log("Zy: ", Crypto.util.bytesToHex(Z.y.toBigInteger().toByteArrayUnsigned()));
|
|
console.log("Zz: ", Crypto.util.bytesToHex(Z.z.toByteArrayUnsigned()));
|
|
|
|
for (var i = m - 1; i >= 0; --i) {
|
|
R = R.twice2D();
|
|
|
|
R.z = BigInteger.ONE;
|
|
|
|
if (k.testBit(i)) {
|
|
if (l.testBit(i)) {
|
|
R = R.add2D(Z);
|
|
if (i > (m-5)) console.log("RC: 1");
|
|
} else {
|
|
R = R.add2D(P);
|
|
if (i > (m-5)) console.log("RC: 2");
|
|
}
|
|
} else {
|
|
if (l.testBit(i)) {
|
|
R = R.add2D(Q);
|
|
if (i > (m-5)) console.log("RC: 3");
|
|
} else {
|
|
if (i > (m-5)) console.log("RC: 4");
|
|
}
|
|
}
|
|
if (i > (m-5)) {
|
|
console.log("Rx: ", Crypto.util.bytesToHex(R.x.toBigInteger().toByteArrayUnsigned()));
|
|
console.log("Ry: ", Crypto.util.bytesToHex(R.y.toBigInteger().toByteArrayUnsigned()));
|
|
console.log("Rz: ", Crypto.util.bytesToHex(R.z.toByteArrayUnsigned()));
|
|
}
|
|
}
|
|
|
|
return R;
|
|
};
|
|
|
|
var ECDSA = {
|
|
getBigRandom: function (limit) {
|
|
return new BigInteger(limit.bitLength(), rng)
|
|
.mod(limit.subtract(BigInteger.ONE))
|
|
.add(BigInteger.ONE)
|
|
;
|
|
},
|
|
sign: function (hash, priv) {
|
|
var d = priv;
|
|
var n = ecparams.getN();
|
|
var e = BigInteger.fromByteArrayUnsigned(hash);
|
|
|
|
console.log("signhash: "+ Crypto.util.bytesToHex(hash));
|
|
console.log("e: "+ Crypto.util.bytesToHex(e.toByteArrayUnsigned()));
|
|
|
|
do {
|
|
var k = ECDSA.getBigRandom(n);
|
|
var G = ecparams.getG();
|
|
var Q = G.multiply(k);
|
|
var r = Q.getX().toBigInteger().mod(n);
|
|
} while (r.compareTo(BigInteger.ZERO) <= 0);
|
|
|
|
console.log("k: "+ Crypto.util.bytesToHex(k.toByteArrayUnsigned()));
|
|
console.log("r: "+ Crypto.util.bytesToHex(r.toByteArrayUnsigned()));
|
|
|
|
var s = k.modInverse(n).multiply(e.add(d.multiply(r))).mod(n);
|
|
|
|
console.log("d*r: "+ Crypto.util.bytesToHex(d.multiply(r).toByteArrayUnsigned()));
|
|
console.log("e+d*r: "+ Crypto.util.bytesToHex(e.add(d.multiply(r)).toByteArrayUnsigned()));
|
|
console.log("s: "+ Crypto.util.bytesToHex(s.toByteArrayUnsigned()));
|
|
|
|
var rBa = r.toByteArrayUnsigned();
|
|
var sBa = s.toByteArrayUnsigned();
|
|
|
|
var sequence = [];
|
|
sequence.push(0x02); // INTEGER
|
|
sequence.push(rBa.length);
|
|
sequence = sequence.concat(rBa);
|
|
|
|
sequence.push(0x02); // INTEGER
|
|
sequence.push(sBa.length);
|
|
sequence = sequence.concat(sBa);
|
|
|
|
sequence.unshift(sequence.length);
|
|
sequence.unshift(0x30) // SEQUENCE
|
|
|
|
return sequence;
|
|
},
|
|
|
|
verify: function (hash, sig, pubkey) {
|
|
var cursor;
|
|
if (sig[0] != 0x30)
|
|
throw new Error("Signature not a valid DERSequence");
|
|
|
|
cursor = 2;
|
|
if (sig[cursor] != 0x02)
|
|
throw new Error("First element in signature must be a DERInteger");;
|
|
var rBa = sig.slice(cursor+2, cursor+2+sig[cursor+1]);
|
|
|
|
cursor += 2+sig[cursor+1];
|
|
if (sig[cursor] != 0x02)
|
|
throw new Error("Second element in signature must be a DERInteger");
|
|
var sBa = sig.slice(cursor+2, cursor+2+sig[cursor+1]);
|
|
|
|
cursor += 2+sig[cursor+1];
|
|
|
|
//if (cursor != sig.length)
|
|
// throw new Error("Extra bytes in signature");
|
|
|
|
var n = ecparams.getN();
|
|
var e = BigInteger.fromByteArrayUnsigned(hash);
|
|
|
|
console.log("e: "+ Crypto.util.bytesToHex(e.toByteArrayUnsigned()));
|
|
|
|
var r = BigInteger.fromByteArrayUnsigned(rBa);
|
|
var s = BigInteger.fromByteArrayUnsigned(sBa);
|
|
|
|
if (r.compareTo(BigInteger.ONE) < 0 ||
|
|
r.compareTo(n) >= 0)
|
|
return false;
|
|
|
|
if (s.compareTo(BigInteger.ONE) < 0 ||
|
|
s.compareTo(n) >= 0)
|
|
return false;
|
|
|
|
var c = s.modInverse(n);
|
|
|
|
var u1 = e.multiply(c).mod(n);
|
|
var u2 = r.multiply(c).mod(n);
|
|
|
|
console.log("r: "+ Crypto.util.bytesToHex(r.toByteArrayUnsigned()));
|
|
console.log("u1: "+ Crypto.util.bytesToHex(u1.toByteArrayUnsigned()));
|
|
console.log("u2: "+ Crypto.util.bytesToHex(u2.toByteArrayUnsigned()));
|
|
|
|
var G = ecparams.getG();
|
|
var Q = ECPointFp.decodeFrom(ecparams.getCurve(), pubkey);
|
|
|
|
console.log("G.x: ", Crypto.util.bytesToHex(G.x.toBigInteger().toByteArrayUnsigned()));
|
|
console.log("G.y: ", Crypto.util.bytesToHex(G.y.toBigInteger().toByteArrayUnsigned()));
|
|
console.log("Q.x: ", Crypto.util.bytesToHex(Q.x.toBigInteger().toByteArrayUnsigned()));
|
|
console.log("Q.y: ", Crypto.util.bytesToHex(Q.y.toBigInteger().toByteArrayUnsigned()));
|
|
|
|
var point = implShamirsTrick(G, u1, Q, u2);
|
|
|
|
console.log("P.x: ", Crypto.util.bytesToHex(point.x.toBigInteger().toByteArrayUnsigned()));
|
|
console.log("P.y: ", Crypto.util.bytesToHex(point.y.toBigInteger().toByteArrayUnsigned()));
|
|
|
|
var v = point.x.toBigInteger().mod(n);
|
|
|
|
return v.equals(r);
|
|
}
|
|
};
|
|
|
|
return ECDSA;
|
|
})();
|