Bitcoinj

如何使用 JCE 簽署比特幣交易?

  • June 28, 2019

我需要使用 JCE 原語(沒有 bitcoinj)簽署比特幣交易,但我的簽名被 bitcoinj 認為無效。

我試圖模擬這個過程。我創建了一個隨機雜湊並使用 JCE 和 bitcoinj 對其進行簽名。簽名不相等。

這是程式碼

import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.Sha256Hash;
import org.spongycastle.crypto.digests.SHA256Digest;
import org.spongycastle.crypto.params.ECPrivateKeyParameters;
import org.spongycastle.crypto.signers.ECDSASigner;
import org.spongycastle.crypto.signers.HMacDSAKCalculator;
import sun.security.ec.ECPrivateKeyImpl;
import sun.security.ec.ECPublicKeyImpl;

import java.math.BigInteger;
import java.security.*;
import java.security.spec.ECGenParameterSpec;

public class ECDSABitcoin {

   private static final String SIGN_ALGORITHM = "SHA256withECDSA";

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

       KeyPairGenerator keyPairGenerator = createGenerator();
       final KeyPair keyPair = keyPairGenerator.generateKeyPair();
       ECPrivateKeyImpl privateKey = (ECPrivateKeyImpl) keyPair.getPrivate();
       ECPublicKeyImpl publicKey = (ECPublicKeyImpl) keyPair.getPublic();
       try {
           Sha256Hash hashOut = Sha256Hash.wrap(toSha256("abc".getBytes()));
           byte[] signatureBytes = sign(hashOut.getBytes(), keyPair);

           ECKey.ECDSASignature mySignature = ECKey.ECDSASignature.decodeFromDER(signatureBytes).toCanonicalised();
           ECKey.ECDSASignature bitcoinSignature = sign(privateKey.getS(), hashOut.getBytes()).toCanonicalised();
           System.out.println("My signature s " + mySignature.s + " r " + mySignature.r + " canonical " + mySignature.isCanonical());
           System.out.println("Verify my " + verify(keyPair, hashOut.getBytes(), signatureBytes));
           System.out.println("Bitcoinj signature s " + bitcoinSignature.s + " r " + bitcoinSignature.r + " canonical " + bitcoinSignature.isCanonical());
           System.out.println("Verify Bitcoinj " + verify(keyPair, hashOut.getBytes(), bitcoinSignature.encodeToDER()));
       } catch (Exception e) {
           e.printStackTrace();
       }
   }

   private static boolean verify(KeyPair keyPair, byte[] message, byte[] signatureBytes) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
       final Signature verifySignature = Signature.getInstance(SIGN_ALGORITHM);
       verifySignature.initVerify(keyPair.getPublic());
       verifySignature.update(message);
       return verifySignature.verify(signatureBytes);
   }

   private static byte[] sign(byte[] message, KeyPair keyPair) throws InvalidKeyException, NoSuchAlgorithmException, SignatureException {
       final Signature signature = Signature.getInstance(SIGN_ALGORITHM);
       signature.initSign(keyPair.getPrivate());
       signature.update(message);
       return signature.sign();
   }

   private static KeyPairGenerator createGenerator() throws NoSuchAlgorithmException, InvalidAlgorithmParameterException {
       final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC");
       ECGenParameterSpec ecParam = new ECGenParameterSpec("secp256k1");
       keyPairGenerator.initialize(ecParam);
       return keyPairGenerator;
   }

   private static ECKey.ECDSASignature sign(BigInteger privateKeyForSigning, byte[] data) {
       ECDSASigner signer = new ECDSASigner(new HMacDSAKCalculator(new SHA256Digest()));
       ECPrivateKeyParameters privKey = new ECPrivateKeyParameters(privateKeyForSigning, ECKey.CURVE);
       signer.init(true, privKey);
       BigInteger[] components = signer.generateSignature(data);
       return new ECKey.ECDSASignature(components[0], components[1]).toCanonicalised();
   }

   private static byte[] toSha256(byte[] message) throws NoSuchAlgorithmException {
       MessageDigest crypt = MessageDigest.getInstance("SHA-256");
       crypt.reset();
       crypt.update(message);
       return crypt.digest();
   }
}

結果是:

My signature s 45669553786690215047884329722902825758089042579493437816717142987836102849876 r 14778973653615637448416336446742229796258878351047437829727432860950944374049 canonical true
Verify my true
Bitcoinj signature s 24278043061766196831119988370534304503511938256487950554838614741011144316017 r 26413727078831382349368962255251267289169651926313668837949728205557969096319 canonical true
Verify Bitcoinj false

如您所見,即使我使用相同的私鑰,簽名也是完全不同的。我的程式碼有什麼問題?我只是不明白。

您的簽名無法驗證,因為在 JCE 中,您使用的算法SHA256withECDSA在進行 kG.x,kinv(h+rd) 計算或類似的驗證計算之前對提供的數據進行散列(重新散列,因為您正在提供散列) ,而在 bitcoinj 中使用的海綿/彈性 ECDSASigner 只做後者,假設輸入已經是正確的雜湊。如果您改用 JCE NONEwithECDSA,它將驗證正常。

但是請注意,比特幣交易簽名的正確雜湊是雙 SHA256,而不是您編碼的單 SHA256。儘管您進行 SHA256 處理的數據根本不是比特幣交易,但您將其散列錯誤的事實是沒有實際意義的。

最後,即使這些固定,簽名仍然會有所不同。使用 SunEC 提供程序的 JCE 根據標準(X9.62 和 FIPS 186-2)使用隨機 K,而 BC LWAPI 可以並且您複製的 bitcoinj 程式碼確實根據 RFC6979 使用“合成”K。兩者都是有效的,但簽名值完全不同。(BC 提供者確實有一些使用此 K 計算的“ECDDSA”(D 表示確定性)算法,但僅限於散列,無論如何,如果您有 BC 提供者,您也有 BC LWAPI。)

此外,您似乎已經發現,比特幣使用“低 S”簽名;JCEBC LWAPI 都不會為您執行此操作,因此無論哪種情況,您都必須添加它(就像 bitcoinj 在 中所做的那樣toCanonicalised)。

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