3
0
mirror of https://github.com/Qortal/altcoinj.git synced 2025-02-12 10:15:52 +00:00

Wallet: add output shuffling (a second time - where did it go?!). It's optional for unit testing.

This commit is contained in:
Mike Hearn 2014-04-30 23:47:24 +02:00
parent c8ffc1eaee
commit 4df728a7d9
5 changed files with 30 additions and 3 deletions

View File

@ -21,6 +21,7 @@ import com.google.bitcoin.crypto.TransactionSignature;
import com.google.bitcoin.script.Script;
import com.google.bitcoin.script.ScriptBuilder;
import com.google.bitcoin.script.ScriptOpCodes;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.primitives.Ints;
import com.google.common.primitives.Longs;
@ -1129,6 +1130,12 @@ public class Transaction extends ChildMessage implements Serializable {
return Collections.unmodifiableList(outputs);
}
/** Randomly re-orders the transaction outputs: good for privacy */
public void shuffleOutputs() {
maybeParse();
Collections.shuffle(outputs);
}
/** @return the given transaction: same as getInputs().get(index). */
public TransactionInput getInput(int index) {
maybeParse();

View File

@ -21,6 +21,7 @@ import com.google.bitcoin.core.TransactionConfidence.ConfidenceType;
import com.google.bitcoin.crypto.KeyCrypter;
import com.google.bitcoin.crypto.KeyCrypterException;
import com.google.bitcoin.crypto.KeyCrypterScrypt;
import com.google.bitcoin.params.UnitTestParams;
import com.google.bitcoin.script.Script;
import com.google.bitcoin.script.ScriptBuilder;
import com.google.bitcoin.script.ScriptChunk;
@ -1655,6 +1656,13 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
*/
public CoinSelector coinSelector = null;
/**
* If true (the default), the outputs will be shuffled during completion to randomize the location of the change
* output, if any. This is normally what you want for privacy reasons but in unit tests it can be annoying
* so it can be disabled here.
*/
public boolean shuffleOutputs = true;
// Tracks if this has been passed to wallet.completeTx already: just a safety check.
private boolean completed;
@ -1738,6 +1746,8 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
*/
public Transaction createSend(Address address, BigInteger nanocoins) throws InsufficientMoneyException {
SendRequest req = SendRequest.to(address, nanocoins);
if (params == UnitTestParams.get())
req.shuffleOutputs = false;
completeTx(req);
return req.tx;
}
@ -1952,6 +1962,10 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
log.info(" with a fee of {}", bitcoinValueToFriendlyString(calculatedFee));
}
// Now shuffle the outputs to obfuscate which is the change.
if (req.shuffleOutputs)
req.tx.shuffleOutputs();
// Now sign the inputs, thus proving that we are entitled to redeem the connected outputs.
req.tx.signInputs(Transaction.SigHash.ALL, this, req.aesKey);

View File

@ -253,6 +253,7 @@ public class PaymentChannelClientState {
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);
BigInteger multisigFee = req.fee;
multisigContract = req.tx;

View File

@ -394,7 +394,8 @@ public class PaymentChannelServerState {
// die. We could probably add features to the SendRequest API to make this a bit more efficient.
signMultisigInput(tx, Transaction.SigHash.NONE, true);
// Let wallet handle adding additional inputs/fee as necessary.
wallet.completeTx(req);
req.shuffleOutputs = false;
wallet.completeTx(req); // TODO: Fix things so shuffling is usable.
feePaidForPayment = req.fee;
log.info("Calculated fee is {}", feePaidForPayment);
if (feePaidForPayment.compareTo(bestValueToMe) >= 0) {

View File

@ -283,6 +283,7 @@ public class WalletTest extends TestWithWallet {
}
// Complete the transaction successfully.
req.shuffleOutputs = false;
wallet.completeTx(req);
Transaction t2 = req.tx;
@ -371,6 +372,7 @@ public class WalletTest extends TestWithWallet {
req.aesKey = aesKey;
Address a = req.changeAddress = new ECKey().toAddress(params);
req.ensureMinRequiredFee = false;
req.shuffleOutputs = false;
wallet.completeTx(req);
Transaction t3 = req.tx;
assertEquals(a, t3.getOutput(1).getScriptPubKey().getToAddress(params));
@ -1760,6 +1762,7 @@ public class WalletTest extends TestWithWallet {
request19.tx.clearInputs();
request19 = SendRequest.forTx(request19.tx);
request19.feePerKb = BigInteger.ONE;
request19.shuffleOutputs = false;
wallet.completeTx(request19);
assertEquals(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE, request19.fee);
assertEquals(2, request19.tx.getInputs().size());
@ -1767,7 +1770,6 @@ public class WalletTest extends TestWithWallet {
for (TransactionOutput out : request19.tx.getOutputs())
outValue19 = outValue19.add(out.getValue());
// But now our change output is CENT-minfee, so we have to pay min fee
// Change this assert when we eventually randomize output order
assertEquals(request19.tx.getOutput(request19.tx.getOutputs().size() - 1).getValue(), CENT.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE));
assertEquals(outValue19, Utils.COIN.add(CENT).subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE));
@ -1827,6 +1829,7 @@ public class WalletTest extends TestWithWallet {
request25 = SendRequest.forTx(request25.tx);
request25.feePerKb = CENT.divide(BigInteger.valueOf(3));
request25.ensureMinRequiredFee = false;
request25.shuffleOutputs = false;
wallet.completeTx(request25);
assertEquals(CENT.subtract(BigInteger.ONE), request25.fee);
assertEquals(2, request25.tx.getInputs().size());
@ -1834,7 +1837,6 @@ public class WalletTest extends TestWithWallet {
for (TransactionOutput out : request25.tx.getOutputs())
outValue25 = outValue25.add(out.getValue());
// Our change output should be one satoshi
// Change this assert when we eventually randomize output order
assertEquals(BigInteger.ONE, request25.tx.getOutput(request25.tx.getOutputs().size() - 1).getValue());
// and our fee should be CENT-1 satoshi
assertEquals(outValue25, Utils.COIN.add(BigInteger.ONE));
@ -2044,6 +2046,7 @@ public class WalletTest extends TestWithWallet {
SendRequest request1 = SendRequest.to(notMyAddr, CENT);
// If we just complete as-is, we will use one of the COIN outputs to get higher priority,
// resulting in a change output
request1.shuffleOutputs = false;
wallet.completeTx(request1);
assertEquals(1, request1.tx.getInputs().size());
assertEquals(2, request1.tx.getOutputs().size());
@ -2066,6 +2069,7 @@ public class WalletTest extends TestWithWallet {
request3.tx.addInput(new TransactionInput(params, request3.tx, new byte[]{}, new TransactionOutPoint(params, 0, tx3.getHash())));
// Now completeTx will result in two inputs, two outputs and a fee of a CENT
// Note that it is simply assumed that the inputs are correctly signed, though in fact the first is not
request3.shuffleOutputs = false;
wallet.completeTx(request3);
assertEquals(2, request3.tx.getInputs().size());
assertEquals(2, request3.tx.getOutputs().size());