seller decrypts buyer payment info on payment sent

This commit is contained in:
woodser 2022-09-19 08:08:13 -04:00
parent 64925d0137
commit 1f32fc2cbe
9 changed files with 65 additions and 49 deletions

View file

@ -24,8 +24,10 @@ import bisq.core.monetary.Price;
import bisq.core.monetary.Volume; import bisq.core.monetary.Volume;
import bisq.core.offer.Offer; import bisq.core.offer.Offer;
import bisq.core.offer.OfferDirection; import bisq.core.offer.OfferDirection;
import bisq.core.payment.payload.PaymentAccountPayload;
import bisq.core.payment.payload.PaymentMethod; import bisq.core.payment.payload.PaymentMethod;
import bisq.core.proto.CoreProtoResolver; import bisq.core.proto.CoreProtoResolver;
import bisq.core.proto.network.CoreNetworkProtoResolver;
import bisq.core.support.dispute.mediation.MediationResultState; import bisq.core.support.dispute.mediation.MediationResultState;
import bisq.core.support.dispute.refund.RefundResultState; import bisq.core.support.dispute.refund.RefundResultState;
import bisq.core.support.messages.ChatMessage; import bisq.core.support.messages.ChatMessage;
@ -41,6 +43,7 @@ import bisq.network.p2p.AckMessage;
import bisq.network.p2p.NodeAddress; import bisq.network.p2p.NodeAddress;
import bisq.network.p2p.P2PService; import bisq.network.p2p.P2PService;
import bisq.common.UserThread; import bisq.common.UserThread;
import bisq.common.crypto.Encryption;
import bisq.common.crypto.PubKeyRing; import bisq.common.crypto.PubKeyRing;
import bisq.common.proto.ProtoUtil; import bisq.common.proto.ProtoUtil;
import bisq.common.taskrunner.Model; import bisq.common.taskrunner.Model;
@ -63,6 +66,7 @@ import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import java.math.BigInteger; import java.math.BigInteger;
import java.time.Clock;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Date; import java.util.Date;
@ -77,6 +81,7 @@ import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.crypto.SecretKey;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
@ -633,11 +638,6 @@ public abstract class Trade implements Tradable, Model {
return trade; return trade;
} }
///////////////////////////////////////////////////////////////////////////////////////////
// API
///////////////////////////////////////////////////////////////////////////////////////////
public void initialize(ProcessModelServiceProvider serviceProvider) { public void initialize(ProcessModelServiceProvider serviceProvider) {
serviceProvider.getArbitratorManager().getDisputeAgentByNodeAddress(arbitratorNodeAddress).ifPresent(arbitrator -> { serviceProvider.getArbitratorManager().getDisputeAgentByNodeAddress(arbitratorNodeAddress).ifPresent(arbitrator -> {
arbitratorPubKeyRing = arbitrator.getPubKeyRing(); arbitratorPubKeyRing = arbitrator.getPubKeyRing();
@ -841,6 +841,32 @@ public abstract class Trade implements Tradable, Model {
walletService.closeMultisigWallet(getId()); 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. * Listen for deposit transactions to unlock and then apply the transactions.
* *

View file

@ -18,7 +18,7 @@
package bisq.core.trade.messages; package bisq.core.trade.messages;
import bisq.network.p2p.NodeAddress; import bisq.network.p2p.NodeAddress;
import com.google.protobuf.ByteString;
import bisq.common.app.Version; import bisq.common.app.Version;
import bisq.common.proto.ProtoUtil; import bisq.common.proto.ProtoUtil;
@ -40,6 +40,8 @@ public final class PaymentSentMessage extends TradeMailboxMessage {
private final String payoutTxHex; private final String payoutTxHex;
@Nullable @Nullable
private final String updatedMultisigHex; private final String updatedMultisigHex;
@Nullable
private final byte[] paymentAccountKey;
// Added after v1.3.7 // 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. // 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, @Nullable String counterCurrencyExtraData,
String uid, String uid,
String signedPayoutTxHex, String signedPayoutTxHex,
String updatedMultisigHex) { String updatedMultisigHex,
@Nullable byte[] paymentAccountKey) {
this(tradeId, this(tradeId,
buyerPayoutAddress, buyerPayoutAddress,
senderNodeAddress, senderNodeAddress,
@ -62,7 +65,8 @@ public final class PaymentSentMessage extends TradeMailboxMessage {
uid, uid,
Version.getP2PMessageVersion(), Version.getP2PMessageVersion(),
signedPayoutTxHex, signedPayoutTxHex,
updatedMultisigHex); updatedMultisigHex,
paymentAccountKey);
} }
@ -78,7 +82,8 @@ public final class PaymentSentMessage extends TradeMailboxMessage {
String uid, String uid,
String messageVersion, String messageVersion,
@Nullable String signedPayoutTxHex, @Nullable String signedPayoutTxHex,
@Nullable String updatedMultisigHex) { @Nullable String updatedMultisigHex,
@Nullable byte[] paymentAccountKey) {
super(messageVersion, tradeId, uid); super(messageVersion, tradeId, uid);
this.buyerPayoutAddress = buyerPayoutAddress; this.buyerPayoutAddress = buyerPayoutAddress;
this.senderNodeAddress = senderNodeAddress; this.senderNodeAddress = senderNodeAddress;
@ -86,6 +91,7 @@ public final class PaymentSentMessage extends TradeMailboxMessage {
this.counterCurrencyExtraData = counterCurrencyExtraData; this.counterCurrencyExtraData = counterCurrencyExtraData;
this.payoutTxHex = signedPayoutTxHex; this.payoutTxHex = signedPayoutTxHex;
this.updatedMultisigHex = updatedMultisigHex; this.updatedMultisigHex = updatedMultisigHex;
this.paymentAccountKey = paymentAccountKey;
} }
@Override @Override
@ -100,6 +106,7 @@ public final class PaymentSentMessage extends TradeMailboxMessage {
Optional.ofNullable(counterCurrencyExtraData).ifPresent(e -> builder.setCounterCurrencyExtraData(counterCurrencyExtraData)); Optional.ofNullable(counterCurrencyExtraData).ifPresent(e -> builder.setCounterCurrencyExtraData(counterCurrencyExtraData));
Optional.ofNullable(payoutTxHex).ifPresent(e -> builder.setPayoutTxHex(payoutTxHex)); Optional.ofNullable(payoutTxHex).ifPresent(e -> builder.setPayoutTxHex(payoutTxHex));
Optional.ofNullable(updatedMultisigHex).ifPresent(e -> builder.setUpdatedMultisigHex(updatedMultisigHex)); Optional.ofNullable(updatedMultisigHex).ifPresent(e -> builder.setUpdatedMultisigHex(updatedMultisigHex));
Optional.ofNullable(paymentAccountKey).ifPresent(e -> builder.setPaymentAccountKey(ByteString.copyFrom(e)));
return getNetworkEnvelopeBuilder().setPaymentSentMessage(builder).build(); return getNetworkEnvelopeBuilder().setPaymentSentMessage(builder).build();
} }
@ -114,7 +121,9 @@ public final class PaymentSentMessage extends TradeMailboxMessage {
proto.getUid(), proto.getUid(),
messageVersion, messageVersion,
ProtoUtil.stringOrNullFromProto(proto.getPayoutTxHex()), 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 uid='" + uid + '\'' +
",\n payoutTxHex=" + payoutTxHex + ",\n payoutTxHex=" + payoutTxHex +
",\n updatedMultisigHex=" + updatedMultisigHex + ",\n updatedMultisigHex=" + updatedMultisigHex +
",\n paymentAccountKey=" + paymentAccountKey +
"\n} " + super.toString(); "\n} " + super.toString();
} }
} }

View file

@ -18,16 +18,9 @@
package bisq.core.trade.protocol.tasks; package bisq.core.trade.protocol.tasks;
import bisq.common.crypto.Encryption;
import bisq.common.taskrunner.TaskRunner; 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;
import bisq.core.trade.messages.PaymentAccountKeyResponse; import bisq.core.trade.messages.PaymentAccountKeyResponse;
import java.time.Clock;
import java.util.Arrays;
import javax.crypto.SecretKey;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@Slf4j @Slf4j
@ -54,22 +47,9 @@ public class BuyerProcessesPaymentAccountKeyResponse extends TradeTask {
return; 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 // decrypt peer's payment account payload
SecretKey sk = Encryption.getSecretKeyFromBytes(trade.getTradingPeer().getPaymentAccountKey()); PaymentAccountKeyResponse request = (PaymentAccountKeyResponse) processModel.getTradeMessage();
byte[] decryptedPaymentAccountPayload = Encryption.decrypt(trade.getTradingPeer().getEncryptedPaymentAccountPayload(), sk); trade.decryptPeersPaymentAccountPayload(request.getPaymentAccountKey());
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);
// store updated multisig hex for processing on payment sent // store updated multisig hex for processing on payment sent
trade.getTradingPeer().setUpdatedMultisigHex(request.getUpdatedMultisigHex()); trade.getTradingPeer().setUpdatedMultisigHex(request.getUpdatedMultisigHex());

View file

@ -71,7 +71,8 @@ public class BuyerSendsPaymentSentMessage extends SendMailboxMessageTask {
trade.getCounterCurrencyExtraData(), trade.getCounterCurrencyExtraData(),
deterministicId, deterministicId,
trade.getBuyer().getPayoutTxHex(), trade.getBuyer().getPayoutTxHex(),
trade.getBuyer().getUpdatedMultisigHex() trade.getBuyer().getUpdatedMultisigHex(),
trade.getSelf().getPaymentAccountKey()
); );
} }
return message; return message;

View file

@ -64,7 +64,7 @@ public class ProcessSignContractRequest extends TradeTask {
TradingPeer trader = trade.getTradingPeer(request.getSenderNodeAddress()); TradingPeer trader = trade.getTradingPeer(request.getSenderNodeAddress());
trader.setDepositTxHash(request.getDepositTxHash()); trader.setDepositTxHash(request.getDepositTxHash());
trader.setAccountId(request.getAccountId()); 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()); trader.setPayoutAddressString(request.getPayoutAddress());
// sign contract only when both deposit txs hashes known // sign contract only when both deposit txs hashes known
@ -86,9 +86,9 @@ public class ProcessSignContractRequest extends TradeTask {
trade.setContractHash(Hash.getSha256Hash(checkNotNull(contractAsJson))); trade.setContractHash(Hash.getSha256Hash(checkNotNull(contractAsJson)));
trade.getSelf().setContractSignature(signature); trade.getSelf().setContractSignature(signature);
// seller sends encrypted payment account payload // traders send encrypted payment account payload
byte[] encryptedPaymentAccountPayload = null; byte[] encryptedPaymentAccountPayload = null;
if (trade.isSeller()) { if (!trade.isArbitrator()) {
// generate random key to encrypt payment account payload // generate random key to encrypt payment account payload
byte[] decryptionKey = ScryptUtil.getKeyCrypterScrypt().deriveKey(UUID.randomUUID().toString()).getKey(); byte[] decryptionKey = ScryptUtil.getKeyCrypterScrypt().deriveKey(UUID.randomUUID().toString()).getKey();

View file

@ -61,11 +61,9 @@ public class ProcessSignContractResponse extends TradeTask {
else if (peer == processModel.getTaker()) peerPubKeyRing = trade.getTakerPubKeyRing(); else if (peer == processModel.getTaker()) peerPubKeyRing = trade.getTakerPubKeyRing();
else throw new RuntimeException(response.getClass().getSimpleName() + " is not from maker, taker, or arbitrator"); else throw new RuntimeException(response.getClass().getSimpleName() + " is not from maker, taker, or arbitrator");
// buyer saves seller's encrypted payment account payload // save peer's encrypted payment account payload
if (trade.isBuyer() && peer == trade.getSeller()) {
peer.setEncryptedPaymentAccountPayload(response.getEncryptedPaymentAccountPayload()); peer.setEncryptedPaymentAccountPayload(response.getEncryptedPaymentAccountPayload());
if (peer.getEncryptedPaymentAccountPayload() == null) throw new RuntimeException("Seller did not send encrypted payment account payload"); if (peer.getEncryptedPaymentAccountPayload() == null) throw new RuntimeException("Peer did not send encrypted payment account payload");
}
// verify signature // verify signature
// TODO (woodser): transfer contract for convenient comparison? // TODO (woodser): transfer contract for convenient comparison?

View file

@ -46,6 +46,9 @@ public class SellerProcessesPaymentSentMessage extends TradeTask {
trade.getBuyer().setPayoutTxHex(message.getPayoutTxHex()); trade.getBuyer().setPayoutTxHex(message.getPayoutTxHex());
trade.getBuyer().setUpdatedMultisigHex(message.getUpdatedMultisigHex()); trade.getBuyer().setUpdatedMultisigHex(message.getUpdatedMultisigHex());
// decrypt peer's payment account payload
trade.decryptPeersPaymentAccountPayload(message.getPaymentAccountKey());
// sync and update multisig wallet // sync and update multisig wallet
if (trade.getBuyer().getUpdatedMultisigHex() != null) { if (trade.getBuyer().getUpdatedMultisigHex() != null) {
XmrWalletService walletService = processModel.getProvider().getXmrWalletService(); XmrWalletService walletService = processModel.getProvider().getXmrWalletService();

View file

@ -28,7 +28,7 @@ import lombok.extern.slf4j.Slf4j;
import monero.wallet.MoneroWallet; 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 @Slf4j
public class SellerSendsPaymentAccountPayloadKey extends SendMailboxMessageTask { public class SellerSendsPaymentAccountPayloadKey extends SendMailboxMessageTask {
@ -52,9 +52,6 @@ public class SellerSendsPaymentAccountPayloadKey extends SendMailboxMessageTask
protected TradeMailboxMessage getTradeMailboxMessage(String tradeId) { protected TradeMailboxMessage getTradeMailboxMessage(String tradeId) {
if (message == null) { if (message == null) {
// set payment account payload
trade.getSelf().setPaymentAccountPayload(processModel.getPaymentAccountPayload(trade));
// get updated multisig hex // get updated multisig hex
if (trade.getSelf().getUpdatedMultisigHex() == null) { if (trade.getSelf().getUpdatedMultisigHex() == null) {
XmrWalletService walletService = processModel.getProvider().getXmrWalletService(); XmrWalletService walletService = processModel.getProvider().getXmrWalletService();

View file

@ -447,6 +447,7 @@ message PaymentSentMessage {
string counter_currency_extra_data = 6; string counter_currency_extra_data = 6;
string payout_tx_hex = 7; string payout_tx_hex = 7;
string updated_multisig_hex = 8; string updated_multisig_hex = 8;
bytes payment_account_key = 9;
} }
message PaymentReceivedMessage { message PaymentReceivedMessage {