From 086c52ce12f997e7269a1132a97a863636d31788 Mon Sep 17 00:00:00 2001 From: Harald Hoyer Date: Thu, 15 Sep 2016 18:24:39 +0200 Subject: [PATCH] checkpoint --- opret-testapp/pom.xml | 3 +- .../java/org/tcpid/key/MasterSigningKey.java | 6 +- .../java/org/tcpid/key/MasterVerifyKey.java | 32 +- .../main/java/org/tcpid/key/VerifyKey.java | 5 +- .../java/org/tcpid/opret/OPRETECParser.java | 315 +++++++++++----- .../java/org/tcpid/opretj/testapp/App.java | 347 ++++++++++++++---- opret-testapp/src/main/resources/logback.xml | 6 +- .../test/java/org/tcpid/opret/TestCrypto.java | 14 +- .../org/tcpid/opret/TestPushTransaction.java | 13 +- .../tcpid/opretj/OPRETHandlerInterface.java | 4 +- .../org/tcpid/opretj/OPRETSimpleLogger.java | 8 +- .../org/tcpid/opretj/OPRETTransaction.java | 13 + .../java/org/tcpid/opretj/OPRETWallet.java | 9 +- .../org/tcpid/opretj/OPRETWalletAppKit.java | 17 + 14 files changed, 591 insertions(+), 201 deletions(-) diff --git a/opret-testapp/pom.xml b/opret-testapp/pom.xml index 9aa3486..7b58dcf 100644 --- a/opret-testapp/pom.xml +++ b/opret-testapp/pom.xml @@ -110,6 +110,7 @@ org.apache.maven.plugins maven-surefire-plugin + 2.19.1 alphabetical @@ -129,7 +130,7 @@ exec-maven-plugin 1.2.1 - org.tcpid.opret.App + org.tcpid.opretj.testapp.App 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 - - + + +