From 765b452f7d5e21d57920e2a9c2dac9d08968198a Mon Sep 17 00:00:00 2001 From: Harald Hoyer Date: Wed, 14 Sep 2016 10:15:47 +0200 Subject: [PATCH 1/6] move special EC parser and key handling to testapp --- .../org.eclipse.core.resources.prefs | 1 + opret-testapp/pom.xml | 11 +- .../java/org/tcpid/key/HMACSHA512256.java | 0 .../java/org/tcpid/key/MasterSigningKey.java | 0 .../java/org/tcpid/key/MasterVerifyKey.java | 10 +- .../main/java/org/tcpid/key/SigningKey.java | 0 .../main/java/org/tcpid/key/VerifyKey.java | 0 .../java/org/tcpid/opretj/testapp/App.java | 1 - .../opretj/testapp}/OPRETECEventListener.java | 2 +- .../tcpid/opretj/testapp}/OPRETECParser.java | 245 ++++++------------ opret-testapp/src/main/resources/logback.xml | 2 +- .../org/tcpid/opretj/testapp}/TestCrypto.java | 9 +- .../org/tcpid/opretj/testapp}/TestECA1.java | 65 +++-- .../org.eclipse.core.resources.prefs | 1 - .../tcpid/opretj/OPRETHandlerInterface.java | 2 +- .../org/tcpid/opretj/OPRETSimpleLogger.java | 3 +- 16 files changed, 149 insertions(+), 203 deletions(-) rename {opretj => opret-testapp}/src/main/java/org/tcpid/key/HMACSHA512256.java (100%) rename {opretj => opret-testapp}/src/main/java/org/tcpid/key/MasterSigningKey.java (100%) rename {opretj => opret-testapp}/src/main/java/org/tcpid/key/MasterVerifyKey.java (71%) rename {opretj => opret-testapp}/src/main/java/org/tcpid/key/SigningKey.java (100%) rename {opretj => opret-testapp}/src/main/java/org/tcpid/key/VerifyKey.java (100%) rename {opretj/src/main/java/org/tcpid/opretj => opret-testapp/src/main/java/org/tcpid/opretj/testapp}/OPRETECEventListener.java (78%) rename {opretj/src/main/java/org/tcpid/opretj => opret-testapp/src/main/java/org/tcpid/opretj/testapp}/OPRETECParser.java (72%) rename {opretj/src/test/java/org/tcpid/opretj => opret-testapp/src/test/java/org/tcpid/opretj/testapp}/TestCrypto.java (95%) rename {opretj/src/test/java/org/tcpid/opretj => opret-testapp/src/test/java/org/tcpid/opretj/testapp}/TestECA1.java (66%) diff --git a/opret-testapp/.settings/org.eclipse.core.resources.prefs b/opret-testapp/.settings/org.eclipse.core.resources.prefs index abdea9a..839d647 100644 --- a/opret-testapp/.settings/org.eclipse.core.resources.prefs +++ b/opret-testapp/.settings/org.eclipse.core.resources.prefs @@ -1,4 +1,5 @@ eclipse.preferences.version=1 encoding//src/main/java=UTF-8 encoding//src/main/resources=UTF-8 +encoding//src/test/java=UTF-8 encoding/=UTF-8 diff --git a/opret-testapp/pom.xml b/opret-testapp/pom.xml index 82054f2..eb72b6e 100644 --- a/opret-testapp/pom.xml +++ b/opret-testapp/pom.xml @@ -28,7 +28,7 @@ junit junit - 3.8.1 + 4.11 test @@ -106,6 +106,15 @@ + + + org.apache.maven.plugins + maven-surefire-plugin + + alphabetical + + + org.apache.maven.plugins maven-compiler-plugin diff --git a/opretj/src/main/java/org/tcpid/key/HMACSHA512256.java b/opret-testapp/src/main/java/org/tcpid/key/HMACSHA512256.java similarity index 100% rename from opretj/src/main/java/org/tcpid/key/HMACSHA512256.java rename to opret-testapp/src/main/java/org/tcpid/key/HMACSHA512256.java diff --git a/opretj/src/main/java/org/tcpid/key/MasterSigningKey.java b/opret-testapp/src/main/java/org/tcpid/key/MasterSigningKey.java similarity index 100% rename from opretj/src/main/java/org/tcpid/key/MasterSigningKey.java rename to opret-testapp/src/main/java/org/tcpid/key/MasterSigningKey.java diff --git a/opretj/src/main/java/org/tcpid/key/MasterVerifyKey.java b/opret-testapp/src/main/java/org/tcpid/key/MasterVerifyKey.java similarity index 71% rename from opretj/src/main/java/org/tcpid/key/MasterVerifyKey.java rename to opret-testapp/src/main/java/org/tcpid/key/MasterVerifyKey.java index 47b11a8..0693e94 100644 --- a/opretj/src/main/java/org/tcpid/key/MasterVerifyKey.java +++ b/opret-testapp/src/main/java/org/tcpid/key/MasterVerifyKey.java @@ -6,7 +6,7 @@ import java.util.NoSuchElementException; import org.tcpid.opretj.OPRETTransaction; public class MasterVerifyKey extends VerifyKey { - private final LinkedList subkeys = new LinkedList<>(); + private final LinkedList subkeys = new LinkedList<>(); public MasterVerifyKey(final byte[] key) { super(key); @@ -16,11 +16,11 @@ public class MasterVerifyKey extends VerifyKey { subkeys.clear(); } - public VerifyKey getValidSubKey() { + public MasterVerifyKey getValidSubKey() { return subkeys.getFirst(); } - public void revokeSubKey(final VerifyKey key) { + public void revokeSubKey(final MasterVerifyKey key) { final int i = subkeys.indexOf(key); if (i == -1) { @@ -31,7 +31,7 @@ public class MasterVerifyKey extends VerifyKey { subkeys.remove(i); } - public void setFirstValidSubKey(final VerifyKey key, final OPRETTransaction t1, final OPRETTransaction t2) { + public void setFirstValidSubKey(final MasterVerifyKey key, final OPRETTransaction t1, final OPRETTransaction t2) { if (!subkeys.isEmpty()) { throw new IndexOutOfBoundsException("Subkey list is not empty"); } @@ -39,7 +39,7 @@ public class MasterVerifyKey extends VerifyKey { subkeys.addLast(key); } - public void setNextValidSubKey(final VerifyKey after, final VerifyKey key) { + public void setNextValidSubKey(final MasterVerifyKey after, final MasterVerifyKey key) { final VerifyKey l = subkeys.getLast(); if (!l.equals(after)) { throw new NoSuchElementException("No such after key, or not last in list"); diff --git a/opretj/src/main/java/org/tcpid/key/SigningKey.java b/opret-testapp/src/main/java/org/tcpid/key/SigningKey.java similarity index 100% rename from opretj/src/main/java/org/tcpid/key/SigningKey.java rename to opret-testapp/src/main/java/org/tcpid/key/SigningKey.java diff --git a/opretj/src/main/java/org/tcpid/key/VerifyKey.java b/opret-testapp/src/main/java/org/tcpid/key/VerifyKey.java similarity index 100% rename from opretj/src/main/java/org/tcpid/key/VerifyKey.java rename to opret-testapp/src/main/java/org/tcpid/key/VerifyKey.java 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 b874d7c..a59b3c5 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 @@ -22,7 +22,6 @@ import org.libsodium.jni.crypto.Hash; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.tcpid.key.MasterSigningKey; -import org.tcpid.opretj.OPRETECParser; import org.tcpid.opretj.OPRETWallet; import org.tcpid.opretj.OPRETWalletAppKit; diff --git a/opretj/src/main/java/org/tcpid/opretj/OPRETECEventListener.java b/opret-testapp/src/main/java/org/tcpid/opretj/testapp/OPRETECEventListener.java similarity index 78% rename from opretj/src/main/java/org/tcpid/opretj/OPRETECEventListener.java rename to opret-testapp/src/main/java/org/tcpid/opretj/testapp/OPRETECEventListener.java index f46db24..a81275d 100644 --- a/opretj/src/main/java/org/tcpid/opretj/OPRETECEventListener.java +++ b/opret-testapp/src/main/java/org/tcpid/opretj/testapp/OPRETECEventListener.java @@ -1,4 +1,4 @@ -package org.tcpid.opretj; +package org.tcpid.opretj.testapp; import org.tcpid.key.MasterVerifyKey; diff --git a/opretj/src/main/java/org/tcpid/opretj/OPRETECParser.java b/opret-testapp/src/main/java/org/tcpid/opretj/testapp/OPRETECParser.java similarity index 72% rename from opretj/src/main/java/org/tcpid/opretj/OPRETECParser.java rename to opret-testapp/src/main/java/org/tcpid/opretj/testapp/OPRETECParser.java index 747e336..20df110 100644 --- a/opretj/src/main/java/org/tcpid/opretj/OPRETECParser.java +++ b/opret-testapp/src/main/java/org/tcpid/opretj/testapp/OPRETECParser.java @@ -1,4 +1,4 @@ -package org.tcpid.opretj; +package org.tcpid.opretj.testapp; import static org.bitcoinj.script.ScriptOpCodes.OP_RETURN; import static org.libsodium.jni.NaCl.sodium; @@ -32,6 +32,8 @@ import org.tcpid.key.MasterSigningKey; import org.tcpid.key.MasterVerifyKey; import org.tcpid.key.SigningKey; import org.tcpid.key.VerifyKey; +import org.tcpid.opretj.OPRETBaseHandler; +import org.tcpid.opretj.OPRETTransaction; import com.google.common.primitives.Bytes; @@ -163,6 +165,85 @@ public class OPRETECParser extends OPRETBaseHandler { return true; } + private boolean handleAnnounce(final OPRETTransaction selfTx, + final Map, List> selfTransHashMap, + final Map, List> otherTransHashMap, final boolean isT1) { + + final byte[] selfData = Bytes.toArray(selfTx.opretData.get(1)); + if (((selfData.length < 48) || (selfData.length > 64))) { + logger.debug("invalid chunk1 size = {}", selfData.length); + return false; + } + + final List pkhash = selfTx.opretData.get(2); + if ((pkhash.size() != 12)) { + logger.debug("chunk 2 size != 12 but {} ", pkhash.size()); + return false; + } + + if (!verifyKeys.containsKey(pkhash)) { + return false; + } + + if (otherTransHashMap.containsKey(pkhash)) { + for (final OPRETTransaction otherTx : otherTransHashMap.get(pkhash)) { + final byte[] otherData = Bytes.toArray(otherTx.opretData.get(1)); + final byte[] cipher = isT1 + ? Bytes.concat(Arrays.copyOfRange(selfData, 0, 48), Arrays.copyOfRange(otherData, 0, 48)) + : Bytes.concat(Arrays.copyOfRange(otherData, 0, 48), Arrays.copyOfRange(selfData, 0, 48)); + final BigInteger selfNonce = (selfData.length == 48) ? BigInteger.ZERO + : new BigInteger(1, Arrays.copyOfRange(selfData, 48, selfData.length)); + final BigInteger otherNonce = (otherData.length == 48) ? BigInteger.ZERO + : new BigInteger(1, Arrays.copyOfRange(otherData, 48, otherData.length)); + + final BigInteger nonce = isT1 ? selfNonce.shiftLeft(16 * 8).or(otherNonce) + : otherNonce.shiftLeft(16 * 8).or(selfNonce); + + byte[] noncebytes = Util.prependZeros(32, nonce.toByteArray()); + noncebytes = Arrays.copyOfRange(noncebytes, noncebytes.length - 32, noncebytes.length); + + for (final MasterVerifyKey k : verifyKeys.get(pkhash)) { + byte[] sharedkey, xornonce; + sharedkey = HASH.sha256(HASH.sha256(Bytes.concat(k.toBytes(), noncebytes))); + xornonce = Arrays.copyOfRange(HASH.sha256(Bytes.concat(sharedkey, noncebytes)), 0, 24); + logger.debug("checking key {}", Encoder.HEX.encode(k.toBytes())); + logger.debug("noncebytes {}", Encoder.HEX.encode(noncebytes)); + logger.debug("noncebytes len {}", noncebytes.length); + logger.debug("xornonce {}", Encoder.HEX.encode(xornonce)); + logger.debug("sharedkey {}", Encoder.HEX.encode(sharedkey)); + sodium(); + final byte[] msg = Util.zeros(96); + Sodium.crypto_stream_xsalsa20_xor(msg, cipher, 96, xornonce, sharedkey); + final byte[] vk = Arrays.copyOfRange(msg, 0, 32); + final byte[] sig = Arrays.copyOfRange(msg, 32, 96); + try { + logger.debug("Checking sig {} with key {}", Encoder.HEX.encode(sig), Encoder.HEX.encode(vk)); + k.verify(vk, sig); + } catch (final RuntimeException e) { + logger.debug("sig does not match"); + continue; + } + logger.debug("sig matches"); + + k.setFirstValidSubKey(new MasterVerifyKey(vk), selfTx, otherTx); + otherTransHashMap.get(pkhash).remove(otherTx); + if (otherTransHashMap.get(pkhash).isEmpty()) { + otherTransHashMap.remove(pkhash); + } + return true; + } + } + } + + // no matching transaction found, save for later + if (!selfTransHashMap.containsKey(pkhash)) { + selfTransHashMap.put(pkhash, new ArrayList()); + } + selfTransHashMap.get(pkhash).add(selfTx); + + return false; + } + private boolean handleEC0F(final OPRETTransaction t) { final byte[] sig = Bytes.toArray(t.opretData.get(1)); if ((sig.length != 64)) { @@ -247,163 +328,13 @@ public class OPRETECParser extends OPRETBaseHandler { } private boolean handleECA1(final OPRETTransaction t1) { - // FIXME: refactor with handleECA2 - logger.debug("handleECA1"); - final byte[] data1 = Bytes.toArray(t1.opretData.get(1)); - if (((data1.length < 48) || (data1.length > 64))) { - logger.debug("invalid chunk1 size = {}", data1.length); - return false; - } - - final List pkhash = t1.opretData.get(2); - if ((pkhash.size() != 12)) { - logger.debug("chunk 2 size != 12 but {} ", pkhash.size()); - return false; - } - - if (!verifyKeys.containsKey(pkhash)) { - return false; - } - - if (transA2HashMap.containsKey(pkhash)) { - for (final OPRETTransaction t2 : transA2HashMap.get(pkhash)) { - final byte[] data2 = Bytes.toArray(t2.opretData.get(1)); - final byte[] cipher = Bytes.concat(Arrays.copyOfRange(data1, 0, 48), Arrays.copyOfRange(data2, 0, 48)); - BigInteger nonce1 = BigInteger.ZERO; - BigInteger nonce2 = BigInteger.ZERO; - if (data1.length > 48) { - nonce1 = new BigInteger(1, Arrays.copyOfRange(data1, 48, data1.length)); - logger.debug("nonce1 {}", Encoder.HEX.encode(nonce1.toByteArray())); - logger.debug("nonce1shift {}", Encoder.HEX.encode(nonce1.shiftLeft(16 * 8).toByteArray())); - } - if (data2.length > 48) { - nonce2 = new BigInteger(1, Arrays.copyOfRange(data2, 48, data2.length)); - logger.debug("nonce2 {}", Encoder.HEX.encode(nonce2.toByteArray())); - } - - final BigInteger nonce = nonce1.shiftLeft(16 * 8).or(nonce2); - logger.debug("nonceshift {}", Encoder.HEX.encode(nonce.toByteArray())); - - byte[] noncebytes = Util.prependZeros(32, nonce.toByteArray()); - noncebytes = Arrays.copyOfRange(noncebytes, noncebytes.length - 32, noncebytes.length); - - for (final MasterVerifyKey k : verifyKeys.get(pkhash)) { - byte[] sharedkey, xornonce; - sharedkey = HASH.sha256(HASH.sha256(Bytes.concat(k.toBytes(), noncebytes))); - xornonce = Arrays.copyOfRange(HASH.sha256(Bytes.concat(sharedkey, noncebytes)), 0, 24); - logger.debug("checking key {}", Encoder.HEX.encode(k.toBytes())); - logger.debug("noncebytes {}", Encoder.HEX.encode(noncebytes)); - logger.debug("noncebytes len {}", noncebytes.length); - logger.debug("xornonce {}", Encoder.HEX.encode(xornonce)); - logger.debug("sharedkey {}", Encoder.HEX.encode(sharedkey)); - sodium(); - final byte[] msg = Util.zeros(96); - Sodium.crypto_stream_xsalsa20_xor(msg, cipher, 96, xornonce, sharedkey); - final byte[] vk = Arrays.copyOfRange(msg, 0, 32); - final byte[] sig = Arrays.copyOfRange(msg, 32, 96); - try { - logger.debug("Checking sig {} with key {}", Encoder.HEX.encode(sig), Encoder.HEX.encode(vk)); - k.verify(vk, sig); - } catch (final RuntimeException e) { - logger.debug("sig does not match"); - continue; - } - logger.debug("sig matches"); - - k.setFirstValidSubKey(new MasterVerifyKey(vk), t1, t2); - transA2HashMap.get(pkhash).remove(t2); - if (transA2HashMap.get(pkhash).isEmpty()) { - transA2HashMap.remove(pkhash); - } - return true; - } - } - } - if (!transA1HashMap.containsKey(pkhash)) { - transA1HashMap.put(pkhash, new ArrayList()); - } - transA1HashMap.get(pkhash).add(t1); - - return false; + return handleAnnounce(t1, transA1HashMap, transA2HashMap, true); } private boolean handleECA2(final OPRETTransaction t2) { - // FIXME: refactor with handleECA1 logger.debug("handleECA2"); - final byte[] data2 = Bytes.toArray(t2.opretData.get(1)); - if (((data2.length < 48) || (data2.length > 64))) { - logger.debug("invalid chunk1 size = {}", data2.length); - return false; - } - - final List pkhash = t2.opretData.get(2); - if ((pkhash.size() != 12)) { - logger.debug("chunk 2 size != 12 but {} ", pkhash.size()); - return false; - } - - if (!verifyKeys.containsKey(pkhash)) { - logger.debug("pkash not in hashmap"); - return false; - } - - if (transA1HashMap.containsKey(pkhash)) { - for (final OPRETTransaction t1 : transA1HashMap.get(pkhash)) { - final byte[] data1 = Bytes.toArray(t1.opretData.get(1)); - final byte[] cipher = Bytes.concat(Arrays.copyOfRange(data1, 0, 48), Arrays.copyOfRange(data2, 0, 48)); - BigInteger nonce1 = BigInteger.ZERO; - BigInteger nonce2 = BigInteger.ZERO; - if (data1.length > 48) { - nonce1 = new BigInteger(1, Arrays.copyOfRange(data1, 48, data1.length)); - } - if (data2.length > 48) { - nonce2 = new BigInteger(1, Arrays.copyOfRange(data2, 48, data2.length)); - } - - final BigInteger nonce = nonce1.shiftLeft(16 * 8).or(nonce2); - byte[] noncebytes = Util.prependZeros(32, nonce.toByteArray()); - noncebytes = Arrays.copyOfRange(noncebytes, noncebytes.length - 32, noncebytes.length); - - for (final MasterVerifyKey k : verifyKeys.get(pkhash)) { - byte[] sharedkey, xornonce; - logger.debug("checking key {}", Encoder.HEX.encode(k.toBytes())); - logger.debug("noncebytes {}", Encoder.HEX.encode(noncebytes)); - logger.debug("noncebytes len {}", noncebytes.length); - sharedkey = HASH.sha256(HASH.sha256(Bytes.concat(k.toBytes(), noncebytes))); - xornonce = Arrays.copyOfRange(HASH.sha256(Bytes.concat(sharedkey, noncebytes)), 0, 24); - logger.debug("xornonce {}", Encoder.HEX.encode(xornonce)); - logger.debug("sharedkey {}", Encoder.HEX.encode(sharedkey)); - - sodium(); - final byte[] msg = Util.zeros(96); - Sodium.crypto_stream_xsalsa20_xor(msg, cipher, 96, xornonce, sharedkey); - final byte[] vk = Arrays.copyOfRange(msg, 0, 32); - final byte[] sig = Arrays.copyOfRange(msg, 32, 96); - try { - logger.debug("Checking sig {} with key {}", Encoder.HEX.encode(sig), Encoder.HEX.encode(vk)); - k.verify(vk, sig); - } catch (final RuntimeException e) { - logger.debug("sig does not match"); - continue; - } - - logger.debug("sig matches"); - k.setFirstValidSubKey(new MasterVerifyKey(vk), t1, t2); - transA1HashMap.get(pkhash).remove(t1); - if (transA1HashMap.get(pkhash).isEmpty()) { - transA1HashMap.remove(pkhash); - } - return true; - } - } - } - if (!transA2HashMap.containsKey(pkhash)) { - transA2HashMap.put(pkhash, new ArrayList()); - } - transA2HashMap.get(pkhash).add(t2); - logger.debug("nothing in A1 HashMap"); - return false; + return handleAnnounce(t2, transA2HashMap, transA1HashMap, false); } private boolean handleECA3(final OPRETTransaction t) { @@ -416,7 +347,8 @@ public class OPRETECParser extends OPRETBaseHandler { return false; } - protected boolean handleTransaction(final OPRETTransaction t) { + @Override + public boolean pushTransaction(final OPRETTransaction t) { logger.debug("checking {}", t.opretData); if ((t.opretData.size() != 2) && (t.opretData.size() != 3) && (t.opretData.size() != 4)) { @@ -464,11 +396,6 @@ public class OPRETECParser extends OPRETBaseHandler { return false; } - @Override - public void pushTransaction(final OPRETTransaction t) { - handleTransaction(t); - } - private void queueOnOPRETId(final MasterVerifyKey k) { // TODO Auto-generated method stub diff --git a/opret-testapp/src/main/resources/logback.xml b/opret-testapp/src/main/resources/logback.xml index 49f1991..cfdb991 100644 --- a/opret-testapp/src/main/resources/logback.xml +++ b/opret-testapp/src/main/resources/logback.xml @@ -14,7 +14,7 @@ - + diff --git a/opret-testapp/src/main/java/org/tcpid/opretj/testapp/OPRETECEventListener.java b/opret-testapp/src/main/java/org/tcpid/opret/OPRETECEventListener.java similarity index 78% rename from opret-testapp/src/main/java/org/tcpid/opretj/testapp/OPRETECEventListener.java rename to opret-testapp/src/main/java/org/tcpid/opret/OPRETECEventListener.java index a81275d..84d6bdb 100644 --- a/opret-testapp/src/main/java/org/tcpid/opretj/testapp/OPRETECEventListener.java +++ b/opret-testapp/src/main/java/org/tcpid/opret/OPRETECEventListener.java @@ -1,4 +1,4 @@ -package org.tcpid.opretj.testapp; +package org.tcpid.opret; import org.tcpid.key.MasterVerifyKey; diff --git a/opret-testapp/src/main/java/org/tcpid/opretj/testapp/OPRETECParser.java b/opret-testapp/src/main/java/org/tcpid/opret/OPRETECParser.java similarity index 99% rename from opret-testapp/src/main/java/org/tcpid/opretj/testapp/OPRETECParser.java rename to opret-testapp/src/main/java/org/tcpid/opret/OPRETECParser.java index 20df110..b65cb1a 100644 --- a/opret-testapp/src/main/java/org/tcpid/opretj/testapp/OPRETECParser.java +++ b/opret-testapp/src/main/java/org/tcpid/opret/OPRETECParser.java @@ -1,4 +1,4 @@ -package org.tcpid.opretj.testapp; +package org.tcpid.opret; import static org.bitcoinj.script.ScriptOpCodes.OP_RETURN; import static org.libsodium.jni.NaCl.sodium; 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 a59b3c5..480d4d8 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 @@ -22,6 +22,7 @@ import org.libsodium.jni.crypto.Hash; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.tcpid.key.MasterSigningKey; +import org.tcpid.opret.OPRETECParser; import org.tcpid.opretj.OPRETWallet; import org.tcpid.opretj.OPRETWalletAppKit; diff --git a/opret-testapp/src/main/resources/logback.xml b/opret-testapp/src/main/resources/logback.xml index cfdb991..ec2cc49 100644 --- a/opret-testapp/src/main/resources/logback.xml +++ b/opret-testapp/src/main/resources/logback.xml @@ -8,13 +8,13 @@ %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - + - + diff --git a/opret-testapp/src/main/java/org/tcpid/key/MasterSigningKey.java b/opret-testapp/src/main/java/org/tcpid/key/MasterSigningKey.java index e7a1b59..34f84b4 100644 --- a/opret-testapp/src/main/java/org/tcpid/key/MasterSigningKey.java +++ b/opret-testapp/src/main/java/org/tcpid/key/MasterSigningKey.java @@ -58,6 +58,10 @@ public class MasterSigningKey extends SigningKey { @Override public String toString() { - return "Index: " + this.keyindex.toString() + " " + Encoder.HEX.encode(seed); + if (this.keyindex.isEmpty()) { + return Encoder.HEX.encode(seed); + } else { + return "Index: " + this.keyindex.toString() + " " + Encoder.HEX.encode(seed); + } } } diff --git a/opret-testapp/src/main/java/org/tcpid/key/MasterVerifyKey.java b/opret-testapp/src/main/java/org/tcpid/key/MasterVerifyKey.java index 9bab179..13bf15a 100644 --- a/opret-testapp/src/main/java/org/tcpid/key/MasterVerifyKey.java +++ b/opret-testapp/src/main/java/org/tcpid/key/MasterVerifyKey.java @@ -10,7 +10,8 @@ import org.tcpid.opretj.OPRETTransaction; import com.google.common.primitives.Bytes; public class MasterVerifyKey extends VerifyKey { - private final LinkedList subkeys = new LinkedList<>(); + // FIXME: make private again + public final LinkedList subkeys = new LinkedList<>(); public MasterVerifyKey(final byte[] key) { super(key); @@ -30,35 +31,44 @@ public class MasterVerifyKey extends VerifyKey { } public MasterVerifyKey getValidSubKey() { - return subkeys.getFirst(); + for (final MasterVerifyKey k : subkeys) { + if (!k.isRevoked()) { + return k; + } + } + return null; } - public void revokeSubKey(final MasterVerifyKey key) { + 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 MasterVerifyKey key, final OPRETTransaction t1, final OPRETTransaction t2) { if (!subkeys.isEmpty()) { throw new IndexOutOfBoundsException("Subkey list is not empty"); } - subkeys.addLast(key); + key.setMasterkey(this); } public void setNextValidSubKey(final MasterVerifyKey after, final MasterVerifyKey key, OPRETTransaction t1, OPRETTransaction t2) { - final MasterVerifyKey l = subkeys.getLast(); - if (!l.equals(after)) { - throw new NoSuchElementException("No such after key, or not last in list"); + + if (subkeys.contains(key)) { + throw new NoSuchElementException("Already in"); } - subkeys.addLast(key); + final int i = subkeys.indexOf(after); + + if (i == -1) { + throw new NoSuchElementException("No such subkey"); + } + + subkeys.add(i + 1, key); + key.setMasterkey(this); } } diff --git a/opret-testapp/src/main/java/org/tcpid/key/VerifyKey.java b/opret-testapp/src/main/java/org/tcpid/key/VerifyKey.java index 8f8ca3a..1bac05c 100644 --- a/opret-testapp/src/main/java/org/tcpid/key/VerifyKey.java +++ b/opret-testapp/src/main/java/org/tcpid/key/VerifyKey.java @@ -102,6 +102,9 @@ public class VerifyKey implements Comparable { if (this.revoked != true) { this.revoked = revoked; } + if (this.masterkey != null) { + this.masterkey.revokeSubKey(this); + } } public byte[] toBytes() { @@ -114,7 +117,7 @@ public class VerifyKey implements Comparable { @Override public String toString() { - return Encoder.HEX.encode(key); + return Encoder.HEX.encode(key) + (revoked ? " - Revoked" : ""); } public boolean verify(final byte[] message, final byte[] signature) { diff --git a/opret-testapp/src/main/java/org/tcpid/opret/OPRETECParser.java b/opret-testapp/src/main/java/org/tcpid/opret/OPRETECParser.java index 179b736..d5ed302 100644 --- a/opret-testapp/src/main/java/org/tcpid/opret/OPRETECParser.java +++ b/opret-testapp/src/main/java/org/tcpid/opret/OPRETECParser.java @@ -9,9 +9,11 @@ import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.NoSuchElementException; import java.util.concurrent.CopyOnWriteArrayList; import org.bitcoinj.core.PartialMerkleTree; @@ -39,9 +41,10 @@ import com.google.common.primitives.Bytes; public class OPRETECParser extends OPRETBaseHandler { private static final Logger logger = LoggerFactory.getLogger(OPRETECParser.class); - public static final Hash HASH = new Hash(); + public static final Hash HASH = new Hash(); private static final List OPRET_MAGIC_EC1C = Bytes.asList(Utils.HEX.decode("ec1c")); + private static final List OPRET_MAGIC_EC1D = Bytes.asList(Utils.HEX.decode("ec1d")); private static final List OPRET_MAGIC_ECA1 = Bytes.asList(Utils.HEX.decode("eca1")); private static final List OPRET_MAGIC_ECA2 = Bytes.asList(Utils.HEX.decode("eca2")); @@ -59,7 +62,7 @@ public class OPRETECParser extends OPRETBaseHandler { logger.debug("Using VerifyKey {}", k); if (k.verify(revokemsg, sig)) { - logger.debug("REVOKED VerifyKey {}", k); + logger.debug("REVOKED Key {}", k.toString()); return true; } else { logger.debug("SIGNATURE does not match!"); @@ -67,6 +70,60 @@ public class OPRETECParser extends OPRETBaseHandler { } } + public static Script[] getAnnounceFirstScript(final MasterSigningKey msk, final MasterVerifyKey subkey) { + logger.debug("getAnnounceFirstScript"); + final MasterVerifyKey mvk = msk.getMasterVerifyKey(); + final byte[] sig = msk.sign(subkey.toBytes()); + + logger.debug("using key {}", Encoder.HEX.encode(mvk.toBytes())); + final byte[] noncebytes = Util.zeros(32); + final byte[] sharedkey = HASH.sha256(HASH.sha256(Bytes.concat(mvk.toBytes(), noncebytes))); + final byte[] xornonce = Arrays.copyOfRange(HASH.sha256(Bytes.concat(sharedkey, noncebytes)), 0, 24); + logger.debug("xornonce {}", Encoder.HEX.encode(xornonce)); + logger.debug("sharedkey {}", Encoder.HEX.encode(sharedkey)); + + final byte[] cipher = Util.zeros(96); + final byte[] msg = Bytes.concat(subkey.toBytes(), sig); + + sodium(); + Sodium.crypto_stream_xsalsa20_xor(cipher, msg, 96, xornonce, sharedkey); + + final Script script1 = new ScriptBuilder().op(OP_RETURN).data(Utils.HEX.decode("eca1")) + .data(Arrays.copyOfRange(cipher, 0, 48)).data(mvk.getShortHash()).build(); + final Script script2 = new ScriptBuilder().op(OP_RETURN).data(Utils.HEX.decode("eca2")) + .data(Arrays.copyOfRange(cipher, 48, 96)).data(mvk.getShortHash()).build(); + final Script[] scripts = { script1, script2 }; + return scripts; + } + + public static Script[] getAnnounceNextScript(final MasterSigningKey msk, final MasterVerifyKey prev, + final MasterVerifyKey next) { + logger.debug("getAnnounceNextScript"); + + final MasterVerifyKey mvk = msk.getMasterVerifyKey(); + + final byte[] sig = msk.sign(next.toBytes()); + + logger.debug("using key {}", Encoder.HEX.encode(prev.toBytes())); + final byte[] sharedkey = HASH.sha256(HASH.sha256(prev.toBytes())); + final byte[] xornonce = Arrays.copyOfRange(HASH.sha256(sharedkey), 0, 24); + logger.debug("xornonce {}", Encoder.HEX.encode(xornonce)); + logger.debug("sharedkey {}", Encoder.HEX.encode(sharedkey)); + + final byte[] cipher = Util.zeros(96); + final byte[] msg = Bytes.concat(next.toBytes(), sig); + + sodium(); + Sodium.crypto_stream_xsalsa20_xor(cipher, msg, 96, xornonce, sharedkey); + + final Script script1 = new ScriptBuilder().op(OP_RETURN).data(Utils.HEX.decode("eca3")) + .data(Arrays.copyOfRange(cipher, 0, 48)).data(mvk.getShortHash()).data(prev.getShortHash()).build(); + final Script script2 = new ScriptBuilder().op(OP_RETURN).data(Utils.HEX.decode("eca4")) + .data(Arrays.copyOfRange(cipher, 48, 96)).data(mvk.getShortHash()).data(prev.getShortHash()).build(); + final Script[] scripts = { script1, script2 }; + return scripts; + } + public static Script getRevokeScript(final SigningKey key) { final byte[] revokemsg = Bytes.concat("Revoke ".getBytes(), key.getVerifyKey().toHash()); final byte[] sig = key.sign(revokemsg); @@ -76,9 +133,12 @@ public class OPRETECParser extends OPRETBaseHandler { return script; } + private boolean needscan; + protected final Map merkleHashMap = Collections.synchronizedMap(new HashMap<>()); protected final Map transHashMap = Collections.synchronizedMap(new HashMap<>()); + protected final Map, List> transA1HashMap = Collections .synchronizedMap(new HashMap<>()); protected final Map, List> transA2HashMap = Collections @@ -91,11 +151,15 @@ public class OPRETECParser extends OPRETBaseHandler { .synchronizedMap(new HashMap<>()); protected final Map, List> trans52HashMap = Collections .synchronizedMap(new HashMap<>()); - protected final Map, List> verifyKeys = Collections.synchronizedMap(new HashMap<>()); private final CopyOnWriteArrayList> opReturnChangeListeners = new CopyOnWriteArrayList<>(); + public OPRETECParser() { + super(); + needscan = true; + } + /** * Adds an event listener object. Methods on this object are called when * scripts watched by this wallet change. The listener is executed by the @@ -113,8 +177,9 @@ public class OPRETECParser extends OPRETBaseHandler { } verifyKeys.get(hash).add(key); - logger.debug("Adding pkhash {}", key.getShortHash()); + logger.debug("Adding pkhash {}", Utils.HEX.encode(key.getShortHash())); addOPRET(key.getShortHash(), earliestTime); + needscan = true; } public boolean cryptoSelfTest() { @@ -224,8 +289,19 @@ public class OPRETECParser extends OPRETBaseHandler { } logger.debug("sig matches"); - - k.setFirstValidSubKey(new MasterVerifyKey(vk), isT1 ? selfTx : otherTx, isT1 ? otherTx : selfTx); + final MasterVerifyKey subkey = new MasterVerifyKey(vk); + try { + k.setFirstValidSubKey(subkey, isT1 ? selfTx : otherTx, isT1 ? otherTx : selfTx); + final Date time = selfTx.getTime(); + if (time != null) { + this.addVerifyKey(subkey, time.getTime() / 1000); + } + needscan = true; + logger.info("MVK {} announced first subkey {}", k.toString(), subkey.toString()); + } catch (final IndexOutOfBoundsException e) { + logger.info("FAILED: MVK {} announced first key {}, but it was already announced", k.toString(), + subkey.toString()); + } otherTransHashMap.get(pkhash).remove(otherTx); if (otherTransHashMap.get(pkhash).isEmpty()) { otherTransHashMap.remove(pkhash); @@ -244,6 +320,99 @@ public class OPRETECParser extends OPRETBaseHandler { return false; } + private boolean handleAnnounceNext(final OPRETTransaction selfTx, + final Map, List> selfTransHashMap, + final Map, List> otherTransHashMap, final boolean isT1) { + + logger.debug("handleAnnounceNext"); + final byte[] selfData = Bytes.toArray(selfTx.opretData.get(1)); + if (selfData.length != 48) { + logger.debug("invalid chunk1 size = {}", selfData.length); + return false; + } + + final List pkhash = selfTx.opretData.get(2); + if (pkhash.size() != 12) { + logger.debug("chunk 2 size != 12 but {} ", pkhash.size()); + return false; + } + + final List subpkhash = selfTx.opretData.get(3); + if (subpkhash.size() != 12) { + logger.debug("chunk 2 size != 12 but {} ", subpkhash.size()); + return false; + } + + if (!verifyKeys.containsKey(pkhash)) { + logger.debug("!verifyKeys.containsKey(pkhash)"); + return false; + } + + if (otherTransHashMap.containsKey(pkhash)) { + for (final OPRETTransaction otherTx : otherTransHashMap.get(pkhash)) { + final byte[] otherData = Bytes.toArray(otherTx.opretData.get(1)); + final byte[] cipher = isT1 ? Bytes.concat(selfData, otherData) : Bytes.concat(otherData, selfData); + + for (final MasterVerifyKey k : verifyKeys.get(pkhash)) { + final MasterVerifyKey vk_n = k.getSubKeybyHash(subpkhash); + if (vk_n == null) { + logger.debug("! k.getSubKeybyHash(subpkhash)"); + continue; + } + + final byte[] sharedkey = HASH.sha256(vk_n.toHash()); + final byte[] xornonce = Arrays.copyOfRange(HASH.sha256(sharedkey), 0, 24); + logger.debug("checking key {}", Encoder.HEX.encode(k.toBytes())); + logger.debug("checking subkey {}", Encoder.HEX.encode(vk_n.toBytes())); + logger.debug("xornonce {}", Encoder.HEX.encode(xornonce)); + logger.debug("sharedkey {}", Encoder.HEX.encode(sharedkey)); + sodium(); + final byte[] msg = Util.zeros(96); + Sodium.crypto_stream_xsalsa20_xor(msg, cipher, 96, xornonce, sharedkey); + final byte[] vk = Arrays.copyOfRange(msg, 0, 32); + final byte[] sig = Arrays.copyOfRange(msg, 32, 96); + logger.debug("Checking sig {} with key {}", Encoder.HEX.encode(sig), Encoder.HEX.encode(vk)); + + if (!k.verify(vk, sig)) { + logger.debug("sig does not match"); + continue; + } + + logger.debug("sig matches"); + final MasterVerifyKey subkey = new MasterVerifyKey(vk); + try { + k.setNextValidSubKey(vk_n, subkey, isT1 ? selfTx : otherTx, isT1 ? otherTx : selfTx); + final Date time = selfTx.getTime(); + if (time != null) { + this.addVerifyKey(subkey, time.getTime() / 1000); + } + needscan = true; + logger.info("MKV {} announced next subkey:\n\t{} -> {}", k.toString(), vk_n.toString(), + subkey.toString()); + } catch (final NoSuchElementException e) { + logger.info("FAILED or duplicate: MKV {} announced next subkey:\n\t{} -> {}", k.toString(), + vk_n.toString(), subkey.toString()); + } + otherTransHashMap.get(pkhash).remove(otherTx); + if (otherTransHashMap.get(pkhash).isEmpty()) { + otherTransHashMap.remove(pkhash); + } + return true; + } + } + } else { + logger.debug("!otherTransHashMap.containsKey(pkhash)"); + } + + // no matching transaction found, save for later + if (!selfTransHashMap.containsKey(pkhash)) { + selfTransHashMap.put(pkhash, new ArrayList()); + } + selfTransHashMap.get(pkhash).add(selfTx); + + return false; + } + private boolean handleEC0F(final OPRETTransaction t) { final byte[] sig = Bytes.toArray(t.opretData.get(1)); if ((sig.length != 64)) { @@ -264,11 +433,11 @@ public class OPRETECParser extends OPRETBaseHandler { for (final MasterVerifyKey k : verifyKeys.get(pkhash)) { if (checkKeyforRevoke(k, sig)) { if (k.isRevoked()) { - logger.debug("Duplicate REVOKE PK {} - SIG {}", Utils.HEX.encode(k.getShortHash()), + logger.info("Duplicate REVOKE PK {} - SIG {}", Utils.HEX.encode(k.getShortHash()), Utils.HEX.encode(sig)); } else { k.setRevoked(true); - logger.debug("REVOKE PK {} - SIG {}", Utils.HEX.encode(k.getShortHash()), Utils.HEX.encode(sig)); + logger.info("REVOKE PK {} - SIG {}", Utils.HEX.encode(k.getShortHash()), Utils.HEX.encode(sig)); } queueOnOPRETRevoke(k); return true; @@ -284,6 +453,7 @@ public class OPRETECParser extends OPRETBaseHandler { } private boolean handleEC1D(final OPRETTransaction t) { + // TODO Auto-generated method stub final byte[] sig = Bytes.toArray(t.opretData.get(1)); if ((sig.length != 64)) { logger.debug("chunk 1 size != 64, but {}", sig.length); @@ -300,20 +470,18 @@ public class OPRETECParser extends OPRETBaseHandler { return false; } - for (final MasterVerifyKey k : verifyKeys.get(pkhash)) { - if (checkKeyforRevoke(k, sig)) { - if (k.isRevoked()) { - logger.debug("Duplicate REVOKE PK {} - SIG {}", Utils.HEX.encode(k.getShortHash()), - Utils.HEX.encode(sig)); - } else { - k.setRevoked(true); - logger.debug("REVOKE PK {} - SIG {}", Utils.HEX.encode(k.getShortHash()), Utils.HEX.encode(sig)); - } - queueOnOPRETId(k); - return true; - - } - } + // FIXME + /* + * for (final MasterVerifyKey k : verifyKeys.get(pkhash)) { if + * (checkKeyforRevoke(k, sig)) { if (k.isRevoked()) { + * logger.debug("Duplicate REVOKE PK {} - SIG {}", + * Utils.HEX.encode(k.getShortHash()), Utils.HEX.encode(sig)); } else { + * k.setRevoked(true); logger.debug("REVOKE PK {} - SIG {}", + * Utils.HEX.encode(k.getShortHash()), Utils.HEX.encode(sig)); } + * queueOnOPRETId(k); return true; + * + * } } + */ return false; } @@ -339,93 +507,19 @@ public class OPRETECParser extends OPRETBaseHandler { private boolean handleECA3(final OPRETTransaction t1) { logger.debug("handleECA3"); - return handleNextKey(t1, transA3HashMap, transA4HashMap, true); + return handleAnnounceNext(t1, transA3HashMap, transA4HashMap, true); } private boolean handleECA4(final OPRETTransaction t2) { logger.debug("handleECA4"); - return handleNextKey(t2, transA4HashMap, transA3HashMap, false); + return handleAnnounceNext(t2, transA4HashMap, transA3HashMap, false); } - private boolean handleNextKey(final OPRETTransaction selfTx, - final Map, List> selfTransHashMap, - final Map, List> otherTransHashMap, final boolean isT1) { - - logger.debug("handleNextKey"); - final byte[] selfData = Bytes.toArray(selfTx.opretData.get(1)); - if (selfData.length != 48) { - logger.debug("invalid chunk1 size = {}", selfData.length); - return false; - } - - final List pkhash = selfTx.opretData.get(2); - if (pkhash.size() != 12) { - logger.debug("chunk 2 size != 12 but {} ", pkhash.size()); - return false; - } - - final List subpkhash = selfTx.opretData.get(3); - if (subpkhash.size() != 12) { - logger.debug("chunk 2 size != 12 but {} ", subpkhash.size()); - return false; - } - - if (!verifyKeys.containsKey(pkhash)) { - return false; - } - - if (otherTransHashMap.containsKey(pkhash)) { - for (final OPRETTransaction otherTx : otherTransHashMap.get(pkhash)) { - final byte[] otherData = Bytes.toArray(otherTx.opretData.get(1)); - final byte[] cipher = isT1 ? Bytes.concat(selfData, otherData) : Bytes.concat(otherData, selfData); - - for (final MasterVerifyKey k : verifyKeys.get(pkhash)) { - final MasterVerifyKey vk_n = k.getSubKeybyHash(subpkhash); - if (vk_n == null) { - continue; - } - - final byte[] sharedkey = HASH.sha256(vk_n.toHash()); - final byte[] xornonce = Arrays.copyOfRange(HASH.sha256(sharedkey), 0, 24); - logger.debug("checking key {}", Encoder.HEX.encode(k.toBytes())); - logger.debug("checking subkey {}", Encoder.HEX.encode(vk_n.toBytes())); - logger.debug("xornonce {}", Encoder.HEX.encode(xornonce)); - logger.debug("sharedkey {}", Encoder.HEX.encode(sharedkey)); - sodium(); - final byte[] msg = Util.zeros(96); - Sodium.crypto_stream_xsalsa20_xor(msg, cipher, 96, xornonce, sharedkey); - final byte[] vk = Arrays.copyOfRange(msg, 0, 32); - final byte[] sig = Arrays.copyOfRange(msg, 32, 96); - logger.debug("Checking sig {} with key {}", Encoder.HEX.encode(sig), Encoder.HEX.encode(vk)); - - if (!k.verify(vk, sig)) { - logger.debug("sig does not match"); - continue; - } - - logger.debug("sig matches"); - - k.setNextValidSubKey(vk_n, new MasterVerifyKey(vk), isT1 ? selfTx : otherTx, - isT1 ? otherTx : selfTx); - otherTransHashMap.get(pkhash).remove(otherTx); - if (otherTransHashMap.get(pkhash).isEmpty()) { - otherTransHashMap.remove(pkhash); - } - return true; - } - } - } - - // no matching transaction found, save for later - if (!selfTransHashMap.containsKey(pkhash)) { - selfTransHashMap.put(pkhash, new ArrayList()); - } - selfTransHashMap.get(pkhash).add(selfTx); - - return false; + public boolean needScan() { + // TODO Auto-generated method stub + return this.needscan; } - @Override public boolean pushTransaction(final OPRETTransaction t) { logger.debug("checking {}", t.opretData); @@ -475,6 +569,20 @@ public class OPRETECParser extends OPRETBaseHandler { return false; } + @Override + public void pushTransactions(List pushlist) { + Collections.sort(pushlist, (a, b) -> { + final List chunka = a.opretData.get(0); + final List chunkb = b.opretData.get(0); + final Long la = ((long) chunka.get(1) * 256) + (long) chunka.get(0); + final Long lb = ((long) chunkb.get(1) * 256) + (long) chunkb.get(0); + return la.compareTo(lb); + }); + for (final OPRETTransaction t : pushlist) { + pushTransaction(t); + } + } + private void queueOnOPRETId(final MasterVerifyKey k) { // TODO Auto-generated method stub @@ -505,4 +613,9 @@ public class OPRETECParser extends OPRETBaseHandler { removeOPRET(key.getShortHash()); } + + public void willScan() { + // TODO Auto-generated method stub + this.needscan = false; + } } 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 480d4d8..29aa639 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 @@ -7,12 +7,14 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.io.PrintWriter; import java.util.Arrays; +import java.util.LinkedList; import org.bitcoinj.core.Address; import org.bitcoinj.core.AddressFormatException; import org.bitcoinj.core.Coin; import org.bitcoinj.core.InsufficientMoneyException; import org.bitcoinj.core.NetworkParameters; +import org.bitcoinj.core.Sha256Hash; import org.bitcoinj.core.Transaction; import org.bitcoinj.core.Utils; import org.bitcoinj.script.Script; @@ -22,6 +24,7 @@ import org.libsodium.jni.crypto.Hash; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.tcpid.key.MasterSigningKey; +import org.tcpid.key.MasterVerifyKey; import org.tcpid.opret.OPRETECParser; import org.tcpid.opretj.OPRETWallet; import org.tcpid.opretj.OPRETWalletAppKit; @@ -40,8 +43,18 @@ public class App { private final static Logger logger = LoggerFactory.getLogger(App.class); private final static MasterSigningKey SK = new MasterSigningKey(HASH.sha256("TESTSEED".getBytes())); + public final static LinkedList WATCHKEYS = new LinkedList<>(); + public static OPRETWalletAppKit KIT; + public static NetworkParameters PARAMS; + public final static OPRETECParser PARSER = new OPRETECParser(); + public static String WALLETNAME; private static void displayBalance(final OPRETWalletAppKit kit, final PrintWriter out) { + if (kit == null) { + out.println("Need blockchain scan"); + out.flush(); + return; + } out.write("Balance: " + kit.wallet().getBalance().toFriendlyString() + "\n"); out.flush(); } @@ -51,10 +64,17 @@ public class App { out.write("\n"); out.write("help - this screen\n"); out.write("quit - exit the application\n"); + out.write("scan - scan the blockchain\n"); out.write("balance - show your available balance\n"); out.write("receive - display an address to receive coins\n"); out.write("empty
- send all coins to the address\n"); - out.write("opret - send opret\n"); + out.write("\n"); + out.write("newkey - create a new key with seed = sha256(hashseed)\n"); + out.write("listsub - list the subkey of with optional \n"); + out.write("revoke - revoke a key on the blockchain\n"); + out.write("announce - announce the subkey signed with its key\n"); + out.write("watch - listen on the blockchain for all actions on \n"); + out.write("listwatch - listen on the blockchain for all actions on \n"); out.write("\n"); out.flush(); @@ -83,15 +103,16 @@ public class App { } - private static void handleConsole(final OPRETWalletAppKit kit) throws IOException { + private static void handleConsole() throws IOException { final ConsoleReader reader = new ConsoleReader(); - final String[] cmds = { "help", "quit", "exit", "balance", "receive", "empty", "opret" }; + final String[] cmds = { "help", "quit", "exit", "balance", "receive", "empty", "opret", "newkey", "listsub", + "revoke", "announce", "listen", "listwatch", "scan", "watch" }; reader.addCompleter(new StringsCompleter(cmds)); final PrintWriter out = new PrintWriter(reader.getOutput()); reader.setPrompt("opret> "); String line; displayHelp(out); - displayBalance(kit, out); + displayBalance(KIT, out); while ((line = reader.readLine()) != null) { String[] argv = line.split("\\s"); @@ -108,15 +129,16 @@ public class App { switch (cmd.toLowerCase()) { case "quit": + case "exit": return; case "help": displayHelp(out); break; case "balance": - displayBalance(kit, out); + displayBalance(KIT, out); break; case "receive": - final String receiveStr = kit.wallet().freshReceiveAddress().toString(); + final String receiveStr = KIT.wallet().freshReceiveAddress().toString(); out.write("send money to: " + receiveStr + "\n"); try { @@ -134,9 +156,9 @@ public class App { out.println("'" + argv[0] + "'"); out.flush(); try { - final SendRequest request = SendRequest.emptyWallet(Address.fromBase58(kit.params(), argv[0])); + final SendRequest request = SendRequest.emptyWallet(Address.fromBase58(KIT.params(), argv[0])); try { - kit.wallet().sendCoins(request); + KIT.wallet().sendCoins(request); } catch (final InsufficientMoneyException e) { out.println(e.getLocalizedMessage()); out.flush(); @@ -146,37 +168,131 @@ public class App { out.flush(); } break; - case "opret": - sendOPReturn(kit, out); + case "newkey": { + if (argv.length != 1) { + out.println("'newkey ' needs a an argument"); + continue; + } + final byte[] seed = Sha256Hash.of(argv[0].getBytes()).getBytes(); + final MasterSigningKey key = new MasterSigningKey(seed); + out.println("Private: " + key.toString()); + out.println("Public: " + key.getMasterVerifyKey().toString()); + out.println("Sha256(pub): " + Utils.HEX.encode(key.getMasterVerifyKey().toHash())); + out.println("Sha256(pub)[0:12]: " + Utils.HEX.encode(key.getMasterVerifyKey().getShortHash())); + out.flush(); + } + break; + case "listsub": { + if (argv.length != 2) { + out.println("'listsub ' needs two arguments"); + continue; + } + final Long index = new Long(argv[0]); + final MasterSigningKey mk = new MasterSigningKey(Utils.HEX.decode(argv[1])); + final MasterSigningKey key = mk.getSubKey(index); + out.println("Private: " + key.toString()); + out.println("Public: " + key.getMasterVerifyKey().toString()); + out.println("Sha256(pub): " + Utils.HEX.encode(key.getMasterVerifyKey().toHash())); + out.println("Sha256(pub)[0:12]: " + Utils.HEX.encode(key.getMasterVerifyKey().getShortHash())); + out.flush(); + } + break; + case "announce": { + if (argv.length != 2) { + out.println("'announce ' needs two arguments"); + continue; + } + final Long index = new Long(argv[0]); + final MasterSigningKey mk = new MasterSigningKey(Utils.HEX.decode(argv[1])); + if (index == 0L) { + final MasterVerifyKey subkey = mk.getSubKey(index).getMasterVerifyKey(); + if (!sendAnnounceFirst(mk, subkey, KIT, out)) { + out.println("announce failed"); + } + } else { + final MasterVerifyKey prev = mk.getSubKey(index - 1L).getMasterVerifyKey(); + final MasterVerifyKey next = mk.getSubKey(index).getMasterVerifyKey(); + if (!sendAnnounceNext(mk, prev, next, KIT, out)) { + out.println("announce failed"); + } + } + } + break; + case "revoke": + if (argv.length != 1) { + out.println("'revoke ' needs a an argument"); + continue; + } + final MasterSigningKey mk = new MasterSigningKey(Utils.HEX.decode(argv[0])); + if (!sendOPReturn(mk, KIT, out)) { + out.println("revoke failed"); + } + break; + case "watch": { + if (argv.length != 1) { + out.println("'watch ' needs a an argument"); + continue; + } + final MasterVerifyKey m = new MasterVerifyKey(Utils.HEX.decode(argv[0])); + if (!WATCHKEYS.contains(m)) { + WATCHKEYS.add(m); + PARSER.addVerifyKey(m, OPRET_BIRTHDAY); + } + } + break; + case "scan": { + scanBlockchain(); + } + // break; Fall through + case "listwatch": + if (!WATCHKEYS.isEmpty()) { + out.println("\n"); + out.println("Watching Keys:"); + for (final MasterVerifyKey m : WATCHKEYS) { + out.println("\tKey: " + m.toString()); + out.println("\t\tSubKeys:"); + + for (final MasterVerifyKey k : m.subkeys) { + out.println("\t\t" + k.toString()); + } + out.println("\n"); + + } + out.flush(); + } break; default: out.println("Unknown command. Use 'help' to display available commands."); break; } } + return; } public static void main(final String[] args) throws Exception { - 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); - parser.accepts("help", "Displays program options"); - final OptionSet opts = parser.parse(args); + final OptionParser optparser = new OptionParser(); + final OptionSpec net = optparser.accepts("net", "The network to run on").withRequiredArg() + .ofType(NetworkEnum.class).defaultsTo(NetworkEnum.TEST); + + final OptionSpec name = optparser.accepts("name", "The name of the wallet").withRequiredArg() + .ofType(String.class).defaultsTo("opretwallet"); + + optparser.accepts("help", "Displays program options"); + final OptionSet opts = optparser.parse(args); if (opts.has("help")) { - System.err.println("usage: App --net=MAIN/TEST/REGTEST"); - parser.printHelpOn(System.err); + System.err.println("usage: App [--net=MAIN/TEST/REGTEST] [--name=]"); + optparser.printHelpOn(System.err); return; } if (!opts.has(net)) { System.err.println("No net specified, using TestNet!"); } - final NetworkParameters params = net.value(opts).get(); + WALLETNAME = name.value(opts); + PARAMS = net.value(opts).get(); - final OPRETECParser bs = new OPRETECParser(); - - final boolean chk = bs.cryptoSelfTest(); + final boolean chk = PARSER.cryptoSelfTest(); if (chk) { System.err.println("Crypto self test: PASSED"); } else { @@ -184,55 +300,84 @@ public class App { System.exit(-1); } - bs.addOPRETECRevokeEventListener((key) -> { + PARSER.addOPRETECRevokeEventListener((key) -> { System.out.println("Revoked Key: " + Utils.HEX.encode(key.toBytes())); }); - long earliestTime; - if (params.getId().equals(NetworkParameters.ID_REGTEST)) { - earliestTime = OPRET_BIRTHDAY; - } else if (params.getId().equals(NetworkParameters.ID_TESTNET)) { - earliestTime = OPRET_BIRTHDAY; + if (PARAMS.getId().equals(NetworkParameters.ID_REGTEST)) { + } else if (PARAMS.getId().equals(NetworkParameters.ID_TESTNET)) { } else { - earliestTime = Utils.currentTimeSeconds(); + Utils.currentTimeSeconds(); } - bs.addVerifyKey(SK.getMasterVerifyKey(), earliestTime); + /* + * final MasterVerifyKey SVK = SK.getMasterVerifyKey(); + * WATCHKEYS.add(SVK); + * + * for (final MasterVerifyKey m : WATCHKEYS) { PARSER.addVerifyKey(m, + * earliestTime); } + * + * scanBlockchain(); + */ - final OPRETWalletAppKit kit = new OPRETWalletAppKit(params, new File("."), "opretwallet" + params.getId(), bs); + handleConsole(); + if (KIT != null) { + System.out.println("shutting down"); + KIT.stopAsync(); + KIT.awaitTerminated(); + } + } - kit.addListener(new Service.Listener() { - @Override - public void failed(final Service.State from, final Throwable failure) { - logger.error(failure.getMessage()); + public static void scanBlockchain() { + if (!PARSER.needScan()) { + System.err.println("No scan needed."); + return; + } + if (KIT != null) { + KIT.rescanBlockchain(); + } + KIT = new OPRETWalletAppKit(PARAMS, new File("."), WALLETNAME + PARAMS.getId(), PARSER); + + while (PARSER.needScan()) { + + KIT.addListener(new Service.Listener() { + @Override + public void failed(final Service.State from, final Throwable failure) { + logger.error(failure.getMessage()); + System.exit(-1); + } + }, Threading.SAME_THREAD); + + if (PARAMS.getId().equals(NetworkParameters.ID_REGTEST)) { + KIT.connectToLocalHost(); + } + final InputStream is = App.class.getResourceAsStream("/" + PARAMS.getId() + ".checkpoints"); + if (is != null) { + KIT.setCheckpoints(is); + } + KIT.startAsync(); + + System.out.println("Please wait for the blockchain to be downloaded!"); + + PARSER.willScan(); + try { + KIT.awaitRunning(); + } catch (final Exception e) { + System.err.println("Aborting - shutting down"); + // e.printStackTrace(); + KIT.stopAsync(); + KIT.awaitTerminated(); System.exit(-1); } - }, Threading.SAME_THREAD); - - if (params.getId().equals(NetworkParameters.ID_REGTEST)) { - kit.connectToLocalHost(); - } - final InputStream is = App.class.getResourceAsStream("/" + params.getId() + ".checkpoints"); - if (is != null) { - kit.setCheckpoints(is); - } - kit.startAsync(); - - System.out.println("Please wait for the blockchain to be downloaded!"); - - try { - kit.awaitRunning(); - } catch (final Exception e) { - System.err.println("Aborting - shutting down"); - // e.printStackTrace(); - kit.stopAsync(); - kit.awaitTerminated(); - System.exit(-1); + // after gathering all the key, replay the blockchain + if (PARSER.needScan()) { + System.out.println("Rescanning the blockchain for the newly learned keys!"); + KIT.rescanBlockchain(); + KIT = new OPRETWalletAppKit(PARAMS, new File("."), WALLETNAME + PARAMS.getId(), PARSER); + } } - final OPRETWallet wallet = kit.opretwallet(); - - wallet.addCoinsReceivedEventListener((wallet1, tx, prevBalance, newBalance) -> { + KIT.opretwallet().addCoinsReceivedEventListener((wallet1, tx, prevBalance, newBalance) -> { final Coin c = tx.getValue(wallet1); if (c.isPositive()) { @@ -267,21 +412,89 @@ public class App { * tx.getConfidence(); System.out.println("new block depth: " + * confidence.getDepthInBlocks()); }); */ - // wallet.allowSpendingUnconfirmedTransactions(); - - handleConsole(kit); - - System.out.println("shutting down"); - kit.stopAsync(); - kit.awaitTerminated(); + KIT.opretwallet().allowSpendingUnconfirmedTransactions(); } - private static boolean sendOPReturn(final OPRETWalletAppKit kit, final PrintWriter output) { + private static boolean sendAnnounceFirst(MasterSigningKey key, MasterVerifyKey subkey, final OPRETWalletAppKit kit, + final PrintWriter output) { + final OPRETWallet wallet = kit.opretwallet(); + final NetworkParameters params = wallet.getNetworkParameters(); + + Transaction t = new Transaction(params); + final Script[] scripts = OPRETECParser.getAnnounceFirstScript(key, subkey); + t.addOutput(Coin.ZERO, scripts[0]); + SendRequest request = SendRequest.forTx(t); + request.ensureMinRequiredFee = true; + request.shuffleOutputs = false; + try { + wallet.sendCoins(request); + } catch (final InsufficientMoneyException e) { + output.println(e.getLocalizedMessage()); + output.flush(); + return false; + } + logger.debug("SendRequest {}", request); + + t = new Transaction(params); + t.addOutput(Coin.ZERO, scripts[1]); + request = SendRequest.forTx(t); + request.ensureMinRequiredFee = true; + request.shuffleOutputs = false; + try { + wallet.sendCoins(request); + } catch (final InsufficientMoneyException e) { + output.println(e.getLocalizedMessage()); + output.flush(); + return false; + } + + logger.debug("SendRequest {}", request); + return true; + } + + private static boolean sendAnnounceNext(MasterSigningKey key, MasterVerifyKey prev, MasterVerifyKey next, + final OPRETWalletAppKit kit, final PrintWriter output) { + final OPRETWallet wallet = kit.opretwallet(); + final NetworkParameters params = wallet.getNetworkParameters(); + + Transaction t = new Transaction(params); + final Script[] scripts = OPRETECParser.getAnnounceNextScript(key, prev, next); + t.addOutput(Coin.ZERO, scripts[0]); + SendRequest request = SendRequest.forTx(t); + request.ensureMinRequiredFee = true; + request.shuffleOutputs = false; + try { + wallet.sendCoins(request); + } catch (final InsufficientMoneyException e) { + output.println(e.getLocalizedMessage()); + output.flush(); + return false; + } + logger.debug("SendRequest {}", request); + + t = new Transaction(params); + t.addOutput(Coin.ZERO, scripts[1]); + request = SendRequest.forTx(t); + request.ensureMinRequiredFee = true; + request.shuffleOutputs = false; + try { + wallet.sendCoins(request); + } catch (final InsufficientMoneyException e) { + output.println(e.getLocalizedMessage()); + output.flush(); + return false; + } + + logger.debug("SendRequest {}", request); + return true; + } + + private static boolean sendOPReturn(MasterSigningKey key, final OPRETWalletAppKit kit, final PrintWriter output) { final OPRETWallet wallet = kit.opretwallet(); final NetworkParameters params = wallet.getNetworkParameters(); final Transaction t = new Transaction(params); - final Script script = OPRETECParser.getRevokeScript(SK); + final Script script = OPRETECParser.getRevokeScript(key); t.addOutput(Coin.ZERO, script); final SendRequest request = SendRequest.forTx(t); request.ensureMinRequiredFee = true; diff --git a/opret-testapp/src/main/resources/logback.xml b/opret-testapp/src/main/resources/logback.xml index dec2de8..024290a 100644 --- a/opret-testapp/src/main/resources/logback.xml +++ b/opret-testapp/src/main/resources/logback.xml @@ -8,15 +8,17 @@ %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - - + + +