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.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.
*

View File

@ -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();
}
}

View File

@ -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());

View File

@ -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;

View File

@ -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();

View File

@ -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?

View File

@ -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();

View File

@ -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();

View File

@ -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 {