/* * Copyright 2014 Christopher Mann * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package de.uni_bonn.bit; import org.bitcoinj.core.*; import org.bitcoinj.crypto.TransactionSignature; import org.bitcoinj.script.Script; import org.bitcoinj.script.ScriptBuilder; import org.bitcoinj.script.ScriptChunk; import de.uni_bonn.bit.wallet_protocol.*; import static de.uni_bonn.bit.BitcoinECMathHelper.*; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; /** * This class implements the desktop's logic for signing a Bitcoin transaction with the two-party ECDSA signature * protocol. This class only contains the Bitcoin-specific logic and uses one or more instances of the class * {@link de.uni_bonn.bit.DesktopSigner} to create ECDSA signatures for the transaction inputs with the help of * the two-party ECDSA signature protocol. In the end, this class returns a correctly signed Bitcoin transaction. */ public class DesktopTransactionSigner { ECKey privateKey; ECKey otherPublicKey; PaillierKeyPair pkpDesktop; PaillierKeyPair pkpPhone; BCParameters desktopBCParameters; BCParameters phoneBCParameters; Map<String,ECKey> keyShareMap; //Address private key map; Transaction transaction; ECKey commonPublicKey; Map<Integer,DesktopSigner> hashSignerMap = new HashMap<>(); public DesktopTransactionSigner(Transaction transaction, ECKey privateKey, ECKey otherPublicKey, PaillierKeyPair pkpDesktop, PaillierKeyPair pkpPhone, BCParameters desktopBCParameters, BCParameters phoneBCParameters) { this.privateKey = privateKey; this.otherPublicKey = otherPublicKey; this.pkpDesktop = pkpDesktop; this.pkpPhone = pkpPhone; this.desktopBCParameters = desktopBCParameters; this.phoneBCParameters = phoneBCParameters; this.transaction = transaction; commonPublicKey = convertPointToPubKEy(convertPubKeyToPoint(otherPublicKey) .multiply(convertPrivKeyToBigInt(privateKey)) .normalize()); } public SignatureParts[] computeSignatureParts(){ SignatureParts[] sigParts = new SignatureParts[transaction.getInputs().size()]; for(int i = 0; i < transaction.getInputs().size(); i++){ TransactionInput transactionInput = transaction.getInput(i); TransactionSignature transactionSignature = retrieveTransactionSignatureFromScriptSig(transactionInput.getScriptSig()); if(isDummySignature(transactionSignature)){ //unsigned input -> we will try to sign it using the two party protocol Script scriptPubKey = transactionInput.getConnectedOutput().getScriptPubKey(); if(Arrays.equals(scriptPubKey.getPubKeyHash(), commonPublicKey.getPubKeyHash())){ //The input uses an output which can be spent with our two party authentication DesktopSigner hashSigner = new DesktopSigner(privateKey, otherPublicKey, pkpDesktop, pkpPhone, desktopBCParameters, phoneBCParameters); hashSignerMap.put(i, hashSigner); sigParts[i] = hashSigner.computeSignatureParts(); } } } return sigParts; } public EphemeralPublicValueWithProof[] computeEphemeralPublicValue(EphemeralValueShare[] ephemeralValueShares){ if(ephemeralValueShares.length != transaction.getInputs().size()){ throw new ProtocolException("The number of ephemeral value shares does not fit the number of inputs."); } EphemeralPublicValueWithProof[] result = new EphemeralPublicValueWithProof[ephemeralValueShares.length]; for(int i = 0; i < ephemeralValueShares.length; i++){ result[i] = hashSignerMap.get(i).computeEphemeralPublicValue(ephemeralValueShares[i]); } return result; } public Transaction addEncryptedSignaturesToTransaction(EncryptedSignatureWithProof[] encryptedSignatureMap){ for(Map.Entry<Integer, DesktopSigner> entry : hashSignerMap.entrySet()){ int i = entry.getKey(); DesktopSigner hashSigner = entry.getValue(); EncryptedSignatureWithProof encSignature = encryptedSignatureMap[i]; if(encSignature == null) throw new ProtocolException("An encrypted signature is missing!"); TransactionInput transactionInput = transaction.getInput(i); Script scriptPubKey = transactionInput.getConnectedOutput().getScriptPubKey(); byte[] hash = transaction.hashForSignature(i, scriptPubKey, Transaction.SigHash.ALL, false).getBytes(); ECKey.ECDSASignature ecdsaSignature = hashSigner.decryptEncryptedSignature(encSignature, hash); TransactionSignature txSignature = new TransactionSignature(ecdsaSignature, Transaction.SigHash.ALL, false); transaction.getInput(i).setScriptSig(replaceSignatureInScriptSig(transactionInput.getScriptSig(), txSignature)); } return transaction; } public static TransactionSignature retrieveTransactionSignatureFromScriptSig(Script script) throws ScriptException{ if(! isStandardScriptSig(script)) throw new ScriptException("Script is not a standard script sig."); try{ return TransactionSignature.decodeFromBitcoin(script.getChunks().get(0).data, false); }catch(VerificationException ex){ throw new ScriptException("Failed to parse signature from the script sig"); } } public static boolean isDummySignature(TransactionSignature signature){ TransactionSignature dummySignature = TransactionSignature.dummy(); return signature.r.equals(dummySignature.r) && signature.s.equals(dummySignature.s); } public static Script replaceSignatureInScriptSig(Script scriptSig, TransactionSignature txSignature){ if(! isStandardScriptSig(scriptSig)) throw new ScriptException("Script is not a standard script sig."); byte[] newSigBytes = txSignature.encodeToBitcoin(); byte[] pubKeyBytes = scriptSig.getPubKey(); ScriptBuilder sb = new ScriptBuilder(); sb.data(newSigBytes); sb.data(pubKeyBytes); Script newScript = sb.build(); return newScript; } /** * Checks, whether the provided script has the layout of a standard script sig. The tests are only heuristic. * @param scriptSig * @return */ public static boolean isStandardScriptSig(Script scriptSig){ List<ScriptChunk> chunks = scriptSig.getChunks(); if(chunks.size() != 2) return false; if(chunks.get(0).isOpCode() || chunks.get(1).isOpCode()) return false; return true; } }