如何使用 JCE 簽署比特幣交易?
我需要使用 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 只做後者,假設輸入已經是正確的雜湊。如果您改用 JCENONEwithECDSA
,它將驗證正常。但是請注意,比特幣交易簽名的正確雜湊是雙 SHA256,而不是您編碼的單 SHA256。儘管您進行 SHA256 處理的數據根本不是比特幣交易,但您將其散列錯誤的事實是沒有實際意義的。
最後,即使這些固定,簽名仍然會有所不同。使用 SunEC 提供程序的 JCE 根據標準(X9.62 和 FIPS 186-2)使用隨機 K,而 BC LWAPI 可以並且您複製的 bitcoinj 程式碼確實根據 RFC6979 使用“合成”K。兩者都是有效的,但簽名值完全不同。(BC 提供者確實有一些使用此 K 計算的“ECDDSA”(D 表示確定性)算法,但僅限於散列,無論如何,如果您有 BC 提供者,您也有 BC LWAPI。)
此外,您似乎已經發現,比特幣使用“低 S”簽名;JCE和BC LWAPI 都不會為您執行此操作,因此無論哪種情況,您都必須添加它(就像 bitcoinj 在 中所做的那樣
toCanonicalised
)。