begin derived keys

This commit is contained in:
Harald Hoyer 2016-09-05 18:43:12 +02:00
parent 9842bc68ef
commit 35dd21f3c1
8 changed files with 246 additions and 184 deletions

View file

@ -21,7 +21,7 @@ import org.bitcoinj.wallet.SendRequest;
import org.libsodium.jni.crypto.Hash;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tcpid.key.SigningKey;
import org.tcpid.key.MasterSigningKey;
import org.tcpid.opretj.OPRETECParser;
import org.tcpid.opretj.OPRETWallet;
import org.tcpid.opretj.OPRETWalletAppKit;
@ -39,7 +39,7 @@ public class App {
public final static long OPRET_BIRTHDAY = 1471989600;
private final static Logger logger = LoggerFactory.getLogger(App.class);
private final static SigningKey SK = new SigningKey(HASH.sha256("TESTSEED".getBytes()));
private final static MasterSigningKey SK = new MasterSigningKey(HASH.sha256("TESTSEED".getBytes()));
private static void displayBalance(final OPRETWalletAppKit kit, final PrintWriter out) {
out.write("Balance: " + kit.wallet().getBalance().toFriendlyString() + "\n");
@ -157,7 +157,15 @@ public class App {
}
public static void main(final String[] args) throws Exception {
MasterSigningKey sk = SK.getSubKey(1L);
System.err.println(SK.toString());
System.err.println(sk.toString());
sk = SK.getSubKey(2L);
System.err.println(sk.toString());
sk = SK.getSubKey(3L);
System.err.println(sk.toString());
System.exit(0);
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);

View file

@ -0,0 +1,71 @@
package org.tcpid.key;
import static org.libsodium.jni.NaCl.sodium;
import static org.libsodium.jni.SodiumConstants.SECRETKEY_BYTES;
import java.util.ArrayList;
import org.libsodium.jni.Sodium;
import org.libsodium.jni.crypto.Hash;
import org.libsodium.jni.crypto.Util;
import org.libsodium.jni.encoders.Encoder;
import com.google.common.primitives.Longs;
public class MasterSigningKey extends SigningKey {
private MasterSigningKey subkey;
private Long subkeyindex;
public static final Hash HASH = new Hash();
private final ArrayList<Long> keyindex;
public MasterSigningKey(final byte[] key, ArrayList<Long> keyindex) {
super(key);
subkey = null;
subkeyindex = 0L;
this.keyindex = new ArrayList<>(keyindex);
}
public MasterSigningKey(final byte[] key) {
super(key);
subkey = null;
subkeyindex = 0L;
this.keyindex = new ArrayList<>();
}
public MasterSigningKey getValidSubKey() {
if (subkey == null) {
subkey = getSubKey(subkeyindex);
}
return subkey;
}
public MasterSigningKey getSubKey(Long offset) {
System.err.println("getSubKey");
final byte[] la = Longs.toByteArray(offset);
final byte[] keynum = Util.prependZeros(16 - la.length, la);
System.err.println("getSubKey key=" + Encoder.HEX.encode(keynum));
final byte[] appid = Util.prependZeros(14, "EC".getBytes());
final byte[] newseed = Util.zeros(SECRETKEY_BYTES);
sodium();
Sodium.crypto_generichash_blake2b_salt_personal(newseed, newseed.length, null, 0, this.seed, this.seed.length,
keynum, appid);
final ArrayList<Long> ki = new ArrayList<>(this.keyindex);
ki.add(offset);
return new MasterSigningKey(newseed, ki);
}
public MasterSigningKey getNextValidSubKey(Long offset) {
return getSubKey(subkeyindex + offset);
}
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,48 @@
package org.tcpid.key;
import java.util.LinkedList;
import java.util.NoSuchElementException;
public class MasterVerifyKey extends VerifyKey {
private final LinkedList<VerifyKey> subkeys = new LinkedList<>();
public MasterVerifyKey(final byte[] key) {
super(key);
}
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) {
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);
}
public void clearSubKeys() {
subkeys.clear();
}
}

View file

@ -21,48 +21,68 @@ 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 {
public class SigningKey implements Comparable<SigningKey> {
protected final byte[] seed;
private final byte[] seed;
private final byte[] secretKey;
private final VerifyKey verifyKey;
private boolean revoked;
public SigningKey() {
this(new Random().randomBytes(SECRETKEY_BYTES));
}
public SigningKey(byte[] seed) {
public SigningKey(final byte[] seed) {
Util.checkLength(seed, SECRETKEY_BYTES);
this.seed = seed;
this.seed = seed.clone();
this.secretKey = Util.zeros(SECRETKEY_BYTES * 2);
final byte[] publicKey = Util.zeros(PUBLICKEY_BYTES);
Util.isValid(sodium().crypto_sign_ed25519_seed_keypair(publicKey, secretKey, seed),
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(String seed, Encoder encoder) {
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().crypto_sign_ed25519_sk_to_curve25519(sk, this.secretKey);
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().crypto_sign_ed25519_sk_to_curve25519(sk, this.secretKey);
sodium();
Sodium.crypto_sign_ed25519_sk_to_curve25519(sk, this.secretKey);
return new PrivateKey(sk);
}
@ -70,15 +90,26 @@ public class SigningKey {
return this.verifyKey;
}
public byte[] sign(byte[] message) {
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().crypto_sign_ed25519(signature, bufferLen, message, message.length, secretKey);
sodium();
Sodium.crypto_sign_ed25519(signature, bufferLen, message, message.length, secretKey);
signature = Util.slice(signature, 0, SIGNATURE_BYTES);
return signature;
}
public String sign(String message, Encoder encoder) {
public String sign(final String message, final Encoder encoder) {
final byte[] signature = sign(encoder.decode(message));
return encoder.encode(signature);
}

View file

@ -22,6 +22,7 @@ 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;
@ -34,18 +35,17 @@ public class VerifyKey implements Comparable<VerifyKey> {
private final byte[] hash;
private final byte[] shortHash;
private boolean revoked;
private MasterVerifyKey masterkey;
public boolean isRevoked() {
return revoked;
public MasterVerifyKey getMasterkey() {
return masterkey;
}
public void setRevoked(boolean revoked) {
if (this.revoked != true) {
this.revoked = revoked;
}
public void setMasterkey(MasterVerifyKey masterkey) {
this.masterkey = masterkey;
}
public VerifyKey(byte[] key) {
public VerifyKey(final byte[] key) {
Util.checkLength(key, PUBLICKEY_BYTES);
this.key = key;
this.hash = HASH.sha256(key);
@ -53,58 +53,10 @@ public class VerifyKey implements Comparable<VerifyKey> {
this.revoked = false;
}
public VerifyKey(String key, Encoder encoder) {
public VerifyKey(final String key, final Encoder encoder) {
this(encoder.decode(key));
}
public PublicKey getPublicKey() {
final byte[] pk = Util.zeros(PUBLICKEY_BYTES);
sodium().crypto_sign_ed25519_pk_to_curve25519(pk, this.key);
return new PublicKey(pk);
}
public byte[] toBytes() {
return key;
}
@Override
public String toString() {
return Encoder.HEX.encode(key);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if ((o == null) || (getClass() != o.getClass())) {
return false;
}
return Arrays.equals(key, ((VerifyKey) o).key);
}
public boolean verify(byte[] message, 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];
return Util.isValid(sodium().crypto_sign_ed25519_open(buffer, bufferLen, sigAndMsg, sigAndMsg.length, key),
"signature was forged or corrupted");
}
public boolean verify(String message, String signature, Encoder encoder) {
return verify(encoder.decode(message), encoder.decode(signature));
}
public byte[] toHash() {
return this.hash;
}
public byte[] getShortHash() {
return this.shortHash;
}
@Override
public int compareTo(final VerifyKey other) {
for (int i = PUBLICKEY_BYTES - 1; i >= 0; i--) {
@ -120,4 +72,64 @@ public class VerifyKey implements Comparable<VerifyKey> {
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 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 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

@ -1,5 +1,6 @@
package org.tcpid.opretj;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -17,7 +18,7 @@ import com.google.common.primitives.Bytes;
public abstract class OPRETBaseHandler implements OPRETHandlerInterface {
private static final Logger logger = LoggerFactory.getLogger(OPRETBaseHandler.class);
private final CopyOnWriteArrayList<ListenerRegistration<OPRETChangeEventListener>> opReturnChangeListeners = new CopyOnWriteArrayList<ListenerRegistration<OPRETChangeEventListener>>();
private final Map<List<Byte>, Long> magicBytes = new HashMap<>();
private final Map<List<Byte>, Long> magicBytes = Collections.synchronizedMap(new HashMap<>());
protected void addOPRET(final byte[] magic, final long earliestTime) {
logger.debug("addMagicBytes: {} - Time {}", Utils.HEX.encode(magic), earliestTime);

View file

@ -1,110 +0,0 @@
package org.tcpid.opretj;
import java.util.ArrayList;
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.utils.ListenerRegistration;
import org.bitcoinj.utils.Threading;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.primitives.Bytes;
public class OPRETECExampleParser extends OPRETBaseHandler {
private static final Logger logger = LoggerFactory.getLogger(OPRETECExampleParser.class);
private static final List<Byte> OPRET_MAGIC = Bytes.asList(Utils.HEX.decode("ec1d"));
protected final Map<Sha256Hash, PartialMerkleTree> merkleHashMap = new HashMap<>();
protected final Map<Sha256Hash, OPRETTransaction> transHashMap = new HashMap<>();
private final CopyOnWriteArrayList<ListenerRegistration<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));
}
private boolean checkData(final OPRETTransaction t1, final OPRETTransaction t2) {
final List<List<Byte>> opret_data = new ArrayList<>(t1.opretData);
opret_data.addAll(t2.opretData);
logger.debug("checking {}", opret_data);
List<Byte> chunk;
chunk = opret_data.get(0);
if (!chunk.equals(OPRET_MAGIC)) {
logger.debug("0: != OPRET_MAGIC");
return false;
}
chunk = opret_data.get(1);
if (chunk.size() != 1) {
logger.debug("1: size != 1");
return false;
}
if (chunk.get(0) == (byte) 0xFE) {
if (opret_data.size() != 8) {
logger.debug("FE: size != 8");
return false;
}
chunk = opret_data.get(4);
if (!chunk.equals(OPRET_MAGIC)) {
logger.debug("FE 4 != OPRET_MAGIC");
return false;
}
if (!opret_data.get(2).equals(opret_data.get(6))) {
logger.debug("FE 2 != 6");
return false;
}
chunk = opret_data.get(5);
if ((chunk.size() != 1) || (chunk.get(0) != (byte) 0xFF)) {
logger.debug("FE 5 size!=1 or != FF");
return false;
}
return handleRevoke(t1, t2);
} else {
logger.debug("1: != 0xFE");
}
return false;
}
private boolean handleRevoke(final OPRETTransaction t1, final OPRETTransaction t2) {
final byte[] pkhash = Bytes.toArray(t1.opretData.get(2));
final byte[] sig = Bytes.concat(Bytes.toArray(t1.opretData.get(3)), Bytes.toArray(t2.opretData.get(3)));
logger.debug("REVOKE PK {} - SIG {}", Utils.HEX.encode(pkhash), Utils.HEX.encode(sig));
queueOnOPRETRevoke(pkhash, sig);
return true;
}
@Override
public void pushTransaction(final OPRETTransaction t) {
logger.debug("pushData: {}", t.opretData);
transHashMap.put(t.txHash, t);
}
protected void queueOnOPRETRevoke(final byte[] pkhash, final byte[] sig) {
for (final ListenerRegistration<OPRETECEventListener> registration : opReturnChangeListeners) {
registration.executor.execute(() -> registration.listener.onOPRETRevoke(pkhash, sig));
}
}
/**
* 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);
}
}

View file

@ -3,6 +3,7 @@ package org.tcpid.opretj;
import static org.bitcoinj.script.ScriptOpCodes.OP_RETURN;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -36,13 +37,13 @@ public class OPRETWallet extends Wallet implements BlocksDownloadedEventListener
private final OPRETHandlerInterface opbs;
private final Logger logger = LoggerFactory.getLogger(OPRETWallet.class);
protected final Map<Sha256Hash, Map<Sha256Hash, OPRETTransaction>> pendingTransactions;
protected final Map<Sha256Hash, Map<Sha256Hash, OPRETTransaction>> pendingTransactions = Collections
.synchronizedMap(new HashMap<>());
public OPRETWallet(final NetworkParameters params, final KeyChainGroup keyChainGroup,
final OPRETHandlerInterface bs) {
super(params, keyChainGroup);
opbs = bs;
pendingTransactions = new HashMap<>();
}
@Override
@ -174,7 +175,7 @@ public class OPRETWallet extends Wallet implements BlocksDownloadedEventListener
final Sha256Hash h = block.getHeader().getHash();
if (!pendingTransactions.containsKey(h)) {
pendingTransactions.put(h, new HashMap<Sha256Hash, OPRETTransaction>());
pendingTransactions.put(h, Collections.synchronizedMap(new HashMap<Sha256Hash, OPRETTransaction>()));
}
pendingTransactions.get(h).put(tx.getHash(), new OPRETTransaction(h, tx.getHash(), myList));