Drop in BTCZJS refinements

This commit is contained in:
Jon Layton 2018-03-18 23:13:20 -05:00
parent 70bed2f5e3
commit 0185517d67
3 changed files with 98 additions and 196 deletions

View File

@ -1,15 +1,12 @@
'use strict';
var bs58check = require('bs58check');
var secp256k1 = require('secp256k1');
var zbufferutils = require('./bufferutils');
var bigi = require('bigi');
var zcrypto = require('./crypto');
var zopcodes = require('./opcodes');
var zconfig = require('./config');
/*
* Makes a private key
* @param {String phrase (Password phrase)
* @param {String} phrase (Password phrase)
* @return {Sting} Private key
*/
function mkPrivKey(phrase) {
@ -20,12 +17,12 @@ function mkPrivKey(phrase) {
* Converts a private key to WIF format
* @param {String} privKey (private key)
* @param {boolean} toCompressed (Convert to WIF compressed key or nah)
* @param {String} wif (wif hashing bytes (default: 0x80))
* @param {string} wif (wif hashing bytes (default: 0x80))
* @return {Sting} WIF format (uncompressed)
*/
function privKeyToWIF(privKey) {
var toCompressed = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
var wif = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : zconfig.mainnet.wif;
function privKeyToWIF(privKey, toCompressed, wif) {
toCompressed = toCompressed || false;
wif = wif || zconfig.mainnet.wif;
if (toCompressed) privKey = privKey + '01';
@ -38,10 +35,10 @@ function privKeyToWIF(privKey) {
* @param {boolean} toCompressed (Convert to public key compressed key or nah)
* @return {Sting} Public Key (default: uncompressed)
*/
function privKeyToPubKey(privKey) {
var toCompressed = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
function privKeyToPubKey(privKey, toCompressed) {
toCompressed = toCompressed || false;
var pkBuffer = Buffer.from(privKey, 'hex');
const pkBuffer = Buffer.from(privKey, 'hex');
var publicKey = secp256k1.publicKeyCreate(pkBuffer, toCompressed);
return publicKey.toString('hex');
}
@ -64,19 +61,20 @@ function WIFToPrivKey(wifPk) {
}
/*
* Converts public key to zencash address
* Converts public key to btcp address
* @param {String} pubKey (public key)
* @param {String} pubKeyHash (public key hash (optional, else use defaul))
* @return {String} zencash address
* @return {Sting} btcp address
*/
function pubKeyToAddr(pubKey) {
var pubKeyHash = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : zconfig.mainnet.pubKeyHash;
function pubKeyToAddr(pubKey, pubKeyHash) {
pubKeyHash = pubKeyHash || zconfig.mainnet.pubKeyHash;
var hash160 = zcrypto.hash160(Buffer.from(pubKey, 'hex'));
const hash160 = zcrypto.hash160(Buffer.from(pubKey, 'hex'));
return bs58check.encode(Buffer.from(pubKeyHash + hash160, 'hex')).toString('hex');
}
/*
/* (Not in use)
* Given a list of public keys, create a M-of-N redeemscript
* @param {[String]} pubKey (array of public keys, NOT ADDRESS)
* @param {Int} M [2 or 3] in M-of-N multisig
@ -91,11 +89,11 @@ function mkMultiSigRedeemScript(pubKeys, M, N) {
var OP_END = (OP_1.readInt8(0) + (N - 1)).toString(16);
return OP_START + pubKeys.map(function (x) {
return zbufferutils.getPushDataLength(x) + x;
return zbufferutils.getStringBufferLength(x) + x;
}).join('') + OP_END + zopcodes.OP_CHECKMULTISIG;
}
/*
/* (Not in use)
* Reference: http://www.soroushjp.com/2014/12/20/bitcoin-multisig-the-hard-way-understanding-raw-multisignature-bitcoin-transactions/
* Given the multi sig redeem script, return the corresponding address
* @param {String} RedeemScript (redeem script)
@ -118,4 +116,4 @@ module.exports = {
WIFToPrivKey: WIFToPrivKey,
mkMultiSigRedeemScript: mkMultiSigRedeemScript,
multiSigRSToAddress: multiSigRSToAddress
};
};

View File

@ -1,14 +1,12 @@
'use strict';
var varuint = require('varuint-bitcoin');
// https://github.com/bitcoinjs/bitcoinjs-lib/issues/14
function numToBytes(num, bytes) {
if (bytes === 0) return [];else return [num % 256].concat(numToBytes(Math.floor(num / 256), bytes - 1));
if (bytes == 0) return [];else return [num % 256].concat(numToBytes(Math.floor(num / 256), bytes - 1));
}
function numToVarInt(num) {
return varuint.encode(num).toString('hex');
var b;
if (num < 253) b = [num];else if (num < 65536) b = [253].concat(numToBytes(num, 2));else if (num < 4294967296) b = [254].concat(numToBytes(num, 4));else b = [253].concat(numToBytes(num, 8));
return Buffer.from(b).toString('hex');
}
// https://github.com/feross/buffer/blob/master/index.js#L1127
@ -50,16 +48,15 @@ function writeUInt64LE(buffer, value, offset) {
* @param {String} hexStr
* return {String} Length of hexStr in bytes
*/
function getPushDataLength(s) {
// https://en.bitcoin.it/wiki/Protocol_documentation#Variable_length_integer
var hexLength = Buffer.from(s, 'hex').length;
return numToVarInt(hexLength);
function getStringBufferLength(hexStr) {
const _tmpBuf = Buffer.from(hexStr, 'hex').length;
return Buffer.from([_tmpBuf]).toString('hex');
}
module.exports = {
readUInt64LE: readUInt64LE,
writeUInt64LE: writeUInt64LE,
getPushDataLength: getPushDataLength,
getStringBufferLength: getStringBufferLength,
numToVarInt: numToVarInt,
numToBytes: numToBytes
};
};

View File

@ -1,9 +1,6 @@
'use strict';
var bs58check = require('bs58check');
var elliptic = require('elliptic');
var secp256k1 = new elliptic.ec('secp256k1'); /* eslint new-cap: ["error", { "newIsCap": false }] */
var secp256k1 = require('secp256k1');
var int64buffer = require('int64-buffer');
var varuint = require('varuint-bitcoin');
var zconfig = require('./config');
var zbufferutils = require('./bufferutils');
@ -11,38 +8,39 @@ var zcrypto = require('./crypto');
var zconstants = require('./constants');
var zaddress = require('./address');
var zopcodes = require('./opcodes');
var zbufferutils = require('./bufferutils');
/*
* Given an address, generates a pubkeyhash replay type script needed for the transaction
/*
* Given an address, generates a pubkeyhash script needed for the transaction
* @param {String} address
* @param {String} pubKeyHash (optional)
* return {String} pubKeyScript
*/
function mkPubkeyHashReplayScript(address) {
// Prefix
var pubKeyHash = zconfig.mainnet.pubKeyHash;
function mkPubkeyHashReplayScript(address, pubKeyHash) {
pubKeyHash = pubKeyHash || zconfig.mainnet.pubKeyHash;
var addrHex = bs58check.decode(address).toString('hex');
// Cut out pubKeyHash
// '14' is the length of the subAddrHex (in bytes)
var subAddrHex = addrHex.substring(pubKeyHash.length, addrHex.length);
return zopcodes.OP_DUP + zopcodes.OP_HASH160 + zbufferutils.getPushDataLength(subAddrHex) + subAddrHex + zopcodes.OP_EQUALVERIFY + zopcodes.OP_CHECKSIG;
// Minimal encoding
// '14' is the length of the subAddrHex (in bytes)
return zopcodes.OP_DUP + zopcodes.OP_HASH160 + zbufferutils.getStringBufferLength(subAddrHex) + subAddrHex + zopcodes.OP_EQUALVERIFY + zopcodes.OP_CHECKSIG;
}
/*
* Given an address, generates a script hash replay type script needed for the transaction
* Given an address, generates a script hash script needed for the transaction
* @param {String} address
* return {String} scriptHash script
*/
function mkScriptHashReplayScript(address) {
var addrHex = bs58check.decode(address).toString('hex');
var subAddrHex = addrHex.substring(4, addrHex.length); // Cut out the '00' (we also only want 14 bytes instead of 16)
// Cut out the '00' (we also only want 14 bytes instead of 16)
var subAddrHex = addrHex.substring(4, addrHex.length);
// '14' is the length of the subAddrHex (in bytes)
return zopcodes.OP_DUP + zopcodes.OP_HASH160 + zbufferutils.getStringBufferLength(subAddrHex) + subAddrHex + zopcodes.OP_EQUAL;
return zopcodes.OP_HASH160 + zbufferutils.getPushDataLength(subAddrHex) + subAddrHex + zopcodes.OP_EQUAL;
}
/*
@ -51,12 +49,10 @@ function mkScriptHashReplayScript(address) {
* return {String} output script
*/
function addressToScript(address) {
// P2SH (with replay protection)
if (address[1] === 'x') {
return mkScriptHashReplayScript(address);
}
// P2PKH (with replay protection)
return mkPubkeyHashReplayScript(address);
}
@ -69,34 +65,30 @@ function addressToScript(address) {
* return {String} output script
*/
function signatureForm(txObj, i, script, hashcode) {
console.log('Hashcode', hashcode);
console.log('signatureForm');
console.log(hashcode);
console.log(script);
// Copy object so we don't rewrite it
var newTx = JSON.parse(JSON.stringify(txObj));
// Only sign the specified index
for (var j = 0; j < newTx.ins.length; j++) {
newTx.ins[j].script = '';
}
newTx.ins[i].script = script;
/*
if (hashcode === zconstants.SIGHASH_NONE) {
newTx.outs = [];
} else if (hashcode === zconstants.SIGHASH_SINGLE) {
newTx.outs = newTx.outs.slice(0, newTx.ins.length);
for (var _j = 0; _j < newTx.ins.length - 1; ++_j) {
newTx.outs[_j].satoshis = Math.pow(2, 64) - 1;
newTx.outs[_j].script = '';
for (var j = 0; j < newTx.ins.length - 1; ++j) {
newTx.outs[j].satoshis = Math.pow(2, 64) - 1;
newTx.outs[j].script = '';
}
} else if (hashcode === zconstants.SIGHASH_ANYONECANPAY) {
newTx.ins = [newTx.ins[i]];
} else { //TODO temp
newTx.ins = [newTx.ins[i]];
}
TODO these | SIGHASH_FORKID
*/
newTx.ins = [newTx.ins[i]];
return newTx;
}
@ -107,13 +99,13 @@ function signatureForm(txObj, i, script, hashcode) {
* @return {Object} txOBJ
*/
function deserializeTx(hexStr) {
var buf = Buffer.from(hexStr, 'hex');
const buf = Buffer.from(hexStr, 'hex');
var offset = 0;
// Out txobj
var txObj = { version: 0, locktime: 0, ins: [], outs: []
// Version
// Version
};txObj.version = buf.readUInt32LE(offset);
offset += 4;
@ -121,20 +113,19 @@ function deserializeTx(hexStr) {
var vinLen = varuint.decode(buf, offset);
offset += varuint.decode.bytes;
for (var i = 0; i < vinLen; i++) {
// Else its
var hash = buf.slice(offset, offset + 32);
const hash = buf.slice(offset, offset + 32);
offset += 32;
var vout = buf.readUInt32LE(offset);
const vout = buf.readUInt32LE(offset);
offset += 4;
var scriptLen = varuint.decode(buf, offset);
const scriptLen = varuint.decode(buf, offset);
offset += varuint.decode.bytes;
var script = buf.slice(offset, offset + scriptLen);
const script = buf.slice(offset, offset + scriptLen);
offset += scriptLen;
var sequence = buf.slice(offset, offset + 4).toString('hex');
const sequence = buf.slice(offset, offset + 4).toString('hex');
offset += 4;
txObj.ins.push({
@ -148,19 +139,19 @@ function deserializeTx(hexStr) {
// Vouts
var voutLen = varuint.decode(buf, offset);
offset += varuint.decode.bytes;
for (var _i = 0; _i < voutLen; _i++) {
var satoshis = zbufferutils.readUInt64LE(buf, offset);
for (var i = 0; i < voutLen; i++) {
const satoshis = zbufferutils.readUInt64LE(buf, offset);
offset += 8;
var _scriptLen = varuint.decode(buf, offset);
const scriptLen = varuint.decode(buf, offset);
offset += varuint.decode.bytes;
var _script = buf.slice(offset, offset + _scriptLen);
offset += _scriptLen;
const script = buf.slice(offset, offset + scriptLen);
offset += scriptLen;
txObj.outs.push({
satoshis: satoshis,
script: _script.toString('hex')
script: script.toString('hex')
});
}
@ -192,9 +183,8 @@ function serializeTx(txObj) {
serializedTx += Buffer.from(i.output.hash, 'hex').reverse().toString('hex');
serializedTx += _buf16.toString('hex');
// Script Signature
// Doesn't work for length > 253 ....
serializedTx += zbufferutils.getPushDataLength(i.script);
// Script
serializedTx += zbufferutils.getStringBufferLength(i.script);
serializedTx += i.script;
// Sequence
@ -209,13 +199,11 @@ function serializeTx(txObj) {
// https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/src/bufferutils.js#L25
var _buf32 = Buffer.alloc(8);
// Satoshis
_buf32.writeInt32LE(o.satoshis & -1, 0);
_buf32.writeUInt32LE(Math.floor(o.satoshis / 0x100000000), 4);
// ScriptPubKey
serializedTx += _buf32.toString('hex');
serializedTx += zbufferutils.getPushDataLength(o.script);
serializedTx += zbufferutils.getStringBufferLength(o.script);
serializedTx += o.script;
});
@ -230,7 +218,8 @@ function serializeTx(txObj) {
* Creates a raw transaction
* @param {[HISTORY]} history type, array of transaction history
* @param {[RECIPIENTS]} recipient type, array of address on where to send coins to
* @return {TXOBJ} Transaction Object (see TXOBJ type for info about structure)
* @param {Number} blockHeight (latest - 300)
* @return {TXOBJ} Transction Object (see TXOBJ type for info about structure)
*/
function createRawTx(history, recipients) {
var txObj = { locktime: 0, version: 1, ins: [], outs: [] };
@ -253,132 +242,53 @@ function createRawTx(history, recipients) {
return txObj;
}
/*
* Gets signature for the vin script
* @params {string} privKey private key
* @params {TXOBJ} signingTx a txobj whereby all the vin script's field are empty except for the one that needs to be signed
* @params {number} hashcode
*/
function getScriptSignature(privKey, signingTx, hashcode) {
const BTCP_FORKID = 42;
// Buffers
var _buf16H = Buffer.alloc(4);
_buf16H.writeUInt16LE(hashcode | (BTCP_FORKID << 8), 0);
var signingTxHex = serializeTx(signingTx);
var signingTxWithHashcode = signingTxHex + _buf16H.toString('hex');
// Sha256 it twice, according to spec
var msg = zcrypto.sha256x2(Buffer.from(signingTxWithHashcode, 'hex'));
// Signing it
var rawsig = secp256k1.sign(Buffer.from(msg, 'hex'), Buffer.from(privKey, 'hex'), { canonical: true });
// SCRIPT_VERIFY_DERSIG is always enforced
// Convert it to DER format
// Appending 41 to it
// Would normally be 01, but OR'd against FORKID
// ScriptSig = <varint of total sig length> <SIG from code, including appended 01 SIGNHASH> <length of pubkey (0x21 or 0x41)> <pubkey>
// https://bitcoin.stackexchange.com/a/36481
var signatureDER = Buffer.from(rawsig.toDER()).toString('hex') + '41';
return signatureDER;
}
/*
* Signs the raw transaction
* @param {String} rawTx raw transaction
* @param {Int} i
* @param {privKey} privKey (not WIF format)
* @param {compressPubKey} compress public key before appending to scriptSig? (default false)
* @param {hashcode} hashtype (default SIGHASH_ALL|SIGHASH_FORKID)
* @param {hashcode} hashcode (default SIGHASH_ALL)
* return {String} signed transaction
*/
function signTx(_txObj, i, privKey) {
var compressPubKey = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
var hashcode = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : zconstants.SIGHASH_ALL | zconstants.SIGHASH_FORKID;
function signTx(_txObj, i, privKey, compressPubKey, hashcode) {
hashcode = hashcode || (zconstants.SIGHASH_ALL | zconstants.SIGHASH_FORKID);
compressPubKey = compressPubKey || false;
// Make a copy
var txObj = JSON.parse(JSON.stringify(_txObj));
var forkHashcode = hashcode | (42 << 8); // BTCP_FORKID = 42
// Buffer
var _buf16 = Buffer.alloc(4);
_buf16.writeUInt16LE(forkHashcode, 0);
// Prepare signing
const script = txObj.ins[i].prevScriptPubKey;
// Prepare our signature
// Get script from the current tx input
var script = txObj.ins[i].prevScriptPubKey;
const signingTx = signatureForm(txObj, i, script, forkHashcode);
const signingTxHex = serializeTx(signingTx);
const signingTxWithHashcode = signingTxHex + _buf16.toString('hex');
// Populate current tx in with the prevScriptPubKey
var signingTx = signatureForm(txObj, i, script, hashcode);
// Sha256 it twice, according to spec
const msg = zcrypto.sha256x2(Buffer.from(signingTxWithHashcode, 'hex'));
// Get script signature
var scriptSig = getScriptSignature(privKey, signingTx, hashcode);
// Signing it
const rawsig = secp256k1.sign(Buffer.from(msg, 'hex'), Buffer.from(privKey, 'hex')).signature;
// Convert it to DER format
// Appending 41 (instead of 01)
// ScriptSig = <varint of total sig length> <SIG from code, including appended 01 SIGNHASH> <length of pubkey (0x21 or 0x41)> <pubkey>
// https://bitcoin.stackexchange.com/a/36481
const signatureDER = secp256k1.signatureExport(rawsig).toString('hex') + '41';
// Chuck it back into txObj and add pubkey
// Protocol:
// PUSHDATA
// signature data and SIGHASH_ALL|SIGHASH_FORKID
// PUSHDATA
// public key data
var compress = false; //TODO does this need to be true?
var pubKey = zaddress.privKeyToPubKey(privKey, compress);
// WHAT? If it fails, uncompress/compress it and it should work...
const pubKey = zaddress.privKeyToPubKey(privKey, compressPubKey);
txObj.ins[i].script = zbufferutils.getPushDataLength(scriptSig) + scriptSig + zbufferutils.getPushDataLength(pubKey) + pubKey;
return txObj;
}
/*
* Gets signatures needed for multi-sign tx
* @param {String} _txObj transaction object you wanna sign
* @param {Int} index fof tx.in to sign
* @param {privKey} An M private key (NOT WIF format!!!)
* @param {string} redeemScript (redeemScript of the multi-sig)
* @param {string} hashcode (SIGHASH_ALL, SIGHASH_NONE, etc | SIGHASH_FORKID)
* return {String} signature
*/
function multiSign(_txObj, i, privKey, redeemScript) {
var hashcode = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : zconstants.SIGHASH_ALL | zconstants.SIGHASH_FORKID;
// Make a copy
var txObj = JSON.parse(JSON.stringify(_txObj));
// Populate current tx.ins[i] with the redeemScript
var signingTx = signatureForm(txObj, i, redeemScript, hashcode);
return getScriptSignature(privKey, signingTx, hashcode);
}
/*
* Applies the signatures to the transaction object
* NOTE: You NEED to supply the signatures in order.
* E.g. You made sigAddr1 with priv1, priv3, priv2
* You can provide signatures of (priv1, priv2) (priv3, priv2) ...
* But not (priv2, priv1)
* @param {String} _txObj transaction object you wanna sign
* @param {Int} index fof tx.in to sign
* @param {[string]} signatures obtained from multiSign
* @param {string} redeemScript (redeemScript of the multi-sig)
* @param {string} hashcode (SIGHASH_ALL, SIGHASH_NONE, etc)
* return {String} signature
*/
function applyMultiSignatures(_txObj, i, signatures, redeemScript) {
// Make a copy
var txObj = JSON.parse(JSON.stringify(_txObj));
var redeemScriptPushDataLength = zbufferutils.getPushDataLength(redeemScript);
// Lmao no idea, just following the source code
if (redeemScriptPushDataLength.length > 2) {
if (redeemScriptPushDataLength.length === 6) {
redeemScriptPushDataLength = redeemScriptPushDataLength.slice(2, 4);
}
}
// http://www.soroushjp.com/2014/12/20/bitcoin-multisig-the-hard-way-understanding-raw-multisignature-bitcoin-transactions/
txObj.ins[i].script = zopcodes.OP_0 + signatures.map(function (x) {
return zbufferutils.getPushDataLength(x) + x;
}).join('') + zopcodes.OP_PUSHDATA1 + redeemScriptPushDataLength + redeemScript;
txObj.ins[i].script = zbufferutils.getStringBufferLength(signatureDER) + signatureDER + zbufferutils.getStringBufferLength(pubKey) + pubKey;
return txObj;
}
@ -391,8 +301,5 @@ module.exports = {
signatureForm: signatureForm,
serializeTx: serializeTx,
deserializeTx: deserializeTx,
signTx: signTx,
multiSign: multiSign,
applyMultiSignatures: applyMultiSignatures,
getScriptSignature: getScriptSignature
signTx: signTx
};