public Script calculateSigScript(Transaction tx, int inOffset, Wallet w) { assert(inOffset >= 0); //get key by pubkeyhash from wallet ECKey key = w.findKeyFromPubHash(this.pubkeyHash); assert(key != null); TransactionSignature ts = tx.calculateSignature(inOffset, key, redeemScript, SigHash.ALL, false); ScriptBuilder sb = new ScriptBuilder(); byte[] sigEncoded = ts.encodeToBitcoin(); sb.data(sigEncoded); assert(TransactionSignature.isEncodingCanonical(sigEncoded)); sb.data(key.getPubKey()); sb.data(redeemScript.getProgram()); return sb.build(); }
private void addOnlyInputToTransaction(Transaction t, TransactionOutPointWithValue prevOut, long sequence) throws ScriptException { TransactionInput input = new TransactionInput(params, t, new byte[]{}, prevOut.outpoint); input.setSequenceNumber(sequence); t.addInput(input); if (prevOut.scriptPubKey.getChunks().get(0).equalsOpCode(OP_TRUE)) { input.setScriptSig(new ScriptBuilder().op(OP_1).build()); } else { // Sign input checkState(prevOut.scriptPubKey.isSentToRawPubKey()); Sha256Hash hash = t.hashForSignature(0, prevOut.scriptPubKey, SigHash.ALL, false); input.setScriptSig(ScriptBuilder.createInputScript( new TransactionSignature(coinbaseOutKey.sign(hash), SigHash.ALL, false)) ); } }
/** Calculates the byte used in the protocol to represent the combination of mode and anyoneCanPay. */ public static int calcSigHashValue(Transaction.SigHash mode, boolean anyoneCanPay) { Preconditions.checkArgument(SigHash.ALL == mode || SigHash.NONE == mode || SigHash.SINGLE == mode); // enforce compatibility since this code was made before the SigHash enum was updated int sighashFlags = mode.value; if (anyoneCanPay) sighashFlags |= Transaction.SigHash.ANYONECANPAY.value; return sighashFlags; }
/** * Returns true if the given signature is has canonical encoding, and will thus be accepted as standard by * Bitcoin Core. DER and the SIGHASH encoding allow for quite some flexibility in how the same structures * are encoded, and this can open up novel attacks in which a man in the middle takes a transaction and then * changes its signature such that the transaction hash is different but it's still valid. This can confuse wallets * and generally violates people's mental model of how Bitcoin should work, thus, non-canonical signatures are now * not relayed by default. */ public static boolean isEncodingCanonical(byte[] signature) { // See Bitcoin Core's IsCanonicalSignature, https://bitcointalk.org/index.php?topic=8392.msg127623#msg127623 // A canonical signature exists of: <30> <total len> <02> <len R> <R> <02> <len S> <S> <hashtype> // Where R and S are not negative (their first byte has its highest bit not set), and not // excessively padded (do not start with a 0 byte, unless an otherwise negative number follows, // in which case a single 0 byte is necessary and even required). if (signature.length < 9 || signature.length > 73) return false; int hashType = (signature[signature.length-1] & 0xff) & ~Transaction.SigHash.ANYONECANPAY.value; // mask the byte to prevent sign-extension hurting us if (hashType < Transaction.SigHash.ALL.value || hashType > Transaction.SigHash.SINGLE.value) return false; // "wrong type" "wrong length marker" if ((signature[0] & 0xff) != 0x30 || (signature[1] & 0xff) != signature.length-3) return false; int lenR = signature[3] & 0xff; if (5 + lenR >= signature.length || lenR == 0) return false; int lenS = signature[5+lenR] & 0xff; if (lenR + lenS + 7 != signature.length || lenS == 0) return false; // R value type mismatch R value negative if (signature[4-2] != 0x02 || (signature[4] & 0x80) == 0x80) return false; if (lenR > 1 && signature[4] == 0x00 && (signature[4+1] & 0x80) != 0x80) return false; // R value excessively padded // S value type mismatch S value negative if (signature[6 + lenR - 2] != 0x02 || (signature[6 + lenR] & 0x80) == 0x80) return false; if (lenS > 1 && signature[6 + lenR] == 0x00 && (signature[6 + lenR + 1] & 0x80) != 0x80) return false; // S value excessively padded return true; }
public Transaction.SigHash sigHashMode() { final int mode = sighashFlags & 0x1f; if (mode == Transaction.SigHash.NONE.value) return Transaction.SigHash.NONE; else if (mode == Transaction.SigHash.SINGLE.value) return Transaction.SigHash.SINGLE; else return Transaction.SigHash.ALL; }
public static Script getMultisigInputScript (ECDSASignature client, ECDSASignature server) { ArrayList<TransactionSignature> signList = new ArrayList<TransactionSignature>(); signList.add(new TransactionSignature(client, SigHash.ALL, false)); signList.add(new TransactionSignature(server, SigHash.ALL, false)); Script inputScript = ScriptBuilder.createMultiSigInputScript(signList); /* * Seems there is a bug here, * https://groups.google.com/forum/#!topic/bitcoinj/A9R8TdUsXms */ Script workaround = new Script(inputScript.getProgram()); return workaround; }
/** * Gets the multisig input script. * * @param client the client * @param server the server * @return the multisig input script */ public static Script getMultisigInputScript (ECDSASignature client, ECDSASignature server) { ArrayList<TransactionSignature> signList = new ArrayList<TransactionSignature>(); signList.add(new TransactionSignature(client, SigHash.ALL, false)); signList.add(new TransactionSignature(server, SigHash.ALL, false)); Script inputScript = ScriptBuilder.createMultiSigInputScript(signList); /* * Seems there is a bug here, * https://groups.google.com/forum/#!topic/bitcoinj/A9R8TdUsXms */ Script workaround = new Script(inputScript.getProgram()); return workaround; }
@Test public void microPayment_failsOnWrongAddressType() throws Exception { final ECKey senderKey = new ECKey(); accountService.createAccount(senderKey); final ECKey receiverKey = new ECKey(); accountService.createAccount(receiverKey); TimeLockedAddress tla = accountService.createTimeLockedAddress(senderKey, validLockTime) .getTimeLockedAddress(); walletService.addWatching(tla.getAddress(params)); Transaction goodInput = FakeTxBuilder.createFakeTxWithoutChangeAddress(params, tla.getAddress(params)); // Bad input is a pay 2 public key utxo from sender, that the server for some reason watches. Transaction badInput = FakeTxBuilder.createFakeTxWithoutChangeAddress(params, senderKey.toAddress(params)); watchAndMineTransactions(goodInput, badInput); Transaction tx = new Transaction(params); tx.addOutput(P2PKOutput(tx, serverKey, 500L, params)); tx.addInput(goodInput.getOutput(0)); tx.addInput(badInput.getOutput(0)); signInput(tx, 0, tla.createRedeemScript(), senderKey); TransactionSignature sig = tx.calculateSignature(1, senderKey, badInput.getOutput(0).getScriptPubKey(), SigHash.ALL, false); tx.getInput(1).setScriptSig(new ScriptBuilder().data(sig.encodeToBitcoin()).build()); SignedDTO dto = buildRequestDTO(senderKey, receiverKey, tx, 500L); sendAndExpect4xxError(dto, "Transaction must spent P2SH addresses"); }
@Test public void microPayment_failsOnSpentUTXOs() throws Exception { final ECKey senderKey = new ECKey(); accountService.createAccount(senderKey); final ECKey receiverKey = new ECKey(); accountService.createAccount(receiverKey); TimeLockedAddress tla = accountService.createTimeLockedAddress(senderKey, validLockTime).getTimeLockedAddress(); Transaction fundingTx = FakeTxBuilder.createFakeTxWithoutChangeAddress(params, tla.getAddress(params)); watchAndMineTransactions(fundingTx); // Spend the funding tx Account accountSender = accountService.getByClientPublicKey(senderKey.getPubKey()); final ECKey serverPrivateKey = ECKey.fromPrivateAndPrecalculatedPublic(accountSender.serverPrivateKey(), accountSender.serverPublicKey()); Transaction spendingTx = new Transaction(params); spendingTx.addInput(fundingTx.getOutput(0)); spendingTx.addOutput(anyP2PKOutput(spendingTx)); TransactionSignature clientSig = spendingTx.calculateSignature(0, senderKey, tla.createRedeemScript(), SigHash.ALL, false); TransactionSignature serverSig = spendingTx.calculateSignature(0, serverPrivateKey, tla.createRedeemScript(), SigHash.ALL, false); Script scriptSig = tla.createScriptSigBeforeLockTime(clientSig, serverSig); spendingTx.getInput(0).setScriptSig(scriptSig); walletService.getWallet().maybeCommitTx(spendingTx); PaymentChannel channel = new PaymentChannel(params, tla.getAddress(params), senderKey, serverKey) .addInputs(tla, fundingTx.getOutput(0)).addToServerOutput(1337L); SignedDTO dto = buildRequestDTO(senderKey, receiverKey, channel.buildTx(), 1337L); sendAndExpect4xxError(dto, "Input is already spent"); }
/** Constructs a signature with the given components and SIGHASH_ALL. */ public TransactionSignature(BigInteger r, BigInteger s) { this(r, s, Transaction.SigHash.ALL.value); }
/** Constructs a transaction signature based on the ECDSA signature. */ public TransactionSignature(ECKey.ECDSASignature signature, Transaction.SigHash mode, boolean anyoneCanPay) { super(signature.r, signature.s); sighashFlags = calcSigHashValue(mode, anyoneCanPay); }
public boolean anyoneCanPay() { return (sighashFlags & Transaction.SigHash.ANYONECANPAY.value) != 0; }
@Test public void testCreateMultiSigInputScript() { // Setup transaction and signatures ECKey key1 = DumpedPrivateKey.fromBase58(PARAMS, "cVLwRLTvz3BxDAWkvS3yzT9pUcTCup7kQnfT2smRjvmmm1wAP6QT").getKey(); ECKey key2 = DumpedPrivateKey.fromBase58(PARAMS, "cTine92s8GLpVqvebi8rYce3FrUYq78ZGQffBYCS1HmDPJdSTxUo").getKey(); ECKey key3 = DumpedPrivateKey.fromBase58(PARAMS, "cVHwXSPRZmL9adctwBwmn4oTZdZMbaCsR5XF6VznqMgcvt1FDDxg").getKey(); Script multisigScript = ScriptBuilder.createMultiSigOutputScript(2, Arrays.asList(key1, key2, key3)); byte[] bytes = HEX.decode("01000000013df681ff83b43b6585fa32dd0e12b0b502e6481e04ee52ff0fdaf55a16a4ef61000000006b483045022100a84acca7906c13c5895a1314c165d33621cdcf8696145080895cbf301119b7cf0220730ff511106aa0e0a8570ff00ee57d7a6f24e30f592a10cae1deffac9e13b990012102b8d567bcd6328fd48a429f9cf4b315b859a58fd28c5088ef3cb1d98125fc4e8dffffffff02364f1c00000000001976a91439a02793b418de8ec748dd75382656453dc99bcb88ac40420f000000000017a9145780b80be32e117f675d6e0ada13ba799bf248e98700000000"); Transaction transaction = PARAMS.getDefaultSerializer().makeTransaction(bytes); TransactionOutput output = transaction.getOutput(1); Transaction spendTx = new Transaction(PARAMS); Address address = Address.fromBase58(PARAMS, "n3CFiCmBXVt5d3HXKQ15EFZyhPz4yj5F3H"); Script outputScript = ScriptBuilder.createOutputScript(address); spendTx.addOutput(output.getValue(), outputScript); spendTx.addInput(output); Sha256Hash sighash = spendTx.hashForSignature(0, multisigScript, SigHash.ALL, false); ECKey.ECDSASignature party1Signature = key1.sign(sighash); ECKey.ECDSASignature party2Signature = key2.sign(sighash); TransactionSignature party1TransactionSignature = new TransactionSignature(party1Signature, SigHash.ALL, false); TransactionSignature party2TransactionSignature = new TransactionSignature(party2Signature, SigHash.ALL, false); // Create p2sh multisig input script Script inputScript = ScriptBuilder.createP2SHMultiSigInputScript(ImmutableList.of(party1TransactionSignature, party2TransactionSignature), multisigScript); // Assert that the input script contains 4 chunks assertTrue(inputScript.getChunks().size() == 4); // Assert that the input script created contains the original multisig // script as the last chunk ScriptChunk scriptChunk = inputScript.getChunks().get(inputScript.getChunks().size() - 1); Assert.assertArrayEquals(scriptChunk.data, multisigScript.getProgram()); // Create regular multisig input script inputScript = ScriptBuilder.createMultiSigInputScript(ImmutableList.of(party1TransactionSignature, party2TransactionSignature)); // Assert that the input script only contains 3 chunks assertTrue(inputScript.getChunks().size() == 3); // Assert that the input script created does not end with the original // multisig script scriptChunk = inputScript.getChunks().get(inputScript.getChunks().size() - 1); Assert.assertThat(scriptChunk.data, IsNot.not(equalTo(multisigScript.getProgram()))); }
public static boolean checkSignature (Transaction transaction, int index, Script outputToSpend, ECKey key, TransactionSignature signature) { Sha256Hash hash = transaction.hashForSignature(index, outputToSpend.getProgram(), SigHash.ALL, false); return key.verify(hash, signature); }
public static TransactionSignature getSignature (Transaction transactionToSign, int index, byte[] outputToSpend, ECKey key) { Sha256Hash hash = transactionToSign.hashForSignature(index, outputToSpend, SigHash.ALL, false); TransactionSignature signature = new TransactionSignature(key.sign(hash).toCanonicalised(), SigHash.ALL, false); return signature; }
@Test public void testCreateMultiSigInputScript() throws AddressFormatException { // Setup transaction and signatures ECKey key1 = new DumpedPrivateKey(params, "cVLwRLTvz3BxDAWkvS3yzT9pUcTCup7kQnfT2smRjvmmm1wAP6QT").getKey(); ECKey key2 = new DumpedPrivateKey(params, "cTine92s8GLpVqvebi8rYce3FrUYq78ZGQffBYCS1HmDPJdSTxUo").getKey(); ECKey key3 = new DumpedPrivateKey(params, "cVHwXSPRZmL9adctwBwmn4oTZdZMbaCsR5XF6VznqMgcvt1FDDxg").getKey(); Script multisigScript = ScriptBuilder.createMultiSigOutputScript(2, Arrays.asList(key1, key2, key3)); byte[] bytes = HEX.decode("01000000013df681ff83b43b6585fa32dd0e12b0b502e6481e04ee52ff0fdaf55a16a4ef61000000006b483045022100a84acca7906c13c5895a1314c165d33621cdcf8696145080895cbf301119b7cf0220730ff511106aa0e0a8570ff00ee57d7a6f24e30f592a10cae1deffac9e13b990012102b8d567bcd6328fd48a429f9cf4b315b859a58fd28c5088ef3cb1d98125fc4e8dffffffff02364f1c00000000001976a91439a02793b418de8ec748dd75382656453dc99bcb88ac40420f000000000017a9145780b80be32e117f675d6e0ada13ba799bf248e98700000000"); Transaction transaction = new Transaction(params, bytes); TransactionOutput output = transaction.getOutput(1); Transaction spendTx = new Transaction(params); Address address = new Address(params, "n3CFiCmBXVt5d3HXKQ15EFZyhPz4yj5F3H"); Script outputScript = ScriptBuilder.createOutputScript(address); spendTx.addOutput(output.getValue(), outputScript); spendTx.addInput(output); Sha256Hash sighash = spendTx.hashForSignature(0, multisigScript, SigHash.ALL, false); ECKey.ECDSASignature party1Signature = key1.sign(sighash); ECKey.ECDSASignature party2Signature = key2.sign(sighash); TransactionSignature party1TransactionSignature = new TransactionSignature(party1Signature, SigHash.ALL, false); TransactionSignature party2TransactionSignature = new TransactionSignature(party2Signature, SigHash.ALL, false); // Create p2sh multisig input script Script inputScript = ScriptBuilder.createP2SHMultiSigInputScript(ImmutableList.of(party1TransactionSignature, party2TransactionSignature), multisigScript); // Assert that the input script contains 4 chunks assertTrue(inputScript.getChunks().size() == 4); // Assert that the input script created contains the original multisig // script as the last chunk ScriptChunk scriptChunk = inputScript.getChunks().get(inputScript.getChunks().size() - 1); Assert.assertArrayEquals(scriptChunk.data, multisigScript.getProgram()); // Create regular multisig input script inputScript = ScriptBuilder.createMultiSigInputScript(ImmutableList.of(party1TransactionSignature, party2TransactionSignature)); // Assert that the input script only contains 3 chunks assertTrue(inputScript.getChunks().size() == 3); // Assert that the input script created does not end with the original // multisig script scriptChunk = inputScript.getChunks().get(inputScript.getChunks().size() - 1); Assert.assertThat(scriptChunk.data, IsNot.not(equalTo(multisigScript.getProgram()))); }
public static TransactionSignature getSignature (Transaction transactionToSign, int index, byte[] outputToSpend, ECKey key) { Sha256Hash hash = transactionToSign.hashForSignature(index, outputToSpend, SigHash.ALL, false); ECDSASignature signature = key.sign(hash).toCanonicalised(); return new TransactionSignature(signature, SigHash.ALL, false); }
/*** * Checks all inputs for validity. * * @param tx The transaction to check * @param accountSender Every input must be from an address from this account * @param neededFeeInSatoshi Fee in Satoshi/Byte that the given transaction should have * @return long (UNIX timestamp) that indicates then when the first address used is unlocked */ private long checkInputs(Transaction tx, Account accountSender, int neededFeeInSatoshi) { Coin valueOfInputs = Coin.ZERO; long earliestLockTime = Long.MAX_VALUE; for (int i=0; i<tx.getInputs().size(); i++) { TransactionInput input = tx.getInput(i); TransactionOutput out = walletService.findOutputFor(input); if (out == null) { throw new RuntimeException("Transaction spends unknown UTXOs"); } valueOfInputs = valueOfInputs.add(out.getValue()); if (!out.isAvailableForSpending()) { throw new RuntimeException("Input is already spent"); } if (out.getParentTransactionDepthInBlocks() < 1) { throw new RuntimeException("UTXO must be mined"); } Address fromAddress = out.getAddressFromP2SH(appConfig.getNetworkParameters()); if (fromAddress == null) { throw new RuntimeException("Transaction must spent P2SH addresses"); } TimeLockedAddressEntity tlaEntity = timeLockedAddressRepository.findByAddressHash(fromAddress.getHash160()); if (tlaEntity == null) { throw new RuntimeException("Used TLA inputs are not known to server"); } if (!tlaEntity.getAccount().equals(accountSender)) { throw new RuntimeException("Inputs must be from sender account"); } final Instant minimumLockedUntil = Instant.now() .plus(Duration.ofSeconds(appConfig.getMinimumLockTimeSeconds())); if (Instant.ofEpochSecond(tlaEntity.getLockTime()).isBefore(minimumLockedUntil)) { throw new RuntimeException("Inputs must be locked at least until " + minimumLockedUntil); } earliestLockTime = Math.min(earliestLockTime, tlaEntity.getLockTime()); Script spendingScript = input.getScriptSig(); if (spendingScript.getChunks().size() == 0) { throw new RuntimeException("Input was not signed"); } if (spendingScript.getChunks().size() != 1 || !spendingScript.getChunks().get(0).isPushData()) { throw new RuntimeException("Signature for input had wrong format"); } byte[] redeemScript = tlaEntity.getRedeemScript(); final ECKey serverPrivateKey = ECKey.fromPrivateAndPrecalculatedPublic(accountSender.serverPrivateKey(), accountSender.serverPublicKey()); TransactionSignature serverSig = tx.calculateSignature(i, serverPrivateKey, redeemScript, SigHash.ALL, false); Script finalSig = new ScriptBuilder(spendingScript).data(serverSig.encodeToBitcoin()).smallNum(1).data (redeemScript).build(); input.setScriptSig(finalSig); finalSig.correctlySpends(tx, i, out.getScriptPubKey(), Script.ALL_VERIFY_FLAGS); } // Check fee Coin givenFee = valueOfInputs.minus(tx.getOutputSum()); Coin neededFee = Coin.valueOf(neededFeeInSatoshi * tx.bitcoinSerialize().length); if (givenFee.isLessThan(neededFee)) { throw new RuntimeException("Insufficient transaction fee. Given: " + givenFee.divide(tx.bitcoinSerialize() .length) + " satoshi per byte. Needed: " + neededFee.divide(tx.bitcoinSerialize().length)); } return earliestLockTime; }
public static void signInput(Transaction tx, int index, Script redeemScript, ECKey signingKey) { TransactionSignature sig = tx.calculateSignature(index, signingKey, redeemScript.getProgram(), SigHash.ALL, false); tx.getInput(index).setScriptSig(new ScriptBuilder().data(sig.encodeToBitcoin()).build()); }