mirror of
https://github.com/bitcoinj/bitcoinj.git
synced 2024-11-20 02:09:29 +01:00
Support creating spends without all the private keys.
Dummy signatures are inserted instead. Also, simplify Transaction.toString().
This commit is contained in:
parent
81d76a76c3
commit
8d839ae5ad
@ -197,6 +197,14 @@ public class ECKey implements Serializable {
|
||||
this(privKey, pubKey, false);
|
||||
}
|
||||
|
||||
public boolean isPubKeyOnly() {
|
||||
return priv == null;
|
||||
}
|
||||
|
||||
public boolean hasPrivKey() {
|
||||
return priv != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Output this ECKey as an ASN.1 encoded private key, as understood by OpenSSL or used by the BitCoin reference
|
||||
* implementation in its wallet storage format.
|
||||
|
@ -26,6 +26,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.spongycastle.crypto.params.KeyParameter;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.*;
|
||||
import java.math.BigInteger;
|
||||
import java.text.ParseException;
|
||||
@ -613,17 +614,10 @@ public class Transaction extends ChildMessage implements Serializable {
|
||||
}
|
||||
for (TransactionInput in : inputs) {
|
||||
s.append(" ");
|
||||
s.append("from ");
|
||||
s.append("in ");
|
||||
|
||||
try {
|
||||
Script scriptSig = in.getScriptSig();
|
||||
if (scriptSig.getChunks().size() == 2)
|
||||
s.append(scriptSig.getFromAddress(params).toString());
|
||||
else if (scriptSig.getChunks().size() == 1) {
|
||||
s.append("[sig:");
|
||||
s.append(bytesToHexString(scriptSig.getChunks().get(0).data));
|
||||
s.append("]");
|
||||
} else
|
||||
s.append(scriptSig);
|
||||
s.append(" / ");
|
||||
s.append(in.getOutpoint().toString());
|
||||
@ -634,18 +628,10 @@ public class Transaction extends ChildMessage implements Serializable {
|
||||
}
|
||||
for (TransactionOutput out : outputs) {
|
||||
s.append(" ");
|
||||
s.append("to ");
|
||||
s.append("out ");
|
||||
try {
|
||||
Script scriptPubKey = out.getScriptPubKey();
|
||||
if (scriptPubKey.isSentToAddress()) {
|
||||
s.append(scriptPubKey.getToAddress(params).toString());
|
||||
} else if (scriptPubKey.isSentToRawPubKey()) {
|
||||
s.append("[pubkey:");
|
||||
s.append(bytesToHexString(scriptPubKey.getPubKey()));
|
||||
s.append("]");
|
||||
} else {
|
||||
s.append(scriptPubKey);
|
||||
}
|
||||
s.append(" ");
|
||||
s.append(bitcoinValueToFriendlyString(out.getValue()));
|
||||
s.append(" BTC");
|
||||
@ -770,7 +756,7 @@ public class Transaction extends ChildMessage implements Serializable {
|
||||
* @param wallet A wallet is required to fetch the keys needed for signing.
|
||||
* @param aesKey The AES key to use to decrypt the key before signing. Null if no decryption is required.
|
||||
*/
|
||||
public synchronized void signInputs(SigHash hashType, Wallet wallet, KeyParameter aesKey) throws ScriptException {
|
||||
public synchronized void signInputs(SigHash hashType, Wallet wallet, @Nullable KeyParameter aesKey) throws ScriptException {
|
||||
// TODO: This should be a method of the TransactionInput that (possibly?) operates with a copy of this object.
|
||||
Preconditions.checkState(inputs.size() > 0);
|
||||
Preconditions.checkState(outputs.size() > 0);
|
||||
@ -816,7 +802,14 @@ public class Transaction extends ChildMessage implements Serializable {
|
||||
// The anyoneCanPay feature isn't used at the moment.
|
||||
boolean anyoneCanPay = false;
|
||||
byte[] connectedPubKeyScript = input.getOutpoint().getConnectedPubKeyScript();
|
||||
if (key.hasPrivKey() || key.isEncrypted()) {
|
||||
signatures[i] = calculateSignature(i, key, aesKey, connectedPubKeyScript, hashType, anyoneCanPay);
|
||||
} else {
|
||||
// Create a dummy signature to ensure the transaction is of the correct size when we try to ensure
|
||||
// the right fee-per-kb is attached. If the wallet doesn't have the privkey, the user is assumed to
|
||||
// be doing something special and that they will replace the dummy signature with a real one later.
|
||||
signatures[i] = TransactionSignature.dummy();
|
||||
}
|
||||
}
|
||||
|
||||
// Now we have calculated each signature, go through and create the scripts. Reminder: the script consists:
|
||||
|
@ -48,6 +48,17 @@ public class TransactionSignature extends ECKey.ECDSASignature {
|
||||
setSigHash(mode, anyoneCanPay);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a dummy invalid signature whose R/S values are set such that they will take up the same number of
|
||||
* encoded bytes as a real signature. This can be useful when you want to fill out a transaction to be of the
|
||||
* right size (e.g. for fee calculations) but don't have the requisite signing key yet and will fill out the
|
||||
* real signature later.
|
||||
*/
|
||||
public static TransactionSignature dummy() {
|
||||
BigInteger val = BigInteger.ONE.shiftLeft(32 * 8); // 32 byte signatures.
|
||||
return new TransactionSignature(val, val);
|
||||
}
|
||||
|
||||
/** Calculates the byte used in the protocol to represent the combination of mode and anyoneCanPay. */
|
||||
public static int calcSigHashValue(Transaction.SigHash mode, boolean anyoneCanPay) {
|
||||
int sighashFlags = mode.ordinal() + 1;
|
||||
|
@ -80,6 +80,11 @@ public class TestWithWallet {
|
||||
return sendMoneyToWallet(wallet, createFakeTx(params, value, toAddress), type);
|
||||
}
|
||||
|
||||
protected Transaction sendMoneyToWallet(Wallet wallet, BigInteger value, ECKey toPubKey, AbstractBlockChain.NewBlockType type)
|
||||
throws IOException, ProtocolException, VerificationException {
|
||||
return sendMoneyToWallet(wallet, createFakeTx(params, value, toPubKey), type);
|
||||
}
|
||||
|
||||
protected Transaction sendMoneyToWallet(BigInteger value, AbstractBlockChain.NewBlockType type) throws IOException,
|
||||
ProtocolException, VerificationException {
|
||||
return sendMoneyToWallet(this.wallet, createFakeTx(params, value, myAddress), type);
|
||||
|
@ -22,6 +22,7 @@ import com.google.bitcoin.core.WalletTransaction.Pool;
|
||||
import com.google.bitcoin.crypto.KeyCrypter;
|
||||
import com.google.bitcoin.crypto.KeyCrypterException;
|
||||
import com.google.bitcoin.crypto.KeyCrypterScrypt;
|
||||
import com.google.bitcoin.crypto.TransactionSignature;
|
||||
import com.google.bitcoin.store.WalletProtobufSerializer;
|
||||
import com.google.bitcoin.utils.Threading;
|
||||
import com.google.bitcoin.wallet.KeyTimeCoinSelector;
|
||||
@ -1995,4 +1996,38 @@ public class WalletTest extends TestWithWallet {
|
||||
assertNotNull(tx);
|
||||
assertEquals(200, tx.getInputs().size());
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
@Test
|
||||
public void completeTxPartiallySigned() throws Exception {
|
||||
// Check the wallet will write dummy scriptSigs for inputs that we have only pubkeys for without the privkey.
|
||||
ECKey priv = new ECKey();
|
||||
ECKey pub = new ECKey(null, priv.getPubKey());
|
||||
wallet.addKey(pub);
|
||||
ECKey priv2 = new ECKey();
|
||||
wallet.addKey(priv2);
|
||||
// Send three transactions, with one being an address type and the other being a raw CHECKSIG type pubkey only,
|
||||
// and the final one being a key we do have. We expect the first two inputs to be dummy values and the last
|
||||
// to be signed correctly.
|
||||
Transaction t1 = sendMoneyToWallet(wallet, Utils.CENT, pub.toAddress(params), AbstractBlockChain.NewBlockType.BEST_CHAIN);
|
||||
Transaction t2 = sendMoneyToWallet(wallet, Utils.CENT, pub, AbstractBlockChain.NewBlockType.BEST_CHAIN);
|
||||
Transaction t3 = sendMoneyToWallet(wallet, Utils.CENT, priv2, AbstractBlockChain.NewBlockType.BEST_CHAIN);
|
||||
|
||||
ECKey dest = new ECKey();
|
||||
Wallet.SendRequest req = Wallet.SendRequest.emptyWallet(dest.toAddress(params));
|
||||
assertTrue(wallet.completeTx(req));
|
||||
byte[] dummySig = TransactionSignature.dummy().encodeToBitcoin();
|
||||
// Selected inputs can be in any order.
|
||||
for (int i = 0; i < req.tx.getInputs().size(); i++) {
|
||||
TransactionInput input = req.tx.getInput(i);
|
||||
if (input.getConnectedOutput().getParentTransaction().equals(t1)) {
|
||||
assertArrayEquals(dummySig, input.getScriptSig().getChunks().get(0).data);
|
||||
} else if (input.getConnectedOutput().getParentTransaction().equals(t2)) {
|
||||
assertArrayEquals(dummySig, input.getScriptSig().getChunks().get(0).data);
|
||||
} else if (input.getConnectedOutput().getParentTransaction().equals(t3)) {
|
||||
input.getScriptSig().correctlySpends(req.tx, i, t3.getOutput(0).getScriptPubKey(), true);
|
||||
}
|
||||
}
|
||||
assertTrue(TransactionSignature.isEncodingCanonical(dummySig));
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user