Compare commits

...

6 commits

Author SHA1 Message Date
Harald Hoyer 086c52ce12 checkpoint 2016-09-15 18:24:39 +02:00
Harald Hoyer d97d867579 README.md: add newlines 2016-09-14 17:03:21 +02:00
Harald Hoyer bc02806bce gitignore 2016-09-14 14:58:07 +02:00
Harald Hoyer 1bd005fb5e add ECA3 and ECA4 2016-09-14 14:37:11 +02:00
Harald Hoyer f68d85cea0 move 2016-09-14 10:45:38 +02:00
Harald Hoyer 765b452f7d move special EC parser and key handling to testapp 2016-09-14 10:15:47 +02:00
24 changed files with 1093 additions and 485 deletions

1
.gitignore vendored
View file

@ -1 +1,2 @@
/opretwalletorg.bitcoin.test.wallet
/README.html

View file

@ -60,6 +60,7 @@ A MK is stored along with the key birthday, which is the date of the first appea
### MVK announce subkey VK 0xECA[1,2] - A-nnounce
nonce[0:32] = nonce[0:16] | nonce[16:32]
data chunks are prepended with zeros, if its length is smaller than 16.
E.g.
@ -74,16 +75,16 @@ If nonce is missing completely, then
is assumed.
A nonce **must** be used only once. Either only one VK_pub is announced per MVK ever and nonce is missing,
or for every MVK announcement, the nonce has to be *unique* or *true random* bytes.
or for every MVK announcement, the nonce has to be **unique** or **true random** bytes.
sharedkey = sha256(sha256(MVK_pub | nonce))
xornonce[24] = sha256(sharedkey | nonce)[0:24]
sharedkey = sha256(sha256(MVK_pub | nonce))
xornonce[24] = sha256(sharedkey | nonce)[0:24]
sig[64] = crypto_sign(VK_pub, MKV)
msg[96] = VK_pub || sig
cipher[96] = crypto_stream_xor(msg, xornonce, sharedkey)
sig[64] = crypto_sign(VK_pub, MKV)
msg[96] = VK_pub || sig
cipher[96] = crypto_stream_xor(msg, xornonce, sharedkey)
clients may flush T1, if T2 does not follow in the next 20 blocks
clients may flush T1, if T2 does not follow in the next 20 blocks
clients may flush T2, if T1 does not follow in the next 20 blocks
| | OP | Chunk1 | Chunk2 | Chunk3 |
@ -94,14 +95,14 @@ clients may flush T2, if T1 does not follow in the next 20 blocks
| Size | 1 | 3 | 49 | 13 |
### MVK announce next subkey VK_n+1 0xECA[3,4] - A-nnounce
sharedkey = sha256(sha256(VK_n_pub))
nonce[24] = sha256(sharedkey)[0:24]
sharedkey = sha256(sha256(VK_n_pub))
nonce[24] = sha256(sharedkey)[0:24]
sig[64] = crypto_sign(VK_n+1_pub, MKV)
msg[96] = VK_n+1_pub || sig
sig[64] = crypto_sign(VK_n+1_pub, MKV)
msg[96] = VK_n+1_pub || sig
cipher[96] = crypto_stream_xor(msg, nonce, sharedkey)
clients may flush T1, if T2 does not follow in the next 20 blocks
clients may flush T1, if T2 does not follow in the next 20 blocks
clients may flush T2, if T1 does not follow in the next 20 blocks
| | OP | Chunk1 | Chunk2 | Chunk3 | Chunk4 |
@ -112,10 +113,10 @@ clients may flush T2, if T1 does not follow in the next 20 blocks
| Size | 1 | 3 | 49 | 13 | 13 |
### Public Doc or other key OK sign 0xEC5[1,2]
sign[64] = Sign_Key('Sign ' || sha256(Doc/OK))
sign[64] = Sign_Key('Sign ' || sha256(Doc/OK))
data = optional data (max 2*19 bytes)
clients may flush T1, if T2 does not follow in the next 20 blocks
clients may flush T1, if T2 does not follow in the next 20 blocks
clients may flush T2, if T1 does not follow in the next 20 blocks
| | OP | Chunk1 | Chunk2 | Chunk3 | Chunk4 |

View file

@ -2,3 +2,6 @@
/walletappkit-example.spvchain
/walletappkit-example.wallet
/opretwalletorg.bitcoin.test.wallet
/opretwalletorg.bitcoin.production.spvchain
/opretwalletorg.bitcoin.production.wallet
/opretwalletorg.bitcoin.test.wallet.bak

View file

@ -1,4 +1,5 @@
eclipse.preferences.version=1
encoding//src/main/java=UTF-8
encoding//src/main/resources=UTF-8
encoding//src/test/java=UTF-8
encoding/<project>=UTF-8

View file

@ -28,7 +28,7 @@
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<version>4.11</version>
<scope>test</scope>
</dependency>
@ -80,7 +80,7 @@
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
<mainClass>org.tcpid.opretj.testapp.App</mainClass>
<mainClass>org.tcpid.opret.App</mainClass>
</manifest>
</archive>
</configuration>
@ -106,6 +106,16 @@
</execution>
</executions>
</plugin>
<!-- Unit tests plugin, to skip runing test add -Dmaven.test.skip -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.19.1</version>
<configuration>
<runOrder>alphabetical</runOrder>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>

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

@ -0,0 +1,74 @@
package org.tcpid.key;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.NoSuchElementException;
import org.tcpid.opretj.OPRETTransaction;
import com.google.common.primitives.Bytes;
public class MasterVerifyKey extends VerifyKey {
// FIXME: make private again
public final LinkedList<MasterVerifyKey> subkeys = new LinkedList<>();
public MasterVerifyKey(final byte[] key) {
super(key);
}
public void clearSubKeys() {
subkeys.clear();
}
public MasterVerifyKey getSubKeybyHash(List<Byte> subpkhash) {
for (final MasterVerifyKey k : subkeys) {
if (Arrays.equals(k.getShortHash(), Bytes.toArray(subpkhash))) {
return k;
}
}
return null;
}
public MasterVerifyKey getValidSubKey() {
for (final MasterVerifyKey k : subkeys) {
if (!k.isRevoked()) {
return k;
}
}
return null;
}
public void revokeSubKey(final VerifyKey key) {
final int i = subkeys.indexOf(key);
if (i == -1) {
throw new NoSuchElementException("No such subkey");
}
}
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) {
if (subkeys.contains(key)) {
throw new NoSuchElementException("Already in");
}
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) {
@ -124,8 +127,13 @@ public class VerifyKey implements Comparable<VerifyKey> {
final int[] bufferLen = new int[1];
sodium();
return Util.isValid(Sodium.crypto_sign_ed25519_open(buffer, bufferLen, sigAndMsg, sigAndMsg.length, key),
"signature was forged or corrupted");
try {
Util.isValid(Sodium.crypto_sign_ed25519_open(buffer, bufferLen, sigAndMsg, sigAndMsg.length, key),
"signature was forged or corrupted");
return true;
} catch (final Exception e) {
return false;
}
}
public boolean verify(final String message, final String signature, final Encoder encoder) {

View file

@ -1,4 +1,4 @@
package org.tcpid.opretj;
package org.tcpid.opret;
import org.tcpid.key.MasterVerifyKey;

View file

@ -1,4 +1,4 @@
package org.tcpid.opretj;
package org.tcpid.opret;
import static org.bitcoinj.script.ScriptOpCodes.OP_RETURN;
import static org.libsodium.jni.NaCl.sodium;
@ -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;
@ -32,14 +34,17 @@ import org.tcpid.key.MasterSigningKey;
import org.tcpid.key.MasterVerifyKey;
import org.tcpid.key.SigningKey;
import org.tcpid.key.VerifyKey;
import org.tcpid.opretj.OPRETBaseHandler;
import org.tcpid.opretj.OPRETTransaction;
import com.google.common.primitives.Bytes;
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"));
@ -57,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!");
@ -65,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);
@ -74,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
@ -89,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
@ -111,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() {
@ -163,6 +230,189 @@ public class OPRETECParser extends OPRETBaseHandler {
return true;
}
private boolean handleAnnounce(final OPRETTransaction selfTx,
final Map<List<Byte>, List<OPRETTransaction>> selfTransHashMap,
final Map<List<Byte>, List<OPRETTransaction>> otherTransHashMap, final boolean isT1) {
logger.debug("handleAnnounce");
final byte[] selfData = Bytes.toArray(selfTx.opretData.get(1));
if (((selfData.length < 48) || (selfData.length > 64))) {
logger.debug("invalid chunk1 size = {}", selfData.length);
return false;
}
final List<Byte> pkhash = selfTx.opretData.get(2);
if (pkhash.size() != 12) {
logger.debug("chunk 2 size != 12 but {} ", pkhash.size());
return false;
}
if (!verifyKeys.containsKey(pkhash)) {
return false;
}
if (otherTransHashMap.containsKey(pkhash)) {
for (final OPRETTransaction otherTx : otherTransHashMap.get(pkhash)) {
final byte[] otherData = Bytes.toArray(otherTx.opretData.get(1));
final byte[] cipher = isT1
? Bytes.concat(Arrays.copyOfRange(selfData, 0, 48), Arrays.copyOfRange(otherData, 0, 48))
: Bytes.concat(Arrays.copyOfRange(otherData, 0, 48), Arrays.copyOfRange(selfData, 0, 48));
final BigInteger selfNonce = (selfData.length == 48) ? BigInteger.ZERO
: new BigInteger(1, Arrays.copyOfRange(selfData, 48, selfData.length));
final BigInteger otherNonce = (otherData.length == 48) ? BigInteger.ZERO
: new BigInteger(1, Arrays.copyOfRange(otherData, 48, otherData.length));
final BigInteger nonce = isT1 ? selfNonce.shiftLeft(16 * 8).or(otherNonce)
: otherNonce.shiftLeft(16 * 8).or(selfNonce);
byte[] noncebytes = Util.prependZeros(32, nonce.toByteArray());
noncebytes = Arrays.copyOfRange(noncebytes, noncebytes.length - 32, noncebytes.length);
for (final MasterVerifyKey k : verifyKeys.get(pkhash)) {
byte[] sharedkey, xornonce;
sharedkey = HASH.sha256(HASH.sha256(Bytes.concat(k.toBytes(), noncebytes)));
xornonce = Arrays.copyOfRange(HASH.sha256(Bytes.concat(sharedkey, noncebytes)), 0, 24);
logger.debug("checking key {}", Encoder.HEX.encode(k.toBytes()));
logger.debug("noncebytes {}", Encoder.HEX.encode(noncebytes));
logger.debug("noncebytes len {}", noncebytes.length);
logger.debug("xornonce {}", Encoder.HEX.encode(xornonce));
logger.debug("sharedkey {}", Encoder.HEX.encode(sharedkey));
sodium();
final byte[] msg = Util.zeros(96);
Sodium.crypto_stream_xsalsa20_xor(msg, cipher, 96, xornonce, sharedkey);
final byte[] vk = Arrays.copyOfRange(msg, 0, 32);
final byte[] sig = Arrays.copyOfRange(msg, 32, 96);
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.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);
}
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;
}
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)) {
@ -183,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;
@ -203,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);
@ -219,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;
}
@ -247,179 +496,35 @@ public class OPRETECParser extends OPRETBaseHandler {
}
private boolean handleECA1(final OPRETTransaction t1) {
// FIXME: refactor with handleECA2
logger.debug("handleECA1");
final byte[] data1 = Bytes.toArray(t1.opretData.get(1));
if (((data1.length < 48) || (data1.length > 64))) {
logger.debug("invalid chunk1 size = {}", data1.length);
return false;
}
final List<Byte> pkhash = t1.opretData.get(2);
if ((pkhash.size() != 12)) {
logger.debug("chunk 2 size != 12 but {} ", pkhash.size());
return false;
}
if (!verifyKeys.containsKey(pkhash)) {
return false;
}
if (transA2HashMap.containsKey(pkhash)) {
for (final OPRETTransaction t2 : transA2HashMap.get(pkhash)) {
final byte[] data2 = Bytes.toArray(t2.opretData.get(1));
final byte[] cipher = Bytes.concat(Arrays.copyOfRange(data1, 0, 48), Arrays.copyOfRange(data2, 0, 48));
BigInteger nonce1 = BigInteger.ZERO;
BigInteger nonce2 = BigInteger.ZERO;
if (data1.length > 48) {
nonce1 = new BigInteger(1, Arrays.copyOfRange(data1, 48, data1.length));
logger.debug("nonce1 {}", Encoder.HEX.encode(nonce1.toByteArray()));
logger.debug("nonce1shift {}", Encoder.HEX.encode(nonce1.shiftLeft(16 * 8).toByteArray()));
}
if (data2.length > 48) {
nonce2 = new BigInteger(1, Arrays.copyOfRange(data2, 48, data2.length));
logger.debug("nonce2 {}", Encoder.HEX.encode(nonce2.toByteArray()));
}
final BigInteger nonce = nonce1.shiftLeft(16 * 8).or(nonce2);
logger.debug("nonceshift {}", Encoder.HEX.encode(nonce.toByteArray()));
byte[] noncebytes = Util.prependZeros(32, nonce.toByteArray());
noncebytes = Arrays.copyOfRange(noncebytes, noncebytes.length - 32, noncebytes.length);
for (final MasterVerifyKey k : verifyKeys.get(pkhash)) {
byte[] sharedkey, xornonce;
sharedkey = HASH.sha256(HASH.sha256(Bytes.concat(k.toBytes(), noncebytes)));
xornonce = Arrays.copyOfRange(HASH.sha256(Bytes.concat(sharedkey, noncebytes)), 0, 24);
logger.debug("checking key {}", Encoder.HEX.encode(k.toBytes()));
logger.debug("noncebytes {}", Encoder.HEX.encode(noncebytes));
logger.debug("noncebytes len {}", noncebytes.length);
logger.debug("xornonce {}", Encoder.HEX.encode(xornonce));
logger.debug("sharedkey {}", Encoder.HEX.encode(sharedkey));
sodium();
final byte[] msg = Util.zeros(96);
Sodium.crypto_stream_xsalsa20_xor(msg, cipher, 96, xornonce, sharedkey);
final byte[] vk = Arrays.copyOfRange(msg, 0, 32);
final byte[] sig = Arrays.copyOfRange(msg, 32, 96);
try {
logger.debug("Checking sig {} with key {}", Encoder.HEX.encode(sig), Encoder.HEX.encode(vk));
k.verify(vk, sig);
} catch (final RuntimeException e) {
logger.debug("sig does not match");
continue;
}
logger.debug("sig matches");
k.setFirstValidSubKey(new MasterVerifyKey(vk), t1, t2);
transA2HashMap.get(pkhash).remove(t2);
if (transA2HashMap.get(pkhash).isEmpty()) {
transA2HashMap.remove(pkhash);
}
return true;
}
}
}
if (!transA1HashMap.containsKey(pkhash)) {
transA1HashMap.put(pkhash, new ArrayList<OPRETTransaction>());
}
transA1HashMap.get(pkhash).add(t1);
return false;
return handleAnnounce(t1, transA1HashMap, transA2HashMap, true);
}
private boolean handleECA2(final OPRETTransaction t2) {
// FIXME: refactor with handleECA1
logger.debug("handleECA2");
final byte[] data2 = Bytes.toArray(t2.opretData.get(1));
if (((data2.length < 48) || (data2.length > 64))) {
logger.debug("invalid chunk1 size = {}", data2.length);
return false;
}
final List<Byte> pkhash = t2.opretData.get(2);
if ((pkhash.size() != 12)) {
logger.debug("chunk 2 size != 12 but {} ", pkhash.size());
return false;
}
if (!verifyKeys.containsKey(pkhash)) {
logger.debug("pkash not in hashmap");
return false;
}
if (transA1HashMap.containsKey(pkhash)) {
for (final OPRETTransaction t1 : transA1HashMap.get(pkhash)) {
final byte[] data1 = Bytes.toArray(t1.opretData.get(1));
final byte[] cipher = Bytes.concat(Arrays.copyOfRange(data1, 0, 48), Arrays.copyOfRange(data2, 0, 48));
BigInteger nonce1 = BigInteger.ZERO;
BigInteger nonce2 = BigInteger.ZERO;
if (data1.length > 48) {
nonce1 = new BigInteger(1, Arrays.copyOfRange(data1, 48, data1.length));
}
if (data2.length > 48) {
nonce2 = new BigInteger(1, Arrays.copyOfRange(data2, 48, data2.length));
}
final BigInteger nonce = nonce1.shiftLeft(16 * 8).or(nonce2);
byte[] noncebytes = Util.prependZeros(32, nonce.toByteArray());
noncebytes = Arrays.copyOfRange(noncebytes, noncebytes.length - 32, noncebytes.length);
for (final MasterVerifyKey k : verifyKeys.get(pkhash)) {
byte[] sharedkey, xornonce;
logger.debug("checking key {}", Encoder.HEX.encode(k.toBytes()));
logger.debug("noncebytes {}", Encoder.HEX.encode(noncebytes));
logger.debug("noncebytes len {}", noncebytes.length);
sharedkey = HASH.sha256(HASH.sha256(Bytes.concat(k.toBytes(), noncebytes)));
xornonce = Arrays.copyOfRange(HASH.sha256(Bytes.concat(sharedkey, noncebytes)), 0, 24);
logger.debug("xornonce {}", Encoder.HEX.encode(xornonce));
logger.debug("sharedkey {}", Encoder.HEX.encode(sharedkey));
sodium();
final byte[] msg = Util.zeros(96);
Sodium.crypto_stream_xsalsa20_xor(msg, cipher, 96, xornonce, sharedkey);
final byte[] vk = Arrays.copyOfRange(msg, 0, 32);
final byte[] sig = Arrays.copyOfRange(msg, 32, 96);
try {
logger.debug("Checking sig {} with key {}", Encoder.HEX.encode(sig), Encoder.HEX.encode(vk));
k.verify(vk, sig);
} catch (final RuntimeException e) {
logger.debug("sig does not match");
continue;
}
logger.debug("sig matches");
k.setFirstValidSubKey(new MasterVerifyKey(vk), t1, t2);
transA1HashMap.get(pkhash).remove(t1);
if (transA1HashMap.get(pkhash).isEmpty()) {
transA1HashMap.remove(pkhash);
}
return true;
}
}
}
if (!transA2HashMap.containsKey(pkhash)) {
transA2HashMap.put(pkhash, new ArrayList<OPRETTransaction>());
}
transA2HashMap.get(pkhash).add(t2);
logger.debug("nothing in A1 HashMap");
return false;
return handleAnnounce(t2, transA2HashMap, transA1HashMap, false);
}
private boolean handleECA3(final OPRETTransaction t) {
private boolean handleECA3(final OPRETTransaction t1) {
logger.debug("handleECA3");
return handleAnnounceNext(t1, transA3HashMap, transA4HashMap, true);
}
private boolean handleECA4(final OPRETTransaction t2) {
logger.debug("handleECA4");
return handleAnnounceNext(t2, transA4HashMap, transA3HashMap, false);
}
public boolean needScan() {
// TODO Auto-generated method stub
return false;
return this.needscan;
}
private boolean handleECA4(final OPRETTransaction t) {
// TODO Auto-generated method stub
return false;
}
protected boolean handleTransaction(final OPRETTransaction t) {
public boolean pushTransaction(final OPRETTransaction t) {
logger.debug("checking {}", t.opretData);
if ((t.opretData.size() != 2) && (t.opretData.size() != 3) && (t.opretData.size() != 4)) {
logger.debug("Wrong chunk count");
return false;
}
@ -465,8 +570,17 @@ public class OPRETECParser extends OPRETBaseHandler {
}
@Override
public void pushTransaction(final OPRETTransaction t) {
handleTransaction(t);
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) {
@ -499,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,7 +24,8 @@ import org.libsodium.jni.crypto.Hash;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tcpid.key.MasterSigningKey;
import org.tcpid.opretj.OPRETECParser;
import org.tcpid.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,14 +8,17 @@
<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.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.opretj.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

@ -1,4 +1,4 @@
package org.tcpid.opretj;
package org.tcpid.opret;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
@ -30,7 +30,7 @@ public class TestCrypto {
@Test
public void testDerive() {
logger.debug("testDerive");
assertTrue("NONCE_BYTES > HMACSHA512256.HMACSHA512256_BYTES", NONCE_BYTES <= HMACSHA512256.HMACSHA512256_BYTES);
assertEquals(SECRETKEY_BYTES, HMACSHA512256.HMACSHA512256_BYTES);
@ -57,6 +57,7 @@ public class TestCrypto {
@Test
public void testSign() {
logger.debug("testSign");
final MasterSigningKey msk = new MasterSigningKey(HASH.sha256("TESTSEED".getBytes()));
final MasterVerifyKey vk = msk.getMasterVerifyKey();
final byte[] revokemsg = Bytes.concat("Revoke ".getBytes(), vk.toHash());
@ -66,9 +67,11 @@ public class TestCrypto {
@Test
public void testSignEnc() {
logger.debug("testSignEnc");
final MasterSigningKey msk = new MasterSigningKey(HASH.sha256("TESTSEED".getBytes()));
final MasterVerifyKey subkey = msk.getSubKey(1L).getMasterVerifyKey();
final MasterVerifyKey vk = msk.getMasterVerifyKey();
byte[] sig = msk.sign(vk.toBytes());
byte[] sig = msk.sign(subkey.toBytes());
logger.debug("using key {}", Encoder.HEX.encode(vk.toBytes()));
final byte[] noncebytes = Util.zeros(32);
@ -78,7 +81,7 @@ public class TestCrypto {
logger.debug("sharedkey {}", Encoder.HEX.encode(sharedkey));
final byte[] cipher = Util.zeros(96);
byte[] msg = Bytes.concat(vk.toBytes(), sig);
byte[] msg = Bytes.concat(subkey.toBytes(), sig);
assertEquals(96, msg.length);
sodium();
@ -93,11 +96,49 @@ public class TestCrypto {
sig = Arrays.copyOfRange(msg, 32, 96);
logger.debug("vkb : {}", Encoder.HEX.encode(vkb));
assertTrue("Verification of signature failed.", vk.verify(vkb, sig));
assertArrayEquals(vk.toBytes(), vkb);
assertArrayEquals(subkey.toBytes(), vkb);
}
@Test
public void testSignEncNext() {
logger.debug("testSignEncNext");
final MasterSigningKey msk = new MasterSigningKey(HASH.sha256("TESTSEED".getBytes()));
final MasterVerifyKey mvk = msk.getMasterVerifyKey();
final MasterVerifyKey prev = msk.getSubKey(1L).getMasterVerifyKey();
final MasterVerifyKey next = msk.getSubKey(2L).getMasterVerifyKey();
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);
byte[] msg = Bytes.concat(next.toBytes(), sig);
assertEquals(96, msg.length);
sodium();
Sodium.crypto_stream_xsalsa20_xor(cipher, msg, 96, xornonce, sharedkey);
assertEquals(96, cipher.length);
logger.debug("Clear : {}", Encoder.HEX.encode(msg));
logger.debug("Cipher: {}", Encoder.HEX.encode(cipher));
msg = Util.zeros(96);
Sodium.crypto_stream_xsalsa20_xor(msg, cipher, 96, xornonce, sharedkey);
final byte[] vkb = Arrays.copyOfRange(msg, 0, 32);
sig = Arrays.copyOfRange(msg, 32, 96);
logger.debug("vkb : {}", Encoder.HEX.encode(vkb));
assertTrue("Verification of signature failed.", mvk.verify(vkb, sig));
assertArrayEquals(next.toBytes(), vkb);
}
@Test
public void testSignEncNoncebytes() {
logger.debug("testSignEncNoncebytes");
final MasterSigningKey msk = new MasterSigningKey(HASH.sha256("TESTSEED".getBytes()));
final MasterVerifyKey vk = msk.getMasterVerifyKey();
byte[] sig = msk.sign(vk.toBytes());

View file

@ -0,0 +1,287 @@
/**
*
*/
package org.tcpid.opret;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.bitcoinj.core.Sha256Hash;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.libsodium.jni.encoders.Encoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tcpid.key.MasterVerifyKey;
import org.tcpid.opretj.OPRETTransaction;
import com.google.common.primitives.Bytes;
public class TestPushTransaction {
private Logger logger;
@Before
public void setUp() throws Exception {
logger = LoggerFactory.getLogger(TestPushTransaction.class);
}
@After
public void tearDown() throws Exception {
}
/**
* Test method for
* {@link org.tcpid.opret.OPRETECParser#pushTransaction(org.tcpid.opretj.OPRETTransaction)}.
*/
@Test
public void testPushTransactionECA12() {
logger.debug("testPushTransactionECA12");
final byte[] cipher = Encoder.HEX.decode(
"a15b671a9890a6bd0b6ed9a50193a15283001ccd72e106198b32242a906c300e263fc31dbfdaad66c40fc9796db3a464ab4313a06bbcd88fc1d503110016114c1da8bdf6e58a82be18d33c1baa96e1a9fe9c6f939b6838b30972be2de53f12d0");
final byte[] vkb = Encoder.HEX.decode("fb2e360caf811b3aaf534d0458c2a2ca3e1f213b244a6f83af1ab50eddacdd8c");
final MasterVerifyKey mvk = new MasterVerifyKey(vkb);
final byte[] vkbsha96 = Arrays.copyOfRange(Sha256Hash.of(vkb).getBytes(), 0, 12);
final byte[] nullbyte = {};
List<List<Byte>> opret_data = new ArrayList<>();
opret_data.add(Bytes.asList(Encoder.HEX.decode("eca1")));
opret_data.add(Bytes.asList(Arrays.copyOfRange(cipher, 0, 48)));
opret_data.add(Bytes.asList(vkbsha96));
final OPRETTransaction t1 = new OPRETTransaction(Sha256Hash.of(nullbyte), Sha256Hash.of(nullbyte), opret_data);
opret_data = new ArrayList<>();
opret_data.add(Bytes.asList(Encoder.HEX.decode("eca2")));
opret_data.add(Bytes.asList(Arrays.copyOfRange(cipher, 48, 96)));
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
opret_data = new ArrayList<>();
opret_data.add(Bytes.asList(Encoder.HEX.decode("eca2")));
opret_data.add(Bytes.asList(Arrays.copyOfRange(cipher, 0, 48)));
opret_data.add(Bytes.asList(vkbsha96));
final OPRETTransaction t3 = new OPRETTransaction(Sha256Hash.of(nullbyte), Sha256Hash.of(nullbyte), opret_data);
opret_data = new ArrayList<>();
opret_data.add(Bytes.asList(Encoder.HEX.decode("eca1")));
opret_data.add(Bytes.asList(Arrays.copyOfRange(cipher, 48, 96)));
opret_data.add(Bytes.asList(vkbsha96));
final OPRETTransaction t4 = new OPRETTransaction(Sha256Hash.of(nullbyte), Sha256Hash.of(nullbyte), opret_data);
final OPRETECParser parser = new OPRETECParser();
parser.addVerifyKey(mvk, 0);
assertFalse(parser.pushTransaction(t2));
assertFalse(parser.pushTransaction(t3));
assertFalse(parser.pushTransaction(t4));
assertTrue(parser.pushTransaction(t1));
MasterVerifyKey subkey = mvk.getValidSubKey();
assertArrayEquals(subkey.toBytes(),
Encoder.HEX.decode("e4acb361f4ec55804af6b5a1bbf5ca74ad78b4edc9a977a1dfed08872aa0a5db"));
mvk.clearSubKeys();
assertFalse(parser.pushTransaction(t1));
assertFalse(parser.pushTransaction(t3));
assertFalse(parser.pushTransaction(t4));
assertTrue(parser.pushTransaction(t2));
subkey = mvk.getValidSubKey();
assertArrayEquals(subkey.toBytes(),
Encoder.HEX.decode("e4acb361f4ec55804af6b5a1bbf5ca74ad78b4edc9a977a1dfed08872aa0a5db"));
mvk.clearSubKeys();
assertFalse(parser.pushTransaction(t1));
assertFalse(parser.pushTransaction(t4));
assertFalse(parser.pushTransaction(t3));
assertTrue(parser.pushTransaction(t2));
subkey = mvk.getValidSubKey();
assertArrayEquals(subkey.toBytes(),
Encoder.HEX.decode("e4acb361f4ec55804af6b5a1bbf5ca74ad78b4edc9a977a1dfed08872aa0a5db"));
mvk.clearSubKeys();
assertFalse(parser.pushTransaction(t2));
assertFalse(parser.pushTransaction(t4));
assertFalse(parser.pushTransaction(t3));
assertTrue(parser.pushTransaction(t1));
subkey = mvk.getValidSubKey();
assertArrayEquals(subkey.toBytes(),
Encoder.HEX.decode("e4acb361f4ec55804af6b5a1bbf5ca74ad78b4edc9a977a1dfed08872aa0a5db"));
}
/**
* Test method for
* {@link org.tcpid.opret.OPRETECParser#pushTransaction(org.tcpid.opretj.OPRETTransaction)}.
*/
@Test
public void testPushTransactionECA12WithNonce() {
logger.debug("testPushTransactionECA12WithNonce");
final byte[] cipher = Encoder.HEX.decode(
"24f99184d03a6ffa5826bd9300a7fb1cff264600f335b3c6042f15cb4d3d9019fa2a9c905cdf6f6c80178def845f0340e6d2e55a7dee433a5af984760adc23e187734e5e4e76aa22f3acab172262633139b6dcd11229fe2385661a70d6c206c0");
final byte[] vkb = Encoder.HEX.decode("fb2e360caf811b3aaf534d0458c2a2ca3e1f213b244a6f83af1ab50eddacdd8c");
final MasterVerifyKey mvk = new MasterVerifyKey(vkb);
final byte[] vkbsha96 = Arrays.copyOfRange(Sha256Hash.of(vkb).getBytes(), 0, 12);
final byte[] nullbyte = {};
List<List<Byte>> opret_data = new ArrayList<>();
opret_data.add(Bytes.asList(Encoder.HEX.decode("eca1")));
final byte[] byte1f = { (byte) 0x11 };
opret_data.add(Bytes.asList(Bytes.concat(Arrays.copyOfRange(cipher, 0, 48), byte1f)));
opret_data.add(Bytes.asList(vkbsha96));
final OPRETTransaction t1 = new OPRETTransaction(Sha256Hash.of(nullbyte), Sha256Hash.of(nullbyte), opret_data);
opret_data = new ArrayList<>();
opret_data.add(Bytes.asList(Encoder.HEX.decode("eca2")));
opret_data.add(Bytes.asList(Arrays.copyOfRange(cipher, 48, 96)));
opret_data.add(Bytes.asList(vkbsha96));
final OPRETTransaction t2 = new OPRETTransaction(Sha256Hash.of(nullbyte), Sha256Hash.of(nullbyte), opret_data);
final OPRETECParser parser = new OPRETECParser();
parser.addVerifyKey(mvk, 0);
assertFalse(parser.pushTransaction(t2));
assertTrue(parser.pushTransaction(t1));
MasterVerifyKey subkey = mvk.getValidSubKey();
assertArrayEquals(subkey.toBytes(),
Encoder.HEX.decode("fb2e360caf811b3aaf534d0458c2a2ca3e1f213b244a6f83af1ab50eddacdd8c"));
mvk.clearSubKeys();
assertFalse(parser.pushTransaction(t1));
assertTrue(parser.pushTransaction(t2));
subkey = mvk.getValidSubKey();
assertArrayEquals(subkey.toBytes(),
Encoder.HEX.decode("fb2e360caf811b3aaf534d0458c2a2ca3e1f213b244a6f83af1ab50eddacdd8c"));
mvk.clearSubKeys();
assertFalse(parser.pushTransaction(t1));
assertTrue(parser.pushTransaction(t2));
subkey = mvk.getValidSubKey();
assertArrayEquals(subkey.toBytes(),
Encoder.HEX.decode("fb2e360caf811b3aaf534d0458c2a2ca3e1f213b244a6f83af1ab50eddacdd8c"));
mvk.clearSubKeys();
assertFalse(parser.pushTransaction(t2));
assertTrue(parser.pushTransaction(t1));
subkey = mvk.getValidSubKey();
assertArrayEquals(subkey.toBytes(),
Encoder.HEX.decode("fb2e360caf811b3aaf534d0458c2a2ca3e1f213b244a6f83af1ab50eddacdd8c"));
}
/**
* Test method for
* {@link org.tcpid.opret.OPRETECParser#pushTransaction(org.tcpid.opretj.OPRETTransaction)}.
*/
@Test
public void testPushTransactionECA34() {
logger.debug("testPushTransactionECA34");
final byte[] cipher = Encoder.HEX.decode(
"6c44299f0b00638ca3e8a42d29082fb8f007eaff491472f9329c950b9181bfece5b7bf749b9b941dd92816c62b89f4673848ffe435576ee4e39d2e5f26041daff667c25dee1116a3f9f223de565465cafb2fdf3422c8c905666c268f7244850c");
final MasterVerifyKey mvk = new MasterVerifyKey(
Encoder.HEX.decode("fb2e360caf811b3aaf534d0458c2a2ca3e1f213b244a6f83af1ab50eddacdd8c"));
final MasterVerifyKey firstsub = new MasterVerifyKey(
Encoder.HEX.decode("e4acb361f4ec55804af6b5a1bbf5ca74ad78b4edc9a977a1dfed08872aa0a5db"));
mvk.setFirstValidSubKey(firstsub, (OPRETTransaction) null, (OPRETTransaction) null);
final byte[] nullbyte = {};
List<List<Byte>> opret_data = new ArrayList<>();
opret_data.add(Bytes.asList(Encoder.HEX.decode("eca3")));
opret_data.add(Bytes.asList(Arrays.copyOfRange(cipher, 0, 48)));
opret_data.add(Bytes.asList(mvk.getShortHash()));
opret_data.add(Bytes.asList(firstsub.getShortHash()));
final OPRETTransaction t1 = new OPRETTransaction(Sha256Hash.of(nullbyte), Sha256Hash.of(nullbyte), opret_data);
opret_data = new ArrayList<>();
opret_data.add(Bytes.asList(Encoder.HEX.decode("eca4")));
opret_data.add(Bytes.asList(Arrays.copyOfRange(cipher, 48, 96)));
opret_data.add(Bytes.asList(mvk.getShortHash()));
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
opret_data = new ArrayList<>();
opret_data.add(Bytes.asList(Encoder.HEX.decode("eca4")));
opret_data.add(Bytes.asList(Arrays.copyOfRange(cipher, 0, 48)));
opret_data.add(Bytes.asList(mvk.getShortHash()));
opret_data.add(Bytes.asList(firstsub.getShortHash()));
final OPRETTransaction t3 = new OPRETTransaction(Sha256Hash.of(nullbyte), Sha256Hash.of(nullbyte), opret_data);
opret_data = new ArrayList<>();
opret_data.add(Bytes.asList(Encoder.HEX.decode("eca3")));
opret_data.add(Bytes.asList(Arrays.copyOfRange(cipher, 48, 96)));
opret_data.add(Bytes.asList(mvk.getShortHash()));
opret_data.add(Bytes.asList(firstsub.getShortHash()));
final OPRETTransaction t4 = new OPRETTransaction(Sha256Hash.of(nullbyte), Sha256Hash.of(nullbyte), opret_data);
final OPRETECParser parser = new OPRETECParser();
parser.addVerifyKey(mvk, 0);
parser.addVerifyKey(firstsub, 0);
assertFalse(parser.pushTransaction(t2));
assertFalse(parser.pushTransaction(t3));
assertFalse(parser.pushTransaction(t4));
assertTrue(parser.pushTransaction(t1));
firstsub.setRevoked(true);
MasterVerifyKey subkey = mvk.getValidSubKey();
logger.debug("FirstValid: {}", subkey.toString());
assertArrayEquals(subkey.toBytes(),
Encoder.HEX.decode("11e14458b16050a23a772e469ee424f513c3eb81682c0f9f81f07e607c6bf917"));
mvk.clearSubKeys();
mvk.setFirstValidSubKey(firstsub, (OPRETTransaction) null, (OPRETTransaction) null);
assertFalse(parser.pushTransaction(t1));
assertFalse(parser.pushTransaction(t3));
assertFalse(parser.pushTransaction(t4));
assertTrue(parser.pushTransaction(t2));
firstsub.setRevoked(true);
subkey = mvk.getValidSubKey();
assertArrayEquals(subkey.toBytes(),
Encoder.HEX.decode("11e14458b16050a23a772e469ee424f513c3eb81682c0f9f81f07e607c6bf917"));
mvk.clearSubKeys();
mvk.setFirstValidSubKey(firstsub, (OPRETTransaction) null, (OPRETTransaction) null);
assertFalse(parser.pushTransaction(t1));
assertFalse(parser.pushTransaction(t4));
assertFalse(parser.pushTransaction(t3));
assertTrue(parser.pushTransaction(t2));
firstsub.setRevoked(true);
subkey = mvk.getValidSubKey();
assertArrayEquals(subkey.toBytes(),
Encoder.HEX.decode("11e14458b16050a23a772e469ee424f513c3eb81682c0f9f81f07e607c6bf917"));
mvk.clearSubKeys();
mvk.setFirstValidSubKey(firstsub, (OPRETTransaction) null, (OPRETTransaction) null);
assertFalse(parser.pushTransaction(t2));
assertFalse(parser.pushTransaction(t4));
assertFalse(parser.pushTransaction(t3));
assertTrue(parser.pushTransaction(t1));
firstsub.setRevoked(true);
subkey = mvk.getValidSubKey();
assertArrayEquals(subkey.toBytes(),
Encoder.HEX.decode("11e14458b16050a23a772e469ee424f513c3eb81682c0f9f81f07e607c6bf917"));
}
}

View file

@ -1,4 +1,3 @@
eclipse.preferences.version=1
encoding//src/main/java=UTF-8
encoding//src/test/java=UTF-8
encoding/<project>=UTF-8

View file

@ -1,50 +0,0 @@
package org.tcpid.key;
import java.util.LinkedList;
import java.util.NoSuchElementException;
import org.tcpid.opretj.OPRETTransaction;
public class MasterVerifyKey extends VerifyKey {
private final LinkedList<VerifyKey> subkeys = new LinkedList<>();
public MasterVerifyKey(final byte[] key) {
super(key);
}
public void clearSubKeys() {
subkeys.clear();
}
public VerifyKey getValidSubKey() {
return subkeys.getFirst();
}
public void revokeSubKey(final VerifyKey key) {
final int i = subkeys.indexOf(key);
if (i == -1) {
throw new NoSuchElementException("No such subkey");
}
subkeys.get(i).setRevoked(true);
subkeys.remove(i);
}
public void setFirstValidSubKey(final VerifyKey key, final OPRETTransaction t1, final OPRETTransaction t2) {
if (!subkeys.isEmpty()) {
throw new IndexOutOfBoundsException("Subkey list is not empty");
}
subkeys.addLast(key);
}
public void setNextValidSubKey(final VerifyKey after, final VerifyKey key) {
final VerifyKey l = subkeys.getLast();
if (!l.equals(after)) {
throw new NoSuchElementException("No such after key, or not last in list");
}
subkeys.addLast(key);
}
}

View file

@ -6,8 +6,6 @@ import java.util.concurrent.Executor;
public interface OPRETHandlerInterface {
// void addOPRET(byte[] magic, long earliestTime);
void addOPRETChangeEventListener(Executor executor, OPRETChangeEventListener listener);
long getEarliestElementCreationTime();
@ -16,10 +14,8 @@ public interface OPRETHandlerInterface {
Set<List<Byte>> getOPRETSet();
// void removeOPRET(byte[] magic);
void pushTransaction(OPRETTransaction t);
boolean removeOPRETChangeEventListener(OPRETChangeEventListener listener);
void pushTransactions(List<OPRETTransaction> pushlist);
}

View file

@ -11,8 +11,7 @@ import com.google.common.primitives.Bytes;
public class OPRETSimpleLogger extends OPRETBaseHandler {
private static final Logger logger = LoggerFactory.getLogger(OPRETSimpleLogger.class);
@Override
public void pushTransaction(final OPRETTransaction t) {
public boolean pushTransaction(final OPRETTransaction t) {
final StringBuilder buf = new StringBuilder();
for (final List<Byte> d : t.opretData) {
@ -21,6 +20,14 @@ public class OPRETSimpleLogger extends OPRETBaseHandler {
}
logger.info("Received in Block: {}\nTX: {}\nData: {}", t.blockHash, t.txHash, buf);
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();
}
}
}

View file

@ -1,146 +0,0 @@
/**
*
*/
package org.tcpid.opretj;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.bitcoinj.core.Sha256Hash;
import org.junit.Test;
import org.libsodium.jni.encoders.Encoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tcpid.key.MasterVerifyKey;
import com.google.common.primitives.Bytes;
public class TestECA1 {
private final static Logger logger = LoggerFactory.getLogger(TestECA1.class);
/**
* Test method for
* {@link org.tcpid.opretj.OPRETECParser#pushTransaction(org.tcpid.opretj.OPRETTransaction)}.
*/
@Test
public void testPushTransaction() {
logger.debug("testPushTransaction");
final byte[] cipher = Encoder.HEX.decode(
"bed9e277c3fde807eecb2100e2a4c9ec1067891b9f021e3bfbc599a3676048598e7c9801d94d9765cb965e64cfb9f493d7ae332bc85affb8bb0337b6835c51d156005db43ab8ea9b988632bfadcaee7dabf08709be248f5354d59a98e53f0cda");
final byte[] vkb = Encoder.HEX.decode("fb2e360caf811b3aaf534d0458c2a2ca3e1f213b244a6f83af1ab50eddacdd8c");
final MasterVerifyKey mvk = new MasterVerifyKey(vkb);
final byte[] vkbsha96 = Arrays.copyOfRange(Sha256Hash.of(vkb).getBytes(), 0, 12);
final byte[] nullbyte = {};
List<List<Byte>> opret_data = new ArrayList<>();
opret_data.add(Bytes.asList(Encoder.HEX.decode("eca1")));
opret_data.add(Bytes.asList(Arrays.copyOfRange(cipher, 0, 48)));
opret_data.add(Bytes.asList(vkbsha96));
final OPRETTransaction t1 = new OPRETTransaction(Sha256Hash.of(nullbyte), Sha256Hash.of(nullbyte), opret_data);
opret_data = new ArrayList<>();
opret_data.add(Bytes.asList(Encoder.HEX.decode("eca2")));
opret_data.add(Bytes.asList(Arrays.copyOfRange(cipher, 48, 96)));
opret_data.add(Bytes.asList(vkbsha96));
final OPRETTransaction t2 = new OPRETTransaction(Sha256Hash.of(nullbyte), Sha256Hash.of(nullbyte), opret_data);
opret_data = new ArrayList<>();
opret_data.add(Bytes.asList(Encoder.HEX.decode("eca2")));
opret_data.add(Bytes.asList(Arrays.copyOfRange(cipher, 0, 48)));
opret_data.add(Bytes.asList(vkbsha96));
final OPRETTransaction t3 = new OPRETTransaction(Sha256Hash.of(nullbyte), Sha256Hash.of(nullbyte), opret_data);
opret_data = new ArrayList<>();
opret_data.add(Bytes.asList(Encoder.HEX.decode("eca1")));
opret_data.add(Bytes.asList(Arrays.copyOfRange(cipher, 48, 96)));
opret_data.add(Bytes.asList(vkbsha96));
final OPRETTransaction t4 = new OPRETTransaction(Sha256Hash.of(nullbyte), Sha256Hash.of(nullbyte), opret_data);
final OPRETECParser parser = new OPRETECParser();
parser.addVerifyKey(mvk, 0);
assertFalse(parser.handleTransaction(t2));
assertFalse(parser.handleTransaction(t3));
assertFalse(parser.handleTransaction(t4));
assertTrue(parser.handleTransaction(t1));
mvk.clearSubKeys();
assertFalse(parser.handleTransaction(t1));
assertFalse(parser.handleTransaction(t3));
assertFalse(parser.handleTransaction(t4));
assertTrue(parser.handleTransaction(t2));
mvk.clearSubKeys();
assertFalse(parser.handleTransaction(t1));
assertFalse(parser.handleTransaction(t4));
assertFalse(parser.handleTransaction(t3));
assertTrue(parser.handleTransaction(t2));
mvk.clearSubKeys();
assertFalse(parser.handleTransaction(t2));
assertFalse(parser.handleTransaction(t4));
assertFalse(parser.handleTransaction(t3));
assertTrue(parser.handleTransaction(t1));
}
/**
* Test method for
* {@link org.tcpid.opretj.OPRETECParser#pushTransaction(org.tcpid.opretj.OPRETTransaction)}.
*/
@Test
public void testPushTransactionWithNonce() {
logger.debug("testPushTransactionWithNonce");
final byte[] cipher = Encoder.HEX.decode(
"24f99184d03a6ffa5826bd9300a7fb1cff264600f335b3c6042f15cb4d3d9019fa2a9c905cdf6f6c80178def845f0340e6d2e55a7dee433a5af984760adc23e187734e5e4e76aa22f3acab172262633139b6dcd11229fe2385661a70d6c206c0");
final byte[] vkb = Encoder.HEX.decode("fb2e360caf811b3aaf534d0458c2a2ca3e1f213b244a6f83af1ab50eddacdd8c");
final MasterVerifyKey mvk = new MasterVerifyKey(vkb);
final byte[] vkbsha96 = Arrays.copyOfRange(Sha256Hash.of(vkb).getBytes(), 0, 12);
final byte[] nullbyte = {};
List<List<Byte>> opret_data = new ArrayList<>();
opret_data.add(Bytes.asList(Encoder.HEX.decode("eca1")));
final byte[] byte1f = { (byte) 0x11 };
opret_data.add(Bytes.asList(Bytes.concat(Arrays.copyOfRange(cipher, 0, 48), byte1f)));
opret_data.add(Bytes.asList(vkbsha96));
final OPRETTransaction t1 = new OPRETTransaction(Sha256Hash.of(nullbyte), Sha256Hash.of(nullbyte), opret_data);
opret_data = new ArrayList<>();
opret_data.add(Bytes.asList(Encoder.HEX.decode("eca2")));
opret_data.add(Bytes.asList(Arrays.copyOfRange(cipher, 48, 96)));
opret_data.add(Bytes.asList(vkbsha96));
final OPRETTransaction t2 = new OPRETTransaction(Sha256Hash.of(nullbyte), Sha256Hash.of(nullbyte), opret_data);
final OPRETECParser parser = new OPRETECParser();
parser.addVerifyKey(mvk, 0);
assertFalse(parser.handleTransaction(t2));
assertTrue(parser.handleTransaction(t1));
mvk.clearSubKeys();
assertFalse(parser.handleTransaction(t1));
assertTrue(parser.handleTransaction(t2));
mvk.clearSubKeys();
assertFalse(parser.handleTransaction(t1));
assertTrue(parser.handleTransaction(t2));
mvk.clearSubKeys();
assertFalse(parser.handleTransaction(t2));
assertTrue(parser.handleTransaction(t1));
}
}