move special EC parser and key handling to testapp

This commit is contained in:
Harald Hoyer 2016-09-14 10:15:47 +02:00
parent 6b50ca4921
commit 765b452f7d
16 changed files with 149 additions and 203 deletions

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>
@ -106,6 +106,15 @@
</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>
<configuration>
<runOrder>alphabetical</runOrder>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>

View file

@ -0,0 +1,18 @@
package org.tcpid.key;
import static org.libsodium.jni.NaCl.sodium;
import org.libsodium.jni.Sodium;
import org.libsodium.jni.crypto.Util;
public class HMACSHA512256 {
public static final int HMACSHA512256_BYTES = sodium().crypto_auth_hmacsha512256_bytes();
public static final int HMACSHA512256_KEYBYTES = sodium().crypto_auth_hmacsha512256_keybytes();
public static byte[] of(final byte[] key, final byte[] msg) {
final byte[] hash = Util.zeros(HMACSHA512256_BYTES);
final byte[] hashkey = Util.prependZeros(HMACSHA512256_KEYBYTES, key);
Sodium.crypto_auth_hmacsha512256(hash, msg, msg.length, hashkey);
return hash;
}
}

View file

@ -0,0 +1,63 @@
package org.tcpid.key;
import java.util.ArrayList;
import org.libsodium.jni.crypto.Hash;
import org.libsodium.jni.encoders.Encoder;
import com.google.common.primitives.Longs;
public class MasterSigningKey extends SigningKey {
public static final Hash HASH = new Hash();
private MasterSigningKey subkey;
private Long subkeyindex;
private final ArrayList<Long> keyindex;
public MasterSigningKey(final byte[] key) {
super(key);
subkey = null;
subkeyindex = 0L;
this.keyindex = new ArrayList<>();
}
public MasterSigningKey(final byte[] key, final ArrayList<Long> keyindex) {
super(key);
subkey = null;
subkeyindex = 0L;
this.keyindex = new ArrayList<>(keyindex);
}
public MasterVerifyKey getMasterVerifyKey() {
return new MasterVerifyKey(this.getVerifyKey().toBytes());
}
public MasterSigningKey getNextValidSubKey(final Long offset) {
return getSubKey(subkeyindex + offset);
}
public MasterSigningKey getSubKey(final Long offset) {
final byte[] ob = Longs.toByteArray(offset);
final byte[] newseed = HMACSHA512256.of(seed, ob);
final ArrayList<Long> ki = new ArrayList<>(this.keyindex);
ki.add(offset);
return new MasterSigningKey(newseed, ki);
}
public MasterSigningKey getValidSubKey() {
if (subkey == null) {
subkey = getSubKey(subkeyindex);
}
return subkey;
}
public void revokeSubKey() {
subkey.setRevoked(true);
subkeyindex++;
subkey = null;
}
@Override
public String toString() {
return "Index: " + this.keyindex.toString() + " " + Encoder.HEX.encode(seed);
}
}

View file

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

View file

@ -0,0 +1,125 @@
/**
* Copyright 2013 Bruno Oliveira, and individual contributors
*
* 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.key;
import static org.libsodium.jni.NaCl.sodium;
import static org.libsodium.jni.SodiumConstants.PUBLICKEY_BYTES;
import static org.libsodium.jni.SodiumConstants.SECRETKEY_BYTES;
import static org.libsodium.jni.SodiumConstants.SIGNATURE_BYTES;
import org.libsodium.jni.Sodium;
import org.libsodium.jni.crypto.Random;
import org.libsodium.jni.crypto.Util;
import org.libsodium.jni.encoders.Encoder;
import org.libsodium.jni.keys.KeyPair;
import org.libsodium.jni.keys.PrivateKey;
public class SigningKey implements Comparable<SigningKey> {
protected final byte[] seed;
private final byte[] secretKey;
private final VerifyKey verifyKey;
private boolean revoked;
public SigningKey() {
this(new Random().randomBytes(SECRETKEY_BYTES));
}
public SigningKey(final byte[] seed) {
Util.checkLength(seed, SECRETKEY_BYTES);
this.seed = seed.clone();
this.secretKey = Util.zeros(SECRETKEY_BYTES * 2);
final byte[] publicKey = Util.zeros(PUBLICKEY_BYTES);
sodium();
Util.isValid(Sodium.crypto_sign_ed25519_seed_keypair(publicKey, secretKey, seed),
"Failed to generate a key pair");
this.verifyKey = new VerifyKey(publicKey);
}
public SigningKey(final String seed, final Encoder encoder) {
this(encoder.decode(seed));
}
@Override
public int compareTo(final SigningKey other) {
for (int i = SECRETKEY_BYTES - 1; i >= 0; i--) {
final int thisByte = this.seed[i] & 0xff;
final int otherByte = other.seed[i] & 0xff;
if (thisByte > otherByte) {
return 1;
}
if (thisByte < otherByte) {
return -1;
}
}
return 0;
}
public KeyPair getKeyPair() {
final byte[] sk = Util.zeros(SECRETKEY_BYTES);
sodium();
Sodium.crypto_sign_ed25519_sk_to_curve25519(sk, this.secretKey);
return new KeyPair(sk);
}
public PrivateKey getPrivateKey() {
final byte[] sk = Util.zeros(SECRETKEY_BYTES);
sodium();
Sodium.crypto_sign_ed25519_sk_to_curve25519(sk, this.secretKey);
return new PrivateKey(sk);
}
public VerifyKey getVerifyKey() {
return this.verifyKey;
}
public boolean isRevoked() {
return revoked;
}
public void setRevoked(final boolean revoked) {
if (this.revoked != true) {
this.revoked = revoked;
}
}
public byte[] sign(final byte[] message) {
byte[] signature = Util.prependZeros(SIGNATURE_BYTES, message);
final int[] bufferLen = new int[1];
sodium();
Sodium.crypto_sign_ed25519(signature, bufferLen, message, message.length, secretKey);
signature = Util.slice(signature, 0, SIGNATURE_BYTES);
return signature;
}
public String sign(final String message, final Encoder encoder) {
final byte[] signature = sign(encoder.decode(message));
return encoder.encode(signature);
}
public byte[] toBytes() {
return seed;
}
@Override
public String toString() {
return Encoder.HEX.encode(seed);
}
}

View file

@ -0,0 +1,135 @@
/**
* Copyright 2013 Bruno Oliveira, and individual contributors
*
* 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.key;
import static org.libsodium.jni.NaCl.sodium;
import static org.libsodium.jni.SodiumConstants.PUBLICKEY_BYTES;
import static org.libsodium.jni.SodiumConstants.SIGNATURE_BYTES;
import java.util.Arrays;
import org.libsodium.jni.Sodium;
import org.libsodium.jni.crypto.Hash;
import org.libsodium.jni.crypto.Util;
import org.libsodium.jni.encoders.Encoder;
import org.libsodium.jni.keys.PublicKey;
public class VerifyKey implements Comparable<VerifyKey> {
public static final Hash HASH = new Hash();
private final byte[] key;
private final byte[] hash;
private final byte[] shortHash;
private boolean revoked;
private MasterVerifyKey masterkey;
public VerifyKey(final byte[] key) {
Util.checkLength(key, PUBLICKEY_BYTES);
this.key = key;
this.hash = HASH.sha256(key);
this.shortHash = Arrays.copyOfRange(hash, 0, 12);
this.revoked = false;
}
public VerifyKey(final String key, final Encoder encoder) {
this(encoder.decode(key));
}
@Override
public int compareTo(final VerifyKey other) {
for (int i = PUBLICKEY_BYTES - 1; i >= 0; i--) {
final int thisByte = this.key[i] & 0xff;
final int otherByte = other.key[i] & 0xff;
if (thisByte > otherByte) {
return 1;
}
if (thisByte < otherByte) {
return -1;
}
}
return 0;
}
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if ((o == null) || (getClass() != o.getClass())) {
return false;
}
return Arrays.equals(key, ((VerifyKey) o).key);
}
public MasterVerifyKey getMasterkey() {
return masterkey;
}
public PublicKey getPublicKey() {
final byte[] pk = Util.zeros(PUBLICKEY_BYTES);
sodium();
Sodium.crypto_sign_ed25519_pk_to_curve25519(pk, this.key);
return new PublicKey(pk);
}
public byte[] getShortHash() {
return this.shortHash;
}
public boolean isRevoked() {
return revoked;
}
public void setMasterkey(final MasterVerifyKey masterkey) {
this.masterkey = masterkey;
}
public void setRevoked(final boolean revoked) {
if (this.revoked != true) {
this.revoked = revoked;
}
}
public byte[] toBytes() {
return key;
}
public byte[] toHash() {
return this.hash;
}
@Override
public String toString() {
return Encoder.HEX.encode(key);
}
public boolean verify(final byte[] message, final byte[] signature) {
Util.checkLength(signature, SIGNATURE_BYTES);
final byte[] sigAndMsg = Util.merge(signature, message);
final byte[] buffer = Util.zeros(sigAndMsg.length);
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");
}
public boolean verify(final String message, final String signature, final Encoder encoder) {
return verify(encoder.decode(message), encoder.decode(signature));
}
}

View file

@ -22,7 +22,6 @@ import org.libsodium.jni.crypto.Hash;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tcpid.key.MasterSigningKey;
import org.tcpid.opretj.OPRETECParser;
import org.tcpid.opretj.OPRETWallet;
import org.tcpid.opretj.OPRETWalletAppKit;

View file

@ -0,0 +1,7 @@
package org.tcpid.opretj.testapp;
import org.tcpid.key.MasterVerifyKey;
public interface OPRETECEventListener {
void onOPRETRevoke(MasterVerifyKey key);
}

View file

@ -0,0 +1,429 @@
package org.tcpid.opretj.testapp;
import static org.bitcoinj.script.ScriptOpCodes.OP_RETURN;
import static org.libsodium.jni.NaCl.sodium;
import static org.libsodium.jni.SodiumConstants.NONCE_BYTES;
import static org.libsodium.jni.SodiumConstants.SECRETKEY_BYTES;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import org.bitcoinj.core.PartialMerkleTree;
import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.core.Utils;
import org.bitcoinj.script.Script;
import org.bitcoinj.script.ScriptBuilder;
import org.bitcoinj.utils.ListenerRegistration;
import org.bitcoinj.utils.Threading;
import org.libsodium.jni.Sodium;
import org.libsodium.jni.crypto.Hash;
import org.libsodium.jni.crypto.Util;
import org.libsodium.jni.encoders.Encoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tcpid.key.HMACSHA512256;
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();
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"));
private static final List<Byte> OPRET_MAGIC_ECA3 = Bytes.asList(Utils.HEX.decode("eca3"));
private static final List<Byte> OPRET_MAGIC_ECA4 = Bytes.asList(Utils.HEX.decode("eca4"));
private static final List<Byte> OPRET_MAGIC_EC51 = Bytes.asList(Utils.HEX.decode("ec51"));
private static final List<Byte> OPRET_MAGIC_EC52 = Bytes.asList(Utils.HEX.decode("ec52"));
private static final List<Byte> OPRET_MAGIC_EC0F = Bytes.asList(Utils.HEX.decode("ec0f"));
public static boolean checkKeyforRevoke(final VerifyKey k, final byte[] sig) {
logger.debug("CHECKING REVOKE PKHASH {} - SIG {}", Utils.HEX.encode(k.toHash()), Utils.HEX.encode(sig));
final byte[] revokemsg = Bytes.concat("Revoke ".getBytes(), k.toHash());
logger.debug("Using VerifyKey {}", k);
if (k.verify(revokemsg, sig)) {
logger.debug("REVOKED VerifyKey {}", k);
return true;
} else {
logger.debug("SIGNATURE does not match!");
return false;
}
}
public static Script getRevokeScript(final SigningKey key) {
final byte[] revokemsg = Bytes.concat("Revoke ".getBytes(), key.getVerifyKey().toHash());
final byte[] sig = key.sign(revokemsg);
final Script script = new ScriptBuilder().op(OP_RETURN).data(Utils.HEX.decode("ec0f")).data(sig)
.data(key.getVerifyKey().getShortHash()).build();
return script;
}
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
.synchronizedMap(new HashMap<>());
protected final Map<List<Byte>, List<OPRETTransaction>> transA3HashMap = Collections
.synchronizedMap(new HashMap<>());
protected final Map<List<Byte>, List<OPRETTransaction>> transA4HashMap = Collections
.synchronizedMap(new HashMap<>());
protected final Map<List<Byte>, List<OPRETTransaction>> trans51HashMap = Collections
.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<>();
/**
* 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(final OPRETECEventListener listener) {
// This is thread safe, so we don't need to take the lock.
opReturnChangeListeners.add(new ListenerRegistration<OPRETECEventListener>(listener, Threading.SAME_THREAD));
}
public void addVerifyKey(final MasterVerifyKey key, final long earliestTime) {
final List<Byte> hash = Bytes.asList(key.getShortHash());
if (!verifyKeys.containsKey(hash)) {
verifyKeys.put(hash, new ArrayList<MasterVerifyKey>());
}
verifyKeys.get(hash).add(key);
logger.debug("Adding pkhash {}", key.getShortHash());
addOPRET(key.getShortHash(), earliestTime);
}
public boolean cryptoSelfTest() {
if (NONCE_BYTES > HMACSHA512256.HMACSHA512256_BYTES) {
logger.error("NONCE_BYTES > HMACSHA512256.HMACSHA512256_BYTES: {} > {}", NONCE_BYTES,
HMACSHA512256.HMACSHA512256_BYTES);
return false;
}
if (SECRETKEY_BYTES != HMACSHA512256.HMACSHA512256_BYTES) {
logger.error("SECRETKEY_BYTES != HMACSHA512256.HMACSHA512256_BYTES: {} > {}", SECRETKEY_BYTES,
HMACSHA512256.HMACSHA512256_BYTES);
return false;
}
final MasterSigningKey msk = new MasterSigningKey(HASH.sha256("TESTSEED".getBytes()));
if (!Arrays.equals(Utils.HEX.decode("4071b2b3db7cc7aecd0b23608e96f44f08463ea0ee0a0c12f5fa21ff449deb55"),
msk.toBytes())) {
logger.error("MasterSigningKey(HASH.sha256('TESTSEED'.getBytes())) test failed");
return false;
}
final MasterSigningKey subkey = msk.getSubKey(1L).getSubKey(2L).getSubKey(3L).getSubKey(4L);
if (!Arrays.equals(Utils.HEX.decode("00cb0c8748318d27eab65159a2261c028d764c1154fc302b9b046aa2bbefab27"),
subkey.toBytes())) {
logger.error("MasterSigningKey subkey derivation failed");
return false;
}
final BigInteger biMSK = new BigInteger(1, msk.toBytes());
BigInteger biSub = new BigInteger(1, subkey.toBytes());
final BigInteger pow2_256 = new BigInteger("10000000000000000000000000000000000000000000000000000000000000000",
16);
final BigInteger biDiff = biSub.subtract(biMSK).mod(pow2_256);
logger.debug("{} = {}", Utils.HEX.encode(msk.toBytes()), biMSK.toString(16));
logger.debug("{} - {} = {}", Utils.HEX.encode(msk.toBytes()), Utils.HEX.encode(subkey.toBytes()),
Utils.HEX.encode(biDiff.toByteArray()));
biSub = biMSK.add(biDiff).mod(pow2_256);
final byte[] bisubb = biSub.toByteArray();
logger.debug("{}", Utils.HEX.encode(Util.prependZeros(32 - bisubb.length, bisubb)));
if (!Arrays.equals(bisubb, subkey.toBytes())) {
logger.error("MasterSigningKey subkey difference calculation failed");
return false;
}
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) {
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);
try {
logger.debug("Checking sig {} with key {}", Encoder.HEX.encode(sig), Encoder.HEX.encode(vk));
k.verify(vk, sig);
} catch (final RuntimeException e) {
logger.debug("sig does not match");
continue;
}
logger.debug("sig matches");
k.setFirstValidSubKey(new MasterVerifyKey(vk), selfTx, otherTx);
otherTransHashMap.get(pkhash).remove(otherTx);
if (otherTransHashMap.get(pkhash).isEmpty()) {
otherTransHashMap.remove(pkhash);
}
return true;
}
}
}
// no matching transaction found, save for later
if (!selfTransHashMap.containsKey(pkhash)) {
selfTransHashMap.put(pkhash, new ArrayList<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)) {
logger.debug("chunk 1 size != 64, but {}", sig.length);
return false;
}
final List<Byte> pkhash = t.opretData.get(2);
if ((pkhash.size() != 12)) {
logger.debug("chunk 2 size!= 12 but {} ", pkhash.size());
return false;
}
if (!verifyKeys.containsKey(pkhash)) {
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));
}
queueOnOPRETRevoke(k);
return true;
}
}
return false;
}
private boolean handleEC1C(final OPRETTransaction t) {
// TODO Auto-generated method stub
return false;
}
private boolean handleEC1D(final OPRETTransaction t) {
final byte[] sig = Bytes.toArray(t.opretData.get(1));
if ((sig.length != 64)) {
logger.debug("chunk 1 size != 64, but {}", sig.length);
return false;
}
final List<Byte> pkhash = t.opretData.get(2);
if ((pkhash.size() != 12)) {
logger.debug("chunk 2 size!= 12 but {} ", pkhash.size());
return false;
}
if (!verifyKeys.containsKey(pkhash)) {
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;
}
}
return false;
}
private boolean handleEC51(final OPRETTransaction t) {
// TODO Auto-generated method stub
return false;
}
private boolean handleEC52(final OPRETTransaction t) {
// TODO Auto-generated method stub
return false;
}
private boolean handleECA1(final OPRETTransaction t1) {
logger.debug("handleECA1");
return handleAnnounce(t1, transA1HashMap, transA2HashMap, true);
}
private boolean handleECA2(final OPRETTransaction t2) {
logger.debug("handleECA2");
return handleAnnounce(t2, transA2HashMap, transA1HashMap, false);
}
private boolean handleECA3(final OPRETTransaction t) {
// TODO Auto-generated method stub
return false;
}
private boolean handleECA4(final OPRETTransaction t) {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean pushTransaction(final OPRETTransaction t) {
logger.debug("checking {}", t.opretData);
if ((t.opretData.size() != 2) && (t.opretData.size() != 3) && (t.opretData.size() != 4)) {
return false;
}
final List<Byte> chunk = t.opretData.get(0);
if (chunk.equals(OPRET_MAGIC_EC0F)) {
return handleEC0F(t);
}
if (chunk.equals(OPRET_MAGIC_EC1C)) {
return handleEC1C(t);
}
if (chunk.equals(OPRET_MAGIC_EC1D)) {
return handleEC1D(t);
}
if (chunk.equals(OPRET_MAGIC_ECA1)) {
return handleECA1(t);
}
if (chunk.equals(OPRET_MAGIC_ECA2)) {
return handleECA2(t);
}
if (chunk.equals(OPRET_MAGIC_ECA3)) {
return handleECA3(t);
}
if (chunk.equals(OPRET_MAGIC_ECA4)) {
return handleECA4(t);
}
if (chunk.equals(OPRET_MAGIC_EC51)) {
return handleEC51(t);
}
if (chunk.equals(OPRET_MAGIC_EC52)) {
return handleEC52(t);
}
return false;
}
private void queueOnOPRETId(final MasterVerifyKey k) {
// TODO Auto-generated method stub
}
protected void queueOnOPRETRevoke(final MasterVerifyKey key) {
for (final ListenerRegistration<OPRETECEventListener> registration : opReturnChangeListeners) {
registration.executor.execute(() -> registration.listener.onOPRETRevoke(key));
}
}
/**
* Removes the given event listener object. Returns true if the listener was
* removed, false if that listener was never added.
*/
public boolean removeOPRETECRevokeEventListener(final OPRETECEventListener listener) {
return ListenerRegistration.removeFromList(listener, opReturnChangeListeners);
}
public void removeVerifyKey(final MasterVerifyKey key) {
final List<Byte> hash = Bytes.asList(key.getShortHash());
if (!verifyKeys.containsKey(hash)) {
return;
}
verifyKeys.get(hash).remove(key);
removeOPRET(key.getShortHash());
}
}

View file

@ -14,7 +14,7 @@
<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.OPRETECParser" level="debug" />
<!--
<logger name="eckey.OPRETSimpleLogger" level="debug" />
<logger name="eckey.OPRETSimpleParser" level="debug" />

View file

@ -0,0 +1,132 @@
package org.tcpid.opretj.testapp;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.libsodium.jni.NaCl.sodium;
import static org.libsodium.jni.SodiumConstants.NONCE_BYTES;
import static org.libsodium.jni.SodiumConstants.SECRETKEY_BYTES;
import java.math.BigInteger;
import java.util.Arrays;
import org.bitcoinj.core.Utils;
import org.junit.Test;
import org.libsodium.jni.Sodium;
import org.libsodium.jni.crypto.Hash;
import org.libsodium.jni.crypto.Util;
import org.libsodium.jni.encoders.Encoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tcpid.key.HMACSHA512256;
import org.tcpid.key.MasterSigningKey;
import org.tcpid.key.MasterVerifyKey;
import com.google.common.primitives.Bytes;
public class TestCrypto {
private final static Logger logger = LoggerFactory.getLogger(TestCrypto.class);
private final static Hash HASH = new Hash();
@Test
public void testDerive() {
assertTrue("NONCE_BYTES > HMACSHA512256.HMACSHA512256_BYTES", NONCE_BYTES <= HMACSHA512256.HMACSHA512256_BYTES);
assertEquals(SECRETKEY_BYTES, HMACSHA512256.HMACSHA512256_BYTES);
final MasterSigningKey msk = new MasterSigningKey(HASH.sha256("TESTSEED".getBytes()));
assertArrayEquals(Utils.HEX.decode("4071b2b3db7cc7aecd0b23608e96f44f08463ea0ee0a0c12f5fa21ff449deb55"),
msk.toBytes());
final MasterSigningKey subkey = msk.getSubKey(1L).getSubKey(2L).getSubKey(3L).getSubKey(4L);
assertArrayEquals(Utils.HEX.decode("00cb0c8748318d27eab65159a2261c028d764c1154fc302b9b046aa2bbefab27"),
subkey.toBytes());
final BigInteger biMSK = new BigInteger(1, msk.toBytes());
BigInteger biSub = new BigInteger(1, subkey.toBytes());
final BigInteger pow2_256 = new BigInteger("10000000000000000000000000000000000000000000000000000000000000000",
16);
final BigInteger biDiff = biSub.subtract(biMSK).mod(pow2_256);
biSub = biMSK.add(biDiff).mod(pow2_256);
final byte[] bisubb = biSub.toByteArray();
assertArrayEquals(bisubb, subkey.toBytes());
}
@Test
public void testSign() {
final MasterSigningKey msk = new MasterSigningKey(HASH.sha256("TESTSEED".getBytes()));
final MasterVerifyKey vk = msk.getMasterVerifyKey();
final byte[] revokemsg = Bytes.concat("Revoke ".getBytes(), vk.toHash());
final byte[] sig = msk.sign(revokemsg);
assertTrue("Verification of signature failed.", vk.verify(revokemsg, sig));
}
@Test
public void 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(subkey.toBytes());
logger.debug("using key {}", Encoder.HEX.encode(vk.toBytes()));
final byte[] noncebytes = Util.zeros(32);
final byte[] sharedkey = HASH.sha256(HASH.sha256(Bytes.concat(vk.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);
byte[] msg = Bytes.concat(subkey.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.", vk.verify(vkb, sig));
assertArrayEquals(subkey.toBytes(), vkb);
}
@Test
public void testSignEncNoncebytes() {
final MasterSigningKey msk = new MasterSigningKey(HASH.sha256("TESTSEED".getBytes()));
final MasterVerifyKey vk = msk.getMasterVerifyKey();
byte[] sig = msk.sign(vk.toBytes());
logger.debug("nonce: using key {}", Encoder.HEX.encode(vk.toBytes()));
final byte[] noncebytes = Encoder.HEX
.decode("0000000000000000000000000000001100000000000000000000000000000000");
final byte[] sharedkey = HASH.sha256(HASH.sha256(Bytes.concat(vk.toBytes(), noncebytes)));
final byte[] xornonce = Arrays.copyOfRange(HASH.sha256(Bytes.concat(sharedkey, noncebytes)), 0, 24);
logger.debug("nonce: nonce {}", Encoder.HEX.encode(xornonce));
logger.debug("nonce: sharedkey {}", Encoder.HEX.encode(sharedkey));
final byte[] cipher = Util.zeros(96);
byte[] msg = Bytes.concat(vk.toBytes(), sig);
assertEquals(96, msg.length);
sodium();
Sodium.crypto_stream_xsalsa20_xor(cipher, msg, 96, xornonce, sharedkey);
assertEquals(96, cipher.length);
logger.debug("nonce: Clear : {}", Encoder.HEX.encode(msg));
logger.debug("nonce: 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("nonce: vkb : {}", Encoder.HEX.encode(vkb));
assertTrue("nonce: Verification of signature failed.", vk.verify(vkb, sig));
assertArrayEquals(vk.toBytes(), vkb);
}
}

View file

@ -0,0 +1,155 @@
/**
*
*/
package org.tcpid.opretj.testapp;
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.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 org.tcpid.opretj.testapp.OPRETECParser;
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.testapp.OPRETECParser#pushTransaction(org.tcpid.opretj.OPRETTransaction)}.
*/
@Test
public void testPushTransaction() {
logger.debug("testPushTransaction");
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);
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));
mvk.clearSubKeys();
assertFalse(parser.pushTransaction(t1));
assertFalse(parser.pushTransaction(t3));
assertFalse(parser.pushTransaction(t4));
assertTrue(parser.pushTransaction(t2));
mvk.clearSubKeys();
assertFalse(parser.pushTransaction(t1));
assertFalse(parser.pushTransaction(t4));
assertFalse(parser.pushTransaction(t3));
assertTrue(parser.pushTransaction(t2));
mvk.clearSubKeys();
assertFalse(parser.pushTransaction(t2));
assertFalse(parser.pushTransaction(t4));
assertFalse(parser.pushTransaction(t3));
assertTrue(parser.pushTransaction(t1));
final MasterVerifyKey subkey = mvk.getValidSubKey();
assertArrayEquals(subkey.toBytes(),
Encoder.HEX.decode("e4acb361f4ec55804af6b5a1bbf5ca74ad78b4edc9a977a1dfed08872aa0a5db"));
}
/**
* Test method for
* {@link org.tcpid.opretj.testapp.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.pushTransaction(t2));
assertTrue(parser.pushTransaction(t1));
mvk.clearSubKeys();
assertFalse(parser.pushTransaction(t1));
assertTrue(parser.pushTransaction(t2));
mvk.clearSubKeys();
assertFalse(parser.pushTransaction(t1));
assertTrue(parser.pushTransaction(t2));
mvk.clearSubKeys();
assertFalse(parser.pushTransaction(t2));
assertTrue(parser.pushTransaction(t1));
final MasterVerifyKey subkey = mvk.getValidSubKey();
assertArrayEquals(subkey.toBytes(),
Encoder.HEX.decode("fb2e360caf811b3aaf534d0458c2a2ca3e1f213b244a6f83af1ab50eddacdd8c"));
}
}