Update stdop: FLO multisig

This commit is contained in:
sairajzero 2023-02-20 17:18:29 +05:30
parent 4a856090c5
commit 46e386c9ee
3 changed files with 421 additions and 64 deletions

View File

@ -1,4 +1,4 @@
(function (EXPORTS) { //floBlockchainAPI v2.3.3e
(function (EXPORTS) { //floBlockchainAPI v2.4.0
/* FLO Blockchain Operator to send/receive data from blockchain using API calls*/
'use strict';
const floBlockchainAPI = EXPORTS;
@ -126,7 +126,7 @@
return new Promise((resolve, reject) => {
if (!floCrypto.validateASCII(floData))
return reject("Invalid FLO_Data: only printable ASCII characters are allowed");
else if (!floCrypto.validateFloID(senderAddr))
else if (!floCrypto.validateFloID(senderAddr, true))
return reject(`Invalid address : ${senderAddr}`);
else if (!floCrypto.validateFloID(receiverAddr))
return reject(`Invalid address : ${receiverAddr}`);
@ -203,7 +203,7 @@
//merge all UTXOs of a given floID into a single UTXO
floBlockchainAPI.mergeUTXOs = function (floID, privKey, floData = '') {
return new Promise((resolve, reject) => {
if (!floCrypto.validateFloID(floID))
if (!floCrypto.validateFloID(floID, true))
return reject(`Invalid floID`);
if (!floCrypto.verifyPrivKey(privKey, floID))
return reject("Invalid Private Key");
@ -381,7 +381,6 @@
for (let floID in senders)
promises.push(promisedAPI(`api/addr/${floID}/utxo`));
Promise.all(promises).then(results => {
let wifSeq = [];
var trx = bitjs.transaction();
for (let floID in senders) {
let utxos = results.shift();
@ -391,13 +390,11 @@
sendAmt = totalSendAmt * ratio;
} else
sendAmt = senders[floID].coins + dividedFee;
let wif = senders[floID].wif;
let utxoAmt = 0.0;
for (let i = utxos.length - 1;
(i >= 0) && (utxoAmt < sendAmt); i--) {
if (utxos[i].confirmations) {
trx.addinput(utxos[i].txid, utxos[i].vout, utxos[i].scriptPubKey);
wifSeq.push(wif);
utxoAmt += utxos[i].amount;
}
}
@ -410,8 +407,8 @@
for (let floID in receivers)
trx.addoutput(floID, receivers[floID]);
trx.addflodata(floData.replace(/\n/g, ' '));
for (let i = 0; i < wifSeq.length; i++)
trx.signinput(i, wifSeq[i], 1);
for (let floID in senders)
trx.sign(senders[floID].wif, 1);
var signedTxHash = trx.serialize();
broadcastTx(signedTxHash)
.then(txid => resolve(txid))
@ -421,6 +418,126 @@
})
}
//Create a multisig transaction
const createMultisigTx = floBlockchainAPI.createMultisigTx = function (redeemScript, receivers, amounts, floData = '', strict_utxo = true) {
return new Promise((resolve, reject) => {
var multisig = floCrypto.decodeRedeemScript(redeemScript);
//validate multisig script and flodata
if (!multisig)
return reject(`Invalid redeemScript`);
var senderAddr = multisig.address;
if (!floCrypto.validateFloID(senderAddr))
return reject(`Invalid multisig : ${senderAddr}`);
else if (!floCrypto.validateASCII(floData))
return reject("Invalid FLO_Data: only printable ASCII characters are allowed");
//validate receiver addresses
if (!Array.isArray(receivers))
receivers = [receivers];
for (let r of receivers)
if (!floCrypto.validateFloID(r))
return reject(`Invalid address : ${r}`);
//validate amounts
if (!Array.isArray(amounts))
amounts = [amounts];
if (amounts.length != receivers.length)
return reject("Receivers and amounts have different length");
var sendAmt = 0;
for (let a of amounts) {
if (typeof a !== 'number' || a <= 0)
return reject(`Invalid amount : ${a}`);
sendAmt += a;
}
getBalance(senderAddr).then(balance => {
var fee = DEFAULT.fee;
if (balance < sendAmt + fee)
return reject("Insufficient FLO balance!");
//get unconfirmed tx list
promisedAPI(`api/addr/${senderAddr}`).then(result => {
readTxs(senderAddr, 0, result.unconfirmedTxApperances).then(result => {
let unconfirmedSpent = {};
for (let tx of result.items)
if (tx.confirmations == 0)
for (let vin of tx.vin)
if (vin.addr === senderAddr) {
if (Array.isArray(unconfirmedSpent[vin.txid]))
unconfirmedSpent[vin.txid].push(vin.vout);
else
unconfirmedSpent[vin.txid] = [vin.vout];
}
//get utxos list
promisedAPI(`api/addr/${senderAddr}/utxo`).then(utxos => {
//form/construct the transaction data
var trx = bitjs.transaction();
var utxoAmt = 0.0;
for (var i = utxos.length - 1;
(i >= 0) && (utxoAmt < sendAmt + fee); i--) {
//use only utxos with confirmations (strict_utxo mode)
if (utxos[i].confirmations || !strict_utxo) {
if (utxos[i].txid in unconfirmedSpent && unconfirmedSpent[utxos[i].txid].includes(utxos[i].vout))
continue; //A transaction has already used the utxo, but is unconfirmed.
trx.addinput(utxos[i].txid, utxos[i].vout, redeemScript); //for multisig, script=redeemScript
utxoAmt += utxos[i].amount;
};
}
if (utxoAmt < sendAmt + fee)
reject("Insufficient FLO: Some UTXOs are unconfirmed");
else {
for (let i in receivers)
trx.addoutput(receivers[i], amounts[i]);
var change = utxoAmt - sendAmt - fee;
if (change > DEFAULT.minChangeAmt)
trx.addoutput(senderAddr, change);
trx.addflodata(floData.replace(/\n/g, ' '));
resolve(trx);
}
}).catch(error => reject(error))
}).catch(error => reject(error))
}).catch(error => reject(error))
}).catch(error => reject(error))
});
}
//Create and send multisig transaction
const sendMultisigTx = floBlockchainAPI.sendMultisigTx = function (redeemScript, privateKeys, receivers, amounts, floData = '', strict_utxo = true) {
return new Promise((resolve, reject) => {
var multisig = floCrypto.decodeRedeemScript(redeemScript);
if (!multisig)
return reject(`Invalid redeemScript`);
if (privateKeys.length < multisig.required)
return reject(`Insufficient privateKeys (required ${multisig.required})`);
for (let pk of privateKeys) {
var flag = false;
for (let pub of multisig.pubkeys)
if (floCrypto.verifyPrivKey(pk, pub, false))
flag = true;
if (!flag)
return reject(`Invalid Private key`);
}
createMultisigTx(redeemScript, receivers, amounts, floData, strict_utxo).then(trx => {
for (let pk of privateKeys)
trx.sign(pk, 1);
var signedTxHash = trx.serialize();
broadcastTx(signedTxHash)
.then(txid => resolve(txid))
.catch(error => reject(error))
}).catch(error => reject(error))
})
}
floBlockchainAPI.writeMultisigData = function (redeemScript, data, privatekeys, receiverAddr = DEFAULT.receiverID, options = {}) {
let strict_utxo = options.strict_utxo === false ? false : true,
sendAmt = isNaN(options.sendAmt) ? DEFAULT.sendAmt : options.sendAmt;
return new Promise((resolve, reject) => {
if (!floCrypto.validateFloID(receiverAddr))
return reject(`Invalid receiver: ${receiverAddr}`);
sendMultisigTx(redeemScript, privatekeys, receiverAddr, sendAmt, data, strict_utxo)
.then(txid => resolve(txid))
.catch(error => reject(error))
})
}
//Broadcast signed Tx in blockchain using API
const broadcastTx = floBlockchainAPI.broadcastTx = function (signedTxHash) {
return new Promise((resolve, reject) => {

View File

@ -1,4 +1,4 @@
(function (EXPORTS) { //floCrypto v2.3.3e
(function (EXPORTS) { //floCrypto v2.3.4a
/* FLO Crypto Operators */
'use strict';
const floCrypto = EXPORTS;
@ -222,7 +222,7 @@
key.setCompressed(true);
if (isfloID && pubKey_floID == key.getBitcoinAddress())
return true;
else if (!isfloID && pubKey_floID == key.getPubKeyHex())
else if (!isfloID && pubKey_floID.toUpperCase() == key.getPubKeyHex().toUpperCase())
return true;
else
return false;
@ -231,12 +231,36 @@
}
}
floCrypto.getMultisigAddress = function (publicKeyList, requiredSignatures) {
if (!Array.isArray(publicKeyList) || !publicKeyList.length)
return null;
if (!Number.isInteger(requiredSignatures) || requiredSignatures < 1)
return null;
try {
var multisig = bitjs.pubkeys2multisig(publicKeyList, requiredSignatures);
return multisig;
} catch {
return null;
}
}
floCrypto.decodeRedeemScript = function (redeemScript) {
try {
var decoded = bitjs.transaction().decodeRedeemScript(redeemScript);
return decoded;
} catch {
return null;
}
}
//Check if the given flo-id is valid or not
floCrypto.validateFloID = function (floID) {
floCrypto.validateFloID = function (floID, regularOnly = false) {
if (!floID)
return false;
try {
let addr = new Bitcoin.Address(floID);
if (regularOnly && addr.version != Bitcoin.Address.standardVersion)
return false;
return true;
} catch {
return false;

View File

@ -1,4 +1,4 @@
(function (GLOBAL) { //lib v1.3.2
(function (GLOBAL) { //lib v1.4.1a
'use strict';
/* Utility Libraries required for Standard operations
* All credits for these codes belong to their respective creators, moderators and owners.
@ -4349,18 +4349,10 @@
var bitjs = GLOBAL.bitjs = function () { };
function ascii_to_hexa(str) {
var arr1 = [];
for (var n = 0, l = str.length; n < l; n++) {
var hex = Number(str.charCodeAt(n)).toString(16);
arr1.push(hex);
}
return arr1.join('');
}
/* public vars */
bitjs.pub = 0x23; // flochange - changed the prefix to FLO Mainnet PublicKey Prefix 0x23
bitjs.priv = 0xa3; //flochange - changed the prefix to FLO Mainnet Private key prefix 0xa3
bitjs.multisig = 0x5e; //flochange - prefix for FLO Mainnet Multisig 0x5e
bitjs.compressed = false;
/* provide a privkey and return an WIF */
@ -4461,6 +4453,45 @@
return B58.encode(r.concat(checksum));
}
/* generate a multisig address from pubkeys and required signatures */
bitjs.pubkeys2multisig = function (pubkeys, required) {
var s = [];
s.push(80 + required); //OP_1
for (var i = 0; i < pubkeys.length; ++i) {
let bytes = Crypto.util.hexToBytes(pubkeys[i]);
s.push(bytes.length);
s = s.concat(bytes);
}
s.push(80 + pubkeys.length); //OP_1
s.push(174); //OP_CHECKMULTISIG
if (s.length > 520) { // too large
throw Error(`redeemScript size(=${s.length}) too large`)
}
var x = ripemd160(Crypto.SHA256(s, {
asBytes: true
}), {
asBytes: true
});
x.unshift(bitjs.multisig);
var r = x;
r = Crypto.SHA256(Crypto.SHA256(r, {
asBytes: true
}), {
asBytes: true
});
var checksum = r.slice(0, 4);
var redeemScript = Crypto.util.bytesToHex(s);
var address = B58.encode(x.concat(checksum));
return {
'address': address,
'redeemScript': redeemScript,
'size': s.length
};
}
bitjs.transaction = function () {
var btrx = {};
btrx.version = 2; //flochange look at this version
@ -4476,7 +4507,6 @@
'hash': txid,
'index': index
};
//o.script = []; Signature and Public Key should be added after singning
o.script = Crypto.util.hexToBytes(scriptPubKey); //push previous output pubkey script
o.sequence = sequence || ((btrx.locktime == 0) ? 4294967295 : 0);
return this.inputs.push(o);
@ -4485,26 +4515,43 @@
btrx.addoutput = function (address, value) {
var o = {};
var buf = [];
var addrDecoded = btrx.addressDecode(address);
var addr = this.addressDecode(address);
o.value = new BigInteger('' + Math.round((value * 1) * 1e8), 10);
buf.push(118); //OP_DUP
buf.push(169); //OP_HASH160
buf.push(addrDecoded.length);
buf = buf.concat(addrDecoded); // address in bytes
buf.push(136); //OP_EQUALVERIFY
buf.push(172); // OP_CHECKSIG
if (addr.version === bitjs.pub) { // regular address
buf.push(118); //OP_DUP
buf.push(169); //OP_HASH160
buf.push(addr.bytes.length);
buf = buf.concat(addr.bytes); // address in bytes
buf.push(136); //OP_EQUALVERIFY
buf.push(172); //OP_CHECKSIG
} else if (addr.version === bitjs.multisig) { // multisig address
buf.push(169); //OP_HASH160
buf.push(addr.bytes.length);
buf = buf.concat(addr.bytes); // address in bytes
buf.push(135); //OP_EQUAL
}
o.script = buf;
return this.outputs.push(o);
}
// flochange - Added fn to assign flodata to tx
btrx.addflodata = function (data) {
//checks for valid flo-data string
if (typeof data !== "string")
throw Error("floData should be String");
if (data.length > 1040)
throw Error("floData Character Limit Exceeded");
if (bitjs.strToBytes(data).some(c => c < 32 || c > 127))
throw Error("floData contains Invalid characters (only ASCII characters allowed");
btrx.addflodata = function (txcomments) { // flochange - this whole function needs to be done
this.floData = txcomments;
return this.floData; //flochange .. returning the txcomments -- check if the function return will assign
this.floData = data;
return this.floData;
}
// Only standard addresses
// Only standard addresses (standard multisig supported)
btrx.addressDecode = function (address) {
var bytes = B58.decode(address);
var front = bytes.slice(0, bytes.length - 4);
@ -4515,7 +4562,10 @@
asBytes: true
}).slice(0, 4);
if (checksum + "" == back + "") {
return front.slice(1);
return {
version: front[0],
bytes: front.slice(1)
};
}
}
@ -4740,6 +4790,62 @@
return KBigInt;
};
btrx.parseScript = function (script) {
var chunks = [];
var i = 0;
function readChunk(n) {
chunks.push(script.slice(i, i + n));
i += n;
};
while (i < script.length) {
var opcode = script[i++];
if (opcode >= 0xF0) {
opcode = (opcode << 8) | script[i++];
}
var len;
if (opcode > 0 && opcode < 76) { //OP_PUSHDATA1
readChunk(opcode);
} else if (opcode == 76) { //OP_PUSHDATA1
len = script[i++];
readChunk(len);
} else if (opcode == 77) { //OP_PUSHDATA2
len = (script[i++] << 8) | script[i++];
readChunk(len);
} else if (opcode == 78) { //OP_PUSHDATA4
len = (script[i++] << 24) | (script[i++] << 16) | (script[i++] << 8) | script[i++];
readChunk(len);
} else {
chunks.push(opcode);
}
if (i < 0x00) {
break;
}
}
return chunks;
}
btrx.decodeRedeemScript = function (rs) {
if (typeof rs == "string")
rs = Crypto.util.hexToBytes(rs);
var script = this.parseScript(rs);
if (!(script[0] > 80 && script[script.length - 2] > 80 && script[script.length - 1] == 174)) //OP_CHECKMULTISIG
throw "Invalid RedeemScript";
var r = {};
r.required = script[0] - 80;
r.pubkeys = [];
for (var i = 1; i < script.length - 2; i++)
r.pubkeys.push(Crypto.util.bytesToHex(script[i]));
r.address = bitjs.pubkeys2multisig(r.pubkeys, r.required).address;
r.redeemscript = Crypto.util.bytesToHex(rs);
return r;
}
/* sign a "standard" input */
btrx.signinput = function (index, wif, sigHashType) {
var key = bitjs.wif2pubkey(wif);
@ -4756,15 +4862,100 @@
return true;
}
/* sign a multisig input */
btrx.signmultisig = function (index, wif, sigHashType) {
var script = Array.from(this.inputs[index].script);
var redeemScript, sigsList = [];
if (script[0] == 0) { //script with signatures
script = this.parseScript(script);
for (var i = 0; i < script.length; i++) {
if (Array.isArray(script[i])) {
if (script[i][0] == 48) //0x30 DERSequence
sigsList.push(script[i]);
else if (script[i][0] >= 80 && script[i][script[i].length - 1] == 174) //OP_CHECKMULTISIG
redeemScript = script[i];
}
}
} else { //script = redeemscript
redeemScript = script;
}
var pubkeyList = this.decodeRedeemScript(redeemScript).pubkeys;
var pubkey = bitjs.wif2pubkey(wif)['pubkey'];
if (!pubkeyList.includes(pubkey)) //wif not a part of this multisig
return false;
pubkeyList = pubkeyList.map(pub => Crypto.util.hexToBytes(bitjs.pubkeydecompress(pub))); //decompress pubkeys
var shType = sigHashType || 1;
this.inputs[index].script = redeemScript; //script to be signed is redeemscript
var signature = Crypto.util.hexToBytes(this.transactionSig(index, wif, shType));
sigsList.push(signature);
var buf = [];
buf.push(0);
//verify signatures and order them (also remove duplicate sigs)
for (let x in pubkeyList) {
for (let y in sigsList) {
var sighash = Crypto.util.hexToBytes(this.transactionHash(index, sigsList[y].slice(-1)[0] * 1));
if (bitjs.verifySignature(sighash, sigsList[y], pubkeyList[x])) {
buf.push(sigsList[y].length);
buf = buf.concat(sigsList[y]);
break; //ensures duplicate sigs from same pubkey are not added
}
}
}
//append redeemscript
buf.push(redeemScript.length);
buf = buf.concat(redeemScript);
this.inputs[index].script = buf;
return true;
}
/* sign inputs */
btrx.sign = function (wif, sigHashType) {
var shType = sigHashType || 1;
for (var i = 0; i < this.inputs.length; i++) {
this.signinput(i, wif, shType);
var decodedScript = this.scriptDecode(i);
if (decodedScript.type == "scriptpubkey" && decodedScript.signed == false) { //regular
var addr = bitjs.wif2address(wif)["address"];;
if (decodedScript.pubhash == Crypto.util.bytesToHex(this.addressDecode(addr).bytes)) //input belongs to wif
this.signinput(i, wif, shType);
} else if (decodedScript.type == "multisig") { //multisig
this.signmultisig(i, wif, shType);
}
}
return this.serialize();
}
// function to find type of the script in input
btrx.scriptDecode = function (index) {
var script = this.parseScript(this.inputs[index].script);
if (script.length == 5 && script[script.length - 1] == 172) {
//OP_DUP OP_HASH160 [address bytes] OP_EQUALVERIFY OP_CHECKSIG
// regular scriptPubkey (not signed)
return { type: 'scriptpubkey', signed: false, pubhash: Crypto.util.bytesToHex(script[2]) };
} else if (script.length == 2 && script[0][0] == 48) {
//[signature] [pubkey]
//(probably) regular signed
return { type: 'scriptpubkey', signed: true };
} else if (script[0] == 0 && script[script.length - 1][script[script.length - 1].length - 1] == 174) {
//0 [signatues] [redeemscript OP_CHECKMULTISIG]
// multisig with signature
return { type: 'multisig', rs: script[script.length - 1] };
} else if (script[0] >= 80 && script[script.length - 1] == 174) {
//redeemscript: 80+ [pubkeys] OP_CHECKMULTISIG
// multisig without signature
return { type: 'multisig', rs: Array.from(this.inputs[index].script) };
}
}
/* serialize a transaction */
btrx.serialize = function () {
@ -4793,29 +4984,14 @@
}
buffer = buffer.concat(bitjs.numToBytes(parseInt(this.locktime), 4));
var flohex = ascii_to_hexa(this.floData);
var floDataCount = this.floData.length;
var floDataCountString;
//flochange -- creating unique data character count logic for floData. This string is prefixed before actual floData string in Raw Transaction
if (floDataCount < 16) {
floDataCountString = floDataCount.toString(16);
floDataCountString = "0" + floDataCountString;
} else if (floDataCount < 253) {
floDataCountString = floDataCount.toString(16);
} else if (floDataCount <= 1040) {
let floDataCountAdjusted = (floDataCount - 253) + parseInt("0xfd00fd");
let floDataCountStringAdjusted = floDataCountAdjusted.toString(16);
floDataCountString = floDataCountStringAdjusted.substr(0, 2) + floDataCountStringAdjusted.substr(4, 2) + floDataCountStringAdjusted.substr(2, 2);
} else {
floDataCountString = "Character Limit Exceeded";
}
//flochange -- append floData field
buffer = buffer.concat(bitjs.numToVarInt(this.floData.length));
buffer = buffer.concat(bitjs.strToBytes(this.floData))
return Crypto.util.bytesToHex(buffer) + floDataCountString + flohex; // flochange -- Addition of floDataCountString and floData in serialization
return Crypto.util.bytesToHex(buffer);
}
return btrx;
}
@ -4856,6 +5032,36 @@
else return bytes[0] + 256 * bitjs.bytesToNum(bytes.slice(1));
}
//flochange - adding fn to convert string (for flodata) to byte
bitjs.strToBytes = function (str) {
return str.split('').map(c => c.charCodeAt(0));
}
/* decompress an compressed public key */
bitjs.pubkeydecompress = function (pubkey) {
if ((typeof (pubkey) == 'string') && pubkey.match(/^[a-f0-9]+$/i)) {
var curve = EllipticCurve.getSECCurveByName("secp256k1");
try {
var pt = curve.curve.decodePointHex(pubkey);
var x = pt.getX().toBigInteger();
var y = pt.getY().toBigInteger();
var publicKeyBytes = EllipticCurve.integerToBytes(x, 32);
publicKeyBytes = publicKeyBytes.concat(EllipticCurve.integerToBytes(y, 32));
publicKeyBytes.unshift(0x04);
return Crypto.util.bytesToHex(publicKeyBytes);
} catch (e) {
// console.log(e);
return false;
}
}
return false;
}
bitjs.verifySignature = function (hash, sig, pubkey) {
return Bitcoin.ECDSA.verify(hash, sig, pubkey);
}
/* clone an object */
bitjs.clone = function (obj) {
if (obj == null || typeof (obj) != 'object') return obj;
@ -5020,17 +5226,26 @@
//https://raw.github.com/bitcoinjs/bitcoinjs-lib/09e8c6e184d6501a0c2c59d73ca64db5c0d3eb95/src/address.js
Bitcoin.Address = function (bytes) {
if (GLOBAL.cryptocoin == "FLO")
this.version = 0x23; // FLO mainnet public address
else if (GLOBAL.cryptocoin == "FLO_TEST")
this.version = 0x73; // FLO testnet public address
if ("string" == typeof bytes) {
bytes = Bitcoin.Address.decodeString(bytes, this.version);
var d = Bitcoin.Address.decodeString(bytes);
bytes = d.hash;
if (GLOBAL.cryptocoin == "FLO" && (d.version == Bitcoin.Address.standardVersion || d.version == Bitcoin.Address.multisigVersion))
this.version = d.version;
else if (GLOBAL.cryptocoin == "FLO_TEST" && d.version == Bitcoin.Address.testnetVersion)
this.version = d.version;
else throw "Version (prefix) " + d.version + " not supported!";
} else {
if (GLOBAL.cryptocoin == "FLO")
this.version = Bitcoin.Address.standardVersion;
else if (GLOBAL.cryptocoin == "FLO_TEST")
this.version = Bitcoin.Address.testnetVersion; // FLO testnet public address
}
this.hash = bytes;
};
Bitcoin.Address.networkVersion = 0x23; // (FLO mainnet 0x23, 35D), (Bitcoin Mainnet, 0x00, 0D) // *this has no effect *
Bitcoin.Address.standardVersion = 0x23; // (FLO mainnet 0x23, 35D), (Bitcoin Mainnet, 0x00, 0D)
Bitcoin.Address.multisigVersion = 0x5e; // (FLO multisig 0x5e, 94D)
Bitcoin.Address.testnetVersion = 0x73; // (FLO testnet 0x73, 115D)
/**
* Serialize this object as a standard Bitcoin address.
@ -5059,7 +5274,7 @@
/**
* Parse a Bitcoin address contained in a string.
*/
Bitcoin.Address.decodeString = function (string, version) {
Bitcoin.Address.decodeString = function (string) {
var bytes = Bitcoin.Base58.decode(string);
var hash = bytes.slice(0, 21);
var checksum = Crypto.SHA256(Crypto.SHA256(hash, {
@ -5075,11 +5290,12 @@
throw "Checksum validation failed!";
}
if (version != hash.shift()) {
/*if (version != hash.shift()) {
throw "Version " + hash.shift() + " not supported!";
}
}*/
return hash;
var version = hash.shift();
return { version, hash };
};
//https://raw.github.com/bitcoinjs/bitcoinjs-lib/e90780d3d3b8fc0d027d2bcb38b80479902f223e/src/ecdsa.js
Bitcoin.ECDSA = (function () {