3
0
mirror of https://github.com/Qortal/altcoinj.git synced 2025-02-14 11:15:51 +00:00

PaymentChannelClient support encrypted wallets. Store will upgrade forward gracefully.

This commit is contained in:
Jarl Fransson 2014-11-09 20:21:48 +01:00 committed by ollekullberg
parent c017070398
commit df00b1e27e
11 changed files with 334 additions and 111 deletions

View File

@ -225,7 +225,8 @@ class ConnectionHandler implements MessageWriteTarget {
} catch (Exception e) { } catch (Exception e) {
// This can happen eg if the channel closes while the thread is about to get killed // This can happen eg if the channel closes while the thread is about to get killed
// (ClosedByInterruptException), or if handler.parser.receiveBytes throws something // (ClosedByInterruptException), or if handler.parser.receiveBytes throws something
log.error("Error handling SelectionKey: {}", Throwables.getRootCause(e).getMessage()); Throwable t = Throwables.getRootCause(e);
log.error("Error handling SelectionKey: {}", t.getMessage() != null ? t.getMessage() : t.getClass().getName());
handler.closeConnection(); handler.closeConnection();
} }
} }

View File

@ -1,5 +1,5 @@
// Generated by the protocol buffer compiler. DO NOT EDIT! // Generated by the protocol buffer compiler. DO NOT EDIT!
// source: storedclientpaymentchannel.proto // source: core/src/storedclientpaymentchannel.proto
package org.bitcoinj.protocols.channels; package org.bitcoinj.protocols.channels;
@ -735,13 +735,31 @@ public final class ClientState {
*/ */
com.google.protobuf.ByteString getRefundTransaction(); com.google.protobuf.ByteString getRefundTransaction();
// required bytes myPublicKey = 8;
/**
* <code>required bytes myPublicKey = 8;</code>
*/
boolean hasMyPublicKey();
/**
* <code>required bytes myPublicKey = 8;</code>
*/
com.google.protobuf.ByteString getMyPublicKey();
// required bytes myKey = 4; // required bytes myKey = 4;
/** /**
* <code>required bytes myKey = 4;</code> * <code>required bytes myKey = 4;</code>
*
* <pre>
* Deprecated, key is stored in the wallet, and found using myPublicKey;
* </pre>
*/ */
boolean hasMyKey(); boolean hasMyKey();
/** /**
* <code>required bytes myKey = 4;</code> * <code>required bytes myKey = 4;</code>
*
* <pre>
* Deprecated, key is stored in the wallet, and found using myPublicKey;
* </pre>
*/ */
com.google.protobuf.ByteString getMyKey(); com.google.protobuf.ByteString getMyKey();
@ -859,25 +877,30 @@ public final class ClientState {
break; break;
} }
case 34: { case 34: {
bitField0_ |= 0x00000008; bitField0_ |= 0x00000010;
myKey_ = input.readBytes(); myKey_ = input.readBytes();
break; break;
} }
case 40: { case 40: {
bitField0_ |= 0x00000010; bitField0_ |= 0x00000020;
valueToMe_ = input.readUInt64(); valueToMe_ = input.readUInt64();
break; break;
} }
case 48: { case 48: {
bitField0_ |= 0x00000020; bitField0_ |= 0x00000040;
refundFees_ = input.readUInt64(); refundFees_ = input.readUInt64();
break; break;
} }
case 58: { case 58: {
bitField0_ |= 0x00000040; bitField0_ |= 0x00000080;
closeTransactionHash_ = input.readBytes(); closeTransactionHash_ = input.readBytes();
break; break;
} }
case 66: {
bitField0_ |= 0x00000008;
myPublicKey_ = input.readBytes();
break;
}
} }
} }
} catch (com.google.protobuf.InvalidProtocolBufferException e) { } catch (com.google.protobuf.InvalidProtocolBufferException e) {
@ -966,17 +989,41 @@ public final class ClientState {
return refundTransaction_; return refundTransaction_;
} }
// required bytes myPublicKey = 8;
public static final int MYPUBLICKEY_FIELD_NUMBER = 8;
private com.google.protobuf.ByteString myPublicKey_;
/**
* <code>required bytes myPublicKey = 8;</code>
*/
public boolean hasMyPublicKey() {
return ((bitField0_ & 0x00000008) == 0x00000008);
}
/**
* <code>required bytes myPublicKey = 8;</code>
*/
public com.google.protobuf.ByteString getMyPublicKey() {
return myPublicKey_;
}
// required bytes myKey = 4; // required bytes myKey = 4;
public static final int MYKEY_FIELD_NUMBER = 4; public static final int MYKEY_FIELD_NUMBER = 4;
private com.google.protobuf.ByteString myKey_; private com.google.protobuf.ByteString myKey_;
/** /**
* <code>required bytes myKey = 4;</code> * <code>required bytes myKey = 4;</code>
*
* <pre>
* Deprecated, key is stored in the wallet, and found using myPublicKey;
* </pre>
*/ */
public boolean hasMyKey() { public boolean hasMyKey() {
return ((bitField0_ & 0x00000008) == 0x00000008); return ((bitField0_ & 0x00000010) == 0x00000010);
} }
/** /**
* <code>required bytes myKey = 4;</code> * <code>required bytes myKey = 4;</code>
*
* <pre>
* Deprecated, key is stored in the wallet, and found using myPublicKey;
* </pre>
*/ */
public com.google.protobuf.ByteString getMyKey() { public com.google.protobuf.ByteString getMyKey() {
return myKey_; return myKey_;
@ -989,7 +1036,7 @@ public final class ClientState {
* <code>required uint64 valueToMe = 5;</code> * <code>required uint64 valueToMe = 5;</code>
*/ */
public boolean hasValueToMe() { public boolean hasValueToMe() {
return ((bitField0_ & 0x00000010) == 0x00000010); return ((bitField0_ & 0x00000020) == 0x00000020);
} }
/** /**
* <code>required uint64 valueToMe = 5;</code> * <code>required uint64 valueToMe = 5;</code>
@ -1005,7 +1052,7 @@ public final class ClientState {
* <code>required uint64 refundFees = 6;</code> * <code>required uint64 refundFees = 6;</code>
*/ */
public boolean hasRefundFees() { public boolean hasRefundFees() {
return ((bitField0_ & 0x00000020) == 0x00000020); return ((bitField0_ & 0x00000040) == 0x00000040);
} }
/** /**
* <code>required uint64 refundFees = 6;</code> * <code>required uint64 refundFees = 6;</code>
@ -1027,7 +1074,7 @@ public final class ClientState {
* </pre> * </pre>
*/ */
public boolean hasCloseTransactionHash() { public boolean hasCloseTransactionHash() {
return ((bitField0_ & 0x00000040) == 0x00000040); return ((bitField0_ & 0x00000080) == 0x00000080);
} }
/** /**
* <code>optional bytes closeTransactionHash = 7;</code> * <code>optional bytes closeTransactionHash = 7;</code>
@ -1046,6 +1093,7 @@ public final class ClientState {
id_ = com.google.protobuf.ByteString.EMPTY; id_ = com.google.protobuf.ByteString.EMPTY;
contractTransaction_ = com.google.protobuf.ByteString.EMPTY; contractTransaction_ = com.google.protobuf.ByteString.EMPTY;
refundTransaction_ = com.google.protobuf.ByteString.EMPTY; refundTransaction_ = com.google.protobuf.ByteString.EMPTY;
myPublicKey_ = com.google.protobuf.ByteString.EMPTY;
myKey_ = com.google.protobuf.ByteString.EMPTY; myKey_ = com.google.protobuf.ByteString.EMPTY;
valueToMe_ = 0L; valueToMe_ = 0L;
refundFees_ = 0L; refundFees_ = 0L;
@ -1068,6 +1116,10 @@ public final class ClientState {
memoizedIsInitialized = 0; memoizedIsInitialized = 0;
return false; return false;
} }
if (!hasMyPublicKey()) {
memoizedIsInitialized = 0;
return false;
}
if (!hasMyKey()) { if (!hasMyKey()) {
memoizedIsInitialized = 0; memoizedIsInitialized = 0;
return false; return false;
@ -1096,18 +1148,21 @@ public final class ClientState {
if (((bitField0_ & 0x00000004) == 0x00000004)) { if (((bitField0_ & 0x00000004) == 0x00000004)) {
output.writeBytes(3, refundTransaction_); output.writeBytes(3, refundTransaction_);
} }
if (((bitField0_ & 0x00000008) == 0x00000008)) { if (((bitField0_ & 0x00000010) == 0x00000010)) {
output.writeBytes(4, myKey_); output.writeBytes(4, myKey_);
} }
if (((bitField0_ & 0x00000010) == 0x00000010)) { if (((bitField0_ & 0x00000020) == 0x00000020)) {
output.writeUInt64(5, valueToMe_); output.writeUInt64(5, valueToMe_);
} }
if (((bitField0_ & 0x00000020) == 0x00000020)) { if (((bitField0_ & 0x00000040) == 0x00000040)) {
output.writeUInt64(6, refundFees_); output.writeUInt64(6, refundFees_);
} }
if (((bitField0_ & 0x00000040) == 0x00000040)) { if (((bitField0_ & 0x00000080) == 0x00000080)) {
output.writeBytes(7, closeTransactionHash_); output.writeBytes(7, closeTransactionHash_);
} }
if (((bitField0_ & 0x00000008) == 0x00000008)) {
output.writeBytes(8, myPublicKey_);
}
getUnknownFields().writeTo(output); getUnknownFields().writeTo(output);
} }
@ -1129,22 +1184,26 @@ public final class ClientState {
size += com.google.protobuf.CodedOutputStream size += com.google.protobuf.CodedOutputStream
.computeBytesSize(3, refundTransaction_); .computeBytesSize(3, refundTransaction_);
} }
if (((bitField0_ & 0x00000008) == 0x00000008)) { if (((bitField0_ & 0x00000010) == 0x00000010)) {
size += com.google.protobuf.CodedOutputStream size += com.google.protobuf.CodedOutputStream
.computeBytesSize(4, myKey_); .computeBytesSize(4, myKey_);
} }
if (((bitField0_ & 0x00000010) == 0x00000010)) { if (((bitField0_ & 0x00000020) == 0x00000020)) {
size += com.google.protobuf.CodedOutputStream size += com.google.protobuf.CodedOutputStream
.computeUInt64Size(5, valueToMe_); .computeUInt64Size(5, valueToMe_);
} }
if (((bitField0_ & 0x00000020) == 0x00000020)) { if (((bitField0_ & 0x00000040) == 0x00000040)) {
size += com.google.protobuf.CodedOutputStream size += com.google.protobuf.CodedOutputStream
.computeUInt64Size(6, refundFees_); .computeUInt64Size(6, refundFees_);
} }
if (((bitField0_ & 0x00000040) == 0x00000040)) { if (((bitField0_ & 0x00000080) == 0x00000080)) {
size += com.google.protobuf.CodedOutputStream size += com.google.protobuf.CodedOutputStream
.computeBytesSize(7, closeTransactionHash_); .computeBytesSize(7, closeTransactionHash_);
} }
if (((bitField0_ & 0x00000008) == 0x00000008)) {
size += com.google.protobuf.CodedOutputStream
.computeBytesSize(8, myPublicKey_);
}
size += getUnknownFields().getSerializedSize(); size += getUnknownFields().getSerializedSize();
memoizedSerializedSize = size; memoizedSerializedSize = size;
return size; return size;
@ -1272,14 +1331,16 @@ public final class ClientState {
bitField0_ = (bitField0_ & ~0x00000002); bitField0_ = (bitField0_ & ~0x00000002);
refundTransaction_ = com.google.protobuf.ByteString.EMPTY; refundTransaction_ = com.google.protobuf.ByteString.EMPTY;
bitField0_ = (bitField0_ & ~0x00000004); bitField0_ = (bitField0_ & ~0x00000004);
myKey_ = com.google.protobuf.ByteString.EMPTY; myPublicKey_ = com.google.protobuf.ByteString.EMPTY;
bitField0_ = (bitField0_ & ~0x00000008); bitField0_ = (bitField0_ & ~0x00000008);
valueToMe_ = 0L; myKey_ = com.google.protobuf.ByteString.EMPTY;
bitField0_ = (bitField0_ & ~0x00000010); bitField0_ = (bitField0_ & ~0x00000010);
refundFees_ = 0L; valueToMe_ = 0L;
bitField0_ = (bitField0_ & ~0x00000020); bitField0_ = (bitField0_ & ~0x00000020);
closeTransactionHash_ = com.google.protobuf.ByteString.EMPTY; refundFees_ = 0L;
bitField0_ = (bitField0_ & ~0x00000040); bitField0_ = (bitField0_ & ~0x00000040);
closeTransactionHash_ = com.google.protobuf.ByteString.EMPTY;
bitField0_ = (bitField0_ & ~0x00000080);
return this; return this;
} }
@ -1323,18 +1384,22 @@ public final class ClientState {
if (((from_bitField0_ & 0x00000008) == 0x00000008)) { if (((from_bitField0_ & 0x00000008) == 0x00000008)) {
to_bitField0_ |= 0x00000008; to_bitField0_ |= 0x00000008;
} }
result.myKey_ = myKey_; result.myPublicKey_ = myPublicKey_;
if (((from_bitField0_ & 0x00000010) == 0x00000010)) { if (((from_bitField0_ & 0x00000010) == 0x00000010)) {
to_bitField0_ |= 0x00000010; to_bitField0_ |= 0x00000010;
} }
result.valueToMe_ = valueToMe_; result.myKey_ = myKey_;
if (((from_bitField0_ & 0x00000020) == 0x00000020)) { if (((from_bitField0_ & 0x00000020) == 0x00000020)) {
to_bitField0_ |= 0x00000020; to_bitField0_ |= 0x00000020;
} }
result.refundFees_ = refundFees_; result.valueToMe_ = valueToMe_;
if (((from_bitField0_ & 0x00000040) == 0x00000040)) { if (((from_bitField0_ & 0x00000040) == 0x00000040)) {
to_bitField0_ |= 0x00000040; to_bitField0_ |= 0x00000040;
} }
result.refundFees_ = refundFees_;
if (((from_bitField0_ & 0x00000080) == 0x00000080)) {
to_bitField0_ |= 0x00000080;
}
result.closeTransactionHash_ = closeTransactionHash_; result.closeTransactionHash_ = closeTransactionHash_;
result.bitField0_ = to_bitField0_; result.bitField0_ = to_bitField0_;
onBuilt(); onBuilt();
@ -1361,6 +1426,9 @@ public final class ClientState {
if (other.hasRefundTransaction()) { if (other.hasRefundTransaction()) {
setRefundTransaction(other.getRefundTransaction()); setRefundTransaction(other.getRefundTransaction());
} }
if (other.hasMyPublicKey()) {
setMyPublicKey(other.getMyPublicKey());
}
if (other.hasMyKey()) { if (other.hasMyKey()) {
setMyKey(other.getMyKey()); setMyKey(other.getMyKey());
} }
@ -1390,6 +1458,10 @@ public final class ClientState {
return false; return false;
} }
if (!hasMyPublicKey()) {
return false;
}
if (!hasMyKey()) { if (!hasMyKey()) {
return false; return false;
@ -1532,37 +1604,89 @@ public final class ClientState {
return this; return this;
} }
// required bytes myPublicKey = 8;
private com.google.protobuf.ByteString myPublicKey_ = com.google.protobuf.ByteString.EMPTY;
/**
* <code>required bytes myPublicKey = 8;</code>
*/
public boolean hasMyPublicKey() {
return ((bitField0_ & 0x00000008) == 0x00000008);
}
/**
* <code>required bytes myPublicKey = 8;</code>
*/
public com.google.protobuf.ByteString getMyPublicKey() {
return myPublicKey_;
}
/**
* <code>required bytes myPublicKey = 8;</code>
*/
public Builder setMyPublicKey(com.google.protobuf.ByteString value) {
if (value == null) {
throw new NullPointerException();
}
bitField0_ |= 0x00000008;
myPublicKey_ = value;
onChanged();
return this;
}
/**
* <code>required bytes myPublicKey = 8;</code>
*/
public Builder clearMyPublicKey() {
bitField0_ = (bitField0_ & ~0x00000008);
myPublicKey_ = getDefaultInstance().getMyPublicKey();
onChanged();
return this;
}
// required bytes myKey = 4; // required bytes myKey = 4;
private com.google.protobuf.ByteString myKey_ = com.google.protobuf.ByteString.EMPTY; private com.google.protobuf.ByteString myKey_ = com.google.protobuf.ByteString.EMPTY;
/** /**
* <code>required bytes myKey = 4;</code> * <code>required bytes myKey = 4;</code>
*
* <pre>
* Deprecated, key is stored in the wallet, and found using myPublicKey;
* </pre>
*/ */
public boolean hasMyKey() { public boolean hasMyKey() {
return ((bitField0_ & 0x00000008) == 0x00000008); return ((bitField0_ & 0x00000010) == 0x00000010);
} }
/** /**
* <code>required bytes myKey = 4;</code> * <code>required bytes myKey = 4;</code>
*
* <pre>
* Deprecated, key is stored in the wallet, and found using myPublicKey;
* </pre>
*/ */
public com.google.protobuf.ByteString getMyKey() { public com.google.protobuf.ByteString getMyKey() {
return myKey_; return myKey_;
} }
/** /**
* <code>required bytes myKey = 4;</code> * <code>required bytes myKey = 4;</code>
*
* <pre>
* Deprecated, key is stored in the wallet, and found using myPublicKey;
* </pre>
*/ */
public Builder setMyKey(com.google.protobuf.ByteString value) { public Builder setMyKey(com.google.protobuf.ByteString value) {
if (value == null) { if (value == null) {
throw new NullPointerException(); throw new NullPointerException();
} }
bitField0_ |= 0x00000008; bitField0_ |= 0x00000010;
myKey_ = value; myKey_ = value;
onChanged(); onChanged();
return this; return this;
} }
/** /**
* <code>required bytes myKey = 4;</code> * <code>required bytes myKey = 4;</code>
*
* <pre>
* Deprecated, key is stored in the wallet, and found using myPublicKey;
* </pre>
*/ */
public Builder clearMyKey() { public Builder clearMyKey() {
bitField0_ = (bitField0_ & ~0x00000008); bitField0_ = (bitField0_ & ~0x00000010);
myKey_ = getDefaultInstance().getMyKey(); myKey_ = getDefaultInstance().getMyKey();
onChanged(); onChanged();
return this; return this;
@ -1574,7 +1698,7 @@ public final class ClientState {
* <code>required uint64 valueToMe = 5;</code> * <code>required uint64 valueToMe = 5;</code>
*/ */
public boolean hasValueToMe() { public boolean hasValueToMe() {
return ((bitField0_ & 0x00000010) == 0x00000010); return ((bitField0_ & 0x00000020) == 0x00000020);
} }
/** /**
* <code>required uint64 valueToMe = 5;</code> * <code>required uint64 valueToMe = 5;</code>
@ -1586,7 +1710,7 @@ public final class ClientState {
* <code>required uint64 valueToMe = 5;</code> * <code>required uint64 valueToMe = 5;</code>
*/ */
public Builder setValueToMe(long value) { public Builder setValueToMe(long value) {
bitField0_ |= 0x00000010; bitField0_ |= 0x00000020;
valueToMe_ = value; valueToMe_ = value;
onChanged(); onChanged();
return this; return this;
@ -1595,7 +1719,7 @@ public final class ClientState {
* <code>required uint64 valueToMe = 5;</code> * <code>required uint64 valueToMe = 5;</code>
*/ */
public Builder clearValueToMe() { public Builder clearValueToMe() {
bitField0_ = (bitField0_ & ~0x00000010); bitField0_ = (bitField0_ & ~0x00000020);
valueToMe_ = 0L; valueToMe_ = 0L;
onChanged(); onChanged();
return this; return this;
@ -1607,7 +1731,7 @@ public final class ClientState {
* <code>required uint64 refundFees = 6;</code> * <code>required uint64 refundFees = 6;</code>
*/ */
public boolean hasRefundFees() { public boolean hasRefundFees() {
return ((bitField0_ & 0x00000020) == 0x00000020); return ((bitField0_ & 0x00000040) == 0x00000040);
} }
/** /**
* <code>required uint64 refundFees = 6;</code> * <code>required uint64 refundFees = 6;</code>
@ -1619,7 +1743,7 @@ public final class ClientState {
* <code>required uint64 refundFees = 6;</code> * <code>required uint64 refundFees = 6;</code>
*/ */
public Builder setRefundFees(long value) { public Builder setRefundFees(long value) {
bitField0_ |= 0x00000020; bitField0_ |= 0x00000040;
refundFees_ = value; refundFees_ = value;
onChanged(); onChanged();
return this; return this;
@ -1628,7 +1752,7 @@ public final class ClientState {
* <code>required uint64 refundFees = 6;</code> * <code>required uint64 refundFees = 6;</code>
*/ */
public Builder clearRefundFees() { public Builder clearRefundFees() {
bitField0_ = (bitField0_ & ~0x00000020); bitField0_ = (bitField0_ & ~0x00000040);
refundFees_ = 0L; refundFees_ = 0L;
onChanged(); onChanged();
return this; return this;
@ -1646,7 +1770,7 @@ public final class ClientState {
* </pre> * </pre>
*/ */
public boolean hasCloseTransactionHash() { public boolean hasCloseTransactionHash() {
return ((bitField0_ & 0x00000040) == 0x00000040); return ((bitField0_ & 0x00000080) == 0x00000080);
} }
/** /**
* <code>optional bytes closeTransactionHash = 7;</code> * <code>optional bytes closeTransactionHash = 7;</code>
@ -1673,7 +1797,7 @@ public final class ClientState {
if (value == null) { if (value == null) {
throw new NullPointerException(); throw new NullPointerException();
} }
bitField0_ |= 0x00000040; bitField0_ |= 0x00000080;
closeTransactionHash_ = value; closeTransactionHash_ = value;
onChanged(); onChanged();
return this; return this;
@ -1688,7 +1812,7 @@ public final class ClientState {
* </pre> * </pre>
*/ */
public Builder clearCloseTransactionHash() { public Builder clearCloseTransactionHash() {
bitField0_ = (bitField0_ & ~0x00000040); bitField0_ = (bitField0_ & ~0x00000080);
closeTransactionHash_ = getDefaultInstance().getCloseTransactionHash(); closeTransactionHash_ = getDefaultInstance().getCloseTransactionHash();
onChanged(); onChanged();
return this; return this;
@ -1724,16 +1848,17 @@ public final class ClientState {
descriptor; descriptor;
static { static {
java.lang.String[] descriptorData = { java.lang.String[] descriptorData = {
"\n storedclientpaymentchannel.proto\022\017paym" + "\n)core/src/storedclientpaymentchannel.pr" +
"entchannels\"\\\n\033StoredClientPaymentChanne" + "oto\022\017paymentchannels\"\\\n\033StoredClientPaym" +
"ls\022=\n\010channels\030\001 \003(\0132+.paymentchannels.S" + "entChannels\022=\n\010channels\030\001 \003(\0132+.paymentc" +
"toredClientPaymentChannel\"\264\001\n\032StoredClie" + "hannels.StoredClientPaymentChannel\"\311\001\n\032S" +
"ntPaymentChannel\022\n\n\002id\030\001 \002(\014\022\033\n\023contract" + "toredClientPaymentChannel\022\n\n\002id\030\001 \002(\014\022\033\n" +
"Transaction\030\002 \002(\014\022\031\n\021refundTransaction\030\003" + "\023contractTransaction\030\002 \002(\014\022\031\n\021refundTran" +
" \002(\014\022\r\n\005myKey\030\004 \002(\014\022\021\n\tvalueToMe\030\005 \002(\004\022\022" + "saction\030\003 \002(\014\022\023\n\013myPublicKey\030\010 \002(\014\022\r\n\005my" +
"\n\nrefundFees\030\006 \002(\004\022\034\n\024closeTransactionHa" + "Key\030\004 \002(\014\022\021\n\tvalueToMe\030\005 \002(\004\022\022\n\nrefundFe" +
"sh\030\007 \001(\014B.\n\037org.bitcoinj.protocols.chann" + "es\030\006 \002(\004\022\034\n\024closeTransactionHash\030\007 \001(\014B." +
"elsB\013ClientState" "\n\037org.bitcoinj.protocols.channelsB\013Clien",
"tState"
}; };
com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner = com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() { new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() {
@ -1751,7 +1876,7 @@ public final class ClientState {
internal_static_paymentchannels_StoredClientPaymentChannel_fieldAccessorTable = new internal_static_paymentchannels_StoredClientPaymentChannel_fieldAccessorTable = new
com.google.protobuf.GeneratedMessage.FieldAccessorTable( com.google.protobuf.GeneratedMessage.FieldAccessorTable(
internal_static_paymentchannels_StoredClientPaymentChannel_descriptor, internal_static_paymentchannels_StoredClientPaymentChannel_descriptor,
new java.lang.String[] { "Id", "ContractTransaction", "RefundTransaction", "MyKey", "ValueToMe", "RefundFees", "CloseTransactionHash", }); new java.lang.String[] { "Id", "ContractTransaction", "RefundTransaction", "MyPublicKey", "MyKey", "ValueToMe", "RefundFees", "CloseTransactionHash", });
return null; return null;
} }
}; };

View File

@ -17,11 +17,13 @@
package org.bitcoinj.protocols.channels; package org.bitcoinj.protocols.channels;
import org.bitcoinj.core.Coin; import org.bitcoinj.core.Coin;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.InsufficientMoneyException; import org.bitcoinj.core.InsufficientMoneyException;
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;
@ -82,9 +84,12 @@ public interface IPaymentChannelClient {
* ({@link PaymentChannelClientConnection#state()}.getTotalValue()) * ({@link PaymentChannelClientConnection#state()}.getTotalValue())
* @throws IllegalStateException If the channel has been closed or is not yet open * @throws IllegalStateException If the channel has been closed or is not yet open
* (see {@link PaymentChannelClientConnection#getChannelOpenFuture()} for the second) * (see {@link PaymentChannelClientConnection#getChannelOpenFuture()} for the second)
* @throws ECKey.KeyIsEncryptedException If the keys are encrypted and no AES key has been provided,
* @return a future that completes when the server acknowledges receipt and acceptance of the payment. * @return a future that completes when the server acknowledges receipt and acceptance of the payment.
*/ */
ListenableFuture<PaymentIncrementAck> incrementPayment(Coin size, @Nullable ByteString info) throws ValueOutOfRangeException, IllegalStateException; ListenableFuture<PaymentIncrementAck> incrementPayment(Coin size, @Nullable ByteString info,
@Nullable KeyParameter userKey)
throws ValueOutOfRangeException, IllegalStateException, ECKey.KeyIsEncryptedException;
/** /**
* Implements the connection between this client and the server, providing an interface which allows messages to be * Implements the connection between this client and the server, providing an interface which allows messages to be

View File

@ -28,9 +28,9 @@ 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.Date;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
@ -93,6 +93,9 @@ public class PaymentChannelClient implements IPaymentChannelClient {
private Coin missing; private Coin missing;
// key to decrypt myKey, if it is encrypted, during setup.
private KeyParameter userKeySetup;
private final long timeWindow; private final long timeWindow;
@GuardedBy("lock") private long minPayment; @GuardedBy("lock") private long minPayment;
@ -127,7 +130,7 @@ public class PaymentChannelClient implements IPaymentChannelClient {
* the server) * the server)
*/ */
public PaymentChannelClient(Wallet wallet, ECKey myKey, Coin maxValue, Sha256Hash serverId, ClientConnection conn) { public PaymentChannelClient(Wallet wallet, ECKey myKey, Coin maxValue, Sha256Hash serverId, ClientConnection conn) {
this(wallet,myKey,maxValue,serverId, DEFAULT_TIME_WINDOW, conn); this(wallet,myKey,maxValue,serverId, DEFAULT_TIME_WINDOW, null, conn);
} }
/** /**
@ -146,10 +149,12 @@ public class PaymentChannelClient implements IPaymentChannelClient {
* @param timeWindow The time in seconds, relative to now, on how long this channel should be kept open. Note that is is * @param timeWindow The time in seconds, relative to now, on how long this channel should be kept open. Note that is is
* a proposal to the server. The server may in turn propose something different. * a proposal to the server. The server may in turn propose something different.
* See {@link org.bitcoinj.protocols.channels.IPaymentChannelClient.ClientConnection#acceptExpireTime(long)} * See {@link org.bitcoinj.protocols.channels.IPaymentChannelClient.ClientConnection#acceptExpireTime(long)}
* @param userKeySetup Key derived from a user password, used to decrypt myKey, if it is encrypted, during setup.
* @param conn A callback listener which represents the connection to the server (forwards messages we generate to * @param conn A callback listener which represents the connection to the server (forwards messages we generate to
* the server) * the server)
*/ */
public PaymentChannelClient(Wallet wallet, ECKey myKey, Coin maxValue, Sha256Hash serverId, long timeWindow, ClientConnection conn) { public PaymentChannelClient(Wallet wallet, ECKey myKey, Coin maxValue, Sha256Hash serverId, long timeWindow,
@Nullable KeyParameter userKeySetup, ClientConnection conn) {
this.wallet = checkNotNull(wallet); this.wallet = checkNotNull(wallet);
this.myKey = checkNotNull(myKey); this.myKey = checkNotNull(myKey);
this.maxValue = checkNotNull(maxValue); this.maxValue = checkNotNull(maxValue);
@ -157,6 +162,7 @@ public class PaymentChannelClient implements IPaymentChannelClient {
checkState(timeWindow >= 0); checkState(timeWindow >= 0);
this.timeWindow = timeWindow; this.timeWindow = timeWindow;
this.conn = checkNotNull(conn); this.conn = checkNotNull(conn);
this.userKeySetup = userKeySetup;
} }
/** /**
@ -171,9 +177,13 @@ public class PaymentChannelClient implements IPaymentChannelClient {
@Nullable @Nullable
@GuardedBy("lock") @GuardedBy("lock")
private CloseReason receiveInitiate(Protos.Initiate initiate, Coin contractValue, Protos.Error.Builder errorBuilder) throws VerificationException, InsufficientMoneyException { private CloseReason receiveInitiate(Protos.Initiate initiate, Coin contractValue, Protos.Error.Builder errorBuilder)
throws VerificationException, InsufficientMoneyException, ECKey.KeyIsEncryptedException {
log.info("Got INITIATE message:\n{}", initiate.toString()); log.info("Got INITIATE message:\n{}", initiate.toString());
if (wallet.isEncrypted() && this.userKeySetup == null)
throw new ECKey.KeyIsEncryptedException();
final long expireTime = initiate.getExpireTimeSecs(); final long expireTime = initiate.getExpireTimeSecs();
checkState( expireTime >= 0 && initiate.getMinAcceptedChannelSize() >= 0); checkState( expireTime >= 0 && initiate.getMinAcceptedChannelSize() >= 0);
@ -207,7 +217,7 @@ public class PaymentChannelClient implements IPaymentChannelClient {
throw new VerificationException("Server gave us a non-canonical public key, protocol error."); throw new VerificationException("Server gave us a non-canonical public key, protocol error.");
state = new PaymentChannelClientState(wallet, myKey, ECKey.fromPublicOnly(pubKeyBytes), contractValue, expireTime); state = new PaymentChannelClientState(wallet, myKey, ECKey.fromPublicOnly(pubKeyBytes), contractValue, expireTime);
try { try {
state.initiate(); state.initiate(userKeySetup);
} catch (ValueOutOfRangeException e) { } catch (ValueOutOfRangeException e) {
log.error("Value out of range when trying to initiate", e); log.error("Value out of range when trying to initiate", e);
errorBuilder.setCode(Protos.Error.ErrorCode.CHANNEL_VALUE_TOO_LARGE); errorBuilder.setCode(Protos.Error.ErrorCode.CHANNEL_VALUE_TOO_LARGE);
@ -228,11 +238,11 @@ public class PaymentChannelClient implements IPaymentChannelClient {
} }
@GuardedBy("lock") @GuardedBy("lock")
private void receiveRefund(Protos.TwoWayChannelMessage refundMsg) throws VerificationException { private void receiveRefund(Protos.TwoWayChannelMessage refundMsg, @Nullable KeyParameter userKey) throws VerificationException {
checkState(step == InitStep.WAITING_FOR_REFUND_RETURN && refundMsg.hasReturnRefund()); checkState(step == InitStep.WAITING_FOR_REFUND_RETURN && refundMsg.hasReturnRefund());
log.info("Got RETURN_REFUND message, providing signed contract"); log.info("Got RETURN_REFUND message, providing signed contract");
Protos.ReturnRefund returnedRefund = refundMsg.getReturnRefund(); Protos.ReturnRefund returnedRefund = refundMsg.getReturnRefund();
state.provideRefundSignature(returnedRefund.getSignature().toByteArray()); state.provideRefundSignature(returnedRefund.getSignature().toByteArray(), userKey);
step = InitStep.WAITING_FOR_CHANNEL_OPEN; step = InitStep.WAITING_FOR_CHANNEL_OPEN;
// Before we can send the server the contract (ie send it to the network), we must ensure that our refund // Before we can send the server the contract (ie send it to the network), we must ensure that our refund
@ -244,7 +254,7 @@ public class PaymentChannelClient implements IPaymentChannelClient {
try { try {
// Make an initial payment of the dust limit, and put it into the message as well. The size of the // Make an initial payment of the dust limit, and put it into the message as well. The size of the
// server-requested dust limit was already sanity checked by this point. // server-requested dust limit was already sanity checked by this point.
PaymentChannelClientState.IncrementedPayment payment = state().incrementPaymentBy(Coin.valueOf(minPayment)); PaymentChannelClientState.IncrementedPayment payment = state().incrementPaymentBy(Coin.valueOf(minPayment), userKey);
Protos.UpdatePayment.Builder initialMsg = contractMsg.getInitialPaymentBuilder(); Protos.UpdatePayment.Builder initialMsg = contractMsg.getInitialPaymentBuilder();
initialMsg.setSignature(ByteString.copyFrom(payment.signature.encodeToBitcoin())); initialMsg.setSignature(ByteString.copyFrom(payment.signature.encodeToBitcoin()));
initialMsg.setClientChangeValue(state.getValueRefunded().value); initialMsg.setClientChangeValue(state.getValueRefunded().value);
@ -311,7 +321,9 @@ public class PaymentChannelClient implements IPaymentChannelClient {
log.error("Initiate failed with error: {}", errorBuilder.build().toString()); log.error("Initiate failed with error: {}", errorBuilder.build().toString());
break; break;
case RETURN_REFUND: case RETURN_REFUND:
receiveRefund(msg); receiveRefund(msg, userKeySetup);
// Key not used anymore
userKeySetup = null;
return; return;
case CHANNEL_OPEN: case CHANNEL_OPEN:
receiveChannelOpen(); receiveChannelOpen();
@ -498,7 +510,7 @@ public class PaymentChannelClient implements IPaymentChannelClient {
* (see {@link PaymentChannelClientConnection#getChannelOpenFuture()} for the second) * (see {@link PaymentChannelClientConnection#getChannelOpenFuture()} for the second)
*/ */
public ListenableFuture<PaymentIncrementAck> incrementPayment(Coin size) throws ValueOutOfRangeException, IllegalStateException { public ListenableFuture<PaymentIncrementAck> incrementPayment(Coin size) throws ValueOutOfRangeException, IllegalStateException {
return incrementPayment(size, null); return incrementPayment(size, null, null);
} }
/** /**
@ -510,22 +522,28 @@ public class PaymentChannelClient implements IPaymentChannelClient {
* *
* @param size How many satoshis to increment the payment by (note: not the new total). * @param size How many satoshis to increment the payment by (note: not the new total).
* @param info Information about this update, used to extend this protocol. * @param info Information about this update, used to extend this protocol.
* @param userKey Key derived from a user password, needed for any signing when the wallet is encrypted.
* The wallet KeyCrypter is assumed.
* @return a future that completes when the server acknowledges receipt and acceptance of the payment. * @return a future that completes when the server acknowledges receipt and acceptance of the payment.
* @throws ValueOutOfRangeException If the size is negative or would pay more than this channel's total value * @throws ValueOutOfRangeException If the size is negative or would pay more than this channel's total value
* ({@link PaymentChannelClientConnection#state()}.getTotalValue()) * ({@link PaymentChannelClientConnection#state()}.getTotalValue())
* @throws IllegalStateException If the channel has been closed or is not yet open * @throws IllegalStateException If the channel has been closed or is not yet open
* (see {@link PaymentChannelClientConnection#getChannelOpenFuture()} for the second) * (see {@link PaymentChannelClientConnection#getChannelOpenFuture()} for the second)
* @throws ECKey.KeyIsEncryptedException If the keys are encrypted and no AES key has been provided,
*/ */
@Override @Override
public ListenableFuture<PaymentIncrementAck> incrementPayment(Coin size, @Nullable ByteString info) throws ValueOutOfRangeException, IllegalStateException { public ListenableFuture<PaymentIncrementAck> incrementPayment(Coin size, @Nullable ByteString info, @Nullable KeyParameter userKey)
throws ValueOutOfRangeException, IllegalStateException, ECKey.KeyIsEncryptedException {
lock.lock(); lock.lock();
try { try {
if (state() == null || !connectionOpen || step != InitStep.CHANNEL_OPEN) if (state() == null || !connectionOpen || step != InitStep.CHANNEL_OPEN)
throw new IllegalStateException("Channel is not fully initialized/has already been closed"); throw new IllegalStateException("Channel is not fully initialized/has already been closed");
if (increasePaymentFuture != null) if (increasePaymentFuture != null)
throw new IllegalStateException("Already incrementing paying, wait for previous payment to complete."); throw new IllegalStateException("Already incrementing paying, wait for previous payment to complete.");
if (wallet.isEncrypted() && userKey == null)
throw new ECKey.KeyIsEncryptedException();
PaymentChannelClientState.IncrementedPayment payment = state().incrementPaymentBy(size); PaymentChannelClientState.IncrementedPayment payment = state().incrementPaymentBy(size, userKey);
Protos.UpdatePayment.Builder updatePaymentBuilder = Protos.UpdatePayment.newBuilder() Protos.UpdatePayment.Builder updatePaymentBuilder = Protos.UpdatePayment.newBuilder()
.setSignature(ByteString.copyFrom(payment.signature.encodeToBitcoin())) .setSignature(ByteString.copyFrom(payment.signature.encodeToBitcoin()))
.setClientChangeValue(state.getValueRefunded().value); .setClientChangeValue(state.getValueRefunded().value);

View File

@ -29,7 +29,9 @@ import com.google.common.util.concurrent.SettableFuture;
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 java.io.IOException; import java.io.IOException;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
@ -45,14 +47,15 @@ public class PaymentChannelClientConnection {
/** /**
* Attempts to open a new connection to and open a payment channel with the given host and port, blocking until the * Attempts to open a new connection to and open a payment channel with the given host and port, blocking until the
* connection is open. The server is requested to keep the channel open for {@link org.bitcoinj.protocols.channels.PaymentChannelClient#DEFAULT_TIME_WINDOW} * connection is open. The server is requested to keep the channel open for
* {@link org.bitcoinj.protocols.channels.PaymentChannelClient#DEFAULT_TIME_WINDOW}
* seconds. If the server proposes a longer time the channel will be closed. * seconds. If the server proposes a longer time the channel will be closed.
* *
* @param server The host/port pair where the server is listening. * @param server The host/port pair where the server is listening.
* @param timeoutSeconds The connection timeout and read timeout during initialization. This should be large enough * @param timeoutSeconds The connection timeout and read timeout during initialization. This should be large enough
* to accommodate ECDSA signature operations and network latency. * to accommodate ECDSA signature operations and network latency.
* @param wallet The wallet which will be paid from, and where completed transactions will be committed. * @param wallet The wallet which will be paid from, and where completed transactions will be committed.
* Must already have a {@link StoredPaymentChannelClientStates} object in its extensions set. * Must be unencrypted. Must already have a {@link StoredPaymentChannelClientStates} object in its extensions set.
* @param myKey A freshly generated keypair used for the multisig contract and refund output. * @param myKey A freshly generated keypair used for the multisig contract and refund output.
* @param maxValue The maximum value this channel is allowed to request * @param maxValue The maximum value this channel is allowed to request
* @param serverId A unique ID which is used to attempt reopening of an existing channel. * @param serverId A unique ID which is used to attempt reopening of an existing channel.
@ -65,7 +68,8 @@ public class PaymentChannelClientConnection {
*/ */
public PaymentChannelClientConnection(InetSocketAddress server, int timeoutSeconds, Wallet wallet, ECKey myKey, public PaymentChannelClientConnection(InetSocketAddress server, int timeoutSeconds, Wallet wallet, ECKey myKey,
Coin maxValue, String serverId) throws IOException, ValueOutOfRangeException { Coin maxValue, String serverId) throws IOException, ValueOutOfRangeException {
this(server, timeoutSeconds, wallet, myKey, maxValue, serverId, PaymentChannelClient.DEFAULT_TIME_WINDOW); this(server, timeoutSeconds, wallet, myKey, maxValue, serverId,
PaymentChannelClient.DEFAULT_TIME_WINDOW, null);
} }
/** /**
@ -77,7 +81,8 @@ public class PaymentChannelClientConnection {
* @param timeoutSeconds The connection timeout and read timeout during initialization. This should be large enough * @param timeoutSeconds The connection timeout and read timeout during initialization. This should be large enough
* to accommodate ECDSA signature operations and network latency. * to accommodate ECDSA signature operations and network latency.
* @param wallet The wallet which will be paid from, and where completed transactions will be committed. * @param wallet The wallet which will be paid from, and where completed transactions will be committed.
* Must already have a {@link StoredPaymentChannelClientStates} object in its extensions set. * Can be encrypted if user key is supplied when needed. Must already have a
* {@link StoredPaymentChannelClientStates} object in its extensions set.
* @param myKey A freshly generated keypair used for the multisig contract and refund output. * @param myKey A freshly generated keypair used for the multisig contract and refund output.
* @param maxValue The maximum value this channel is allowed to request * @param maxValue The maximum value this channel is allowed to request
* @param serverId A unique ID which is used to attempt reopening of an existing channel. * @param serverId A unique ID which is used to attempt reopening of an existing channel.
@ -85,16 +90,19 @@ public class PaymentChannelClientConnection {
* API, this should also probably encompass some caller UID to avoid applications opening channels * API, this should also probably encompass some caller UID to avoid applications opening channels
* which were created by others. * which were created by others.
* @param timeWindow The time in seconds, relative to now, on how long this channel should be kept open. * @param timeWindow The time in seconds, relative to now, on how long this channel should be kept open.
* @param userKeySetup Key derived from a user password, used to decrypt myKey, if it is encrypted, during setup.
* *
* @throws IOException if there's an issue using the network. * @throws IOException if there's an issue using the network.
* @throws ValueOutOfRangeException if the balance of wallet is lower than maxValue. * @throws ValueOutOfRangeException if the balance of wallet is lower than maxValue.
*/ */
public PaymentChannelClientConnection(InetSocketAddress server, int timeoutSeconds, Wallet wallet, ECKey myKey, public PaymentChannelClientConnection(InetSocketAddress server, int timeoutSeconds, Wallet wallet, ECKey myKey,
Coin maxValue, String serverId, final long timeWindow) throws IOException, ValueOutOfRangeException { Coin maxValue, String serverId, final long timeWindow,
@Nullable KeyParameter userKeySetup)
throws IOException, ValueOutOfRangeException {
// Glue the object which vends/ingests protobuf messages in order to manage state to the network object which // Glue the object which vends/ingests protobuf messages in order to manage state to the network object which
// reads/writes them to the wire in length prefixed form. // reads/writes them to the wire in length prefixed form.
channelClient = new PaymentChannelClient(wallet, myKey, maxValue, Sha256Hash.create(serverId.getBytes()), timeWindow, channelClient = new PaymentChannelClient(wallet, myKey, maxValue, Sha256Hash.create(serverId.getBytes()), timeWindow,
new PaymentChannelClient.ClientConnection() { userKeySetup, new PaymentChannelClient.ClientConnection() {
@Override @Override
public void sendToServer(Protos.TwoWayChannelMessage msg) { public void sendToServer(Protos.TwoWayChannelMessage msg) {
wireParser.write(msg); wireParser.write(msg);
@ -170,7 +178,7 @@ public class PaymentChannelClientConnection {
* (see {@link PaymentChannelClientConnection#getChannelOpenFuture()} for the second) * (see {@link PaymentChannelClientConnection#getChannelOpenFuture()} for the second)
*/ */
public ListenableFuture<PaymentIncrementAck> incrementPayment(Coin size) throws ValueOutOfRangeException, IllegalStateException { public ListenableFuture<PaymentIncrementAck> incrementPayment(Coin size) throws ValueOutOfRangeException, IllegalStateException {
return channelClient.incrementPayment(size, null); return channelClient.incrementPayment(size, null, null);
} }
/** /**
* Increments the total value which we pay the server. * Increments the total value which we pay the server.
@ -182,8 +190,28 @@ public class PaymentChannelClientConnection {
* @throws IllegalStateException If the channel has been closed or is not yet open * @throws IllegalStateException If the channel has been closed or is not yet open
* (see {@link PaymentChannelClientConnection#getChannelOpenFuture()} for the second) * (see {@link PaymentChannelClientConnection#getChannelOpenFuture()} for the second)
*/ */
/*
public ListenableFuture<PaymentIncrementAck> incrementPayment(Coin size, ByteString info) throws ValueOutOfRangeException, IllegalStateException { public ListenableFuture<PaymentIncrementAck> incrementPayment(Coin size, ByteString info) throws ValueOutOfRangeException, IllegalStateException {
return channelClient.incrementPayment(size, info); return channelClient.incrementPayment(size, info, null);
}
*/
/**
* Increments the total value which we pay the server.
*
* @param size How many satoshis to increment the payment by (note: not the new total).
* @param info Information about this payment increment, used to extend this protocol.
* @param userKey Key derived from a user password, needed for any signing when the wallet is encrypted.
* The wallet KeyCrypter is assumed.
* @throws ValueOutOfRangeException If the size is negative or would pay more than this channel's total value
* ({@link PaymentChannelClientConnection#state()}.getTotalValue())
* @throws IllegalStateException If the channel has been closed or is not yet open
* (see {@link PaymentChannelClientConnection#getChannelOpenFuture()} for the second)
*/
public ListenableFuture<PaymentIncrementAck> incrementPayment(Coin size,
@Nullable ByteString info,
@Nullable KeyParameter userKey)
throws ValueOutOfRangeException, IllegalStateException {
return channelClient.incrementPayment(size, info, userKey);
} }
/** /**

View File

@ -22,6 +22,7 @@ import org.bitcoinj.script.Script;
import org.bitcoinj.script.ScriptBuilder; import org.bitcoinj.script.ScriptBuilder;
import org.bitcoinj.utils.Threading; import org.bitcoinj.utils.Threading;
import org.bitcoinj.wallet.AllowUnconfirmedCoinSelector; import org.bitcoinj.wallet.AllowUnconfirmedCoinSelector;
import org.spongycastle.crypto.params.KeyParameter;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Throwables; import com.google.common.base.Throwables;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
@ -31,6 +32,7 @@ import com.google.common.util.concurrent.ListenableFuture;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import java.util.List; import java.util.List;
import static com.google.common.base.Preconditions.*; import static com.google.common.base.Preconditions.*;
@ -59,7 +61,7 @@ import static com.google.common.base.Preconditions.*;
* INITIATED and creates the initial multi-sig contract and refund transaction. If the wallet has insufficient funds an * INITIATED and creates the initial multi-sig contract and refund transaction. If the wallet has insufficient funds an
* exception will be thrown at this point. Once this is done, call * exception will be thrown at this point. Once this is done, call
* {@link PaymentChannelClientState#getIncompleteRefundTransaction()} and pass the resultant transaction through to the * {@link PaymentChannelClientState#getIncompleteRefundTransaction()} and pass the resultant transaction through to the
* server. Once you have retrieved the signature, use {@link PaymentChannelClientState#provideRefundSignature(byte[])}. * server. Once you have retrieved the signature, use {@link PaymentChannelClientState#provideRefundSignature(byte[], KeyParameter)}.
* You must then call {@link PaymentChannelClientState#storeChannelInWallet(Sha256Hash)} to store the refund transaction * You must then call {@link PaymentChannelClientState#storeChannelInWallet(Sha256Hash)} to store the refund transaction
* in the wallet, protecting you against a malicious server attempting to destroy all your coins. At this point, you can * in the wallet, protecting you against a malicious server attempting to destroy all your coins. At this point, you can
* provide the server with the multi-sig contract (via {@link PaymentChannelClientState#getMultisigContract()}) safely. * provide the server with the multi-sig contract (via {@link PaymentChannelClientState#getMultisigContract()}) safely.
@ -149,7 +151,7 @@ public class PaymentChannelClientState {
* @param myKey a freshly generated private key for this channel. * @param myKey a freshly generated private key for this channel.
* @param serverMultisigKey a public key retrieved from the server used for the initial multisig contract * @param serverMultisigKey a public key retrieved from the server used for the initial multisig contract
* @param value how many satoshis to put into this contract. If the channel reaches this limit, it must be closed. * @param value how many satoshis to put into this contract. If the channel reaches this limit, it must be closed.
* It is suggested you use at least {@link Utils#CENT} to avoid paying fees if you need to spend the refund transaction * It is suggested you use at least {@link Coin#CENT} to avoid paying fees if you need to spend the refund transaction
* @param expiryTimeInSeconds At what point (UNIX timestamp +/- a few hours) the channel will expire * @param expiryTimeInSeconds At what point (UNIX timestamp +/- a few hours) the channel will expire
* *
* @throws VerificationException If either myKey's pubkey or serverMultisigKey's pubkey are non-canonical (ie invalid) * @throws VerificationException If either myKey's pubkey or serverMultisigKey's pubkey are non-canonical (ie invalid)
@ -235,7 +237,23 @@ public class PaymentChannelClientState {
* @throws ValueOutOfRangeException if the value being used is too small to be accepted by the network * @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 * @throws InsufficientMoneyException if the wallet doesn't contain enough balance to initiate
*/ */
public synchronized void initiate() throws ValueOutOfRangeException, InsufficientMoneyException { public void initiate() throws ValueOutOfRangeException, InsufficientMoneyException {
initiate(null);
}
/**
* 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(org.bitcoinj.core.Wallet.SendRequest)}.
* By default unconfirmed coins are allowed to be used, as for micropayments the risk should be relatively low.
* @param userKey Key derived from a user password, needed for any signing when the wallet is encrypted.
* The wallet KeyCrypter is assumed.
*
* @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(@Nullable KeyParameter userKey) throws ValueOutOfRangeException, InsufficientMoneyException {
final NetworkParameters params = wallet.getParams(); final NetworkParameters params = wallet.getParams();
Transaction template = new Transaction(params); Transaction template = new Transaction(params);
// We always place the client key before the server key because, if either side wants some privacy, they can // We always place the client key before the server key because, if either side wants some privacy, they can
@ -251,6 +269,7 @@ public class PaymentChannelClientState {
req.coinSelector = AllowUnconfirmedCoinSelector.get(); req.coinSelector = AllowUnconfirmedCoinSelector.get();
editContractSendRequest(req); editContractSendRequest(req);
req.shuffleOutputs = false; // TODO: Fix things so shuffling is usable. req.shuffleOutputs = false; // TODO: Fix things so shuffling is usable.
req.aesKey = userKey;
wallet.completeTx(req); wallet.completeTx(req);
Coin multisigFee = req.tx.getFee(); Coin multisigFee = req.tx.getFee();
multisigContract = req.tx; multisigContract = req.tx;
@ -291,7 +310,7 @@ public class PaymentChannelClientState {
/** /**
* Returns the transaction that locks the money to the agreement of both parties. Do not mutate the result. * Returns the transaction that locks the money to the agreement of both parties. Do not mutate the result.
* Once this step is done, you can use {@link PaymentChannelClientState#incrementPaymentBy(Coin)} to * Once this step is done, you can use {@link PaymentChannelClientState#incrementPaymentBy(Coin, KeyParameter)} to
* start paying the server. * start paying the server.
*/ */
public synchronized Transaction getMultisigContract() { public synchronized Transaction getMultisigContract() {
@ -304,7 +323,7 @@ public class PaymentChannelClientState {
/** /**
* Returns a partially signed (invalid) refund transaction that should be passed to the server. Once the server * Returns a partially signed (invalid) refund transaction that should be passed to the server. Once the server
* has checked it out and provided its own signature, call * has checked it out and provided its own signature, call
* {@link PaymentChannelClientState#provideRefundSignature(byte[])} with the result. * {@link PaymentChannelClientState#provideRefundSignature(byte[], KeyParameter)} with the result.
*/ */
public synchronized Transaction getIncompleteRefundTransaction() { public synchronized Transaction getIncompleteRefundTransaction() {
checkState(refundTx != null); checkState(refundTx != null);
@ -322,7 +341,8 @@ public class PaymentChannelClientState {
* transaction are automatically committed to wallet so that it can handle broadcasting the refund transaction at * transaction are automatically committed to wallet so that it can handle broadcasting the refund transaction at
* the appropriate time if necessary.</p> * the appropriate time if necessary.</p>
*/ */
public synchronized void provideRefundSignature(byte[] theirSignature) throws VerificationException { public synchronized void provideRefundSignature(byte[] theirSignature, @Nullable KeyParameter userKey)
throws VerificationException {
checkNotNull(theirSignature); checkNotNull(theirSignature);
checkState(state == State.WAITING_FOR_SIGNED_REFUND); checkState(state == State.WAITING_FOR_SIGNED_REFUND);
TransactionSignature theirSig = TransactionSignature.decodeFromBitcoin(theirSignature, true); TransactionSignature theirSig = TransactionSignature.decodeFromBitcoin(theirSignature, true);
@ -336,7 +356,8 @@ public class PaymentChannelClientState {
throw new RuntimeException(e); // Cannot happen: we built this ourselves. throw new RuntimeException(e); // Cannot happen: we built this ourselves.
} }
TransactionSignature ourSignature = TransactionSignature ourSignature =
refundTx.calculateSignature(0, myKey, multisigScript, Transaction.SigHash.ALL, false); refundTx.calculateSignature(0, myKey.maybeDecrypt(userKey),
multisigScript, Transaction.SigHash.ALL, false);
// Insert the signatures. // Insert the signatures.
Script scriptSig = ScriptBuilder.createMultiSigInputScript(ourSignature, theirSig); Script scriptSig = ScriptBuilder.createMultiSigInputScript(ourSignature, theirSig);
log.info("Refund scriptSig: {}", scriptSig); log.info("Refund scriptSig: {}", scriptSig);
@ -390,7 +411,8 @@ public class PaymentChannelClientState {
* @throws ValueOutOfRangeException If size is negative or the channel does not have sufficient money in it to * @throws ValueOutOfRangeException If size is negative or the channel does not have sufficient money in it to
* complete this payment. * complete this payment.
*/ */
public synchronized IncrementedPayment incrementPaymentBy(Coin size) throws ValueOutOfRangeException { public synchronized IncrementedPayment incrementPaymentBy(Coin size, @Nullable KeyParameter userKey)
throws ValueOutOfRangeException {
checkState(state == State.READY); checkState(state == State.READY);
checkNotExpired(); checkNotExpired();
checkNotNull(size); // Validity of size will be checked by makeUnsignedChannelContract. checkNotNull(size); // Validity of size will be checked by makeUnsignedChannelContract.
@ -413,7 +435,7 @@ public class PaymentChannelClientState {
mode = Transaction.SigHash.NONE; mode = Transaction.SigHash.NONE;
else else
mode = Transaction.SigHash.SINGLE; mode = Transaction.SigHash.SINGLE;
TransactionSignature sig = tx.calculateSignature(0, myKey, multisigScript, mode, true); TransactionSignature sig = tx.calculateSignature(0, myKey.maybeDecrypt(userKey), multisigScript, mode, true);
valueToMe = newValueToMe; valueToMe = newValueToMe;
updateChannelInWallet(); updateChannelInWallet();
IncrementedPayment payment = new IncrementedPayment(); IncrementedPayment payment = new IncrementedPayment();
@ -506,7 +528,7 @@ public class PaymentChannelClientState {
/** /**
* Once the servers signature over the refund transaction has been received and provided using * Once the servers signature over the refund transaction has been received and provided using
* {@link PaymentChannelClientState#provideRefundSignature(byte[])} then this * {@link PaymentChannelClientState#provideRefundSignature(byte[], KeyParameter)} then this
* method can be called to receive the now valid and broadcastable refund transaction. * method can be called to receive the now valid and broadcastable refund transaction.
*/ */
public synchronized Transaction getCompletedRefundTransaction() { public synchronized Transaction getCompletedRefundTransaction() {

View File

@ -264,13 +264,14 @@ public class StoredPaymentChannelClientStates implements WalletExtension {
// First a few asserts to make sure things won't break // First a few asserts to make sure things won't break
checkState(channel.valueToMe.signum() >= 0 && channel.valueToMe.compareTo(NetworkParameters.MAX_MONEY) < 0); checkState(channel.valueToMe.signum() >= 0 && channel.valueToMe.compareTo(NetworkParameters.MAX_MONEY) < 0);
checkState(channel.refundFees.signum() >= 0 && channel.refundFees.compareTo(NetworkParameters.MAX_MONEY) < 0); checkState(channel.refundFees.signum() >= 0 && channel.refundFees.compareTo(NetworkParameters.MAX_MONEY) < 0);
checkNotNull(channel.myKey.getPrivKeyBytes()); checkNotNull(channel.myKey.getPubKey());
checkState(channel.refund.getConfidence().getSource() == TransactionConfidence.Source.SELF); checkState(channel.refund.getConfidence().getSource() == TransactionConfidence.Source.SELF);
final ClientState.StoredClientPaymentChannel.Builder value = ClientState.StoredClientPaymentChannel.newBuilder() final ClientState.StoredClientPaymentChannel.Builder value = ClientState.StoredClientPaymentChannel.newBuilder()
.setId(ByteString.copyFrom(channel.id.getBytes())) .setId(ByteString.copyFrom(channel.id.getBytes()))
.setContractTransaction(ByteString.copyFrom(channel.contract.bitcoinSerialize())) .setContractTransaction(ByteString.copyFrom(channel.contract.bitcoinSerialize()))
.setRefundTransaction(ByteString.copyFrom(channel.refund.bitcoinSerialize())) .setRefundTransaction(ByteString.copyFrom(channel.refund.bitcoinSerialize()))
.setMyKey(ByteString.copyFrom(channel.myKey.getPrivKeyBytes())) .setMyKey(ByteString.copyFrom(new byte[0])) // Not used, but protobuf message requires
.setMyPublicKey(ByteString.copyFrom(channel.myKey.getPubKey()))
.setValueToMe(channel.valueToMe.value) .setValueToMe(channel.valueToMe.value)
.setRefundFees(channel.refundFees.value); .setRefundFees(channel.refundFees.value);
if (channel.close != null) if (channel.close != null)
@ -294,10 +295,13 @@ public class StoredPaymentChannelClientStates implements WalletExtension {
for (ClientState.StoredClientPaymentChannel storedState : states.getChannelsList()) { for (ClientState.StoredClientPaymentChannel storedState : states.getChannelsList()) {
Transaction refundTransaction = new Transaction(params, storedState.getRefundTransaction().toByteArray()); Transaction refundTransaction = new Transaction(params, storedState.getRefundTransaction().toByteArray());
refundTransaction.getConfidence().setSource(TransactionConfidence.Source.SELF); refundTransaction.getConfidence().setSource(TransactionConfidence.Source.SELF);
ECKey myKey = (storedState.getMyKey().isEmpty()) ?
containingWallet.findKeyFromPubKey(storedState.getMyPublicKey().toByteArray()) :
ECKey.fromPrivate(storedState.getMyKey().toByteArray());
StoredClientChannel channel = new StoredClientChannel(new Sha256Hash(storedState.getId().toByteArray()), StoredClientChannel channel = new StoredClientChannel(new Sha256Hash(storedState.getId().toByteArray()),
new Transaction(params, storedState.getContractTransaction().toByteArray()), new Transaction(params, storedState.getContractTransaction().toByteArray()),
refundTransaction, refundTransaction,
ECKey.fromPrivate(storedState.getMyKey().toByteArray()), myKey,
Coin.valueOf(storedState.getValueToMe()), Coin.valueOf(storedState.getValueToMe()),
Coin.valueOf(storedState.getRefundFees()), false); Coin.valueOf(storedState.getRefundFees()), false);
if (storedState.hasCloseTransactionHash()) { if (storedState.hasCloseTransactionHash()) {

View File

@ -39,6 +39,8 @@ message StoredClientPaymentChannel {
required bytes id = 1; required bytes id = 1;
required bytes contractTransaction = 2; required bytes contractTransaction = 2;
required bytes refundTransaction = 3; required bytes refundTransaction = 3;
required bytes myPublicKey = 8;
// Deprecated, key is already stored in the wallet, and found using myPublicKey;
required bytes myKey = 4; required bytes myKey = 4;
required uint64 valueToMe = 5; required uint64 valueToMe = 5;
required uint64 refundFees = 6; required uint64 refundFees = 6;

View File

@ -29,6 +29,7 @@ import org.bitcoin.paymentchannel.Protos;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.spongycastle.crypto.params.KeyParameter;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
@ -116,6 +117,20 @@ public class ChannelConnectionTest extends TestWithWallet {
@Test @Test
public void testSimpleChannel() throws Exception { public void testSimpleChannel() throws Exception {
exectuteSimpleChannelTest(null);
}
@Test
public void testEncryptedClientWallet() throws Exception {
// Encrypt the client wallet
String mySecretPw = "MySecret";
wallet.encrypt(mySecretPw);
KeyParameter userKeySetup = wallet.getKeyCrypter().deriveKey(mySecretPw);
exectuteSimpleChannelTest(userKeySetup);
}
public void exectuteSimpleChannelTest(KeyParameter userKeySetup) throws Exception {
// Test with network code and without any issues. We'll broadcast two txns: multisig contract and settle transaction. // Test with network code and without any issues. We'll broadcast two txns: multisig contract and settle transaction.
final SettableFuture<ListenableFuture<PaymentChannelServerState>> serverCloseFuture = SettableFuture.create(); final SettableFuture<ListenableFuture<PaymentChannelServerState>> serverCloseFuture = SettableFuture.create();
final SettableFuture<Sha256Hash> channelOpenFuture = SettableFuture.create(); final SettableFuture<Sha256Hash> channelOpenFuture = SettableFuture.create();
@ -147,7 +162,7 @@ public class ChannelConnectionTest extends TestWithWallet {
server.bindAndStart(4243); server.bindAndStart(4243);
PaymentChannelClientConnection client = new PaymentChannelClientConnection( PaymentChannelClientConnection client = new PaymentChannelClientConnection(
new InetSocketAddress("localhost", 4243), 30, wallet, myKey, COIN, ""); new InetSocketAddress("localhost", 4243), 30, wallet, myKey, COIN, "", PaymentChannelClient.DEFAULT_TIME_WINDOW, userKeySetup);
// Wait for the multi-sig tx to be transmitted. // Wait for the multi-sig tx to be transmitted.
broadcastTxPause.release(); broadcastTxPause.release();
@ -177,7 +192,7 @@ public class ChannelConnectionTest extends TestWithWallet {
q.take().assertPair(amount, null); q.take().assertPair(amount, null);
for (String info : new String[] {null, "one", "two"} ) { for (String info : new String[] {null, "one", "two"} ) {
final ByteString bytes = (info==null) ? null :ByteString.copyFromUtf8(info); final ByteString bytes = (info==null) ? null :ByteString.copyFromUtf8(info);
final PaymentIncrementAck ack = client.incrementPayment(CENT, bytes).get(); final PaymentIncrementAck ack = client.incrementPayment(CENT, bytes, userKeySetup).get();
if (info != null) { if (info != null) {
final ByteString ackInfo = ack.getInfo(); final ByteString ackInfo = ack.getInfo();
assertNotNull("Ack info is null", ackInfo); assertNotNull("Ack info is null", ackInfo);

View File

@ -6,6 +6,7 @@ import org.easymock.Capture;
import org.easymock.EasyMock; import org.easymock.EasyMock;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.spongycastle.crypto.params.KeyParameter;
import java.util.HashMap; import java.util.HashMap;
@ -49,7 +50,9 @@ public class PaymentChannelClientTest {
@Test @Test
public void shouldSendTimeWindowInClientVersion() throws Exception { public void shouldSendTimeWindowInClientVersion() throws Exception {
long timeWindow = 4000; long timeWindow = 4000;
PaymentChannelClient dut = new PaymentChannelClient(wallet, ecKey, maxValue, serverHash, timeWindow, connection); KeyParameter userKey = null;
PaymentChannelClient dut =
new PaymentChannelClient(wallet, ecKey, maxValue, serverHash, timeWindow, userKey, connection);
connection.sendToServer(capture(clientVersionCapture)); connection.sendToServer(capture(clientVersionCapture));
EasyMock.expect(wallet.getExtensions()).andReturn(new HashMap<String, WalletExtension>()); EasyMock.expect(wallet.getExtensions()).andReturn(new HashMap<String, WalletExtension>());
replay(connection, wallet); replay(connection, wallet);

View File

@ -130,7 +130,7 @@ public class PaymentChannelStateTest extends TestWithWallet {
byte[] refundSig = serverState.provideRefundTransaction(refund, myKey.getPubKey()); byte[] refundSig = serverState.provideRefundTransaction(refund, myKey.getPubKey());
assertEquals(PaymentChannelServerState.State.WAITING_FOR_MULTISIG_CONTRACT, serverState.getState()); assertEquals(PaymentChannelServerState.State.WAITING_FOR_MULTISIG_CONTRACT, serverState.getState());
// This verifies that the refund can spend the multi-sig output when run. // This verifies that the refund can spend the multi-sig output when run.
clientState.provideRefundSignature(refundSig); clientState.provideRefundSignature(refundSig, null);
assertEquals(PaymentChannelClientState.State.SAVE_STATE_IN_WALLET, clientState.getState()); assertEquals(PaymentChannelClientState.State.SAVE_STATE_IN_WALLET, clientState.getState());
clientState.fakeSave(); clientState.fakeSave();
assertEquals(PaymentChannelClientState.State.PROVIDE_MULTISIG_CONTRACT_TO_SERVER, clientState.getState()); assertEquals(PaymentChannelClientState.State.PROVIDE_MULTISIG_CONTRACT_TO_SERVER, clientState.getState());
@ -169,7 +169,7 @@ public class PaymentChannelStateTest extends TestWithWallet {
Coin size = halfCoin.divide(100); Coin size = halfCoin.divide(100);
Coin totalPayment = Coin.ZERO; Coin totalPayment = Coin.ZERO;
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
byte[] signature = clientState.incrementPaymentBy(size).signature.encodeToBitcoin(); byte[] signature = clientState.incrementPaymentBy(size, null).signature.encodeToBitcoin();
totalPayment = totalPayment.add(size); totalPayment = totalPayment.add(size);
serverState.incrementPayment(halfCoin.subtract(totalPayment), signature); serverState.incrementPayment(halfCoin.subtract(totalPayment), signature);
} }
@ -177,7 +177,7 @@ public class PaymentChannelStateTest extends TestWithWallet {
// Now confirm the contract transaction and make sure payments still work // Now confirm the contract transaction and make sure payments still work
chain.add(makeSolvedTestBlock(blockStore.getChainHead().getHeader(), multisigContract)); chain.add(makeSolvedTestBlock(blockStore.getChainHead().getHeader(), multisigContract));
byte[] signature = clientState.incrementPaymentBy(size).signature.encodeToBitcoin(); byte[] signature = clientState.incrementPaymentBy(size, null).signature.encodeToBitcoin();
totalPayment = totalPayment.add(size); totalPayment = totalPayment.add(size);
serverState.incrementPayment(halfCoin.subtract(totalPayment), signature); serverState.incrementPayment(halfCoin.subtract(totalPayment), signature);
@ -248,7 +248,7 @@ public class PaymentChannelStateTest extends TestWithWallet {
byte[] refundSig = serverState.provideRefundTransaction(refund, myKey.getPubKey()); byte[] refundSig = serverState.provideRefundTransaction(refund, myKey.getPubKey());
assertEquals(PaymentChannelServerState.State.WAITING_FOR_MULTISIG_CONTRACT, serverState.getState()); assertEquals(PaymentChannelServerState.State.WAITING_FOR_MULTISIG_CONTRACT, serverState.getState());
// This verifies that the refund can spend the multi-sig output when run. // This verifies that the refund can spend the multi-sig output when run.
clientState.provideRefundSignature(refundSig); clientState.provideRefundSignature(refundSig, null);
assertEquals(PaymentChannelClientState.State.SAVE_STATE_IN_WALLET, clientState.getState()); assertEquals(PaymentChannelClientState.State.SAVE_STATE_IN_WALLET, clientState.getState());
clientState.fakeSave(); clientState.fakeSave();
assertEquals(PaymentChannelClientState.State.PROVIDE_MULTISIG_CONTRACT_TO_SERVER, clientState.getState()); assertEquals(PaymentChannelClientState.State.PROVIDE_MULTISIG_CONTRACT_TO_SERVER, clientState.getState());
@ -272,7 +272,7 @@ public class PaymentChannelStateTest extends TestWithWallet {
// Pay a tiny bit // Pay a tiny bit
serverState.incrementPayment(CENT.divide(2).subtract(CENT.divide(10)), serverState.incrementPayment(CENT.divide(2).subtract(CENT.divide(10)),
clientState.incrementPaymentBy(CENT.divide(10)).signature.encodeToBitcoin()); clientState.incrementPaymentBy(CENT.divide(10), null).signature.encodeToBitcoin());
// Advance time until our we get close enough to lock time that server should rebroadcast // Advance time until our we get close enough to lock time that server should rebroadcast
Utils.rollMockClock(60*60*22); Utils.rollMockClock(60*60*22);
@ -319,7 +319,7 @@ public class PaymentChannelStateTest extends TestWithWallet {
try { try {
// After its expired, we cant still increment payment // After its expired, we cant still increment payment
clientState.incrementPaymentBy(CENT); clientState.incrementPaymentBy(CENT, null);
fail(); fail();
} catch (IllegalStateException e) { } } catch (IllegalStateException e) { }
} }
@ -378,7 +378,7 @@ public class PaymentChannelStateTest extends TestWithWallet {
byte[] refundSigCopy = Arrays.copyOf(refundSig, refundSig.length); byte[] refundSigCopy = Arrays.copyOf(refundSig, refundSig.length);
refundSigCopy[refundSigCopy.length-1] = (byte) (Transaction.SigHash.NONE.ordinal() + 1); refundSigCopy[refundSigCopy.length-1] = (byte) (Transaction.SigHash.NONE.ordinal() + 1);
try { try {
clientState.provideRefundSignature(refundSigCopy); clientState.provideRefundSignature(refundSigCopy, null);
fail(); fail();
} catch (VerificationException e) { } catch (VerificationException e) {
assertTrue(e.getMessage().contains("SIGHASH_NONE")); assertTrue(e.getMessage().contains("SIGHASH_NONE"));
@ -387,7 +387,7 @@ public class PaymentChannelStateTest extends TestWithWallet {
refundSigCopy = Arrays.copyOf(refundSig, refundSig.length); refundSigCopy = Arrays.copyOf(refundSig, refundSig.length);
refundSigCopy[3] ^= 0x42; // Make the signature fail standard checks refundSigCopy[3] ^= 0x42; // Make the signature fail standard checks
try { try {
clientState.provideRefundSignature(refundSigCopy); clientState.provideRefundSignature(refundSigCopy, null);
fail(); fail();
} catch (VerificationException e) { } catch (VerificationException e) {
assertTrue(e.getMessage().contains("not canonical")); assertTrue(e.getMessage().contains("not canonical"));
@ -396,7 +396,7 @@ public class PaymentChannelStateTest extends TestWithWallet {
refundSigCopy = Arrays.copyOf(refundSig, refundSig.length); refundSigCopy = Arrays.copyOf(refundSig, refundSig.length);
refundSigCopy[10] ^= 0x42; // Flip some random bits in the signature (to make it invalid, not just nonstandard) refundSigCopy[10] ^= 0x42; // Flip some random bits in the signature (to make it invalid, not just nonstandard)
try { try {
clientState.provideRefundSignature(refundSigCopy); clientState.provideRefundSignature(refundSigCopy, null);
fail(); fail();
} catch (VerificationException e) { } catch (VerificationException e) {
assertFalse(e.getMessage().contains("not canonical")); assertFalse(e.getMessage().contains("not canonical"));
@ -404,13 +404,13 @@ public class PaymentChannelStateTest extends TestWithWallet {
refundSigCopy = Arrays.copyOf(refundSig, refundSig.length); refundSigCopy = Arrays.copyOf(refundSig, refundSig.length);
try { clientState.getCompletedRefundTransaction(); fail(); } catch (IllegalStateException e) {} try { clientState.getCompletedRefundTransaction(); fail(); } catch (IllegalStateException e) {}
clientState.provideRefundSignature(refundSigCopy); clientState.provideRefundSignature(refundSigCopy, null);
try { clientState.provideRefundSignature(refundSigCopy); fail(); } catch (IllegalStateException e) {} try { clientState.provideRefundSignature(refundSigCopy, null); fail(); } catch (IllegalStateException e) {}
assertEquals(PaymentChannelClientState.State.SAVE_STATE_IN_WALLET, clientState.getState()); assertEquals(PaymentChannelClientState.State.SAVE_STATE_IN_WALLET, clientState.getState());
clientState.fakeSave(); clientState.fakeSave();
assertEquals(PaymentChannelClientState.State.PROVIDE_MULTISIG_CONTRACT_TO_SERVER, clientState.getState()); assertEquals(PaymentChannelClientState.State.PROVIDE_MULTISIG_CONTRACT_TO_SERVER, clientState.getState());
try { clientState.incrementPaymentBy(Coin.SATOSHI); fail(); } catch (IllegalStateException e) {} try { clientState.incrementPaymentBy(Coin.SATOSHI, null); fail(); } catch (IllegalStateException e) {}
byte[] multisigContractSerialized = clientState.getMultisigContract().bitcoinSerialize(); byte[] multisigContractSerialized = clientState.getMultisigContract().bitcoinSerialize();
@ -456,11 +456,11 @@ public class PaymentChannelStateTest extends TestWithWallet {
Coin size = halfCoin.divide(100); Coin size = halfCoin.divide(100);
Coin totalPayment = Coin.ZERO; Coin totalPayment = Coin.ZERO;
try { try {
clientState.incrementPaymentBy(COIN); clientState.incrementPaymentBy(COIN, null);
fail(); fail();
} catch (ValueOutOfRangeException e) {} } catch (ValueOutOfRangeException e) {}
byte[] signature = clientState.incrementPaymentBy(size).signature.encodeToBitcoin(); byte[] signature = clientState.incrementPaymentBy(size, null).signature.encodeToBitcoin();
totalPayment = totalPayment.add(size); totalPayment = totalPayment.add(size);
byte[] signatureCopy = Arrays.copyOf(signature, signature.length); byte[] signatureCopy = Arrays.copyOf(signature, signature.length);
@ -491,7 +491,7 @@ public class PaymentChannelStateTest extends TestWithWallet {
serverState.incrementPayment(halfCoin.subtract(totalPayment), signature); serverState.incrementPayment(halfCoin.subtract(totalPayment), signature);
// Pay the rest (signed with SIGHASH_NONE|SIGHASH_ANYONECANPAY) // Pay the rest (signed with SIGHASH_NONE|SIGHASH_ANYONECANPAY)
byte[] signature2 = clientState.incrementPaymentBy(halfCoin.subtract(totalPayment)).signature.encodeToBitcoin(); byte[] signature2 = clientState.incrementPaymentBy(halfCoin.subtract(totalPayment), null).signature.encodeToBitcoin();
totalPayment = totalPayment.add(halfCoin.subtract(totalPayment)); totalPayment = totalPayment.add(halfCoin.subtract(totalPayment));
assertEquals(totalPayment, halfCoin); assertEquals(totalPayment, halfCoin);
@ -512,12 +512,12 @@ public class PaymentChannelStateTest extends TestWithWallet {
assertEquals(serverState.getBestValueToMe(), totalPayment); assertEquals(serverState.getBestValueToMe(), totalPayment);
try { try {
clientState.incrementPaymentBy(Coin.SATOSHI.negate()); clientState.incrementPaymentBy(Coin.SATOSHI.negate(), null);
fail(); fail();
} catch (ValueOutOfRangeException e) {} } catch (ValueOutOfRangeException e) {}
try { try {
clientState.incrementPaymentBy(halfCoin.subtract(size).add(Coin.SATOSHI)); clientState.incrementPaymentBy(halfCoin.subtract(size).add(Coin.SATOSHI), null);
fail(); fail();
} catch (ValueOutOfRangeException e) {} } catch (ValueOutOfRangeException e) {}
} }
@ -577,7 +577,7 @@ public class PaymentChannelStateTest extends TestWithWallet {
byte[] refundSig = serverState.provideRefundTransaction(refund, myKey.getPubKey()); byte[] refundSig = serverState.provideRefundTransaction(refund, myKey.getPubKey());
assertEquals(PaymentChannelServerState.State.WAITING_FOR_MULTISIG_CONTRACT, serverState.getState()); assertEquals(PaymentChannelServerState.State.WAITING_FOR_MULTISIG_CONTRACT, serverState.getState());
// This verifies that the refund can spend the multi-sig output when run. // This verifies that the refund can spend the multi-sig output when run.
clientState.provideRefundSignature(refundSig); clientState.provideRefundSignature(refundSig, null);
assertEquals(PaymentChannelClientState.State.SAVE_STATE_IN_WALLET, clientState.getState()); assertEquals(PaymentChannelClientState.State.SAVE_STATE_IN_WALLET, clientState.getState());
clientState.fakeSave(); clientState.fakeSave();
assertEquals(PaymentChannelClientState.State.PROVIDE_MULTISIG_CONTRACT_TO_SERVER, clientState.getState()); assertEquals(PaymentChannelClientState.State.PROVIDE_MULTISIG_CONTRACT_TO_SERVER, clientState.getState());
@ -597,7 +597,7 @@ public class PaymentChannelStateTest extends TestWithWallet {
Coin totalPayment = Coin.ZERO; Coin totalPayment = Coin.ZERO;
// We can send as little as we want - its up to the server to get the fees right // We can send as little as we want - its up to the server to get the fees right
byte[] signature = clientState.incrementPaymentBy(Coin.SATOSHI).signature.encodeToBitcoin(); byte[] signature = clientState.incrementPaymentBy(Coin.SATOSHI, null).signature.encodeToBitcoin();
totalPayment = totalPayment.add(Coin.SATOSHI); totalPayment = totalPayment.add(Coin.SATOSHI);
serverState.incrementPayment(CENT.subtract(totalPayment), signature); serverState.incrementPayment(CENT.subtract(totalPayment), signature);
@ -610,7 +610,7 @@ public class PaymentChannelStateTest extends TestWithWallet {
// We cannot send just under the total value - our refund would make it unspendable. So the client // We cannot send just under the total value - our refund would make it unspendable. So the client
// will correct it for us to be larger than the requested amount, to make the change output zero. // will correct it for us to be larger than the requested amount, to make the change output zero.
PaymentChannelClientState.IncrementedPayment payment = PaymentChannelClientState.IncrementedPayment payment =
clientState.incrementPaymentBy(CENT.subtract(Transaction.MIN_NONDUST_OUTPUT)); clientState.incrementPaymentBy(CENT.subtract(Transaction.MIN_NONDUST_OUTPUT), null);
assertEquals(CENT.subtract(SATOSHI), payment.amount); assertEquals(CENT.subtract(SATOSHI), payment.amount);
totalPayment = totalPayment.add(payment.amount); totalPayment = totalPayment.add(payment.amount);
@ -657,7 +657,7 @@ public class PaymentChannelStateTest extends TestWithWallet {
byte[] refundSig = serverState.provideRefundTransaction(refund, myKey.getPubKey()); byte[] refundSig = serverState.provideRefundTransaction(refund, myKey.getPubKey());
assertEquals(PaymentChannelServerState.State.WAITING_FOR_MULTISIG_CONTRACT, serverState.getState()); assertEquals(PaymentChannelServerState.State.WAITING_FOR_MULTISIG_CONTRACT, serverState.getState());
// This verifies that the refund can spend the multi-sig output when run. // This verifies that the refund can spend the multi-sig output when run.
clientState.provideRefundSignature(refundSig); clientState.provideRefundSignature(refundSig, null);
assertEquals(PaymentChannelClientState.State.SAVE_STATE_IN_WALLET, clientState.getState()); assertEquals(PaymentChannelClientState.State.SAVE_STATE_IN_WALLET, clientState.getState());
clientState.fakeSave(); clientState.fakeSave();
assertEquals(PaymentChannelClientState.State.PROVIDE_MULTISIG_CONTRACT_TO_SERVER, clientState.getState()); assertEquals(PaymentChannelClientState.State.PROVIDE_MULTISIG_CONTRACT_TO_SERVER, clientState.getState());
@ -680,7 +680,7 @@ public class PaymentChannelStateTest extends TestWithWallet {
assertEquals(PaymentChannelServerState.State.READY, serverState.getState()); assertEquals(PaymentChannelServerState.State.READY, serverState.getState());
// Both client and server are now in the ready state, split the channel in half // Both client and server are now in the ready state, split the channel in half
byte[] signature = clientState.incrementPaymentBy(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.subtract(Coin.SATOSHI)) byte[] signature = clientState.incrementPaymentBy(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.subtract(Coin.SATOSHI), null)
.signature.encodeToBitcoin(); .signature.encodeToBitcoin();
Coin totalRefund = CENT.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.subtract(SATOSHI)); Coin totalRefund = CENT.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.subtract(SATOSHI));
serverState.incrementPayment(totalRefund, signature); serverState.incrementPayment(totalRefund, signature);
@ -705,7 +705,7 @@ public class PaymentChannelStateTest extends TestWithWallet {
assertTrue(e.getMessage().contains("more in fees")); assertTrue(e.getMessage().contains("more in fees"));
} }
signature = clientState.incrementPaymentBy(SATOSHI.multiply(2)).signature.encodeToBitcoin(); signature = clientState.incrementPaymentBy(SATOSHI.multiply(2), null).signature.encodeToBitcoin();
totalRefund = totalRefund.subtract(SATOSHI.multiply(2)); totalRefund = totalRefund.subtract(SATOSHI.multiply(2));
serverState.incrementPayment(totalRefund, signature); serverState.incrementPayment(totalRefund, signature);
@ -738,7 +738,7 @@ public class PaymentChannelStateTest extends TestWithWallet {
byte[] refundSig = serverState.provideRefundTransaction(refund, myKey.getPubKey()); byte[] refundSig = serverState.provideRefundTransaction(refund, myKey.getPubKey());
assertEquals(PaymentChannelServerState.State.WAITING_FOR_MULTISIG_CONTRACT, serverState.getState()); assertEquals(PaymentChannelServerState.State.WAITING_FOR_MULTISIG_CONTRACT, serverState.getState());
// This verifies that the refund can spend the multi-sig output when run. // This verifies that the refund can spend the multi-sig output when run.
clientState.provideRefundSignature(refundSig); clientState.provideRefundSignature(refundSig, null);
assertEquals(PaymentChannelClientState.State.SAVE_STATE_IN_WALLET, clientState.getState()); assertEquals(PaymentChannelClientState.State.SAVE_STATE_IN_WALLET, clientState.getState());
clientState.fakeSave(); clientState.fakeSave();
assertEquals(PaymentChannelClientState.State.PROVIDE_MULTISIG_CONTRACT_TO_SERVER, clientState.getState()); assertEquals(PaymentChannelClientState.State.PROVIDE_MULTISIG_CONTRACT_TO_SERVER, clientState.getState());
@ -777,7 +777,7 @@ public class PaymentChannelStateTest extends TestWithWallet {
Coin size = halfCoin.divide(100); Coin size = halfCoin.divide(100);
Coin totalPayment = Coin.ZERO; Coin totalPayment = Coin.ZERO;
for (int i = 0; i < 5; i++) { for (int i = 0; i < 5; i++) {
byte[] signature = clientState.incrementPaymentBy(size).signature.encodeToBitcoin(); byte[] signature = clientState.incrementPaymentBy(size, null).signature.encodeToBitcoin();
totalPayment = totalPayment.add(size); totalPayment = totalPayment.add(size);
serverState.incrementPayment(halfCoin.subtract(totalPayment), signature); serverState.incrementPayment(halfCoin.subtract(totalPayment), signature);
} }
@ -794,7 +794,7 @@ public class PaymentChannelStateTest extends TestWithWallet {
// Now if we try to spend again the server will reject it since it saw a double-spend // Now if we try to spend again the server will reject it since it saw a double-spend
try { try {
byte[] signature = clientState.incrementPaymentBy(size).signature.encodeToBitcoin(); byte[] signature = clientState.incrementPaymentBy(size, null).signature.encodeToBitcoin();
totalPayment = totalPayment.add(size); totalPayment = totalPayment.add(size);
serverState.incrementPayment(halfCoin.subtract(totalPayment), signature); serverState.incrementPayment(halfCoin.subtract(totalPayment), signature);
fail(); fail();