mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-02-13 02:35:52 +00:00
Payment channels: Add KeyParameter support to PaymentChannelServer
I added KeyParameter support to PaymentChannelServer. This results in an API change---`PaymentChannelServer.ServerConnection` now has a new method `getUserKey()`
This commit is contained in:
parent
56f1db8dd8
commit
01cff428d3
@ -17,6 +17,7 @@
|
||||
package org.bitcoinj.protocols.channels;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.util.concurrent.AsyncFunction;
|
||||
import org.bitcoinj.core.*;
|
||||
import org.bitcoinj.protocols.channels.PaymentChannelCloseException.CloseReason;
|
||||
import org.bitcoinj.utils.Threading;
|
||||
@ -29,6 +30,7 @@ import com.google.protobuf.ByteString;
|
||||
import net.jcip.annotations.GuardedBy;
|
||||
import org.bitcoin.paymentchannel.Protos;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.spongycastle.crypto.params.KeyParameter;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Map;
|
||||
@ -117,6 +119,13 @@ public class PaymentChannelServer {
|
||||
*/
|
||||
@Nullable
|
||||
ListenableFuture<ByteString> paymentIncrease(Coin by, Coin to, @Nullable ByteString info);
|
||||
|
||||
/**
|
||||
* <p>Called when a channel is being closed and must be signed, possibly with an encrypted key.</p>
|
||||
* @return A future for the (nullable) KeyParameter for the ECKey, or <code>null</code> if no key is required.
|
||||
*/
|
||||
@Nullable
|
||||
ListenableFuture<KeyParameter> getUserKey();
|
||||
}
|
||||
private final ServerConnection conn;
|
||||
|
||||
@ -520,7 +529,19 @@ public class PaymentChannelServer {
|
||||
// close() on us here below via the stored channel state.
|
||||
// TODO: Strongly separate the lifecycle of the payment channel from the TCP connection in these classes.
|
||||
channelSettling = true;
|
||||
Futures.addCallback(state.close(), new FutureCallback<Transaction>() {
|
||||
ListenableFuture<KeyParameter> keyFuture = conn.getUserKey();
|
||||
ListenableFuture<Transaction> result;
|
||||
if (keyFuture != null) {
|
||||
result = Futures.transform(conn.getUserKey(), new AsyncFunction<KeyParameter, Transaction>() {
|
||||
@Override
|
||||
public ListenableFuture<Transaction> apply(KeyParameter userKey) throws Exception {
|
||||
return state.close(userKey);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
result = state.close();
|
||||
}
|
||||
Futures.addCallback(result, new FutureCallback<Transaction>() {
|
||||
@Override
|
||||
public void onSuccess(Transaction result) {
|
||||
// Send the successfully accepted transaction back to the client.
|
||||
|
@ -28,6 +28,7 @@ import org.bitcoinj.wallet.Wallet;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.protobuf.ByteString;
|
||||
import org.bitcoin.paymentchannel.Protos;
|
||||
import org.spongycastle.crypto.params.KeyParameter;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@ -88,6 +89,12 @@ public class PaymentChannelServerListener {
|
||||
@Override public ListenableFuture<ByteString> paymentIncrease(Coin by, Coin to, @Nullable ByteString info) {
|
||||
return eventHandler.paymentIncrease(by, to, info);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public ListenableFuture<KeyParameter> getUserKey() {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
protobufHandlerListener = new ProtobufConnection.Listener<Protos.TwoWayChannelMessage>() {
|
||||
|
@ -30,6 +30,7 @@ import org.bitcoinj.crypto.TransactionSignature;
|
||||
import org.bitcoinj.script.Script;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.spongycastle.crypto.params.KeyParameter;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@ -300,7 +301,20 @@ public abstract class PaymentChannelServerState {
|
||||
* will never complete, a timeout should be used.
|
||||
* @throws InsufficientMoneyException If the payment tx would have cost more in fees to spend than it is worth.
|
||||
*/
|
||||
public abstract ListenableFuture<Transaction> close() throws InsufficientMoneyException;
|
||||
public ListenableFuture<Transaction> close() throws InsufficientMoneyException {
|
||||
return close(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Closes this channel and broadcasts the highest value payment transaction on the network.</p>
|
||||
*
|
||||
* @param userKey The AES key to use for decryption of the private key. If null then no decryption is required.
|
||||
* @return a future which completes when the provided multisig contract successfully broadcasts, or throws if the
|
||||
* broadcast fails for some reason. Note that if the network simply rejects the transaction, this future
|
||||
* will never complete, a timeout should be used.
|
||||
* @throws InsufficientMoneyException If the payment tx would have cost more in fees to spend than it is worth.
|
||||
*/
|
||||
public abstract ListenableFuture<Transaction> close(@Nullable KeyParameter userKey) throws InsufficientMoneyException;
|
||||
|
||||
/**
|
||||
* Gets the highest payment to ourselves (which we will receive on settle(), not including fees)
|
||||
|
@ -30,7 +30,9 @@ import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.common.util.concurrent.SettableFuture;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.spongycastle.crypto.params.KeyParameter;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Locale;
|
||||
|
||||
import static com.google.common.base.Preconditions.*;
|
||||
@ -163,8 +165,9 @@ public class PaymentChannelV1ServerState extends PaymentChannelServerState {
|
||||
}
|
||||
|
||||
// Signs the first input of the transaction which must spend the multisig contract.
|
||||
private void signMultisigInput(Transaction tx, Transaction.SigHash hashType, boolean anyoneCanPay) {
|
||||
TransactionSignature signature = tx.calculateSignature(0, serverKey, getContractScript(), hashType, anyoneCanPay);
|
||||
private void signMultisigInput(Transaction tx, Transaction.SigHash hashType,
|
||||
boolean anyoneCanPay, @Nullable KeyParameter userKey) {
|
||||
TransactionSignature signature = tx.calculateSignature(0, serverKey, userKey, getContractScript(), hashType, anyoneCanPay);
|
||||
byte[] mySig = signature.encodeToBitcoin();
|
||||
Script scriptSig = ScriptBuilder.createMultiSigInputScriptBytes(ImmutableList.of(bestValueSignature, mySig));
|
||||
tx.getInput(0).setScriptSig(scriptSig);
|
||||
@ -181,13 +184,14 @@ public class PaymentChannelV1ServerState extends PaymentChannelServerState {
|
||||
* simply set the state to {@link State#CLOSED} and let the client handle getting its refund transaction confirmed.
|
||||
* </p>
|
||||
*
|
||||
* @param userKey The AES key to use for decryption of the private key. If null then no decryption is required.
|
||||
* @return a future which completes when the provided multisig contract successfully broadcasts, or throws if the
|
||||
* broadcast fails for some reason. Note that if the network simply rejects the transaction, this future
|
||||
* will never complete, a timeout should be used.
|
||||
* @throws InsufficientMoneyException If the payment tx would have cost more in fees to spend than it is worth.
|
||||
*/
|
||||
@Override
|
||||
public synchronized ListenableFuture<Transaction> close() throws InsufficientMoneyException {
|
||||
public synchronized ListenableFuture<Transaction> close(@Nullable KeyParameter userKey) throws InsufficientMoneyException {
|
||||
if (storedServerChannel != null) {
|
||||
StoredServerChannel temp = storedServerChannel;
|
||||
storedServerChannel = null;
|
||||
@ -217,7 +221,7 @@ public class PaymentChannelV1ServerState extends PaymentChannelServerState {
|
||||
// know how to sign. Note that this signature does actually have to be valid, so we can't use a dummy
|
||||
// signature to save time, because otherwise completeTx will try to re-sign it to make it valid and then
|
||||
// die. We could probably add features to the SendRequest API to make this a bit more efficient.
|
||||
signMultisigInput(tx, Transaction.SigHash.NONE, true);
|
||||
signMultisigInput(tx, Transaction.SigHash.NONE, true, userKey);
|
||||
// Let wallet handle adding additional inputs/fee as necessary.
|
||||
req.shuffleOutputs = false;
|
||||
req.missingSigsMode = Wallet.MissingSigsMode.USE_DUMMY_SIG;
|
||||
@ -230,7 +234,7 @@ public class PaymentChannelV1ServerState extends PaymentChannelServerState {
|
||||
throw new InsufficientMoneyException(feePaidForPayment.subtract(bestValueToMe), msg);
|
||||
}
|
||||
// Now really sign the multisig input.
|
||||
signMultisigInput(tx, Transaction.SigHash.ALL, false);
|
||||
signMultisigInput(tx, Transaction.SigHash.ALL, false, userKey);
|
||||
// Some checks that shouldn't be necessary but it can't hurt to check.
|
||||
tx.verify(); // Sanity check syntax.
|
||||
for (TransactionInput input : tx.getInputs())
|
||||
|
@ -30,7 +30,9 @@ import org.bitcoinj.wallet.SendRequest;
|
||||
import org.bitcoinj.wallet.Wallet;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.spongycastle.crypto.params.KeyParameter;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.math.BigInteger;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
@ -132,8 +134,9 @@ public class PaymentChannelV2ServerState extends PaymentChannelServerState {
|
||||
}
|
||||
|
||||
// Signs the first input of the transaction which must spend the multisig contract.
|
||||
private void signP2SHInput(Transaction tx, Transaction.SigHash hashType, boolean anyoneCanPay) {
|
||||
TransactionSignature signature = tx.calculateSignature(0, serverKey, createP2SHRedeemScript(), hashType, anyoneCanPay);
|
||||
private void signP2SHInput(Transaction tx, Transaction.SigHash hashType,
|
||||
boolean anyoneCanPay, @Nullable KeyParameter userKey) {
|
||||
TransactionSignature signature = tx.calculateSignature(0, serverKey, userKey, createP2SHRedeemScript(), hashType, anyoneCanPay);
|
||||
byte[] mySig = signature.encodeToBitcoin();
|
||||
Script scriptSig = ScriptBuilder.createCLTVPaymentChannelP2SHInput(bestValueSignature, mySig, createP2SHRedeemScript());
|
||||
tx.getInput(0).setScriptSig(scriptSig);
|
||||
@ -142,7 +145,7 @@ public class PaymentChannelV2ServerState extends PaymentChannelServerState {
|
||||
final SettableFuture<Transaction> closedFuture = SettableFuture.create();
|
||||
|
||||
@Override
|
||||
public synchronized ListenableFuture<Transaction> close() throws InsufficientMoneyException {
|
||||
public synchronized ListenableFuture<Transaction> close(@Nullable KeyParameter userKey) throws InsufficientMoneyException {
|
||||
if (storedServerChannel != null) {
|
||||
StoredServerChannel temp = storedServerChannel;
|
||||
storedServerChannel = null;
|
||||
@ -172,7 +175,7 @@ public class PaymentChannelV2ServerState extends PaymentChannelServerState {
|
||||
// know how to sign. Note that this signature does actually have to be valid, so we can't use a dummy
|
||||
// signature to save time, because otherwise completeTx will try to re-sign it to make it valid and then
|
||||
// die. We could probably add features to the SendRequest API to make this a bit more efficient.
|
||||
signP2SHInput(tx, Transaction.SigHash.NONE, true);
|
||||
signP2SHInput(tx, Transaction.SigHash.NONE, true, userKey);
|
||||
// Let wallet handle adding additional inputs/fee as necessary.
|
||||
req.shuffleOutputs = false;
|
||||
req.missingSigsMode = Wallet.MissingSigsMode.USE_DUMMY_SIG;
|
||||
@ -185,7 +188,7 @@ public class PaymentChannelV2ServerState extends PaymentChannelServerState {
|
||||
throw new InsufficientMoneyException(feePaidForPayment.subtract(bestValueToMe), msg);
|
||||
}
|
||||
// Now really sign the multisig input.
|
||||
signP2SHInput(tx, Transaction.SigHash.ALL, false);
|
||||
signP2SHInput(tx, Transaction.SigHash.ALL, false, userKey);
|
||||
// Some checks that shouldn't be necessary but it can't hurt to check.
|
||||
tx.verify(); // Sanity check syntax.
|
||||
for (TransactionInput input : tx.getInputs())
|
||||
|
@ -24,6 +24,7 @@ import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.protobuf.ByteString;
|
||||
import org.bitcoin.paymentchannel.Protos;
|
||||
import org.spongycastle.crypto.params.KeyParameter;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
@ -59,6 +60,12 @@ public class ChannelTestUtils {
|
||||
return Futures.immediateFuture(ByteString.copyFromUtf8(by.toPlainString()));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public ListenableFuture<KeyParameter> getUserKey() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Protos.TwoWayChannelMessage getNextMsg() throws InterruptedException {
|
||||
return (Protos.TwoWayChannelMessage) q.take();
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user