Added some address generation and handling

This commit is contained in:
Christian Basler 2015-04-24 09:53:58 +02:00
parent 096f5db1bb
commit d3499c9627
17 changed files with 565 additions and 80 deletions

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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()));
}
/**
* Receiving node expects that the RIPE hash encoded in their address preceedes the encrypted message data of msg
* messages bound for them.
* Bits 0 through 29 are yet undefined
*/
int FEATURE_INCLUDE_DESTINATION = 30;
/**
* If true, the receiving node does send acknowledgements (rather than dropping them).
*/
int FEATURE_DOES_ACK = 31;
public enum Feature {
/**
* Receiving node expects that the RIPE hash encoded in their address preceedes the encrypted message data of msg
* messages bound for them.
*/
INCLUDE_DESTINATION(1 << 30),
/**
* If true, the receiving node does send acknowledgements (rather than dropping them).
*/
DOES_ACK(1 << 31);
long getVersion();
private int bit;
byte[] getSigningKey();
Feature(int bit) {
this.bit = bit;
}
byte[] getEncryptionKey();
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()]);
}
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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));
}

View File

@ -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);
}
}

View File

@ -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:

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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));
}
}

View File

@ -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());
}
}

View File

@ -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) {

View File

@ -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);
}
}
}

View File

@ -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());

View File

@ -0,0 +1,6 @@
CREATE TABLE Address (
address VARCHAR(40) NOT NULL PRIMARY KEY,
alias VARCHAR(255),
public_key BLOB,
private_key BLOB
);