122 lines
4.0 KiB
JavaScript
122 lines
4.0 KiB
JavaScript
/*
|
|
bitcoinsig.js - sign and verify messages with bitcoin address (public domain)
|
|
*/
|
|
|
|
function msg_numToVarInt(i) {
|
|
if (i < 0xfd) {
|
|
return [i];
|
|
} else if (i <= 0xffff) {
|
|
// can't use numToVarInt from bitcoinjs, BitcoinQT wants big endian here (!)
|
|
return [0xfd, i & 255, i >>> 8];
|
|
} else {
|
|
throw ("message too large");
|
|
}
|
|
}
|
|
|
|
function msg_bytes(message) {
|
|
var b = Crypto.charenc.UTF8.stringToBytes(message);
|
|
return msg_numToVarInt(b.length).concat(b);
|
|
}
|
|
|
|
function msg_digest(message) {
|
|
var b = msg_bytes("Bitcoin Signed Message:\n").concat(msg_bytes(message));
|
|
return Crypto.SHA256(Crypto.SHA256(b, {asBytes:true}), {asBytes:true});
|
|
}
|
|
|
|
function verify_message(signature, message, addrtype) {
|
|
try {
|
|
var sig = Crypto.util.base64ToBytes(signature);
|
|
} catch(err) {
|
|
return false;
|
|
}
|
|
|
|
if (sig.length != 65)
|
|
return false;
|
|
|
|
// extract r,s from signature
|
|
var r = BigInteger.fromByteArrayUnsigned(sig.slice(1,1+32));
|
|
var s = BigInteger.fromByteArrayUnsigned(sig.slice(33,33+32));
|
|
|
|
// get recid
|
|
var compressed = false;
|
|
var nV = sig[0];
|
|
if (nV < 27 || nV >= 35)
|
|
return false;
|
|
if (nV >= 31) {
|
|
compressed = true;
|
|
nV -= 4;
|
|
}
|
|
var recid = BigInteger.valueOf(nV - 27);
|
|
|
|
var ecparams = getSECCurveByName("secp256k1");
|
|
var curve = ecparams.getCurve();
|
|
var a = curve.getA().toBigInteger();
|
|
var b = curve.getB().toBigInteger();
|
|
var p = curve.getQ();
|
|
var G = ecparams.getG();
|
|
var order = ecparams.getN();
|
|
|
|
var x = r.add(order.multiply(recid.divide(BigInteger.valueOf(2))));
|
|
var alpha = x.multiply(x).multiply(x).add(a.multiply(x)).add(b).mod(p);
|
|
var beta = alpha.modPow(p.add(BigInteger.ONE).divide(BigInteger.valueOf(4)), p);
|
|
var y = beta.subtract(recid).isEven() ? beta : p.subtract(beta);
|
|
|
|
var R = new ECPointFp(curve, curve.fromBigInteger(x), curve.fromBigInteger(y));
|
|
var e = BigInteger.fromByteArrayUnsigned(msg_digest(message));
|
|
var minus_e = e.negate().mod(order);
|
|
var inv_r = r.modInverse(order);
|
|
var Q = (R.multiply(s).add(G.multiply(minus_e))).multiply(inv_r);
|
|
|
|
var public_key = Q.getEncoded(compressed);
|
|
var addr = new Bitcoin.Address(Bitcoin.Util.sha256ripe160(public_key));
|
|
|
|
addr.version = addrtype ? addrtype : 0;
|
|
return addr.toString();
|
|
}
|
|
|
|
function sign_message(private_key, message, compressed, addrtype) {
|
|
if (!private_key)
|
|
return false;
|
|
|
|
var signature = private_key.sign(msg_digest(message));
|
|
var address = new Bitcoin.Address(private_key.getPubKeyHash());
|
|
address.version = addrtype ? addrtype : 0;
|
|
|
|
//convert ASN.1-serialized signature to bitcoin-qt format
|
|
var obj = Bitcoin.ECDSA.parseSig(signature);
|
|
var sequence = [0];
|
|
sequence = sequence.concat(obj.r.toByteArrayUnsigned());
|
|
sequence = sequence.concat(obj.s.toByteArrayUnsigned());
|
|
|
|
for (var i = 0; i < 4; i++) {
|
|
var nV = 27 + i;
|
|
if (compressed)
|
|
nV += 4;
|
|
sequence[0] = nV;
|
|
var sig = Crypto.util.bytesToBase64(sequence);
|
|
if (verify_message(sig, message, addrtype) == address)
|
|
return sig;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function bitcoinsig_test() {
|
|
var k = '5JeWZ1z6sRcLTJXdQEDdB986E6XfLAkj9CgNE4EHzr5GmjrVFpf';
|
|
var a = '17mDAmveV5wBwxajBsY7g1trbMW1DVWcgL';
|
|
var s = 'HDiv4Oe9SjM1FFVbKk4m3N34efYiRgkQGGoEm564ldYt44jHVTuX23+WnihNMi4vujvpUs1M529P3kftjDezn9E=';
|
|
var m = 'test message';
|
|
payload = Bitcoin.Base58.decode(k);
|
|
secret = payload.slice(1, 33);
|
|
compressed = payload.length == 38;
|
|
console.log(verify_message(s, m));
|
|
sig = sign_message(new Bitcoin.ECKey(secret), m, compressed);
|
|
console.log(verify_message(sig, m));
|
|
}
|
|
|
|
if (typeof require != 'undefined' && require.main === module) {
|
|
window = global; navigator = {}; Bitcoin = {};
|
|
eval(require('fs').readFileSync('./bitcoinjs-min.js')+'');
|
|
eval(require('path').basename(module.filename,'.js')+'_test()');
|
|
}
|