Generalizes the interface for block explorers in OTS

This commit is contained in:
Vitor Pamplona
2024-02-25 15:43:01 -05:00
parent 31c9041461
commit 9eefb65047
4 changed files with 68 additions and 42 deletions
@@ -24,6 +24,7 @@ import android.util.Log
import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.encoders.hexToByteArray
import com.vitorpamplona.quartz.ots.BlockstreamExplorer
import com.vitorpamplona.quartz.ots.DetachedTimestampFile
import com.vitorpamplona.quartz.ots.Hash
import com.vitorpamplona.quartz.ots.OpenTimestamps
@@ -57,7 +58,7 @@ class OtsEvent(
val detachedOts = DetachedTimestampFile.deserialize(otsByteArray())
val result = OpenTimestamps.verify(detachedOts, digest)
val result = OpenTimestamps(BlockstreamExplorer()).verify(detachedOts, digest)
if (result == null || result.isEmpty()) {
return null
} else {
@@ -72,7 +73,7 @@ class OtsEvent(
fun info(): String {
val detachedOts = DetachedTimestampFile.deserialize(otsByteArray())
return OpenTimestamps.info(detachedOts)
return OpenTimestamps(BlockstreamExplorer()).info(detachedOts)
}
companion object {
@@ -82,7 +83,7 @@ class OtsEvent(
fun stamp(eventId: HexKey): ByteArray {
val hash = Hash(eventId.hexToByteArray(), OpSHA256._TAG)
val file = DetachedTimestampFile.from(hash)
val timestamp = OpenTimestamps.stamp(file)
val timestamp = OpenTimestamps(BlockstreamExplorer()).stamp(file)
val detachedToSerialize = DetachedTimestampFile(hash.getOp(), timestamp)
return detachedToSerialize.serialize()
}
@@ -0,0 +1,21 @@
package com.vitorpamplona.quartz.ots;
public interface BitcoinExplorer {
/**
* Retrieve the block information from the block hash.
*
* @param hash Hash of the block.
* @return the blockheader of the hash
* @throws Exception desc
*/
public BlockHeader block(final String hash) throws Exception;
/**
* Retrieve the block hash from the block height.
*
* @param height Height of the block.
* @return the hash of the block at height height
* @throws Exception desc
*/
public String blockHash(final Integer height) throws Exception;
}
@@ -1,17 +1,15 @@
package com.vitorpamplona.quartz.ots;
import android.util.Log;
import com.fasterxml.jackson.databind.JsonNode;
import com.vitorpamplona.quartz.ots.http.Request;
import com.vitorpamplona.quartz.ots.http.Response;
import org.json.JSONObject;
import java.net.URL;
import java.util.concurrent.*;
import java.util.logging.Logger;
public class Esplora {
private static final Logger log = Utils.getLogger(Esplora.class.getName());
public class BlockstreamExplorer implements BitcoinExplorer {
private static final String esploraUrl = "https://blockstream.info/api";
/**
@@ -21,7 +19,7 @@ public class Esplora {
* @return the blockheader of the hash
* @throws Exception desc
*/
public static BlockHeader block(final String hash) throws Exception {
public BlockHeader block(final String hash) throws Exception {
final URL url = new URL(esploraUrl + "/block/" + hash);
final Request task = new Request(url);
final ExecutorService executor = Executors.newSingleThreadExecutor();
@@ -31,14 +29,14 @@ public class Esplora {
if (!take.isOk())
throw new Exception();
final JSONObject jsonObject = take.getJson();
final String merkleroot = jsonObject.getString("merkle_root");
final String time = String.valueOf(jsonObject.getInt("timestamp"));
final JsonNode jsonObject = take.getJson();
final String merkleroot = jsonObject.get("merkle_root").asText();
final String time = String.valueOf(jsonObject.get("timestamp").asInt());
final BlockHeader blockHeader = new BlockHeader();
blockHeader.setMerkleroot(merkleroot);
blockHeader.setTime(time);
blockHeader.setBlockHash(hash);
log.info(take.getFromUrl() + " " + blockHeader);
Log.i("BlockstreamExplorer", take.getFromUrl() + " " + blockHeader);
return blockHeader;
//log.warning("Cannot parse merkleroot from body: " + jsonObject + ": " + e.getMessage());
}
@@ -50,7 +48,7 @@ public class Esplora {
* @return the hash of the block at height height
* @throws Exception desc
*/
public static String blockHash(final Integer height) throws Exception {
public String blockHash(final Integer height) throws Exception {
final URL url = new URL(esploraUrl + "/block-height/" + height);
final Request task = new Request(url);
final ExecutorService executor = Executors.newSingleThreadExecutor();
@@ -60,7 +58,7 @@ public class Esplora {
if (!take.isOk())
throw new Exception();
final String blockHash = take.getString();
log.info(take.getFromUrl() + " " + blockHash);
Log.i("BlockstreamExplorer", take.getFromUrl() + " " + blockHash);
return blockHash;
}
}
@@ -32,13 +32,19 @@ import java.util.concurrent.Executors;
*/
public class OpenTimestamps {
BitcoinExplorer explorer;
public OpenTimestamps(BitcoinExplorer explorer) {
this.explorer = explorer;
}
/**
* Show information on a detached timestamp.
*
* @param detachedTimestampFile The DetachedTimestampFile ots.
* @return the string representation of the timestamp.
*/
public static String info(DetachedTimestampFile detachedTimestampFile) {
public String info(DetachedTimestampFile detachedTimestampFile) {
return info(detachedTimestampFile, false);
}
@@ -49,7 +55,7 @@ public class OpenTimestamps {
* @param verbose Show verbose output.
* @return the string representation of the timestamp.
*/
public static String info(DetachedTimestampFile detachedTimestampFile, boolean verbose) {
public String info(DetachedTimestampFile detachedTimestampFile, boolean verbose) {
if (detachedTimestampFile == null) {
return "No ots file";
}
@@ -68,7 +74,7 @@ public class OpenTimestamps {
* @param timestamp The timestamp buffered.
* @return the string representation of the timestamp.
*/
public static String info(Timestamp timestamp) {
public String info(Timestamp timestamp) {
if (timestamp == null) {
return "No timestamp";
}
@@ -86,8 +92,8 @@ public class OpenTimestamps {
* @return The plain array buffer of stamped.
* @throws IOException if fileTimestamp is not valid, or the stamp procedure fails.
*/
public static Timestamp stamp(DetachedTimestampFile fileTimestamp) throws IOException {
return OpenTimestamps.stamp(fileTimestamp, null, 0);
public Timestamp stamp(DetachedTimestampFile fileTimestamp) throws IOException {
return stamp(fileTimestamp, null, 0);
}
/**
@@ -99,11 +105,11 @@ public class OpenTimestamps {
* @return The plain array buffer of stamped.
* @throws IOException if fileTimestamp is not valid, or the stamp procedure fails.
*/
public static Timestamp stamp(DetachedTimestampFile fileTimestamp, List<String> calendarsUrl, Integer m) throws IOException {
public Timestamp stamp(DetachedTimestampFile fileTimestamp, List<String> calendarsUrl, Integer m) throws IOException {
List<DetachedTimestampFile> fileTimestamps = new ArrayList<DetachedTimestampFile>();
fileTimestamps.add(fileTimestamp);
return OpenTimestamps.stamp(fileTimestamps, calendarsUrl, m);
return stamp(fileTimestamps, calendarsUrl, m);
}
/**
@@ -115,7 +121,7 @@ public class OpenTimestamps {
* @return The plain array buffer of stamped.
* @throws IOException if fileTimestamp is not valid, or the stamp procedure fails.
*/
public static Timestamp stamp(List<DetachedTimestampFile> fileTimestamps, List<String> calendarsUrl, Integer m) throws IOException {
public Timestamp stamp(List<DetachedTimestampFile> fileTimestamps, List<String> calendarsUrl, Integer m) throws IOException {
// Parse parameters
if (fileTimestamps == null || fileTimestamps.size() == 0) {
throw new IOException();
@@ -144,14 +150,14 @@ public class OpenTimestamps {
}
// Build markle tree
Timestamp merkleTip = OpenTimestamps.makeMerkleTree(fileTimestamps);
Timestamp merkleTip = makeMerkleTree(fileTimestamps);
if (merkleTip == null) {
throw new IOException();
}
// Stamping
Timestamp resultTimestamp = OpenTimestamps.create(merkleTip, calendarsUrl, m);
Timestamp resultTimestamp = create(merkleTip, calendarsUrl, m);
if (resultTimestamp == null) {
throw new IOException();
@@ -173,7 +179,7 @@ public class OpenTimestamps {
* @param m Number of calendars to use.
* @return The created timestamp.
*/
private static Timestamp create(Timestamp timestamp, List<String> calendarUrls, Integer m) {
private Timestamp create(Timestamp timestamp, List<String> calendarUrls, Integer m) {
int capacity = calendarUrls.size();
ExecutorService executor = Executors.newFixedThreadPool(4);
ArrayBlockingQueue<Optional<Timestamp>> queue = new ArrayBlockingQueue<>(capacity);
@@ -227,7 +233,7 @@ public class OpenTimestamps {
* @param fileTimestamps The list of DetachedTimestampFile.
* @return merkle tip timestamp.
*/
public static Timestamp makeMerkleTree(List<DetachedTimestampFile> fileTimestamps) {
public Timestamp makeMerkleTree(List<DetachedTimestampFile> fileTimestamps) {
List<Timestamp> merkleRoots = new ArrayList<>();
for (DetachedTimestampFile fileTimestamp : fileTimestamps) {
@@ -259,14 +265,14 @@ public class OpenTimestamps {
* @throws Exception if the verification procedure fails.
*/
public static HashMap<VerifyResult.Chains, VerifyResult> verify(DetachedTimestampFile ots, byte[] diggest) throws Exception {
public HashMap<VerifyResult.Chains, VerifyResult> verify(DetachedTimestampFile ots, byte[] diggest) throws Exception {
if (!Arrays.equals(ots.fileDigest(), diggest)) {
Log.e("OpenTimestamp", "Expected digest " + Hex.encode(ots.fileDigest()).toLowerCase());
Log.e("OpenTimestamp", "File does not match original!");
throw new Exception("File does not match original!");
}
return OpenTimestamps.verify(ots.timestamp);
return verify(ots.timestamp);
}
/**
@@ -276,7 +282,7 @@ public class OpenTimestamps {
* @return HashMap of block heights and timestamps indexed by chain: timestamp in seconds from 1 January 1970.
* @throws Exception if the verification procedure fails.
*/
public static HashMap<VerifyResult.Chains, VerifyResult> verify(Timestamp timestamp) throws Exception {
public HashMap<VerifyResult.Chains, VerifyResult> verify(Timestamp timestamp) throws Exception {
HashMap<VerifyResult.Chains, VerifyResult> verifyResults = new HashMap<>();
for (Map.Entry<byte[], TimeAttestation> item : timestamp.allAttestations().entrySet()) {
@@ -339,13 +345,13 @@ public class OpenTimestamps {
* @throws VerificationException if it doesn't check the merkle root of the block.
* @throws Exception if the verification procedure fails.
*/
public static Long verify(BitcoinBlockHeaderAttestation attestation, byte[] msg) throws VerificationException, Exception {
public Long verify(BitcoinBlockHeaderAttestation attestation, byte[] msg) throws VerificationException, Exception {
Integer height = attestation.getHeight();
BlockHeader blockInfo;
try {
String blockHash = Esplora.blockHash(height);
blockInfo = Esplora.block(blockHash);
String blockHash = explorer.blockHash(height);
blockInfo = explorer.block(blockHash);
Log.i("OpenTimestamps", "Lite-client verification, assuming block " + blockHash + " is valid");
} catch (Exception e2) {
e2.printStackTrace();
@@ -364,13 +370,13 @@ public class OpenTimestamps {
* @throws VerificationException if it doesn't check the merkle root of the block.
* @throws Exception if the verification procedure fails.
*/
public static Long verify(LitecoinBlockHeaderAttestation attestation, byte[] msg) throws VerificationException, Exception {
public Long verify(LitecoinBlockHeaderAttestation attestation, byte[] msg) throws VerificationException, Exception {
Integer height = attestation.getHeight();
BlockHeader blockInfo;
try {
String blockHash = Esplora.blockHash(height);
blockInfo = Esplora.block(blockHash);
String blockHash = explorer.blockHash(height);
blockInfo = explorer.block(blockHash);
Log.i("OpenTimestamps", "Lite-client verification, assuming block " + blockHash + " is valid");
} catch (Exception e2) {
e2.printStackTrace();
@@ -387,9 +393,9 @@ public class OpenTimestamps {
* @return a boolean representing if the timestamp has changed.
* @throws Exception if the upgrading procedure fails.
*/
public static boolean upgrade(DetachedTimestampFile detachedTimestamp) throws Exception {
public boolean upgrade(DetachedTimestampFile detachedTimestamp) throws Exception {
// Upgrade timestamp
boolean changed = OpenTimestamps.upgrade(detachedTimestamp.timestamp);
boolean changed = upgrade(detachedTimestamp.timestamp);
return changed;
}
@@ -401,7 +407,7 @@ public class OpenTimestamps {
* @return a boolean representing if the timestamp has changed.
* @throws Exception if the upgrading procedure fails.
*/
public static boolean upgrade(Timestamp timestamp) throws Exception {
public boolean upgrade(Timestamp timestamp) throws Exception {
// Check remote calendars for upgrades.
// This time we only check PendingAttestations - we can't be as agressive.
@@ -417,7 +423,7 @@ public class OpenTimestamps {
try {
Calendar calendar = new Calendar(calendarUrl);
Timestamp upgradedStamp = OpenTimestamps.upgrade(subStamp, calendar, commitment, existingAttestations);
Timestamp upgradedStamp = upgrade(subStamp, calendar, commitment, existingAttestations);
try {
subStamp.merge(upgradedStamp);
@@ -436,7 +442,7 @@ public class OpenTimestamps {
return upgraded;
}
private static Timestamp upgrade(Timestamp subStamp, Calendar calendar, byte[] commitment, Set<TimeAttestation> existingAttestations) throws Exception {
private Timestamp upgrade(Timestamp subStamp, Calendar calendar, byte[] commitment, Set<TimeAttestation> existingAttestations) throws Exception {
Timestamp upgradedStamp;
try {