enable editing offers

This commit is contained in:
woodser 2023-08-14 13:14:02 -04:00
parent cb7d9364e5
commit b2b4706f14
11 changed files with 93 additions and 26 deletions

View file

@ -243,7 +243,7 @@ public class OfferFilterService {
public boolean hasValidSignature(Offer offer) { public boolean hasValidSignature(Offer offer) {
Arbitrator arbitrator = user.getAcceptedArbitratorByAddress(offer.getOfferPayload().getArbitratorSigner()); Arbitrator arbitrator = user.getAcceptedArbitratorByAddress(offer.getOfferPayload().getArbitratorSigner());
if (arbitrator == null) return false; // invalid arbitrator if (arbitrator == null) return false; // invalid arbitrator
return HavenoUtils.isArbitratorSignatureValid(offer, arbitrator); return HavenoUtils.isArbitratorSignatureValid(offer.getOfferPayload(), arbitrator);
} }
public boolean isReservedFundsSpent(Offer offer) { public boolean isReservedFundsSpent(Offer offer) {

View file

@ -243,6 +243,52 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
return this.hash; return this.hash;
} }
public byte[] getSignatureHash() {
// create copy with ignored fields standardized
OfferPayload signee = new OfferPayload(
id,
date,
ownerNodeAddress,
pubKeyRing,
direction,
price,
0,
false,
amount,
minAmount,
baseCurrencyCode,
counterCurrencyCode,
paymentMethodId,
makerPaymentAccountId,
offerFeeTxId,
countryCode,
acceptedCountryCodes,
bankId,
acceptedBankIds,
versionNr,
blockHeightAtOfferCreation,
makerFee,
buyerSecurityDeposit,
sellerSecurityDeposit,
maxTradeLimit,
maxTradePeriod,
useAutoClose,
useReOpenAfterAutoClose,
lowerClosePrice,
upperClosePrice,
isPrivateOffer,
hashOfChallenge,
extraDataMap,
protocolVersion,
arbitratorSigner,
null,
reserveTxKeyImages
);
return signee.getHash();
}
@Override @Override
public long getTTL() { public long getTTL() {
return TTL; return TTL;

View file

@ -30,6 +30,7 @@ import lombok.Setter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@ -103,6 +104,21 @@ public final class OpenOffer implements Tradable {
state = State.SCHEDULED; state = State.SCHEDULED;
} }
public OpenOffer(Offer offer, long triggerPrice, OpenOffer openOffer) {
this.offer = offer;
this.triggerPrice = triggerPrice;
// copy open offer fields
this.state = openOffer.state;
this.reserveExactAmount = openOffer.reserveExactAmount;
this.scheduledAmount = openOffer.scheduledAmount;
this.scheduledTxHashes = openOffer.scheduledTxHashes == null ? null : new ArrayList<String>(openOffer.scheduledTxHashes);
this.splitOutputTxHash = openOffer.splitOutputTxHash;
this.reserveTxHash = openOffer.reserveTxHash;
this.reserveTxHex = openOffer.reserveTxHex;
this.reserveTxKey = openOffer.reserveTxKey;
}
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// PROTO BUFFER // PROTO BUFFER
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////

View file

@ -624,7 +624,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
openOffer.setState(OpenOffer.State.CANCELED); openOffer.setState(OpenOffer.State.CANCELED);
removeOpenOffer(openOffer); removeOpenOffer(openOffer);
OpenOffer editedOpenOffer = new OpenOffer(editedOffer, triggerPrice); OpenOffer editedOpenOffer = new OpenOffer(editedOffer, triggerPrice, openOffer);
editedOpenOffer.setState(originalState); editedOpenOffer.setState(originalState);
addOpenOffer(editedOpenOffer); addOpenOffer(editedOpenOffer);
@ -1172,8 +1172,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
true); true);
// arbitrator signs offer to certify they have valid reserve tx // arbitrator signs offer to certify they have valid reserve tx
String offerPayloadAsJson = JsonUtil.objectToJson(request.getOfferPayload()); byte[] signature = HavenoUtils.signOffer(request.getOfferPayload(), keyRing);
byte[] signature = HavenoUtils.sign(keyRing, offerPayloadAsJson);
OfferPayload signedOfferPayload = request.getOfferPayload(); OfferPayload signedOfferPayload = request.getOfferPayload();
signedOfferPayload.setArbitratorSignature(signature); signedOfferPayload.setArbitratorSignature(signature);

View file

@ -41,7 +41,7 @@ public class MakerProcessSignOfferResponse extends Task<PlaceOfferModel> {
Arbitrator arbitrator = checkNotNull(model.getUser().getAcceptedArbitratorByAddress(offer.getOfferPayload().getArbitratorSigner()), "user.getAcceptedArbitratorByAddress(arbitratorSigner) must not be null"); Arbitrator arbitrator = checkNotNull(model.getUser().getAcceptedArbitratorByAddress(offer.getOfferPayload().getArbitratorSigner()), "user.getAcceptedArbitratorByAddress(arbitratorSigner) must not be null");
// validate arbitrator signature // validate arbitrator signature
if (!HavenoUtils.isArbitratorSignatureValid(new Offer(model.getSignOfferResponse().getSignedOfferPayload()), arbitrator)) { if (!HavenoUtils.isArbitratorSignatureValid(model.getSignOfferResponse().getSignedOfferPayload(), arbitrator)) {
throw new RuntimeException("Offer payload has invalid arbitrator signature"); throw new RuntimeException("Offer payload has invalid arbitrator signature");
} }

View file

@ -20,13 +20,13 @@ package haveno.core.trade;
import com.google.common.base.CaseFormat; import com.google.common.base.CaseFormat;
import com.google.common.base.Charsets; import com.google.common.base.Charsets;
import haveno.common.config.Config; import haveno.common.config.Config;
import haveno.common.crypto.CryptoException;
import haveno.common.crypto.Hash; import haveno.common.crypto.Hash;
import haveno.common.crypto.KeyRing; import haveno.common.crypto.KeyRing;
import haveno.common.crypto.PubKeyRing; import haveno.common.crypto.PubKeyRing;
import haveno.common.crypto.Sig; import haveno.common.crypto.Sig;
import haveno.common.util.Utilities; import haveno.common.util.Utilities;
import haveno.core.app.HavenoSetup; import haveno.core.app.HavenoSetup;
import haveno.core.offer.Offer;
import haveno.core.offer.OfferPayload; import haveno.core.offer.OfferPayload;
import haveno.core.support.dispute.arbitration.ArbitrationManager; import haveno.core.support.dispute.arbitration.ArbitrationManager;
import haveno.core.support.dispute.arbitration.arbitrator.Arbitrator; import haveno.core.support.dispute.arbitration.arbitrator.Arbitrator;
@ -245,6 +245,10 @@ public class HavenoUtils {
return sign(keyRing.getSignatureKeyPair().getPrivate(), message); return sign(keyRing.getSignatureKeyPair().getPrivate(), message);
} }
public static byte[] sign(KeyRing keyRing, byte[] bytes) {
return sign(keyRing.getSignatureKeyPair().getPrivate(), bytes);
}
public static byte[] sign(PrivateKey privateKey, String message) { public static byte[] sign(PrivateKey privateKey, String message) {
return sign(privateKey, message.getBytes(Charsets.UTF_8)); return sign(privateKey, message.getBytes(Charsets.UTF_8));
} }
@ -263,9 +267,10 @@ public class HavenoUtils {
public static void verifySignature(PubKeyRing pubKeyRing, byte[] bytes, byte[] signature) { public static void verifySignature(PubKeyRing pubKeyRing, byte[] bytes, byte[] signature) {
try { try {
Sig.verify(pubKeyRing.getSignaturePubKey(), bytes, signature); boolean isValid = Sig.verify(pubKeyRing.getSignaturePubKey(), bytes, signature);
} catch (Exception e) { if (!isValid) throw new IllegalArgumentException("Signature verification failed.");
throw new RuntimeException(e); } catch (CryptoException e) {
throw new IllegalArgumentException(e);
} }
} }
@ -282,6 +287,17 @@ public class HavenoUtils {
} }
} }
/**
* Sign an offer.
*
* @param offer is an unsigned offer to sign
* @param keyRing is the arbitrator's key ring to sign with
* @return the arbitrator's signature
*/
public static byte[] signOffer(OfferPayload offer, KeyRing keyRing) {
return HavenoUtils.sign(keyRing, offer.getSignatureHash());
}
/** /**
* Check if the arbitrator signature is valid for an offer. * Check if the arbitrator signature is valid for an offer.
* *
@ -289,20 +305,8 @@ public class HavenoUtils {
* @param arbitrator is the original signing arbitrator * @param arbitrator is the original signing arbitrator
* @return true if the arbitrator's signature is valid for the offer * @return true if the arbitrator's signature is valid for the offer
*/ */
public static boolean isArbitratorSignatureValid(Offer offer, Arbitrator arbitrator) { public static boolean isArbitratorSignatureValid(OfferPayload offer, Arbitrator arbitrator) {
return isSignatureValid(arbitrator.getPubKeyRing(), offer.getSignatureHash(), offer.getArbitratorSignature());
// copy offer payload
OfferPayload offerPayloadCopy = OfferPayload.fromProto(offer.toProtoMessage().getOfferPayload());
// remove arbitrator signature from signed payload
byte[] signature = offerPayloadCopy.getArbitratorSignature();
offerPayloadCopy.setArbitratorSignature(null);
// get unsigned offer payload as json string
String unsignedOfferAsJson = JsonUtil.objectToJson(offerPayloadCopy);
// verify signature
return isSignatureValid(arbitrator.getPubKeyRing(), unsignedOfferAsJson, signature);
} }
/** /**

View file

@ -71,6 +71,7 @@ public class ArbitratorProcessReserveTx extends TradeTask {
null, null,
true); true);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("Error processing reserve tx from " + (isFromTaker ? "taker " : "maker ") + request.getSenderNodeAddress() + ", offerId=" + offer.getId() + ": " + e.getMessage()); throw new RuntimeException("Error processing reserve tx from " + (isFromTaker ? "taker " : "maker ") + request.getSenderNodeAddress() + ", offerId=" + offer.getId() + ": " + e.getMessage());
} }

View file

@ -157,7 +157,7 @@ public class FormattingUtils {
} }
public static String formatPrice(Price price) { public static String formatPrice(Price price) {
return formatPrice(price, getPriceMonetaryFormat(price.getCurrencyCode()), false); return formatPrice(price, price == null ? null : getPriceMonetaryFormat(price.getCurrencyCode()), false);
} }
public static String formatMarketPrice(double price, String currencyCode) { public static String formatMarketPrice(double price, String currencyCode) {

View file

@ -467,6 +467,7 @@ public class XmrWalletService {
* @return tuple with the verified tx and its actual security deposit * @return tuple with the verified tx and its actual security deposit
*/ */
public Tuple2<MoneroTx, BigInteger> verifyTradeTx(String offerId, BigInteger tradeFee, BigInteger sendAmount, BigInteger securityDeposit, String address, String txHash, String txHex, String txKey, List<String> keyImages, boolean isReserveTx) { public Tuple2<MoneroTx, BigInteger> verifyTradeTx(String offerId, BigInteger tradeFee, BigInteger sendAmount, BigInteger securityDeposit, String address, String txHash, String txHex, String txKey, List<String> keyImages, boolean isReserveTx) {
if (txHash == null) throw new IllegalArgumentException("Cannot verify trade tx with null id");
MoneroDaemonRpc daemon = getDaemon(); MoneroDaemonRpc daemon = getDaemon();
MoneroWallet wallet = getWallet(); MoneroWallet wallet = getWallet();
MoneroTx tx = null; MoneroTx tx = null;

View file

@ -1078,7 +1078,7 @@ abstract public class OfferBookView<R extends GridPane, M extends OfferBookViewM
hbox.setSpacing(8); hbox.setSpacing(8);
hbox.setAlignment(Pos.CENTER); hbox.setAlignment(Pos.CENTER);
hbox.getChildren().add(button); hbox.getChildren().add(button);
//hbox.getChildren().add(button2); // TODO: re-enable editing offers hbox.getChildren().add(button2);
HBox.setHgrow(button, Priority.ALWAYS); HBox.setHgrow(button, Priority.ALWAYS);
HBox.setHgrow(button2, Priority.ALWAYS); HBox.setHgrow(button2, Priority.ALWAYS);
} }

View file

@ -153,7 +153,7 @@ public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersView
setPaymentMethodColumnCellFactory(); setPaymentMethodColumnCellFactory();
setDateColumnCellFactory(); setDateColumnCellFactory();
setDeactivateColumnCellFactory(); setDeactivateColumnCellFactory();
// setEditColumnCellFactory(); // TODO: re-enable to edit offer price, etc? setEditColumnCellFactory();
setTriggerIconColumnCellFactory(); setTriggerIconColumnCellFactory();
setTriggerPriceColumnCellFactory(); setTriggerPriceColumnCellFactory();
setRemoveColumnCellFactory(); setRemoveColumnCellFactory();