Renamed module 'domain' to 'core' to make its purpose more clear
This commit is contained in:
514
core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
Normal file
514
core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
Normal file
@ -0,0 +1,514 @@
|
||||
/*
|
||||
* 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.*;
|
||||
import ch.dissem.bitmessage.entity.payload.Pubkey.Feature;
|
||||
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
|
||||
import ch.dissem.bitmessage.entity.valueobject.Label;
|
||||
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.Arrays;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
import static ch.dissem.bitmessage.entity.Plaintext.Status.*;
|
||||
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.DAY;
|
||||
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 ExecutorService pool;
|
||||
|
||||
private final InternalContext ctx;
|
||||
|
||||
private final Listener listener;
|
||||
private final NetworkHandler.MessageListener networkListener;
|
||||
|
||||
private final boolean sendPubkeyOnIdentityCreation;
|
||||
|
||||
private BitmessageContext(Builder builder) {
|
||||
ctx = new InternalContext(builder);
|
||||
listener = builder.listener;
|
||||
networkListener = new DefaultMessageListener(ctx, listener);
|
||||
|
||||
// As this thread is used for parts that do POW, which itself uses parallel threads, only
|
||||
// one should be executed at any time.
|
||||
pool = Executors.newFixedThreadPool(1);
|
||||
|
||||
sendPubkeyOnIdentityCreation = builder.sendPubkeyOnIdentityCreation;
|
||||
|
||||
new Timer().schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
ctx.getProofOfWorkService().doMissingProofOfWork();
|
||||
}
|
||||
}, 30_000); // After 30 seconds
|
||||
}
|
||||
|
||||
public AddressRepository addresses() {
|
||||
return ctx.getAddressRepository();
|
||||
}
|
||||
|
||||
public MessageRepository messages() {
|
||||
return ctx.getMessageRepository();
|
||||
}
|
||||
|
||||
public BitmessageAddress createIdentity(boolean shorter, Feature... features) {
|
||||
final BitmessageAddress identity = new BitmessageAddress(new PrivateKey(
|
||||
shorter,
|
||||
ctx.getStreams()[0],
|
||||
ctx.getNetworkNonceTrialsPerByte(),
|
||||
ctx.getNetworkExtraBytes(),
|
||||
features
|
||||
));
|
||||
ctx.getAddressRepository().save(identity);
|
||||
if (sendPubkeyOnIdentityCreation) {
|
||||
pool.submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ctx.sendPubkey(identity, identity.getStream());
|
||||
}
|
||||
});
|
||||
}
|
||||
return identity;
|
||||
}
|
||||
|
||||
public void addDistributedMailingList(String address, String alias) {
|
||||
// TODO
|
||||
throw new RuntimeException("not implemented");
|
||||
}
|
||||
|
||||
public void broadcast(final BitmessageAddress from, final String subject, final String message) {
|
||||
pool.submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Plaintext msg = new Plaintext.Builder(BROADCAST)
|
||||
.from(from)
|
||||
.message(subject, message)
|
||||
.build();
|
||||
|
||||
LOG.info("Sending message.");
|
||||
msg.setStatus(DOING_PROOF_OF_WORK);
|
||||
ctx.getMessageRepository().save(msg);
|
||||
ctx.send(
|
||||
from,
|
||||
from,
|
||||
Factory.getBroadcast(from, msg),
|
||||
+2 * DAY
|
||||
);
|
||||
msg.setStatus(SENT);
|
||||
msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.BROADCAST, Label.Type.SENT));
|
||||
ctx.getMessageRepository().save(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.");
|
||||
}
|
||||
pool.submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Plaintext msg = new Plaintext.Builder(MSG)
|
||||
.from(from)
|
||||
.to(to)
|
||||
.message(subject, message)
|
||||
.build();
|
||||
if (to.getPubkey() == null) {
|
||||
tryToFindMatchingPubkey(to);
|
||||
}
|
||||
if (to.getPubkey() == null) {
|
||||
LOG.info("Public key is missing from recipient. Requesting.");
|
||||
requestPubkey(from, to);
|
||||
msg.setStatus(PUBKEY_REQUESTED);
|
||||
ctx.getMessageRepository().save(msg);
|
||||
} else {
|
||||
LOG.info("Sending message.");
|
||||
msg.setStatus(DOING_PROOF_OF_WORK);
|
||||
ctx.getMessageRepository().save(msg);
|
||||
ctx.send(
|
||||
from,
|
||||
to,
|
||||
new Msg(msg),
|
||||
+2 * DAY
|
||||
);
|
||||
msg.setStatus(SENT);
|
||||
msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.SENT));
|
||||
ctx.getMessageRepository().save(msg);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void send(final Plaintext msg) {
|
||||
if (msg.getFrom() == null || msg.getFrom().getPrivateKey() == null) {
|
||||
throw new IllegalArgumentException("'From' must be an identity, i.e. have a private key.");
|
||||
}
|
||||
pool.submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
BitmessageAddress to = msg.getTo();
|
||||
if (to.getPubkey() == null) {
|
||||
tryToFindMatchingPubkey(to);
|
||||
}
|
||||
if (to.getPubkey() == null) {
|
||||
LOG.info("Public key is missing from recipient. Requesting.");
|
||||
requestPubkey(msg.getFrom(), to);
|
||||
msg.setStatus(PUBKEY_REQUESTED);
|
||||
ctx.getMessageRepository().save(msg);
|
||||
} else {
|
||||
LOG.info("Sending message.");
|
||||
msg.setStatus(DOING_PROOF_OF_WORK);
|
||||
ctx.getMessageRepository().save(msg);
|
||||
ctx.send(
|
||||
msg.getFrom(),
|
||||
to,
|
||||
new Msg(msg),
|
||||
+2 * DAY
|
||||
);
|
||||
msg.setStatus(SENT);
|
||||
msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.SENT));
|
||||
ctx.getMessageRepository().save(msg);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void requestPubkey(BitmessageAddress requestingIdentity, BitmessageAddress address) {
|
||||
ctx.send(
|
||||
requestingIdentity,
|
||||
address,
|
||||
new GetPubkey(address),
|
||||
+28 * DAY
|
||||
);
|
||||
}
|
||||
|
||||
public void startup() {
|
||||
ctx.getNetworkHandler().start(networkListener);
|
||||
}
|
||||
|
||||
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, networkListener, 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);
|
||||
}
|
||||
|
||||
public void cleanup() {
|
||||
ctx.getInventory().cleanup();
|
||||
}
|
||||
|
||||
public boolean isRunning() {
|
||||
return ctx.getNetworkHandler().isRunning();
|
||||
}
|
||||
|
||||
public void addContact(BitmessageAddress contact) {
|
||||
ctx.getAddressRepository().save(contact);
|
||||
tryToFindMatchingPubkey(contact);
|
||||
if (contact.getPubkey() == null) {
|
||||
ctx.requestPubkey(contact);
|
||||
}
|
||||
}
|
||||
|
||||
private void tryToFindMatchingPubkey(BitmessageAddress address) {
|
||||
for (ObjectMessage object : ctx.getInventory().getObjects(address.getStream(), address.getVersion(), ObjectType.PUBKEY)) {
|
||||
try {
|
||||
Pubkey pubkey = (Pubkey) object.getPayload();
|
||||
if (address.getVersion() == 4) {
|
||||
V4Pubkey v4Pubkey = (V4Pubkey) pubkey;
|
||||
if (Arrays.equals(address.getTag(), v4Pubkey.getTag())) {
|
||||
v4Pubkey.decrypt(address.getPublicDecryptionKey());
|
||||
if (object.isSignatureValid(v4Pubkey)) {
|
||||
address.setPubkey(v4Pubkey);
|
||||
ctx.getAddressRepository().save(address);
|
||||
break;
|
||||
} else {
|
||||
LOG.info("Found pubkey for " + address + " but signature is invalid");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (Arrays.equals(pubkey.getRipe(), address.getRipe())) {
|
||||
address.setPubkey(pubkey);
|
||||
ctx.getAddressRepository().save(address);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.debug(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.getVersion(address), ObjectType.BROADCAST)) {
|
||||
try {
|
||||
Broadcast broadcast = (Broadcast) object.getPayload();
|
||||
broadcast.decrypt(address);
|
||||
listener.receive(broadcast.getPlaintext());
|
||||
} catch (DecryptionFailedException ignore) {
|
||||
} catch (Exception e) {
|
||||
LOG.debug(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Property status() {
|
||||
return new Property("status", null,
|
||||
ctx.getNetworkHandler().getNetworkStatus()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
int port = 8444;
|
||||
Inventory inventory;
|
||||
NodeRegistry nodeRegistry;
|
||||
NetworkHandler networkHandler;
|
||||
AddressRepository addressRepo;
|
||||
MessageRepository messageRepo;
|
||||
ProofOfWorkRepository proofOfWorkRepository;
|
||||
ProofOfWorkEngine proofOfWorkEngine;
|
||||
Cryptography cryptography;
|
||||
MessageCallback messageCallback;
|
||||
CustomCommandHandler customCommandHandler;
|
||||
Listener listener;
|
||||
int connectionLimit = 150;
|
||||
long connectionTTL = 30 * MINUTE;
|
||||
boolean sendPubkeyOnIdentityCreation = true;
|
||||
long pubkeyTTL = 28;
|
||||
|
||||
public Builder() {
|
||||
}
|
||||
|
||||
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 messageCallback(MessageCallback callback) {
|
||||
this.messageCallback = callback;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder customCommandHandler(CustomCommandHandler handler) {
|
||||
this.customCommandHandler = handler;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder proofOfWorkEngine(ProofOfWorkEngine proofOfWorkEngine) {
|
||||
this.proofOfWorkEngine = proofOfWorkEngine;
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Time to live in seconds for public keys the client sends. Defaults to the maximum of 28 days,
|
||||
* but on weak devices smaller values might be desirable.
|
||||
* <p>
|
||||
* Please be aware that this might cause some problems where you can't receive a message (the
|
||||
* sender can't receive your public key) in some special situations. Also note that it's probably
|
||||
* not a good idea to set it too low.
|
||||
* </p>
|
||||
*/
|
||||
public Builder pubkeyTTL(long days) {
|
||||
if (days < 0 || days > 28 * DAY) throw new IllegalArgumentException("TTL must be between 1 and 28 days");
|
||||
this.pubkeyTTL = days;
|
||||
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 (messageCallback == null) {
|
||||
messageCallback = new MessageCallback() {
|
||||
@Override
|
||||
public void proofOfWorkStarted(ObjectPayload message) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void proofOfWorkCompleted(ObjectPayload message) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void messageOffered(ObjectPayload message, InventoryVector iv) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void messageAcknowledged(InventoryVector iv) {
|
||||
}
|
||||
};
|
||||
}
|
||||
if (customCommandHandler == null) {
|
||||
customCommandHandler = new CustomCommandHandler() {
|
||||
@Override
|
||||
public MessagePayload handle(CustomMessage request) {
|
||||
throw new RuntimeException("Received custom request, but no custom command handler configured.");
|
||||
}
|
||||
};
|
||||
}
|
||||
return new BitmessageContext(this);
|
||||
}
|
||||
|
||||
private void nonNull(String name, Object o) {
|
||||
if (o == null) throw new IllegalStateException(name + " must not be null");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,156 @@
|
||||
/*
|
||||
* 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.BitmessageAddress;
|
||||
import ch.dissem.bitmessage.entity.ObjectMessage;
|
||||
import ch.dissem.bitmessage.entity.Plaintext;
|
||||
import ch.dissem.bitmessage.entity.payload.*;
|
||||
import ch.dissem.bitmessage.entity.valueobject.Label;
|
||||
import ch.dissem.bitmessage.exception.DecryptionFailedException;
|
||||
import ch.dissem.bitmessage.ports.NetworkHandler;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static ch.dissem.bitmessage.entity.Plaintext.Status.*;
|
||||
import static ch.dissem.bitmessage.utils.UnixTime.DAY;
|
||||
|
||||
class DefaultMessageListener implements NetworkHandler.MessageListener {
|
||||
private final static Logger LOG = LoggerFactory.getLogger(DefaultMessageListener.class);
|
||||
private final InternalContext ctx;
|
||||
private final BitmessageContext.Listener listener;
|
||||
|
||||
public DefaultMessageListener(InternalContext context, BitmessageContext.Listener listener) {
|
||||
this.ctx = context;
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(ObjectMessage object) throws IOException {
|
||||
ObjectPayload payload = object.getPayload();
|
||||
if (payload.getType() == null) return;
|
||||
|
||||
switch (payload.getType()) {
|
||||
case GET_PUBKEY: {
|
||||
receive(object, (GetPubkey) payload);
|
||||
break;
|
||||
}
|
||||
case PUBKEY: {
|
||||
receive(object, (Pubkey) payload);
|
||||
break;
|
||||
}
|
||||
case MSG: {
|
||||
receive(object, (Msg) payload);
|
||||
break;
|
||||
}
|
||||
case BROADCAST: {
|
||||
receive(object, (Broadcast) payload);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void receive(ObjectMessage object, GetPubkey getPubkey) {
|
||||
BitmessageAddress identity = ctx.getAddressRepository().findIdentity(getPubkey.getRipeTag());
|
||||
if (identity != null && identity.getPrivateKey() != null) {
|
||||
LOG.info("Got pubkey request for identity " + identity);
|
||||
// FIXME: only send pubkey if it wasn't sent in the last 28 days
|
||||
ctx.sendPubkey(identity, object.getStream());
|
||||
}
|
||||
}
|
||||
|
||||
protected void receive(ObjectMessage object, Pubkey pubkey) throws IOException {
|
||||
BitmessageAddress address;
|
||||
try {
|
||||
if (pubkey instanceof V4Pubkey) {
|
||||
V4Pubkey v4Pubkey = (V4Pubkey) pubkey;
|
||||
address = ctx.getAddressRepository().findContact(v4Pubkey.getTag());
|
||||
if (address != null) {
|
||||
v4Pubkey.decrypt(address.getPublicDecryptionKey());
|
||||
}
|
||||
} else {
|
||||
address = ctx.getAddressRepository().findContact(pubkey.getRipe());
|
||||
}
|
||||
if (address != null) {
|
||||
address.setPubkey(pubkey);
|
||||
LOG.info("Got pubkey for contact " + address);
|
||||
ctx.getAddressRepository().save(address);
|
||||
List<Plaintext> messages = ctx.getMessageRepository().findMessages(Plaintext.Status.PUBKEY_REQUESTED, address);
|
||||
LOG.info("Sending " + messages.size() + " messages for contact " + address);
|
||||
for (Plaintext msg : messages) {
|
||||
msg.setStatus(DOING_PROOF_OF_WORK);
|
||||
ctx.getMessageRepository().save(msg);
|
||||
ctx.send(
|
||||
msg.getFrom(),
|
||||
msg.getTo(),
|
||||
new Msg(msg),
|
||||
+2 * DAY
|
||||
);
|
||||
msg.setStatus(SENT);
|
||||
ctx.getMessageRepository().save(msg);
|
||||
}
|
||||
}
|
||||
} catch (DecryptionFailedException ignore) {
|
||||
}
|
||||
}
|
||||
|
||||
protected void receive(ObjectMessage object, Msg msg) throws IOException {
|
||||
for (BitmessageAddress identity : ctx.getAddressRepository().getIdentities()) {
|
||||
try {
|
||||
msg.decrypt(identity.getPrivateKey().getPrivateEncryptionKey());
|
||||
msg.getPlaintext().setTo(identity);
|
||||
if (!object.isSignatureValid(msg.getPlaintext().getFrom().getPubkey())) {
|
||||
LOG.warn("Msg with IV " + object.getInventoryVector() + " was successfully decrypted, but signature check failed. Ignoring.");
|
||||
} else {
|
||||
msg.getPlaintext().setStatus(RECEIVED);
|
||||
msg.getPlaintext().addLabels(ctx.getMessageRepository().getLabels(Label.Type.INBOX, Label.Type.UNREAD));
|
||||
msg.getPlaintext().setInventoryVector(object.getInventoryVector());
|
||||
ctx.getMessageRepository().save(msg.getPlaintext());
|
||||
listener.receive(msg.getPlaintext());
|
||||
}
|
||||
break;
|
||||
} catch (DecryptionFailedException ignore) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void receive(ObjectMessage object, Broadcast broadcast) throws IOException {
|
||||
byte[] tag = broadcast instanceof V5Broadcast ? ((V5Broadcast) broadcast).getTag() : null;
|
||||
for (BitmessageAddress subscription : ctx.getAddressRepository().getSubscriptions(broadcast.getVersion())) {
|
||||
if (tag != null && !Arrays.equals(tag, subscription.getTag())) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
broadcast.decrypt(subscription.getPublicDecryptionKey());
|
||||
if (!object.isSignatureValid(broadcast.getPlaintext().getFrom().getPubkey())) {
|
||||
LOG.warn("Broadcast with IV " + object.getInventoryVector() + " was successfully decrypted, but signature check failed. Ignoring.");
|
||||
} else {
|
||||
broadcast.getPlaintext().setStatus(RECEIVED);
|
||||
broadcast.getPlaintext().addLabels(ctx.getMessageRepository().getLabels(Label.Type.INBOX, Label.Type.BROADCAST, Label.Type.UNREAD));
|
||||
broadcast.getPlaintext().setInventoryVector(object.getInventoryVector());
|
||||
ctx.getMessageRepository().save(broadcast.getPlaintext());
|
||||
listener.receive(broadcast.getPlaintext());
|
||||
}
|
||||
} catch (DecryptionFailedException ignore) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
246
core/src/main/java/ch/dissem/bitmessage/InternalContext.java
Normal file
246
core/src/main/java/ch/dissem/bitmessage/InternalContext.java
Normal file
@ -0,0 +1,246 @@
|
||||
/*
|
||||
* 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.BitmessageAddress;
|
||||
import ch.dissem.bitmessage.entity.Encrypted;
|
||||
import ch.dissem.bitmessage.entity.ObjectMessage;
|
||||
import ch.dissem.bitmessage.entity.payload.Broadcast;
|
||||
import ch.dissem.bitmessage.entity.payload.GetPubkey;
|
||||
import ch.dissem.bitmessage.entity.payload.ObjectPayload;
|
||||
import ch.dissem.bitmessage.ports.*;
|
||||
import ch.dissem.bitmessage.utils.Singleton;
|
||||
import ch.dissem.bitmessage.utils.UnixTime;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.TreeSet;
|
||||
|
||||
/**
|
||||
* The internal context should normally only be used for port implementations. If you need it in your client
|
||||
* implementation, you're either doing something wrong, something very weird, or the BitmessageContext should
|
||||
* get extended.
|
||||
* <p>
|
||||
* On the other hand, if you need the BitmessageContext in a port implementation, the same thing might apply.
|
||||
* </p>
|
||||
*/
|
||||
public class InternalContext {
|
||||
private final static Logger LOG = LoggerFactory.getLogger(InternalContext.class);
|
||||
|
||||
private final Cryptography cryptography;
|
||||
private final Inventory inventory;
|
||||
private final NodeRegistry nodeRegistry;
|
||||
private final NetworkHandler networkHandler;
|
||||
private final AddressRepository addressRepository;
|
||||
private final MessageRepository messageRepository;
|
||||
private final ProofOfWorkRepository proofOfWorkRepository;
|
||||
private final ProofOfWorkEngine proofOfWorkEngine;
|
||||
private final MessageCallback messageCallback;
|
||||
private final CustomCommandHandler customCommandHandler;
|
||||
private final ProofOfWorkService proofOfWorkService;
|
||||
|
||||
private final TreeSet<Long> streams = new TreeSet<>();
|
||||
private final int port;
|
||||
private final long clientNonce;
|
||||
private final long networkNonceTrialsPerByte = 1000;
|
||||
private final long networkExtraBytes = 1000;
|
||||
private final long pubkeyTTL;
|
||||
private long connectionTTL;
|
||||
private int connectionLimit;
|
||||
|
||||
public InternalContext(BitmessageContext.Builder builder) {
|
||||
this.cryptography = builder.cryptography;
|
||||
this.inventory = builder.inventory;
|
||||
this.nodeRegistry = builder.nodeRegistry;
|
||||
this.networkHandler = builder.networkHandler;
|
||||
this.addressRepository = builder.addressRepo;
|
||||
this.messageRepository = builder.messageRepo;
|
||||
this.proofOfWorkRepository = builder.proofOfWorkRepository;
|
||||
this.proofOfWorkService = new ProofOfWorkService();
|
||||
this.proofOfWorkEngine = builder.proofOfWorkEngine;
|
||||
this.clientNonce = cryptography.randomNonce();
|
||||
this.messageCallback = builder.messageCallback;
|
||||
this.customCommandHandler = builder.customCommandHandler;
|
||||
this.port = builder.port;
|
||||
this.connectionLimit = builder.connectionLimit;
|
||||
this.connectionTTL = builder.connectionTTL;
|
||||
this.pubkeyTTL = builder.pubkeyTTL;
|
||||
|
||||
Singleton.initialize(cryptography);
|
||||
|
||||
// TODO: streams of new identities and subscriptions should also be added. This works only after a restart.
|
||||
for (BitmessageAddress address : addressRepository.getIdentities()) {
|
||||
streams.add(address.getStream());
|
||||
}
|
||||
for (BitmessageAddress address : addressRepository.getSubscriptions()) {
|
||||
streams.add(address.getStream());
|
||||
}
|
||||
if (streams.isEmpty()) {
|
||||
streams.add(1L);
|
||||
}
|
||||
|
||||
init(cryptography, inventory, nodeRegistry, networkHandler, addressRepository, messageRepository,
|
||||
proofOfWorkRepository, proofOfWorkService, proofOfWorkEngine,
|
||||
messageCallback, customCommandHandler);
|
||||
for (BitmessageAddress identity : addressRepository.getIdentities()) {
|
||||
streams.add(identity.getStream());
|
||||
}
|
||||
}
|
||||
|
||||
private void init(Object... objects) {
|
||||
for (Object o : objects) {
|
||||
if (o instanceof ContextHolder) {
|
||||
((ContextHolder) o).setContext(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Cryptography getCryptography() {
|
||||
return cryptography;
|
||||
}
|
||||
|
||||
public Inventory getInventory() {
|
||||
return inventory;
|
||||
}
|
||||
|
||||
public NodeRegistry getNodeRegistry() {
|
||||
return nodeRegistry;
|
||||
}
|
||||
|
||||
public NetworkHandler getNetworkHandler() {
|
||||
return networkHandler;
|
||||
}
|
||||
|
||||
public AddressRepository getAddressRepository() {
|
||||
return addressRepository;
|
||||
}
|
||||
|
||||
public MessageRepository getMessageRepository() {
|
||||
return messageRepository;
|
||||
}
|
||||
|
||||
public ProofOfWorkRepository getProofOfWorkRepository() {
|
||||
return proofOfWorkRepository;
|
||||
}
|
||||
|
||||
public ProofOfWorkEngine getProofOfWorkEngine() {
|
||||
return proofOfWorkEngine;
|
||||
}
|
||||
|
||||
public ProofOfWorkService getProofOfWorkService() {
|
||||
return proofOfWorkService;
|
||||
}
|
||||
|
||||
public long[] getStreams() {
|
||||
long[] result = new long[streams.size()];
|
||||
int i = 0;
|
||||
for (long stream : streams) {
|
||||
result[i++] = stream;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public long getNetworkNonceTrialsPerByte() {
|
||||
return networkNonceTrialsPerByte;
|
||||
}
|
||||
|
||||
public long getNetworkExtraBytes() {
|
||||
return networkExtraBytes;
|
||||
}
|
||||
|
||||
public void send(final BitmessageAddress from, BitmessageAddress to, final ObjectPayload payload,
|
||||
final long timeToLive) {
|
||||
try {
|
||||
if (to == null) to = from;
|
||||
long expires = UnixTime.now(+timeToLive);
|
||||
LOG.info("Expires at " + expires);
|
||||
final ObjectMessage object = new ObjectMessage.Builder()
|
||||
.stream(to.getStream())
|
||||
.expiresTime(expires)
|
||||
.payload(payload)
|
||||
.build();
|
||||
if (object.isSigned()) {
|
||||
object.sign(from.getPrivateKey());
|
||||
}
|
||||
if (payload instanceof Broadcast) {
|
||||
((Broadcast) payload).encrypt();
|
||||
} else if (payload instanceof Encrypted) {
|
||||
object.encrypt(to.getPubkey());
|
||||
}
|
||||
messageCallback.proofOfWorkStarted(payload);
|
||||
proofOfWorkService.doProofOfWork(to, object);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void sendPubkey(final BitmessageAddress identity, final long targetStream) {
|
||||
try {
|
||||
long expires = UnixTime.now(pubkeyTTL);
|
||||
LOG.info("Expires at " + expires);
|
||||
final ObjectMessage response = new ObjectMessage.Builder()
|
||||
.stream(targetStream)
|
||||
.expiresTime(expires)
|
||||
.payload(identity.getPubkey())
|
||||
.build();
|
||||
response.sign(identity.getPrivateKey());
|
||||
response.encrypt(cryptography.createPublicKey(identity.getPublicDecryptionKey()));
|
||||
messageCallback.proofOfWorkStarted(identity.getPubkey());
|
||||
// TODO: remember that the pubkey is just about to be sent, and on which stream!
|
||||
proofOfWorkService.doProofOfWork(response);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void requestPubkey(final BitmessageAddress contact) {
|
||||
long expires = UnixTime.now(+pubkeyTTL);
|
||||
LOG.info("Expires at " + expires);
|
||||
final ObjectMessage response = new ObjectMessage.Builder()
|
||||
.stream(contact.getStream())
|
||||
.expiresTime(expires)
|
||||
.payload(new GetPubkey(contact))
|
||||
.build();
|
||||
messageCallback.proofOfWorkStarted(response.getPayload());
|
||||
proofOfWorkService.doProofOfWork(response);
|
||||
}
|
||||
|
||||
public long getClientNonce() {
|
||||
return clientNonce;
|
||||
}
|
||||
|
||||
public long getConnectionTTL() {
|
||||
return connectionTTL;
|
||||
}
|
||||
|
||||
public int getConnectionLimit() {
|
||||
return connectionLimit;
|
||||
}
|
||||
|
||||
public CustomCommandHandler getCustomCommandHandler() {
|
||||
return customCommandHandler;
|
||||
}
|
||||
|
||||
public interface ContextHolder {
|
||||
void setContext(InternalContext context);
|
||||
}
|
||||
}
|
52
core/src/main/java/ch/dissem/bitmessage/MessageCallback.java
Normal file
52
core/src/main/java/ch/dissem/bitmessage/MessageCallback.java
Normal file
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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.payload.ObjectPayload;
|
||||
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
|
||||
|
||||
/**
|
||||
* Callback for message sending events, mostly so the user can be notified when POW is done.
|
||||
*/
|
||||
public interface MessageCallback {
|
||||
/**
|
||||
* Called before calculation of proof of work begins.
|
||||
*/
|
||||
void proofOfWorkStarted(ObjectPayload message);
|
||||
|
||||
/**
|
||||
* Called after calculation of proof of work finished.
|
||||
*/
|
||||
void proofOfWorkCompleted(ObjectPayload message);
|
||||
|
||||
/**
|
||||
* Called once the message is offered to the network. Please note that this doesn't mean the message was sent,
|
||||
* if the client is not connected to the network it's just stored in the inventory.
|
||||
* <p>
|
||||
* Also, please note that this is where the original payload as well as the {@link InventoryVector} of the sent
|
||||
* message is available. If the callback needs the IV for some reason, it should be retrieved here. (Plaintext
|
||||
* and Broadcast messages will have their IV property set automatically though.)
|
||||
* </p>
|
||||
*/
|
||||
void messageOffered(ObjectPayload message, InventoryVector iv);
|
||||
|
||||
/**
|
||||
* This isn't called yet, as ACK messages aren't being processed yet. Also, this is only relevant for Plaintext
|
||||
* messages.
|
||||
*/
|
||||
void messageAcknowledged(InventoryVector iv);
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
package ch.dissem.bitmessage;
|
||||
|
||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
||||
import ch.dissem.bitmessage.entity.ObjectMessage;
|
||||
import ch.dissem.bitmessage.entity.Plaintext;
|
||||
import ch.dissem.bitmessage.entity.PlaintextHolder;
|
||||
import ch.dissem.bitmessage.ports.MessageRepository;
|
||||
import ch.dissem.bitmessage.ports.ProofOfWorkEngine;
|
||||
import ch.dissem.bitmessage.ports.ProofOfWorkRepository;
|
||||
import ch.dissem.bitmessage.ports.Cryptography;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static ch.dissem.bitmessage.utils.Singleton.security;
|
||||
|
||||
/**
|
||||
* @author Christian Basler
|
||||
*/
|
||||
public class ProofOfWorkService implements ProofOfWorkEngine.Callback, InternalContext.ContextHolder {
|
||||
private final static Logger LOG = LoggerFactory.getLogger(ProofOfWorkService.class);
|
||||
|
||||
private Cryptography cryptography;
|
||||
private InternalContext ctx;
|
||||
private ProofOfWorkRepository powRepo;
|
||||
private MessageRepository messageRepo;
|
||||
|
||||
public void doMissingProofOfWork() {
|
||||
List<byte[]> items = powRepo.getItems();
|
||||
if (items.isEmpty()) return;
|
||||
|
||||
LOG.info("Doing POW for " + items.size() + " tasks.");
|
||||
for (byte[] initialHash : items) {
|
||||
ProofOfWorkRepository.Item item = powRepo.getItem(initialHash);
|
||||
cryptography.doProofOfWork(item.object, item.nonceTrialsPerByte, item.extraBytes, this);
|
||||
}
|
||||
}
|
||||
|
||||
public void doProofOfWork(ObjectMessage object) {
|
||||
doProofOfWork(null, object);
|
||||
}
|
||||
|
||||
public void doProofOfWork(BitmessageAddress recipient, ObjectMessage object) {
|
||||
long nonceTrialsPerByte = recipient == null ?
|
||||
ctx.getNetworkNonceTrialsPerByte() : recipient.getPubkey().getNonceTrialsPerByte();
|
||||
long extraBytes = recipient == null ?
|
||||
ctx.getNetworkExtraBytes() : recipient.getPubkey().getExtraBytes();
|
||||
|
||||
powRepo.putObject(object, nonceTrialsPerByte, extraBytes);
|
||||
if (object.getPayload() instanceof PlaintextHolder) {
|
||||
Plaintext plaintext = ((PlaintextHolder) object.getPayload()).getPlaintext();
|
||||
plaintext.setInitialHash(cryptography.getInitialHash(object));
|
||||
messageRepo.save(plaintext);
|
||||
}
|
||||
cryptography.doProofOfWork(object, nonceTrialsPerByte, extraBytes, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNonceCalculated(byte[] initialHash, byte[] nonce) {
|
||||
ObjectMessage object = powRepo.getItem(initialHash).object;
|
||||
object.setNonce(nonce);
|
||||
// messageCallback.proofOfWorkCompleted(payload);
|
||||
Plaintext plaintext = messageRepo.getMessage(initialHash);
|
||||
if (plaintext != null) {
|
||||
plaintext.setInventoryVector(object.getInventoryVector());
|
||||
messageRepo.save(plaintext);
|
||||
}
|
||||
ctx.getInventory().storeObject(object);
|
||||
ctx.getProofOfWorkRepository().removeObject(initialHash);
|
||||
ctx.getNetworkHandler().offer(object.getInventoryVector());
|
||||
// messageCallback.messageOffered(payload, object.getInventoryVector());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContext(InternalContext ctx) {
|
||||
this.ctx = ctx;
|
||||
this.cryptography = security();
|
||||
this.powRepo = ctx.getProofOfWorkRepository();
|
||||
this.messageRepo = ctx.getMessageRepository();
|
||||
}
|
||||
}
|
75
core/src/main/java/ch/dissem/bitmessage/entity/Addr.java
Normal file
75
core/src/main/java/ch/dissem/bitmessage/entity/Addr.java
Normal file
@ -0,0 +1,75 @@
|
||||
/*
|
||||
* 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.entity;
|
||||
|
||||
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress;
|
||||
import ch.dissem.bitmessage.utils.Encode;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The 'addr' command holds a list of known active Bitmessage nodes.
|
||||
*/
|
||||
public class Addr implements MessagePayload {
|
||||
private final List<NetworkAddress> addresses;
|
||||
|
||||
private Addr(Builder builder) {
|
||||
addresses = builder.addresses;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Command getCommand() {
|
||||
return Command.ADDR;
|
||||
}
|
||||
|
||||
public List<NetworkAddress> getAddresses() {
|
||||
return addresses;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(OutputStream stream) throws IOException {
|
||||
Encode.varInt(addresses.size(), stream);
|
||||
for (NetworkAddress address : addresses) {
|
||||
address.write(stream);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
private List<NetworkAddress> addresses = new ArrayList<NetworkAddress>();
|
||||
|
||||
public Builder() {
|
||||
}
|
||||
|
||||
public Builder addresses(Collection<NetworkAddress> addresses){
|
||||
this.addresses.addAll(addresses);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder addAddress(final NetworkAddress address) {
|
||||
this.addresses.add(address);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Addr build() {
|
||||
return new Addr(this);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,223 @@
|
||||
/*
|
||||
* 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.entity;
|
||||
|
||||
import ch.dissem.bitmessage.entity.payload.Pubkey;
|
||||
import ch.dissem.bitmessage.entity.payload.V4Pubkey;
|
||||
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
|
||||
import ch.dissem.bitmessage.utils.AccessCounter;
|
||||
import ch.dissem.bitmessage.utils.Base58;
|
||||
import ch.dissem.bitmessage.utils.Bytes;
|
||||
import ch.dissem.bitmessage.utils.Encode;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
|
||||
import static ch.dissem.bitmessage.utils.Decode.bytes;
|
||||
import static ch.dissem.bitmessage.utils.Decode.varInt;
|
||||
import static ch.dissem.bitmessage.utils.Singleton.security;
|
||||
|
||||
/**
|
||||
* A Bitmessage address. Can be a user's private address, an address string without public keys or a recipient's address
|
||||
* holding private keys.
|
||||
*/
|
||||
public class BitmessageAddress implements Serializable {
|
||||
private final long version;
|
||||
private final long stream;
|
||||
private final byte[] ripe;
|
||||
private final byte[] tag;
|
||||
/**
|
||||
* Used for V4 address encryption. It's easier to just create it regardless of address version.
|
||||
*/
|
||||
private final byte[] publicDecryptionKey;
|
||||
|
||||
private String address;
|
||||
|
||||
private PrivateKey privateKey;
|
||||
private Pubkey pubkey;
|
||||
|
||||
private String alias;
|
||||
private boolean subscribed;
|
||||
|
||||
BitmessageAddress(long version, long stream, byte[] ripe) {
|
||||
try {
|
||||
this.version = version;
|
||||
this.stream = stream;
|
||||
this.ripe = ripe;
|
||||
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
Encode.varInt(version, os);
|
||||
Encode.varInt(stream, os);
|
||||
if (version < 4) {
|
||||
byte[] checksum = security().sha512(os.toByteArray(), ripe);
|
||||
this.tag = null;
|
||||
this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32);
|
||||
} else {
|
||||
// for tag and decryption key, the checksum has to be created with 0x00 padding
|
||||
byte[] checksum = security().doubleSha512(os.toByteArray(), ripe);
|
||||
this.tag = Arrays.copyOfRange(checksum, 32, 64);
|
||||
this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32);
|
||||
}
|
||||
// but for the address and its checksum they need to be stripped
|
||||
int offset = Bytes.numberOfLeadingZeros(ripe);
|
||||
os.write(ripe, offset, ripe.length - offset);
|
||||
byte[] checksum = security().doubleSha512(os.toByteArray());
|
||||
os.write(checksum, 0, 4);
|
||||
this.address = "BM-" + Base58.encode(os.toByteArray());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public BitmessageAddress(Pubkey publicKey) {
|
||||
this(publicKey.getVersion(), publicKey.getStream(), publicKey.getRipe());
|
||||
this.pubkey = publicKey;
|
||||
}
|
||||
|
||||
public BitmessageAddress(PrivateKey privateKey) {
|
||||
this(privateKey.getPubkey());
|
||||
this.privateKey = privateKey;
|
||||
}
|
||||
|
||||
public BitmessageAddress(String address) {
|
||||
try {
|
||||
this.address = address;
|
||||
byte[] bytes = Base58.decode(address.substring(3));
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(bytes);
|
||||
AccessCounter counter = new AccessCounter();
|
||||
this.version = varInt(in, counter);
|
||||
this.stream = varInt(in, counter);
|
||||
this.ripe = Bytes.expand(bytes(in, bytes.length - counter.length() - 4), 20);
|
||||
|
||||
// test checksum
|
||||
byte[] checksum = security().doubleSha512(bytes, bytes.length - 4);
|
||||
byte[] expectedChecksum = bytes(in, 4);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (expectedChecksum[i] != checksum[i])
|
||||
throw new IllegalArgumentException("Checksum of address failed");
|
||||
}
|
||||
if (version < 4) {
|
||||
checksum = security().sha512(Arrays.copyOfRange(bytes, 0, counter.length()), ripe);
|
||||
this.tag = null;
|
||||
this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32);
|
||||
} else {
|
||||
checksum = security().doubleSha512(Arrays.copyOfRange(bytes, 0, counter.length()), ripe);
|
||||
this.tag = Arrays.copyOfRange(checksum, 32, 64);
|
||||
this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] calculateTag(long version, long stream, byte[] ripe) {
|
||||
try {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
Encode.varInt(version, out);
|
||||
Encode.varInt(stream, out);
|
||||
out.write(ripe);
|
||||
return Arrays.copyOfRange(security().doubleSha512(out.toByteArray()), 32, 64);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public long getStream() {
|
||||
return stream;
|
||||
}
|
||||
|
||||
public long getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public Pubkey getPubkey() {
|
||||
return pubkey;
|
||||
}
|
||||
|
||||
public void setPubkey(Pubkey pubkey) {
|
||||
if (pubkey instanceof V4Pubkey) {
|
||||
if (!Arrays.equals(tag, ((V4Pubkey) pubkey).getTag()))
|
||||
throw new IllegalArgumentException("Pubkey has incompatible tag");
|
||||
}
|
||||
if (!Arrays.equals(ripe, pubkey.getRipe()))
|
||||
throw new IllegalArgumentException("Pubkey has incompatible ripe");
|
||||
this.pubkey = pubkey;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the private key used to decrypt Pubkey objects (for v4 addresses) and broadcasts.
|
||||
*/
|
||||
public byte[] getPublicDecryptionKey() {
|
||||
return publicDecryptionKey;
|
||||
}
|
||||
|
||||
public PrivateKey getPrivateKey() {
|
||||
return privateKey;
|
||||
}
|
||||
|
||||
public String getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
public String getAlias() {
|
||||
return alias;
|
||||
}
|
||||
|
||||
public void setAlias(String alias) {
|
||||
this.alias = alias;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return alias != null ? alias : address;
|
||||
}
|
||||
|
||||
public byte[] getRipe() {
|
||||
return ripe;
|
||||
}
|
||||
|
||||
public byte[] getTag() {
|
||||
return tag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
BitmessageAddress address = (BitmessageAddress) o;
|
||||
return Objects.equals(version, address.version) &&
|
||||
Objects.equals(stream, address.stream) &&
|
||||
Arrays.equals(ripe, address.ripe);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Arrays.hashCode(ripe);
|
||||
}
|
||||
|
||||
public boolean isSubscribed() {
|
||||
return subscribed;
|
||||
}
|
||||
|
||||
public void setSubscribed(boolean subscribed) {
|
||||
this.subscribed = subscribed;
|
||||
}
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
/*
|
||||
* 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.entity;
|
||||
|
||||
import ch.dissem.bitmessage.utils.AccessCounter;
|
||||
import ch.dissem.bitmessage.utils.Encode;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
import static ch.dissem.bitmessage.utils.Decode.bytes;
|
||||
import static ch.dissem.bitmessage.utils.Decode.varString;
|
||||
|
||||
/**
|
||||
* @author Christian Basler
|
||||
*/
|
||||
public class CustomMessage implements MessagePayload {
|
||||
public static final String COMMAND_ERROR = "ERROR";
|
||||
|
||||
private final String command;
|
||||
private final byte[] data;
|
||||
|
||||
public CustomMessage(String command) {
|
||||
this.command = command;
|
||||
this.data = null;
|
||||
}
|
||||
|
||||
public CustomMessage(String command, byte[] data) {
|
||||
this.command = command;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public static CustomMessage read(InputStream in, int length) throws IOException {
|
||||
AccessCounter counter = new AccessCounter();
|
||||
return new CustomMessage(varString(in, counter), bytes(in, length - counter.length()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Command getCommand() {
|
||||
return Command.CUSTOM;
|
||||
}
|
||||
|
||||
public String getCustomCommand() {
|
||||
return command;
|
||||
}
|
||||
|
||||
public byte[] getData() {
|
||||
if (data != null) {
|
||||
return data;
|
||||
} else {
|
||||
try {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
write(out);
|
||||
return out.toByteArray();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(OutputStream out) throws IOException {
|
||||
if (data != null) {
|
||||
Encode.varString(command, out);
|
||||
out.write(data);
|
||||
} else {
|
||||
throw new RuntimeException("Tried to write custom message without data. " +
|
||||
"Programmer: did you forget to override #write()?");
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isError() {
|
||||
return COMMAND_ERROR.equals(command);
|
||||
}
|
||||
|
||||
public static CustomMessage error(String message) {
|
||||
try {
|
||||
return new CustomMessage(COMMAND_ERROR, message.getBytes("UTF-8"));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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.entity;
|
||||
|
||||
import ch.dissem.bitmessage.exception.DecryptionFailedException;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Used for objects that have encrypted content
|
||||
*/
|
||||
public interface Encrypted {
|
||||
void encrypt(byte[] publicKey) throws IOException;
|
||||
|
||||
void decrypt(byte[] privateKey) throws IOException, DecryptionFailedException;
|
||||
|
||||
boolean isDecrypted();
|
||||
}
|
74
core/src/main/java/ch/dissem/bitmessage/entity/GetData.java
Normal file
74
core/src/main/java/ch/dissem/bitmessage/entity/GetData.java
Normal file
@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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.entity;
|
||||
|
||||
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
|
||||
import ch.dissem.bitmessage.utils.Encode;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The 'getdata' command is used to request objects from a node.
|
||||
*/
|
||||
public class GetData implements MessagePayload {
|
||||
List<InventoryVector> inventory;
|
||||
|
||||
private GetData(Builder builder) {
|
||||
inventory = builder.inventory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Command getCommand() {
|
||||
return Command.GETDATA;
|
||||
}
|
||||
|
||||
public List<InventoryVector> getInventory() {
|
||||
return inventory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(OutputStream out) throws IOException {
|
||||
Encode.varInt(inventory.size(), out);
|
||||
for (InventoryVector iv : inventory) {
|
||||
iv.write(out);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
private List<InventoryVector> inventory = new LinkedList<>();
|
||||
|
||||
public Builder() {
|
||||
}
|
||||
|
||||
public Builder addInventoryVector(InventoryVector inventoryVector) {
|
||||
this.inventory.add(inventoryVector);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder inventory(List<InventoryVector> inventory) {
|
||||
this.inventory = inventory;
|
||||
return this;
|
||||
}
|
||||
|
||||
public GetData build() {
|
||||
return new GetData(this);
|
||||
}
|
||||
}
|
||||
}
|
74
core/src/main/java/ch/dissem/bitmessage/entity/Inv.java
Normal file
74
core/src/main/java/ch/dissem/bitmessage/entity/Inv.java
Normal file
@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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.entity;
|
||||
|
||||
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
|
||||
import ch.dissem.bitmessage.utils.Encode;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The 'inv' command holds up to 50000 inventory vectors, i.e. hashes of inventory items.
|
||||
*/
|
||||
public class Inv implements MessagePayload {
|
||||
private List<InventoryVector> inventory;
|
||||
|
||||
private Inv(Builder builder) {
|
||||
inventory = builder.inventory;
|
||||
}
|
||||
|
||||
public List<InventoryVector> getInventory() {
|
||||
return inventory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Command getCommand() {
|
||||
return Command.INV;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(OutputStream stream) throws IOException {
|
||||
Encode.varInt(inventory.size(), stream);
|
||||
for (InventoryVector iv : inventory) {
|
||||
iv.write(stream);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
private List<InventoryVector> inventory = new LinkedList<>();
|
||||
|
||||
public Builder() {
|
||||
}
|
||||
|
||||
public Builder addInventoryVector(InventoryVector inventoryVector) {
|
||||
this.inventory.add(inventoryVector);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder inventory(List<InventoryVector> inventory) {
|
||||
this.inventory = inventory;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Inv build() {
|
||||
return new Inv(this);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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.entity;
|
||||
|
||||
/**
|
||||
* A command can hold a network message payload
|
||||
*/
|
||||
public interface MessagePayload extends Streamable {
|
||||
Command getCommand();
|
||||
|
||||
enum Command {
|
||||
VERSION, VERACK, ADDR, INV, GETDATA, OBJECT, CUSTOM
|
||||
}
|
||||
}
|
@ -0,0 +1,93 @@
|
||||
/*
|
||||
* 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.entity;
|
||||
|
||||
import ch.dissem.bitmessage.utils.Encode;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
|
||||
import static ch.dissem.bitmessage.utils.Singleton.security;
|
||||
|
||||
/**
|
||||
* A network message is exchanged between two nodes.
|
||||
*/
|
||||
public class NetworkMessage implements Streamable {
|
||||
/**
|
||||
* Magic value indicating message origin network, and used to seek to next message when stream state is unknown
|
||||
*/
|
||||
public final static int MAGIC = 0xE9BEB4D9;
|
||||
public final static byte[] MAGIC_BYTES = ByteBuffer.allocate(4).putInt(MAGIC).array();
|
||||
|
||||
private final MessagePayload payload;
|
||||
|
||||
public NetworkMessage(MessagePayload payload) {
|
||||
this.payload = payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* First 4 bytes of sha512(payload)
|
||||
*/
|
||||
private byte[] getChecksum(byte[] bytes) throws NoSuchProviderException, NoSuchAlgorithmException {
|
||||
byte[] d = security().sha512(bytes);
|
||||
return new byte[]{d[0], d[1], d[2], d[3]};
|
||||
}
|
||||
|
||||
/**
|
||||
* The actual data, a message or an object. Not to be confused with objectPayload.
|
||||
*/
|
||||
public MessagePayload getPayload() {
|
||||
return payload;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(OutputStream out) throws IOException {
|
||||
// magic
|
||||
Encode.int32(MAGIC, out);
|
||||
|
||||
// ASCII string identifying the packet content, NULL padded (non-NULL padding results in packet rejected)
|
||||
String command = payload.getCommand().name().toLowerCase();
|
||||
out.write(command.getBytes("ASCII"));
|
||||
for (int i = command.length(); i < 12; i++) {
|
||||
out.write('\0');
|
||||
}
|
||||
|
||||
ByteArrayOutputStream payloadStream = new ByteArrayOutputStream();
|
||||
payload.write(payloadStream);
|
||||
byte[] payloadBytes = payloadStream.toByteArray();
|
||||
|
||||
// Length of payload in number of bytes. Because of other restrictions, there is no reason why this length would
|
||||
// ever be larger than 1600003 bytes. Some clients include a sanity-check to avoid processing messages which are
|
||||
// larger than this.
|
||||
Encode.int32(payloadBytes.length, out);
|
||||
|
||||
// checksum
|
||||
try {
|
||||
out.write(getChecksum(payloadBytes));
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
// message payload
|
||||
out.write(payloadBytes);
|
||||
}
|
||||
}
|
@ -0,0 +1,233 @@
|
||||
/*
|
||||
* 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.entity;
|
||||
|
||||
import ch.dissem.bitmessage.entity.payload.ObjectPayload;
|
||||
import ch.dissem.bitmessage.entity.payload.ObjectType;
|
||||
import ch.dissem.bitmessage.entity.payload.Pubkey;
|
||||
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
|
||||
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
|
||||
import ch.dissem.bitmessage.exception.DecryptionFailedException;
|
||||
import ch.dissem.bitmessage.utils.Bytes;
|
||||
import ch.dissem.bitmessage.utils.Encode;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import static ch.dissem.bitmessage.utils.Singleton.security;
|
||||
|
||||
/**
|
||||
* The 'object' command sends an object that is shared throughout the network.
|
||||
*/
|
||||
public class ObjectMessage implements MessagePayload {
|
||||
private byte[] nonce;
|
||||
private long expiresTime;
|
||||
private long objectType;
|
||||
/**
|
||||
* The object's version
|
||||
*/
|
||||
private long version;
|
||||
private long stream;
|
||||
|
||||
private ObjectPayload payload;
|
||||
private byte[] payloadBytes;
|
||||
|
||||
private ObjectMessage(Builder builder) {
|
||||
nonce = builder.nonce;
|
||||
expiresTime = builder.expiresTime;
|
||||
objectType = builder.objectType;
|
||||
version = builder.payload.getVersion();
|
||||
stream = builder.streamNumber;
|
||||
payload = builder.payload;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Command getCommand() {
|
||||
return Command.OBJECT;
|
||||
}
|
||||
|
||||
public byte[] getNonce() {
|
||||
return nonce;
|
||||
}
|
||||
|
||||
public void setNonce(byte[] nonce) {
|
||||
this.nonce = nonce;
|
||||
}
|
||||
|
||||
public long getExpiresTime() {
|
||||
return expiresTime;
|
||||
}
|
||||
|
||||
public long getType() {
|
||||
return objectType;
|
||||
}
|
||||
|
||||
public ObjectPayload getPayload() {
|
||||
return payload;
|
||||
}
|
||||
|
||||
public long getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public long getStream() {
|
||||
return stream;
|
||||
}
|
||||
|
||||
public InventoryVector getInventoryVector() {
|
||||
return new InventoryVector(
|
||||
Bytes.truncate(security().doubleSha512(nonce, getPayloadBytesWithoutNonce()), 32)
|
||||
);
|
||||
}
|
||||
|
||||
private boolean isEncrypted() {
|
||||
return payload instanceof Encrypted && !((Encrypted) payload).isDecrypted();
|
||||
}
|
||||
|
||||
public boolean isSigned() {
|
||||
return payload.isSigned();
|
||||
}
|
||||
|
||||
private byte[] getBytesToSign() {
|
||||
try {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
writeHeaderWithoutNonce(out);
|
||||
payload.writeBytesToSign(out);
|
||||
return out.toByteArray();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void sign(PrivateKey key) {
|
||||
if (payload.isSigned()) {
|
||||
payload.setSignature(security().getSignature(getBytesToSign(), key));
|
||||
}
|
||||
}
|
||||
|
||||
public void decrypt(PrivateKey key) throws IOException, DecryptionFailedException {
|
||||
if (payload instanceof Encrypted) {
|
||||
((Encrypted) payload).decrypt(key.getPrivateEncryptionKey());
|
||||
}
|
||||
}
|
||||
|
||||
public void decrypt(byte[] privateEncryptionKey) throws IOException, DecryptionFailedException {
|
||||
if (payload instanceof Encrypted) {
|
||||
((Encrypted) payload).decrypt(privateEncryptionKey);
|
||||
}
|
||||
}
|
||||
|
||||
public void encrypt(byte[] publicEncryptionKey) throws IOException {
|
||||
if (payload instanceof Encrypted) {
|
||||
((Encrypted) payload).encrypt(publicEncryptionKey);
|
||||
}
|
||||
}
|
||||
|
||||
public void encrypt(Pubkey publicKey) {
|
||||
try {
|
||||
if (payload instanceof Encrypted) {
|
||||
((Encrypted) payload).encrypt(publicKey.getEncryptionKey());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isSignatureValid(Pubkey pubkey) throws IOException {
|
||||
if (isEncrypted()) throw new IllegalStateException("Payload must be decrypted first");
|
||||
return security().isSignatureValid(getBytesToSign(), payload.getSignature(), pubkey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(OutputStream out) throws IOException {
|
||||
if (nonce != null) {
|
||||
out.write(nonce);
|
||||
} else {
|
||||
out.write(new byte[8]);
|
||||
}
|
||||
out.write(getPayloadBytesWithoutNonce());
|
||||
}
|
||||
|
||||
private void writeHeaderWithoutNonce(OutputStream out) throws IOException {
|
||||
Encode.int64(expiresTime, out);
|
||||
Encode.int32(objectType, out);
|
||||
Encode.varInt(version, out);
|
||||
Encode.varInt(stream, out);
|
||||
}
|
||||
|
||||
public byte[] getPayloadBytesWithoutNonce() {
|
||||
try {
|
||||
if (payloadBytes == null) {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
writeHeaderWithoutNonce(out);
|
||||
payload.write(out);
|
||||
payloadBytes = out.toByteArray();
|
||||
}
|
||||
return payloadBytes;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
private byte[] nonce;
|
||||
private long expiresTime;
|
||||
private long objectType = -1;
|
||||
private long streamNumber;
|
||||
private ObjectPayload payload;
|
||||
|
||||
public Builder() {
|
||||
}
|
||||
|
||||
public Builder nonce(byte[] nonce) {
|
||||
this.nonce = nonce;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder expiresTime(long expiresTime) {
|
||||
this.expiresTime = expiresTime;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder objectType(long objectType) {
|
||||
this.objectType = objectType;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder objectType(ObjectType objectType) {
|
||||
this.objectType = objectType.getNumber();
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder stream(long streamNumber) {
|
||||
this.streamNumber = streamNumber;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder payload(ObjectPayload payload) {
|
||||
this.payload = payload;
|
||||
if (this.objectType == -1)
|
||||
this.objectType = payload.getType().getNumber();
|
||||
return this;
|
||||
}
|
||||
|
||||
public ObjectMessage build() {
|
||||
return new ObjectMessage(this);
|
||||
}
|
||||
}
|
||||
}
|
464
core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java
Normal file
464
core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java
Normal file
@ -0,0 +1,464 @@
|
||||
/*
|
||||
* 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.entity;
|
||||
|
||||
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
|
||||
import ch.dissem.bitmessage.entity.valueobject.Label;
|
||||
import ch.dissem.bitmessage.factory.Factory;
|
||||
import ch.dissem.bitmessage.utils.Decode;
|
||||
import ch.dissem.bitmessage.utils.Encode;
|
||||
import ch.dissem.bitmessage.utils.UnixTime;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* The unencrypted message to be sent by 'msg' or 'broadcast'.
|
||||
*/
|
||||
public class Plaintext implements Streamable {
|
||||
private final Type type;
|
||||
private final BitmessageAddress from;
|
||||
private final long encoding;
|
||||
private final byte[] message;
|
||||
private final byte[] ack;
|
||||
private Object id;
|
||||
private InventoryVector inventoryVector;
|
||||
private BitmessageAddress to;
|
||||
private byte[] signature;
|
||||
private Status status;
|
||||
private Long sent;
|
||||
private Long received;
|
||||
|
||||
private Set<Label> labels;
|
||||
private byte[] initialHash;
|
||||
|
||||
private Plaintext(Builder builder) {
|
||||
id = builder.id;
|
||||
inventoryVector = builder.inventoryVector;
|
||||
type = builder.type;
|
||||
from = builder.from;
|
||||
to = builder.to;
|
||||
encoding = builder.encoding;
|
||||
message = builder.message;
|
||||
ack = builder.ack;
|
||||
signature = builder.signature;
|
||||
status = builder.status;
|
||||
sent = builder.sent;
|
||||
received = builder.received;
|
||||
labels = builder.labels;
|
||||
}
|
||||
|
||||
public static Plaintext read(Type type, InputStream in) throws IOException {
|
||||
return readWithoutSignature(type, in)
|
||||
.signature(Decode.varBytes(in))
|
||||
.received(UnixTime.now())
|
||||
.build();
|
||||
}
|
||||
|
||||
public static Plaintext.Builder readWithoutSignature(Type type, InputStream in) throws IOException {
|
||||
long version = Decode.varInt(in);
|
||||
return new Builder(type)
|
||||
.addressVersion(version)
|
||||
.stream(Decode.varInt(in))
|
||||
.behaviorBitfield(Decode.int32(in))
|
||||
.publicSigningKey(Decode.bytes(in, 64))
|
||||
.publicEncryptionKey(Decode.bytes(in, 64))
|
||||
.nonceTrialsPerByte(version >= 3 ? Decode.varInt(in) : 0)
|
||||
.extraBytes(version >= 3 ? Decode.varInt(in) : 0)
|
||||
.destinationRipe(type == Type.MSG ? Decode.bytes(in, 20) : null)
|
||||
.encoding(Decode.varInt(in))
|
||||
.message(Decode.varBytes(in))
|
||||
.ack(type == Type.MSG ? Decode.varBytes(in) : null);
|
||||
}
|
||||
|
||||
public InventoryVector getInventoryVector() {
|
||||
return inventoryVector;
|
||||
}
|
||||
|
||||
public void setInventoryVector(InventoryVector inventoryVector) {
|
||||
this.inventoryVector = inventoryVector;
|
||||
}
|
||||
|
||||
public Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public byte[] getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public BitmessageAddress getFrom() {
|
||||
return from;
|
||||
}
|
||||
|
||||
public BitmessageAddress getTo() {
|
||||
return to;
|
||||
}
|
||||
|
||||
public void setTo(BitmessageAddress to) {
|
||||
if (this.to.getVersion() != 0)
|
||||
throw new RuntimeException("Correct address already set");
|
||||
if (!Arrays.equals(this.to.getRipe(), to.getRipe())) {
|
||||
throw new RuntimeException("RIPEs don't match");
|
||||
}
|
||||
this.to = to;
|
||||
}
|
||||
|
||||
public Set<Label> getLabels() {
|
||||
return labels;
|
||||
}
|
||||
|
||||
public long getStream() {
|
||||
return from.getStream();
|
||||
}
|
||||
|
||||
public byte[] getSignature() {
|
||||
return signature;
|
||||
}
|
||||
|
||||
public void setSignature(byte[] signature) {
|
||||
this.signature = signature;
|
||||
}
|
||||
|
||||
public boolean isUnread() {
|
||||
for (Label label : labels) {
|
||||
if (label.getType() == Label.Type.UNREAD) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void write(OutputStream out, boolean includeSignature) throws IOException {
|
||||
Encode.varInt(from.getVersion(), out);
|
||||
Encode.varInt(from.getStream(), out);
|
||||
Encode.int32(from.getPubkey().getBehaviorBitfield(), out);
|
||||
out.write(from.getPubkey().getSigningKey(), 1, 64);
|
||||
out.write(from.getPubkey().getEncryptionKey(), 1, 64);
|
||||
if (from.getVersion() >= 3) {
|
||||
Encode.varInt(from.getPubkey().getNonceTrialsPerByte(), out);
|
||||
Encode.varInt(from.getPubkey().getExtraBytes(), out);
|
||||
}
|
||||
if (type == Type.MSG) {
|
||||
out.write(to.getRipe());
|
||||
}
|
||||
Encode.varInt(encoding, out);
|
||||
Encode.varInt(message.length, out);
|
||||
out.write(message);
|
||||
if (type == Type.MSG) {
|
||||
Encode.varInt(ack.length, out);
|
||||
out.write(ack);
|
||||
}
|
||||
if (includeSignature) {
|
||||
if (signature == null) {
|
||||
Encode.varInt(0, out);
|
||||
} else {
|
||||
Encode.varInt(signature.length, out);
|
||||
out.write(signature);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(OutputStream out) throws IOException {
|
||||
write(out, true);
|
||||
}
|
||||
|
||||
public Object getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(long id) {
|
||||
if (this.id != null) throw new IllegalStateException("ID already set");
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Long getSent() {
|
||||
return sent;
|
||||
}
|
||||
|
||||
public Long getReceived() {
|
||||
return received;
|
||||
}
|
||||
|
||||
public Status getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(Status status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public String getSubject() {
|
||||
Scanner s = new Scanner(new ByteArrayInputStream(message), "UTF-8");
|
||||
String firstLine = s.nextLine();
|
||||
if (encoding == 2) {
|
||||
return firstLine.substring("Subject:".length()).trim();
|
||||
} else if (firstLine.length() > 50) {
|
||||
return firstLine.substring(0, 50).trim() + "...";
|
||||
} else {
|
||||
return firstLine;
|
||||
}
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
try {
|
||||
String text = new String(message, "UTF-8");
|
||||
if (encoding == 2) {
|
||||
return text.substring(text.indexOf("\nBody:") + 6);
|
||||
}
|
||||
return text;
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
Plaintext plaintext = (Plaintext) o;
|
||||
return Objects.equals(encoding, plaintext.encoding) &&
|
||||
Objects.equals(from, plaintext.from) &&
|
||||
Arrays.equals(message, plaintext.message) &&
|
||||
Arrays.equals(ack, plaintext.ack) &&
|
||||
Arrays.equals(to.getRipe(), plaintext.to.getRipe()) &&
|
||||
Arrays.equals(signature, plaintext.signature) &&
|
||||
Objects.equals(status, plaintext.status) &&
|
||||
Objects.equals(sent, plaintext.sent) &&
|
||||
Objects.equals(received, plaintext.received) &&
|
||||
Objects.equals(labels, plaintext.labels);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(from, encoding, message, ack, to, signature, status, sent, received, labels);
|
||||
}
|
||||
|
||||
public void addLabels(Label... labels) {
|
||||
if (labels != null) {
|
||||
Collections.addAll(this.labels, labels);
|
||||
}
|
||||
}
|
||||
|
||||
public void addLabels(Collection<Label> labels) {
|
||||
if (labels != null) {
|
||||
this.labels.addAll(labels);
|
||||
}
|
||||
}
|
||||
|
||||
public void setInitialHash(byte[] initialHash) {
|
||||
this.initialHash = initialHash;
|
||||
}
|
||||
|
||||
public byte[] getInitialHash() {
|
||||
return initialHash;
|
||||
}
|
||||
|
||||
public enum Encoding {
|
||||
IGNORE(0), TRIVIAL(1), SIMPLE(2);
|
||||
|
||||
long code;
|
||||
|
||||
Encoding(long code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public long getCode() {
|
||||
return code;
|
||||
}
|
||||
}
|
||||
|
||||
public enum Status {
|
||||
DRAFT,
|
||||
// For sent messages
|
||||
PUBKEY_REQUESTED,
|
||||
DOING_PROOF_OF_WORK,
|
||||
SENT,
|
||||
SENT_ACKNOWLEDGED,
|
||||
RECEIVED
|
||||
}
|
||||
|
||||
public enum Type {
|
||||
MSG, BROADCAST
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
private Object id;
|
||||
private InventoryVector inventoryVector;
|
||||
private Type type;
|
||||
private BitmessageAddress from;
|
||||
private BitmessageAddress to;
|
||||
private long addressVersion;
|
||||
private long stream;
|
||||
private int behaviorBitfield;
|
||||
private byte[] publicSigningKey;
|
||||
private byte[] publicEncryptionKey;
|
||||
private long nonceTrialsPerByte;
|
||||
private long extraBytes;
|
||||
private byte[] destinationRipe;
|
||||
private long encoding;
|
||||
private byte[] message = new byte[0];
|
||||
private byte[] ack = new byte[0];
|
||||
private byte[] signature;
|
||||
private long sent;
|
||||
private long received;
|
||||
private Status status;
|
||||
private Set<Label> labels = new HashSet<>();
|
||||
|
||||
public Builder(Type type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public Builder id(Object id) {
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder IV(InventoryVector iv) {
|
||||
this.inventoryVector = iv;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder from(BitmessageAddress address) {
|
||||
from = address;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder to(BitmessageAddress address) {
|
||||
if (type != Type.MSG && to != null)
|
||||
throw new IllegalArgumentException("recipient address only allowed for msg");
|
||||
to = address;
|
||||
return this;
|
||||
}
|
||||
|
||||
private Builder addressVersion(long addressVersion) {
|
||||
this.addressVersion = addressVersion;
|
||||
return this;
|
||||
}
|
||||
|
||||
private Builder stream(long stream) {
|
||||
this.stream = stream;
|
||||
return this;
|
||||
}
|
||||
|
||||
private Builder behaviorBitfield(int behaviorBitfield) {
|
||||
this.behaviorBitfield = behaviorBitfield;
|
||||
return this;
|
||||
}
|
||||
|
||||
private Builder publicSigningKey(byte[] publicSigningKey) {
|
||||
this.publicSigningKey = publicSigningKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
private Builder publicEncryptionKey(byte[] publicEncryptionKey) {
|
||||
this.publicEncryptionKey = publicEncryptionKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
private Builder nonceTrialsPerByte(long nonceTrialsPerByte) {
|
||||
this.nonceTrialsPerByte = nonceTrialsPerByte;
|
||||
return this;
|
||||
}
|
||||
|
||||
private Builder extraBytes(long extraBytes) {
|
||||
this.extraBytes = extraBytes;
|
||||
return this;
|
||||
}
|
||||
|
||||
private Builder destinationRipe(byte[] ripe) {
|
||||
if (type != Type.MSG && ripe != null) throw new IllegalArgumentException("ripe only allowed for msg");
|
||||
this.destinationRipe = ripe;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder encoding(Encoding encoding) {
|
||||
this.encoding = encoding.getCode();
|
||||
return this;
|
||||
}
|
||||
|
||||
private Builder encoding(long encoding) {
|
||||
this.encoding = encoding;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder message(String subject, String message) {
|
||||
try {
|
||||
this.encoding = Encoding.SIMPLE.getCode();
|
||||
this.message = ("Subject:" + subject + '\n' + "Body:" + message).getBytes("UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder message(byte[] message) {
|
||||
this.message = message;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder ack(byte[] ack) {
|
||||
if (type != Type.MSG && ack != null) throw new IllegalArgumentException("ack only allowed for msg");
|
||||
this.ack = ack;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder signature(byte[] signature) {
|
||||
this.signature = signature;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder sent(long sent) {
|
||||
this.sent = sent;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder received(long received) {
|
||||
this.received = received;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder status(Status status) {
|
||||
this.status = status;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder labels(Collection<Label> labels) {
|
||||
this.labels.addAll(labels);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Plaintext build() {
|
||||
if (from == null) {
|
||||
from = new BitmessageAddress(Factory.createPubkey(
|
||||
addressVersion,
|
||||
stream,
|
||||
publicSigningKey,
|
||||
publicEncryptionKey,
|
||||
nonceTrialsPerByte,
|
||||
extraBytes,
|
||||
behaviorBitfield
|
||||
));
|
||||
}
|
||||
if (to == null && type != Type.BROADCAST) {
|
||||
to = new BitmessageAddress(0, 0, destinationRipe);
|
||||
}
|
||||
return new Plaintext(this);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
/*
|
||||
* 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.entity;
|
||||
|
||||
public interface PlaintextHolder {
|
||||
Plaintext getPlaintext();
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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.entity;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* An object that can be written to an {@link OutputStream}
|
||||
*/
|
||||
public interface Streamable extends Serializable {
|
||||
void write(OutputStream stream) throws IOException;
|
||||
}
|
35
core/src/main/java/ch/dissem/bitmessage/entity/VerAck.java
Normal file
35
core/src/main/java/ch/dissem/bitmessage/entity/VerAck.java
Normal file
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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.entity;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* The 'verack' command answers a 'version' command, accepting the other node's version.
|
||||
*/
|
||||
public class VerAck implements MessagePayload {
|
||||
@Override
|
||||
public Command getCommand() {
|
||||
return Command.VERACK;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(OutputStream stream) throws IOException {
|
||||
// 'verack' doesn't have any payload, so there is nothing to write
|
||||
}
|
||||
}
|
203
core/src/main/java/ch/dissem/bitmessage/entity/Version.java
Normal file
203
core/src/main/java/ch/dissem/bitmessage/entity/Version.java
Normal file
@ -0,0 +1,203 @@
|
||||
/*
|
||||
* 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.entity;
|
||||
|
||||
import ch.dissem.bitmessage.BitmessageContext;
|
||||
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress;
|
||||
import ch.dissem.bitmessage.utils.Encode;
|
||||
import ch.dissem.bitmessage.utils.UnixTime;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* The 'version' command advertises this node's latest supported protocol version upon initiation.
|
||||
*/
|
||||
public class Version implements MessagePayload {
|
||||
/**
|
||||
* Identifies protocol version being used by the node. Should equal 3. Nodes should disconnect if the remote node's
|
||||
* version is lower but continue with the connection if it is higher.
|
||||
*/
|
||||
private final int version;
|
||||
|
||||
/**
|
||||
* bitfield of features to be enabled for this connection
|
||||
*/
|
||||
private final long services;
|
||||
|
||||
/**
|
||||
* standard UNIX timestamp in seconds
|
||||
*/
|
||||
private final long timestamp;
|
||||
|
||||
/**
|
||||
* The network address of the node receiving this message (not including the time or stream number)
|
||||
*/
|
||||
private final NetworkAddress addrRecv;
|
||||
|
||||
/**
|
||||
* The network address of the node emitting this message (not including the time or stream number and the ip itself
|
||||
* is ignored by the receiver)
|
||||
*/
|
||||
private final NetworkAddress addrFrom;
|
||||
|
||||
/**
|
||||
* Random nonce used to detect connections to self.
|
||||
*/
|
||||
private final long nonce;
|
||||
|
||||
/**
|
||||
* User Agent (0x00 if string is 0 bytes long). Sending nodes must not include a user_agent longer than 5000 bytes.
|
||||
*/
|
||||
private final String userAgent;
|
||||
|
||||
/**
|
||||
* The stream numbers that the emitting node is interested in. Sending nodes must not include more than 160000
|
||||
* stream numbers.
|
||||
*/
|
||||
private final long[] streams;
|
||||
|
||||
private Version(Builder builder) {
|
||||
version = builder.version;
|
||||
services = builder.services;
|
||||
timestamp = builder.timestamp;
|
||||
addrRecv = builder.addrRecv;
|
||||
addrFrom = builder.addrFrom;
|
||||
nonce = builder.nonce;
|
||||
userAgent = builder.userAgent;
|
||||
streams = builder.streamNumbers;
|
||||
}
|
||||
|
||||
public int getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public long getServices() {
|
||||
return services;
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public NetworkAddress getAddrRecv() {
|
||||
return addrRecv;
|
||||
}
|
||||
|
||||
public NetworkAddress getAddrFrom() {
|
||||
return addrFrom;
|
||||
}
|
||||
|
||||
public long getNonce() {
|
||||
return nonce;
|
||||
}
|
||||
|
||||
public String getUserAgent() {
|
||||
return userAgent;
|
||||
}
|
||||
|
||||
public long[] getStreams() {
|
||||
return streams;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Command getCommand() {
|
||||
return Command.VERSION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(OutputStream stream) throws IOException {
|
||||
Encode.int32(version, stream);
|
||||
Encode.int64(services, stream);
|
||||
Encode.int64(timestamp, stream);
|
||||
addrRecv.write(stream, true);
|
||||
addrFrom.write(stream, true);
|
||||
Encode.int64(nonce, stream);
|
||||
Encode.varString(userAgent, stream);
|
||||
Encode.varIntList(streams, stream);
|
||||
}
|
||||
|
||||
|
||||
public static final class Builder {
|
||||
private int version;
|
||||
private long services;
|
||||
private long timestamp;
|
||||
private NetworkAddress addrRecv;
|
||||
private NetworkAddress addrFrom;
|
||||
private long nonce;
|
||||
private String userAgent;
|
||||
private long[] streamNumbers;
|
||||
|
||||
public Builder() {
|
||||
}
|
||||
|
||||
public Builder defaults() {
|
||||
version = BitmessageContext.CURRENT_VERSION;
|
||||
services = 1;
|
||||
timestamp = UnixTime.now();
|
||||
nonce = new Random().nextInt();
|
||||
userAgent = "/Jabit:0.0.1/";
|
||||
streamNumbers = new long[]{1};
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder version(int version) {
|
||||
this.version = version;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder services(long services) {
|
||||
this.services = services;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder timestamp(long timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder addrRecv(NetworkAddress addrRecv) {
|
||||
this.addrRecv = addrRecv;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder addrFrom(NetworkAddress addrFrom) {
|
||||
this.addrFrom = addrFrom;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder nonce(long nonce) {
|
||||
this.nonce = nonce;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder userAgent(String userAgent) {
|
||||
this.userAgent = userAgent;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder streams(long... streamNumbers) {
|
||||
this.streamNumbers = streamNumbers;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Version build() {
|
||||
return new Version(this);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
/*
|
||||
* 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.entity.payload;
|
||||
|
||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
||||
import ch.dissem.bitmessage.entity.Encrypted;
|
||||
import ch.dissem.bitmessage.entity.Plaintext;
|
||||
import ch.dissem.bitmessage.entity.PlaintextHolder;
|
||||
import ch.dissem.bitmessage.exception.DecryptionFailedException;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static ch.dissem.bitmessage.entity.Plaintext.Type.BROADCAST;
|
||||
import static ch.dissem.bitmessage.utils.Singleton.security;
|
||||
|
||||
/**
|
||||
* Users who are subscribed to the sending address will see the message appear in their inbox.
|
||||
* Broadcasts are version 4 or 5.
|
||||
*/
|
||||
public abstract class Broadcast extends ObjectPayload implements Encrypted, PlaintextHolder {
|
||||
protected final long stream;
|
||||
protected CryptoBox encrypted;
|
||||
protected Plaintext plaintext;
|
||||
|
||||
protected Broadcast(long version, long stream, CryptoBox encrypted, Plaintext plaintext) {
|
||||
super(version);
|
||||
this.stream = stream;
|
||||
this.encrypted = encrypted;
|
||||
this.plaintext = plaintext;
|
||||
}
|
||||
|
||||
public static long getVersion(BitmessageAddress address) {
|
||||
return address.getVersion() < 4 ? 4 : 5;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSigned() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getSignature() {
|
||||
return plaintext.getSignature();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSignature(byte[] signature) {
|
||||
plaintext.setSignature(signature);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getStream() {
|
||||
return stream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Plaintext getPlaintext() {
|
||||
return plaintext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encrypt(byte[] publicKey) throws IOException {
|
||||
this.encrypted = new CryptoBox(plaintext, publicKey);
|
||||
}
|
||||
|
||||
public void encrypt() throws IOException {
|
||||
encrypt(security().createPublicKey(plaintext.getFrom().getPublicDecryptionKey()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decrypt(byte[] privateKey) throws IOException, DecryptionFailedException {
|
||||
plaintext = Plaintext.read(BROADCAST, encrypted.decrypt(privateKey));
|
||||
}
|
||||
|
||||
public void decrypt(BitmessageAddress address) throws IOException, DecryptionFailedException {
|
||||
decrypt(address.getPublicDecryptionKey());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDecrypted() {
|
||||
return plaintext != null;
|
||||
}
|
||||
}
|
@ -0,0 +1,196 @@
|
||||
/*
|
||||
* 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.entity.payload;
|
||||
|
||||
import ch.dissem.bitmessage.entity.Streamable;
|
||||
import ch.dissem.bitmessage.exception.DecryptionFailedException;
|
||||
import ch.dissem.bitmessage.utils.*;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static ch.dissem.bitmessage.entity.valueobject.PrivateKey.PRIVATE_KEY_SIZE;
|
||||
import static ch.dissem.bitmessage.utils.Singleton.security;
|
||||
|
||||
|
||||
public class CryptoBox implements Streamable {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CryptoBox.class);
|
||||
|
||||
private final byte[] initializationVector;
|
||||
private final int curveType;
|
||||
private final byte[] R;
|
||||
private final byte[] mac;
|
||||
private byte[] encrypted;
|
||||
|
||||
private long addressVersion;
|
||||
|
||||
|
||||
public CryptoBox(Streamable data, byte[] K) throws IOException {
|
||||
this(Encode.bytes(data), K);
|
||||
}
|
||||
|
||||
public CryptoBox(byte[] data, byte[] K) throws IOException {
|
||||
curveType = 0x02CA;
|
||||
|
||||
// 1. The destination public key is called K.
|
||||
// 2. Generate 16 random bytes using a secure random number generator. Call them IV.
|
||||
initializationVector = security().randomBytes(16);
|
||||
|
||||
// 3. Generate a new random EC key pair with private key called r and public key called R.
|
||||
byte[] r = security().randomBytes(PRIVATE_KEY_SIZE);
|
||||
R = security().createPublicKey(r);
|
||||
// 4. Do an EC point multiply with public key K and private key r. This gives you public key P.
|
||||
byte[] P = security().multiply(K, r);
|
||||
byte[] X = Points.getX(P);
|
||||
// 5. Use the X component of public key P and calculate the SHA512 hash H.
|
||||
byte[] H = security().sha512(X);
|
||||
// 6. The first 32 bytes of H are called key_e and the last 32 bytes are called key_m.
|
||||
byte[] key_e = Arrays.copyOfRange(H, 0, 32);
|
||||
byte[] key_m = Arrays.copyOfRange(H, 32, 64);
|
||||
// 7. Pad the input text to a multiple of 16 bytes, in accordance to PKCS7.
|
||||
// 8. Encrypt the data with AES-256-CBC, using IV as initialization vector, key_e as encryption key and the padded input text as payload. Call the output cipher text.
|
||||
encrypted = security().crypt(true, data, key_e, initializationVector);
|
||||
// 9. Calculate a 32 byte MAC with HMACSHA256, using key_m as salt and IV + R + cipher text as data. Call the output MAC.
|
||||
mac = calculateMac(key_m);
|
||||
|
||||
// The resulting data is: IV + R + cipher text + MAC
|
||||
}
|
||||
|
||||
private CryptoBox(Builder builder) {
|
||||
initializationVector = builder.initializationVector;
|
||||
curveType = builder.curveType;
|
||||
R = security().createPoint(builder.xComponent, builder.yComponent);
|
||||
encrypted = builder.encrypted;
|
||||
mac = builder.mac;
|
||||
}
|
||||
|
||||
public static CryptoBox read(InputStream stream, int length) throws IOException {
|
||||
AccessCounter counter = new AccessCounter();
|
||||
return new Builder()
|
||||
.IV(Decode.bytes(stream, 16, counter))
|
||||
.curveType(Decode.uint16(stream, counter))
|
||||
.X(Decode.shortVarBytes(stream, counter))
|
||||
.Y(Decode.shortVarBytes(stream, counter))
|
||||
.encrypted(Decode.bytes(stream, length - counter.length() - 32))
|
||||
.MAC(Decode.bytes(stream, 32))
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param k a private key, typically should be 32 bytes long
|
||||
* @return an InputStream yielding the decrypted data
|
||||
* @throws DecryptionFailedException if the payload can't be decrypted using this private key
|
||||
* @see <a href='https://bitmessage.org/wiki/Encryption#Decryption'>https://bitmessage.org/wiki/Encryption#Decryption</a>
|
||||
*/
|
||||
public InputStream decrypt(byte[] k) throws DecryptionFailedException {
|
||||
// 1. The private key used to decrypt is called k.
|
||||
// 2. Do an EC point multiply with private key k and public key R. This gives you public key P.
|
||||
byte[] P = security().multiply(R, k);
|
||||
// 3. Use the X component of public key P and calculate the SHA512 hash H.
|
||||
byte[] H = security().sha512(Arrays.copyOfRange(P, 1, 33));
|
||||
// 4. The first 32 bytes of H are called key_e and the last 32 bytes are called key_m.
|
||||
byte[] key_e = Arrays.copyOfRange(H, 0, 32);
|
||||
byte[] key_m = Arrays.copyOfRange(H, 32, 64);
|
||||
|
||||
// 5. Calculate MAC' with HMACSHA256, using key_m as salt and IV + R + cipher text as data.
|
||||
// 6. Compare MAC with MAC'. If not equal, decryption will fail.
|
||||
if (!Arrays.equals(mac, calculateMac(key_m))) {
|
||||
throw new DecryptionFailedException();
|
||||
}
|
||||
|
||||
// 7. Decrypt the cipher text with AES-256-CBC, using IV as initialization vector, key_e as decryption key
|
||||
// and the cipher text as payload. The output is the padded input text.
|
||||
return new ByteArrayInputStream(security().crypt(false, encrypted, key_e, initializationVector));
|
||||
}
|
||||
|
||||
private byte[] calculateMac(byte[] key_m) {
|
||||
try {
|
||||
ByteArrayOutputStream macData = new ByteArrayOutputStream();
|
||||
writeWithoutMAC(macData);
|
||||
return security().mac(key_m, macData.toByteArray());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeWithoutMAC(OutputStream out) throws IOException {
|
||||
out.write(initializationVector);
|
||||
Encode.int16(curveType, out);
|
||||
writeCoordinateComponent(out, Points.getX(R));
|
||||
writeCoordinateComponent(out, Points.getY(R));
|
||||
out.write(encrypted);
|
||||
}
|
||||
|
||||
private void writeCoordinateComponent(OutputStream out, byte[] x) throws IOException {
|
||||
int offset = Bytes.numberOfLeadingZeros(x);
|
||||
int length = x.length - offset;
|
||||
Encode.int16(length, out);
|
||||
out.write(x, offset, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(OutputStream stream) throws IOException {
|
||||
writeWithoutMAC(stream);
|
||||
stream.write(mac);
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
private byte[] initializationVector;
|
||||
private int curveType;
|
||||
private byte[] xComponent;
|
||||
private byte[] yComponent;
|
||||
private byte[] encrypted;
|
||||
private byte[] mac;
|
||||
|
||||
public Builder IV(byte[] initializationVector) {
|
||||
this.initializationVector = initializationVector;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder curveType(int curveType) {
|
||||
if (curveType != 0x2CA) LOG.trace("Unexpected curve type " + curveType);
|
||||
this.curveType = curveType;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder X(byte[] xComponent) {
|
||||
this.xComponent = xComponent;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder Y(byte[] yComponent) {
|
||||
this.yComponent = yComponent;
|
||||
return this;
|
||||
}
|
||||
|
||||
private Builder encrypted(byte[] encrypted) {
|
||||
this.encrypted = encrypted;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder MAC(byte[] mac) {
|
||||
this.mac = mac;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CryptoBox build() {
|
||||
return new CryptoBox(this);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
/*
|
||||
* 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.entity.payload;
|
||||
|
||||
import ch.dissem.bitmessage.utils.Decode;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* In cases we don't know what to do with an object, we just store its bytes and send it again - we don't really
|
||||
* have to know what it is.
|
||||
*/
|
||||
public class GenericPayload extends ObjectPayload {
|
||||
private long stream;
|
||||
private byte[] data;
|
||||
|
||||
public GenericPayload(long version, long stream, byte[] data) {
|
||||
super(version);
|
||||
this.stream = stream;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public static GenericPayload read(long version, InputStream is, long stream, int length) throws IOException {
|
||||
return new GenericPayload(version, stream, Decode.bytes(is, length));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectType getType() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getStream() {
|
||||
return stream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(OutputStream stream) throws IOException {
|
||||
stream.write(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
GenericPayload that = (GenericPayload) o;
|
||||
|
||||
if (stream != that.stream) return false;
|
||||
return Arrays.equals(data, that.data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = (int) (stream ^ (stream >>> 32));
|
||||
result = 31 * result + Arrays.hashCode(data);
|
||||
return result;
|
||||
}
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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.entity.payload;
|
||||
|
||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
||||
import ch.dissem.bitmessage.utils.Decode;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* Request for a public key.
|
||||
*/
|
||||
public class GetPubkey extends ObjectPayload {
|
||||
private long stream;
|
||||
private byte[] ripeTag;
|
||||
|
||||
public GetPubkey(BitmessageAddress address) {
|
||||
super(address.getVersion());
|
||||
this.stream = address.getStream();
|
||||
if (address.getVersion() < 4)
|
||||
this.ripeTag = address.getRipe();
|
||||
else
|
||||
this.ripeTag = address.getTag();
|
||||
}
|
||||
|
||||
private GetPubkey(long version, long stream, byte[] ripeOrTag) {
|
||||
super(version);
|
||||
this.stream = stream;
|
||||
this.ripeTag = ripeOrTag;
|
||||
}
|
||||
|
||||
public static GetPubkey read(InputStream is, long stream, int length, long version) throws IOException {
|
||||
return new GetPubkey(version, stream, Decode.bytes(is, length));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return an array of bytes that represent either the ripe, or the tag of an address, depending on the
|
||||
* address version.
|
||||
*/
|
||||
public byte[] getRipeTag() {
|
||||
return ripeTag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectType getType() {
|
||||
return ObjectType.GET_PUBKEY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getStream() {
|
||||
return stream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(OutputStream stream) throws IOException {
|
||||
stream.write(ripeTag);
|
||||
}
|
||||
}
|
109
core/src/main/java/ch/dissem/bitmessage/entity/payload/Msg.java
Normal file
109
core/src/main/java/ch/dissem/bitmessage/entity/payload/Msg.java
Normal file
@ -0,0 +1,109 @@
|
||||
/*
|
||||
* 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.entity.payload;
|
||||
|
||||
import ch.dissem.bitmessage.entity.Encrypted;
|
||||
import ch.dissem.bitmessage.entity.Plaintext;
|
||||
import ch.dissem.bitmessage.entity.PlaintextHolder;
|
||||
import ch.dissem.bitmessage.exception.DecryptionFailedException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import static ch.dissem.bitmessage.entity.Plaintext.Type.MSG;
|
||||
|
||||
/**
|
||||
* Used for person-to-person messages.
|
||||
*/
|
||||
public class Msg extends ObjectPayload implements Encrypted, PlaintextHolder {
|
||||
private long stream;
|
||||
private CryptoBox encrypted;
|
||||
private Plaintext plaintext;
|
||||
|
||||
private Msg(long stream, CryptoBox encrypted) {
|
||||
super(1);
|
||||
this.stream = stream;
|
||||
this.encrypted = encrypted;
|
||||
}
|
||||
|
||||
public Msg(Plaintext plaintext) {
|
||||
super(1);
|
||||
this.stream = plaintext.getStream();
|
||||
this.plaintext = plaintext;
|
||||
}
|
||||
|
||||
public static Msg read(InputStream in, long stream, int length) throws IOException {
|
||||
return new Msg(stream, CryptoBox.read(in, length));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Plaintext getPlaintext() {
|
||||
return plaintext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectType getType() {
|
||||
return ObjectType.MSG;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getStream() {
|
||||
return stream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSigned() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeBytesToSign(OutputStream out) throws IOException {
|
||||
plaintext.write(out, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getSignature() {
|
||||
return plaintext.getSignature();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSignature(byte[] signature) {
|
||||
plaintext.setSignature(signature);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encrypt(byte[] publicKey) throws IOException {
|
||||
this.encrypted = new CryptoBox(plaintext, publicKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decrypt(byte[] privateKey) throws IOException, DecryptionFailedException {
|
||||
plaintext = Plaintext.read(MSG, encrypted.decrypt(privateKey));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDecrypted() {
|
||||
return plaintext != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(OutputStream out) throws IOException {
|
||||
if (encrypted == null) throw new IllegalStateException("Msg must be signed and encrypted before writing it.");
|
||||
encrypted.write(out);
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
/*
|
||||
* 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.entity.payload;
|
||||
|
||||
import ch.dissem.bitmessage.entity.ObjectMessage;
|
||||
import ch.dissem.bitmessage.entity.Streamable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* The payload of an 'object' command. This is shared by the network.
|
||||
*/
|
||||
public abstract class ObjectPayload implements Streamable {
|
||||
private final long version;
|
||||
|
||||
protected ObjectPayload(long version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
|
||||
public abstract ObjectType getType();
|
||||
|
||||
public abstract long getStream();
|
||||
|
||||
public long getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public boolean isSigned() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void writeBytesToSign(OutputStream out) throws IOException {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the ECDSA signature which, as of protocol v3, covers the object header starting with the time,
|
||||
* appended with the data described in this table down to the extra_bytes. Therefore, this must
|
||||
* be checked and set in the {@link ObjectMessage} object.
|
||||
*/
|
||||
public byte[] getSignature() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setSignature(byte[] signature) {
|
||||
// nothing to do
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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.entity.payload;
|
||||
|
||||
/**
|
||||
* Known types for 'object' messages. Must not be used where an unknown type must be resent.
|
||||
*/
|
||||
public enum ObjectType {
|
||||
GET_PUBKEY(0),
|
||||
PUBKEY(1),
|
||||
MSG(2),
|
||||
BROADCAST(3);
|
||||
|
||||
int number;
|
||||
|
||||
ObjectType(int number) {
|
||||
this.number = number;
|
||||
}
|
||||
|
||||
public static ObjectType fromNumber(long number) {
|
||||
for (ObjectType type : values()) {
|
||||
if (type.number == number) return type;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public long getNumber() {
|
||||
return number;
|
||||
}
|
||||
}
|
@ -0,0 +1,107 @@
|
||||
/*
|
||||
* 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.entity.payload;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import static ch.dissem.bitmessage.utils.Singleton.security;
|
||||
|
||||
/**
|
||||
* Public keys for signing and encryption, the answer to a 'getpubkey' request.
|
||||
*/
|
||||
public abstract class Pubkey extends ObjectPayload {
|
||||
public final static long LATEST_VERSION = 4;
|
||||
|
||||
protected Pubkey(long version) {
|
||||
super(version);
|
||||
}
|
||||
|
||||
public static byte[] getRipe(byte[] publicSigningKey, byte[] publicEncryptionKey) {
|
||||
return security().ripemd160(security().sha512(publicSigningKey, publicEncryptionKey));
|
||||
}
|
||||
|
||||
public abstract byte[] getSigningKey();
|
||||
|
||||
public abstract byte[] getEncryptionKey();
|
||||
|
||||
public abstract int getBehaviorBitfield();
|
||||
|
||||
public byte[] getRipe() {
|
||||
return security().ripemd160(security().sha512(getSigningKey(), getEncryptionKey()));
|
||||
}
|
||||
|
||||
public long getNonceTrialsPerByte() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public long getExtraBytes() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void writeUnencrypted(OutputStream out) throws IOException {
|
||||
write(out);
|
||||
}
|
||||
|
||||
protected byte[] add0x04(byte[] key) {
|
||||
if (key.length == 65) return key;
|
||||
byte[] result = new byte[65];
|
||||
result[0] = 4;
|
||||
System.arraycopy(key, 0, result, 1, 64);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bits 0 through 29 are yet undefined
|
||||
*/
|
||||
public enum Feature {
|
||||
/**
|
||||
* Receiving node expects that the RIPE hash encoded in their address preceedes the encrypted message data of msg
|
||||
* messages bound for them.
|
||||
*/
|
||||
INCLUDE_DESTINATION(1 << 30),
|
||||
/**
|
||||
* If true, the receiving node does send acknowledgements (rather than dropping them).
|
||||
*/
|
||||
DOES_ACK(1 << 31);
|
||||
|
||||
private int bit;
|
||||
|
||||
Feature(int bit) {
|
||||
this.bit = bit;
|
||||
}
|
||||
|
||||
public static int bitfield(Feature... features) {
|
||||
int bits = 0;
|
||||
for (Feature feature : features) {
|
||||
bits |= feature.bit;
|
||||
}
|
||||
return bits;
|
||||
}
|
||||
|
||||
public static Feature[] features(int bitfield) {
|
||||
ArrayList<Feature> features = new ArrayList<>(Feature.values().length);
|
||||
for (Feature feature : Feature.values()) {
|
||||
if ((bitfield & feature.bit) != 0) {
|
||||
features.add(feature);
|
||||
}
|
||||
}
|
||||
return features.toArray(new Feature[features.size()]);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,126 @@
|
||||
/*
|
||||
* 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.entity.payload;
|
||||
|
||||
import ch.dissem.bitmessage.utils.Decode;
|
||||
import ch.dissem.bitmessage.utils.Encode;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* A version 2 public key.
|
||||
*/
|
||||
public class V2Pubkey extends Pubkey {
|
||||
protected long stream;
|
||||
protected int behaviorBitfield;
|
||||
protected byte[] publicSigningKey; // 64 Bytes
|
||||
protected byte[] publicEncryptionKey; // 64 Bytes
|
||||
|
||||
protected V2Pubkey(long version) {
|
||||
super(version);
|
||||
}
|
||||
|
||||
private V2Pubkey(long version, Builder builder) {
|
||||
super(version);
|
||||
stream = builder.streamNumber;
|
||||
behaviorBitfield = builder.behaviorBitfield;
|
||||
publicSigningKey = add0x04(builder.publicSigningKey);
|
||||
publicEncryptionKey = add0x04(builder.publicEncryptionKey);
|
||||
}
|
||||
|
||||
public static V2Pubkey read(InputStream is, long stream) throws IOException {
|
||||
return new V2Pubkey.Builder()
|
||||
.stream(stream)
|
||||
.behaviorBitfield((int) Decode.uint32(is))
|
||||
.publicSigningKey(Decode.bytes(is, 64))
|
||||
.publicEncryptionKey(Decode.bytes(is, 64))
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getVersion() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectType getType() {
|
||||
return ObjectType.PUBKEY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getStream() {
|
||||
return stream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getSigningKey() {
|
||||
return publicSigningKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getEncryptionKey() {
|
||||
return publicEncryptionKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBehaviorBitfield() {
|
||||
return behaviorBitfield;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(OutputStream os) throws IOException {
|
||||
Encode.int32(behaviorBitfield, os);
|
||||
os.write(publicSigningKey, 1, 64);
|
||||
os.write(publicEncryptionKey, 1, 64);
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private long streamNumber;
|
||||
private int behaviorBitfield;
|
||||
private byte[] publicSigningKey;
|
||||
private byte[] publicEncryptionKey;
|
||||
|
||||
public Builder() {
|
||||
}
|
||||
|
||||
public Builder stream(long streamNumber) {
|
||||
this.streamNumber = streamNumber;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder behaviorBitfield(int behaviorBitfield) {
|
||||
this.behaviorBitfield = behaviorBitfield;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder publicSigningKey(byte[] publicSigningKey) {
|
||||
this.publicSigningKey = publicSigningKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder publicEncryptionKey(byte[] publicEncryptionKey) {
|
||||
this.publicEncryptionKey = publicEncryptionKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
public V2Pubkey build() {
|
||||
return new V2Pubkey(2, this);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,168 @@
|
||||
/*
|
||||
* 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.entity.payload;
|
||||
|
||||
import ch.dissem.bitmessage.utils.Decode;
|
||||
import ch.dissem.bitmessage.utils.Encode;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* A version 3 public key.
|
||||
*/
|
||||
public class V3Pubkey extends V2Pubkey {
|
||||
long nonceTrialsPerByte;
|
||||
long extraBytes;
|
||||
byte[] signature;
|
||||
|
||||
protected V3Pubkey(long version, Builder builder) {
|
||||
super(version);
|
||||
stream = builder.streamNumber;
|
||||
behaviorBitfield = builder.behaviorBitfield;
|
||||
publicSigningKey = add0x04(builder.publicSigningKey);
|
||||
publicEncryptionKey = add0x04(builder.publicEncryptionKey);
|
||||
nonceTrialsPerByte = builder.nonceTrialsPerByte;
|
||||
extraBytes = builder.extraBytes;
|
||||
signature = builder.signature;
|
||||
}
|
||||
|
||||
public static V3Pubkey read(InputStream is, long stream) throws IOException {
|
||||
return new V3Pubkey.Builder()
|
||||
.stream(stream)
|
||||
.behaviorBitfield(Decode.int32(is))
|
||||
.publicSigningKey(Decode.bytes(is, 64))
|
||||
.publicEncryptionKey(Decode.bytes(is, 64))
|
||||
.nonceTrialsPerByte(Decode.varInt(is))
|
||||
.extraBytes(Decode.varInt(is))
|
||||
.signature(Decode.varBytes(is))
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(OutputStream out) throws IOException {
|
||||
writeBytesToSign(out);
|
||||
Encode.varInt(signature.length, out);
|
||||
out.write(signature);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getVersion() {
|
||||
return 3;
|
||||
}
|
||||
|
||||
public long getNonceTrialsPerByte() {
|
||||
return nonceTrialsPerByte;
|
||||
}
|
||||
|
||||
public long getExtraBytes() {
|
||||
return extraBytes;
|
||||
}
|
||||
|
||||
public boolean isSigned() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void writeBytesToSign(OutputStream out) throws IOException {
|
||||
super.write(out);
|
||||
Encode.varInt(nonceTrialsPerByte, out);
|
||||
Encode.varInt(extraBytes, out);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getSignature() {
|
||||
return signature;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSignature(byte[] signature) {
|
||||
this.signature = signature;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
V3Pubkey pubkey = (V3Pubkey) o;
|
||||
return Objects.equals(nonceTrialsPerByte, pubkey.nonceTrialsPerByte) &&
|
||||
Objects.equals(extraBytes, pubkey.extraBytes) &&
|
||||
stream == pubkey.stream &&
|
||||
behaviorBitfield == pubkey.behaviorBitfield &&
|
||||
Arrays.equals(publicSigningKey, pubkey.publicSigningKey) &&
|
||||
Arrays.equals(publicEncryptionKey, pubkey.publicEncryptionKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(nonceTrialsPerByte, extraBytes);
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private long streamNumber;
|
||||
private int behaviorBitfield;
|
||||
private byte[] publicSigningKey;
|
||||
private byte[] publicEncryptionKey;
|
||||
private long nonceTrialsPerByte;
|
||||
private long extraBytes;
|
||||
private byte[] signature = new byte[0];
|
||||
|
||||
public Builder() {
|
||||
}
|
||||
|
||||
public Builder stream(long streamNumber) {
|
||||
this.streamNumber = streamNumber;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder behaviorBitfield(int behaviorBitfield) {
|
||||
this.behaviorBitfield = behaviorBitfield;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder publicSigningKey(byte[] publicSigningKey) {
|
||||
this.publicSigningKey = publicSigningKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder publicEncryptionKey(byte[] publicEncryptionKey) {
|
||||
this.publicEncryptionKey = publicEncryptionKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder nonceTrialsPerByte(long nonceTrialsPerByte) {
|
||||
this.nonceTrialsPerByte = nonceTrialsPerByte;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder extraBytes(long extraBytes) {
|
||||
this.extraBytes = extraBytes;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder signature(byte[] signature) {
|
||||
this.signature = signature;
|
||||
return this;
|
||||
}
|
||||
|
||||
public V3Pubkey build() {
|
||||
return new V3Pubkey(3, this);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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.entity.payload;
|
||||
|
||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
||||
import ch.dissem.bitmessage.entity.Plaintext;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* Users who are subscribed to the sending address will see the message appear in their inbox.
|
||||
* Broadcasts are version 4 or 5.
|
||||
*/
|
||||
public class V4Broadcast extends Broadcast {
|
||||
protected V4Broadcast(long version, long stream, CryptoBox encrypted, Plaintext plaintext) {
|
||||
super(version, stream, encrypted, plaintext);
|
||||
}
|
||||
|
||||
public V4Broadcast(BitmessageAddress senderAddress, Plaintext plaintext) {
|
||||
super(4, senderAddress.getStream(), null, plaintext);
|
||||
if (senderAddress.getVersion() >= 4)
|
||||
throw new IllegalArgumentException("Address version 3 or older expected, but was " + senderAddress.getVersion());
|
||||
}
|
||||
|
||||
public static V4Broadcast read(InputStream in, long stream, int length) throws IOException {
|
||||
return new V4Broadcast(4, stream, CryptoBox.read(in, length), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectType getType() {
|
||||
return ObjectType.BROADCAST;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeBytesToSign(OutputStream out) throws IOException {
|
||||
plaintext.write(out, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(OutputStream out) throws IOException {
|
||||
encrypted.write(out);
|
||||
}
|
||||
}
|
@ -0,0 +1,177 @@
|
||||
/*
|
||||
* 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.entity.payload;
|
||||
|
||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
||||
import ch.dissem.bitmessage.entity.Encrypted;
|
||||
import ch.dissem.bitmessage.exception.DecryptionFailedException;
|
||||
import ch.dissem.bitmessage.utils.Decode;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* A version 4 public key. When version 4 pubkeys are created, most of the data in the pubkey is encrypted. This is
|
||||
* done in such a way that only someone who has the Bitmessage address which corresponds to a pubkey can decrypt and
|
||||
* use that pubkey. This prevents people from gathering pubkeys sent around the network and using the data from them
|
||||
* to create messages to be used in spam or in flooding attacks.
|
||||
*/
|
||||
public class V4Pubkey extends Pubkey implements Encrypted {
|
||||
private long stream;
|
||||
private byte[] tag;
|
||||
private CryptoBox encrypted;
|
||||
private V3Pubkey decrypted;
|
||||
|
||||
private V4Pubkey(long stream, byte[] tag, CryptoBox encrypted) {
|
||||
super(4);
|
||||
this.stream = stream;
|
||||
this.tag = tag;
|
||||
this.encrypted = encrypted;
|
||||
}
|
||||
|
||||
public V4Pubkey(V3Pubkey decrypted) {
|
||||
super(4);
|
||||
this.decrypted = decrypted;
|
||||
this.stream = decrypted.stream;
|
||||
this.tag = BitmessageAddress.calculateTag(4, decrypted.getStream(), decrypted.getRipe());
|
||||
}
|
||||
|
||||
public static V4Pubkey read(InputStream in, long stream, int length, boolean encrypted) throws IOException {
|
||||
if (encrypted)
|
||||
return new V4Pubkey(stream,
|
||||
Decode.bytes(in, 32),
|
||||
CryptoBox.read(in, length - 32));
|
||||
else
|
||||
return new V4Pubkey(V3Pubkey.read(in, stream));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encrypt(byte[] publicKey) throws IOException {
|
||||
if (getSignature() == null) throw new IllegalStateException("Pubkey must be signed before encryption.");
|
||||
this.encrypted = new CryptoBox(decrypted, publicKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decrypt(byte[] privateKey) throws IOException, DecryptionFailedException {
|
||||
decrypted = V3Pubkey.read(encrypted.decrypt(privateKey), stream);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDecrypted() {
|
||||
return decrypted != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(OutputStream stream) throws IOException {
|
||||
stream.write(tag);
|
||||
encrypted.write(stream);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeUnencrypted(OutputStream out) throws IOException {
|
||||
decrypted.write(out);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeBytesToSign(OutputStream out) throws IOException {
|
||||
out.write(tag);
|
||||
decrypted.writeBytesToSign(out);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getVersion() {
|
||||
return 4;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectType getType() {
|
||||
return ObjectType.PUBKEY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getStream() {
|
||||
return stream;
|
||||
}
|
||||
|
||||
public byte[] getTag() {
|
||||
return tag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getSigningKey() {
|
||||
return decrypted.getSigningKey();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getEncryptionKey() {
|
||||
return decrypted.getEncryptionKey();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBehaviorBitfield() {
|
||||
return decrypted.getBehaviorBitfield();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getSignature() {
|
||||
if (decrypted != null)
|
||||
return decrypted.getSignature();
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSignature(byte[] signature) {
|
||||
decrypted.setSignature(signature);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSigned() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public long getNonceTrialsPerByte() {
|
||||
return decrypted.getNonceTrialsPerByte();
|
||||
}
|
||||
|
||||
public long getExtraBytes() {
|
||||
return decrypted.getExtraBytes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
V4Pubkey v4Pubkey = (V4Pubkey) o;
|
||||
|
||||
if (stream != v4Pubkey.stream) return false;
|
||||
if (!Arrays.equals(tag, v4Pubkey.tag)) return false;
|
||||
return !(decrypted != null ? !decrypted.equals(v4Pubkey.decrypted) : v4Pubkey.decrypted != null);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = (int) (stream ^ (stream >>> 32));
|
||||
result = 31 * result + Arrays.hashCode(tag);
|
||||
result = 31 * result + (decrypted != null ? decrypted.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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.entity.payload;
|
||||
|
||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
||||
import ch.dissem.bitmessage.entity.Plaintext;
|
||||
import ch.dissem.bitmessage.utils.Decode;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* Users who are subscribed to the sending address will see the message appear in their inbox.
|
||||
*/
|
||||
public class V5Broadcast extends V4Broadcast {
|
||||
private byte[] tag;
|
||||
|
||||
private V5Broadcast(long stream, byte[] tag, CryptoBox encrypted) {
|
||||
super(5, stream, encrypted, null);
|
||||
this.tag = tag;
|
||||
}
|
||||
|
||||
public V5Broadcast(BitmessageAddress senderAddress, Plaintext plaintext) {
|
||||
super(5, senderAddress.getStream(), null, plaintext);
|
||||
if (senderAddress.getVersion() < 4)
|
||||
throw new IllegalArgumentException("Address version 4 (or newer) expected, but was " + senderAddress.getVersion());
|
||||
this.tag = senderAddress.getTag();
|
||||
}
|
||||
|
||||
public static V5Broadcast read(InputStream is, long stream, int length) throws IOException {
|
||||
return new V5Broadcast(stream, Decode.bytes(is, 32), CryptoBox.read(is, length - 32));
|
||||
}
|
||||
|
||||
public byte[] getTag() {
|
||||
return tag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeBytesToSign(OutputStream out) throws IOException {
|
||||
out.write(tag);
|
||||
super.writeBytesToSign(out);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(OutputStream out) throws IOException {
|
||||
out.write(tag);
|
||||
super.write(out);
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* 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.entity.valueobject;
|
||||
|
||||
import ch.dissem.bitmessage.entity.Streamable;
|
||||
import ch.dissem.bitmessage.utils.Strings;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.Serializable;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class InventoryVector implements Streamable, Serializable {
|
||||
/**
|
||||
* Hash of the object
|
||||
*/
|
||||
private final byte[] hash;
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof InventoryVector)) return false;
|
||||
|
||||
InventoryVector that = (InventoryVector) o;
|
||||
|
||||
return Arrays.equals(hash, that.hash);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return hash != null ? Arrays.hashCode(hash) : 0;
|
||||
}
|
||||
|
||||
public byte[] getHash() {
|
||||
return hash;
|
||||
}
|
||||
|
||||
public InventoryVector(byte[] hash) {
|
||||
this.hash = hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(OutputStream stream) throws IOException {
|
||||
stream.write(hash);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return Strings.hex(hash).toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
/*
|
||||
* 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.entity.valueobject;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Objects;
|
||||
|
||||
public class Label implements Serializable {
|
||||
private Object id;
|
||||
private String label;
|
||||
private Type type;
|
||||
private int color;
|
||||
|
||||
public Label(String label, Type type, int color) {
|
||||
this.label = label;
|
||||
this.type = type;
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return RGBA representation for the color.
|
||||
*/
|
||||
public int getColor() {
|
||||
return color;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param color RGBA representation for the color.
|
||||
*/
|
||||
public void setColor(int color) {
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return label;
|
||||
}
|
||||
|
||||
public Object getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Object id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
Label label1 = (Label) o;
|
||||
return Objects.equals(label, label1.label);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(label);
|
||||
}
|
||||
|
||||
public enum Type {
|
||||
INBOX,
|
||||
BROADCAST,
|
||||
DRAFT,
|
||||
SENT,
|
||||
UNREAD,
|
||||
TRASH
|
||||
}
|
||||
}
|
@ -0,0 +1,209 @@
|
||||
/*
|
||||
* 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.entity.valueobject;
|
||||
|
||||
import ch.dissem.bitmessage.entity.Streamable;
|
||||
import ch.dissem.bitmessage.utils.Encode;
|
||||
import ch.dissem.bitmessage.utils.UnixTime;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* A node's address. It's written in IPv6 format.
|
||||
*/
|
||||
public class NetworkAddress implements Streamable {
|
||||
private long time;
|
||||
|
||||
/**
|
||||
* Stream number for this node
|
||||
*/
|
||||
private long stream;
|
||||
|
||||
/**
|
||||
* same service(s) listed in version
|
||||
*/
|
||||
private long services;
|
||||
|
||||
/**
|
||||
* IPv6 address. IPv4 addresses are written into the message as a 16 byte IPv4-mapped IPv6 address
|
||||
* (12 bytes 00 00 00 00 00 00 00 00 00 00 FF FF, followed by the 4 bytes of the IPv4 address).
|
||||
*/
|
||||
private byte[] ipv6;
|
||||
private int port;
|
||||
|
||||
private NetworkAddress(Builder builder) {
|
||||
time = builder.time;
|
||||
stream = builder.stream;
|
||||
services = builder.services;
|
||||
ipv6 = builder.ipv6;
|
||||
port = builder.port;
|
||||
}
|
||||
|
||||
public byte[] getIPv6() {
|
||||
return ipv6;
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public long getServices() {
|
||||
return services;
|
||||
}
|
||||
|
||||
public long getStream() {
|
||||
return stream;
|
||||
}
|
||||
|
||||
public long getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
public void setTime(long time) {
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
public InetAddress toInetAddress() {
|
||||
try {
|
||||
return InetAddress.getByAddress(ipv6);
|
||||
} catch (UnknownHostException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
NetworkAddress that = (NetworkAddress) o;
|
||||
|
||||
return port == that.port && Arrays.equals(ipv6, that.ipv6);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = ipv6 != null ? Arrays.hashCode(ipv6) : 0;
|
||||
result = 31 * result + port;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[" + toInetAddress() + "]:" + port;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(OutputStream stream) throws IOException {
|
||||
write(stream, false);
|
||||
}
|
||||
|
||||
public void write(OutputStream stream, boolean light) throws IOException {
|
||||
if (!light) {
|
||||
Encode.int64(time, stream);
|
||||
Encode.int32(this.stream, stream);
|
||||
}
|
||||
Encode.int64(services, stream);
|
||||
stream.write(ipv6);
|
||||
Encode.int16(port, stream);
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
private long time;
|
||||
private long stream;
|
||||
private long services = 1;
|
||||
private byte[] ipv6;
|
||||
private int port;
|
||||
|
||||
public Builder() {
|
||||
}
|
||||
|
||||
public Builder time(final long time) {
|
||||
this.time = time;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder stream(final long stream) {
|
||||
this.stream = stream;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder services(final long services) {
|
||||
this.services = services;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder ip(InetAddress inetAddress) {
|
||||
byte[] addr = inetAddress.getAddress();
|
||||
if (addr.length == 16) {
|
||||
this.ipv6 = addr;
|
||||
} else if (addr.length == 4) {
|
||||
this.ipv6 = new byte[16];
|
||||
this.ipv6[10] = (byte) 0xff;
|
||||
this.ipv6[11] = (byte) 0xff;
|
||||
System.arraycopy(addr, 0, this.ipv6, 12, 4);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Weird address " + inetAddress);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder ipv6(byte[] ipv6) {
|
||||
this.ipv6 = ipv6;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder ipv6(int p00, int p01, int p02, int p03,
|
||||
int p04, int p05, int p06, int p07,
|
||||
int p08, int p09, int p10, int p11,
|
||||
int p12, int p13, int p14, int p15) {
|
||||
this.ipv6 = new byte[]{
|
||||
(byte) p00, (byte) p01, (byte) p02, (byte) p03,
|
||||
(byte) p04, (byte) p05, (byte) p06, (byte) p07,
|
||||
(byte) p08, (byte) p09, (byte) p10, (byte) p11,
|
||||
(byte) p12, (byte) p13, (byte) p14, (byte) p15
|
||||
};
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder ipv4(int p00, int p01, int p02, int p03) {
|
||||
this.ipv6 = new byte[]{
|
||||
(byte) 0, (byte) 0, (byte) 0x00, (byte) 0x00,
|
||||
(byte) 0, (byte) 0, (byte) 0x00, (byte) 0x00,
|
||||
(byte) 0, (byte) 0, (byte) 0xff, (byte) 0xff,
|
||||
(byte) p00, (byte) p01, (byte) p02, (byte) p03
|
||||
};
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder port(final int port) {
|
||||
this.port = port;
|
||||
return this;
|
||||
}
|
||||
|
||||
public NetworkAddress build() {
|
||||
if (time == 0) {
|
||||
time = UnixTime.now();
|
||||
}
|
||||
return new NetworkAddress(this);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,115 @@
|
||||
/*
|
||||
* 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.entity.valueobject;
|
||||
|
||||
import ch.dissem.bitmessage.entity.Streamable;
|
||||
import ch.dissem.bitmessage.entity.payload.Pubkey;
|
||||
import ch.dissem.bitmessage.factory.Factory;
|
||||
import ch.dissem.bitmessage.utils.Bytes;
|
||||
import ch.dissem.bitmessage.utils.Decode;
|
||||
import ch.dissem.bitmessage.utils.Encode;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
import static ch.dissem.bitmessage.utils.Singleton.security;
|
||||
|
||||
/**
|
||||
* Represents a private key. Additional information (stream, version, features, ...) is stored in the accompanying
|
||||
* {@link Pubkey} object.
|
||||
*/
|
||||
public class PrivateKey implements Streamable {
|
||||
public static final int PRIVATE_KEY_SIZE = 32;
|
||||
private final byte[] privateSigningKey;
|
||||
private final byte[] privateEncryptionKey;
|
||||
|
||||
private final Pubkey pubkey;
|
||||
|
||||
public PrivateKey(boolean shorter, long stream, long nonceTrialsPerByte, long extraBytes, Pubkey.Feature... features) {
|
||||
byte[] privSK;
|
||||
byte[] pubSK;
|
||||
byte[] privEK;
|
||||
byte[] pubEK;
|
||||
byte[] ripe;
|
||||
do {
|
||||
privSK = security().randomBytes(PRIVATE_KEY_SIZE);
|
||||
privEK = security().randomBytes(PRIVATE_KEY_SIZE);
|
||||
pubSK = security().createPublicKey(privSK);
|
||||
pubEK = security().createPublicKey(privEK);
|
||||
ripe = Pubkey.getRipe(pubSK, pubEK);
|
||||
} while (ripe[0] != 0 || (shorter && ripe[1] != 0));
|
||||
this.privateSigningKey = privSK;
|
||||
this.privateEncryptionKey = privEK;
|
||||
this.pubkey = security().createPubkey(Pubkey.LATEST_VERSION, stream, privateSigningKey, privateEncryptionKey,
|
||||
nonceTrialsPerByte, extraBytes, features);
|
||||
}
|
||||
|
||||
public PrivateKey(byte[] privateSigningKey, byte[] privateEncryptionKey, Pubkey pubkey) {
|
||||
this.privateSigningKey = privateSigningKey;
|
||||
this.privateEncryptionKey = privateEncryptionKey;
|
||||
this.pubkey = pubkey;
|
||||
}
|
||||
|
||||
public PrivateKey(long version, long stream, String passphrase, long nonceTrialsPerByte, long extraBytes, Pubkey.Feature... features) {
|
||||
try {
|
||||
// FIXME: this is most definitely wrong
|
||||
this.privateSigningKey = Bytes.truncate(security().sha512(passphrase.getBytes("UTF-8"), new byte[]{0}), 32);
|
||||
this.privateEncryptionKey = Bytes.truncate(security().sha512(passphrase.getBytes("UTF-8"), new byte[]{1}), 32);
|
||||
this.pubkey = security().createPubkey(version, stream, privateSigningKey, privateEncryptionKey,
|
||||
nonceTrialsPerByte, extraBytes, features);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static PrivateKey read(InputStream is) throws IOException {
|
||||
int version = (int) Decode.varInt(is);
|
||||
long stream = Decode.varInt(is);
|
||||
int len = (int) Decode.varInt(is);
|
||||
Pubkey pubkey = Factory.readPubkey(version, stream, is, len, false);
|
||||
len = (int) Decode.varInt(is);
|
||||
byte[] signingKey = Decode.bytes(is, len);
|
||||
len = (int) Decode.varInt(is);
|
||||
byte[] encryptionKey = Decode.bytes(is, len);
|
||||
return new PrivateKey(signingKey, encryptionKey, pubkey);
|
||||
}
|
||||
|
||||
public byte[] getPrivateSigningKey() {
|
||||
return privateSigningKey;
|
||||
}
|
||||
|
||||
public byte[] getPrivateEncryptionKey() {
|
||||
return privateEncryptionKey;
|
||||
}
|
||||
|
||||
public Pubkey getPubkey() {
|
||||
return pubkey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(OutputStream out) throws IOException {
|
||||
Encode.varInt(pubkey.getVersion(), out);
|
||||
Encode.varInt(pubkey.getStream(), out);
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
pubkey.writeUnencrypted(baos);
|
||||
Encode.varInt(baos.size(), out);
|
||||
out.write(baos.toByteArray());
|
||||
Encode.varInt(privateSigningKey.length, out);
|
||||
out.write(privateSigningKey);
|
||||
Encode.varInt(privateEncryptionKey.length, out);
|
||||
out.write(privateEncryptionKey);
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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.exception;
|
||||
|
||||
/**
|
||||
* Indicates an illegal Bitmessage address
|
||||
*/
|
||||
public class AddressFormatException extends RuntimeException {
|
||||
public AddressFormatException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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.exception;
|
||||
|
||||
public class DecryptionFailedException extends Exception {
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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.exception;
|
||||
|
||||
import ch.dissem.bitmessage.utils.Strings;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class InsufficientProofOfWorkException extends IOException {
|
||||
public InsufficientProofOfWorkException(byte[] target, byte[] hash) {
|
||||
super("Insufficient proof of work: " + Strings.hex(target) + " required, " + Strings.hex(Arrays.copyOfRange(hash, 0, 8)) + " achieved.");
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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.exception;
|
||||
|
||||
/**
|
||||
* An exception on the node that's severe enough to cause the client to disconnect this node.
|
||||
*
|
||||
* @author Ch. Basler
|
||||
*/
|
||||
public class NodeException extends RuntimeException {
|
||||
public NodeException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public NodeException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
206
core/src/main/java/ch/dissem/bitmessage/factory/Factory.java
Normal file
206
core/src/main/java/ch/dissem/bitmessage/factory/Factory.java
Normal file
@ -0,0 +1,206 @@
|
||||
/*
|
||||
* 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.factory;
|
||||
|
||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
||||
import ch.dissem.bitmessage.entity.NetworkMessage;
|
||||
import ch.dissem.bitmessage.entity.ObjectMessage;
|
||||
import ch.dissem.bitmessage.entity.Plaintext;
|
||||
import ch.dissem.bitmessage.entity.payload.*;
|
||||
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
|
||||
import ch.dissem.bitmessage.exception.NodeException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.SocketException;
|
||||
import java.net.SocketTimeoutException;
|
||||
|
||||
import static ch.dissem.bitmessage.utils.Singleton.security;
|
||||
|
||||
/**
|
||||
* Creates {@link NetworkMessage} objects from {@link InputStream InputStreams}
|
||||
*/
|
||||
public class Factory {
|
||||
public static final Logger LOG = LoggerFactory.getLogger(Factory.class);
|
||||
|
||||
public static NetworkMessage getNetworkMessage(int version, InputStream stream) throws SocketTimeoutException {
|
||||
try {
|
||||
return V3MessageFactory.read(stream);
|
||||
} catch (SocketTimeoutException | NodeException e) {
|
||||
throw e;
|
||||
} catch (SocketException e) {
|
||||
throw new NodeException(e.getMessage(), e);
|
||||
} catch (Exception e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static ObjectMessage getObjectMessage(int version, InputStream stream, int length) {
|
||||
try {
|
||||
return V3MessageFactory.readObject(stream, length);
|
||||
} catch (IOException e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static Pubkey createPubkey(long version, long stream, byte[] publicSigningKey, byte[] publicEncryptionKey,
|
||||
long nonceTrialsPerByte, long extraBytes, Pubkey.Feature... features) {
|
||||
return createPubkey(version, stream, publicSigningKey, publicEncryptionKey, nonceTrialsPerByte, extraBytes,
|
||||
Pubkey.Feature.bitfield(features));
|
||||
}
|
||||
|
||||
public static Pubkey createPubkey(long version, long stream, byte[] publicSigningKey, byte[] publicEncryptionKey,
|
||||
long nonceTrialsPerByte, long extraBytes, int behaviourBitfield) {
|
||||
if (publicSigningKey.length != 64 && publicSigningKey.length != 65)
|
||||
throw new IllegalArgumentException("64 bytes signing key expected, but it was "
|
||||
+ publicSigningKey.length + " bytes long.");
|
||||
if (publicEncryptionKey.length != 64 && publicEncryptionKey.length != 65)
|
||||
throw new IllegalArgumentException("64 bytes encryption key expected, but it was "
|
||||
+ publicEncryptionKey.length + " bytes long.");
|
||||
|
||||
switch ((int) version) {
|
||||
case 2:
|
||||
return new V2Pubkey.Builder()
|
||||
.stream(stream)
|
||||
.publicSigningKey(publicSigningKey)
|
||||
.publicEncryptionKey(publicEncryptionKey)
|
||||
.behaviorBitfield(behaviourBitfield)
|
||||
.build();
|
||||
case 3:
|
||||
return new V3Pubkey.Builder()
|
||||
.stream(stream)
|
||||
.publicSigningKey(publicSigningKey)
|
||||
.publicEncryptionKey(publicEncryptionKey)
|
||||
.behaviorBitfield(behaviourBitfield)
|
||||
.nonceTrialsPerByte(nonceTrialsPerByte)
|
||||
.extraBytes(extraBytes)
|
||||
.build();
|
||||
case 4:
|
||||
return new V4Pubkey(
|
||||
new V3Pubkey.Builder()
|
||||
.stream(stream)
|
||||
.publicSigningKey(publicSigningKey)
|
||||
.publicEncryptionKey(publicEncryptionKey)
|
||||
.behaviorBitfield(behaviourBitfield)
|
||||
.nonceTrialsPerByte(nonceTrialsPerByte)
|
||||
.extraBytes(extraBytes)
|
||||
.build()
|
||||
);
|
||||
default:
|
||||
throw new IllegalArgumentException("Unexpected pubkey version " + version);
|
||||
}
|
||||
}
|
||||
|
||||
public static BitmessageAddress createIdentityFromPrivateKey(String address,
|
||||
byte[] privateSigningKey, byte[] privateEncryptionKey,
|
||||
long nonceTrialsPerByte, long extraBytes,
|
||||
int behaviourBitfield) {
|
||||
BitmessageAddress temp = new BitmessageAddress(address);
|
||||
PrivateKey privateKey = new PrivateKey(privateSigningKey, privateEncryptionKey,
|
||||
createPubkey(temp.getVersion(), temp.getStream(),
|
||||
security().createPublicKey(privateSigningKey),
|
||||
security().createPublicKey(privateEncryptionKey),
|
||||
nonceTrialsPerByte, extraBytes, behaviourBitfield));
|
||||
BitmessageAddress result = new BitmessageAddress(privateKey);
|
||||
if (!result.getAddress().equals(address)) {
|
||||
throw new IllegalArgumentException("Address not matching private key. Address: " + address
|
||||
+ "; Address derived from private key: " + result.getAddress());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static BitmessageAddress generatePrivateAddress(boolean shorter,
|
||||
long stream,
|
||||
Pubkey.Feature... features) {
|
||||
return new BitmessageAddress(new PrivateKey(shorter, stream, 1000, 1000, features));
|
||||
}
|
||||
|
||||
static ObjectPayload getObjectPayload(long objectType,
|
||||
long version,
|
||||
long streamNumber,
|
||||
InputStream stream,
|
||||
int length) throws IOException {
|
||||
ObjectType type = ObjectType.fromNumber(objectType);
|
||||
if (type != null) {
|
||||
switch (type) {
|
||||
case GET_PUBKEY:
|
||||
return parseGetPubkey(version, streamNumber, stream, length);
|
||||
case PUBKEY:
|
||||
return parsePubkey(version, streamNumber, stream, length);
|
||||
case MSG:
|
||||
return parseMsg(version, streamNumber, stream, length);
|
||||
case BROADCAST:
|
||||
return parseBroadcast(version, streamNumber, stream, length);
|
||||
default:
|
||||
LOG.error("This should not happen, someone broke something in the code!");
|
||||
}
|
||||
}
|
||||
// fallback: just store the message - we don't really care what it is
|
||||
LOG.trace("Unexpected object type: " + objectType);
|
||||
return GenericPayload.read(version, stream, streamNumber, length);
|
||||
}
|
||||
|
||||
private static ObjectPayload parseGetPubkey(long version, long streamNumber, InputStream stream, int length) throws IOException {
|
||||
return GetPubkey.read(stream, streamNumber, length, version);
|
||||
}
|
||||
|
||||
public static Pubkey readPubkey(long version, long stream, InputStream is, int length, boolean encrypted) throws IOException {
|
||||
switch ((int) version) {
|
||||
case 2:
|
||||
return V2Pubkey.read(is, stream);
|
||||
case 3:
|
||||
return V3Pubkey.read(is, stream);
|
||||
case 4:
|
||||
return V4Pubkey.read(is, stream, length, encrypted);
|
||||
}
|
||||
LOG.debug("Unexpected pubkey version " + version + ", handling as generic payload object");
|
||||
return null;
|
||||
}
|
||||
|
||||
private static ObjectPayload parsePubkey(long version, long streamNumber, InputStream stream, int length) throws IOException {
|
||||
Pubkey pubkey = readPubkey(version, streamNumber, stream, length, true);
|
||||
return pubkey != null ? pubkey : GenericPayload.read(version, stream, streamNumber, length);
|
||||
}
|
||||
|
||||
private static ObjectPayload parseMsg(long version, long streamNumber, InputStream stream, int length) throws IOException {
|
||||
return Msg.read(stream, streamNumber, length);
|
||||
}
|
||||
|
||||
private static ObjectPayload parseBroadcast(long version, long streamNumber, InputStream stream, int length) throws IOException {
|
||||
switch ((int) version) {
|
||||
case 4:
|
||||
return V4Broadcast.read(stream, streamNumber, length);
|
||||
case 5:
|
||||
return V5Broadcast.read(stream, streamNumber, length);
|
||||
default:
|
||||
LOG.debug("Encountered unknown broadcast version " + version);
|
||||
return GenericPayload.read(version, stream, streamNumber, length);
|
||||
}
|
||||
}
|
||||
|
||||
public static ObjectPayload getBroadcast(BitmessageAddress sendingAddress, Plaintext plaintext) {
|
||||
if (sendingAddress.getVersion() < 4) {
|
||||
return new V4Broadcast(sendingAddress, plaintext);
|
||||
} else {
|
||||
return new V5Broadcast(sendingAddress, plaintext);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,223 @@
|
||||
/*
|
||||
* 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.factory;
|
||||
|
||||
import ch.dissem.bitmessage.entity.*;
|
||||
import ch.dissem.bitmessage.entity.payload.GenericPayload;
|
||||
import ch.dissem.bitmessage.entity.payload.ObjectPayload;
|
||||
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
|
||||
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress;
|
||||
import ch.dissem.bitmessage.exception.NodeException;
|
||||
import ch.dissem.bitmessage.utils.AccessCounter;
|
||||
import ch.dissem.bitmessage.utils.Decode;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import static ch.dissem.bitmessage.entity.NetworkMessage.MAGIC_BYTES;
|
||||
import static ch.dissem.bitmessage.utils.Singleton.security;
|
||||
|
||||
/**
|
||||
* Creates protocol v3 network messages from {@link InputStream InputStreams}
|
||||
*/
|
||||
class V3MessageFactory {
|
||||
private static Logger LOG = LoggerFactory.getLogger(V3MessageFactory.class);
|
||||
|
||||
public static NetworkMessage read(InputStream in) throws IOException {
|
||||
findMagic(in);
|
||||
String command = getCommand(in);
|
||||
int length = (int) Decode.uint32(in);
|
||||
byte[] checksum = Decode.bytes(in, 4);
|
||||
|
||||
byte[] payloadBytes = Decode.bytes(in, length);
|
||||
|
||||
if (testChecksum(checksum, payloadBytes)) {
|
||||
MessagePayload payload = getPayload(command, new ByteArrayInputStream(payloadBytes), length);
|
||||
if (payload != null)
|
||||
return new NetworkMessage(payload);
|
||||
else
|
||||
return null;
|
||||
} else {
|
||||
throw new IOException("Checksum failed for message '" + command + "'");
|
||||
}
|
||||
}
|
||||
|
||||
private static MessagePayload getPayload(String command, InputStream stream, int length) throws IOException {
|
||||
switch (command) {
|
||||
case "version":
|
||||
return parseVersion(stream);
|
||||
case "verack":
|
||||
return new VerAck();
|
||||
case "addr":
|
||||
return parseAddr(stream);
|
||||
case "inv":
|
||||
return parseInv(stream);
|
||||
case "getdata":
|
||||
return parseGetData(stream);
|
||||
case "object":
|
||||
return readObject(stream, length);
|
||||
case "custom":
|
||||
return readCustom(stream, length);
|
||||
default:
|
||||
LOG.debug("Unknown command: " + command);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static MessagePayload readCustom(InputStream in, int length) throws IOException {
|
||||
return CustomMessage.read(in, length);
|
||||
}
|
||||
|
||||
public static ObjectMessage readObject(InputStream in, int length) throws IOException {
|
||||
AccessCounter counter = new AccessCounter();
|
||||
byte nonce[] = Decode.bytes(in, 8, counter);
|
||||
long expiresTime = Decode.int64(in, counter);
|
||||
long objectType = Decode.uint32(in, counter);
|
||||
long version = Decode.varInt(in, counter);
|
||||
long stream = Decode.varInt(in, counter);
|
||||
|
||||
byte[] data = Decode.bytes(in, length - counter.length());
|
||||
ObjectPayload payload;
|
||||
try {
|
||||
ByteArrayInputStream dataStream = new ByteArrayInputStream(data);
|
||||
payload = Factory.getObjectPayload(objectType, version, stream, dataStream, data.length);
|
||||
} catch (Exception e) {
|
||||
LOG.trace("Could not parse object payload - using generic payload instead", e);
|
||||
payload = new GenericPayload(version, stream, data);
|
||||
}
|
||||
|
||||
return new ObjectMessage.Builder()
|
||||
.nonce(nonce)
|
||||
.expiresTime(expiresTime)
|
||||
.objectType(objectType)
|
||||
.stream(stream)
|
||||
.payload(payload)
|
||||
.build();
|
||||
}
|
||||
|
||||
private static GetData parseGetData(InputStream stream) throws IOException {
|
||||
long count = Decode.varInt(stream);
|
||||
GetData.Builder builder = new GetData.Builder();
|
||||
for (int i = 0; i < count; i++) {
|
||||
builder.addInventoryVector(parseInventoryVector(stream));
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private static Inv parseInv(InputStream stream) throws IOException {
|
||||
long count = Decode.varInt(stream);
|
||||
Inv.Builder builder = new Inv.Builder();
|
||||
for (int i = 0; i < count; i++) {
|
||||
builder.addInventoryVector(parseInventoryVector(stream));
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private static Addr parseAddr(InputStream stream) throws IOException {
|
||||
long count = Decode.varInt(stream);
|
||||
Addr.Builder builder = new Addr.Builder();
|
||||
for (int i = 0; i < count; i++) {
|
||||
builder.addAddress(parseAddress(stream, false));
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private static Version parseVersion(InputStream stream) throws IOException {
|
||||
int version = Decode.int32(stream);
|
||||
long services = Decode.int64(stream);
|
||||
long timestamp = Decode.int64(stream);
|
||||
NetworkAddress addrRecv = parseAddress(stream, true);
|
||||
NetworkAddress addrFrom = parseAddress(stream, true);
|
||||
long nonce = Decode.int64(stream);
|
||||
String userAgent = Decode.varString(stream);
|
||||
long[] streamNumbers = Decode.varIntList(stream);
|
||||
|
||||
return new Version.Builder()
|
||||
.version(version)
|
||||
.services(services)
|
||||
.timestamp(timestamp)
|
||||
.addrRecv(addrRecv).addrFrom(addrFrom)
|
||||
.nonce(nonce)
|
||||
.userAgent(userAgent)
|
||||
.streams(streamNumbers).build();
|
||||
}
|
||||
|
||||
private static InventoryVector parseInventoryVector(InputStream stream) throws IOException {
|
||||
return new InventoryVector(Decode.bytes(stream, 32));
|
||||
}
|
||||
|
||||
private static NetworkAddress parseAddress(InputStream stream, boolean light) throws IOException {
|
||||
long time;
|
||||
long streamNumber;
|
||||
if (!light) {
|
||||
time = Decode.int64(stream);
|
||||
streamNumber = Decode.uint32(stream); // This isn't consistent, not sure if this is correct
|
||||
} else {
|
||||
time = 0;
|
||||
streamNumber = 0;
|
||||
}
|
||||
long services = Decode.int64(stream);
|
||||
byte[] ipv6 = Decode.bytes(stream, 16);
|
||||
int port = Decode.uint16(stream);
|
||||
return new NetworkAddress.Builder().time(time).stream(streamNumber).services(services).ipv6(ipv6).port(port).build();
|
||||
}
|
||||
|
||||
private static boolean testChecksum(byte[] checksum, byte[] payload) {
|
||||
byte[] payloadChecksum = security().sha512(payload);
|
||||
for (int i = 0; i < checksum.length; i++) {
|
||||
if (checksum[i] != payloadChecksum[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static String getCommand(InputStream stream) throws IOException {
|
||||
byte[] bytes = new byte[12];
|
||||
int end = -1;
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
bytes[i] = (byte) stream.read();
|
||||
if (end == -1) {
|
||||
if (bytes[i] == 0) end = i;
|
||||
} else {
|
||||
if (bytes[i] != 0) throw new IOException("'\\0' padding expected for command");
|
||||
}
|
||||
}
|
||||
return new String(bytes, 0, end, "ASCII");
|
||||
}
|
||||
|
||||
private static void findMagic(InputStream in) throws IOException {
|
||||
int pos = 0;
|
||||
for (int i = 0; i < 1620000; i++) {
|
||||
byte b = (byte) in.read();
|
||||
if (b == MAGIC_BYTES[pos]) {
|
||||
if (pos + 1 == MAGIC_BYTES.length) {
|
||||
return;
|
||||
}
|
||||
} else if (pos > 0 && b == MAGIC_BYTES[0]) {
|
||||
pos = 1;
|
||||
} else {
|
||||
pos = 0;
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
throw new NodeException("Failed to find MAGIC bytes in stream");
|
||||
}
|
||||
}
|
@ -0,0 +1,183 @@
|
||||
/*
|
||||
* 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.ports;
|
||||
|
||||
import ch.dissem.bitmessage.InternalContext;
|
||||
import ch.dissem.bitmessage.entity.ObjectMessage;
|
||||
import ch.dissem.bitmessage.entity.payload.Pubkey;
|
||||
import ch.dissem.bitmessage.exception.InsufficientProofOfWorkException;
|
||||
import ch.dissem.bitmessage.factory.Factory;
|
||||
import ch.dissem.bitmessage.utils.Bytes;
|
||||
import ch.dissem.bitmessage.utils.UnixTime;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
import static ch.dissem.bitmessage.utils.Numbers.max;
|
||||
|
||||
/**
|
||||
* Implements everything that isn't directly dependent on either Spongy- or Bouncycastle.
|
||||
*/
|
||||
public abstract class AbstractCryptography implements Cryptography, InternalContext.ContextHolder {
|
||||
public static final Logger LOG = LoggerFactory.getLogger(Cryptography.class);
|
||||
private static final SecureRandom RANDOM = new SecureRandom();
|
||||
private static final BigInteger TWO = BigInteger.valueOf(2);
|
||||
private static final BigInteger TWO_POW_64 = TWO.pow(64);
|
||||
private static final BigInteger TWO_POW_16 = TWO.pow(16);
|
||||
|
||||
private final String provider;
|
||||
private InternalContext context;
|
||||
|
||||
protected AbstractCryptography(String provider) {
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContext(InternalContext context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public byte[] sha512(byte[]... data) {
|
||||
return hash("SHA-512", data);
|
||||
}
|
||||
|
||||
public byte[] doubleSha512(byte[]... data) {
|
||||
MessageDigest mda = md("SHA-512");
|
||||
for (byte[] d : data) {
|
||||
mda.update(d);
|
||||
}
|
||||
return mda.digest(mda.digest());
|
||||
}
|
||||
|
||||
public byte[] doubleSha512(byte[] data, int length) {
|
||||
MessageDigest mda = md("SHA-512");
|
||||
mda.update(data, 0, length);
|
||||
return mda.digest(mda.digest());
|
||||
}
|
||||
|
||||
public byte[] ripemd160(byte[]... data) {
|
||||
return hash("RIPEMD160", data);
|
||||
}
|
||||
|
||||
public byte[] doubleSha256(byte[] data, int length) {
|
||||
MessageDigest mda = md("SHA-256");
|
||||
mda.update(data, 0, length);
|
||||
return mda.digest(mda.digest());
|
||||
}
|
||||
|
||||
public byte[] sha1(byte[]... data) {
|
||||
return hash("SHA-1", data);
|
||||
}
|
||||
|
||||
public byte[] randomBytes(int length) {
|
||||
byte[] result = new byte[length];
|
||||
RANDOM.nextBytes(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
public void doProofOfWork(ObjectMessage object, long nonceTrialsPerByte,
|
||||
long extraBytes, ProofOfWorkEngine.Callback callback) {
|
||||
nonceTrialsPerByte = max(nonceTrialsPerByte, context.getNetworkNonceTrialsPerByte());
|
||||
extraBytes = max(extraBytes, context.getNetworkExtraBytes());
|
||||
|
||||
byte[] initialHash = getInitialHash(object);
|
||||
|
||||
byte[] target = getProofOfWorkTarget(object, nonceTrialsPerByte, extraBytes);
|
||||
|
||||
context.getProofOfWorkEngine().calculateNonce(initialHash, target, callback);
|
||||
}
|
||||
|
||||
public void checkProofOfWork(ObjectMessage object, long nonceTrialsPerByte, long extraBytes)
|
||||
throws IOException {
|
||||
byte[] target = getProofOfWorkTarget(object, nonceTrialsPerByte, extraBytes);
|
||||
byte[] value = doubleSha512(object.getNonce(), getInitialHash(object));
|
||||
if (Bytes.lt(target, value, 8)) {
|
||||
throw new InsufficientProofOfWorkException(target, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getInitialHash(ObjectMessage object) {
|
||||
return sha512(object.getPayloadBytesWithoutNonce());
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getProofOfWorkTarget(ObjectMessage object, long nonceTrialsPerByte, long extraBytes) {
|
||||
if (nonceTrialsPerByte == 0) nonceTrialsPerByte = context.getNetworkNonceTrialsPerByte();
|
||||
if (extraBytes == 0) extraBytes = context.getNetworkExtraBytes();
|
||||
|
||||
BigInteger TTL = BigInteger.valueOf(object.getExpiresTime() - UnixTime.now());
|
||||
BigInteger numerator = TWO_POW_64;
|
||||
BigInteger powLength = BigInteger.valueOf(object.getPayloadBytesWithoutNonce().length + extraBytes);
|
||||
BigInteger denominator = BigInteger.valueOf(nonceTrialsPerByte)
|
||||
.multiply(
|
||||
powLength.add(
|
||||
powLength.multiply(TTL).divide(TWO_POW_16)
|
||||
)
|
||||
);
|
||||
return Bytes.expand(numerator.divide(denominator).toByteArray(), 8);
|
||||
}
|
||||
|
||||
private byte[] hash(String algorithm, byte[]... data) {
|
||||
MessageDigest mda = md(algorithm);
|
||||
for (byte[] d : data) {
|
||||
mda.update(d);
|
||||
}
|
||||
return mda.digest();
|
||||
}
|
||||
|
||||
private MessageDigest md(String algorithm) {
|
||||
try {
|
||||
return MessageDigest.getInstance(algorithm, provider);
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] mac(byte[] key_m, byte[] data) {
|
||||
try {
|
||||
Mac mac = Mac.getInstance("HmacSHA256", provider);
|
||||
mac.init(new SecretKeySpec(key_m, "HmacSHA256"));
|
||||
return mac.doFinal(data);
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public Pubkey createPubkey(long version, long stream, byte[] privateSigningKey, byte[] privateEncryptionKey,
|
||||
long nonceTrialsPerByte, long extraBytes, Pubkey.Feature... features) {
|
||||
return Factory.createPubkey(version, stream,
|
||||
createPublicKey(privateSigningKey),
|
||||
createPublicKey(privateEncryptionKey),
|
||||
nonceTrialsPerByte, extraBytes, features);
|
||||
}
|
||||
|
||||
public BigInteger keyToBigInt(byte[] privateKey) {
|
||||
return new BigInteger(1, privateKey);
|
||||
}
|
||||
|
||||
public long randomNonce() {
|
||||
return RANDOM.nextLong();
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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.ports;
|
||||
|
||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface AddressRepository {
|
||||
BitmessageAddress findContact(byte[] ripeOrTag);
|
||||
|
||||
BitmessageAddress findIdentity(byte[] ripeOrTag);
|
||||
|
||||
/**
|
||||
* @return all Bitmessage addresses that belong to this user, i.e. have a private key.
|
||||
*/
|
||||
List<BitmessageAddress> getIdentities();
|
||||
|
||||
List<BitmessageAddress> getSubscriptions();
|
||||
|
||||
List<BitmessageAddress> getSubscriptions(long broadcastVersion);
|
||||
|
||||
/**
|
||||
* @return all Bitmessage addresses that have no private key.
|
||||
*/
|
||||
List<BitmessageAddress> getContacts();
|
||||
|
||||
void save(BitmessageAddress address);
|
||||
|
||||
void remove(BitmessageAddress address);
|
||||
|
||||
BitmessageAddress getAddress(String address);
|
||||
}
|
210
core/src/main/java/ch/dissem/bitmessage/ports/Cryptography.java
Normal file
210
core/src/main/java/ch/dissem/bitmessage/ports/Cryptography.java
Normal file
@ -0,0 +1,210 @@
|
||||
/*
|
||||
* 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.ports;
|
||||
|
||||
import ch.dissem.bitmessage.entity.ObjectMessage;
|
||||
import ch.dissem.bitmessage.entity.payload.Pubkey;
|
||||
import ch.dissem.bitmessage.exception.InsufficientProofOfWorkException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
/**
|
||||
* Provides some methods to help with hashing and encryption. All randoms are created using {@link SecureRandom},
|
||||
* which should be secure enough.
|
||||
*/
|
||||
public interface Cryptography {
|
||||
/**
|
||||
* A helper method to calculate SHA-512 hashes. Please note that a new {@link MessageDigest} object is created at
|
||||
* each call (to ensure thread safety), so you shouldn't use this if you need to do many hash calculations in
|
||||
* success on the same thread.
|
||||
*
|
||||
* @param data to get hashed
|
||||
* @return SHA-512 hash of data
|
||||
*/
|
||||
byte[] sha512(byte[]... data);
|
||||
|
||||
/**
|
||||
* A helper method to calculate doubleSHA-512 hashes. Please note that a new {@link MessageDigest} object is created
|
||||
* at each call (to ensure thread safety), so you shouldn't use this if you need to do many hash calculations in
|
||||
* success on the same thread.
|
||||
*
|
||||
* @param data to get hashed
|
||||
* @return SHA-512 hash of data
|
||||
*/
|
||||
byte[] doubleSha512(byte[]... data);
|
||||
|
||||
/**
|
||||
* A helper method to calculate double SHA-512 hashes. This method allows to only use a part of the available bytes
|
||||
* to use for the hash calculation.
|
||||
* <p>
|
||||
* Please note that a new {@link MessageDigest} object is created at each call (to ensure thread safety), so you
|
||||
* shouldn't use this if you need to do many hash calculations in short order on the same thread.
|
||||
* </p>
|
||||
*
|
||||
* @param data to get hashed
|
||||
* @param length number of bytes to be taken into account
|
||||
* @return SHA-512 hash of data
|
||||
*/
|
||||
byte[] doubleSha512(byte[] data, int length);
|
||||
|
||||
/**
|
||||
* A helper method to calculate RIPEMD-160 hashes. Supplying multiple byte arrays has the same result as a
|
||||
* concatenation of all arrays, but might perform better.
|
||||
* <p>
|
||||
* Please note that a new {@link MessageDigest} object is created at
|
||||
* each call (to ensure thread safety), so you shouldn't use this if you need to do many hash calculations in short
|
||||
* order on the same thread.
|
||||
* </p>
|
||||
*
|
||||
* @param data to get hashed
|
||||
* @return RIPEMD-160 hash of data
|
||||
*/
|
||||
byte[] ripemd160(byte[]... data);
|
||||
|
||||
/**
|
||||
* A helper method to calculate double SHA-256 hashes. This method allows to only use a part of the available bytes
|
||||
* to use for the hash calculation.
|
||||
* <p>
|
||||
* Please note that a new {@link MessageDigest} object is created at
|
||||
* each call (to ensure thread safety), so you shouldn't use this if you need to do many hash calculations in short
|
||||
* order on the same thread.
|
||||
* </p>
|
||||
*
|
||||
* @param data to get hashed
|
||||
* @param length number of bytes to be taken into account
|
||||
* @return SHA-256 hash of data
|
||||
*/
|
||||
byte[] doubleSha256(byte[] data, int length);
|
||||
|
||||
/**
|
||||
* A helper method to calculate SHA-1 hashes. Supplying multiple byte arrays has the same result as a
|
||||
* concatenation of all arrays, but might perform better.
|
||||
* <p>
|
||||
* Please note that a new {@link MessageDigest} object is created at
|
||||
* each call (to ensure thread safety), so you shouldn't use this if you need to do many hash calculations in short
|
||||
* order on the same thread.
|
||||
* </p>
|
||||
*
|
||||
* @param data to get hashed
|
||||
* @return SHA hash of data
|
||||
*/
|
||||
byte[] sha1(byte[]... data);
|
||||
|
||||
/**
|
||||
* @param length number of bytes to return
|
||||
* @return an array of the given size containing random bytes
|
||||
*/
|
||||
byte[] randomBytes(int length);
|
||||
|
||||
/**
|
||||
* Calculates the proof of work. This might take a long time, depending on the hardware, message size and time to
|
||||
* live.
|
||||
*
|
||||
* @param object to do the proof of work for
|
||||
* @param nonceTrialsPerByte difficulty
|
||||
* @param extraBytes bytes to add to the object size (makes it more difficult to send small messages)
|
||||
* @param callback to handle nonce once it's calculated
|
||||
*/
|
||||
void doProofOfWork(ObjectMessage object, long nonceTrialsPerByte,
|
||||
long extraBytes, ProofOfWorkEngine.Callback callback);
|
||||
|
||||
/**
|
||||
* @param object to be checked
|
||||
* @param nonceTrialsPerByte difficulty
|
||||
* @param extraBytes bytes to add to the object size
|
||||
* @throws InsufficientProofOfWorkException if proof of work doesn't check out (makes it more difficult to send small messages)
|
||||
*/
|
||||
void checkProofOfWork(ObjectMessage object, long nonceTrialsPerByte, long extraBytes)
|
||||
throws IOException;
|
||||
|
||||
byte[] getInitialHash(ObjectMessage object);
|
||||
|
||||
byte[] getProofOfWorkTarget(ObjectMessage object, long nonceTrialsPerByte, long extraBytes);
|
||||
|
||||
/**
|
||||
* Calculates the MAC for a message (data)
|
||||
*
|
||||
* @param key_m the symmetric key used
|
||||
* @param data the message data to calculate the MAC for
|
||||
* @return the MAC
|
||||
*/
|
||||
byte[] mac(byte[] key_m, byte[] data);
|
||||
|
||||
/**
|
||||
* @param encrypt if true, encrypts data, otherwise tries to decrypt it.
|
||||
* @param data
|
||||
* @param key_e
|
||||
* @return
|
||||
*/
|
||||
byte[] crypt(boolean encrypt, byte[] data, byte[] key_e, byte[] initializationVector);
|
||||
|
||||
/**
|
||||
* Create a new public key fom given private keys.
|
||||
*
|
||||
* @param version of the public key / address
|
||||
* @param stream of the address
|
||||
* @param privateSigningKey private key used for signing
|
||||
* @param privateEncryptionKey private key used for encryption
|
||||
* @param nonceTrialsPerByte proof of work difficulty
|
||||
* @param extraBytes bytes to add for the proof of work (make it harder for small messages)
|
||||
* @param features of the address
|
||||
* @return a public key object
|
||||
*/
|
||||
Pubkey createPubkey(long version, long stream, byte[] privateSigningKey, byte[] privateEncryptionKey,
|
||||
long nonceTrialsPerByte, long extraBytes, Pubkey.Feature... features);
|
||||
|
||||
/**
|
||||
* @param privateKey private key as byte array
|
||||
* @return a public key corresponding to the given private key
|
||||
*/
|
||||
byte[] createPublicKey(byte[] privateKey);
|
||||
|
||||
/**
|
||||
* @param privateKey private key as byte array
|
||||
* @return a big integer representation (unsigned) of the given bytes
|
||||
*/
|
||||
BigInteger keyToBigInt(byte[] privateKey);
|
||||
|
||||
/**
|
||||
* @param data to check
|
||||
* @param signature the signature of the message
|
||||
* @param pubkey the sender's public key
|
||||
* @return true if the signature is valid, false otherwise
|
||||
*/
|
||||
boolean isSignatureValid(byte[] data, byte[] signature, Pubkey pubkey);
|
||||
|
||||
/**
|
||||
* Calculate the signature of data, using the given private key.
|
||||
*
|
||||
* @param data to be signed
|
||||
* @param privateKey to be used for signing
|
||||
* @return the signature
|
||||
*/
|
||||
byte[] getSignature(byte[] data, ch.dissem.bitmessage.entity.valueobject.PrivateKey privateKey);
|
||||
|
||||
/**
|
||||
* @return a random number of type long
|
||||
*/
|
||||
long randomNonce();
|
||||
|
||||
byte[] multiply(byte[] k, byte[] r);
|
||||
|
||||
byte[] createPoint(byte[] x, byte[] y);
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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.ports;
|
||||
|
||||
import ch.dissem.bitmessage.entity.CustomMessage;
|
||||
import ch.dissem.bitmessage.entity.MessagePayload;
|
||||
|
||||
/**
|
||||
* @author Christian Basler
|
||||
*/
|
||||
public interface CustomCommandHandler {
|
||||
MessagePayload handle(CustomMessage request);
|
||||
}
|
57
core/src/main/java/ch/dissem/bitmessage/ports/Inventory.java
Normal file
57
core/src/main/java/ch/dissem/bitmessage/ports/Inventory.java
Normal file
@ -0,0 +1,57 @@
|
||||
/*
|
||||
* 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.ports;
|
||||
|
||||
import ch.dissem.bitmessage.entity.ObjectMessage;
|
||||
import ch.dissem.bitmessage.entity.payload.ObjectType;
|
||||
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The Inventory stores and retrieves objects, cleans up outdated objects and can tell which objects are still missing.
|
||||
*/
|
||||
public interface Inventory {
|
||||
/**
|
||||
* Returns the IVs of all valid objects we have for the given streams
|
||||
*/
|
||||
List<InventoryVector> getInventory(long... streams);
|
||||
|
||||
/**
|
||||
* Returns the IVs of all objects in the offer that we don't have already. Implementations are allowed to
|
||||
* ignore the streams parameter, but it must be set when calling this method.
|
||||
*/
|
||||
List<InventoryVector> getMissing(List<InventoryVector> offer, long... streams);
|
||||
|
||||
ObjectMessage getObject(InventoryVector vector);
|
||||
|
||||
/**
|
||||
* This method is mainly used to search for public keys to newly added addresses or broadcasts from new
|
||||
* subscriptions.
|
||||
*/
|
||||
List<ObjectMessage> getObjects(long stream, long version, ObjectType... types);
|
||||
|
||||
void storeObject(ObjectMessage object);
|
||||
|
||||
boolean contains(ObjectMessage object);
|
||||
|
||||
/**
|
||||
* Deletes all objects that expired 5 minutes ago or earlier
|
||||
* (so we don't accidentally request objects we just deleted)
|
||||
*/
|
||||
void cleanup();
|
||||
}
|
@ -0,0 +1,128 @@
|
||||
/*
|
||||
* 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.ports;
|
||||
|
||||
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress;
|
||||
import ch.dissem.bitmessage.utils.UnixTime;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import static ch.dissem.bitmessage.utils.Collections.selectRandom;
|
||||
import static ch.dissem.bitmessage.utils.UnixTime.HOUR;
|
||||
import static java.util.Collections.newSetFromMap;
|
||||
|
||||
public class MemoryNodeRegistry implements NodeRegistry {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MemoryNodeRegistry.class);
|
||||
|
||||
private final Map<Long, Set<NetworkAddress>> stableNodes = new ConcurrentHashMap<>();
|
||||
private final Map<Long, Set<NetworkAddress>> knownNodes = new ConcurrentHashMap<>();
|
||||
|
||||
private void loadStableNodes() {
|
||||
try (InputStream in = getClass().getClassLoader().getResourceAsStream("nodes.txt")) {
|
||||
Scanner scanner = new Scanner(in);
|
||||
long stream = 0;
|
||||
Set<NetworkAddress> streamSet = null;
|
||||
while (scanner.hasNext()) {
|
||||
try {
|
||||
String line = scanner.nextLine().trim();
|
||||
if (line.startsWith("#") || line.isEmpty()) {
|
||||
// Ignore
|
||||
continue;
|
||||
}
|
||||
if (line.startsWith("[stream")) {
|
||||
stream = Long.parseLong(line.substring(8, line.lastIndexOf(']')));
|
||||
streamSet = new HashSet<>();
|
||||
stableNodes.put(stream, streamSet);
|
||||
} else if (streamSet != null) {
|
||||
int portIndex = line.lastIndexOf(':');
|
||||
InetAddress[] inetAddresses = InetAddress.getAllByName(line.substring(0, portIndex));
|
||||
int port = Integer.valueOf(line.substring(portIndex + 1));
|
||||
for (InetAddress inetAddress : inetAddresses) {
|
||||
streamSet.add(new NetworkAddress.Builder().ip(inetAddress).port(port).stream(stream).build());
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.warn(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
if (LOG.isDebugEnabled()) {
|
||||
for (Map.Entry<Long, Set<NetworkAddress>> e : stableNodes.entrySet()) {
|
||||
LOG.debug("Stream " + e.getKey() + ": loaded " + e.getValue().size() + " bootstrap nodes.");
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<NetworkAddress> getKnownAddresses(int limit, long... streams) {
|
||||
List<NetworkAddress> result = new LinkedList<>();
|
||||
for (long stream : streams) {
|
||||
Set<NetworkAddress> known = knownNodes.get(stream);
|
||||
if (known != null && !known.isEmpty()) {
|
||||
for (NetworkAddress node : known) {
|
||||
if (node.getTime() > UnixTime.now(-3 * HOUR)) {
|
||||
result.add(node);
|
||||
} else {
|
||||
known.remove(node);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Set<NetworkAddress> nodes = stableNodes.get(stream);
|
||||
if (nodes == null || nodes.isEmpty()) {
|
||||
loadStableNodes();
|
||||
nodes = stableNodes.get(stream);
|
||||
}
|
||||
if (nodes != null && !nodes.isEmpty()) {
|
||||
// To reduce load on stable nodes, only return one
|
||||
result.add(selectRandom(nodes));
|
||||
}
|
||||
}
|
||||
}
|
||||
return selectRandom(limit, result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void offerAddresses(List<NetworkAddress> addresses) {
|
||||
for (NetworkAddress node : addresses) {
|
||||
if (node.getTime() <= UnixTime.now()) {
|
||||
if (!knownNodes.containsKey(node.getStream())) {
|
||||
synchronized (knownNodes) {
|
||||
if (!knownNodes.containsKey(node.getStream())) {
|
||||
knownNodes.put(
|
||||
node.getStream(),
|
||||
newSetFromMap(new ConcurrentHashMap<NetworkAddress, Boolean>())
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (node.getTime() <= UnixTime.now()) {
|
||||
// TODO: This isn't quite correct
|
||||
// If the node is already known, the one with the more recent time should be used
|
||||
knownNodes.get(node.getStream()).add(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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.ports;
|
||||
|
||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
||||
import ch.dissem.bitmessage.entity.Plaintext;
|
||||
import ch.dissem.bitmessage.entity.Plaintext.Status;
|
||||
import ch.dissem.bitmessage.entity.valueobject.Label;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface MessageRepository {
|
||||
List<Label> getLabels();
|
||||
|
||||
List<Label> getLabels(Label.Type... types);
|
||||
|
||||
int countUnread(Label label);
|
||||
|
||||
Plaintext getMessage(byte[] initialHash);
|
||||
|
||||
List<Plaintext> findMessages(Label label);
|
||||
|
||||
List<Plaintext> findMessages(Status status);
|
||||
|
||||
List<Plaintext> findMessages(Status status, BitmessageAddress recipient);
|
||||
|
||||
List<Plaintext> findMessages(BitmessageAddress sender);
|
||||
|
||||
void save(Plaintext message);
|
||||
|
||||
void remove(Plaintext message);
|
||||
}
|
@ -0,0 +1,141 @@
|
||||
/*
|
||||
* 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.ports;
|
||||
|
||||
import ch.dissem.bitmessage.utils.Bytes;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Semaphore;
|
||||
|
||||
import static ch.dissem.bitmessage.utils.Bytes.inc;
|
||||
|
||||
/**
|
||||
* A POW engine using all available CPU cores.
|
||||
*/
|
||||
public class MultiThreadedPOWEngine implements ProofOfWorkEngine {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MultiThreadedPOWEngine.class);
|
||||
private static final Semaphore semaphore = new Semaphore(1, true);
|
||||
|
||||
/**
|
||||
* This method will block until all pending nonce calculations are done, but not wait for its own calculation
|
||||
* to finish.
|
||||
* (This implementation becomes very inefficient if multiple nonce are calculated at the same time.)
|
||||
*
|
||||
* @param initialHash the SHA-512 hash of the object to send, sans nonce
|
||||
* @param target the target, representing an unsigned long
|
||||
* @param callback called with the calculated nonce as argument. The ProofOfWorkEngine implementation must make
|
||||
*/
|
||||
@Override
|
||||
public void calculateNonce(byte[] initialHash, byte[] target, Callback callback) {
|
||||
try {
|
||||
semaphore.acquire();
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
callback = new CallbackWrapper(callback);
|
||||
int cores = Runtime.getRuntime().availableProcessors();
|
||||
if (cores > 255) cores = 255;
|
||||
LOG.info("Doing POW using " + cores + " cores");
|
||||
List<Worker> workers = new ArrayList<>(cores);
|
||||
for (int i = 0; i < cores; i++) {
|
||||
Worker w = new Worker(workers, (byte) cores, i, initialHash, target, callback);
|
||||
workers.add(w);
|
||||
}
|
||||
for (Worker w : workers) {
|
||||
// Doing this in the previous loop might cause a ConcurrentModificationException in the worker
|
||||
// if a worker finds a nonce while new ones are still being added.
|
||||
w.start();
|
||||
}
|
||||
}
|
||||
|
||||
private static class Worker extends Thread {
|
||||
private final Callback callback;
|
||||
private final byte numberOfCores;
|
||||
private final List<Worker> workers;
|
||||
private final byte[] initialHash;
|
||||
private final byte[] target;
|
||||
private final MessageDigest mda;
|
||||
private final byte[] nonce = new byte[8];
|
||||
|
||||
public Worker(List<Worker> workers, byte numberOfCores, int core, byte[] initialHash, byte[] target,
|
||||
Callback callback) {
|
||||
this.callback = callback;
|
||||
this.numberOfCores = numberOfCores;
|
||||
this.workers = workers;
|
||||
this.initialHash = initialHash;
|
||||
this.target = target;
|
||||
this.nonce[7] = (byte) core;
|
||||
try {
|
||||
mda = MessageDigest.getInstance("SHA-512");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
do {
|
||||
inc(nonce, numberOfCores);
|
||||
mda.update(nonce);
|
||||
mda.update(initialHash);
|
||||
if (!Bytes.lt(target, mda.digest(mda.digest()), 8)) {
|
||||
synchronized (callback) {
|
||||
if (!Thread.interrupted()) {
|
||||
try {
|
||||
callback.onNonceCalculated(initialHash, nonce);
|
||||
} finally {
|
||||
semaphore.release();
|
||||
for (Worker w : workers) {
|
||||
w.interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
} while (!Thread.interrupted());
|
||||
}
|
||||
}
|
||||
|
||||
public static class CallbackWrapper implements Callback {
|
||||
private final Callback callback;
|
||||
private final long startTime;
|
||||
private boolean waiting = true;
|
||||
|
||||
public CallbackWrapper(Callback callback) {
|
||||
this.startTime = System.currentTimeMillis();
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNonceCalculated(byte[] initialHash, byte[] nonce) {
|
||||
synchronized (this) {
|
||||
if (waiting) {
|
||||
LOG.info("Nonce calculated in " + ((System.currentTimeMillis() - startTime) / 1000) + " seconds");
|
||||
waiting = false;
|
||||
callback.onNonceCalculated(initialHash, nonce);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
/*
|
||||
* 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.ports;
|
||||
|
||||
import ch.dissem.bitmessage.entity.CustomMessage;
|
||||
import ch.dissem.bitmessage.entity.ObjectMessage;
|
||||
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
|
||||
import ch.dissem.bitmessage.utils.Property;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
/**
|
||||
* Handles incoming messages
|
||||
*/
|
||||
public interface NetworkHandler {
|
||||
/**
|
||||
* Connects to the trusted host, fetches and offers new messages and disconnects afterwards.
|
||||
* <p>
|
||||
* An implementation should disconnect if either the timeout is reached or the returned thread is interrupted.
|
||||
* </p>
|
||||
*/
|
||||
Future<?> synchronize(InetAddress server, int port, MessageListener listener, long timeoutInSeconds);
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
CustomMessage send(InetAddress server, int port, CustomMessage request);
|
||||
|
||||
/**
|
||||
* Start a full network node, accepting incoming connections and relaying objects.
|
||||
*/
|
||||
void start(MessageListener listener);
|
||||
|
||||
/**
|
||||
* Stop the full network node.
|
||||
*/
|
||||
void stop();
|
||||
|
||||
/**
|
||||
* Offer new objects to up to 8 random nodes.
|
||||
*/
|
||||
void offer(InventoryVector iv);
|
||||
|
||||
Property getNetworkStatus();
|
||||
|
||||
boolean isRunning();
|
||||
|
||||
interface MessageListener {
|
||||
void receive(ObjectMessage object) throws IOException;
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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.ports;
|
||||
|
||||
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Stores and provides known peers.
|
||||
*/
|
||||
public interface NodeRegistry {
|
||||
List<NetworkAddress> getKnownAddresses(int limit, long... streams);
|
||||
|
||||
void offerAddresses(List<NetworkAddress> addresses);
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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.ports;
|
||||
|
||||
/**
|
||||
* Does the proof of work necessary to send an object.
|
||||
*/
|
||||
public interface ProofOfWorkEngine {
|
||||
/**
|
||||
* Returns a nonce, such that the first 8 bytes from sha512(sha512(nonce||initialHash)) represent a unsigned long
|
||||
* smaller than target.
|
||||
*
|
||||
* @param initialHash the SHA-512 hash of the object to send, sans nonce
|
||||
* @param target the target, representing an unsigned long
|
||||
* @param callback called with the calculated nonce as argument. The ProofOfWorkEngine implementation must make
|
||||
* sure this is only called once.
|
||||
*/
|
||||
void calculateNonce(byte[] initialHash, byte[] target, Callback callback);
|
||||
|
||||
interface Callback {
|
||||
/**
|
||||
* @param nonce 8 bytes nonce
|
||||
*/
|
||||
void onNonceCalculated(byte[] initialHash, byte[] nonce);
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package ch.dissem.bitmessage.ports;
|
||||
|
||||
import ch.dissem.bitmessage.entity.ObjectMessage;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Objects that proof of work is currently being done for.
|
||||
*
|
||||
* @author Christian Basler
|
||||
*/
|
||||
public interface ProofOfWorkRepository {
|
||||
Item getItem(byte[] initialHash);
|
||||
|
||||
List<byte[]> getItems();
|
||||
|
||||
void putObject(ObjectMessage object, long nonceTrialsPerByte, long extraBytes);
|
||||
|
||||
void removeObject(byte[] initialHash);
|
||||
|
||||
class Item {
|
||||
public final ObjectMessage object;
|
||||
public final long nonceTrialsPerByte;
|
||||
public final long extraBytes;
|
||||
|
||||
public Item(ObjectMessage object, long nonceTrialsPerByte, long extraBytes) {
|
||||
this.object = object;
|
||||
this.nonceTrialsPerByte = nonceTrialsPerByte;
|
||||
this.extraBytes = extraBytes;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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.ports;
|
||||
|
||||
import ch.dissem.bitmessage.utils.Bytes;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
|
||||
import static ch.dissem.bitmessage.utils.Bytes.inc;
|
||||
|
||||
/**
|
||||
* You should really use the MultiThreadedPOWEngine, but this one might help you grok the other one.
|
||||
*/
|
||||
public class SimplePOWEngine implements ProofOfWorkEngine {
|
||||
@Override
|
||||
public void calculateNonce(byte[] initialHash, byte[] target, Callback callback) {
|
||||
byte[] nonce = new byte[8];
|
||||
MessageDigest mda;
|
||||
try {
|
||||
mda = MessageDigest.getInstance("SHA-512");
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
do {
|
||||
inc(nonce);
|
||||
mda.update(nonce);
|
||||
mda.update(initialHash);
|
||||
} while (Bytes.lt(target, mda.digest(mda.digest()), 8));
|
||||
callback.onNonceCalculated(initialHash, nonce);
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* 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.utils;
|
||||
|
||||
/**
|
||||
* Intended to count the bytes read or written during (de-)serialization.
|
||||
*/
|
||||
public class AccessCounter {
|
||||
private int count;
|
||||
|
||||
/**
|
||||
* Increases the counter by one, if not null.
|
||||
*/
|
||||
public static void inc(AccessCounter counter) {
|
||||
if (counter != null) counter.inc();
|
||||
}
|
||||
|
||||
/**
|
||||
* Increases the counter by length, if not null.
|
||||
*/
|
||||
public static void inc(AccessCounter counter, int length) {
|
||||
if (counter != null) counter.inc(length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Increases the counter by one.
|
||||
*/
|
||||
private void inc() {
|
||||
count++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increases the counter by length.
|
||||
*/
|
||||
private void inc(int length) {
|
||||
count += length;
|
||||
}
|
||||
|
||||
public int length() {
|
||||
return count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.valueOf(count);
|
||||
}
|
||||
}
|
167
core/src/main/java/ch/dissem/bitmessage/utils/Base58.java
Normal file
167
core/src/main/java/ch/dissem/bitmessage/utils/Base58.java
Normal file
@ -0,0 +1,167 @@
|
||||
/*
|
||||
* Copyright 2011 Google Inc.
|
||||
* 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.utils;
|
||||
|
||||
import ch.dissem.bitmessage.exception.AddressFormatException;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
import static java.util.Arrays.copyOfRange;
|
||||
|
||||
/**
|
||||
* Base58 encoder and decoder.
|
||||
*
|
||||
* @author Christian Basler: I removed some dependencies to the BitcoinJ code so it can be used here more easily.
|
||||
*/
|
||||
public class Base58 {
|
||||
private static final int[] INDEXES = new int[128];
|
||||
private static char[] ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray();
|
||||
|
||||
static {
|
||||
for (int i = 0; i < INDEXES.length; i++) {
|
||||
INDEXES[i] = -1;
|
||||
}
|
||||
for (int i = 0; i < ALPHABET.length; i++) {
|
||||
INDEXES[ALPHABET[i]] = i;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the given bytes in base58. No checksum is appended.
|
||||
*
|
||||
* @param input to encode
|
||||
* @return base58 encoded input
|
||||
*/
|
||||
public static String encode(byte[] input) {
|
||||
if (input.length == 0) {
|
||||
return "";
|
||||
}
|
||||
input = copyOfRange(input, 0, input.length);
|
||||
// Count leading zeroes.
|
||||
int zeroCount = 0;
|
||||
while (zeroCount < input.length && input[zeroCount] == 0) {
|
||||
++zeroCount;
|
||||
}
|
||||
// The actual encoding.
|
||||
byte[] temp = new byte[input.length * 2];
|
||||
int j = temp.length;
|
||||
|
||||
int startAt = zeroCount;
|
||||
while (startAt < input.length) {
|
||||
byte mod = divmod58(input, startAt);
|
||||
if (input[startAt] == 0) {
|
||||
++startAt;
|
||||
}
|
||||
temp[--j] = (byte) ALPHABET[mod];
|
||||
}
|
||||
|
||||
// Strip extra '1' if there are some after decoding.
|
||||
while (j < temp.length && temp[j] == ALPHABET[0]) {
|
||||
++j;
|
||||
}
|
||||
// Add as many leading '1' as there were leading zeros.
|
||||
while (--zeroCount >= 0) {
|
||||
temp[--j] = (byte) ALPHABET[0];
|
||||
}
|
||||
|
||||
byte[] output = copyOfRange(temp, j, temp.length);
|
||||
try {
|
||||
return new String(output, "US-ASCII");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new RuntimeException(e); // Cannot happen.
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] decode(String input) throws AddressFormatException {
|
||||
if (input.length() == 0) {
|
||||
return new byte[0];
|
||||
}
|
||||
byte[] input58 = new byte[input.length()];
|
||||
// Transform the String to a base58 byte sequence
|
||||
for (int i = 0; i < input.length(); ++i) {
|
||||
char c = input.charAt(i);
|
||||
|
||||
int digit58 = -1;
|
||||
if (c >= 0 && c < 128) {
|
||||
digit58 = INDEXES[c];
|
||||
}
|
||||
if (digit58 < 0) {
|
||||
throw new AddressFormatException("Illegal character " + c + " at " + i);
|
||||
}
|
||||
|
||||
input58[i] = (byte) digit58;
|
||||
}
|
||||
// Count leading zeroes
|
||||
int zeroCount = 0;
|
||||
while (zeroCount < input58.length && input58[zeroCount] == 0) {
|
||||
++zeroCount;
|
||||
}
|
||||
// The encoding
|
||||
byte[] temp = new byte[input.length()];
|
||||
int j = temp.length;
|
||||
|
||||
int startAt = zeroCount;
|
||||
while (startAt < input58.length) {
|
||||
byte mod = divmod256(input58, startAt);
|
||||
if (input58[startAt] == 0) {
|
||||
++startAt;
|
||||
}
|
||||
|
||||
temp[--j] = mod;
|
||||
}
|
||||
// Do no add extra leading zeroes, move j to first non null byte.
|
||||
while (j < temp.length && temp[j] == 0) {
|
||||
++j;
|
||||
}
|
||||
return copyOfRange(temp, j - zeroCount, temp.length);
|
||||
}
|
||||
|
||||
//
|
||||
// number -> number / 58, returns number % 58
|
||||
//
|
||||
private static byte divmod58(byte[] number, int startAt) {
|
||||
int remainder = 0;
|
||||
for (int i = startAt; i < number.length; i++) {
|
||||
int digit256 = (int) number[i] & 0xFF;
|
||||
int temp = remainder * 256 + digit256;
|
||||
|
||||
number[i] = (byte) (temp / 58);
|
||||
|
||||
remainder = temp % 58;
|
||||
}
|
||||
|
||||
return (byte) remainder;
|
||||
}
|
||||
|
||||
//
|
||||
// number -> number / 256, returns number % 256
|
||||
//
|
||||
private static byte divmod256(byte[] number58, int startAt) {
|
||||
int remainder = 0;
|
||||
for (int i = startAt; i < number58.length; i++) {
|
||||
int digit58 = (int) number58[i] & 0xFF;
|
||||
int temp = remainder * 58 + digit58;
|
||||
|
||||
number58[i] = (byte) (temp / 256);
|
||||
|
||||
remainder = temp % 256;
|
||||
}
|
||||
|
||||
return (byte) remainder;
|
||||
}
|
||||
}
|
147
core/src/main/java/ch/dissem/bitmessage/utils/Bytes.java
Normal file
147
core/src/main/java/ch/dissem/bitmessage/utils/Bytes.java
Normal file
@ -0,0 +1,147 @@
|
||||
/*
|
||||
* 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.utils;
|
||||
|
||||
/**
|
||||
* A helper class for working with byte arrays interpreted as unsigned big endian integers.
|
||||
* This is one part due to the fact that Java doesn't support unsigned numbers, and another
|
||||
* part so we don't have to convert between byte arrays and numbers in time critical
|
||||
* situations.
|
||||
*/
|
||||
public class Bytes {
|
||||
public static void inc(byte[] nonce) {
|
||||
for (int i = nonce.length - 1; i >= 0; i--) {
|
||||
nonce[i]++;
|
||||
if (nonce[i] != 0) break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Increases nonce by value, which is used as an unsigned byte value.
|
||||
*
|
||||
* @param nonce an unsigned number
|
||||
* @param value to be added to nonce
|
||||
*/
|
||||
public static void inc(byte[] nonce, byte value) {
|
||||
int i = nonce.length - 1;
|
||||
nonce[i] += value;
|
||||
if (value > 0 && (nonce[i] < 0 || nonce[i] >= value))
|
||||
return;
|
||||
if (value < 0 && (nonce[i] < 0 && nonce[i] >= value))
|
||||
return;
|
||||
|
||||
for (i = i - 1; i >= 0; i--) {
|
||||
nonce[i]++;
|
||||
if (nonce[i] != 0) break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if a < b.
|
||||
*/
|
||||
public static boolean lt(byte[] a, byte[] b) {
|
||||
byte[] max = (a.length > b.length ? a : b);
|
||||
byte[] min = (max == a ? b : a);
|
||||
int diff = max.length - min.length;
|
||||
|
||||
for (int i = 0; i < max.length - min.length; i++) {
|
||||
if (max[i] != 0) return a != max;
|
||||
}
|
||||
for (int i = diff; i < max.length; i++) {
|
||||
if (max[i] != min[i - diff]) {
|
||||
return lt(max[i], min[i - diff]) == (a == max);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if a < b, where the first [size] bytes are used as the numbers to check.
|
||||
*/
|
||||
public static boolean lt(byte[] a, byte[] b, int size) {
|
||||
for (int i = 0; i < size; i++) {
|
||||
if (a[i] != b[i]) {
|
||||
return lt(a[i], b[i]);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean lt(byte a, byte b) {
|
||||
if (a < 0) return b < 0 && a < b;
|
||||
if (b < 0) return a >= 0 || a < b;
|
||||
return a < b;
|
||||
// This would be easier to understand, but is (slightly) slower:
|
||||
// return (a & 0xff) < (b & 0xff);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a new byte array of length, left-padded with '0'.
|
||||
*/
|
||||
public static byte[] expand(byte[] source, int size) {
|
||||
byte[] result = new byte[size];
|
||||
System.arraycopy(source, 0, result, size - source.length, source.length);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a new byte array containing the first <em>size</em> bytes of the given array.
|
||||
*/
|
||||
public static byte[] truncate(byte[] source, int size) {
|
||||
byte[] result = new byte[size];
|
||||
System.arraycopy(source, 0, result, 0, size);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the byte array that hex represents. This is meant for test use and should be rewritten if used in
|
||||
* production code.
|
||||
*/
|
||||
public static byte[] fromHex(String hex) {
|
||||
if (hex.length() % 2 != 0) throw new IllegalArgumentException("expected even number of characters");
|
||||
byte[] result = new byte[hex.length() / 2];
|
||||
for (int i = 0; i < result.length; i++) {
|
||||
result[i] = (byte) (hexValue(hex.charAt(i * 2)) * 16);
|
||||
result[i] += hexValue(hex.charAt(i * 2 + 1));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static int hexValue(char c) {
|
||||
if (c >= '0' && c <= '9') {
|
||||
return c - '0';
|
||||
}
|
||||
if (c >= 'a' && c <= 'f') {
|
||||
return 10 + c - 'a';
|
||||
}
|
||||
if (c >= 'A' && c <= 'F') {
|
||||
return 10 + c - 'A';
|
||||
}
|
||||
throw new IllegalArgumentException("'" + c + "' is not a valid hex value");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the number of leading '0' of a byte array.
|
||||
*/
|
||||
public static int numberOfLeadingZeros(byte[] bytes) {
|
||||
int i;
|
||||
for (i = 0; i < bytes.length; i++) {
|
||||
if (bytes[i] != 0) return i;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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.utils;
|
||||
|
||||
/**
|
||||
* Waits for a value within a callback method to be set.
|
||||
*/
|
||||
public class CallbackWaiter<T> {
|
||||
private final long startTime = System.currentTimeMillis();
|
||||
private volatile boolean isSet;
|
||||
private T value;
|
||||
private long time;
|
||||
|
||||
public void setValue(T value) {
|
||||
synchronized (this) {
|
||||
this.time = System.currentTimeMillis() - startTime;
|
||||
this.value = value;
|
||||
this.isSet = true;
|
||||
}
|
||||
}
|
||||
|
||||
public T waitForValue() throws InterruptedException {
|
||||
while (!isSet) {
|
||||
Thread.sleep(100);
|
||||
}
|
||||
synchronized (this) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
public long getTime() {
|
||||
return time;
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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.utils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
public class Collections {
|
||||
private final static Random RANDOM = new Random();
|
||||
|
||||
/**
|
||||
* @param count the number of elements to return (if possible)
|
||||
* @param collection the collection to take samples from
|
||||
* @return a random subset of the given collection, or a copy of the collection if it's not larger than count. The
|
||||
* result is by no means securely random, but should be random enough so not the same objects get selected over
|
||||
* and over again.
|
||||
*/
|
||||
public static <T> List<T> selectRandom(int count, Collection<T> collection) {
|
||||
ArrayList<T> result = new ArrayList<>(count);
|
||||
if (collection.size() <= count) {
|
||||
result.addAll(collection);
|
||||
} else {
|
||||
double collectionRest = collection.size();
|
||||
double resultRest = count;
|
||||
int skipMax = (int) Math.ceil(collectionRest / resultRest);
|
||||
int skip = RANDOM.nextInt(skipMax);
|
||||
for (T item : collection) {
|
||||
collectionRest--;
|
||||
if (skip > 0) {
|
||||
skip--;
|
||||
} else {
|
||||
result.add(item);
|
||||
resultRest--;
|
||||
if (resultRest == 0) {
|
||||
break;
|
||||
}
|
||||
skipMax = (int) Math.ceil(collectionRest / resultRest);
|
||||
skip = RANDOM.nextInt(skipMax);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static <T> T selectRandom(Collection<T> collection) {
|
||||
int index = RANDOM.nextInt(collection.size());
|
||||
for (T item : collection) {
|
||||
if (index == 0) {
|
||||
return item;
|
||||
}
|
||||
index--;
|
||||
}
|
||||
throw new IllegalArgumentException("Empty collection? Size: " + collection.size());
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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.utils;
|
||||
|
||||
import ch.dissem.bitmessage.entity.ObjectMessage;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
public class DebugUtils {
|
||||
private final static Logger LOG = LoggerFactory.getLogger(DebugUtils.class);
|
||||
|
||||
public static void saveToFile(ObjectMessage objectMessage) {
|
||||
try {
|
||||
File f = new File(System.getProperty("user.home") + "/jabit.error/" + objectMessage.getInventoryVector() + ".inv");
|
||||
f.createNewFile();
|
||||
objectMessage.write(new FileOutputStream(f));
|
||||
} catch (IOException e) {
|
||||
LOG.debug(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public static <K> void inc(Map<K, Integer> map, K key) {
|
||||
if (map.containsKey(key)) {
|
||||
map.put(key, map.get(key) + 1);
|
||||
} else {
|
||||
map.put(key, 1);
|
||||
}
|
||||
}
|
||||
}
|
142
core/src/main/java/ch/dissem/bitmessage/utils/Decode.java
Normal file
142
core/src/main/java/ch/dissem/bitmessage/utils/Decode.java
Normal file
@ -0,0 +1,142 @@
|
||||
/*
|
||||
* 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.utils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static ch.dissem.bitmessage.utils.AccessCounter.inc;
|
||||
|
||||
/**
|
||||
* This class handles decoding simple types from byte stream, according to
|
||||
* https://bitmessage.org/wiki/Protocol_specification#Common_structures
|
||||
*/
|
||||
public class Decode {
|
||||
public static byte[] shortVarBytes(InputStream stream, AccessCounter counter) throws IOException {
|
||||
int length = uint16(stream, counter);
|
||||
return bytes(stream, length, counter);
|
||||
}
|
||||
|
||||
public static byte[] varBytes(InputStream stream) throws IOException {
|
||||
int length = (int) varInt(stream, null);
|
||||
return bytes(stream, length, null);
|
||||
}
|
||||
|
||||
public static byte[] varBytes(InputStream stream, AccessCounter counter) throws IOException {
|
||||
int length = (int) varInt(stream, counter);
|
||||
return bytes(stream, length, counter);
|
||||
}
|
||||
|
||||
public static byte[] bytes(InputStream stream, int count) throws IOException {
|
||||
return bytes(stream, count, null);
|
||||
}
|
||||
|
||||
public static byte[] bytes(InputStream stream, int count, AccessCounter counter) throws IOException {
|
||||
byte[] result = new byte[count];
|
||||
int off = 0;
|
||||
while (off < count) {
|
||||
int read = stream.read(result, off, count - off);
|
||||
if (read < 0) {
|
||||
throw new IOException("Unexpected end of stream, wanted to read " + count + " bytes but only got " + off);
|
||||
}
|
||||
off += read;
|
||||
}
|
||||
inc(counter, count);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static long[] varIntList(InputStream stream) throws IOException {
|
||||
int length = (int) varInt(stream);
|
||||
long[] result = new long[length];
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
result[i] = varInt(stream);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static long varInt(InputStream stream) throws IOException {
|
||||
return varInt(stream, null);
|
||||
}
|
||||
|
||||
public static long varInt(InputStream stream, AccessCounter counter) throws IOException {
|
||||
int first = stream.read();
|
||||
inc(counter);
|
||||
switch (first) {
|
||||
case 0xfd:
|
||||
return uint16(stream, counter);
|
||||
case 0xfe:
|
||||
return uint32(stream, counter);
|
||||
case 0xff:
|
||||
return int64(stream, counter);
|
||||
default:
|
||||
return first;
|
||||
}
|
||||
}
|
||||
|
||||
public static int uint8(InputStream stream) throws IOException {
|
||||
return stream.read();
|
||||
}
|
||||
|
||||
public static int uint16(InputStream stream) throws IOException {
|
||||
return uint16(stream, null);
|
||||
}
|
||||
|
||||
public static int uint16(InputStream stream, AccessCounter counter) throws IOException {
|
||||
inc(counter, 2);
|
||||
return stream.read() * 256 + stream.read();
|
||||
}
|
||||
|
||||
public static long uint32(InputStream stream) throws IOException {
|
||||
return uint32(stream, null);
|
||||
}
|
||||
|
||||
public static long uint32(InputStream stream, AccessCounter counter) throws IOException {
|
||||
inc(counter, 4);
|
||||
return stream.read() * 16777216L + stream.read() * 65536L + stream.read() * 256L + stream.read();
|
||||
}
|
||||
|
||||
public static int int32(InputStream stream) throws IOException {
|
||||
return int32(stream, null);
|
||||
}
|
||||
|
||||
public static int int32(InputStream stream, AccessCounter counter) throws IOException {
|
||||
inc(counter, 4);
|
||||
return ByteBuffer.wrap(bytes(stream, 4)).getInt();
|
||||
}
|
||||
|
||||
public static long int64(InputStream stream) throws IOException {
|
||||
return int64(stream, null);
|
||||
}
|
||||
|
||||
public static long int64(InputStream stream, AccessCounter counter) throws IOException {
|
||||
inc(counter, 8);
|
||||
return ByteBuffer.wrap(bytes(stream, 8)).getLong();
|
||||
}
|
||||
|
||||
public static String varString(InputStream stream) throws IOException {
|
||||
return varString(stream, null);
|
||||
}
|
||||
|
||||
public static String varString(InputStream stream, AccessCounter counter) throws IOException {
|
||||
int length = (int) varInt(stream, counter);
|
||||
// FIXME: technically, it says the length in characters, but I think this one might be correct
|
||||
// otherwise it will get complicated, as we'll need to read UTF-8 char by char...
|
||||
return new String(bytes(stream, length, counter), "utf-8");
|
||||
}
|
||||
}
|
150
core/src/main/java/ch/dissem/bitmessage/utils/Encode.java
Normal file
150
core/src/main/java/ch/dissem/bitmessage/utils/Encode.java
Normal file
@ -0,0 +1,150 @@
|
||||
/*
|
||||
* 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.utils;
|
||||
|
||||
import ch.dissem.bitmessage.entity.Streamable;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static ch.dissem.bitmessage.utils.AccessCounter.inc;
|
||||
|
||||
/**
|
||||
* This class handles encoding simple types from byte stream, according to
|
||||
* https://bitmessage.org/wiki/Protocol_specification#Common_structures
|
||||
*/
|
||||
public class Encode {
|
||||
public static void varIntList(long[] values, OutputStream stream) throws IOException {
|
||||
varInt(values.length, stream);
|
||||
for (long value : values) {
|
||||
varInt(value, stream);
|
||||
}
|
||||
}
|
||||
|
||||
public static void varInt(long value, OutputStream stream) throws IOException {
|
||||
varInt(value, stream, null);
|
||||
}
|
||||
|
||||
public static void varInt(long value, OutputStream stream, AccessCounter counter) throws IOException {
|
||||
if (value < 0) {
|
||||
// This is due to the fact that Java doesn't really support unsigned values.
|
||||
// Please be aware that this might be an error due to a smaller negative value being cast to long.
|
||||
// Normally, negative values shouldn't occur within the protocol, and I large enough longs
|
||||
// to being recognized as negatives aren't realistic.
|
||||
stream.write(0xff);
|
||||
inc(counter);
|
||||
int64(value, stream, counter);
|
||||
} else if (value < 0xfd) {
|
||||
int8(value, stream, counter);
|
||||
} else if (value <= 0xffffL) {
|
||||
stream.write(0xfd);
|
||||
inc(counter);
|
||||
int16(value, stream, counter);
|
||||
} else if (value <= 0xffffffffL) {
|
||||
stream.write(0xfe);
|
||||
inc(counter);
|
||||
int32(value, stream, counter);
|
||||
} else {
|
||||
stream.write(0xff);
|
||||
inc(counter);
|
||||
int64(value, stream, counter);
|
||||
}
|
||||
}
|
||||
|
||||
public static void int8(long value, OutputStream stream) throws IOException {
|
||||
int8(value, stream, null);
|
||||
}
|
||||
|
||||
public static void int8(long value, OutputStream stream, AccessCounter counter) throws IOException {
|
||||
stream.write((int) value);
|
||||
inc(counter);
|
||||
}
|
||||
|
||||
public static void int16(long value, OutputStream stream) throws IOException {
|
||||
int16(value, stream, null);
|
||||
}
|
||||
|
||||
public static void int16(long value, OutputStream stream, AccessCounter counter) throws IOException {
|
||||
stream.write(ByteBuffer.allocate(4).putInt((int) value).array(), 2, 2);
|
||||
inc(counter, 2);
|
||||
}
|
||||
|
||||
public static void int32(long value, OutputStream stream) throws IOException {
|
||||
int32(value, stream, null);
|
||||
}
|
||||
|
||||
public static void int32(long value, OutputStream stream, AccessCounter counter) throws IOException {
|
||||
stream.write(ByteBuffer.allocate(4).putInt((int) value).array());
|
||||
inc(counter, 4);
|
||||
}
|
||||
|
||||
public static void int64(long value, OutputStream stream) throws IOException {
|
||||
int64(value, stream, null);
|
||||
}
|
||||
|
||||
public static void int64(long value, OutputStream stream, AccessCounter counter) throws IOException {
|
||||
stream.write(ByteBuffer.allocate(8).putLong(value).array());
|
||||
inc(counter, 8);
|
||||
}
|
||||
|
||||
public static void varString(String value, OutputStream out) throws IOException {
|
||||
byte[] bytes = value.getBytes("utf-8");
|
||||
// Technically, it says the length in characters, but I think this one might be correct.
|
||||
// It doesn't really matter, as only ASCII characters are being used.
|
||||
// see also Decode#varString()
|
||||
varInt(bytes.length, out);
|
||||
out.write(bytes);
|
||||
}
|
||||
|
||||
public static void varBytes(byte[] data, OutputStream out) throws IOException {
|
||||
varInt(data.length, out);
|
||||
out.write(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes a {@link Streamable} object and returns the byte array.
|
||||
*
|
||||
* @param streamable the object to be serialized
|
||||
* @return an array of bytes representing the given streamable object.
|
||||
* @throws IOException if an I/O error occurs.
|
||||
*/
|
||||
public static byte[] bytes(Streamable streamable) throws IOException {
|
||||
if (streamable == null) return null;
|
||||
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
streamable.write(stream);
|
||||
return stream.toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param streamable the object to be serialized
|
||||
* @param padding the result will be padded such that its length is a multiple of <em>padding</em>
|
||||
* @return the bytes of the given {@link Streamable} object, 0-padded such that the final length is x*padding.
|
||||
* @throws IOException if an I/O error occurs.
|
||||
*/
|
||||
public static byte[] bytes(Streamable streamable, int padding) throws IOException {
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
streamable.write(stream);
|
||||
int offset = padding - stream.size() % padding;
|
||||
int length = stream.size() + offset;
|
||||
byte[] result = new byte[length];
|
||||
stream.write(result, offset, stream.size());
|
||||
return result;
|
||||
}
|
||||
}
|
10
core/src/main/java/ch/dissem/bitmessage/utils/Numbers.java
Normal file
10
core/src/main/java/ch/dissem/bitmessage/utils/Numbers.java
Normal file
@ -0,0 +1,10 @@
|
||||
package ch.dissem.bitmessage.utils;
|
||||
|
||||
/**
|
||||
* Created by chrig on 07.12.2015.
|
||||
*/
|
||||
public class Numbers {
|
||||
public static long max(long a, long b) {
|
||||
return a > b ? a : b;
|
||||
}
|
||||
}
|
32
core/src/main/java/ch/dissem/bitmessage/utils/Points.java
Normal file
32
core/src/main/java/ch/dissem/bitmessage/utils/Points.java
Normal file
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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.utils;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Created by chris on 20.07.15.
|
||||
*/
|
||||
public class Points {
|
||||
public static byte[] getX(byte[] P) {
|
||||
return Arrays.copyOfRange(P, 1, ((P.length - 1) / 2) + 1);
|
||||
}
|
||||
|
||||
public static byte[] getY(byte[] P) {
|
||||
return Arrays.copyOfRange(P, ((P.length - 1) / 2) + 1, P.length);
|
||||
}
|
||||
}
|
91
core/src/main/java/ch/dissem/bitmessage/utils/Property.java
Normal file
91
core/src/main/java/ch/dissem/bitmessage/utils/Property.java
Normal file
@ -0,0 +1,91 @@
|
||||
/*
|
||||
* 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.utils;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Some property that has a name, a value and/or other properties. This can be used for any purpose, but is for now
|
||||
* used to contain different status information. It is by default displayed in some JSON inspired human readable
|
||||
* notation, but you might only want to rely on the 'human readable' part.
|
||||
* <p>
|
||||
* If you need a real JSON representation, please add a method <code>toJson()</code>.
|
||||
* </p>
|
||||
*/
|
||||
public class Property {
|
||||
private String name;
|
||||
private Object value;
|
||||
private Property[] properties;
|
||||
|
||||
public Property(String name, Object value, Property... properties) {
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Object getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the property if available or <code>null</code> otherwise.
|
||||
* Subproperties can be requested by submitting the sequence of properties.
|
||||
*/
|
||||
public Property getProperty(String... name) {
|
||||
if (name == null || name.length == 0) return null;
|
||||
|
||||
for (Property p : properties) {
|
||||
if (Objects.equals(name[0], p.name)) {
|
||||
if (name.length == 1)
|
||||
return p;
|
||||
else
|
||||
return p.getProperty(Arrays.copyOfRange(name, 1, name.length));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Property[] getProperties() {
|
||||
return properties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return toString("");
|
||||
}
|
||||
|
||||
private String toString(String indentation) {
|
||||
StringBuilder result = new StringBuilder();
|
||||
result.append(indentation).append(name).append(": ");
|
||||
if (value != null || properties.length == 0) {
|
||||
result.append(value);
|
||||
}
|
||||
if (properties.length > 0) {
|
||||
result.append("{\n");
|
||||
for (Property property : properties) {
|
||||
result.append(property.toString(indentation + " ")).append('\n');
|
||||
}
|
||||
result.append(indentation).append("}");
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
}
|
38
core/src/main/java/ch/dissem/bitmessage/utils/Singleton.java
Normal file
38
core/src/main/java/ch/dissem/bitmessage/utils/Singleton.java
Normal file
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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.utils;
|
||||
|
||||
import ch.dissem.bitmessage.ports.Cryptography;
|
||||
|
||||
/**
|
||||
* Created by chris on 20.07.15.
|
||||
*/
|
||||
public class Singleton {
|
||||
private static Cryptography cryptography;
|
||||
|
||||
public static void initialize(Cryptography cryptography) {
|
||||
synchronized (Singleton.class) {
|
||||
if (Singleton.cryptography == null) {
|
||||
Singleton.cryptography = cryptography;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Cryptography security() {
|
||||
return cryptography;
|
||||
}
|
||||
}
|
42
core/src/main/java/ch/dissem/bitmessage/utils/Strings.java
Normal file
42
core/src/main/java/ch/dissem/bitmessage/utils/Strings.java
Normal file
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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.utils;
|
||||
|
||||
import ch.dissem.bitmessage.entity.payload.ObjectType;
|
||||
|
||||
/**
|
||||
* Some utilities to handle strings.
|
||||
* TODO: Probably this should be split in a GUI related and an SQL related utility class.
|
||||
*/
|
||||
public class Strings {
|
||||
public static StringBuilder join(Object... objects) {
|
||||
StringBuilder streamList = new StringBuilder();
|
||||
for (int i = 0; i < objects.length; i++) {
|
||||
if (i > 0) streamList.append(", ");
|
||||
streamList.append(objects[i]);
|
||||
}
|
||||
return streamList;
|
||||
}
|
||||
|
||||
public static StringBuilder hex(byte[] bytes) {
|
||||
StringBuilder hex = new StringBuilder(bytes.length + 2);
|
||||
for (byte b : bytes) {
|
||||
hex.append(String.format("%02x", b));
|
||||
}
|
||||
return hex;
|
||||
}
|
||||
}
|
52
core/src/main/java/ch/dissem/bitmessage/utils/UnixTime.java
Normal file
52
core/src/main/java/ch/dissem/bitmessage/utils/UnixTime.java
Normal file
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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.utils;
|
||||
|
||||
/**
|
||||
* A simple utility class that simplifies using the second based time used in Bitmessage.
|
||||
*/
|
||||
public class UnixTime {
|
||||
/**
|
||||
* Length of a minute in seconds, intended for use with {@link #now(long)}.
|
||||
*/
|
||||
public static final int MINUTE = 60;
|
||||
/**
|
||||
* Length of an hour in seconds, intended for use with {@link #now(long)}.
|
||||
*/
|
||||
public static final long HOUR = 60 * MINUTE;
|
||||
/**
|
||||
* Length of a day in seconds, intended for use with {@link #now(long)}.
|
||||
*/
|
||||
public static final long DAY = 24 * HOUR;
|
||||
|
||||
/**
|
||||
* @return the time in second based Unix time ({@link System#currentTimeMillis()}/1000)
|
||||
*/
|
||||
public static long now() {
|
||||
return System.currentTimeMillis() / 1000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as {@link #now()} + shiftSeconds, but might be more readable.
|
||||
*
|
||||
* @param shiftSeconds number of seconds from now we're interested in
|
||||
* @return the Unix time in shiftSeconds seconds / shiftSeconds seconds ago
|
||||
*/
|
||||
public static long now(long shiftSeconds) {
|
||||
return (System.currentTimeMillis() / 1000) + shiftSeconds;
|
||||
}
|
||||
}
|
8
core/src/main/resources/nodes.txt
Normal file
8
core/src/main/resources/nodes.txt
Normal file
@ -0,0 +1,8 @@
|
||||
[stream 1]
|
||||
|
||||
dissem.ch:8444
|
||||
bootstrap8080.bitmessage.org:8080
|
||||
bootstrap8444.bitmessage.org:8444
|
||||
|
||||
[stream 2]
|
||||
# none yet
|
55
core/src/test/java/ch/dissem/bitmessage/DecryptionTest.java
Normal file
55
core/src/test/java/ch/dissem/bitmessage/DecryptionTest.java
Normal file
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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.BitmessageAddress;
|
||||
import ch.dissem.bitmessage.entity.ObjectMessage;
|
||||
import ch.dissem.bitmessage.entity.payload.V4Broadcast;
|
||||
import ch.dissem.bitmessage.entity.payload.V5Broadcast;
|
||||
import ch.dissem.bitmessage.exception.DecryptionFailedException;
|
||||
import ch.dissem.bitmessage.utils.TestBase;
|
||||
import ch.dissem.bitmessage.utils.TestUtils;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class DecryptionTest extends TestBase {
|
||||
@Test
|
||||
public void ensureV4BroadcastIsDecryptedCorrectly() throws IOException, DecryptionFailedException {
|
||||
BitmessageAddress address = new BitmessageAddress("BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ");
|
||||
TestUtils.loadPubkey(address);
|
||||
ObjectMessage objectMessage = TestUtils.loadObjectMessage(5, "V4Broadcast.payload");
|
||||
V4Broadcast broadcast = (V4Broadcast) objectMessage.getPayload();
|
||||
broadcast.decrypt(address);
|
||||
assertEquals("Test-Broadcast", broadcast.getPlaintext().getSubject());
|
||||
assertTrue(objectMessage.isSignatureValid(address.getPubkey()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ensureV5BroadcastIsDecryptedCorrectly() throws IOException, DecryptionFailedException {
|
||||
BitmessageAddress address = new BitmessageAddress("BM-2cXxfcSetKnbHJX2Y85rSkaVpsdNUZ5q9h");
|
||||
TestUtils.loadPubkey(address);
|
||||
ObjectMessage objectMessage = TestUtils.loadObjectMessage(5, "V5Broadcast.payload");
|
||||
V5Broadcast broadcast = (V5Broadcast) objectMessage.getPayload();
|
||||
broadcast.decrypt(address);
|
||||
assertEquals("Test-Broadcast", broadcast.getPlaintext().getSubject());
|
||||
assertTrue(objectMessage.isSignatureValid(address.getPubkey()));
|
||||
}
|
||||
}
|
64
core/src/test/java/ch/dissem/bitmessage/EncryptionTest.java
Normal file
64
core/src/test/java/ch/dissem/bitmessage/EncryptionTest.java
Normal file
@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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.BitmessageAddress;
|
||||
import ch.dissem.bitmessage.entity.ObjectMessage;
|
||||
import ch.dissem.bitmessage.entity.Plaintext;
|
||||
import ch.dissem.bitmessage.entity.payload.CryptoBox;
|
||||
import ch.dissem.bitmessage.entity.payload.GenericPayload;
|
||||
import ch.dissem.bitmessage.entity.payload.Msg;
|
||||
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
|
||||
import ch.dissem.bitmessage.exception.DecryptionFailedException;
|
||||
import ch.dissem.bitmessage.utils.TestBase;
|
||||
import ch.dissem.bitmessage.utils.TestUtils;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static ch.dissem.bitmessage.utils.Singleton.security;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
public class EncryptionTest extends TestBase {
|
||||
@Test
|
||||
public void ensureDecryptedDataIsSameAsBeforeEncryption() throws IOException, DecryptionFailedException {
|
||||
GenericPayload before = new GenericPayload(0, 1, security().randomBytes(100));
|
||||
|
||||
PrivateKey privateKey = new PrivateKey(false, 1, 1000, 1000);
|
||||
CryptoBox cryptoBox = new CryptoBox(before, privateKey.getPubkey().getEncryptionKey());
|
||||
|
||||
GenericPayload after = GenericPayload.read(0, cryptoBox.decrypt(privateKey.getPrivateEncryptionKey()), 1, 100);
|
||||
|
||||
assertEquals(before, after);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ensureMessageCanBeDecrypted() throws IOException, DecryptionFailedException {
|
||||
PrivateKey privateKey = PrivateKey.read(TestUtils.getResource("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8.privkey"));
|
||||
BitmessageAddress identity = new BitmessageAddress(privateKey);
|
||||
assertEquals("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8", identity.getAddress());
|
||||
|
||||
ObjectMessage object = TestUtils.loadObjectMessage(3, "V1Msg.payload");
|
||||
Msg msg = (Msg) object.getPayload();
|
||||
msg.decrypt(privateKey.getPrivateEncryptionKey());
|
||||
Plaintext plaintext = msg.getPlaintext();
|
||||
assertNotNull(plaintext);
|
||||
assertEquals("Test", plaintext.getSubject());
|
||||
assertEquals("Hallo, das ist ein Test von der v4-Adresse", plaintext.getText());
|
||||
}
|
||||
}
|
71
core/src/test/java/ch/dissem/bitmessage/SignatureTest.java
Normal file
71
core/src/test/java/ch/dissem/bitmessage/SignatureTest.java
Normal file
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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.BitmessageAddress;
|
||||
import ch.dissem.bitmessage.entity.ObjectMessage;
|
||||
import ch.dissem.bitmessage.entity.Plaintext;
|
||||
import ch.dissem.bitmessage.entity.payload.Msg;
|
||||
import ch.dissem.bitmessage.entity.payload.ObjectType;
|
||||
import ch.dissem.bitmessage.entity.payload.Pubkey;
|
||||
import ch.dissem.bitmessage.entity.payload.V4Pubkey;
|
||||
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
|
||||
import ch.dissem.bitmessage.exception.DecryptionFailedException;
|
||||
import ch.dissem.bitmessage.utils.TestBase;
|
||||
import ch.dissem.bitmessage.utils.TestUtils;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class SignatureTest extends TestBase {
|
||||
@Test
|
||||
public void ensureValidationWorks() throws IOException {
|
||||
ObjectMessage object = TestUtils.loadObjectMessage(3, "V3Pubkey.payload");
|
||||
Pubkey pubkey = (Pubkey) object.getPayload();
|
||||
assertTrue(object.isSignatureValid(pubkey));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ensureSigningWorks() throws IOException {
|
||||
PrivateKey privateKey = new PrivateKey(false, 1, 1000, 1000);
|
||||
|
||||
ObjectMessage objectMessage = new ObjectMessage.Builder()
|
||||
.objectType(ObjectType.PUBKEY)
|
||||
.stream(1)
|
||||
.payload(privateKey.getPubkey())
|
||||
.build();
|
||||
objectMessage.sign(privateKey);
|
||||
|
||||
assertTrue(objectMessage.isSignatureValid(privateKey.getPubkey()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ensureMessageIsProperlySigned() throws IOException, DecryptionFailedException {
|
||||
BitmessageAddress identity = TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8");
|
||||
|
||||
ObjectMessage object = TestUtils.loadObjectMessage(3, "V1Msg.payload");
|
||||
Msg msg = (Msg) object.getPayload();
|
||||
msg.decrypt(identity.getPrivateKey().getPrivateEncryptionKey());
|
||||
Plaintext plaintext = msg.getPlaintext();
|
||||
assertEquals(TestUtils.loadContact().getPubkey(), plaintext.getFrom().getPubkey());
|
||||
assertNotNull(plaintext);
|
||||
assertTrue(object.isSignatureValid(plaintext.getFrom().getPubkey()));
|
||||
}
|
||||
}
|
@ -0,0 +1,143 @@
|
||||
/*
|
||||
* 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.entity;
|
||||
|
||||
import ch.dissem.bitmessage.entity.payload.Pubkey;
|
||||
import ch.dissem.bitmessage.entity.payload.V4Pubkey;
|
||||
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
|
||||
import ch.dissem.bitmessage.exception.DecryptionFailedException;
|
||||
import ch.dissem.bitmessage.utils.*;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static ch.dissem.bitmessage.entity.payload.Pubkey.Feature.DOES_ACK;
|
||||
import static ch.dissem.bitmessage.utils.Singleton.security;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class BitmessageAddressTest {
|
||||
@Test
|
||||
public void ensureBase58DecodesCorrectly() {
|
||||
assertHexEquals("800C28FCA386C7A227600B2FE50B7CAE11EC86D3BF1FBE471BE89827E19D72AA1D507A5B8D",
|
||||
Base58.decode("5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ensureAddressStaysSame() {
|
||||
String address = "BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ";
|
||||
assertEquals(address, new BitmessageAddress(address).toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ensureStreamAndVersionAreParsed() {
|
||||
BitmessageAddress address = new BitmessageAddress("BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ");
|
||||
assertEquals(1, address.getStream());
|
||||
assertEquals(3, address.getVersion());
|
||||
|
||||
address = new BitmessageAddress("BM-87hJ99tPAXxtetvnje7Z491YSvbEtBJVc5e");
|
||||
assertEquals(1, address.getStream());
|
||||
assertEquals(4, address.getVersion());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateAddress() {
|
||||
BitmessageAddress address = new BitmessageAddress(new PrivateKey(false, 1, 1000, 1000, DOES_ACK));
|
||||
assertNotNull(address.getPubkey());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testV2PubkeyImport() throws IOException {
|
||||
ObjectMessage object = TestUtils.loadObjectMessage(2, "V2Pubkey.payload");
|
||||
Pubkey pubkey = (Pubkey) object.getPayload();
|
||||
BitmessageAddress address = new BitmessageAddress("BM-opWQhvk9xtMFvQA2Kvetedpk8LkbraWHT");
|
||||
address.setPubkey(pubkey);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testV3PubkeyImport() throws IOException {
|
||||
BitmessageAddress address = new BitmessageAddress("BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ");
|
||||
assertArrayEquals(Bytes.fromHex("007402be6e76c3cb87caa946d0c003a3d4d8e1d5"), address.getRipe());
|
||||
|
||||
ObjectMessage object = TestUtils.loadObjectMessage(3, "V3Pubkey.payload");
|
||||
Pubkey pubkey = (Pubkey) object.getPayload();
|
||||
assertTrue(object.isSignatureValid(pubkey));
|
||||
address.setPubkey(pubkey);
|
||||
|
||||
assertArrayEquals(Bytes.fromHex("007402be6e76c3cb87caa946d0c003a3d4d8e1d5"), pubkey.getRipe());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testV4PubkeyImport() throws IOException, DecryptionFailedException {
|
||||
BitmessageAddress address = new BitmessageAddress("BM-2cXxfcSetKnbHJX2Y85rSkaVpsdNUZ5q9h");
|
||||
ObjectMessage object = TestUtils.loadObjectMessage(4, "V4Pubkey.payload");
|
||||
object.decrypt(address.getPublicDecryptionKey());
|
||||
V4Pubkey pubkey = (V4Pubkey) object.getPayload();
|
||||
assertTrue(object.isSignatureValid(pubkey));
|
||||
address.setPubkey(pubkey);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testV3AddressImport() throws IOException {
|
||||
String address_string = "BM-2DAjcCFrqFrp88FUxExhJ9kPqHdunQmiyn";
|
||||
assertEquals(3, new BitmessageAddress(address_string).getVersion());
|
||||
assertEquals(1, new BitmessageAddress(address_string).getStream());
|
||||
|
||||
byte[] privsigningkey = getSecret("5KU2gbe9u4rKJ8PHYb1rvwMnZnAJj4gtV5GLwoYckeYzygWUzB9");
|
||||
byte[] privencryptionkey = getSecret("5KHd4c6cavd8xv4kzo3PwnVaYuBgEfg7voPQ5V97aZKgpYBXGck");
|
||||
|
||||
System.out.println("\n\n" + Strings.hex(privsigningkey) + "\n\n");
|
||||
|
||||
BitmessageAddress address = new BitmessageAddress(new PrivateKey(privsigningkey, privencryptionkey,
|
||||
security().createPubkey(3, 1, privsigningkey, privencryptionkey, 320, 14000)));
|
||||
assertEquals(address_string, address.getAddress());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSecret() throws IOException {
|
||||
assertHexEquals("0C28FCA386C7A227600B2FE50B7CAE11EC86D3BF1FBE471BE89827E19D72AA1D",
|
||||
getSecret("5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ"));
|
||||
}
|
||||
|
||||
private byte[] getSecret(String walletImportFormat) throws IOException {
|
||||
byte[] bytes = Base58.decode(walletImportFormat);
|
||||
if (bytes[0] != (byte) 0x80)
|
||||
throw new IOException("Unknown format: 0x80 expected as first byte, but secret " + walletImportFormat + " was " + bytes[0]);
|
||||
if (bytes.length != 37)
|
||||
throw new IOException("Unknown format: 37 bytes expected, but secret " + walletImportFormat + " was " + bytes.length + " long");
|
||||
|
||||
byte[] hash = security().doubleSha256(bytes, 33);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (hash[i] != bytes[33 + i]) throw new IOException("Hash check failed for secret " + walletImportFormat);
|
||||
}
|
||||
return Arrays.copyOfRange(bytes, 1, 33);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testV4AddressImport() throws IOException {
|
||||
assertEquals(4, new BitmessageAddress("BM-2cV5f9EpzaYARxtoruSpa6pDoucSf9ZNke").getVersion());
|
||||
byte[] privsigningkey = getSecret("5KMWqfCyJZGFgW6QrnPJ6L9Gatz25B51y7ErgqNr1nXUVbtZbdU");
|
||||
byte[] privencryptionkey = getSecret("5JXXWEuhHQEPk414SzEZk1PHDRi8kCuZd895J7EnKeQSahJPxGz");
|
||||
BitmessageAddress address = new BitmessageAddress(new PrivateKey(privsigningkey, privencryptionkey,
|
||||
security().createPubkey(4, 1, privsigningkey, privencryptionkey, 320, 14000)));
|
||||
assertEquals("BM-2cV5f9EpzaYARxtoruSpa6pDoucSf9ZNke", address.getAddress());
|
||||
}
|
||||
|
||||
private void assertHexEquals(String hex, byte[] bytes) {
|
||||
assertEquals(hex.toLowerCase(), Strings.hex(bytes).toString().toLowerCase());
|
||||
}
|
||||
}
|
@ -0,0 +1,125 @@
|
||||
/*
|
||||
* 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.entity;
|
||||
|
||||
import ch.dissem.bitmessage.entity.payload.*;
|
||||
import ch.dissem.bitmessage.entity.valueobject.Label;
|
||||
import ch.dissem.bitmessage.factory.Factory;
|
||||
import ch.dissem.bitmessage.utils.TestBase;
|
||||
import ch.dissem.bitmessage.utils.TestUtils;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.*;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Collections;
|
||||
|
||||
import static ch.dissem.bitmessage.entity.Plaintext.Type.MSG;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class SerializationTest extends TestBase {
|
||||
@Test
|
||||
public void ensureGetPubkeyIsDeserializedAndSerializedCorrectly() throws IOException {
|
||||
doTest("V2GetPubkey.payload", 2, GetPubkey.class);
|
||||
doTest("V3GetPubkey.payload", 2, GetPubkey.class);
|
||||
doTest("V4GetPubkey.payload", 2, GetPubkey.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ensureV2PubkeyIsDeserializedAndSerializedCorrectly() throws IOException {
|
||||
doTest("V2Pubkey.payload", 2, V2Pubkey.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ensureV3PubkeyIsDeserializedAndSerializedCorrectly() throws IOException {
|
||||
doTest("V3Pubkey.payload", 3, V3Pubkey.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ensureV4PubkeyIsDeserializedAndSerializedCorrectly() throws IOException {
|
||||
doTest("V4Pubkey.payload", 4, V4Pubkey.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ensureV1MsgIsDeserializedAndSerializedCorrectly() throws IOException {
|
||||
doTest("V1Msg.payload", 1, Msg.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ensureV4BroadcastIsDeserializedAndSerializedCorrectly() throws IOException {
|
||||
doTest("V4Broadcast.payload", 4, V4Broadcast.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ensureV5BroadcastIsDeserializedAndSerializedCorrectly() throws IOException {
|
||||
doTest("V5Broadcast.payload", 5, V5Broadcast.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ensureUnknownDataIsDeserializedAndSerializedCorrectly() throws IOException {
|
||||
doTest("V1MsgStrangeData.payload", 1, GenericPayload.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ensurePlaintextIsSerializedAndDeserializedCorrectly() throws Exception {
|
||||
Plaintext p1 = new Plaintext.Builder(MSG)
|
||||
.from(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"))
|
||||
.to(TestUtils.loadContact())
|
||||
.message("Subject", "Message")
|
||||
.ack("ack".getBytes())
|
||||
.signature(new byte[0])
|
||||
.build();
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
p1.write(out);
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
|
||||
Plaintext p2 = Plaintext.read(MSG, in);
|
||||
|
||||
// Received is automatically set on deserialization, so we'll need to set it to 0
|
||||
Field received = Plaintext.class.getDeclaredField("received");
|
||||
received.setAccessible(true);
|
||||
received.set(p2, 0L);
|
||||
|
||||
assertEquals(p1, p2);
|
||||
}
|
||||
|
||||
private void doTest(String resourceName, int version, Class<?> expectedPayloadType) throws IOException {
|
||||
byte[] data = TestUtils.getBytes(resourceName);
|
||||
InputStream in = new ByteArrayInputStream(data);
|
||||
ObjectMessage object = Factory.getObjectMessage(version, in, data.length);
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
assertNotNull(object);
|
||||
object.write(out);
|
||||
assertArrayEquals(data, out.toByteArray());
|
||||
assertEquals(expectedPayloadType.getCanonicalName(), object.getPayload().getClass().getCanonicalName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ensureSystemSerializationWorks() throws Exception {
|
||||
Plaintext plaintext = new Plaintext.Builder(MSG)
|
||||
.from(TestUtils.loadContact())
|
||||
.to(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"))
|
||||
.labels(Collections.singletonList(new Label("Test", Label.Type.INBOX, 0)))
|
||||
.message("Test", "Test Test.\nTest")
|
||||
.build();
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
ObjectOutputStream oos = new ObjectOutputStream(out);
|
||||
oos.writeObject(plaintext);
|
||||
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
|
||||
ObjectInputStream ois = new ObjectInputStream(in);
|
||||
assertEquals(plaintext, ois.readObject());
|
||||
}
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
/*
|
||||
* 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.ports;
|
||||
|
||||
import ch.dissem.bitmessage.utils.Bytes;
|
||||
import ch.dissem.bitmessage.utils.CallbackWaiter;
|
||||
import ch.dissem.bitmessage.utils.TestBase;
|
||||
import org.junit.Test;
|
||||
|
||||
import static ch.dissem.bitmessage.utils.Singleton.security;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class ProofOfWorkEngineTest extends TestBase {
|
||||
@Test(timeout = 90_000)
|
||||
public void testSimplePOWEngine() throws InterruptedException {
|
||||
testPOW(new SimplePOWEngine());
|
||||
}
|
||||
|
||||
@Test(timeout = 90_000)
|
||||
public void testThreadedPOWEngine() throws InterruptedException {
|
||||
testPOW(new MultiThreadedPOWEngine());
|
||||
}
|
||||
|
||||
private void testPOW(ProofOfWorkEngine engine) throws InterruptedException {
|
||||
byte[] initialHash = security().sha512(new byte[]{1, 3, 6, 4});
|
||||
byte[] target = {0, 0, 0, -1, -1, -1, -1, -1};
|
||||
|
||||
final CallbackWaiter<byte[]> waiter1 = new CallbackWaiter<>();
|
||||
engine.calculateNonce(initialHash, target,
|
||||
new ProofOfWorkEngine.Callback() {
|
||||
@Override
|
||||
public void onNonceCalculated(byte[] initialHash, byte[] nonce) {
|
||||
waiter1.setValue(nonce);
|
||||
}
|
||||
});
|
||||
byte[] nonce = waiter1.waitForValue();
|
||||
System.out.println("Calculating nonce took " + waiter1.getTime() + "ms");
|
||||
assertTrue(Bytes.lt(security().doubleSha512(nonce, initialHash), target, 8));
|
||||
|
||||
// Let's add a second (shorter) run to find possible multi threading issues
|
||||
byte[] initialHash2 = security().sha512(new byte[]{1, 3, 6, 5});
|
||||
byte[] target2 = {0, 0, -1, -1, -1, -1, -1, -1};
|
||||
|
||||
final CallbackWaiter<byte[]> waiter2 = new CallbackWaiter<>();
|
||||
engine.calculateNonce(initialHash2, target2,
|
||||
new ProofOfWorkEngine.Callback() {
|
||||
@Override
|
||||
public void onNonceCalculated(byte[] initialHash, byte[] nonce) {
|
||||
waiter2.setValue(nonce);
|
||||
}
|
||||
});
|
||||
byte[] nonce2 = waiter2.waitForValue();
|
||||
System.out.println("Calculating nonce took " + waiter2.getTime() + "ms");
|
||||
assertTrue(Bytes.lt(security().doubleSha512(nonce2, initialHash2), target2, 8));
|
||||
assertTrue("Second nonce must be quicker to find", waiter1.getTime() > waiter2.getTime());
|
||||
}
|
||||
|
||||
}
|
97
core/src/test/java/ch/dissem/bitmessage/utils/BytesTest.java
Normal file
97
core/src/test/java/ch/dissem/bitmessage/utils/BytesTest.java
Normal file
@ -0,0 +1,97 @@
|
||||
/*
|
||||
* 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.utils;
|
||||
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.util.Random;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class BytesTest {
|
||||
public static final Random rnd = new Random();
|
||||
|
||||
@Test
|
||||
public void ensureExpandsCorrectly() {
|
||||
byte[] source = {1};
|
||||
byte[] expected = {0, 1};
|
||||
assertArrayEquals(expected, Bytes.expand(source, 2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ensureIncrementCarryWorks() throws IOException {
|
||||
byte[] bytes = {0, -1};
|
||||
Bytes.inc(bytes);
|
||||
assertArrayEquals(TestUtils.int16(256), bytes);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIncrementByValue() throws IOException {
|
||||
for (int v = 0; v < 256; v++) {
|
||||
for (int i = 1; i < 256; i++) {
|
||||
byte[] bytes = {0, (byte) v};
|
||||
Bytes.inc(bytes, (byte) i);
|
||||
assertArrayEquals("value = " + v + "; inc = " + i + "; expected = " + (v + i), TestUtils.int16(v + i), bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This test is used to compare different implementations of the single byte lt comparison. It an safely be ignored.
|
||||
*/
|
||||
@Test
|
||||
@Ignore
|
||||
public void testLowerThanSingleByte() {
|
||||
byte[] a = new byte[1];
|
||||
byte[] b = new byte[1];
|
||||
for (int i = 0; i < 255; i++) {
|
||||
for (int j = 0; j < 255; j++) {
|
||||
System.out.println("a = " + i + "\tb = " + j);
|
||||
a[0] = (byte) i;
|
||||
b[0] = (byte) j;
|
||||
assertEquals(i < j, Bytes.lt(a, b));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLowerThan() {
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
BigInteger a = BigInteger.valueOf(rnd.nextLong()).pow((rnd.nextInt(5) + 1)).abs();
|
||||
BigInteger b = BigInteger.valueOf(rnd.nextLong()).pow((rnd.nextInt(5) + 1)).abs();
|
||||
System.out.println("a = " + a.toString(16) + "\tb = " + b.toString(16));
|
||||
assertEquals(a.compareTo(b) == -1, Bytes.lt(a.toByteArray(), b.toByteArray()));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLowerThanBounded() {
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
BigInteger a = BigInteger.valueOf(rnd.nextLong()).pow((rnd.nextInt(5) + 1)).abs();
|
||||
BigInteger b = BigInteger.valueOf(rnd.nextLong()).pow((rnd.nextInt(5) + 1)).abs();
|
||||
System.out.println("a = " + a.toString(16) + "\tb = " + b.toString(16));
|
||||
assertEquals(a.compareTo(b) == -1, Bytes.lt(
|
||||
Bytes.expand(a.toByteArray(), 100),
|
||||
Bytes.expand(b.toByteArray(), 100),
|
||||
100));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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.utils;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class CollectionsTest {
|
||||
@Test
|
||||
public void ensureSelectRandomReturnsMaximumPossibleItems() throws Exception {
|
||||
List<Integer> list = new LinkedList<>();
|
||||
for (int i = 0; i < 10; i++) {
|
||||
list.add(i);
|
||||
}
|
||||
assertEquals(9, Collections.selectRandom(9, list).size());
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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.utils;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class DecodeTest {
|
||||
@Test
|
||||
public void ensureDecodingWorks() throws Exception {
|
||||
// This should test all relevant cases for var_int and therefore also uint_16, uint_32 and int_64
|
||||
testCodec(0);
|
||||
for (long i = 1; i > 0; i = 3 * i + 7) {
|
||||
testCodec(i);
|
||||
}
|
||||
}
|
||||
|
||||
private void testCodec(long number) throws IOException {
|
||||
ByteArrayOutputStream is = new ByteArrayOutputStream();
|
||||
Encode.varInt(number, is);
|
||||
assertEquals(number, Decode.varInt(new ByteArrayInputStream(is.toByteArray())));
|
||||
}
|
||||
}
|
124
core/src/test/java/ch/dissem/bitmessage/utils/EncodeTest.java
Normal file
124
core/src/test/java/ch/dissem/bitmessage/utils/EncodeTest.java
Normal file
@ -0,0 +1,124 @@
|
||||
/*
|
||||
* 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.utils;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class EncodeTest {
|
||||
@Test
|
||||
public void testUint8() throws IOException {
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
Encode.int8(0, stream);
|
||||
checkBytes(stream, 0);
|
||||
|
||||
stream = new ByteArrayOutputStream();
|
||||
Encode.int8(255, stream);
|
||||
checkBytes(stream, 255);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUint16() throws IOException {
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
Encode.int16(0, stream);
|
||||
checkBytes(stream, 0, 0);
|
||||
|
||||
stream = new ByteArrayOutputStream();
|
||||
Encode.int16(513, stream);
|
||||
checkBytes(stream, 2, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUint32() throws IOException {
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
Encode.int32(0, stream);
|
||||
checkBytes(stream, 0, 0, 0, 0);
|
||||
|
||||
stream = new ByteArrayOutputStream();
|
||||
Encode.int32(67305985, stream);
|
||||
checkBytes(stream, 4, 3, 2, 1);
|
||||
|
||||
stream = new ByteArrayOutputStream();
|
||||
Encode.int32(3355443201l, stream);
|
||||
checkBytes(stream, 200, 0, 0, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUint64() throws IOException {
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
Encode.int64(0, stream);
|
||||
checkBytes(stream, 0, 0, 0, 0, 0, 0, 0, 0);
|
||||
|
||||
stream = new ByteArrayOutputStream();
|
||||
Encode.int64(578437695752307201L, stream);
|
||||
checkBytes(stream, 8, 7, 6, 5, 4, 3, 2, 1);
|
||||
|
||||
stream = new ByteArrayOutputStream();
|
||||
// 200 * 72057594037927936L + 1
|
||||
Encode.int64(0xc800000000000001L, stream);
|
||||
checkBytes(stream, 200, 0, 0, 0, 0, 0, 0, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVarInt() throws IOException {
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
Encode.varInt(0, stream);
|
||||
checkBytes(stream, 0);
|
||||
|
||||
stream = new ByteArrayOutputStream();
|
||||
Encode.varInt(252, stream);
|
||||
checkBytes(stream, 252);
|
||||
|
||||
stream = new ByteArrayOutputStream();
|
||||
Encode.varInt(253, stream);
|
||||
checkBytes(stream, 253, 0, 253);
|
||||
|
||||
stream = new ByteArrayOutputStream();
|
||||
Encode.varInt(65535, stream);
|
||||
checkBytes(stream, 253, 255, 255);
|
||||
|
||||
stream = new ByteArrayOutputStream();
|
||||
Encode.varInt(65536, stream);
|
||||
checkBytes(stream, 254, 0, 1, 0, 0);
|
||||
|
||||
stream = new ByteArrayOutputStream();
|
||||
Encode.varInt(4294967295L, stream);
|
||||
checkBytes(stream, 254, 255, 255, 255, 255);
|
||||
|
||||
stream = new ByteArrayOutputStream();
|
||||
Encode.varInt(4294967296L, stream);
|
||||
checkBytes(stream, 255, 0, 0, 0, 1, 0, 0, 0, 0);
|
||||
|
||||
stream = new ByteArrayOutputStream();
|
||||
Encode.varInt(-1L, stream);
|
||||
checkBytes(stream, 255, 255, 255, 255, 255, 255, 255, 255, 255);
|
||||
}
|
||||
|
||||
|
||||
public void checkBytes(ByteArrayOutputStream stream, int... bytes) {
|
||||
assertEquals(bytes.length, stream.size());
|
||||
byte[] streamBytes = stream.toByteArray();
|
||||
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
assertEquals((byte) bytes[i], streamBytes[i]);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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.utils;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class StringsTest {
|
||||
@Test
|
||||
public void testHexString() {
|
||||
assertEquals("48656c6c6f21", Strings.hex("Hello!".getBytes()).toString());
|
||||
assertEquals("0001", Strings.hex(new byte[]{0, 1}).toString());
|
||||
}
|
||||
}
|
28
core/src/test/java/ch/dissem/bitmessage/utils/TestBase.java
Normal file
28
core/src/test/java/ch/dissem/bitmessage/utils/TestBase.java
Normal file
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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.utils;
|
||||
|
||||
import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography;
|
||||
|
||||
/**
|
||||
* Created by chris on 20.07.15.
|
||||
*/
|
||||
public class TestBase {
|
||||
static {
|
||||
Singleton.initialize(new BouncyCryptography());
|
||||
}
|
||||
}
|
86
core/src/test/java/ch/dissem/bitmessage/utils/TestUtils.java
Normal file
86
core/src/test/java/ch/dissem/bitmessage/utils/TestUtils.java
Normal file
@ -0,0 +1,86 @@
|
||||
/*
|
||||
* 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.utils;
|
||||
|
||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
||||
import ch.dissem.bitmessage.entity.ObjectMessage;
|
||||
import ch.dissem.bitmessage.entity.payload.Pubkey;
|
||||
import ch.dissem.bitmessage.entity.payload.V4Pubkey;
|
||||
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
|
||||
import ch.dissem.bitmessage.exception.DecryptionFailedException;
|
||||
import ch.dissem.bitmessage.factory.Factory;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* If there's ever a need for this in production code, it should be rewritten to be more efficient.
|
||||
*/
|
||||
public class TestUtils {
|
||||
public static byte[] int16(int number) throws IOException {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
Encode.int16(number, out);
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
public static ObjectMessage loadObjectMessage(int version, String resourceName) throws IOException {
|
||||
byte[] data = getBytes(resourceName);
|
||||
InputStream in = new ByteArrayInputStream(data);
|
||||
return Factory.getObjectMessage(version, in, data.length);
|
||||
}
|
||||
|
||||
public static byte[] getBytes(String resourceName) throws IOException {
|
||||
InputStream in = TestUtils.class.getClassLoader().getResourceAsStream(resourceName);
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
byte[] buffer = new byte[1024];
|
||||
int len = in.read(buffer);
|
||||
while (len != -1) {
|
||||
out.write(buffer, 0, len);
|
||||
len = in.read(buffer);
|
||||
}
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
public static InputStream getResource(String resourceName) {
|
||||
return TestUtils.class.getClassLoader().getResourceAsStream(resourceName);
|
||||
}
|
||||
|
||||
public static BitmessageAddress loadIdentity(String address) throws IOException {
|
||||
PrivateKey privateKey = PrivateKey.read(TestUtils.getResource(address + ".privkey"));
|
||||
BitmessageAddress identity = new BitmessageAddress(privateKey);
|
||||
assertEquals(address, identity.getAddress());
|
||||
return identity;
|
||||
}
|
||||
|
||||
public static BitmessageAddress loadContact() throws IOException, DecryptionFailedException {
|
||||
BitmessageAddress address = new BitmessageAddress("BM-2cXxfcSetKnbHJX2Y85rSkaVpsdNUZ5q9h");
|
||||
ObjectMessage object = TestUtils.loadObjectMessage(4, "V4Pubkey.payload");
|
||||
object.decrypt(address.getPublicDecryptionKey());
|
||||
address.setPubkey((V4Pubkey) object.getPayload());
|
||||
return address;
|
||||
}
|
||||
|
||||
public static void loadPubkey(BitmessageAddress address) throws IOException {
|
||||
byte[] bytes = getBytes(address.getAddress() + ".pubkey");
|
||||
Pubkey pubkey = Factory.readPubkey(address.getVersion(), address.getStream(), new ByteArrayInputStream(bytes), bytes.length, false);
|
||||
address.setPubkey(pubkey);
|
||||
}
|
||||
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
core/src/test/resources/V1Msg.payload
Normal file
BIN
core/src/test/resources/V1Msg.payload
Normal file
Binary file not shown.
BIN
core/src/test/resources/V1MsgStrangeData.payload
Normal file
BIN
core/src/test/resources/V1MsgStrangeData.payload
Normal file
Binary file not shown.
BIN
core/src/test/resources/V2GetPubkey.payload
Normal file
BIN
core/src/test/resources/V2GetPubkey.payload
Normal file
Binary file not shown.
BIN
core/src/test/resources/V2Pubkey.payload
Normal file
BIN
core/src/test/resources/V2Pubkey.payload
Normal file
Binary file not shown.
BIN
core/src/test/resources/V3GetPubkey.payload
Normal file
BIN
core/src/test/resources/V3GetPubkey.payload
Normal file
Binary file not shown.
BIN
core/src/test/resources/V3Pubkey.payload
Normal file
BIN
core/src/test/resources/V3Pubkey.payload
Normal file
Binary file not shown.
BIN
core/src/test/resources/V4Broadcast.payload
Normal file
BIN
core/src/test/resources/V4Broadcast.payload
Normal file
Binary file not shown.
BIN
core/src/test/resources/V4GetPubkey.payload
Normal file
BIN
core/src/test/resources/V4GetPubkey.payload
Normal file
Binary file not shown.
BIN
core/src/test/resources/V4Pubkey.payload
Normal file
BIN
core/src/test/resources/V4Pubkey.payload
Normal file
Binary file not shown.
BIN
core/src/test/resources/V5Broadcast.payload
Normal file
BIN
core/src/test/resources/V5Broadcast.payload
Normal file
Binary file not shown.
Reference in New Issue
Block a user