initial commit

This commit is contained in:
Harald Hoyer 2016-08-30 16:05:27 +02:00
commit e72cebcb28
27 changed files with 1281 additions and 0 deletions

17
.project Normal file
View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>opret-parent</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures>
</projectDescription>

View file

@ -0,0 +1,4 @@
activeProfiles=
eclipse.preferences.version=1
resolveWorkspaceProjects=true
version=1

31
opret-testapp/.classpath Normal file
View file

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>

4
opret-testapp/.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
/target/
/walletappkit-example.spvchain
/walletappkit-example.wallet
/opretwalletorg.bitcoin.test.wallet

23
opret-testapp/.project Normal file
View file

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>opret-testapp</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures>
</projectDescription>

View file

@ -0,0 +1,5 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
org.eclipse.jdt.core.compiler.compliance=1.8
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
org.eclipse.jdt.core.compiler.source=1.8

View file

@ -0,0 +1,4 @@
activeProfiles=
eclipse.preferences.version=1
resolveWorkspaceProjects=true
version=1

84
opret-testapp/pom.xml Normal file
View file

@ -0,0 +1,84 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.tcpid</groupId>
<artifactId>opret-parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>opret-testapp</artifactId>
<dependencies>
<dependency>
<groupId>org.bitcoinj</groupId>
<artifactId>bitcoinj-core</artifactId>
<version>0.14.3</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.0.13</version>
</dependency>
<dependency>
<groupId>org.abstractj.kalium</groupId>
<artifactId>kalium</artifactId>
<version>0.5.0</version>
</dependency>
<dependency>
<groupId>org.tcpid</groupId>
<artifactId>opretj</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>net.sf.jopt-simple</groupId>
<artifactId>jopt-simple</artifactId>
<version>4.3</version>
</dependency>
</dependencies>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.2.1</version>
<configuration>
<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>
</configuration>
</plugin>
</plugins>
</build>
</project>

View file

@ -0,0 +1,297 @@
package org.tcpid.opretj.testapp;
import static org.bitcoinj.script.ScriptOpCodes.OP_RETURN;
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.InputMismatchException;
import java.util.List;
import java.util.Scanner;
import joptsimple.OptionParser;
import joptsimple.OptionSet;
import joptsimple.OptionSpec;
import org.abstractj.kalium.keys.SigningKey;
import org.abstractj.kalium.keys.VerifyKey;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.InsufficientMoneyException;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionConfidence;
import org.bitcoinj.core.TransactionOutput;
import org.bitcoinj.core.Utils;
import org.bitcoinj.core.listeners.TransactionConfidenceEventListener;
import org.bitcoinj.script.Script;
import org.bitcoinj.script.ScriptBuilder;
import org.bitcoinj.wallet.KeyChain.KeyPurpose;
import org.bitcoinj.wallet.SendRequest;
import org.bitcoinj.wallet.Wallet;
import org.bitcoinj.wallet.Wallet.SendResult;
import org.bitcoinj.wallet.listeners.KeyChainEventListener;
import org.bitcoinj.wallet.listeners.ScriptsChangeEventListener;
import org.bitcoinj.wallet.listeners.WalletCoinsReceivedEventListener;
import org.bitcoinj.wallet.listeners.WalletCoinsSentEventListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tcpid.opretj.*;
import com.google.common.primitives.Bytes;
public class App {
private static final Logger logger = LoggerFactory.getLogger(App.class);
public static final long OPRET_BIRTHDAY = 1471989600;
static byte[] rawKey = Sha256Hash.hash("TESTSEED".getBytes());
static SigningKey sk = new SigningKey(rawKey);
static VerifyKey v = sk.getVerifyKey();
private static byte[] pkhash = Sha256Hash.hash(v.toBytes());
static byte[] revokemsg = Bytes.concat("Revoke ".getBytes(), pkhash);
public static void check(final byte[] pkhash, final byte[] sig) {
logger.warn("CHECKING REVOKE PK {} - SIG {}", Utils.HEX.encode(pkhash), Utils.HEX.encode(sig));
if (!Arrays.equals(App.pkhash, pkhash)) {
logger.warn("Unknown PK {}", Utils.HEX.encode(pkhash));
return;
}
logger.warn("Using VerifyKey {}", v);
if (v.verify(revokemsg, sig)) {
logger.warn("REVOKED VerifyKey {}", v);
} else {
logger.warn("SIGNATURE does not match!");
}
}
public static void main(String[] args) throws Exception {
OptionParser parser = new OptionParser();
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");
OptionSet opts = parser.parse(args);
if (opts.has("help") || !opts.has(net)) {
System.err.println("usage: App --net=MAIN/TEST/REGTEST");
parser.printHelpOn(System.err);
return;
}
final NetworkParameters params = net.value(opts).get();
final OPRETECParser bs = new OPRETECParser();
bs.addOPRETECRevokeEventListener(new OPRETECRevokeEventListener() {
public void onOPRETRevoke(final byte[] pkhash, final byte[] sig) {
// logger.warn("REVOKE PK {} - SIG {}",
// Utils.HEX.encode(pkhash), Utils.HEX.encode(sig));
check(pkhash, sig);
}
});
long earliestTime;
if (params.getId().equals(NetworkParameters.ID_REGTEST)) {
earliestTime = OPRET_BIRTHDAY;
} else if (params.getId().equals(NetworkParameters.ID_TESTNET)) {
earliestTime = OPRET_BIRTHDAY;
} else {
earliestTime = Utils.currentTimeSeconds();
}
bs.addOPRET(pkhash, earliestTime);
//bs.addOPRET(Sha256Hash.hash("test1".getBytes()), earliestTime);
//bs.addOPRET(Utils.HEX.decode("0f490dee643b01b06e0ea84c253a90050a3543cfb7c74319fb47b04afee5b872"), earliestTime);
// Now we initialize a new WalletAppKit. The kit handles all the
// boilerplate for us and is the easiest way to get everything up and
// running.
// Have a look at the WalletAppKit documentation and its source to
// understand what's happening behind the scenes:
// https://github.com/bitcoinj/bitcoinj/blob/master/core/src/main/java/org/bitcoinj/kits/WalletAppKit.java
final OPRETWalletAppKit kit = new OPRETWalletAppKit(params, new File("."), "opretwallet" + params.getId(), bs);
// In case you want to connect with your local bitcoind tell the kit to
// connect to localhost.
// You must do that in reg test mode.
if (params.getId().equals(NetworkParameters.ID_REGTEST)) {
kit.connectToLocalHost();
}
// Now we start the kit and sync the blockchain.
// bitcoinj is working a lot with the Google Guava libraries. The
// WalletAppKit extends the AbstractIdleService. Have a look at the
// introduction to Guava services:
// https://github.com/google/guava/wiki/ServiceExplained
kit.startAsync();
kit.awaitRunning();
final OPRETWallet wallet = kit.opretwallet();
wallet.addCoinsReceivedEventListener(new WalletCoinsReceivedEventListener() {
public void onCoinsReceived(Wallet wallet, Transaction tx, Coin prevBalance, Coin newBalance) {
System.out.println("-----> coins resceived: " + tx.getHashAsString());
System.out.println("received: " + tx.getValue(wallet));
}
});
wallet.addCoinsSentEventListener(new WalletCoinsSentEventListener() {
public void onCoinsSent(Wallet wallet, Transaction tx, Coin prevBalance, Coin newBalance) {
System.out.println("coins sent");
}
});
wallet.addKeyChainEventListener(new KeyChainEventListener() {
public void onKeysAdded(List<ECKey> keys) {
System.out.println("new key added");
}
});
wallet.addScriptsChangeEventListener(new ScriptsChangeEventListener() {
public void onScriptsChanged(Wallet wallet, List<Script> scripts, boolean isAddingScripts) {
System.out.println("new script added");
}
});
wallet.addTransactionConfidenceEventListener(new TransactionConfidenceEventListener() {
public void onTransactionConfidenceChanged(Wallet wallet, Transaction tx) {
System.out.println("-----> confidence changed: " + tx.getHashAsString());
final TransactionConfidence confidence = tx.getConfidence();
System.out.println("new block depth: " + confidence.getDepthInBlocks());
}
});
//wallet.allowSpendingUnconfirmedTransactions();
// Ready to run. The kit syncs the blockchain and our wallet event
// listener gets notified when something happens.
// To test everything we create and print a fresh receiving address.
// Send some coins to that address and see if everything works.
String receiveStr = wallet.freshReceiveAddress().toString();
System.out.println("send money to: " + receiveStr);
try {
System.out.print(executeCommand("qrencode -t UTF8 -o - " + receiveStr));
} catch (Exception e) {
;
}
final Scanner input = new Scanner(System.in);
display: while (true) {
System.out.println("-- Actions --");
System.out.println("Select an option: \n" + " 0) QUIT\n" + " 1) Display Balance\n"
+ " 2) Display Receive Address\n" + " 3) Send OP_Return\n");
try {
final int selection = input.nextInt();
switch (selection) {
case 0:
System.out.println("Returning...");
break display;
case 1:
System.out.println("Balance: " + wallet.getBalance().toFriendlyString());
break;
case 2:
System.out.println("send money to: " + wallet.freshReceiveAddress().toString());
break;
case 3:
sendOPReturn(kit);
break;
default:
System.out.println("Invalid action.");
break;
}
} catch (final InputMismatchException e) {
;
}
input.nextLine();
}
input.close();
// Make sure to properly shut down all the running services when you
// manually want to stop the kit. The WalletAppKit registers a runtime
// ShutdownHook so we actually do not need to worry about that when our
// application is stopping.
System.out.println("shutting down again");
kit.stopAsync();
kit.awaitTerminated();
}
private static boolean sendOPReturn(OPRETWalletAppKit kit) {
final OPRETWallet wallet = kit.opretwallet();
final NetworkParameters params = wallet.getNetworkParameters();
Transaction t = new Transaction(params);
final byte[] sig = sk.sign(revokemsg);
Script script = new ScriptBuilder().op(OP_RETURN).data(Utils.HEX.decode("ec1d")).data(Utils.HEX.decode("fe"))
.data(pkhash).data(Arrays.copyOfRange(sig, 0, 32)).build();
t.addOutput(Coin.ZERO, script);
t.addOutput(Transaction.DEFAULT_TX_FEE, wallet.freshAddress(KeyPurpose.CHANGE));
SendRequest request = SendRequest.forTx(t);
request.ensureMinRequiredFee = true;
SendResult sr = null;
try {
sr = wallet.sendCoins(request);
} catch (final InsufficientMoneyException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return false;
}
logger.debug("SendRequest {}", request);
script = new ScriptBuilder().op(OP_RETURN).data(Utils.HEX.decode("ec1d")).data(Utils.HEX.decode("ff"))
.data(pkhash).data(Arrays.copyOfRange(sig, 32, 64)).build();
t = new Transaction(params);
for (final TransactionOutput out : sr.tx.getOutputs()) {
if (out.getValue().compareTo(Transaction.DEFAULT_TX_FEE) == 0) {
logger.debug("Add Output: {} of value {}", out, out.getValue());
t.addInput(out);
}
}
t.addOutput(Coin.ZERO, script);
request = SendRequest.forTx(t);
request.ensureMinRequiredFee = true;
sr = null;
try {
sr = wallet.sendCoins(request);
} catch (final InsufficientMoneyException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return false;
}
logger.debug("SendRequest {}", request);
return true;
}
private static String executeCommand(String command) {
StringBuffer output = new StringBuffer();
Process p;
try {
p = Runtime.getRuntime().exec(command);
p.waitFor();
BufferedReader reader =
new BufferedReader(new InputStreamReader(p.getInputStream()));
String line = "";
while ((line = reader.readLine())!= null) {
output.append(line + "\n");
}
} catch (Exception e) {
e.printStackTrace();
}
return output.toString();
}
}

View file

@ -0,0 +1,43 @@
/*
* Copyright 2013 Google Inc.
* Copyright 2014 Andreas Schildbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.tcpid.opretj.testapp;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.params.MainNetParams;
import org.bitcoinj.params.RegTestParams;
import org.bitcoinj.params.TestNet3Params;
public enum NetworkEnum {
MAIN,
PROD, // alias for MAIN
TEST,
REGTEST;
public NetworkParameters get() {
switch(this) {
case MAIN:
case PROD:
return MainNetParams.get();
case TEST:
return TestNet3Params.get();
case REGTEST:
default:
return RegTestParams.get();
}
}
}

View file

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- encoders are assigned the type ch.qos.logback.classic.encoder.PatternLayoutEncoder
by default -->
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<logger name="org.tcpid.opretj.testapp.App" level="debug" />
<logger name="org.tcpid.opretj.OPRETECParser" level="debug" />
<!--
<logger name="eckey.OPRETSimpleLogger" level="debug" />
<logger name="eckey.OPRETSimpleParser" level="debug" />
<logger name="eckey.OPRETBaseHandler" level="debug" />
-->
<root level="warn">
<appender-ref ref="STDOUT" />
</root>
</configuration>

26
opretj/.classpath Normal file
View file

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>

1
opretj/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target/

23
opretj/.project Normal file
View file

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>opretj</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures>
</projectDescription>

View file

@ -0,0 +1,12 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
org.eclipse.jdt.core.compiler.compliance=1.8
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
org.eclipse.jdt.core.compiler.source=1.8

View file

@ -0,0 +1,4 @@
activeProfiles=
eclipse.preferences.version=1
resolveWorkspaceProjects=true
version=1

54
opretj/pom.xml Normal file
View file

@ -0,0 +1,54 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.tcpid</groupId>
<artifactId>opret-parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>opretj</artifactId>
<dependencies>
<dependency>
<groupId>org.bitcoinj</groupId>
<artifactId>bitcoinj-core</artifactId>
<version>0.14.3</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.0.13</version>
</dependency>
</dependencies>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
</project>

View file

@ -0,0 +1,92 @@
package org.tcpid.opretj;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import org.bitcoinj.core.Utils;
import org.bitcoinj.utils.ListenerRegistration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.primitives.Bytes;
public abstract class OPRETBaseHandler implements OPRETHandlerInterface {
private static final Logger logger = LoggerFactory.getLogger(OPRETBaseHandler.class);
private final CopyOnWriteArrayList<ListenerRegistration<OPRETChangeEventListener>> opReturnChangeListeners = new CopyOnWriteArrayList<ListenerRegistration<OPRETChangeEventListener>>();
private final Map<List<Byte>, Long> magicBytes = new HashMap<>();
@Override
public void addOPRET(byte[] magic, long earliestTime) {
logger.debug("addMagicBytes: {} - Time {}", Utils.HEX.encode(magic), earliestTime);
final List<Byte> blist = Bytes.asList(magic);
magicBytes.put(blist, earliestTime);
queueOnOPRETChanged();
}
/**
* Adds an event listener object. Methods on this object are called when
* scripts watched by this wallet change. The listener is executed by the
* given executor.
*/
@Override
public void addOPRETChangeEventListener(Executor executor, OPRETChangeEventListener listener) {
// This is thread safe, so we don't need to take the lock.
opReturnChangeListeners.add(new ListenerRegistration<OPRETChangeEventListener>(listener, executor));
}
@Override
public long getEarliestElementCreationTime() {
long earliestTime = Long.MAX_VALUE;
for (Long t : magicBytes.values()) {
if (t == Long.MAX_VALUE) {
t = Utils.currentTimeSeconds();
}
earliestTime = Math.min(t, earliestTime);
}
return earliestTime;
}
@Override
public int getOPRETCount() {
return magicBytes.size();
}
@Override
public Set<List<Byte>> getOPRETSet() {
return magicBytes.keySet();
}
protected void queueOnOPRETChanged() {
for (final ListenerRegistration<OPRETChangeEventListener> registration : opReturnChangeListeners) {
registration.executor.execute(new Runnable() {
@Override
public void run() {
registration.listener.onOPRETChanged();
}
});
}
}
@Override
public void removeOPRET(byte[] magic) {
magicBytes.remove(Bytes.asList(magic));
queueOnOPRETChanged();
}
/**
* Removes the given event listener object. Returns true if the listener was
* removed, false if that listener was never added.
*/
@Override
public boolean removeOPRETChangeEventListener(OPRETChangeEventListener listener) {
return ListenerRegistration.removeFromList(listener, opReturnChangeListeners);
}
}

View file

@ -0,0 +1,6 @@
package org.tcpid.opretj;
public interface OPRETChangeEventListener {
void onOPRETChanged();
}

View file

@ -0,0 +1,136 @@
package org.tcpid.opretj;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import org.bitcoinj.core.PartialMerkleTree;
import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.core.Utils;
import org.bitcoinj.utils.ListenerRegistration;
import org.bitcoinj.utils.Threading;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.primitives.Bytes;
public class OPRETECParser extends OPRETBaseHandler {
private static final Logger logger = LoggerFactory.getLogger(OPRETECParser.class);
private static final List<Byte> OPRET_MAGIC = Bytes.asList(Utils.HEX.decode("ec1d"));
protected final Map<Sha256Hash, PartialMerkleTree> merkleHashMap = new HashMap<>();
protected final Map<Sha256Hash, OPRETTransaction> transHashMap = new HashMap<>();
private final CopyOnWriteArrayList<ListenerRegistration<OPRETECRevokeEventListener>> opReturnChangeListeners = new CopyOnWriteArrayList<>();
/**
* Adds an event listener object. Methods on this object are called when
* scripts watched by this wallet change. The listener is executed by the
* given executor.
*/
public void addOPRETECRevokeEventListener(OPRETECRevokeEventListener listener) {
// This is thread safe, so we don't need to take the lock.
opReturnChangeListeners
.add(new ListenerRegistration<OPRETECRevokeEventListener>(listener, Threading.USER_THREAD));
}
/**
* Removes the given event listener object. Returns true if the listener was
* removed, false if that listener was never added.
*/
public boolean removeOPRETECRevokeEventListener(OPRETECRevokeEventListener listener) {
return ListenerRegistration.removeFromList(listener, opReturnChangeListeners);
}
protected void queueOnOPRETRevoke(final byte[] pkhash, final byte[] sig) {
for (final ListenerRegistration<OPRETECRevokeEventListener> registration : opReturnChangeListeners) {
registration.executor.execute(new Runnable() {
@Override
public void run() {
registration.listener.onOPRETRevoke(pkhash, sig);
}
});
}
}
private boolean checkData(OPRETTransaction t1, OPRETTransaction t2) {
final List<List<Byte>> opret_data = new ArrayList<>(t1.opretData);
opret_data.addAll(t2.opretData);
logger.debug("checking {}", opret_data);
List<Byte> chunk;
chunk = opret_data.get(0);
if (!chunk.equals(OPRET_MAGIC)) {
logger.debug("0: != OPRET_MAGIC");
return false;
}
chunk = opret_data.get(1);
if (chunk.size() != 1) {
logger.debug("1: size != 1");
return false;
}
if (chunk.get(0) == (byte) 0xFE) {
if (opret_data.size() != 8) {
logger.debug("FE: size != 8");
return false;
}
chunk = opret_data.get(4);
if (!chunk.equals(OPRET_MAGIC)) {
logger.debug("FE 4 != OPRET_MAGIC");
return false;
}
if (!opret_data.get(2).equals(opret_data.get(6))) {
logger.debug("FE 2 != 6");
return false;
}
chunk = opret_data.get(5);
if ((chunk.size() != 1) || (chunk.get(0) != (byte) 0xFF)) {
logger.debug("FE 5 size!=1 or != FF");
return false;
}
return handleRevoke(t1, t2);
} else {
logger.debug("1: != 0xFE");
}
return false;
}
private boolean handleRevoke(OPRETTransaction t1, OPRETTransaction t2) {
final byte[] pkhash = Bytes.toArray(t1.opretData.get(2));
final byte[] sig = Bytes.concat(Bytes.toArray(t1.opretData.get(3)), Bytes.toArray(t2.opretData.get(3)));
logger.debug("REVOKE PK {} - SIG {}", Utils.HEX.encode(pkhash), Utils.HEX.encode(sig));
queueOnOPRETRevoke(pkhash, sig);
return true;
}
@Override
public void pushData(final Sha256Hash blockHash, final Sha256Hash txHash, final Set<Sha256Hash> txPrevHash,
final List<List<Byte>> opret_data) {
final OPRETTransaction optrans = new OPRETTransaction(blockHash, txHash, txPrevHash, opret_data);
logger.debug("pushData: {}", optrans);
for (final Sha256Hash t : txPrevHash) {
if (transHashMap.containsKey(t)) {
final OPRETTransaction opprev = transHashMap.get(t);
if (checkData(opprev, optrans)) {
transHashMap.remove(t);
return;
}
}
}
transHashMap.put(txHash, optrans);
}
@Override
public void pushMerkle(final Sha256Hash blockHash, final PartialMerkleTree partialMerkleTree) {
merkleHashMap.put(blockHash, partialMerkleTree);
logger.info("block hash {}", blockHash);
logger.info("Merkle Tree: {}", partialMerkleTree);
}
}

View file

@ -0,0 +1,6 @@
package org.tcpid.opretj;
public interface OPRETECRevokeEventListener {
void onOPRETRevoke(final byte[] pkhash, final byte[] sig);
}

View file

@ -0,0 +1,32 @@
package org.tcpid.opretj;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executor;
import org.bitcoinj.core.PartialMerkleTree;
import org.bitcoinj.core.Sha256Hash;
public interface OPRETHandlerInterface {
void addOPRET(byte[] magic, long earliestTime);
void addOPRETChangeEventListener(Executor executor, OPRETChangeEventListener listener);
long getEarliestElementCreationTime();
int getOPRETCount();
Set<List<Byte>> getOPRETSet();
void pushData(final Sha256Hash h, final Sha256Hash sha256Hash, final Set<Sha256Hash> txprev,
final List<List<Byte>> myList);
void pushMerkle(final Sha256Hash sha256Hash, final PartialMerkleTree partialMerkleTree);
void removeOPRET(byte[] magic);
boolean removeOPRETChangeEventListener(OPRETChangeEventListener listener);
}

View file

@ -0,0 +1,41 @@
package org.tcpid.opretj;
import java.util.List;
import java.util.Set;
import org.bitcoinj.core.PartialMerkleTree;
import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.core.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.primitives.Bytes;
public class OPRETSimpleLogger extends OPRETBaseHandler {
private static final Logger logger = LoggerFactory.getLogger(OPRETSimpleLogger.class);
@Override
public void pushData(final Sha256Hash blockHash, final Sha256Hash txHash, final Set<Sha256Hash> txPrevHash,
final List<List<Byte>> opret_data) {
final StringBuilder buf = new StringBuilder();
final StringBuilder bufPrev = new StringBuilder();
for (final List<Byte> d : opret_data) {
buf.append(Utils.HEX.encode(Bytes.toArray(d)));
buf.append(" ");
}
for (final Sha256Hash d : txPrevHash) {
bufPrev.append(d.toString());
bufPrev.append(" ");
}
logger.info("Received in Block: {}\nTX: {}\nTxPrev: {}\nData: {}", blockHash, txHash, bufPrev, buf);
}
@Override
public void pushMerkle(final Sha256Hash blockHash, final PartialMerkleTree partialMerkleTree) {
logger.info("block hash {}", blockHash);
logger.info("Merkle Tree: {}", partialMerkleTree);
}
}

View file

@ -0,0 +1,50 @@
package org.tcpid.opretj;
import java.io.Serializable;
import java.util.List;
import java.util.Set;
import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.core.Utils;
import com.google.common.primitives.Bytes;
public class OPRETTransaction implements Serializable {
/**
*
*/
private static final long serialVersionUID = 4234625243756902517L;
public final Sha256Hash blockHash;
public final Sha256Hash txHash;
public final Set<Sha256Hash> txPrevHash;
public final List<List<Byte>> opretData;
public OPRETTransaction(final Sha256Hash blockHash, final Sha256Hash txHash, final Set<Sha256Hash> txPrevHash,
final List<List<Byte>> opret_data) {
this.blockHash = blockHash;
this.txHash = txHash;
this.txPrevHash = txPrevHash;
this.opretData = opret_data;
}
@Override
public String toString() {
final StringBuilder buf = new StringBuilder();
buf.append("Received in Block: ").append(blockHash).append("\n");
buf.append("TX: ").append(txHash).append("\n");
buf.append("TxPrev: ");
for (final Sha256Hash d : txPrevHash) {
buf.append(d.toString());
buf.append(" ");
}
buf.append("\n");
buf.append("Data: ");
for (final List<Byte> d : opretData) {
buf.append(Utils.HEX.encode(Bytes.toArray(d)));
buf.append(" ");
}
return buf.toString();
}
}

View file

@ -0,0 +1,194 @@
package org.tcpid.opretj;
import static org.bitcoinj.script.ScriptOpCodes.OP_RETURN;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.bitcoinj.core.Block;
import org.bitcoinj.core.BlockChain;
import org.bitcoinj.core.BloomFilter;
import org.bitcoinj.core.FilteredBlock;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.Peer;
import org.bitcoinj.core.ScriptException;
import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.core.StoredBlock;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionInput;
import org.bitcoinj.core.TransactionOutput;
import org.bitcoinj.core.Utils;
import org.bitcoinj.core.VerificationException;
import org.bitcoinj.core.listeners.BlocksDownloadedEventListener;
import org.bitcoinj.script.Script;
import org.bitcoinj.script.ScriptChunk;
import org.bitcoinj.wallet.KeyChainGroup;
import org.bitcoinj.wallet.Wallet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.primitives.Bytes;
public class OPRETWallet extends Wallet implements BlocksDownloadedEventListener, OPRETChangeEventListener {
private final OPRETHandlerInterface opbs;
private final Logger logger = LoggerFactory.getLogger(OPRETWallet.class);
private final Set<Sha256Hash> blocksToStore = new HashSet<>();
protected final Map<Sha256Hash, Transaction> pendingTransactions;
public OPRETWallet(NetworkParameters params, KeyChainGroup keyChainGroup, OPRETHandlerInterface bs) {
super(params, keyChainGroup);
opbs = bs;
pendingTransactions = new HashMap<Sha256Hash, Transaction>();
}
@Override
public BloomFilter getBloomFilter(int size, double falsePositiveRate, long nTweak) {
beginBloomFilterCalculation();
try {
final BloomFilter filter = super.getBloomFilter(size, falsePositiveRate, nTweak);
for (final List<Byte> magic : opbs.getOPRETSet()) {
logger.debug("Magic add bloom: {}", Utils.HEX.encode(Bytes.toArray(magic)));
filter.insert(Bytes.toArray(magic));
}
return filter;
} finally {
endBloomFilterCalculation();
}
}
@Override
public int getBloomFilterElementCount() {
beginBloomFilterCalculation();
try {
logger.debug("Magic Bytes Size: {}", opbs.getOPRETCount());
return super.getBloomFilterElementCount() + opbs.getOPRETCount();
} finally {
endBloomFilterCalculation();
}
}
@Override
public long getEarliestKeyCreationTime() {
long earliestTime = opbs.getEarliestElementCreationTime();
if (earliestTime == Long.MAX_VALUE) {
earliestTime = Utils.currentTimeSeconds();
}
return Math.min(super.getEarliestKeyCreationTime(), earliestTime);
}
@Override
public boolean isPendingTransactionRelevant(Transaction tx) throws ScriptException {
logger.debug("isPendingTransactionRelevant {}", tx.getHashAsString());
if (pendingTransactions.containsValue(tx)) {
return true;
}
if (isTransactionOPReturn(tx) != null) {
return true;
}
return false;
}
public List<List<Byte>> isTransactionOPReturn(Transaction tx) throws ScriptException {
final Set<List<Byte>> magicBytes = opbs.getOPRETSet();
final List<List<Byte>> myList = new ArrayList<>();
for (final TransactionOutput out : tx.getOutputs()) {
final Script script = out.getScriptPubKey();
final List<ScriptChunk> chunks = script.getChunks();
if (chunks.size() == 0) {
continue;
}
if (!chunks.get(0).equalsOpCode(OP_RETURN)) {
continue;
}
boolean found = false;
for (final ScriptChunk chunk : chunks) {
if (chunk.data == null) {
continue;
}
final List<Byte> magic = Bytes.asList(chunk.data);
if (chunk.equalsOpCode(OP_RETURN)) {
continue;
} else {
myList.add(magic);
}
if (magicBytes.contains(magic)) {
found = true;
}
}
if (found == true) {
return myList;
}
return null;
}
return null;
}
@Override
public void onBlocksDownloaded(Peer peer, Block block, FilteredBlock filteredBlock, int blocksLeft) {
if (!blocksToStore.contains(block.getHash())) {
return;
}
opbs.pushMerkle(block.getHash(), filteredBlock.getPartialMerkleTree());
blocksToStore.remove(block.getHash());
}
@Override
public void onOPRETChanged() {
queueOnScriptsChanged(null, false);
}
@Override
public void receiveFromBlock(Transaction tx, StoredBlock block, BlockChain.NewBlockType blockType,
int relativityOffset) throws VerificationException {
super.receiveFromBlock(tx, block, blockType, relativityOffset);
final List<List<Byte>> myList = isTransactionOPReturn(tx);
if (myList == null) {
logger.debug("False Positive Transaction {}", tx.toString());
return;
}
final Set<Sha256Hash> txprev = new HashSet<>();
for (final TransactionInput in : tx.getInputs()) {
try {
final Transaction t = pendingTransactions.get(in.getOutpoint().getHash());
txprev.add(t.getHash());
pendingTransactions.remove(t);
} catch (final Exception e) {
;
}
}
pendingTransactions.put(tx.getHash(), tx);
final Sha256Hash h = block.getHeader().getHash();
if (!blocksToStore.contains(h)) {
blocksToStore.add(h);
}
opbs.pushData(h, tx.getHash(), txprev, myList);
}
}

View file

@ -0,0 +1,60 @@
package org.tcpid.opretj;
import static com.google.common.base.Preconditions.checkState;
import java.io.File;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.kits.WalletAppKit;
import org.bitcoinj.store.BlockStore;
import org.bitcoinj.store.BlockStoreException;
import org.bitcoinj.store.SPVBlockStore;
import org.bitcoinj.utils.Threading;
import org.bitcoinj.wallet.KeyChainGroup;
import org.bitcoinj.wallet.Wallet;
import org.bitcoinj.wallet.WalletProtobufSerializer.WalletFactory;
public class OPRETWalletAppKit extends WalletAppKit {
// private final Logger logger = LoggerFactory.getLogger(OPRETWallet.class);
private final OPRETHandlerInterface opbs;
public OPRETWalletAppKit(NetworkParameters params, File directory, String filePrefix, OPRETHandlerInterface bs) {
super(params, directory, filePrefix);
opbs = bs;
walletFactory = new WalletFactory() {
@Override
public Wallet create(NetworkParameters params, KeyChainGroup keyChainGroup) {
return new OPRETWallet(params, keyChainGroup, opbs);
}
};
}
@Override
protected void onSetupCompleted() {
final OPRETWallet wallet = opretwallet();
opbs.addOPRETChangeEventListener(Threading.USER_THREAD, wallet);
// TODO: remove
wallet.reset();
peerGroup().addBlocksDownloadedEventListener(wallet);
}
public OPRETWallet opretwallet() throws RuntimeException, IllegalStateException {
checkState((state() == State.STARTING) || (state() == State.RUNNING), "Cannot call until startup is complete");
final Wallet w = wallet();
if (w instanceof OPRETWallet) {
return (OPRETWallet) w;
} else {
throw new RuntimeException("wallet != OPTRETWallet");
}
}
@Override
protected BlockStore provideBlockStore(File file) throws BlockStoreException {
// TODO: save state
if (params.getId().equals(NetworkParameters.ID_REGTEST) || params.getId().equals(NetworkParameters.ID_TESTNET)) {
file.deleteOnExit();
}
return new SPVBlockStore(params, file);
}
}

11
pom.xml Normal file
View file

@ -0,0 +1,11 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.tcpid</groupId>
<artifactId>opret-parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>opretj</module>
<module>opret-testapp</module>
</modules>
</project>