Signature

如何通過比特幣核心驗證簽名消息?

  • July 11, 2020

我想在 Java 中驗證使用比特幣核心生成的簽名signmessagewithprivkey(相當於verifymessage)。我注意到一種類似於比特幣核心的 bitcoinj 方法verifymessage- org.bitcoinj.core.ECKey.verify。接下來是我的實現嘗試及其測試。

為了測試,我將生成一個簽名並在比特幣核心中檢查它:

>bitcoin-cli signmessagewithprivkey $(bitcoin-cli dumpprivkey 1CwKH9PQPkFPjQagEv483FUM5ngk57L3Pp) ""
H2wp/+5N2+OQwP6a5GFRbt8S+EfML1Szx4uhWPfiO0e/QcY2rZQOkLOR+unknNl4NgDWBacRRXOLjr+m53V0xic=
> bitcoin-cli verifymessage "1CwKH9PQPkFPjQagEv483FUM5ngk57L3Pp" "H2wp/+5N2+OQwP6a5GFRbt8S+EfML1Szx4uhWPfiO0e/QcY2rZQOkLOR+unknNl4NgDWBacRRXOLjr+m53V0xic=" ""
true

1CwKH9PQPkFPjQagEv483FUM5ngk57L3Pp因此,使用 RPC 使用地址(公鑰:)對空消息進行021c3be5fb7820c56d881ea2d02a906d87540ec8888bbe819b7abd2e39f6f6e512簽名,生成簽名H2wp/+5N2+OQwP6a5GFRbt8S+EfML1Szx4uhWPfiO0e/QcY2rZQOkLOR+unknNl4NgDWBacRRXOLjr+m53V0xic=

我的 Java 實現嘗試:

import org.apache.commons.codec.binary.Hex;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.Sha256Hash;
import java.math.BigInteger;
import java.util.Base64;

public class Notebook {

   public static BigInteger[] ParseSig(byte[] sigBytes, int sigOff) {

       BigInteger r = new BigInteger( 1 ,sigBytes, sigOff, 32);
       BigInteger s = new BigInteger( 1, sigBytes, sigOff + 32, 32);
       return new BigInteger[] { r, s };
   }

   public static void main(String[] args) throws Exception{

       // Signature
       String signatureString = "H2wp/+5N2+OQwP6a5GFRbt8S+EfML1Szx4uhWPfiO0e/QcY2rZQOkLOR+unknNl4NgDWBacRRXOLjr+m53V0xic=";
       byte[] signatureBytes = Base64.getDecoder().decode(signatureString);
       BigInteger[] signatureRandS = ParseSig(signatureBytes, 1);
       ECKey.ECDSASignature signature = new ECKey.ECDSASignature(signatureRandS[0], signatureRandS[1]);

       // Public key
       String pubKeyString = "021c3be5fb7820c56d881ea2d02a906d87540ec8888bbe819b7abd2e39f6f6e512";
       byte[] decodedPubKeyString = Hex.decodeHex(pubKeyString.toCharArray());
       ECKey publicKey = ECKey.fromPublicOnly(decodedPubKeyString);

       // Message
       String message = "Bitcoin Signed Message:\n";
       Sha256Hash messageHash = Sha256Hash.of(message.getBytes());
       Sha256Hash messageDoubleHash = Sha256Hash.of(messageHash.getBytes());

       // Test
       boolean result = publicKey.verify(messageDoubleHash, signature);
       System.out.println("Result: " + result);
   }
}

請注意,我在對消息進行散列之前使用了鹽Bitcoin Signed Message:\n- 這是通過signmessagewithprivkey顯示此處此處完成的。問題是上面的實現沒有通過我的測試,我不知道為什麼……

編輯:

感謝Septem151,我能夠解決這個問題。如果將消息部分替換為以下程式碼,則程式碼通過測試:

       // Message
       String message = "Bitcoin Signed Message:\n";
       byte[] messageBytesEncoded = new byte[message.getBytes().length + 2];
       messageBytesEncoded[0] = 0x18;
       messageBytesEncoded[messageBytesEncoded.length - 1] = 0x00;
       for(int i=1; i <= message.getBytes().length; i++) messageBytesEncoded[i] = message.getBytes()[i-1];

       Sha256Hash messageHash = Sha256Hash.of(messageBytesEncoded);
       Sha256Hash messageDoubleHash = Sha256Hash.of(messageHash.getBytes());

使用 Bitcoin Core 簽署消息時使用的原像是“魔術”片語的組合:Bitcoin Signed Message:\n和您正在簽署的消息。然而,有一個警告,你必須在“魔法”和消息前面都有一個VarInt 。VarInt 對消息的字節長度進行編碼(以 UTF-8 編碼)。由於“Magic”值永遠不會改變,它的 VarInt 值將始終為0x18.

對於您的範例,消息什麼都不是,因此您的 VarInt 將是0x00. 您的原像和 sighash 變為:

Preimage: 0x18426974636f696e205369676e6564204d6573736167653a0a00
Sighash: SHA256(SHA256(preimage)) = 0x80e795d4a4caadd7047af389d9f7f220562feb6196032e2131e10563352c4bcc

   0x18       0x426974636f696e205369676e6564204d6573736167653a0a        0x00      (empty)
MAGIC_VARINT                       MAGIC                           MESSAGE_VARINT  MESSAGE

引用自:https://bitcoin.stackexchange.com/questions/92406