bitcoinjs/bitcoinjs-lib

Sign message got different result between with .sign() and wallet signMessage( )

peter-jim opened this issue · 11 comments

I was looking forward using wallet sign transaction but I got different

here is some part of bitcoinjs-lib code

const secret = Buffer.from('123', 'utf-8'); 
const secretHash = bitcoin.crypto.sha256(secret);//  secretHash is a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3
const signatureHash = redeemTx.hashForSignature(0, lockingScript, hashType); 
const signature =  bitcoin.script.signature.encode(alice.sign(secretHash), hashType); //result is MEUCIQCiNSGUJRzpm8mKh3vAGwnTd/oOqaVnvJqEPrdYXcmrlgIga3U0aLwPNANSubaMtXs605CQyJ3aQHi+swgnOhEnysgB
console.log('alice sign hash base64 is ',alice.sign(secretHash).toString('base64')) // result is ojUhlCUc6ZvJiod7wBsJ03f6DqmlZ7yahD63WF3Jq5ZrdTRovA80A1K5toy1ezrTkJDIndpAeL6zCCc6ESfKyA==
console.log("signature base64",    signature.toString('base64')) 

If I using wallet to sign such as okxwallet or unisat wallet , I got both same result ,which is this IFFMjSOZQFX8sxk0XZ36ckZsD1zaoSn2+EAZkTCrfCmKQUIneGKXc9GC9bp5eQjFpI8b9ASXxvjZvzmVxl0+gAY= and my input is the secretHash a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3 , it using ecdsa to sign and same private key

Just from a quick glance it looks like those wallets are using the old Bitcoin Core signmessage method.

We have a library for that.

https://github.com/bitcoinjs/bitcoinjs-message

thanks , I got the same result now. but I had another question. I want to use this signature to redeem a script(HTLC) ,If I use old version I can spend ,but if I use this I cant spend ,

//
const psbts = bitcoin.Psbt.fromHex('70736274ff01005502000000016f8a0e9aef2399e90c2040443cb5e5f82bd2b3177263aeb1ef2fb5e5969518860000000000ffffffff0158020000000000001976a914b163d850125f1b65eadbcf15f88b7ef16836c1ee88ac00000000000100df0200000001a5fcb8900be46f9b321d286a51065db33be92e5b6df92e46129193c8be6101bd010000006a47304402203ebcf5f9407077196ced5d19925529be3bb3aea4926697786e0d8fff6eae960602207e0be4d60ddc2d2e4fade7bde7e5a0a5ab7567c963515d759f1d238ec40521760121032cf9dd2b7cf826a8d0e176ae0127e1e32b7e33dc55d78754c5f60bfd8f1173b8ffffffff02e80300000000000017a914d20cde47c6c181596b04c0a1d2a5a86c9b2c8cbe8729270000000000001976a914b163d850125f1b65eadbcf15f88b7ef16836c1ee88ac000000002202032cf9dd2b7cf826a8d0e176ae0127e1e32b7e33dc55d78754c5f60bfd8f1173b8473044022026c6604b0dfb9e0995b59b3c56956e7b7ea8685534c77466e672c143d547f87e022012fed14b3c954d6b3ab6c57130dc94d5020e8e57560a5cc1ca01c19d015fc8780101045963a820a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae38876a914b163d850125f1b65eadbcf15f88b7ef16836c1ee6700b27576a914b163d850125f1b65eadbcf15f88b7ef16836c1ee6888ac0000')
console.log('psbt',psbts)

var privateKey = alice.privateKey
var message = 'a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3'
var signatures = bitcoinMessage.sign(message, privateKey, alice.compressed)
console.log(signatures.toString('base64'))


const hashType = bitcoin.Transaction.SIGHASH_ALL;
// const signatureHash = redeemTx.hashForSignature(0, lockingScript, hashType); 
// const signature =  bitcoin.script.signature.encode(alice.sign(signatureHash), hashType);

const getFinalScripts = () => {
   
    const paymentFirstBranch = bitcoin.payments.p2sh({
      redeem: {
        input: bitcoin.script.compile([
            signatures,
            alice.publicKey,
            secret,
            bitcoin.opcodes.OP_TRUE,
        ]),
        output: lockingScript
      }
    })
  return {
    finalScriptSig: paymentFirstBranch.input
  }
}

psbts.finalizeInput(0,getFinalScripts)

I got error with Error: mandatory-script-verify-flag-failed (Non-canonical DER signature)

I don't understand.

Why are you trying to sign a transaction with a message signing protocol?

Please take about 10 steps back and tell me what you're trying to accomplish here.

thanks for your help. I want to using bitcoinjs-lib and wallet to create a atomic swap project. So the Hash time lock is locking in P2SH script. If I want to redeem a script of HTLC , I need wallet account to sign a signature,which need using psbt to sign

var privateKey = alice.privateKey
var message = 'a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3'
var signatures = bitcoinMessage.sign(message, privateKey, alice.compressed)
console.log(signatures.toString('base64')) // signature is IFFMjSOZQFX8sxk0XZ36ckZsD1zaoSn2+EAZkTCrfCmKQUIneGKXc9GC9bp5eQjFpI8b9ASXxvjZvzmVxl0+gAY=

above is your give me method . If I use new bitcoin.Transaction(TESTNET); method it offer hashForSignature function it easy to redeem and spend utxo. But I don't know how to using psbt .

const signatureHash = redeemTx.hashForSignature(0, lockingScript, hashType); 
const signature =  bitcoin.script.signature.encode(alice.sign(signatureHash), hashType);

on HTLC redeem script I need the signatures of spender and publickey and secret. here my signatures is using alice publickey hash a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3 as meesage to sign

const getFinalScripts = () => {
    const paymentFirstBranch = bitcoin.payments.p2sh({
      redeem: {
        input: bitcoin.script.compile([
            signatures,
            alice.publicKey,
            secret,
            bitcoin.opcodes.OP_TRUE,
        ]),
        output: lockingScript
      }
    })
  return {
    finalScriptSig: paymentFirstBranch.input
  }
}

and then, processing with

const validator = (
    pubkey,
    msghash,
    signature,
  ) => ECPair.fromPublicKey(pubkey).verify(msghash, signature);

// psbts.finalizeInput(0,getFinalScripts)
// psbt.updateInput(0,getFinalScripts)
psbt.signInput(0,alice);
psbt.validateSignaturesOfInput(0, validator);
psbt.finalizeInput(0,getFinalScripts)

finally, I got the hex 70736274ff01005502000000016f8a0e9aef2399e90c2040443cb5e5f82bd2b3177263aeb1ef2fb5e5969518860000000000ffffffff0158020000000000001976a914b163d850125f1b65eadbcf15f88b7ef16836c1ee88ac00000000000100df0200000001a5fcb8900be46f9b321d286a51065db33be92e5b6df92e46129193c8be6101bd010000006a47304402203ebcf5f9407077196ced5d19925529be3bb3aea4926697786e0d8fff6eae960602207e0be4d60ddc2d2e4fade7bde7e5a0a5ab7567c963515d759f1d238ec40521760121032cf9dd2b7cf826a8d0e176ae0127e1e32b7e33dc55d78754c5f60bfd8f1173b8ffffffff02e80300000000000017a914d20cde47c6c181596b04c0a1d2a5a86c9b2c8cbe8729270000000000001976a914b163d850125f1b65eadbcf15f88b7ef16836c1ee88ac000000000107c44120514c8d23994055fcb319345d9dfa72466c0f5cdaa129f6f840199130ab7c298a41422778629773d182f5ba797908c5a48f1bf40497c6f8d9bf3995c65d3e800621032cf9dd2b7cf826a8d0e176ae0127e1e32b7e33dc55d78754c5f60bfd8f1173b803313233514c5963a820a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae38876a914b163d850125f1b65eadbcf15f88b7ef16836c1ee6700b27576a914b163d850125f1b65eadbcf15f88b7ef16836c1ee6888ac0000

but I can broadcast it, I got error with TX decode failed. Make sure the tx has at least one input.

here is my total code of how to redeem htlc and spend it

const bitcoin = require('bitcoinjs-lib');
const ecc = require('tiny-secp256k1');
const ecpair = require('ecpair');
const crypto = require("crypto");
var bitcoinMessage = require('bitcoinjs-message')
const TESTNET = bitcoin.networks.testnet;
const ECPair = ecpair.ECPairFactory(ecc);

const alice = ECPair.fromWIF(
    'xxxx',
);

const address = bitcoin.payments.p2pkh({
    pubkey: alice.publicKey,
    network: TESTNET,
});


const txId = "86189596e5b52fefb1ae637217b3d22bf8e5b53c4440200ce99923ef9a0e8a6f";
const vout = 0;
const value = 400;
const secret = Buffer.from('123', 'utf-8'); 
const secretHash = bitcoin.crypto.sha256(secret);// 
const expiry = 1000; 

const lockingScript = bitcoin.script.compile([
    bitcoin.opcodes.OP_IF,
    bitcoin.opcodes.OP_SHA256,
    secretHash,
    bitcoin.opcodes.OP_EQUALVERIFY,
    bitcoin.opcodes.OP_DUP,
    bitcoin.opcodes.OP_HASH160,
    bitcoin.crypto.hash160(alice.publicKey),
    bitcoin.opcodes.OP_ELSE,
    bitcoin.script.number.encode(expiry).toString('hex'),
    bitcoin.opcodes.OP_CHECKSEQUENCEVERIFY,
    bitcoin.opcodes.OP_DROP,
    bitcoin.opcodes.OP_DUP,
    bitcoin.opcodes.OP_HASH160,
    bitcoin.crypto.hash160(alice.publicKey),
    bitcoin.opcodes.OP_ENDIF,
    bitcoin.opcodes.OP_EQUALVERIFY,
    bitcoin.opcodes.OP_CHECKSIG
]);


const p2shAddress = bitcoin.payments.p2sh({
    redeem: { output: lockingScript },
    network: TESTNET
});
console.log('p2shAddress :', p2shAddress.address);  //address 2N3JprojTWnMvwCvYv4TzFJa7AqhsWskmwx

const psbt = new bitcoin.Psbt({ network: TESTNET });

// 
const serialized = Buffer.from(
    '0200000001a5fcb8900be46f9b321d286a51065db33be92e5b6df92e46129193c8be6101bd010000006a47304402203ebcf5f9407077196ced5d19925529be3bb3aea4926697786e0d8fff6eae960602207e0be4d60ddc2d2e4fade7bde7e5a0a5ab7567c963515d759f1d238ec40521760121032cf9dd2b7cf826a8d0e176ae0127e1e32b7e33dc55d78754c5f60bfd8f1173b8ffffffff02e80300000000000017a914d20cde47c6c181596b04c0a1d2a5a86c9b2c8cbe8729270000000000001976a914b163d850125f1b65eadbcf15f88b7ef16836c1ee88ac00000000',
    'hex'
);

psbt.setVersion(2)

psbt.addInput({
    hash: txId,
    index: vout,
    nonWitnessUtxo: serialized,
    redeemScript:p2shAddress.redeem.output

});

psbt.addOutput({
    address: 'mwguWMjTcJguvFiBkaAKQ7gEvj72TE4msB',
    value: 600,
});



console.log(psbt.toHex())


//
// const psbts = bitcoin.Psbt.fromHex('70736274ff01005502000000016f8a0e9aef2399e90c2040443cb5e5f82bd2b3177263aeb1ef2fb5e5969518860000000000ffffffff0158020000000000001976a914b163d850125f1b65eadbcf15f88b7ef16836c1ee88ac00000000000100df0200000001a5fcb8900be46f9b321d286a51065db33be92e5b6df92e46129193c8be6101bd010000006a47304402203ebcf5f9407077196ced5d19925529be3bb3aea4926697786e0d8fff6eae960602207e0be4d60ddc2d2e4fade7bde7e5a0a5ab7567c963515d759f1d238ec40521760121032cf9dd2b7cf826a8d0e176ae0127e1e32b7e33dc55d78754c5f60bfd8f1173b8ffffffff02e80300000000000017a914d20cde47c6c181596b04c0a1d2a5a86c9b2c8cbe8729270000000000001976a914b163d850125f1b65eadbcf15f88b7ef16836c1ee88ac000000002202032cf9dd2b7cf826a8d0e176ae0127e1e32b7e33dc55d78754c5f60bfd8f1173b8473044022026c6604b0dfb9e0995b59b3c56956e7b7ea8685534c77466e672c143d547f87e022012fed14b3c954d6b3ab6c57130dc94d5020e8e57560a5cc1ca01c19d015fc8780101045963a820a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae38876a914b163d850125f1b65eadbcf15f88b7ef16836c1ee6700b27576a914b163d850125f1b65eadbcf15f88b7ef16836c1ee6888ac0000')
// console.log('psbt',psbts)

var privateKey = alice.privateKey
var message = 'a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3'
var signatures = bitcoinMessage.sign(message, privateKey, alice.compressed)
console.log(signatures.toString('base64'))


const getFinalScripts = () => {
    const paymentFirstBranch = bitcoin.payments.p2sh({
      redeem: {
        input: bitcoin.script.compile([
            signatures,
            alice.publicKey,
            secret,
            bitcoin.opcodes.OP_TRUE,
        ]),
        output: lockingScript
      }
    })
  return {
    finalScriptSig: paymentFirstBranch.input
  }
}

const validator = (
    pubkey,
    msghash,
    signature,
  ) => ECPair.fromPublicKey(pubkey).verify(msghash, signature);

// psbts.finalizeInput(0,getFinalScripts)
// psbt.updateInput(0,getFinalScripts)
psbt.signInput(0,alice);
psbt.validateSignaturesOfInput(0, validator);
psbt.finalizeInput(0,getFinalScripts)

// psbt.finalizeAllInputs(0,getFinalScripts)

psbt.extractTransaction()

console.log(psbt.toHex())

You can’t broadcast a Psbt. You need to broadcast the transaction that is extracted.

I had using psbt.extractTransaction() ,but still got same error ,It seems like signatures reason ,could you help me check that part?

You aren't using it. You're just throwing away the result.

Hi,I got new problem, When I using bitcoinmessage sign a message ,I got 65 byte buffer. But If I using alice.sign() sign same message I got 64 byte, I cant encode for bitcoin.script.signature.encode() with bitcoinmessage signature and I can't recovery r & s value. Can you tell me what reason and how to solve it?

Hi,I got new problem, When I using bitcoinmessage sign a message ,I got 65 byte buffer. But If I using alice.sign() sign same message I got 64 byte, I cant encode for bitcoin.script.signature.encode() with bitcoinmessage signature and I can't recovery r & s value. Can you tell me what reason and how to solve it?

bitcoin sign should be 64 byte signature and 1 byte signHashType

the first 32 byte is r value and the next 32 byte is s value

So if you want get full signature you can use bitcoin.script.signature.encode() to combine signature and signHashType
and bitcoin.script.signature.decode() to split signature and signHashType from 65 byte.

Another tip: why not open a new issue?

Hi,I got new problem, When I using bitcoinmessage sign a message ,I got 65 byte buffer. But If I using alice.sign() sign same message I got 64 byte, I cant encode for bitcoin.script.signature.encode() with bitcoinmessage signature and I can't recovery r & s value. Can you tell me what reason and how to solve it?

bitcoin sign should be 64 byte signature and 1 byte signHashType

the first 32 byte is r value and the next 32 byte is s value

So if you want get full signature you can use bitcoin.script.signature.encode() to combine signature and signHashType and bitcoin.script.signature.decode() to split signature and signHashType from 65 byte.

Another tip: why not open a new issue?

Thank for you replay, some day ago I push a new issue on here [https://github.com/bitcoinjs/bitcoinjs-message/issues/48],but no one replay it seems not maintenance.

I had try your method but I got error with 'Error: Invalid hashType 203'

here is my code

console.log('this is psbt method' )
const hashType = bitcoin.Transaction.SIGHASH_ALL;
var privateKey = alice.privateKey
var message = '0948924b208d78cdde843cfebf72fdb32f9093f5671b4e09dc8d9371aaa0314f'
var derSignature = bitcoinMessage.sign(message, privateKey, alice.compressed)
console.log('derSignature base64',derSignature.toString('base64'))
console.log('derSignature hex',derSignature.toString('hex'))
// console.log('alice sign ', alice.sign(Buffer.from(message,'hex')).toString('hex'))
// 
// console.log(' alice.privateKey' , alice.privateKey.toString('hex'))

// const signature = bitcoin.script.signature.encode(derSignature.slice(1), hashType);
// console.log('der alice sign',signature.toString('hex'))

// 
console.log('this is tx method' )
const signature_tx = bitcoin.script.signature.encode(alice.sign(Buffer.from(message,'hex')), hashType);
console.log('signature_tx hex' ,alice.sign(Buffer.from(message,'hex')).toString('hex'))
console.log('signature_tx base64' ,alice.sign(Buffer.from(message,'hex')).toString('base64'))
console.log('encode signature_tx base64' ,signature_tx.toString('hex'))


console.log('this is equal method' )
const decode_sign = bitcoin.script.signature.decode(derSignature);
console.log('decode_sign',decode_sign)

In general, I need a signature for reedem script and spend a utxo(HTLC),So I need get the value from wallet such as unisat or okx,which offer a signMessage() fuction ,which could sign a str message,result like this H2QH9xYzfTAbdDn/UuN+gp8tNbUViWMuV18gO86bpMtSamVvr4JqQXVXdToUs5eo1pZSC03RPNjhJY7ATiYl3cs=. I have no idea wether the signMessage can using for transaction redeem signature.

signMessage will add messagePrefix of \x18Bitcoin Signed Message:\n

so i thinks its unsuitable for sign tansaction

I can`t connect error with your provide code, maybe something is missing?