Migrated BitmessageContext and fixed some tests
This commit is contained in:
parent
fa0e53289c
commit
83e50e1ad1
@ -1,452 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.*;
|
|
||||||
import ch.dissem.bitmessage.entity.payload.Broadcast;
|
|
||||||
import ch.dissem.bitmessage.entity.payload.ObjectType;
|
|
||||||
import ch.dissem.bitmessage.entity.payload.Pubkey.Feature;
|
|
||||||
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
|
|
||||||
import ch.dissem.bitmessage.exception.DecryptionFailedException;
|
|
||||||
import ch.dissem.bitmessage.factory.Factory;
|
|
||||||
import ch.dissem.bitmessage.ports.*;
|
|
||||||
import ch.dissem.bitmessage.utils.Property;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.CancellationException;
|
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
import java.util.concurrent.Future;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.InternalContext.NETWORK_EXTRA_BYTES;
|
|
||||||
import static ch.dissem.bitmessage.InternalContext.NETWORK_NONCE_TRIALS_PER_BYTE;
|
|
||||||
import static ch.dissem.bitmessage.entity.Plaintext.Type.BROADCAST;
|
|
||||||
import static ch.dissem.bitmessage.entity.Plaintext.Type.MSG;
|
|
||||||
import static ch.dissem.bitmessage.utils.UnixTime.HOUR;
|
|
||||||
import static ch.dissem.bitmessage.utils.UnixTime.MINUTE;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>Use this class if you want to create a Bitmessage client.</p>
|
|
||||||
* You'll need the Builder to create a BitmessageContext, and set the following properties:
|
|
||||||
* <ul>
|
|
||||||
* <li>addressRepo</li>
|
|
||||||
* <li>inventory</li>
|
|
||||||
* <li>nodeRegistry</li>
|
|
||||||
* <li>networkHandler</li>
|
|
||||||
* <li>messageRepo</li>
|
|
||||||
* <li>streams</li>
|
|
||||||
* </ul>
|
|
||||||
* <p>The default implementations in the different module builds can be used.</p>
|
|
||||||
* <p>The port defaults to 8444 (the default Bitmessage port)</p>
|
|
||||||
*/
|
|
||||||
public class BitmessageContext {
|
|
||||||
public static final int CURRENT_VERSION = 3;
|
|
||||||
private final static Logger LOG = LoggerFactory.getLogger(BitmessageContext.class);
|
|
||||||
|
|
||||||
private final InternalContext ctx;
|
|
||||||
|
|
||||||
private final Labeler labeler;
|
|
||||||
|
|
||||||
private final boolean sendPubkeyOnIdentityCreation;
|
|
||||||
|
|
||||||
private BitmessageContext(Builder builder) {
|
|
||||||
ctx = new InternalContext(
|
|
||||||
builder.cryptography,
|
|
||||||
builder.inventory,
|
|
||||||
builder.nodeRegistry,
|
|
||||||
builder.networkHandler,
|
|
||||||
builder.addressRepo,
|
|
||||||
builder.messageRepo,
|
|
||||||
builder.proofOfWorkRepository,
|
|
||||||
builder.proofOfWorkEngine,
|
|
||||||
builder.customCommandHandler,
|
|
||||||
builder.listener,
|
|
||||||
builder.labeler,
|
|
||||||
builder.port,
|
|
||||||
builder.connectionTTL,
|
|
||||||
builder.connectionLimit
|
|
||||||
);
|
|
||||||
labeler = builder.labeler;
|
|
||||||
ctx.getProofOfWorkService().doMissingProofOfWork(30_000); // TODO: this should be configurable
|
|
||||||
sendPubkeyOnIdentityCreation = builder.sendPubkeyOnIdentityCreation;
|
|
||||||
if (builder.listener instanceof Listener.WithContext) {
|
|
||||||
((Listener.WithContext) builder.listener).setContext(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public AddressRepository addresses() {
|
|
||||||
return ctx.getAddressRepository();
|
|
||||||
}
|
|
||||||
|
|
||||||
public MessageRepository messages() {
|
|
||||||
return ctx.getMessageRepository();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Labeler labeler() {
|
|
||||||
return labeler;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BitmessageAddress createIdentity(boolean shorter, Feature... features) {
|
|
||||||
final BitmessageAddress identity = new BitmessageAddress(new PrivateKey(
|
|
||||||
shorter,
|
|
||||||
ctx.getStreams()[0],
|
|
||||||
NETWORK_NONCE_TRIALS_PER_BYTE,
|
|
||||||
NETWORK_EXTRA_BYTES,
|
|
||||||
features
|
|
||||||
));
|
|
||||||
ctx.getAddressRepository().save(identity);
|
|
||||||
if (sendPubkeyOnIdentityCreation) {
|
|
||||||
ctx.sendPubkey(identity, identity.getStream());
|
|
||||||
}
|
|
||||||
return identity;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BitmessageAddress joinChan(String passphrase, String address) {
|
|
||||||
BitmessageAddress chan = BitmessageAddress.Companion.chan(address, passphrase);
|
|
||||||
chan.setAlias(passphrase);
|
|
||||||
ctx.getAddressRepository().save(chan);
|
|
||||||
return chan;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BitmessageAddress createChan(String passphrase) {
|
|
||||||
// FIXME: hardcoded stream number
|
|
||||||
BitmessageAddress chan = BitmessageAddress.Companion.chan(1, passphrase);
|
|
||||||
ctx.getAddressRepository().save(chan);
|
|
||||||
return chan;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<BitmessageAddress> createDeterministicAddresses(
|
|
||||||
String passphrase, int numberOfAddresses, long version, long stream, boolean shorter) {
|
|
||||||
List<BitmessageAddress> result = BitmessageAddress.Companion.deterministic(
|
|
||||||
passphrase, numberOfAddresses, version, stream, shorter);
|
|
||||||
for (int i = 0; i < result.size(); i++) {
|
|
||||||
BitmessageAddress address = result.get(i);
|
|
||||||
address.setAlias("deterministic (" + (i + 1) + ")");
|
|
||||||
ctx.getAddressRepository().save(address);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void broadcast(final BitmessageAddress from, final String subject, final String message) {
|
|
||||||
Plaintext msg = new Plaintext.Builder(BROADCAST)
|
|
||||||
.from(from)
|
|
||||||
.message(subject, message)
|
|
||||||
.build();
|
|
||||||
send(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void send(final BitmessageAddress from, final BitmessageAddress to, final String subject, final String message) {
|
|
||||||
if (from.getPrivateKey() == null) {
|
|
||||||
throw new IllegalArgumentException("'From' must be an identity, i.e. have a private key.");
|
|
||||||
}
|
|
||||||
Plaintext msg = new Plaintext.Builder(MSG)
|
|
||||||
.from(from)
|
|
||||||
.to(to)
|
|
||||||
.message(subject, message)
|
|
||||||
.build();
|
|
||||||
send(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void send(final Plaintext msg) {
|
|
||||||
if (msg.getFrom().getPrivateKey() == null) {
|
|
||||||
throw new IllegalArgumentException("'From' must be an identity, i.e. have a private key.");
|
|
||||||
}
|
|
||||||
labeler().markAsSending(msg);
|
|
||||||
BitmessageAddress to = msg.getTo();
|
|
||||||
if (to != null) {
|
|
||||||
if (to.getPubkey() == null) {
|
|
||||||
LOG.info("Public key is missing from recipient. Requesting.");
|
|
||||||
ctx.requestPubkey(to);
|
|
||||||
}
|
|
||||||
if (to.getPubkey() == null) {
|
|
||||||
ctx.getMessageRepository().save(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (to == null || to.getPubkey() != null) {
|
|
||||||
LOG.info("Sending message.");
|
|
||||||
ctx.getMessageRepository().save(msg);
|
|
||||||
if (msg.getType() == MSG) {
|
|
||||||
ctx.send(msg);
|
|
||||||
} else {
|
|
||||||
ctx.send(
|
|
||||||
msg.getFrom(),
|
|
||||||
to,
|
|
||||||
Factory.getBroadcast(msg),
|
|
||||||
msg.getTTL()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void startup() {
|
|
||||||
ctx.getNetworkHandler().start();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void shutdown() {
|
|
||||||
ctx.getNetworkHandler().stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param host a trusted node that must be reliable (it's used for every synchronization)
|
|
||||||
* @param port of the trusted host, default is 8444
|
|
||||||
* @param timeoutInSeconds synchronization should end no later than about 5 seconds after the timeout elapsed, even
|
|
||||||
* if not all objects were fetched
|
|
||||||
* @param wait waits for the synchronization thread to finish
|
|
||||||
*/
|
|
||||||
public void synchronize(InetAddress host, int port, long timeoutInSeconds, boolean wait) {
|
|
||||||
Future<?> future = ctx.getNetworkHandler().synchronize(host, port, timeoutInSeconds);
|
|
||||||
if (wait) {
|
|
||||||
try {
|
|
||||||
future.get();
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
LOG.info("Thread was interrupted. Trying to shut down synchronization and returning.");
|
|
||||||
future.cancel(true);
|
|
||||||
} catch (CancellationException | ExecutionException e) {
|
|
||||||
LOG.debug(e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send a custom message to a specific node (that should implement handling for this message type) and returns
|
|
||||||
* the response, which in turn is expected to be a {@link CustomMessage}.
|
|
||||||
*
|
|
||||||
* @param server the node's address
|
|
||||||
* @param port the node's port
|
|
||||||
* @param request the request
|
|
||||||
* @return the response
|
|
||||||
*/
|
|
||||||
public CustomMessage send(InetAddress server, int port, CustomMessage request) {
|
|
||||||
return ctx.getNetworkHandler().send(server, port, request);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes expired objects from the inventory. You should call this method regularly,
|
|
||||||
* e.g. daily and on each shutdown.
|
|
||||||
*/
|
|
||||||
public void cleanup() {
|
|
||||||
ctx.getInventory().cleanup();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends messages again whose time to live expired without being acknowledged. (And whose
|
|
||||||
* recipient is expected to send acknowledgements.
|
|
||||||
* <p>
|
|
||||||
* You should call this method regularly, but be aware of the following:
|
|
||||||
* <ul>
|
|
||||||
* <li>As messages might be sent, POW will be done. It is therefore not advised to
|
|
||||||
* call it on shutdown.</li>
|
|
||||||
* <li>It shouldn't be called right after startup, as it's possible the missing
|
|
||||||
* acknowledgement was sent while the client was offline.</li>
|
|
||||||
* <li>Other than that, the call isn't expensive as long as there is no message
|
|
||||||
* to send, so it might be a good idea to just call it every few minutes.</li>
|
|
||||||
* </ul>
|
|
||||||
*/
|
|
||||||
public void resendUnacknowledgedMessages() {
|
|
||||||
ctx.resendUnacknowledged();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isRunning() {
|
|
||||||
return ctx.getNetworkHandler().isRunning();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addContact(BitmessageAddress contact) {
|
|
||||||
ctx.getAddressRepository().save(contact);
|
|
||||||
if (contact.getPubkey() == null) {
|
|
||||||
BitmessageAddress stored = ctx.getAddressRepository().getAddress(contact.getAddress());
|
|
||||||
if (stored.getPubkey() == null) {
|
|
||||||
ctx.requestPubkey(contact);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addSubscribtion(BitmessageAddress address) {
|
|
||||||
address.setSubscribed(true);
|
|
||||||
ctx.getAddressRepository().save(address);
|
|
||||||
tryToFindBroadcastsForAddress(address);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void tryToFindBroadcastsForAddress(BitmessageAddress address) {
|
|
||||||
for (ObjectMessage object : ctx.getInventory().getObjects(address.getStream(), Broadcast.Companion.getVersion(address), ObjectType.BROADCAST)) {
|
|
||||||
try {
|
|
||||||
Broadcast broadcast = (Broadcast) object.getPayload();
|
|
||||||
broadcast.decrypt(address);
|
|
||||||
// This decrypts it twice, but on the other hand it doesn't try to decrypt the objects with
|
|
||||||
// other subscriptions and the interface stays as simple as possible.
|
|
||||||
ctx.getNetworkListener().receive(object);
|
|
||||||
} catch (DecryptionFailedException ignore) {
|
|
||||||
} catch (Exception e) {
|
|
||||||
LOG.debug(e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Property status() {
|
|
||||||
return new Property("status", null,
|
|
||||||
ctx.getNetworkHandler().getNetworkStatus(),
|
|
||||||
new Property("unacknowledged", ctx.getMessageRepository().findMessagesToResend().size())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the {@link InternalContext} - normally you wouldn't need it,
|
|
||||||
* unless you are doing something crazy with the protocol.
|
|
||||||
*/
|
|
||||||
public InternalContext internals() {
|
|
||||||
return ctx;
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface Listener {
|
|
||||||
void receive(Plaintext plaintext);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A message listener that needs a {@link BitmessageContext}, i.e. for implementing some sort of chat bot.
|
|
||||||
*/
|
|
||||||
interface WithContext extends Listener {
|
|
||||||
void setContext(BitmessageContext ctx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final class Builder {
|
|
||||||
int port = 8444;
|
|
||||||
Inventory inventory;
|
|
||||||
NodeRegistry nodeRegistry;
|
|
||||||
NetworkHandler networkHandler;
|
|
||||||
AddressRepository addressRepo;
|
|
||||||
MessageRepository messageRepo;
|
|
||||||
ProofOfWorkRepository proofOfWorkRepository;
|
|
||||||
ProofOfWorkEngine proofOfWorkEngine;
|
|
||||||
Cryptography cryptography;
|
|
||||||
CustomCommandHandler customCommandHandler;
|
|
||||||
Labeler labeler;
|
|
||||||
Listener listener;
|
|
||||||
int connectionLimit = 150;
|
|
||||||
long connectionTTL = 30 * MINUTE;
|
|
||||||
boolean sendPubkeyOnIdentityCreation = true;
|
|
||||||
|
|
||||||
public Builder port(int port) {
|
|
||||||
this.port = port;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder inventory(Inventory inventory) {
|
|
||||||
this.inventory = inventory;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder nodeRegistry(NodeRegistry nodeRegistry) {
|
|
||||||
this.nodeRegistry = nodeRegistry;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder networkHandler(NetworkHandler networkHandler) {
|
|
||||||
this.networkHandler = networkHandler;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder addressRepo(AddressRepository addressRepo) {
|
|
||||||
this.addressRepo = addressRepo;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder messageRepo(MessageRepository messageRepo) {
|
|
||||||
this.messageRepo = messageRepo;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder powRepo(ProofOfWorkRepository proofOfWorkRepository) {
|
|
||||||
this.proofOfWorkRepository = proofOfWorkRepository;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder cryptography(Cryptography cryptography) {
|
|
||||||
this.cryptography = cryptography;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder customCommandHandler(CustomCommandHandler handler) {
|
|
||||||
this.customCommandHandler = handler;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder proofOfWorkEngine(ProofOfWorkEngine proofOfWorkEngine) {
|
|
||||||
this.proofOfWorkEngine = proofOfWorkEngine;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder labeler(Labeler labeler) {
|
|
||||||
this.labeler = labeler;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder listener(Listener listener) {
|
|
||||||
this.listener = listener;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder connectionLimit(int connectionLimit) {
|
|
||||||
this.connectionLimit = connectionLimit;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder connectionTTL(int hours) {
|
|
||||||
this.connectionTTL = hours * HOUR;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* By default a client will send the public key when an identity is being created. On weaker devices
|
|
||||||
* this behaviour might not be desirable.
|
|
||||||
*/
|
|
||||||
public Builder doNotSendPubkeyOnIdentityCreation() {
|
|
||||||
this.sendPubkeyOnIdentityCreation = false;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BitmessageContext build() {
|
|
||||||
nonNull("inventory", inventory);
|
|
||||||
nonNull("nodeRegistry", nodeRegistry);
|
|
||||||
nonNull("networkHandler", networkHandler);
|
|
||||||
nonNull("addressRepo", addressRepo);
|
|
||||||
nonNull("messageRepo", messageRepo);
|
|
||||||
nonNull("proofOfWorkRepo", proofOfWorkRepository);
|
|
||||||
if (proofOfWorkEngine == null) {
|
|
||||||
proofOfWorkEngine = new MultiThreadedPOWEngine();
|
|
||||||
}
|
|
||||||
if (labeler == null) {
|
|
||||||
labeler = new DefaultLabeler();
|
|
||||||
}
|
|
||||||
if (customCommandHandler == null) {
|
|
||||||
customCommandHandler = new CustomCommandHandler() {
|
|
||||||
@Override
|
|
||||||
public MessagePayload handle(CustomMessage request) {
|
|
||||||
LOG.debug("Received custom request, but no custom command handler configured.");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return new BitmessageContext(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void nonNull(String name, Object o) {
|
|
||||||
if (o == null) throw new IllegalStateException(name + " must not be null");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
451
core/src/main/java/ch/dissem/bitmessage/BitmessageContext.kt
Normal file
451
core/src/main/java/ch/dissem/bitmessage/BitmessageContext.kt
Normal file
@ -0,0 +1,451 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2015 Christian Basler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.dissem.bitmessage
|
||||||
|
|
||||||
|
import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_EXTRA_BYTES
|
||||||
|
import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_NONCE_TRIALS_PER_BYTE
|
||||||
|
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||||
|
import ch.dissem.bitmessage.entity.CustomMessage
|
||||||
|
import ch.dissem.bitmessage.entity.MessagePayload
|
||||||
|
import ch.dissem.bitmessage.entity.Plaintext
|
||||||
|
import ch.dissem.bitmessage.entity.Plaintext.Status.DRAFT
|
||||||
|
import ch.dissem.bitmessage.entity.Plaintext.Type.BROADCAST
|
||||||
|
import ch.dissem.bitmessage.entity.Plaintext.Type.MSG
|
||||||
|
import ch.dissem.bitmessage.entity.payload.Broadcast
|
||||||
|
import ch.dissem.bitmessage.entity.payload.ObjectType
|
||||||
|
import ch.dissem.bitmessage.entity.payload.Pubkey.Feature
|
||||||
|
import ch.dissem.bitmessage.entity.valueobject.PrivateKey
|
||||||
|
import ch.dissem.bitmessage.exception.DecryptionFailedException
|
||||||
|
import ch.dissem.bitmessage.factory.Factory
|
||||||
|
import ch.dissem.bitmessage.ports.*
|
||||||
|
import ch.dissem.bitmessage.utils.Property
|
||||||
|
import ch.dissem.bitmessage.utils.UnixTime.HOUR
|
||||||
|
import ch.dissem.bitmessage.utils.UnixTime.MINUTE
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import java.net.InetAddress
|
||||||
|
import java.util.concurrent.CancellationException
|
||||||
|
import java.util.concurrent.ExecutionException
|
||||||
|
import kotlin.properties.Delegates
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Use this class if you want to create a Bitmessage client.
|
||||||
|
* You'll need the Builder to create a BitmessageContext, and set the following properties:
|
||||||
|
*
|
||||||
|
* * addressRepo
|
||||||
|
* * inventory
|
||||||
|
* * nodeRegistry
|
||||||
|
* * networkHandler
|
||||||
|
* * messageRepo
|
||||||
|
* * streams
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* The default implementations in the different module builds can be used.
|
||||||
|
*
|
||||||
|
* The port defaults to 8444 (the default Bitmessage port)
|
||||||
|
*/
|
||||||
|
class BitmessageContext private constructor(builder: BitmessageContext.Builder) {
|
||||||
|
|
||||||
|
private val sendPubkeyOnIdentityCreation: Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The [InternalContext] - normally you wouldn't need it,
|
||||||
|
* unless you are doing something crazy with the protocol.
|
||||||
|
*/
|
||||||
|
val internals: InternalContext
|
||||||
|
@JvmName("internals") get
|
||||||
|
|
||||||
|
val labeler: Labeler
|
||||||
|
@JvmName("labeler") get
|
||||||
|
|
||||||
|
init {
|
||||||
|
labeler = builder.labeler ?: DefaultLabeler()
|
||||||
|
internals = InternalContext(
|
||||||
|
builder.cryptography,
|
||||||
|
builder.inventory,
|
||||||
|
builder.nodeRegistry,
|
||||||
|
builder.networkHandler,
|
||||||
|
builder.addressRepo,
|
||||||
|
builder.messageRepo,
|
||||||
|
builder.proofOfWorkRepository,
|
||||||
|
builder.proofOfWorkEngine ?: MultiThreadedPOWEngine(),
|
||||||
|
builder.customCommandHandler ?: object : CustomCommandHandler {
|
||||||
|
override fun handle(request: CustomMessage): MessagePayload? {
|
||||||
|
LOG.debug("Received custom request, but no custom command handler configured.")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
builder.listener,
|
||||||
|
labeler,
|
||||||
|
builder.port,
|
||||||
|
builder.connectionTTL,
|
||||||
|
builder.connectionLimit
|
||||||
|
)
|
||||||
|
internals.proofOfWorkService.doMissingProofOfWork(30000) // TODO: this should be configurable
|
||||||
|
sendPubkeyOnIdentityCreation = builder.sendPubkeyOnIdentityCreation
|
||||||
|
(builder.listener as? Listener.WithContext)?.setContext(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
val addresses: AddressRepository = internals.addressRepository
|
||||||
|
@JvmName("addresses") get
|
||||||
|
|
||||||
|
val messages: MessageRepository = internals.messageRepository
|
||||||
|
@JvmName("messages") get
|
||||||
|
|
||||||
|
fun createIdentity(shorter: Boolean, vararg features: Feature): BitmessageAddress {
|
||||||
|
val identity = BitmessageAddress(PrivateKey(
|
||||||
|
shorter,
|
||||||
|
internals.streams[0],
|
||||||
|
NETWORK_NONCE_TRIALS_PER_BYTE,
|
||||||
|
NETWORK_EXTRA_BYTES,
|
||||||
|
*features
|
||||||
|
))
|
||||||
|
internals.addressRepository.save(identity)
|
||||||
|
if (sendPubkeyOnIdentityCreation) {
|
||||||
|
internals.sendPubkey(identity, identity.stream)
|
||||||
|
}
|
||||||
|
return identity
|
||||||
|
}
|
||||||
|
|
||||||
|
fun joinChan(passphrase: String, address: String): BitmessageAddress {
|
||||||
|
val chan = BitmessageAddress.chan(address, passphrase)
|
||||||
|
chan.alias = passphrase
|
||||||
|
internals.addressRepository.save(chan)
|
||||||
|
return chan
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createChan(passphrase: String): BitmessageAddress {
|
||||||
|
// FIXME: hardcoded stream number
|
||||||
|
val chan = BitmessageAddress.chan(1, passphrase)
|
||||||
|
internals.addressRepository.save(chan)
|
||||||
|
return chan
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createDeterministicAddresses(
|
||||||
|
passphrase: String, numberOfAddresses: Int, version: Long, stream: Long, shorter: Boolean): List<BitmessageAddress> {
|
||||||
|
val result = BitmessageAddress.deterministic(
|
||||||
|
passphrase, numberOfAddresses, version, stream, shorter)
|
||||||
|
for (i in result.indices) {
|
||||||
|
val address = result[i]
|
||||||
|
address.alias = "deterministic (" + (i + 1) + ")"
|
||||||
|
internals.addressRepository.save(address)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
fun broadcast(from: BitmessageAddress, subject: String, message: String) {
|
||||||
|
send(Plaintext(
|
||||||
|
type = BROADCAST,
|
||||||
|
from = from,
|
||||||
|
subject = subject,
|
||||||
|
body = message,
|
||||||
|
status = DRAFT
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun send(from: BitmessageAddress, to: BitmessageAddress, subject: String, message: String) {
|
||||||
|
if (from.privateKey == null) {
|
||||||
|
throw IllegalArgumentException("'From' must be an identity, i.e. have a private key.")
|
||||||
|
}
|
||||||
|
send(Plaintext(
|
||||||
|
type = MSG,
|
||||||
|
from = from,
|
||||||
|
to = to,
|
||||||
|
subject = subject,
|
||||||
|
body = message
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun send(msg: Plaintext) {
|
||||||
|
if (msg.from.privateKey == null) {
|
||||||
|
throw IllegalArgumentException("'From' must be an identity, i.e. have a private key.")
|
||||||
|
}
|
||||||
|
labeler.markAsSending(msg)
|
||||||
|
val to = msg.to
|
||||||
|
if (to != null) {
|
||||||
|
if (to.pubkey == null) {
|
||||||
|
LOG.info("Public key is missing from recipient. Requesting.")
|
||||||
|
internals.requestPubkey(to)
|
||||||
|
}
|
||||||
|
if (to.pubkey == null) {
|
||||||
|
internals.messageRepository.save(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (to == null || to.pubkey != null) {
|
||||||
|
LOG.info("Sending message.")
|
||||||
|
internals.messageRepository.save(msg)
|
||||||
|
if (msg.type == MSG) {
|
||||||
|
internals.send(msg)
|
||||||
|
} else {
|
||||||
|
internals.send(
|
||||||
|
msg.from,
|
||||||
|
to,
|
||||||
|
Factory.getBroadcast(msg),
|
||||||
|
msg.ttl
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun startup() {
|
||||||
|
internals.networkHandler.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun shutdown() {
|
||||||
|
internals.networkHandler.stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param host a trusted node that must be reliable (it's used for every synchronization)
|
||||||
|
* *
|
||||||
|
* @param port of the trusted host, default is 8444
|
||||||
|
* *
|
||||||
|
* @param timeoutInSeconds synchronization should end no later than about 5 seconds after the timeout elapsed, even
|
||||||
|
* * if not all objects were fetched
|
||||||
|
* *
|
||||||
|
* @param wait waits for the synchronization thread to finish
|
||||||
|
*/
|
||||||
|
fun synchronize(host: InetAddress, port: Int, timeoutInSeconds: Long, wait: Boolean) {
|
||||||
|
val future = internals.networkHandler.synchronize(host, port, timeoutInSeconds)
|
||||||
|
if (wait) {
|
||||||
|
try {
|
||||||
|
future.get()
|
||||||
|
} catch (e: InterruptedException) {
|
||||||
|
LOG.info("Thread was interrupted. Trying to shut down synchronization and returning.")
|
||||||
|
future.cancel(true)
|
||||||
|
} catch (e: CancellationException) {
|
||||||
|
LOG.debug(e.message, e)
|
||||||
|
} catch (e: ExecutionException) {
|
||||||
|
LOG.debug(e.message, e)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a custom message to a specific node (that should implement handling for this message type) and returns
|
||||||
|
* the response, which in turn is expected to be a [CustomMessage].
|
||||||
|
|
||||||
|
* @param server the node's address
|
||||||
|
* *
|
||||||
|
* @param port the node's port
|
||||||
|
* *
|
||||||
|
* @param request the request
|
||||||
|
* *
|
||||||
|
* @return the response
|
||||||
|
*/
|
||||||
|
fun send(server: InetAddress, port: Int, request: CustomMessage): CustomMessage {
|
||||||
|
return internals.networkHandler.send(server, port, request)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes expired objects from the inventory. You should call this method regularly,
|
||||||
|
* e.g. daily and on each shutdown.
|
||||||
|
*/
|
||||||
|
fun cleanup() {
|
||||||
|
internals.inventory.cleanup()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends messages again whose time to live expired without being acknowledged. (And whose
|
||||||
|
* recipient is expected to send acknowledgements.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* You should call this method regularly, but be aware of the following:
|
||||||
|
*
|
||||||
|
* * As messages might be sent, POW will be done. It is therefore not advised to
|
||||||
|
* call it on shutdown.
|
||||||
|
* * It shouldn't be called right after startup, as it's possible the missing
|
||||||
|
* acknowledgement was sent while the client was offline.
|
||||||
|
* * Other than that, the call isn't expensive as long as there is no message
|
||||||
|
* to send, so it might be a good idea to just call it every few minutes.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
fun resendUnacknowledgedMessages() {
|
||||||
|
internals.resendUnacknowledged()
|
||||||
|
}
|
||||||
|
|
||||||
|
val isRunning: Boolean
|
||||||
|
get() = internals.networkHandler.isRunning
|
||||||
|
|
||||||
|
fun addContact(contact: BitmessageAddress) {
|
||||||
|
internals.addressRepository.save(contact)
|
||||||
|
if (contact.pubkey == null) {
|
||||||
|
internals.addressRepository.getAddress(contact.address)?.let {
|
||||||
|
if (it.pubkey == null) {
|
||||||
|
internals.requestPubkey(contact)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addSubscribtion(address: BitmessageAddress) {
|
||||||
|
address.isSubscribed = true
|
||||||
|
internals.addressRepository.save(address)
|
||||||
|
tryToFindBroadcastsForAddress(address)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun tryToFindBroadcastsForAddress(address: BitmessageAddress) {
|
||||||
|
for (`object` in internals.inventory.getObjects(address.stream, Broadcast.getVersion(address), ObjectType.BROADCAST)) {
|
||||||
|
try {
|
||||||
|
val broadcast = `object`.payload as Broadcast
|
||||||
|
broadcast.decrypt(address)
|
||||||
|
// This decrypts it twice, but on the other hand it doesn't try to decrypt the objects with
|
||||||
|
// other subscriptions and the interface stays as simple as possible.
|
||||||
|
internals.networkListener.receive(`object`)
|
||||||
|
} catch (ignore: DecryptionFailedException) {
|
||||||
|
} catch (e: Exception) {
|
||||||
|
LOG.debug(e.message, e)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun status(): Property {
|
||||||
|
return Property("status",
|
||||||
|
internals.networkHandler.networkStatus,
|
||||||
|
Property("unacknowledged", internals.messageRepository.findMessagesToResend().size)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Listener {
|
||||||
|
fun receive(plaintext: Plaintext)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A message listener that needs a [BitmessageContext], i.e. for implementing some sort of chat bot.
|
||||||
|
*/
|
||||||
|
interface WithContext : Listener {
|
||||||
|
fun setContext(ctx: BitmessageContext)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Builder {
|
||||||
|
internal var port = 8444
|
||||||
|
internal var inventory by Delegates.notNull<Inventory>()
|
||||||
|
internal var nodeRegistry by Delegates.notNull<NodeRegistry>()
|
||||||
|
internal var networkHandler by Delegates.notNull<NetworkHandler>()
|
||||||
|
internal var addressRepo by Delegates.notNull<AddressRepository>()
|
||||||
|
internal var messageRepo by Delegates.notNull<MessageRepository>()
|
||||||
|
internal var proofOfWorkRepository by Delegates.notNull<ProofOfWorkRepository>()
|
||||||
|
internal var proofOfWorkEngine: ProofOfWorkEngine? = null
|
||||||
|
internal var cryptography by Delegates.notNull<Cryptography>()
|
||||||
|
internal var customCommandHandler: CustomCommandHandler? = null
|
||||||
|
internal var labeler: Labeler? = null
|
||||||
|
internal var listener by Delegates.notNull<Listener>()
|
||||||
|
internal var connectionLimit = 150
|
||||||
|
internal var connectionTTL = 30 * MINUTE
|
||||||
|
internal var sendPubkeyOnIdentityCreation = true
|
||||||
|
|
||||||
|
fun port(port: Int): Builder {
|
||||||
|
this.port = port
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun inventory(inventory: Inventory): Builder {
|
||||||
|
this.inventory = inventory
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun nodeRegistry(nodeRegistry: NodeRegistry): Builder {
|
||||||
|
this.nodeRegistry = nodeRegistry
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun networkHandler(networkHandler: NetworkHandler): Builder {
|
||||||
|
this.networkHandler = networkHandler
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addressRepo(addressRepo: AddressRepository): Builder {
|
||||||
|
this.addressRepo = addressRepo
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun messageRepo(messageRepo: MessageRepository): Builder {
|
||||||
|
this.messageRepo = messageRepo
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun powRepo(proofOfWorkRepository: ProofOfWorkRepository): Builder {
|
||||||
|
this.proofOfWorkRepository = proofOfWorkRepository
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cryptography(cryptography: Cryptography): Builder {
|
||||||
|
this.cryptography = cryptography
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun customCommandHandler(handler: CustomCommandHandler): Builder {
|
||||||
|
this.customCommandHandler = handler
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun proofOfWorkEngine(proofOfWorkEngine: ProofOfWorkEngine): Builder {
|
||||||
|
this.proofOfWorkEngine = proofOfWorkEngine
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun labeler(labeler: Labeler): Builder {
|
||||||
|
this.labeler = labeler
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun listener(listener: Listener): Builder {
|
||||||
|
this.listener = listener
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun connectionLimit(connectionLimit: Int): Builder {
|
||||||
|
this.connectionLimit = connectionLimit
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun connectionTTL(hours: Int): Builder {
|
||||||
|
this.connectionTTL = hours * HOUR
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* By default a client will send the public key when an identity is being created. On weaker devices
|
||||||
|
* this behaviour might not be desirable.
|
||||||
|
*/
|
||||||
|
fun doNotSendPubkeyOnIdentityCreation(): Builder {
|
||||||
|
this.sendPubkeyOnIdentityCreation = false
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun build(): BitmessageContext {
|
||||||
|
nonNull("inventory", inventory)
|
||||||
|
nonNull("nodeRegistry", nodeRegistry)
|
||||||
|
nonNull("networkHandler", networkHandler)
|
||||||
|
nonNull("addressRepo", addressRepo)
|
||||||
|
nonNull("messageRepo", messageRepo)
|
||||||
|
nonNull("proofOfWorkRepo", proofOfWorkRepository)
|
||||||
|
return BitmessageContext(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun nonNull(name: String, o: Any?) {
|
||||||
|
if (o == null) throw IllegalStateException(name + " must not be null")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmField val CURRENT_VERSION = 3
|
||||||
|
private val LOG = LoggerFactory.getLogger(BitmessageContext::class.java)
|
||||||
|
}
|
||||||
|
}
|
@ -16,8 +16,7 @@
|
|||||||
|
|
||||||
package ch.dissem.bitmessage.entity
|
package ch.dissem.bitmessage.entity
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.Plaintext.Encoding.EXTENDED
|
import ch.dissem.bitmessage.entity.Plaintext.Encoding.*
|
||||||
import ch.dissem.bitmessage.entity.Plaintext.Encoding.SIMPLE
|
|
||||||
import ch.dissem.bitmessage.entity.payload.Msg
|
import ch.dissem.bitmessage.entity.payload.Msg
|
||||||
import ch.dissem.bitmessage.entity.payload.Pubkey.Feature
|
import ch.dissem.bitmessage.entity.payload.Pubkey.Feature
|
||||||
import ch.dissem.bitmessage.entity.valueobject.ExtendedEncoding
|
import ch.dissem.bitmessage.entity.valueobject.ExtendedEncoding
|
||||||
@ -36,6 +35,13 @@ import java.util.*
|
|||||||
import java.util.Collections
|
import java.util.Collections
|
||||||
import kotlin.collections.HashSet
|
import kotlin.collections.HashSet
|
||||||
|
|
||||||
|
fun message(encoding: Plaintext.Encoding, subject: String, body: String): ByteArray = when (encoding) {
|
||||||
|
SIMPLE -> "Subject:$subject\nBody:$body".toByteArray()
|
||||||
|
EXTENDED -> Message.Builder().subject(subject).body(body).build().zip()
|
||||||
|
TRIVIAL -> (subject+body).toByteArray()
|
||||||
|
IGNORE -> ByteArray(0)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The unencrypted message to be sent by 'msg' or 'broadcast'.
|
* The unencrypted message to be sent by 'msg' or 'broadcast'.
|
||||||
*/
|
*/
|
||||||
@ -182,9 +188,39 @@ class Plaintext private constructor(
|
|||||||
status
|
status
|
||||||
)
|
)
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
type: Type,
|
||||||
|
from: BitmessageAddress,
|
||||||
|
to: BitmessageAddress? = null,
|
||||||
|
encoding: Encoding = SIMPLE,
|
||||||
|
subject: String,
|
||||||
|
body: String,
|
||||||
|
ackData: ByteArray = cryptography().randomBytes(Msg.ACK_LENGTH),
|
||||||
|
conversationId: UUID = UUID.randomUUID(),
|
||||||
|
ttl: Long = TTL.msg,
|
||||||
|
labels: MutableSet<Label> = HashSet(),
|
||||||
|
status: Status = Status.DRAFT
|
||||||
|
) : this(
|
||||||
|
type,
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
encoding.code,
|
||||||
|
message(encoding, subject, body),
|
||||||
|
ackData,
|
||||||
|
lazy { Factory.createAck(from, ackData, ttl) },
|
||||||
|
conversationId,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
ttl,
|
||||||
|
labels,
|
||||||
|
status
|
||||||
|
)
|
||||||
|
|
||||||
constructor(builder: Builder) : this(
|
constructor(builder: Builder) : this(
|
||||||
builder.type,
|
builder.type,
|
||||||
builder.from!!,
|
builder.from ?: throw IllegalStateException("sender identity not set"),
|
||||||
builder.to,
|
builder.to,
|
||||||
builder.encoding,
|
builder.encoding,
|
||||||
builder.message,
|
builder.message,
|
||||||
|
@ -19,6 +19,7 @@ package ch.dissem.bitmessage.ports
|
|||||||
import ch.dissem.bitmessage.InternalContext
|
import ch.dissem.bitmessage.InternalContext
|
||||||
import ch.dissem.bitmessage.entity.Plaintext
|
import ch.dissem.bitmessage.entity.Plaintext
|
||||||
import ch.dissem.bitmessage.entity.Plaintext.Status.*
|
import ch.dissem.bitmessage.entity.Plaintext.Status.*
|
||||||
|
import ch.dissem.bitmessage.entity.Plaintext.Type.BROADCAST
|
||||||
import ch.dissem.bitmessage.entity.valueobject.Label
|
import ch.dissem.bitmessage.entity.valueobject.Label
|
||||||
|
|
||||||
open class DefaultLabeler : Labeler {
|
open class DefaultLabeler : Labeler {
|
||||||
@ -26,7 +27,7 @@ open class DefaultLabeler : Labeler {
|
|||||||
|
|
||||||
override fun setLabels(msg: Plaintext) {
|
override fun setLabels(msg: Plaintext) {
|
||||||
msg.status = RECEIVED
|
msg.status = RECEIVED
|
||||||
if (msg.type === Plaintext.Type.BROADCAST) {
|
if (msg.type == BROADCAST) {
|
||||||
msg.addLabels(ctx.messageRepository.getLabels(Label.Type.INBOX, Label.Type.BROADCAST, Label.Type.UNREAD))
|
msg.addLabels(ctx.messageRepository.getLabels(Label.Type.INBOX, Label.Type.BROADCAST, Label.Type.UNREAD))
|
||||||
} else {
|
} else {
|
||||||
msg.addLabels(ctx.messageRepository.getLabels(Label.Type.INBOX, Label.Type.UNREAD))
|
msg.addLabels(ctx.messageRepository.getLabels(Label.Type.INBOX, Label.Type.UNREAD))
|
||||||
|
@ -114,12 +114,12 @@ class BitmessageContextTest {
|
|||||||
@Test
|
@Test
|
||||||
fun `ensure contact is saved and pubkey requested`() {
|
fun `ensure contact is saved and pubkey requested`() {
|
||||||
val contact = BitmessageAddress("BM-opWQhvk9xtMFvQA2Kvetedpk8LkbraWHT")
|
val contact = BitmessageAddress("BM-opWQhvk9xtMFvQA2Kvetedpk8LkbraWHT")
|
||||||
whenever(ctx.addresses().getAddress(contact.address)).thenReturn(contact)
|
whenever(ctx.addresses.getAddress(contact.address)).thenReturn(contact)
|
||||||
|
|
||||||
ctx.addContact(contact)
|
ctx.addContact(contact)
|
||||||
|
|
||||||
verify(ctx.addresses(), timeout(1000).atLeastOnce()).save(contact)
|
verify(ctx.addresses, timeout(1000).atLeastOnce()).save(contact)
|
||||||
verify(ctx.internals().proofOfWorkEngine, timeout(1000)).calculateNonce(any(), any(), any())
|
verify(ctx.internals.proofOfWorkEngine, timeout(1000)).calculateNonce(any(), any(), any())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -131,8 +131,8 @@ class BitmessageContextTest {
|
|||||||
|
|
||||||
ctx.addContact(contact)
|
ctx.addContact(contact)
|
||||||
|
|
||||||
verify(ctx.addresses(), times(1)).save(contact)
|
verify(ctx.addresses, times(1)).save(contact)
|
||||||
verify(ctx.internals().proofOfWorkEngine, never()).calculateNonce(any(), any(), any())
|
verify(ctx.internals.proofOfWorkEngine, never()).calculateNonce(any(), any(), any())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -150,12 +150,12 @@ class BitmessageContextTest {
|
|||||||
)
|
)
|
||||||
val contact = BitmessageAddress("BM-opWQhvk9xtMFvQA2Kvetedpk8LkbraWHT")
|
val contact = BitmessageAddress("BM-opWQhvk9xtMFvQA2Kvetedpk8LkbraWHT")
|
||||||
|
|
||||||
whenever(ctx.addresses().getAddress(contact.address)).thenReturn(contact)
|
whenever(ctx.addresses.getAddress(contact.address)).thenReturn(contact)
|
||||||
|
|
||||||
ctx.addContact(contact)
|
ctx.addContact(contact)
|
||||||
|
|
||||||
verify(ctx.addresses(), atLeastOnce()).save(contact)
|
verify(ctx.addresses, atLeastOnce()).save(contact)
|
||||||
verify(ctx.internals().proofOfWorkEngine, never()).calculateNonce(any(), any(), any())
|
verify(ctx.internals.proofOfWorkEngine, never()).calculateNonce(any(), any(), any())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -174,12 +174,12 @@ class BitmessageContextTest {
|
|||||||
val contact = BitmessageAddress("BM-2cXxfcSetKnbHJX2Y85rSkaVpsdNUZ5q9h")
|
val contact = BitmessageAddress("BM-2cXxfcSetKnbHJX2Y85rSkaVpsdNUZ5q9h")
|
||||||
val stored = BitmessageAddress(contact.address)
|
val stored = BitmessageAddress(contact.address)
|
||||||
stored.alias = "Test"
|
stored.alias = "Test"
|
||||||
whenever(ctx.addresses().getAddress(contact.address)).thenReturn(stored)
|
whenever(ctx.addresses.getAddress(contact.address)).thenReturn(stored)
|
||||||
|
|
||||||
ctx.addContact(contact)
|
ctx.addContact(contact)
|
||||||
|
|
||||||
verify(ctx.addresses(), atLeastOnce()).save(any())
|
verify(ctx.addresses, atLeastOnce()).save(any())
|
||||||
verify(ctx.internals().proofOfWorkEngine, never()).calculateNonce(any(), any(), any())
|
verify(ctx.internals.proofOfWorkEngine, never()).calculateNonce(any(), any(), any())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -191,12 +191,12 @@ class BitmessageContextTest {
|
|||||||
"V5Broadcast.payload"
|
"V5Broadcast.payload"
|
||||||
)
|
)
|
||||||
|
|
||||||
whenever(ctx.addresses().getSubscriptions(any())).thenReturn(listOf(address))
|
whenever(ctx.addresses.getSubscriptions(any())).thenReturn(listOf(address))
|
||||||
ctx.addSubscribtion(address)
|
ctx.addSubscribtion(address)
|
||||||
|
|
||||||
verify(ctx.addresses(), atLeastOnce()).save(address)
|
verify(ctx.addresses, atLeastOnce()).save(address)
|
||||||
assertThat(address.isSubscribed, `is`(true))
|
assertThat(address.isSubscribed, `is`(true))
|
||||||
verify(ctx.internals().inventory).getObjects(eq(address.stream), any(), any())
|
verify(ctx.internals.inventory).getObjects(eq(address.stream), any(), any())
|
||||||
verify(listener).receive(any())
|
verify(listener).receive(any())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -210,9 +210,9 @@ class BitmessageContextTest {
|
|||||||
ctx.send(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"), TestUtils.loadContact(),
|
ctx.send(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"), TestUtils.loadContact(),
|
||||||
"Subject", "Message")
|
"Subject", "Message")
|
||||||
assertEquals(2, testPowRepo.added)
|
assertEquals(2, testPowRepo.added)
|
||||||
verify(ctx.internals().proofOfWorkRepository, timeout(10000).atLeastOnce())
|
verify(ctx.internals.proofOfWorkRepository, timeout(10000).atLeastOnce())
|
||||||
.putObject(argThat { payload.type == ObjectType.MSG }, eq(1000L), eq(1000L))
|
.putObject(argThat { payload.type == ObjectType.MSG }, eq(1000L), eq(1000L))
|
||||||
verify(ctx.messages(), timeout(10000).atLeastOnce()).save(argThat { type == Type.MSG })
|
verify(ctx.messages, timeout(10000).atLeastOnce()).save(argThat { type == Type.MSG })
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -222,7 +222,7 @@ class BitmessageContextTest {
|
|||||||
"Subject", "Message")
|
"Subject", "Message")
|
||||||
verify(testPowRepo, timeout(10000).atLeastOnce())
|
verify(testPowRepo, timeout(10000).atLeastOnce())
|
||||||
.putObject(argThat { payload.type == ObjectType.GET_PUBKEY }, eq(1000L), eq(1000L))
|
.putObject(argThat { payload.type == ObjectType.GET_PUBKEY }, eq(1000L), eq(1000L))
|
||||||
verify(ctx.messages(), timeout(10000).atLeastOnce()).save(argThat { type == Type.MSG })
|
verify(ctx.messages, timeout(10000).atLeastOnce()).save(argThat { type == Type.MSG })
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = IllegalArgumentException::class)
|
@Test(expected = IllegalArgumentException::class)
|
||||||
@ -236,11 +236,11 @@ class BitmessageContextTest {
|
|||||||
fun `ensure broadcast is sent`() {
|
fun `ensure broadcast is sent`() {
|
||||||
ctx.broadcast(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"),
|
ctx.broadcast(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"),
|
||||||
"Subject", "Message")
|
"Subject", "Message")
|
||||||
verify(ctx.internals().proofOfWorkRepository, timeout(10000).atLeastOnce())
|
verify(ctx.internals.proofOfWorkRepository, timeout(10000).atLeastOnce())
|
||||||
.putObject(argThat { payload.type == ObjectType.BROADCAST }, eq(1000L), eq(1000L))
|
.putObject(argThat { payload.type == ObjectType.BROADCAST }, eq(1000L), eq(1000L))
|
||||||
verify(ctx.internals().proofOfWorkEngine)
|
verify(ctx.internals.proofOfWorkEngine)
|
||||||
.calculateNonce(any(), any(), any())
|
.calculateNonce(any(), any(), any())
|
||||||
verify(ctx.messages(), timeout(10000).atLeastOnce()).save(argThat { type == Type.BROADCAST })
|
verify(ctx.messages, timeout(10000).atLeastOnce()).save(argThat { type == Type.BROADCAST })
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = IllegalArgumentException::class)
|
@Test(expected = IllegalArgumentException::class)
|
||||||
@ -311,9 +311,9 @@ class BitmessageContextTest {
|
|||||||
.to(TestUtils.loadContact())
|
.to(TestUtils.loadContact())
|
||||||
.build()
|
.build()
|
||||||
assertTrue(plaintext.to!!.has(Pubkey.Feature.DOES_ACK))
|
assertTrue(plaintext.to!!.has(Pubkey.Feature.DOES_ACK))
|
||||||
whenever(ctx.messages().findMessagesToResend()).thenReturn(listOf(plaintext))
|
whenever(ctx.messages.findMessagesToResend()).thenReturn(listOf(plaintext))
|
||||||
whenever(ctx.messages().getMessage(any<ByteArray>())).thenReturn(plaintext)
|
whenever(ctx.messages.getMessage(any<ByteArray>())).thenReturn(plaintext)
|
||||||
ctx.resendUnacknowledgedMessages()
|
ctx.resendUnacknowledgedMessages()
|
||||||
verify(ctx.labeler(), timeout(1000).times(1)).markAsSent(eq(plaintext))
|
verify(ctx.labeler, timeout(1000).times(1)).markAsSent(eq(plaintext))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,4 +15,5 @@ dependencies {
|
|||||||
compile 'org.bouncycastle:bcprov-jdk15on:1.56'
|
compile 'org.bouncycastle:bcprov-jdk15on:1.56'
|
||||||
testCompile 'junit:junit:4.12'
|
testCompile 'junit:junit:4.12'
|
||||||
testCompile 'org.mockito:mockito-core:2.7.21'
|
testCompile 'org.mockito:mockito-core:2.7.21'
|
||||||
|
testCompile project(path: ':core', configuration: 'testArtifacts')
|
||||||
}
|
}
|
||||||
|
@ -1,172 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2017 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.security;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.InternalContext;
|
|
||||||
import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography;
|
|
||||||
import ch.dissem.bitmessage.entity.ObjectMessage;
|
|
||||||
import ch.dissem.bitmessage.entity.payload.GenericPayload;
|
|
||||||
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
|
|
||||||
import ch.dissem.bitmessage.exception.InsufficientProofOfWorkException;
|
|
||||||
import ch.dissem.bitmessage.ports.MultiThreadedPOWEngine;
|
|
||||||
import ch.dissem.bitmessage.ports.ProofOfWorkEngine;
|
|
||||||
import ch.dissem.bitmessage.utils.CallbackWaiter;
|
|
||||||
import ch.dissem.bitmessage.utils.Singleton;
|
|
||||||
import ch.dissem.bitmessage.utils.UnixTime;
|
|
||||||
import org.junit.BeforeClass;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import javax.xml.bind.DatatypeConverter;
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.utils.UnixTime.DAY;
|
|
||||||
import static ch.dissem.bitmessage.utils.UnixTime.MINUTE;
|
|
||||||
import static org.hamcrest.CoreMatchers.is;
|
|
||||||
import static org.junit.Assert.*;
|
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Christian Basler
|
|
||||||
*/
|
|
||||||
public class CryptographyTest {
|
|
||||||
public static final byte[] TEST_VALUE = "teststring".getBytes();
|
|
||||||
public static final byte[] TEST_SHA1 = DatatypeConverter.parseHexBinary(""
|
|
||||||
+ "b8473b86d4c2072ca9b08bd28e373e8253e865c4");
|
|
||||||
public static final byte[] TEST_SHA512 = DatatypeConverter.parseHexBinary(""
|
|
||||||
+ "6253b39071e5df8b5098f59202d414c37a17d6a38a875ef5f8c7d89b0212b028"
|
|
||||||
+ "692d3d2090ce03ae1de66c862fa8a561e57ed9eb7935ce627344f742c0931d72");
|
|
||||||
public static final byte[] TEST_RIPEMD160 = DatatypeConverter.parseHexBinary(""
|
|
||||||
+ "cd566972b5e50104011a92b59fa8e0b1234851ae");
|
|
||||||
|
|
||||||
private static BouncyCryptography crypto;
|
|
||||||
|
|
||||||
@BeforeClass
|
|
||||||
public static void setUp() {
|
|
||||||
crypto = new BouncyCryptography();
|
|
||||||
Singleton.initialize(crypto);
|
|
||||||
InternalContext ctx = mock(InternalContext.class);
|
|
||||||
when(ctx.getProofOfWorkEngine()).thenReturn(new MultiThreadedPOWEngine());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testRipemd160() {
|
|
||||||
assertArrayEquals(TEST_RIPEMD160, crypto.ripemd160(TEST_VALUE));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSha1() {
|
|
||||||
assertArrayEquals(TEST_SHA1, crypto.sha1(TEST_VALUE));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSha512() {
|
|
||||||
assertArrayEquals(TEST_SHA512, crypto.sha512(TEST_VALUE));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testChaining() {
|
|
||||||
assertArrayEquals(TEST_SHA512, crypto.sha512("test".getBytes(), "string".getBytes()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void ensureDoubleHashYieldsSameResultAsHashOfHash() {
|
|
||||||
assertArrayEquals(crypto.sha512(TEST_SHA512), crypto.doubleSha512(TEST_VALUE));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(expected = IOException.class)
|
|
||||||
public void ensureExceptionForInsufficientProofOfWork() throws IOException {
|
|
||||||
ObjectMessage objectMessage = new ObjectMessage.Builder()
|
|
||||||
.nonce(new byte[8])
|
|
||||||
.expiresTime(UnixTime.now() + 28 * DAY)
|
|
||||||
.objectType(0)
|
|
||||||
.payload(GenericPayload.Companion.read(0, 1, new ByteArrayInputStream(new byte[0]), 0))
|
|
||||||
.build();
|
|
||||||
crypto.checkProofOfWork(objectMessage, 1000, 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testDoProofOfWork() throws Exception {
|
|
||||||
ObjectMessage objectMessage = new ObjectMessage.Builder()
|
|
||||||
.nonce(new byte[8])
|
|
||||||
.expiresTime(UnixTime.now() + 2 * MINUTE)
|
|
||||||
.objectType(0)
|
|
||||||
.payload(GenericPayload.Companion.read(0, 1, new ByteArrayInputStream(new byte[0]), 0))
|
|
||||||
.build();
|
|
||||||
final CallbackWaiter<byte[]> waiter = new CallbackWaiter<>();
|
|
||||||
crypto.doProofOfWork(objectMessage, 1000, 1000,
|
|
||||||
new ProofOfWorkEngine.Callback() {
|
|
||||||
@Override
|
|
||||||
public void onNonceCalculated(byte[] initialHash, byte[] nonce) {
|
|
||||||
waiter.setValue(nonce);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
objectMessage.setNonce(waiter.waitForValue());
|
|
||||||
try {
|
|
||||||
crypto.checkProofOfWork(objectMessage, 1000, 1000);
|
|
||||||
} catch (InsufficientProofOfWorkException e) {
|
|
||||||
fail(e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void ensureEncryptionAndDecryptionWorks() {
|
|
||||||
byte[] data = crypto.randomBytes(100);
|
|
||||||
byte[] key_e = crypto.randomBytes(32);
|
|
||||||
byte[] iv = crypto.randomBytes(16);
|
|
||||||
byte[] encrypted = crypto.crypt(true, data, key_e, iv);
|
|
||||||
byte[] decrypted = crypto.crypt(false, encrypted, key_e, iv);
|
|
||||||
assertArrayEquals(data, decrypted);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(expected = IllegalArgumentException.class)
|
|
||||||
public void ensureDecryptionFailsWithInvalidCypherText() {
|
|
||||||
byte[] data = crypto.randomBytes(128);
|
|
||||||
byte[] key_e = crypto.randomBytes(32);
|
|
||||||
byte[] iv = crypto.randomBytes(16);
|
|
||||||
crypto.crypt(false, data, key_e, iv);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testMultiplication() {
|
|
||||||
byte[] a = crypto.randomBytes(PrivateKey.PRIVATE_KEY_SIZE);
|
|
||||||
byte[] A = crypto.createPublicKey(a);
|
|
||||||
|
|
||||||
byte[] b = crypto.randomBytes(PrivateKey.PRIVATE_KEY_SIZE);
|
|
||||||
byte[] B = crypto.createPublicKey(b);
|
|
||||||
|
|
||||||
assertArrayEquals(crypto.multiply(A, b), crypto.multiply(B, a));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void ensureSignatureIsValid() {
|
|
||||||
byte[] data = crypto.randomBytes(100);
|
|
||||||
PrivateKey privateKey = new PrivateKey(false, 1, 1000, 1000);
|
|
||||||
byte[] signature = crypto.getSignature(data, privateKey);
|
|
||||||
assertThat(crypto.isSignatureValid(data, signature, privateKey.getPubkey()), is(true));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void ensureSignatureIsInvalidForTemperedData() {
|
|
||||||
byte[] data = crypto.randomBytes(100);
|
|
||||||
PrivateKey privateKey = new PrivateKey(false, 1, 1000, 1000);
|
|
||||||
byte[] signature = crypto.getSignature(data, privateKey);
|
|
||||||
data[0]++;
|
|
||||||
assertThat(crypto.isSignatureValid(data, signature, privateKey.getPubkey()), is(false));
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,172 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Christian Basler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.dissem.bitmessage.security
|
||||||
|
|
||||||
|
import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography
|
||||||
|
import ch.dissem.bitmessage.entity.ObjectMessage
|
||||||
|
import ch.dissem.bitmessage.entity.payload.GenericPayload
|
||||||
|
import ch.dissem.bitmessage.entity.valueobject.PrivateKey
|
||||||
|
import ch.dissem.bitmessage.exception.InsufficientProofOfWorkException
|
||||||
|
import ch.dissem.bitmessage.ports.MultiThreadedPOWEngine
|
||||||
|
import ch.dissem.bitmessage.ports.ProofOfWorkEngine
|
||||||
|
import ch.dissem.bitmessage.utils.CallbackWaiter
|
||||||
|
import ch.dissem.bitmessage.utils.Singleton
|
||||||
|
import ch.dissem.bitmessage.utils.TestUtils
|
||||||
|
import ch.dissem.bitmessage.utils.UnixTime
|
||||||
|
import ch.dissem.bitmessage.utils.UnixTime.DAY
|
||||||
|
import ch.dissem.bitmessage.utils.UnixTime.MINUTE
|
||||||
|
import ch.dissem.bitmessage.utils.UnixTime.now
|
||||||
|
import org.hamcrest.CoreMatchers.`is`
|
||||||
|
import org.junit.Assert.*
|
||||||
|
import org.junit.Test
|
||||||
|
import java.io.ByteArrayInputStream
|
||||||
|
import java.io.IOException
|
||||||
|
import javax.xml.bind.DatatypeConverter
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Christian Basler
|
||||||
|
*/
|
||||||
|
class CryptographyTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testRipemd160() {
|
||||||
|
assertArrayEquals(TEST_RIPEMD160, crypto.ripemd160(TEST_VALUE))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testSha1() {
|
||||||
|
assertArrayEquals(TEST_SHA1, crypto.sha1(TEST_VALUE))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testSha512() {
|
||||||
|
assertArrayEquals(TEST_SHA512, crypto.sha512(TEST_VALUE))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testChaining() {
|
||||||
|
assertArrayEquals(TEST_SHA512, crypto.sha512("test".toByteArray(), "string".toByteArray()))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun ensureDoubleHashYieldsSameResultAsHashOfHash() {
|
||||||
|
assertArrayEquals(crypto.sha512(TEST_SHA512), crypto.doubleSha512(TEST_VALUE))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IOException::class)
|
||||||
|
fun ensureExceptionForInsufficientProofOfWork() {
|
||||||
|
val objectMessage = ObjectMessage.Builder()
|
||||||
|
.nonce(ByteArray(8))
|
||||||
|
.expiresTime(UnixTime.now + 28 * DAY)
|
||||||
|
.objectType(0)
|
||||||
|
.payload(GenericPayload.read(0, 1, ByteArrayInputStream(ByteArray(0)), 0))
|
||||||
|
.build()
|
||||||
|
crypto.checkProofOfWork(objectMessage, 1000, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testDoProofOfWork() {
|
||||||
|
TestUtils.mockedInternalContext(
|
||||||
|
cryptography = crypto,
|
||||||
|
proofOfWorkEngine = MultiThreadedPOWEngine()
|
||||||
|
)
|
||||||
|
val objectMessage = ObjectMessage(
|
||||||
|
nonce = ByteArray(8),
|
||||||
|
expiresTime = now + 2 * MINUTE,
|
||||||
|
type = 0,
|
||||||
|
payload = GenericPayload.read(0, 1, ByteArrayInputStream(ByteArray(0)), 0),
|
||||||
|
version = 0,
|
||||||
|
stream = 1
|
||||||
|
)
|
||||||
|
val waiter = CallbackWaiter<ByteArray>()
|
||||||
|
crypto.doProofOfWork(objectMessage, 1000, 1000,
|
||||||
|
object : ProofOfWorkEngine.Callback {
|
||||||
|
override fun onNonceCalculated(initialHash: ByteArray, nonce: ByteArray) {
|
||||||
|
waiter.setValue(nonce)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
objectMessage.nonce = waiter.waitForValue()
|
||||||
|
try {
|
||||||
|
crypto.checkProofOfWork(objectMessage, 1000, 1000)
|
||||||
|
} catch (e: InsufficientProofOfWorkException) {
|
||||||
|
fail(e.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun ensureEncryptionAndDecryptionWorks() {
|
||||||
|
val data = crypto.randomBytes(100)
|
||||||
|
val key_e = crypto.randomBytes(32)
|
||||||
|
val iv = crypto.randomBytes(16)
|
||||||
|
val encrypted = crypto.crypt(true, data, key_e, iv)
|
||||||
|
val decrypted = crypto.crypt(false, encrypted, key_e, iv)
|
||||||
|
assertArrayEquals(data, decrypted)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException::class)
|
||||||
|
fun ensureDecryptionFailsWithInvalidCypherText() {
|
||||||
|
val data = crypto.randomBytes(128)
|
||||||
|
val key_e = crypto.randomBytes(32)
|
||||||
|
val iv = crypto.randomBytes(16)
|
||||||
|
crypto.crypt(false, data, key_e, iv)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testMultiplication() {
|
||||||
|
val a = crypto.randomBytes(PrivateKey.PRIVATE_KEY_SIZE)
|
||||||
|
val A = crypto.createPublicKey(a)
|
||||||
|
|
||||||
|
val b = crypto.randomBytes(PrivateKey.PRIVATE_KEY_SIZE)
|
||||||
|
val B = crypto.createPublicKey(b)
|
||||||
|
|
||||||
|
assertArrayEquals(crypto.multiply(A, b), crypto.multiply(B, a))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun ensureSignatureIsValid() {
|
||||||
|
val data = crypto.randomBytes(100)
|
||||||
|
val privateKey = PrivateKey(false, 1, 1000, 1000)
|
||||||
|
val signature = crypto.getSignature(data, privateKey)
|
||||||
|
assertThat(crypto.isSignatureValid(data, signature, privateKey.pubkey), `is`(true))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun ensureSignatureIsInvalidForTemperedData() {
|
||||||
|
val data = crypto.randomBytes(100)
|
||||||
|
val privateKey = PrivateKey(false, 1, 1000, 1000)
|
||||||
|
val signature = crypto.getSignature(data, privateKey)
|
||||||
|
data[0]++
|
||||||
|
assertThat(crypto.isSignatureValid(data, signature, privateKey.pubkey), `is`(false))
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val TEST_VALUE = "teststring".toByteArray()
|
||||||
|
val TEST_SHA1 = DatatypeConverter.parseHexBinary(""
|
||||||
|
+ "b8473b86d4c2072ca9b08bd28e373e8253e865c4")
|
||||||
|
val TEST_SHA512 = DatatypeConverter.parseHexBinary(""
|
||||||
|
+ "6253b39071e5df8b5098f59202d414c37a17d6a38a875ef5f8c7d89b0212b028"
|
||||||
|
+ "692d3d2090ce03ae1de66c862fa8a561e57ed9eb7935ce627344f742c0931d72")
|
||||||
|
val TEST_RIPEMD160 = DatatypeConverter.parseHexBinary(""
|
||||||
|
+ "cd566972b5e50104011a92b59fa8e0b1234851ae")
|
||||||
|
|
||||||
|
private val crypto = BouncyCryptography()
|
||||||
|
|
||||||
|
init {
|
||||||
|
Singleton.initialize(crypto)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -15,4 +15,5 @@ dependencies {
|
|||||||
compile 'com.madgag.spongycastle:prov:1.54.0.0'
|
compile 'com.madgag.spongycastle:prov:1.54.0.0'
|
||||||
testCompile 'junit:junit:4.12'
|
testCompile 'junit:junit:4.12'
|
||||||
testCompile 'org.mockito:mockito-core:2.7.21'
|
testCompile 'org.mockito:mockito-core:2.7.21'
|
||||||
|
testCompile project(path: ':core', configuration: 'testArtifacts')
|
||||||
}
|
}
|
||||||
|
@ -1,174 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2017 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.security;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.InternalContext;
|
|
||||||
import ch.dissem.bitmessage.cryptography.sc.SpongyCryptography;
|
|
||||||
import ch.dissem.bitmessage.entity.ObjectMessage;
|
|
||||||
import ch.dissem.bitmessage.entity.payload.GenericPayload;
|
|
||||||
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
|
|
||||||
import ch.dissem.bitmessage.exception.InsufficientProofOfWorkException;
|
|
||||||
import ch.dissem.bitmessage.ports.MultiThreadedPOWEngine;
|
|
||||||
import ch.dissem.bitmessage.ports.ProofOfWorkEngine;
|
|
||||||
import ch.dissem.bitmessage.utils.CallbackWaiter;
|
|
||||||
import ch.dissem.bitmessage.utils.Singleton;
|
|
||||||
import ch.dissem.bitmessage.utils.UnixTime;
|
|
||||||
import org.junit.Assert;
|
|
||||||
import org.junit.BeforeClass;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import javax.xml.bind.DatatypeConverter;
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.utils.UnixTime.DAY;
|
|
||||||
import static ch.dissem.bitmessage.utils.UnixTime.MINUTE;
|
|
||||||
import static org.hamcrest.CoreMatchers.is;
|
|
||||||
import static org.junit.Assert.*;
|
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Christian Basler
|
|
||||||
*/
|
|
||||||
public class CryptographyTest {
|
|
||||||
public static final byte[] TEST_VALUE = "teststring".getBytes();
|
|
||||||
public static final byte[] TEST_SHA1 = DatatypeConverter.parseHexBinary(""
|
|
||||||
+ "b8473b86d4c2072ca9b08bd28e373e8253e865c4");
|
|
||||||
public static final byte[] TEST_SHA512 = DatatypeConverter.parseHexBinary(""
|
|
||||||
+ "6253b39071e5df8b5098f59202d414c37a17d6a38a875ef5f8c7d89b0212b028"
|
|
||||||
+ "692d3d2090ce03ae1de66c862fa8a561e57ed9eb7935ce627344f742c0931d72");
|
|
||||||
public static final byte[] TEST_RIPEMD160 = DatatypeConverter.parseHexBinary(""
|
|
||||||
+ "cd566972b5e50104011a92b59fa8e0b1234851ae");
|
|
||||||
|
|
||||||
private static SpongyCryptography crypto;
|
|
||||||
|
|
||||||
@BeforeClass
|
|
||||||
public static void setUp() {
|
|
||||||
crypto = new SpongyCryptography();
|
|
||||||
Singleton.initialize(crypto);
|
|
||||||
InternalContext ctx = mock(InternalContext.class);
|
|
||||||
InternalContext.Companion.setValue(null, null, ctx);
|
|
||||||
when(ctx.getProofOfWorkEngine()).thenReturn(new MultiThreadedPOWEngine());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testRipemd160() {
|
|
||||||
Assert.assertArrayEquals(TEST_RIPEMD160, crypto.ripemd160(TEST_VALUE));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSha1() {
|
|
||||||
Assert.assertArrayEquals(TEST_SHA1, crypto.sha1(TEST_VALUE));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSha512() {
|
|
||||||
Assert.assertArrayEquals(TEST_SHA512, crypto.sha512(TEST_VALUE));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testChaining() {
|
|
||||||
Assert.assertArrayEquals(TEST_SHA512, crypto.sha512("test".getBytes(), "string".getBytes()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void ensureDoubleHashYieldsSameResultAsHashOfHash() {
|
|
||||||
Assert.assertArrayEquals(crypto.sha512(TEST_SHA512), crypto.doubleSha512(TEST_VALUE));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(expected = IOException.class)
|
|
||||||
public void ensureExceptionForInsufficientProofOfWork() throws IOException {
|
|
||||||
ObjectMessage objectMessage = new ObjectMessage.Builder()
|
|
||||||
.nonce(new byte[8])
|
|
||||||
.expiresTime(UnixTime.now() + 28 * DAY)
|
|
||||||
.objectType(0)
|
|
||||||
.payload(GenericPayload.Companion.read(0, 1, new ByteArrayInputStream(new byte[0]), 0))
|
|
||||||
.build();
|
|
||||||
crypto.checkProofOfWork(objectMessage, 1000, 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testDoProofOfWork() throws Exception {
|
|
||||||
ObjectMessage objectMessage = new ObjectMessage.Builder()
|
|
||||||
.nonce(new byte[8])
|
|
||||||
.expiresTime(UnixTime.now() + 2 * MINUTE)
|
|
||||||
.objectType(0)
|
|
||||||
.payload(GenericPayload.Companion.read(0, 1, new ByteArrayInputStream(new byte[0]), 0))
|
|
||||||
.build();
|
|
||||||
final CallbackWaiter<byte[]> waiter = new CallbackWaiter<>();
|
|
||||||
crypto.doProofOfWork(objectMessage, 1000, 1000,
|
|
||||||
new ProofOfWorkEngine.Callback() {
|
|
||||||
@Override
|
|
||||||
public void onNonceCalculated(byte[] initialHash, byte[] nonce) {
|
|
||||||
waiter.setValue(nonce);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
objectMessage.setNonce(waiter.waitForValue());
|
|
||||||
try {
|
|
||||||
crypto.checkProofOfWork(objectMessage, 1000, 1000);
|
|
||||||
} catch (InsufficientProofOfWorkException e) {
|
|
||||||
fail(e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void ensureEncryptionAndDecryptionWorks() {
|
|
||||||
byte[] data = crypto.randomBytes(100);
|
|
||||||
byte[] key_e = crypto.randomBytes(32);
|
|
||||||
byte[] iv = crypto.randomBytes(16);
|
|
||||||
byte[] encrypted = crypto.crypt(true, data, key_e, iv);
|
|
||||||
byte[] decrypted = crypto.crypt(false, encrypted, key_e, iv);
|
|
||||||
assertArrayEquals(data, decrypted);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(expected = IllegalArgumentException.class)
|
|
||||||
public void ensureDecryptionFailsWithInvalidCypherText() {
|
|
||||||
byte[] data = crypto.randomBytes(128);
|
|
||||||
byte[] key_e = crypto.randomBytes(32);
|
|
||||||
byte[] iv = crypto.randomBytes(16);
|
|
||||||
crypto.crypt(false, data, key_e, iv);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testMultiplication() {
|
|
||||||
byte[] a = crypto.randomBytes(PrivateKey.PRIVATE_KEY_SIZE);
|
|
||||||
byte[] A = crypto.createPublicKey(a);
|
|
||||||
|
|
||||||
byte[] b = crypto.randomBytes(PrivateKey.PRIVATE_KEY_SIZE);
|
|
||||||
byte[] B = crypto.createPublicKey(b);
|
|
||||||
|
|
||||||
assertArrayEquals(crypto.multiply(A, b), crypto.multiply(B, a));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void ensureSignatureIsValid() {
|
|
||||||
byte[] data = crypto.randomBytes(100);
|
|
||||||
PrivateKey privateKey = new PrivateKey(false, 1, 1000, 1000);
|
|
||||||
byte[] signature = crypto.getSignature(data, privateKey);
|
|
||||||
assertThat(crypto.isSignatureValid(data, signature, privateKey.getPubkey()), is(true));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void ensureSignatureIsInvalidForTemperedData() {
|
|
||||||
byte[] data = crypto.randomBytes(100);
|
|
||||||
PrivateKey privateKey = new PrivateKey(false, 1, 1000, 1000);
|
|
||||||
byte[] signature = crypto.getSignature(data, privateKey);
|
|
||||||
data[0]++;
|
|
||||||
assertThat(crypto.isSignatureValid(data, signature, privateKey.getPubkey()), is(false));
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,177 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Christian Basler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.dissem.bitmessage.security
|
||||||
|
|
||||||
|
import ch.dissem.bitmessage.InternalContext
|
||||||
|
import ch.dissem.bitmessage.cryptography.sc.SpongyCryptography
|
||||||
|
import ch.dissem.bitmessage.entity.ObjectMessage
|
||||||
|
import ch.dissem.bitmessage.entity.payload.GenericPayload
|
||||||
|
import ch.dissem.bitmessage.entity.valueobject.PrivateKey
|
||||||
|
import ch.dissem.bitmessage.exception.InsufficientProofOfWorkException
|
||||||
|
import ch.dissem.bitmessage.ports.MultiThreadedPOWEngine
|
||||||
|
import ch.dissem.bitmessage.ports.ProofOfWorkEngine
|
||||||
|
import ch.dissem.bitmessage.utils.CallbackWaiter
|
||||||
|
import ch.dissem.bitmessage.utils.Singleton
|
||||||
|
import ch.dissem.bitmessage.utils.UnixTime
|
||||||
|
import org.junit.Assert
|
||||||
|
import org.junit.BeforeClass
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
import javax.xml.bind.DatatypeConverter
|
||||||
|
import java.io.ByteArrayInputStream
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
|
import ch.dissem.bitmessage.utils.UnixTime.DAY
|
||||||
|
import ch.dissem.bitmessage.utils.UnixTime.MINUTE
|
||||||
|
import org.hamcrest.CoreMatchers.`is`
|
||||||
|
import org.junit.Assert.*
|
||||||
|
import org.mockito.Mockito.mock
|
||||||
|
import org.mockito.Mockito.`when`
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Christian Basler
|
||||||
|
*/
|
||||||
|
class CryptographyTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testRipemd160() {
|
||||||
|
assertArrayEquals(TEST_RIPEMD160, crypto.ripemd160(TEST_VALUE))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testSha1() {
|
||||||
|
assertArrayEquals(TEST_SHA1, crypto.sha1(TEST_VALUE))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testSha512() {
|
||||||
|
assertArrayEquals(TEST_SHA512, crypto.sha512(TEST_VALUE))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testChaining() {
|
||||||
|
assertArrayEquals(TEST_SHA512, crypto.sha512("test".toByteArray(), "string".toByteArray()))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun ensureDoubleHashYieldsSameResultAsHashOfHash() {
|
||||||
|
assertArrayEquals(crypto.sha512(TEST_SHA512), crypto.doubleSha512(TEST_VALUE))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IOException::class)
|
||||||
|
fun ensureExceptionForInsufficientProofOfWork() {
|
||||||
|
val objectMessage = ObjectMessage.Builder()
|
||||||
|
.nonce(ByteArray(8))
|
||||||
|
.expiresTime(UnixTime.now + 28 * DAY)
|
||||||
|
.objectType(0)
|
||||||
|
.payload(GenericPayload.read(0, 1, ByteArrayInputStream(ByteArray(0)), 0))
|
||||||
|
.build()
|
||||||
|
crypto.checkProofOfWork(objectMessage, 1000, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testDoProofOfWork() {
|
||||||
|
TestUtils.mockedInternalContext(
|
||||||
|
cryptography = crypto,
|
||||||
|
proofOfWorkEngine = MultiThreadedPOWEngine()
|
||||||
|
)
|
||||||
|
val objectMessage = ObjectMessage(
|
||||||
|
nonce = ByteArray(8),
|
||||||
|
expiresTime = UnixTime.now + 2 * MINUTE,
|
||||||
|
type = 0,
|
||||||
|
payload = GenericPayload.read(0, 1, ByteArrayInputStream(ByteArray(0)), 0),
|
||||||
|
version = 0,
|
||||||
|
stream = 1
|
||||||
|
)
|
||||||
|
val waiter = CallbackWaiter<ByteArray>()
|
||||||
|
crypto.doProofOfWork(objectMessage, 1000, 1000,
|
||||||
|
object : ProofOfWorkEngine.Callback {
|
||||||
|
override fun onNonceCalculated(initialHash: ByteArray, nonce: ByteArray) {
|
||||||
|
waiter.setValue(nonce)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
objectMessage.nonce = waiter.waitForValue()
|
||||||
|
try {
|
||||||
|
crypto.checkProofOfWork(objectMessage, 1000, 1000)
|
||||||
|
} catch (e: InsufficientProofOfWorkException) {
|
||||||
|
fail(e.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun ensureEncryptionAndDecryptionWorks() {
|
||||||
|
val data = crypto.randomBytes(100)
|
||||||
|
val key_e = crypto.randomBytes(32)
|
||||||
|
val iv = crypto.randomBytes(16)
|
||||||
|
val encrypted = crypto.crypt(true, data, key_e, iv)
|
||||||
|
val decrypted = crypto.crypt(false, encrypted, key_e, iv)
|
||||||
|
assertArrayEquals(data, decrypted)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException::class)
|
||||||
|
fun ensureDecryptionFailsWithInvalidCypherText() {
|
||||||
|
val data = crypto.randomBytes(128)
|
||||||
|
val key_e = crypto.randomBytes(32)
|
||||||
|
val iv = crypto.randomBytes(16)
|
||||||
|
crypto.crypt(false, data, key_e, iv)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testMultiplication() {
|
||||||
|
val a = crypto.randomBytes(PrivateKey.PRIVATE_KEY_SIZE)
|
||||||
|
val A = crypto.createPublicKey(a)
|
||||||
|
|
||||||
|
val b = crypto.randomBytes(PrivateKey.PRIVATE_KEY_SIZE)
|
||||||
|
val B = crypto.createPublicKey(b)
|
||||||
|
|
||||||
|
assertArrayEquals(crypto.multiply(A, b), crypto.multiply(B, a))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun ensureSignatureIsValid() {
|
||||||
|
val data = crypto.randomBytes(100)
|
||||||
|
val privateKey = PrivateKey(false, 1, 1000, 1000)
|
||||||
|
val signature = crypto.getSignature(data, privateKey)
|
||||||
|
assertThat(crypto.isSignatureValid(data, signature, privateKey.pubkey), `is`(true))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun ensureSignatureIsInvalidForTemperedData() {
|
||||||
|
val data = crypto.randomBytes(100)
|
||||||
|
val privateKey = PrivateKey(false, 1, 1000, 1000)
|
||||||
|
val signature = crypto.getSignature(data, privateKey)
|
||||||
|
data[0]++
|
||||||
|
assertThat(crypto.isSignatureValid(data, signature, privateKey.pubkey), `is`(false))
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val TEST_VALUE = "teststring".toByteArray()
|
||||||
|
val TEST_SHA1 = DatatypeConverter.parseHexBinary(""
|
||||||
|
+ "b8473b86d4c2072ca9b08bd28e373e8253e865c4")
|
||||||
|
val TEST_SHA512 = DatatypeConverter.parseHexBinary(""
|
||||||
|
+ "6253b39071e5df8b5098f59202d414c37a17d6a38a875ef5f8c7d89b0212b028"
|
||||||
|
+ "692d3d2090ce03ae1de66c862fa8a561e57ed9eb7935ce627344f742c0931d72")
|
||||||
|
val TEST_RIPEMD160 = DatatypeConverter.parseHexBinary(""
|
||||||
|
+ "cd566972b5e50104011a92b59fa8e0b1234851ae")
|
||||||
|
|
||||||
|
private val crypto = SpongyCryptography()
|
||||||
|
|
||||||
|
init {
|
||||||
|
Singleton.initialize(crypto)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -131,7 +131,7 @@ public class JdbcInventoryTest extends TestBase {
|
|||||||
private ObjectMessage getObjectMessage(long stream, long TTL, ObjectPayload payload) {
|
private ObjectMessage getObjectMessage(long stream, long TTL, ObjectPayload payload) {
|
||||||
return new ObjectMessage.Builder()
|
return new ObjectMessage.Builder()
|
||||||
.nonce(new byte[8])
|
.nonce(new byte[8])
|
||||||
.expiresTime(now(+TTL))
|
.expiresTime(now() + TTL)
|
||||||
.stream(stream)
|
.stream(stream)
|
||||||
.payload(payload)
|
.payload(payload)
|
||||||
.build();
|
.build();
|
||||||
|
Loading…
Reference in New Issue
Block a user