Fixed some stuff and broke some other - my goal is to solely use the java.security API
This commit is contained in:
parent
a65907f13b
commit
fde6398156
@ -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<ObjectMessage> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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());
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -41,4 +41,9 @@ public class AccessCounter {
|
||||
public int length() {
|
||||
return count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.valueOf(count);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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];
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user