@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.getProgram()); // 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(IsEqual.equalTo(multisigScript.getProgram()))); }
/** * Creates the initial multisig contract and incomplete refund transaction which can be requested * at the appropriate time using {@link PaymentChannelClientState#getIncompleteRefundTransaction} * and {@link PaymentChannelClientState#getMultisigContract()}. The way the contract is crafted * can be adjusted by overriding {@link * PaymentChannelClientState#editContractSendRequest(com.google.bitcoin.core.Wallet.SendRequest)}. * By default unconfirmed coins are allowed to be used, as for micropayments the risk should be * relatively low. * * @throws ValueOutOfRangeException if the value being used is too small to be accepted by the * network * @throws InsufficientMoneyException if the wallet doesn't contain enough balance to initiate */ public synchronized void initiate() throws ValueOutOfRangeException, InsufficientMoneyException { final NetworkParameters params = wallet.getParams(); Transaction template = new Transaction(params); // We always place the client key before the server key because, if either side wants some // privacy, they can // use a fresh key for the the multisig contract and nowhere else List<ECKey> keys = Lists.newArrayList(myKey, serverMultisigKey); // There is also probably a change output, but we don't bother shuffling them as it's obvious // from the // format which one is the change. If we start obfuscating the change output better in future // this may // be worth revisiting. TransactionOutput multisigOutput = template.addOutput(totalValue, ScriptBuilder.createMultiSigOutputScript(2, keys)); if (multisigOutput.getMinNonDustValue().compareTo(totalValue) > 0) throw new ValueOutOfRangeException("totalValue too small to use"); Wallet.SendRequest req = Wallet.SendRequest.forTx(template); req.coinSelector = AllowUnconfirmedCoinSelector.get(); editContractSendRequest(req); req.shuffleOutputs = false; // TODO: Fix things so shuffling is usable. wallet.completeTx(req); Coin multisigFee = req.tx.getFee(); multisigContract = req.tx; // Build a refund transaction that protects us in the case of a bad server that's just trying to // cause havoc // by locking up peoples money (perhaps as a precursor to a ransom attempt). We time lock it so // the server // has an assurance that we cannot take back our money by claiming a refund before the channel // closes - this // relies on the fact that since Bitcoin 0.8 time locked transactions are non-final. This will // need to change // in future as it breaks the intended design of timelocking/tx replacement, but for now it // simplifies this // specific protocol somewhat. refundTx = new Transaction(params); refundTx .addInput(multisigOutput) .setSequenceNumber(0); // Allow replacement when it's eventually reactivated. refundTx.setLockTime(expiryTime); if (totalValue.compareTo(Coin.CENT) < 0) { // Must pay min fee. final Coin valueAfterFee = totalValue.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE); if (Transaction.MIN_NONDUST_OUTPUT.compareTo(valueAfterFee) > 0) throw new ValueOutOfRangeException("totalValue too small to use"); refundTx.addOutput(valueAfterFee, myKey.toAddress(params)); refundFees = multisigFee.add(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE); } else { refundTx.addOutput(totalValue, myKey.toAddress(params)); refundFees = multisigFee; } refundTx.getConfidence().setSource(TransactionConfidence.Source.SELF); log.info( "initiated channel with multi-sig contract {}, refund {}", multisigContract.getHashAsString(), refundTx.getHashAsString()); state = State.INITIATED; // Client should now call getIncompleteRefundTransaction() and send it to the server. }
private synchronized Transaction makeUnsignedChannelContract(Coin valueToMe) throws ValueOutOfRangeException { Transaction tx = new Transaction(wallet.getParams()); tx.addInput(multisigContract.getOutput(0)); // Our output always comes first. // TODO: We should drop myKey in favor of output key + multisig key separation // (as its always obvious who the client is based on T2 output order) tx.addOutput(valueToMe, myKey.toAddress(wallet.getParams())); return tx; }
public void sweepKey(ECKey key, long fee, int accountId, JSONArray outputs) { mLogger.info("sweepKey starting"); mLogger.info("key addr " + key.toAddress(mParams).toString()); Transaction tx = new Transaction(mParams); long balance = 0; ArrayList<Script> scripts = new ArrayList<Script>(); try { for (int ii = 0; ii < outputs.length(); ++ii) { JSONObject output; output = outputs.getJSONObject(ii); String tx_hash = output.getString("tx_hash"); int tx_output_n = output.getInt("tx_output_n"); String script = output.getString("script"); // Reverse byte order, create hash. Sha256Hash hash = new Sha256Hash(WalletUtil.msgHexToBytes(tx_hash)); tx.addInput( new TransactionInput( mParams, tx, new byte[] {}, new TransactionOutPoint(mParams, tx_output_n, hash))); scripts.add(new Script(Hex.decode(script))); balance += output.getLong("value"); } } catch (JSONException e) { e.printStackTrace(); throw new RuntimeException("trouble parsing unspent outputs"); } // Compute balance - fee. long amount = balance - fee; mLogger.info(String.format("sweeping %d", amount)); // Figure out the destination address. Address to = mHDWallet.nextReceiveAddress(accountId); mLogger.info("sweeping to " + to.toString()); // Add output. tx.addOutput(BigInteger.valueOf(amount), to); WalletUtil.signTransactionInputs(tx, Transaction.SigHash.ALL, key, scripts); mLogger.info("tx bytes: " + new String(Hex.encode(tx.bitcoinSerialize()))); // mKit.peerGroup().broadcastTransaction(tx); broadcastTransaction(mKit.peerGroup(), tx); mLogger.info("sweepKey finished"); }
private void readTransaction(Protos.Transaction txProto, NetworkParameters params) { Transaction tx = new Transaction(params); if (txProto.hasUpdatedAt()) { tx.setUpdateTime(new Date(txProto.getUpdatedAt())); } for (Protos.TransactionOutput outputProto : txProto.getTransactionOutputList()) { BigInteger value = BigInteger.valueOf(outputProto.getValue()); byte[] scriptBytes = outputProto.getScriptBytes().toByteArray(); TransactionOutput output = new TransactionOutput(params, tx, value, scriptBytes); tx.addOutput(output); } for (Protos.TransactionInput transactionInput : txProto.getTransactionInputList()) { byte[] scriptBytes = transactionInput.getScriptBytes().toByteArray(); TransactionOutPoint outpoint = new TransactionOutPoint( params, transactionInput.getTransactionOutPointIndex(), byteStringToHash(transactionInput.getTransactionOutPointHash())); TransactionInput input = new TransactionInput(params, tx, scriptBytes, outpoint); if (transactionInput.hasSequence()) { input.setSequenceNumber(transactionInput.getSequence()); } tx.addInput(input); } for (ByteString blockHash : txProto.getBlockHashList()) { tx.addBlockAppearance(byteStringToHash(blockHash)); } if (txProto.hasLockTime()) { tx.setLockTime(0xffffffffL & txProto.getLockTime()); } // Transaction should now be complete. Sha256Hash protoHash = byteStringToHash(txProto.getHash()); Preconditions.checkState( tx.getHash().equals(protoHash), "Transaction did not deserialize completely: %s vs %s", tx.getHash(), protoHash); Preconditions.checkState( !txMap.containsKey(txProto.getHash()), "Wallet contained duplicate transaction %s", byteStringToHash(txProto.getHash())); txMap.put(txProto.getHash(), tx); }
public static List<Transaction> getTransactionsFromBither( JSONObject jsonObject, int storeBlockHeight) throws JSONException, WrongNetworkException, AddressFormatException, VerificationException, ParseException, NoSuchFieldException, IllegalAccessException, IllegalArgumentException { List<Transaction> transactions = new ArrayList<Transaction>(); if (!jsonObject.isNull(TXS)) { JSONArray txArray = jsonObject.getJSONArray(TXS); double count = 0; double size = txArray.length(); for (int j = 0; j < txArray.length(); j++) { JSONObject tranJsonObject = txArray.getJSONObject(j); String blockHash = tranJsonObject.getString(BITHER_BLOCK_HASH); String txHash = tranJsonObject.getString(TX_HASH); int height = tranJsonObject.getInt(BITHER_BLOCK_NO); if (height > storeBlockHeight && storeBlockHeight > 0) { continue; } int version = 1; Date updateTime = new Date(); if (!tranJsonObject.isNull(EXPLORER_TIME)) { updateTime = DateTimeUtil.getDateTimeForTimeZone(tranJsonObject.getString(EXPLORER_TIME)); } if (!tranJsonObject.isNull(EXPLORER_VERSION)) { version = tranJsonObject.getInt(EXPLORER_VERSION); } Transaction transaction = new Transaction(BitherSetting.NETWORK_PARAMETERS, version, new Sha256Hash(txHash)); transaction.addBlockAppearance(new Sha256Hash(blockHash), height); if (!tranJsonObject.isNull(EXPLORER_OUT)) { JSONArray tranOutArray = tranJsonObject.getJSONArray(EXPLORER_OUT); for (int i = 0; i < tranOutArray.length(); i++) { JSONObject tranOutJson = tranOutArray.getJSONObject(i); BigInteger value = BigInteger.valueOf(tranOutJson.getLong(BITHER_VALUE)); if (!tranOutJson.isNull(SCRIPT_PUB_KEY)) { String str = tranOutJson.getString(SCRIPT_PUB_KEY); // Script script = new Script( // ); // byte[] bytes1 = ScriptBuilder.createOutputScript( // address).getProgram(); // byte[] bytes2 = StringUtil // .hexStringToByteArray(str); // LogUtil.d("tx", Arrays.equals(bytes1, bytes2) + // ";"); TransactionOutput transactionOutput = new TransactionOutput( BitherSetting.NETWORK_PARAMETERS, transaction, value, StringUtil.hexStringToByteArray(str)); transaction.addOutput(transactionOutput); } } } if (!tranJsonObject.isNull(EXPLORER_IN)) { JSONArray tranInArray = tranJsonObject.getJSONArray(EXPLORER_IN); for (int i = 0; i < tranInArray.length(); i++) { JSONObject tranInJson = tranInArray.getJSONObject(i); TransactionOutPoint transactionOutPoint = null; if (!tranInJson.isNull(EXPLORER_COINBASE)) { long index = 0; if (!tranInJson.isNull(EXPLORER_SEQUENCE)) { index = tranInJson.getLong(EXPLORER_SEQUENCE); } transactionOutPoint = new TransactionOutPoint( BitherSetting.NETWORK_PARAMETERS, index, Sha256Hash.ZERO_HASH); } else { String prevOutHash = tranInJson.getString(PREV_TX_HASH); long n = 0; if (!tranInJson.isNull(PREV_OUTPUT_SN)) { n = tranInJson.getLong(PREV_OUTPUT_SN); } transactionOutPoint = new TransactionOutPoint( BitherSetting.NETWORK_PARAMETERS, n, new Sha256Hash(prevOutHash)); } // Log.d("transaction", transaction.toString()); if (transactionOutPoint != null) { TransactionInput transactionInput = new TransactionInput( BitherSetting.NETWORK_PARAMETERS, transaction, Script.createInputScript(EMPTY_BYTES, EMPTY_BYTES), transactionOutPoint); transaction.addInput(transactionInput); } } } transaction.getConfidence().setAppearedAtChainHeight(height); transaction.getConfidence().setConfidenceType(ConfidenceType.BUILDING); transaction.getConfidence().setDepthInBlocks(storeBlockHeight - height + 1); transaction.setUpdateTime(updateTime); // Log.d("transaction", "transaction.num:" + transaction); Field txField = Transaction.class.getDeclaredField("hash"); txField.setAccessible(true); txField.set(transaction, new Sha256Hash(txHash)); transactions.add(transaction); count++; double progress = BitherSetting.SYNC_TX_PROGRESS_BLOCK_HEIGHT + BitherSetting.SYNC_TX_PROGRESS_STEP1 + BitherSetting.SYNC_TX_PROGRESS_STEP2 * (count / size); BroadcastUtil.sendBroadcastProgressState(progress); } } LogUtil.d("transaction", "transactions.num:" + transactions.size()); return transactions; }