Merge branch 'release/1.0.0'
This commit is contained in:
commit
e56d8c25e0
@ -1,2 +1,3 @@
|
||||
language: java
|
||||
|
||||
jdk:
|
||||
- oraclejdk8
|
||||
|
1541
Bitmessage.uml
1541
Bitmessage.uml
File diff suppressed because it is too large
Load Diff
24
CONTRIBUTING.md
Normal file
24
CONTRIBUTING.md
Normal file
@ -0,0 +1,24 @@
|
||||
# Contributing
|
||||
|
||||
We love pull requests from everyone. Please be nice and forgive us
|
||||
if we can't process your request right away.
|
||||
|
||||
Fork, then clone the repo:
|
||||
|
||||
git clone git@github.com:your-username/Jabit.git
|
||||
|
||||
Make sure the tests pass:
|
||||
|
||||
./gradlew test
|
||||
|
||||
Make your change. Add tests for your change. Make the tests pass:
|
||||
|
||||
./gradlew test
|
||||
|
||||
Push to your fork and [submit a pull request][pr].
|
||||
|
||||
[pr]: https://github.com/Dissem/Jabit/compare/
|
||||
|
||||
Unfortunately we can't always answer right away, so we ask you to have
|
||||
some patience. Then we may suggest some changes or improvements or
|
||||
alternatives.
|
@ -29,18 +29,21 @@ Setup
|
||||
|
||||
Add Jabit as Gradle dependency:
|
||||
```Gradle
|
||||
compile 'ch.dissem.jabit:jabit-domain:0.2.0'
|
||||
compile 'ch.dissem.jabit:jabit-core:0.2.0'
|
||||
```
|
||||
Unless you want to implement your own, also add the following:
|
||||
```Gradle
|
||||
compile 'ch.dissem.jabit:jabit-networking:0.2.0'
|
||||
compile 'ch.dissem.jabit:jabit-repositories:0.2.0'
|
||||
compile 'ch.dissem.jabit:jabit-cryptography-bc:0.2.0'
|
||||
```
|
||||
And if you want to import from or export to the Wallet Import Format (used by PyBitmessage) you might also want to add:
|
||||
```Gradle
|
||||
compile 'ch.dissem.jabit:jabit-wif:0.2.0'
|
||||
```
|
||||
|
||||
For Android clients use `jabit-cryptography-sc` instead of `jabit-cryptography-bc`.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
@ -53,6 +56,7 @@ BitmessageContext ctx = new BitmessageContext.Builder()
|
||||
.messageRepo(new JdbcMessageRepository(jdbcConfig))
|
||||
.nodeRegistry(new MemoryNodeRegistry())
|
||||
.networkHandler(new NetworkNode())
|
||||
.cryptography(new BouncyCryptography())
|
||||
.build();
|
||||
```
|
||||
This creates a simple context using a H2 database that will be created in the user's home directory. Next you'll need to
|
||||
|
13
build.gradle
13
build.gradle
@ -5,7 +5,7 @@ subprojects {
|
||||
|
||||
sourceCompatibility = 1.7
|
||||
group = 'ch.dissem.jabit'
|
||||
version = '0.2.0'
|
||||
version = '1.0.0'
|
||||
|
||||
ext.isReleaseVersion = !version.endsWith("SNAPSHOT")
|
||||
|
||||
@ -13,6 +13,12 @@ subprojects {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
test {
|
||||
testLogging {
|
||||
exceptionFormat = 'full'
|
||||
}
|
||||
}
|
||||
|
||||
task javadocJar(type: Jar) {
|
||||
classifier = 'javadoc'
|
||||
from javadoc
|
||||
@ -37,11 +43,6 @@ subprojects {
|
||||
mavenDeployer {
|
||||
beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
|
||||
|
||||
if (!hasProperty('ossrhUsername')) {
|
||||
ext.ossrhUsername = 'dummy'
|
||||
ext.ossrhPassword = 'dummy'
|
||||
}
|
||||
|
||||
repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") {
|
||||
authentication(userName: ossrhUsername, password: ossrhPassword)
|
||||
}
|
||||
|
31
core/build.gradle
Normal file
31
core/build.gradle
Normal file
@ -0,0 +1,31 @@
|
||||
uploadArchives {
|
||||
repositories {
|
||||
mavenDeployer {
|
||||
pom.project {
|
||||
name 'Jabit Core'
|
||||
artifactId = 'jabit-core'
|
||||
description 'A Java implementation of the Bitmessage protocol. This is the core part. You\'ll either need the networking and repositories modules, too, or implement your own.'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
configurations {
|
||||
testArtifacts.extendsFrom testRuntime
|
||||
}
|
||||
|
||||
task testJar(type: Jar) {
|
||||
classifier = 'test'
|
||||
from sourceSets.test.output
|
||||
}
|
||||
|
||||
artifacts {
|
||||
testArtifacts testJar
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile 'org.slf4j:slf4j-api:1.7.12'
|
||||
testCompile 'junit:junit:4.11'
|
||||
testCompile 'org.mockito:mockito-core:1.10.19'
|
||||
testCompile project(':cryptography-bc')
|
||||
}
|
515
core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
Normal file
515
core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
Normal file
@ -0,0 +1,515 @@
|
||||
/*
|
||||
* 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)
|
||||
.labels(messages().getLabels(Label.Type.SENT))
|
||||
.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");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -69,9 +69,10 @@ class DefaultMessageListener implements NetworkHandler.MessageListener {
|
||||
}
|
||||
|
||||
protected void receive(ObjectMessage object, GetPubkey getPubkey) {
|
||||
BitmessageAddress identity = ctx.getAddressRepo().findIdentity(getPubkey.getRipeTag());
|
||||
BitmessageAddress identity = ctx.getAddressRepository().findIdentity(getPubkey.getRipeTag());
|
||||
if (identity != null && identity.getPrivateKey() != null) {
|
||||
LOG.debug("Got pubkey request for identity " + identity);
|
||||
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());
|
||||
}
|
||||
}
|
||||
@ -81,19 +82,26 @@ class DefaultMessageListener implements NetworkHandler.MessageListener {
|
||||
try {
|
||||
if (pubkey instanceof V4Pubkey) {
|
||||
V4Pubkey v4Pubkey = (V4Pubkey) pubkey;
|
||||
address = ctx.getAddressRepo().findContact(v4Pubkey.getTag());
|
||||
address = ctx.getAddressRepository().findContact(v4Pubkey.getTag());
|
||||
if (address != null) {
|
||||
v4Pubkey.decrypt(address.getPublicDecryptionKey());
|
||||
}
|
||||
} else {
|
||||
address = ctx.getAddressRepo().findContact(pubkey.getRipe());
|
||||
address = ctx.getAddressRepository().findContact(pubkey.getRipe());
|
||||
}
|
||||
if (address != null) {
|
||||
updatePubkey(address, pubkey);
|
||||
}
|
||||
} catch (DecryptionFailedException ignore) {
|
||||
}
|
||||
}
|
||||
|
||||
private void updatePubkey(BitmessageAddress address, Pubkey pubkey){
|
||||
address.setPubkey(pubkey);
|
||||
LOG.debug("Got pubkey for contact " + address);
|
||||
ctx.getAddressRepo().save(address);
|
||||
LOG.info("Got pubkey for contact " + address);
|
||||
ctx.getAddressRepository().save(address);
|
||||
List<Plaintext> messages = ctx.getMessageRepository().findMessages(Plaintext.Status.PUBKEY_REQUESTED, address);
|
||||
LOG.debug("Sending " + messages.size() + " messages for contact " + address);
|
||||
LOG.info("Sending " + messages.size() + " messages for contact " + address);
|
||||
for (Plaintext msg : messages) {
|
||||
msg.setStatus(DOING_PROOF_OF_WORK);
|
||||
ctx.getMessageRepository().save(msg);
|
||||
@ -101,20 +109,15 @@ class DefaultMessageListener implements NetworkHandler.MessageListener {
|
||||
msg.getFrom(),
|
||||
msg.getTo(),
|
||||
new Msg(msg),
|
||||
+2 * DAY,
|
||||
ctx.getNonceTrialsPerByte(msg.getTo()),
|
||||
ctx.getExtraBytes(msg.getTo())
|
||||
+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.getAddressRepo().getIdentities()) {
|
||||
for (BitmessageAddress identity : ctx.getAddressRepository().getIdentities()) {
|
||||
try {
|
||||
msg.decrypt(identity.getPrivateKey().getPrivateEncryptionKey());
|
||||
msg.getPlaintext().setTo(identity);
|
||||
@ -126,6 +129,7 @@ class DefaultMessageListener implements NetworkHandler.MessageListener {
|
||||
msg.getPlaintext().setInventoryVector(object.getInventoryVector());
|
||||
ctx.getMessageRepository().save(msg.getPlaintext());
|
||||
listener.receive(msg.getPlaintext());
|
||||
updatePubkey(msg.getPlaintext().getFrom(), msg.getPlaintext().getFrom().getPubkey());
|
||||
}
|
||||
break;
|
||||
} catch (DecryptionFailedException ignore) {
|
||||
@ -135,7 +139,7 @@ class DefaultMessageListener implements NetworkHandler.MessageListener {
|
||||
|
||||
protected void receive(ObjectMessage object, Broadcast broadcast) throws IOException {
|
||||
byte[] tag = broadcast instanceof V5Broadcast ? ((V5Broadcast) broadcast).getTag() : null;
|
||||
for (BitmessageAddress subscription : ctx.getAddressRepo().getSubscriptions(broadcast.getVersion())) {
|
||||
for (BitmessageAddress subscription : ctx.getAddressRepository().getSubscriptions(broadcast.getVersion())) {
|
||||
if (tag != null && !Arrays.equals(tag, subscription.getTag())) {
|
||||
continue;
|
||||
}
|
||||
@ -149,6 +153,7 @@ class DefaultMessageListener implements NetworkHandler.MessageListener {
|
||||
broadcast.getPlaintext().setInventoryVector(object.getInventoryVector());
|
||||
ctx.getMessageRepository().save(broadcast.getPlaintext());
|
||||
listener.receive(broadcast.getPlaintext());
|
||||
updatePubkey(broadcast.getPlaintext().getFrom(), broadcast.getPlaintext().getFrom().getPubkey());
|
||||
}
|
||||
} catch (DecryptionFailedException ignore) {
|
||||
}
|
@ -16,12 +16,14 @@
|
||||
|
||||
package ch.dissem.bitmessage;
|
||||
|
||||
import ch.dissem.bitmessage.entity.*;
|
||||
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.Security;
|
||||
import ch.dissem.bitmessage.utils.Singleton;
|
||||
import ch.dissem.bitmessage.utils.UnixTime;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -29,8 +31,6 @@ import org.slf4j.LoggerFactory;
|
||||
import java.io.IOException;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import static ch.dissem.bitmessage.utils.UnixTime.DAY;
|
||||
|
||||
/**
|
||||
* 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
|
||||
@ -42,32 +42,64 @@ import static ch.dissem.bitmessage.utils.UnixTime.DAY;
|
||||
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 long networkNonceTrialsPerByte = 1000;
|
||||
private long networkExtraBytes = 1000;
|
||||
private long clientNonce;
|
||||
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 = Security.randomNonce();
|
||||
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;
|
||||
|
||||
port = builder.port;
|
||||
streams.add(1L); // FIXME
|
||||
Singleton.initialize(cryptography);
|
||||
|
||||
init(inventory, nodeRegistry, networkHandler, addressRepository, messageRepository, proofOfWorkEngine);
|
||||
// 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) {
|
||||
@ -78,6 +110,10 @@ public class InternalContext {
|
||||
}
|
||||
}
|
||||
|
||||
public Cryptography getCryptography() {
|
||||
return cryptography;
|
||||
}
|
||||
|
||||
public Inventory getInventory() {
|
||||
return inventory;
|
||||
}
|
||||
@ -90,7 +126,7 @@ public class InternalContext {
|
||||
return networkHandler;
|
||||
}
|
||||
|
||||
public AddressRepository getAddressRepo() {
|
||||
public AddressRepository getAddressRepository() {
|
||||
return addressRepository;
|
||||
}
|
||||
|
||||
@ -98,10 +134,18 @@ public class InternalContext {
|
||||
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;
|
||||
@ -111,14 +155,6 @@ public class InternalContext {
|
||||
return result;
|
||||
}
|
||||
|
||||
public void addStream(long stream) {
|
||||
streams.add(stream);
|
||||
}
|
||||
|
||||
public void removeStream(long stream) {
|
||||
streams.remove(stream);
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
@ -127,26 +163,17 @@ public class InternalContext {
|
||||
return networkNonceTrialsPerByte;
|
||||
}
|
||||
|
||||
public long getNonceTrialsPerByte(BitmessageAddress address) {
|
||||
long nonceTrialsPerByte = address.getPubkey().getNonceTrialsPerByte();
|
||||
return networkNonceTrialsPerByte > nonceTrialsPerByte ? networkNonceTrialsPerByte : nonceTrialsPerByte;
|
||||
}
|
||||
|
||||
public long getNetworkExtraBytes() {
|
||||
return networkExtraBytes;
|
||||
}
|
||||
|
||||
public long getExtraBytes(BitmessageAddress address) {
|
||||
long extraBytes = address.getPubkey().getExtraBytes();
|
||||
return networkExtraBytes > extraBytes ? networkExtraBytes : extraBytes;
|
||||
}
|
||||
|
||||
public void send(BitmessageAddress from, BitmessageAddress to, ObjectPayload payload, long timeToLive, long nonceTrialsPerByte, long extraBytes) {
|
||||
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);
|
||||
ObjectMessage object = new ObjectMessage.Builder()
|
||||
final ObjectMessage object = new ObjectMessage.Builder()
|
||||
.stream(to.getStream())
|
||||
.expiresTime(expires)
|
||||
.payload(payload)
|
||||
@ -159,64 +186,58 @@ public class InternalContext {
|
||||
} else if (payload instanceof Encrypted) {
|
||||
object.encrypt(to.getPubkey());
|
||||
}
|
||||
Security.doProofOfWork(object, proofOfWorkEngine, nonceTrialsPerByte, extraBytes);
|
||||
if (payload instanceof PlaintextHolder) {
|
||||
Plaintext plaintext = ((PlaintextHolder) payload).getPlaintext();
|
||||
plaintext.setInventoryVector(object.getInventoryVector());
|
||||
messageRepository.save(plaintext);
|
||||
}
|
||||
inventory.storeObject(object);
|
||||
networkHandler.offer(object.getInventoryVector());
|
||||
messageCallback.proofOfWorkStarted(payload);
|
||||
proofOfWorkService.doProofOfWork(to, object);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void sendPubkey(BitmessageAddress identity, long targetStream) {
|
||||
public void sendPubkey(final BitmessageAddress identity, final long targetStream) {
|
||||
try {
|
||||
long expires = UnixTime.now(+28 * DAY);
|
||||
long expires = UnixTime.now(pubkeyTTL);
|
||||
LOG.info("Expires at " + expires);
|
||||
ObjectMessage response = new ObjectMessage.Builder()
|
||||
final ObjectMessage response = new ObjectMessage.Builder()
|
||||
.stream(targetStream)
|
||||
.expiresTime(expires)
|
||||
.payload(identity.getPubkey())
|
||||
.build();
|
||||
response.sign(identity.getPrivateKey());
|
||||
response.encrypt(Security.createPublicKey(identity.getPublicDecryptionKey()).getEncoded(false));
|
||||
Security.doProofOfWork(response, proofOfWorkEngine, networkNonceTrialsPerByte, networkExtraBytes);
|
||||
if (response.isSigned()) {
|
||||
response.sign(identity.getPrivateKey());
|
||||
}
|
||||
if (response instanceof Encrypted) {
|
||||
response.encrypt(Security.createPublicKey(identity.getPublicDecryptionKey()).getEncoded(false));
|
||||
}
|
||||
inventory.storeObject(response);
|
||||
networkHandler.offer(response.getInventoryVector());
|
||||
// TODO: save that the pubkey was just sent, and on which stream!
|
||||
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(BitmessageAddress contact) {
|
||||
long expires = UnixTime.now(+2 * DAY);
|
||||
public void requestPubkey(final BitmessageAddress contact) {
|
||||
long expires = UnixTime.now(+pubkeyTTL);
|
||||
LOG.info("Expires at " + expires);
|
||||
ObjectMessage response = new ObjectMessage.Builder()
|
||||
final ObjectMessage response = new ObjectMessage.Builder()
|
||||
.stream(contact.getStream())
|
||||
.expiresTime(expires)
|
||||
.payload(new GetPubkey(contact))
|
||||
.build();
|
||||
Security.doProofOfWork(response, proofOfWorkEngine, networkNonceTrialsPerByte, networkExtraBytes);
|
||||
inventory.storeObject(response);
|
||||
networkHandler.offer(response.getInventoryVector());
|
||||
messageCallback.proofOfWorkStarted(response.getPayload());
|
||||
proofOfWorkService.doProofOfWork(response);
|
||||
}
|
||||
|
||||
public long getClientNonce() {
|
||||
return clientNonce;
|
||||
}
|
||||
|
||||
public void setClientNonce(long clientNonce) {
|
||||
this.clientNonce = clientNonce;
|
||||
public long getConnectionTTL() {
|
||||
return connectionTTL;
|
||||
}
|
||||
|
||||
public int getConnectionLimit() {
|
||||
return connectionLimit;
|
||||
}
|
||||
|
||||
public CustomCommandHandler getCustomCommandHandler() {
|
||||
return customCommandHandler;
|
||||
}
|
||||
|
||||
public interface ContextHolder {
|
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();
|
||||
}
|
||||
}
|
@ -19,22 +19,27 @@ 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.*;
|
||||
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 {
|
||||
public class BitmessageAddress implements Serializable {
|
||||
private final long version;
|
||||
private final long stream;
|
||||
private final byte[] ripe;
|
||||
@ -62,19 +67,19 @@ public class BitmessageAddress {
|
||||
Encode.varInt(version, os);
|
||||
Encode.varInt(stream, os);
|
||||
if (version < 4) {
|
||||
byte[] checksum = Security.sha512(os.toByteArray(), ripe);
|
||||
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);
|
||||
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());
|
||||
byte[] checksum = security().doubleSha512(os.toByteArray());
|
||||
os.write(checksum, 0, 4);
|
||||
this.address = "BM-" + Base58.encode(os.toByteArray());
|
||||
} catch (IOException e) {
|
||||
@ -82,7 +87,7 @@ public class BitmessageAddress {
|
||||
}
|
||||
}
|
||||
|
||||
BitmessageAddress(Pubkey publicKey) {
|
||||
public BitmessageAddress(Pubkey publicKey) {
|
||||
this(publicKey.getVersion(), publicKey.getStream(), publicKey.getRipe());
|
||||
this.pubkey = publicKey;
|
||||
}
|
||||
@ -103,18 +108,18 @@ public class BitmessageAddress {
|
||||
this.ripe = Bytes.expand(bytes(in, bytes.length - counter.length() - 4), 20);
|
||||
|
||||
// test checksum
|
||||
byte[] checksum = Security.doubleSha512(bytes, bytes.length - 4);
|
||||
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);
|
||||
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);
|
||||
checksum = security().doubleSha512(Arrays.copyOfRange(bytes, 0, counter.length()), ripe);
|
||||
this.tag = Arrays.copyOfRange(checksum, 32, 64);
|
||||
this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32);
|
||||
}
|
||||
@ -129,7 +134,7 @@ public class BitmessageAddress {
|
||||
Encode.varInt(version, out);
|
||||
Encode.varInt(stream, out);
|
||||
out.write(ripe);
|
||||
return Arrays.copyOfRange(Security.doubleSha512(out.toByteArray()), 32, 64);
|
||||
return Arrays.copyOfRange(security().doubleSha512(out.toByteArray()), 32, 64);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -44,10 +44,10 @@ public class GetData implements MessagePayload {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(OutputStream stream) throws IOException {
|
||||
Encode.varInt(inventory.size(), stream);
|
||||
public void write(OutputStream out) throws IOException {
|
||||
Encode.varInt(inventory.size(), out);
|
||||
for (InventoryVector iv : inventory) {
|
||||
iv.write(stream);
|
||||
iv.write(out);
|
||||
}
|
||||
}
|
||||
|
@ -44,10 +44,10 @@ public class Inv implements MessagePayload {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(OutputStream stream) throws IOException {
|
||||
Encode.varInt(inventory.size(), stream);
|
||||
public void write(OutputStream out) throws IOException {
|
||||
Encode.varInt(inventory.size(), out);
|
||||
for (InventoryVector iv : inventory) {
|
||||
iv.write(stream);
|
||||
iv.write(out);
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,6 @@ public interface MessagePayload extends Streamable {
|
||||
Command getCommand();
|
||||
|
||||
enum Command {
|
||||
VERSION, VERACK, ADDR, INV, GETDATA, OBJECT
|
||||
VERSION, VERACK, ADDR, INV, GETDATA, OBJECT, CUSTOM
|
||||
}
|
||||
}
|
@ -26,7 +26,7 @@ import java.security.GeneralSecurityException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
|
||||
import static ch.dissem.bitmessage.utils.Security.sha512;
|
||||
import static ch.dissem.bitmessage.utils.Singleton.security;
|
||||
|
||||
/**
|
||||
* A network message is exchanged between two nodes.
|
||||
@ -48,7 +48,7 @@ public class NetworkMessage implements Streamable {
|
||||
* First 4 bytes of sha512(payload)
|
||||
*/
|
||||
private byte[] getChecksum(byte[] bytes) throws NoSuchProviderException, NoSuchAlgorithmException {
|
||||
byte[] d = sha512(bytes);
|
||||
byte[] d = security().sha512(bytes);
|
||||
return new byte[]{d[0], d[1], d[2], d[3]};
|
||||
}
|
||||
|
@ -24,12 +24,13 @@ 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 ch.dissem.bitmessage.utils.Security;
|
||||
|
||||
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.
|
||||
*/
|
||||
@ -89,7 +90,9 @@ public class ObjectMessage implements MessagePayload {
|
||||
}
|
||||
|
||||
public InventoryVector getInventoryVector() {
|
||||
return new InventoryVector(Bytes.truncate(Security.doubleSha512(nonce, getPayloadBytesWithoutNonce()), 32));
|
||||
return new InventoryVector(
|
||||
Bytes.truncate(security().doubleSha512(nonce, getPayloadBytesWithoutNonce()), 32)
|
||||
);
|
||||
}
|
||||
|
||||
private boolean isEncrypted() {
|
||||
@ -113,7 +116,7 @@ public class ObjectMessage implements MessagePayload {
|
||||
|
||||
public void sign(PrivateKey key) {
|
||||
if (payload.isSigned()) {
|
||||
payload.setSignature(Security.getSignature(getBytesToSign(), key));
|
||||
payload.setSignature(security().getSignature(getBytesToSign(), key));
|
||||
}
|
||||
}
|
||||
|
||||
@ -147,12 +150,16 @@ public class ObjectMessage implements MessagePayload {
|
||||
|
||||
public boolean isSignatureValid(Pubkey pubkey) throws IOException {
|
||||
if (isEncrypted()) throw new IllegalStateException("Payload must be decrypted first");
|
||||
return Security.isSignatureValid(getBytesToSign(), payload.getSignature(), pubkey);
|
||||
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());
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ 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.*;
|
||||
@ -43,6 +44,7 @@ public class Plaintext implements Streamable {
|
||||
private Long received;
|
||||
|
||||
private Set<Label> labels;
|
||||
private byte[] initialHash;
|
||||
|
||||
private Plaintext(Builder builder) {
|
||||
id = builder.id;
|
||||
@ -63,6 +65,7 @@ public class Plaintext implements Streamable {
|
||||
public static Plaintext read(Type type, InputStream in) throws IOException {
|
||||
return readWithoutSignature(type, in)
|
||||
.signature(Decode.varBytes(in))
|
||||
.received(UnixTime.now())
|
||||
.build();
|
||||
}
|
||||
|
||||
@ -131,6 +134,15 @@ public class Plaintext implements Streamable {
|
||||
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);
|
||||
@ -249,6 +261,14 @@ public class Plaintext implements Streamable {
|
||||
}
|
||||
}
|
||||
|
||||
public void setInitialHash(byte[] initialHash) {
|
||||
this.initialHash = initialHash;
|
||||
}
|
||||
|
||||
public byte[] getInitialHash() {
|
||||
return initialHash;
|
||||
}
|
||||
|
||||
public enum Encoding {
|
||||
IGNORE(0), TRIVIAL(1), SIMPLE(2);
|
||||
|
@ -18,10 +18,11 @@ 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 {
|
||||
public interface Streamable extends Serializable {
|
||||
void write(OutputStream stream) throws IOException;
|
||||
}
|
@ -21,11 +21,11 @@ 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 ch.dissem.bitmessage.utils.Security;
|
||||
|
||||
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.
|
||||
@ -78,7 +78,7 @@ public abstract class Broadcast extends ObjectPayload implements Encrypted, Plai
|
||||
}
|
||||
|
||||
public void encrypt() throws IOException {
|
||||
encrypt(Security.createPublicKey(plaintext.getFrom().getPublicDecryptionKey()).getEncoded(false));
|
||||
encrypt(security().createPublicKey(plaintext.getFrom().getPublicDecryptionKey()));
|
||||
}
|
||||
|
||||
@Override
|
@ -17,28 +17,16 @@
|
||||
package ch.dissem.bitmessage.entity.payload;
|
||||
|
||||
import ch.dissem.bitmessage.entity.Streamable;
|
||||
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
|
||||
import ch.dissem.bitmessage.exception.DecryptionFailedException;
|
||||
import ch.dissem.bitmessage.utils.*;
|
||||
import org.bouncycastle.crypto.BufferedBlockCipher;
|
||||
import org.bouncycastle.crypto.CipherParameters;
|
||||
import org.bouncycastle.crypto.InvalidCipherTextException;
|
||||
import org.bouncycastle.crypto.engines.AESEngine;
|
||||
import org.bouncycastle.crypto.modes.CBCBlockCipher;
|
||||
import org.bouncycastle.crypto.paddings.PKCS7Padding;
|
||||
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
|
||||
import org.bouncycastle.crypto.params.KeyParameter;
|
||||
import org.bouncycastle.crypto.params.ParametersWithIV;
|
||||
import org.bouncycastle.math.ec.ECFieldElement;
|
||||
import org.bouncycastle.math.ec.ECPoint;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.*;
|
||||
import java.math.BigInteger;
|
||||
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 {
|
||||
@ -46,35 +34,38 @@ public class CryptoBox implements Streamable {
|
||||
|
||||
private final byte[] initializationVector;
|
||||
private final int curveType;
|
||||
private final ECPoint R;
|
||||
private final byte[] R;
|
||||
private final byte[] mac;
|
||||
private byte[] encrypted;
|
||||
|
||||
public CryptoBox(Streamable data, byte[] encryptionKey) throws IOException {
|
||||
this(data, Security.keyToPoint(encryptionKey));
|
||||
private long addressVersion;
|
||||
|
||||
|
||||
public CryptoBox(Streamable data, byte[] K) throws IOException {
|
||||
this(Encode.bytes(data), K);
|
||||
}
|
||||
|
||||
public CryptoBox(Streamable data, ECPoint K) throws IOException {
|
||||
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);
|
||||
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);
|
||||
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.
|
||||
ECPoint P = K.multiply(Security.keyToBigInt(r)).normalize();
|
||||
byte[] X = P.getXCoord().getEncoded();
|
||||
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);
|
||||
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 = crypt(true, Encode.bytes(data), key_e);
|
||||
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);
|
||||
|
||||
@ -84,7 +75,7 @@ public class CryptoBox implements Streamable {
|
||||
private CryptoBox(Builder builder) {
|
||||
initializationVector = builder.initializationVector;
|
||||
curveType = builder.curveType;
|
||||
R = Security.createPoint(builder.xComponent, builder.yComponent);
|
||||
R = security().createPoint(builder.xComponent, builder.yComponent);
|
||||
encrypted = builder.encrypted;
|
||||
mac = builder.mac;
|
||||
}
|
||||
@ -102,18 +93,17 @@ public class CryptoBox implements Streamable {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param privateKey a private key, typically should be 32 bytes long
|
||||
* @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[] privateKey) throws DecryptionFailedException {
|
||||
public InputStream decrypt(byte[] k) throws DecryptionFailedException {
|
||||
// 1. The private key used to decrypt is called k.
|
||||
BigInteger k = Security.keyToBigInt(privateKey);
|
||||
// 2. Do an EC point multiply with private key k and public key R. This gives you public key P.
|
||||
ECPoint P = R.multiply(k).normalize();
|
||||
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(P.getXCoord().getEncoded());
|
||||
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);
|
||||
@ -126,49 +116,28 @@ public class CryptoBox implements Streamable {
|
||||
|
||||
// 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(crypt(false, encrypted, key_e));
|
||||
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());
|
||||
return security().mac(key_m, macData.toByteArray());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] crypt(boolean encrypt, byte[] data, byte[] key_e) {
|
||||
BufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESEngine()), new PKCS7Padding());
|
||||
|
||||
CipherParameters params = new ParametersWithIV(new KeyParameter(key_e), initializationVector);
|
||||
|
||||
cipher.init(encrypt, params);
|
||||
|
||||
byte[] buffer = new byte[cipher.getOutputSize(data.length)];
|
||||
int length = cipher.processBytes(data, 0, data.length, buffer, 0);
|
||||
try {
|
||||
length += cipher.doFinal(buffer, length);
|
||||
} catch (InvalidCipherTextException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
if (length < buffer.length) {
|
||||
return Arrays.copyOfRange(buffer, 0, length);
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
private void writeWithoutMAC(OutputStream out) throws IOException {
|
||||
out.write(initializationVector);
|
||||
Encode.int16(curveType, out);
|
||||
writeCoordinateComponent(out, R.getXCoord());
|
||||
writeCoordinateComponent(out, R.getYCoord());
|
||||
writeCoordinateComponent(out, Points.getX(R));
|
||||
writeCoordinateComponent(out, Points.getY(R));
|
||||
out.write(encrypted);
|
||||
}
|
||||
|
||||
private void writeCoordinateComponent(OutputStream out, ECFieldElement coord) throws IOException {
|
||||
byte[] x = coord.getEncoded();
|
||||
private void writeCoordinateComponent(OutputStream out, byte[] x) throws IOException {
|
||||
int offset = Bytes.numberOfLeadingZeros(x);
|
||||
int length = x.length - offset;
|
||||
Encode.int16(length, out);
|
||||
@ -195,7 +164,7 @@ public class CryptoBox implements Streamable {
|
||||
}
|
||||
|
||||
public Builder curveType(int curveType) {
|
||||
if (curveType != 0x2CA) LOG.debug("Unexpected curve type " + curveType);
|
||||
if (curveType != 0x2CA) LOG.trace("Unexpected curve type " + curveType);
|
||||
this.curveType = curveType;
|
||||
return this;
|
||||
}
|
@ -21,6 +21,7 @@ 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.
|
@ -20,8 +20,7 @@ import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import static ch.dissem.bitmessage.utils.Security.ripemd160;
|
||||
import static ch.dissem.bitmessage.utils.Security.sha512;
|
||||
import static ch.dissem.bitmessage.utils.Singleton.security;
|
||||
|
||||
/**
|
||||
* Public keys for signing and encryption, the answer to a 'getpubkey' request.
|
||||
@ -34,7 +33,7 @@ public abstract class Pubkey extends ObjectPayload {
|
||||
}
|
||||
|
||||
public static byte[] getRipe(byte[] publicSigningKey, byte[] publicEncryptionKey) {
|
||||
return ripemd160(sha512(publicSigningKey, publicEncryptionKey));
|
||||
return security().ripemd160(security().sha512(publicSigningKey, publicEncryptionKey));
|
||||
}
|
||||
|
||||
public abstract byte[] getSigningKey();
|
||||
@ -44,7 +43,7 @@ public abstract class Pubkey extends ObjectPayload {
|
||||
public abstract int getBehaviorBitfield();
|
||||
|
||||
public byte[] getRipe() {
|
||||
return ripemd160(sha512(getSigningKey(), getEncryptionKey()));
|
||||
return security().ripemd160(security().sha512(getSigningKey(), getEncryptionKey()));
|
||||
}
|
||||
|
||||
public long getNonceTrialsPerByte() {
|
@ -21,9 +21,10 @@ 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 {
|
||||
public class InventoryVector implements Streamable, Serializable {
|
||||
/**
|
||||
* Hash of the object
|
||||
*/
|
||||
@ -37,7 +38,6 @@ public class InventoryVector implements Streamable {
|
||||
InventoryVector that = (InventoryVector) o;
|
||||
|
||||
return Arrays.equals(hash, that.hash);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
@ -16,9 +16,10 @@
|
||||
|
||||
package ch.dissem.bitmessage.entity.valueobject;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Objects;
|
||||
|
||||
public class Label {
|
||||
public class Label implements Serializable {
|
||||
private Object id;
|
||||
private String label;
|
||||
private Type type;
|
@ -18,18 +18,18 @@ package ch.dissem.bitmessage.entity.valueobject;
|
||||
|
||||
import ch.dissem.bitmessage.entity.Streamable;
|
||||
import ch.dissem.bitmessage.entity.payload.Pubkey;
|
||||
import ch.dissem.bitmessage.entity.payload.V3Pubkey;
|
||||
import ch.dissem.bitmessage.entity.payload.V4Pubkey;
|
||||
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 ch.dissem.bitmessage.utils.Security;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
import static ch.dissem.bitmessage.utils.Singleton.security;
|
||||
|
||||
/**
|
||||
* Created by chris on 18.04.15.
|
||||
* 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;
|
||||
@ -45,15 +45,15 @@ public class PrivateKey implements Streamable {
|
||||
byte[] pubEK;
|
||||
byte[] ripe;
|
||||
do {
|
||||
privSK = Security.randomBytes(PRIVATE_KEY_SIZE);
|
||||
privEK = Security.randomBytes(PRIVATE_KEY_SIZE);
|
||||
pubSK = Security.createPublicKey(privSK).getEncoded(false);
|
||||
pubEK = Security.createPublicKey(privEK).getEncoded(false);
|
||||
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,
|
||||
this.pubkey = security().createPubkey(Pubkey.LATEST_VERSION, stream, privateSigningKey, privateEncryptionKey,
|
||||
nonceTrialsPerByte, extraBytes, features);
|
||||
}
|
||||
|
||||
@ -66,9 +66,9 @@ public class PrivateKey implements Streamable {
|
||||
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,
|
||||
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);
|
@ -23,7 +23,6 @@ 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 ch.dissem.bitmessage.utils.Security;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@ -32,6 +31,8 @@ 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}
|
||||
*/
|
||||
@ -115,8 +116,8 @@ public class Factory {
|
||||
BitmessageAddress temp = new BitmessageAddress(address);
|
||||
PrivateKey privateKey = new PrivateKey(privateSigningKey, privateEncryptionKey,
|
||||
createPubkey(temp.getVersion(), temp.getStream(),
|
||||
Security.createPublicKey(privateSigningKey).getEncoded(false),
|
||||
Security.createPublicKey(privateEncryptionKey).getEncoded(false),
|
||||
security().createPublicKey(privateSigningKey),
|
||||
security().createPublicKey(privateEncryptionKey),
|
||||
nonceTrialsPerByte, extraBytes, behaviourBitfield));
|
||||
BitmessageAddress result = new BitmessageAddress(privateKey);
|
||||
if (!result.getAddress().equals(address)) {
|
||||
@ -126,11 +127,17 @@ public class Factory {
|
||||
return result;
|
||||
}
|
||||
|
||||
public static BitmessageAddress generatePrivateAddress(boolean shorter, long stream, Pubkey.Feature... features) {
|
||||
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 {
|
||||
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) {
|
||||
@ -147,7 +154,7 @@ public class Factory {
|
||||
}
|
||||
}
|
||||
// fallback: just store the message - we don't really care what it is
|
||||
// LOG.info("Unexpected object type: " + objectType);
|
||||
LOG.trace("Unexpected object type: " + objectType);
|
||||
return GenericPayload.read(version, stream, streamNumber, length);
|
||||
}
|
||||
|
@ -24,7 +24,6 @@ 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 ch.dissem.bitmessage.utils.Security;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@ -33,6 +32,7 @@ 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}
|
||||
@ -44,6 +44,9 @@ class V3MessageFactory {
|
||||
findMagic(in);
|
||||
String command = getCommand(in);
|
||||
int length = (int) Decode.uint32(in);
|
||||
if (length > 1600003) {
|
||||
throw new NodeException("Payload of " + length + " bytes received, no more than 1600003 was expected.");
|
||||
}
|
||||
byte[] checksum = Decode.bytes(in, 4);
|
||||
|
||||
byte[] payloadBytes = Decode.bytes(in, length);
|
||||
@ -73,12 +76,18 @@ class V3MessageFactory {
|
||||
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);
|
||||
@ -92,7 +101,7 @@ class V3MessageFactory {
|
||||
try {
|
||||
ByteArrayInputStream dataStream = new ByteArrayInputStream(data);
|
||||
payload = Factory.getObjectPayload(objectType, version, stream, dataStream, data.length);
|
||||
} catch (IOException e) {
|
||||
} catch (Exception e) {
|
||||
LOG.trace("Could not parse object payload - using generic payload instead", e);
|
||||
payload = new GenericPayload(version, stream, data);
|
||||
}
|
||||
@ -174,7 +183,7 @@ class V3MessageFactory {
|
||||
}
|
||||
|
||||
private static boolean testChecksum(byte[] checksum, byte[] payload) {
|
||||
byte[] payloadChecksum = Security.sha512(payload);
|
||||
byte[] payloadChecksum = security().sha512(payload);
|
||||
for (int i = 0; i < checksum.length; i++) {
|
||||
if (checksum[i] != payloadChecksum[i]) {
|
||||
return false;
|
||||
@ -185,10 +194,10 @@ class V3MessageFactory {
|
||||
|
||||
private static String getCommand(InputStream stream) throws IOException {
|
||||
byte[] bytes = new byte[12];
|
||||
int end = -1;
|
||||
int end = bytes.length;
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
bytes[i] = (byte) stream.read();
|
||||
if (end == -1) {
|
||||
if (end == bytes.length) {
|
||||
if (bytes[i] == 0) end = i;
|
||||
} else {
|
||||
if (bytes[i] != 0) throw new IOException("'\\0' padding expected for command");
|
@ -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();
|
||||
}
|
||||
}
|
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);
|
||||
}
|
@ -16,25 +16,12 @@
|
||||
|
||||
package ch.dissem.bitmessage.ports;
|
||||
|
||||
import ch.dissem.bitmessage.entity.ObjectMessage;
|
||||
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
|
||||
import ch.dissem.bitmessage.utils.Property;
|
||||
|
||||
import java.io.IOException;
|
||||
import ch.dissem.bitmessage.entity.CustomMessage;
|
||||
import ch.dissem.bitmessage.entity.MessagePayload;
|
||||
|
||||
/**
|
||||
* Handles incoming messages
|
||||
* @author Christian Basler
|
||||
*/
|
||||
public interface NetworkHandler {
|
||||
void start(MessageListener listener);
|
||||
|
||||
void stop();
|
||||
|
||||
void offer(InventoryVector iv);
|
||||
|
||||
Property getNetworkStatus();
|
||||
|
||||
interface MessageListener {
|
||||
void receive(ObjectMessage object) throws IOException;
|
||||
}
|
||||
public interface CustomCommandHandler {
|
||||
MessagePayload handle(CustomMessage request);
|
||||
}
|
@ -26,15 +26,32 @@ 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();
|
||||
}
|
@ -14,10 +14,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.repository;
|
||||
package ch.dissem.bitmessage.ports;
|
||||
|
||||
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress;
|
||||
import ch.dissem.bitmessage.ports.NodeRegistry;
|
||||
import ch.dissem.bitmessage.utils.UnixTime;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -35,10 +34,10 @@ 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 HashMap<>();
|
||||
private final Map<Long, Set<NetworkAddress>> stableNodes = new ConcurrentHashMap<>();
|
||||
private final Map<Long, Set<NetworkAddress>> knownNodes = new ConcurrentHashMap<>();
|
||||
|
||||
public MemoryNodeRegistry() {
|
||||
private void loadStableNodes() {
|
||||
try (InputStream in = getClass().getClassLoader().getResourceAsStream("nodes.txt")) {
|
||||
Scanner scanner = new Scanner(in);
|
||||
long stream = 0;
|
||||
@ -56,14 +55,21 @@ public class MemoryNodeRegistry implements NodeRegistry {
|
||||
stableNodes.put(stream, streamSet);
|
||||
} else if (streamSet != null) {
|
||||
int portIndex = line.lastIndexOf(':');
|
||||
InetAddress inetAddress = InetAddress.getByName(line.substring(0, portIndex));
|
||||
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);
|
||||
}
|
||||
@ -82,9 +88,16 @@ public class MemoryNodeRegistry implements NodeRegistry {
|
||||
known.remove(node);
|
||||
}
|
||||
}
|
||||
} else if (stableNodes.containsKey(stream)) {
|
||||
} 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(stableNodes.get(stream)));
|
||||
result.add(selectRandom(nodes));
|
||||
}
|
||||
}
|
||||
}
|
||||
return selectRandom(limit, result);
|
@ -28,12 +28,18 @@ public interface MessageRepository {
|
||||
|
||||
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);
|
@ -24,6 +24,7 @@ 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;
|
||||
|
||||
@ -31,46 +32,53 @@ import static ch.dissem.bitmessage.utils.Bytes.inc;
|
||||
* A POW engine using all available CPU cores.
|
||||
*/
|
||||
public class MultiThreadedPOWEngine implements ProofOfWorkEngine {
|
||||
private static Logger LOG = LoggerFactory.getLogger(MultiThreadedPOWEngine.class);
|
||||
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 byte[] calculateNonce(byte[] initialHash, byte[] target) {
|
||||
int cores = Runtime.getRuntime().availableProcessors();
|
||||
if (cores > 255) cores = 255;
|
||||
LOG.info("Doing POW using " + cores + " cores");
|
||||
long time = System.currentTimeMillis();
|
||||
List<Worker> workers = new ArrayList<>(cores);
|
||||
for (int i = 0; i < cores; i++) {
|
||||
Worker w = new Worker(workers, (byte) cores, i, initialHash, target);
|
||||
workers.add(w);
|
||||
}
|
||||
for (Worker w : workers) {
|
||||
w.start();
|
||||
}
|
||||
for (Worker w : workers) {
|
||||
public void calculateNonce(byte[] initialHash, byte[] target, Callback callback) {
|
||||
try {
|
||||
w.join();
|
||||
semaphore.acquire();
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
if (w.isSuccessful()) {
|
||||
LOG.info("Nonce calculated in " + ((System.currentTimeMillis() - time) / 1000) + " seconds");
|
||||
return w.getNonce();
|
||||
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();
|
||||
}
|
||||
throw new RuntimeException("All workers ended without yielding a nonce - something is seriously broken!");
|
||||
}
|
||||
|
||||
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];
|
||||
private boolean successful = false;
|
||||
|
||||
public Worker(List<Worker> workers, byte numberOfCores, int core, byte[] initialHash, byte[] target) {
|
||||
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;
|
||||
@ -84,14 +92,6 @@ public class MultiThreadedPOWEngine implements ProofOfWorkEngine {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isSuccessful() {
|
||||
return successful;
|
||||
}
|
||||
|
||||
public byte[] getNonce() {
|
||||
return nonce;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
do {
|
||||
@ -99,13 +99,43 @@ public class MultiThreadedPOWEngine implements ProofOfWorkEngine {
|
||||
mda.update(nonce);
|
||||
mda.update(initialHash);
|
||||
if (!Bytes.lt(target, mda.digest(mda.digest()), 8)) {
|
||||
successful = true;
|
||||
synchronized (callback) {
|
||||
if (!Thread.interrupted()) {
|
||||
for (Worker w : workers) {
|
||||
w.interrupt();
|
||||
}
|
||||
// Clear interrupted flag for callback
|
||||
Thread.interrupted();
|
||||
callback.onNonceCalculated(initialHash, nonce);
|
||||
}
|
||||
}
|
||||
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) {
|
||||
// Prevents the callback from being called twice if two nonces are found simultaneously
|
||||
synchronized (this) {
|
||||
if (waiting) {
|
||||
semaphore.release();
|
||||
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;
|
||||
}
|
||||
}
|
@ -26,7 +26,15 @@ public interface ProofOfWorkEngine {
|
||||
*
|
||||
* @param initialHash the SHA-512 hash of the object to send, sans nonce
|
||||
* @param target the target, representing an unsigned long
|
||||
* @return 8 bytes nonce
|
||||
* @param callback called with the calculated nonce as argument. The ProofOfWorkEngine implementation must make
|
||||
* sure this is only called once.
|
||||
*/
|
||||
byte[] calculateNonce(byte[] initialHash, byte[] target);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@ -23,11 +23,15 @@ import java.security.MessageDigest;
|
||||
import static ch.dissem.bitmessage.utils.Bytes.inc;
|
||||
|
||||
/**
|
||||
* Created by chris on 14.04.15.
|
||||
* You should really use the MultiThreadedPOWEngine, but this one might help you grok the other one.
|
||||
* <p>
|
||||
* <strong>Warning:</strong> implementations probably depend on POW being asynchronous, that's
|
||||
* another reason not to use this one.
|
||||
* </p>
|
||||
*/
|
||||
public class SimplePOWEngine implements ProofOfWorkEngine {
|
||||
@Override
|
||||
public byte[] calculateNonce(byte[] initialHash, byte[] target) {
|
||||
public void calculateNonce(byte[] initialHash, byte[] target, Callback callback) {
|
||||
byte[] nonce = new byte[8];
|
||||
MessageDigest mda;
|
||||
try {
|
||||
@ -40,6 +44,6 @@ public class SimplePOWEngine implements ProofOfWorkEngine {
|
||||
mda.update(nonce);
|
||||
mda.update(initialHash);
|
||||
} while (Bytes.lt(target, mda.digest(mda.digest()), 8));
|
||||
return nonce;
|
||||
callback.onNonceCalculated(initialHash, nonce);
|
||||
}
|
||||
}
|
@ -16,12 +16,6 @@
|
||||
|
||||
package ch.dissem.bitmessage.utils;
|
||||
|
||||
import ch.dissem.bitmessage.entity.Streamable;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* 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
|
||||
@ -91,6 +85,8 @@ public class Bytes {
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
@ -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;
|
||||
}
|
||||
}
|
@ -130,9 +130,13 @@ public class Decode {
|
||||
}
|
||||
|
||||
public static String varString(InputStream stream) throws IOException {
|
||||
int length = (int) varInt(stream);
|
||||
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), "utf-8");
|
||||
return new String(bytes(stream, length, counter), "utf-8");
|
||||
}
|
||||
}
|
@ -103,20 +103,30 @@ public class Encode {
|
||||
inc(counter, 8);
|
||||
}
|
||||
|
||||
public static void varString(String value, OutputStream stream) throws IOException {
|
||||
public static void varString(String value, OutputStream out) throws IOException {
|
||||
byte[] bytes = value.getBytes("utf-8");
|
||||
// FIXME: technically, it says the length in characters, but I think this one might be correct
|
||||
// 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, stream);
|
||||
stream.write(bytes);
|
||||
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();
|
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);
|
||||
}
|
||||
}
|
@ -16,8 +16,16 @@
|
||||
|
||||
package ch.dissem.bitmessage.utils;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Created by chris on 14.06.15.
|
||||
* 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;
|
||||
@ -30,6 +38,36 @@ public class Property {
|
||||
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("");
|
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;
|
||||
}
|
||||
}
|
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
|
@ -21,6 +21,7 @@ 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;
|
||||
|
||||
@ -29,7 +30,7 @@ import java.io.IOException;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class DecryptionTest {
|
||||
public class DecryptionTest extends TestBase {
|
||||
@Test
|
||||
public void ensureV4BroadcastIsDecryptedCorrectly() throws IOException, DecryptionFailedException {
|
||||
BitmessageAddress address = new BitmessageAddress("BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ");
|
@ -24,20 +24,20 @@ 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.Security;
|
||||
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;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class EncryptionTest {
|
||||
public class EncryptionTest extends TestBase {
|
||||
@Test
|
||||
public void ensureDecryptedDataIsSameAsBeforeEncryption() throws IOException, DecryptionFailedException {
|
||||
GenericPayload before = new GenericPayload(0, 1, Security.randomBytes(100));
|
||||
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());
|
@ -25,6 +25,7 @@ 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;
|
||||
|
||||
@ -33,7 +34,7 @@ import java.util.Date;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class SignatureTest {
|
||||
public class SignatureTest extends TestBase {
|
||||
@Test
|
||||
public void ensureValidationWorks() throws IOException {
|
||||
ObjectMessage object = TestUtils.loadObjectMessage(3, "V3Pubkey.payload");
|
@ -27,6 +27,7 @@ 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 {
|
||||
@ -102,7 +103,7 @@ public class BitmessageAddressTest {
|
||||
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)));
|
||||
security().createPubkey(3, 1, privsigningkey, privencryptionkey, 320, 14000)));
|
||||
assertEquals(address_string, address.getAddress());
|
||||
}
|
||||
|
||||
@ -119,7 +120,7 @@ public class BitmessageAddressTest {
|
||||
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);
|
||||
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);
|
||||
}
|
||||
@ -132,7 +133,7 @@ public class BitmessageAddressTest {
|
||||
byte[] privsigningkey = getSecret("5KMWqfCyJZGFgW6QrnPJ6L9Gatz25B51y7ErgqNr1nXUVbtZbdU");
|
||||
byte[] privencryptionkey = getSecret("5JXXWEuhHQEPk414SzEZk1PHDRi8kCuZd895J7EnKeQSahJPxGz");
|
||||
BitmessageAddress address = new BitmessageAddress(new PrivateKey(privsigningkey, privencryptionkey,
|
||||
Security.createPubkey(4, 1, privsigningkey, privencryptionkey, 320, 14000)));
|
||||
security().createPubkey(4, 1, privsigningkey, privencryptionkey, 320, 14000)));
|
||||
assertEquals("BM-2cV5f9EpzaYARxtoruSpa6pDoucSf9ZNke", address.getAddress());
|
||||
}
|
||||
|
@ -17,21 +17,23 @@
|
||||
package ch.dissem.bitmessage.entity;
|
||||
|
||||
import ch.dissem.bitmessage.entity.payload.*;
|
||||
import ch.dissem.bitmessage.exception.DecryptionFailedException;
|
||||
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.TestBase;
|
||||
import ch.dissem.bitmessage.utils.TestUtils;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.*;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
||||
import static ch.dissem.bitmessage.entity.Plaintext.Type.MSG;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static ch.dissem.bitmessage.utils.Singleton.security;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class SerializationTest {
|
||||
public class SerializationTest extends TestBase {
|
||||
@Test
|
||||
public void ensureGetPubkeyIsDeserializedAndSerializedCorrectly() throws IOException {
|
||||
doTest("V2GetPubkey.payload", 2, GetPubkey.class);
|
||||
@ -75,7 +77,7 @@ public class SerializationTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ensurePlaintextIsSerializedAndDeserializedCorrectly() throws IOException, DecryptionFailedException {
|
||||
public void ensurePlaintextIsSerializedAndDeserializedCorrectly() throws Exception {
|
||||
Plaintext p1 = new Plaintext.Builder(MSG)
|
||||
.from(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"))
|
||||
.to(TestUtils.loadContact())
|
||||
@ -87,16 +89,57 @@ public class SerializationTest {
|
||||
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);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ensureNetworkMessageIsSerializedAndDeserializedCorrectly() throws Exception {
|
||||
ArrayList<InventoryVector> ivs = new ArrayList<>(50000);
|
||||
for (int i = 0; i < 50000; i++) {
|
||||
ivs.add(new InventoryVector(security().randomBytes(32)));
|
||||
}
|
||||
|
||||
Inv inv = new Inv.Builder().inventory(ivs).build();
|
||||
NetworkMessage before = new NetworkMessage(inv);
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
before.write(out);
|
||||
|
||||
NetworkMessage after = Factory.getNetworkMessage(3, new ByteArrayInputStream(out.toByteArray()));
|
||||
Inv invAfter = (Inv) after.getPayload();
|
||||
assertEquals(ivs, invAfter.getInventory());
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
}
|
@ -16,6 +16,7 @@
|
||||
|
||||
package ch.dissem.bitmessage.utils;
|
||||
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
@ -25,16 +26,13 @@ import java.util.Random;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* Created by chris on 10.04.15.
|
||||
*/
|
||||
public class BytesTest {
|
||||
public static final Random rnd = new Random();
|
||||
|
||||
@Test
|
||||
public void ensureExpandsCorrectly() {
|
||||
byte[] source = {1};
|
||||
byte[] expected = {0,1};
|
||||
byte[] expected = {0, 1};
|
||||
assertArrayEquals(expected, Bytes.expand(source, 2));
|
||||
}
|
||||
|
||||
@ -56,6 +54,24 @@ public class BytesTest {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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++) {
|
@ -22,9 +22,6 @@ import java.io.*;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* Created by chris on 20.03.15.
|
||||
*/
|
||||
public class DecodeTest {
|
||||
@Test
|
||||
public void ensureDecodingWorks() throws Exception {
|
@ -23,9 +23,6 @@ import java.io.IOException;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* Created by chris on 13.03.15.
|
||||
*/
|
||||
public class EncodeTest {
|
||||
@Test
|
||||
public void testUint8() throws IOException {
|
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());
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user