From f70c15da382294e2e5ce46704d5e179bde26a881 Mon Sep 17 00:00:00 2001 From: Christian Basler Date: Thu, 7 Apr 2016 23:01:16 +0200 Subject: [PATCH] Some code for deterministic addresses - needs tests --- .../dissem/bitmessage/BitmessageContext.java | 13 +++ .../bitmessage/entity/BitmessageAddress.java | 14 +++ .../entity/valueobject/InventoryVector.java | 4 +- .../bitmessage/entity/valueobject/Label.java | 2 + .../entity/valueobject/NetworkAddress.java | 2 + .../entity/valueobject/PrivateKey.java | 90 ++++++++++++++----- .../ch/dissem/bitmessage/utils/Base58.java | 2 +- 7 files changed, 104 insertions(+), 23 deletions(-) diff --git a/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java b/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java index 6348bcc..8377e22 100644 --- a/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java +++ b/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java @@ -31,6 +31,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.net.InetAddress; +import java.util.List; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.*; @@ -128,6 +129,18 @@ public class BitmessageContext { return chan; } + public List createDeterministicAddresses( + String passphrase, int numberOfAddresses, int version, int stream, boolean shorter) { + List result = BitmessageAddress.deterministic( + passphrase, numberOfAddresses, version, stream, shorter); + for (int i = 0; i < result.size(); i++) { + BitmessageAddress address = result.get(i); + address.setAlias(passphrase + " (" + (i + 1) + ")"); + ctx.getAddressRepository().save(address); + } + return result; + } + public void broadcast(final BitmessageAddress from, final String subject, final String message) { Plaintext msg = new Plaintext.Builder(BROADCAST) .from(from) diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.java b/core/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.java index 229da14..2458854 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.java +++ b/core/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.java @@ -29,7 +29,9 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.Serializable; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.Objects; import static ch.dissem.bitmessage.utils.Decode.bytes; @@ -41,6 +43,8 @@ import static ch.dissem.bitmessage.utils.Singleton.security; * holding private keys. */ public class BitmessageAddress implements Serializable { + private static final long serialVersionUID = 2386328540805994064L; + private final long version; private final long stream; private final byte[] ripe; @@ -115,6 +119,16 @@ public class BitmessageAddress implements Serializable { return result; } + public static List deterministic(String passphrase, int numberOfAddresses, + int version, int stream, boolean shorter) { + List result = new ArrayList<>(numberOfAddresses); + List privateKeys = PrivateKey.deterministic(passphrase, numberOfAddresses, version, stream, shorter); + for (PrivateKey pk : privateKeys) { + result.add(new BitmessageAddress(pk)); + } + return result; + } + public BitmessageAddress(PrivateKey privateKey) { this(privateKey.getPubkey()); this.privateKey = privateKey; diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/InventoryVector.java b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/InventoryVector.java index fc67422..127b90a 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/InventoryVector.java +++ b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/InventoryVector.java @@ -25,6 +25,8 @@ import java.io.Serializable; import java.util.Arrays; public class InventoryVector implements Streamable, Serializable { + private static final long serialVersionUID = -7349009673063348719L; + /** * Hash of the object */ @@ -42,7 +44,7 @@ public class InventoryVector implements Streamable, Serializable { @Override public int hashCode() { - return hash != null ? Arrays.hashCode(hash) : 0; + return hash == null ? 0 : Arrays.hashCode(hash); } public byte[] getHash() { diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/Label.java b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/Label.java index 02f0384..facbe55 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/Label.java +++ b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/Label.java @@ -20,6 +20,8 @@ import java.io.Serializable; import java.util.Objects; public class Label implements Serializable { + private static final long serialVersionUID = 831782893630994914L; + private Object id; private String label; private Type type; diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/NetworkAddress.java b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/NetworkAddress.java index 0637da9..d0324a4 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/NetworkAddress.java +++ b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/NetworkAddress.java @@ -31,6 +31,8 @@ import java.util.Arrays; * A node's address. It's written in IPv6 format. */ public class NetworkAddress implements Streamable { + private static final long serialVersionUID = 2500120578167100300L; + private long time; /** diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/PrivateKey.java b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/PrivateKey.java index 6348add..73594ff 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/PrivateKey.java +++ b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/PrivateKey.java @@ -26,6 +26,8 @@ import ch.dissem.bitmessage.utils.Decode; import ch.dissem.bitmessage.utils.Encode; import java.io.*; +import java.util.ArrayList; +import java.util.List; import static ch.dissem.bitmessage.utils.Singleton.security; @@ -34,7 +36,10 @@ import static ch.dissem.bitmessage.utils.Singleton.security; * {@link Pubkey} object. */ public class PrivateKey implements Streamable { + private static final long serialVersionUID = 8562555470709110558L; + public static final int PRIVATE_KEY_SIZE = 32; + private final byte[] privateSigningKey; private final byte[] privateEncryptionKey; @@ -70,29 +75,72 @@ public class PrivateKey implements Streamable { } public PrivateKey(long version, long stream, String passphrase) { - try { - byte[] signingKey; - int signingKeyNonce = 0; - byte[] encryptionKey; - int encryptionKeyNonce = 1; - byte[] passPhraseBytes = passphrase.getBytes("UTF-8"); - byte[] ripe; - do { - signingKey = Bytes.truncate(security().sha512(passPhraseBytes, Encode.varInt(signingKeyNonce)), 32); - encryptionKey = Bytes.truncate(security().sha512(passPhraseBytes, Encode.varInt(encryptionKeyNonce)), 32); - byte[] publicSigningKey = security().createPublicKey(signingKey); - byte[] publicEncryptionKey = security().createPublicKey(encryptionKey); - ripe = security().ripemd160(security().sha512(publicSigningKey, publicEncryptionKey)); + this(new Builder(version, stream, false).seed(passphrase).generate()); + } - signingKeyNonce += 2; - encryptionKeyNonce += 2; - } while (ripe[0] != 0); - this.privateSigningKey = signingKey; - this.privateEncryptionKey = encryptionKey; - this.pubkey = security().createPubkey(version, stream, privateSigningKey, privateEncryptionKey, 0, 0); - } catch (IOException e) { - throw new ApplicationException(e); + private PrivateKey(Builder builder) { + this.privateSigningKey = builder.privSK; + this.privateEncryptionKey = builder.privEK; + this.pubkey = Factory.createPubkey(builder.version, builder.stream, builder.pubSK, builder.pubEK, 0, 0); + } + + private static class Builder { + final long version; + final long stream; + final boolean shorter; + + byte[] seed; + long nextNonce; + + byte[] privSK, privEK; + byte[] pubSK, pubEK; + + private Builder(long version, long stream, boolean shorter) { + this.version = version; + this.stream = stream; + this.shorter = shorter; } + + Builder seed(String passphrase) { + try { + seed = passphrase.getBytes("UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new ApplicationException(e); + } + return this; + } + + Builder generate() { + try { + long signingKeyNonce = nextNonce; + long encryptionKeyNonce = nextNonce + 1; + byte[] ripe; + do { + privEK = Bytes.truncate(security().sha512(seed, Encode.varInt(encryptionKeyNonce)), 32); + privSK = Bytes.truncate(security().sha512(seed, Encode.varInt(signingKeyNonce)), 32); + pubSK = security().createPublicKey(privSK); + pubEK = security().createPublicKey(privEK); + ripe = security().ripemd160(security().sha512(pubSK, pubEK)); + + signingKeyNonce += 2; + encryptionKeyNonce += 2; + } while (ripe[0] != 0 || (shorter && ripe[1] != 0)); + nextNonce = signingKeyNonce; + } catch (IOException e) { + throw new ApplicationException(e); + } + return this; + } + } + + public static List deterministic(String passphrase, int numberOfAddresses, long version, long stream, boolean shorter) { + List result = new ArrayList<>(numberOfAddresses); + Builder builder = new Builder(version, stream, shorter).seed(passphrase); + for (int i = 0; i < numberOfAddresses; i++) { + builder.generate(); + result.add(new PrivateKey(builder)); + } + return result; } public static PrivateKey read(InputStream is) throws IOException { diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/Base58.java b/core/src/main/java/ch/dissem/bitmessage/utils/Base58.java index 167d862..a67e344 100644 --- a/core/src/main/java/ch/dissem/bitmessage/utils/Base58.java +++ b/core/src/main/java/ch/dissem/bitmessage/utils/Base58.java @@ -31,7 +31,7 @@ import static java.util.Arrays.copyOfRange; */ public class Base58 { private static final int[] INDEXES = new int[128]; - private static char[] ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray(); + private static final char[] ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray(); static { for (int i = 0; i < INDEXES.length; i++) {