diff --git a/core/src/main/java/bisq/core/trade/Trade.java b/core/src/main/java/bisq/core/trade/Trade.java index e65d405c..2e0fc19d 100644 --- a/core/src/main/java/bisq/core/trade/Trade.java +++ b/core/src/main/java/bisq/core/trade/Trade.java @@ -24,8 +24,10 @@ import bisq.core.monetary.Price; import bisq.core.monetary.Volume; import bisq.core.offer.Offer; import bisq.core.offer.OfferDirection; +import bisq.core.payment.payload.PaymentAccountPayload; import bisq.core.payment.payload.PaymentMethod; import bisq.core.proto.CoreProtoResolver; +import bisq.core.proto.network.CoreNetworkProtoResolver; import bisq.core.support.dispute.mediation.MediationResultState; import bisq.core.support.dispute.refund.RefundResultState; import bisq.core.support.messages.ChatMessage; @@ -41,6 +43,7 @@ import bisq.network.p2p.AckMessage; import bisq.network.p2p.NodeAddress; import bisq.network.p2p.P2PService; import bisq.common.UserThread; +import bisq.common.crypto.Encryption; import bisq.common.crypto.PubKeyRing; import bisq.common.proto.ProtoUtil; import bisq.common.taskrunner.Model; @@ -63,6 +66,7 @@ import javafx.beans.property.StringProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import java.math.BigInteger; +import java.time.Clock; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; @@ -77,6 +81,7 @@ import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.NotNull; import javax.annotation.Nullable; +import javax.crypto.SecretKey; import static com.google.common.base.Preconditions.checkNotNull; @@ -633,11 +638,6 @@ public abstract class Trade implements Tradable, Model { return trade; } - - /////////////////////////////////////////////////////////////////////////////////////////// - // API - /////////////////////////////////////////////////////////////////////////////////////////// - public void initialize(ProcessModelServiceProvider serviceProvider) { serviceProvider.getArbitratorManager().getDisputeAgentByNodeAddress(arbitratorNodeAddress).ifPresent(arbitrator -> { arbitratorPubKeyRing = arbitrator.getPubKeyRing(); @@ -841,6 +841,32 @@ public abstract class Trade implements Tradable, Model { walletService.closeMultisigWallet(getId()); } + /** + * Decrypt the peer's payment account payload using the given key. + * + * @param paymentAccountKey is the key to decrypt the payment account payload + */ + public void decryptPeersPaymentAccountPayload(byte[] paymentAccountKey) { + try { + + // decrypt payment account payload + getTradingPeer().setPaymentAccountKey(paymentAccountKey); + SecretKey sk = Encryption.getSecretKeyFromBytes(getTradingPeer().getPaymentAccountKey()); + byte[] decryptedPaymentAccountPayload = Encryption.decrypt(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 = this instanceof MakerTrade ? getContract().getTakerPaymentAccountPayloadHash() : getContract().getMakerPaymentAccountPayloadHash(); + if (!Arrays.equals(paymentAccountPayload.getHash(), peerPaymentAccountPayloadHash)) throw new RuntimeException("Hash of peer's payment account payload does not match contract"); + + // set payment account payload + getTradingPeer().setPaymentAccountPayload(paymentAccountPayload); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + /** * Listen for deposit transactions to unlock and then apply the transactions. * diff --git a/core/src/main/java/bisq/core/trade/messages/PaymentSentMessage.java b/core/src/main/java/bisq/core/trade/messages/PaymentSentMessage.java index 0337913e..fcf5674e 100644 --- a/core/src/main/java/bisq/core/trade/messages/PaymentSentMessage.java +++ b/core/src/main/java/bisq/core/trade/messages/PaymentSentMessage.java @@ -18,7 +18,7 @@ package bisq.core.trade.messages; import bisq.network.p2p.NodeAddress; - +import com.google.protobuf.ByteString; import bisq.common.app.Version; import bisq.common.proto.ProtoUtil; @@ -40,6 +40,8 @@ public final class PaymentSentMessage extends TradeMailboxMessage { private final String payoutTxHex; @Nullable private final String updatedMultisigHex; + @Nullable + private final byte[] paymentAccountKey; // Added after v1.3.7 // We use that for the XMR txKey but want to keep it generic to be flexible for data of other payment methods or assets. @@ -53,7 +55,8 @@ public final class PaymentSentMessage extends TradeMailboxMessage { @Nullable String counterCurrencyExtraData, String uid, String signedPayoutTxHex, - String updatedMultisigHex) { + String updatedMultisigHex, + @Nullable byte[] paymentAccountKey) { this(tradeId, buyerPayoutAddress, senderNodeAddress, @@ -62,7 +65,8 @@ public final class PaymentSentMessage extends TradeMailboxMessage { uid, Version.getP2PMessageVersion(), signedPayoutTxHex, - updatedMultisigHex); + updatedMultisigHex, + paymentAccountKey); } @@ -78,7 +82,8 @@ public final class PaymentSentMessage extends TradeMailboxMessage { String uid, String messageVersion, @Nullable String signedPayoutTxHex, - @Nullable String updatedMultisigHex) { + @Nullable String updatedMultisigHex, + @Nullable byte[] paymentAccountKey) { super(messageVersion, tradeId, uid); this.buyerPayoutAddress = buyerPayoutAddress; this.senderNodeAddress = senderNodeAddress; @@ -86,6 +91,7 @@ public final class PaymentSentMessage extends TradeMailboxMessage { this.counterCurrencyExtraData = counterCurrencyExtraData; this.payoutTxHex = signedPayoutTxHex; this.updatedMultisigHex = updatedMultisigHex; + this.paymentAccountKey = paymentAccountKey; } @Override @@ -100,6 +106,7 @@ public final class PaymentSentMessage extends TradeMailboxMessage { Optional.ofNullable(counterCurrencyExtraData).ifPresent(e -> builder.setCounterCurrencyExtraData(counterCurrencyExtraData)); Optional.ofNullable(payoutTxHex).ifPresent(e -> builder.setPayoutTxHex(payoutTxHex)); Optional.ofNullable(updatedMultisigHex).ifPresent(e -> builder.setUpdatedMultisigHex(updatedMultisigHex)); + Optional.ofNullable(paymentAccountKey).ifPresent(e -> builder.setPaymentAccountKey(ByteString.copyFrom(e))); return getNetworkEnvelopeBuilder().setPaymentSentMessage(builder).build(); } @@ -114,7 +121,9 @@ public final class PaymentSentMessage extends TradeMailboxMessage { proto.getUid(), messageVersion, ProtoUtil.stringOrNullFromProto(proto.getPayoutTxHex()), - ProtoUtil.stringOrNullFromProto(proto.getUpdatedMultisigHex())); + ProtoUtil.stringOrNullFromProto(proto.getUpdatedMultisigHex()), + ProtoUtil.byteArrayOrNullFromProto(proto.getPaymentAccountKey()) + ); } @@ -128,6 +137,7 @@ public final class PaymentSentMessage extends TradeMailboxMessage { ",\n uid='" + uid + '\'' + ",\n payoutTxHex=" + payoutTxHex + ",\n updatedMultisigHex=" + updatedMultisigHex + + ",\n paymentAccountKey=" + paymentAccountKey + "\n} " + super.toString(); } } diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/BuyerProcessesPaymentAccountKeyResponse.java b/core/src/main/java/bisq/core/trade/protocol/tasks/BuyerProcessesPaymentAccountKeyResponse.java index 595be36c..1fe58a39 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/BuyerProcessesPaymentAccountKeyResponse.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/BuyerProcessesPaymentAccountKeyResponse.java @@ -18,16 +18,9 @@ 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.messages.PaymentAccountKeyResponse; -import java.time.Clock; -import java.util.Arrays; -import javax.crypto.SecretKey; import lombok.extern.slf4j.Slf4j; @Slf4j @@ -54,22 +47,9 @@ public class BuyerProcessesPaymentAccountKeyResponse extends TradeTask { 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(); - if (!Arrays.equals(paymentAccountPayload.getHash(), peerPaymentAccountPayloadHash)) throw new RuntimeException("Hash of peer's payment account payload does not match contract"); - - // set payment account payload - trade.getTradingPeer().setPaymentAccountPayload(paymentAccountPayload); + PaymentAccountKeyResponse request = (PaymentAccountKeyResponse) processModel.getTradeMessage(); + trade.decryptPeersPaymentAccountPayload(request.getPaymentAccountKey()); // store updated multisig hex for processing on payment sent trade.getTradingPeer().setUpdatedMultisigHex(request.getUpdatedMultisigHex()); diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/BuyerSendsPaymentSentMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/BuyerSendsPaymentSentMessage.java index 0022e489..f8dc535f 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/BuyerSendsPaymentSentMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/BuyerSendsPaymentSentMessage.java @@ -71,7 +71,8 @@ public class BuyerSendsPaymentSentMessage extends SendMailboxMessageTask { trade.getCounterCurrencyExtraData(), deterministicId, trade.getBuyer().getPayoutTxHex(), - trade.getBuyer().getUpdatedMultisigHex() + trade.getBuyer().getUpdatedMultisigHex(), + trade.getSelf().getPaymentAccountKey() ); } return message; 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 a1686419..765d29ce 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 @@ -57,16 +57,16 @@ public class ProcessSignContractRequest extends TradeTask { protected void run() { try { runInterceptHook(); - + // extract fields from request // TODO (woodser): verify request and from maker or taker SignContractRequest request = (SignContractRequest) processModel.getTradeMessage(); TradingPeer trader = trade.getTradingPeer(request.getSenderNodeAddress()); trader.setDepositTxHash(request.getDepositTxHash()); trader.setAccountId(request.getAccountId()); - trader.setPaymentAccountPayloadHash(request.getPaymentAccountPayloadHash()); // TODO: only seller's payment account payload is shared, so no need to send payment hash + trader.setPaymentAccountPayloadHash(request.getPaymentAccountPayloadHash()); trader.setPayoutAddressString(request.getPayoutAddress()); - + // sign contract only when both deposit txs hashes known // TODO (woodser): synchronize contract creation; both requests received at the same time // TODO (woodser): remove makerDepositTxId and takerDepositTxId from Trade @@ -85,10 +85,10 @@ public class ProcessSignContractRequest extends TradeTask { trade.setContractAsJson(contractAsJson); trade.setContractHash(Hash.getSha256Hash(checkNotNull(contractAsJson))); trade.getSelf().setContractSignature(signature); - - // seller sends encrypted payment account payload + + // traders send encrypted payment account payload byte[] encryptedPaymentAccountPayload = null; - if (trade.isSeller()) { + if (!trade.isArbitrator()) { // generate random key to encrypt payment account payload byte[] decryptionKey = ScryptUtil.getKeyCrypterScrypt().deriveKey(UUID.randomUUID().toString()).getKey(); 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 17f9ba0e..f14e254f 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,11 +61,9 @@ 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"); - } + // save peer's encrypted payment account payload + peer.setEncryptedPaymentAccountPayload(response.getEncryptedPaymentAccountPayload()); + if (peer.getEncryptedPaymentAccountPayload() == null) throw new RuntimeException("Peer did not send encrypted payment account payload"); // verify signature // TODO (woodser): transfer contract for convenient comparison? diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/SellerProcessesPaymentSentMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/SellerProcessesPaymentSentMessage.java index 99b12ae6..a462312c 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/SellerProcessesPaymentSentMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/SellerProcessesPaymentSentMessage.java @@ -45,7 +45,10 @@ public class SellerProcessesPaymentSentMessage extends TradeTask { trade.getBuyer().setPayoutAddressString(Validator.nonEmptyStringOf(message.getBuyerPayoutAddress())); // TODO (woodser): verify against contract trade.getBuyer().setPayoutTxHex(message.getPayoutTxHex()); trade.getBuyer().setUpdatedMultisigHex(message.getUpdatedMultisigHex()); - + + // decrypt peer's payment account payload + trade.decryptPeersPaymentAccountPayload(message.getPaymentAccountKey()); + // sync and update multisig wallet if (trade.getBuyer().getUpdatedMultisigHex() != null) { XmrWalletService walletService = processModel.getProvider().getXmrWalletService(); 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 index b0b90b44..5e7015a3 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/SellerSendsPaymentAccountPayloadKey.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/SellerSendsPaymentAccountPayloadKey.java @@ -28,7 +28,7 @@ import lombok.extern.slf4j.Slf4j; import monero.wallet.MoneroWallet; /** - * Send the buyer payment account info when the trade state is confirmed. + * Allow sender's payment account info to be decrypted when trade state is confirmed. */ @Slf4j public class SellerSendsPaymentAccountPayloadKey extends SendMailboxMessageTask { @@ -52,9 +52,6 @@ public class SellerSendsPaymentAccountPayloadKey extends SendMailboxMessageTask 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(); diff --git a/proto/src/main/proto/pb.proto b/proto/src/main/proto/pb.proto index 4f99ce07..e9f0b0ae 100644 --- a/proto/src/main/proto/pb.proto +++ b/proto/src/main/proto/pb.proto @@ -447,6 +447,7 @@ message PaymentSentMessage { string counter_currency_extra_data = 6; string payout_tx_hex = 7; string updated_multisig_hex = 8; + bytes payment_account_key = 9; } message PaymentReceivedMessage {