checkpoint

This commit is contained in:
Harald Hoyer 2016-09-15 18:24:39 +02:00
parent d97d867579
commit 086c52ce12
14 changed files with 591 additions and 201 deletions

View file

@ -110,6 +110,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.19.1</version>
<configuration>
<runOrder>alphabetical</runOrder>
</configuration>
@ -129,7 +130,7 @@
<artifactId>exec-maven-plugin</artifactId>
<version>1.2.1</version>
<configuration>
<mainClass>org.tcpid.opret.App</mainClass>
<mainClass>org.tcpid.opretj.testapp.App</mainClass>
<!-- <systemProperties> <systemProperty> <key>logback.configurationFile</key>
<value>${basedir}/opret-testapp/src/main/resources/logback.xml</value> </systemProperty>
</systemProperties> -->

View file

@ -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);
}
}
}

View file

@ -10,7 +10,8 @@ import org.tcpid.opretj.OPRETTransaction;
import com.google.common.primitives.Bytes;
public class MasterVerifyKey extends VerifyKey {
private final LinkedList<MasterVerifyKey> subkeys = new LinkedList<>();
// FIXME: make private again
public final LinkedList<MasterVerifyKey> 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);
}
}

View file

@ -102,6 +102,9 @@ public class VerifyKey implements Comparable<VerifyKey> {
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<VerifyKey> {
@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) {

View file

@ -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<Byte> OPRET_MAGIC_EC1C = Bytes.asList(Utils.HEX.decode("ec1c"));
private static final List<Byte> OPRET_MAGIC_EC1D = Bytes.asList(Utils.HEX.decode("ec1d"));
private static final List<Byte> OPRET_MAGIC_ECA1 = Bytes.asList(Utils.HEX.decode("eca1"));
private static final List<Byte> 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<Sha256Hash, PartialMerkleTree> merkleHashMap = Collections.synchronizedMap(new HashMap<>());
protected final Map<Sha256Hash, OPRETTransaction> transHashMap = Collections.synchronizedMap(new HashMap<>());
protected final Map<List<Byte>, List<OPRETTransaction>> transA1HashMap = Collections
.synchronizedMap(new HashMap<>());
protected final Map<List<Byte>, List<OPRETTransaction>> transA2HashMap = Collections
@ -91,11 +151,15 @@ public class OPRETECParser extends OPRETBaseHandler {
.synchronizedMap(new HashMap<>());
protected final Map<List<Byte>, List<OPRETTransaction>> trans52HashMap = Collections
.synchronizedMap(new HashMap<>());
protected final Map<List<Byte>, List<MasterVerifyKey>> verifyKeys = Collections.synchronizedMap(new HashMap<>());
private final CopyOnWriteArrayList<ListenerRegistration<OPRETECEventListener>> 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<Byte>, List<OPRETTransaction>> selfTransHashMap,
final Map<List<Byte>, List<OPRETTransaction>> 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<Byte> pkhash = selfTx.opretData.get(2);
if (pkhash.size() != 12) {
logger.debug("chunk 2 size != 12 but {} ", pkhash.size());
return false;
}
final List<Byte> 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<OPRETTransaction>());
}
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<Byte>, List<OPRETTransaction>> selfTransHashMap,
final Map<List<Byte>, List<OPRETTransaction>> 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<Byte> pkhash = selfTx.opretData.get(2);
if (pkhash.size() != 12) {
logger.debug("chunk 2 size != 12 but {} ", pkhash.size());
return false;
}
final List<Byte> 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<OPRETTransaction>());
}
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<OPRETTransaction> pushlist) {
Collections.sort(pushlist, (a, b) -> {
final List<Byte> chunka = a.opretData.get(0);
final List<Byte> 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;
}
}

View file

@ -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<MasterVerifyKey> 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 <address> - send all coins to the address\n");
out.write("opret - send opret\n");
out.write("\n");
out.write("newkey <hashseed> - create a new key with seed = sha256(hashseed)\n");
out.write("listsub <index> <key> - list the subkey of <key> with optional <index>\n");
out.write("revoke <key> - revoke a key on the blockchain\n");
out.write("announce <index> <master> - announce the subkey <index> signed with its <master> key\n");
out.write("watch <key> - listen on the blockchain for all actions on <key>\n");
out.write("listwatch <key> - listen on the blockchain for all actions on <key>\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 <hash>' 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 <index> <key>' 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 <index> <key>' 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 <key>' 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 <key>' 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<NetworkEnum> 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<NetworkEnum> net = optparser.accepts("net", "The network to run on").withRequiredArg()
.ofType(NetworkEnum.class).defaultsTo(NetworkEnum.TEST);
final OptionSpec<String> 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=<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;

View file

@ -8,15 +8,17 @@
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<logger name="org.tcpid.opret.App" level="debug" />
<logger name="org.bitcoinj.core.listeners.DownloadProgressTracker" level="info" />
<logger name="org.bitcoinj.core.PeerGroup" level="error" />
<logger name="org.bitcoinj.core.AbstractBlockChain" level="error" />
<logger name="org.bitcoinj.core.PeerSocketHandler" level="error" />
<logger name="org.bitcoinj.net.ConnectionHandler" level="error" />
<logger name="org.tcpid.opret.OPRETECParser" level="debug" />
<logger name="org.tcpid.opretj.testapp.App" level="debug" />
<logger name="org.tcpid.opret.OPRETECParser" level="info" />
<logger name="org.tcpid.opret.TestCrypto" level="debug" />
<logger name="org.tcpid.opret.TestPushTransaction" level="debug" />
<!--
<logger name="org.tcpid.opretj.OPRETWallet" level="debug" />
<logger name="eckey.OPRETSimpleLogger" level="debug" />
<logger name="eckey.OPRETSimpleParser" level="debug" />
<logger name="eckey.OPRETBaseHandler" level="debug" />

View file

@ -105,19 +105,19 @@ public class TestCrypto {
final MasterSigningKey msk = new MasterSigningKey(HASH.sha256("TESTSEED".getBytes()));
final MasterVerifyKey mvk = msk.getMasterVerifyKey();
final MasterVerifyKey subkey1 = msk.getSubKey(1L).getMasterVerifyKey();
final MasterVerifyKey subkey2 = msk.getSubKey(2L).getMasterVerifyKey();
final MasterVerifyKey prev = msk.getSubKey(1L).getMasterVerifyKey();
final MasterVerifyKey next = msk.getSubKey(2L).getMasterVerifyKey();
byte[] sig = msk.sign(subkey2.toBytes());
byte[] sig = msk.sign(next.toBytes());
logger.debug("using key {}", Encoder.HEX.encode(subkey1.toBytes()));
final byte[] sharedkey = HASH.sha256(HASH.sha256(subkey1.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);
byte[] msg = Bytes.concat(subkey2.toBytes(), sig);
byte[] msg = Bytes.concat(next.toBytes(), sig);
assertEquals(96, msg.length);
sodium();
@ -132,7 +132,7 @@ public class TestCrypto {
sig = Arrays.copyOfRange(msg, 32, 96);
logger.debug("vkb : {}", Encoder.HEX.encode(vkb));
assertTrue("Verification of signature failed.", mvk.verify(vkb, sig));
assertArrayEquals(subkey2.toBytes(), vkb);
assertArrayEquals(next.toBytes(), vkb);
}
@Test

View file

@ -63,7 +63,7 @@ public class TestPushTransaction {
opret_data.add(Bytes.asList(vkbsha96));
final OPRETTransaction t2 = new OPRETTransaction(Sha256Hash.of(nullbyte), Sha256Hash.of(nullbyte), opret_data);
// create t3 and t4 to test if the parser handles garbage
// create t3 and t4 to test if the PARSER handles garbage
opret_data = new ArrayList<>();
opret_data.add(Bytes.asList(Encoder.HEX.decode("eca2")));
opret_data.add(Bytes.asList(Arrays.copyOfRange(cipher, 0, 48)));
@ -214,7 +214,7 @@ public class TestPushTransaction {
opret_data.add(Bytes.asList(firstsub.getShortHash()));
final OPRETTransaction t2 = new OPRETTransaction(Sha256Hash.of(nullbyte), Sha256Hash.of(nullbyte), opret_data);
// create t3 and t4 to test if the parser handles garbage
// create t3 and t4 to test if the PARSER handles garbage
opret_data = new ArrayList<>();
opret_data.add(Bytes.asList(Encoder.HEX.decode("eca4")));
opret_data.add(Bytes.asList(Arrays.copyOfRange(cipher, 0, 48)));
@ -239,8 +239,9 @@ public class TestPushTransaction {
assertFalse(parser.pushTransaction(t4));
assertTrue(parser.pushTransaction(t1));
mvk.revokeSubKey(firstsub);
firstsub.setRevoked(true);
MasterVerifyKey subkey = mvk.getValidSubKey();
logger.debug("FirstValid: {}", subkey.toString());
assertArrayEquals(subkey.toBytes(),
Encoder.HEX.decode("11e14458b16050a23a772e469ee424f513c3eb81682c0f9f81f07e607c6bf917"));
@ -252,7 +253,7 @@ public class TestPushTransaction {
assertFalse(parser.pushTransaction(t4));
assertTrue(parser.pushTransaction(t2));
mvk.revokeSubKey(firstsub);
firstsub.setRevoked(true);
subkey = mvk.getValidSubKey();
assertArrayEquals(subkey.toBytes(),
Encoder.HEX.decode("11e14458b16050a23a772e469ee424f513c3eb81682c0f9f81f07e607c6bf917"));
@ -265,7 +266,7 @@ public class TestPushTransaction {
assertFalse(parser.pushTransaction(t3));
assertTrue(parser.pushTransaction(t2));
mvk.revokeSubKey(firstsub);
firstsub.setRevoked(true);
subkey = mvk.getValidSubKey();
assertArrayEquals(subkey.toBytes(),
Encoder.HEX.decode("11e14458b16050a23a772e469ee424f513c3eb81682c0f9f81f07e607c6bf917"));
@ -278,7 +279,7 @@ public class TestPushTransaction {
assertFalse(parser.pushTransaction(t3));
assertTrue(parser.pushTransaction(t1));
mvk.revokeSubKey(firstsub);
firstsub.setRevoked(true);
subkey = mvk.getValidSubKey();
assertArrayEquals(subkey.toBytes(),
Encoder.HEX.decode("11e14458b16050a23a772e469ee424f513c3eb81682c0f9f81f07e607c6bf917"));

View file

@ -14,8 +14,8 @@ public interface OPRETHandlerInterface {
Set<List<Byte>> getOPRETSet();
boolean pushTransaction(OPRETTransaction t);
boolean removeOPRETChangeEventListener(OPRETChangeEventListener listener);
void pushTransactions(List<OPRETTransaction> pushlist);
}

View file

@ -11,7 +11,6 @@ import com.google.common.primitives.Bytes;
public class OPRETSimpleLogger extends OPRETBaseHandler {
private static final Logger logger = LoggerFactory.getLogger(OPRETSimpleLogger.class);
@Override
public boolean pushTransaction(final OPRETTransaction t) {
final StringBuilder buf = new StringBuilder();
@ -24,4 +23,11 @@ public class OPRETSimpleLogger extends OPRETBaseHandler {
return true;
}
@Override
public void pushTransactions(List<OPRETTransaction> pushlist) {
for (final OPRETTransaction t : pushlist) {
pushTransaction(t);
}
}
}

View file

@ -1,6 +1,7 @@
package org.tcpid.opretj;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
import org.bitcoinj.core.PartialMerkleTree;
@ -18,11 +19,13 @@ public class OPRETTransaction implements Serializable {
public final Sha256Hash txHash;
public final List<List<Byte>> opretData;
private PartialMerkleTree partialMerkleTree;
private Date date;
public OPRETTransaction(final Sha256Hash blockHash, final Sha256Hash txHash, final List<List<Byte>> opret_data) {
this.blockHash = blockHash;
this.txHash = txHash;
this.opretData = opret_data;
this.date = null;
}
public PartialMerkleTree getPartialMerkleTree() {
@ -45,4 +48,14 @@ public class OPRETTransaction implements Serializable {
}
return buf.toString();
}
public void setTime(Date time) {
if (this.date == null) {
this.date = time;
}
}
public Date getTime() {
return this.date;
}
}

View file

@ -142,6 +142,7 @@ public class OPRETWallet extends Wallet implements BlocksDownloadedEventListener
@Override
public void onBlocksDownloaded(final Peer peer, final Block block, final FilteredBlock filteredBlock,
final int blocksLeft) {
final ArrayList<OPRETTransaction> pushlist = new ArrayList<>();
if (!pendingTransactions.containsKey(block.getHash())) {
return;
@ -149,7 +150,12 @@ public class OPRETWallet extends Wallet implements BlocksDownloadedEventListener
for (final OPRETTransaction t : pendingTransactions.get(block.getHash()).values()) {
t.setPartialMerkleTree(filteredBlock.getPartialMerkleTree());
opbs.pushTransaction(t);
t.setTime(block.getTime());
pushlist.add(t);
}
if (!pushlist.isEmpty()) {
opbs.pushTransactions(pushlist);
}
pendingTransactions.remove(block.getHash());
@ -172,6 +178,7 @@ public class OPRETWallet extends Wallet implements BlocksDownloadedEventListener
logger.debug("False Positive Transaction {}", tx.toString());
return;
}
logger.debug("Found Transaction {}", tx.toString());
final Sha256Hash h = block.getHeader().getHash();
if (!pendingTransactions.containsKey(h)) {

View file

@ -15,12 +15,14 @@ import org.bitcoinj.wallet.Wallet;
public class OPRETWalletAppKit extends WalletAppKit {
// private final Logger logger = LoggerFactory.getLogger(OPRETWallet.class);
private final OPRETHandlerInterface opbs;
private File spvblockstorefile;
public OPRETWalletAppKit(final NetworkParameters params, final File directory, final String filePrefix,
final OPRETHandlerInterface bs) {
super(params, directory, filePrefix);
opbs = bs;
walletFactory = (params1, keyChainGroup) -> new OPRETWallet(params1, keyChainGroup, opbs);
spvblockstorefile = null;
}
@Override
@ -53,6 +55,21 @@ public class OPRETWalletAppKit extends WalletAppKit {
|| params.getId().equals(NetworkParameters.ID_TESTNET)) {
file.deleteOnExit();
}
this.spvblockstorefile = file;
return new SPVBlockStore(params, file);
}
public void rescanBlockchain() {
wallet().clearTransactions(0);
wallet().setLastBlockSeenHeight(-1); // magic value
wallet().setLastBlockSeenHash(null);
stopAsync();
awaitTerminated();
try {
this.spvblockstorefile.delete();
} catch (final Exception e) {
e.printStackTrace();
}
}
}