From 3f5fe671cdcc63191f73119bc715abda7a0986a3 Mon Sep 17 00:00:00 2001 From: woodser Date: Sun, 4 Sep 2022 11:24:10 -0400 Subject: [PATCH] decrypt payment info after confirmation for double spend protection retrieve decryption key from arbitrator if peer fails to send --- .../network/CoreNetworkProtoResolver.java | 9 +- .../bisq/core/support/dispute/Dispute.java | 2 + .../core/support/dispute/DisputeManager.java | 3 +- core/src/main/java/bisq/core/trade/Trade.java | 9 +- .../bisq/core/trade/TradeDataValidation.java | 6 +- .../java/bisq/core/trade/TradeManager.java | 14 +-- .../core/trade/messages/DepositRequest.java | 15 ++- .../messages/PaymentAccountKeyRequest.java | 77 ++++++++++++ ...st.java => PaymentAccountKeyResponse.java} | 47 ++++---- .../trade/messages/SignContractResponse.java | 13 +- .../trade/protocol/ArbitratorProtocol.java | 32 ++++- .../trade/protocol/BuyerAsMakerProtocol.java | 6 +- .../trade/protocol/BuyerAsTakerProtocol.java | 8 +- .../core/trade/protocol/BuyerProtocol.java | 113 ++++++++++++++++-- .../trade/protocol/SellerAsMakerProtocol.java | 6 - .../trade/protocol/SellerAsTakerProtocol.java | 8 +- .../core/trade/protocol/SellerProtocol.java | 60 ++++++++-- .../core/trade/protocol/TradeProtocol.java | 43 +------ .../core/trade/protocol/TraderProtocol.java | 2 - .../bisq/core/trade/protocol/TradingPeer.java | 8 ++ .../ArbitratorProcessesDepositRequest.java | 1 + ...atorProcessesPaymentAccountKeyRequest.java | 77 ++++++++++++ .../BuyerPreparesPaymentSentMessage.java | 10 +- ...erProcessesPaymentAccountKeyResponse.java} | 42 +++++-- .../BuyerProcessesPaymentReceivedMessage.java | 2 +- ...sPaymentAccountKeyRequestToArbitrator.java | 73 +++++++++++ .../tasks/MaybeSendSignContractRequest.java | 3 +- .../tasks/ProcessDepositResponse.java | 37 +----- .../tasks/ProcessSignContractRequest.java | 22 +++- .../tasks/ProcessSignContractResponse.java | 9 +- .../SellerPreparesPaymentReceivedMessage.java | 2 - .../SellerSendsPaymentAccountPayloadKey.java | 101 ++++++++++++++++ .../SellerSendsPaymentReceivedMessage.java | 10 +- .../{ => tasks}/TakerReservesTradeFunds.java | 3 +- ...akerSendsInitTradeRequestToArbitrator.java | 3 +- .../TakerVerifyMakerFeePayment.java | 4 +- .../bisq/desktop/main/debug/DebugView.java | 2 +- proto/src/main/proto/pb.proto | 62 ++++++---- 38 files changed, 706 insertions(+), 238 deletions(-) create mode 100644 core/src/main/java/bisq/core/trade/messages/PaymentAccountKeyRequest.java rename core/src/main/java/bisq/core/trade/messages/{PaymentAccountPayloadRequest.java => PaymentAccountKeyResponse.java} (62%) create mode 100644 core/src/main/java/bisq/core/trade/protocol/tasks/ArbitratorProcessesPaymentAccountKeyRequest.java rename core/src/main/java/bisq/core/trade/protocol/tasks/{ProcessPaymentAccountPayloadRequest.java => BuyerProcessesPaymentAccountKeyResponse.java} (51%) create mode 100644 core/src/main/java/bisq/core/trade/protocol/tasks/BuyerSendsPaymentAccountKeyRequestToArbitrator.java create mode 100644 core/src/main/java/bisq/core/trade/protocol/tasks/SellerSendsPaymentAccountPayloadKey.java rename core/src/main/java/bisq/core/trade/protocol/{ => tasks}/TakerReservesTradeFunds.java (97%) rename core/src/main/java/bisq/core/trade/protocol/{ => tasks}/TakerSendsInitTradeRequestToArbitrator.java (98%) rename core/src/main/java/bisq/core/trade/protocol/{ => tasks}/TakerVerifyMakerFeePayment.java (94%) diff --git a/core/src/main/java/bisq/core/proto/network/CoreNetworkProtoResolver.java b/core/src/main/java/bisq/core/proto/network/CoreNetworkProtoResolver.java index b61e51bff3..d0bd3b7c1a 100644 --- a/core/src/main/java/bisq/core/proto/network/CoreNetworkProtoResolver.java +++ b/core/src/main/java/bisq/core/proto/network/CoreNetworkProtoResolver.java @@ -46,7 +46,8 @@ import bisq.core.trade.messages.InitMultisigRequest; import bisq.core.trade.messages.InitTradeRequest; import bisq.core.trade.messages.MediatedPayoutTxPublishedMessage; import bisq.core.trade.messages.MediatedPayoutTxSignatureMessage; -import bisq.core.trade.messages.PaymentAccountPayloadRequest; +import bisq.core.trade.messages.PaymentAccountKeyRequest; +import bisq.core.trade.messages.PaymentAccountKeyResponse; import bisq.core.trade.messages.PaymentReceivedMessage; import bisq.core.trade.messages.RefreshTradeStateRequest; import bisq.core.trade.messages.SignContractRequest; @@ -157,8 +158,10 @@ public class CoreNetworkProtoResolver extends CoreProtoResolver implements Netwo return DepositRequest.fromProto(proto.getDepositRequest(), this, messageVersion); case DEPOSIT_RESPONSE: return DepositResponse.fromProto(proto.getDepositResponse(), this, messageVersion); - case PAYMENT_ACCOUNT_PAYLOAD_REQUEST: - return PaymentAccountPayloadRequest.fromProto(proto.getPaymentAccountPayloadRequest(), this, messageVersion); + case PAYMENT_ACCOUNT_KEY_REQUEST: + return PaymentAccountKeyRequest.fromProto(proto.getPaymentAccountKeyRequest(), this, messageVersion); + case PAYMENT_ACCOUNT_KEY_RESPONSE: + return PaymentAccountKeyResponse.fromProto(proto.getPaymentAccountKeyResponse(), this, messageVersion); case UPDATE_MULTISIG_REQUEST: return UpdateMultisigRequest.fromProto(proto.getUpdateMultisigRequest(), this, messageVersion); case UPDATE_MULTISIG_RESPONSE: diff --git a/core/src/main/java/bisq/core/support/dispute/Dispute.java b/core/src/main/java/bisq/core/support/dispute/Dispute.java index db88d88e2c..7bf2107687 100644 --- a/core/src/main/java/bisq/core/support/dispute/Dispute.java +++ b/core/src/main/java/bisq/core/support/dispute/Dispute.java @@ -506,6 +506,8 @@ public final class Dispute implements NetworkPayload, PersistablePayload { ",\n mediatorsDisputeResult='" + mediatorsDisputeResult + '\'' + ",\n delayedPayoutTxId='" + delayedPayoutTxId + '\'' + ",\n donationAddressOfDelayedPayoutTx='" + donationAddressOfDelayedPayoutTx + '\'' + + ",\n makerPaymentAccountPayload='" + makerPaymentAccountPayload + '\'' + + ",\n takerPaymentAccountPayload='" + takerPaymentAccountPayload + '\'' + "\n}"; } } diff --git a/core/src/main/java/bisq/core/support/dispute/DisputeManager.java b/core/src/main/java/bisq/core/support/dispute/DisputeManager.java index 061ac9f3c0..ff1ff7ada2 100644 --- a/core/src/main/java/bisq/core/support/dispute/DisputeManager.java +++ b/core/src/main/java/bisq/core/support/dispute/DisputeManager.java @@ -65,7 +65,6 @@ import javafx.collections.ObservableList; import java.security.KeyPair; -import java.util.Arrays; import java.util.Date; import java.util.List; import java.util.Optional; @@ -373,7 +372,7 @@ public abstract class DisputeManager> extends Sup addMediationResultMessage(dispute); try { - TradeDataValidation.validatePaymentAccountPayloads(dispute); + TradeDataValidation.validatePaymentAccountPayload(dispute); TradeDataValidation.validateDonationAddress(dispute.getDonationAddressOfDelayedPayoutTx()); //TradeDataValidation.testIfDisputeTriesReplay(dispute, disputeList.getList()); // TODO (woodser): disabled for xmr, needed? TradeDataValidation.validateNodeAddress(dispute, dispute.getContract().getBuyerNodeAddress(), config); diff --git a/core/src/main/java/bisq/core/trade/Trade.java b/core/src/main/java/bisq/core/trade/Trade.java index 0674634490..55d1e4bb05 100644 --- a/core/src/main/java/bisq/core/trade/Trade.java +++ b/core/src/main/java/bisq/core/trade/Trade.java @@ -75,6 +75,7 @@ import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.NotNull; + import javax.annotation.Nullable; import static com.google.common.base.Preconditions.checkNotNull; @@ -117,14 +118,14 @@ public abstract class Trade implements Tradable, Model { // deposit requested SENT_PUBLISH_DEPOSIT_TX_REQUEST(Phase.DEPOSIT_REQUESTED), SAW_ARRIVED_PUBLISH_DEPOSIT_TX_REQUEST(Phase.DEPOSIT_REQUESTED), - STORED_IN_MAILBOX_PUBLISH_DEPOSIT_TX_REQUEST(Phase.DEPOSIT_REQUESTED), //not a mailbox msg, not used... + STORED_IN_MAILBOX_PUBLISH_DEPOSIT_TX_REQUEST(Phase.DEPOSIT_REQUESTED), // not a mailbox msg, not used... remove SEND_FAILED_PUBLISH_DEPOSIT_TX_REQUEST(Phase.DEPOSIT_REQUESTED), // deposit published DEPOSIT_TXS_SEEN_IN_BLOCKCHAIN(Phase.DEPOSITS_PUBLISHED), // TODO: seeing in network usually happens after arbitrator publishes ARBITRATOR_PUBLISHED_DEPOSIT_TXS(Phase.DEPOSITS_PUBLISHED), - // deposit confirmed (TODO) + // deposit confirmed DEPOSIT_TXS_CONFIRMED_IN_BLOCKCHAIN(Phase.DEPOSITS_CONFIRMED), // deposit unlocked @@ -1114,11 +1115,11 @@ public abstract class Trade implements Tradable, Model { /////////////////////////////////////////////////////////////////////////////////////////// public boolean isBuyer() { - return offer.getDirection() == OfferDirection.BUY; + return getBuyer() == getSelf(); } public boolean isSeller() { - return offer.getDirection() == OfferDirection.SELL; + return getSeller() == getSelf(); } public boolean isMaker() { diff --git a/core/src/main/java/bisq/core/trade/TradeDataValidation.java b/core/src/main/java/bisq/core/trade/TradeDataValidation.java index 54d2987771..01ac2fd31d 100644 --- a/core/src/main/java/bisq/core/trade/TradeDataValidation.java +++ b/core/src/main/java/bisq/core/trade/TradeDataValidation.java @@ -54,9 +54,9 @@ import static com.google.common.base.Preconditions.checkNotNull; @Slf4j public class TradeDataValidation { - public static void validatePaymentAccountPayloads(Dispute dispute) throws InvalidPaymentAccountPayloadException { - if (!Arrays.equals(dispute.getMakerPaymentAccountPayload().getHash(), dispute.getContract().getMakerPaymentAccountPayloadHash())) throw new InvalidPaymentAccountPayloadException(dispute, "Hash of maker's payment account payload does not match contract"); - if (!Arrays.equals(dispute.getTakerPaymentAccountPayload().getHash(), dispute.getContract().getTakerPaymentAccountPayloadHash())) throw new InvalidPaymentAccountPayloadException(dispute, "Hash of taker's payment account payload does not match contract"); + public static void validatePaymentAccountPayload(Dispute dispute) throws InvalidPaymentAccountPayloadException { + if (dispute.getSellerPaymentAccountPayload() == null) throw new InvalidPaymentAccountPayloadException(dispute, "Seller's payment account payload is null in dispute opened for trade " + dispute.getTradeId()); + if (!Arrays.equals(dispute.getSellerPaymentAccountPayload().getHash(), dispute.getContract().getSellerPaymentAccountPayloadHash())) throw new InvalidPaymentAccountPayloadException(dispute, "Hash of maker's payment account payload does not match contract"); } public static void validateDonationAddress(String addressAsString) diff --git a/core/src/main/java/bisq/core/trade/TradeManager.java b/core/src/main/java/bisq/core/trade/TradeManager.java index e9f7d2a3cb..d33051aaea 100644 --- a/core/src/main/java/bisq/core/trade/TradeManager.java +++ b/core/src/main/java/bisq/core/trade/TradeManager.java @@ -41,7 +41,7 @@ import bisq.core.trade.messages.DepositRequest; import bisq.core.trade.messages.DepositResponse; import bisq.core.trade.messages.InitMultisigRequest; import bisq.core.trade.messages.InitTradeRequest; -import bisq.core.trade.messages.PaymentAccountPayloadRequest; +import bisq.core.trade.messages.PaymentAccountKeyRequest; import bisq.core.trade.messages.SignContractRequest; import bisq.core.trade.messages.SignContractResponse; import bisq.core.trade.messages.UpdateMultisigRequest; @@ -253,8 +253,8 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi handleDepositRequest((DepositRequest) networkEnvelope, peer); } else if (networkEnvelope instanceof DepositResponse) { handleDepositResponse((DepositResponse) networkEnvelope, peer); - } else if (networkEnvelope instanceof PaymentAccountPayloadRequest) { - handlePaymentAccountPayloadRequest((PaymentAccountPayloadRequest) networkEnvelope, peer); + } else if (networkEnvelope instanceof PaymentAccountKeyRequest) { + handlePaymentAccountKeyRequest((PaymentAccountKeyRequest) networkEnvelope, peer); } else if (networkEnvelope instanceof UpdateMultisigRequest) { handleUpdateMultisigRequest((UpdateMultisigRequest) networkEnvelope, peer); } @@ -666,13 +666,13 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi ((TraderProtocol) getTradeProtocol(trade)).handleDepositResponse(response, peer); } - private void handlePaymentAccountPayloadRequest(PaymentAccountPayloadRequest request, NodeAddress peer) { - log.info("Received PaymentAccountPayloadRequest from {} with tradeId {} and uid {}", peer, request.getTradeId(), request.getUid()); + private void handlePaymentAccountKeyRequest(PaymentAccountKeyRequest request, NodeAddress peer) { + log.info("Received PaymentAccountKeyRequest from {} with tradeId {} and uid {}", peer, request.getTradeId(), request.getUid()); try { Validator.nonEmptyStringOf(request.getTradeId()); } catch (Throwable t) { - log.warn("Invalid PaymentAccountPayloadRequest message " + request.toString()); + log.warn("Invalid PaymentAccountKeyRequest message " + request.toString()); return; } @@ -682,7 +682,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi return; } Trade trade = tradeOptional.get(); - ((TraderProtocol) getTradeProtocol(trade)).handlePaymentAccountPayloadRequest(request, peer); + ((ArbitratorProtocol) getTradeProtocol(trade)).handlePaymentAccountKeyRequest(request, peer); } private void handleUpdateMultisigRequest(UpdateMultisigRequest request, NodeAddress peer) { diff --git a/core/src/main/java/bisq/core/trade/messages/DepositRequest.java b/core/src/main/java/bisq/core/trade/messages/DepositRequest.java index b76f2c5c6a..0a994befcc 100644 --- a/core/src/main/java/bisq/core/trade/messages/DepositRequest.java +++ b/core/src/main/java/bisq/core/trade/messages/DepositRequest.java @@ -22,8 +22,10 @@ import bisq.core.proto.CoreProtoResolver; import bisq.network.p2p.DirectMessage; import bisq.network.p2p.NodeAddress; import com.google.protobuf.ByteString; +import java.util.Optional; +import javax.annotation.Nullable; import bisq.common.crypto.PubKeyRing; - +import bisq.common.proto.ProtoUtil; import lombok.EqualsAndHashCode; import lombok.Value; @@ -36,6 +38,8 @@ public final class DepositRequest extends TradeMessage implements DirectMessage private final String contractSignature; private final String depositTxHex; private final String depositTxKey; + @Nullable + private final byte[] paymentAccountKey; public DepositRequest(String tradeId, NodeAddress senderNodeAddress, @@ -45,7 +49,8 @@ public final class DepositRequest extends TradeMessage implements DirectMessage long currentDate, String contractSignature, String depositTxHex, - String depositTxKey) { + String depositTxKey, + @Nullable byte[] paymentAccountKey) { super(messageVersion, tradeId, uid); this.senderNodeAddress = senderNodeAddress; this.pubKeyRing = pubKeyRing; @@ -53,6 +58,7 @@ public final class DepositRequest extends TradeMessage implements DirectMessage this.contractSignature = contractSignature; this.depositTxHex = depositTxHex; this.depositTxKey = depositTxKey; + this.paymentAccountKey = paymentAccountKey; } @@ -71,6 +77,7 @@ public final class DepositRequest extends TradeMessage implements DirectMessage .setDepositTxHex(depositTxHex) .setDepositTxKey(depositTxKey); builder.setCurrentDate(currentDate); + Optional.ofNullable(paymentAccountKey).ifPresent(e -> builder.setPaymentAccountKey(ByteString.copyFrom(e))); return getNetworkEnvelopeBuilder().setDepositRequest(builder).build(); } @@ -86,7 +93,8 @@ public final class DepositRequest extends TradeMessage implements DirectMessage proto.getCurrentDate(), proto.getContractSignature(), proto.getDepositTxHex(), - proto.getDepositTxKey()); + proto.getDepositTxKey(), + ProtoUtil.byteArrayOrNullFromProto(proto.getPaymentAccountKey())); } @Override @@ -98,6 +106,7 @@ public final class DepositRequest extends TradeMessage implements DirectMessage ",\n contractSignature=" + contractSignature + ",\n depositTxHex='" + depositTxHex + ",\n depositTxKey='" + depositTxKey + + ",\n paymentAccountKey='" + paymentAccountKey + "\n} " + super.toString(); } } diff --git a/core/src/main/java/bisq/core/trade/messages/PaymentAccountKeyRequest.java b/core/src/main/java/bisq/core/trade/messages/PaymentAccountKeyRequest.java new file mode 100644 index 0000000000..81186e8a00 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/messages/PaymentAccountKeyRequest.java @@ -0,0 +1,77 @@ +/* + * This file is part of Haveno. + * + * Haveno is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Haveno is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Haveno. If not, see . + */ + +package bisq.core.trade.messages; + +import bisq.core.proto.CoreProtoResolver; + +import bisq.network.p2p.DirectMessage; +import bisq.network.p2p.NodeAddress; +import bisq.common.crypto.PubKeyRing; + +import lombok.EqualsAndHashCode; +import lombok.Value; + +@EqualsAndHashCode(callSuper = true) +@Value +public final class PaymentAccountKeyRequest extends TradeMessage implements DirectMessage { + private final NodeAddress senderNodeAddress; + private final PubKeyRing pubKeyRing; + + public PaymentAccountKeyRequest(String tradeId, + NodeAddress senderNodeAddress, + PubKeyRing pubKeyRing, + String uid, + String messageVersion) { + super(messageVersion, tradeId, uid); + this.senderNodeAddress = senderNodeAddress; + this.pubKeyRing = pubKeyRing; + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // PROTO BUFFER + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public protobuf.NetworkEnvelope toProtoNetworkEnvelope() { + protobuf.PaymentAccountKeyRequest.Builder builder = protobuf.PaymentAccountKeyRequest.newBuilder() + .setTradeId(tradeId) + .setSenderNodeAddress(senderNodeAddress.toProtoMessage()) + .setPubKeyRing(pubKeyRing.toProtoMessage()) + .setUid(uid); + return getNetworkEnvelopeBuilder().setPaymentAccountKeyRequest(builder).build(); + } + + public static PaymentAccountKeyRequest fromProto(protobuf.PaymentAccountKeyRequest proto, + CoreProtoResolver coreProtoResolver, + String messageVersion) { + return new PaymentAccountKeyRequest(proto.getTradeId(), + NodeAddress.fromProto(proto.getSenderNodeAddress()), + PubKeyRing.fromProto(proto.getPubKeyRing()), + proto.getUid(), + messageVersion); + } + + @Override + public String toString() { + return "PaymentAccountKeyRequest {" + + "\n senderNodeAddress=" + senderNodeAddress + + ",\n pubKeyRing=" + pubKeyRing + + "\n} " + super.toString(); + } +} diff --git a/core/src/main/java/bisq/core/trade/messages/PaymentAccountPayloadRequest.java b/core/src/main/java/bisq/core/trade/messages/PaymentAccountKeyResponse.java similarity index 62% rename from core/src/main/java/bisq/core/trade/messages/PaymentAccountPayloadRequest.java rename to core/src/main/java/bisq/core/trade/messages/PaymentAccountKeyResponse.java index a99538b785..25bee5abe0 100644 --- a/core/src/main/java/bisq/core/trade/messages/PaymentAccountPayloadRequest.java +++ b/core/src/main/java/bisq/core/trade/messages/PaymentAccountKeyResponse.java @@ -17,37 +17,40 @@ package bisq.core.trade.messages; -import bisq.core.payment.payload.PaymentAccountPayload; import bisq.core.proto.CoreProtoResolver; import bisq.network.p2p.DirectMessage; import bisq.network.p2p.NodeAddress; import com.google.protobuf.ByteString; +import java.util.Optional; +import javax.annotation.Nullable; import bisq.common.crypto.PubKeyRing; - +import bisq.common.proto.ProtoUtil; import lombok.EqualsAndHashCode; import lombok.Value; @EqualsAndHashCode(callSuper = true) @Value -public final class PaymentAccountPayloadRequest extends TradeMessage implements DirectMessage { +public final class PaymentAccountKeyResponse extends TradeMailboxMessage implements DirectMessage { private final NodeAddress senderNodeAddress; private final PubKeyRing pubKeyRing; - private final long currentDate; - private final PaymentAccountPayload paymentAccountPayload; + @Nullable + private final byte[] paymentAccountKey; + @Nullable + private final String updatedMultisigHex; - public PaymentAccountPayloadRequest(String tradeId, + public PaymentAccountKeyResponse(String tradeId, NodeAddress senderNodeAddress, PubKeyRing pubKeyRing, String uid, String messageVersion, - long currentDate, - PaymentAccountPayload paymentAccountPayload) { + @Nullable byte[] paymentAccountKey, + @Nullable String updatedMultisigHex) { super(messageVersion, tradeId, uid); this.senderNodeAddress = senderNodeAddress; this.pubKeyRing = pubKeyRing; - this.currentDate = currentDate; - this.paymentAccountPayload = paymentAccountPayload; + this.paymentAccountKey = paymentAccountKey; + this.updatedMultisigHex = updatedMultisigHex; } @@ -57,36 +60,34 @@ public final class PaymentAccountPayloadRequest extends TradeMessage implements @Override public protobuf.NetworkEnvelope toProtoNetworkEnvelope() { - protobuf.PaymentAccountPayloadRequest.Builder builder = protobuf.PaymentAccountPayloadRequest.newBuilder() + protobuf.PaymentAccountKeyResponse.Builder builder = protobuf.PaymentAccountKeyResponse.newBuilder() .setTradeId(tradeId) .setSenderNodeAddress(senderNodeAddress.toProtoMessage()) .setPubKeyRing(pubKeyRing.toProtoMessage()) - .setUid(uid) - .setPaymentAccountPayload((protobuf.PaymentAccountPayload) paymentAccountPayload.toProtoMessage()); - builder.setCurrentDate(currentDate); - - return getNetworkEnvelopeBuilder().setPaymentAccountPayloadRequest(builder).build(); + .setUid(uid); + Optional.ofNullable(paymentAccountKey).ifPresent(e -> builder.setPaymentAccountKey(ByteString.copyFrom(e))); + Optional.ofNullable(updatedMultisigHex).ifPresent(e -> builder.setUpdatedMultisigHex(updatedMultisigHex)); + return getNetworkEnvelopeBuilder().setPaymentAccountKeyResponse(builder).build(); } - public static PaymentAccountPayloadRequest fromProto(protobuf.PaymentAccountPayloadRequest proto, + public static PaymentAccountKeyResponse fromProto(protobuf.PaymentAccountKeyResponse proto, CoreProtoResolver coreProtoResolver, String messageVersion) { - return new PaymentAccountPayloadRequest(proto.getTradeId(), + return new PaymentAccountKeyResponse(proto.getTradeId(), NodeAddress.fromProto(proto.getSenderNodeAddress()), PubKeyRing.fromProto(proto.getPubKeyRing()), proto.getUid(), messageVersion, - proto.getCurrentDate(), - coreProtoResolver.fromProto(proto.getPaymentAccountPayload())); + ProtoUtil.byteArrayOrNullFromProto(proto.getPaymentAccountKey()), + ProtoUtil.stringOrNullFromProto(proto.getUpdatedMultisigHex())); } @Override public String toString() { - return "PaymentAccountPayloadRequest {" + + return "PaymentAccountKeyResponse {" + "\n senderNodeAddress=" + senderNodeAddress + ",\n pubKeyRing=" + pubKeyRing + - ",\n currentDate=" + currentDate + - ",\n paymentAccountPayload=" + paymentAccountPayload + + ",\n paymentAccountKey=" + paymentAccountKey + "\n} " + super.toString(); } } diff --git a/core/src/main/java/bisq/core/trade/messages/SignContractResponse.java b/core/src/main/java/bisq/core/trade/messages/SignContractResponse.java index d0f6ab6ff9..cd9ff89f86 100644 --- a/core/src/main/java/bisq/core/trade/messages/SignContractResponse.java +++ b/core/src/main/java/bisq/core/trade/messages/SignContractResponse.java @@ -21,12 +21,12 @@ import bisq.core.proto.CoreProtoResolver; import bisq.network.p2p.DirectMessage; import bisq.network.p2p.NodeAddress; - +import com.google.protobuf.ByteString; import bisq.common.crypto.PubKeyRing; import bisq.common.proto.ProtoUtil; import java.util.Optional; - +import javax.annotation.Nullable; import lombok.EqualsAndHashCode; import lombok.Value; @@ -38,6 +38,7 @@ public final class SignContractResponse extends TradeMessage implements DirectMe private final long currentDate; private final String contractAsJson; private final String contractSignature; + private final byte[] encryptedPaymentAccountPayload; public SignContractResponse(String tradeId, NodeAddress senderNodeAddress, @@ -46,13 +47,15 @@ public final class SignContractResponse extends TradeMessage implements DirectMe String messageVersion, long currentDate, String contractAsJson, - String contractSignature) { + String contractSignature, + @Nullable byte[] encryptedPaymentAccountPayload) { super(messageVersion, tradeId, uid); this.senderNodeAddress = senderNodeAddress; this.pubKeyRing = pubKeyRing; this.currentDate = currentDate; this.contractAsJson = contractAsJson; this.contractSignature = contractSignature; + this.encryptedPaymentAccountPayload = encryptedPaymentAccountPayload; } @@ -70,6 +73,7 @@ public final class SignContractResponse extends TradeMessage implements DirectMe Optional.ofNullable(contractAsJson).ifPresent(e -> builder.setContractAsJson(contractAsJson)); Optional.ofNullable(contractSignature).ifPresent(e -> builder.setContractSignature(contractSignature)); + Optional.ofNullable(encryptedPaymentAccountPayload).ifPresent(e -> builder.setEncryptedPaymentAccountPayload(ByteString.copyFrom(e))); builder.setCurrentDate(currentDate); @@ -86,7 +90,8 @@ public final class SignContractResponse extends TradeMessage implements DirectMe messageVersion, proto.getCurrentDate(), ProtoUtil.stringOrNullFromProto(proto.getContractAsJson()), - ProtoUtil.stringOrNullFromProto(proto.getContractSignature())); + ProtoUtil.stringOrNullFromProto(proto.getContractSignature()), + proto.getEncryptedPaymentAccountPayload().toByteArray()); } @Override diff --git a/core/src/main/java/bisq/core/trade/protocol/ArbitratorProtocol.java b/core/src/main/java/bisq/core/trade/protocol/ArbitratorProtocol.java index 965e11a77b..a1f33c6cd9 100644 --- a/core/src/main/java/bisq/core/trade/protocol/ArbitratorProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/ArbitratorProtocol.java @@ -5,11 +5,13 @@ import bisq.core.trade.Trade; import bisq.core.trade.messages.DepositRequest; import bisq.core.trade.messages.DepositResponse; import bisq.core.trade.messages.InitTradeRequest; -import bisq.core.trade.messages.PaymentAccountPayloadRequest; +import bisq.core.trade.messages.PaymentAccountKeyRequest; import bisq.core.trade.messages.SignContractResponse; +import bisq.core.trade.protocol.FluentProtocol.Condition; import bisq.core.trade.protocol.tasks.ApplyFilter; import bisq.core.trade.protocol.tasks.ArbitratorSendsInitTradeOrMultisigRequests; import bisq.core.trade.protocol.tasks.ArbitratorProcessesDepositRequest; +import bisq.core.trade.protocol.tasks.ArbitratorProcessesPaymentAccountKeyRequest; import bisq.core.trade.protocol.tasks.ArbitratorProcessesReserveTx; import bisq.core.trade.protocol.tasks.ProcessInitTradeRequest; import bisq.core.util.Validator; @@ -94,10 +96,30 @@ public class ArbitratorProtocol extends DisputeProtocol { public void handleDepositResponse(DepositResponse response, NodeAddress sender) { log.warn("Arbitrator ignoring DepositResponse"); } - - @Override - public void handlePaymentAccountPayloadRequest(PaymentAccountPayloadRequest request, NodeAddress sender) { - log.warn("Arbitrator ignoring PaymentAccountPayloadRequest"); + + public void handlePaymentAccountKeyRequest(PaymentAccountKeyRequest request, NodeAddress sender) { + System.out.println("ArbitratorProtocol.handlePaymentAccountKeyRequest() " + trade.getId()); + synchronized (trade) { + latchTrade(); + Validator.checkTradeId(processModel.getOfferId(), request); + processModel.setTradeMessage(request); + expect(new Condition(trade) + .with(request) + .from(sender)) + .setup(tasks( + ArbitratorProcessesPaymentAccountKeyRequest.class) + .using(new TradeTaskRunner(trade, + () -> { + stopTimeout(); + handleTaskRunnerSuccess(sender, request); + }, + errorMessage -> { + handleTaskRunnerFault(sender, request, errorMessage); + })) + .withTimeout(TRADE_TIMEOUT)) + .executeTasks(true); + awaitTradeLatch(); + } } /////////////////////////////////////////////////////////////////////////////////////////// diff --git a/core/src/main/java/bisq/core/trade/protocol/BuyerAsMakerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/BuyerAsMakerProtocol.java index 1b1769f88c..1cb6e838c7 100644 --- a/core/src/main/java/bisq/core/trade/protocol/BuyerAsMakerProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/BuyerAsMakerProtocol.java @@ -22,7 +22,7 @@ import bisq.core.trade.Trade; import bisq.core.trade.messages.DepositResponse; import bisq.core.trade.messages.InitMultisigRequest; import bisq.core.trade.messages.InitTradeRequest; -import bisq.core.trade.messages.PaymentAccountPayloadRequest; +import bisq.core.trade.messages.PaymentAccountKeyResponse; import bisq.core.trade.messages.PaymentReceivedMessage; import bisq.core.trade.messages.SignContractRequest; import bisq.core.trade.messages.SignContractResponse; @@ -100,8 +100,8 @@ public class BuyerAsMakerProtocol extends BuyerProtocol implements MakerProtocol } @Override - public void handlePaymentAccountPayloadRequest(PaymentAccountPayloadRequest request, NodeAddress sender) { - super.handlePaymentAccountPayloadRequest(request, sender); + public void handle(PaymentAccountKeyResponse request, NodeAddress sender) { + super.handle(request, sender); } /////////////////////////////////////////////////////////////////////////////////////////// diff --git a/core/src/main/java/bisq/core/trade/protocol/BuyerAsTakerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/BuyerAsTakerProtocol.java index f9803d3ef6..6146e3ac5a 100644 --- a/core/src/main/java/bisq/core/trade/protocol/BuyerAsTakerProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/BuyerAsTakerProtocol.java @@ -24,12 +24,14 @@ import bisq.core.trade.Trade; import bisq.core.trade.handlers.TradeResultHandler; import bisq.core.trade.messages.DepositResponse; import bisq.core.trade.messages.InitMultisigRequest; -import bisq.core.trade.messages.PaymentAccountPayloadRequest; +import bisq.core.trade.messages.PaymentAccountKeyResponse; import bisq.core.trade.messages.PaymentReceivedMessage; import bisq.core.trade.messages.SignContractRequest; import bisq.core.trade.messages.SignContractResponse; import bisq.core.trade.messages.TradeMessage; import bisq.core.trade.protocol.tasks.ApplyFilter; +import bisq.core.trade.protocol.tasks.TakerReservesTradeFunds; +import bisq.core.trade.protocol.tasks.TakerSendsInitTradeRequestToArbitrator; import bisq.network.p2p.NodeAddress; import bisq.common.handlers.ErrorMessageHandler; import bisq.common.handlers.ResultHandler; @@ -109,8 +111,8 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol } @Override - public void handlePaymentAccountPayloadRequest(PaymentAccountPayloadRequest request, NodeAddress sender) { - super.handlePaymentAccountPayloadRequest(request, sender); + public void handle(PaymentAccountKeyResponse request, NodeAddress sender) { + super.handle(request, sender); } /////////////////////////////////////////////////////////////////////////////////////////// diff --git a/core/src/main/java/bisq/core/trade/protocol/BuyerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/BuyerProtocol.java index 786e16eff3..e0a6143a56 100644 --- a/core/src/main/java/bisq/core/trade/protocol/BuyerProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/BuyerProtocol.java @@ -19,25 +19,36 @@ package bisq.core.trade.protocol; import bisq.core.trade.BuyerTrade; import bisq.core.trade.Trade; +import bisq.core.trade.messages.PaymentAccountKeyResponse; import bisq.core.trade.messages.PaymentReceivedMessage; +import bisq.core.trade.messages.SignContractResponse; import bisq.core.trade.messages.TradeMessage; +import bisq.core.trade.protocol.FluentProtocol.Condition; import bisq.core.trade.protocol.tasks.ApplyFilter; import bisq.core.trade.protocol.tasks.BuyerPreparesPaymentSentMessage; import bisq.core.trade.protocol.tasks.BuyerProcessesPaymentReceivedMessage; +import bisq.core.trade.protocol.tasks.BuyerSendsPaymentAccountKeyRequestToArbitrator; import bisq.core.trade.protocol.tasks.BuyerSendsPaymentSentMessage; import bisq.core.trade.protocol.tasks.BuyerSetupPayoutTxListener; +import bisq.core.trade.protocol.tasks.BuyerProcessesPaymentAccountKeyResponse; import bisq.core.trade.protocol.tasks.SetupDepositTxsListener; import bisq.core.util.Validator; import bisq.network.p2p.NodeAddress; +import bisq.common.UserThread; import bisq.common.handlers.ErrorMessageHandler; import bisq.common.handlers.ResultHandler; import lombok.extern.slf4j.Slf4j; +import org.fxmisc.easybind.EasyBind; @Slf4j public abstract class BuyerProtocol extends DisputeProtocol { + + private boolean listeningToSendPaymentAccountKey; + private boolean paymentAccountPayloadKeyRequestSent; enum BuyerEvent implements FluentProtocol.Event { STARTUP, + DEPOSIT_TXS_CONFIRMED, PAYMENT_SENT } @@ -54,6 +65,9 @@ public abstract class BuyerProtocol extends DisputeProtocol { super.onInitialized(); // TODO: run with trade lock and latch, otherwise getting invalid transition warnings on startup after offline trades + + // request key to decrypt seller's payment account payload after first confirmation + sendPaymentAccountKeyRequestIfWhenNeeded(BuyerEvent.STARTUP, false); given(anyPhase(Trade.Phase.DEPOSIT_REQUESTED, Trade.Phase.DEPOSITS_PUBLISHED, Trade.Phase.DEPOSITS_CONFIRMED) .with(BuyerEvent.STARTUP)) @@ -66,21 +80,60 @@ public abstract class BuyerProtocol extends DisputeProtocol { .executeTasks(); given(anyPhase(Trade.Phase.PAYMENT_SENT, Trade.Phase.PAYMENT_RECEIVED) - .anyState(Trade.State.BUYER_STORED_IN_MAILBOX_PAYMENT_SENT_MSG, - Trade.State.BUYER_SEND_FAILED_PAYMENT_SENT_MSG) + .anyState(Trade.State.BUYER_STORED_IN_MAILBOX_PAYMENT_SENT_MSG, Trade.State.BUYER_SEND_FAILED_PAYMENT_SENT_MSG) .with(BuyerEvent.STARTUP)) .setup(tasks(BuyerSendsPaymentSentMessage.class)) .executeTasks(); } + @Override + protected void onTradeMessage(TradeMessage message, NodeAddress peer) { + super.onTradeMessage(message, peer); + if (message instanceof PaymentReceivedMessage) { + handle((PaymentReceivedMessage) message, peer); + } if (message instanceof PaymentAccountKeyResponse) { + handle((PaymentAccountKeyResponse) message, peer); + } + } + @Override public void onMailboxMessage(TradeMessage message, NodeAddress peer) { super.onMailboxMessage(message, peer); if (message instanceof PaymentReceivedMessage) { handle((PaymentReceivedMessage) message, peer); + } else if (message instanceof PaymentAccountKeyResponse) { + handle((PaymentAccountKeyResponse) message, peer); } } + @Override + public void handleSignContractResponse(SignContractResponse response, NodeAddress sender) { + sendPaymentAccountKeyRequestIfWhenNeeded(BuyerEvent.DEPOSIT_TXS_CONFIRMED, true); + super.handleSignContractResponse(response, sender); + } + + public void handle(PaymentAccountKeyResponse response, NodeAddress sender) { + System.out.println(getClass().getCanonicalName() + ".handlePaymentAccountKeyResponse()"); + new Thread(() -> { + synchronized (trade) { + latchTrade(); + expect(new Condition(trade) + .with(response) + .from(sender)) + .setup(tasks(BuyerProcessesPaymentAccountKeyResponse.class) + .using(new TradeTaskRunner(trade, + () -> { + handleTaskRunnerSuccess(sender, response); + }, + errorMessage -> { + handleTaskRunnerFault(sender, response, errorMessage); + }))) + .executeTasks(); + awaitTradeLatch(); + } + }).start(); + } + /////////////////////////////////////////////////////////////////////////////////////////// // User interaction /////////////////////////////////////////////////////////////////////////////////////////// @@ -126,7 +179,7 @@ public abstract class BuyerProtocol extends DisputeProtocol { /////////////////////////////////////////////////////////////////////////////////////////// protected void handle(PaymentReceivedMessage message, NodeAddress peer) { - log.info("BuyerProtocol.handle(PaymentReceivedMessage)"); + System.out.println("BuyerProtocol.handle(PaymentReceivedMessage)"); new Thread(() -> { synchronized (trade) { latchTrade(); @@ -150,16 +203,54 @@ public abstract class BuyerProtocol extends DisputeProtocol { }).start(); } + private void sendPaymentAccountKeyRequestIfWhenNeeded(BuyerEvent event, boolean waitForSellerOnConfirm) { - /////////////////////////////////////////////////////////////////////////////////////////// - // Message dispatcher - /////////////////////////////////////////////////////////////////////////////////////////// + // skip if payment account payload already decrypted or not enough progress + if (trade.getSeller().getPaymentAccountPayload() != null) return; + if (trade.getPhase().ordinal() < Trade.Phase.DEPOSIT_REQUESTED.ordinal()) return; - @Override - protected void onTradeMessage(TradeMessage message, NodeAddress peer) { - super.onTradeMessage(message, peer); - if (message instanceof PaymentReceivedMessage) { - handle((PaymentReceivedMessage) message, peer); + // if confirmed and waiting for seller, recheck later + if (trade.getState() == Trade.State.DEPOSIT_TXS_CONFIRMED_IN_BLOCKCHAIN && waitForSellerOnConfirm) { + UserThread.runAfter(() -> { + sendPaymentAccountKeyRequestIfWhenNeeded(event, false); + }, TRADE_TIMEOUT); + return; + } + + // else if confirmed send request and return + else if (trade.getState().ordinal() >= Trade.State.DEPOSIT_TXS_CONFIRMED_IN_BLOCKCHAIN.ordinal()) { + sendPaymentAccountKeyRequest(event); + return; + } + + // register for state changes once + if (!listeningToSendPaymentAccountKey) { + listeningToSendPaymentAccountKey = true; + EasyBind.subscribe(trade.stateProperty(), state -> { + sendPaymentAccountKeyRequestIfWhenNeeded(event, waitForSellerOnConfirm); + }); } } + + private void sendPaymentAccountKeyRequest(BuyerEvent event) { + new Thread(() -> { + synchronized (trade) { + if (paymentAccountPayloadKeyRequestSent) return; + if (trade.getSeller().getPaymentAccountPayload() != null) return; // skip if initialized + latchTrade(); + expect(new Condition(trade)) + .setup(tasks(BuyerSendsPaymentAccountKeyRequestToArbitrator.class) + .using(new TradeTaskRunner(trade, + () -> { + handleTaskRunnerSuccess(event); + }, + (errorMessage) -> { + handleTaskRunnerFault(event, errorMessage); + }))) + .executeTasks(true); + awaitTradeLatch(); + paymentAccountPayloadKeyRequestSent = true; + } + }).start(); + } } diff --git a/core/src/main/java/bisq/core/trade/protocol/SellerAsMakerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/SellerAsMakerProtocol.java index 7de59b5acd..234d03aa5c 100644 --- a/core/src/main/java/bisq/core/trade/protocol/SellerAsMakerProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/SellerAsMakerProtocol.java @@ -26,7 +26,6 @@ import bisq.core.trade.messages.SignContractResponse; import bisq.core.trade.messages.DepositResponse; import bisq.core.trade.messages.InitMultisigRequest; import bisq.core.trade.messages.InitTradeRequest; -import bisq.core.trade.messages.PaymentAccountPayloadRequest; import bisq.core.trade.messages.TradeMessage; import bisq.core.trade.protocol.tasks.MakerSendsInitTradeRequest; import bisq.core.trade.protocol.tasks.ProcessInitTradeRequest; @@ -101,11 +100,6 @@ public class SellerAsMakerProtocol extends SellerProtocol implements MakerProtoc super.handleDepositResponse(response, sender); } - @Override - public void handlePaymentAccountPayloadRequest(PaymentAccountPayloadRequest request, NodeAddress sender) { - super.handlePaymentAccountPayloadRequest(request, sender); - } - /////////////////////////////////////////////////////////////////////////////////////////// // User interaction /////////////////////////////////////////////////////////////////////////////////////////// diff --git a/core/src/main/java/bisq/core/trade/protocol/SellerAsTakerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/SellerAsTakerProtocol.java index dac7882f77..80deda53f3 100644 --- a/core/src/main/java/bisq/core/trade/protocol/SellerAsTakerProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/SellerAsTakerProtocol.java @@ -27,9 +27,10 @@ import bisq.core.trade.messages.SignContractRequest; import bisq.core.trade.messages.SignContractResponse; import bisq.core.trade.messages.DepositResponse; import bisq.core.trade.messages.InitMultisigRequest; -import bisq.core.trade.messages.PaymentAccountPayloadRequest; import bisq.core.trade.messages.TradeMessage; import bisq.core.trade.protocol.tasks.ApplyFilter; +import bisq.core.trade.protocol.tasks.TakerReservesTradeFunds; +import bisq.core.trade.protocol.tasks.TakerSendsInitTradeRequestToArbitrator; import bisq.network.p2p.NodeAddress; import bisq.common.handlers.ErrorMessageHandler; import bisq.common.handlers.ResultHandler; @@ -107,11 +108,6 @@ public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtoc super.handleDepositResponse(response, sender); } - @Override - public void handlePaymentAccountPayloadRequest(PaymentAccountPayloadRequest request, NodeAddress sender) { - super.handlePaymentAccountPayloadRequest(request, sender); - } - /////////////////////////////////////////////////////////////////////////////////////////// // Incoming message when buyer has clicked payment started button /////////////////////////////////////////////////////////////////////////////////////////// diff --git a/core/src/main/java/bisq/core/trade/protocol/SellerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/SellerProtocol.java index b79c07f8a5..9fc6e6f184 100644 --- a/core/src/main/java/bisq/core/trade/protocol/SellerProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/SellerProtocol.java @@ -19,24 +19,29 @@ package bisq.core.trade.protocol; import bisq.core.trade.SellerTrade; import bisq.core.trade.Trade; +import bisq.core.trade.messages.DepositResponse; import bisq.core.trade.messages.PaymentSentMessage; +import bisq.core.trade.messages.SignContractResponse; import bisq.core.trade.messages.TradeMessage; -import bisq.core.trade.protocol.BuyerProtocol.BuyerEvent; +import bisq.core.trade.protocol.FluentProtocol.Condition; import bisq.core.trade.protocol.tasks.ApplyFilter; import bisq.core.trade.protocol.tasks.SellerPreparesPaymentReceivedMessage; import bisq.core.trade.protocol.tasks.SellerProcessesPaymentSentMessage; import bisq.core.trade.protocol.tasks.SellerSendsPaymentReceivedMessage; +import bisq.core.trade.protocol.tasks.SellerSendsPaymentAccountPayloadKey; import bisq.core.trade.protocol.tasks.SetupDepositTxsListener; import bisq.network.p2p.NodeAddress; import bisq.common.handlers.ErrorMessageHandler; import bisq.common.handlers.ResultHandler; import lombok.extern.slf4j.Slf4j; +import org.fxmisc.easybind.EasyBind; @Slf4j public abstract class SellerProtocol extends DisputeProtocol { enum SellerEvent implements FluentProtocol.Event { STARTUP, + DEPOSIT_TXS_CONFIRMED, PAYMENT_RECEIVED } @@ -50,16 +55,25 @@ public abstract class SellerProtocol extends DisputeProtocol { // TODO: run with trade lock and latch, otherwise getting invalid transition warnings on startup after offline trades + // send payment account payload key when trade state is confirmed + if (trade.getPhase() == Trade.Phase.DEPOSIT_REQUESTED || trade.getPhase() == Trade.Phase.DEPOSITS_PUBLISHED) { + sendPaymentAccountPayloadKeyWhenConfirmed(SellerEvent.STARTUP); + } + + // listen for changes to deposit txs given(anyPhase(Trade.Phase.DEPOSIT_REQUESTED, Trade.Phase.DEPOSITS_PUBLISHED, Trade.Phase.DEPOSITS_CONFIRMED) - .with(BuyerEvent.STARTUP)) + .with(SellerEvent.STARTUP)) .setup(tasks(SetupDepositTxsListener.class)) .executeTasks(); } - - /////////////////////////////////////////////////////////////////////////////////////////// - // Mailbox - /////////////////////////////////////////////////////////////////////////////////////////// + @Override + protected void onTradeMessage(TradeMessage message, NodeAddress peer) { + super.onTradeMessage(message, peer); + if (message instanceof PaymentSentMessage) { + handle((PaymentSentMessage) message, peer); + } + } @Override public void onMailboxMessage(TradeMessage message, NodeAddress peerNodeAddress) { @@ -70,6 +84,12 @@ public abstract class SellerProtocol extends DisputeProtocol { } } + @Override + public void handleSignContractResponse(SignContractResponse response, NodeAddress sender) { + sendPaymentAccountPayloadKeyWhenConfirmed(SellerEvent.DEPOSIT_TXS_CONFIRMED); + super.handleSignContractResponse(response, sender); + } + /////////////////////////////////////////////////////////////////////////////////////////// // Incoming message when buyer has clicked payment started button @@ -154,12 +174,26 @@ public abstract class SellerProtocol extends DisputeProtocol { }).start(); } - @Override - protected void onTradeMessage(TradeMessage message, NodeAddress peer) { - super.onTradeMessage(message, peer); - - if (message instanceof PaymentSentMessage) { - handle((PaymentSentMessage) message, peer); - } + private void sendPaymentAccountPayloadKeyWhenConfirmed(SellerEvent event) { + EasyBind.subscribe(trade.stateProperty(), state -> { + if (state == Trade.State.DEPOSIT_TXS_CONFIRMED_IN_BLOCKCHAIN) { + new Thread(() -> { + synchronized (trade) { + latchTrade(); + expect(new Condition(trade)) + .setup(tasks(SellerSendsPaymentAccountPayloadKey.class) + .using(new TradeTaskRunner(trade, + () -> { + handleTaskRunnerSuccess(event); + }, + (errorMessage) -> { + handleTaskRunnerFault(event, errorMessage); + }))) + .executeTasks(true); + awaitTradeLatch(); + } + }).start(); + } + }); } } diff --git a/core/src/main/java/bisq/core/trade/protocol/TradeProtocol.java b/core/src/main/java/bisq/core/trade/protocol/TradeProtocol.java index 40d5cc275e..7402d4d3ce 100644 --- a/core/src/main/java/bisq/core/trade/protocol/TradeProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/TradeProtocol.java @@ -25,16 +25,13 @@ import bisq.core.trade.handlers.TradeResultHandler; import bisq.core.trade.messages.PaymentSentMessage; import bisq.core.trade.messages.DepositResponse; import bisq.core.trade.messages.InitMultisigRequest; -import bisq.core.trade.messages.PaymentAccountPayloadRequest; import bisq.core.trade.messages.SignContractRequest; import bisq.core.trade.messages.SignContractResponse; import bisq.core.trade.messages.TradeMessage; import bisq.core.trade.messages.UpdateMultisigRequest; -import bisq.core.trade.protocol.tasks.MaybeRemoveOpenOffer; import bisq.core.trade.protocol.tasks.MaybeSendSignContractRequest; import bisq.core.trade.protocol.tasks.ProcessDepositResponse; import bisq.core.trade.protocol.tasks.ProcessInitMultisigRequest; -import bisq.core.trade.protocol.tasks.ProcessPaymentAccountPayloadRequest; import bisq.core.trade.protocol.tasks.ProcessSignContractRequest; import bisq.core.trade.protocol.tasks.ProcessSignContractResponse; import bisq.core.trade.protocol.tasks.ProcessUpdateMultisigRequest; @@ -331,8 +328,10 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D ProcessDepositResponse.class) .using(new TradeTaskRunner(trade, () -> { - startTimeout(TRADE_TIMEOUT); + stopTimeout(); + this.errorMessageHandler = null; handleTaskRunnerSuccess(sender, response); + if (tradeResultHandler != null) tradeResultHandler.handleResult(trade); // trade is initialized }, errorMessage -> { handleTaskRunnerFault(sender, response, errorMessage); @@ -343,42 +342,6 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D } } - public void handlePaymentAccountPayloadRequest(PaymentAccountPayloadRequest request, NodeAddress sender) { - System.out.println(getClass().getCanonicalName() + ".handlePaymentAccountPayloadRequest()"); - synchronized (trade) { - Validator.checkTradeId(processModel.getOfferId(), request); - if (trade.getState() == Trade.State.ARBITRATOR_PUBLISHED_DEPOSIT_TXS) { - latchTrade(); - Validator.checkTradeId(processModel.getOfferId(), request); - processModel.setTradeMessage(request); - expect(state(Trade.State.ARBITRATOR_PUBLISHED_DEPOSIT_TXS) - .with(request) - .from(sender)) // TODO (woodser): ensure this asserts sender == response.getSenderNodeAddress() - .setup(tasks( - // TODO (woodser): validate request - ProcessPaymentAccountPayloadRequest.class, - MaybeRemoveOpenOffer.class) - .using(new TradeTaskRunner(trade, - () -> { - stopTimeout(); - this.errorMessageHandler = null; - handleTaskRunnerSuccess(sender, request); - if (tradeResultHandler != null) tradeResultHandler.handleResult(trade); // trade is initialized - }, - errorMessage -> { - handleTaskRunnerFault(sender, request, errorMessage); - })) - .withTimeout(TRADE_TIMEOUT)) - .executeTasks(true); - awaitTradeLatch(); - } else { - EasyBind.subscribe(trade.stateProperty(), state -> { - if (state == Trade.State.ARBITRATOR_PUBLISHED_DEPOSIT_TXS) new Thread(() -> handlePaymentAccountPayloadRequest(request, sender)).start(); // process notification without trade lock - }); - } - } - } - // TODO (woodser): update to use fluent for consistency public void handleUpdateMultisigRequest(UpdateMultisigRequest message, NodeAddress peer, ErrorMessageHandler errorMessageHandler) { latchTrade(); diff --git a/core/src/main/java/bisq/core/trade/protocol/TraderProtocol.java b/core/src/main/java/bisq/core/trade/protocol/TraderProtocol.java index 842b00fbdf..67ce4bfa2e 100644 --- a/core/src/main/java/bisq/core/trade/protocol/TraderProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/TraderProtocol.java @@ -19,12 +19,10 @@ package bisq.core.trade.protocol; import bisq.core.trade.messages.DepositResponse; -import bisq.core.trade.messages.PaymentAccountPayloadRequest; import bisq.core.trade.messages.SignContractResponse; import bisq.network.p2p.NodeAddress; public interface TraderProtocol { public void handleSignContractResponse(SignContractResponse message, NodeAddress peer); public void handleDepositResponse(DepositResponse response, NodeAddress peer); - public void handlePaymentAccountPayloadRequest(PaymentAccountPayloadRequest request, NodeAddress peer); } diff --git a/core/src/main/java/bisq/core/trade/protocol/TradingPeer.java b/core/src/main/java/bisq/core/trade/protocol/TradingPeer.java index 7302ee3e19..eaa67fe9e1 100644 --- a/core/src/main/java/bisq/core/trade/protocol/TradingPeer.java +++ b/core/src/main/java/bisq/core/trade/protocol/TradingPeer.java @@ -64,6 +64,10 @@ public final class TradingPeer implements PersistablePayload { @Nullable private byte[] paymentAccountPayloadHash; @Nullable + private byte[] encryptedPaymentAccountPayload; + @Nullable + private byte[] paymentAccountKey; + @Nullable private PaymentAccountPayload paymentAccountPayload; @Nullable private String payoutAddressString; @@ -134,6 +138,8 @@ public final class TradingPeer implements PersistablePayload { Optional.ofNullable(paymentAccountId).ifPresent(builder::setPaymentAccountId); Optional.ofNullable(paymentMethodId).ifPresent(builder::setPaymentMethodId); Optional.ofNullable(paymentAccountPayloadHash).ifPresent(e -> builder.setPaymentAccountPayloadHash(ByteString.copyFrom(paymentAccountPayloadHash))); + Optional.ofNullable(encryptedPaymentAccountPayload).ifPresent(e -> builder.setEncryptedPaymentAccountPayload(ByteString.copyFrom(e))); + Optional.ofNullable(paymentAccountKey).ifPresent(e -> builder.setPaymentAccountKey(ByteString.copyFrom(e))); Optional.ofNullable(paymentAccountPayload).ifPresent(e -> builder.setPaymentAccountPayload((protobuf.PaymentAccountPayload) e.toProtoMessage())); Optional.ofNullable(payoutAddressString).ifPresent(builder::setPayoutAddressString); Optional.ofNullable(contractAsJson).ifPresent(builder::setContractAsJson); @@ -173,6 +179,8 @@ public final class TradingPeer implements PersistablePayload { tradingPeer.setPaymentAccountId(ProtoUtil.stringOrNullFromProto(proto.getPaymentAccountId())); tradingPeer.setPaymentMethodId(ProtoUtil.stringOrNullFromProto(proto.getPaymentMethodId())); tradingPeer.setPaymentAccountPayloadHash(proto.getPaymentAccountPayloadHash().toByteArray()); + tradingPeer.setEncryptedPaymentAccountPayload(proto.getEncryptedPaymentAccountPayload().toByteArray()); + tradingPeer.setPaymentAccountKey(ProtoUtil.byteArrayOrNullFromProto(proto.getPaymentAccountKey())); tradingPeer.setPaymentAccountPayload(proto.hasPaymentAccountPayload() ? coreProtoResolver.fromProto(proto.getPaymentAccountPayload()) : null); tradingPeer.setPayoutAddressString(ProtoUtil.stringOrNullFromProto(proto.getPayoutAddressString())); tradingPeer.setContractAsJson(ProtoUtil.stringOrNullFromProto(proto.getContractAsJson())); diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/ArbitratorProcessesDepositRequest.java b/core/src/main/java/bisq/core/trade/protocol/tasks/ArbitratorProcessesDepositRequest.java index eaa92b8505..da33a36b91 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/ArbitratorProcessesDepositRequest.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/ArbitratorProcessesDepositRequest.java @@ -96,6 +96,7 @@ public class ArbitratorProcessesDepositRequest extends TradeTask { // set deposit info trader.setDepositTxHex(request.getDepositTxHex()); trader.setDepositTxKey(request.getDepositTxKey()); + if (request.getPaymentAccountKey() != null) trader.setPaymentAccountKey(request.getPaymentAccountKey()); // relay deposit txs when both available // TODO (woodser): add small delay so tx has head start against double spend attempts? diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/ArbitratorProcessesPaymentAccountKeyRequest.java b/core/src/main/java/bisq/core/trade/protocol/tasks/ArbitratorProcessesPaymentAccountKeyRequest.java new file mode 100644 index 0000000000..d328fbc1ea --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/ArbitratorProcessesPaymentAccountKeyRequest.java @@ -0,0 +1,77 @@ +/* + * This file is part of Haveno. + * + * Haveno is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Haveno is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Haveno. If not, see . + */ + +package bisq.core.trade.protocol.tasks; + + +import bisq.common.app.Version; +import bisq.common.crypto.PubKeyRing; +import bisq.common.taskrunner.TaskRunner; +import bisq.core.trade.Trade; +import bisq.core.trade.messages.PaymentAccountKeyResponse; +import bisq.network.p2p.NodeAddress; +import bisq.network.p2p.SendDirectMessageListener; +import java.util.UUID; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class ArbitratorProcessesPaymentAccountKeyRequest extends TradeTask { + + @SuppressWarnings({"unused"}) + public ArbitratorProcessesPaymentAccountKeyRequest(TaskRunner taskHandler, Trade trade) { + super(taskHandler, trade); + } + + @Override + protected void run() { + try { + runInterceptHook(); + + // create response for buyer with key to decrypt seller's payment account payload + PaymentAccountKeyResponse response = new PaymentAccountKeyResponse( + trade.getId(), + processModel.getMyNodeAddress(), + processModel.getPubKeyRing(), + UUID.randomUUID().toString(), + Version.getP2PMessageVersion(), + trade.getSeller().getPaymentAccountKey(), + null + ); + + // send response to buyer + boolean isMakerBuyer = trade.getOffer().isBuyOffer(); + NodeAddress buyerAddress = isMakerBuyer ? trade.getMakerNodeAddress() : trade.getTakerNodeAddress(); // TODO: trade.getBuyer().getNodeAddress() + PubKeyRing buyerPubKeyRing = isMakerBuyer ? trade.getMakerPubKeyRing() : trade.getTakerPubKeyRing(); + log.info("Arbitrator sending PaymentAccountKeyResponse to buyer={}; offerId={}", buyerAddress, trade.getId()); + processModel.getP2PService().sendEncryptedDirectMessage(buyerAddress, buyerPubKeyRing, response, new SendDirectMessageListener() { + @Override + public void onArrived() { + log.info("{} arrived: trading peer={}; offerId={}; uid={}", response.getClass().getSimpleName(), buyerAddress, trade.getId()); + complete(); + } + @Override + public void onFault(String errorMessage) { + log.error("Sending {} failed: uid={}; peer={}; error={}", response.getClass().getSimpleName(), buyerAddress, trade.getId(), errorMessage); + appendToErrorMessage("Sending message failed: message=" + response + "\nerrorMessage=" + errorMessage); + failed(); + } + }); + } catch (Throwable t) { + failed(t); + } + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/BuyerPreparesPaymentSentMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/BuyerPreparesPaymentSentMessage.java index 8070fa863c..7662615865 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/BuyerPreparesPaymentSentMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/BuyerPreparesPaymentSentMessage.java @@ -51,27 +51,29 @@ public class BuyerPreparesPaymentSentMessage extends TradeTask { protected void run() { try { runInterceptHook(); - + // validate state + Preconditions.checkNotNull(trade.getSeller().getPaymentAccountPayload(), "Seller's payment account payload is null"); Preconditions.checkNotNull(trade.getAmount(), "trade.getTradeAmount() must not be null"); Preconditions.checkNotNull(trade.getMakerDepositTx(), "trade.getMakerDepositTx() must not be null"); Preconditions.checkNotNull(trade.getTakerDepositTx(), "trade.getTakerDepositTx() must not be null"); checkNotNull(trade.getOffer(), "offer must not be null"); - + // get multisig wallet XmrWalletService walletService = processModel.getProvider().getXmrWalletService(); MoneroWallet multisigWallet = walletService.getMultisigWallet(trade.getId()); // create payout tx if we have seller's updated multisig hex - if (!multisigWallet.isMultisigImportNeeded()) { + if (trade.getTradingPeer().getUpdatedMultisigHex() != null) { log.info("Buyer creating unsigned payout tx"); + multisigWallet.importMultisigHex(trade.getTradingPeer().getUpdatedMultisigHex()); MoneroTxWallet payoutTx = trade.createPayoutTx(); trade.getBuyer().setPayoutTx(payoutTx); trade.getBuyer().setPayoutTxHex(payoutTx.getTxSet().getMultisigTxHex()); } else { if (trade.getSelf().getUpdatedMultisigHex() == null) trade.getSelf().setUpdatedMultisigHex(multisigWallet.exportMultisigHex()); // only export multisig hex once } - + // close multisig wallet walletService.closeMultisigWallet(trade.getId()); complete(); diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessPaymentAccountPayloadRequest.java b/core/src/main/java/bisq/core/trade/protocol/tasks/BuyerProcessesPaymentAccountKeyResponse.java similarity index 51% rename from core/src/main/java/bisq/core/trade/protocol/tasks/ProcessPaymentAccountPayloadRequest.java rename to core/src/main/java/bisq/core/trade/protocol/tasks/BuyerProcessesPaymentAccountKeyResponse.java index 8e7c8ddf10..595be36ce3 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessPaymentAccountPayloadRequest.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/BuyerProcessesPaymentAccountKeyResponse.java @@ -18,24 +18,23 @@ package bisq.core.trade.protocol.tasks; +import bisq.common.crypto.Encryption; import bisq.common.taskrunner.TaskRunner; import bisq.core.payment.payload.PaymentAccountPayload; +import bisq.core.proto.network.CoreNetworkProtoResolver; import bisq.core.trade.MakerTrade; import bisq.core.trade.Trade; -import bisq.core.trade.Trade.State; -import bisq.core.trade.messages.PaymentAccountPayloadRequest; +import bisq.core.trade.messages.PaymentAccountKeyResponse; +import java.time.Clock; import java.util.Arrays; +import javax.crypto.SecretKey; import lombok.extern.slf4j.Slf4j; -import org.fxmisc.easybind.EasyBind; -import org.fxmisc.easybind.Subscription; @Slf4j -public class ProcessPaymentAccountPayloadRequest extends TradeTask { - - private Subscription tradeStateSubscription; +public class BuyerProcessesPaymentAccountKeyResponse extends TradeTask { @SuppressWarnings({"unused"}) - public ProcessPaymentAccountPayloadRequest(TaskRunner taskHandler, Trade trade) { + public BuyerProcessesPaymentAccountKeyResponse(TaskRunner taskHandler, Trade trade) { super(taskHandler, trade); } @@ -43,11 +42,27 @@ public class ProcessPaymentAccountPayloadRequest extends TradeTask { protected void run() { try { runInterceptHook(); - if (trade.getTradingPeer().getPaymentAccountPayload() != null) throw new RuntimeException("Peer's payment account payload has already been set"); - // get peer's payment account payload - PaymentAccountPayloadRequest request = (PaymentAccountPayloadRequest) processModel.getTradeMessage(); // TODO (woodser): verify request - PaymentAccountPayload paymentAccountPayload = request.getPaymentAccountPayload(); + // update peer node address if not from arbitrator + if (!processModel.getTempTradingPeerNodeAddress().equals(trade.getArbitratorNodeAddress())) { + trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress()); + } + + // buyer may already have decrypted payment account payload from arbitrator request + if (trade.getTradingPeer().getPaymentAccountPayload() != null) { + complete(); + return; + } + + // get peer's payment account payload key + PaymentAccountKeyResponse request = (PaymentAccountKeyResponse) processModel.getTradeMessage(); // TODO (woodser): verify request + trade.getTradingPeer().setPaymentAccountKey(request.getPaymentAccountKey()); + + // decrypt peer's payment account payload + SecretKey sk = Encryption.getSecretKeyFromBytes(trade.getTradingPeer().getPaymentAccountKey()); + byte[] decryptedPaymentAccountPayload = Encryption.decrypt(trade.getTradingPeer().getEncryptedPaymentAccountPayload(), sk); + CoreNetworkProtoResolver resolver = new CoreNetworkProtoResolver(Clock.systemDefaultZone()); // TODO: reuse resolver from elsewhere? + PaymentAccountPayload paymentAccountPayload = resolver.fromProto(protobuf.PaymentAccountPayload.parseFrom(decryptedPaymentAccountPayload)); // verify hash of payment account payload byte[] peerPaymentAccountPayloadHash = trade instanceof MakerTrade ? trade.getContract().getTakerPaymentAccountPayloadHash() : trade.getContract().getMakerPaymentAccountPayloadHash(); @@ -56,6 +71,9 @@ public class ProcessPaymentAccountPayloadRequest extends TradeTask { // set payment account payload trade.getTradingPeer().setPaymentAccountPayload(paymentAccountPayload); + // store updated multisig hex for processing on payment sent + trade.getTradingPeer().setUpdatedMultisigHex(request.getUpdatedMultisigHex()); + // persist and complete processModel.getTradeManager().requestPersistence(); complete(); diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/BuyerProcessesPaymentReceivedMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/BuyerProcessesPaymentReceivedMessage.java index 04f7ccbba3..37cd304d3f 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/BuyerProcessesPaymentReceivedMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/BuyerProcessesPaymentReceivedMessage.java @@ -54,7 +54,7 @@ public class BuyerProcessesPaymentReceivedMessage extends TradeTask { // update to the latest peer address of our peer if the message is correct trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress()); - + // handle if payout tx is not seen on network if (trade.getPayoutTx() == null) { diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/BuyerSendsPaymentAccountKeyRequestToArbitrator.java b/core/src/main/java/bisq/core/trade/protocol/tasks/BuyerSendsPaymentAccountKeyRequestToArbitrator.java new file mode 100644 index 0000000000..979c710788 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/BuyerSendsPaymentAccountKeyRequestToArbitrator.java @@ -0,0 +1,73 @@ +/* + * This file is part of Haveno. + * + * Haveno is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Haveno is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Haveno. If not, see . + */ + +package bisq.core.trade.protocol.tasks; + +import bisq.common.app.Version; +import bisq.common.taskrunner.TaskRunner; +import bisq.core.trade.Trade; +import bisq.core.trade.messages.InitTradeRequest; +import bisq.core.trade.messages.PaymentAccountKeyRequest; +import bisq.network.p2p.SendDirectMessageListener; +import java.util.UUID; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class BuyerSendsPaymentAccountKeyRequestToArbitrator extends TradeTask { + + @SuppressWarnings({"unused"}) + public BuyerSendsPaymentAccountKeyRequestToArbitrator(TaskRunner taskHandler, Trade trade) { + super(taskHandler, trade); + } + + @Override + protected void run() { + try { + runInterceptHook(); + + // create request to arbitrator + PaymentAccountKeyRequest request = new PaymentAccountKeyRequest( + trade.getId(), + processModel.getMyNodeAddress(), + processModel.getPubKeyRing(), + UUID.randomUUID().toString(), + Version.getP2PMessageVersion() + ); + + // send request to arbitrator + log.info("Sending {} with offerId {} and uid {} to arbitrator {} with pub key ring {}", request.getClass().getSimpleName(), request.getTradeId(), request.getUid(), trade.getArbitratorNodeAddress(), trade.getArbitratorPubKeyRing()); + processModel.getP2PService().sendEncryptedDirectMessage( + trade.getArbitratorNodeAddress(), + trade.getArbitratorPubKeyRing(), + request, + new SendDirectMessageListener() { + @Override + public void onArrived() { + log.info("{} arrived at arbitrator: offerId={}", PaymentAccountKeyRequest.class.getSimpleName(), trade.getId()); + complete(); + } + @Override + public void onFault(String errorMessage) { + log.warn("Failed to send {} to arbitrator, error={}.", PaymentAccountKeyRequest.class.getSimpleName(), errorMessage); + failed(); + } + }); + } catch (Throwable t) { + failed(t); + } + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/MaybeSendSignContractRequest.java b/core/src/main/java/bisq/core/trade/protocol/tasks/MaybeSendSignContractRequest.java index 2ed19d8d49..5c1acb7f5d 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/MaybeSendSignContractRequest.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/MaybeSendSignContractRequest.java @@ -81,6 +81,7 @@ public class MaybeSendSignContractRequest extends TradeTask { processModel.setDepositTxXmr(depositTx); // TODO: trade.getSelf().setDepositTx() trade.getSelf().setDepositTxHash(depositTx.getHash()); trade.getSelf().setPayoutAddressString(trade.getXmrWalletService().getAddressEntry(processModel.getOffer().getId(), XmrAddressEntry.Context.TRADE_PAYOUT).get().getAddressString()); // TODO (woodser): allow custom payout address? + trade.getSelf().setPaymentAccountPayload(trade.getProcessModel().getPaymentAccountPayload(trade)); // create request for peer and arbitrator to sign contract SignContractRequest request = new SignContractRequest( @@ -91,7 +92,7 @@ public class MaybeSendSignContractRequest extends TradeTask { Version.getP2PMessageVersion(), new Date().getTime(), trade.getProcessModel().getAccountId(), - trade.getProcessModel().getPaymentAccountPayload(trade).getHash(), + trade.getSelf().getPaymentAccountPayload().getHash(), trade.getSelf().getPayoutAddressString(), depositTx.getHash()); diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessDepositResponse.java b/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessDepositResponse.java index 5d39362315..b19e4455ce 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessDepositResponse.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessDepositResponse.java @@ -18,13 +18,8 @@ package bisq.core.trade.protocol.tasks; -import bisq.common.app.Version; import bisq.common.taskrunner.TaskRunner; import bisq.core.trade.Trade; -import bisq.core.trade.messages.PaymentAccountPayloadRequest; -import bisq.network.p2p.SendDirectMessageListener; -import java.util.Date; -import java.util.UUID; import lombok.extern.slf4j.Slf4j; @Slf4j @@ -39,37 +34,9 @@ public class ProcessDepositResponse extends TradeTask { protected void run() { try { runInterceptHook(); - - // arbitrator has broadcast deposit txs trade.setState(Trade.State.ARBITRATOR_PUBLISHED_DEPOSIT_TXS); - - // set payment account payload - trade.getSelf().setPaymentAccountPayload(processModel.getPaymentAccountPayload(trade)); - - // create request with payment account payload - PaymentAccountPayloadRequest request = new PaymentAccountPayloadRequest( - trade.getOffer().getId(), - processModel.getMyNodeAddress(), - processModel.getPubKeyRing(), - UUID.randomUUID().toString(), - Version.getP2PMessageVersion(), - new Date().getTime(), - trade.getSelf().getPaymentAccountPayload()); - - // send payment account payload to trading peer - processModel.getP2PService().sendEncryptedDirectMessage(trade.getTradingPeerNodeAddress(), trade.getTradingPeerPubKeyRing(), request, new SendDirectMessageListener() { - @Override - public void onArrived() { - log.info("{} arrived: trading peer={}; offerId={}; uid={}", request.getClass().getSimpleName(), trade.getTradingPeerNodeAddress(), trade.getId()); - complete(); - } - @Override - public void onFault(String errorMessage) { - log.error("Sending {} failed: uid={}; peer={}; error={}", request.getClass().getSimpleName(), trade.getTradingPeerNodeAddress(), trade.getId(), errorMessage); - appendToErrorMessage("Sending message failed: message=" + request + "\nerrorMessage=" + errorMessage); - failed(); - } - }); + processModel.getTradeManager().requestPersistence(); + complete(); } catch (Throwable t) { failed(t); } diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessSignContractRequest.java b/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessSignContractRequest.java index 951225f501..a1686419b1 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessSignContractRequest.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessSignContractRequest.java @@ -21,8 +21,10 @@ package bisq.core.trade.protocol.tasks; import static com.google.common.base.Preconditions.checkNotNull; import bisq.common.app.Version; +import bisq.common.crypto.Encryption; import bisq.common.crypto.Hash; import bisq.common.crypto.PubKeyRing; +import bisq.common.crypto.ScryptUtil; import bisq.common.crypto.Sig; import bisq.common.taskrunner.TaskRunner; import bisq.core.trade.ArbitratorTrade; @@ -37,6 +39,7 @@ import bisq.network.p2p.NodeAddress; import bisq.network.p2p.SendDirectMessageListener; import java.util.Date; import java.util.UUID; +import javax.crypto.SecretKey; import lombok.extern.slf4j.Slf4j; @Slf4j @@ -61,7 +64,7 @@ public class ProcessSignContractRequest extends TradeTask { TradingPeer trader = trade.getTradingPeer(request.getSenderNodeAddress()); trader.setDepositTxHash(request.getDepositTxHash()); trader.setAccountId(request.getAccountId()); - trader.setPaymentAccountPayloadHash(request.getPaymentAccountPayloadHash()); + trader.setPaymentAccountPayloadHash(request.getPaymentAccountPayloadHash()); // TODO: only seller's payment account payload is shared, so no need to send payment hash trader.setPayoutAddressString(request.getPayoutAddress()); // sign contract only when both deposit txs hashes known @@ -82,6 +85,20 @@ public class ProcessSignContractRequest extends TradeTask { trade.setContractAsJson(contractAsJson); trade.setContractHash(Hash.getSha256Hash(checkNotNull(contractAsJson))); trade.getSelf().setContractSignature(signature); + + // seller sends encrypted payment account payload + byte[] encryptedPaymentAccountPayload = null; + if (trade.isSeller()) { + + // generate random key to encrypt payment account payload + byte[] decryptionKey = ScryptUtil.getKeyCrypterScrypt().deriveKey(UUID.randomUUID().toString()).getKey(); + trade.getSelf().setPaymentAccountKey(decryptionKey); + + // encrypt payment account payload + byte[] unencrypted = trade.getSelf().getPaymentAccountPayload().toProtoMessage().toByteArray(); + SecretKey sk = Encryption.getSecretKeyFromBytes(trade.getSelf().getPaymentAccountKey()); + encryptedPaymentAccountPayload = Encryption.encrypt(unencrypted, sk); + } // create response with contract signature SignContractResponse response = new SignContractResponse( @@ -92,7 +109,8 @@ public class ProcessSignContractRequest extends TradeTask { Version.getP2PMessageVersion(), new Date().getTime(), contractAsJson, - signature); + signature, + encryptedPaymentAccountPayload); // get response recipients. only arbitrator sends response to both peers NodeAddress recipient1 = trade instanceof ArbitratorTrade ? trade.getMakerNodeAddress() : trade.getTradingPeerNodeAddress(); diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessSignContractResponse.java b/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessSignContractResponse.java index 4593901f0e..17f9ba0ee0 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessSignContractResponse.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessSignContractResponse.java @@ -61,6 +61,12 @@ public class ProcessSignContractResponse extends TradeTask { else if (peer == processModel.getTaker()) peerPubKeyRing = trade.getTakerPubKeyRing(); else throw new RuntimeException(response.getClass().getSimpleName() + " is not from maker, taker, or arbitrator"); + // buyer saves seller's encrypted payment account payload + if (trade.isBuyer() && peer == trade.getSeller()) { + peer.setEncryptedPaymentAccountPayload(response.getEncryptedPaymentAccountPayload()); + if (peer.getEncryptedPaymentAccountPayload() == null) throw new RuntimeException("Seller did not send encrypted payment account payload"); + } + // verify signature // TODO (woodser): transfer contract for convenient comparison? String signature = response.getContractSignature(); @@ -85,7 +91,8 @@ public class ProcessSignContractResponse extends TradeTask { new Date().getTime(), trade.getSelf().getContractSignature(), processModel.getDepositTxXmr().getFullHex(), - processModel.getDepositTxXmr().getKey()); + processModel.getDepositTxXmr().getKey(), + trade.getSelf().getPaymentAccountKey()); // send request to arbitrator log.info("Sending {} to arbitrator {}; offerId={}; uid={}", request.getClass().getSimpleName(), trade.getArbitratorNodeAddress(), trade.getId(), request.getUid()); diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/SellerPreparesPaymentReceivedMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/SellerPreparesPaymentReceivedMessage.java index 4e9ca1d331..e3e3e21e83 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/SellerPreparesPaymentReceivedMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/SellerPreparesPaymentReceivedMessage.java @@ -49,8 +49,6 @@ public class SellerPreparesPaymentReceivedMessage extends TradeTask { trade.getSeller().setPayoutTxHex(payoutTx.getTxSet().getMultisigTxHex()); } - // close multisig wallet - processModel.getProvider().getXmrWalletService().closeMultisigWallet(trade.getId()); complete(); } catch (Throwable t) { failed(t); diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/SellerSendsPaymentAccountPayloadKey.java b/core/src/main/java/bisq/core/trade/protocol/tasks/SellerSendsPaymentAccountPayloadKey.java new file mode 100644 index 0000000000..b0b90b4456 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/SellerSendsPaymentAccountPayloadKey.java @@ -0,0 +1,101 @@ +/* + * This file is part of Haveno. + * + * Haveno is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Haveno is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Haveno. If not, see . + */ + +package bisq.core.trade.protocol.tasks; + +import bisq.core.btc.wallet.XmrWalletService; +import bisq.core.trade.Trade; +import bisq.core.trade.messages.PaymentAccountKeyResponse; +import bisq.core.trade.messages.TradeMailboxMessage; +import bisq.common.app.Version; +import bisq.common.taskrunner.TaskRunner; + +import lombok.extern.slf4j.Slf4j; +import monero.wallet.MoneroWallet; + +/** + * Send the buyer payment account info when the trade state is confirmed. + */ +@Slf4j +public class SellerSendsPaymentAccountPayloadKey extends SendMailboxMessageTask { + private PaymentAccountKeyResponse message; + + public SellerSendsPaymentAccountPayloadKey(TaskRunner taskHandler, Trade trade) { + super(taskHandler, trade); + } + + @Override + protected void run() { + try { + runInterceptHook(); + super.run(); + } catch (Throwable t) { + failed(t); + } + } + + @Override + protected TradeMailboxMessage getTradeMailboxMessage(String tradeId) { + if (message == null) { + + // set payment account payload + trade.getSelf().setPaymentAccountPayload(processModel.getPaymentAccountPayload(trade)); + + // get updated multisig hex + if (trade.getSelf().getUpdatedMultisigHex() == null) { + XmrWalletService walletService = processModel.getProvider().getXmrWalletService(); + MoneroWallet multisigWallet = walletService.getMultisigWallet(tradeId); + trade.getSelf().setUpdatedMultisigHex(multisigWallet.exportMultisigHex()); // only export multisig hex once + } + + // We do not use a real unique ID here as we want to be able to re-send the exact same message in case the + // peer does not respond with an ACK msg in a certain time interval. To avoid that we get dangling mailbox + // messages where only the one which gets processed by the peer would be removed we use the same uid. All + // other data stays the same when we re-send the message at any time later. + String deterministicId = tradeId + processModel.getMyNodeAddress().getFullAddress(); + message = new PaymentAccountKeyResponse( + trade.getOffer().getId(), + processModel.getMyNodeAddress(), + processModel.getPubKeyRing(), + deterministicId, + Version.getP2PMessageVersion(), + trade.getSelf().getPaymentAccountKey(), + trade.getSelf().getUpdatedMultisigHex()); + } + return message; + } + + @Override + protected void setStateSent() { + // no additional handling + } + + @Override + protected void setStateArrived() { + // no additional handling + } + + @Override + protected void setStateStoredInMailbox() { + // no additional handling + } + + @Override + protected void setStateFault() { + // no additional handling + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/SellerSendsPaymentReceivedMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/SellerSendsPaymentReceivedMessage.java index a1d617bf70..8a79f48fb9 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/SellerSendsPaymentReceivedMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/SellerSendsPaymentReceivedMessage.java @@ -18,7 +18,6 @@ package bisq.core.trade.protocol.tasks; import bisq.core.account.sign.SignedWitness; -import bisq.core.account.witness.AccountAgeWitnessService; import bisq.core.trade.Trade; import bisq.core.trade.messages.PaymentReceivedMessage; import bisq.core.trade.messages.TradeMailboxMessage; @@ -58,13 +57,6 @@ public class SellerSendsPaymentReceivedMessage extends SendMailboxMessageTask { @Override protected TradeMailboxMessage getTradeMailboxMessage(String id) { checkNotNull(trade.getSeller().getPayoutTxHex(), "Payout tx must not be null"); - - AccountAgeWitnessService accountAgeWitnessService = processModel.getAccountAgeWitnessService(); - if (accountAgeWitnessService.isSignWitnessTrade(trade)) { - // Broadcast is done in accountAgeWitness domain. - accountAgeWitnessService.traderSignAndPublishPeersAccountAgeWitness(trade).ifPresent(witness -> signedWitness = witness); - } - return new PaymentReceivedMessage( id, processModel.getMyNodeAddress(), @@ -83,7 +75,7 @@ public class SellerSendsPaymentReceivedMessage extends SendMailboxMessageTask { @Override protected void setStateArrived() { - trade.setState(trade.getState() == Trade.State.SELLER_PUBLISHED_PAYOUT_TX ? Trade.State.SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG : Trade.State.SELLER_SAW_ARRIVED_PAYMENT_RECEIVED_MSG); + trade.setState(trade.getState() == Trade.State.SELLER_SENT_PAYOUT_TX_PUBLISHED_MSG ? Trade.State.SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG : Trade.State.SELLER_SAW_ARRIVED_PAYMENT_RECEIVED_MSG); log.info("Seller's PaymentReceivedMessage arrived: tradeId={} at peer {} SignedWitness {}", trade.getId(), trade.getTradingPeerNodeAddress(), signedWitness); processModel.getTradeManager().requestPersistence(); diff --git a/core/src/main/java/bisq/core/trade/protocol/TakerReservesTradeFunds.java b/core/src/main/java/bisq/core/trade/protocol/tasks/TakerReservesTradeFunds.java similarity index 97% rename from core/src/main/java/bisq/core/trade/protocol/TakerReservesTradeFunds.java rename to core/src/main/java/bisq/core/trade/protocol/tasks/TakerReservesTradeFunds.java index d5c9c28a9a..843e26c124 100644 --- a/core/src/main/java/bisq/core/trade/protocol/TakerReservesTradeFunds.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/TakerReservesTradeFunds.java @@ -15,12 +15,11 @@ * along with Haveno. If not, see . */ -package bisq.core.trade.protocol; +package bisq.core.trade.protocol.tasks; import bisq.common.taskrunner.TaskRunner; import bisq.core.btc.model.XmrAddressEntry; import bisq.core.trade.Trade; -import bisq.core.trade.protocol.tasks.TradeTask; import bisq.core.util.ParsingUtils; import java.math.BigInteger; import java.util.ArrayList; diff --git a/core/src/main/java/bisq/core/trade/protocol/TakerSendsInitTradeRequestToArbitrator.java b/core/src/main/java/bisq/core/trade/protocol/tasks/TakerSendsInitTradeRequestToArbitrator.java similarity index 98% rename from core/src/main/java/bisq/core/trade/protocol/TakerSendsInitTradeRequestToArbitrator.java rename to core/src/main/java/bisq/core/trade/protocol/tasks/TakerSendsInitTradeRequestToArbitrator.java index 137a5822d5..3480b71321 100644 --- a/core/src/main/java/bisq/core/trade/protocol/TakerSendsInitTradeRequestToArbitrator.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/TakerSendsInitTradeRequestToArbitrator.java @@ -15,7 +15,7 @@ * along with Haveno. If not, see . */ -package bisq.core.trade.protocol; +package bisq.core.trade.protocol.tasks; import bisq.core.offer.availability.DisputeAgentSelection; import bisq.core.support.dispute.arbitration.arbitrator.Arbitrator; @@ -23,7 +23,6 @@ import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager; import bisq.core.support.dispute.mediation.mediator.Mediator; import bisq.core.trade.Trade; import bisq.core.trade.messages.InitTradeRequest; -import bisq.core.trade.protocol.tasks.TradeTask; import bisq.core.trade.statistics.TradeStatisticsManager; import bisq.network.p2p.NodeAddress; import bisq.network.p2p.SendDirectMessageListener; diff --git a/core/src/main/java/bisq/core/trade/protocol/TakerVerifyMakerFeePayment.java b/core/src/main/java/bisq/core/trade/protocol/tasks/TakerVerifyMakerFeePayment.java similarity index 94% rename from core/src/main/java/bisq/core/trade/protocol/TakerVerifyMakerFeePayment.java rename to core/src/main/java/bisq/core/trade/protocol/tasks/TakerVerifyMakerFeePayment.java index 5f3849cf98..a4cee67100 100644 --- a/core/src/main/java/bisq/core/trade/protocol/TakerVerifyMakerFeePayment.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/TakerVerifyMakerFeePayment.java @@ -15,11 +15,9 @@ * along with Haveno. If not, see . */ -package bisq.core.trade.protocol; +package bisq.core.trade.protocol.tasks; import bisq.core.trade.Trade; -import bisq.core.trade.protocol.tasks.TradeTask; - import bisq.common.taskrunner.TaskRunner; import lombok.extern.slf4j.Slf4j; diff --git a/desktop/src/main/java/bisq/desktop/main/debug/DebugView.java b/desktop/src/main/java/bisq/desktop/main/debug/DebugView.java index 68b1964978..2b710a60e9 100644 --- a/desktop/src/main/java/bisq/desktop/main/debug/DebugView.java +++ b/desktop/src/main/java/bisq/desktop/main/debug/DebugView.java @@ -26,7 +26,6 @@ import bisq.core.offer.availability.tasks.SendOfferAvailabilityRequest; import bisq.core.offer.placeoffer.tasks.AddToOfferBook; import bisq.core.offer.placeoffer.tasks.MakerReservesOfferFunds; import bisq.core.offer.placeoffer.tasks.ValidateOffer; -import bisq.core.trade.protocol.TakerVerifyMakerFeePayment; import bisq.core.trade.protocol.tasks.ApplyFilter; import bisq.core.trade.protocol.tasks.BuyerPreparesPaymentSentMessage; import bisq.core.trade.protocol.tasks.BuyerProcessesPaymentReceivedMessage; @@ -39,6 +38,7 @@ import bisq.core.trade.protocol.tasks.SellerProcessesPaymentSentMessage; import bisq.core.trade.protocol.tasks.SellerPublishesDepositTx; import bisq.core.trade.protocol.tasks.SellerPublishesTradeStatistics; import bisq.core.trade.protocol.tasks.SellerSendsPaymentReceivedMessage; +import bisq.core.trade.protocol.tasks.TakerVerifyMakerFeePayment; import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness; import bisq.common.taskrunner.Task; import bisq.common.util.Tuple2; diff --git a/proto/src/main/proto/pb.proto b/proto/src/main/proto/pb.proto index b65a4b7896..e1426d36a5 100644 --- a/proto/src/main/proto/pb.proto +++ b/proto/src/main/proto/pb.proto @@ -74,14 +74,15 @@ message NetworkEnvelope { SignContractResponse sign_contract_response = 1006; DepositRequest deposit_request = 1007; DepositResponse deposit_response = 1008; - PaymentAccountPayloadRequest payment_account_payload_request = 1009; - PaymentSentMessage payment_sent_message = 1010; - PaymentReceivedMessage payment_received_message = 1011; - PayoutTxPublishedMessage payout_tx_published_message = 1012; - UpdateMultisigRequest update_multisig_request = 1013; - UpdateMultisigResponse update_multisig_response = 1014; - ArbitratorPayoutTxRequest arbitrator_payout_tx_request = 1015; - ArbitratorPayoutTxResponse arbitrator_payout_tx_response = 1016; + PaymentAccountKeyRequest payment_account_key_request = 1009; + PaymentAccountKeyResponse payment_account_key_response = 1010; + PaymentSentMessage payment_sent_message = 1011; + PaymentReceivedMessage payment_received_message = 1012; + PayoutTxPublishedMessage payout_tx_published_message = 1013; + UpdateMultisigRequest update_multisig_request = 1014; + UpdateMultisigResponse update_multisig_response = 1015; + ArbitratorPayoutTxRequest arbitrator_payout_tx_request = 1016; + ArbitratorPayoutTxResponse arbitrator_payout_tx_response = 1017; } } @@ -329,6 +330,7 @@ message SignContractResponse { int64 current_date = 5; string contract_as_json = 6; string contract_signature = 7; + bytes encrypted_payment_account_payload = 8; } message DepositRequest { @@ -340,6 +342,7 @@ message DepositRequest { string contract_signature = 6; string deposit_tx_hex = 7; string deposit_tx_key = 8; + bytes payment_account_key = 9; } message DepositResponse { @@ -350,13 +353,20 @@ message DepositResponse { int64 current_date = 5; } -message PaymentAccountPayloadRequest { +message PaymentAccountKeyRequest { string trade_id = 1; NodeAddress sender_node_address = 2; PubKeyRing pub_key_ring = 3; string uid = 4; - int64 current_date = 5; - PaymentAccountPayload payment_account_payload = 6; +} + +message PaymentAccountKeyResponse { + string trade_id = 1; + NodeAddress sender_node_address = 2; + PubKeyRing pub_key_ring = 3; + string uid = 4; + bytes payment_account_key = 5; + string updated_multisig_hex = 6; } message UpdateMultisigRequest { @@ -1791,20 +1801,22 @@ message TradingPeer { string payment_account_id = 2; string payment_method_id = 3; bytes payment_account_payload_hash = 4; - PaymentAccountPayload payment_account_payload = 5; - string payout_address_string = 6; - string contract_as_json = 7; - string contract_signature = 8; - bytes signature = 9; // TODO (woodser): remove unused fields? this was buyer-signed payout tx as bytes - PubKeyRing pub_key_ring = 10; - bytes multi_sig_pub_key = 11; - repeated RawTransactionInput raw_transaction_inputs = 12; - int64 change_output_value = 13; - string change_output_address = 14; - bytes account_age_witness_nonce = 15; - bytes account_age_witness_signature = 16; - int64 current_date = 17; - bytes mediated_payout_tx_signature = 18; + bytes encrypted_payment_account_payload = 5; + bytes payment_account_key = 6; + PaymentAccountPayload payment_account_payload = 7; + string payout_address_string = 8; + string contract_as_json = 9; + string contract_signature = 10; + bytes signature = 11; // TODO (woodser): remove unused fields? this was buyer-signed payout tx as bytes + PubKeyRing pub_key_ring = 12; + bytes multi_sig_pub_key = 13; + repeated RawTransactionInput raw_transaction_inputs = 14; + int64 change_output_value = 15; + string change_output_address = 16; + bytes account_age_witness_nonce = 17; + bytes account_age_witness_signature = 18; + int64 current_date = 19; + bytes mediated_payout_tx_signature = 20; string reserve_tx_hash = 1001; string reserve_tx_hex = 1002;