-
-
Save daanporon/28087f042fb83507f5dc9389119a0508 to your computer and use it in GitHub Desktop.
'use strict'; | |
var crypto = require('crypto'), | |
secp256k1 = require('secp256k1'); | |
var OPS = { | |
"OP_PUSHDATA1": 76, | |
"OP_PUSHDATA2": 77, | |
"OP_PUSHDATA4": 78, | |
"OP_DUP": 118, | |
"OP_HASH160": 169, | |
"OP_EQUALVERIFY": 136, | |
"OP_CHECKSIG": 172 | |
}; | |
var signTransaction = function(transaction, publicKey, privateKey) { | |
var ripemd160Hash = ripemd160(sha256(publicKey)); | |
var chunks = []; | |
chunks.push(uint8Buffer(OPS.OP_DUP)); | |
chunks.push(uint8Buffer(OPS.OP_HASH160)); | |
chunks.push(pushDataIntBuffer(ripemd160Hash.length)); | |
chunks.push(ripemd160Hash); | |
chunks.push(uint8Buffer(OPS.OP_EQUALVERIFY)); | |
chunks.push(uint8Buffer(OPS.OP_CHECKSIG)); | |
transaction.vin[0].script = Buffer.concat(chunks); | |
var hashType = 0x01; // SIGHASH_ALL | |
var hashForSignature = hash256(Buffer.concat([toBuffer(transaction), uint32Buffer(hashType)])); | |
console.log('hash for signature', hashForSignature.toString('hex')); | |
var signature = secp256k1.sign(hashForSignature, privateKey).signature; | |
var signatureDER = secp256k1.signatureExport(signature); | |
console.log('signature', signature.toString('hex')); | |
console.log('signature DER', signatureDER.toString('hex')); | |
var scriptSignature = Buffer.concat([signatureDER, uint8Buffer(hashType)]); // public key hash input | |
console.log('script signature', scriptSignature.toString('hex')); | |
var scriptSig = Buffer.concat([pushDataIntBuffer(scriptSignature.length), scriptSignature, pushDataIntBuffer(publicKey.length), publicKey]); | |
console.log('script sig', scriptSig.toString('hex')); | |
transaction.vin[0].script = scriptSig; | |
var signedTransaction = toBuffer(transaction); | |
console.log('signed hex string', signedTransaction.toString('hex')); | |
}; | |
// hexstring: 0100000001a85d392dfbb9c0e3b91de2853d2ece6e42be30cbcd98319a6835b86ae0b08eea0100000000ffffffff0200000000000000003176a914c1fe3d898175f021f827fed320408adebc266a4288ac1673706b710700000008010000274701000000000000007500000000000000003176a91442ecb7a618d3fe7dd2c57d3a1d81a2433a4a4c7c88ac1673706b710700000008010000274762000000000000007500000000 | |
signTransaction({ | |
'txid': '6d0fa76f4862547a74e237af8461cfa8faaa3e17135f10a3643cc22c05ed70f5', | |
'version': 1, | |
'locktime': 0, | |
'vin': [ | |
{ | |
'txid': 'ea8eb0e06ab835689a3198cdcb30be426ece2e3d85e21db9e3c0b9fb2d395da8', | |
'vout': 1, | |
'scriptSig': { | |
'asm': '', | |
'hex': '' | |
}, | |
'sequence': 4294967295 | |
} | |
], | |
'vout': [ | |
{ | |
'value': 0, | |
'n': 0, | |
'scriptPubKey': { | |
'asm': 'OP_DUP OP_HASH160 c1fe3d898175f021f827fed320408adebc266a42 OP_EQUALVERIFY OP_CHECKSIG 73706b71070000000801000027470100000000000000 OP_DROP', | |
'hex': '76a914c1fe3d898175f021f827fed320408adebc266a4288ac1673706b7107000000080100002747010000000000000075', | |
'reqSigs': 1, | |
'type': 'pubkeyhash', | |
'addresses': [ | |
'1TDi2mfj5A73p3ME6x7nCJSuC4tAPajXV6tJBn' | |
] | |
}, | |
'assets': [ | |
{ | |
'name': 'VOTE', | |
'issuetxid': '2747d7ffebcdae0638e9afed48371df16fbdfadc5174f4cef459d36d9354d14a', | |
'assetref': '7-264-18215', | |
'qty': 1, | |
'raw': 1 | |
} | |
], | |
'permissions': [] | |
}, | |
{ | |
'value': 0, | |
'n': 1, | |
'scriptPubKey': { | |
'asm': 'OP_DUP OP_HASH160 42ecb7a618d3fe7dd2c57d3a1d81a2433a4a4c7c OP_EQUALVERIFY OP_CHECKSIG 73706b71070000000801000027476200000000000000 OP_DROP', | |
'hex': '76a91442ecb7a618d3fe7dd2c57d3a1d81a2433a4a4c7c88ac1673706b7107000000080100002747620000000000000075', | |
'reqSigs': 1, | |
'type': 'pubkeyhash', | |
'addresses': [ | |
'1A3d1nw4eCTgaxGnZ6YFZbt4yC7u8TJPPof3dc' | |
] | |
}, | |
'assets': [ | |
{ | |
'name': 'VOTE', | |
'issuetxid': '2747d7ffebcdae0638e9afed48371df16fbdfadc5174f4cef459d36d9354d14a', | |
'assetref': '7-264-18215', | |
'qty': 98, | |
'raw': 98 | |
} | |
], | |
'permissions': [] | |
} | |
], | |
'data': [] | |
}, Buffer.from('02fdcee0919d98a318fb34318ab6645420252eadf5cbfe09f5c4cc07d58f22c628', 'hex'), Buffer.from('825119fcc0c096916d192a8b62f69a78996cc3c7a73fe8665b74b7d7fe6d9a98', 'hex')); | |
// result: | |
// 0100000001a85d392dfbb9c0e3b91de2853d2ece6e42be30cbcd98319a6835b86ae0b08eea010000006b483045022100c424fe2e42e9223ca5ddbfa6dc7fe4964997d16aa53910924aeca0a2330c87e002207710aaeb20845f8d08b78295b05f5c90c3cb96025d893f8c53345d677a1def18012102fdcee0919d98a318fb34318ab6645420252eadf5cbfe09f5c4cc07d58f22c628ffffffff0200000000000000003176a914c1fe3d898175f021f827fed320408adebc266a4288ac1673706b710700000008010000274701000000000000007500000000000000003176a91442ecb7a618d3fe7dd2c57d3a1d81a2433a4a4c7c88ac1673706b710700000008010000274762000000000000007500000000 | |
// result RPC call: | |
// 0100000001a85d392dfbb9c0e3b91de2853d2ece6e42be30cbcd98319a6835b86ae0b08eea010000006a473044022032e6bffb014c0929d1aa0fb2a0eeeb37858ffe9bc44697f9a51fce2b41cb5695022025d72a36fd104d4a8666f3248d06b7456d3acd98be08de913577a6e25ae539e5012102fdcee0919d98a318fb34318ab6645420252eadf5cbfe09f5c4cc07d58f22c628ffffffff0200000000000000003176a914c1fe3d898175f021f827fed320408adebc266a4288ac1673706b710700000008010000274701000000000000007500000000000000003176a91442ecb7a618d3fe7dd2c57d3a1d81a2433a4a4c7c88ac1673706b710700000008010000274762000000000000007500000000 | |
function toBuffer(transaction) { | |
var chunks = []; | |
chunks.push(uint32Buffer(transaction.version)); | |
chunks.push(varIntBuffer(transaction.vin.length)); | |
transaction.vin.forEach(function (txIn) { | |
var hash = [].reverse.call(new Buffer(txIn.txid, 'hex')); | |
chunks.push(hash); | |
chunks.push(uint32Buffer(txIn.vout)); // index | |
if (txIn.script != null) { | |
chunks.push(varIntBuffer(txIn.script.length)); | |
chunks.push(txIn.script); | |
} else { | |
chunks.push(varIntBuffer(0)); | |
} | |
chunks.push(uint32Buffer(txIn.sequence)); | |
}); | |
chunks.push(varIntBuffer(transaction.vout.length)); | |
transaction.vout.forEach(function (txOut) { | |
chunks.push(uint64Buffer(txOut.value)); | |
var script = Buffer.from(txOut.scriptPubKey.hex, 'hex'); | |
chunks.push(varIntBuffer(script.length)); | |
chunks.push(script); | |
}); | |
chunks.push(uint32Buffer(transaction.locktime)); | |
return Buffer.concat(chunks); | |
} | |
function pushDataIntBuffer(number) { | |
var chunks = []; | |
var pushDataSize = number < OPS.OP_PUSHDATA1 ? 1 | |
: number < 0xff ? 2 | |
: number < 0xffff ? 3 | |
: 5; | |
if (pushDataSize === 1) { | |
chunks.push(uint8Buffer(number)); | |
} else if (pushDataSize === 2) { | |
chunks.push(uint8Buffer(OPS.OP_PUSHDATA1)); | |
chunks.push(uint8Buffer(number)); | |
} else if (pushDataSize === 3) { | |
chunks.push(uint8Buffer(OPS.OP_PUSHDATA2)); | |
chunks.push(uint16Buffer(number)); | |
} else { | |
chunks.push(uint8Buffer(OPS.OP_PUSHDATA4)); | |
chunks.push(uint32Buffer(number)); | |
} | |
return Buffer.concat(chunks); | |
} | |
function varIntBuffer(number) { | |
var chunks = []; | |
var size = number < 253 ? 1 | |
: number < 0x10000 ? 3 | |
: number < 0x100000000 ? 5 | |
: 9; | |
// 8 bit | |
if (size === 1) { | |
chunks.push(uint8Buffer(number)); | |
// 16 bit | |
} else if (size === 3) { | |
chunks.push(uint8Buffer(253)); | |
chunks.push(uint16Buffer(number)); | |
// 32 bit | |
} else if (size === 5) { | |
chunks.push(uint8Buffer(254)); | |
chunks.push(uint32Buffer(number)); | |
// 64 bit | |
} else { | |
chunks.push(uint8Buffer(255)); | |
chunks.push(uint64Buffer(number)); | |
} | |
return Buffer.concat(chunks); | |
} | |
function uint8Buffer(number) { | |
var buffer = new Buffer(1); | |
buffer.writeUInt8(number, 0); | |
return buffer; | |
} | |
function uint16Buffer(number) { | |
var buffer = new Buffer(2); | |
buffer.writeUInt16LE(number, 0); | |
return buffer; | |
} | |
function uint32Buffer(number) { | |
var buffer = new Buffer(4); | |
buffer.writeUInt32LE(number, 0); | |
return buffer; | |
} | |
function uint64Buffer(number) { | |
var buffer = new Buffer(8); | |
buffer.writeInt32LE(number & -1, 0); | |
buffer.writeUInt32LE(Math.floor(number / 0x100000000), 4); | |
return buffer; | |
} | |
function hash256 (buffer) { | |
return sha256(sha256(buffer)) | |
} | |
function ripemd160 (buffer) { | |
return crypto.createHash('rmd160').update(buffer).digest() | |
} | |
function sha256 (buffer) { | |
return crypto.createHash('sha256').update(buffer).digest() | |
} |
Hi, daanporon!
Thank you very much for your work!
I'm trying to use the code above in the web browser and the result differs from the result from RPC call.
When I compare results, see difference in signature (signature DES).
I think the problem is in the transmission of private key to the signTransaction function.
I have private key in wif format and use bitcoin-js library for manage it.
All my actions step by step i put below.
Can you explain to me what am I doing wrong?
Thanks.
multichain-cli wallets createrawsendfrom 1D5y7FeVySiwFGaDGypJbB9WNNkUiHgCwW "{"145MCTHfKu8fFzVFvAEkD8cv2DNB4xnybc":{"openAsset":3}}" "[]"
0100000001c35312d70472624d78d3c18b7c2012b49d70e3256ec2eed8bbc45f5ffb37aa860000000000ffffffff0200000000000000003776a91421bae6b4d78b63f5691fb920f0ea1e12dea30e8f88ac1c73706b71be8f53808eea929b60b1c9d647cb6dbc2c010000000000007500000000000000003776a9148492027ada4f94978b86ffe7f1a707bb09d2df1b88ac1c73706b71be8f53808eea929b60b1c9d647cb6dbcc8000000000000007500000000
multichain-cli wallets decoderawtransaction 0100000001c35312d70472624d78d3c18b7c2012b49d70e3256ec2eed8bbc45f5ffb37aa860000000000ffffffff0200000000000000003776a91421bae6b4d78b63f5691fb920f0ea1e12dea30e8f88ac1c73706b71be8f53808eea929b60b1c9d647cb6dbc2c010000000000007500000000000000003776a9148492027ada4f94978b86ffe7f1a707bb09d2df1b88ac1c73706b71be8f53808eea929b60b1c9d647cb6dbcc8000000000000007500000000
{
"txid" : "a07a8c447a08674d9dc4417ec56097144e277327a633f55ce6fbc81b7c59ea51",
"version" : 1,
"locktime" : 0,
"vin" : [
{
"txid" : "86aa37fb5f5fc4bbd8eec26e25e3709db412207c8bc1d3784d627204d71253c3",
"vout" : 0,
"scriptSig" : {
"asm" : "",
"hex" : ""
},
"sequence" : 4294967295
}
],
"vout" : [
{
"value" : 0.00000000,
"n" : 0,
"scriptPubKey" : {
"asm" : "OP_DUP OP_HASH160 21bae6b4d78b63f5691fb920f0ea1e12dea30e8f OP_EQUALVERIFY OP_CHECKSIG 73706b71be8f53808eea929b60b1c9d647cb6dbc2c01000000000000 OP_DROP",
"hex" : "76a91421bae6b4d78b63f5691fb920f0ea1e12dea30e8f88ac1c73706b71be8f53808eea929b60b1c9d647cb6dbc2c0100000000000075",
"reqSigs" : 1,
"type" : "pubkeyhash",
"addresses" : [
"145MCTHfKu8fFzVFvAEkD8cv2DNB4xnybc"
]
},
"assets" : [
{
"name" : "openAsset",
"issuetxid" : "bc6dcb47d6c9b1609b92ea8e80538fbe47664a8dd9b8f041c63ac37206030b5f",
"assetref" : "60-265-28092",
"qty" : 3.00000000,
"raw" : 300,
"type" : "transfer"
}
],
"permissions" : [
],
"items" : [
]
},
{
"value" : 0.00000000,
"n" : 1,
"scriptPubKey" : {
"asm" : "OP_DUP OP_HASH160 8492027ada4f94978b86ffe7f1a707bb09d2df1b OP_EQUALVERIFY OP_CHECKSIG 73706b71be8f53808eea929b60b1c9d647cb6dbcc800000000000000 OP_DROP",
"hex" : "76a9148492027ada4f94978b86ffe7f1a707bb09d2df1b88ac1c73706b71be8f53808eea929b60b1c9d647cb6dbcc80000000000000075",
"reqSigs" : 1,
"type" : "pubkeyhash",
"addresses" : [
"1D5y7FeVySiwFGaDGypJbB9WNNkUiHgCwW"
]
},
"assets" : [
{
"name" : "openAsset",
"issuetxid" : "bc6dcb47d6c9b1609b92ea8e80538fbe47664a8dd9b8f041c63ac37206030b5f",
"assetref" : "60-265-28092",
"qty" : 2.00000000,
"raw" : 200,
"type" : "transfer"
}
],
"permissions" : [
],
"items" : [
]
}
],
"data" : [
]
}
multichain-cli wallets signrawtransaction 0100000001c35312d70472624d78d3c18b7c2012b49d70e3256ec2eed8bbc45f5ffb37aa860000000000ffffffff0200000000000000003776a91421bae6b4d78b63f5691fb920f0ea1e12dea30e8f88ac1c73706b71be8f53808eea929b60b1c9d647cb6dbc2c010000000000007500000000000000003776a9148492027ada4f94978b86ffe7f1a707bb09d2df1b88ac1c73706b71be8f53808eea929b60b1c9d647cb6dbcc8000000000000007500000000 "[]" "["KyTmUpr9JoeftiXKz6DoPUEk7g3ePPCeXU7pXLtFGEzbcrFj6SGy"]"
{
"hex" : "0100000001c35312d70472624d78d3c18b7c2012b49d70e3256ec2eed8bbc45f5ffb37aa86000000006a473044022074fe405664f9b7481d9203c7ac0a0d3a862aa7d388b1a47161478602e0fc6ea2022061a9da26cf8273a629506182b1dfac5cd99d0d6c7a93d29557ac5fddda699798012102d13bdbb64759c69c71e5fb3a7794209ef44b6cffb56904121e24747476dc985cffffffff0200000000000000003776a91421bae6b4d78b63f5691fb920f0ea1e12dea30e8f88ac1c73706b71be8f53808eea929b60b1c9d647cb6dbc2c010000000000007500000000000000003776a9148492027ada4f94978b86ffe7f1a707bb09d2df1b88ac1c73706b71be8f53808eea929b60b1c9d647cb6dbcc8000000000000007500000000",
"complete" : true
}
from sign.js
var keyPair = bitcoin.ECPair.fromWIF("KyTmUpr9JoeftiXKz6DoPUEk7g3ePPCeXU7pXLtFGEzbcrFj6SGy", bitcoin.networks.bitcoin);
signTransaction(jsonTransaction, Buffer.from(keyPair.getPublicKeyBuffer().toString('hex'), 'hex'), Buffer.from(keyPair.d.toHex(), 'hex'));
0100000001c35312d70472624d78d3c18b7c2012b49d70e3256ec2eed8bbc45f5ffb37aa86000000006a4730440220215ddd43ac8915fa3d6e93956161c0d7997a12a292ee80e2a4b14e0cd635f5c402207a9cab5c8f010b262929aea3f0f1e91a091ed2e893c6a2214ebd55fe06ea84d9012102d13bdbb64759c69c71e5fb3a7794209ef44b6cffb56904121e24747476dc985cffffffff0200000000000000003776a91421bae6b4d78b63f5691fb920f0ea1e12dea30e8f88ac1c73706b71be8f53808eea929b60b1c9d647cb6dbc2c010000000000007500000000000000003776a9148492027ada4f94978b86ffe7f1a707bb09d2df1b88ac1c73706b71be8f53808eea929b60b1c9d647cb6dbcc8000000000000007500000000
Hi, i am using your function but i get a error after sendrawtransaction
error code: -26
error message:
16: ConnectInputs failed: mandatory-script-verify-flag-failed (Script failed an OP_CHECKSIGVERIFY operation)
Hi Daan,
Would you happen to know how to modify your function to sign multiple inputs? I assumed the approach would be similar to https://bitcoin.stackexchange.com/questions/41209/how-to-sign-a-transaction-with-multiple-inputs, where I just need to clear the inputs for each other vin to generate the hash to be signed for each input:
` var signTransaction = function(transaction, publicKey, privateKey) {
var ripemd160Hash = ripemd160(sha256(publicKey));
var chunks = [];
chunks.push(uint8Buffer(OPS.OP_DUP));
chunks.push(uint8Buffer(OPS.OP_HASH160));
chunks.push(pushDataIntBuffer(ripemd160Hash.length));
chunks.push(ripemd160Hash);
chunks.push(uint8Buffer(OPS.OP_EQUALVERIFY));
chunks.push(uint8Buffer(OPS.OP_CHECKSIG));
let newScripts = [];
for (let index = 0, size=transaction.vin.length; index<size; index++) {
for (let i = 0; i<size; i++) {
delete transaction.vin[i].script;
}
transaction.vin[index].script = Buffer.concat(chunks);
console.log("checking..." + index);
for (let index2 = 0; index2<size; index2++) {
console.log('script ' + index2, transaction.vin[index2].script);
}
var hashType = 0x01; // SIGHASH_ALL
var hashForSignature = hash256(Buffer.concat([toBuffer(transaction), uint32Buffer(hashType)]));
console.log('hash for signature', hashForSignature.toString('hex'));
var signature = secp256k1.sign(hashForSignature, privateKey).signature;
var signatureDER = secp256k1.signatureExport(signature);
console.log('signature', signature.toString('hex'));
console.log('signature DER', signatureDER.toString('hex'));
var scriptSignature = Buffer.concat([signatureDER, uint8Buffer(hashType)]); // public key hash input
console.log('script signature', scriptSignature.toString('hex'));
var scriptSig = Buffer.concat([pushDataIntBuffer(scriptSignature.length), scriptSignature, pushDataIntBuffer(publicKey.length), publicKey]);
console.log('script sig', scriptSig.toString('hex'));
newScripts[index] = scriptSig;
}
for (let index = 0, size=transaction.vin.length; index<size; index++) {
transaction.vin[index].script = newScripts[index];
}
console.log(transaction);
var signedTransaction = toBuffer(transaction);
console.log('signed transaction', signedTransaction);
console.log('signed hex string', signedTransaction.toString('hex'));
return signedTransaction.toString('hex');
};
`
But when I tested this with a transaction where I had to sign two inputs (using the same private key), I found that the generated signature for the first vin is incorrect, but the second vin's is correct (as compared to when signing using multichain's signrawtransaction
). So I figure I'm at least on the right track and maybe just missing a step or two somewhere, would you have any advice?
result:
0100000001a85d392dfbb9c0e3b91de2853d2ece6e42be30cbcd98319a6835b86ae0b08eea010000006b483045022100c424fe2e42e9223ca5ddbfa6dc7fe4964997d16aa53910924aeca0a2330c87e002207710aaeb20845f8d08b78295b05f5c90c3cb96025d893f8c53345d677a1def18012102fdcee0919d98a318fb34318ab6645420252eadf5cbfe09f5c4cc07d58f22c628ffffffff0200000000000000003176a914c1fe3d898175f021f827fed320408adebc266a4288ac1673706b710700000008010000274701000000000000007500000000000000003176a91442ecb7a618d3fe7dd2c57d3a1d81a2433a4a4c7c88ac1673706b710700000008010000274762000000000000007500000000
result from RPC call:
0100000001a85d392dfbb9c0e3b91de2853d2ece6e42be30cbcd98319a6835b86ae0b08eea010000006a473044022032e6bffb014c0929d1aa0fb2a0eeeb37858ffe9bc44697f9a51fce2b41cb5695022025d72a36fd104d4a8666f3248d06b7456d3acd98be08de913577a6e25ae539e5012102fdcee0919d98a318fb34318ab6645420252eadf5cbfe09f5c4cc07d58f22c628ffffffff0200000000000000003176a914c1fe3d898175f021f827fed320408adebc266a4288ac1673706b710700000008010000274701000000000000007500000000000000003176a91442ecb7a618d3fe7dd2c57d3a1d81a2433a4a4c7c88ac1673706b710700000008010000274762000000000000007500000000