Added some address generation and handling
This commit is contained in:
parent
096f5db1bb
commit
d3499c9627
@ -16,7 +16,7 @@
|
||||
|
||||
package ch.dissem.bitmessage;
|
||||
|
||||
import ch.dissem.bitmessage.ports.AddressRepository;
|
||||
import ch.dissem.bitmessage.ports.NodeRegistry;
|
||||
import ch.dissem.bitmessage.ports.Inventory;
|
||||
import ch.dissem.bitmessage.ports.NetworkHandler;
|
||||
|
||||
@ -32,7 +32,7 @@ public class Context {
|
||||
private static Context instance;
|
||||
|
||||
private Inventory inventory;
|
||||
private AddressRepository addressRepo;
|
||||
private NodeRegistry addressRepo;
|
||||
private NetworkHandler networkHandler;
|
||||
|
||||
private Collection<Long> streams = new TreeSet<>();
|
||||
@ -42,7 +42,7 @@ public class Context {
|
||||
private long networkNonceTrialsPerByte = 1000;
|
||||
private long networkExtraBytes = 1000;
|
||||
|
||||
private Context(Inventory inventory, AddressRepository addressRepo,
|
||||
private Context(Inventory inventory, NodeRegistry addressRepo,
|
||||
NetworkHandler networkHandler, int port) {
|
||||
this.inventory = inventory;
|
||||
this.addressRepo = addressRepo;
|
||||
@ -50,8 +50,8 @@ public class Context {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public static void init(Inventory inventory, AddressRepository addressRepository, NetworkHandler networkHandler, int port) {
|
||||
instance = new Context(inventory, addressRepository, networkHandler, port);
|
||||
public static void init(Inventory inventory, NodeRegistry nodeRegistry, NetworkHandler networkHandler, int port) {
|
||||
instance = new Context(inventory, nodeRegistry, networkHandler, port);
|
||||
}
|
||||
|
||||
public static Context getInstance() {
|
||||
@ -62,7 +62,7 @@ public class Context {
|
||||
return inventory;
|
||||
}
|
||||
|
||||
public AddressRepository getAddressRepository() {
|
||||
public NodeRegistry getAddressRepository() {
|
||||
return addressRepo;
|
||||
}
|
||||
|
||||
|
@ -17,55 +17,115 @@
|
||||
package ch.dissem.bitmessage.entity;
|
||||
|
||||
import ch.dissem.bitmessage.entity.payload.Pubkey;
|
||||
import ch.dissem.bitmessage.utils.Base58;
|
||||
import ch.dissem.bitmessage.utils.Encode;
|
||||
import ch.dissem.bitmessage.utils.Security;
|
||||
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
|
||||
import ch.dissem.bitmessage.utils.*;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import static ch.dissem.bitmessage.utils.Security.ripemd160;
|
||||
import static ch.dissem.bitmessage.utils.Security.sha512;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* 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 abstract class BitmessageAddress {
|
||||
public class BitmessageAddress {
|
||||
private long version;
|
||||
private long streamNumber;
|
||||
private long stream;
|
||||
private byte[] ripe;
|
||||
|
||||
private String address;
|
||||
|
||||
private PrivateKey privateKey;
|
||||
private Pubkey pubkey;
|
||||
|
||||
public BitmessageAddress(Pubkey pubkey) {
|
||||
this.pubkey = pubkey;
|
||||
private String alias;
|
||||
|
||||
public BitmessageAddress(PrivateKey privateKey) {
|
||||
this.privateKey = privateKey;
|
||||
this.pubkey = privateKey.getPubkey();
|
||||
this.ripe = pubkey.getRipe();
|
||||
this.address = generateAddress();
|
||||
}
|
||||
|
||||
public BitmessageAddress(String address) {
|
||||
Base58.decode(address.substring(3));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
try {
|
||||
byte[] combinedKeys = new byte[pubkey.getSigningKey().length + pubkey.getEncryptionKey().length];
|
||||
System.arraycopy(pubkey.getSigningKey(), 0, combinedKeys, 0, pubkey.getSigningKey().length);
|
||||
System.arraycopy(pubkey.getEncryptionKey(), 0, combinedKeys, pubkey.getSigningKey().length, pubkey.getEncryptionKey().length);
|
||||
|
||||
byte[] hash = ripemd160(sha512(combinedKeys));
|
||||
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
Encode.varInt(version, stream);
|
||||
Encode.varInt(streamNumber, stream);
|
||||
stream.write(hash);
|
||||
|
||||
byte[] checksum = Security.doubleSha512(stream.toByteArray());
|
||||
for (int i = 0; i < 4; i++) {
|
||||
stream.write(checksum[i]);
|
||||
}
|
||||
return "BM-" + Base58.encode(stream.toByteArray());
|
||||
byte[] bytes = Base58.decode(address.substring(3));
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(bytes);
|
||||
AccessCounter counter = new AccessCounter();
|
||||
this.version = Decode.varInt(in, counter);
|
||||
this.stream = Decode.varInt(in, counter);
|
||||
this.ripe = Decode.bytes(in, bytes.length - counter.length() - 4);
|
||||
testChecksum(Decode.bytes(in, 4), bytes);
|
||||
this.address = generateAddress();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void testChecksum(byte[] expected, byte[] address) {
|
||||
byte[] checksum = Security.doubleSha512(address, address.length - 4);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (expected[i] != checksum[i]) throw new IllegalArgumentException("Checksum of address failed");
|
||||
}
|
||||
}
|
||||
|
||||
private String generateAddress() {
|
||||
try {
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
Encode.varInt(version, os);
|
||||
Encode.varInt(stream, os);
|
||||
os.write(ripe);
|
||||
|
||||
byte[] checksum = Security.doubleSha512(os.toByteArray());
|
||||
for (int i = 0; i < 4; i++) {
|
||||
os.write(checksum[i]);
|
||||
}
|
||||
return "BM-" + Base58.encode(os.toByteArray());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public long getStream() {
|
||||
return stream;
|
||||
}
|
||||
|
||||
public long getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public Pubkey getPubkey() {
|
||||
return pubkey;
|
||||
}
|
||||
|
||||
public void setPubkey(Pubkey pubkey) {
|
||||
if (!Arrays.equals(ripe, pubkey.getRipe())) throw new IllegalArgumentException("Pubkey has incompatible RIPE");
|
||||
this.pubkey = pubkey;
|
||||
}
|
||||
|
||||
public PrivateKey getPrivateKey() {
|
||||
return privateKey;
|
||||
}
|
||||
|
||||
public void setAlias(String alias) {
|
||||
this.alias = alias;
|
||||
}
|
||||
|
||||
public String getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
public String getAlias() {
|
||||
return alias;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return alias != null ? alias : address;
|
||||
}
|
||||
|
||||
public byte[] getRipe() {
|
||||
return ripe;
|
||||
}
|
||||
}
|
||||
|
@ -16,24 +16,63 @@
|
||||
|
||||
package ch.dissem.bitmessage.entity.payload;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import static ch.dissem.bitmessage.utils.Security.ripemd160;
|
||||
import static ch.dissem.bitmessage.utils.Security.sha512;
|
||||
|
||||
/**
|
||||
* Public keys for signing and encryption, the answer to a 'getpubkey' request.
|
||||
*/
|
||||
public interface Pubkey extends ObjectPayload {
|
||||
// bits 0 through 29 are yet undefined
|
||||
public abstract class Pubkey implements ObjectPayload {
|
||||
public final static long LATEST_VERSION = 4;
|
||||
|
||||
public abstract long getVersion();
|
||||
|
||||
public abstract byte[] getSigningKey();
|
||||
|
||||
public abstract byte[] getEncryptionKey();
|
||||
|
||||
public byte[] getRipe() {
|
||||
return ripemd160(sha512(getSigningKey(), getEncryptionKey()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Bits 0 through 29 are yet undefined
|
||||
*/
|
||||
public enum Feature {
|
||||
/**
|
||||
* Receiving node expects that the RIPE hash encoded in their address preceedes the encrypted message data of msg
|
||||
* messages bound for them.
|
||||
*/
|
||||
int FEATURE_INCLUDE_DESTINATION = 30;
|
||||
INCLUDE_DESTINATION(1 << 30),
|
||||
/**
|
||||
* If true, the receiving node does send acknowledgements (rather than dropping them).
|
||||
*/
|
||||
int FEATURE_DOES_ACK = 31;
|
||||
DOES_ACK(1 << 31);
|
||||
|
||||
long getVersion();
|
||||
private int bit;
|
||||
|
||||
byte[] getSigningKey();
|
||||
|
||||
byte[] getEncryptionKey();
|
||||
Feature(int bit) {
|
||||
this.bit = bit;
|
||||
}
|
||||
|
||||
public static int bitfield(Feature... features) {
|
||||
int bits = 0;
|
||||
for (Feature feature : features) {
|
||||
bits |= feature.bit;
|
||||
}
|
||||
return bits;
|
||||
}
|
||||
|
||||
public static Feature[] features(int bitfield){
|
||||
ArrayList<Feature> features = new ArrayList<>(Feature.values().length);
|
||||
for (Feature feature:Feature.values()){
|
||||
if ((bitfield & feature.bit) != 0){
|
||||
features.add(feature);
|
||||
}
|
||||
}
|
||||
return features.toArray(new Feature[features.size()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ import java.io.OutputStream;
|
||||
/**
|
||||
* A version 2 public key.
|
||||
*/
|
||||
public class V2Pubkey implements Pubkey {
|
||||
public class V2Pubkey extends Pubkey {
|
||||
protected long stream;
|
||||
protected int behaviorBitfield;
|
||||
protected byte[] publicSigningKey; // 64 Bytes
|
||||
@ -44,7 +44,7 @@ public class V2Pubkey implements Pubkey {
|
||||
|
||||
public static V2Pubkey read(InputStream is, long stream) throws IOException {
|
||||
return new V2Pubkey.Builder()
|
||||
.streamNumber(stream)
|
||||
.stream(stream)
|
||||
.behaviorBitfield((int) Decode.uint32(is))
|
||||
.publicSigningKey(Decode.bytes(is, 64))
|
||||
.publicEncryptionKey(Decode.bytes(is, 64))
|
||||
@ -87,7 +87,7 @@ public class V2Pubkey implements Pubkey {
|
||||
public Builder() {
|
||||
}
|
||||
|
||||
public Builder streamNumber(long streamNumber) {
|
||||
public Builder stream(long streamNumber) {
|
||||
this.streamNumber = streamNumber;
|
||||
return this;
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ public class V3Pubkey extends V2Pubkey {
|
||||
|
||||
public static V3Pubkey read(InputStream is, long stream) throws IOException {
|
||||
V3Pubkey.Builder v3 = new V3Pubkey.Builder()
|
||||
.streamNumber(stream)
|
||||
.stream(stream)
|
||||
.behaviorBitfield((int) Decode.uint32(is))
|
||||
.publicSigningKey(Decode.bytes(is, 64))
|
||||
.publicEncryptionKey(Decode.bytes(is, 64))
|
||||
@ -82,7 +82,7 @@ public class V3Pubkey extends V2Pubkey {
|
||||
public Builder() {
|
||||
}
|
||||
|
||||
public Builder streamNumber(long streamNumber) {
|
||||
public Builder stream(long streamNumber) {
|
||||
this.streamNumber = streamNumber;
|
||||
return this;
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ import java.io.OutputStream;
|
||||
* use that pubkey. This prevents people from gathering pubkeys sent around the network and using the data from them
|
||||
* to create messages to be used in spam or in flooding attacks.
|
||||
*/
|
||||
public class V4Pubkey implements Pubkey {
|
||||
public class V4Pubkey extends Pubkey {
|
||||
private long stream;
|
||||
private byte[] tag;
|
||||
private byte[] encrypted;
|
||||
@ -47,7 +47,7 @@ public class V4Pubkey implements Pubkey {
|
||||
// TODO: this.encrypted
|
||||
}
|
||||
|
||||
public static ObjectPayload read(InputStream stream, long streamNumber, int length) throws IOException {
|
||||
public static V4Pubkey read(InputStream stream, long streamNumber, int length) throws IOException {
|
||||
return new V4Pubkey(streamNumber, Decode.bytes(stream, 32), Decode.bytes(stream, length - 32));
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright 2015 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.entity.valueobject;
|
||||
|
||||
import ch.dissem.bitmessage.entity.Streamable;
|
||||
import ch.dissem.bitmessage.entity.payload.Pubkey;
|
||||
import ch.dissem.bitmessage.factory.Factory;
|
||||
import ch.dissem.bitmessage.utils.Bytes;
|
||||
import ch.dissem.bitmessage.utils.Decode;
|
||||
import ch.dissem.bitmessage.utils.Encode;
|
||||
import ch.dissem.bitmessage.utils.Security;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
/**
|
||||
* Created by chris on 18.04.15.
|
||||
*/
|
||||
public class PrivateKey implements Streamable {
|
||||
private final byte[] privateSigningKey; // 32 bytes
|
||||
private final byte[] privateEncryptionKey; // 32 bytes
|
||||
|
||||
private final Pubkey pubkey;
|
||||
|
||||
public PrivateKey(long nonceTrialsPerByte, long extraBytes, Pubkey.Feature... features) {
|
||||
this.privateSigningKey = Security.randomBytes(64);
|
||||
this.privateEncryptionKey = Security.randomBytes(64);
|
||||
this.pubkey = Security.createPubkey(Pubkey.LATEST_VERSION, privateSigningKey, privateEncryptionKey,
|
||||
nonceTrialsPerByte, extraBytes, features);
|
||||
}
|
||||
|
||||
private PrivateKey(byte[] privateSigningKey, byte[] privateEncryptionKey, Pubkey pubkey) {
|
||||
this.privateSigningKey = privateSigningKey;
|
||||
this.privateEncryptionKey = privateEncryptionKey;
|
||||
this.pubkey = pubkey;
|
||||
}
|
||||
|
||||
public PrivateKey(long version, 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, privateSigningKey, privateEncryptionKey,
|
||||
nonceTrialsPerByte, extraBytes, features);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static PrivateKey read(InputStream is) throws IOException {
|
||||
int version = (int) Decode.varInt(is);
|
||||
long stream = Decode.varInt(is);
|
||||
int len = (int) Decode.varInt(is);
|
||||
Pubkey pubkey = Factory.readPubkey(version, stream, is, len);
|
||||
len = (int) Decode.varInt(is);
|
||||
byte[] signingKey = Decode.bytes(is, len);
|
||||
len = (int) Decode.varInt(is);
|
||||
byte[] encryptionKey = Decode.bytes(is, len);
|
||||
return new PrivateKey(signingKey, encryptionKey, pubkey);
|
||||
}
|
||||
|
||||
public Pubkey getPubkey() {
|
||||
return pubkey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(OutputStream os) throws IOException {
|
||||
Encode.varInt(pubkey.getVersion(), os);
|
||||
Encode.varInt(pubkey.getStream(), os);
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
pubkey.write(baos);
|
||||
Encode.varInt(baos.size(), os);
|
||||
os.write(baos.toByteArray());
|
||||
Encode.varInt(privateSigningKey.length, os);
|
||||
os.write(privateSigningKey);
|
||||
Encode.varInt(privateEncryptionKey.length, os);
|
||||
os.write(privateEncryptionKey);
|
||||
}
|
||||
}
|
@ -16,10 +16,11 @@
|
||||
|
||||
package ch.dissem.bitmessage.factory;
|
||||
|
||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
||||
import ch.dissem.bitmessage.entity.NetworkMessage;
|
||||
import ch.dissem.bitmessage.entity.ObjectMessage;
|
||||
import ch.dissem.bitmessage.entity.payload.*;
|
||||
import ch.dissem.bitmessage.utils.Decode;
|
||||
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@ -53,17 +54,60 @@ public class Factory {
|
||||
}
|
||||
}
|
||||
|
||||
public static Pubkey createPubkey(long version, byte[] publicSigningKey, byte[] publicEncryptionKey,
|
||||
long nonceTrialsPerByte, long extraBytes, Pubkey.Feature... features) {
|
||||
if (publicSigningKey.length != 64)
|
||||
throw new IllegalArgumentException("64 bytes signing key expected, but it was "
|
||||
+ publicSigningKey.length + " bytes long.");
|
||||
if (publicEncryptionKey.length != 64)
|
||||
throw new IllegalArgumentException("64 bytes encryption key expected, but it was "
|
||||
+ publicEncryptionKey.length + " bytes long.");
|
||||
|
||||
switch ((int) version) {
|
||||
case 2:
|
||||
return new V2Pubkey.Builder()
|
||||
.publicSigningKey(publicSigningKey)
|
||||
.publicEncryptionKey(publicEncryptionKey)
|
||||
.behaviorBitfield(Pubkey.Feature.bitfield(features))
|
||||
.build();
|
||||
case 3:
|
||||
return new V3Pubkey.Builder()
|
||||
.publicSigningKey(publicSigningKey)
|
||||
.publicEncryptionKey(publicEncryptionKey)
|
||||
.behaviorBitfield(Pubkey.Feature.bitfield(features))
|
||||
.nonceTrialsPerByte(nonceTrialsPerByte)
|
||||
.extraBytes(extraBytes)
|
||||
.build();
|
||||
case 4:
|
||||
return new V4Pubkey(
|
||||
new V3Pubkey.Builder()
|
||||
.publicSigningKey(publicSigningKey)
|
||||
.publicEncryptionKey(publicEncryptionKey)
|
||||
.behaviorBitfield(Pubkey.Feature.bitfield(features))
|
||||
.nonceTrialsPerByte(nonceTrialsPerByte)
|
||||
.extraBytes(extraBytes)
|
||||
.build()
|
||||
);
|
||||
default:
|
||||
throw new IllegalArgumentException("Unexpected pubkey version " + version);
|
||||
}
|
||||
}
|
||||
|
||||
public static BitmessageAddress generatePrivateAddress(Pubkey.Feature... features) {
|
||||
return new BitmessageAddress(new PrivateKey(1000, 1000, features));
|
||||
}
|
||||
|
||||
static ObjectPayload getObjectPayload(long objectType, long version, long streamNumber, InputStream stream, int length) throws IOException {
|
||||
if (objectType < 4) {
|
||||
switch ((int) objectType) {
|
||||
case 0:
|
||||
return parseGetPubkey((int) version, streamNumber, stream, length);
|
||||
return parseGetPubkey(version, streamNumber, stream, length);
|
||||
case 1:
|
||||
return parsePubkey((int) version, streamNumber, stream, length);
|
||||
return parsePubkey(version, streamNumber, stream, length);
|
||||
case 2:
|
||||
return parseMsg((int) version, streamNumber, stream, length);
|
||||
return parseMsg(version, streamNumber, stream, length);
|
||||
case 3:
|
||||
return parseBroadcast((int) version, streamNumber, stream, length);
|
||||
return parseBroadcast(version, streamNumber, stream, length);
|
||||
default:
|
||||
LOG.error("This should not happen, someone broke something in the code!");
|
||||
}
|
||||
@ -73,29 +117,34 @@ public class Factory {
|
||||
return GenericPayload.read(stream, streamNumber, length);
|
||||
}
|
||||
|
||||
private static ObjectPayload parseGetPubkey(int version, long streamNumber, InputStream stream, int length) throws IOException {
|
||||
private static ObjectPayload parseGetPubkey(long version, long streamNumber, InputStream stream, int length) throws IOException {
|
||||
return GetPubkey.read(stream, streamNumber, length);
|
||||
}
|
||||
|
||||
private static ObjectPayload parsePubkey(int version, long streamNumber, InputStream stream, int length) throws IOException {
|
||||
switch (version) {
|
||||
public static Pubkey readPubkey(long version, long stream, InputStream is, int length) throws IOException {
|
||||
switch ((int) version) {
|
||||
case 2:
|
||||
return V2Pubkey.read(stream, streamNumber);
|
||||
return V2Pubkey.read(is, stream);
|
||||
case 3:
|
||||
return V3Pubkey.read(stream, streamNumber);
|
||||
return V3Pubkey.read(is, stream);
|
||||
case 4:
|
||||
return V4Pubkey.read(stream, streamNumber, length);
|
||||
return V4Pubkey.read(is, stream, length);
|
||||
}
|
||||
LOG.debug("Unexpected pubkey version " + version + ", handling as generic payload object");
|
||||
return GenericPayload.read(stream, streamNumber, length);
|
||||
return null;
|
||||
}
|
||||
|
||||
private static ObjectPayload parseMsg(int version, long streamNumber, InputStream stream, int length) throws IOException {
|
||||
private static ObjectPayload parsePubkey(long version, long streamNumber, InputStream stream, int length) throws IOException {
|
||||
Pubkey pubkey = readPubkey(version, streamNumber, stream, length);
|
||||
return pubkey != null ? pubkey : GenericPayload.read(stream, streamNumber, length);
|
||||
}
|
||||
|
||||
private static ObjectPayload parseMsg(long version, long streamNumber, InputStream stream, int length) throws IOException {
|
||||
return Msg.read(stream, streamNumber, length);
|
||||
}
|
||||
|
||||
private static ObjectPayload parseBroadcast(int version, long streamNumber, InputStream stream, int length) throws IOException {
|
||||
switch (version) {
|
||||
private static ObjectPayload parseBroadcast(long version, long streamNumber, InputStream stream, int length) throws IOException {
|
||||
switch ((int) version) {
|
||||
case 4:
|
||||
return V4Broadcast.read(stream, streamNumber, length);
|
||||
case 5:
|
||||
|
@ -16,15 +16,25 @@
|
||||
|
||||
package ch.dissem.bitmessage.ports;
|
||||
|
||||
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress;
|
||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Stores and provides known peers.
|
||||
* Created by chris on 23.04.15.
|
||||
*/
|
||||
public interface AddressRepository {
|
||||
List<NetworkAddress> getKnownAddresses(int limit, long... streams);
|
||||
/**
|
||||
* Returns all Bitmessage addresses that belong to this user, i.e. have a private key.
|
||||
*/
|
||||
List<BitmessageAddress> findIdentities();
|
||||
|
||||
void offerAddresses(List<NetworkAddress> addresses);
|
||||
/**
|
||||
* Returns all Bitmessage addresses that have no private key.
|
||||
*/
|
||||
List<BitmessageAddress> findContacts();
|
||||
|
||||
void save(BitmessageAddress address);
|
||||
|
||||
void remove(BitmessageAddress address);
|
||||
}
|
||||
|
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2015 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.ports;
|
||||
|
||||
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Stores and provides known peers.
|
||||
*/
|
||||
public interface NodeRegistry {
|
||||
List<NetworkAddress> getKnownAddresses(int limit, long... streams);
|
||||
|
||||
void offerAddresses(List<NetworkAddress> addresses);
|
||||
}
|
@ -78,9 +78,25 @@ public class Bytes {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new byte array containing the first <em>size</em> bytes of the given array.
|
||||
*/
|
||||
public static byte[] truncate(byte[] source, int size) {
|
||||
byte[] result = new byte[size];
|
||||
System.arraycopy(source, 0, result, 0, size);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static byte[] subArray(byte[] source, int offset, int length) {
|
||||
byte[] result = new byte[length];
|
||||
System.arraycopy(source, offset, result, 0, length);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static byte[] concatenate(byte first, byte[] bytes) {
|
||||
byte[] result = new byte[bytes.length + 1];
|
||||
result[0] = first;
|
||||
System.arraycopy(bytes, 0, result, 1, bytes.length);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,12 @@
|
||||
package ch.dissem.bitmessage.utils;
|
||||
|
||||
import ch.dissem.bitmessage.entity.ObjectMessage;
|
||||
import ch.dissem.bitmessage.entity.payload.Pubkey;
|
||||
import ch.dissem.bitmessage.factory.Factory;
|
||||
import ch.dissem.bitmessage.ports.ProofOfWorkEngine;
|
||||
import org.bouncycastle.asn1.sec.SECNamedCurves;
|
||||
import org.bouncycastle.asn1.x9.X9ECParameters;
|
||||
import org.bouncycastle.crypto.params.ECDomainParameters;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -35,6 +40,8 @@ public class Security {
|
||||
public static final Logger LOG = LoggerFactory.getLogger(Security.class);
|
||||
private static final SecureRandom RANDOM = new SecureRandom();
|
||||
private static final BigInteger TWO = BigInteger.valueOf(2);
|
||||
private static final X9ECParameters EC_CURVE = SECNamedCurves.getByName("secp256k1");
|
||||
private static final ECDomainParameters EC_PARAMETERS = new ECDomainParameters(EC_CURVE.getCurve(), EC_CURVE.getG(), EC_CURVE.getN(), EC_CURVE.getH());
|
||||
|
||||
static {
|
||||
java.security.Security.addProvider(new BouncyCastleProvider());
|
||||
@ -52,6 +59,12 @@ public class Security {
|
||||
return mda.digest(mda.digest());
|
||||
}
|
||||
|
||||
public static byte[] doubleSha512(byte[] data, int length) {
|
||||
MessageDigest mda = md("SHA-512");
|
||||
mda.update(data, 0, length);
|
||||
return mda.digest(mda.digest());
|
||||
}
|
||||
|
||||
public static byte[] ripemd160(byte[]... data) {
|
||||
return hash("RIPEMD160", data);
|
||||
}
|
||||
@ -115,4 +128,16 @@ public class Security {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static Pubkey createPubkey(long version, byte[] privateSigningKey, byte[] privateEncryptionKey,
|
||||
long nonceTrialsPerByte, long extraBytes, Pubkey.Feature... features) {
|
||||
byte[] publicSigningKey = EC_PARAMETERS.getG().multiply(keyToBigInt(privateSigningKey)).getEncoded(false);
|
||||
byte[] publicEncryptionKey = EC_PARAMETERS.getG().multiply(keyToBigInt(privateEncryptionKey)).getEncoded(false);
|
||||
return Factory.createPubkey(Bytes.subArray(publicSigningKey, 1, 64), Bytes.subArray(publicEncryptionKey, 1, 64),
|
||||
nonceTrialsPerByte, extraBytes, features);
|
||||
}
|
||||
|
||||
private static BigInteger keyToBigInt(byte[] key) {
|
||||
return new BigInteger(Bytes.concatenate((byte) 0x00, key));
|
||||
}
|
||||
}
|
||||
|
@ -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.entity;
|
||||
|
||||
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
public class BitmessageAddressTest {
|
||||
@Test
|
||||
public void ensureAddressStaysSame() {
|
||||
String address = "BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ";
|
||||
assertEquals(address, new BitmessageAddress(address).toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ensureStreamAndVersionAreParsed() {
|
||||
BitmessageAddress address = new BitmessageAddress("BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ");
|
||||
assertEquals(1, address.getStream());
|
||||
assertEquals(3, address.getVersion());
|
||||
|
||||
address = new BitmessageAddress("BM-87hJ99tPAXxtetvnje7Z491YSvbEtBJVc5e");
|
||||
assertEquals(1, address.getStream());
|
||||
assertEquals(4, address.getVersion());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateAddress() {
|
||||
BitmessageAddress address = new BitmessageAddress(new PrivateKey(0, 0));
|
||||
assertNotNull(address.getPubkey());
|
||||
}
|
||||
}
|
@ -17,17 +17,19 @@
|
||||
package ch.dissem.bitmessage.inventory;
|
||||
|
||||
import ch.dissem.bitmessage.entity.ObjectMessage;
|
||||
import ch.dissem.bitmessage.entity.Streamable;
|
||||
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
|
||||
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress;
|
||||
import ch.dissem.bitmessage.factory.Factory;
|
||||
import ch.dissem.bitmessage.ports.AddressRepository;
|
||||
import ch.dissem.bitmessage.ports.Inventory;
|
||||
import ch.dissem.bitmessage.ports.NodeRegistry;
|
||||
import org.flywaydb.core.Flyway;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.sql.*;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
@ -38,7 +40,7 @@ import static ch.dissem.bitmessage.utils.UnixTime.now;
|
||||
/**
|
||||
* Stores everything in a database
|
||||
*/
|
||||
public class DatabaseRepository implements Inventory, AddressRepository {
|
||||
public class DatabaseRepository implements Inventory, NodeRegistry {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DatabaseRepository.class);
|
||||
|
||||
private static final String DB_URL = "jdbc:h2:~/jabit";
|
||||
@ -170,6 +172,13 @@ public class DatabaseRepository implements Inventory, AddressRepository {
|
||||
}
|
||||
}
|
||||
|
||||
protected void writeBlob(PreparedStatement ps, int parameterIndex, Streamable data) throws SQLException, IOException {
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
data.write(os);
|
||||
ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray());
|
||||
ps.setBlob(parameterIndex, is);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanup() {
|
||||
try {
|
||||
@ -180,7 +189,7 @@ public class DatabaseRepository implements Inventory, AddressRepository {
|
||||
}
|
||||
}
|
||||
|
||||
private Connection getConnection() {
|
||||
protected Connection getConnection() {
|
||||
try {
|
||||
return DriverManager.getConnection(DB_URL, DB_USER, DB_PWD);
|
||||
} catch (SQLException e) {
|
||||
|
@ -0,0 +1,101 @@
|
||||
/*
|
||||
* 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.inventory;
|
||||
|
||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
||||
import ch.dissem.bitmessage.entity.payload.Pubkey;
|
||||
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
|
||||
import ch.dissem.bitmessage.factory.Factory;
|
||||
import ch.dissem.bitmessage.ports.AddressRepository;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.sql.*;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Created by chris on 23.04.15.
|
||||
*/
|
||||
public class JdbcAddressRepository extends DatabaseRepository implements AddressRepository {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DatabaseRepository.class);
|
||||
|
||||
@Override
|
||||
public List<BitmessageAddress> findIdentities() {
|
||||
return find("private_signing_key IS NOT NULL");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<BitmessageAddress> findContacts() {
|
||||
return find("private_signing_key IS NULL");
|
||||
}
|
||||
|
||||
private List<BitmessageAddress> find(String where) {
|
||||
List<BitmessageAddress> result = new LinkedList<>();
|
||||
try {
|
||||
Statement stmt = getConnection().createStatement();
|
||||
ResultSet rs = stmt.executeQuery("SELECT address, alias, public_key, private_key FROM Address WHERE " + where);
|
||||
while (rs.next()) {
|
||||
BitmessageAddress address;
|
||||
Blob privateKeyBlob = rs.getBlob("private_key");
|
||||
if (privateKeyBlob != null) {
|
||||
PrivateKey privateKey = PrivateKey.read(privateKeyBlob.getBinaryStream());
|
||||
address = new BitmessageAddress(privateKey);
|
||||
} else {
|
||||
address = new BitmessageAddress(rs.getString("address"));
|
||||
Blob publicKeyBlob = rs.getBlob("public_key");
|
||||
if (publicKeyBlob != null) {
|
||||
Pubkey pubkey = Factory.readPubkey(address.getVersion(), address.getStream(),
|
||||
publicKeyBlob.getBinaryStream(), (int)publicKeyBlob.length());
|
||||
address.setPubkey(pubkey);
|
||||
}
|
||||
}
|
||||
address.setAlias(rs.getString("alias"));
|
||||
|
||||
result.add(address);
|
||||
}
|
||||
} catch (IOException | SQLException e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(BitmessageAddress address) {
|
||||
try {
|
||||
PreparedStatement ps = getConnection().prepareStatement(
|
||||
"INSERT INTO Address (address, alias, public_key, private_key) VALUES (?, ?, ?, ?, ?)");
|
||||
ps.setString(1, address.getAddress());
|
||||
ps.setString(2, address.getAlias());
|
||||
writeBlob(ps, 3, address.getPubkey());
|
||||
writeBlob(ps, 4, address.getPrivateKey());
|
||||
} catch (IOException | SQLException e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(BitmessageAddress address) {
|
||||
try {
|
||||
Statement stmt = getConnection().createStatement();
|
||||
stmt.executeUpdate("DELETE FROM Address WHERE address = '" + address.getAddress() + "'");
|
||||
} catch (SQLException e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
@ -17,7 +17,7 @@
|
||||
package ch.dissem.bitmessage.inventory;
|
||||
|
||||
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress;
|
||||
import ch.dissem.bitmessage.ports.AddressRepository;
|
||||
import ch.dissem.bitmessage.ports.NodeRegistry;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@ -25,7 +25,7 @@ import java.util.List;
|
||||
/**
|
||||
* Created by chris on 06.04.15.
|
||||
*/
|
||||
public class SimpleAddressRepository implements AddressRepository {
|
||||
public class SimpleNodeRegistry implements NodeRegistry {
|
||||
@Override
|
||||
public List<NetworkAddress> getKnownAddresses(int limit, long... streams) {
|
||||
return Collections.singletonList(new NetworkAddress.Builder().ipv4(127, 0, 0, 1).port(8444).build());
|
@ -0,0 +1,6 @@
|
||||
CREATE TABLE Address (
|
||||
address VARCHAR(40) NOT NULL PRIMARY KEY,
|
||||
alias VARCHAR(255),
|
||||
public_key BLOB,
|
||||
private_key BLOB
|
||||
);
|
Loading…
Reference in New Issue
Block a user