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.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
public byte[] getPayloadBytesWithoutNonce() throws IOException {
|
||||||
if (payloadBytes == null) {
|
if (payloadBytes == null) {
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
Encode.int64(expiresTime, out);
|
writeHeaderWithoutNonce(out);
|
||||||
Encode.int32(objectType, out);
|
|
||||||
Encode.varInt(version, out);
|
|
||||||
Encode.varInt(stream, out);
|
|
||||||
payload.write(out);
|
payload.write(out);
|
||||||
payloadBytes = out.toByteArray();
|
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.
|
* 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();
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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,8 +72,10 @@ 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);
|
||||||
Encode.varInt(signature.length, os);
|
if (includeSignature) {
|
||||||
os.write(signature);
|
Encode.varInt(signature.length, os);
|
||||||
|
os.write(signature);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class Builder {
|
public static final class Builder {
|
||||||
|
@ -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 {
|
||||||
|
@ -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;
|
||||||
|
@ -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());
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -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];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user