Add DHT mailbox

This commit is contained in:
Manfred Karrer 2015-03-21 11:04:59 +01:00
parent 506ac37293
commit 239389b529
41 changed files with 680 additions and 391 deletions

View File

@ -69,7 +69,7 @@ public class BlockChainService {
}
@SuppressWarnings("SameReturnValue")
private boolean isBankAccountBlacklisted(FiatAccount fiatAccount) {
private boolean isFiatAccountBlacklisted(FiatAccount fiatAccount) {
// TODO check if accountID is on blacklist
return false;
}

View File

@ -471,14 +471,14 @@ public class WalletService {
return tradeWalletService;
}
public void payRegistrationFee(String stringifiedBankAccounts, FutureCallback<Transaction> callback) throws
public void payRegistrationFee(String stringifiedFiatAccounts, FutureCallback<Transaction> callback) throws
InsufficientMoneyException {
log.debug("payRegistrationFee");
log.trace("stringifiedBankAccounts " + stringifiedBankAccounts);
log.trace("stringifiedFiatAccounts " + stringifiedFiatAccounts);
Transaction tx = new Transaction(params);
byte[] data = signatureService.digestMessageWithSignature(getRegistrationAddressEntry().getKeyPair(), stringifiedBankAccounts);
byte[] data = signatureService.digestMessageWithSignature(getRegistrationAddressEntry().getKeyPair(), stringifiedFiatAccounts);
tx.addOutput(Transaction.MIN_NONDUST_OUTPUT, new ScriptBuilder().op(OP_RETURN).data(data).build());
// We don't take a fee at the moment

View File

@ -0,0 +1,32 @@
/*
* 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.crypto;
import java.io.Serializable;
public class EncryptionPackage implements Serializable {
private static final long serialVersionUID = -8709538217388076762L;
public final byte[] encryptedKey;
public final byte[] encryptedPayload;
public EncryptionPackage(byte[] encryptedKey, byte[] encryptedPayload) {
this.encryptedKey = encryptedKey;
this.encryptedPayload = encryptedPayload;
}
}

View File

@ -19,11 +19,11 @@ package io.bitsquare.crypto;
import io.bitsquare.util.Utilities;
import java.io.Serializable;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
@ -32,98 +32,82 @@ import javax.inject.Inject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
public class EncryptionService<T> {
private static final Logger log = LoggerFactory.getLogger(EncryptionService.class);
private static final String ALGO_SYM = "AES";
private static final String CIPHER_SYM = "AES";
private static final String ALGO_ASYM = "RSA";
private static final String CIPHER_ASYM = "RSA/ECB/PKCS1Padding";
private static final int KEY_SIZE_SYM = 128;
private static final int KEY_SIZE_ASYM = 1024;
@Inject
public EncryptionService() {
}
public KeyPair getKeyPair() {
KeyPair keyPair = null;
try {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(1024);
keyPair = keyPairGenerator.genKeyPair();
} catch (Exception e) {
e.printStackTrace();
log.error("Exception at init " + e.getMessage());
}
return keyPair;
public KeyPair getKeyPair() throws NoSuchAlgorithmException {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(ALGO_ASYM);
keyPairGenerator.initialize(KEY_SIZE_ASYM);
return keyPairGenerator.genKeyPair();
}
public Tuple encryptObject(PublicKey publicKey, Object object) {
public EncryptionPackage encryptObject(PublicKey publicKey, Object object) throws IllegalBlockSizeException, InvalidKeyException, BadPaddingException,
NoSuchAlgorithmException, NoSuchPaddingException {
return encrypt(publicKey, Utilities.objectToBytArray(object));
}
public T decryptToObject(PrivateKey privateKey, Tuple tuple) {
return (T) Utilities.byteArrayToObject(decrypt(privateKey, tuple));
public T decryptToObject(PrivateKey privateKey, EncryptionPackage encryptionPackage) throws IllegalBlockSizeException, InvalidKeyException,
BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException {
return (T) Utilities.byteArrayToObject(decrypt(privateKey, encryptionPackage));
}
public Tuple encrypt(PublicKey publicKey, byte[] payload) {
byte[] encryptedPayload = null;
byte[] encryptedKey = null;
try {
// Create symmetric key and
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(256);
SecretKey secretKey = keyGenerator.generateKey();
public EncryptionPackage encrypt(PublicKey publicKey, byte[] payload) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
BadPaddingException, IllegalBlockSizeException {
// Create symmetric key and
KeyGenerator keyGenerator = KeyGenerator.getInstance(ALGO_SYM);
keyGenerator.init(KEY_SIZE_SYM);
SecretKey secretKey = keyGenerator.generateKey();
// Encrypt secretKey with asymmetric key
Cipher cipher = Cipher.getInstance("RSA/NONE/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
encryptedKey = cipher.doFinal(secretKey.getEncoded());
// Encrypt secretKey with asymmetric key
Cipher cipherAsym = Cipher.getInstance(CIPHER_ASYM);
cipherAsym.init(Cipher.ENCRYPT_MODE, publicKey);
log.debug("encrypt secret key length: " + secretKey.getEncoded().length);
byte[] encryptedKey = cipherAsym.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) {
e.printStackTrace();
log.error("Exception at encrypt " + e.getMessage());
}
return new Tuple(encryptedKey, encryptedPayload);
// Encrypt payload with symmetric key
SecretKeySpec keySpec = new SecretKeySpec(secretKey.getEncoded(), ALGO_SYM);
Cipher cipherSym = Cipher.getInstance(CIPHER_SYM);
cipherSym.init(Cipher.ENCRYPT_MODE, keySpec);
log.debug("encrypt payload length: " + payload.length);
byte[] encryptedPayload = cipherSym.doFinal(payload);
return new EncryptionPackage(encryptedKey, encryptedPayload);
}
public byte[] decrypt(PrivateKey privateKey, Tuple tuple) {
byte[] encryptedPayload = tuple.encryptedPayload;
byte[] encryptedKey = tuple.encryptedKey;
public byte[] decrypt(PrivateKey privateKey, EncryptionPackage encryptionPackage) throws NoSuchPaddingException, NoSuchAlgorithmException,
InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
byte[] encryptedPayload = encryptionPackage.encryptedPayload;
byte[] encryptedKey = encryptionPackage.encryptedKey;
byte[] payload = null;
try {
// Decrypt secretKey key with asymmetric key
Cipher cipher = Cipher.getInstance("RSA/NONE/PKCS1Padding");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
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) {
e.printStackTrace();
log.error("Exception at decrypt " + e.getMessage());
}
// Decrypt secretKey key with asymmetric key
Cipher cipherAsym = Cipher.getInstance(CIPHER_ASYM);
cipherAsym.init(Cipher.DECRYPT_MODE, privateKey);
byte[] secretKey = cipherAsym.doFinal(encryptedKey);
log.debug("decrypt secret key length: " + secretKey.length);
// Decrypt payload with symmetric key
Key key = new SecretKeySpec(secretKey, ALGO_SYM);
Cipher cipherSym = Cipher.getInstance(CIPHER_SYM);
cipherSym.init(Cipher.DECRYPT_MODE, key);
byte[] payload = cipherSym.doFinal(encryptedPayload);
log.debug("decrypt payload length: " + payload.length);
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;
}
}
}

View File

@ -154,7 +154,7 @@ class RestrictionsDataModel implements Activatable, DataModel {
private void addMockArbitrator() {
if (accountSettings.getAcceptedArbitrators().isEmpty() && user.getP2pSigKeyPair() != null) {
byte[] pubKey = new ECKey().getPubKey();
String messagePubKeyAsHex = DSAKeyUtil.getHexStringFromPublicKey(user.getP2PSigPubKey());
String p2pSigPubKeyAsHex = DSAKeyUtil.getHexStringFromPublicKey(user.getP2PSigPubKey());
List<Locale> languages = new ArrayList<>();
languages.add(LanguageUtil.getDefaultLanguageLocale());
List<Arbitrator.METHOD> arbitrationMethods = new ArrayList<>();
@ -166,7 +166,7 @@ class RestrictionsDataModel implements Activatable, DataModel {
// TODO use very small sec. dposit to make testing in testnet less expensive
// Revert later to 0.1 BTC again
Arbitrator arbitrator = new Arbitrator(pubKey,
messagePubKeyAsHex,
p2pSigPubKeyAsHex,
"Manfred Karrer",
Arbitrator.ID_TYPE.REAL_LIFE_ID,
languages,

View File

@ -45,7 +45,7 @@ import io.bitsquare.trade.protocol.trade.offerer.tasks.VerifyTakerAccount;
import io.bitsquare.trade.protocol.trade.taker.SellerAsTakerProtocol;
import io.bitsquare.trade.protocol.trade.taker.tasks.CreateAndSignContract;
import io.bitsquare.trade.protocol.trade.taker.tasks.CreateTakeOfferFeeTx;
import io.bitsquare.trade.protocol.trade.taker.tasks.ProcessBankTransferStartedMessage;
import io.bitsquare.trade.protocol.trade.taker.tasks.ProcessFiatTransferStartedMessage;
import io.bitsquare.trade.protocol.trade.taker.tasks.ProcessDepositTxPublishedMessage;
import io.bitsquare.trade.protocol.trade.taker.tasks.ProcessRequestDepositPaymentMessage;
import io.bitsquare.trade.protocol.trade.taker.tasks.SendPayoutTxToOfferer;
@ -135,7 +135,7 @@ public class DebugView extends InitializableView {
ProcessDepositTxPublishedMessage.class,
TakerCommitDepositTx.class,
ProcessBankTransferStartedMessage.class,
ProcessFiatTransferStartedMessage.class,
SignAndPublishPayoutTx.class,
VerifyOfferFeePayment.class,

View File

@ -74,7 +74,7 @@ class ClosedTradesDataModel implements Activatable, DataModel {
}
public Direction getDirection(Offer offer) {
return offer.getMessagePublicKey().equals(user.getP2PSigPubKey()) ?
return offer.getP2PSigPubKey().equals(user.getP2PSigPubKey()) ?
offer.getDirection() : offer.getMirroredDirection();
}

View File

@ -86,7 +86,7 @@ class OffersDataModel implements Activatable, DataModel {
}
public Direction getDirection(Offer offer) {
return offer.getMessagePublicKey().equals(user.getP2PSigPubKey()) ?
return offer.getP2PSigPubKey().equals(user.getP2PSigPubKey()) ?
offer.getDirection() : offer.getMirroredDirection();
}
}

View File

@ -146,7 +146,7 @@ class PendingTradesDataModel implements Activatable, DataModel {
selectedItem = item;
if (selectedItem != null) {
isOfferer = getTrade().getOffer().getMessagePublicKey().equals(user.getP2PSigPubKey());
isOfferer = getTrade().getOffer().getP2PSigPubKey().equals(user.getP2PSigPubKey());
Trade trade = getTrade();
trade.stateProperty().addListener(tradeStateChangeListener);
@ -172,7 +172,6 @@ class PendingTradesDataModel implements Activatable, DataModel {
}
void fiatPaymentStarted() {
getTrade().setState(Trade.State.FIAT_PAYMENT_STARTED);
tradeManager.onFiatPaymentStarted(getTrade().getId());
}
@ -209,7 +208,7 @@ class PendingTradesDataModel implements Activatable, DataModel {
log.error(e.getMessage());
}
tradeManager.closeTrade(getTrade());
tradeManager.onWithdrawAtTradeCompleted(getTrade());
/*
Action response = Popups.openConfirmPopup(
@ -269,7 +268,7 @@ class PendingTradesDataModel implements Activatable, DataModel {
}
public Direction getDirection(Offer offer) {
return offer.getMessagePublicKey().equals(user.getP2PSigPubKey()) ?
return offer.getP2PSigPubKey().equals(user.getP2PSigPubKey()) ?
offer.getDirection() : offer.getMirroredDirection();
}

View File

@ -213,7 +213,7 @@ class OfferBookDataModel implements Activatable, DataModel {
}
boolean isMyOffer(Offer offer) {
return offer.getMessagePublicKey() != null && offer.getMessagePublicKey().equals(user.getP2PSigPubKey());
return offer.getP2PSigPubKey() != null && offer.getP2PSigPubKey().equals(user.getP2PSigPubKey());
}
Coin getAmountAsCoin() {

View File

@ -97,7 +97,7 @@ class TakeOfferDataModel implements Activatable, DataModel {
@Override
public void deactivate() {
btcCode.unbind();
tradeManager.cancelGetOfferAvailableStateRequest(offer);
tradeManager.cancelCheckOfferAvailabilityRequest(offer);
}
void initWithData(Coin amount, Offer offer) {

View File

@ -70,7 +70,7 @@ public class Offer implements Serializable {
private final Coin amount;
private final Coin minAmount;
//TODO use hex string
private final PublicKey messagePublicKey;
private final PublicKey p2pSigPubKey;
private final FiatAccountType fiatAccountType;
private final Country bankAccountCountry;
@ -94,7 +94,7 @@ public class Offer implements Serializable {
///////////////////////////////////////////////////////////////////////////////////////////
public Offer(String id,
PublicKey messagePublicKey,
PublicKey p2pSigPubKey,
Direction direction,
long fiatPrice,
Coin amount,
@ -108,7 +108,7 @@ public class Offer implements Serializable {
List<Country> acceptedCountries,
List<Locale> acceptedLanguageLocales) {
this.id = id;
this.messagePublicKey = messagePublicKey;
this.p2pSigPubKey = p2pSigPubKey;
this.direction = direction;
this.fiatPrice = fiatPrice;
this.amount = amount;
@ -221,8 +221,8 @@ public class Offer implements Serializable {
return bankAccountUID;
}
public PublicKey getMessagePublicKey() {
return messagePublicKey;
public PublicKey getP2PSigPubKey() {
return p2pSigPubKey;
}
public Date getCreationDate() {
@ -250,7 +250,7 @@ public class Offer implements Serializable {
checkNotNull(getCurrency(), "Currency is null");
checkNotNull(getDirection(), "Direction is null");
checkNotNull(getId(), "Id is null");
checkNotNull(getMessagePublicKey(), "MessagePublicKey is null");
checkNotNull(getP2PSigPubKey(), "p2pSigPubKey is null");
checkNotNull(getMinAmount(), "MinAmount is null");
checkNotNull(getPrice(), "Price is null");
@ -277,7 +277,7 @@ public class Offer implements Serializable {
", fiatPrice=" + fiatPrice +
", amount=" + amount +
", minAmount=" + minAmount +
", messagePublicKey=" + messagePublicKey +
", p2pSigPubKey=" + p2pSigPubKey +
", fiatAccountType=" + fiatAccountType +
", bankAccountCountry=" + bankAccountCountry +
", securityDeposit=" + securityDeposit +

View File

@ -23,5 +23,5 @@ import io.bitsquare.p2p.listener.GetPeerAddressListener;
import java.security.PublicKey;
public interface AddressService extends DHTService{
void findPeerAddress(PublicKey messagePublicKey, GetPeerAddressListener getPeerAddressListener);
void findPeerAddress(PublicKey p2pSigPubKey, GetPeerAddressListener getPeerAddressListener);
}

View File

@ -17,7 +17,7 @@
package io.bitsquare.p2p;
import io.bitsquare.crypto.EncryptionService;
import io.bitsquare.crypto.EncryptionPackage;
import java.io.Serializable;
@ -31,13 +31,13 @@ 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;
private EncryptionPackage encryptionPackage;
public EncryptedMailboxMessage(EncryptionService.Tuple tuple) {
this.tuple = tuple;
public EncryptedMailboxMessage(EncryptionPackage encryptionPackage) {
this.encryptionPackage = encryptionPackage;
}
public EncryptionService.Tuple getTuple() {
return tuple;
public EncryptionPackage getEncryptionPackage() {
return encryptionPackage;
}
}

View File

@ -0,0 +1,24 @@
/*
* 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 java.util.List;
public interface MailboxMessagesResultHandler {
void handleResult(List<EncryptedMailboxMessage> messages);
}

View File

@ -19,14 +19,14 @@ package io.bitsquare.p2p;
import io.bitsquare.common.handlers.FaultHandler;
import io.bitsquare.common.handlers.ResultHandler;
import io.bitsquare.p2p.tomp2p.TomP2PMailboxService;
import java.security.PublicKey;
public interface MailboxService {
void addMessage(PublicKey publicKey, EncryptedMailboxMessage message, ResultHandler resultHandler, FaultHandler faultHandler);
void addMessage(PublicKey p2pSigPubKey, EncryptedMailboxMessage message, ResultHandler resultHandler, FaultHandler faultHandler);
void getAllMessages(PublicKey p2pSigPubKey, MailboxMessagesResultHandler resultHandler);
void removeMessage(PublicKey publicKey, EncryptedMailboxMessage message, ResultHandler resultHandler, FaultHandler faultHandler);
void removeAllMessages(PublicKey p2pSigPubKey, ResultHandler resultHandler, FaultHandler faultHandler);
void getMessages(PublicKey publicKey, TomP2PMailboxService.MailboxMessagesResultHandler resultHandler);
}

View File

@ -20,10 +20,14 @@ package io.bitsquare.p2p;
import io.bitsquare.p2p.listener.SendMessageListener;
import java.security.PublicKey;
public interface MessageService extends P2PService {
void sendMessage(Peer peer, Message message, SendMessageListener listener);
void sendMessage(Peer peer, Message message, PublicKey p2pSigPubKey, PublicKey p2pEncryptPubKey, SendMessageListener listener);
void addMessageHandler(MessageHandler listener);
void removeMessageHandler(MessageHandler listener);

View File

@ -91,9 +91,9 @@ public class TomP2PAddressService extends TomP2PDHTService implements AddressSer
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void findPeerAddress(PublicKey publicKey, GetPeerAddressListener listener) {
final Number160 locationKey = Utils.makeSHAHash(publicKey.getEncoded());
FutureGet futureGet = getDataOfProtectedDomain(locationKey, publicKey);
public void findPeerAddress(PublicKey p2pSigPubKey, GetPeerAddressListener listener) {
final Number160 locationKey = Utils.makeSHAHash(p2pSigPubKey.getEncoded());
FutureGet futureGet = getDataOfProtectedDomain(locationKey, p2pSigPubKey);
log.trace("findPeerAddress called");
futureGet.addListener(new BaseFutureAdapter<BaseFuture>() {
@Override

View File

@ -247,6 +247,18 @@ public class TomP2PDHTService extends TomP2PService implements DHTService {
log.trace("getDataFromMapOfMyProtectedDomain");
return peerDHT.get(locationKey).all().domainKey(pubKeyHashForMyDomain).start();
}
/**
* Remove all data from map for given locationKey.
* Access: Only the domain owner.
*
* @param locationKey
* @return
*/
public FutureRemove removeAllDataFromMapOfMyProtectedDomain(Number160 locationKey) {
log.trace("getDataFromMapOfMyProtectedDomain");
return peerDHT.remove(locationKey).domainKey(pubKeyHashForMyDomain).keyPair(keyPair).all().domainKey(pubKeyHashForMyDomain).keyPair(keyPair).start();
}
}

View File

@ -21,6 +21,7 @@ import io.bitsquare.common.handlers.FaultHandler;
import io.bitsquare.common.handlers.ResultHandler;
import io.bitsquare.offer.OfferBookService;
import io.bitsquare.p2p.EncryptedMailboxMessage;
import io.bitsquare.p2p.MailboxMessagesResultHandler;
import io.bitsquare.p2p.MailboxService;
import io.bitsquare.user.User;
@ -49,7 +50,7 @@ import org.slf4j.LoggerFactory;
public class TomP2PMailboxService extends TomP2PDHTService implements MailboxService {
private static final Logger log = LoggerFactory.getLogger(TomP2PMailboxService.class);
private static final int TTL = 15 * 24 * 60 * 60; // the message is default 15 days valid, as a max trade period might be about 2 weeks.
private static final int TTL = 21 * 24 * 60 * 60; // the message is default 21 days valid, as a max trade period might be about 2 weeks.
private final List<OfferBookService.Listener> offerRepositoryListeners = new ArrayList<>();
@ -69,14 +70,14 @@ public class TomP2PMailboxService extends TomP2PDHTService implements MailboxSer
}
@Override
public void addMessage(PublicKey publicKey, EncryptedMailboxMessage message, ResultHandler resultHandler, FaultHandler faultHandler) {
public void addMessage(PublicKey p2pSigPubKey, EncryptedMailboxMessage message, ResultHandler resultHandler, FaultHandler faultHandler) {
try {
final Data data = new Data(message);
data.ttlSeconds(TTL);
log.trace("Add message to DHT requested. Added data: [locationKey: " + getLocationKey(publicKey) +
log.trace("Add message to DHT requested. Added data: [locationKey: " + getLocationKey(p2pSigPubKey) +
", hash: " + data.hash().toString() + "]");
FuturePut futurePut = addDataToMapOfProtectedDomain(getLocationKey(publicKey), data, publicKey);
FuturePut futurePut = addDataToMapOfProtectedDomain(getLocationKey(p2pSigPubKey), data, p2pSigPubKey);
futurePut.addListener(new BaseFutureListener<BaseFuture>() {
@Override
public void operationComplete(BaseFuture future) throws Exception {
@ -84,7 +85,7 @@ public class TomP2PMailboxService extends TomP2PDHTService implements MailboxSer
executor.execute(() -> {
resultHandler.handleResult();
log.trace("Add message to mailbox was successful. Added data: [locationKey: " + getLocationKey(publicKey) +
log.trace("Add message to mailbox was successful. Added data: [locationKey: " + getLocationKey(p2pSigPubKey) +
", value: " + data + "]");
});
}
@ -101,41 +102,9 @@ public class TomP2PMailboxService extends TomP2PDHTService implements MailboxSer
}
@Override
public void removeMessage(PublicKey publicKey, EncryptedMailboxMessage message, ResultHandler resultHandler, FaultHandler faultHandler) {
try {
final Data data = new Data(message);
log.trace("Remove message from DHT requested. Removed data: [locationKey: " + getLocationKey(publicKey) +
", hash: " + data.hash().toString() + "]");
FutureRemove futureRemove = removeDataFromMapOfMyProtectedDomain(getLocationKey(publicKey), data);
futureRemove.addListener(new BaseFutureListener<BaseFuture>() {
@Override
public void operationComplete(BaseFuture future) throws Exception {
// We don't test futureRemove.isSuccess() as this API does not fit well to that operation,
// it might change in future to something like foundAndRemoved and notFound
// See discussion at: https://github.com/tomp2p/TomP2P/issues/57#issuecomment-62069840
log.trace("isRemoved? " + futureRemove.isRemoved());
executor.execute(() -> {
resultHandler.handleResult();
});
}
@Override
public void exceptionCaught(Throwable t) throws Exception {
log.error("Remove message from DHT failed. Error: " + t.getMessage());
faultHandler.handleFault("Remove message from DHT failed. Error: " + t.getMessage(), t);
}
});
} catch (IOException e) {
e.printStackTrace();
log.error("Remove message from DHT failed. Error: " + e.getMessage());
faultHandler.handleFault("Remove message from DHT failed. Error: " + e.getMessage(), e);
}
}
@Override
public void getMessages(PublicKey publicKey, MailboxMessagesResultHandler resultHandler) {
log.trace("Get messages from DHT requested for locationKey: " + getLocationKey(publicKey));
FutureGet futureGet = getDataFromMapOfMyProtectedDomain(getLocationKey(publicKey));
public void getAllMessages(PublicKey p2pSigPubKey, MailboxMessagesResultHandler resultHandler) {
log.trace("Get messages from DHT requested for locationKey: " + getLocationKey(p2pSigPubKey));
FutureGet futureGet = getDataFromMapOfMyProtectedDomain(getLocationKey(p2pSigPubKey));
futureGet.addListener(new BaseFutureAdapter<BaseFuture>() {
@Override
public void operationComplete(BaseFuture future) throws Exception {
@ -156,7 +125,7 @@ public class TomP2PMailboxService extends TomP2PDHTService implements MailboxSer
executor.execute(() -> resultHandler.handleResult(messages));
}
log.trace("Get messages from DHT was successful. Stored data: [key: " + getLocationKey(publicKey)
log.trace("Get messages from DHT was successful. Stored data: [key: " + getLocationKey(p2pSigPubKey)
+ ", values: " + futureGet.dataMap() + "]");
}
else {
@ -173,16 +142,32 @@ public class TomP2PMailboxService extends TomP2PDHTService implements MailboxSer
});
}
@Override
public void removeAllMessages(PublicKey p2pSigPubKey, ResultHandler resultHandler, FaultHandler faultHandler) {
log.trace("Remove all messages from DHT requested. locationKey: " + getLocationKey(p2pSigPubKey));
FutureRemove futureRemove = removeAllDataFromMapOfMyProtectedDomain(getLocationKey(p2pSigPubKey));
futureRemove.addListener(new BaseFutureListener<BaseFuture>() {
@Override
public void operationComplete(BaseFuture future) throws Exception {
// We don't test futureRemove.isSuccess() as this API does not fit well to that operation,
// it might change in future to something like foundAndRemoved and notFound
// See discussion at: https://github.com/tomp2p/TomP2P/issues/57#issuecomment-62069840
log.trace("isRemoved? " + futureRemove.isRemoved());
executor.execute(() -> {
resultHandler.handleResult();
});
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private
///////////////////////////////////////////////////////////////////////////////////////////
private Number160 getLocationKey(PublicKey publicKey) {
return Number160.createHash("mailbox" + publicKey.hashCode());
@Override
public void exceptionCaught(Throwable t) throws Exception {
log.error("Remove all messages from DHT failed. Error: " + t.getMessage());
faultHandler.handleFault("Remove all messages from DHT failed. Error: " + t.getMessage(), t);
}
});
}
public interface MailboxMessagesResultHandler {
void handleResult(List<EncryptedMailboxMessage> messages);
private Number160 getLocationKey(PublicKey p2pSigPubKey) {
return Number160.createHash("mailbox" + p2pSigPubKey.hashCode());
}
}

View File

@ -17,6 +17,7 @@
package io.bitsquare.p2p.tomp2p;
import io.bitsquare.crypto.EncryptionPackage;
import io.bitsquare.crypto.EncryptionService;
import io.bitsquare.p2p.EncryptedMailboxMessage;
import io.bitsquare.p2p.MailboxMessage;
@ -28,6 +29,8 @@ import io.bitsquare.p2p.Peer;
import io.bitsquare.p2p.listener.SendMessageListener;
import io.bitsquare.user.User;
import java.security.PublicKey;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.inject.Inject;
@ -53,7 +56,7 @@ public class TomP2PMessageService extends TomP2PService implements MessageServic
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
public TomP2PMessageService(TomP2PNode tomP2PNode, MailboxService mailboxService, User user, EncryptionService encryptionService) {
public TomP2PMessageService(TomP2PNode tomP2PNode, MailboxService mailboxService, User user, EncryptionService<MailboxMessage> encryptionService) {
super(tomP2PNode);
this.mailboxService = mailboxService;
this.user = user;
@ -74,6 +77,12 @@ public class TomP2PMessageService extends TomP2PService implements MessageServic
@Override
public void sendMessage(Peer peer, Message message, SendMessageListener listener) {
sendMessage(peer, message, null, null, listener);
}
@Override
public void sendMessage(Peer peer, Message message, PublicKey p2pSigPubKey, PublicKey p2pEncryptPubKey,
SendMessageListener listener) {
if (!(peer instanceof TomP2PPeer))
throw new IllegalArgumentException("Peer must be of type TomP2PPeer");
@ -86,8 +95,9 @@ public class TomP2PMessageService extends TomP2PService implements MessageServic
executor.execute(listener::handleResult);
}
else {
if (message instanceof MailboxMessage) {
sendMailboxMessage((MailboxMessage) message, listener);
if (p2pSigPubKey != null && p2pEncryptPubKey != null) {
log.info("sendMessage failed. We will try to send the message to the mailbox. Fault reason: " + futureDirect.failedReason());
sendMailboxMessage(p2pSigPubKey, p2pEncryptPubKey, (MailboxMessage) message, listener);
}
else {
log.error("sendMessage failed with reason " + futureDirect.failedReason());
@ -98,8 +108,9 @@ public class TomP2PMessageService extends TomP2PService implements MessageServic
@Override
public void exceptionCaught(Throwable t) throws Exception {
if (message instanceof MailboxMessage) {
sendMailboxMessage((MailboxMessage) message, listener);
if (p2pSigPubKey != null && p2pEncryptPubKey != null) {
log.info("sendMessage failed with exception. We will try to send the message to the mailbox. Exception: " + t.getMessage());
sendMailboxMessage(p2pSigPubKey, p2pEncryptPubKey, (MailboxMessage) message, listener);
}
else {
log.error("sendMessage failed with exception " + t.getMessage());
@ -109,11 +120,18 @@ public class TomP2PMessageService extends TomP2PService implements MessageServic
});
}
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,
private void sendMailboxMessage(PublicKey p2pSigPubKey, PublicKey p2pEncryptPubKey, MailboxMessage message, SendMessageListener listener) {
EncryptionPackage encryptionPackage = null;
try {
encryptionPackage = encryptionService.encryptObject(p2pEncryptPubKey, message);
} catch (Throwable t) {
t.printStackTrace();
log.error(t.getMessage());
executor.execute(listener::handleFault);
}
EncryptedMailboxMessage encrypted = new EncryptedMailboxMessage(encryptionPackage);
mailboxService.addMessage(p2pSigPubKey,
encrypted,
() -> {
log.debug("Message successfully added to peers mailbox.");
executor.execute(listener::handleResult);

View File

@ -40,8 +40,8 @@ public class Contract implements Serializable {
private final String takerAccountID;
private final FiatAccount offererFiatAccount;
private final FiatAccount takerFiatAccount;
private final String offererMessagePublicKeyAsString;
private final String takerMessagePublicKeyAsString;
private final String offererP2PSigPubKeyAsString;
private final String takerP2PSigPubKeyAsString;
public Contract(Offer offer,
Coin tradeAmount,
@ -50,8 +50,8 @@ public class Contract implements Serializable {
String takerAccountID,
FiatAccount offererFiatAccount,
FiatAccount takerFiatAccount,
PublicKey offererMessagePublicKey,
PublicKey takerMessagePublicKey) {
PublicKey offererP2PSigPubKey,
PublicKey takerP2PSigPubKey) {
this.offer = offer;
this.tradeAmount = tradeAmount;
this.takeOfferFeeTxID = takeOfferFeeTxID;
@ -59,8 +59,8 @@ public class Contract implements Serializable {
this.takerAccountID = takerAccountID;
this.offererFiatAccount = offererFiatAccount;
this.takerFiatAccount = takerFiatAccount;
this.offererMessagePublicKeyAsString = DSAKeyUtil.getHexStringFromPublicKey(offererMessagePublicKey);
this.takerMessagePublicKeyAsString = DSAKeyUtil.getHexStringFromPublicKey(takerMessagePublicKey);
this.offererP2PSigPubKeyAsString = DSAKeyUtil.getHexStringFromPublicKey(offererP2PSigPubKey);
this.takerP2PSigPubKeyAsString = DSAKeyUtil.getHexStringFromPublicKey(takerP2PSigPubKey);
}
@ -97,11 +97,11 @@ public class Contract implements Serializable {
}
public String getTakerMessagePublicKey() {
return takerMessagePublicKeyAsString;
return takerP2PSigPubKeyAsString;
}
public String getOffererMessagePublicKey() {
return offererMessagePublicKeyAsString;
return offererP2PSigPubKeyAsString;
}
@Override
@ -114,8 +114,8 @@ public class Contract implements Serializable {
", takerAccountID='" + takerAccountID + '\'' +
", offererBankAccount=" + offererFiatAccount +
", takerBankAccount=" + takerFiatAccount +
", takerMessagePublicKeyAsString=" + takerMessagePublicKeyAsString +
", offererMessagePublicKeyAsString=" + offererMessagePublicKeyAsString +
", takerP2PSigPubKeyAsString=" + takerP2PSigPubKeyAsString +
", offererP2PSigPubKeyAsString=" + offererP2PSigPubKeyAsString +
'}';
}
}

View File

@ -21,12 +21,15 @@ import io.bitsquare.btc.BlockChainService;
import io.bitsquare.btc.WalletService;
import io.bitsquare.common.handlers.ErrorMessageHandler;
import io.bitsquare.common.handlers.ResultHandler;
import io.bitsquare.crypto.EncryptionService;
import io.bitsquare.crypto.SignatureService;
import io.bitsquare.fiat.FiatAccount;
import io.bitsquare.offer.Direction;
import io.bitsquare.offer.Offer;
import io.bitsquare.offer.OfferBookService;
import io.bitsquare.p2p.AddressService;
import io.bitsquare.p2p.EncryptedMailboxMessage;
import io.bitsquare.p2p.MailboxMessage;
import io.bitsquare.p2p.MailboxService;
import io.bitsquare.p2p.Message;
import io.bitsquare.p2p.MessageService;
@ -41,6 +44,7 @@ import io.bitsquare.trade.protocol.availability.messages.ReportOfferAvailability
import io.bitsquare.trade.protocol.availability.messages.RequestIsOfferAvailableMessage;
import io.bitsquare.trade.protocol.placeoffer.PlaceOfferModel;
import io.bitsquare.trade.protocol.placeoffer.PlaceOfferProtocol;
import io.bitsquare.trade.protocol.trade.messages.TradeMessage;
import io.bitsquare.trade.protocol.trade.offerer.BuyerAsOffererProtocol;
import io.bitsquare.trade.protocol.trade.offerer.models.BuyerAsOffererModel;
import io.bitsquare.trade.protocol.trade.taker.SellerAsTakerProtocol;
@ -54,6 +58,7 @@ import org.bitcoinj.utils.Fiat;
import java.io.Serializable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
@ -78,6 +83,7 @@ public class TradeManager {
private final BlockChainService blockChainService;
private final WalletService walletService;
private final SignatureService signatureService;
private EncryptionService<MailboxMessage> encryptionService;
private final OfferBookService offerBookService;
private final Map<String, SellerAsTakerProtocol> sellerAsTakerProtocolMap = new HashMap<>();
@ -87,6 +93,7 @@ public class TradeManager {
private final ObservableMap<String, Offer> openOffers = FXCollections.observableHashMap();
private final ObservableMap<String, Trade> pendingTrades = FXCollections.observableHashMap();
private final ObservableMap<String, Trade> closedTrades = FXCollections.observableHashMap();
private final Map<String, MailboxMessage> mailboxMessages = new HashMap<>();
private Trade currentPendingTrade;
@ -98,7 +105,7 @@ public class TradeManager {
@Inject
public TradeManager(User user, AccountSettings accountSettings, Persistence persistence,
MessageService messageService, MailboxService mailboxService, AddressService addressService, BlockChainService blockChainService,
WalletService walletService, SignatureService signatureService,
WalletService walletService, SignatureService signatureService, EncryptionService<MailboxMessage> encryptionService,
OfferBookService offerBookService) {
this.user = user;
this.accountSettings = accountSettings;
@ -109,6 +116,7 @@ public class TradeManager {
this.blockChainService = blockChainService;
this.walletService = walletService;
this.signatureService = signatureService;
this.encryptionService = encryptionService;
this.offerBookService = offerBookService;
Serializable openOffersObject = persistence.read(this, "openOffers");
@ -125,10 +133,13 @@ public class TradeManager {
if (closedTradesObject instanceof Map<?, ?>) {
closedTrades.putAll((Map<String, Trade>) closedTradesObject);
}
messageService.addMessageHandler(this::handleMessage);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Public API
///////////////////////////////////////////////////////////////////////////////////////////
// When all services are initialized we create the protocols for our open offers and persisted not completed pendingTrades
// BuyerAcceptsOfferProtocol listens for take offer requests, so we need to instantiate it early.
public void onAllServicesInitialized() {
@ -161,41 +172,25 @@ public class TradeManager {
}
}
}
mailboxService.getAllMessages(user.getP2PSigPubKey(),
(messages) -> {
decryptMailboxMessages(messages);
emptyMailbox();
});
messageService.addMessageHandler(this::handleMessage);
}
public boolean isMyOffer(Offer offer) {
return offer.getMessagePublicKey().equals(user.getP2PSigPubKey());
return offer.getP2PSigPubKey().equals(user.getP2PSigPubKey());
}
///////////////////////////////////////////////////////////////////////////////////////////
// Called from UI
// Offer
///////////////////////////////////////////////////////////////////////////////////////////
public void checkOfferAvailability(Offer offer) {
offer.setState(Offer.State.UNKNOWN);
if (!checkOfferAvailabilityProtocolMap.containsKey(offer.getId())) {
CheckOfferAvailabilityModel model = new CheckOfferAvailabilityModel(
offer,
messageService,
addressService);
CheckOfferAvailabilityProtocol protocol = new CheckOfferAvailabilityProtocol(model,
() -> disposeCheckOfferAvailabilityRequest(offer),
(errorMessage) -> disposeCheckOfferAvailabilityRequest(offer));
checkOfferAvailabilityProtocolMap.put(offer.getId(), protocol);
protocol.checkOfferAvailability();
}
else {
log.error("That should never happen: onCheckOfferAvailability already called for offer with ID:" + offer.getId());
}
}
// When closing take offer view, we are not interested in the onCheckOfferAvailability result anymore, so remove from the map
public void cancelGetOfferAvailableStateRequest(Offer offer) {
disposeCheckOfferAvailabilityRequest(offer);
}
public void placeOffer(String id,
Direction direction,
Fiat price,
@ -240,6 +235,35 @@ public class TradeManager {
removeOpenOffer(offer, resultHandler, errorMessageHandler, true);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Take offer
///////////////////////////////////////////////////////////////////////////////////////////
public void checkOfferAvailability(Offer offer) {
offer.setState(Offer.State.UNKNOWN);
if (!checkOfferAvailabilityProtocolMap.containsKey(offer.getId())) {
CheckOfferAvailabilityModel model = new CheckOfferAvailabilityModel(
offer,
messageService,
addressService);
CheckOfferAvailabilityProtocol protocol = new CheckOfferAvailabilityProtocol(model,
() -> disposeCheckOfferAvailabilityRequest(offer),
(errorMessage) -> disposeCheckOfferAvailabilityRequest(offer));
checkOfferAvailabilityProtocolMap.put(offer.getId(), protocol);
protocol.checkOfferAvailability();
}
else {
log.error("That should never happen: onCheckOfferAvailability already called for offer with ID:" + offer.getId());
}
}
// When closing take offer view, we are not interested in the onCheckOfferAvailability result anymore, so remove from the map
public void cancelCheckOfferAvailabilityRequest(Offer offer) {
disposeCheckOfferAvailabilityRequest(offer);
}
public void requestTakeOffer(Coin amount, Offer offer, TradeResultHandler tradeResultHandler) {
CheckOfferAvailabilityModel model = new CheckOfferAvailabilityModel(offer, messageService, addressService);
CheckOfferAvailabilityProtocol protocol = new CheckOfferAvailabilityProtocol(model,
@ -255,6 +279,138 @@ public class TradeManager {
protocol.checkOfferAvailability();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Trade
///////////////////////////////////////////////////////////////////////////////////////////
public void onFiatPaymentStarted(String tradeId) {
// TODO remove if check when persistence is impl.
if (buyerAcceptsOfferProtocolMap.containsKey(tradeId)) {
buyerAcceptsOfferProtocolMap.get(tradeId).onFiatPaymentStarted();
persistPendingTrades();
}
}
public void onFiatPaymentReceived(String tradeId) {
sellerAsTakerProtocolMap.get(tradeId).onFiatPaymentReceived();
}
public void onWithdrawAtTradeCompleted(Trade trade) {
closeTrade(trade);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Called from Offerbook when offer gets removed from DHT
///////////////////////////////////////////////////////////////////////////////////////////
public void onOfferRemovedFromRemoteOfferBook(Offer offer) {
disposeCheckOfferAvailabilityRequest(offer);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Message handler
///////////////////////////////////////////////////////////////////////////////////////////
// Offerer handles those requests
private void handleMessage(Message message, Peer sender) {
if (message instanceof RequestIsOfferAvailableMessage) {
String offerId = ((RequestIsOfferAvailableMessage) message).offerId;
checkNotNull(offerId);
ReportOfferAvailabilityMessage reportOfferAvailabilityMessage = new ReportOfferAvailabilityMessage(offerId, isOfferOpen(offerId));
messageService.sendMessage(sender, reportOfferAvailabilityMessage, new SendMessageListener() {
@Override
public void handleResult() {
// Offerer does not do anything at that moment. Peer might only watch the offer and does nto start a trade.
log.trace("ReportOfferAvailabilityMessage successfully arrived at peer");
}
@Override
public void handleFault() {
log.warn("Sending ReportOfferAvailabilityMessage failed.");
}
});
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Getters
///////////////////////////////////////////////////////////////////////////////////////////
public ObservableMap<String, Offer> getOpenOffers() {
return openOffers;
}
public ObservableMap<String, Trade> getPendingTrades() {
return pendingTrades;
}
public ObservableMap<String, Trade> getClosedTrades() {
return closedTrades;
}
public Trade getCurrentPendingTrade() {
return currentPendingTrade;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private methods
///////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////
// Offer
///////////////////////////////////////////////////////////////////////////////////////////
private void removeOpenOffer(Offer offer,
ResultHandler resultHandler,
ErrorMessageHandler errorMessageHandler,
boolean removeFromBuyerAcceptsOfferProtocolMap) {
String offerId = offer.getId();
offerBookService.removeOffer(offer,
() -> {
if (openOffers.containsKey(offerId)) {
openOffers.remove(offerId);
disposeCheckOfferAvailabilityRequest(offer);
persistOpenOffers();
if (removeFromBuyerAcceptsOfferProtocolMap && buyerAcceptsOfferProtocolMap.containsKey(offerId)) {
buyerAcceptsOfferProtocolMap.get(offerId).cleanup();
buyerAcceptsOfferProtocolMap.remove(offerId);
}
resultHandler.handleResult();
}
else {
log.error("Locally stored offers does not contain the offer with the ID " + offerId);
errorMessageHandler.handleErrorMessage("Locally stored offers does not contain the offer with the ID " + offerId);
}
},
(message, throwable) -> errorMessageHandler.handleErrorMessage(message));
}
///////////////////////////////////////////////////////////////////////////////////////////
// Take offer
///////////////////////////////////////////////////////////////////////////////////////////
private void disposeCheckOfferAvailabilityRequest(Offer offer) {
if (checkOfferAvailabilityProtocolMap.containsKey(offer.getId())) {
CheckOfferAvailabilityProtocol protocol = checkOfferAvailabilityProtocolMap.get(offer.getId());
protocol.cancel();
protocol.cleanup();
checkOfferAvailabilityProtocolMap.remove(offer.getId());
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Trade
///////////////////////////////////////////////////////////////////////////////////////////
private Trade takeAvailableOffer(Coin amount, Offer offer, Peer peer) {
Trade trade = createTrade(offer);
trade.setTradeAmount(amount);
@ -264,6 +420,19 @@ public class TradeManager {
return trade;
}
private Trade createTrade(Offer offer) {
if (pendingTrades.containsKey(offer.getId()))
log.error("That must never happen: Trades contains already an trade with the ID " + offer.getId());
Trade trade = new Trade(offer);
pendingTrades.put(offer.getId(), trade);
persistPendingTrades();
currentPendingTrade = trade;
return trade;
}
private SellerAsTakerProtocol createSellerAsTakerProtocol(Trade trade, Peer peer) {
trade.stateProperty().addListener((ov, oldValue, newValue) -> {
log.debug("trade state = " + newValue);
@ -292,6 +461,7 @@ public class TradeManager {
trade,
peer,
messageService,
mailboxService,
walletService,
blockChainService,
signatureService,
@ -300,122 +470,13 @@ public class TradeManager {
SellerAsTakerProtocol sellerTakesOfferProtocol = new SellerAsTakerProtocol(model);
sellerAsTakerProtocolMap.put(trade.getId(), sellerTakesOfferProtocol);
if (mailboxMessages.containsKey(trade.getId()))
sellerTakesOfferProtocol.setMailboxMessage(mailboxMessages.get(trade.getId()));
return sellerTakesOfferProtocol;
}
public void onFiatPaymentStarted(String tradeId) {
// TODO remove if check when persistence is impl.
if (buyerAcceptsOfferProtocolMap.containsKey(tradeId)) {
buyerAcceptsOfferProtocolMap.get(tradeId).onFiatPaymentStarted();
persistPendingTrades();
}
}
public void onFiatPaymentReceived(String tradeId) {
sellerAsTakerProtocolMap.get(tradeId).onFiatPaymentReceived();
}
public void closeTrade(Trade trade) {
if (pendingTrades.containsKey(trade.getId())) {
pendingTrades.remove(trade.getId());
persistPendingTrades();
}
if (sellerAsTakerProtocolMap.containsKey(trade.getId())) {
sellerAsTakerProtocolMap.get(trade.getId()).cleanup();
sellerAsTakerProtocolMap.remove(trade.getId());
}
else if (buyerAcceptsOfferProtocolMap.containsKey(trade.getId())) {
buyerAcceptsOfferProtocolMap.get(trade.getId()).cleanup();
buyerAcceptsOfferProtocolMap.remove(trade.getId());
}
if (!closedTrades.containsKey(trade.getId())) {
closedTrades.put(trade.getId(), trade);
persistClosedTrades();
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Called from Offerbook when offer gets removed from DHT
///////////////////////////////////////////////////////////////////////////////////////////
public void onOfferRemovedFromRemoteOfferBook(Offer offer) {
disposeCheckOfferAvailabilityRequest(offer);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Process new tradeMessages
///////////////////////////////////////////////////////////////////////////////////////////
// Offerer handles those requests
private void handleMessage(Message message, Peer sender) {
if (message instanceof RequestIsOfferAvailableMessage) {
String offerId = ((RequestIsOfferAvailableMessage) message).offerId;
checkNotNull(offerId);
ReportOfferAvailabilityMessage reportOfferAvailabilityMessage = new ReportOfferAvailabilityMessage(offerId, isOfferOpen(offerId));
messageService.sendMessage(sender, reportOfferAvailabilityMessage, new SendMessageListener() {
@Override
public void handleResult() {
// Offerer does not do anything at that moment. Peer might only watch the offer and does nto start a trade.
log.trace("ReportOfferAvailabilityMessage successfully arrived at peer");
}
@Override
public void handleFault() {
log.warn("Sending ReportOfferAvailabilityMessage failed.");
}
});
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private
///////////////////////////////////////////////////////////////////////////////////////////
private void removeOpenOffer(Offer offer,
ResultHandler resultHandler,
ErrorMessageHandler errorMessageHandler,
boolean removeFromBuyerAcceptsOfferProtocolMap) {
String offerId = offer.getId();
offerBookService.removeOffer(offer,
() -> {
if (openOffers.containsKey(offerId)) {
openOffers.remove(offerId);
disposeCheckOfferAvailabilityRequest(offer);
persistOpenOffers();
if (removeFromBuyerAcceptsOfferProtocolMap && buyerAcceptsOfferProtocolMap.containsKey(offerId)) {
buyerAcceptsOfferProtocolMap.get(offerId).cleanup();
buyerAcceptsOfferProtocolMap.remove(offerId);
}
resultHandler.handleResult();
}
else {
log.error("Locally stored offers does not contain the offer with the ID " + offerId);
errorMessageHandler.handleErrorMessage("Locally stored offers does not contain the offer with the ID " + offerId);
}
},
(message, throwable) -> errorMessageHandler.handleErrorMessage(message));
}
private Trade createTrade(Offer offer) {
if (pendingTrades.containsKey(offer.getId()))
log.error("That must never happen: Trades contains already an trade with the ID " + offer.getId());
Trade trade = new Trade(offer);
pendingTrades.put(offer.getId(), trade);
persistPendingTrades();
currentPendingTrade = trade;
return trade;
}
private void createBuyerAcceptsOfferProtocol(Offer offer) {
Trade trade;
@ -482,13 +543,63 @@ public class TradeManager {
buyerAcceptsOfferProtocolMap.put(offer.getId(), buyerAcceptsOfferProtocol);
}
private void disposeCheckOfferAvailabilityRequest(Offer offer) {
if (checkOfferAvailabilityProtocolMap.containsKey(offer.getId())) {
CheckOfferAvailabilityProtocol protocol = checkOfferAvailabilityProtocolMap.get(offer.getId());
protocol.cancel();
protocol.cleanup();
checkOfferAvailabilityProtocolMap.remove(offer.getId());
private void closeTrade(Trade trade) {
if (pendingTrades.containsKey(trade.getId())) {
pendingTrades.remove(trade.getId());
persistPendingTrades();
}
if (sellerAsTakerProtocolMap.containsKey(trade.getId())) {
sellerAsTakerProtocolMap.get(trade.getId()).cleanup();
sellerAsTakerProtocolMap.remove(trade.getId());
}
else if (buyerAcceptsOfferProtocolMap.containsKey(trade.getId())) {
buyerAcceptsOfferProtocolMap.get(trade.getId()).cleanup();
buyerAcceptsOfferProtocolMap.remove(trade.getId());
}
if (!closedTrades.containsKey(trade.getId())) {
closedTrades.put(trade.getId(), trade);
persistClosedTrades();
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Mailbox
///////////////////////////////////////////////////////////////////////////////////////////
private void decryptMailboxMessages(List<EncryptedMailboxMessage> encryptedMailboxMessages) {
log.trace("applyMailboxMessage encryptedMailboxMessage.size=" + encryptedMailboxMessages.size());
for (EncryptedMailboxMessage encrypted : encryptedMailboxMessages) {
MailboxMessage mailboxMessage = null;
try {
mailboxMessage = encryptionService.decryptToObject(user.getP2pEncryptPrivateKey(), encrypted.getEncryptionPackage());
} catch (Throwable e) {
e.printStackTrace();
log.error(e.getMessage());
}
if (mailboxMessage instanceof TradeMessage) {
String tradeId = ((TradeMessage) mailboxMessage).tradeId;
mailboxMessages.put(tradeId, mailboxMessage);
log.trace("mailboxMessage with tradeID " + tradeId);
if (sellerAsTakerProtocolMap.containsKey(tradeId)) {
sellerAsTakerProtocolMap.get(tradeId).setMailboxMessage(encrypted);
log.trace("sellerAsTakerProtocolMap exist with tradeID " + tradeId);
}
}
}
}
private void emptyMailbox() {
mailboxService.removeAllMessages(user.getP2PSigPubKey(),
() -> {
log.debug("All mailbox entries removed");
},
(errorMessage, fault) -> {
log.error(errorMessage);
log.error(fault.getMessage());
});
}
boolean isOfferOpen(String offerId) {
@ -498,31 +609,6 @@ public class TradeManager {
|| openOffers.get(offerId).getState() == Offer.State.AVAILABLE);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Getters
///////////////////////////////////////////////////////////////////////////////////////////
public ObservableMap<String, Offer> getOpenOffers() {
return openOffers;
}
public ObservableMap<String, Trade> getPendingTrades() {
return pendingTrades;
}
public ObservableMap<String, Trade> getClosedTrades() {
return closedTrades;
}
public Trade getCurrentPendingTrade() {
return currentPendingTrade;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private
///////////////////////////////////////////////////////////////////////////////////////////
private void persistOpenOffers() {
persistence.write(this, "openOffers", (Map<String, Offer>) new HashMap<>(openOffers));
}

View File

@ -39,7 +39,7 @@ public class GetPeerAddress extends Task<CheckOfferAvailabilityModel> {
@Override
protected void doRun() {
try {
model.addressService.findPeerAddress(model.offer.getMessagePublicKey(), new GetPeerAddressListener() {
model.addressService.findPeerAddress(model.offer.getP2PSigPubKey(), new GetPeerAddressListener() {
@Override
public void onResult(Peer peer) {
model.setPeer(peer);

View File

@ -22,8 +22,10 @@ import io.bitsquare.btc.TradeWalletService;
import io.bitsquare.btc.WalletService;
import io.bitsquare.common.taskrunner.SharedTaskModel;
import io.bitsquare.crypto.SignatureService;
import io.bitsquare.p2p.MessageService;
import io.bitsquare.offer.Offer;
import io.bitsquare.p2p.MailboxMessage;
import io.bitsquare.p2p.MailboxService;
import io.bitsquare.p2p.MessageService;
import io.bitsquare.persistence.Persistence;
import io.bitsquare.trade.protocol.trade.messages.TradeMessage;
@ -39,11 +41,14 @@ public class SharedTradeModel extends SharedTaskModel implements Serializable {
// provided
transient public final Offer offer;
transient public final MessageService messageService;
transient public final MailboxService mailboxService;
transient public final WalletService walletService;
transient public final BlockChainService blockChainService;
transient public final SignatureService signatureService;
transient protected final Persistence persistence;
transient public MailboxMessage mailboxMessage;
// derived
transient public final String id;
transient public final TradeWalletService tradeWalletService;
@ -54,12 +59,14 @@ public class SharedTradeModel extends SharedTaskModel implements Serializable {
protected SharedTradeModel(Offer offer,
MessageService messageService,
MailboxService mailboxService,
WalletService walletService,
BlockChainService blockChainService,
SignatureService signatureService,
Persistence persistence) {
this.offer = offer;
this.messageService = messageService;
this.mailboxService = mailboxService;
this.walletService = walletService;
this.blockChainService = blockChainService;
this.signatureService = signatureService;

View File

@ -23,7 +23,7 @@ import org.bitcoinj.core.Coin;
import java.io.Serializable;
public class BankTransferStartedMessage extends TradeMessage implements MailboxMessage, Serializable {
public class FiatTransferStartedMessage extends TradeMessage implements MailboxMessage, Serializable {
private static final long serialVersionUID = -3479634129543632523L;
public final byte[] offererSignature;
@ -31,7 +31,7 @@ public class BankTransferStartedMessage extends TradeMessage implements MailboxM
public final Coin takerPayoutAmount;
public final String offererPayoutAddress;
public BankTransferStartedMessage(String tradeId,
public FiatTransferStartedMessage(String tradeId,
byte[] offererSignature,
Coin offererPayoutAmount,
Coin takerPayoutAmount,

View File

@ -33,7 +33,8 @@ public class RequestOffererPublishDepositTxMessage extends TradeMessage implemen
public final FiatAccount takerFiatAccount;
public final String takerAccountId;
public final PublicKey takerMessagePublicKey;
public final PublicKey takerP2PSigPublicKey;
public final PublicKey takerP2PEncryptPublicKey;
public final String takerContractAsJson;
public final String takerContractSignature;
public final String takerPayoutAddressString;
@ -44,7 +45,8 @@ public class RequestOffererPublishDepositTxMessage extends TradeMessage implemen
public RequestOffererPublishDepositTxMessage(String tradeId,
FiatAccount takerFiatAccount,
String takerAccountId,
PublicKey takerMessagePublicKey,
PublicKey takerP2PSigPublicKey,
PublicKey takerP2PEncryptPublicKey,
String takerContractAsJson,
String takerContractSignature,
String takerPayoutAddressString,
@ -54,7 +56,8 @@ public class RequestOffererPublishDepositTxMessage extends TradeMessage implemen
this.tradeId = tradeId;
this.takerFiatAccount = takerFiatAccount;
this.takerAccountId = takerAccountId;
this.takerMessagePublicKey = takerMessagePublicKey;
this.takerP2PSigPublicKey = takerP2PSigPublicKey;
this.takerP2PEncryptPublicKey = takerP2PEncryptPublicKey;
this.takerContractAsJson = takerContractAsJson;
this.takerContractSignature = takerContractSignature;
this.takerPayoutAddressString = takerPayoutAddressString;

View File

@ -37,7 +37,6 @@ public class BuyerAsOffererModel extends SharedTradeModel implements Serializabl
private static final Logger log = LoggerFactory.getLogger(BuyerAsOffererModel.class);
transient public final Trade trade;
transient public final MailboxService mailboxService;
public final TakerModel taker;
public final OffererModel offerer;
@ -54,13 +53,13 @@ public class BuyerAsOffererModel extends SharedTradeModel implements Serializabl
Persistence persistence) {
super(trade.getOffer(),
messageService,
mailboxService,
walletService,
blockChainService,
signatureService,
persistence);
this.trade = trade;
this.mailboxService = mailboxService;
Serializable serializable = persistence.read(this, "BuyerAsOffererModel_" + id);
if (serializable instanceof BuyerAsOffererModel) {

View File

@ -38,6 +38,7 @@ public class TakerModel implements Serializable {
public String accountId;
public FiatAccount fiatAccount;
public PublicKey p2pSigPublicKey;
public PublicKey p2pEncryptPubKey;
public String contractAsJson;//TODO only write access now, missing impl.
public String contractSignature;
public Coin payoutAmount;

View File

@ -43,7 +43,8 @@ public class ProcessRequestOffererPublishDepositTxMessage extends Task<BuyerAsOf
model.taker.fiatAccount = checkNotNull(message.takerFiatAccount);
model.taker.accountId = nonEmptyStringOf(message.takerAccountId);
model.taker.p2pSigPublicKey = checkNotNull(message.takerMessagePublicKey);
model.taker.p2pSigPublicKey = checkNotNull(message.takerP2PSigPublicKey);
model.taker.p2pEncryptPubKey = checkNotNull(message.takerP2PEncryptPublicKey);
model.taker.contractAsJson = nonEmptyStringOf(message.takerContractAsJson);
model.taker.contractSignature = nonEmptyStringOf(message.takerContractSignature);
model.taker.payoutAddressString = nonEmptyStringOf(message.takerPayoutAddressString);

View File

@ -20,7 +20,8 @@ package io.bitsquare.trade.protocol.trade.offerer.tasks;
import io.bitsquare.common.taskrunner.Task;
import io.bitsquare.common.taskrunner.TaskRunner;
import io.bitsquare.p2p.listener.SendMessageListener;
import io.bitsquare.trade.protocol.trade.messages.BankTransferStartedMessage;
import io.bitsquare.trade.Trade;
import io.bitsquare.trade.protocol.trade.messages.FiatTransferStartedMessage;
import io.bitsquare.trade.protocol.trade.offerer.models.BuyerAsOffererModel;
import org.slf4j.Logger;
@ -36,26 +37,32 @@ public class SendBankTransferStartedMessage extends Task<BuyerAsOffererModel> {
@Override
protected void doRun() {
try {
BankTransferStartedMessage tradeMessage = new BankTransferStartedMessage(
model.id,
FiatTransferStartedMessage tradeMessage = new FiatTransferStartedMessage(model.id,
model.offerer.payoutTxSignature,
model.offerer.payoutAmount,
model.taker.payoutAmount,
model.offerer.addressEntry.getAddressString());
model.messageService.sendMessage(model.taker.peer, tradeMessage, new SendMessageListener() {
@Override
public void handleResult() {
log.trace("Sending BankTransferInitedMessage succeeded.");
complete();
}
@Override
public void handleFault() {
failed("Sending BankTransferInitedMessage failed.");
}
});
model.messageService.sendMessage(model.taker.peer, tradeMessage,
model.taker.p2pSigPublicKey,
model.taker.p2pEncryptPubKey,
new SendMessageListener() {
@Override
public void handleResult() {
log.trace("Sending BankTransferInitedMessage succeeded.");
model.trade.setState(Trade.State.FIAT_PAYMENT_STARTED);
complete();
}
@Override
public void handleFault() {
failed("Sending BankTransferInitedMessage failed.");
model.trade.setState(Trade.State.FAULT);
}
});
} catch (Throwable t) {
failed("Sending BankTransferInitedMessage failed.");
model.trade.setState(Trade.State.FAULT);
}
}
}

View File

@ -18,20 +18,21 @@
package io.bitsquare.trade.protocol.trade.taker;
import io.bitsquare.common.taskrunner.TaskRunner;
import io.bitsquare.p2p.MailboxMessage;
import io.bitsquare.p2p.Message;
import io.bitsquare.p2p.MessageHandler;
import io.bitsquare.p2p.Peer;
import io.bitsquare.trade.Trade;
import io.bitsquare.trade.protocol.trade.messages.BankTransferStartedMessage;
import io.bitsquare.trade.protocol.trade.messages.DepositTxPublishedMessage;
import io.bitsquare.trade.protocol.trade.messages.FiatTransferStartedMessage;
import io.bitsquare.trade.protocol.trade.messages.RequestDepositPaymentMessage;
import io.bitsquare.trade.protocol.trade.messages.TradeMessage;
import io.bitsquare.trade.protocol.trade.taker.models.SellerAsTakerModel;
import io.bitsquare.trade.protocol.trade.taker.tasks.BroadcastTakeOfferFeeTx;
import io.bitsquare.trade.protocol.trade.taker.tasks.CreateAndSignContract;
import io.bitsquare.trade.protocol.trade.taker.tasks.CreateTakeOfferFeeTx;
import io.bitsquare.trade.protocol.trade.taker.tasks.ProcessBankTransferStartedMessage;
import io.bitsquare.trade.protocol.trade.taker.tasks.ProcessDepositTxPublishedMessage;
import io.bitsquare.trade.protocol.trade.taker.tasks.ProcessFiatTransferStartedMessage;
import io.bitsquare.trade.protocol.trade.taker.tasks.ProcessRequestDepositPaymentMessage;
import io.bitsquare.trade.protocol.trade.taker.tasks.SendPayoutTxToOfferer;
import io.bitsquare.trade.protocol.trade.taker.tasks.SendRequestDepositTxInputsMessage;
@ -70,6 +71,8 @@ public class SellerAsTakerProtocol {
this.model = model;
messageHandler = this::handleMessage;
model.messageService.addMessageHandler(messageHandler);
}
@ -82,6 +85,17 @@ public class SellerAsTakerProtocol {
model.messageService.removeMessageHandler(messageHandler);
}
public void setMailboxMessage(MailboxMessage mailboxMessage) {
log.debug("setMailboxMessage " + mailboxMessage);
// Might be called twice, so check that its only processed once
if (model.mailboxMessage == null) {
model.mailboxMessage = mailboxMessage;
if (mailboxMessage instanceof FiatTransferStartedMessage) {
handleFiatTransferStartedMessage((FiatTransferStartedMessage) mailboxMessage);
}
}
}
public void takeAvailableOffer() {
TaskRunner<SellerAsTakerModel> taskRunner = new TaskRunner<>(model,
() -> {
@ -99,7 +113,7 @@ public class SellerAsTakerProtocol {
taskRunner.run();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Incoming message handling
///////////////////////////////////////////////////////////////////////////////////////////
@ -143,19 +157,19 @@ public class SellerAsTakerProtocol {
taskRunner.run();
}
private void handleBankTransferStartedMessage(BankTransferStartedMessage tradeMessage) {
private void handleFiatTransferStartedMessage(FiatTransferStartedMessage tradeMessage) {
model.setTradeMessage(tradeMessage);
TaskRunner<SellerAsTakerModel> taskRunner = new TaskRunner<>(model,
() -> {
log.debug("taskRunner at handleBankTransferInitedMessage completed");
log.debug("taskRunner at handleFiatTransferStartedMessage completed");
model.trade.setState(Trade.State.FIAT_PAYMENT_STARTED);
},
(errorMessage) -> {
log.error(errorMessage);
}
);
taskRunner.addTasks(ProcessBankTransferStartedMessage.class);
taskRunner.addTasks(ProcessFiatTransferStartedMessage.class);
taskRunner.run();
}
@ -201,8 +215,8 @@ public class SellerAsTakerProtocol {
else if (tradeMessage instanceof DepositTxPublishedMessage) {
handleDepositTxPublishedMessage((DepositTxPublishedMessage) tradeMessage);
}
else if (tradeMessage instanceof BankTransferStartedMessage) {
handleBankTransferStartedMessage((BankTransferStartedMessage) tradeMessage);
else if (tradeMessage instanceof FiatTransferStartedMessage) {
handleFiatTransferStartedMessage((FiatTransferStartedMessage) tradeMessage);
}
else {
log.error("Incoming message not supported. " + tradeMessage);

View File

@ -20,6 +20,7 @@ package io.bitsquare.trade.protocol.trade.taker.models;
import io.bitsquare.btc.BlockChainService;
import io.bitsquare.btc.WalletService;
import io.bitsquare.crypto.SignatureService;
import io.bitsquare.p2p.MailboxService;
import io.bitsquare.p2p.MessageService;
import io.bitsquare.p2p.Peer;
import io.bitsquare.persistence.Persistence;
@ -49,6 +50,7 @@ public class SellerAsTakerModel extends SharedTradeModel implements Serializable
public SellerAsTakerModel(Trade trade,
Peer offererPeer,
MessageService messageService,
MailboxService mailboxService,
WalletService walletService,
BlockChainService blockChainService,
SignatureService signatureService,
@ -56,6 +58,7 @@ public class SellerAsTakerModel extends SharedTradeModel implements Serializable
Persistence persistence) {
super(trade.getOffer(),
messageService,
mailboxService,
walletService,
blockChainService,
signatureService,
@ -87,6 +90,7 @@ public class SellerAsTakerModel extends SharedTradeModel implements Serializable
taker.fiatAccount = user.getBankAccount(offer.getBankAccountId());
taker.accountId = user.getAccountId();
taker.p2pSigPubKey = user.getP2PSigPubKey();
taker.p2pEncryptPublicKey = user.getP2PEncryptPubKey();
taker.pubKey = taker.addressEntry.getPubKey();
}

View File

@ -39,6 +39,7 @@ public class TakerModel implements Serializable {
transient public FiatAccount fiatAccount;
transient public String accountId;
transient public PublicKey p2pSigPubKey;
transient public PublicKey p2pEncryptPublicKey;
transient public byte[] registrationPubKey; // TODO not read yet, missing impl.
transient public DeterministicKey registrationKeyPair;
transient public AddressEntry addressEntry;

View File

@ -45,7 +45,7 @@ public class CreateAndSignContract extends Task<SellerAsTakerModel> {
model.taker.accountId,
model.offerer.fiatAccount,
model.taker.fiatAccount,
model.offer.getMessagePublicKey(),
model.offer.getP2PSigPubKey(),
model.taker.p2pSigPubKey);
String contractAsJson = Utilities.objectToJson(contract);
String signature = model.signatureService.signMessage(model.taker.registrationKeyPair, contractAsJson);

View File

@ -19,7 +19,7 @@ package io.bitsquare.trade.protocol.trade.taker.tasks;
import io.bitsquare.common.taskrunner.Task;
import io.bitsquare.common.taskrunner.TaskRunner;
import io.bitsquare.trade.protocol.trade.messages.BankTransferStartedMessage;
import io.bitsquare.trade.protocol.trade.messages.FiatTransferStartedMessage;
import io.bitsquare.trade.protocol.trade.taker.models.SellerAsTakerModel;
import org.slf4j.Logger;
@ -28,10 +28,10 @@ import org.slf4j.LoggerFactory;
import static com.google.common.base.Preconditions.checkNotNull;
import static io.bitsquare.util.Validator.*;
public class ProcessBankTransferStartedMessage extends Task<SellerAsTakerModel> {
private static final Logger log = LoggerFactory.getLogger(ProcessBankTransferStartedMessage.class);
public class ProcessFiatTransferStartedMessage extends Task<SellerAsTakerModel> {
private static final Logger log = LoggerFactory.getLogger(ProcessFiatTransferStartedMessage.class);
public ProcessBankTransferStartedMessage(TaskRunner taskHandler, SellerAsTakerModel model) {
public ProcessFiatTransferStartedMessage(TaskRunner taskHandler, SellerAsTakerModel model) {
super(taskHandler, model);
}
@ -39,7 +39,7 @@ public class ProcessBankTransferStartedMessage extends Task<SellerAsTakerModel>
protected void doRun() {
try {
checkTradeId(model.id, model.getTradeMessage());
BankTransferStartedMessage message = (BankTransferStartedMessage) model.getTradeMessage();
FiatTransferStartedMessage message = (FiatTransferStartedMessage) model.getTradeMessage();
model.offerer.signature = checkNotNull(message.offererSignature);
model.offerer.payoutAmount = positiveCoinOf(nonZeroCoinOf(message.offererPayoutAmount));

View File

@ -40,6 +40,7 @@ public class SendSignedTakerDepositTx extends Task<SellerAsTakerModel> {
model.taker.fiatAccount,
model.taker.accountId,
model.taker.p2pSigPubKey,
model.taker.p2pEncryptPublicKey,
model.trade.getContractAsJson(),
model.trade.getTakerContractSignature(),
model.taker.addressEntry.getAddressString(),

View File

@ -24,6 +24,8 @@ import io.bitsquare.util.DSAKeyUtil;
import java.io.Serializable;
import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.ArrayList;
@ -39,12 +41,16 @@ import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The User is persisted locally.
* It must never be transmitted over the wire (messageKeyPair contains private key!).
*/
public class User implements Serializable {
private static final long serialVersionUID = 7409078808248518638L;
private static final Logger log = LoggerFactory.getLogger(User.class);
private KeyPair p2pSigKeyPair;
private KeyPair p2pEncryptKeyPair;
@ -87,8 +93,13 @@ public class User implements Serializable {
}
else {
// First time
p2pSigKeyPair = DSAKeyUtil.generateKeyPair();
p2pEncryptKeyPair = encryptionService.getKeyPair();
p2pSigKeyPair = DSAKeyUtil.generateDSAKeyPair();
try {
p2pEncryptKeyPair = encryptionService.getKeyPair();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
log.error(e.getMessage());
}
}
}
@ -179,6 +190,10 @@ public class User implements Serializable {
return p2pSigKeyPair.getPublic();
}
public PublicKey getP2PEncryptPubKey() {
return p2pEncryptKeyPair.getPublic();
}
public ObjectProperty<FiatAccount> currentBankAccountProperty() {
return currentBankAccount;
}
@ -192,7 +207,11 @@ public class User implements Serializable {
return _currentFiatAccount;
}
public KeyPair getP2pEncryptKeyPair() {
public PrivateKey getP2pEncryptPrivateKey() {
return p2pEncryptKeyPair.getPrivate();
}
KeyPair getP2pEncryptKeyPair() {
return p2pEncryptKeyPair;
}
}

View File

@ -36,7 +36,7 @@ public class DSAKeyUtil {
return Utils.HEX.encode(x509EncodedKeySpec.getEncoded());
}
public static KeyPair generateKeyPair() {
public static KeyPair generateDSAKeyPair() {
try {
final KeyPairGenerator keyGen = KeyPairGenerator.getInstance("DSA");
keyGen.initialize(1024);

View File

@ -0,0 +1,88 @@
/*
* 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.crypto;
import io.bitsquare.p2p.MailboxMessage;
import java.security.KeyPair;
import java.util.Random;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.springframework.test.util.AssertionErrors.assertEquals;
public class EncryptionServiceTests {
private static final Logger log = LoggerFactory.getLogger(EncryptionServiceTests.class);
@Test
public void testEncryptionWithMailboxMessage() throws Exception {
EncryptionService<MailboxMessage> encryptionService = new EncryptionService<>();
KeyPair p2pEncryptKeyPair = encryptionService.getKeyPair();
TestMessage message = new TestMessage("test");
EncryptionPackage encryptionPackage = encryptionService.encryptObject(p2pEncryptKeyPair.getPublic(), message);
MailboxMessage result = encryptionService.decryptToObject(p2pEncryptKeyPair.getPrivate(), encryptionPackage);
assertEquals("", message.data, ((TestMessage) result).data);
}
@Test
public void testEncryptionWithInteger() throws Exception {
EncryptionService<Integer> encryptionService = new EncryptionService<>();
KeyPair p2pEncryptKeyPair = encryptionService.getKeyPair();
int data = 1234;
EncryptionPackage encryptionPackage = encryptionService.encryptObject(p2pEncryptKeyPair.getPublic(), data);
Integer result = encryptionService.decryptToObject(p2pEncryptKeyPair.getPrivate(), encryptionPackage);
assertEquals("", data, result);
}
@Test
public void testEncryptionWithBytes() throws Exception {
EncryptionService encryptionService = new EncryptionService();
KeyPair p2pEncryptKeyPair = encryptionService.getKeyPair();
byte[] data = new byte[]{0x00, 0x01, 0x02, 0x03, 0x04};
EncryptionPackage encryptionPackage = encryptionService.encrypt(p2pEncryptKeyPair.getPublic(), data);
byte[] result = encryptionService.decrypt(p2pEncryptKeyPair.getPrivate(), encryptionPackage);
assertEquals("", result, data);
}
@Test
public void testEncryptionWithLargeData() throws Exception {
EncryptionService encryptionService = new EncryptionService();
KeyPair p2pEncryptKeyPair = encryptionService.getKeyPair();
byte[] data = new byte[2000];
new Random().nextBytes(data);
EncryptionPackage encryptionPackage = encryptionService.encrypt(p2pEncryptKeyPair.getPublic(), data);
byte[] result = encryptionService.decrypt(p2pEncryptKeyPair.getPrivate(), encryptionPackage);
assertEquals("", result, data);
}
}
class TestMessage implements MailboxMessage {
public String data = "test";
public TestMessage(String data) {
this.data = data;
}
}

View File

@ -296,7 +296,7 @@ public class PlaceOfferProtocolTest {
private Offer getOffer() {
return new Offer(OFFER_ID,
DSAKeyUtil.generateKeyPair().getPublic(),
DSAKeyUtil.generateDSAKeyPair().getPublic(),
Direction.BUY,
100L,
Coin.CENT,