Fixed some stuff and broke some other - my goal is to solely use the java.security API

This commit is contained in:
Christian Basler 2015-05-05 20:48:42 +02:00
parent a65907f13b
commit fde6398156
19 changed files with 318 additions and 80 deletions

View File

@ -21,9 +21,13 @@ import ch.dissem.bitmessage.entity.ObjectMessage;
import ch.dissem.bitmessage.entity.payload.ObjectType; import ch.dissem.bitmessage.entity.payload.ObjectType;
import ch.dissem.bitmessage.entity.payload.Pubkey; import ch.dissem.bitmessage.entity.payload.Pubkey;
import ch.dissem.bitmessage.inventory.JdbcInventory; import ch.dissem.bitmessage.inventory.JdbcInventory;
import ch.dissem.bitmessage.utils.Base58;
import ch.dissem.bitmessage.utils.Encode;
import ch.dissem.bitmessage.utils.Security;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
@ -73,10 +77,13 @@ public class Main {
// LOG.info("Shutting down client"); // LOG.info("Shutting down client");
// ctx.getNetworkHandler().stop(); // ctx.getNetworkHandler().stop();
List<ObjectMessage> objects = new JdbcInventory().getObjects(address.getStream(), address.getVersion(), ObjectType.PUBKEY); List<ObjectMessage> objects = new JdbcInventory().getObjects(address.getStream(), address.getVersion(), ObjectType.PUBKEY);
System.out.println("Address version: " + address.getVersion()); System.out.println("Address version: " + address.getVersion());
System.out.println("Address stream: " + address.getStream()); System.out.println("Address stream: " + address.getStream());
for (ObjectMessage o : objects) { for (ObjectMessage o : objects) {
// if (!o.isSignatureValid()) System.out.println("Invalid signature.");
// System.out.println(o.getPayload().getSignature().length);
Pubkey pubkey = (Pubkey) o.getPayload(); Pubkey pubkey = (Pubkey) o.getPayload();
if (Arrays.equals(address.getRipe(), pubkey.getRipe())) if (Arrays.equals(address.getRipe(), pubkey.getRipe()))
System.out.println("Pubkey found!"); System.out.println("Pubkey found!");
@ -85,10 +92,26 @@ public class Main {
System.out.println(address); System.out.println(address);
} catch (Exception ignore) { } catch (Exception ignore) {
System.out.println("But setPubkey failed? " + address.getRipe().length + "/" + pubkey.getRipe().length); System.out.println("But setPubkey failed? " + address.getRipe().length + "/" + pubkey.getRipe().length);
System.out.println("Failed address: " + generateAddress(address.getStream(), address.getVersion(), pubkey.getRipe()));
if (Arrays.equals(address.getRipe(), pubkey.getRipe())) { if (Arrays.equals(address.getRipe(), pubkey.getRipe())) {
ignore.printStackTrace(); ignore.printStackTrace();
} }
} }
} }
} }
public static String generateAddress(long stream, long version, byte[] ripe) {
try {
ByteArrayOutputStream os = new ByteArrayOutputStream();
Encode.varInt(version, os);
Encode.varInt(stream, os);
os.write(ripe);
byte[] checksum = Security.doubleSha512(os.toByteArray());
os.write(checksum, 0, 4);
return "BM-" + Base58.encode(os.toByteArray());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
} }

View File

@ -18,7 +18,10 @@ package ch.dissem.bitmessage.entity;
import ch.dissem.bitmessage.entity.payload.Pubkey; import ch.dissem.bitmessage.entity.payload.Pubkey;
import ch.dissem.bitmessage.entity.valueobject.PrivateKey; import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
import ch.dissem.bitmessage.utils.*; import ch.dissem.bitmessage.utils.AccessCounter;
import ch.dissem.bitmessage.utils.Base58;
import ch.dissem.bitmessage.utils.Encode;
import ch.dissem.bitmessage.utils.Security;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
@ -58,7 +61,7 @@ public class BitmessageAddress {
AccessCounter counter = new AccessCounter(); AccessCounter counter = new AccessCounter();
this.version = varInt(in, counter); this.version = varInt(in, counter);
this.stream = varInt(in, counter); this.stream = varInt(in, counter);
this.ripe = Bytes.expand(bytes(in, bytes.length - counter.length() - 4), 20); this.ripe = bytes(in, bytes.length - counter.length() - 4);
testChecksum(bytes(in, 4), bytes); testChecksum(bytes(in, 4), bytes);
this.address = generateAddress(); this.address = generateAddress();
} catch (IOException e) { } catch (IOException e) {
@ -81,9 +84,7 @@ public class BitmessageAddress {
os.write(ripe); os.write(ripe);
byte[] checksum = Security.doubleSha512(os.toByteArray()); byte[] checksum = Security.doubleSha512(os.toByteArray());
for (int i = 0; i < 4; i++) { os.write(checksum, 0, 4);
os.write(checksum[i]);
}
return "BM-" + Base58.encode(os.toByteArray()); return "BM-" + Base58.encode(os.toByteArray());
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
@ -103,7 +104,8 @@ public class BitmessageAddress {
} }
public void setPubkey(Pubkey pubkey) { public void setPubkey(Pubkey pubkey) {
if (!Arrays.equals(ripe, pubkey.getRipe())) throw new IllegalArgumentException("Pubkey has incompatible RIPE"); if (!Arrays.equals(ripe, pubkey.getRipe()))
throw new IllegalArgumentException("Pubkey has incompatible RIPE");
this.pubkey = pubkey; this.pubkey = pubkey;
} }

View File

@ -17,7 +17,9 @@
package ch.dissem.bitmessage.entity; package ch.dissem.bitmessage.entity;
import ch.dissem.bitmessage.entity.payload.ObjectPayload; import ch.dissem.bitmessage.entity.payload.ObjectPayload;
import ch.dissem.bitmessage.entity.payload.Pubkey;
import ch.dissem.bitmessage.entity.valueobject.InventoryVector; import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
import ch.dissem.bitmessage.utils.Bytes; import ch.dissem.bitmessage.utils.Bytes;
import ch.dissem.bitmessage.utils.Encode; import ch.dissem.bitmessage.utils.Encode;
import ch.dissem.bitmessage.utils.Security; import ch.dissem.bitmessage.utils.Security;
@ -88,19 +90,43 @@ public class ObjectMessage implements MessagePayload {
return new InventoryVector(Bytes.truncate(Security.doubleSha512(nonce, getPayloadBytesWithoutNonce()), 32)); return new InventoryVector(Bytes.truncate(Security.doubleSha512(nonce, getPayloadBytesWithoutNonce()), 32));
} }
public boolean isSigned() {
return payload.isSigned();
}
private byte[] getBytesToSign() throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
writeHeaderWithoutNonce(out);
payload.writeBytesToSign(out);
return out.toByteArray();
}
public void sign(PrivateKey key) {
// TODO
}
public boolean isSignatureValid() throws IOException {
Pubkey pubkey=null; // TODO
return Security.isSignatureValid(getBytesToSign(), payload.getSignature(), pubkey);
}
@Override @Override
public void write(OutputStream out) throws IOException { public void write(OutputStream out) throws IOException {
out.write(nonce); out.write(nonce);
out.write(getPayloadBytesWithoutNonce()); out.write(getPayloadBytesWithoutNonce());
} }
public byte[] getPayloadBytesWithoutNonce() throws IOException { private void writeHeaderWithoutNonce(OutputStream out) throws IOException {
if (payloadBytes == null) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
Encode.int64(expiresTime, out); Encode.int64(expiresTime, out);
Encode.int32(objectType, out); Encode.int32(objectType, out);
Encode.varInt(version, out); Encode.varInt(version, out);
Encode.varInt(stream, out); Encode.varInt(stream, out);
}
public byte[] getPayloadBytesWithoutNonce() throws IOException {
if (payloadBytes == null) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
writeHeaderWithoutNonce(out);
payload.write(out); payload.write(out);
payloadBytes = out.toByteArray(); payloadBytes = out.toByteArray();
} }

View File

@ -20,6 +20,6 @@ package ch.dissem.bitmessage.entity.payload;
* Users who are subscribed to the sending address will see the message appear in their inbox. * Users who are subscribed to the sending address will see the message appear in their inbox.
* Broadcasts are version 4 or 5. * Broadcasts are version 4 or 5.
*/ */
public interface Broadcast extends ObjectPayload { public abstract class Broadcast extends ObjectPayload {
byte[] getEncrypted(); public abstract byte[] getEncrypted();
} }

View File

@ -26,7 +26,7 @@ import java.io.OutputStream;
* In cases we don't know what to do with an object, we just store its bytes and send it again - we don't really * In cases we don't know what to do with an object, we just store its bytes and send it again - we don't really
* have to know what it is. * have to know what it is.
*/ */
public class GenericPayload implements ObjectPayload { public class GenericPayload extends ObjectPayload {
private long stream; private long stream;
private byte[] data; private byte[] data;

View File

@ -27,7 +27,7 @@ import java.io.OutputStream;
/** /**
* Request for a public key. * Request for a public key.
*/ */
public class GetPubkey implements ObjectPayload { public class GetPubkey extends ObjectPayload {
private long stream; private long stream;
private byte[] ripe; private byte[] ripe;
private byte[] tag; private byte[] tag;

View File

@ -18,6 +18,7 @@ package ch.dissem.bitmessage.entity.payload;
import ch.dissem.bitmessage.utils.Decode; import ch.dissem.bitmessage.utils.Decode;
import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
@ -25,7 +26,7 @@ import java.io.OutputStream;
/** /**
* Used for person-to-person messages. * Used for person-to-person messages.
*/ */
public class Msg implements ObjectPayload { public class Msg extends ObjectPayload {
private long stream; private long stream;
private byte[] encrypted; private byte[] encrypted;
private UnencryptedMessage unencrypted; private UnencryptedMessage unencrypted;
@ -54,9 +55,29 @@ public class Msg implements ObjectPayload {
return stream; return stream;
} }
@Override
public boolean isSigned() {
return unencrypted != null;
}
@Override
public void writeBytesToSign(OutputStream out) throws IOException {
unencrypted.write(out, false);
}
@Override
public byte[] getSignature() {
return unencrypted.getSignature();
}
@Override
public void setSignature(byte[] signature) {
unencrypted.setSignature(signature);
}
public byte[] getEncrypted() { public byte[] getEncrypted() {
if (encrypted == null) { if (encrypted == null) {
// TODO // TODO encrypt
} }
return encrypted; return encrypted;
} }

View File

@ -16,13 +16,38 @@
package ch.dissem.bitmessage.entity.payload; package ch.dissem.bitmessage.entity.payload;
import ch.dissem.bitmessage.entity.ObjectMessage;
import ch.dissem.bitmessage.entity.Streamable; import ch.dissem.bitmessage.entity.Streamable;
import java.io.IOException;
import java.io.OutputStream;
/** /**
* The payload of an 'object' command. This is shared by the network. * The payload of an 'object' command. This is shared by the network.
*/ */
public interface ObjectPayload extends Streamable { public abstract class ObjectPayload implements Streamable {
ObjectType getType(); public abstract ObjectType getType();
long getStream(); public abstract long getStream();
public boolean isSigned() {
return false;
}
public void writeBytesToSign(OutputStream out) throws IOException{
// nothing to do
}
/**
* The ECDSA signature which, as of protocol v3, covers the object header starting with the time,
* appended with the data described in this table down to the extra_bytes. Therefore, this must
* be checked and set in the {@link ObjectMessage} object.
*/
public byte[] getSignature() {
return null;
}
public void setSignature(byte[] signature) {
// nothing to do
}
} }

View File

@ -16,6 +16,8 @@
package ch.dissem.bitmessage.entity.payload; package ch.dissem.bitmessage.entity.payload;
import ch.dissem.bitmessage.utils.Bytes;
import java.util.ArrayList; import java.util.ArrayList;
import static ch.dissem.bitmessage.utils.Security.ripemd160; import static ch.dissem.bitmessage.utils.Security.ripemd160;
@ -24,7 +26,7 @@ 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 abstract class Pubkey implements ObjectPayload { public abstract class Pubkey extends ObjectPayload {
public final static long LATEST_VERSION = 4; public final static long LATEST_VERSION = 4;
public abstract long getVersion(); public abstract long getVersion();
@ -34,7 +36,15 @@ public abstract class Pubkey implements ObjectPayload {
public abstract byte[] getEncryptionKey(); public abstract byte[] getEncryptionKey();
public byte[] getRipe() { public byte[] getRipe() {
return ripemd160(sha512(getSigningKey(), getEncryptionKey())); return Bytes.stripLeadingZeros(ripemd160(sha512(getSigningKey(), getEncryptionKey())));
}
protected byte[] add0x04(byte[] key){
if (key.length==65) return key;
byte[] result = new byte[65];
result[0] = 4;
System.arraycopy(key, 0, result, 1, 64);
return result;
} }
/** /**

View File

@ -16,7 +16,6 @@
package ch.dissem.bitmessage.entity.payload; package ch.dissem.bitmessage.entity.payload;
import ch.dissem.bitmessage.entity.Streamable;
import ch.dissem.bitmessage.utils.Encode; import ch.dissem.bitmessage.utils.Encode;
import java.io.IOException; import java.io.IOException;
@ -25,7 +24,7 @@ import java.io.OutputStream;
/** /**
* The unencrypted message to be sent by 'msg' or 'broadcast'. * The unencrypted message to be sent by 'msg' or 'broadcast'.
*/ */
public class UnencryptedMessage implements Streamable { public class UnencryptedMessage {
private final long addressVersion; private final long addressVersion;
private final long stream; private final long stream;
private final int behaviorBitfield; private final int behaviorBitfield;
@ -35,11 +34,7 @@ public class UnencryptedMessage implements Streamable {
private final long extraBytes; private final long extraBytes;
private final long encoding; private final long encoding;
private final byte[] message; private final byte[] message;
private final byte[] signature; private byte[] signature;
public long getStream() {
return stream;
}
private UnencryptedMessage(Builder builder) { private UnencryptedMessage(Builder builder) {
addressVersion = builder.addressVersion; addressVersion = builder.addressVersion;
@ -54,8 +49,19 @@ public class UnencryptedMessage implements Streamable {
signature = builder.signature; signature = builder.signature;
} }
@Override public long getStream() {
public void write(OutputStream os) throws IOException { return stream;
}
public byte[] getSignature() {
return signature;
}
public void setSignature(byte[] signature) {
this.signature = signature;
}
public void write(OutputStream os, boolean includeSignature) throws IOException {
Encode.varInt(addressVersion, os); Encode.varInt(addressVersion, os);
Encode.varInt(stream, os); Encode.varInt(stream, os);
Encode.int32(behaviorBitfield, os); Encode.int32(behaviorBitfield, os);
@ -66,9 +72,11 @@ public class UnencryptedMessage implements Streamable {
Encode.varInt(encoding, os); Encode.varInt(encoding, os);
Encode.varInt(message.length, os); Encode.varInt(message.length, os);
os.write(message); os.write(message);
if (includeSignature) {
Encode.varInt(signature.length, os); Encode.varInt(signature.length, os);
os.write(signature); os.write(signature);
} }
}
public static final class Builder { public static final class Builder {
private long addressVersion; private long addressVersion;

View File

@ -38,8 +38,8 @@ public class V2Pubkey extends Pubkey {
private V2Pubkey(Builder builder) { private V2Pubkey(Builder builder) {
stream = builder.streamNumber; stream = builder.streamNumber;
behaviorBitfield = builder.behaviorBitfield; behaviorBitfield = builder.behaviorBitfield;
publicSigningKey = builder.publicSigningKey; publicSigningKey = add0x04(builder.publicSigningKey);
publicEncryptionKey = builder.publicEncryptionKey; publicEncryptionKey = add0x04(builder.publicEncryptionKey);
} }
public static V2Pubkey read(InputStream is, long stream) throws IOException { public static V2Pubkey read(InputStream is, long stream) throws IOException {
@ -79,8 +79,8 @@ public class V2Pubkey extends Pubkey {
@Override @Override
public void write(OutputStream os) throws IOException { public void write(OutputStream os) throws IOException {
Encode.int32(behaviorBitfield, os); Encode.int32(behaviorBitfield, os);
os.write(publicSigningKey); os.write(publicSigningKey, 1, 64);
os.write(publicEncryptionKey); os.write(publicEncryptionKey, 1, 64);
} }
public static class Builder { public static class Builder {

View File

@ -34,8 +34,8 @@ public class V3Pubkey extends V2Pubkey {
protected V3Pubkey(Builder builder) { protected V3Pubkey(Builder builder) {
stream = builder.streamNumber; stream = builder.streamNumber;
behaviorBitfield = builder.behaviorBitfield; behaviorBitfield = builder.behaviorBitfield;
publicSigningKey = builder.publicSigningKey; publicSigningKey = add0x04(builder.publicSigningKey);
publicEncryptionKey = builder.publicEncryptionKey; publicEncryptionKey = add0x04(builder.publicEncryptionKey);
nonceTrialsPerByte = builder.nonceTrialsPerByte; nonceTrialsPerByte = builder.nonceTrialsPerByte;
extraBytes = builder.extraBytes; extraBytes = builder.extraBytes;
@ -56,12 +56,10 @@ public class V3Pubkey extends V2Pubkey {
} }
@Override @Override
public void write(OutputStream os) throws IOException { public void write(OutputStream out) throws IOException {
super.write(os); writeBytesToSign(out);
Encode.varInt(nonceTrialsPerByte, os); Encode.varInt(signature.length, out);
Encode.varInt(extraBytes, os); out.write(signature);
Encode.varInt(signature.length, os);
os.write(signature);
} }
@Override @Override
@ -69,7 +67,27 @@ public class V3Pubkey extends V2Pubkey {
return 3; return 3;
} }
public static class Builder extends V2Pubkey.Builder { public boolean isSigned() {
return true;
}
public void writeBytesToSign(OutputStream out) throws IOException {
super.write(out);
Encode.varInt(nonceTrialsPerByte, out);
Encode.varInt(extraBytes, out);
}
@Override
public byte[] getSignature() {
return signature;
}
@Override
public void setSignature(byte[] signature) {
this.signature = signature;
}
public static class Builder {
private long streamNumber; private long streamNumber;
private int behaviorBitfield; private int behaviorBitfield;
private byte[] publicSigningKey; private byte[] publicSigningKey;

View File

@ -26,7 +26,7 @@ import java.io.OutputStream;
* Users who are subscribed to the sending address will see the message appear in their inbox. * Users who are subscribed to the sending address will see the message appear in their inbox.
* Broadcasts are version 4 or 5. * Broadcasts are version 4 or 5.
*/ */
public class V4Broadcast implements Broadcast { public class V4Broadcast extends Broadcast {
private long stream; private long stream;
private byte[] encrypted; private byte[] encrypted;
private UnencryptedMessage unencrypted; private UnencryptedMessage unencrypted;
@ -54,6 +54,21 @@ public class V4Broadcast implements Broadcast {
return encrypted; return encrypted;
} }
@Override
public void writeBytesToSign(OutputStream out) throws IOException {
unencrypted.write(out, false);
}
@Override
public byte[] getSignature() {
return unencrypted.getSignature();
}
@Override
public void setSignature(byte[] signature) {
unencrypted.setSignature(signature);
}
@Override @Override
public void write(OutputStream stream) throws IOException { public void write(OutputStream stream) throws IOException {
stream.write(getEncrypted()); stream.write(getEncrypted());

View File

@ -85,4 +85,17 @@ public class V4Pubkey extends Pubkey {
public byte[] getEncryptionKey() { public byte[] getEncryptionKey() {
return decrypted.getEncryptionKey(); return decrypted.getEncryptionKey();
} }
@Override
public byte[] getSignature() {
if (decrypted != null)
return decrypted.getSignature();
else
return null;
}
@Override
public void setSignature(byte[] signature) {
decrypted.setSignature(signature);
}
} }

View File

@ -41,4 +41,9 @@ public class AccessCounter {
public int length() { public int length() {
return count; return count;
} }
@Override
public String toString() {
return String.valueOf(count);
}
} }

View File

@ -19,6 +19,8 @@ package ch.dissem.bitmessage.utils;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import static java.util.Arrays.copyOfRange;
/** /**
* Base58 encoder and decoder * Base58 encoder and decoder
*/ */
@ -120,7 +122,6 @@ public class Base58 {
while (j < temp.length && temp[j] == 0) { while (j < temp.length && temp[j] == 0) {
++j; ++j;
} }
return copyOfRange(temp, j - zeroCount, temp.length); return copyOfRange(temp, j - zeroCount, temp.length);
} }
@ -157,11 +158,4 @@ public class Base58 {
return (byte) remainder; return (byte) remainder;
} }
private static byte[] copyOfRange(byte[] source, int from, int to) {
byte[] range = new byte[to - from];
System.arraycopy(source, from, range, 0, range.length);
return range;
}
} }

View File

@ -122,4 +122,15 @@ public class Bytes {
} }
throw new IllegalArgumentException("'" + c + "' is not a valid hex value"); throw new IllegalArgumentException("'" + c + "' is not a valid hex value");
} }
public static byte[] stripLeadingZeros(byte[] bytes) {
for (int i = 0; i < bytes.length; i++) {
if (bytes[i] != 0) {
byte[] result = new byte[bytes.length - i];
System.arraycopy(bytes, i, result, i, bytes.length - i);
return result;
}
}
return new byte[0];
}
} }

View File

@ -20,18 +20,19 @@ import ch.dissem.bitmessage.entity.ObjectMessage;
import ch.dissem.bitmessage.entity.payload.Pubkey; import ch.dissem.bitmessage.entity.payload.Pubkey;
import ch.dissem.bitmessage.factory.Factory; 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.jce.ECNamedCurveTable;
import org.bouncycastle.asn1.x9.X9ECParameters; import org.bouncycastle.jce.ECPointUtil;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECNamedCurveSpec;
import org.bouncycastle.util.encoders.Hex;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.IOException; import java.io.IOException;
import java.math.BigInteger; import java.math.BigInteger;
import java.security.GeneralSecurityException; import java.security.*;
import java.security.MessageDigest; import java.security.interfaces.ECPublicKey;
import java.security.SecureRandom; import java.security.spec.*;
/** /**
* Provides some methods to help with hashing and encryption. * Provides some methods to help with hashing and encryption.
@ -40,8 +41,7 @@ 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 ECGenParameterSpec EC_PARAMETERS = new ECGenParameterSpec("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());
@ -69,6 +69,12 @@ public class Security {
return hash("RIPEMD160", data); return hash("RIPEMD160", data);
} }
public static byte[] doubleSha256(byte[] data, int length) {
MessageDigest mda = md("SHA-256");
mda.update(data, 0, length);
return mda.digest(mda.digest());
}
public static byte[] sha1(byte[]... data) { public static byte[] sha1(byte[]... data) {
return hash("SHA-1", data); return hash("SHA-1", data);
} }
@ -134,15 +140,48 @@ public class Security {
public static Pubkey createPubkey(long version, long stream, byte[] privateSigningKey, byte[] privateEncryptionKey, public static Pubkey createPubkey(long version, long stream, byte[] privateSigningKey, byte[] privateEncryptionKey,
long nonceTrialsPerByte, long extraBytes, Pubkey.Feature... features) { long nonceTrialsPerByte, long extraBytes, Pubkey.Feature... features) {
byte[] publicSigningKey = EC_PARAMETERS.getG().multiply(keyToBigInt(privateSigningKey)).getEncoded(false); // ECPublicKeySpec pubKey = new ECPublicKeySpec(
byte[] publicEncryptionKey = EC_PARAMETERS.getG().multiply(keyToBigInt(privateEncryptionKey)).getEncoded(false); // ECPointUtil.decodePoint(curve, Hex.decode("025b6dc53bc61a2548ffb0f671472de6c9521a9d2d2534e65abfcbd5fe0c70")), // Q
return Factory.createPubkey(version, stream, // publicSigningKey, publicEncryptionKey, // EC_PARAMETERS);
Bytes.subArray(publicSigningKey, 1, publicSigningKey.length - 1), // byte[] publicSigningKey = EC_PARAMETERS.getG().multiply(keyToBigInt(privateSigningKey)).getEncoded(false);
Bytes.subArray(publicEncryptionKey, 1, publicEncryptionKey.length - 1), // byte[] publicEncryptionKey = EC_PARAMETERS.getG().multiply(keyToBigInt(privateEncryptionKey)).getEncoded(false);
nonceTrialsPerByte, extraBytes, features); // return Factory.createPubkey(version, stream, // publicSigningKey, publicEncryptionKey,
// Bytes.subArray(publicSigningKey, 1, publicSigningKey.length - 1),
// Bytes.subArray(publicEncryptionKey, 1, publicEncryptionKey.length - 1),
// nonceTrialsPerByte, extraBytes, features);
return null;
}
private static byte[] createPublicKey(byte[] privateKey){
// ECParameterSpec spec = new ECNamedCurveSpec(ECNamedCurveTable.getParameterSpec("prime239v1"));
// ECPrivateKeySpec priKey = new ECPrivateKeySpec(
// new BigInteger("876300101507107567501066130761671078357010671067781776716671676178726717"), // d
// spec);
// ECPublicKeySpec pubKey = new ECPublicKeySpec(
// ECPointUtil.decodePoint(
// spec.getCurve(),
// Hex.decode("025b6dc53bc61a2548ffb0f671472de6c9521a9d2d2534e65abfcbd5fe0c70")), // Q
// spec);
return null;
} }
private static BigInteger keyToBigInt(byte[] key) { private static BigInteger keyToBigInt(byte[] key) {
return new BigInteger(1, key); return new BigInteger(1, key);
} }
public static boolean isSignatureValid(byte[] bytesToSign, byte[] signature, Pubkey pubkey) {
// ECPoint W = EC_CURVE.getCurve().decodePoint(pubkey.getSigningKey()); // TODO: probably this needs 0x04 added
try {
ECParameterSpec param = null;
// KeySpec keySpec = new ECPublicKeySpec(W,param);;
// PublicKey publicKey = KeyFactory.getInstance("ECDSA", "BC").generatePublic(keySpec);
Signature sig = Signature.getInstance("ECDSA", "BC");
// sig.initVerify(publicKey);
sig.update(bytesToSign);
return sig.verify(signature);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
} }

View File

@ -16,16 +16,26 @@
package ch.dissem.bitmessage.entity; package ch.dissem.bitmessage.entity;
import ch.dissem.bitmessage.entity.payload.ObjectType;
import ch.dissem.bitmessage.entity.payload.Pubkey;
import ch.dissem.bitmessage.entity.payload.V3Pubkey; import ch.dissem.bitmessage.entity.payload.V3Pubkey;
import ch.dissem.bitmessage.entity.valueobject.PrivateKey; import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
import ch.dissem.bitmessage.inventory.JdbcInventory;
import ch.dissem.bitmessage.utils.*; import ch.dissem.bitmessage.utils.*;
import org.junit.Test; import org.junit.Test;
import java.io.IOException; import java.io.IOException;
import java.util.List;
import static org.junit.Assert.*; import static org.junit.Assert.*;
public class BitmessageAddressTest { public class BitmessageAddressTest {
@Test
public void ensureBase58DecodesCorrectly() {
assertHexEquals("800C28FCA386C7A227600B2FE50B7CAE11EC86D3BF1FBE471BE89827E19D72AA1D507A5B8D",
Base58.decode("5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ"));
}
@Test @Test
public void ensureAddressStaysSame() { public void ensureAddressStaysSame() {
String address = "BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ"; String address = "BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ";
@ -64,6 +74,13 @@ public class BitmessageAddressTest {
assertArrayEquals(Bytes.fromHex("007402be6e76c3cb87caa946d0c003a3d4d8e1d5"), address.getRipe()); assertArrayEquals(Bytes.fromHex("007402be6e76c3cb87caa946d0c003a3d4d8e1d5"), address.getRipe());
} }
@Test
public void testV2PubkeyImport() throws IOException {
ObjectMessage object = TestUtils.loadObjectMessage(2, "V2Pubkey.payload");
V3Pubkey pubkey = (V3Pubkey) object.getPayload();
BitmessageAddress address = new BitmessageAddress("BM-2DAjcCFrqFrp88FUxExhJ9kPqHdunQmiyn"); // TODO: find address
address.setPubkey(pubkey);
}
@Test @Test
public void testV3PubkeyImport() throws IOException { public void testV3PubkeyImport() throws IOException {
ObjectMessage object = TestUtils.loadObjectMessage(3, "V3Pubkey.payload"); ObjectMessage object = TestUtils.loadObjectMessage(3, "V3Pubkey.payload");
@ -73,7 +90,7 @@ public class BitmessageAddressTest {
} }
@Test @Test
public void testV3Import() { public void testV3Import() throws IOException {
String address_string = "BM-2DAjcCFrqFrp88FUxExhJ9kPqHdunQmiyn"; String address_string = "BM-2DAjcCFrqFrp88FUxExhJ9kPqHdunQmiyn";
assertEquals(3, new BitmessageAddress(address_string).getVersion()); assertEquals(3, new BitmessageAddress(address_string).getVersion());
assertEquals(1, new BitmessageAddress(address_string).getStream()); assertEquals(1, new BitmessageAddress(address_string).getStream());
@ -83,24 +100,31 @@ public class BitmessageAddressTest {
System.out.println("\n\n" + Strings.hex(privsigningkey) + "\n\n"); System.out.println("\n\n" + Strings.hex(privsigningkey) + "\n\n");
// privsigningkey = Bytes.expand(privsigningkey, 32);
// privencryptionkey = Bytes.expand(privencryptionkey, 32);
BitmessageAddress address = new BitmessageAddress(new PrivateKey(privsigningkey, privencryptionkey, BitmessageAddress address = new BitmessageAddress(new PrivateKey(privsigningkey, privencryptionkey,
Security.createPubkey(3, 1, privsigningkey, privencryptionkey, 320, 14000))); Security.createPubkey(3, 1, privsigningkey, privencryptionkey, 320, 14000)));
assertEquals(address_string, address.getAddress()); assertEquals(address_string, address.getAddress());
} }
private byte[] getSecret(String walletImportFormat) { @Test
byte[] bytes = Base58.decode("5KU2gbe9u4rKJ8PHYb1rvwMnZnAJj4gtV5GLwoYckeYzygWUzB9"); public void testGetSecret() throws IOException {
assertEquals(37, bytes.length); assertHexEquals("040C28FCA386C7A227600B2FE50B7CAE11EC86D3BF1FBE471BE89827E19D72AA1D",
assertEquals((byte) 0x80, bytes[0]); getSecret("5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ"));
byte[] checksum = Bytes.subArray(bytes, bytes.length - 4, 4); }
byte[] secret = Bytes.subArray(bytes, 1, 32);
// assertArrayEquals("Checksum failed", checksum, Bytes.subArray(Security.doubleSha512(new byte[]{(byte) 0x80}, secret, new byte[]{0x01}), 0, 4)); private byte[] getSecret(String walletImportFormat) throws IOException {
byte[] bytes = Base58.decode(walletImportFormat);
if (bytes[0] != (byte) 0x80)
throw new IOException("Unknown format: 0x80 expected as first byte, but secret " + walletImportFormat + " was " + bytes[0]);
if (bytes.length != 37)
throw new IOException("Unknown format: 37 bytes expected, but secret " + walletImportFormat + " was " + bytes.length + " long");
byte[] hash = Security.doubleSha256(bytes, 33);
for (int i = 0; i < 4; i++) {
if (hash[i] != bytes[33 + i]) throw new IOException("Hash check failed for secret " + walletImportFormat);
}
byte[] result = new byte[33]; byte[] result = new byte[33];
result[0] = 0x04; result[0] = 0x04;
System.arraycopy(secret, 0, result, 1, secret.length); System.arraycopy(bytes, 1, result, 1, 32);
return result; return result;
} }
@ -113,4 +137,8 @@ public class BitmessageAddressTest {
Security.createPubkey(3, 1, privsigningkey, privencryptionkey, 320, 14000))); Security.createPubkey(3, 1, privsigningkey, privencryptionkey, 320, 14000)));
assertEquals("BM-2cV5f9EpzaYARxtoruSpa6pDoucSf9ZNke", address.getAddress()); assertEquals("BM-2cV5f9EpzaYARxtoruSpa6pDoucSf9ZNke", address.getAddress());
} }
private void assertHexEquals(String hex, byte[] bytes) {
assertEquals(hex.toLowerCase(), Strings.hex(bytes).toString().toLowerCase());
}
} }