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; 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.Inventory;
import ch.dissem.bitmessage.ports.NetworkHandler; import ch.dissem.bitmessage.ports.NetworkHandler;
@ -32,7 +32,7 @@ public class Context {
private static Context instance; private static Context instance;
private Inventory inventory; private Inventory inventory;
private AddressRepository addressRepo; private NodeRegistry addressRepo;
private NetworkHandler networkHandler; private NetworkHandler networkHandler;
private Collection<Long> streams = new TreeSet<>(); private Collection<Long> streams = new TreeSet<>();
@ -42,7 +42,7 @@ public class Context {
private long networkNonceTrialsPerByte = 1000; private long networkNonceTrialsPerByte = 1000;
private long networkExtraBytes = 1000; private long networkExtraBytes = 1000;
private Context(Inventory inventory, AddressRepository addressRepo, private Context(Inventory inventory, NodeRegistry addressRepo,
NetworkHandler networkHandler, int port) { NetworkHandler networkHandler, int port) {
this.inventory = inventory; this.inventory = inventory;
this.addressRepo = addressRepo; this.addressRepo = addressRepo;
@ -50,8 +50,8 @@ public class Context {
this.port = port; this.port = port;
} }
public static void init(Inventory inventory, AddressRepository addressRepository, NetworkHandler networkHandler, int port) { public static void init(Inventory inventory, NodeRegistry nodeRegistry, NetworkHandler networkHandler, int port) {
instance = new Context(inventory, addressRepository, networkHandler, port); instance = new Context(inventory, nodeRegistry, networkHandler, port);
} }
public static Context getInstance() { public static Context getInstance() {
@ -62,7 +62,7 @@ public class Context {
return inventory; return inventory;
} }
public AddressRepository getAddressRepository() { public NodeRegistry getAddressRepository() {
return addressRepo; return addressRepo;
} }

View File

@ -17,55 +17,115 @@
package ch.dissem.bitmessage.entity; package ch.dissem.bitmessage.entity;
import ch.dissem.bitmessage.entity.payload.Pubkey; import ch.dissem.bitmessage.entity.payload.Pubkey;
import ch.dissem.bitmessage.utils.Base58; import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
import ch.dissem.bitmessage.utils.Encode; import ch.dissem.bitmessage.utils.*;
import ch.dissem.bitmessage.utils.Security;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays;
import static ch.dissem.bitmessage.utils.Security.ripemd160;
import static ch.dissem.bitmessage.utils.Security.sha512;
/** /**
* A Bitmessage address. Can be a user's private address, an address string without public keys or a recipient's address * A Bitmessage address. Can be a user's private address, an address string without public keys or a recipient's address
* holding private keys. * holding private keys.
*/ */
public abstract class BitmessageAddress { public class BitmessageAddress {
private long version; private long version;
private long streamNumber; private long stream;
private byte[] ripe;
private String address;
private PrivateKey privateKey;
private Pubkey pubkey; private Pubkey pubkey;
public BitmessageAddress(Pubkey pubkey) { private String alias;
this.pubkey = pubkey;
public BitmessageAddress(PrivateKey privateKey) {
this.privateKey = privateKey;
this.pubkey = privateKey.getPubkey();
this.ripe = pubkey.getRipe();
this.address = generateAddress();
} }
public BitmessageAddress(String address) { public BitmessageAddress(String address) {
Base58.decode(address.substring(3));
}
@Override
public String toString() {
try { try {
byte[] combinedKeys = new byte[pubkey.getSigningKey().length + pubkey.getEncryptionKey().length]; byte[] bytes = Base58.decode(address.substring(3));
System.arraycopy(pubkey.getSigningKey(), 0, combinedKeys, 0, pubkey.getSigningKey().length); ByteArrayInputStream in = new ByteArrayInputStream(bytes);
System.arraycopy(pubkey.getEncryptionKey(), 0, combinedKeys, pubkey.getSigningKey().length, pubkey.getEncryptionKey().length); AccessCounter counter = new AccessCounter();
this.version = Decode.varInt(in, counter);
byte[] hash = ripemd160(sha512(combinedKeys)); this.stream = Decode.varInt(in, counter);
this.ripe = Decode.bytes(in, bytes.length - counter.length() - 4);
ByteArrayOutputStream stream = new ByteArrayOutputStream(); testChecksum(Decode.bytes(in, 4), bytes);
Encode.varInt(version, stream); this.address = generateAddress();
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());
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(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; 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 keys for signing and encryption, the answer to a 'getpubkey' request.
*/ */
public interface Pubkey extends ObjectPayload { public abstract class Pubkey implements ObjectPayload {
// bits 0 through 29 are yet undefined 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 * Receiving node expects that the RIPE hash encoded in their address preceedes the encrypted message data of msg
* messages bound for them. * 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). * 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(); 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. * A version 2 public key.
*/ */
public class V2Pubkey implements Pubkey { public class V2Pubkey extends Pubkey {
protected long stream; protected long stream;
protected int behaviorBitfield; protected int behaviorBitfield;
protected byte[] publicSigningKey; // 64 Bytes protected byte[] publicSigningKey; // 64 Bytes
@ -44,7 +44,7 @@ public class V2Pubkey implements Pubkey {
public static V2Pubkey read(InputStream is, long stream) throws IOException { public static V2Pubkey read(InputStream is, long stream) throws IOException {
return new V2Pubkey.Builder() return new V2Pubkey.Builder()
.streamNumber(stream) .stream(stream)
.behaviorBitfield((int) Decode.uint32(is)) .behaviorBitfield((int) Decode.uint32(is))
.publicSigningKey(Decode.bytes(is, 64)) .publicSigningKey(Decode.bytes(is, 64))
.publicEncryptionKey(Decode.bytes(is, 64)) .publicEncryptionKey(Decode.bytes(is, 64))
@ -87,7 +87,7 @@ public class V2Pubkey implements Pubkey {
public Builder() { public Builder() {
} }
public Builder streamNumber(long streamNumber) { public Builder stream(long streamNumber) {
this.streamNumber = streamNumber; this.streamNumber = streamNumber;
return this; return this;
} }

View File

@ -44,7 +44,7 @@ public class V3Pubkey extends V2Pubkey {
public static V3Pubkey read(InputStream is, long stream) throws IOException { public static V3Pubkey read(InputStream is, long stream) throws IOException {
V3Pubkey.Builder v3 = new V3Pubkey.Builder() V3Pubkey.Builder v3 = new V3Pubkey.Builder()
.streamNumber(stream) .stream(stream)
.behaviorBitfield((int) Decode.uint32(is)) .behaviorBitfield((int) Decode.uint32(is))
.publicSigningKey(Decode.bytes(is, 64)) .publicSigningKey(Decode.bytes(is, 64))
.publicEncryptionKey(Decode.bytes(is, 64)) .publicEncryptionKey(Decode.bytes(is, 64))
@ -82,7 +82,7 @@ public class V3Pubkey extends V2Pubkey {
public Builder() { public Builder() {
} }
public Builder streamNumber(long streamNumber) { public Builder stream(long streamNumber) {
this.streamNumber = streamNumber; this.streamNumber = streamNumber;
return this; 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 * 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. * 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 long stream;
private byte[] tag; private byte[] tag;
private byte[] encrypted; private byte[] encrypted;
@ -47,7 +47,7 @@ public class V4Pubkey implements Pubkey {
// TODO: this.encrypted // 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)); 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; package ch.dissem.bitmessage.factory;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.NetworkMessage; import ch.dissem.bitmessage.entity.NetworkMessage;
import ch.dissem.bitmessage.entity.ObjectMessage; import ch.dissem.bitmessage.entity.ObjectMessage;
import ch.dissem.bitmessage.entity.payload.*; 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.Logger;
import org.slf4j.LoggerFactory; 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 { static ObjectPayload getObjectPayload(long objectType, long version, long streamNumber, InputStream stream, int length) throws IOException {
if (objectType < 4) { if (objectType < 4) {
switch ((int) objectType) { switch ((int) objectType) {
case 0: case 0:
return parseGetPubkey((int) version, streamNumber, stream, length); return parseGetPubkey(version, streamNumber, stream, length);
case 1: case 1:
return parsePubkey((int) version, streamNumber, stream, length); return parsePubkey(version, streamNumber, stream, length);
case 2: case 2:
return parseMsg((int) version, streamNumber, stream, length); return parseMsg(version, streamNumber, stream, length);
case 3: case 3:
return parseBroadcast((int) version, streamNumber, stream, length); return parseBroadcast(version, streamNumber, stream, length);
default: default:
LOG.error("This should not happen, someone broke something in the code!"); 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); 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); return GetPubkey.read(stream, streamNumber, length);
} }
private static ObjectPayload parsePubkey(int version, long streamNumber, InputStream stream, int length) throws IOException { public static Pubkey readPubkey(long version, long stream, InputStream is, int length) throws IOException {
switch (version) { switch ((int) version) {
case 2: case 2:
return V2Pubkey.read(stream, streamNumber); return V2Pubkey.read(is, stream);
case 3: case 3:
return V3Pubkey.read(stream, streamNumber); return V3Pubkey.read(is, stream);
case 4: 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"); 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); return Msg.read(stream, streamNumber, length);
} }
private static ObjectPayload parseBroadcast(int version, long streamNumber, InputStream stream, int length) throws IOException { private static ObjectPayload parseBroadcast(long version, long streamNumber, InputStream stream, int length) throws IOException {
switch (version) { switch ((int) version) {
case 4: case 4:
return V4Broadcast.read(stream, streamNumber, length); return V4Broadcast.read(stream, streamNumber, length);
case 5: case 5:

View File

@ -16,15 +16,25 @@
package ch.dissem.bitmessage.ports; package ch.dissem.bitmessage.ports;
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress; import ch.dissem.bitmessage.entity.BitmessageAddress;
import java.util.List; import java.util.List;
/** /**
* Stores and provides known peers. * Created by chris on 23.04.15.
*/ */
public interface AddressRepository { 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; 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) { public static byte[] truncate(byte[] source, int size) {
byte[] result = new byte[size]; byte[] result = new byte[size];
System.arraycopy(source, 0, result, 0, size); System.arraycopy(source, 0, result, 0, size);
return result; 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; package ch.dissem.bitmessage.utils;
import ch.dissem.bitmessage.entity.ObjectMessage; 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 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.bouncycastle.jce.provider.BouncyCastleProvider;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -35,6 +40,8 @@ public class Security {
public static final Logger LOG = LoggerFactory.getLogger(Security.class); public static final Logger LOG = LoggerFactory.getLogger(Security.class);
private static final SecureRandom RANDOM = new SecureRandom(); private static final SecureRandom RANDOM = new SecureRandom();
private static final BigInteger TWO = BigInteger.valueOf(2); 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 { static {
java.security.Security.addProvider(new BouncyCastleProvider()); java.security.Security.addProvider(new BouncyCastleProvider());
@ -52,6 +59,12 @@ public class Security {
return mda.digest(mda.digest()); 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) { public static byte[] ripemd160(byte[]... data) {
return hash("RIPEMD160", data); return hash("RIPEMD160", data);
} }
@ -115,4 +128,16 @@ public class Security {
throw new RuntimeException(e); 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; package ch.dissem.bitmessage.inventory;
import ch.dissem.bitmessage.entity.ObjectMessage; 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.InventoryVector;
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress; import ch.dissem.bitmessage.entity.valueobject.NetworkAddress;
import ch.dissem.bitmessage.factory.Factory; import ch.dissem.bitmessage.factory.Factory;
import ch.dissem.bitmessage.ports.AddressRepository;
import ch.dissem.bitmessage.ports.Inventory; import ch.dissem.bitmessage.ports.Inventory;
import ch.dissem.bitmessage.ports.NodeRegistry;
import org.flywaydb.core.Flyway; import org.flywaydb.core.Flyway;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.sql.*; import java.sql.*;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
@ -38,7 +40,7 @@ import static ch.dissem.bitmessage.utils.UnixTime.now;
/** /**
* Stores everything in a database * 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 Logger LOG = LoggerFactory.getLogger(DatabaseRepository.class);
private static final String DB_URL = "jdbc:h2:~/jabit"; 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 @Override
public void cleanup() { public void cleanup() {
try { try {
@ -180,7 +189,7 @@ public class DatabaseRepository implements Inventory, AddressRepository {
} }
} }
private Connection getConnection() { protected Connection getConnection() {
try { try {
return DriverManager.getConnection(DB_URL, DB_USER, DB_PWD); return DriverManager.getConnection(DB_URL, DB_USER, DB_PWD);
} catch (SQLException e) { } 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; package ch.dissem.bitmessage.inventory;
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress; 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.Collections;
import java.util.List; import java.util.List;
@ -25,7 +25,7 @@ import java.util.List;
/** /**
* Created by chris on 06.04.15. * Created by chris on 06.04.15.
*/ */
public class SimpleAddressRepository implements AddressRepository { public class SimpleNodeRegistry implements NodeRegistry {
@Override @Override
public List<NetworkAddress> getKnownAddresses(int limit, long... streams) { public List<NetworkAddress> getKnownAddresses(int limit, long... streams) {
return Collections.singletonList(new NetworkAddress.Builder().ipv4(127, 0, 0, 1).port(8444).build()); 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
);