diff --git a/demo/src/main/java/ch/dissem/bitmessage/demo/Main.java b/demo/src/main/java/ch/dissem/bitmessage/demo/Main.java index 76f3642..5c5085d 100644 --- a/demo/src/main/java/ch/dissem/bitmessage/demo/Main.java +++ b/demo/src/main/java/ch/dissem/bitmessage/demo/Main.java @@ -21,9 +21,13 @@ import ch.dissem.bitmessage.entity.ObjectMessage; import ch.dissem.bitmessage.entity.payload.ObjectType; import ch.dissem.bitmessage.entity.payload.Pubkey; 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.LoggerFactory; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.Arrays; import java.util.List; @@ -73,10 +77,13 @@ public class Main { // LOG.info("Shutting down client"); // ctx.getNetworkHandler().stop(); + List objects = new JdbcInventory().getObjects(address.getStream(), address.getVersion(), ObjectType.PUBKEY); System.out.println("Address version: " + address.getVersion()); System.out.println("Address stream: " + address.getStream()); for (ObjectMessage o : objects) { +// if (!o.isSignatureValid()) System.out.println("Invalid signature."); +// System.out.println(o.getPayload().getSignature().length); Pubkey pubkey = (Pubkey) o.getPayload(); if (Arrays.equals(address.getRipe(), pubkey.getRipe())) System.out.println("Pubkey found!"); @@ -85,10 +92,26 @@ public class Main { System.out.println(address); } catch (Exception ignore) { 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())) { 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); + } + } } diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.java b/domain/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.java index 0abb03f..0e7228c 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.java +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.java @@ -18,7 +18,10 @@ package ch.dissem.bitmessage.entity; import ch.dissem.bitmessage.entity.payload.Pubkey; 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.ByteArrayOutputStream; @@ -58,7 +61,7 @@ public class BitmessageAddress { AccessCounter counter = new AccessCounter(); this.version = 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); this.address = generateAddress(); } catch (IOException e) { @@ -81,9 +84,7 @@ public class BitmessageAddress { os.write(ripe); byte[] checksum = Security.doubleSha512(os.toByteArray()); - for (int i = 0; i < 4; i++) { - os.write(checksum[i]); - } + os.write(checksum, 0, 4); return "BM-" + Base58.encode(os.toByteArray()); } catch (IOException e) { throw new RuntimeException(e); @@ -103,7 +104,8 @@ public class BitmessageAddress { } 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; } diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.java b/domain/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.java index e0f6864..67cea53 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.java +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.java @@ -17,7 +17,9 @@ package ch.dissem.bitmessage.entity; 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.PrivateKey; import ch.dissem.bitmessage.utils.Bytes; import ch.dissem.bitmessage.utils.Encode; 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)); } + 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 public void write(OutputStream out) throws IOException { out.write(nonce); out.write(getPayloadBytesWithoutNonce()); } + private void writeHeaderWithoutNonce(OutputStream out) throws IOException { + Encode.int64(expiresTime, out); + Encode.int32(objectType, out); + Encode.varInt(version, out); + Encode.varInt(stream, out); + } + public byte[] getPayloadBytesWithoutNonce() throws IOException { if (payloadBytes == null) { ByteArrayOutputStream out = new ByteArrayOutputStream(); - Encode.int64(expiresTime, out); - Encode.int32(objectType, out); - Encode.varInt(version, out); - Encode.varInt(stream, out); + writeHeaderWithoutNonce(out); payload.write(out); payloadBytes = out.toByteArray(); } diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Broadcast.java b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Broadcast.java index eb4890d..1630caf 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Broadcast.java +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Broadcast.java @@ -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. * Broadcasts are version 4 or 5. */ -public interface Broadcast extends ObjectPayload { - byte[] getEncrypted(); +public abstract class Broadcast extends ObjectPayload { + public abstract byte[] getEncrypted(); } diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/GenericPayload.java b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/GenericPayload.java index dbd3dd6..cd94c6f 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/GenericPayload.java +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/GenericPayload.java @@ -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 * have to know what it is. */ -public class GenericPayload implements ObjectPayload { +public class GenericPayload extends ObjectPayload { private long stream; private byte[] data; diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/GetPubkey.java b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/GetPubkey.java index 0b1fce8..514eae5 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/GetPubkey.java +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/GetPubkey.java @@ -27,7 +27,7 @@ import java.io.OutputStream; /** * Request for a public key. */ -public class GetPubkey implements ObjectPayload { +public class GetPubkey extends ObjectPayload { private long stream; private byte[] ripe; private byte[] tag; diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Msg.java b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Msg.java index 94b8dd1..c0c1c6b 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Msg.java +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Msg.java @@ -18,6 +18,7 @@ package ch.dissem.bitmessage.entity.payload; import ch.dissem.bitmessage.utils.Decode; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -25,7 +26,7 @@ import java.io.OutputStream; /** * Used for person-to-person messages. */ -public class Msg implements ObjectPayload { +public class Msg extends ObjectPayload { private long stream; private byte[] encrypted; private UnencryptedMessage unencrypted; @@ -54,9 +55,29 @@ public class Msg implements ObjectPayload { 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() { if (encrypted == null) { - // TODO + // TODO encrypt } return encrypted; } diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectPayload.java b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectPayload.java index 67bc856..690e0ed 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectPayload.java +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectPayload.java @@ -16,13 +16,38 @@ package ch.dissem.bitmessage.entity.payload; +import ch.dissem.bitmessage.entity.ObjectMessage; 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. */ -public interface ObjectPayload extends Streamable { - ObjectType getType(); +public abstract class ObjectPayload implements Streamable { + 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 + } } diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Pubkey.java b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Pubkey.java index 828b563..eb6aad0 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Pubkey.java +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Pubkey.java @@ -16,6 +16,8 @@ package ch.dissem.bitmessage.entity.payload; +import ch.dissem.bitmessage.utils.Bytes; + import java.util.ArrayList; 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 abstract class Pubkey implements ObjectPayload { +public abstract class Pubkey extends ObjectPayload { public final static long LATEST_VERSION = 4; public abstract long getVersion(); @@ -34,7 +36,15 @@ public abstract class Pubkey implements ObjectPayload { public abstract byte[] getEncryptionKey(); 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; } /** diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/UnencryptedMessage.java b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/UnencryptedMessage.java index 3abd04f..3cb47f1 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/UnencryptedMessage.java +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/UnencryptedMessage.java @@ -16,7 +16,6 @@ package ch.dissem.bitmessage.entity.payload; -import ch.dissem.bitmessage.entity.Streamable; import ch.dissem.bitmessage.utils.Encode; import java.io.IOException; @@ -25,7 +24,7 @@ import java.io.OutputStream; /** * 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 stream; private final int behaviorBitfield; @@ -35,11 +34,7 @@ public class UnencryptedMessage implements Streamable { private final long extraBytes; private final long encoding; private final byte[] message; - private final byte[] signature; - - public long getStream() { - return stream; - } + private byte[] signature; private UnencryptedMessage(Builder builder) { addressVersion = builder.addressVersion; @@ -54,8 +49,19 @@ public class UnencryptedMessage implements Streamable { signature = builder.signature; } - @Override - public void write(OutputStream os) throws IOException { + public long getStream() { + 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(stream, os); Encode.int32(behaviorBitfield, os); @@ -66,8 +72,10 @@ public class UnencryptedMessage implements Streamable { Encode.varInt(encoding, os); Encode.varInt(message.length, os); os.write(message); - Encode.varInt(signature.length, os); - os.write(signature); + if (includeSignature) { + Encode.varInt(signature.length, os); + os.write(signature); + } } public static final class Builder { diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V2Pubkey.java b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V2Pubkey.java index 0411822..f99c04d 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V2Pubkey.java +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V2Pubkey.java @@ -38,8 +38,8 @@ public class V2Pubkey extends Pubkey { private V2Pubkey(Builder builder) { stream = builder.streamNumber; behaviorBitfield = builder.behaviorBitfield; - publicSigningKey = builder.publicSigningKey; - publicEncryptionKey = builder.publicEncryptionKey; + publicSigningKey = add0x04(builder.publicSigningKey); + publicEncryptionKey = add0x04(builder.publicEncryptionKey); } public static V2Pubkey read(InputStream is, long stream) throws IOException { @@ -79,8 +79,8 @@ public class V2Pubkey extends Pubkey { @Override public void write(OutputStream os) throws IOException { Encode.int32(behaviorBitfield, os); - os.write(publicSigningKey); - os.write(publicEncryptionKey); + os.write(publicSigningKey, 1, 64); + os.write(publicEncryptionKey, 1, 64); } public static class Builder { diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V3Pubkey.java b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V3Pubkey.java index b30c25e..1ce9340 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V3Pubkey.java +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V3Pubkey.java @@ -34,8 +34,8 @@ public class V3Pubkey extends V2Pubkey { protected V3Pubkey(Builder builder) { stream = builder.streamNumber; behaviorBitfield = builder.behaviorBitfield; - publicSigningKey = builder.publicSigningKey; - publicEncryptionKey = builder.publicEncryptionKey; + publicSigningKey = add0x04(builder.publicSigningKey); + publicEncryptionKey = add0x04(builder.publicEncryptionKey); nonceTrialsPerByte = builder.nonceTrialsPerByte; extraBytes = builder.extraBytes; @@ -56,12 +56,10 @@ public class V3Pubkey extends V2Pubkey { } @Override - public void write(OutputStream os) throws IOException { - super.write(os); - Encode.varInt(nonceTrialsPerByte, os); - Encode.varInt(extraBytes, os); - Encode.varInt(signature.length, os); - os.write(signature); + public void write(OutputStream out) throws IOException { + writeBytesToSign(out); + Encode.varInt(signature.length, out); + out.write(signature); } @Override @@ -69,7 +67,27 @@ public class V3Pubkey extends V2Pubkey { 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 int behaviorBitfield; private byte[] publicSigningKey; diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V4Broadcast.java b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V4Broadcast.java index af5b0c9..e529b38 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V4Broadcast.java +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V4Broadcast.java @@ -26,7 +26,7 @@ import java.io.OutputStream; * Users who are subscribed to the sending address will see the message appear in their inbox. * Broadcasts are version 4 or 5. */ -public class V4Broadcast implements Broadcast { +public class V4Broadcast extends Broadcast { private long stream; private byte[] encrypted; private UnencryptedMessage unencrypted; @@ -54,6 +54,21 @@ public class V4Broadcast implements Broadcast { 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 public void write(OutputStream stream) throws IOException { stream.write(getEncrypted()); diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V4Pubkey.java b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V4Pubkey.java index db1d3f5..fef778a 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V4Pubkey.java +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V4Pubkey.java @@ -85,4 +85,17 @@ public class V4Pubkey extends Pubkey { public byte[] 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); + } } diff --git a/domain/src/main/java/ch/dissem/bitmessage/utils/AccessCounter.java b/domain/src/main/java/ch/dissem/bitmessage/utils/AccessCounter.java index aa662fa..5143fc8 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/utils/AccessCounter.java +++ b/domain/src/main/java/ch/dissem/bitmessage/utils/AccessCounter.java @@ -41,4 +41,9 @@ public class AccessCounter { public int length() { return count; } + + @Override + public String toString() { + return String.valueOf(count); + } } diff --git a/domain/src/main/java/ch/dissem/bitmessage/utils/Base58.java b/domain/src/main/java/ch/dissem/bitmessage/utils/Base58.java index 658a5b1..c76a4e1 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/utils/Base58.java +++ b/domain/src/main/java/ch/dissem/bitmessage/utils/Base58.java @@ -19,6 +19,8 @@ package ch.dissem.bitmessage.utils; import java.io.UnsupportedEncodingException; +import static java.util.Arrays.copyOfRange; + /** * Base58 encoder and decoder */ @@ -120,7 +122,6 @@ public class Base58 { while (j < temp.length && temp[j] == 0) { ++j; } - return copyOfRange(temp, j - zeroCount, temp.length); } @@ -157,11 +158,4 @@ public class Base58 { 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; - } } \ No newline at end of file diff --git a/domain/src/main/java/ch/dissem/bitmessage/utils/Bytes.java b/domain/src/main/java/ch/dissem/bitmessage/utils/Bytes.java index 07b4e3c..4b3e888 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/utils/Bytes.java +++ b/domain/src/main/java/ch/dissem/bitmessage/utils/Bytes.java @@ -122,4 +122,15 @@ public class Bytes { } 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]; + } } diff --git a/domain/src/main/java/ch/dissem/bitmessage/utils/Security.java b/domain/src/main/java/ch/dissem/bitmessage/utils/Security.java index 0cef4ff..2a64062 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/utils/Security.java +++ b/domain/src/main/java/ch/dissem/bitmessage/utils/Security.java @@ -20,18 +20,19 @@ 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.ECNamedCurveTable; +import org.bouncycastle.jce.ECPointUtil; 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.LoggerFactory; import java.io.IOException; import java.math.BigInteger; -import java.security.GeneralSecurityException; -import java.security.MessageDigest; -import java.security.SecureRandom; +import java.security.*; +import java.security.interfaces.ECPublicKey; +import java.security.spec.*; /** * 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); 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()); + private static final ECGenParameterSpec EC_PARAMETERS = new ECGenParameterSpec("secp256k1"); static { java.security.Security.addProvider(new BouncyCastleProvider()); @@ -69,6 +69,12 @@ public class Security { 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) { return hash("SHA-1", data); } @@ -134,15 +140,48 @@ public class Security { public static Pubkey createPubkey(long version, long stream, 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(version, stream, // publicSigningKey, publicEncryptionKey, - Bytes.subArray(publicSigningKey, 1, publicSigningKey.length - 1), - Bytes.subArray(publicEncryptionKey, 1, publicEncryptionKey.length - 1), - nonceTrialsPerByte, extraBytes, features); +// ECPublicKeySpec pubKey = new ECPublicKeySpec( +// ECPointUtil.decodePoint(curve, Hex.decode("025b6dc53bc61a2548ffb0f671472de6c9521a9d2d2534e65abfcbd5fe0c70")), // Q +// EC_PARAMETERS); +// byte[] publicSigningKey = EC_PARAMETERS.getG().multiply(keyToBigInt(privateSigningKey)).getEncoded(false); +// byte[] publicEncryptionKey = EC_PARAMETERS.getG().multiply(keyToBigInt(privateEncryptionKey)).getEncoded(false); +// 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) { 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); + } + } } diff --git a/domain/src/test/java/ch/dissem/bitmessage/entity/BitmessageAddressTest.java b/domain/src/test/java/ch/dissem/bitmessage/entity/BitmessageAddressTest.java index dcbc766..ea1022f 100644 --- a/domain/src/test/java/ch/dissem/bitmessage/entity/BitmessageAddressTest.java +++ b/domain/src/test/java/ch/dissem/bitmessage/entity/BitmessageAddressTest.java @@ -16,16 +16,26 @@ 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.valueobject.PrivateKey; +import ch.dissem.bitmessage.inventory.JdbcInventory; import ch.dissem.bitmessage.utils.*; import org.junit.Test; import java.io.IOException; +import java.util.List; import static org.junit.Assert.*; public class BitmessageAddressTest { + @Test + public void ensureBase58DecodesCorrectly() { + assertHexEquals("800C28FCA386C7A227600B2FE50B7CAE11EC86D3BF1FBE471BE89827E19D72AA1D507A5B8D", + Base58.decode("5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ")); + } + @Test public void ensureAddressStaysSame() { String address = "BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ"; @@ -64,6 +74,13 @@ public class BitmessageAddressTest { 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 public void testV3PubkeyImport() throws IOException { ObjectMessage object = TestUtils.loadObjectMessage(3, "V3Pubkey.payload"); @@ -73,7 +90,7 @@ public class BitmessageAddressTest { } @Test - public void testV3Import() { + public void testV3Import() throws IOException { String address_string = "BM-2DAjcCFrqFrp88FUxExhJ9kPqHdunQmiyn"; assertEquals(3, new BitmessageAddress(address_string).getVersion()); assertEquals(1, new BitmessageAddress(address_string).getStream()); @@ -83,24 +100,31 @@ public class BitmessageAddressTest { 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, Security.createPubkey(3, 1, privsigningkey, privencryptionkey, 320, 14000))); assertEquals(address_string, address.getAddress()); } - private byte[] getSecret(String walletImportFormat) { - byte[] bytes = Base58.decode("5KU2gbe9u4rKJ8PHYb1rvwMnZnAJj4gtV5GLwoYckeYzygWUzB9"); - assertEquals(37, bytes.length); - assertEquals((byte) 0x80, bytes[0]); - 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)); + @Test + public void testGetSecret() throws IOException { + assertHexEquals("040C28FCA386C7A227600B2FE50B7CAE11EC86D3BF1FBE471BE89827E19D72AA1D", + getSecret("5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ")); + } + + 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]; result[0] = 0x04; - System.arraycopy(secret, 0, result, 1, secret.length); + System.arraycopy(bytes, 1, result, 1, 32); return result; } @@ -113,4 +137,8 @@ public class BitmessageAddressTest { Security.createPubkey(3, 1, privsigningkey, privencryptionkey, 320, 14000))); assertEquals("BM-2cV5f9EpzaYARxtoruSpa6pDoucSf9ZNke", address.getAddress()); } + + private void assertHexEquals(String hex, byte[] bytes) { + assertEquals(hex.toLowerCase(), Strings.hex(bytes).toString().toLowerCase()); + } }