如何通過比特幣核心驗證簽名消息?
我想在 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