checkpoint
This commit is contained in:
		
							parent
							
								
									d97d867579
								
							
						
					
					
						commit
						086c52ce12
					
				
					 14 changed files with 591 additions and 201 deletions
				
			
		|  | @ -110,6 +110,7 @@ | ||||||
| 				<plugin> | 				<plugin> | ||||||
| 					<groupId>org.apache.maven.plugins</groupId> | 					<groupId>org.apache.maven.plugins</groupId> | ||||||
| 					<artifactId>maven-surefire-plugin</artifactId> | 					<artifactId>maven-surefire-plugin</artifactId> | ||||||
|  | 					    <version>2.19.1</version> | ||||||
| 					<configuration> | 					<configuration> | ||||||
| 						<runOrder>alphabetical</runOrder> | 						<runOrder>alphabetical</runOrder> | ||||||
| 					</configuration> | 					</configuration> | ||||||
|  | @ -129,7 +130,7 @@ | ||||||
| 				<artifactId>exec-maven-plugin</artifactId> | 				<artifactId>exec-maven-plugin</artifactId> | ||||||
| 				<version>1.2.1</version> | 				<version>1.2.1</version> | ||||||
| 				<configuration> | 				<configuration> | ||||||
| 					<mainClass>org.tcpid.opret.App</mainClass> | 					<mainClass>org.tcpid.opretj.testapp.App</mainClass> | ||||||
| 					<!-- <systemProperties> <systemProperty> <key>logback.configurationFile</key>  | 					<!-- <systemProperties> <systemProperty> <key>logback.configurationFile</key>  | ||||||
| 						<value>${basedir}/opret-testapp/src/main/resources/logback.xml</value> </systemProperty>  | 						<value>${basedir}/opret-testapp/src/main/resources/logback.xml</value> </systemProperty>  | ||||||
| 						</systemProperties> --> | 						</systemProperties> --> | ||||||
|  |  | ||||||
|  | @ -58,6 +58,10 @@ public class MasterSigningKey extends SigningKey { | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public String toString() { |     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); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -10,7 +10,8 @@ import org.tcpid.opretj.OPRETTransaction; | ||||||
| import com.google.common.primitives.Bytes; | import com.google.common.primitives.Bytes; | ||||||
| 
 | 
 | ||||||
| public class MasterVerifyKey extends VerifyKey { | public class MasterVerifyKey extends VerifyKey { | ||||||
|     private final LinkedList<MasterVerifyKey> subkeys = new LinkedList<>(); |     // FIXME: make private again | ||||||
|  |     public final LinkedList<MasterVerifyKey> subkeys = new LinkedList<>(); | ||||||
| 
 | 
 | ||||||
|     public MasterVerifyKey(final byte[] key) { |     public MasterVerifyKey(final byte[] key) { | ||||||
|         super(key); |         super(key); | ||||||
|  | @ -30,35 +31,44 @@ public class MasterVerifyKey extends VerifyKey { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public MasterVerifyKey getValidSubKey() { |     public MasterVerifyKey getValidSubKey() { | ||||||
|         return subkeys.getFirst(); |         for (final MasterVerifyKey k : subkeys) { | ||||||
|  |             if (!k.isRevoked()) { | ||||||
|  |                 return k; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return null; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public void revokeSubKey(final MasterVerifyKey key) { |     public void revokeSubKey(final VerifyKey key) { | ||||||
|         final int i = subkeys.indexOf(key); |         final int i = subkeys.indexOf(key); | ||||||
| 
 | 
 | ||||||
|         if (i == -1) { |         if (i == -1) { | ||||||
|             throw new NoSuchElementException("No such subkey"); |             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) { |     public void setFirstValidSubKey(final MasterVerifyKey key, final OPRETTransaction t1, final OPRETTransaction t2) { | ||||||
|         if (!subkeys.isEmpty()) { |         if (!subkeys.isEmpty()) { | ||||||
|             throw new IndexOutOfBoundsException("Subkey list is not empty"); |             throw new IndexOutOfBoundsException("Subkey list is not empty"); | ||||||
|         } |         } | ||||||
| 
 |  | ||||||
|         subkeys.addLast(key); |         subkeys.addLast(key); | ||||||
|  |         key.setMasterkey(this); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public void setNextValidSubKey(final MasterVerifyKey after, final MasterVerifyKey key, OPRETTransaction t1, |     public void setNextValidSubKey(final MasterVerifyKey after, final MasterVerifyKey key, OPRETTransaction t1, | ||||||
|             OPRETTransaction t2) { |             OPRETTransaction t2) { | ||||||
|         final MasterVerifyKey l = subkeys.getLast(); | 
 | ||||||
|         if (!l.equals(after)) { |         if (subkeys.contains(key)) { | ||||||
|             throw new NoSuchElementException("No such after key, or not last in list"); |             throw new NoSuchElementException("Already in"); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         subkeys.addLast(key); |         final int i = subkeys.indexOf(after); | ||||||
|  | 
 | ||||||
|  |         if (i == -1) { | ||||||
|  |             throw new NoSuchElementException("No such subkey"); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         subkeys.add(i + 1, key); | ||||||
|  |         key.setMasterkey(this); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -102,6 +102,9 @@ public class VerifyKey implements Comparable<VerifyKey> { | ||||||
|         if (this.revoked != true) { |         if (this.revoked != true) { | ||||||
|             this.revoked = revoked; |             this.revoked = revoked; | ||||||
|         } |         } | ||||||
|  |         if (this.masterkey != null) { | ||||||
|  |             this.masterkey.revokeSubKey(this); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public byte[] toBytes() { |     public byte[] toBytes() { | ||||||
|  | @ -114,7 +117,7 @@ public class VerifyKey implements Comparable<VerifyKey> { | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public String toString() { |     public String toString() { | ||||||
|         return Encoder.HEX.encode(key); |         return Encoder.HEX.encode(key) + (revoked ? " - Revoked" : ""); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public boolean verify(final byte[] message, final byte[] signature) { |     public boolean verify(final byte[] message, final byte[] signature) { | ||||||
|  |  | ||||||
|  | @ -9,9 +9,11 @@ import java.math.BigInteger; | ||||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||||
| import java.util.Arrays; | import java.util.Arrays; | ||||||
| import java.util.Collections; | import java.util.Collections; | ||||||
|  | import java.util.Date; | ||||||
| import java.util.HashMap; | import java.util.HashMap; | ||||||
| import java.util.List; | import java.util.List; | ||||||
| import java.util.Map; | import java.util.Map; | ||||||
|  | import java.util.NoSuchElementException; | ||||||
| import java.util.concurrent.CopyOnWriteArrayList; | import java.util.concurrent.CopyOnWriteArrayList; | ||||||
| 
 | 
 | ||||||
| import org.bitcoinj.core.PartialMerkleTree; | import org.bitcoinj.core.PartialMerkleTree; | ||||||
|  | @ -39,9 +41,10 @@ import com.google.common.primitives.Bytes; | ||||||
| 
 | 
 | ||||||
| public class OPRETECParser extends OPRETBaseHandler { | public class OPRETECParser extends OPRETBaseHandler { | ||||||
|     private static final Logger logger = LoggerFactory.getLogger(OPRETECParser.class); |     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_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_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_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_ECA2 = Bytes.asList(Utils.HEX.decode("eca2")); | ||||||
|  | @ -59,7 +62,7 @@ public class OPRETECParser extends OPRETBaseHandler { | ||||||
|         logger.debug("Using VerifyKey {}", k); |         logger.debug("Using VerifyKey {}", k); | ||||||
| 
 | 
 | ||||||
|         if (k.verify(revokemsg, sig)) { |         if (k.verify(revokemsg, sig)) { | ||||||
|             logger.debug("REVOKED VerifyKey {}", k); |             logger.debug("REVOKED Key {}", k.toString()); | ||||||
|             return true; |             return true; | ||||||
|         } else { |         } else { | ||||||
|             logger.debug("SIGNATURE does not match!"); |             logger.debug("SIGNATURE does not match!"); | ||||||
|  | @ -67,6 +70,60 @@ public class OPRETECParser extends OPRETBaseHandler { | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public static Script[] getAnnounceFirstScript(final MasterSigningKey msk, final MasterVerifyKey subkey) { | ||||||
|  |         logger.debug("getAnnounceFirstScript"); | ||||||
|  |         final MasterVerifyKey mvk = msk.getMasterVerifyKey(); | ||||||
|  |         final byte[] sig = msk.sign(subkey.toBytes()); | ||||||
|  | 
 | ||||||
|  |         logger.debug("using key {}", Encoder.HEX.encode(mvk.toBytes())); | ||||||
|  |         final byte[] noncebytes = Util.zeros(32); | ||||||
|  |         final byte[] sharedkey = HASH.sha256(HASH.sha256(Bytes.concat(mvk.toBytes(), noncebytes))); | ||||||
|  |         final byte[] xornonce = Arrays.copyOfRange(HASH.sha256(Bytes.concat(sharedkey, noncebytes)), 0, 24); | ||||||
|  |         logger.debug("xornonce {}", Encoder.HEX.encode(xornonce)); | ||||||
|  |         logger.debug("sharedkey {}", Encoder.HEX.encode(sharedkey)); | ||||||
|  | 
 | ||||||
|  |         final byte[] cipher = Util.zeros(96); | ||||||
|  |         final byte[] msg = Bytes.concat(subkey.toBytes(), sig); | ||||||
|  | 
 | ||||||
|  |         sodium(); | ||||||
|  |         Sodium.crypto_stream_xsalsa20_xor(cipher, msg, 96, xornonce, sharedkey); | ||||||
|  | 
 | ||||||
|  |         final Script script1 = new ScriptBuilder().op(OP_RETURN).data(Utils.HEX.decode("eca1")) | ||||||
|  |                 .data(Arrays.copyOfRange(cipher, 0, 48)).data(mvk.getShortHash()).build(); | ||||||
|  |         final Script script2 = new ScriptBuilder().op(OP_RETURN).data(Utils.HEX.decode("eca2")) | ||||||
|  |                 .data(Arrays.copyOfRange(cipher, 48, 96)).data(mvk.getShortHash()).build(); | ||||||
|  |         final Script[] scripts = { script1, script2 }; | ||||||
|  |         return scripts; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static Script[] getAnnounceNextScript(final MasterSigningKey msk, final MasterVerifyKey prev, | ||||||
|  |             final MasterVerifyKey next) { | ||||||
|  |         logger.debug("getAnnounceNextScript"); | ||||||
|  | 
 | ||||||
|  |         final MasterVerifyKey mvk = msk.getMasterVerifyKey(); | ||||||
|  | 
 | ||||||
|  |         final byte[] sig = msk.sign(next.toBytes()); | ||||||
|  | 
 | ||||||
|  |         logger.debug("using key {}", Encoder.HEX.encode(prev.toBytes())); | ||||||
|  |         final byte[] sharedkey = HASH.sha256(HASH.sha256(prev.toBytes())); | ||||||
|  |         final byte[] xornonce = Arrays.copyOfRange(HASH.sha256(sharedkey), 0, 24); | ||||||
|  |         logger.debug("xornonce {}", Encoder.HEX.encode(xornonce)); | ||||||
|  |         logger.debug("sharedkey {}", Encoder.HEX.encode(sharedkey)); | ||||||
|  | 
 | ||||||
|  |         final byte[] cipher = Util.zeros(96); | ||||||
|  |         final byte[] msg = Bytes.concat(next.toBytes(), sig); | ||||||
|  | 
 | ||||||
|  |         sodium(); | ||||||
|  |         Sodium.crypto_stream_xsalsa20_xor(cipher, msg, 96, xornonce, sharedkey); | ||||||
|  | 
 | ||||||
|  |         final Script script1 = new ScriptBuilder().op(OP_RETURN).data(Utils.HEX.decode("eca3")) | ||||||
|  |                 .data(Arrays.copyOfRange(cipher, 0, 48)).data(mvk.getShortHash()).data(prev.getShortHash()).build(); | ||||||
|  |         final Script script2 = new ScriptBuilder().op(OP_RETURN).data(Utils.HEX.decode("eca4")) | ||||||
|  |                 .data(Arrays.copyOfRange(cipher, 48, 96)).data(mvk.getShortHash()).data(prev.getShortHash()).build(); | ||||||
|  |         final Script[] scripts = { script1, script2 }; | ||||||
|  |         return scripts; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public static Script getRevokeScript(final SigningKey key) { |     public static Script getRevokeScript(final SigningKey key) { | ||||||
|         final byte[] revokemsg = Bytes.concat("Revoke ".getBytes(), key.getVerifyKey().toHash()); |         final byte[] revokemsg = Bytes.concat("Revoke ".getBytes(), key.getVerifyKey().toHash()); | ||||||
|         final byte[] sig = key.sign(revokemsg); |         final byte[] sig = key.sign(revokemsg); | ||||||
|  | @ -76,9 +133,12 @@ public class OPRETECParser extends OPRETBaseHandler { | ||||||
|         return script; |         return script; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     private boolean needscan; | ||||||
|  | 
 | ||||||
|     protected final Map<Sha256Hash, PartialMerkleTree> merkleHashMap = Collections.synchronizedMap(new HashMap<>()); |     protected final Map<Sha256Hash, PartialMerkleTree> merkleHashMap = Collections.synchronizedMap(new HashMap<>()); | ||||||
| 
 | 
 | ||||||
|     protected final Map<Sha256Hash, OPRETTransaction> transHashMap = Collections.synchronizedMap(new HashMap<>()); |     protected final Map<Sha256Hash, OPRETTransaction> transHashMap = Collections.synchronizedMap(new HashMap<>()); | ||||||
|  | 
 | ||||||
|     protected final Map<List<Byte>, List<OPRETTransaction>> transA1HashMap = Collections |     protected final Map<List<Byte>, List<OPRETTransaction>> transA1HashMap = Collections | ||||||
|             .synchronizedMap(new HashMap<>()); |             .synchronizedMap(new HashMap<>()); | ||||||
|     protected final Map<List<Byte>, List<OPRETTransaction>> transA2HashMap = Collections |     protected final Map<List<Byte>, List<OPRETTransaction>> transA2HashMap = Collections | ||||||
|  | @ -91,11 +151,15 @@ public class OPRETECParser extends OPRETBaseHandler { | ||||||
|             .synchronizedMap(new HashMap<>()); |             .synchronizedMap(new HashMap<>()); | ||||||
|     protected final Map<List<Byte>, List<OPRETTransaction>> trans52HashMap = Collections |     protected final Map<List<Byte>, List<OPRETTransaction>> trans52HashMap = Collections | ||||||
|             .synchronizedMap(new HashMap<>()); |             .synchronizedMap(new HashMap<>()); | ||||||
| 
 |  | ||||||
|     protected final Map<List<Byte>, List<MasterVerifyKey>> verifyKeys = Collections.synchronizedMap(new HashMap<>()); |     protected final Map<List<Byte>, List<MasterVerifyKey>> verifyKeys = Collections.synchronizedMap(new HashMap<>()); | ||||||
| 
 | 
 | ||||||
|     private final CopyOnWriteArrayList<ListenerRegistration<OPRETECEventListener>> opReturnChangeListeners = new CopyOnWriteArrayList<>(); |     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 |      * Adds an event listener object. Methods on this object are called when | ||||||
|      * scripts watched by this wallet change. The listener is executed by the |      * scripts watched by this wallet change. The listener is executed by the | ||||||
|  | @ -113,8 +177,9 @@ public class OPRETECParser extends OPRETBaseHandler { | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         verifyKeys.get(hash).add(key); |         verifyKeys.get(hash).add(key); | ||||||
|         logger.debug("Adding pkhash {}", key.getShortHash()); |         logger.debug("Adding pkhash {}", Utils.HEX.encode(key.getShortHash())); | ||||||
|         addOPRET(key.getShortHash(), earliestTime); |         addOPRET(key.getShortHash(), earliestTime); | ||||||
|  |         needscan = true; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public boolean cryptoSelfTest() { |     public boolean cryptoSelfTest() { | ||||||
|  | @ -224,8 +289,19 @@ public class OPRETECParser extends OPRETBaseHandler { | ||||||
|                     } |                     } | ||||||
| 
 | 
 | ||||||
|                     logger.debug("sig matches"); |                     logger.debug("sig matches"); | ||||||
| 
 |                     final MasterVerifyKey subkey = new MasterVerifyKey(vk); | ||||||
|                     k.setFirstValidSubKey(new MasterVerifyKey(vk), isT1 ? selfTx : otherTx, isT1 ? otherTx : selfTx); |                     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); |                     otherTransHashMap.get(pkhash).remove(otherTx); | ||||||
|                     if (otherTransHashMap.get(pkhash).isEmpty()) { |                     if (otherTransHashMap.get(pkhash).isEmpty()) { | ||||||
|                         otherTransHashMap.remove(pkhash); |                         otherTransHashMap.remove(pkhash); | ||||||
|  | @ -244,6 +320,99 @@ public class OPRETECParser extends OPRETBaseHandler { | ||||||
|         return false; |         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) { |     private boolean handleEC0F(final OPRETTransaction t) { | ||||||
|         final byte[] sig = Bytes.toArray(t.opretData.get(1)); |         final byte[] sig = Bytes.toArray(t.opretData.get(1)); | ||||||
|         if ((sig.length != 64)) { |         if ((sig.length != 64)) { | ||||||
|  | @ -264,11 +433,11 @@ public class OPRETECParser extends OPRETBaseHandler { | ||||||
|         for (final MasterVerifyKey k : verifyKeys.get(pkhash)) { |         for (final MasterVerifyKey k : verifyKeys.get(pkhash)) { | ||||||
|             if (checkKeyforRevoke(k, sig)) { |             if (checkKeyforRevoke(k, sig)) { | ||||||
|                 if (k.isRevoked()) { |                 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)); |                             Utils.HEX.encode(sig)); | ||||||
|                 } else { |                 } else { | ||||||
|                     k.setRevoked(true); |                     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); |                 queueOnOPRETRevoke(k); | ||||||
|                 return true; |                 return true; | ||||||
|  | @ -284,6 +453,7 @@ public class OPRETECParser extends OPRETBaseHandler { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private boolean handleEC1D(final OPRETTransaction t) { |     private boolean handleEC1D(final OPRETTransaction t) { | ||||||
|  |         // TODO Auto-generated method stub | ||||||
|         final byte[] sig = Bytes.toArray(t.opretData.get(1)); |         final byte[] sig = Bytes.toArray(t.opretData.get(1)); | ||||||
|         if ((sig.length != 64)) { |         if ((sig.length != 64)) { | ||||||
|             logger.debug("chunk 1 size != 64, but {}", sig.length); |             logger.debug("chunk 1 size != 64, but {}", sig.length); | ||||||
|  | @ -300,20 +470,18 @@ public class OPRETECParser extends OPRETBaseHandler { | ||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         for (final MasterVerifyKey k : verifyKeys.get(pkhash)) { |         // FIXME | ||||||
|             if (checkKeyforRevoke(k, sig)) { |         /* | ||||||
|                 if (k.isRevoked()) { |          * for (final MasterVerifyKey k : verifyKeys.get(pkhash)) { if | ||||||
|                     logger.debug("Duplicate REVOKE PK {} - SIG {}", Utils.HEX.encode(k.getShortHash()), |          * (checkKeyforRevoke(k, sig)) { if (k.isRevoked()) { | ||||||
|                             Utils.HEX.encode(sig)); |          * logger.debug("Duplicate REVOKE PK {} - SIG {}", | ||||||
|                 } else { |          * Utils.HEX.encode(k.getShortHash()), Utils.HEX.encode(sig)); } else { | ||||||
|                     k.setRevoked(true); |          * k.setRevoked(true); logger.debug("REVOKE PK {} - SIG {}", | ||||||
|                     logger.debug("REVOKE PK {} - SIG {}", Utils.HEX.encode(k.getShortHash()), Utils.HEX.encode(sig)); |          * Utils.HEX.encode(k.getShortHash()), Utils.HEX.encode(sig)); } | ||||||
|                 } |          * queueOnOPRETId(k); return true; | ||||||
|                 queueOnOPRETId(k); |          * | ||||||
|                 return true; |          * } } | ||||||
| 
 |          */ | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -339,93 +507,19 @@ public class OPRETECParser extends OPRETBaseHandler { | ||||||
| 
 | 
 | ||||||
|     private boolean handleECA3(final OPRETTransaction t1) { |     private boolean handleECA3(final OPRETTransaction t1) { | ||||||
|         logger.debug("handleECA3"); |         logger.debug("handleECA3"); | ||||||
|         return handleNextKey(t1, transA3HashMap, transA4HashMap, true); |         return handleAnnounceNext(t1, transA3HashMap, transA4HashMap, true); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private boolean handleECA4(final OPRETTransaction t2) { |     private boolean handleECA4(final OPRETTransaction t2) { | ||||||
|         logger.debug("handleECA4"); |         logger.debug("handleECA4"); | ||||||
|         return handleNextKey(t2, transA4HashMap, transA3HashMap, false); |         return handleAnnounceNext(t2, transA4HashMap, transA3HashMap, false); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private boolean handleNextKey(final OPRETTransaction selfTx, |     public boolean needScan() { | ||||||
|             final Map<List<Byte>, List<OPRETTransaction>> selfTransHashMap, |         // TODO Auto-generated method stub | ||||||
|             final Map<List<Byte>, List<OPRETTransaction>> otherTransHashMap, final boolean isT1) { |         return this.needscan; | ||||||
| 
 |  | ||||||
|         logger.debug("handleNextKey"); |  | ||||||
|         final byte[] selfData = Bytes.toArray(selfTx.opretData.get(1)); |  | ||||||
|         if (selfData.length != 48) { |  | ||||||
|             logger.debug("invalid chunk1 size = {}", selfData.length); |  | ||||||
|             return false; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         final List<Byte> pkhash = selfTx.opretData.get(2); |  | ||||||
|         if (pkhash.size() != 12) { |  | ||||||
|             logger.debug("chunk 2 size != 12 but {} ", pkhash.size()); |  | ||||||
|             return false; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         final List<Byte> subpkhash = selfTx.opretData.get(3); |  | ||||||
|         if (subpkhash.size() != 12) { |  | ||||||
|             logger.debug("chunk 2 size != 12 but {} ", subpkhash.size()); |  | ||||||
|             return false; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         if (!verifyKeys.containsKey(pkhash)) { |  | ||||||
|             return false; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         if (otherTransHashMap.containsKey(pkhash)) { |  | ||||||
|             for (final OPRETTransaction otherTx : otherTransHashMap.get(pkhash)) { |  | ||||||
|                 final byte[] otherData = Bytes.toArray(otherTx.opretData.get(1)); |  | ||||||
|                 final byte[] cipher = isT1 ? Bytes.concat(selfData, otherData) : Bytes.concat(otherData, selfData); |  | ||||||
| 
 |  | ||||||
|                 for (final MasterVerifyKey k : verifyKeys.get(pkhash)) { |  | ||||||
|                     final MasterVerifyKey vk_n = k.getSubKeybyHash(subpkhash); |  | ||||||
|                     if (vk_n == null) { |  | ||||||
|                         continue; |  | ||||||
|                     } |  | ||||||
| 
 |  | ||||||
|                     final byte[] sharedkey = HASH.sha256(vk_n.toHash()); |  | ||||||
|                     final byte[] xornonce = Arrays.copyOfRange(HASH.sha256(sharedkey), 0, 24); |  | ||||||
|                     logger.debug("checking key {}", Encoder.HEX.encode(k.toBytes())); |  | ||||||
|                     logger.debug("checking subkey {}", Encoder.HEX.encode(vk_n.toBytes())); |  | ||||||
|                     logger.debug("xornonce {}", Encoder.HEX.encode(xornonce)); |  | ||||||
|                     logger.debug("sharedkey {}", Encoder.HEX.encode(sharedkey)); |  | ||||||
|                     sodium(); |  | ||||||
|                     final byte[] msg = Util.zeros(96); |  | ||||||
|                     Sodium.crypto_stream_xsalsa20_xor(msg, cipher, 96, xornonce, sharedkey); |  | ||||||
|                     final byte[] vk = Arrays.copyOfRange(msg, 0, 32); |  | ||||||
|                     final byte[] sig = Arrays.copyOfRange(msg, 32, 96); |  | ||||||
|                     logger.debug("Checking sig {} with key {}", Encoder.HEX.encode(sig), Encoder.HEX.encode(vk)); |  | ||||||
| 
 |  | ||||||
|                     if (!k.verify(vk, sig)) { |  | ||||||
|                         logger.debug("sig does not match"); |  | ||||||
|                         continue; |  | ||||||
|                     } |  | ||||||
| 
 |  | ||||||
|                     logger.debug("sig matches"); |  | ||||||
| 
 |  | ||||||
|                     k.setNextValidSubKey(vk_n, new MasterVerifyKey(vk), isT1 ? selfTx : otherTx, |  | ||||||
|                             isT1 ? otherTx : selfTx); |  | ||||||
|                     otherTransHashMap.get(pkhash).remove(otherTx); |  | ||||||
|                     if (otherTransHashMap.get(pkhash).isEmpty()) { |  | ||||||
|                         otherTransHashMap.remove(pkhash); |  | ||||||
|                     } |  | ||||||
|                     return true; |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         // no matching transaction found, save for later |  | ||||||
|         if (!selfTransHashMap.containsKey(pkhash)) { |  | ||||||
|             selfTransHashMap.put(pkhash, new ArrayList<OPRETTransaction>()); |  | ||||||
|         } |  | ||||||
|         selfTransHashMap.get(pkhash).add(selfTx); |  | ||||||
| 
 |  | ||||||
|         return false; |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |  | ||||||
|     public boolean pushTransaction(final OPRETTransaction t) { |     public boolean pushTransaction(final OPRETTransaction t) { | ||||||
|         logger.debug("checking {}", t.opretData); |         logger.debug("checking {}", t.opretData); | ||||||
| 
 | 
 | ||||||
|  | @ -475,6 +569,20 @@ public class OPRETECParser extends OPRETBaseHandler { | ||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     @Override | ||||||
|  |     public void pushTransactions(List<OPRETTransaction> pushlist) { | ||||||
|  |         Collections.sort(pushlist, (a, b) -> { | ||||||
|  |             final List<Byte> chunka = a.opretData.get(0); | ||||||
|  |             final List<Byte> chunkb = b.opretData.get(0); | ||||||
|  |             final Long la = ((long) chunka.get(1) * 256) + (long) chunka.get(0); | ||||||
|  |             final Long lb = ((long) chunkb.get(1) * 256) + (long) chunkb.get(0); | ||||||
|  |             return la.compareTo(lb); | ||||||
|  |         }); | ||||||
|  |         for (final OPRETTransaction t : pushlist) { | ||||||
|  |             pushTransaction(t); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     private void queueOnOPRETId(final MasterVerifyKey k) { |     private void queueOnOPRETId(final MasterVerifyKey k) { | ||||||
|         // TODO Auto-generated method stub |         // TODO Auto-generated method stub | ||||||
| 
 | 
 | ||||||
|  | @ -505,4 +613,9 @@ public class OPRETECParser extends OPRETBaseHandler { | ||||||
| 
 | 
 | ||||||
|         removeOPRET(key.getShortHash()); |         removeOPRET(key.getShortHash()); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     public void willScan() { | ||||||
|  |         // TODO Auto-generated method stub | ||||||
|  |         this.needscan = false; | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -7,12 +7,14 @@ import java.io.InputStream; | ||||||
| import java.io.InputStreamReader; | import java.io.InputStreamReader; | ||||||
| import java.io.PrintWriter; | import java.io.PrintWriter; | ||||||
| import java.util.Arrays; | import java.util.Arrays; | ||||||
|  | import java.util.LinkedList; | ||||||
| 
 | 
 | ||||||
| import org.bitcoinj.core.Address; | import org.bitcoinj.core.Address; | ||||||
| import org.bitcoinj.core.AddressFormatException; | import org.bitcoinj.core.AddressFormatException; | ||||||
| import org.bitcoinj.core.Coin; | import org.bitcoinj.core.Coin; | ||||||
| import org.bitcoinj.core.InsufficientMoneyException; | import org.bitcoinj.core.InsufficientMoneyException; | ||||||
| import org.bitcoinj.core.NetworkParameters; | import org.bitcoinj.core.NetworkParameters; | ||||||
|  | import org.bitcoinj.core.Sha256Hash; | ||||||
| import org.bitcoinj.core.Transaction; | import org.bitcoinj.core.Transaction; | ||||||
| import org.bitcoinj.core.Utils; | import org.bitcoinj.core.Utils; | ||||||
| import org.bitcoinj.script.Script; | import org.bitcoinj.script.Script; | ||||||
|  | @ -22,6 +24,7 @@ import org.libsodium.jni.crypto.Hash; | ||||||
| import org.slf4j.Logger; | import org.slf4j.Logger; | ||||||
| import org.slf4j.LoggerFactory; | import org.slf4j.LoggerFactory; | ||||||
| import org.tcpid.key.MasterSigningKey; | import org.tcpid.key.MasterSigningKey; | ||||||
|  | import org.tcpid.key.MasterVerifyKey; | ||||||
| import org.tcpid.opret.OPRETECParser; | import org.tcpid.opret.OPRETECParser; | ||||||
| import org.tcpid.opretj.OPRETWallet; | import org.tcpid.opretj.OPRETWallet; | ||||||
| import org.tcpid.opretj.OPRETWalletAppKit; | import org.tcpid.opretj.OPRETWalletAppKit; | ||||||
|  | @ -40,8 +43,18 @@ public class App { | ||||||
| 
 | 
 | ||||||
|     private final static Logger logger = LoggerFactory.getLogger(App.class); |     private final static Logger logger = LoggerFactory.getLogger(App.class); | ||||||
|     private final static MasterSigningKey SK = new MasterSigningKey(HASH.sha256("TESTSEED".getBytes())); |     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) { |     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.write("Balance: " + kit.wallet().getBalance().toFriendlyString() + "\n"); | ||||||
|         out.flush(); |         out.flush(); | ||||||
|     } |     } | ||||||
|  | @ -51,10 +64,17 @@ public class App { | ||||||
|         out.write("\n"); |         out.write("\n"); | ||||||
|         out.write("help            - this screen\n"); |         out.write("help            - this screen\n"); | ||||||
|         out.write("quit            - exit the application\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("balance         - show your available balance\n"); | ||||||
|         out.write("receive         - display an address to receive coins\n"); |         out.write("receive         - display an address to receive coins\n"); | ||||||
|         out.write("empty <address> - send all coins to the address\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.write("\n"); | ||||||
| 
 | 
 | ||||||
|         out.flush(); |         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 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)); |         reader.addCompleter(new StringsCompleter(cmds)); | ||||||
|         final PrintWriter out = new PrintWriter(reader.getOutput()); |         final PrintWriter out = new PrintWriter(reader.getOutput()); | ||||||
|         reader.setPrompt("opret> "); |         reader.setPrompt("opret> "); | ||||||
|         String line; |         String line; | ||||||
|         displayHelp(out); |         displayHelp(out); | ||||||
|         displayBalance(kit, out); |         displayBalance(KIT, out); | ||||||
| 
 | 
 | ||||||
|         while ((line = reader.readLine()) != null) { |         while ((line = reader.readLine()) != null) { | ||||||
|             String[] argv = line.split("\\s"); |             String[] argv = line.split("\\s"); | ||||||
|  | @ -108,15 +129,16 @@ public class App { | ||||||
| 
 | 
 | ||||||
|             switch (cmd.toLowerCase()) { |             switch (cmd.toLowerCase()) { | ||||||
|             case "quit": |             case "quit": | ||||||
|  |             case "exit": | ||||||
|                 return; |                 return; | ||||||
|             case "help": |             case "help": | ||||||
|                 displayHelp(out); |                 displayHelp(out); | ||||||
|                 break; |                 break; | ||||||
|             case "balance": |             case "balance": | ||||||
|                 displayBalance(kit, out); |                 displayBalance(KIT, out); | ||||||
|                 break; |                 break; | ||||||
|             case "receive": |             case "receive": | ||||||
|                 final String receiveStr = kit.wallet().freshReceiveAddress().toString(); |                 final String receiveStr = KIT.wallet().freshReceiveAddress().toString(); | ||||||
| 
 | 
 | ||||||
|                 out.write("send money to: " + receiveStr + "\n"); |                 out.write("send money to: " + receiveStr + "\n"); | ||||||
|                 try { |                 try { | ||||||
|  | @ -134,9 +156,9 @@ public class App { | ||||||
|                 out.println("'" + argv[0] + "'"); |                 out.println("'" + argv[0] + "'"); | ||||||
|                 out.flush(); |                 out.flush(); | ||||||
|                 try { |                 try { | ||||||
|                     final SendRequest request = SendRequest.emptyWallet(Address.fromBase58(kit.params(), argv[0])); |                     final SendRequest request = SendRequest.emptyWallet(Address.fromBase58(KIT.params(), argv[0])); | ||||||
|                     try { |                     try { | ||||||
|                         kit.wallet().sendCoins(request); |                         KIT.wallet().sendCoins(request); | ||||||
|                     } catch (final InsufficientMoneyException e) { |                     } catch (final InsufficientMoneyException e) { | ||||||
|                         out.println(e.getLocalizedMessage()); |                         out.println(e.getLocalizedMessage()); | ||||||
|                         out.flush(); |                         out.flush(); | ||||||
|  | @ -146,37 +168,131 @@ public class App { | ||||||
|                     out.flush(); |                     out.flush(); | ||||||
|                 } |                 } | ||||||
|                 break; |                 break; | ||||||
|             case "opret": |             case "newkey": { | ||||||
|                 sendOPReturn(kit, out); |                 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; |                 break; | ||||||
|             default: |             default: | ||||||
|                 out.println("Unknown command. Use 'help' to display available commands."); |                 out.println("Unknown command. Use 'help' to display available commands."); | ||||||
|                 break; |                 break; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |         return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public static void main(final String[] args) throws Exception { |     public static void main(final String[] args) throws Exception { | ||||||
| 
 | 
 | ||||||
|         final OptionParser parser = new OptionParser(); |         final OptionParser optparser = new OptionParser(); | ||||||
|         final OptionSpec<NetworkEnum> net = parser.accepts("net", "The network to run the examples on") |         final OptionSpec<NetworkEnum> net = optparser.accepts("net", "The network to run on").withRequiredArg() | ||||||
|                 .withRequiredArg().ofType(NetworkEnum.class).defaultsTo(NetworkEnum.TEST); |                 .ofType(NetworkEnum.class).defaultsTo(NetworkEnum.TEST); | ||||||
|         parser.accepts("help", "Displays program options"); | 
 | ||||||
|         final OptionSet opts = parser.parse(args); |         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")) { |         if (opts.has("help")) { | ||||||
|             System.err.println("usage: App --net=MAIN/TEST/REGTEST"); |             System.err.println("usage: App [--net=MAIN/TEST/REGTEST] [--name=<name>]"); | ||||||
|             parser.printHelpOn(System.err); |             optparser.printHelpOn(System.err); | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|         if (!opts.has(net)) { |         if (!opts.has(net)) { | ||||||
|             System.err.println("No net specified, using TestNet!"); |             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 = PARSER.cryptoSelfTest(); | ||||||
| 
 |  | ||||||
|         final boolean chk = bs.cryptoSelfTest(); |  | ||||||
|         if (chk) { |         if (chk) { | ||||||
|             System.err.println("Crypto self test: PASSED"); |             System.err.println("Crypto self test: PASSED"); | ||||||
|         } else { |         } else { | ||||||
|  | @ -184,55 +300,84 @@ public class App { | ||||||
|             System.exit(-1); |             System.exit(-1); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         bs.addOPRETECRevokeEventListener((key) -> { |         PARSER.addOPRETECRevokeEventListener((key) -> { | ||||||
|             System.out.println("Revoked Key: " + Utils.HEX.encode(key.toBytes())); |             System.out.println("Revoked Key: " + Utils.HEX.encode(key.toBytes())); | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         long earliestTime; |         if (PARAMS.getId().equals(NetworkParameters.ID_REGTEST)) { | ||||||
|         if (params.getId().equals(NetworkParameters.ID_REGTEST)) { |         } else if (PARAMS.getId().equals(NetworkParameters.ID_TESTNET)) { | ||||||
|             earliestTime = OPRET_BIRTHDAY; |  | ||||||
|         } else if (params.getId().equals(NetworkParameters.ID_TESTNET)) { |  | ||||||
|             earliestTime = OPRET_BIRTHDAY; |  | ||||||
|         } else { |         } 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() { |     public static void scanBlockchain() { | ||||||
|             @Override |         if (!PARSER.needScan()) { | ||||||
|             public void failed(final Service.State from, final Throwable failure) { |             System.err.println("No scan needed."); | ||||||
|                 logger.error(failure.getMessage()); |             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); |                 System.exit(-1); | ||||||
|             } |             } | ||||||
|         }, Threading.SAME_THREAD); |             // after gathering all the key, replay the blockchain | ||||||
| 
 |             if (PARSER.needScan()) { | ||||||
|         if (params.getId().equals(NetworkParameters.ID_REGTEST)) { |                 System.out.println("Rescanning the blockchain for the newly learned keys!"); | ||||||
|             kit.connectToLocalHost(); |                 KIT.rescanBlockchain(); | ||||||
|         } |                 KIT = new OPRETWalletAppKit(PARAMS, new File("."), WALLETNAME + PARAMS.getId(), PARSER); | ||||||
|         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); |  | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         final OPRETWallet wallet = kit.opretwallet(); |         KIT.opretwallet().addCoinsReceivedEventListener((wallet1, tx, prevBalance, newBalance) -> { | ||||||
| 
 |  | ||||||
|         wallet.addCoinsReceivedEventListener((wallet1, tx, prevBalance, newBalance) -> { |  | ||||||
|             final Coin c = tx.getValue(wallet1); |             final Coin c = tx.getValue(wallet1); | ||||||
| 
 | 
 | ||||||
|             if (c.isPositive()) { |             if (c.isPositive()) { | ||||||
|  | @ -267,21 +412,89 @@ public class App { | ||||||
|          * tx.getConfidence(); System.out.println("new block depth: " + |          * tx.getConfidence(); System.out.println("new block depth: " + | ||||||
|          * confidence.getDepthInBlocks()); }); |          * confidence.getDepthInBlocks()); }); | ||||||
|          */ |          */ | ||||||
|         // wallet.allowSpendingUnconfirmedTransactions(); |         KIT.opretwallet().allowSpendingUnconfirmedTransactions(); | ||||||
| 
 |  | ||||||
|         handleConsole(kit); |  | ||||||
| 
 |  | ||||||
|         System.out.println("shutting down"); |  | ||||||
|         kit.stopAsync(); |  | ||||||
|         kit.awaitTerminated(); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     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 OPRETWallet wallet = kit.opretwallet(); | ||||||
|         final NetworkParameters params = wallet.getNetworkParameters(); |         final NetworkParameters params = wallet.getNetworkParameters(); | ||||||
| 
 | 
 | ||||||
|         final Transaction t = new Transaction(params); |         final Transaction t = new Transaction(params); | ||||||
|         final Script script = OPRETECParser.getRevokeScript(SK); |         final Script script = OPRETECParser.getRevokeScript(key); | ||||||
|         t.addOutput(Coin.ZERO, script); |         t.addOutput(Coin.ZERO, script); | ||||||
|         final SendRequest request = SendRequest.forTx(t); |         final SendRequest request = SendRequest.forTx(t); | ||||||
|         request.ensureMinRequiredFee = true; |         request.ensureMinRequiredFee = true; | ||||||
|  |  | ||||||
|  | @ -8,15 +8,17 @@ | ||||||
| 			<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> | 			<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> | ||||||
| 		</encoder> | 		</encoder> | ||||||
| 	</appender> | 	</appender> | ||||||
| 	<logger name="org.tcpid.opret.App" level="debug" /> |  | ||||||
| 	<logger name="org.bitcoinj.core.listeners.DownloadProgressTracker" level="info" /> | 	<logger name="org.bitcoinj.core.listeners.DownloadProgressTracker" level="info" /> | ||||||
| 	<logger name="org.bitcoinj.core.PeerGroup" level="error" /> | 	<logger name="org.bitcoinj.core.PeerGroup" level="error" /> | ||||||
| 	<logger name="org.bitcoinj.core.AbstractBlockChain" level="error" /> | 	<logger name="org.bitcoinj.core.AbstractBlockChain" level="error" /> | ||||||
| 	<logger name="org.bitcoinj.core.PeerSocketHandler" level="error" /> | 	<logger name="org.bitcoinj.core.PeerSocketHandler" level="error" /> | ||||||
| 	<logger name="org.bitcoinj.net.ConnectionHandler" level="error" /> | 	<logger name="org.bitcoinj.net.ConnectionHandler" level="error" /> | ||||||
| 	<logger name="org.tcpid.opret.OPRETECParser" level="debug" /> | 	<logger name="org.tcpid.opretj.testapp.App" level="debug" /> | ||||||
|  | 	<logger name="org.tcpid.opret.OPRETECParser" level="info" /> | ||||||
| 	<logger name="org.tcpid.opret.TestCrypto" level="debug" /> | 	<logger name="org.tcpid.opret.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.OPRETSimpleLogger" level="debug" /> | ||||||
| 	<logger name="eckey.OPRETSimpleParser" level="debug" /> | 	<logger name="eckey.OPRETSimpleParser" level="debug" /> | ||||||
| 	<logger name="eckey.OPRETBaseHandler" level="debug" /> | 	<logger name="eckey.OPRETBaseHandler" level="debug" /> | ||||||
|  |  | ||||||
|  | @ -105,19 +105,19 @@ public class TestCrypto { | ||||||
| 
 | 
 | ||||||
|         final MasterSigningKey msk = new MasterSigningKey(HASH.sha256("TESTSEED".getBytes())); |         final MasterSigningKey msk = new MasterSigningKey(HASH.sha256("TESTSEED".getBytes())); | ||||||
|         final MasterVerifyKey mvk = msk.getMasterVerifyKey(); |         final MasterVerifyKey mvk = msk.getMasterVerifyKey(); | ||||||
|         final MasterVerifyKey subkey1 = msk.getSubKey(1L).getMasterVerifyKey(); |         final MasterVerifyKey prev = msk.getSubKey(1L).getMasterVerifyKey(); | ||||||
|         final MasterVerifyKey subkey2 = msk.getSubKey(2L).getMasterVerifyKey(); |         final MasterVerifyKey next = msk.getSubKey(2L).getMasterVerifyKey(); | ||||||
| 
 | 
 | ||||||
|         byte[] sig = msk.sign(subkey2.toBytes()); |         byte[] sig = msk.sign(next.toBytes()); | ||||||
| 
 | 
 | ||||||
|         logger.debug("using key {}", Encoder.HEX.encode(subkey1.toBytes())); |         logger.debug("using key {}", Encoder.HEX.encode(prev.toBytes())); | ||||||
|         final byte[] sharedkey = HASH.sha256(HASH.sha256(subkey1.toBytes())); |         final byte[] sharedkey = HASH.sha256(HASH.sha256(prev.toBytes())); | ||||||
|         final byte[] xornonce = Arrays.copyOfRange(HASH.sha256(sharedkey), 0, 24); |         final byte[] xornonce = Arrays.copyOfRange(HASH.sha256(sharedkey), 0, 24); | ||||||
|         logger.debug("xornonce {}", Encoder.HEX.encode(xornonce)); |         logger.debug("xornonce {}", Encoder.HEX.encode(xornonce)); | ||||||
|         logger.debug("sharedkey {}", Encoder.HEX.encode(sharedkey)); |         logger.debug("sharedkey {}", Encoder.HEX.encode(sharedkey)); | ||||||
| 
 | 
 | ||||||
|         final byte[] cipher = Util.zeros(96); |         final byte[] cipher = Util.zeros(96); | ||||||
|         byte[] msg = Bytes.concat(subkey2.toBytes(), sig); |         byte[] msg = Bytes.concat(next.toBytes(), sig); | ||||||
|         assertEquals(96, msg.length); |         assertEquals(96, msg.length); | ||||||
| 
 | 
 | ||||||
|         sodium(); |         sodium(); | ||||||
|  | @ -132,7 +132,7 @@ public class TestCrypto { | ||||||
|         sig = Arrays.copyOfRange(msg, 32, 96); |         sig = Arrays.copyOfRange(msg, 32, 96); | ||||||
|         logger.debug("vkb : {}", Encoder.HEX.encode(vkb)); |         logger.debug("vkb : {}", Encoder.HEX.encode(vkb)); | ||||||
|         assertTrue("Verification of signature failed.", mvk.verify(vkb, sig)); |         assertTrue("Verification of signature failed.", mvk.verify(vkb, sig)); | ||||||
|         assertArrayEquals(subkey2.toBytes(), vkb); |         assertArrayEquals(next.toBytes(), vkb); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Test |     @Test | ||||||
|  |  | ||||||
|  | @ -63,7 +63,7 @@ public class TestPushTransaction { | ||||||
|         opret_data.add(Bytes.asList(vkbsha96)); |         opret_data.add(Bytes.asList(vkbsha96)); | ||||||
|         final OPRETTransaction t2 = new OPRETTransaction(Sha256Hash.of(nullbyte), Sha256Hash.of(nullbyte), opret_data); |         final OPRETTransaction t2 = new OPRETTransaction(Sha256Hash.of(nullbyte), Sha256Hash.of(nullbyte), opret_data); | ||||||
| 
 | 
 | ||||||
|         // create t3 and t4 to test if the parser handles garbage |         // create t3 and t4 to test if the PARSER handles garbage | ||||||
|         opret_data = new ArrayList<>(); |         opret_data = new ArrayList<>(); | ||||||
|         opret_data.add(Bytes.asList(Encoder.HEX.decode("eca2"))); |         opret_data.add(Bytes.asList(Encoder.HEX.decode("eca2"))); | ||||||
|         opret_data.add(Bytes.asList(Arrays.copyOfRange(cipher, 0, 48))); |         opret_data.add(Bytes.asList(Arrays.copyOfRange(cipher, 0, 48))); | ||||||
|  | @ -214,7 +214,7 @@ public class TestPushTransaction { | ||||||
|         opret_data.add(Bytes.asList(firstsub.getShortHash())); |         opret_data.add(Bytes.asList(firstsub.getShortHash())); | ||||||
|         final OPRETTransaction t2 = new OPRETTransaction(Sha256Hash.of(nullbyte), Sha256Hash.of(nullbyte), opret_data); |         final OPRETTransaction t2 = new OPRETTransaction(Sha256Hash.of(nullbyte), Sha256Hash.of(nullbyte), opret_data); | ||||||
| 
 | 
 | ||||||
|         // create t3 and t4 to test if the parser handles garbage |         // create t3 and t4 to test if the PARSER handles garbage | ||||||
|         opret_data = new ArrayList<>(); |         opret_data = new ArrayList<>(); | ||||||
|         opret_data.add(Bytes.asList(Encoder.HEX.decode("eca4"))); |         opret_data.add(Bytes.asList(Encoder.HEX.decode("eca4"))); | ||||||
|         opret_data.add(Bytes.asList(Arrays.copyOfRange(cipher, 0, 48))); |         opret_data.add(Bytes.asList(Arrays.copyOfRange(cipher, 0, 48))); | ||||||
|  | @ -239,8 +239,9 @@ public class TestPushTransaction { | ||||||
|         assertFalse(parser.pushTransaction(t4)); |         assertFalse(parser.pushTransaction(t4)); | ||||||
|         assertTrue(parser.pushTransaction(t1)); |         assertTrue(parser.pushTransaction(t1)); | ||||||
| 
 | 
 | ||||||
|         mvk.revokeSubKey(firstsub); |         firstsub.setRevoked(true); | ||||||
|         MasterVerifyKey subkey = mvk.getValidSubKey(); |         MasterVerifyKey subkey = mvk.getValidSubKey(); | ||||||
|  |         logger.debug("FirstValid: {}", subkey.toString()); | ||||||
|         assertArrayEquals(subkey.toBytes(), |         assertArrayEquals(subkey.toBytes(), | ||||||
|                 Encoder.HEX.decode("11e14458b16050a23a772e469ee424f513c3eb81682c0f9f81f07e607c6bf917")); |                 Encoder.HEX.decode("11e14458b16050a23a772e469ee424f513c3eb81682c0f9f81f07e607c6bf917")); | ||||||
| 
 | 
 | ||||||
|  | @ -252,7 +253,7 @@ public class TestPushTransaction { | ||||||
|         assertFalse(parser.pushTransaction(t4)); |         assertFalse(parser.pushTransaction(t4)); | ||||||
|         assertTrue(parser.pushTransaction(t2)); |         assertTrue(parser.pushTransaction(t2)); | ||||||
| 
 | 
 | ||||||
|         mvk.revokeSubKey(firstsub); |         firstsub.setRevoked(true); | ||||||
|         subkey = mvk.getValidSubKey(); |         subkey = mvk.getValidSubKey(); | ||||||
|         assertArrayEquals(subkey.toBytes(), |         assertArrayEquals(subkey.toBytes(), | ||||||
|                 Encoder.HEX.decode("11e14458b16050a23a772e469ee424f513c3eb81682c0f9f81f07e607c6bf917")); |                 Encoder.HEX.decode("11e14458b16050a23a772e469ee424f513c3eb81682c0f9f81f07e607c6bf917")); | ||||||
|  | @ -265,7 +266,7 @@ public class TestPushTransaction { | ||||||
|         assertFalse(parser.pushTransaction(t3)); |         assertFalse(parser.pushTransaction(t3)); | ||||||
|         assertTrue(parser.pushTransaction(t2)); |         assertTrue(parser.pushTransaction(t2)); | ||||||
| 
 | 
 | ||||||
|         mvk.revokeSubKey(firstsub); |         firstsub.setRevoked(true); | ||||||
|         subkey = mvk.getValidSubKey(); |         subkey = mvk.getValidSubKey(); | ||||||
|         assertArrayEquals(subkey.toBytes(), |         assertArrayEquals(subkey.toBytes(), | ||||||
|                 Encoder.HEX.decode("11e14458b16050a23a772e469ee424f513c3eb81682c0f9f81f07e607c6bf917")); |                 Encoder.HEX.decode("11e14458b16050a23a772e469ee424f513c3eb81682c0f9f81f07e607c6bf917")); | ||||||
|  | @ -278,7 +279,7 @@ public class TestPushTransaction { | ||||||
|         assertFalse(parser.pushTransaction(t3)); |         assertFalse(parser.pushTransaction(t3)); | ||||||
|         assertTrue(parser.pushTransaction(t1)); |         assertTrue(parser.pushTransaction(t1)); | ||||||
| 
 | 
 | ||||||
|         mvk.revokeSubKey(firstsub); |         firstsub.setRevoked(true); | ||||||
|         subkey = mvk.getValidSubKey(); |         subkey = mvk.getValidSubKey(); | ||||||
|         assertArrayEquals(subkey.toBytes(), |         assertArrayEquals(subkey.toBytes(), | ||||||
|                 Encoder.HEX.decode("11e14458b16050a23a772e469ee424f513c3eb81682c0f9f81f07e607c6bf917")); |                 Encoder.HEX.decode("11e14458b16050a23a772e469ee424f513c3eb81682c0f9f81f07e607c6bf917")); | ||||||
|  |  | ||||||
|  | @ -14,8 +14,8 @@ public interface OPRETHandlerInterface { | ||||||
| 
 | 
 | ||||||
|     Set<List<Byte>> getOPRETSet(); |     Set<List<Byte>> getOPRETSet(); | ||||||
| 
 | 
 | ||||||
|     boolean pushTransaction(OPRETTransaction t); |  | ||||||
| 
 |  | ||||||
|     boolean removeOPRETChangeEventListener(OPRETChangeEventListener listener); |     boolean removeOPRETChangeEventListener(OPRETChangeEventListener listener); | ||||||
| 
 | 
 | ||||||
|  |     void pushTransactions(List<OPRETTransaction> pushlist); | ||||||
|  | 
 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -11,7 +11,6 @@ import com.google.common.primitives.Bytes; | ||||||
| public class OPRETSimpleLogger extends OPRETBaseHandler { | public class OPRETSimpleLogger extends OPRETBaseHandler { | ||||||
|     private static final Logger logger = LoggerFactory.getLogger(OPRETSimpleLogger.class); |     private static final Logger logger = LoggerFactory.getLogger(OPRETSimpleLogger.class); | ||||||
| 
 | 
 | ||||||
|     @Override |  | ||||||
|     public boolean pushTransaction(final OPRETTransaction t) { |     public boolean pushTransaction(final OPRETTransaction t) { | ||||||
|         final StringBuilder buf = new StringBuilder(); |         final StringBuilder buf = new StringBuilder(); | ||||||
| 
 | 
 | ||||||
|  | @ -24,4 +23,11 @@ public class OPRETSimpleLogger extends OPRETBaseHandler { | ||||||
|         return true; |         return true; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     @Override | ||||||
|  |     public void pushTransactions(List<OPRETTransaction> pushlist) { | ||||||
|  |         for (final OPRETTransaction t : pushlist) { | ||||||
|  |             pushTransaction(t); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,6 +1,7 @@ | ||||||
| package org.tcpid.opretj; | package org.tcpid.opretj; | ||||||
| 
 | 
 | ||||||
| import java.io.Serializable; | import java.io.Serializable; | ||||||
|  | import java.util.Date; | ||||||
| import java.util.List; | import java.util.List; | ||||||
| 
 | 
 | ||||||
| import org.bitcoinj.core.PartialMerkleTree; | import org.bitcoinj.core.PartialMerkleTree; | ||||||
|  | @ -18,11 +19,13 @@ public class OPRETTransaction implements Serializable { | ||||||
|     public final Sha256Hash txHash; |     public final Sha256Hash txHash; | ||||||
|     public final List<List<Byte>> opretData; |     public final List<List<Byte>> opretData; | ||||||
|     private PartialMerkleTree partialMerkleTree; |     private PartialMerkleTree partialMerkleTree; | ||||||
|  |     private Date date; | ||||||
| 
 | 
 | ||||||
|     public OPRETTransaction(final Sha256Hash blockHash, final Sha256Hash txHash, final List<List<Byte>> opret_data) { |     public OPRETTransaction(final Sha256Hash blockHash, final Sha256Hash txHash, final List<List<Byte>> opret_data) { | ||||||
|         this.blockHash = blockHash; |         this.blockHash = blockHash; | ||||||
|         this.txHash = txHash; |         this.txHash = txHash; | ||||||
|         this.opretData = opret_data; |         this.opretData = opret_data; | ||||||
|  |         this.date = null; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public PartialMerkleTree getPartialMerkleTree() { |     public PartialMerkleTree getPartialMerkleTree() { | ||||||
|  | @ -45,4 +48,14 @@ public class OPRETTransaction implements Serializable { | ||||||
|         } |         } | ||||||
|         return buf.toString(); |         return buf.toString(); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     public void setTime(Date time) { | ||||||
|  |         if (this.date == null) { | ||||||
|  |             this.date = time; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public Date getTime() { | ||||||
|  |         return this.date; | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -142,6 +142,7 @@ public class OPRETWallet extends Wallet implements BlocksDownloadedEventListener | ||||||
|     @Override |     @Override | ||||||
|     public void onBlocksDownloaded(final Peer peer, final Block block, final FilteredBlock filteredBlock, |     public void onBlocksDownloaded(final Peer peer, final Block block, final FilteredBlock filteredBlock, | ||||||
|             final int blocksLeft) { |             final int blocksLeft) { | ||||||
|  |         final ArrayList<OPRETTransaction> pushlist = new ArrayList<>(); | ||||||
| 
 | 
 | ||||||
|         if (!pendingTransactions.containsKey(block.getHash())) { |         if (!pendingTransactions.containsKey(block.getHash())) { | ||||||
|             return; |             return; | ||||||
|  | @ -149,7 +150,12 @@ public class OPRETWallet extends Wallet implements BlocksDownloadedEventListener | ||||||
| 
 | 
 | ||||||
|         for (final OPRETTransaction t : pendingTransactions.get(block.getHash()).values()) { |         for (final OPRETTransaction t : pendingTransactions.get(block.getHash()).values()) { | ||||||
|             t.setPartialMerkleTree(filteredBlock.getPartialMerkleTree()); |             t.setPartialMerkleTree(filteredBlock.getPartialMerkleTree()); | ||||||
|             opbs.pushTransaction(t); |             t.setTime(block.getTime()); | ||||||
|  |             pushlist.add(t); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (!pushlist.isEmpty()) { | ||||||
|  |             opbs.pushTransactions(pushlist); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         pendingTransactions.remove(block.getHash()); |         pendingTransactions.remove(block.getHash()); | ||||||
|  | @ -172,6 +178,7 @@ public class OPRETWallet extends Wallet implements BlocksDownloadedEventListener | ||||||
|             logger.debug("False Positive Transaction {}", tx.toString()); |             logger.debug("False Positive Transaction {}", tx.toString()); | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  |         logger.debug("Found Transaction {}", tx.toString()); | ||||||
|         final Sha256Hash h = block.getHeader().getHash(); |         final Sha256Hash h = block.getHeader().getHash(); | ||||||
| 
 | 
 | ||||||
|         if (!pendingTransactions.containsKey(h)) { |         if (!pendingTransactions.containsKey(h)) { | ||||||
|  |  | ||||||
|  | @ -15,12 +15,14 @@ import org.bitcoinj.wallet.Wallet; | ||||||
| public class OPRETWalletAppKit extends WalletAppKit { | public class OPRETWalletAppKit extends WalletAppKit { | ||||||
|     // private final Logger logger = LoggerFactory.getLogger(OPRETWallet.class); |     // private final Logger logger = LoggerFactory.getLogger(OPRETWallet.class); | ||||||
|     private final OPRETHandlerInterface opbs; |     private final OPRETHandlerInterface opbs; | ||||||
|  |     private File spvblockstorefile; | ||||||
| 
 | 
 | ||||||
|     public OPRETWalletAppKit(final NetworkParameters params, final File directory, final String filePrefix, |     public OPRETWalletAppKit(final NetworkParameters params, final File directory, final String filePrefix, | ||||||
|             final OPRETHandlerInterface bs) { |             final OPRETHandlerInterface bs) { | ||||||
|         super(params, directory, filePrefix); |         super(params, directory, filePrefix); | ||||||
|         opbs = bs; |         opbs = bs; | ||||||
|         walletFactory = (params1, keyChainGroup) -> new OPRETWallet(params1, keyChainGroup, opbs); |         walletFactory = (params1, keyChainGroup) -> new OPRETWallet(params1, keyChainGroup, opbs); | ||||||
|  |         spvblockstorefile = null; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|  | @ -53,6 +55,21 @@ public class OPRETWalletAppKit extends WalletAppKit { | ||||||
|                 || params.getId().equals(NetworkParameters.ID_TESTNET)) { |                 || params.getId().equals(NetworkParameters.ID_TESTNET)) { | ||||||
|             file.deleteOnExit(); |             file.deleteOnExit(); | ||||||
|         } |         } | ||||||
|  |         this.spvblockstorefile = file; | ||||||
|         return new SPVBlockStore(params, 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(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Harald Hoyer
						Harald Hoyer