minimum support for account witness verification & storage based on bisq

verify peer witness data and share with arbitrator for signing
arbitrator can sign witness hash, owner pub key (see developer-guide.md)
taker signs offer id nonce in SendOfferAvailabilityRequest
maker signs deposit tx hash in SendSignContractRequest
buyer verifies witness with payload on deposit confirmation
seller verifies witness with payload on payment sent message
add synchronization to User paymentAccountsAsObservable and elsewhere
This commit is contained in:
woodser 2023-01-09 15:32:24 -05:00
parent 963d92478d
commit 646380bc7a
28 changed files with 210 additions and 98 deletions

View File

@ -64,7 +64,7 @@ public class AccountAgeWitness implements ProcessOncePersistableNetworkPayload,
return protobuf.PersistableNetworkPayload.newBuilder().setAccountAgeWitness(builder).build(); return protobuf.PersistableNetworkPayload.newBuilder().setAccountAgeWitness(builder).build();
} }
protobuf.AccountAgeWitness toProtoAccountAgeWitness() { public protobuf.AccountAgeWitness toProtoAccountAgeWitness() {
return toProtoMessage().getAccountAgeWitness(); return toProtoMessage().getAccountAgeWitness();
} }

View File

@ -531,6 +531,15 @@ public class AccountAgeWitnessService {
byte[] nonce, byte[] nonce,
byte[] signature, byte[] signature,
ErrorMessageHandler errorMessageHandler) { ErrorMessageHandler errorMessageHandler) {
log.info("Verifying account age witness for {} {}, payment account payload hash={}, peers current date={}, nonce={}, signature={}",
trade.getClass().getSimpleName(),
trade.getId(),
Utilities.bytesAsHexString(peersPaymentAccountPayload.getHash()),
peersCurrentDate,
Utilities.bytesAsHexString(nonce),
Utilities.bytesAsHexString(signature));
final Optional<AccountAgeWitness> accountAgeWitnessOptional = final Optional<AccountAgeWitness> accountAgeWitnessOptional =
findWitness(peersPaymentAccountPayload, peersPubKeyRing); findWitness(peersPaymentAccountPayload, peersPubKeyRing);
// If we don't find a stored witness data we create a new dummy object which makes is easier to reuse the // If we don't find a stored witness data we create a new dummy object which makes is easier to reuse the

View File

@ -31,6 +31,7 @@ import com.google.protobuf.ByteString;
import bisq.common.crypto.PubKeyRing; import bisq.common.crypto.PubKeyRing;
import bisq.common.proto.network.NetworkPayload; import bisq.common.proto.network.NetworkPayload;
import bisq.common.util.JsonExclude; import bisq.common.util.JsonExclude;
import bisq.common.util.Utilities;
import org.bitcoinj.core.Coin; import org.bitcoinj.core.Coin;
@ -289,8 +290,8 @@ public final class Contract implements NetworkPayload {
",\n takerAccountId='" + takerAccountId + '\'' + ",\n takerAccountId='" + takerAccountId + '\'' +
",\n makerPaymentMethodId='" + makerPaymentMethodId + '\'' + ",\n makerPaymentMethodId='" + makerPaymentMethodId + '\'' +
",\n takerPaymentMethodId='" + takerPaymentMethodId + '\'' + ",\n takerPaymentMethodId='" + takerPaymentMethodId + '\'' +
",\n makerPaymentAccountPayloadHash=" + makerPaymentAccountPayloadHash + ",\n makerPaymentAccountPayloadHash=" + Utilities.bytesAsHexString(makerPaymentAccountPayloadHash) +
",\n takerPaymentAccountPayloadHash=" + takerPaymentAccountPayloadHash + ",\n takerPaymentAccountPayloadHash=" + Utilities.bytesAsHexString(takerPaymentAccountPayloadHash) +
",\n makerPubKeyRing=" + makerPubKeyRing + ",\n makerPubKeyRing=" + makerPubKeyRing +
",\n takerPubKeyRing=" + takerPubKeyRing + ",\n takerPubKeyRing=" + takerPubKeyRing +
",\n makerPayoutAddressString='" + makerPayoutAddressString + '\'' + ",\n makerPayoutAddressString='" + makerPayoutAddressString + '\'' +

View File

@ -1020,8 +1020,10 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
} }
public Optional<Trade> getOpenTrade(String tradeId) { public Optional<Trade> getOpenTrade(String tradeId) {
synchronized (tradableList) {
return tradableList.stream().filter(e -> e.getId().equals(tradeId)).findFirst(); return tradableList.stream().filter(e -> e.getId().equals(tradeId)).findFirst();
} }
}
public List<Trade> getOpenTrades() { public List<Trade> getOpenTrades() {
return ImmutableList.copyOf(getObservableList().stream() return ImmutableList.copyOf(getObservableList().stream()

View File

@ -18,7 +18,7 @@
package bisq.core.trade.messages; package bisq.core.trade.messages;
import bisq.core.account.sign.SignedWitness; import bisq.core.account.sign.SignedWitness;
import bisq.core.account.witness.AccountAgeWitness;
import bisq.network.p2p.NodeAddress; import bisq.network.p2p.NodeAddress;
import bisq.common.app.Version; import bisq.common.app.Version;
@ -31,7 +31,6 @@ import java.util.UUID;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import lombok.Value;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -49,32 +48,34 @@ public final class PaymentReceivedMessage extends TradeMailboxMessage {
private final String signedPayoutTxHex; private final String signedPayoutTxHex;
private final String updatedMultisigHex; private final String updatedMultisigHex;
private final boolean deferPublishPayout; private final boolean deferPublishPayout;
@Nullable
private final AccountAgeWitness buyerAccountAgeWitness;
@Nullable
private final SignedWitness buyerSignedWitness;
private final PaymentSentMessage paymentSentMessage; private final PaymentSentMessage paymentSentMessage;
@Setter @Setter
@Nullable @Nullable
private byte[] sellerSignature; private byte[] sellerSignature;
// Added in v1.4.0
@Nullable
private final SignedWitness signedWitness;
public PaymentReceivedMessage(String tradeId, public PaymentReceivedMessage(String tradeId,
NodeAddress senderNodeAddress, NodeAddress senderNodeAddress,
@Nullable SignedWitness signedWitness,
String unsignedPayoutTxHex, String unsignedPayoutTxHex,
String signedPayoutTxHex, String signedPayoutTxHex,
String updatedMultisigHex, String updatedMultisigHex,
boolean deferPublishPayout, boolean deferPublishPayout,
AccountAgeWitness buyerAccountAgeWitness,
@Nullable SignedWitness buyerSignedWitness,
PaymentSentMessage paymentSentMessage) { PaymentSentMessage paymentSentMessage) {
this(tradeId, this(tradeId,
senderNodeAddress, senderNodeAddress,
signedWitness,
UUID.randomUUID().toString(), UUID.randomUUID().toString(),
Version.getP2PMessageVersion(), Version.getP2PMessageVersion(),
unsignedPayoutTxHex, unsignedPayoutTxHex,
signedPayoutTxHex, signedPayoutTxHex,
updatedMultisigHex, updatedMultisigHex,
deferPublishPayout, deferPublishPayout,
buyerAccountAgeWitness,
buyerSignedWitness,
paymentSentMessage); paymentSentMessage);
} }
@ -85,22 +86,24 @@ public final class PaymentReceivedMessage extends TradeMailboxMessage {
private PaymentReceivedMessage(String tradeId, private PaymentReceivedMessage(String tradeId,
NodeAddress senderNodeAddress, NodeAddress senderNodeAddress,
@Nullable SignedWitness signedWitness,
String uid, String uid,
String messageVersion, String messageVersion,
String unsignedPayoutTxHex, String unsignedPayoutTxHex,
String signedPayoutTxHex, String signedPayoutTxHex,
String updatedMultisigHex, String updatedMultisigHex,
boolean deferPublishPayout, boolean deferPublishPayout,
AccountAgeWitness buyerAccountAgeWitness,
@Nullable SignedWitness buyerSignedWitness,
PaymentSentMessage paymentSentMessage) { PaymentSentMessage paymentSentMessage) {
super(messageVersion, tradeId, uid); super(messageVersion, tradeId, uid);
this.senderNodeAddress = senderNodeAddress; this.senderNodeAddress = senderNodeAddress;
this.signedWitness = signedWitness;
this.unsignedPayoutTxHex = unsignedPayoutTxHex; this.unsignedPayoutTxHex = unsignedPayoutTxHex;
this.signedPayoutTxHex = signedPayoutTxHex; this.signedPayoutTxHex = signedPayoutTxHex;
this.updatedMultisigHex = updatedMultisigHex; this.updatedMultisigHex = updatedMultisigHex;
this.deferPublishPayout = deferPublishPayout; this.deferPublishPayout = deferPublishPayout;
this.paymentSentMessage = paymentSentMessage; this.paymentSentMessage = paymentSentMessage;
this.buyerAccountAgeWitness = buyerAccountAgeWitness;
this.buyerSignedWitness = buyerSignedWitness;
} }
@Override @Override
@ -110,10 +113,11 @@ public final class PaymentReceivedMessage extends TradeMailboxMessage {
.setSenderNodeAddress(senderNodeAddress.toProtoMessage()) .setSenderNodeAddress(senderNodeAddress.toProtoMessage())
.setUid(uid) .setUid(uid)
.setDeferPublishPayout(deferPublishPayout); .setDeferPublishPayout(deferPublishPayout);
Optional.ofNullable(signedWitness).ifPresent(signedWitness -> builder.setSignedWitness(signedWitness.toProtoSignedWitness()));
Optional.ofNullable(updatedMultisigHex).ifPresent(e -> builder.setUpdatedMultisigHex(updatedMultisigHex)); Optional.ofNullable(updatedMultisigHex).ifPresent(e -> builder.setUpdatedMultisigHex(updatedMultisigHex));
Optional.ofNullable(unsignedPayoutTxHex).ifPresent(e -> builder.setUnsignedPayoutTxHex(unsignedPayoutTxHex)); Optional.ofNullable(unsignedPayoutTxHex).ifPresent(e -> builder.setUnsignedPayoutTxHex(unsignedPayoutTxHex));
Optional.ofNullable(signedPayoutTxHex).ifPresent(e -> builder.setSignedPayoutTxHex(signedPayoutTxHex)); Optional.ofNullable(signedPayoutTxHex).ifPresent(e -> builder.setSignedPayoutTxHex(signedPayoutTxHex));
Optional.ofNullable(buyerAccountAgeWitness).ifPresent(buyerAccountAgeWitness -> builder.setBuyerAccountAgeWitness(buyerAccountAgeWitness.toProtoAccountAgeWitness()));
Optional.ofNullable(buyerSignedWitness).ifPresent(buyerSignedWitness -> builder.setBuyerSignedWitness(buyerSignedWitness.toProtoSignedWitness()));
Optional.ofNullable(paymentSentMessage).ifPresent(e -> builder.setPaymentSentMessage(paymentSentMessage.toProtoNetworkEnvelope().getPaymentSentMessage())); Optional.ofNullable(paymentSentMessage).ifPresent(e -> builder.setPaymentSentMessage(paymentSentMessage.toProtoNetworkEnvelope().getPaymentSentMessage()));
Optional.ofNullable(sellerSignature).ifPresent(e -> builder.setSellerSignature(ByteString.copyFrom(e))); Optional.ofNullable(sellerSignature).ifPresent(e -> builder.setSellerSignature(ByteString.copyFrom(e)));
return getNetworkEnvelopeBuilder().setPaymentReceivedMessage(builder).build(); return getNetworkEnvelopeBuilder().setPaymentReceivedMessage(builder).build();
@ -121,20 +125,23 @@ public final class PaymentReceivedMessage extends TradeMailboxMessage {
public static NetworkEnvelope fromProto(protobuf.PaymentReceivedMessage proto, String messageVersion) { public static NetworkEnvelope fromProto(protobuf.PaymentReceivedMessage proto, String messageVersion) {
// There is no method to check for a nullable non-primitive data type object but we know that all fields // There is no method to check for a nullable non-primitive data type object but we know that all fields
// are empty/null, so we check for the signature to see if we got a valid signedWitness. // are empty/null, so we check for the signature to see if we got a valid buyerSignedWitness.
protobuf.SignedWitness protoSignedWitness = proto.getSignedWitness(); protobuf.AccountAgeWitness protoAccountAgeWitness = proto.getBuyerAccountAgeWitness();
SignedWitness signedWitness = !protoSignedWitness.getSignature().isEmpty() ? AccountAgeWitness buyerAccountAgeWitness = protoAccountAgeWitness.getHash().isEmpty() ? null : AccountAgeWitness.fromProto(protoAccountAgeWitness);
protobuf.SignedWitness protoSignedWitness = proto.getBuyerSignedWitness();
SignedWitness buyerSignedWitness = !protoSignedWitness.getSignature().isEmpty() ?
SignedWitness.fromProto(protoSignedWitness) : SignedWitness.fromProto(protoSignedWitness) :
null; null;
PaymentReceivedMessage message = new PaymentReceivedMessage(proto.getTradeId(), PaymentReceivedMessage message = new PaymentReceivedMessage(proto.getTradeId(),
NodeAddress.fromProto(proto.getSenderNodeAddress()), NodeAddress.fromProto(proto.getSenderNodeAddress()),
signedWitness,
proto.getUid(), proto.getUid(),
messageVersion, messageVersion,
ProtoUtil.stringOrNullFromProto(proto.getUnsignedPayoutTxHex()), ProtoUtil.stringOrNullFromProto(proto.getUnsignedPayoutTxHex()),
ProtoUtil.stringOrNullFromProto(proto.getSignedPayoutTxHex()), ProtoUtil.stringOrNullFromProto(proto.getSignedPayoutTxHex()),
ProtoUtil.stringOrNullFromProto(proto.getUpdatedMultisigHex()), ProtoUtil.stringOrNullFromProto(proto.getUpdatedMultisigHex()),
proto.getDeferPublishPayout(), proto.getDeferPublishPayout(),
buyerAccountAgeWitness,
buyerSignedWitness,
proto.hasPaymentSentMessage() ? PaymentSentMessage.fromProto(proto.getPaymentSentMessage(), messageVersion) : null); proto.hasPaymentSentMessage() ? PaymentSentMessage.fromProto(proto.getPaymentSentMessage(), messageVersion) : null);
message.setSellerSignature(ProtoUtil.byteArrayOrNullFromProto(proto.getSellerSignature())); message.setSellerSignature(ProtoUtil.byteArrayOrNullFromProto(proto.getSellerSignature()));
return message; return message;
@ -144,7 +151,7 @@ public final class PaymentReceivedMessage extends TradeMailboxMessage {
public String toString() { public String toString() {
return "PaymentReceivedMessage{" + return "PaymentReceivedMessage{" +
"\n senderNodeAddress=" + senderNodeAddress + "\n senderNodeAddress=" + senderNodeAddress +
",\n signedWitness=" + signedWitness + ",\n buyerSignedWitness=" + buyerSignedWitness +
",\n unsignedPayoutTxHex=" + unsignedPayoutTxHex + ",\n unsignedPayoutTxHex=" + unsignedPayoutTxHex +
",\n signedPayoutTxHex=" + signedPayoutTxHex + ",\n signedPayoutTxHex=" + signedPayoutTxHex +
",\n updatedMultisigHex=" + (updatedMultisigHex == null ? null : updatedMultisigHex.substring(0, Math.max(updatedMultisigHex.length(), 1000))) + ",\n updatedMultisigHex=" + (updatedMultisigHex == null ? null : updatedMultisigHex.substring(0, Math.max(updatedMultisigHex.length(), 1000))) +

View File

@ -21,6 +21,7 @@ import bisq.network.p2p.NodeAddress;
import com.google.protobuf.ByteString; 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;
import bisq.core.account.witness.AccountAgeWitness;
import java.util.Optional; import java.util.Optional;
@ -42,6 +43,8 @@ public final class PaymentSentMessage extends TradeMailboxMessage {
private final String updatedMultisigHex; private final String updatedMultisigHex;
@Nullable @Nullable
private final byte[] paymentAccountKey; private final byte[] paymentAccountKey;
@Nullable
private AccountAgeWitness sellerAccountAgeWitness;
@Setter @Setter
@Nullable @Nullable
private byte[] buyerSignature; private byte[] buyerSignature;
@ -58,7 +61,8 @@ public final class PaymentSentMessage extends TradeMailboxMessage {
String uid, String uid,
@Nullable String signedPayoutTxHex, @Nullable String signedPayoutTxHex,
@Nullable String updatedMultisigHex, @Nullable String updatedMultisigHex,
@Nullable byte[] paymentAccountKey) { @Nullable byte[] paymentAccountKey,
AccountAgeWitness sellerAccountAgeWitness) {
this(tradeId, this(tradeId,
senderNodeAddress, senderNodeAddress,
counterCurrencyTxId, counterCurrencyTxId,
@ -67,7 +71,8 @@ public final class PaymentSentMessage extends TradeMailboxMessage {
Version.getP2PMessageVersion(), Version.getP2PMessageVersion(),
signedPayoutTxHex, signedPayoutTxHex,
updatedMultisigHex, updatedMultisigHex,
paymentAccountKey); paymentAccountKey,
sellerAccountAgeWitness);
} }
@ -83,7 +88,8 @@ public final class PaymentSentMessage extends TradeMailboxMessage {
String messageVersion, String messageVersion,
@Nullable String signedPayoutTxHex, @Nullable String signedPayoutTxHex,
@Nullable String updatedMultisigHex, @Nullable String updatedMultisigHex,
@Nullable byte[] paymentAccountKey) { @Nullable byte[] paymentAccountKey,
AccountAgeWitness sellerAccountAgeWitness) {
super(messageVersion, tradeId, uid); super(messageVersion, tradeId, uid);
this.senderNodeAddress = senderNodeAddress; this.senderNodeAddress = senderNodeAddress;
this.counterCurrencyTxId = counterCurrencyTxId; this.counterCurrencyTxId = counterCurrencyTxId;
@ -91,6 +97,7 @@ public final class PaymentSentMessage extends TradeMailboxMessage {
this.payoutTxHex = signedPayoutTxHex; this.payoutTxHex = signedPayoutTxHex;
this.updatedMultisigHex = updatedMultisigHex; this.updatedMultisigHex = updatedMultisigHex;
this.paymentAccountKey = paymentAccountKey; this.paymentAccountKey = paymentAccountKey;
this.sellerAccountAgeWitness = sellerAccountAgeWitness;
} }
@Override @Override
@ -106,12 +113,17 @@ public final class PaymentSentMessage extends TradeMailboxMessage {
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))); Optional.ofNullable(paymentAccountKey).ifPresent(e -> builder.setPaymentAccountKey(ByteString.copyFrom(e)));
Optional.ofNullable(buyerSignature).ifPresent(e -> builder.setBuyerSignature(ByteString.copyFrom(e))); Optional.ofNullable(buyerSignature).ifPresent(e -> builder.setBuyerSignature(ByteString.copyFrom(e)));
Optional.ofNullable(sellerAccountAgeWitness).ifPresent(e -> builder.setSellerAccountAgeWitness(sellerAccountAgeWitness.toProtoAccountAgeWitness()));
return getNetworkEnvelopeBuilder().setPaymentSentMessage(builder).build(); return getNetworkEnvelopeBuilder().setPaymentSentMessage(builder).build();
} }
public static PaymentSentMessage fromProto(protobuf.PaymentSentMessage proto, public static PaymentSentMessage fromProto(protobuf.PaymentSentMessage proto,
String messageVersion) { String messageVersion) {
protobuf.AccountAgeWitness protoAccountAgeWitness = proto.getSellerAccountAgeWitness();
AccountAgeWitness accountAgeWitness = protoAccountAgeWitness.getHash().isEmpty() ? null : AccountAgeWitness.fromProto(protoAccountAgeWitness);
PaymentSentMessage message = new PaymentSentMessage(proto.getTradeId(), PaymentSentMessage message = new PaymentSentMessage(proto.getTradeId(),
NodeAddress.fromProto(proto.getSenderNodeAddress()), NodeAddress.fromProto(proto.getSenderNodeAddress()),
ProtoUtil.stringOrNullFromProto(proto.getCounterCurrencyTxId()), ProtoUtil.stringOrNullFromProto(proto.getCounterCurrencyTxId()),
@ -120,7 +132,8 @@ public final class PaymentSentMessage extends TradeMailboxMessage {
messageVersion, messageVersion,
ProtoUtil.stringOrNullFromProto(proto.getPayoutTxHex()), ProtoUtil.stringOrNullFromProto(proto.getPayoutTxHex()),
ProtoUtil.stringOrNullFromProto(proto.getUpdatedMultisigHex()), ProtoUtil.stringOrNullFromProto(proto.getUpdatedMultisigHex()),
ProtoUtil.byteArrayOrNullFromProto(proto.getPaymentAccountKey()) ProtoUtil.byteArrayOrNullFromProto(proto.getPaymentAccountKey()),
accountAgeWitness
); );
message.setBuyerSignature(ProtoUtil.byteArrayOrNullFromProto(proto.getBuyerSignature())); message.setBuyerSignature(ProtoUtil.byteArrayOrNullFromProto(proto.getBuyerSignature()));
return message; return message;

View File

@ -21,9 +21,15 @@ import bisq.core.proto.CoreProtoResolver;
import bisq.network.p2p.DirectMessage; import bisq.network.p2p.DirectMessage;
import bisq.network.p2p.NodeAddress; import bisq.network.p2p.NodeAddress;
import java.util.Optional;
import javax.annotation.Nullable;
import com.google.protobuf.ByteString; import com.google.protobuf.ByteString;
import bisq.common.crypto.PubKeyRing; import bisq.common.crypto.PubKeyRing;
import bisq.common.proto.ProtoUtil;
import bisq.common.util.Utilities;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.Value; import lombok.Value;
@ -37,6 +43,7 @@ public final class SignContractRequest extends TradeMessage implements DirectMes
private final byte[] paymentAccountPayloadHash; private final byte[] paymentAccountPayloadHash;
private final String payoutAddress; private final String payoutAddress;
private final String depositTxHash; private final String depositTxHash;
private final byte[] accountAgeWitnessSignatureOfDepositHash;
public SignContractRequest(String tradeId, public SignContractRequest(String tradeId,
NodeAddress senderNodeAddress, NodeAddress senderNodeAddress,
@ -47,7 +54,8 @@ public final class SignContractRequest extends TradeMessage implements DirectMes
String accountId, String accountId,
byte[] paymentAccountPayloadHash, byte[] paymentAccountPayloadHash,
String payoutAddress, String payoutAddress,
String depositTxHash) { String depositTxHash,
@Nullable byte[] accountAgeWitnessSignatureOfDepositHash) {
super(messageVersion, tradeId, uid); super(messageVersion, tradeId, uid);
this.senderNodeAddress = senderNodeAddress; this.senderNodeAddress = senderNodeAddress;
this.pubKeyRing = pubKeyRing; this.pubKeyRing = pubKeyRing;
@ -56,6 +64,7 @@ public final class SignContractRequest extends TradeMessage implements DirectMes
this.paymentAccountPayloadHash = paymentAccountPayloadHash; this.paymentAccountPayloadHash = paymentAccountPayloadHash;
this.payoutAddress = payoutAddress; this.payoutAddress = payoutAddress;
this.depositTxHash = depositTxHash; this.depositTxHash = depositTxHash;
this.accountAgeWitnessSignatureOfDepositHash = accountAgeWitnessSignatureOfDepositHash;
} }
@ -75,6 +84,7 @@ public final class SignContractRequest extends TradeMessage implements DirectMes
.setPayoutAddress(payoutAddress) .setPayoutAddress(payoutAddress)
.setDepositTxHash(depositTxHash); .setDepositTxHash(depositTxHash);
Optional.ofNullable(accountAgeWitnessSignatureOfDepositHash).ifPresent(e -> builder.setAccountAgeWitnessSignatureOfDepositHash(ByteString.copyFrom(e)));
builder.setCurrentDate(currentDate); builder.setCurrentDate(currentDate);
return getNetworkEnvelopeBuilder().setSignContractRequest(builder).build(); return getNetworkEnvelopeBuilder().setSignContractRequest(builder).build();
@ -92,7 +102,8 @@ public final class SignContractRequest extends TradeMessage implements DirectMes
proto.getAccountId(), proto.getAccountId(),
proto.getPaymentAccountPayloadHash().toByteArray(), proto.getPaymentAccountPayloadHash().toByteArray(),
proto.getPayoutAddress(), proto.getPayoutAddress(),
proto.getDepositTxHash()); proto.getDepositTxHash(),
ProtoUtil.byteArrayOrNullFromProto(proto.getAccountAgeWitnessSignatureOfDepositHash()));
} }
@Override @Override
@ -102,9 +113,10 @@ public final class SignContractRequest extends TradeMessage implements DirectMes
",\n pubKeyRing=" + pubKeyRing + ",\n pubKeyRing=" + pubKeyRing +
",\n currentDate=" + currentDate + ",\n currentDate=" + currentDate +
",\n accountId=" + accountId + ",\n accountId=" + accountId +
",\n paymentAccountPayloadHash='" + paymentAccountPayloadHash + ",\n paymentAccountPayloadHash='" + Utilities.bytesAsHexString(paymentAccountPayloadHash) +
",\n payoutAddress='" + payoutAddress + ",\n payoutAddress='" + payoutAddress +
",\n depositTxHash='" + depositTxHash + ",\n depositTxHash='" + depositTxHash +
",\n accountAgeWitnessSignatureOfDepositHash='" + Utilities.bytesAsHexString(accountAgeWitnessSignatureOfDepositHash) +
"\n} " + super.toString(); "\n} " + super.toString();
} }
} }

View File

@ -22,19 +22,10 @@ import bisq.core.offer.Offer;
import bisq.core.trade.BuyerAsTakerTrade; import bisq.core.trade.BuyerAsTakerTrade;
import bisq.core.trade.Trade; import bisq.core.trade.Trade;
import bisq.core.trade.handlers.TradeResultHandler; import bisq.core.trade.handlers.TradeResultHandler;
import bisq.core.trade.messages.DepositResponse;
import bisq.core.trade.messages.InitMultisigRequest;
import bisq.core.trade.messages.DepositsConfirmedMessage;
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.ApplyFilter;
import bisq.core.trade.protocol.tasks.TakerReserveTradeFunds; import bisq.core.trade.protocol.tasks.TakerReserveTradeFunds;
import bisq.core.trade.protocol.tasks.TakerSendInitTradeRequestToArbitrator; import bisq.core.trade.protocol.tasks.TakerSendInitTradeRequestToArbitrator;
import bisq.network.p2p.NodeAddress;
import bisq.common.handlers.ErrorMessageHandler; import bisq.common.handlers.ErrorMessageHandler;
import bisq.common.handlers.ResultHandler;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;

View File

@ -37,6 +37,7 @@ import bisq.core.trade.messages.TradeMessage;
import bisq.core.trade.protocol.tasks.RemoveOffer; import bisq.core.trade.protocol.tasks.RemoveOffer;
import bisq.core.trade.protocol.tasks.ProcessPaymentSentMessage; import bisq.core.trade.protocol.tasks.ProcessPaymentSentMessage;
import bisq.core.trade.protocol.tasks.TradeTask; import bisq.core.trade.protocol.tasks.TradeTask;
import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness;
import bisq.core.trade.protocol.FluentProtocol.Condition; import bisq.core.trade.protocol.FluentProtocol.Condition;
import bisq.core.trade.protocol.tasks.ApplyFilter; import bisq.core.trade.protocol.tasks.ApplyFilter;
import bisq.core.trade.protocol.tasks.MaybeSendSignContractRequest; import bisq.core.trade.protocol.tasks.MaybeSendSignContractRequest;
@ -387,7 +388,9 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
expect(new Condition(trade) expect(new Condition(trade)
.with(response) .with(response)
.from(sender)) .from(sender))
.setup(tasks(ProcessDepositsConfirmedMessage.class) .setup(tasks(
ProcessDepositsConfirmedMessage.class,
VerifyPeersAccountAgeWitness.class)
.using(new TradeTaskRunner(trade, .using(new TradeTaskRunner(trade,
() -> { () -> {
handleTaskRunnerSuccess(sender, response); handleTaskRunnerSuccess(sender, response);
@ -431,7 +434,8 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
})) }))
.setup(tasks( .setup(tasks(
ApplyFilter.class, ApplyFilter.class,
ProcessPaymentSentMessage.class) ProcessPaymentSentMessage.class,
VerifyPeersAccountAgeWitness.class)
.using(new TradeTaskRunner(trade, .using(new TradeTaskRunner(trade,
() -> { () -> {
handleTaskRunnerSuccess(peer, message); handleTaskRunnerSuccess(peer, message);

View File

@ -17,6 +17,7 @@
package bisq.core.trade.protocol; package bisq.core.trade.protocol;
import bisq.core.account.witness.AccountAgeWitness;
import bisq.core.btc.model.RawTransactionInput; import bisq.core.btc.model.RawTransactionInput;
import bisq.core.payment.payload.PaymentAccountPayload; import bisq.core.payment.payload.PaymentAccountPayload;
import bisq.core.proto.CoreProtoResolver; import bisq.core.proto.CoreProtoResolver;
@ -97,6 +98,10 @@ public final class TradingPeer implements PersistablePayload {
private byte[] accountAgeWitnessNonce; private byte[] accountAgeWitnessNonce;
@Nullable @Nullable
private byte[] accountAgeWitnessSignature; private byte[] accountAgeWitnessSignature;
@Getter
@Setter
@Nullable
private AccountAgeWitness accountAgeWitness;
private long currentDate; private long currentDate;
// Added in v.1.1.6 // Added in v.1.1.6
@ -155,6 +160,7 @@ public final class TradingPeer implements PersistablePayload {
Optional.ofNullable(changeOutputAddress).ifPresent(builder::setChangeOutputAddress); Optional.ofNullable(changeOutputAddress).ifPresent(builder::setChangeOutputAddress);
Optional.ofNullable(accountAgeWitnessNonce).ifPresent(e -> builder.setAccountAgeWitnessNonce(ByteString.copyFrom(e))); Optional.ofNullable(accountAgeWitnessNonce).ifPresent(e -> builder.setAccountAgeWitnessNonce(ByteString.copyFrom(e)));
Optional.ofNullable(accountAgeWitnessSignature).ifPresent(e -> builder.setAccountAgeWitnessSignature(ByteString.copyFrom(e))); Optional.ofNullable(accountAgeWitnessSignature).ifPresent(e -> builder.setAccountAgeWitnessSignature(ByteString.copyFrom(e)));
Optional.ofNullable(accountAgeWitness).ifPresent(e -> builder.setAccountAgeWitness(accountAgeWitness.toProtoAccountAgeWitness()));
Optional.ofNullable(mediatedPayoutTxSignature).ifPresent(e -> builder.setMediatedPayoutTxSignature(ByteString.copyFrom(e))); Optional.ofNullable(mediatedPayoutTxSignature).ifPresent(e -> builder.setMediatedPayoutTxSignature(ByteString.copyFrom(e)));
Optional.ofNullable(reserveTxHash).ifPresent(e -> builder.setReserveTxHash(reserveTxHash)); Optional.ofNullable(reserveTxHash).ifPresent(e -> builder.setReserveTxHash(reserveTxHash));
Optional.ofNullable(reserveTxHex).ifPresent(e -> builder.setReserveTxHex(reserveTxHex)); Optional.ofNullable(reserveTxHex).ifPresent(e -> builder.setReserveTxHex(reserveTxHex));
@ -203,6 +209,8 @@ public final class TradingPeer implements PersistablePayload {
tradingPeer.setChangeOutputAddress(ProtoUtil.stringOrNullFromProto(proto.getChangeOutputAddress())); tradingPeer.setChangeOutputAddress(ProtoUtil.stringOrNullFromProto(proto.getChangeOutputAddress()));
tradingPeer.setAccountAgeWitnessNonce(ProtoUtil.byteArrayOrNullFromProto(proto.getAccountAgeWitnessNonce())); tradingPeer.setAccountAgeWitnessNonce(ProtoUtil.byteArrayOrNullFromProto(proto.getAccountAgeWitnessNonce()));
tradingPeer.setAccountAgeWitnessSignature(ProtoUtil.byteArrayOrNullFromProto(proto.getAccountAgeWitnessSignature())); tradingPeer.setAccountAgeWitnessSignature(ProtoUtil.byteArrayOrNullFromProto(proto.getAccountAgeWitnessSignature()));
protobuf.AccountAgeWitness protoAccountAgeWitness = proto.getAccountAgeWitness();
tradingPeer.setAccountAgeWitness(protoAccountAgeWitness.getHash().isEmpty() ? null : AccountAgeWitness.fromProto(protoAccountAgeWitness));
tradingPeer.setCurrentDate(proto.getCurrentDate()); tradingPeer.setCurrentDate(proto.getCurrentDate());
tradingPeer.setMediatedPayoutTxSignature(ProtoUtil.byteArrayOrNullFromProto(proto.getMediatedPayoutTxSignature())); tradingPeer.setMediatedPayoutTxSignature(ProtoUtil.byteArrayOrNullFromProto(proto.getMediatedPayoutTxSignature()));
tradingPeer.setReserveTxHash(ProtoUtil.stringOrNullFromProto(proto.getReserveTxHash())); tradingPeer.setReserveTxHash(ProtoUtil.stringOrNullFromProto(proto.getReserveTxHash()));

View File

@ -54,9 +54,6 @@ public class ArbitratorSendInitTradeOrMultisigRequests extends TradeTask {
runInterceptHook(); runInterceptHook();
InitTradeRequest request = (InitTradeRequest) processModel.getTradeMessage(); InitTradeRequest request = (InitTradeRequest) processModel.getTradeMessage();
// arbitrator signs offer id as nonce to avoid challenge protocol
byte[] sig = Sig.sign(processModel.getKeyRing().getSignatureKeyPair().getPrivate(), processModel.getOfferId().getBytes(Charsets.UTF_8));
// handle request from taker // handle request from taker
if (request.getSenderNodeAddress().equals(trade.getTaker().getNodeAddress())) { if (request.getSenderNodeAddress().equals(trade.getTaker().getNodeAddress())) {
@ -73,7 +70,7 @@ public class ArbitratorSendInitTradeOrMultisigRequests extends TradeTask {
request.getPaymentMethodId(), request.getPaymentMethodId(),
UUID.randomUUID().toString(), UUID.randomUUID().toString(),
Version.getP2PMessageVersion(), Version.getP2PMessageVersion(),
sig, request.getAccountAgeWitnessSignatureOfOfferId(),
new Date().getTime(), new Date().getTime(),
trade.getMaker().getNodeAddress(), trade.getMaker().getNodeAddress(),
trade.getTaker().getNodeAddress(), trade.getTaker().getNodeAddress(),

View File

@ -89,7 +89,8 @@ public abstract class BuyerSendPaymentSentMessage extends SendMailboxMessageTask
deterministicId, deterministicId,
trade.getPayoutTxHex(), trade.getPayoutTxHex(),
trade.getSelf().getUpdatedMultisigHex(), trade.getSelf().getUpdatedMultisigHex(),
trade.getSelf().getPaymentAccountKey() trade.getSelf().getPaymentAccountKey(),
trade.getTradingPeer().getAccountAgeWitness()
); );
// sign message // sign message

View File

@ -53,11 +53,8 @@ public class MakerSendInitTradeRequest extends TradeTask {
checkNotNull(makerRequest); checkNotNull(makerRequest);
checkTradeId(processModel.getOfferId(), makerRequest); checkTradeId(processModel.getOfferId(), makerRequest);
// maker signs offer id as nonce to avoid challenge protocol // TODO: how is this used?
Offer offer = processModel.getOffer();
byte[] sig = Sig.sign(processModel.getKeyRing().getSignatureKeyPair().getPrivate(), offer.getId().getBytes(Charsets.UTF_8));
// create request to arbitrator // create request to arbitrator
Offer offer = processModel.getOffer();
InitTradeRequest arbitratorRequest = new InitTradeRequest( InitTradeRequest arbitratorRequest = new InitTradeRequest(
offer.getId(), offer.getId(),
processModel.getMyNodeAddress(), processModel.getMyNodeAddress(),
@ -70,7 +67,7 @@ public class MakerSendInitTradeRequest extends TradeTask {
offer.getOfferPayload().getPaymentMethodId(), offer.getOfferPayload().getPaymentMethodId(),
UUID.randomUUID().toString(), UUID.randomUUID().toString(),
Version.getP2PMessageVersion(), Version.getP2PMessageVersion(),
sig, null,
makerRequest.getCurrentDate(), makerRequest.getCurrentDate(),
trade.getMaker().getNodeAddress(), trade.getMaker().getNodeAddress(),
trade.getTaker().getNodeAddress(), trade.getTaker().getNodeAddress(),

View File

@ -18,17 +18,21 @@
package bisq.core.trade.protocol.tasks; package bisq.core.trade.protocol.tasks;
import bisq.common.app.Version; import bisq.common.app.Version;
import bisq.common.crypto.Sig;
import bisq.common.taskrunner.TaskRunner; import bisq.common.taskrunner.TaskRunner;
import bisq.core.btc.model.XmrAddressEntry; import bisq.core.btc.model.XmrAddressEntry;
import bisq.core.trade.ArbitratorTrade; import bisq.core.trade.ArbitratorTrade;
import bisq.core.trade.MakerTrade;
import bisq.core.trade.Trade; import bisq.core.trade.Trade;
import bisq.core.trade.Trade.State; import bisq.core.trade.Trade.State;
import bisq.core.trade.messages.SignContractRequest; import bisq.core.trade.messages.SignContractRequest;
import bisq.network.p2p.SendDirectMessageListener; import bisq.network.p2p.SendDirectMessageListener;
import java.util.Date; import java.util.Date;
import java.util.UUID; import java.util.UUID;
import com.google.common.base.Charsets;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import monero.wallet.MoneroWallet;
import monero.wallet.model.MoneroTxWallet; import monero.wallet.model.MoneroTxWallet;
// TODO (woodser): separate classes for deposit tx creation and contract request, or combine into ProcessInitMultisigRequest // TODO (woodser): separate classes for deposit tx creation and contract request, or combine into ProcessInitMultisigRequest
@ -75,6 +79,12 @@ public class MaybeSendSignContractRequest extends TradeTask {
trade.getSelf().setPayoutAddressString(trade.getXmrWalletService().getAddressEntry(processModel.getOffer().getId(), XmrAddressEntry.Context.TRADE_PAYOUT).get().getAddressString()); // TODO (woodser): allow custom payout address? 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)); trade.getSelf().setPaymentAccountPayload(trade.getProcessModel().getPaymentAccountPayload(trade));
// maker signs deposit hash nonce to avoid challenge protocol
byte[] sig = null;
if (trade instanceof MakerTrade) {
sig = Sig.sign(processModel.getP2PService().getKeyRing().getSignatureKeyPair().getPrivate(), depositTx.getHash().getBytes(Charsets.UTF_8));
}
// create request for peer and arbitrator to sign contract // create request for peer and arbitrator to sign contract
SignContractRequest request = new SignContractRequest( SignContractRequest request = new SignContractRequest(
trade.getOffer().getId(), trade.getOffer().getId(),
@ -86,7 +96,8 @@ public class MaybeSendSignContractRequest extends TradeTask {
trade.getProcessModel().getAccountId(), trade.getProcessModel().getAccountId(),
trade.getSelf().getPaymentAccountPayload().getHash(), trade.getSelf().getPaymentAccountPayload().getHash(),
trade.getSelf().getPayoutAddressString(), trade.getSelf().getPayoutAddressString(),
depositTx.getHash()); depositTx.getHash(),
sig);
// send request to trading peer // send request to trading peer
processModel.getP2PService().sendEncryptedDirectMessage(trade.getTradingPeer().getNodeAddress(), trade.getTradingPeer().getPubKeyRing(), request, new SendDirectMessageListener() { processModel.getP2PService().sendEncryptedDirectMessage(trade.getTradingPeer().getNodeAddress(), trade.getTradingPeer().getPubKeyRing(), request, new SendDirectMessageListener() {

View File

@ -53,7 +53,7 @@ public class ProcessDepositsConfirmedMessage extends TradeTask {
// decrypt seller payment account payload if key given // decrypt seller payment account payload if key given
if (request.getSellerPaymentAccountKey() != null && trade.getTradingPeer().getPaymentAccountPayload() == null) { if (request.getSellerPaymentAccountKey() != null && trade.getTradingPeer().getPaymentAccountPayload() == null) {
log.info(trade.getClass().getSimpleName() + " decryping using seller payment account key: " + request.getSellerPaymentAccountKey()); log.info(trade.getClass().getSimpleName() + " decrypting using seller payment account key");
trade.decryptPeerPaymentAccountPayload(request.getSellerPaymentAccountKey()); trade.decryptPeerPaymentAccountPayload(request.getSellerPaymentAccountKey());
} }

View File

@ -95,7 +95,7 @@ public class ProcessInitTradeRequest extends TradeTask {
} }
} }
// handle maker trade // handle request as maker
else if (trade instanceof MakerTrade) { else if (trade instanceof MakerTrade) {
multisigParticipant = processModel.getTaker(); multisigParticipant = processModel.getTaker();
trade.getTaker().setNodeAddress(request.getSenderNodeAddress()); // arbitrator sends maker InitTradeRequest with taker's node address and pub key ring trade.getTaker().setNodeAddress(request.getSenderNodeAddress()); // arbitrator sends maker InitTradeRequest with taker's node address and pub key ring

View File

@ -20,6 +20,7 @@ package bisq.core.trade.protocol.tasks;
import bisq.core.account.sign.SignedWitness; import bisq.core.account.sign.SignedWitness;
import bisq.core.support.dispute.Dispute; import bisq.core.support.dispute.Dispute;
import bisq.core.trade.ArbitratorTrade; import bisq.core.trade.ArbitratorTrade;
import bisq.core.trade.BuyerTrade;
import bisq.core.trade.HavenoUtils; import bisq.core.trade.HavenoUtils;
import bisq.core.trade.Trade; import bisq.core.trade.Trade;
import bisq.core.trade.messages.PaymentReceivedMessage; import bisq.core.trade.messages.PaymentReceivedMessage;
@ -55,6 +56,7 @@ public class ProcessPaymentReceivedMessage extends TradeTask {
HavenoUtils.verifyPaymentReceivedMessage(trade, message); HavenoUtils.verifyPaymentReceivedMessage(trade, message);
trade.getSeller().setUpdatedMultisigHex(message.getUpdatedMultisigHex()); trade.getSeller().setUpdatedMultisigHex(message.getUpdatedMultisigHex());
trade.getBuyer().setUpdatedMultisigHex(message.getPaymentSentMessage().getUpdatedMultisigHex()); trade.getBuyer().setUpdatedMultisigHex(message.getPaymentSentMessage().getUpdatedMultisigHex());
trade.getBuyer().setAccountAgeWitness(message.getBuyerAccountAgeWitness());
// update to the latest peer address of our peer if message is correct // update to the latest peer address of our peer if message is correct
trade.getSeller().setNodeAddress(processModel.getTempTradingPeerNodeAddress()); trade.getSeller().setNodeAddress(processModel.getTempTradingPeerNodeAddress());
@ -71,8 +73,8 @@ public class ProcessPaymentReceivedMessage extends TradeTask {
// process payout tx unless already unlocked // process payout tx unless already unlocked
if (!trade.isPayoutUnlocked()) processPayoutTx(message); if (!trade.isPayoutUnlocked()) processPayoutTx(message);
SignedWitness signedWitness = message.getSignedWitness(); SignedWitness signedWitness = message.getBuyerSignedWitness();
if (signedWitness != null) { if (signedWitness != null && trade instanceof BuyerTrade) {
// We received the signedWitness from the seller and publish the data to the network. // We received the signedWitness from the seller and publish the data to the network.
// The signer has published it as well but we prefer to re-do it on our side as well to achieve higher // The signer has published it as well but we prefer to re-do it on our side as well to achieve higher
// resilience. // resilience.

View File

@ -48,6 +48,7 @@ public class ProcessPaymentSentMessage extends TradeTask {
trade.setPayoutTxHex(message.getPayoutTxHex()); trade.setPayoutTxHex(message.getPayoutTxHex());
trade.getBuyer().setUpdatedMultisigHex(message.getUpdatedMultisigHex()); trade.getBuyer().setUpdatedMultisigHex(message.getUpdatedMultisigHex());
trade.getBuyer().setPaymentSentMessage(message); trade.getBuyer().setPaymentSentMessage(message);
trade.getSeller().setAccountAgeWitness(message.getSellerAccountAgeWitness());
// if seller, decrypt buyer's payment account payload // if seller, decrypt buyer's payment account payload
if (trade.isSeller()) trade.decryptPeerPaymentAccountPayload(message.getPaymentAccountKey()); if (trade.isSeller()) trade.decryptPeerPaymentAccountPayload(message.getPaymentAccountKey());

View File

@ -37,9 +37,13 @@ import bisq.core.trade.protocol.TradingPeer;
import bisq.core.util.JsonUtil; import bisq.core.util.JsonUtil;
import bisq.network.p2p.NodeAddress; import bisq.network.p2p.NodeAddress;
import bisq.network.p2p.SendDirectMessageListener; import bisq.network.p2p.SendDirectMessageListener;
import java.util.Date; import java.util.Date;
import java.util.UUID; import java.util.UUID;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
import com.google.common.base.Charsets;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@Slf4j @Slf4j
@ -67,6 +71,12 @@ public class ProcessSignContractRequest extends TradeTask {
trader.setPaymentAccountPayloadHash(request.getPaymentAccountPayloadHash()); trader.setPaymentAccountPayloadHash(request.getPaymentAccountPayloadHash());
trader.setPayoutAddressString(request.getPayoutAddress()); trader.setPayoutAddressString(request.getPayoutAddress());
// maker sends witness signature of deposit tx hash
if (trader == trade.getMaker()) {
trader.setAccountAgeWitnessNonce(request.getDepositTxHash().getBytes(Charsets.UTF_8));
trader.setAccountAgeWitnessSignature(request.getAccountAgeWitnessSignatureOfDepositHash());
}
// sign contract only when both deposit txs hashes known // sign contract only when both deposit txs hashes known
// TODO (woodser): remove makerDepositTxId and takerDepositTxId from Trade // TODO (woodser): remove makerDepositTxId and takerDepositTxId from Trade
if (processModel.getMaker().getDepositTxHash() == null || processModel.getTaker().getDepositTxHash() == null) { if (processModel.getMaker().getDepositTxHash() == null || processModel.getTaker().getDepositTxHash() == null) {

View File

@ -18,6 +18,7 @@
package bisq.core.trade.protocol.tasks; package bisq.core.trade.protocol.tasks;
import bisq.core.account.sign.SignedWitness; import bisq.core.account.sign.SignedWitness;
import bisq.core.account.witness.AccountAgeWitnessService;
import bisq.core.trade.Trade; import bisq.core.trade.Trade;
import bisq.core.trade.messages.PaymentReceivedMessage; import bisq.core.trade.messages.PaymentReceivedMessage;
import bisq.core.trade.messages.TradeMailboxMessage; import bisq.core.trade.messages.TradeMailboxMessage;
@ -63,22 +64,23 @@ public abstract class SellerSendPaymentReceivedMessage extends SendMailboxMessag
checkNotNull(trade.getPayoutTxHex(), "Payout tx must not be null"); checkNotNull(trade.getPayoutTxHex(), "Payout tx must not be null");
if (message == null) { if (message == null) {
// TODO: sign witness // sign account witness
// AccountAgeWitnessService accountAgeWitnessService = processModel.getAccountAgeWitnessService(); AccountAgeWitnessService accountAgeWitnessService = processModel.getAccountAgeWitnessService();
// if (accountAgeWitnessService.isSignWitnessTrade(trade)) { if (accountAgeWitnessService.isSignWitnessTrade(trade)) {
// // Broadcast is done in accountAgeWitness domain. accountAgeWitnessService.traderSignAndPublishPeersAccountAgeWitness(trade).ifPresent(witness -> signedWitness = witness);
// accountAgeWitnessService.traderSignAndPublishPeersAccountAgeWitness(trade).ifPresent(witness -> signedWitness = witness); log.info("{} {} signed and published peers account age witness", trade.getClass().getSimpleName(), trade.getId());
// } }
// TODO: create with deterministic id like BuyerSendPaymentSentMessage // TODO: create with deterministic id like BuyerSendPaymentSentMessage
message = new PaymentReceivedMessage( message = new PaymentReceivedMessage(
tradeId, tradeId,
processModel.getMyNodeAddress(), processModel.getMyNodeAddress(),
signedWitness,
trade.isPayoutPublished() ? null : trade.getPayoutTxHex(), // unsigned trade.isPayoutPublished() ? null : trade.getPayoutTxHex(), // unsigned
trade.isPayoutPublished() ? trade.getPayoutTxHex() : null, // signed trade.isPayoutPublished() ? trade.getPayoutTxHex() : null, // signed
trade.getSelf().getUpdatedMultisigHex(), trade.getSelf().getUpdatedMultisigHex(),
trade.getState().ordinal() >= Trade.State.SELLER_SAW_ARRIVED_PAYMENT_RECEIVED_MSG.ordinal(), // informs to expect payout trade.getState().ordinal() >= Trade.State.SELLER_SAW_ARRIVED_PAYMENT_RECEIVED_MSG.ordinal(), // informs to expect payout
trade.getTradingPeer().getAccountAgeWitness(),
signedWitness,
trade.getBuyer().getPaymentSentMessage() trade.getBuyer().getPaymentSentMessage()
); );

View File

@ -21,6 +21,7 @@ import bisq.core.account.witness.AccountAgeWitnessService;
import bisq.core.locale.CurrencyUtil; import bisq.core.locale.CurrencyUtil;
import bisq.core.offer.Offer; import bisq.core.offer.Offer;
import bisq.core.payment.payload.PaymentAccountPayload; import bisq.core.payment.payload.PaymentAccountPayload;
import bisq.core.trade.ArbitratorTrade;
import bisq.core.trade.Trade; import bisq.core.trade.Trade;
import bisq.core.trade.protocol.TradingPeer; import bisq.core.trade.protocol.TradingPeer;
@ -46,14 +47,27 @@ public class VerifyPeersAccountAgeWitness extends TradeTask {
try { try {
runInterceptHook(); runInterceptHook();
// only verify fiat offer
Offer offer = checkNotNull(trade.getOffer()); Offer offer = checkNotNull(trade.getOffer());
if (CurrencyUtil.isCryptoCurrency(offer.getCurrencyCode())) { if (CurrencyUtil.isCryptoCurrency(offer.getCurrencyCode())) {
complete(); complete();
return; return;
} }
AccountAgeWitnessService accountAgeWitnessService = processModel.getAccountAgeWitnessService(); // skip if arbitrator
if (trade instanceof ArbitratorTrade) {
complete();
return;
}
// skip if payment account payload is null
TradingPeer tradingPeer = trade.getTradingPeer(); TradingPeer tradingPeer = trade.getTradingPeer();
if (tradingPeer.getPaymentAccountPayload() == null) {
complete();
return;
}
AccountAgeWitnessService accountAgeWitnessService = processModel.getAccountAgeWitnessService();
PaymentAccountPayload peersPaymentAccountPayload = checkNotNull(tradingPeer.getPaymentAccountPayload(), PaymentAccountPayload peersPaymentAccountPayload = checkNotNull(tradingPeer.getPaymentAccountPayload(),
"Peers peersPaymentAccountPayload must not be null"); "Peers peersPaymentAccountPayload must not be null");
PubKeyRing peersPubKeyRing = checkNotNull(tradingPeer.getPubKeyRing(), "peersPubKeyRing must not be null"); PubKeyRing peersPubKeyRing = checkNotNull(tradingPeer.getPubKeyRing(), "peersPubKeyRing must not be null");
@ -71,6 +85,8 @@ public class VerifyPeersAccountAgeWitness extends TradeTask {
signature, signature,
errorMsg::set); errorMsg::set);
if (isValid) { if (isValid) {
trade.getTradingPeer().setAccountAgeWitness(processModel.getAccountAgeWitnessService().findWitness(trade.getTradingPeer().getPaymentAccountPayload(), trade.getTradingPeer().getPubKeyRing()).orElse(null));
log.info("{} {} verified witness data of peer {}", trade.getClass().getSimpleName(), trade.getId(), tradingPeer.getNodeAddress());
complete(); complete();
} else { } else {
failed(errorMsg.get()); failed(errorMsg.get());

View File

@ -119,8 +119,10 @@ public class User implements PersistedDataHost {
userPayload.getAcceptedLanguageLocaleCodes().add(english); userPayload.getAcceptedLanguageLocaleCodes().add(english);
paymentAccountsAsObservable.addListener((SetChangeListener<PaymentAccount>) change -> { paymentAccountsAsObservable.addListener((SetChangeListener<PaymentAccount>) change -> {
synchronized (paymentAccountsAsObservable) {
userPayload.setPaymentAccounts(new HashSet<>(paymentAccountsAsObservable)); userPayload.setPaymentAccounts(new HashSet<>(paymentAccountsAsObservable));
requestPersistence(); requestPersistence();
}
}); });
currentPaymentAccountProperty.addListener((ov) -> { currentPaymentAccountProperty.addListener((ov) -> {
userPayload.setCurrentPaymentAccount(currentPaymentAccountProperty.get()); userPayload.setCurrentPaymentAccount(currentPaymentAccountProperty.get());
@ -212,14 +214,16 @@ public class User implements PersistedDataHost {
public void addPaymentAccount(PaymentAccount paymentAccount) { public void addPaymentAccount(PaymentAccount paymentAccount) {
paymentAccount.onAddToUser(); paymentAccount.onAddToUser();
synchronized (paymentAccountsAsObservable) {
boolean changed = paymentAccountsAsObservable.add(paymentAccount); boolean changed = paymentAccountsAsObservable.add(paymentAccount);
setCurrentPaymentAccount(paymentAccount); setCurrentPaymentAccount(paymentAccount);
if (changed) if (changed)
requestPersistence(); requestPersistence();
} }
}
public void addImportedPaymentAccounts(Collection<PaymentAccount> paymentAccounts) { public void addImportedPaymentAccounts(Collection<PaymentAccount> paymentAccounts) {
synchronized (paymentAccountsAsObservable) {
isPaymentAccountImport = true; isPaymentAccountImport = true;
boolean changed = paymentAccountsAsObservable.addAll(paymentAccounts); boolean changed = paymentAccountsAsObservable.addAll(paymentAccounts);
@ -229,12 +233,15 @@ public class User implements PersistedDataHost {
isPaymentAccountImport = false; isPaymentAccountImport = false;
} }
}
public void removePaymentAccount(PaymentAccount paymentAccount) { public void removePaymentAccount(PaymentAccount paymentAccount) {
synchronized (paymentAccountsAsObservable) {
boolean changed = paymentAccountsAsObservable.remove(paymentAccount); boolean changed = paymentAccountsAsObservable.remove(paymentAccount);
if (changed) if (changed)
requestPersistence(); requestPersistence();
} }
}
public boolean addAcceptedArbitrator(Arbitrator arbitrator) { public boolean addAcceptedArbitrator(Arbitrator arbitrator) {
List<Arbitrator> arbitrators = userPayload.getAcceptedArbitrators(); List<Arbitrator> arbitrators = userPayload.getAcceptedArbitrators();
@ -513,8 +520,10 @@ public class User implements PersistedDataHost {
} }
private boolean paymentAccountExists(PaymentAccount paymentAccount) { private boolean paymentAccountExists(PaymentAccount paymentAccount) {
synchronized (paymentAccountsAsObservable) {
return getPaymentAccountsAsObservable().stream().anyMatch(e -> e.equals(paymentAccount)); return getPaymentAccountsAsObservable().stream().anyMatch(e -> e.equals(paymentAccount));
} }
}
public Cookie getCookie() { public Cookie getCookie() {
return userPayload.getCookie(); return userPayload.getCookie();

View File

@ -523,7 +523,7 @@ public class PopOver extends PopupControl {
skinNode); skinNode);
fadeOut.setFromValue(skinNode.getOpacity()); fadeOut.setFromValue(skinNode.getOpacity());
fadeOut.setToValue(0); fadeOut.setToValue(0);
fadeOut.setOnFinished(evt -> super.hide()); fadeOut.setOnFinished(evt -> { if (super.isShowing()) super.hide(); });
fadeOut.play(); fadeOut.play();
} else { } else {
super.hide(); super.hide();

View File

@ -200,9 +200,7 @@ public class SignPaymentAccountsWindow extends Overlay<SignPaymentAccountsWindow
++rowIndex, Res.get("popup.accountSigning.confirmSelectedAccounts.headline")); ++rowIndex, Res.get("popup.accountSigning.confirmSelectedAccounts.headline"));
GridPane.setRowSpan(selectedPaymentAccountsTuple.third, 2); GridPane.setRowSpan(selectedPaymentAccountsTuple.third, 2);
selectedPaymentAccountsList = selectedPaymentAccountsTuple.second; selectedPaymentAccountsList = selectedPaymentAccountsTuple.second;
ObservableList<Dispute> disputesAsObservableList = useDevPrivilegeKeys ? ObservableList<Dispute> disputesAsObservableList = arbitrationManager.getDisputesAsObservableList();
mediationManager.getDisputesAsObservableList()
: arbitrationManager.getDisputesAsObservableList();
long safeDate = datePicker.getValue().atStartOfDay().toEpochSecond(ZoneOffset.UTC) * 1000; long safeDate = datePicker.getValue().atStartOfDay().toEpochSecond(ZoneOffset.UTC) * 1000;
List<TraderDataItem> traderDataItemList; List<TraderDataItem> traderDataItemList;
StringBuilder sb = new StringBuilder("Summary for ").append(appName).append("\n"); StringBuilder sb = new StringBuilder("Summary for ").append(appName).append("\n");

View File

@ -43,6 +43,7 @@ import bisq.network.p2p.NodeAddress;
import bisq.common.UserThread; import bisq.common.UserThread;
import bisq.common.util.Tuple3; import bisq.common.util.Tuple3;
import bisq.common.util.Utilities;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named; import javax.inject.Named;
@ -313,6 +314,11 @@ public class TradeDetailsWindow extends Overlay<TradeDetailsWindow> {
HBox.setHgrow(spacer, Priority.ALWAYS); HBox.setHgrow(spacer, Priority.ALWAYS);
hBox.getChildren().add(0, spacer); hBox.getChildren().add(0, spacer);
String buyerWitnessHash = trade.getBuyer().getAccountAgeWitness() == null ? "null" : Utilities.bytesAsHexString(trade.getBuyer().getAccountAgeWitness().getHash());
String buyerPubKeyRingHash = Utilities.bytesAsHexString(trade.getBuyer().getPubKeyRing().getSignaturePubKeyBytes());
String sellerWitnessHash = trade.getSeller().getAccountAgeWitness() == null ? "null" : Utilities.bytesAsHexString(trade.getSeller().getAccountAgeWitness().getHash());
String sellerPubKeyRingHash = Utilities.bytesAsHexString(trade.getSeller().getPubKeyRing().getSignaturePubKeyBytes());
if (contract != null) { if (contract != null) {
viewContractButton.setOnAction(e -> { viewContractButton.setOnAction(e -> {
TextArea textArea = new HavenoTextArea(); TextArea textArea = new HavenoTextArea();
@ -321,8 +327,10 @@ public class TradeDetailsWindow extends Overlay<TradeDetailsWindow> {
data += trade.getContractAsJson(); data += trade.getContractAsJson();
data += "\n\nOther detail data:"; data += "\n\nOther detail data:";
if (offer.isFiatOffer()) { if (offer.isFiatOffer()) {
data += "\n\nBuyersAccountAge: " + buyersAccountAge; data += "\n\nBuyers witness hash,pub key ring hash: " + buyerWitnessHash + "," + buyerPubKeyRingHash;
data += "\nSellersAccountAge: " + sellersAccountAge; data += "\nBuyers account age: " + buyersAccountAge;
data += "\nSellers witness hash,pub key ring hash: " + sellerWitnessHash + "," + sellerPubKeyRingHash;
data += "\nSellers account age: " + sellersAccountAge;
} }
// TODO (woodser): include maker and taker deposit tx hex in contract? // TODO (woodser): include maker and taker deposit tx hex in contract?

View File

@ -448,7 +448,7 @@ public class SellerStep3View extends TradeStepView {
} }
if (model.dataModel.isSignWitnessTrade()) { if (model.dataModel.isSignWitnessTrade()) {
message += Res.get("portfolio.pending.step3_seller.onPaymentReceived.signer"); message += "\n\n" + Res.get("portfolio.pending.step3_seller.onPaymentReceived.signer");
} }
} }
if (!DevEnv.isDevMode() && DontShowAgainLookup.showAgain(key)) { if (!DevEnv.isDevMode() && DontShowAgainLookup.showAgain(key)) {

View File

@ -34,6 +34,15 @@ Follow [instructions](https://github.com/haveno-dex/haveno-ts#run-tests) to run
9. Run the tests with `npm run test -- -t 'my test'` to run tests by name and `npm test` to run all tests together. Ensure all tests pass and there are no exception stacktraces in the terminals of Alice, Bob, or the arbitrator. 9. Run the tests with `npm run test -- -t 'my test'` to run tests by name and `npm test` to run all tests together. Ensure all tests pass and there are no exception stacktraces in the terminals of Alice, Bob, or the arbitrator.
10. Open pull requests to the haveno and haveno-ts projects for the backend and frontend implementations. 10. Open pull requests to the haveno and haveno-ts projects for the backend and frontend implementations.
## How to manually sign accounts as the arbitrator
1. Open legacy UI as the arbitrator.
2. Go to the 'Account' tab.
3. Open Signing tab: `ctrl+i`
a. Sign payment account: `ctrl+s`, select payment accounts to sign (sourced from disputes).
b. Sign account age witness: `ctrl+p` then enter <witness hash>,<pub key hash> (from past trade details) and click the "Import unsigned account age witness" button.
c. Sign unsigned witness pub keys: `ctrl+o`
## How to rebase and squash your commits ## How to rebase and squash your commits
When submitting a pull request for review, please first rebase and squash your commits. When submitting a pull request for review, please first rebase and squash your commits.

View File

@ -312,6 +312,7 @@ message SignContractRequest {
bytes payment_account_payload_hash = 7; bytes payment_account_payload_hash = 7;
string payout_address = 8;; string payout_address = 8;;
string deposit_tx_hash = 9; string deposit_tx_hash = 9;
bytes account_age_witness_signature_of_deposit_hash = 10;
} }
message SignContractResponse { message SignContractResponse {
@ -404,20 +405,22 @@ message PaymentSentMessage {
string payout_tx_hex = 6; string payout_tx_hex = 6;
string updated_multisig_hex = 7; string updated_multisig_hex = 7;
bytes payment_account_key = 8; bytes payment_account_key = 8;
bytes buyer_signature = 9; AccountAgeWitness seller_account_age_witness = 9;
bytes buyer_signature = 10;
} }
message PaymentReceivedMessage { message PaymentReceivedMessage {
string trade_id = 1; string trade_id = 1;
NodeAddress sender_node_address = 2; NodeAddress sender_node_address = 2;
string uid = 3; string uid = 3;
SignedWitness signed_witness = 4; // Added in v1.4.0 string unsigned_payout_tx_hex = 4;
string unsigned_payout_tx_hex = 5; string signed_payout_tx_hex = 5;
string signed_payout_tx_hex = 6; string updated_multisig_hex = 6;
string updated_multisig_hex = 7; bool defer_publish_payout = 7;
bool defer_publish_payout = 8; AccountAgeWitness buyer_account_age_witness = 8;
PaymentSentMessage payment_sent_message = 9; SignedWitness buyer_signed_witness = 9;
bytes seller_signature = 10; PaymentSentMessage payment_sent_message = 10;
bytes seller_signature = 11;
} }
message MediatedPayoutTxPublishedMessage { message MediatedPayoutTxPublishedMessage {
@ -1732,8 +1735,9 @@ message TradingPeer {
string change_output_address = 17; string change_output_address = 17;
bytes account_age_witness_nonce = 18; bytes account_age_witness_nonce = 18;
bytes account_age_witness_signature = 19; bytes account_age_witness_signature = 19;
int64 current_date = 20; AccountAgeWitness account_age_witness = 20;
bytes mediated_payout_tx_signature = 21; int64 current_date = 21;
bytes mediated_payout_tx_signature = 22;
string reserve_tx_hash = 1001; string reserve_tx_hash = 1001;
string reserve_tx_hex = 1002; string reserve_tx_hex = 1002;