mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-02-13 10:45:51 +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;
|
package org.bitcoinj.protocols.channels;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.google.common.util.concurrent.AsyncFunction;
|
||||||
import org.bitcoinj.core.*;
|
import org.bitcoinj.core.*;
|
||||||
import org.bitcoinj.protocols.channels.PaymentChannelCloseException.CloseReason;
|
import org.bitcoinj.protocols.channels.PaymentChannelCloseException.CloseReason;
|
||||||
import org.bitcoinj.utils.Threading;
|
import org.bitcoinj.utils.Threading;
|
||||||
@ -29,6 +30,7 @@ import com.google.protobuf.ByteString;
|
|||||||
import net.jcip.annotations.GuardedBy;
|
import net.jcip.annotations.GuardedBy;
|
||||||
import org.bitcoin.paymentchannel.Protos;
|
import org.bitcoin.paymentchannel.Protos;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.spongycastle.crypto.params.KeyParameter;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -117,6 +119,13 @@ public class PaymentChannelServer {
|
|||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
ListenableFuture<ByteString> paymentIncrease(Coin by, Coin to, @Nullable ByteString info);
|
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;
|
private final ServerConnection conn;
|
||||||
|
|
||||||
@ -520,7 +529,19 @@ public class PaymentChannelServer {
|
|||||||
// close() on us here below via the stored channel state.
|
// 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.
|
// TODO: Strongly separate the lifecycle of the payment channel from the TCP connection in these classes.
|
||||||
channelSettling = true;
|
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
|
@Override
|
||||||
public void onSuccess(Transaction result) {
|
public void onSuccess(Transaction result) {
|
||||||
// Send the successfully accepted transaction back to the client.
|
// 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.common.util.concurrent.ListenableFuture;
|
||||||
import com.google.protobuf.ByteString;
|
import com.google.protobuf.ByteString;
|
||||||
import org.bitcoin.paymentchannel.Protos;
|
import org.bitcoin.paymentchannel.Protos;
|
||||||
|
import org.spongycastle.crypto.params.KeyParameter;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
@ -88,6 +89,12 @@ public class PaymentChannelServerListener {
|
|||||||
@Override public ListenableFuture<ByteString> paymentIncrease(Coin by, Coin to, @Nullable ByteString info) {
|
@Override public ListenableFuture<ByteString> paymentIncrease(Coin by, Coin to, @Nullable ByteString info) {
|
||||||
return eventHandler.paymentIncrease(by, to, info);
|
return eventHandler.paymentIncrease(by, to, info);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public ListenableFuture<KeyParameter> getUserKey() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
protobufHandlerListener = new ProtobufConnection.Listener<Protos.TwoWayChannelMessage>() {
|
protobufHandlerListener = new ProtobufConnection.Listener<Protos.TwoWayChannelMessage>() {
|
||||||
|
@ -30,6 +30,7 @@ import org.bitcoinj.crypto.TransactionSignature;
|
|||||||
import org.bitcoinj.script.Script;
|
import org.bitcoinj.script.Script;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.spongycastle.crypto.params.KeyParameter;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
@ -300,7 +301,20 @@ public abstract class PaymentChannelServerState {
|
|||||||
* will never complete, a timeout should be used.
|
* 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.
|
* @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)
|
* 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 com.google.common.util.concurrent.SettableFuture;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.spongycastle.crypto.params.KeyParameter;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.*;
|
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.
|
// Signs the first input of the transaction which must spend the multisig contract.
|
||||||
private void signMultisigInput(Transaction tx, Transaction.SigHash hashType, boolean anyoneCanPay) {
|
private void signMultisigInput(Transaction tx, Transaction.SigHash hashType,
|
||||||
TransactionSignature signature = tx.calculateSignature(0, serverKey, getContractScript(), hashType, anyoneCanPay);
|
boolean anyoneCanPay, @Nullable KeyParameter userKey) {
|
||||||
|
TransactionSignature signature = tx.calculateSignature(0, serverKey, userKey, getContractScript(), hashType, anyoneCanPay);
|
||||||
byte[] mySig = signature.encodeToBitcoin();
|
byte[] mySig = signature.encodeToBitcoin();
|
||||||
Script scriptSig = ScriptBuilder.createMultiSigInputScriptBytes(ImmutableList.of(bestValueSignature, mySig));
|
Script scriptSig = ScriptBuilder.createMultiSigInputScriptBytes(ImmutableList.of(bestValueSignature, mySig));
|
||||||
tx.getInput(0).setScriptSig(scriptSig);
|
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.
|
* simply set the state to {@link State#CLOSED} and let the client handle getting its refund transaction confirmed.
|
||||||
* </p>
|
* </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
|
* @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
|
* broadcast fails for some reason. Note that if the network simply rejects the transaction, this future
|
||||||
* will never complete, a timeout should be used.
|
* 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.
|
* @throws InsufficientMoneyException If the payment tx would have cost more in fees to spend than it is worth.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public synchronized ListenableFuture<Transaction> close() throws InsufficientMoneyException {
|
public synchronized ListenableFuture<Transaction> close(@Nullable KeyParameter userKey) throws InsufficientMoneyException {
|
||||||
if (storedServerChannel != null) {
|
if (storedServerChannel != null) {
|
||||||
StoredServerChannel temp = storedServerChannel;
|
StoredServerChannel temp = storedServerChannel;
|
||||||
storedServerChannel = null;
|
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
|
// 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
|
// 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.
|
// 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.
|
// Let wallet handle adding additional inputs/fee as necessary.
|
||||||
req.shuffleOutputs = false;
|
req.shuffleOutputs = false;
|
||||||
req.missingSigsMode = Wallet.MissingSigsMode.USE_DUMMY_SIG;
|
req.missingSigsMode = Wallet.MissingSigsMode.USE_DUMMY_SIG;
|
||||||
@ -230,7 +234,7 @@ public class PaymentChannelV1ServerState extends PaymentChannelServerState {
|
|||||||
throw new InsufficientMoneyException(feePaidForPayment.subtract(bestValueToMe), msg);
|
throw new InsufficientMoneyException(feePaidForPayment.subtract(bestValueToMe), msg);
|
||||||
}
|
}
|
||||||
// Now really sign the multisig input.
|
// 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.
|
// Some checks that shouldn't be necessary but it can't hurt to check.
|
||||||
tx.verify(); // Sanity check syntax.
|
tx.verify(); // Sanity check syntax.
|
||||||
for (TransactionInput input : tx.getInputs())
|
for (TransactionInput input : tx.getInputs())
|
||||||
|
@ -30,7 +30,9 @@ import org.bitcoinj.wallet.SendRequest;
|
|||||||
import org.bitcoinj.wallet.Wallet;
|
import org.bitcoinj.wallet.Wallet;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.spongycastle.crypto.params.KeyParameter;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Locale;
|
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.
|
// Signs the first input of the transaction which must spend the multisig contract.
|
||||||
private void signP2SHInput(Transaction tx, Transaction.SigHash hashType, boolean anyoneCanPay) {
|
private void signP2SHInput(Transaction tx, Transaction.SigHash hashType,
|
||||||
TransactionSignature signature = tx.calculateSignature(0, serverKey, createP2SHRedeemScript(), hashType, anyoneCanPay);
|
boolean anyoneCanPay, @Nullable KeyParameter userKey) {
|
||||||
|
TransactionSignature signature = tx.calculateSignature(0, serverKey, userKey, createP2SHRedeemScript(), hashType, anyoneCanPay);
|
||||||
byte[] mySig = signature.encodeToBitcoin();
|
byte[] mySig = signature.encodeToBitcoin();
|
||||||
Script scriptSig = ScriptBuilder.createCLTVPaymentChannelP2SHInput(bestValueSignature, mySig, createP2SHRedeemScript());
|
Script scriptSig = ScriptBuilder.createCLTVPaymentChannelP2SHInput(bestValueSignature, mySig, createP2SHRedeemScript());
|
||||||
tx.getInput(0).setScriptSig(scriptSig);
|
tx.getInput(0).setScriptSig(scriptSig);
|
||||||
@ -142,7 +145,7 @@ public class PaymentChannelV2ServerState extends PaymentChannelServerState {
|
|||||||
final SettableFuture<Transaction> closedFuture = SettableFuture.create();
|
final SettableFuture<Transaction> closedFuture = SettableFuture.create();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized ListenableFuture<Transaction> close() throws InsufficientMoneyException {
|
public synchronized ListenableFuture<Transaction> close(@Nullable KeyParameter userKey) throws InsufficientMoneyException {
|
||||||
if (storedServerChannel != null) {
|
if (storedServerChannel != null) {
|
||||||
StoredServerChannel temp = storedServerChannel;
|
StoredServerChannel temp = storedServerChannel;
|
||||||
storedServerChannel = null;
|
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
|
// 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
|
// 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.
|
// 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.
|
// Let wallet handle adding additional inputs/fee as necessary.
|
||||||
req.shuffleOutputs = false;
|
req.shuffleOutputs = false;
|
||||||
req.missingSigsMode = Wallet.MissingSigsMode.USE_DUMMY_SIG;
|
req.missingSigsMode = Wallet.MissingSigsMode.USE_DUMMY_SIG;
|
||||||
@ -185,7 +188,7 @@ public class PaymentChannelV2ServerState extends PaymentChannelServerState {
|
|||||||
throw new InsufficientMoneyException(feePaidForPayment.subtract(bestValueToMe), msg);
|
throw new InsufficientMoneyException(feePaidForPayment.subtract(bestValueToMe), msg);
|
||||||
}
|
}
|
||||||
// Now really sign the multisig input.
|
// 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.
|
// Some checks that shouldn't be necessary but it can't hurt to check.
|
||||||
tx.verify(); // Sanity check syntax.
|
tx.verify(); // Sanity check syntax.
|
||||||
for (TransactionInput input : tx.getInputs())
|
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.common.util.concurrent.ListenableFuture;
|
||||||
import com.google.protobuf.ByteString;
|
import com.google.protobuf.ByteString;
|
||||||
import org.bitcoin.paymentchannel.Protos;
|
import org.bitcoin.paymentchannel.Protos;
|
||||||
|
import org.spongycastle.crypto.params.KeyParameter;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import java.util.concurrent.BlockingQueue;
|
import java.util.concurrent.BlockingQueue;
|
||||||
@ -59,6 +60,12 @@ public class ChannelTestUtils {
|
|||||||
return Futures.immediateFuture(ByteString.copyFromUtf8(by.toPlainString()));
|
return Futures.immediateFuture(ByteString.copyFromUtf8(by.toPlainString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public ListenableFuture<KeyParameter> getUserKey() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public Protos.TwoWayChannelMessage getNextMsg() throws InterruptedException {
|
public Protos.TwoWayChannelMessage getNextMsg() throws InterruptedException {
|
||||||
return (Protos.TwoWayChannelMessage) q.take();
|
return (Protos.TwoWayChannelMessage) q.take();
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user