diff --git a/opret-testapp/src/main/java/org/tcpid/opretj/testapp/App.java b/opret-testapp/src/main/java/org/tcpid/opretj/testapp/App.java index 02f13ec..77ce198 100644 --- a/opret-testapp/src/main/java/org/tcpid/opretj/testapp/App.java +++ b/opret-testapp/src/main/java/org/tcpid/opretj/testapp/App.java @@ -21,7 +21,7 @@ import org.bitcoinj.wallet.SendRequest; import org.libsodium.jni.crypto.Hash; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.tcpid.key.SigningKey; +import org.tcpid.key.MasterSigningKey; import org.tcpid.opretj.OPRETECParser; import org.tcpid.opretj.OPRETWallet; import org.tcpid.opretj.OPRETWalletAppKit; @@ -39,7 +39,7 @@ public class App { public final static long OPRET_BIRTHDAY = 1471989600; private final static Logger logger = LoggerFactory.getLogger(App.class); - private final static SigningKey SK = new SigningKey(HASH.sha256("TESTSEED".getBytes())); + private final static MasterSigningKey SK = new MasterSigningKey(HASH.sha256("TESTSEED".getBytes())); private static void displayBalance(final OPRETWalletAppKit kit, final PrintWriter out) { out.write("Balance: " + kit.wallet().getBalance().toFriendlyString() + "\n"); @@ -157,7 +157,15 @@ public class App { } public static void main(final String[] args) throws Exception { + MasterSigningKey sk = SK.getSubKey(1L); + System.err.println(SK.toString()); + System.err.println(sk.toString()); + sk = SK.getSubKey(2L); + System.err.println(sk.toString()); + sk = SK.getSubKey(3L); + System.err.println(sk.toString()); + System.exit(0); final OptionParser parser = new OptionParser(); final OptionSpec net = parser.accepts("net", "The network to run the examples on") .withRequiredArg().ofType(NetworkEnum.class).defaultsTo(NetworkEnum.TEST); diff --git a/opretj/src/main/java/org/tcpid/key/MasterSigningKey.java b/opretj/src/main/java/org/tcpid/key/MasterSigningKey.java new file mode 100644 index 0000000..db9a3d8 --- /dev/null +++ b/opretj/src/main/java/org/tcpid/key/MasterSigningKey.java @@ -0,0 +1,71 @@ +package org.tcpid.key; + +import static org.libsodium.jni.NaCl.sodium; +import static org.libsodium.jni.SodiumConstants.SECRETKEY_BYTES; + +import java.util.ArrayList; + +import org.libsodium.jni.Sodium; +import org.libsodium.jni.crypto.Hash; +import org.libsodium.jni.crypto.Util; +import org.libsodium.jni.encoders.Encoder; + +import com.google.common.primitives.Longs; + +public class MasterSigningKey extends SigningKey { + private MasterSigningKey subkey; + private Long subkeyindex; + public static final Hash HASH = new Hash(); + private final ArrayList keyindex; + + public MasterSigningKey(final byte[] key, ArrayList keyindex) { + super(key); + subkey = null; + subkeyindex = 0L; + this.keyindex = new ArrayList<>(keyindex); + } + + public MasterSigningKey(final byte[] key) { + super(key); + subkey = null; + subkeyindex = 0L; + this.keyindex = new ArrayList<>(); + } + + public MasterSigningKey getValidSubKey() { + if (subkey == null) { + subkey = getSubKey(subkeyindex); + } + return subkey; + } + + public MasterSigningKey getSubKey(Long offset) { + System.err.println("getSubKey"); + final byte[] la = Longs.toByteArray(offset); + final byte[] keynum = Util.prependZeros(16 - la.length, la); + System.err.println("getSubKey key=" + Encoder.HEX.encode(keynum)); + final byte[] appid = Util.prependZeros(14, "EC".getBytes()); + final byte[] newseed = Util.zeros(SECRETKEY_BYTES); + sodium(); + Sodium.crypto_generichash_blake2b_salt_personal(newseed, newseed.length, null, 0, this.seed, this.seed.length, + keynum, appid); + final ArrayList ki = new ArrayList<>(this.keyindex); + ki.add(offset); + return new MasterSigningKey(newseed, ki); + } + + public MasterSigningKey getNextValidSubKey(Long offset) { + return getSubKey(subkeyindex + offset); + } + + public void revokeSubKey() { + subkey.setRevoked(true); + subkeyindex++; + subkey = null; + } + + @Override + public String toString() { + return "Index: " + this.keyindex.toString() + " " + Encoder.HEX.encode(seed); + } +} diff --git a/opretj/src/main/java/org/tcpid/key/MasterVerifyKey.java b/opretj/src/main/java/org/tcpid/key/MasterVerifyKey.java new file mode 100644 index 0000000..0b57ff6 --- /dev/null +++ b/opretj/src/main/java/org/tcpid/key/MasterVerifyKey.java @@ -0,0 +1,48 @@ +package org.tcpid.key; + +import java.util.LinkedList; +import java.util.NoSuchElementException; + +public class MasterVerifyKey extends VerifyKey { + private final LinkedList subkeys = new LinkedList<>(); + + public MasterVerifyKey(final byte[] key) { + super(key); + } + + public VerifyKey getValidSubKey() { + return subkeys.getFirst(); + } + + public void revokeSubKey(final VerifyKey key) { + final int i = subkeys.indexOf(key); + + if (i == -1) { + throw new NoSuchElementException("No such subkey"); + } + + subkeys.get(i).setRevoked(true); + subkeys.remove(i); + } + + public void setFirstValidSubKey(final VerifyKey key) { + if (!subkeys.isEmpty()) { + throw new IndexOutOfBoundsException("Subkey list is not empty"); + } + + subkeys.addLast(key); + } + + public void setNextValidSubKey(final VerifyKey after, final VerifyKey key) { + final VerifyKey l = subkeys.getLast(); + if (!l.equals(after)) { + throw new NoSuchElementException("No such after key, or not last in list"); + } + + subkeys.addLast(key); + } + + public void clearSubKeys() { + subkeys.clear(); + } +} diff --git a/opretj/src/main/java/org/tcpid/key/SigningKey.java b/opretj/src/main/java/org/tcpid/key/SigningKey.java index b873734..1af893b 100644 --- a/opretj/src/main/java/org/tcpid/key/SigningKey.java +++ b/opretj/src/main/java/org/tcpid/key/SigningKey.java @@ -21,48 +21,68 @@ import static org.libsodium.jni.SodiumConstants.PUBLICKEY_BYTES; import static org.libsodium.jni.SodiumConstants.SECRETKEY_BYTES; import static org.libsodium.jni.SodiumConstants.SIGNATURE_BYTES; +import org.libsodium.jni.Sodium; import org.libsodium.jni.crypto.Random; import org.libsodium.jni.crypto.Util; import org.libsodium.jni.encoders.Encoder; import org.libsodium.jni.keys.KeyPair; import org.libsodium.jni.keys.PrivateKey; -public class SigningKey { +public class SigningKey implements Comparable { + + protected final byte[] seed; - private final byte[] seed; private final byte[] secretKey; - private final VerifyKey verifyKey; + private boolean revoked; public SigningKey() { this(new Random().randomBytes(SECRETKEY_BYTES)); } - public SigningKey(byte[] seed) { + public SigningKey(final byte[] seed) { Util.checkLength(seed, SECRETKEY_BYTES); - this.seed = seed; + this.seed = seed.clone(); this.secretKey = Util.zeros(SECRETKEY_BYTES * 2); final byte[] publicKey = Util.zeros(PUBLICKEY_BYTES); - Util.isValid(sodium().crypto_sign_ed25519_seed_keypair(publicKey, secretKey, seed), + sodium(); + Util.isValid(Sodium.crypto_sign_ed25519_seed_keypair(publicKey, secretKey, seed), "Failed to generate a key pair"); this.verifyKey = new VerifyKey(publicKey); } - public SigningKey(String seed, Encoder encoder) { + public SigningKey(final String seed, final Encoder encoder) { this(encoder.decode(seed)); } + @Override + public int compareTo(final SigningKey other) { + for (int i = SECRETKEY_BYTES - 1; i >= 0; i--) { + final int thisByte = this.seed[i] & 0xff; + final int otherByte = other.seed[i] & 0xff; + if (thisByte > otherByte) { + return 1; + } + if (thisByte < otherByte) { + return -1; + } + } + return 0; + } + public KeyPair getKeyPair() { final byte[] sk = Util.zeros(SECRETKEY_BYTES); - sodium().crypto_sign_ed25519_sk_to_curve25519(sk, this.secretKey); + sodium(); + Sodium.crypto_sign_ed25519_sk_to_curve25519(sk, this.secretKey); return new KeyPair(sk); } public PrivateKey getPrivateKey() { final byte[] sk = Util.zeros(SECRETKEY_BYTES); - sodium().crypto_sign_ed25519_sk_to_curve25519(sk, this.secretKey); + sodium(); + Sodium.crypto_sign_ed25519_sk_to_curve25519(sk, this.secretKey); return new PrivateKey(sk); } @@ -70,15 +90,26 @@ public class SigningKey { return this.verifyKey; } - public byte[] sign(byte[] message) { + public boolean isRevoked() { + return revoked; + } + + public void setRevoked(final boolean revoked) { + if (this.revoked != true) { + this.revoked = revoked; + } + } + + public byte[] sign(final byte[] message) { byte[] signature = Util.prependZeros(SIGNATURE_BYTES, message); final int[] bufferLen = new int[1]; - sodium().crypto_sign_ed25519(signature, bufferLen, message, message.length, secretKey); + sodium(); + Sodium.crypto_sign_ed25519(signature, bufferLen, message, message.length, secretKey); signature = Util.slice(signature, 0, SIGNATURE_BYTES); return signature; } - public String sign(String message, Encoder encoder) { + public String sign(final String message, final Encoder encoder) { final byte[] signature = sign(encoder.decode(message)); return encoder.encode(signature); } diff --git a/opretj/src/main/java/org/tcpid/key/VerifyKey.java b/opretj/src/main/java/org/tcpid/key/VerifyKey.java index 07ecb1f..f06a6cb 100644 --- a/opretj/src/main/java/org/tcpid/key/VerifyKey.java +++ b/opretj/src/main/java/org/tcpid/key/VerifyKey.java @@ -22,6 +22,7 @@ import static org.libsodium.jni.SodiumConstants.SIGNATURE_BYTES; import java.util.Arrays; +import org.libsodium.jni.Sodium; import org.libsodium.jni.crypto.Hash; import org.libsodium.jni.crypto.Util; import org.libsodium.jni.encoders.Encoder; @@ -34,18 +35,17 @@ public class VerifyKey implements Comparable { private final byte[] hash; private final byte[] shortHash; private boolean revoked; + private MasterVerifyKey masterkey; - public boolean isRevoked() { - return revoked; + public MasterVerifyKey getMasterkey() { + return masterkey; } - public void setRevoked(boolean revoked) { - if (this.revoked != true) { - this.revoked = revoked; - } + public void setMasterkey(MasterVerifyKey masterkey) { + this.masterkey = masterkey; } - public VerifyKey(byte[] key) { + public VerifyKey(final byte[] key) { Util.checkLength(key, PUBLICKEY_BYTES); this.key = key; this.hash = HASH.sha256(key); @@ -53,58 +53,10 @@ public class VerifyKey implements Comparable { this.revoked = false; } - public VerifyKey(String key, Encoder encoder) { + public VerifyKey(final String key, final Encoder encoder) { this(encoder.decode(key)); } - public PublicKey getPublicKey() { - final byte[] pk = Util.zeros(PUBLICKEY_BYTES); - sodium().crypto_sign_ed25519_pk_to_curve25519(pk, this.key); - return new PublicKey(pk); - } - - public byte[] toBytes() { - return key; - } - - @Override - public String toString() { - return Encoder.HEX.encode(key); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if ((o == null) || (getClass() != o.getClass())) { - return false; - } - return Arrays.equals(key, ((VerifyKey) o).key); - } - - public boolean verify(byte[] message, byte[] signature) { - Util.checkLength(signature, SIGNATURE_BYTES); - final byte[] sigAndMsg = Util.merge(signature, message); - final byte[] buffer = Util.zeros(sigAndMsg.length); - final int[] bufferLen = new int[1]; - - return Util.isValid(sodium().crypto_sign_ed25519_open(buffer, bufferLen, sigAndMsg, sigAndMsg.length, key), - "signature was forged or corrupted"); - } - - public boolean verify(String message, String signature, Encoder encoder) { - return verify(encoder.decode(message), encoder.decode(signature)); - } - - public byte[] toHash() { - return this.hash; - } - - public byte[] getShortHash() { - return this.shortHash; - } - @Override public int compareTo(final VerifyKey other) { for (int i = PUBLICKEY_BYTES - 1; i >= 0; i--) { @@ -120,4 +72,64 @@ public class VerifyKey implements Comparable { return 0; } + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if ((o == null) || (getClass() != o.getClass())) { + return false; + } + return Arrays.equals(key, ((VerifyKey) o).key); + } + + public PublicKey getPublicKey() { + final byte[] pk = Util.zeros(PUBLICKEY_BYTES); + sodium(); + Sodium.crypto_sign_ed25519_pk_to_curve25519(pk, this.key); + return new PublicKey(pk); + } + + public byte[] getShortHash() { + return this.shortHash; + } + + public boolean isRevoked() { + return revoked; + } + + public void setRevoked(final boolean revoked) { + if (this.revoked != true) { + this.revoked = revoked; + } + } + + public byte[] toBytes() { + return key; + } + + public byte[] toHash() { + return this.hash; + } + + @Override + public String toString() { + return Encoder.HEX.encode(key); + } + + public boolean verify(final byte[] message, final byte[] signature) { + Util.checkLength(signature, SIGNATURE_BYTES); + final byte[] sigAndMsg = Util.merge(signature, message); + final byte[] buffer = Util.zeros(sigAndMsg.length); + final int[] bufferLen = new int[1]; + + sodium(); + return Util.isValid(Sodium.crypto_sign_ed25519_open(buffer, bufferLen, sigAndMsg, sigAndMsg.length, key), + "signature was forged or corrupted"); + } + + public boolean verify(final String message, final String signature, final Encoder encoder) { + return verify(encoder.decode(message), encoder.decode(signature)); + } + } diff --git a/opretj/src/main/java/org/tcpid/opretj/OPRETBaseHandler.java b/opretj/src/main/java/org/tcpid/opretj/OPRETBaseHandler.java index 47de69f..06894cd 100644 --- a/opretj/src/main/java/org/tcpid/opretj/OPRETBaseHandler.java +++ b/opretj/src/main/java/org/tcpid/opretj/OPRETBaseHandler.java @@ -1,5 +1,6 @@ package org.tcpid.opretj; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -17,7 +18,7 @@ import com.google.common.primitives.Bytes; public abstract class OPRETBaseHandler implements OPRETHandlerInterface { private static final Logger logger = LoggerFactory.getLogger(OPRETBaseHandler.class); private final CopyOnWriteArrayList> opReturnChangeListeners = new CopyOnWriteArrayList>(); - private final Map, Long> magicBytes = new HashMap<>(); + private final Map, Long> magicBytes = Collections.synchronizedMap(new HashMap<>()); protected void addOPRET(final byte[] magic, final long earliestTime) { logger.debug("addMagicBytes: {} - Time {}", Utils.HEX.encode(magic), earliestTime); diff --git a/opretj/src/main/java/org/tcpid/opretj/OPRETECExampleParser.java b/opretj/src/main/java/org/tcpid/opretj/OPRETECExampleParser.java deleted file mode 100644 index c029640..0000000 --- a/opretj/src/main/java/org/tcpid/opretj/OPRETECExampleParser.java +++ /dev/null @@ -1,110 +0,0 @@ -package org.tcpid.opretj; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CopyOnWriteArrayList; - -import org.bitcoinj.core.PartialMerkleTree; -import org.bitcoinj.core.Sha256Hash; -import org.bitcoinj.core.Utils; -import org.bitcoinj.utils.ListenerRegistration; -import org.bitcoinj.utils.Threading; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.common.primitives.Bytes; - -public class OPRETECExampleParser extends OPRETBaseHandler { - private static final Logger logger = LoggerFactory.getLogger(OPRETECExampleParser.class); - private static final List OPRET_MAGIC = Bytes.asList(Utils.HEX.decode("ec1d")); - protected final Map merkleHashMap = new HashMap<>(); - protected final Map transHashMap = new HashMap<>(); - private final CopyOnWriteArrayList> opReturnChangeListeners = new CopyOnWriteArrayList<>(); - - /** - * Adds an event listener object. Methods on this object are called when - * scripts watched by this wallet change. The listener is executed by the - * given executor. - */ - public void addOPRETECRevokeEventListener(final OPRETECEventListener listener) { - // This is thread safe, so we don't need to take the lock. - opReturnChangeListeners.add(new ListenerRegistration(listener, Threading.SAME_THREAD)); - } - - private boolean checkData(final OPRETTransaction t1, final OPRETTransaction t2) { - final List> opret_data = new ArrayList<>(t1.opretData); - opret_data.addAll(t2.opretData); - logger.debug("checking {}", opret_data); - - List chunk; - chunk = opret_data.get(0); - if (!chunk.equals(OPRET_MAGIC)) { - logger.debug("0: != OPRET_MAGIC"); - return false; - } - - chunk = opret_data.get(1); - if (chunk.size() != 1) { - logger.debug("1: size != 1"); - return false; - } - - if (chunk.get(0) == (byte) 0xFE) { - if (opret_data.size() != 8) { - logger.debug("FE: size != 8"); - return false; - } - chunk = opret_data.get(4); - if (!chunk.equals(OPRET_MAGIC)) { - logger.debug("FE 4 != OPRET_MAGIC"); - return false; - } - if (!opret_data.get(2).equals(opret_data.get(6))) { - logger.debug("FE 2 != 6"); - return false; - } - chunk = opret_data.get(5); - if ((chunk.size() != 1) || (chunk.get(0) != (byte) 0xFF)) { - logger.debug("FE 5 size!=1 or != FF"); - return false; - } - - return handleRevoke(t1, t2); - } else { - logger.debug("1: != 0xFE"); - } - - return false; - } - - private boolean handleRevoke(final OPRETTransaction t1, final OPRETTransaction t2) { - final byte[] pkhash = Bytes.toArray(t1.opretData.get(2)); - final byte[] sig = Bytes.concat(Bytes.toArray(t1.opretData.get(3)), Bytes.toArray(t2.opretData.get(3))); - - logger.debug("REVOKE PK {} - SIG {}", Utils.HEX.encode(pkhash), Utils.HEX.encode(sig)); - queueOnOPRETRevoke(pkhash, sig); - return true; - } - - @Override - public void pushTransaction(final OPRETTransaction t) { - logger.debug("pushData: {}", t.opretData); - transHashMap.put(t.txHash, t); - } - - protected void queueOnOPRETRevoke(final byte[] pkhash, final byte[] sig) { - for (final ListenerRegistration registration : opReturnChangeListeners) { - registration.executor.execute(() -> registration.listener.onOPRETRevoke(pkhash, sig)); - } - } - - /** - * Removes the given event listener object. Returns true if the listener was - * removed, false if that listener was never added. - */ - public boolean removeOPRETECRevokeEventListener(final OPRETECEventListener listener) { - return ListenerRegistration.removeFromList(listener, opReturnChangeListeners); - } -} diff --git a/opretj/src/main/java/org/tcpid/opretj/OPRETWallet.java b/opretj/src/main/java/org/tcpid/opretj/OPRETWallet.java index dba2a74..0720333 100644 --- a/opretj/src/main/java/org/tcpid/opretj/OPRETWallet.java +++ b/opretj/src/main/java/org/tcpid/opretj/OPRETWallet.java @@ -3,6 +3,7 @@ package org.tcpid.opretj; import static org.bitcoinj.script.ScriptOpCodes.OP_RETURN; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -36,13 +37,13 @@ public class OPRETWallet extends Wallet implements BlocksDownloadedEventListener private final OPRETHandlerInterface opbs; private final Logger logger = LoggerFactory.getLogger(OPRETWallet.class); - protected final Map> pendingTransactions; + protected final Map> pendingTransactions = Collections + .synchronizedMap(new HashMap<>()); public OPRETWallet(final NetworkParameters params, final KeyChainGroup keyChainGroup, final OPRETHandlerInterface bs) { super(params, keyChainGroup); opbs = bs; - pendingTransactions = new HashMap<>(); } @Override @@ -174,7 +175,7 @@ public class OPRETWallet extends Wallet implements BlocksDownloadedEventListener final Sha256Hash h = block.getHeader().getHash(); if (!pendingTransactions.containsKey(h)) { - pendingTransactions.put(h, new HashMap()); + pendingTransactions.put(h, Collections.synchronizedMap(new HashMap())); } pendingTransactions.get(h).put(tx.getHash(), new OPRETTransaction(h, tx.getHash(), myList));