mirror of
https://github.com/haveno-dex/haveno.git
synced 2025-12-15 07:52:58 -05:00
Add sym encryption
This commit is contained in:
parent
b531c3a0c2
commit
506ac37293
29 changed files with 189 additions and 84 deletions
|
|
@ -31,7 +31,7 @@ public class Arbitrator implements Serializable {
|
||||||
private String id;
|
private String id;
|
||||||
private String pubKeyAsHex;
|
private String pubKeyAsHex;
|
||||||
private byte[] pubKey;
|
private byte[] pubKey;
|
||||||
private String messagePubKeyAsHex;
|
private String p2pSigPubKeyAsHex;
|
||||||
private String name;
|
private String name;
|
||||||
private ID_TYPE idType;
|
private ID_TYPE idType;
|
||||||
private List<Locale> languages;
|
private List<Locale> languages;
|
||||||
|
|
@ -48,7 +48,7 @@ public class Arbitrator implements Serializable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Arbitrator(byte[] pubKey,
|
public Arbitrator(byte[] pubKey,
|
||||||
String messagePubKeyAsHex,
|
String p2pSigPubKeyAsHex,
|
||||||
String name,
|
String name,
|
||||||
ID_TYPE idType,
|
ID_TYPE idType,
|
||||||
List<Locale> languages,
|
List<Locale> languages,
|
||||||
|
|
@ -59,7 +59,7 @@ public class Arbitrator implements Serializable {
|
||||||
String webUrl,
|
String webUrl,
|
||||||
String description) {
|
String description) {
|
||||||
this.pubKey = pubKey;
|
this.pubKey = pubKey;
|
||||||
this.messagePubKeyAsHex = messagePubKeyAsHex;
|
this.p2pSigPubKeyAsHex = p2pSigPubKeyAsHex;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.idType = idType;
|
this.idType = idType;
|
||||||
this.languages = languages;
|
this.languages = languages;
|
||||||
|
|
@ -76,7 +76,7 @@ public class Arbitrator implements Serializable {
|
||||||
|
|
||||||
public void applyPersistedArbitrator(Arbitrator persistedArbitrator) {
|
public void applyPersistedArbitrator(Arbitrator persistedArbitrator) {
|
||||||
this.pubKeyAsHex = persistedArbitrator.getPubKeyAsHex();
|
this.pubKeyAsHex = persistedArbitrator.getPubKeyAsHex();
|
||||||
this.messagePubKeyAsHex = persistedArbitrator.getPubKeyAsHex();
|
this.p2pSigPubKeyAsHex = persistedArbitrator.getPubKeyAsHex();
|
||||||
this.name = persistedArbitrator.getName();
|
this.name = persistedArbitrator.getName();
|
||||||
this.idType = persistedArbitrator.getIdType();
|
this.idType = persistedArbitrator.getIdType();
|
||||||
this.languages = persistedArbitrator.getLanguages();
|
this.languages = persistedArbitrator.getLanguages();
|
||||||
|
|
@ -126,8 +126,8 @@ public class Arbitrator implements Serializable {
|
||||||
return pubKey;
|
return pubKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getMessagePubKeyAsHex() {
|
public String getP2pSigPubKeyAsHex() {
|
||||||
return messagePubKeyAsHex;
|
return p2pSigPubKeyAsHex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,5 +21,5 @@ package io.bitsquare.common.handlers;
|
||||||
* For reporting a description message and throwable
|
* For reporting a description message and throwable
|
||||||
*/
|
*/
|
||||||
public interface FaultHandler {
|
public interface FaultHandler {
|
||||||
void handleFault(String message, Throwable throwable);
|
void handleFault(String errorMessage, Throwable throwable);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,9 @@ package io.bitsquare.crypto;
|
||||||
|
|
||||||
import io.bitsquare.util.Utilities;
|
import io.bitsquare.util.Utilities;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
import java.security.Key;
|
||||||
import java.security.KeyPair;
|
import java.security.KeyPair;
|
||||||
import java.security.KeyPairGenerator;
|
import java.security.KeyPairGenerator;
|
||||||
import java.security.PrivateKey;
|
import java.security.PrivateKey;
|
||||||
|
|
@ -30,8 +33,11 @@ import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.crypto.Cipher;
|
import javax.crypto.Cipher;
|
||||||
|
import javax.crypto.KeyGenerator;
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
|
||||||
public class EncryptionService {
|
public class EncryptionService<T> {
|
||||||
private static final Logger log = LoggerFactory.getLogger(EncryptionService.class);
|
private static final Logger log = LoggerFactory.getLogger(EncryptionService.class);
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
|
|
@ -51,38 +57,73 @@ public class EncryptionService {
|
||||||
return keyPair;
|
return keyPair;
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] encryptObject(PublicKey publicKey, Object object) {
|
public Tuple encryptObject(PublicKey publicKey, Object object) {
|
||||||
return encrypt(publicKey, Utilities.objectToBytArray(object));
|
return encrypt(publicKey, Utilities.objectToBytArray(object));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Object decryptObject(PrivateKey privateKey, byte[] cipherMessage) {
|
public T decryptToObject(PrivateKey privateKey, Tuple tuple) {
|
||||||
return Utilities.byteArrayToObject(decrypt(privateKey, cipherMessage));
|
return (T) Utilities.byteArrayToObject(decrypt(privateKey, tuple));
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] encrypt(PublicKey publicKey, byte[] data) {
|
public Tuple encrypt(PublicKey publicKey, byte[] payload) {
|
||||||
byte[] cipherData = null;
|
byte[] encryptedPayload = null;
|
||||||
|
byte[] encryptedKey = null;
|
||||||
try {
|
try {
|
||||||
Cipher cipher = Cipher.getInstance("RSA");
|
// Create symmetric key and
|
||||||
|
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
|
||||||
|
keyGenerator.init(256);
|
||||||
|
SecretKey secretKey = keyGenerator.generateKey();
|
||||||
|
|
||||||
|
// Encrypt secretKey with asymmetric key
|
||||||
|
Cipher cipher = Cipher.getInstance("RSA/NONE/PKCS1Padding");
|
||||||
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
|
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
|
||||||
cipherData = cipher.doFinal(data);
|
encryptedKey = cipher.doFinal(secretKey.getEncoded());
|
||||||
|
|
||||||
|
// Encrypt payload with symmetric key
|
||||||
|
SecretKeySpec keySpec = new SecretKeySpec(secretKey.getEncoded(), "AES");
|
||||||
|
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||||
|
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
|
||||||
|
encryptedPayload = cipher.doFinal(payload);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
log.error("Exception at encrypt " + e.getMessage());
|
log.error("Exception at encrypt " + e.getMessage());
|
||||||
}
|
}
|
||||||
return cipherData;
|
return new Tuple(encryptedKey, encryptedPayload);
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] decrypt(PrivateKey privateKey, byte[] cipherText) {
|
public byte[] decrypt(PrivateKey privateKey, Tuple tuple) {
|
||||||
byte[] data = null;
|
byte[] encryptedPayload = tuple.encryptedPayload;
|
||||||
|
byte[] encryptedKey = tuple.encryptedKey;
|
||||||
|
|
||||||
|
byte[] payload = null;
|
||||||
try {
|
try {
|
||||||
Cipher cipher = Cipher.getInstance("RSA");
|
// Decrypt secretKey key with asymmetric key
|
||||||
|
Cipher cipher = Cipher.getInstance("RSA/NONE/PKCS1Padding");
|
||||||
cipher.init(Cipher.DECRYPT_MODE, privateKey);
|
cipher.init(Cipher.DECRYPT_MODE, privateKey);
|
||||||
data = cipher.doFinal(cipherText);
|
byte[] secretKey = cipher.doFinal(encryptedKey);
|
||||||
|
|
||||||
|
// Decrypt payload with symmetric key
|
||||||
|
Key key = new SecretKeySpec(secretKey, "AES");
|
||||||
|
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||||
|
cipher.init(Cipher.DECRYPT_MODE, key);
|
||||||
|
payload = cipher.doFinal(encryptedPayload);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
log.error("Exception at decrypt " + e.getMessage());
|
log.error("Exception at decrypt " + e.getMessage());
|
||||||
}
|
}
|
||||||
return data;
|
return payload;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class Tuple implements Serializable {
|
||||||
|
private static final long serialVersionUID = -8709538217388076762L;
|
||||||
|
|
||||||
|
public final byte[] encryptedKey;
|
||||||
|
public final byte[] encryptedPayload;
|
||||||
|
|
||||||
|
public Tuple(byte[] encryptedKey, byte[] encryptedPayload) {
|
||||||
|
this.encryptedKey = encryptedKey;
|
||||||
|
this.encryptedPayload = encryptedPayload;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,10 +29,10 @@ import io.bitsquare.fiat.FiatAccountType;
|
||||||
import io.bitsquare.gui.util.BSFormatter;
|
import io.bitsquare.gui.util.BSFormatter;
|
||||||
import io.bitsquare.locale.CountryUtil;
|
import io.bitsquare.locale.CountryUtil;
|
||||||
import io.bitsquare.locale.LanguageUtil;
|
import io.bitsquare.locale.LanguageUtil;
|
||||||
|
import io.bitsquare.p2p.BaseP2PService;
|
||||||
import io.bitsquare.p2p.BootstrapState;
|
import io.bitsquare.p2p.BootstrapState;
|
||||||
import io.bitsquare.p2p.ClientNode;
|
import io.bitsquare.p2p.ClientNode;
|
||||||
import io.bitsquare.p2p.MessageService;
|
import io.bitsquare.p2p.MessageService;
|
||||||
import io.bitsquare.p2p.BaseP2PService;
|
|
||||||
import io.bitsquare.persistence.Persistence;
|
import io.bitsquare.persistence.Persistence;
|
||||||
import io.bitsquare.trade.Trade;
|
import io.bitsquare.trade.Trade;
|
||||||
import io.bitsquare.trade.TradeManager;
|
import io.bitsquare.trade.TradeManager;
|
||||||
|
|
@ -356,7 +356,7 @@ class MainViewModel implements ViewModel {
|
||||||
private void addMockArbitrator() {
|
private void addMockArbitrator() {
|
||||||
if (accountSettings.getAcceptedArbitrators().isEmpty() && user.getP2pSigKeyPair() != null) {
|
if (accountSettings.getAcceptedArbitrators().isEmpty() && user.getP2pSigKeyPair() != null) {
|
||||||
byte[] pubKey = new ECKey().getPubKey();
|
byte[] pubKey = new ECKey().getPubKey();
|
||||||
String messagePubKeyAsHex = DSAKeyUtil.getHexStringFromPublicKey(user.getMessagePubKey());
|
String p2pSigPubKeyAsHex = DSAKeyUtil.getHexStringFromPublicKey(user.getP2PSigPubKey());
|
||||||
List<Locale> languages = new ArrayList<>();
|
List<Locale> languages = new ArrayList<>();
|
||||||
languages.add(LanguageUtil.getDefaultLanguageLocale());
|
languages.add(LanguageUtil.getDefaultLanguageLocale());
|
||||||
List<Arbitrator.METHOD> arbitrationMethods = new ArrayList<>();
|
List<Arbitrator.METHOD> arbitrationMethods = new ArrayList<>();
|
||||||
|
|
@ -366,7 +366,7 @@ class MainViewModel implements ViewModel {
|
||||||
idVerifications.add(Arbitrator.ID_VERIFICATION.GOV_ID);
|
idVerifications.add(Arbitrator.ID_VERIFICATION.GOV_ID);
|
||||||
|
|
||||||
Arbitrator arbitrator = new Arbitrator(pubKey,
|
Arbitrator arbitrator = new Arbitrator(pubKey,
|
||||||
messagePubKeyAsHex,
|
p2pSigPubKeyAsHex,
|
||||||
"Manfred Karrer",
|
"Manfred Karrer",
|
||||||
Arbitrator.ID_TYPE.REAL_LIFE_ID,
|
Arbitrator.ID_TYPE.REAL_LIFE_ID,
|
||||||
languages,
|
languages,
|
||||||
|
|
|
||||||
|
|
@ -370,14 +370,14 @@ public class ArbitratorRegistrationView extends ActivatableView<AnchorPane, Void
|
||||||
|
|
||||||
private Arbitrator getEditedArbitrator() {
|
private Arbitrator getEditedArbitrator() {
|
||||||
byte[] pubKey = walletService.getArbitratorDepositAddressEntry().getPubKey();
|
byte[] pubKey = walletService.getArbitratorDepositAddressEntry().getPubKey();
|
||||||
String messagePubKeyAsHex = DSAKeyUtil.getHexStringFromPublicKey(user.getMessagePubKey());
|
String p2pSigPubKeyAsHex = DSAKeyUtil.getHexStringFromPublicKey(user.getP2PSigPubKey());
|
||||||
String name = nameTextField.getText();
|
String name = nameTextField.getText();
|
||||||
Coin fee = formatter.parseToCoin(arbitrationFeeTextField.getText());
|
Coin fee = formatter.parseToCoin(arbitrationFeeTextField.getText());
|
||||||
String webUrl = webPageTextField.getText();
|
String webUrl = webPageTextField.getText();
|
||||||
String description = descriptionTextArea.getText();
|
String description = descriptionTextArea.getText();
|
||||||
|
|
||||||
return new Arbitrator(pubKey,
|
return new Arbitrator(pubKey,
|
||||||
messagePubKeyAsHex,
|
p2pSigPubKeyAsHex,
|
||||||
name,
|
name,
|
||||||
idType,
|
idType,
|
||||||
languageList,
|
languageList,
|
||||||
|
|
|
||||||
|
|
@ -124,7 +124,7 @@ class IrcAccountDataModel implements Activatable, DataModel {
|
||||||
private void addMockArbitrator() {
|
private void addMockArbitrator() {
|
||||||
if (accountSettings.getAcceptedArbitrators().isEmpty() && user.getP2pSigKeyPair() != null) {
|
if (accountSettings.getAcceptedArbitrators().isEmpty() && user.getP2pSigKeyPair() != null) {
|
||||||
byte[] pubKey = new ECKey().getPubKey();
|
byte[] pubKey = new ECKey().getPubKey();
|
||||||
String messagePubKeyAsHex = DSAKeyUtil.getHexStringFromPublicKey(user.getMessagePubKey());
|
String p2pSigPubKeyAsHex = DSAKeyUtil.getHexStringFromPublicKey(user.getP2PSigPubKey());
|
||||||
List<Locale> languages = new ArrayList<>();
|
List<Locale> languages = new ArrayList<>();
|
||||||
languages.add(LanguageUtil.getDefaultLanguageLocale());
|
languages.add(LanguageUtil.getDefaultLanguageLocale());
|
||||||
List<Arbitrator.METHOD> arbitrationMethods = new ArrayList<>();
|
List<Arbitrator.METHOD> arbitrationMethods = new ArrayList<>();
|
||||||
|
|
@ -134,7 +134,7 @@ class IrcAccountDataModel implements Activatable, DataModel {
|
||||||
idVerifications.add(Arbitrator.ID_VERIFICATION.GOV_ID);
|
idVerifications.add(Arbitrator.ID_VERIFICATION.GOV_ID);
|
||||||
|
|
||||||
Arbitrator arbitrator = new Arbitrator(pubKey,
|
Arbitrator arbitrator = new Arbitrator(pubKey,
|
||||||
messagePubKeyAsHex,
|
p2pSigPubKeyAsHex,
|
||||||
"Manfred Karrer",
|
"Manfred Karrer",
|
||||||
Arbitrator.ID_TYPE.REAL_LIFE_ID,
|
Arbitrator.ID_TYPE.REAL_LIFE_ID,
|
||||||
languages,
|
languages,
|
||||||
|
|
|
||||||
|
|
@ -154,7 +154,7 @@ class RestrictionsDataModel implements Activatable, DataModel {
|
||||||
private void addMockArbitrator() {
|
private void addMockArbitrator() {
|
||||||
if (accountSettings.getAcceptedArbitrators().isEmpty() && user.getP2pSigKeyPair() != null) {
|
if (accountSettings.getAcceptedArbitrators().isEmpty() && user.getP2pSigKeyPair() != null) {
|
||||||
byte[] pubKey = new ECKey().getPubKey();
|
byte[] pubKey = new ECKey().getPubKey();
|
||||||
String messagePubKeyAsHex = DSAKeyUtil.getHexStringFromPublicKey(user.getMessagePubKey());
|
String messagePubKeyAsHex = DSAKeyUtil.getHexStringFromPublicKey(user.getP2PSigPubKey());
|
||||||
List<Locale> languages = new ArrayList<>();
|
List<Locale> languages = new ArrayList<>();
|
||||||
languages.add(LanguageUtil.getDefaultLanguageLocale());
|
languages.add(LanguageUtil.getDefaultLanguageLocale());
|
||||||
List<Arbitrator.METHOD> arbitrationMethods = new ArrayList<>();
|
List<Arbitrator.METHOD> arbitrationMethods = new ArrayList<>();
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ class ClosedTradesDataModel implements Activatable, DataModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Direction getDirection(Offer offer) {
|
public Direction getDirection(Offer offer) {
|
||||||
return offer.getMessagePublicKey().equals(user.getMessagePubKey()) ?
|
return offer.getMessagePublicKey().equals(user.getP2PSigPubKey()) ?
|
||||||
offer.getDirection() : offer.getMirroredDirection();
|
offer.getDirection() : offer.getMirroredDirection();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -86,7 +86,7 @@ class OffersDataModel implements Activatable, DataModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Direction getDirection(Offer offer) {
|
public Direction getDirection(Offer offer) {
|
||||||
return offer.getMessagePublicKey().equals(user.getMessagePubKey()) ?
|
return offer.getMessagePublicKey().equals(user.getP2PSigPubKey()) ?
|
||||||
offer.getDirection() : offer.getMirroredDirection();
|
offer.getDirection() : offer.getMirroredDirection();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -146,7 +146,7 @@ class PendingTradesDataModel implements Activatable, DataModel {
|
||||||
selectedItem = item;
|
selectedItem = item;
|
||||||
|
|
||||||
if (selectedItem != null) {
|
if (selectedItem != null) {
|
||||||
isOfferer = getTrade().getOffer().getMessagePublicKey().equals(user.getMessagePubKey());
|
isOfferer = getTrade().getOffer().getMessagePublicKey().equals(user.getP2PSigPubKey());
|
||||||
|
|
||||||
Trade trade = getTrade();
|
Trade trade = getTrade();
|
||||||
trade.stateProperty().addListener(tradeStateChangeListener);
|
trade.stateProperty().addListener(tradeStateChangeListener);
|
||||||
|
|
@ -269,7 +269,7 @@ class PendingTradesDataModel implements Activatable, DataModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Direction getDirection(Offer offer) {
|
public Direction getDirection(Offer offer) {
|
||||||
return offer.getMessagePublicKey().equals(user.getMessagePubKey()) ?
|
return offer.getMessagePublicKey().equals(user.getP2PSigPubKey()) ?
|
||||||
offer.getDirection() : offer.getMirroredDirection();
|
offer.getDirection() : offer.getMirroredDirection();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -213,7 +213,7 @@ class OfferBookDataModel implements Activatable, DataModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isMyOffer(Offer offer) {
|
boolean isMyOffer(Offer offer) {
|
||||||
return offer.getMessagePublicKey() != null && offer.getMessagePublicKey().equals(user.getMessagePubKey());
|
return offer.getMessagePublicKey() != null && offer.getMessagePublicKey().equals(user.getP2PSigPubKey());
|
||||||
}
|
}
|
||||||
|
|
||||||
Coin getAmountAsCoin() {
|
Coin getAmountAsCoin() {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
* This file is part of Bitsquare.
|
||||||
|
*
|
||||||
|
* Bitsquare is free software: you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or (at
|
||||||
|
* your option) any later version.
|
||||||
|
*
|
||||||
|
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||||
|
* License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.bitsquare.p2p;
|
||||||
|
|
||||||
|
import io.bitsquare.crypto.EncryptionService;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/*
|
||||||
|
Stores a message in encrypted form, so it never leaves the client in plain text.
|
||||||
|
*/
|
||||||
|
public class EncryptedMailboxMessage implements MailboxMessage, Serializable {
|
||||||
|
private static final long serialVersionUID = -3111178895546299769L;
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(EncryptedMailboxMessage.class);
|
||||||
|
|
||||||
|
private EncryptionService.Tuple tuple;
|
||||||
|
|
||||||
|
public EncryptedMailboxMessage(EncryptionService.Tuple tuple) {
|
||||||
|
this.tuple = tuple;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EncryptionService.Tuple getTuple() {
|
||||||
|
return tuple;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -19,23 +19,5 @@ package io.bitsquare.p2p;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
public interface MailboxMessage extends Message, Serializable {
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
/*
|
|
||||||
Stores a message in encrypted form, so it never leaves the client in plain text.
|
|
||||||
*/
|
|
||||||
public class MailboxMessage implements Message, Serializable {
|
|
||||||
private static final long serialVersionUID = -3111178895546299769L;
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(MailboxMessage.class);
|
|
||||||
|
|
||||||
private byte[] cipherMessage;
|
|
||||||
|
|
||||||
public MailboxMessage(byte[] cipherMessage) {
|
|
||||||
this.cipherMessage = cipherMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getCipherMessage() {
|
|
||||||
return cipherMessage;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,9 +24,9 @@ import io.bitsquare.p2p.tomp2p.TomP2PMailboxService;
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
|
|
||||||
public interface MailboxService {
|
public interface MailboxService {
|
||||||
void addMessage(PublicKey publicKey, MailboxMessage message, ResultHandler resultHandler, FaultHandler faultHandler);
|
void addMessage(PublicKey publicKey, EncryptedMailboxMessage message, ResultHandler resultHandler, FaultHandler faultHandler);
|
||||||
|
|
||||||
void removeMessage(PublicKey publicKey, MailboxMessage message, ResultHandler resultHandler, FaultHandler faultHandler);
|
void removeMessage(PublicKey publicKey, EncryptedMailboxMessage message, ResultHandler resultHandler, FaultHandler faultHandler);
|
||||||
|
|
||||||
void getMessages(PublicKey publicKey, TomP2PMailboxService.MailboxMessagesResultHandler resultHandler);
|
void getMessages(PublicKey publicKey, TomP2PMailboxService.MailboxMessagesResultHandler resultHandler);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ package io.bitsquare.p2p.tomp2p;
|
||||||
import io.bitsquare.common.handlers.FaultHandler;
|
import io.bitsquare.common.handlers.FaultHandler;
|
||||||
import io.bitsquare.common.handlers.ResultHandler;
|
import io.bitsquare.common.handlers.ResultHandler;
|
||||||
import io.bitsquare.offer.OfferBookService;
|
import io.bitsquare.offer.OfferBookService;
|
||||||
import io.bitsquare.p2p.MailboxMessage;
|
import io.bitsquare.p2p.EncryptedMailboxMessage;
|
||||||
import io.bitsquare.p2p.MailboxService;
|
import io.bitsquare.p2p.MailboxService;
|
||||||
import io.bitsquare.user.User;
|
import io.bitsquare.user.User;
|
||||||
|
|
||||||
|
|
@ -69,7 +69,7 @@ public class TomP2PMailboxService extends TomP2PDHTService implements MailboxSer
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addMessage(PublicKey publicKey, MailboxMessage message, ResultHandler resultHandler, FaultHandler faultHandler) {
|
public void addMessage(PublicKey publicKey, EncryptedMailboxMessage message, ResultHandler resultHandler, FaultHandler faultHandler) {
|
||||||
try {
|
try {
|
||||||
final Data data = new Data(message);
|
final Data data = new Data(message);
|
||||||
data.ttlSeconds(TTL);
|
data.ttlSeconds(TTL);
|
||||||
|
|
@ -101,7 +101,7 @@ public class TomP2PMailboxService extends TomP2PDHTService implements MailboxSer
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removeMessage(PublicKey publicKey, MailboxMessage message, ResultHandler resultHandler, FaultHandler faultHandler) {
|
public void removeMessage(PublicKey publicKey, EncryptedMailboxMessage message, ResultHandler resultHandler, FaultHandler faultHandler) {
|
||||||
try {
|
try {
|
||||||
final Data data = new Data(message);
|
final Data data = new Data(message);
|
||||||
log.trace("Remove message from DHT requested. Removed data: [locationKey: " + getLocationKey(publicKey) +
|
log.trace("Remove message from DHT requested. Removed data: [locationKey: " + getLocationKey(publicKey) +
|
||||||
|
|
@ -141,13 +141,13 @@ public class TomP2PMailboxService extends TomP2PDHTService implements MailboxSer
|
||||||
public void operationComplete(BaseFuture future) throws Exception {
|
public void operationComplete(BaseFuture future) throws Exception {
|
||||||
if (future.isSuccess()) {
|
if (future.isSuccess()) {
|
||||||
final Map<Number640, Data> dataMap = futureGet.dataMap();
|
final Map<Number640, Data> dataMap = futureGet.dataMap();
|
||||||
List<MailboxMessage> messages = new ArrayList<>();
|
List<EncryptedMailboxMessage> messages = new ArrayList<>();
|
||||||
if (dataMap != null) {
|
if (dataMap != null) {
|
||||||
for (Data messageData : dataMap.values()) {
|
for (Data messageData : dataMap.values()) {
|
||||||
try {
|
try {
|
||||||
Object messageDataObject = messageData.object();
|
Object messageDataObject = messageData.object();
|
||||||
if (messageDataObject instanceof MailboxMessage) {
|
if (messageDataObject instanceof EncryptedMailboxMessage) {
|
||||||
messages.add((MailboxMessage) messageDataObject);
|
messages.add((EncryptedMailboxMessage) messageDataObject);
|
||||||
}
|
}
|
||||||
} catch (ClassNotFoundException | IOException e) {
|
} catch (ClassNotFoundException | IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
|
@ -183,6 +183,6 @@ public class TomP2PMailboxService extends TomP2PDHTService implements MailboxSer
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface MailboxMessagesResultHandler {
|
public interface MailboxMessagesResultHandler {
|
||||||
void handleResult(List<MailboxMessage> messages);
|
void handleResult(List<EncryptedMailboxMessage> messages);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -17,11 +17,16 @@
|
||||||
|
|
||||||
package io.bitsquare.p2p.tomp2p;
|
package io.bitsquare.p2p.tomp2p;
|
||||||
|
|
||||||
|
import io.bitsquare.crypto.EncryptionService;
|
||||||
|
import io.bitsquare.p2p.EncryptedMailboxMessage;
|
||||||
|
import io.bitsquare.p2p.MailboxMessage;
|
||||||
|
import io.bitsquare.p2p.MailboxService;
|
||||||
import io.bitsquare.p2p.Message;
|
import io.bitsquare.p2p.Message;
|
||||||
import io.bitsquare.p2p.MessageHandler;
|
import io.bitsquare.p2p.MessageHandler;
|
||||||
import io.bitsquare.p2p.MessageService;
|
import io.bitsquare.p2p.MessageService;
|
||||||
import io.bitsquare.p2p.Peer;
|
import io.bitsquare.p2p.Peer;
|
||||||
import io.bitsquare.p2p.listener.SendMessageListener;
|
import io.bitsquare.p2p.listener.SendMessageListener;
|
||||||
|
import io.bitsquare.user.User;
|
||||||
|
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
|
||||||
|
|
@ -38,6 +43,9 @@ public class TomP2PMessageService extends TomP2PService implements MessageServic
|
||||||
private static final Logger log = LoggerFactory.getLogger(TomP2PMessageService.class);
|
private static final Logger log = LoggerFactory.getLogger(TomP2PMessageService.class);
|
||||||
|
|
||||||
private final CopyOnWriteArrayList<MessageHandler> messageHandlers = new CopyOnWriteArrayList<>();
|
private final CopyOnWriteArrayList<MessageHandler> messageHandlers = new CopyOnWriteArrayList<>();
|
||||||
|
private MailboxService mailboxService;
|
||||||
|
private User user;
|
||||||
|
private EncryptionService<MailboxMessage> encryptionService;
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
@ -45,8 +53,11 @@ public class TomP2PMessageService extends TomP2PService implements MessageServic
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public TomP2PMessageService(TomP2PNode tomP2PNode) {
|
public TomP2PMessageService(TomP2PNode tomP2PNode, MailboxService mailboxService, User user, EncryptionService encryptionService) {
|
||||||
super(tomP2PNode);
|
super(tomP2PNode);
|
||||||
|
this.mailboxService = mailboxService;
|
||||||
|
this.user = user;
|
||||||
|
this.encryptionService = encryptionService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -74,20 +85,46 @@ public class TomP2PMessageService extends TomP2PService implements MessageServic
|
||||||
log.debug("sendMessage completed");
|
log.debug("sendMessage completed");
|
||||||
executor.execute(listener::handleResult);
|
executor.execute(listener::handleResult);
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
if (message instanceof MailboxMessage) {
|
||||||
|
sendMailboxMessage((MailboxMessage) message, listener);
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
log.error("sendMessage failed with reason " + futureDirect.failedReason());
|
log.error("sendMessage failed with reason " + futureDirect.failedReason());
|
||||||
executor.execute(listener::handleFault);
|
executor.execute(listener::handleFault);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void exceptionCaught(Throwable t) throws Exception {
|
public void exceptionCaught(Throwable t) throws Exception {
|
||||||
log.error("Exception at sendMessage " + t.toString());
|
if (message instanceof MailboxMessage) {
|
||||||
|
sendMailboxMessage((MailboxMessage) message, listener);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
log.error("sendMessage failed with exception " + t.getMessage());
|
||||||
executor.execute(listener::handleFault);
|
executor.execute(listener::handleFault);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void sendMailboxMessage(MailboxMessage message, SendMessageListener listener) {
|
||||||
|
EncryptionService.Tuple tuple = encryptionService.encryptObject(user.getP2pEncryptKeyPair().getPublic(), message);
|
||||||
|
|
||||||
|
EncryptedMailboxMessage encrypted = new EncryptedMailboxMessage(tuple);
|
||||||
|
mailboxService.addMessage(user.getP2PSigPubKey(), encrypted,
|
||||||
|
() -> {
|
||||||
|
log.debug("Message successfully added to peers mailbox.");
|
||||||
|
executor.execute(listener::handleResult);
|
||||||
|
},
|
||||||
|
(errorMessage, throwable) -> {
|
||||||
|
log.error("Message failed to add to peers mailbox.");
|
||||||
|
executor.execute(listener::handleFault);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addMessageHandler(MessageHandler listener) {
|
public void addMessageHandler(MessageHandler listener) {
|
||||||
if (!messageHandlers.add(listener))
|
if (!messageHandlers.add(listener))
|
||||||
|
|
|
||||||
|
|
@ -164,7 +164,7 @@ public class TradeManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isMyOffer(Offer offer) {
|
public boolean isMyOffer(Offer offer) {
|
||||||
return offer.getMessagePublicKey().equals(user.getMessagePubKey());
|
return offer.getMessagePublicKey().equals(user.getP2PSigPubKey());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -206,7 +206,7 @@ public class TradeManager {
|
||||||
|
|
||||||
FiatAccount currentFiatAccount = user.getCurrentBankAccount().get();
|
FiatAccount currentFiatAccount = user.getCurrentBankAccount().get();
|
||||||
Offer offer = new Offer(id,
|
Offer offer = new Offer(id,
|
||||||
user.getMessagePubKey(),
|
user.getP2PSigPubKey(),
|
||||||
direction,
|
direction,
|
||||||
price.getValue(),
|
price.getValue(),
|
||||||
amount,
|
amount,
|
||||||
|
|
|
||||||
|
|
@ -17,11 +17,13 @@
|
||||||
|
|
||||||
package io.bitsquare.trade.protocol.trade.messages;
|
package io.bitsquare.trade.protocol.trade.messages;
|
||||||
|
|
||||||
|
import io.bitsquare.p2p.MailboxMessage;
|
||||||
|
|
||||||
import org.bitcoinj.core.Coin;
|
import org.bitcoinj.core.Coin;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
||||||
public class BankTransferStartedMessage extends TradeMessage implements Serializable {
|
public class BankTransferStartedMessage extends TradeMessage implements MailboxMessage, Serializable {
|
||||||
private static final long serialVersionUID = -3479634129543632523L;
|
private static final long serialVersionUID = -3479634129543632523L;
|
||||||
|
|
||||||
public final byte[] offererSignature;
|
public final byte[] offererSignature;
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ public class BuyerAsOffererModel extends SharedTradeModel implements Serializabl
|
||||||
private static final Logger log = LoggerFactory.getLogger(BuyerAsOffererModel.class);
|
private static final Logger log = LoggerFactory.getLogger(BuyerAsOffererModel.class);
|
||||||
|
|
||||||
transient public final Trade trade;
|
transient public final Trade trade;
|
||||||
public final MailboxService mailboxService;
|
transient public final MailboxService mailboxService;
|
||||||
public final TakerModel taker;
|
public final TakerModel taker;
|
||||||
public final OffererModel offerer;
|
public final OffererModel offerer;
|
||||||
|
|
||||||
|
|
@ -82,7 +82,7 @@ public class BuyerAsOffererModel extends SharedTradeModel implements Serializabl
|
||||||
offerer.addressEntry = walletService.getAddressEntry(id);
|
offerer.addressEntry = walletService.getAddressEntry(id);
|
||||||
offerer.fiatAccount = user.getBankAccount(offer.getBankAccountId());
|
offerer.fiatAccount = user.getBankAccount(offer.getBankAccountId());
|
||||||
offerer.accountId = user.getAccountId();
|
offerer.accountId = user.getAccountId();
|
||||||
offerer.messagePubKey = user.getMessagePubKey();
|
offerer.p2pSigPubKey = user.getP2PSigPubKey();
|
||||||
offerer.pubKey = offerer.addressEntry.getPubKey();
|
offerer.pubKey = offerer.addressEntry.getPubKey();
|
||||||
log.debug("BuyerAsOffererModel addressEntry " + offerer.addressEntry);
|
log.debug("BuyerAsOffererModel addressEntry " + offerer.addressEntry);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ public class OffererModel implements Serializable {
|
||||||
// Declared transient as they will be provided in any case at construction time
|
// Declared transient as they will be provided in any case at construction time
|
||||||
transient public FiatAccount fiatAccount;
|
transient public FiatAccount fiatAccount;
|
||||||
transient public String accountId;
|
transient public String accountId;
|
||||||
transient public PublicKey messagePubKey;
|
transient public PublicKey p2pSigPubKey;
|
||||||
transient public byte[] registrationPubKey;
|
transient public byte[] registrationPubKey;
|
||||||
transient public DeterministicKey registrationKeyPair;
|
transient public DeterministicKey registrationKeyPair;
|
||||||
transient public AddressEntry addressEntry;
|
transient public AddressEntry addressEntry;
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ public class TakerModel implements Serializable {
|
||||||
public Peer peer;
|
public Peer peer;
|
||||||
public String accountId;
|
public String accountId;
|
||||||
public FiatAccount fiatAccount;
|
public FiatAccount fiatAccount;
|
||||||
public PublicKey messagePublicKey;
|
public PublicKey p2pSigPublicKey;
|
||||||
public String contractAsJson;//TODO only write access now, missing impl.
|
public String contractAsJson;//TODO only write access now, missing impl.
|
||||||
public String contractSignature;
|
public String contractSignature;
|
||||||
public Coin payoutAmount;
|
public Coin payoutAmount;
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ public class ProcessRequestOffererPublishDepositTxMessage extends Task<BuyerAsOf
|
||||||
|
|
||||||
model.taker.fiatAccount = checkNotNull(message.takerFiatAccount);
|
model.taker.fiatAccount = checkNotNull(message.takerFiatAccount);
|
||||||
model.taker.accountId = nonEmptyStringOf(message.takerAccountId);
|
model.taker.accountId = nonEmptyStringOf(message.takerAccountId);
|
||||||
model.taker.messagePublicKey = checkNotNull(message.takerMessagePublicKey);
|
model.taker.p2pSigPublicKey = checkNotNull(message.takerMessagePublicKey);
|
||||||
model.taker.contractAsJson = nonEmptyStringOf(message.takerContractAsJson);
|
model.taker.contractAsJson = nonEmptyStringOf(message.takerContractAsJson);
|
||||||
model.taker.contractSignature = nonEmptyStringOf(message.takerContractSignature);
|
model.taker.contractSignature = nonEmptyStringOf(message.takerContractSignature);
|
||||||
model.taker.payoutAddressString = nonEmptyStringOf(message.takerPayoutAddressString);
|
model.taker.payoutAddressString = nonEmptyStringOf(message.takerPayoutAddressString);
|
||||||
|
|
|
||||||
|
|
@ -46,8 +46,8 @@ public class VerifyAndSignContract extends Task<BuyerAsOffererModel> {
|
||||||
model.taker.accountId,
|
model.taker.accountId,
|
||||||
model.offerer.fiatAccount,
|
model.offerer.fiatAccount,
|
||||||
model.taker.fiatAccount,
|
model.taker.fiatAccount,
|
||||||
model.offerer.messagePubKey,
|
model.offerer.p2pSigPubKey,
|
||||||
model.taker.messagePublicKey);
|
model.taker.p2pSigPublicKey);
|
||||||
String contractAsJson = Utilities.objectToJson(contract);
|
String contractAsJson = Utilities.objectToJson(contract);
|
||||||
String signature = model.signatureService.signMessage(model.offerer.registrationKeyPair, contractAsJson);
|
String signature = model.signatureService.signMessage(model.offerer.registrationKeyPair, contractAsJson);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -86,7 +86,7 @@ public class SellerAsTakerModel extends SharedTradeModel implements Serializable
|
||||||
taker.addressEntry = walletService.getAddressEntry(id);
|
taker.addressEntry = walletService.getAddressEntry(id);
|
||||||
taker.fiatAccount = user.getBankAccount(offer.getBankAccountId());
|
taker.fiatAccount = user.getBankAccount(offer.getBankAccountId());
|
||||||
taker.accountId = user.getAccountId();
|
taker.accountId = user.getAccountId();
|
||||||
taker.messagePubKey = user.getMessagePubKey();
|
taker.p2pSigPubKey = user.getP2PSigPubKey();
|
||||||
taker.pubKey = taker.addressEntry.getPubKey();
|
taker.pubKey = taker.addressEntry.getPubKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ public class TakerModel implements Serializable {
|
||||||
// Declared transient as they will be provided in any case at construction time
|
// Declared transient as they will be provided in any case at construction time
|
||||||
transient public FiatAccount fiatAccount;
|
transient public FiatAccount fiatAccount;
|
||||||
transient public String accountId;
|
transient public String accountId;
|
||||||
transient public PublicKey messagePubKey;
|
transient public PublicKey p2pSigPubKey;
|
||||||
transient public byte[] registrationPubKey; // TODO not read yet, missing impl.
|
transient public byte[] registrationPubKey; // TODO not read yet, missing impl.
|
||||||
transient public DeterministicKey registrationKeyPair;
|
transient public DeterministicKey registrationKeyPair;
|
||||||
transient public AddressEntry addressEntry;
|
transient public AddressEntry addressEntry;
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ public class CreateAndSignContract extends Task<SellerAsTakerModel> {
|
||||||
model.offerer.fiatAccount,
|
model.offerer.fiatAccount,
|
||||||
model.taker.fiatAccount,
|
model.taker.fiatAccount,
|
||||||
model.offer.getMessagePublicKey(),
|
model.offer.getMessagePublicKey(),
|
||||||
model.taker.messagePubKey);
|
model.taker.p2pSigPubKey);
|
||||||
String contractAsJson = Utilities.objectToJson(contract);
|
String contractAsJson = Utilities.objectToJson(contract);
|
||||||
String signature = model.signatureService.signMessage(model.taker.registrationKeyPair, contractAsJson);
|
String signature = model.signatureService.signMessage(model.taker.registrationKeyPair, contractAsJson);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ public class SendSignedTakerDepositTx extends Task<SellerAsTakerModel> {
|
||||||
model.id,
|
model.id,
|
||||||
model.taker.fiatAccount,
|
model.taker.fiatAccount,
|
||||||
model.taker.accountId,
|
model.taker.accountId,
|
||||||
model.taker.messagePubKey,
|
model.taker.p2pSigPubKey,
|
||||||
model.trade.getContractAsJson(),
|
model.trade.getContractAsJson(),
|
||||||
model.trade.getTakerContractSignature(),
|
model.trade.getTakerContractSignature(),
|
||||||
model.taker.addressEntry.getAddressString(),
|
model.taker.addressEntry.getAddressString(),
|
||||||
|
|
|
||||||
|
|
@ -175,7 +175,7 @@ public class User implements Serializable {
|
||||||
return p2pSigKeyPair;
|
return p2pSigKeyPair;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PublicKey getMessagePubKey() {
|
public PublicKey getP2PSigPubKey() {
|
||||||
return p2pSigKeyPair.getPublic();
|
return p2pSigKeyPair.getPublic();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,7 @@ public class PlaceOfferProtocolTest {
|
||||||
user.applyPersistedUser(null);
|
user.applyPersistedUser(null);
|
||||||
bootstrappedPeerBuilder = new BootstrappedPeerBuilder(Node.DEFAULT_PORT, false, bootstrapNode, "<unspecified>");
|
bootstrappedPeerBuilder = new BootstrappedPeerBuilder(Node.DEFAULT_PORT, false, bootstrapNode, "<unspecified>");
|
||||||
tomP2PNode = new TomP2PNode(bootstrappedPeerBuilder);
|
tomP2PNode = new TomP2PNode(bootstrappedPeerBuilder);
|
||||||
messageService = new TomP2PMessageService(tomP2PNode);
|
messageService = new TomP2PMessageService(tomP2PNode, null, null, null);
|
||||||
|
|
||||||
Observable<BootstrapState> messageObservable = tomP2PNode.bootstrap(user.getP2pSigKeyPair());
|
Observable<BootstrapState> messageObservable = tomP2PNode.bootstrap(user.getP2pSigKeyPair());
|
||||||
messageObservable.publish();
|
messageObservable.publish();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue