Adds support for OpenTimestamps in Quartz
This commit is contained in:
@@ -111,6 +111,7 @@ class EventFactory {
|
||||
MetadataEvent.KIND -> MetadataEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
MuteListEvent.KIND -> MuteListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
NNSEvent.KIND -> NNSEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
OtsEvent.KIND -> OtsEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
PeopleListEvent.KIND -> PeopleListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
PinListEvent.KIND -> PinListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
PollNoteEvent.KIND -> PollNoteEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* Copyright (c) 2024 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.events
|
||||
|
||||
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.DetachedTimestampFile
|
||||
import com.vitorpamplona.quartz.ots.Hash
|
||||
import com.vitorpamplona.quartz.ots.OpenTimestamps
|
||||
import com.vitorpamplona.quartz.ots.VerifyResult
|
||||
import com.vitorpamplona.quartz.ots.op.OpSHA256
|
||||
import com.vitorpamplona.quartz.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import java.util.Base64
|
||||
|
||||
@Immutable
|
||||
class OtsEvent(
|
||||
id: HexKey,
|
||||
pubKey: HexKey,
|
||||
createdAt: Long,
|
||||
tags: Array<Array<String>>,
|
||||
content: String,
|
||||
sig: HexKey,
|
||||
) : Event(id, pubKey, createdAt, KIND, tags, content, sig) {
|
||||
fun digestEvent() = tags.firstOrNull { it.size > 1 && it[0] == "e" }?.get(1)
|
||||
|
||||
fun digest() = digestEvent()?.hexToByteArray()
|
||||
|
||||
fun otsByteArray(): ByteArray {
|
||||
return Base64.getDecoder().decode(content)
|
||||
}
|
||||
|
||||
fun verify(): Long? {
|
||||
try {
|
||||
val digest = digest() ?: return null
|
||||
|
||||
val detachedOts = DetachedTimestampFile.deserialize(otsByteArray())
|
||||
|
||||
val result = OpenTimestamps.verify(detachedOts, digest)
|
||||
if (result == null || result.isEmpty()) {
|
||||
return null
|
||||
} else {
|
||||
return result.get(VerifyResult.Chains.BITCOIN)?.timestamp
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
if (e is CancellationException) throw e
|
||||
Log.e("OpenTimeStamps", "Failed to verify", e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
fun info(): String {
|
||||
val detachedOts = DetachedTimestampFile.deserialize(otsByteArray())
|
||||
return OpenTimestamps.info(detachedOts)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val KIND = 1040
|
||||
const val ALT = "Opentimestamps Attestation"
|
||||
|
||||
fun stamp(eventId: HexKey): ByteArray {
|
||||
val hash = Hash(eventId.hexToByteArray(), OpSHA256._TAG)
|
||||
val file = DetachedTimestampFile.from(hash)
|
||||
val timestamp = OpenTimestamps.stamp(file)
|
||||
val detachedToSerialize = DetachedTimestampFile(hash.getOp(), timestamp)
|
||||
return detachedToSerialize.serialize()
|
||||
}
|
||||
|
||||
fun create(
|
||||
eventId: HexKey,
|
||||
signer: NostrSigner,
|
||||
createdAt: Long = TimeUtils.now(),
|
||||
onReady: (OtsEvent) -> Unit,
|
||||
) {
|
||||
val otsFile = stamp(eventId)
|
||||
val tags =
|
||||
arrayOf(
|
||||
arrayOf("e", eventId),
|
||||
arrayOf("alt", ALT),
|
||||
)
|
||||
signer.sign(createdAt, KIND, tags, Base64.getEncoder().encodeToString(otsFile), onReady)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package com.vitorpamplona.quartz.ots;
|
||||
|
||||
public class BlockHeader {
|
||||
|
||||
private String merkleroot;
|
||||
private String blockHash;
|
||||
private String time;
|
||||
|
||||
public void setTime(String time) {
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
public Long getTime() {
|
||||
return Long.valueOf(time);
|
||||
}
|
||||
|
||||
public String getMerkleroot() {
|
||||
return merkleroot;
|
||||
}
|
||||
|
||||
public void setMerkleroot(String merkleroot) {
|
||||
this.merkleroot = merkleroot;
|
||||
}
|
||||
|
||||
public String getBlockHash() {
|
||||
return blockHash;
|
||||
}
|
||||
|
||||
public void setBlockHash(String blockHash) {
|
||||
this.blockHash = blockHash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
BlockHeader that = (BlockHeader) o;
|
||||
|
||||
if (merkleroot != null ? !merkleroot.equals(that.merkleroot) : that.merkleroot != null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (blockHash != null ? !blockHash.equals(that.blockHash) : that.blockHash != null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return time != null ? time.equals(that.time) : that.time == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = merkleroot != null ? merkleroot.hashCode() : 0;
|
||||
result = 31 * result + (blockHash != null ? blockHash.hashCode() : 0);
|
||||
result = 31 * result + (time != null ? time.hashCode() : 0);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "BlockHeader{" +
|
||||
"merkleroot='" + merkleroot + '\'' +
|
||||
", blockHash='" + blockHash + '\'' +
|
||||
", time='" + time + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package com.vitorpamplona.quartz.ots;
|
||||
|
||||
import com.vitorpamplona.quartz.ots.exceptions.CommitmentNotFoundException;
|
||||
import com.vitorpamplona.quartz.ots.exceptions.DeserializationException;
|
||||
import com.vitorpamplona.quartz.ots.exceptions.ExceededSizeException;
|
||||
import com.vitorpamplona.quartz.ots.exceptions.UrlException;
|
||||
import com.vitorpamplona.quartz.ots.http.Request;
|
||||
import com.vitorpamplona.quartz.ots.http.Response;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Class representing remote calendar server interface.
|
||||
*/
|
||||
public class Calendar {
|
||||
|
||||
private String url;
|
||||
|
||||
|
||||
/**
|
||||
* Create a RemoteCalendar.
|
||||
*
|
||||
* @param url The server url.
|
||||
*/
|
||||
public Calendar(String url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get calendar url.
|
||||
*
|
||||
* @return The calendar url.
|
||||
*/
|
||||
public String getUrl() {
|
||||
return this.url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Submitting a digest to remote calendar. Returns a com.eternitywall.ots.Timestamp committing to that digest.
|
||||
*
|
||||
* @param digest The digest hash to send.
|
||||
* @return the Timestamp received from the calendar.
|
||||
* @throws ExceededSizeException if response is too big.
|
||||
* @throws UrlException if url is not reachable.
|
||||
* @throws DeserializationException if the data is corrupt
|
||||
*/
|
||||
public Timestamp submit(byte[] digest)
|
||||
throws ExceededSizeException, UrlException, DeserializationException {
|
||||
try {
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
headers.put("Accept", "application/vnd.opentimestamps.v1");
|
||||
headers.put("User-Agent", "java-opentimestamps");
|
||||
headers.put("Content-Type", "application/x-www-form-urlencoded");
|
||||
|
||||
URL obj = new URL(url + "/digest");
|
||||
Request task = new Request(obj);
|
||||
task.setData(digest);
|
||||
task.setHeaders(headers);
|
||||
Response response = task.call();
|
||||
byte[] body = response.getBytes();
|
||||
if (body.length > 10000) {
|
||||
throw new ExceededSizeException("Calendar response exceeded size limit");
|
||||
}
|
||||
|
||||
StreamDeserializationContext ctx = new StreamDeserializationContext(body);
|
||||
return Timestamp.deserialize(ctx, digest);
|
||||
} catch (ExceededSizeException | DeserializationException e)
|
||||
{
|
||||
throw e;
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new UrlException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a timestamp for a given commitment.
|
||||
*
|
||||
* @param commitment The digest hash to send.
|
||||
* @return the Timestamp from the calendar server (with blockchain information if already written).
|
||||
* @throws ExceededSizeException if response is too big.
|
||||
* @throws UrlException if url is not reachable.
|
||||
* @throws CommitmentNotFoundException if commit is not found.
|
||||
* @throws DeserializationException if the data is corrupt
|
||||
*/
|
||||
public Timestamp getTimestamp(byte[] commitment) throws DeserializationException, ExceededSizeException, CommitmentNotFoundException, UrlException {
|
||||
try {
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
headers.put("Accept", "application/vnd.opentimestamps.v1");
|
||||
headers.put("User-Agent", "java-opentimestamps");
|
||||
headers.put("Content-Type", "application/x-www-form-urlencoded");
|
||||
|
||||
URL obj = new URL(url + "/timestamp/" + Utils.bytesToHex(commitment).toLowerCase());
|
||||
Request task = new Request(obj);
|
||||
task.setHeaders(headers);
|
||||
Response response = task.call();
|
||||
byte[] body = response.getBytes();
|
||||
if (body.length > 10000) {
|
||||
throw new ExceededSizeException("Calendar response exceeded size limit");
|
||||
}
|
||||
|
||||
if (!response.isOk()) {
|
||||
throw new CommitmentNotFoundException("com.eternitywall.ots.Calendar response a status code != 200 which is: " + response.getStatus());
|
||||
}
|
||||
|
||||
StreamDeserializationContext ctx = new StreamDeserializationContext(body);
|
||||
|
||||
return Timestamp.deserialize(ctx, commitment);
|
||||
}
|
||||
catch (DeserializationException | ExceededSizeException | CommitmentNotFoundException e)
|
||||
{
|
||||
throw e;
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new UrlException(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package com.vitorpamplona.quartz.ots;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.vitorpamplona.quartz.ots.http.Request;
|
||||
import com.vitorpamplona.quartz.ots.http.Response;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
/**
|
||||
* For making async calls to a calendar server
|
||||
*/
|
||||
public class CalendarAsyncSubmit implements Callable<Optional<Timestamp>> {
|
||||
|
||||
private String url;
|
||||
private byte[] digest;
|
||||
private BlockingQueue<Optional<Timestamp>> queue;
|
||||
|
||||
public CalendarAsyncSubmit(String url, byte[] digest) {
|
||||
this.url = url;
|
||||
this.digest = digest;
|
||||
}
|
||||
|
||||
public void setQueue(BlockingQueue<Optional<Timestamp>> queue) {
|
||||
this.queue = queue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Timestamp> call() throws Exception {
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
headers.put("Accept", "application/vnd.opentimestamps.v1");
|
||||
headers.put("User-Agent", "java-opentimestamps");
|
||||
headers.put("Content-Type", "application/x-www-form-urlencoded");
|
||||
|
||||
URL obj = new URL(url + "/digest");
|
||||
Request task = new Request(obj);
|
||||
task.setData(digest);
|
||||
task.setHeaders(headers);
|
||||
Response response = task.call();
|
||||
|
||||
if (response.isOk()) {
|
||||
byte[] body = response.getBytes();
|
||||
StreamDeserializationContext ctx = new StreamDeserializationContext(body);
|
||||
Timestamp timestamp = Timestamp.deserialize(ctx, digest);
|
||||
Optional<Timestamp> of = Optional.of(timestamp);
|
||||
queue.add(of);
|
||||
return of;
|
||||
}
|
||||
|
||||
queue.add(Optional.empty());
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
package com.vitorpamplona.quartz.ots;
|
||||
|
||||
import com.vitorpamplona.quartz.ots.exceptions.DeserializationException;
|
||||
import com.vitorpamplona.quartz.ots.op.Op;
|
||||
import com.vitorpamplona.quartz.ots.op.OpCrypto;
|
||||
import com.vitorpamplona.quartz.ots.op.OpSHA256;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
/**
|
||||
* Class representing Detached com.vitorpamplona.quartz.ots.Timestamp File.
|
||||
* A file containing a timestamp for another file.
|
||||
* Contains a timestamp, along with a header and the digest of the file.
|
||||
*/
|
||||
public class DetachedTimestampFile {
|
||||
|
||||
/**
|
||||
* Header magic bytes Designed to be give the user some information in a hexdump, while being
|
||||
* identified as 'data' by the file utility.
|
||||
*
|
||||
* @default \x00OpenTimestamps\x00\x00Proof\x00\xbf\x89\xe2\xe8\x84\xe8\x92\x94
|
||||
*/
|
||||
static byte[] HEADER_MAGIC = {(byte) 0x00, (byte) 0x4f, (byte) 0x70, (byte) 0x65, (byte) 0x6e,
|
||||
(byte) 0x54, (byte) 0x69, (byte) 0x6d, (byte) 0x65, (byte) 0x73,
|
||||
(byte) 0x74, (byte) 0x61, (byte) 0x6d, (byte) 0x70, (byte) 0x73, (byte) 0x00, (byte) 0x00,
|
||||
(byte) 0x50, (byte) 0x72, (byte) 0x6f, (byte) 0x6f, (byte) 0x66, (byte) 0x00,
|
||||
(byte) 0xbf, (byte) 0x89, (byte) 0xe2, (byte) 0xe8, (byte) 0x84, (byte) 0xe8, (byte) 0x92,
|
||||
(byte) 0x94};
|
||||
|
||||
/**
|
||||
* While the git commit timestamps have a minor version, probably better to
|
||||
* leave it out here: unlike Git commits round-tripping is an issue when
|
||||
* timestamps are upgraded, and we could end up with bugs related to not
|
||||
* saving/updating minor version numbers correctly.
|
||||
*
|
||||
* @default 1
|
||||
*/
|
||||
static byte MAJOR_VERSION = 1;
|
||||
|
||||
Op fileHashOp;
|
||||
Timestamp timestamp;
|
||||
|
||||
public DetachedTimestampFile(Op fileHashOp, Timestamp timestamp) {
|
||||
this.fileHashOp = fileHashOp;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* The digest of the file that was timestamped.
|
||||
*
|
||||
* @return The message inside the timestamp.
|
||||
*/
|
||||
public byte[] fileDigest() {
|
||||
return this.timestamp.msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the internal timestamp.
|
||||
*
|
||||
* @return the timestamp.
|
||||
*/
|
||||
public Timestamp getTimestamp() {
|
||||
return this.timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize a com.vitorpamplona.quartz.ots.Timestamp File.
|
||||
*
|
||||
* @param ctx The stream serialization context.
|
||||
*/
|
||||
public void serialize(StreamSerializationContext ctx) {
|
||||
ctx.writeBytes(HEADER_MAGIC);
|
||||
ctx.writeVaruint(MAJOR_VERSION);
|
||||
this.fileHashOp.serialize(ctx);
|
||||
ctx.writeBytes(this.timestamp.msg);
|
||||
this.timestamp.serialize(ctx);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize a com.vitorpamplona.quartz.ots.Timestamp File.
|
||||
*
|
||||
* @return The byte array of serialized data.
|
||||
*/
|
||||
public byte[] serialize() {
|
||||
StreamSerializationContext ctx = new StreamSerializationContext();
|
||||
this.serialize(ctx);
|
||||
|
||||
return ctx.getOutput();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize a com.vitorpamplona.quartz.ots.Timestamp File.
|
||||
*
|
||||
* @param ctx The stream deserialization context.
|
||||
* @return The generated com.vitorpamplona.quartz.ots.DetachedTimestampFile object.
|
||||
*/
|
||||
public static DetachedTimestampFile deserialize(StreamDeserializationContext ctx) throws DeserializationException {
|
||||
ctx.assertMagic(HEADER_MAGIC);
|
||||
ctx.readVaruint();
|
||||
|
||||
OpCrypto fileHashOp = (OpCrypto) OpCrypto.deserialize(ctx);
|
||||
byte[] fileHash = ctx.readBytes(fileHashOp._DIGEST_LENGTH());
|
||||
Timestamp timestamp = Timestamp.deserialize(ctx, fileHash);
|
||||
|
||||
ctx.assertEof();
|
||||
|
||||
return new DetachedTimestampFile(fileHashOp, timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize a com.vitorpamplona.quartz.ots.Timestamp File.
|
||||
*
|
||||
* @param ots The byte array of deserialization DetachedFileTimestamped.
|
||||
* @return The generated com.vitorpamplona.quartz.ots.DetachedTimestampFile object.
|
||||
*/
|
||||
public static DetachedTimestampFile deserialize(byte[] ots) throws DeserializationException {
|
||||
StreamDeserializationContext ctx = new StreamDeserializationContext(ots);
|
||||
|
||||
return DetachedTimestampFile.deserialize(ctx);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the Detached com.vitorpamplona.quartz.ots.Timestamp File from bytes.
|
||||
*
|
||||
* @param fileHashOp The file hash operation.
|
||||
* @param ctx The stream deserialization context.
|
||||
* @return The generated com.vitorpamplona.quartz.ots.DetachedTimestampFile object.
|
||||
* @throws NoSuchAlgorithmException desc
|
||||
*/
|
||||
public static DetachedTimestampFile from(OpCrypto fileHashOp, StreamDeserializationContext ctx) throws NoSuchAlgorithmException {
|
||||
byte[] fdHash = fileHashOp.hashFd(ctx);
|
||||
|
||||
return new DetachedTimestampFile(fileHashOp, new Timestamp(fdHash));
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the Detached com.vitorpamplona.quartz.ots.Timestamp File from bytes.
|
||||
*
|
||||
* @param fileHashOp The file hash operation.
|
||||
* @param bytes The byte array of data to hash
|
||||
* @return The generated com.vitorpamplona.quartz.ots.DetachedTimestampFile object.
|
||||
* @throws NoSuchAlgorithmException desc
|
||||
*/
|
||||
public static DetachedTimestampFile from(OpCrypto fileHashOp, byte[] bytes) throws Exception {
|
||||
byte[] fdHash = fileHashOp.hashFd(bytes);
|
||||
|
||||
return new DetachedTimestampFile(fileHashOp, new Timestamp(fdHash));
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the Detached com.vitorpamplona.quartz.ots.Timestamp File from hash.
|
||||
*
|
||||
* @param inputStream The InputStream of the file to hash
|
||||
* @return The generated com.vitorpamplona.quartz.ots.DetachedTimestampFile object.
|
||||
* @throws Exception if the input stream is null
|
||||
*/
|
||||
public static DetachedTimestampFile from(InputStream inputStream) throws Exception {
|
||||
if (inputStream == null) {
|
||||
throw new Exception(); // TODO: Add exception string later on
|
||||
}
|
||||
|
||||
try {
|
||||
DetachedTimestampFile fileTimestamp = DetachedTimestampFile.from(new OpSHA256(), inputStream); // Read from file reader stream
|
||||
return fileTimestamp;
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
e.printStackTrace();
|
||||
throw new Exception();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the Detached com.vitorpamplona.quartz.ots.Timestamp File from InputStream.
|
||||
*
|
||||
* @param fileHashOp The file hash operation.
|
||||
* @param inputStream The input stream file.
|
||||
* @return The generated com.vitorpamplona.quartz.ots.DetachedTimestampFile object.
|
||||
* @throws IOException desc
|
||||
* @throws NoSuchAlgorithmException desc
|
||||
*/
|
||||
public static DetachedTimestampFile from(OpCrypto fileHashOp, InputStream inputStream) throws IOException, NoSuchAlgorithmException {
|
||||
byte[] fdHash = fileHashOp.hashFd(inputStream);
|
||||
|
||||
return new DetachedTimestampFile(fileHashOp, new Timestamp(fdHash));
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the Detached com.vitorpamplona.quartz.ots.Timestamp File from hash.
|
||||
*
|
||||
* @param hash The hash of the file.
|
||||
* @return The generated com.vitorpamplona.quartz.ots.DetachedTimestampFile object.
|
||||
*/
|
||||
public static DetachedTimestampFile from(Hash hash) {
|
||||
return new DetachedTimestampFile(hash.getOp(), new Timestamp(hash.getValue()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the Detached com.vitorpamplona.quartz.ots.Timestamp File from File.
|
||||
*
|
||||
* @param fileHashOp The file hash operation.
|
||||
* @param file The hash file.
|
||||
* @return The generated com.vitorpamplona.quartz.ots.DetachedTimestampFile object.
|
||||
* @throws IOException desc
|
||||
* @throws NoSuchAlgorithmException desc
|
||||
*/
|
||||
public static DetachedTimestampFile from(OpCrypto fileHashOp, File file) throws IOException, NoSuchAlgorithmException {
|
||||
byte[] fdHash = fileHashOp.hashFd(file);
|
||||
|
||||
return new DetachedTimestampFile(fileHashOp, new Timestamp(fdHash));
|
||||
}
|
||||
|
||||
/**
|
||||
* Print the object.
|
||||
*
|
||||
* @return The output.
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
String output = "com.vitorpamplona.quartz.ots.DetachedTimestampFile\n";
|
||||
output += "fileHashOp: " + this.fileHashOp.toString() + '\n';
|
||||
output += "timestamp: " + this.timestamp.toString() + '\n';
|
||||
|
||||
return output;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.vitorpamplona.quartz.ots;
|
||||
|
||||
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());
|
||||
private static final String esploraUrl = "https://blockstream.info/api";
|
||||
|
||||
/**
|
||||
* Retrieve the block information from the block hash.
|
||||
*
|
||||
* @param hash Hash of the block.
|
||||
* @return the blockheader of the hash
|
||||
* @throws Exception desc
|
||||
*/
|
||||
public static 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();
|
||||
final Future<Response> future = executor.submit(task);
|
||||
final Response take = future.get();
|
||||
executor.shutdown();
|
||||
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 BlockHeader blockHeader = new BlockHeader();
|
||||
blockHeader.setMerkleroot(merkleroot);
|
||||
blockHeader.setTime(time);
|
||||
blockHeader.setBlockHash(hash);
|
||||
log.info(take.getFromUrl() + " " + blockHeader);
|
||||
return blockHeader;
|
||||
//log.warning("Cannot parse merkleroot from body: " + jsonObject + ": " + e.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 static 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();
|
||||
final Future<Response> future = executor.submit(task);
|
||||
final Response take = future.get();
|
||||
executor.shutdown();
|
||||
if (!take.isOk())
|
||||
throw new Exception();
|
||||
final String blockHash = take.getString();
|
||||
log.info(take.getFromUrl() + " " + blockHash);
|
||||
return blockHash;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
package com.vitorpamplona.quartz.ots;
|
||||
|
||||
import com.vitorpamplona.quartz.ots.op.OpCrypto;
|
||||
import com.vitorpamplona.quartz.ots.op.OpKECCAK256;
|
||||
import com.vitorpamplona.quartz.ots.op.OpRIPEMD160;
|
||||
import com.vitorpamplona.quartz.ots.op.OpSHA1;
|
||||
import com.vitorpamplona.quartz.ots.op.OpSHA256;
|
||||
import com.vitorpamplona.quartz.encoders.Hex;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
public class Hash {
|
||||
|
||||
private byte[] value;
|
||||
private byte algorithm;
|
||||
|
||||
/**
|
||||
* Create a Hash object.
|
||||
*
|
||||
* @param value - The byte array of the hash
|
||||
* @param algorithm - The hashlib tag of crypto operation
|
||||
*/
|
||||
public Hash(byte[] value, byte algorithm) {
|
||||
this.value = value;
|
||||
this.algorithm = algorithm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Hash object.
|
||||
*
|
||||
* @param value - The byte array of the hash
|
||||
* @param label - The hashlib name of crypto operation
|
||||
*/
|
||||
public Hash(byte[] value, String label) {
|
||||
this.value = value;
|
||||
this.algorithm = getOp(label)._TAG();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Value.
|
||||
*
|
||||
* @return value - The hash in byte array.
|
||||
*/
|
||||
public byte[] getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Value tag.
|
||||
*
|
||||
* @param value - The hash in byte array.
|
||||
*/
|
||||
public void setValue(byte[] value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Algorithm tag.
|
||||
*
|
||||
* @return algorithm - The algorithm tag of crypto operation.
|
||||
*/
|
||||
public byte getAlgorithm() {
|
||||
return algorithm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Algorithm tag.
|
||||
*
|
||||
* @param algorithm - The algorithm tag of crypto operation.
|
||||
*/
|
||||
public void setAlgorithm(byte algorithm) {
|
||||
this.algorithm = algorithm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current Crypto operation.
|
||||
*
|
||||
* @return The generated com.vitorpamplona.quartz.ots.OpCrypto object.
|
||||
*/
|
||||
public OpCrypto getOp() {
|
||||
if (this.algorithm == OpSHA1._TAG) {
|
||||
return new OpSHA1();
|
||||
} else if (this.algorithm == OpSHA256._TAG) {
|
||||
return new OpSHA256();
|
||||
} else if (this.algorithm == OpRIPEMD160._TAG) {
|
||||
return new OpRIPEMD160();
|
||||
} else if (this.algorithm == OpKECCAK256._TAG) {
|
||||
return new OpKECCAK256();
|
||||
}
|
||||
|
||||
return new OpSHA256();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Crypto operation from hashlib tag.
|
||||
*
|
||||
* @param algorithm The hashlib tag.
|
||||
* @return The generated com.vitorpamplona.quartz.ots.OpCrypto object.
|
||||
*/
|
||||
public static OpCrypto getOp(byte algorithm) {
|
||||
if (algorithm == OpSHA1._TAG) {
|
||||
return new OpSHA1();
|
||||
} else if (algorithm == OpSHA256._TAG) {
|
||||
return new OpSHA256();
|
||||
} else if (algorithm == OpRIPEMD160._TAG) {
|
||||
return new OpRIPEMD160();
|
||||
} else if (algorithm == OpKECCAK256._TAG) {
|
||||
return new OpKECCAK256();
|
||||
}
|
||||
|
||||
return new OpSHA256();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Crypto operation from hashlib name.
|
||||
*
|
||||
* @param label The hashlib name.
|
||||
* @return The generated com.vitorpamplona.quartz.ots.OpCrypto object.
|
||||
*/
|
||||
public static OpCrypto getOp(String label) {
|
||||
if (label.toLowerCase().equals(new OpSHA1()._TAG_NAME())) {
|
||||
return new OpSHA1();
|
||||
} else if (label.toLowerCase().equals(new OpSHA256()._TAG_NAME())) {
|
||||
return new OpSHA256();
|
||||
} else if (label.toLowerCase().equals(new OpRIPEMD160()._TAG_NAME())) {
|
||||
return new OpRIPEMD160();
|
||||
} else if (label.toLowerCase().equals(new OpKECCAK256()._TAG_NAME())) {
|
||||
return new OpKECCAK256();
|
||||
}
|
||||
|
||||
return new OpSHA256();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build hash from data.
|
||||
*
|
||||
* @param bytes The byte array of data to hash.
|
||||
* @param algorithm The hash file.
|
||||
* @return The generated com.vitorpamplona.quartz.ots.Hash object.
|
||||
* @throws IOException desc
|
||||
* @throws NoSuchAlgorithmException desc
|
||||
*/
|
||||
public static Hash from(byte[] bytes, byte algorithm) throws IOException, NoSuchAlgorithmException {
|
||||
OpCrypto opCrypto = getOp(algorithm);
|
||||
byte[] value = opCrypto.hashFd(bytes);
|
||||
|
||||
return new Hash(value, algorithm);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build hash from File.
|
||||
*
|
||||
* @param file The File of data to hash.
|
||||
* @param algorithm The hash file.
|
||||
* @return The generated com.vitorpamplona.quartz.ots.Hash object.
|
||||
* @throws IOException desc
|
||||
* @throws NoSuchAlgorithmException desc
|
||||
*/
|
||||
public static Hash from(File file, byte algorithm) throws IOException, NoSuchAlgorithmException {
|
||||
OpCrypto opCrypto = getOp(algorithm);
|
||||
byte[] value = opCrypto.hashFd(file);
|
||||
|
||||
return new Hash(value, algorithm);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build hash from InputStream.
|
||||
*
|
||||
* @param inputStream The InputStream of data to hash.
|
||||
* @param algorithm The hash file.
|
||||
* @return The generated com.vitorpamplona.quartz.ots.Hash object.
|
||||
* @throws IOException desc
|
||||
* @throws NoSuchAlgorithmException desc
|
||||
*/
|
||||
public static Hash from(InputStream inputStream, byte algorithm) throws IOException, NoSuchAlgorithmException {
|
||||
OpCrypto opCrypto = getOp(algorithm);
|
||||
byte[] value = opCrypto.hashFd(inputStream);
|
||||
|
||||
return new Hash(value, algorithm);
|
||||
}
|
||||
|
||||
/**
|
||||
* Print the object.
|
||||
*
|
||||
* @return The output.
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
String output = "com.vitorpamplona.quartz.ots.Hash\n";
|
||||
output += "algorithm: " + this.getOp()._HASHLIB_NAME() + '\n';
|
||||
output += "value: " + Hex.encode(this.value) + '\n';
|
||||
|
||||
return output;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package com.vitorpamplona.quartz.ots;
|
||||
|
||||
import com.vitorpamplona.quartz.ots.op.OpAppend;
|
||||
import com.vitorpamplona.quartz.ots.op.OpPrepend;
|
||||
import com.vitorpamplona.quartz.ots.op.OpSHA256;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Utility functions for merkle trees
|
||||
*/
|
||||
public class Merkle {
|
||||
|
||||
/**
|
||||
* Concatenate left and right, then perform a unary operation on them left and right can be either timestamps or bytes.
|
||||
* Appropriate intermediary append/prepend operations will be created as needed for left and right.
|
||||
*
|
||||
* @param left the left timestamp parameter
|
||||
* @param right the right timestamp parameter
|
||||
* @return the concatenation of left and right
|
||||
*/
|
||||
public static Timestamp catThenUnaryOp(Timestamp left, Timestamp right) {
|
||||
// rightPrependStamp = right.ops.add(OpPrepend(left.msg))
|
||||
Timestamp rightPrependStamp = right.add(new OpPrepend(left.msg));
|
||||
|
||||
// Left and right should produce the same thing, so we can set the timestamp of the left to the right.
|
||||
// left.ops[OpAppend(right.msg)] = right_prepend_stamp
|
||||
// leftAppendStamp = left.ops.add(OpAppend(right.msg))
|
||||
//Timestamp leftPrependStamp = left.add(new OpAppend(right.msg));
|
||||
left.ops.put(new OpAppend(right.msg), rightPrependStamp);
|
||||
|
||||
// return rightPrependStamp.ops.add(unaryOpCls())
|
||||
Timestamp res = rightPrependStamp.add(new OpSHA256());
|
||||
return res;
|
||||
}
|
||||
|
||||
public static Timestamp catSha256(Timestamp left, Timestamp right) {
|
||||
return Merkle.catThenUnaryOp(left, right);
|
||||
}
|
||||
|
||||
public static Timestamp catSha256d(Timestamp left, Timestamp right) {
|
||||
Timestamp sha256Timestamp = Merkle.catSha256(left, right);
|
||||
// res = sha256Timestamp.ops.add(OpSHA256());
|
||||
Timestamp res = sha256Timestamp.add(new OpSHA256());
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merkelize a set of timestamps.
|
||||
* A merkle tree of all the timestamps is built in-place using binop() to
|
||||
* timestamp each pair of timestamps. The exact algorithm used is structurally
|
||||
* identical to a merkle-mountain-range, although leaf sums aren't committed.
|
||||
* As this function is under the consensus-critical core, it's guaranteed that
|
||||
* the algorithm will not be changed in the future.
|
||||
*
|
||||
* @param timestamps a list of timestamps
|
||||
* @return the timestamp for the tip of the tree.
|
||||
*/
|
||||
public static Timestamp makeMerkleTree(List<Timestamp> timestamps) {
|
||||
List<Timestamp> stamps = timestamps;
|
||||
Timestamp prevStamp = null;
|
||||
boolean exit = false;
|
||||
|
||||
while (!exit) {
|
||||
if (stamps.size() > 0) {
|
||||
prevStamp = stamps.get(0);
|
||||
}
|
||||
|
||||
List<Timestamp> subStamps = stamps.subList(1, stamps.size());
|
||||
List<Timestamp> nextStamps = new ArrayList<>();
|
||||
|
||||
for (Timestamp stamp : subStamps) {
|
||||
if (prevStamp == null) {
|
||||
prevStamp = stamp;
|
||||
} else {
|
||||
nextStamps.add(Merkle.catSha256(prevStamp, stamp));
|
||||
prevStamp = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (nextStamps.size() == 0) {
|
||||
exit = true;
|
||||
} else {
|
||||
if (prevStamp != null) {
|
||||
nextStamps.add(prevStamp);
|
||||
}
|
||||
|
||||
stamps = nextStamps;
|
||||
}
|
||||
}
|
||||
|
||||
return prevStamp;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,475 @@
|
||||
package com.vitorpamplona.quartz.ots;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.vitorpamplona.quartz.ots.attestation.BitcoinBlockHeaderAttestation;
|
||||
import com.vitorpamplona.quartz.ots.attestation.EthereumBlockHeaderAttestation;
|
||||
import com.vitorpamplona.quartz.ots.attestation.LitecoinBlockHeaderAttestation;
|
||||
import com.vitorpamplona.quartz.ots.attestation.PendingAttestation;
|
||||
import com.vitorpamplona.quartz.ots.attestation.TimeAttestation;
|
||||
import com.vitorpamplona.quartz.encoders.Hex;
|
||||
import com.vitorpamplona.quartz.ots.exceptions.VerificationException;
|
||||
import com.vitorpamplona.quartz.ots.op.OpAppend;
|
||||
import com.vitorpamplona.quartz.ots.op.OpCrypto;
|
||||
import com.vitorpamplona.quartz.ots.op.OpSHA256;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* The main class for timestamp operations.
|
||||
*/
|
||||
public class OpenTimestamps {
|
||||
|
||||
/**
|
||||
* Show information on a detached timestamp.
|
||||
*
|
||||
* @param detachedTimestampFile The DetachedTimestampFile ots.
|
||||
* @return the string representation of the timestamp.
|
||||
*/
|
||||
public static String info(DetachedTimestampFile detachedTimestampFile) {
|
||||
return info(detachedTimestampFile, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show information on a detached timestamp with verbose option.
|
||||
*
|
||||
* @param detachedTimestampFile The DetachedTimestampFile ots.
|
||||
* @param verbose Show verbose output.
|
||||
* @return the string representation of the timestamp.
|
||||
*/
|
||||
public static String info(DetachedTimestampFile detachedTimestampFile, boolean verbose) {
|
||||
if (detachedTimestampFile == null) {
|
||||
return "No ots file";
|
||||
}
|
||||
|
||||
String fileHash = Utils.bytesToHex(detachedTimestampFile.timestamp.msg).toLowerCase();
|
||||
String hashOp = ((OpCrypto) detachedTimestampFile.fileHashOp)._TAG_NAME();
|
||||
|
||||
String firstLine = "File " + hashOp + " hash: " + fileHash + '\n';
|
||||
|
||||
return firstLine + "Timestamp:\n" + detachedTimestampFile.timestamp.strTree(0, verbose);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show information on a timestamp.
|
||||
*
|
||||
* @param timestamp The timestamp buffered.
|
||||
* @return the string representation of the timestamp.
|
||||
*/
|
||||
public static String info(Timestamp timestamp) {
|
||||
if (timestamp == null) {
|
||||
return "No timestamp";
|
||||
}
|
||||
|
||||
String fileHash = Utils.bytesToHex(timestamp.msg).toLowerCase();
|
||||
String firstLine = "Hash: " + fileHash + '\n';
|
||||
|
||||
return firstLine + "Timestamp:\n" + timestamp.strTree(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create timestamp with the aid of a remote calendar. May be specified multiple times.
|
||||
*
|
||||
* @param fileTimestamp The Detached Timestamp File.
|
||||
* @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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create timestamp with the aid of a remote calendar. May be specified multiple times.
|
||||
*
|
||||
* @param fileTimestamp The timestamp to stamp.
|
||||
* @param calendarsUrl The list of calendar urls.
|
||||
* @param m The number of calendar to use.
|
||||
* @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 {
|
||||
List<DetachedTimestampFile> fileTimestamps = new ArrayList<DetachedTimestampFile>();
|
||||
fileTimestamps.add(fileTimestamp);
|
||||
|
||||
return OpenTimestamps.stamp(fileTimestamps, calendarsUrl, m);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create timestamp with the aid of a remote calendar. May be specified multiple times.
|
||||
*
|
||||
* @param fileTimestamps The list of timestamp to stamp.
|
||||
* @param calendarsUrl The list of calendar urls.
|
||||
* @param m The number of calendar to use.
|
||||
* @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 {
|
||||
// Parse parameters
|
||||
if (fileTimestamps == null || fileTimestamps.size() == 0) {
|
||||
throw new IOException();
|
||||
}
|
||||
|
||||
if (calendarsUrl == null || calendarsUrl.size() == 0) {
|
||||
calendarsUrl = new ArrayList<String>();
|
||||
calendarsUrl.add("https://alice.btc.calendar.opentimestamps.org");
|
||||
calendarsUrl.add("https://bob.btc.calendar.opentimestamps.org");
|
||||
calendarsUrl.add("https://finney.calendar.eternitywall.com");
|
||||
}
|
||||
|
||||
if (m == null || m <= 0) {
|
||||
if (calendarsUrl.size() == 0) {
|
||||
m = 2;
|
||||
} else if (calendarsUrl.size() == 1) {
|
||||
m = 1;
|
||||
} else {
|
||||
m = calendarsUrl.size();
|
||||
}
|
||||
}
|
||||
|
||||
if (m < 0 || m > calendarsUrl.size()) {
|
||||
Log.e("OpenTimestamp", "m cannot be greater than available calendar neither less or equal 0");
|
||||
throw new IOException();
|
||||
}
|
||||
|
||||
// Build markle tree
|
||||
Timestamp merkleTip = OpenTimestamps.makeMerkleTree(fileTimestamps);
|
||||
|
||||
if (merkleTip == null) {
|
||||
throw new IOException();
|
||||
}
|
||||
|
||||
// Stamping
|
||||
Timestamp resultTimestamp = OpenTimestamps.create(merkleTip, calendarsUrl, m);
|
||||
|
||||
if (resultTimestamp == null) {
|
||||
throw new IOException();
|
||||
}
|
||||
|
||||
// Result of timestamp serialization
|
||||
if (fileTimestamps.size() == 1) {
|
||||
return fileTimestamps.get(0).timestamp;
|
||||
} else {
|
||||
return merkleTip;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a timestamp
|
||||
*
|
||||
* @param timestamp The timestamp.
|
||||
* @param calendarUrls List of calendar's to use.
|
||||
* @param m Number of calendars to use.
|
||||
* @return The created timestamp.
|
||||
*/
|
||||
private static 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);
|
||||
|
||||
// Submit to all public calendars
|
||||
for (final String calendarUrl : calendarUrls) {
|
||||
Log.i("OpenTimestamps", "Submitting to remote calendar " + calendarUrl);
|
||||
|
||||
try {
|
||||
CalendarAsyncSubmit task = new CalendarAsyncSubmit(calendarUrl, timestamp.msg);
|
||||
task.setQueue(queue);
|
||||
executor.submit(task);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
|
||||
for (count = 0; count < capacity && count < m; count++) {
|
||||
try {
|
||||
Optional<Timestamp> optionalStamp = queue.take();
|
||||
|
||||
if (optionalStamp.isPresent()) {
|
||||
try {
|
||||
Timestamp time = optionalStamp.get();
|
||||
timestamp.merge(time);
|
||||
Log.i("Open", ""+ timestamp.attestations.size());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
if (count < m) {
|
||||
Log.e("OpenTimestamp", "Failed to create timestamp: requested " + String.valueOf(m) + " attestation" + ((m > 1) ? "s" : "") + " but received only " + String.valueOf(count));
|
||||
}
|
||||
|
||||
//shut down the executor service now
|
||||
executor.shutdown();
|
||||
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make Merkle Tree of detached timestamps.
|
||||
*
|
||||
* @param fileTimestamps The list of DetachedTimestampFile.
|
||||
* @return merkle tip timestamp.
|
||||
*/
|
||||
public static Timestamp makeMerkleTree(List<DetachedTimestampFile> fileTimestamps) {
|
||||
List<Timestamp> merkleRoots = new ArrayList<>();
|
||||
|
||||
for (DetachedTimestampFile fileTimestamp : fileTimestamps) {
|
||||
byte[] bytesRandom16 = new byte[16];
|
||||
|
||||
try {
|
||||
bytesRandom16 = Utils.randBytes(16);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
// nonce_appended_stamp = file_timestamp.timestamp.ops.add(com.vitorpamplona.quartz.ots.op.OpAppend(os.urandom(16)))
|
||||
Timestamp nonceAppendedStamp = fileTimestamp.timestamp.add(new OpAppend(bytesRandom16));
|
||||
// merkle_root = nonce_appended_stamp.ops.add(com.vitorpamplona.quartz.ots.op.OpSHA256())
|
||||
Timestamp merkleRoot = nonceAppendedStamp.add(new OpSHA256());
|
||||
merkleRoots.add(merkleRoot);
|
||||
}
|
||||
|
||||
Timestamp merkleTip = Merkle.makeMerkleTree(merkleRoots);
|
||||
return merkleTip;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare and verify a detached timestamp.
|
||||
*
|
||||
* @param ots The DetachedTimestampFile containing the proof to verify.
|
||||
* @param diggest The hash of the stamped file, in bytes
|
||||
* @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(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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify a timestamp.
|
||||
*
|
||||
* @param timestamp The timestamp.
|
||||
* @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 {
|
||||
HashMap<VerifyResult.Chains, VerifyResult> verifyResults = new HashMap<>();
|
||||
|
||||
for (Map.Entry<byte[], TimeAttestation> item : timestamp.allAttestations().entrySet()) {
|
||||
byte[] msg = item.getKey();
|
||||
TimeAttestation attestation = item.getValue();
|
||||
VerifyResult verifyResult = null;
|
||||
VerifyResult.Chains chain = null;
|
||||
|
||||
try {
|
||||
if (attestation instanceof BitcoinBlockHeaderAttestation) {
|
||||
chain = VerifyResult.Chains.BITCOIN;
|
||||
Long time = verify((BitcoinBlockHeaderAttestation) attestation, msg);
|
||||
int height = ((BitcoinBlockHeaderAttestation) attestation).getHeight();
|
||||
verifyResult = new VerifyResult(time, height);
|
||||
} else if (attestation instanceof LitecoinBlockHeaderAttestation) {
|
||||
chain = VerifyResult.Chains.LITECOIN;
|
||||
Long time = verify((LitecoinBlockHeaderAttestation) attestation, msg);
|
||||
int height = ((LitecoinBlockHeaderAttestation) attestation).getHeight();
|
||||
verifyResult = new VerifyResult(time, height);
|
||||
}
|
||||
|
||||
if (verifyResult != null && verifyResults.containsKey(chain)) {
|
||||
if (verifyResult.height < verifyResults.get(chain).height) {
|
||||
verifyResults.put(chain, verifyResult);
|
||||
}
|
||||
}
|
||||
|
||||
if (verifyResult != null && !verifyResults.containsKey(chain)) {
|
||||
verifyResults.put(chain, verifyResult);
|
||||
}
|
||||
} catch (VerificationException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
String text = "";
|
||||
|
||||
if (chain == VerifyResult.Chains.BITCOIN) {
|
||||
text = BitcoinBlockHeaderAttestation.chain;
|
||||
} else if (chain == VerifyResult.Chains.LITECOIN) {
|
||||
text = LitecoinBlockHeaderAttestation.chain;
|
||||
} else if (chain == VerifyResult.Chains.ETHEREUM) {
|
||||
text = EthereumBlockHeaderAttestation.chain;
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
|
||||
Log.e("OpenTimestamp", Utils.toUpperFirstLetter(text) + " verification failed: " + e.getMessage());
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
return verifyResults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify an Bitcoin Block Header Attestation.
|
||||
* if the node is not reachable or it fails, uses Lite-client verification.
|
||||
*
|
||||
* @param attestation The BitcoinBlockHeaderAttestation attestation.
|
||||
* @param msg The digest to verify.
|
||||
* @return The unix timestamp in seconds from 1 January 1970.
|
||||
* @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 {
|
||||
Integer height = attestation.getHeight();
|
||||
BlockHeader blockInfo;
|
||||
|
||||
try {
|
||||
String blockHash = Esplora.blockHash(height);
|
||||
blockInfo = Esplora.block(blockHash);
|
||||
Log.i("OpenTimestamps", "Lite-client verification, assuming block " + blockHash + " is valid");
|
||||
} catch (Exception e2) {
|
||||
e2.printStackTrace();
|
||||
throw e2;
|
||||
}
|
||||
|
||||
return attestation.verifyAgainstBlockheader(Utils.arrayReverse(msg), blockInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify an Litecoin Block Header Attestation. Litecoin verification uses only lite-client verification.
|
||||
*
|
||||
* @param attestation The LitecoinBlockHeaderAttestation attestation.
|
||||
* @param msg The digest to verify.
|
||||
* @return The unix timestamp in seconds from 1 January 1970.
|
||||
* @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 {
|
||||
Integer height = attestation.getHeight();
|
||||
BlockHeader blockInfo;
|
||||
|
||||
try {
|
||||
String blockHash = Esplora.blockHash(height);
|
||||
blockInfo = Esplora.block(blockHash);
|
||||
Log.i("OpenTimestamps", "Lite-client verification, assuming block " + blockHash + " is valid");
|
||||
} catch (Exception e2) {
|
||||
e2.printStackTrace();
|
||||
throw e2;
|
||||
}
|
||||
|
||||
return attestation.verifyAgainstBlockheader(Utils.arrayReverse(msg), blockInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Upgrade a timestamp.
|
||||
*
|
||||
* @param detachedTimestamp The DetachedTimestampFile containing the proof to verify.
|
||||
* @return a boolean representing if the timestamp has changed.
|
||||
* @throws Exception if the upgrading procedure fails.
|
||||
*/
|
||||
public static boolean upgrade(DetachedTimestampFile detachedTimestamp) throws Exception {
|
||||
// Upgrade timestamp
|
||||
boolean changed = OpenTimestamps.upgrade(detachedTimestamp.timestamp);
|
||||
return changed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to upgrade an incomplete timestamp to make it verifiable.
|
||||
* Note that this means if the timestamp that is already complete, False will be returned as nothing has changed.
|
||||
*
|
||||
* @param timestamp The timestamp to upgrade.
|
||||
* @return a boolean representing if the timestamp has changed.
|
||||
* @throws Exception if the upgrading procedure fails.
|
||||
*/
|
||||
public static boolean upgrade(Timestamp timestamp) throws Exception {
|
||||
// Check remote calendars for upgrades.
|
||||
// This time we only check PendingAttestations - we can't be as agressive.
|
||||
|
||||
boolean upgraded = false;
|
||||
Set<TimeAttestation> existingAttestations = timestamp.getAttestations();
|
||||
|
||||
for (Timestamp subStamp : timestamp.directlyVerified()) {
|
||||
for (TimeAttestation attestation : subStamp.attestations) {
|
||||
if (attestation instanceof PendingAttestation && !subStamp.isTimestampComplete()) {
|
||||
String calendarUrl = new String(((PendingAttestation) attestation).getUri(), StandardCharsets.UTF_8);
|
||||
// var calendarUrl = calendarUrls[0];
|
||||
byte[] commitment = subStamp.msg;
|
||||
|
||||
try {
|
||||
Calendar calendar = new Calendar(calendarUrl);
|
||||
Timestamp upgradedStamp = OpenTimestamps.upgrade(subStamp, calendar, commitment, existingAttestations);
|
||||
|
||||
try {
|
||||
subStamp.merge(upgradedStamp);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
upgraded = true;
|
||||
} catch (Exception e) {
|
||||
Log.i("OpenTimestamps", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return upgraded;
|
||||
}
|
||||
|
||||
private static Timestamp upgrade(Timestamp subStamp, Calendar calendar, byte[] commitment, Set<TimeAttestation> existingAttestations) throws Exception {
|
||||
Timestamp upgradedStamp;
|
||||
|
||||
try {
|
||||
upgradedStamp = calendar.getTimestamp(commitment);
|
||||
|
||||
if (upgradedStamp == null) {
|
||||
throw new Exception("Invalid stamp");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.i("OpenTimestamps", "Calendar " + calendar.getUrl() + ": " + e.getMessage());
|
||||
throw e;
|
||||
}
|
||||
|
||||
Set<TimeAttestation> attsFromRemote = upgradedStamp.getAttestations();
|
||||
|
||||
if (attsFromRemote.size() > 0) {
|
||||
Log.i("OpenTimestamps", "Got 1 attestation(s) from " + calendar.getUrl());
|
||||
}
|
||||
|
||||
// Set difference from remote attestations & existing attestations
|
||||
Set<TimeAttestation> newAttestations = attsFromRemote;
|
||||
newAttestations.removeAll(existingAttestations);
|
||||
|
||||
// changed & found_new_attestations
|
||||
// foundNewAttestations = true;
|
||||
// Log.i("OpenTimestamps", attsFromRemote.size + ' attestation(s) from ' + calendar.url);
|
||||
|
||||
// Set union of existingAttestations & newAttestations
|
||||
existingAttestations.addAll(newAttestations);
|
||||
|
||||
return upgradedStamp;
|
||||
// subStamp.merge(upgradedStamp);
|
||||
// args.cache.merge(upgraded_stamp)
|
||||
// sub_stamp.merge(upgraded_stamp)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
This code came from https://github.com/opentimestamps/java-opentimestamps
|
||||
|
||||
And includes modifications to avoid dependencies that do not work on Android
|
||||
|
||||
—
|
||||
|
||||
Original License
|
||||
|
||||
The OpenTimestamps Client is free software: you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or (at your
|
||||
option) any later version.
|
||||
|
||||
The OpenTimestamps Client is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
below for more details.
|
||||
@@ -0,0 +1,107 @@
|
||||
package com.vitorpamplona.quartz.ots;
|
||||
|
||||
import com.vitorpamplona.quartz.ots.exceptions.DeserializationException;
|
||||
import java.util.Arrays;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class StreamDeserializationContext {
|
||||
|
||||
byte[] buffer;
|
||||
int counter = 0;
|
||||
|
||||
public StreamDeserializationContext(byte[] stream) {
|
||||
this.buffer = stream;
|
||||
this.counter = 0;
|
||||
}
|
||||
|
||||
public byte[] getOutput() {
|
||||
return this.buffer;
|
||||
}
|
||||
|
||||
public int getCounter() {
|
||||
return this.counter;
|
||||
}
|
||||
|
||||
public byte[] read(int l) {
|
||||
if (this.counter == this.buffer.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (l+this.counter > this.buffer.length) {
|
||||
l = this.buffer.length-this.counter;
|
||||
}
|
||||
|
||||
// const uint8Array = new Uint8Array(this.buffer,this.counter,l);
|
||||
byte[] uint8Array = Arrays.copyOfRange(this.buffer, this.counter, this.counter + l);
|
||||
this.counter += l;
|
||||
|
||||
return uint8Array;
|
||||
}
|
||||
|
||||
public boolean readBool() {
|
||||
byte b = this.read(1)[0];
|
||||
|
||||
if (b == 0xff) {
|
||||
return true;
|
||||
} else if (b == 0x00) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public int readVaruint() {
|
||||
int value = 0;
|
||||
byte shift = 0;
|
||||
byte b;
|
||||
|
||||
do {
|
||||
b = this.read(1)[0];
|
||||
value |= (b & 0b01111111) << shift;
|
||||
shift += 7;
|
||||
} while ((b & 0b10000000) != 0b00000000);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public byte[] readBytes(int expectedLength) throws DeserializationException {
|
||||
|
||||
|
||||
if (expectedLength == 0) {
|
||||
return this.readVarbytes(1024, 0);
|
||||
}
|
||||
|
||||
return this.read(expectedLength);
|
||||
}
|
||||
|
||||
public byte[] readVarbytes(int maxLen) throws DeserializationException {
|
||||
return readVarbytes(maxLen, 0);
|
||||
}
|
||||
|
||||
public byte[] readVarbytes(int maxLen, int minLen) throws DeserializationException {
|
||||
int l = this.readVaruint();
|
||||
|
||||
if (l > maxLen) {
|
||||
throw new DeserializationException("varbytes max length exceeded;");
|
||||
} else if (l < minLen) {
|
||||
throw new DeserializationException("varbytes min length not met;");
|
||||
}
|
||||
|
||||
return this.read(l);
|
||||
}
|
||||
|
||||
public boolean assertMagic(byte[] expectedMagic) {
|
||||
byte[] actualMagic = this.read(expectedMagic.length);
|
||||
|
||||
return Arrays.equals(expectedMagic, actualMagic);
|
||||
}
|
||||
|
||||
public boolean assertEof() {
|
||||
byte[] excess = this.read(1);
|
||||
return excess != null;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return Arrays.toString(this.buffer);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package com.vitorpamplona.quartz.ots;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class StreamSerializationContext {
|
||||
|
||||
List<Byte> buffer = new ArrayList<>();
|
||||
|
||||
public StreamSerializationContext() {
|
||||
this.buffer = new ArrayList<>();
|
||||
}
|
||||
|
||||
public byte[] getOutput() {
|
||||
byte[] bytes = new byte[this.buffer.size()];
|
||||
|
||||
for (int i = 0; i < this.buffer.size(); i++) {
|
||||
bytes[i] = this.buffer.get(i);
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public int getLength() {
|
||||
return this.buffer.size();
|
||||
}
|
||||
|
||||
public void writeBool(boolean value) {
|
||||
if (value == true) {
|
||||
this.writeByte((byte) 0xff);
|
||||
} else {
|
||||
this.writeByte((byte) 0x00);
|
||||
}
|
||||
}
|
||||
|
||||
public void writeVaruint(int value) {
|
||||
if ((value) == 0b00000000) {
|
||||
this.writeByte((byte) 0x00);
|
||||
} else {
|
||||
while (value != 0) {
|
||||
byte b = (byte) ((value & 0xff) & 0b01111111);
|
||||
|
||||
if ((value) > 0b01111111) {
|
||||
b |= 0b10000000;
|
||||
}
|
||||
|
||||
this.writeByte(b);
|
||||
|
||||
if ((value) <= 0b01111111) {
|
||||
break;
|
||||
}
|
||||
|
||||
value = value >> 7;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void writeByte(byte value) {
|
||||
this.buffer.add(new Byte(value));
|
||||
}
|
||||
|
||||
public void writeByte(Byte value) {
|
||||
this.buffer.add(value);
|
||||
}
|
||||
|
||||
public void writeBytes(byte[] value) {
|
||||
for (int i = 0; i < value.length; i++) {
|
||||
this.writeByte(value[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public void writeVarbytes(byte[] value) {
|
||||
this.writeVaruint(value.length);
|
||||
this.writeBytes(value);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return Arrays.toString(this.getOutput());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,617 @@
|
||||
package com.vitorpamplona.quartz.ots;
|
||||
|
||||
import com.vitorpamplona.quartz.ots.attestation.BitcoinBlockHeaderAttestation;
|
||||
import com.vitorpamplona.quartz.ots.attestation.TimeAttestation;
|
||||
import com.vitorpamplona.quartz.encoders.Hex;
|
||||
import com.vitorpamplona.quartz.ots.exceptions.DeserializationException;
|
||||
import com.vitorpamplona.quartz.ots.op.Op;
|
||||
import com.vitorpamplona.quartz.ots.op.OpBinary;
|
||||
import com.vitorpamplona.quartz.ots.op.OpSHA256;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
/**
|
||||
* Proof that one or more attestations commit to a message.
|
||||
* The proof is in the form of a tree, with each node being a message, and the
|
||||
* edges being operations acting on those messages. The leafs of the tree are
|
||||
* attestations that attest to the time that messages in the tree existed prior.
|
||||
*/
|
||||
public class Timestamp {
|
||||
|
||||
public byte[] msg;
|
||||
public List<TimeAttestation> attestations;
|
||||
public HashMap<Op, Timestamp> ops;
|
||||
|
||||
/**
|
||||
* Create a com.vitorpamplona.quartz.ots.Timestamp object.
|
||||
*
|
||||
* @param msg - Desc
|
||||
*/
|
||||
public Timestamp(byte[] msg) {
|
||||
this.msg = msg;
|
||||
this.attestations = new ArrayList<>();
|
||||
this.ops = new HashMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize a Timestamp.
|
||||
*
|
||||
* @param ots - The serialized byte array.
|
||||
* @param initialMsg - The initial message.
|
||||
* @return The deserialized Timestamp.
|
||||
*/
|
||||
public static Timestamp deserialize(byte[] ots, byte[] initialMsg) throws DeserializationException {
|
||||
StreamDeserializationContext ctx = new StreamDeserializationContext(ots);
|
||||
|
||||
return Timestamp.deserialize(ctx, initialMsg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize a Timestamp.
|
||||
* Because the serialization format doesn't include the message that the
|
||||
* timestamp operates on, you have to provide it so that the correct
|
||||
* operation results can be calculated.
|
||||
* The message you provide is assumed to be correct; if it causes a op to
|
||||
* raise MsgValueError when the results are being calculated (done
|
||||
* immediately, not lazily) DeserializationError is raised instead.
|
||||
*
|
||||
* @param ctx - The stream deserialization context.
|
||||
* @param initialMsg - The initial message.
|
||||
* @return The deserialized Timestamp.
|
||||
*/
|
||||
public static Timestamp deserialize(StreamDeserializationContext ctx, byte[] initialMsg)
|
||||
throws DeserializationException {
|
||||
Timestamp self = new Timestamp(initialMsg);
|
||||
byte tag = ctx.readBytes(1)[0];
|
||||
|
||||
while ((tag & 0xff) == 0xff) {
|
||||
byte current = ctx.readBytes(1)[0];
|
||||
doTagOrAttestation(self, ctx, current, initialMsg);
|
||||
tag = ctx.readBytes(1)[0];
|
||||
}
|
||||
|
||||
doTagOrAttestation(self, ctx, tag, initialMsg);
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
private static void doTagOrAttestation(Timestamp self, StreamDeserializationContext ctx, byte tag, byte[] initialMsg)
|
||||
throws DeserializationException {
|
||||
if ((tag & 0xff) == 0x00) {
|
||||
TimeAttestation attestation = TimeAttestation.deserialize(ctx);
|
||||
self.attestations.add(attestation);
|
||||
} else {
|
||||
Op op = Op.deserializeFromTag(ctx, tag);
|
||||
byte[] result = op.call(initialMsg);
|
||||
|
||||
Timestamp stamp = Timestamp.deserialize(ctx, result);
|
||||
self.ops.put(op, stamp);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Serialize object.
|
||||
*
|
||||
* @return The byte array of the serialized timestamp
|
||||
*/
|
||||
public byte[] serialize() {
|
||||
StreamSerializationContext ctx = new StreamSerializationContext();
|
||||
serialize(ctx);
|
||||
|
||||
return ctx.getOutput();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Serialize object.
|
||||
*
|
||||
* @param ctx - The stream serialization context.
|
||||
*/
|
||||
public void serialize(StreamSerializationContext ctx) {
|
||||
List<TimeAttestation> sortedAttestations = this.attestations; // TODO: Hm, this is just a reference copy...
|
||||
Collections.sort(sortedAttestations);
|
||||
|
||||
if (sortedAttestations.size() > 1) {
|
||||
for (int i = 0; i < sortedAttestations.size() - 1; i++) {
|
||||
ctx.writeBytes(new byte[]{(byte) 0xff, (byte) 0x00});
|
||||
sortedAttestations.get(i).serialize(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.ops.isEmpty()) {
|
||||
ctx.writeByte((byte) 0x00);
|
||||
|
||||
if (!sortedAttestations.isEmpty()) {
|
||||
sortedAttestations.get(sortedAttestations.size() - 1).serialize(ctx);
|
||||
}
|
||||
} else if (!this.ops.isEmpty()) {
|
||||
if (!sortedAttestations.isEmpty()) {
|
||||
ctx.writeBytes(new byte[]{(byte) 0xff, (byte) 0x00});
|
||||
sortedAttestations.get(sortedAttestations.size() - 1).serialize(ctx);
|
||||
}
|
||||
|
||||
int counter = 0;
|
||||
List<Map.Entry<Op, Timestamp>> list = sortToList(this.ops.entrySet());
|
||||
|
||||
for (Map.Entry<Op, Timestamp> entry : list) {
|
||||
Timestamp stamp = entry.getValue();
|
||||
Op op = entry.getKey();
|
||||
|
||||
if (counter < this.ops.size() - 1) {
|
||||
ctx.writeBytes(new byte[]{(byte) 0xff});
|
||||
counter++;
|
||||
}
|
||||
|
||||
op.serialize(ctx);
|
||||
stamp.serialize(ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add all operations and attestations from another timestamp to this one.
|
||||
*
|
||||
* @param other - Initial other com.vitorpamplona.quartz.ots.Timestamp to merge.
|
||||
* @throws Exception different timestamps messages
|
||||
*/
|
||||
public void merge(Timestamp other) throws Exception {
|
||||
if (!Arrays.equals(this.msg, other.msg)) {
|
||||
//Log.e("OpenTimestamp", "Can\'t merge timestamps for different messages together");
|
||||
throw new Exception("Can\'t merge timestamps for different messages together");
|
||||
}
|
||||
|
||||
for (final TimeAttestation attestation : other.attestations) {
|
||||
this.attestations.add(attestation);
|
||||
}
|
||||
|
||||
for (Map.Entry<Op, Timestamp> entry : other.ops.entrySet()) {
|
||||
Timestamp otherOpStamp = entry.getValue();
|
||||
Op otherOp = entry.getKey();
|
||||
|
||||
Timestamp ourOpStamp = this.ops.get(otherOp);
|
||||
|
||||
if (ourOpStamp == null) {
|
||||
ourOpStamp = new Timestamp(otherOp.call(this.msg));
|
||||
this.ops.put(otherOp, ourOpStamp);
|
||||
}
|
||||
|
||||
ourOpStamp.merge(otherOpStamp);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shrink Timestamp.
|
||||
* Remove useless pending attestions if exist a full bitcoin attestation.
|
||||
*
|
||||
* @return TimeAttestation - the minimal attestation.
|
||||
* @throws Exception no attestion founds.
|
||||
*/
|
||||
public TimeAttestation shrink() throws Exception {
|
||||
// Get all attestations
|
||||
HashMap<byte[], TimeAttestation> allAttestations = this.allAttestations();
|
||||
|
||||
if (allAttestations.size() == 0) {
|
||||
throw new Exception();
|
||||
} else if (allAttestations.size() == 1) {
|
||||
return allAttestations.values().iterator().next();
|
||||
} else if (this.ops.size() == 0) {
|
||||
throw new Exception(); // TODO: Need a descriptive exception string here
|
||||
}
|
||||
|
||||
// Fore >1 attestations :
|
||||
// Search first BitcoinBlockHeaderAttestation
|
||||
TimeAttestation minAttestation = null;
|
||||
|
||||
for (Map.Entry<Op, Timestamp> entry : this.ops.entrySet()) {
|
||||
Timestamp timestamp = entry.getValue();
|
||||
//Op op = entry.getKey();
|
||||
|
||||
for (TimeAttestation attestation : timestamp.getAttestations()) {
|
||||
if (attestation instanceof BitcoinBlockHeaderAttestation) {
|
||||
if (minAttestation == null) {
|
||||
minAttestation = attestation;
|
||||
} else {
|
||||
if (minAttestation instanceof BitcoinBlockHeaderAttestation
|
||||
&& attestation instanceof BitcoinBlockHeaderAttestation
|
||||
&& ((BitcoinBlockHeaderAttestation) minAttestation).getHeight()
|
||||
> ((BitcoinBlockHeaderAttestation) attestation).getHeight()) {
|
||||
minAttestation = attestation;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Only pending attestations : return the first
|
||||
if (minAttestation == null) {
|
||||
return allAttestations.values().iterator().next();
|
||||
}
|
||||
|
||||
// Remove attestation if not min attestation
|
||||
boolean shrinked = false;
|
||||
|
||||
for (Iterator<Entry<Op, Timestamp>> it = this.ops.entrySet().iterator(); it.hasNext(); ) {
|
||||
Map.Entry<Op, Timestamp> entry = it.next();
|
||||
Timestamp timestamp = entry.getValue();
|
||||
Op op = entry.getKey();
|
||||
Set<TimeAttestation> attestations = timestamp.getAttestations();
|
||||
|
||||
if (attestations.size() > 0 && attestations.contains(minAttestation) && shrinked == false) {
|
||||
timestamp.shrink();
|
||||
shrinked = true;
|
||||
} else {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
|
||||
return minAttestation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the digest of the timestamp.
|
||||
*
|
||||
* @return The byte[] digest string.
|
||||
*/
|
||||
public byte[] getDigest() {
|
||||
return this.msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return as memory hierarchical object.
|
||||
*
|
||||
* @param indent - Initial hierarchical indention.
|
||||
* @return The output string.
|
||||
*/
|
||||
public String toString(int indent) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append(Timestamp.indention(indent) + "msg: " + Hex.encode(this.msg) + "\n");
|
||||
builder.append(Timestamp.indention(indent) + this.attestations.size() + " attestations: \n");
|
||||
int i = 0;
|
||||
|
||||
for (final TimeAttestation attestation : this.attestations) {
|
||||
builder.append(Timestamp.indention(indent) + "[" + i + "] " + attestation.toString() + "\n");
|
||||
i++;
|
||||
}
|
||||
|
||||
i = 0;
|
||||
builder.append(Timestamp.indention(indent) + this.ops.size() + " ops: \n");
|
||||
|
||||
for (Map.Entry<Op, Timestamp> entry : this.ops.entrySet()) {
|
||||
Timestamp stamp = entry.getValue();
|
||||
Op op = entry.getKey();
|
||||
|
||||
builder.append(Timestamp.indention(indent) + "[" + i + "] op: " + op.toString() + "\n");
|
||||
builder.append(Timestamp.indention(indent) + "[" + i + "] timestamp: \n");
|
||||
builder.append(stamp.toString(indent + 1));
|
||||
i++;
|
||||
}
|
||||
|
||||
builder.append('\n');
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indention function for printing tree.
|
||||
*
|
||||
* @param pos - Initial hierarchical indention.
|
||||
* @return The output space string.
|
||||
*/
|
||||
public static String indention(int pos) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < pos; i++) {
|
||||
builder.append(" ");
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public String strTree(int indent) {
|
||||
return strTree(indent, false);
|
||||
}
|
||||
|
||||
private String strResult(boolean verbosity, byte[] parameter, byte[] result) {
|
||||
final String ANSI_HEADER = "\u001B[95m";
|
||||
final String ANSI_OKBLUE = "\u001B[94m";
|
||||
final String ANSI_OKGREEN = "\u001B[92m";
|
||||
final String ANSI_WARNING = "\u001B[93m";
|
||||
final String ANSI_FAIL = "\u001B[91m";
|
||||
final String ANSI_ENDC = "\u001B[0m";
|
||||
final String ANSI_BOLD = "\u001B[1m";
|
||||
final String ANSI_UNDERLINE = "\u001B[4m";
|
||||
|
||||
String rr = "";
|
||||
|
||||
if (verbosity == true && result != null) {
|
||||
rr += " == ";
|
||||
String resultHex = Utils.bytesToHex(result);
|
||||
|
||||
if (parameter == null) {
|
||||
rr += resultHex;
|
||||
} else {
|
||||
String parameterHex = Utils.bytesToHex(parameter);
|
||||
|
||||
try {
|
||||
int index = resultHex.indexOf(parameterHex);
|
||||
String parameterHexHighlight = ANSI_BOLD + parameterHex + ANSI_ENDC;
|
||||
|
||||
if (index == 0) {
|
||||
rr += parameterHexHighlight + resultHex.substring(index + parameterHex.length(), resultHex.length());
|
||||
} else {
|
||||
rr += resultHex.substring(0, index) + parameterHexHighlight;
|
||||
}
|
||||
} catch (Exception err) {
|
||||
rr += resultHex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return as tree hierarchical object.
|
||||
*
|
||||
* @param indent - Initial hierarchical indention.
|
||||
* @param verbosity - Verbose option.
|
||||
* @return The output string.
|
||||
*/
|
||||
public String strTree(int indent, boolean verbosity) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
if (!this.attestations.isEmpty()) {
|
||||
for (final TimeAttestation attestation : this.attestations) {
|
||||
builder.append(Timestamp.indention(indent));
|
||||
builder.append("verify " + attestation.toString() + strResult(verbosity, this.msg, null) + "\n");
|
||||
|
||||
if (attestation instanceof BitcoinBlockHeaderAttestation) {
|
||||
String tx = Utils.bytesToHex(Utils.arrayReverse(this.msg));
|
||||
builder.append(Timestamp.indention(indent) + "# Bitcoin block merkle root " + tx.toLowerCase() + "\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.ops.size() > 1) {
|
||||
TreeMap<Op, Timestamp> ordered = new TreeMap<>(this.ops);
|
||||
|
||||
for (Map.Entry<Op, Timestamp> entry : ordered.entrySet()) {
|
||||
Timestamp timestamp = entry.getValue();
|
||||
Op op = entry.getKey();
|
||||
|
||||
byte[] curRes = op.call(this.msg);
|
||||
byte[] curPar = null;
|
||||
|
||||
if (op instanceof OpBinary) {
|
||||
curPar = ((OpBinary) op).arg;
|
||||
}
|
||||
|
||||
builder.append(Timestamp.indention(indent) + " -> " + op.toString().toLowerCase() + strResult(verbosity, curPar, curRes).toLowerCase() + "\n");
|
||||
builder.append(timestamp.strTree(indent + 1, verbosity));
|
||||
}
|
||||
} else if (this.ops.size() > 0) {
|
||||
// output += com.eternitywall.ots.Timestamp.indention(indent);
|
||||
for (Map.Entry<Op, Timestamp> entry : this.ops.entrySet()) {
|
||||
Timestamp timestamp = entry.getValue();
|
||||
Op op = entry.getKey();
|
||||
|
||||
byte[] curRes = op.call(this.msg);
|
||||
byte[] curPar = null;
|
||||
|
||||
if (op instanceof OpBinary) {
|
||||
curPar = ((OpBinary) op).arg;
|
||||
}
|
||||
|
||||
builder.append(Timestamp.indention(indent) + op.toString().toLowerCase() + strResult(verbosity, curPar, curRes).toLowerCase() + "\n");
|
||||
builder.append(timestamp.strTree(indent, verbosity));
|
||||
}
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all sub timestamps with attestations.
|
||||
*
|
||||
* @return List of all sub timestamps with attestations.
|
||||
*/
|
||||
public List<Timestamp> directlyVerified() {
|
||||
if (!this.attestations.isEmpty()) {
|
||||
List<Timestamp> list = new ArrayList<>();
|
||||
list.add(this);
|
||||
return list;
|
||||
}
|
||||
|
||||
List<Timestamp> list = new ArrayList<>();
|
||||
|
||||
for (Map.Entry<Op, Timestamp> entry : this.ops.entrySet()) {
|
||||
Timestamp ts = entry.getValue();
|
||||
//Op op = entry.getKey();
|
||||
|
||||
List<Timestamp> result = ts.directlyVerified();
|
||||
list.addAll(result);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a set of all Attestations.
|
||||
*
|
||||
* @return Set of all timestamp attestations.
|
||||
*/
|
||||
public Set<TimeAttestation> getAttestations() {
|
||||
Set set = new HashSet<TimeAttestation>();
|
||||
|
||||
for (Map.Entry<byte[], TimeAttestation> item : this.allAttestations().entrySet()) {
|
||||
//byte[] msg = item.getKey();
|
||||
TimeAttestation attestation = item.getValue();
|
||||
set.add(attestation);
|
||||
}
|
||||
|
||||
return set;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if timestamp is complete and can be verified.
|
||||
*
|
||||
* @return True if the timestamp is complete, False otherwise.
|
||||
*/
|
||||
public Boolean isTimestampComplete() {
|
||||
for (Map.Entry<byte[], TimeAttestation> item : this.allAttestations().entrySet()) {
|
||||
//byte[] msg = item.getKey();
|
||||
TimeAttestation attestation = item.getValue();
|
||||
|
||||
if (attestation instanceof BitcoinBlockHeaderAttestation) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterate over all attestations recursively
|
||||
*
|
||||
* @return Returns iterable of (msg, attestation)
|
||||
*/
|
||||
public HashMap<byte[], TimeAttestation> allAttestations() {
|
||||
HashMap<byte[], TimeAttestation> map = new HashMap<>();
|
||||
|
||||
for (TimeAttestation attestation : this.attestations) {
|
||||
map.put(this.msg, attestation);
|
||||
}
|
||||
|
||||
for (Map.Entry<Op, Timestamp> entry : this.ops.entrySet()) {
|
||||
Timestamp ts = entry.getValue();
|
||||
//Op op = entry.getKey();
|
||||
|
||||
HashMap<byte[], TimeAttestation> subMap = ts.allAttestations();
|
||||
|
||||
for (Map.Entry<byte[], TimeAttestation> item : subMap.entrySet()) {
|
||||
byte[] msg = item.getKey();
|
||||
TimeAttestation attestation = item.getValue();
|
||||
map.put(msg, attestation);
|
||||
}
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterate over all tips recursively
|
||||
*
|
||||
* @return Returns iterable of (msg, attestation)
|
||||
*/
|
||||
public Set<byte[]> allTips() {
|
||||
Set<byte[]> set = new HashSet<>();
|
||||
|
||||
if (this.ops.size() == 0) {
|
||||
set.add(this.msg);
|
||||
}
|
||||
|
||||
for (Map.Entry<Op, Timestamp> entry : this.ops.entrySet()) {
|
||||
Timestamp ts = entry.getValue();
|
||||
//Op op = entry.getKey();
|
||||
|
||||
Set<byte[]> subSet = ts.allTips();
|
||||
|
||||
for (byte[] msg : subSet) {
|
||||
set.add(msg);
|
||||
}
|
||||
}
|
||||
|
||||
return set;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare timestamps.
|
||||
*
|
||||
* @param timestamp the timestamp to compare with
|
||||
* @return Returns true if timestamps are equals
|
||||
*/
|
||||
public boolean equals(Timestamp timestamp) {
|
||||
if (Arrays.equals(this.getDigest(), timestamp.getDigest()) == false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check attestations
|
||||
if (this.attestations.size() != timestamp.attestations.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < this.attestations.size(); i++) {
|
||||
TimeAttestation ta1 = this.attestations.get(i);
|
||||
TimeAttestation ta2 = timestamp.attestations.get(i);
|
||||
|
||||
if (!(ta1.equals(ta2))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check operations
|
||||
if (this.ops.size() != timestamp.ops.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Order list of operations
|
||||
List<Map.Entry<Op, Timestamp>> list1 = sortToList(this.ops.entrySet());
|
||||
List<Map.Entry<Op, Timestamp>> list2 = sortToList(timestamp.ops.entrySet());
|
||||
|
||||
for (int i = 0; i < list1.size(); i++) {
|
||||
Op op1 = list1.get(i).getKey();
|
||||
Op op2 = list2.get(i).getKey();
|
||||
|
||||
if (!op1.equals(op2)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Timestamp t1 = list1.get(i).getValue();
|
||||
Timestamp t2 = list2.get(i).getValue();
|
||||
|
||||
if (!t1.equals(t2)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Op to current timestamp and return the sub stamp
|
||||
*
|
||||
* @param op - The operation to insert
|
||||
* @return Returns the sub timestamp
|
||||
*/
|
||||
public Timestamp add(Op op) {
|
||||
// nonce_appended_stamp = timestamp.ops.add(com.vitorpamplona.quartz.ots.op.OpAppend(os.urandom(16)))
|
||||
//Op opAppend = new OpAppend(bytes);
|
||||
|
||||
if (this.ops.containsKey(op)) {
|
||||
return this.ops.get(op);
|
||||
}
|
||||
|
||||
Timestamp stamp = new Timestamp(op.call(this.msg));
|
||||
this.ops.put(op, stamp);
|
||||
|
||||
return stamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a sorted list of all map entries.
|
||||
*
|
||||
* @param setEntries - The entries set of ops hashmap
|
||||
* @return Returns the sorted list of map entries
|
||||
*/
|
||||
public List<Map.Entry<Op, Timestamp>> sortToList(Set<Entry<Op, Timestamp>> setEntries) {
|
||||
List<Map.Entry<Op, Timestamp>> entries = new ArrayList<>(setEntries);
|
||||
Collections.sort(entries, new Comparator<Map.Entry<Op, Timestamp>>() {
|
||||
@Override
|
||||
public int compare(Entry<Op, Timestamp> a, Entry<Op, Timestamp> b) {
|
||||
return a.getKey().compareTo(b.getKey());
|
||||
}
|
||||
});
|
||||
|
||||
return entries;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
package com.vitorpamplona.quartz.ots;
|
||||
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.logging.ConsoleHandler;
|
||||
import java.util.logging.LogRecord;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.logging.SimpleFormatter;
|
||||
|
||||
/**
|
||||
* Utility functions for (mostly) manipulating byte arrays.
|
||||
*/
|
||||
public class Utils {
|
||||
/**
|
||||
* Fills a byte array with the given byte value.
|
||||
*
|
||||
* @param array the array to fill
|
||||
* @param value the value to fill the array with
|
||||
*/
|
||||
public static void arrayFill(byte[] array, byte value) {
|
||||
for (int i = 0; i < array.length; i++) {
|
||||
array[i] = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first value that is not null. If all objects are null, then it returns null.
|
||||
*
|
||||
* @param items the array of Ts
|
||||
* @param <T> This describes my type parameter
|
||||
* @return the first value that is not null. If all objects are null, then it returns null.
|
||||
* @deprecated Not used by Java OpenTimestamps itself, and doesn't offer much useful functionality.
|
||||
*/
|
||||
@Deprecated
|
||||
public static <T> T coalesce(T... items) {
|
||||
for (T i : items) {
|
||||
if (i != null) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of the byte array argument, or null if the byte array argument is null.
|
||||
*
|
||||
* @param data the array of bytes to copy
|
||||
* @return the copied byte array
|
||||
*/
|
||||
public static byte[] arraysCopy(byte[] data) {
|
||||
if (data == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
byte[] copy = new byte[data.length];
|
||||
System.arraycopy(data, 0, copy, 0, data.length);
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a byte array which is the result of concatenating the two passed in byte arrays.
|
||||
* None of the passed in arrays may be null.
|
||||
*
|
||||
* @param array1 the first array of bytes
|
||||
* @param array2 the second array of bytes
|
||||
* @return a copy of array1 and array2 concatenated together
|
||||
*/
|
||||
public static byte[] arraysConcat(byte[] array1, byte[] array2) {
|
||||
byte[] array1and2 = new byte[array1.length + array2.length];
|
||||
System.arraycopy(array1, 0, array1and2, 0, array1.length);
|
||||
System.arraycopy(array2, 0, array1and2, array1.length, array2.length);
|
||||
|
||||
return array1and2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a given length array of random bytes.
|
||||
*
|
||||
* @param length the requested length of the byte array
|
||||
* @return a given length array of random bytes
|
||||
* @throws NoSuchAlgorithmException for Java 8 implementations
|
||||
*/
|
||||
public static byte[] randBytes(int length) throws NoSuchAlgorithmException {
|
||||
//Java 6 & 7:
|
||||
SecureRandom random = new SecureRandom();
|
||||
byte[] bytes = new byte[length];
|
||||
random.nextBytes(bytes);
|
||||
|
||||
// Java 8 (even more secure):
|
||||
// SecureRandom.getInstanceStrong().nextBytes(bytes);
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reversed copy of the passed in byte array.
|
||||
*
|
||||
* @param array the byte array to reverse
|
||||
* @return a copy of the byte array, reversed
|
||||
*/
|
||||
public static byte[] arrayReverse(byte[] array) {
|
||||
byte[] reversedArray = new byte[array.length];
|
||||
|
||||
for (int i = array.length - 1, j = 0; i >= 0; i--, j++) {
|
||||
reversedArray[j] = array[i];
|
||||
}
|
||||
|
||||
return reversedArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two byte arrays.
|
||||
*
|
||||
* @param left the left byte array to compare with
|
||||
* @param right the right byte array to compare with
|
||||
* @return 0 if the arrays are identical, negative if left < right, positive if left > right
|
||||
*/
|
||||
public static int compare(byte[] left, byte[] right) {
|
||||
for (int i = 0, j = 0; i < left.length && j < right.length; i++, j++) {
|
||||
int a = (left[i] & 0xff);
|
||||
int b = (right[j] & 0xff);
|
||||
|
||||
if (a != b) {
|
||||
return a - b;
|
||||
}
|
||||
}
|
||||
|
||||
return left.length - right.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a HEX representation of the passed in byte array.
|
||||
*
|
||||
* @param bytes the byte array to convert into a string of HEX
|
||||
* @return a string representation of the byte array
|
||||
*/
|
||||
public static String bytesToHex(byte[] bytes) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
for (byte b : bytes) {
|
||||
sb.append(String.format("%02X", b));
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a HEX representation to its byte array representation. The passed in string must not be null.
|
||||
*
|
||||
* @param s a string in HEX format to be converted to a corresponding byte array
|
||||
* @return the byte array representation of the string in HEX format
|
||||
* @throws IllegalArgumentException if the passed in HEX string can't be converted to a byte array
|
||||
*/
|
||||
public static byte[] hexToBytes(String s) throws IllegalArgumentException {
|
||||
int len = s.length();
|
||||
|
||||
if (len % 2 == 1) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
byte[] data = new byte[len / 2];
|
||||
|
||||
for (int i = 0; i < len; i += 2) {
|
||||
if ((Character.digit(s.charAt(i), 16) == -1) || (Character.digit(s.charAt(i + 1), 16) == -1)) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
|
||||
+ Character.digit(s.charAt(i + 1), 16));
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string with the first letter uppercase.
|
||||
*
|
||||
* @param string the string to get its first character converted to uppercase
|
||||
* @return the string, with its first character converted to uppercase
|
||||
*/
|
||||
public static String toUpperFirstLetter(String string) {
|
||||
return string.substring(0, 1).toUpperCase() + string.substring(1).toLowerCase();
|
||||
}
|
||||
|
||||
// TODO: This is not the way to do logging. Fix later, possibly with slf4j annotation. Need to read up on the subject.
|
||||
public static Logger getLogger(String name) {
|
||||
Logger log = Logger.getLogger(name);
|
||||
ConsoleHandler handler = new ConsoleHandler();
|
||||
|
||||
handler.setFormatter(new SimpleFormatter() {
|
||||
@Override
|
||||
public synchronized String format(LogRecord lr) {
|
||||
return lr.getMessage() + "\r\n";
|
||||
}
|
||||
});
|
||||
|
||||
log.setUseParentHandlers(false);
|
||||
log.addHandler(handler);
|
||||
|
||||
return log;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.vitorpamplona.quartz.ots;
|
||||
|
||||
import java.text.DateFormatSymbols;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Class that lets us compare, sort, store and print timestamps.
|
||||
*/
|
||||
public class VerifyResult implements Comparable<VerifyResult> {
|
||||
public static enum Chains {
|
||||
BITCOIN, LITECOIN, ETHEREUM
|
||||
}
|
||||
|
||||
public Long timestamp;
|
||||
public int height;
|
||||
|
||||
public VerifyResult(Long timestamp, int height) {
|
||||
this.timestamp = timestamp;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns, if existing, a string representation describing the existence of a block attest
|
||||
*/
|
||||
public String toString() {
|
||||
if (height == 0 || timestamp == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
String pattern = "YYYY-MM-dd z";
|
||||
Locale locale = new Locale("en", "UK");
|
||||
DateFormatSymbols dateFormatSymbols = new DateFormatSymbols(locale);
|
||||
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(pattern, dateFormatSymbols);
|
||||
String string = simpleDateFormat.format(new Date(timestamp * 1000));
|
||||
|
||||
return "block " + String.valueOf(height) + " attests data existed as of " + string;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(VerifyResult vr) {
|
||||
return this.height - vr.height;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
VerifyResult vr = (VerifyResult) obj;
|
||||
return this.timestamp == vr.timestamp && this.height == vr.height;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return ((int) (long) (this.timestamp)) ^ this.height;
|
||||
}
|
||||
}
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
package com.vitorpamplona.quartz.ots.attestation;
|
||||
|
||||
import com.vitorpamplona.quartz.ots.BlockHeader;
|
||||
import com.vitorpamplona.quartz.ots.StreamDeserializationContext;
|
||||
import com.vitorpamplona.quartz.ots.StreamSerializationContext;
|
||||
import com.vitorpamplona.quartz.ots.Utils;
|
||||
import com.vitorpamplona.quartz.ots.exceptions.VerificationException;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Bitcoin Block Header Attestation.
|
||||
* The commitment digest will be the merkleroot of the blockheader.
|
||||
* The block height is recorded so that looking up the correct block header in
|
||||
* an external block header database doesn't require every header to be stored
|
||||
* locally (33MB and counting). (remember that a memory-constrained local
|
||||
* client can save an MMR that commits to all blocks, and use an external service to fill
|
||||
* in pruned details).
|
||||
* Otherwise no additional redundant data about the block header is recorded.
|
||||
* This is very intentional: since the attestation contains (nearly) the
|
||||
* absolute bare minimum amount of data, we encourage implementations to do
|
||||
* the correct thing and get the block header from a by-height index, check
|
||||
* that the merkleroots match, and then calculate the time from the header
|
||||
* information. Providing more data would encourage implementations to cheat.
|
||||
* Remember that the only thing that would invalidate the block height is a
|
||||
* reorg, but in the event of a reorg the merkleroot will be invalid anyway,
|
||||
* so there's no point to recording data in the attestation like the header
|
||||
* itself. At best that would just give us extra confirmation that a reorg
|
||||
* made the attestation invalid; reorgs deep enough to invalidate timestamps are
|
||||
* exceptionally rare events anyway, so better to just tell the user the timestamp
|
||||
* can't be verified rather than add almost-never tested code to handle that case
|
||||
* more gracefully.
|
||||
*
|
||||
* @see TimeAttestation
|
||||
*/
|
||||
public class BitcoinBlockHeaderAttestation extends TimeAttestation {
|
||||
|
||||
public static byte[] _TAG = {(byte) 0x05, (byte) 0x88, (byte) 0x96, (byte) 0x0d, (byte) 0x73, (byte) 0xd7, (byte) 0x19, (byte) 0x01};
|
||||
public static String chain = "bitcoin";
|
||||
|
||||
@Override
|
||||
public byte[] _TAG() {
|
||||
return BitcoinBlockHeaderAttestation._TAG;
|
||||
}
|
||||
|
||||
private int height = 0;
|
||||
|
||||
public int getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
public BitcoinBlockHeaderAttestation(int height_) {
|
||||
super();
|
||||
this.height = height_;
|
||||
}
|
||||
|
||||
public static BitcoinBlockHeaderAttestation deserialize(StreamDeserializationContext ctxPayload) {
|
||||
int height = ctxPayload.readVaruint();
|
||||
|
||||
return new BitcoinBlockHeaderAttestation(height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serializePayload(StreamSerializationContext ctx) {
|
||||
ctx.writeVaruint(this.height);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "BitcoinBlockHeaderAttestation(" + this.height + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(TimeAttestation o) {
|
||||
BitcoinBlockHeaderAttestation ob = (BitcoinBlockHeaderAttestation) o;
|
||||
|
||||
return this.height - ob.height;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof BitcoinBlockHeaderAttestation)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Arrays.equals(this._TAG(), ((BitcoinBlockHeaderAttestation) obj)._TAG())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.height != ((BitcoinBlockHeaderAttestation) obj).height) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Arrays.hashCode(this._TAG()) ^ this.height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify attestation against a Bitcoin block header.
|
||||
* @param digest the digest
|
||||
* @param block the Bitcoin block header
|
||||
* @return the block time on success; raises VerificationError on failure.
|
||||
* @throws VerificationException verification exception
|
||||
*/
|
||||
public Long verifyAgainstBlockheader(byte[] digest, BlockHeader block) throws VerificationException {
|
||||
if (digest.length != 32) {
|
||||
throw new VerificationException("Expected digest with length 32 bytes; got " + digest.length + " bytes");
|
||||
} else if (!Arrays.equals(digest, Utils.hexToBytes(block.getMerkleroot()))) {
|
||||
throw new VerificationException("Digest does not match merkleroot");
|
||||
}
|
||||
|
||||
return block.getTime();
|
||||
}
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
package com.vitorpamplona.quartz.ots.attestation;
|
||||
|
||||
import com.vitorpamplona.quartz.ots.StreamDeserializationContext;
|
||||
import com.vitorpamplona.quartz.ots.StreamSerializationContext;
|
||||
import com.vitorpamplona.quartz.ots.Utils;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Ethereum Block Header Attestation.
|
||||
*
|
||||
* @see TimeAttestation
|
||||
*/
|
||||
public class EthereumBlockHeaderAttestation extends TimeAttestation {
|
||||
|
||||
public static byte[] _TAG = {(byte) 0x30, (byte) 0xfe, (byte) 0x80, (byte) 0x87, (byte) 0xb5, (byte) 0xc7, (byte) 0xea, (byte) 0xd7};
|
||||
public static String chain = "ethereum";
|
||||
|
||||
@Override
|
||||
public byte[] _TAG() {
|
||||
return EthereumBlockHeaderAttestation._TAG;
|
||||
}
|
||||
|
||||
private int height = 0;
|
||||
|
||||
public int getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
EthereumBlockHeaderAttestation(int height_) {
|
||||
super();
|
||||
this.height = height_;
|
||||
}
|
||||
|
||||
public static EthereumBlockHeaderAttestation deserialize(StreamDeserializationContext ctxPayload) {
|
||||
int height = ctxPayload.readVaruint();
|
||||
|
||||
return new EthereumBlockHeaderAttestation(height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serializePayload(StreamSerializationContext ctx) {
|
||||
ctx.writeVaruint(this.height);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "EthereumBlockHeaderAttestation(" + this.height + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(TimeAttestation o) {
|
||||
EthereumBlockHeaderAttestation ob = (EthereumBlockHeaderAttestation) o;
|
||||
|
||||
return this.height - ob.height;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof EthereumBlockHeaderAttestation)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Arrays.equals(this._TAG(), ((EthereumBlockHeaderAttestation) obj)._TAG())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.height != ((EthereumBlockHeaderAttestation) obj).height) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Arrays.hashCode(this._TAG()) ^ this.height;
|
||||
}
|
||||
}
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
package com.vitorpamplona.quartz.ots.attestation;
|
||||
|
||||
import com.vitorpamplona.quartz.ots.BlockHeader;
|
||||
import com.vitorpamplona.quartz.ots.StreamDeserializationContext;
|
||||
import com.vitorpamplona.quartz.ots.StreamSerializationContext;
|
||||
import com.vitorpamplona.quartz.ots.Utils;
|
||||
import com.vitorpamplona.quartz.ots.exceptions.VerificationException;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Litecoin Block Header Attestation.
|
||||
*
|
||||
* @see TimeAttestation
|
||||
*/
|
||||
public class LitecoinBlockHeaderAttestation extends TimeAttestation {
|
||||
|
||||
public static byte[] _TAG = {(byte) 0x06, (byte) 0x86, (byte) 0x9a, (byte) 0x0d, (byte) 0x73, (byte) 0xd7, (byte) 0x1b, (byte) 0x45};
|
||||
|
||||
public static String chain = "litecoin";
|
||||
|
||||
@Override
|
||||
public byte[] _TAG() {
|
||||
return LitecoinBlockHeaderAttestation._TAG;
|
||||
}
|
||||
|
||||
private int height = 0;
|
||||
|
||||
public int getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
public LitecoinBlockHeaderAttestation(int height_) {
|
||||
super();
|
||||
this.height = height_;
|
||||
}
|
||||
|
||||
public static LitecoinBlockHeaderAttestation deserialize(StreamDeserializationContext ctxPayload) {
|
||||
int height = ctxPayload.readVaruint();
|
||||
|
||||
return new LitecoinBlockHeaderAttestation(height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serializePayload(StreamSerializationContext ctx) {
|
||||
ctx.writeVaruint(this.height);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "LitecoinBlockHeaderAttestation(" + this.height + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(TimeAttestation o) {
|
||||
LitecoinBlockHeaderAttestation ob = (LitecoinBlockHeaderAttestation) o;
|
||||
|
||||
return this.height - ob.height;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof LitecoinBlockHeaderAttestation)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Arrays.equals(this._TAG(), ((LitecoinBlockHeaderAttestation) obj)._TAG())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.height != ((LitecoinBlockHeaderAttestation) obj).height) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Arrays.hashCode(this._TAG()) ^ this.height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify attestation against a Litecoin block header.
|
||||
* @param digest the digest
|
||||
* @param block the Litecoin block header
|
||||
* @return the block time on success; raises VerificationError on failure.
|
||||
* @throws VerificationException verification exception
|
||||
*/
|
||||
public Long verifyAgainstBlockheader(byte[] digest, BlockHeader block) throws VerificationException {
|
||||
if (digest.length != 32) {
|
||||
throw new VerificationException("Expected digest with length 32 bytes; got " + digest.length + " bytes");
|
||||
} else if (!Arrays.equals(digest, Utils.hexToBytes(block.getMerkleroot()))) {
|
||||
throw new VerificationException("Digest does not match merkleroot");
|
||||
}
|
||||
|
||||
return block.getTime();
|
||||
}
|
||||
}
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
package com.vitorpamplona.quartz.ots.attestation;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.vitorpamplona.quartz.ots.StreamDeserializationContext;
|
||||
import com.vitorpamplona.quartz.ots.StreamSerializationContext;
|
||||
import com.vitorpamplona.quartz.ots.Utils;
|
||||
|
||||
import com.vitorpamplona.quartz.ots.exceptions.DeserializationException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Pending attestations.
|
||||
* Commitment has been recorded in a remote calendar for future attestation,
|
||||
* and we have a URI to find a more complete timestamp in the future.
|
||||
* Nothing other than the URI is recorded, nor is there provision made to add
|
||||
* extra metadata (other than the URI) in future upgrades. The rational here
|
||||
* is that remote calendars promise to keep commitments indefinitely, so from
|
||||
* the moment they are created it should be possible to find the commitment in
|
||||
* the calendar. Thus if you're not satisfied with the local verifiability of
|
||||
* a timestamp, the correct thing to do is just ask the remote calendar if
|
||||
* additional attestations are available and/or when they'll be available.
|
||||
* While we could additional metadata like what types of attestations the
|
||||
* remote calendar expects to be able to provide in the future, that metadata
|
||||
* can easily change in the future too. Given that we don't expect timestamps
|
||||
* to normally have more than a small number of remote calendar attestations,
|
||||
* it'd be better to have verifiers get the most recent status of such
|
||||
* information (possibly with appropriate negative response caching).
|
||||
*
|
||||
* @see TimeAttestation
|
||||
*/
|
||||
public class PendingAttestation extends TimeAttestation {
|
||||
|
||||
public static byte[] _TAG = {(byte) 0x83, (byte) 0xdf, (byte) 0xe3, (byte) 0x0d, (byte) 0x2e, (byte) 0xf9, (byte) 0x0c, (byte) 0x8e};
|
||||
|
||||
@Override
|
||||
public byte[] _TAG() {
|
||||
return PendingAttestation._TAG;
|
||||
}
|
||||
|
||||
public static int _MAX_URI_LENGTH = 1000;
|
||||
|
||||
public static String _ALLOWED_URI_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._/:";
|
||||
|
||||
private byte[] uri;
|
||||
|
||||
public byte[] getUri() {
|
||||
return uri;
|
||||
}
|
||||
|
||||
public PendingAttestation(byte[] uri_) {
|
||||
super();
|
||||
this.uri = uri_;
|
||||
}
|
||||
|
||||
public static boolean checkUri(byte[] uri) {
|
||||
if (uri.length > PendingAttestation._MAX_URI_LENGTH) {
|
||||
System.err.print("URI exceeds maximum length");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < uri.length; i++) {
|
||||
Character c = String.format("%c", uri[i]).charAt(0);
|
||||
|
||||
if (PendingAttestation._ALLOWED_URI_CHARS.indexOf(c) < 0) {
|
||||
Log.e("OpenTimestamp","URI contains invalid character ");
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static PendingAttestation deserialize(StreamDeserializationContext ctxPayload)
|
||||
throws DeserializationException {
|
||||
|
||||
byte[] utf8Uri;
|
||||
try {
|
||||
utf8Uri = ctxPayload.readVarbytes(PendingAttestation._MAX_URI_LENGTH);
|
||||
} catch (DeserializationException e) {
|
||||
Log.e("OpenTimestamp","URI too long and thus invalid: ");
|
||||
throw new DeserializationException("Invalid URI: ");
|
||||
}
|
||||
|
||||
if (PendingAttestation.checkUri(utf8Uri) == false) {
|
||||
Log.e("OpenTimestamp","Invalid URI: ");
|
||||
throw new DeserializationException("Invalid URI: ");
|
||||
}
|
||||
|
||||
return new PendingAttestation(utf8Uri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serializePayload(StreamSerializationContext ctx) {
|
||||
ctx.writeVarbytes(this.uri);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "PendingAttestation(\'" + new String(this.uri, StandardCharsets.UTF_8) + "\')";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(TimeAttestation o) {
|
||||
PendingAttestation opa = (PendingAttestation) o;
|
||||
|
||||
return Utils.compare(this.uri, opa.uri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof PendingAttestation)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Arrays.equals(this._TAG(), ((PendingAttestation) obj)._TAG())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Arrays.equals(this.uri, ((PendingAttestation) obj).uri)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Arrays.hashCode(this._TAG()) ^ Arrays.hashCode(this.uri);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package com.vitorpamplona.quartz.ots.attestation;
|
||||
|
||||
import com.vitorpamplona.quartz.ots.StreamDeserializationContext;
|
||||
import com.vitorpamplona.quartz.ots.StreamSerializationContext;
|
||||
import com.vitorpamplona.quartz.ots.Utils;
|
||||
|
||||
import com.vitorpamplona.quartz.ots.exceptions.DeserializationException;
|
||||
import java.util.Arrays;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Class representing {@link com.vitorpamplona.quartz.ots.Timestamp} signature verification
|
||||
*/
|
||||
public abstract class TimeAttestation implements Comparable<TimeAttestation> {
|
||||
|
||||
public static int _TAG_SIZE = 8;
|
||||
|
||||
public static int _MAX_PAYLOAD_SIZE = 8192;
|
||||
|
||||
public byte[] _TAG;
|
||||
|
||||
public byte[] _TAG() {
|
||||
return new byte[]{};
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize a general Time Attestation to the specific subclass Attestation.
|
||||
*
|
||||
* @param ctx The stream deserialization context.
|
||||
* @return The specific subclass Attestation.
|
||||
*/
|
||||
public static TimeAttestation deserialize(StreamDeserializationContext ctx) throws DeserializationException {
|
||||
// console.log('attestation deserialize');
|
||||
|
||||
byte[] tag = ctx.readBytes(_TAG_SIZE);
|
||||
// console.log('tag: ', com.vitorpamplona.quartz.ots.Utils.bytesToHex(tag));
|
||||
|
||||
byte[] serializedAttestation = ctx.readVarbytes(_MAX_PAYLOAD_SIZE);
|
||||
// console.log('serializedAttestation: ', com.vitorpamplona.quartz.ots.Utils.bytesToHex(serializedAttestation));
|
||||
|
||||
StreamDeserializationContext ctxPayload = new StreamDeserializationContext(serializedAttestation);
|
||||
|
||||
/* eslint no-use-before-define: ["error", { "classes": false }] */
|
||||
if (Arrays.equals(tag, PendingAttestation._TAG) == true) {
|
||||
return PendingAttestation.deserialize(ctxPayload);
|
||||
} else if (Arrays.equals(tag, BitcoinBlockHeaderAttestation._TAG) == true) {
|
||||
return BitcoinBlockHeaderAttestation.deserialize(ctxPayload);
|
||||
} else if (Arrays.equals(tag, LitecoinBlockHeaderAttestation._TAG) == true) {
|
||||
return LitecoinBlockHeaderAttestation.deserialize(ctxPayload);
|
||||
} else if (Arrays.equals(tag, EthereumBlockHeaderAttestation._TAG) == true) {
|
||||
return EthereumBlockHeaderAttestation.deserialize(ctxPayload);
|
||||
}
|
||||
|
||||
return new UnknownAttestation(tag, serializedAttestation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize a a general Time Attestation to the specific subclass Attestation.
|
||||
*
|
||||
* @param ctx The output stream serialization context.
|
||||
*/
|
||||
public void serialize(StreamSerializationContext ctx) {
|
||||
ctx.writeBytes(this._TAG());
|
||||
StreamSerializationContext ctxPayload = new StreamSerializationContext();
|
||||
serializePayload(ctxPayload);
|
||||
ctx.writeVarbytes(ctxPayload.getOutput());
|
||||
}
|
||||
|
||||
public void serializePayload(StreamSerializationContext ctxPayload) {
|
||||
// TODO: Is this intentional?
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(TimeAttestation o) {
|
||||
int deltaTag = Utils.compare(this._TAG(), o._TAG());
|
||||
|
||||
if (deltaTag == 0) {
|
||||
return this.compareTo(o);
|
||||
} else {
|
||||
return deltaTag;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.vitorpamplona.quartz.ots.attestation;
|
||||
|
||||
import com.vitorpamplona.quartz.ots.StreamDeserializationContext;
|
||||
import com.vitorpamplona.quartz.ots.StreamSerializationContext;
|
||||
import com.vitorpamplona.quartz.ots.Utils;
|
||||
|
||||
import com.vitorpamplona.quartz.ots.exceptions.DeserializationException;
|
||||
import java.util.Arrays;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Placeholder for attestations that don't support
|
||||
*
|
||||
* @see TimeAttestation
|
||||
*/
|
||||
public class UnknownAttestation extends TimeAttestation {
|
||||
|
||||
byte[] payload;
|
||||
|
||||
public static byte[] _TAG = new byte[]{};
|
||||
|
||||
@Override
|
||||
public byte[] _TAG() {
|
||||
return _TAG;
|
||||
}
|
||||
|
||||
UnknownAttestation(byte[] tag, byte[] payload) {
|
||||
super();
|
||||
this._TAG = tag;
|
||||
this.payload = payload;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serializePayload(StreamSerializationContext ctx) {
|
||||
ctx.writeBytes(this.payload);
|
||||
}
|
||||
|
||||
public static UnknownAttestation deserialize(StreamDeserializationContext ctxPayload, byte[] tag) throws DeserializationException {
|
||||
byte[] payload = ctxPayload.readVarbytes(_MAX_PAYLOAD_SIZE);
|
||||
|
||||
return new UnknownAttestation(tag, payload);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "UnknownAttestation " + Utils.bytesToHex(this._TAG()) + ' ' + Utils.bytesToHex(this.payload);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(TimeAttestation o) {
|
||||
UnknownAttestation ota = (UnknownAttestation) o;
|
||||
|
||||
return Utils.compare(this.payload, ota.payload);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof UnknownAttestation)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Arrays.equals(this._TAG(), ((UnknownAttestation) obj)._TAG())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Arrays.equals(this.payload, ((UnknownAttestation) obj).payload)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Arrays.hashCode(this._TAG()) ^ Arrays.hashCode(this.payload);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.vitorpamplona.quartz.ots.crypto;
|
||||
|
||||
/**
|
||||
* Message digest interface
|
||||
*/
|
||||
public interface Digest {
|
||||
/**
|
||||
* Return the algorithm name
|
||||
*
|
||||
* @return the algorithm name
|
||||
*/
|
||||
public String getAlgorithmName();
|
||||
|
||||
/**
|
||||
* Return the size, in bytes, of the digest produced by this message digest.
|
||||
*
|
||||
* @return the size, in bytes, of the digest produced by this message digest.
|
||||
*/
|
||||
public int getDigestSize();
|
||||
|
||||
/**
|
||||
* Update the message digest with a single byte.
|
||||
*
|
||||
* @param in the input byte to be entered.
|
||||
*/
|
||||
public void update(byte in);
|
||||
|
||||
/**
|
||||
* Update the message digest with a block of bytes.
|
||||
*
|
||||
* @param in the byte array containing the data.
|
||||
* @param inOff the offset into the byte array where the data starts.
|
||||
* @param len the length of the data.
|
||||
*/
|
||||
public void update(byte[] in, int inOff, int len);
|
||||
|
||||
/**
|
||||
* Close the digest, producing the final digest value. The doFinal
|
||||
* call also resets the digest.
|
||||
*
|
||||
* @param out the array the digest is to be copied into.
|
||||
* @param outOff the offset into the out array the digest is to start at.
|
||||
* @return something
|
||||
* @see #reset()
|
||||
*/
|
||||
public int doFinal(byte[] out, int outOff);
|
||||
|
||||
/**
|
||||
* Reset the digest back to it's initial state.
|
||||
*/
|
||||
public void reset();
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.vitorpamplona.quartz.ots.crypto;
|
||||
|
||||
public interface ExtendedDigest extends Digest {
|
||||
/**
|
||||
* Return the size in bytes of the internal buffer the digest applies it's compression
|
||||
* function to.
|
||||
*
|
||||
* @return byte length of the digests internal buffer.
|
||||
*/
|
||||
public int getByteLength();
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
package com.vitorpamplona.quartz.ots.crypto;
|
||||
|
||||
/**
|
||||
* Base implementation of MD4 family style digest as outlined in
|
||||
* "Handbook of Applied Cryptography", pages 344 - 347.
|
||||
*/
|
||||
public abstract class GeneralDigest implements ExtendedDigest, Memoable {
|
||||
|
||||
private static final int BYTE_LENGTH = 64;
|
||||
|
||||
private final byte[] xBuf = new byte[4];
|
||||
private int xBufOff;
|
||||
|
||||
private long byteCount;
|
||||
|
||||
/**
|
||||
* Standard constructor
|
||||
*/
|
||||
protected GeneralDigest() {
|
||||
xBufOff = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy constructor. We are using copy constructors in place
|
||||
* of the Object.clone() interface as this interface is not
|
||||
* supported by J2ME.
|
||||
*
|
||||
* @param t the GeneralDigest
|
||||
*/
|
||||
protected GeneralDigest(GeneralDigest t) {
|
||||
copyIn(t);
|
||||
}
|
||||
|
||||
protected GeneralDigest(byte[] encodedState) {
|
||||
System.arraycopy(encodedState, 0, xBuf, 0, xBuf.length);
|
||||
xBufOff = Pack.bigEndianToInt(encodedState, 4);
|
||||
byteCount = Pack.bigEndianToLong(encodedState, 8);
|
||||
}
|
||||
|
||||
protected void copyIn(GeneralDigest t) {
|
||||
System.arraycopy(t.xBuf, 0, xBuf, 0, t.xBuf.length);
|
||||
|
||||
xBufOff = t.xBufOff;
|
||||
byteCount = t.byteCount;
|
||||
}
|
||||
|
||||
public void update(byte in) {
|
||||
xBuf[xBufOff++] = in;
|
||||
|
||||
if (xBufOff == xBuf.length) {
|
||||
processWord(xBuf, 0);
|
||||
xBufOff = 0;
|
||||
}
|
||||
|
||||
byteCount++;
|
||||
}
|
||||
|
||||
public void update(byte[] in, int inOff, int len) {
|
||||
len = Math.max(0, len);
|
||||
|
||||
//
|
||||
// fill the current word
|
||||
//
|
||||
int i = 0;
|
||||
|
||||
if (xBufOff != 0) {
|
||||
while (i < len) {
|
||||
xBuf[xBufOff++] = in[inOff + i++];
|
||||
|
||||
if (xBufOff == 4) {
|
||||
processWord(xBuf, 0);
|
||||
xBufOff = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// process whole words.
|
||||
//
|
||||
int limit = ((len - i) & ~3) + i;
|
||||
|
||||
for (; i < limit; i += 4) {
|
||||
processWord(in, inOff + i);
|
||||
}
|
||||
|
||||
//
|
||||
// load in the remainder.
|
||||
//
|
||||
while (i < len) {
|
||||
xBuf[xBufOff++] = in[inOff + i++];
|
||||
}
|
||||
|
||||
byteCount += len;
|
||||
}
|
||||
|
||||
public void finish() {
|
||||
long bitLength = (byteCount << 3);
|
||||
|
||||
//
|
||||
// add the pad bytes.
|
||||
//
|
||||
update((byte) 128);
|
||||
|
||||
while (xBufOff != 0) {
|
||||
update((byte) 0);
|
||||
}
|
||||
|
||||
processLength(bitLength);
|
||||
processBlock();
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
byteCount = 0;
|
||||
|
||||
xBufOff = 0;
|
||||
|
||||
for (int i = 0; i < xBuf.length; i++) {
|
||||
xBuf[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
protected void populateState(byte[] state) {
|
||||
System.arraycopy(xBuf, 0, state, 0, xBufOff);
|
||||
Pack.intToBigEndian(xBufOff, state, 4);
|
||||
Pack.longToBigEndian(byteCount, state, 8);
|
||||
}
|
||||
|
||||
public int getByteLength() {
|
||||
return BYTE_LENGTH;
|
||||
}
|
||||
|
||||
protected abstract void processWord(byte[] in, int inOff);
|
||||
|
||||
protected abstract void processLength(long bitLength);
|
||||
|
||||
protected abstract void processBlock();
|
||||
}
|
||||
@@ -0,0 +1,473 @@
|
||||
package com.vitorpamplona.quartz.ots.crypto;
|
||||
|
||||
import com.vitorpamplona.quartz.ots.Utils;
|
||||
|
||||
/**
|
||||
* Implementation of Keccak based on following KeccakNISTInterface.c from http://keccak.noekeon.org/
|
||||
* <p>
|
||||
* Following the naming conventions used in the C source code to enable easy review of the implementation.
|
||||
*/
|
||||
public class KeccakDigest implements ExtendedDigest {
|
||||
|
||||
private static long[] KeccakRoundConstants = keccakInitializeRoundConstants();
|
||||
|
||||
private static int[] KeccakRhoOffsets = keccakInitializeRhoOffsets();
|
||||
|
||||
private static long[] keccakInitializeRoundConstants() {
|
||||
long[] keccakRoundConstants = new long[24];
|
||||
byte[] LFSRstate = new byte[1];
|
||||
|
||||
LFSRstate[0] = 0x01;
|
||||
int i, j, bitPosition;
|
||||
|
||||
for (i = 0; i < 24; i++) {
|
||||
keccakRoundConstants[i] = 0;
|
||||
|
||||
for (j = 0; j < 7; j++) {
|
||||
bitPosition = (1 << j) - 1;
|
||||
|
||||
if (LFSR86540(LFSRstate)) {
|
||||
keccakRoundConstants[i] ^= 1L << bitPosition;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return keccakRoundConstants;
|
||||
}
|
||||
|
||||
private static boolean LFSR86540(byte[] LFSR) {
|
||||
boolean result = (((LFSR[0]) & 0x01) != 0);
|
||||
|
||||
if (((LFSR[0]) & 0x80) != 0) {
|
||||
LFSR[0] = (byte) (((LFSR[0]) << 1) ^ 0x71);
|
||||
} else {
|
||||
LFSR[0] <<= 1;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static int[] keccakInitializeRhoOffsets() {
|
||||
int[] keccakRhoOffsets = new int[25];
|
||||
int x, y, t, newX, newY;
|
||||
|
||||
keccakRhoOffsets[(((0) % 5) + 5 * ((0) % 5))] = 0;
|
||||
x = 1;
|
||||
y = 0;
|
||||
|
||||
for (t = 0; t < 24; t++) {
|
||||
keccakRhoOffsets[(((x) % 5) + 5 * ((y) % 5))] = ((t + 1) * (t + 2) / 2) % 64;
|
||||
newX = (0 * x + 1 * y) % 5;
|
||||
newY = (2 * x + 3 * y) % 5;
|
||||
x = newX;
|
||||
y = newY;
|
||||
}
|
||||
|
||||
return keccakRhoOffsets;
|
||||
}
|
||||
|
||||
protected byte[] state = new byte[(1600 / 8)];
|
||||
protected byte[] dataQueue = new byte[(1536 / 8)];
|
||||
protected int rate;
|
||||
protected int bitsInQueue;
|
||||
protected int fixedOutputLength;
|
||||
protected boolean squeezing;
|
||||
protected int bitsAvailableForSqueezing;
|
||||
protected byte[] chunk;
|
||||
protected byte[] oneByte;
|
||||
|
||||
private void clearDataQueueSection(int off, int len) {
|
||||
for (int i = off; i != off + len; i++) {
|
||||
dataQueue[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public KeccakDigest() {
|
||||
this(288);
|
||||
}
|
||||
|
||||
public KeccakDigest(int bitLength) {
|
||||
init(bitLength);
|
||||
}
|
||||
|
||||
public KeccakDigest(KeccakDigest source) {
|
||||
System.arraycopy(source.state, 0, this.state, 0, source.state.length);
|
||||
System.arraycopy(source.dataQueue, 0, this.dataQueue, 0, source.dataQueue.length);
|
||||
this.rate = source.rate;
|
||||
this.bitsInQueue = source.bitsInQueue;
|
||||
this.fixedOutputLength = source.fixedOutputLength;
|
||||
this.squeezing = source.squeezing;
|
||||
this.bitsAvailableForSqueezing = source.bitsAvailableForSqueezing;
|
||||
this.chunk = Utils.arraysCopy(source.chunk);
|
||||
this.oneByte = Utils.arraysCopy(source.oneByte);
|
||||
}
|
||||
|
||||
public String getAlgorithmName() {
|
||||
return "Keccak-" + fixedOutputLength;
|
||||
}
|
||||
|
||||
public int getDigestSize() {
|
||||
return fixedOutputLength / 8;
|
||||
}
|
||||
|
||||
public void update(byte in) {
|
||||
oneByte[0] = in;
|
||||
|
||||
absorb(oneByte, 0, 8L);
|
||||
}
|
||||
|
||||
public void update(byte[] in, int inOff, int len) {
|
||||
absorb(in, inOff, len * 8L);
|
||||
}
|
||||
|
||||
public int doFinal(byte[] out, int outOff) {
|
||||
squeeze(out, outOff, fixedOutputLength);
|
||||
reset();
|
||||
|
||||
return getDigestSize();
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO Possible API change to support partial-byte suffixes.
|
||||
*/
|
||||
protected int doFinal(byte[] out, int outOff, byte partialByte, int partialBits) {
|
||||
if (partialBits > 0) {
|
||||
oneByte[0] = partialByte;
|
||||
absorb(oneByte, 0, partialBits);
|
||||
}
|
||||
|
||||
squeeze(out, outOff, fixedOutputLength);
|
||||
reset();
|
||||
|
||||
return getDigestSize();
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
init(fixedOutputLength);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the size of block that the compression function is applied to in bytes.
|
||||
*
|
||||
* @return internal byte length of a block.
|
||||
*/
|
||||
public int getByteLength() {
|
||||
return rate / 8;
|
||||
}
|
||||
|
||||
private void init(int bitLength) {
|
||||
switch (bitLength) {
|
||||
case 288:
|
||||
initSponge(1024, 576);
|
||||
break;
|
||||
case 128:
|
||||
initSponge(1344, 256);
|
||||
break;
|
||||
case 224:
|
||||
initSponge(1152, 448);
|
||||
break;
|
||||
case 256:
|
||||
initSponge(1088, 512);
|
||||
break;
|
||||
case 384:
|
||||
initSponge(832, 768);
|
||||
break;
|
||||
case 512:
|
||||
initSponge(576, 1024);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("bitLength must be one of 128, 224, 256, 288, 384, or 512.");
|
||||
}
|
||||
}
|
||||
|
||||
private void initSponge(int rate, int capacity) {
|
||||
if (rate + capacity != 1600) {
|
||||
throw new IllegalStateException("rate + capacity != 1600");
|
||||
}
|
||||
|
||||
if ((rate <= 0) || (rate >= 1600) || ((rate % 64) != 0)) {
|
||||
throw new IllegalStateException("invalid rate value");
|
||||
}
|
||||
|
||||
this.rate = rate;
|
||||
// this is never read, need to check to see why we want to save it
|
||||
// this.capacity = capacity;
|
||||
Utils.arrayFill(this.state, (byte) 0);
|
||||
Utils.arrayFill(this.dataQueue, (byte) 0);
|
||||
this.bitsInQueue = 0;
|
||||
this.squeezing = false;
|
||||
this.bitsAvailableForSqueezing = 0;
|
||||
this.fixedOutputLength = capacity / 2;
|
||||
this.chunk = new byte[rate / 8];
|
||||
this.oneByte = new byte[1];
|
||||
}
|
||||
|
||||
private void absorbQueue() {
|
||||
KeccakAbsorb(state, dataQueue, rate / 8);
|
||||
bitsInQueue = 0;
|
||||
}
|
||||
|
||||
protected void absorb(byte[] data, int off, long databitlen) {
|
||||
long i, j, wholeBlocks;
|
||||
|
||||
if ((bitsInQueue % 8) != 0) {
|
||||
throw new IllegalStateException("attempt to absorb with odd length queue");
|
||||
}
|
||||
|
||||
if (squeezing) {
|
||||
throw new IllegalStateException("attempt to absorb while squeezing");
|
||||
}
|
||||
|
||||
i = 0;
|
||||
|
||||
while (i < databitlen) {
|
||||
if ((bitsInQueue == 0) && (databitlen >= rate) && (i <= (databitlen - rate))) {
|
||||
wholeBlocks = (databitlen - i) / rate;
|
||||
|
||||
for (j = 0; j < wholeBlocks; j++) {
|
||||
System.arraycopy(data, (int) (off + (i / 8) + (j * chunk.length)), chunk, 0, chunk.length);
|
||||
|
||||
// displayIntermediateValues.displayBytes(1, "Block to be absorbed", curData, rate / 8);
|
||||
|
||||
KeccakAbsorb(state, chunk, chunk.length);
|
||||
}
|
||||
|
||||
i += wholeBlocks * rate;
|
||||
} else {
|
||||
int partialBlock = (int) (databitlen - i);
|
||||
|
||||
if (partialBlock + bitsInQueue > rate) {
|
||||
partialBlock = rate - bitsInQueue;
|
||||
}
|
||||
|
||||
int partialByte = partialBlock % 8;
|
||||
partialBlock -= partialByte;
|
||||
System.arraycopy(data, off + (int) (i / 8), dataQueue, bitsInQueue / 8, partialBlock / 8);
|
||||
|
||||
bitsInQueue += partialBlock;
|
||||
i += partialBlock;
|
||||
|
||||
if (bitsInQueue == rate) {
|
||||
absorbQueue();
|
||||
}
|
||||
|
||||
if (partialByte > 0) {
|
||||
int mask = (1 << partialByte) - 1;
|
||||
dataQueue[bitsInQueue / 8] = (byte) (data[off + ((int) (i / 8))] & mask);
|
||||
bitsInQueue += partialByte;
|
||||
i += partialByte;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void padAndSwitchToSqueezingPhase() {
|
||||
if (bitsInQueue + 1 == rate) {
|
||||
dataQueue[bitsInQueue / 8] |= 1 << (bitsInQueue % 8);
|
||||
absorbQueue();
|
||||
clearDataQueueSection(0, rate / 8);
|
||||
} else {
|
||||
clearDataQueueSection((bitsInQueue + 7) / 8, rate / 8 - (bitsInQueue + 7) / 8);
|
||||
dataQueue[bitsInQueue / 8] |= 1 << (bitsInQueue % 8);
|
||||
}
|
||||
|
||||
dataQueue[(rate - 1) / 8] |= 1 << ((rate - 1) % 8);
|
||||
absorbQueue();
|
||||
|
||||
// displayIntermediateValues.displayText(1, "--- Switching to squeezing phase ---");
|
||||
|
||||
if (rate == 1024) {
|
||||
KeccakExtract1024bits(state, dataQueue);
|
||||
bitsAvailableForSqueezing = 1024;
|
||||
} else {
|
||||
KeccakExtract(state, dataQueue, rate / 64);
|
||||
bitsAvailableForSqueezing = rate;
|
||||
}
|
||||
|
||||
// displayIntermediateValues.displayBytes(1, "Block available for squeezing", dataQueue, bitsAvailableForSqueezing / 8);
|
||||
|
||||
squeezing = true;
|
||||
}
|
||||
|
||||
protected void squeeze(byte[] output, int offset, long outputLength) {
|
||||
long i;
|
||||
int partialBlock;
|
||||
|
||||
if (!squeezing) {
|
||||
padAndSwitchToSqueezingPhase();
|
||||
}
|
||||
|
||||
if ((outputLength % 8) != 0) {
|
||||
throw new IllegalStateException("outputLength not a multiple of 8");
|
||||
}
|
||||
|
||||
i = 0;
|
||||
|
||||
while (i < outputLength) {
|
||||
if (bitsAvailableForSqueezing == 0) {
|
||||
keccakPermutation(state);
|
||||
|
||||
if (rate == 1024) {
|
||||
KeccakExtract1024bits(state, dataQueue);
|
||||
bitsAvailableForSqueezing = 1024;
|
||||
} else {
|
||||
KeccakExtract(state, dataQueue, rate / 64);
|
||||
bitsAvailableForSqueezing = rate;
|
||||
}
|
||||
|
||||
// displayIntermediateValues.displayBytes(1, "Block available for squeezing", dataQueue, bitsAvailableForSqueezing / 8);
|
||||
|
||||
}
|
||||
|
||||
partialBlock = bitsAvailableForSqueezing;
|
||||
|
||||
if ((long) partialBlock > outputLength - i) {
|
||||
partialBlock = (int) (outputLength - i);
|
||||
}
|
||||
|
||||
System.arraycopy(dataQueue, (rate - bitsAvailableForSqueezing) / 8, output, offset + (int) (i / 8), partialBlock / 8);
|
||||
bitsAvailableForSqueezing -= partialBlock;
|
||||
i += partialBlock;
|
||||
}
|
||||
}
|
||||
|
||||
private void fromBytesToWords(long[] stateAsWords, byte[] state) {
|
||||
for (int i = 0; i < (1600 / 64); i++) {
|
||||
stateAsWords[i] = 0;
|
||||
int index = i * (64 / 8);
|
||||
|
||||
for (int j = 0; j < (64 / 8); j++) {
|
||||
stateAsWords[i] |= ((long) state[index + j] & 0xff) << ((8 * j));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void fromWordsToBytes(byte[] state, long[] stateAsWords) {
|
||||
for (int i = 0; i < (1600 / 64); i++) {
|
||||
int index = i * (64 / 8);
|
||||
|
||||
for (int j = 0; j < (64 / 8); j++) {
|
||||
state[index + j] = (byte) ((stateAsWords[i] >>> ((8 * j))) & 0xFF);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void keccakPermutation(byte[] state) {
|
||||
long[] longState = new long[state.length / 8];
|
||||
|
||||
fromBytesToWords(longState, state);
|
||||
|
||||
// displayIntermediateValues.displayStateAsBytes(1, "Input of permutation", longState);
|
||||
|
||||
keccakPermutationOnWords(longState);
|
||||
|
||||
// displayIntermediateValues.displayStateAsBytes(1, "State after permutation", longState);
|
||||
|
||||
fromWordsToBytes(state, longState);
|
||||
}
|
||||
|
||||
private void keccakPermutationAfterXor(byte[] state, byte[] data, int dataLengthInBytes) {
|
||||
int i;
|
||||
|
||||
for (i = 0; i < dataLengthInBytes; i++) {
|
||||
state[i] ^= data[i];
|
||||
}
|
||||
|
||||
keccakPermutation(state);
|
||||
}
|
||||
|
||||
private void keccakPermutationOnWords(long[] state) {
|
||||
int i;
|
||||
|
||||
// displayIntermediateValues.displayStateAs64bitWords(3, "Same, with lanes as 64-bit words", state);
|
||||
|
||||
for (i = 0; i < 24; i++) {
|
||||
// displayIntermediateValues.displayRoundNumber(3, i);
|
||||
|
||||
theta(state);
|
||||
// displayIntermediateValues.displayStateAs64bitWords(3, "After theta", state);
|
||||
|
||||
rho(state);
|
||||
// displayIntermediateValues.displayStateAs64bitWords(3, "After rho", state);
|
||||
|
||||
pi(state);
|
||||
// displayIntermediateValues.displayStateAs64bitWords(3, "After pi", state);
|
||||
|
||||
chi(state);
|
||||
// displayIntermediateValues.displayStateAs64bitWords(3, "After chi", state);
|
||||
|
||||
iota(state, i);
|
||||
// displayIntermediateValues.displayStateAs64bitWords(3, "After iota", state);
|
||||
}
|
||||
}
|
||||
|
||||
long[] C = new long[5];
|
||||
|
||||
private void theta(long[] A) {
|
||||
for (int x = 0; x < 5; x++) {
|
||||
C[x] = 0;
|
||||
|
||||
for (int y = 0; y < 5; y++) {
|
||||
C[x] ^= A[x + 5 * y];
|
||||
}
|
||||
}
|
||||
for (int x = 0; x < 5; x++) {
|
||||
long dX = ((((C[(x + 1) % 5]) << 1) ^ ((C[(x + 1) % 5]) >>> (64 - 1)))) ^ C[(x + 4) % 5];
|
||||
|
||||
for (int y = 0; y < 5; y++) {
|
||||
A[x + 5 * y] ^= dX;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void rho(long[] A) {
|
||||
for (int x = 0; x < 5; x++) {
|
||||
for (int y = 0; y < 5; y++) {
|
||||
int index = x + 5 * y;
|
||||
A[index] = ((KeccakRhoOffsets[index] != 0) ? (((A[index]) << KeccakRhoOffsets[index]) ^ ((A[index]) >>> (64 - KeccakRhoOffsets[index]))) : A[index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
long[] tempA = new long[25];
|
||||
|
||||
private void pi(long[] A) {
|
||||
System.arraycopy(A, 0, tempA, 0, tempA.length);
|
||||
|
||||
for (int x = 0; x < 5; x++) {
|
||||
for (int y = 0; y < 5; y++) {
|
||||
A[y + 5 * ((2 * x + 3 * y) % 5)] = tempA[x + 5 * y];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
long[] chiC = new long[5];
|
||||
|
||||
private void chi(long[] A) {
|
||||
for (int y = 0; y < 5; y++) {
|
||||
for (int x = 0; x < 5; x++) {
|
||||
chiC[x] = A[x + 5 * y] ^ ((~A[(((x + 1) % 5) + 5 * y)]) & A[(((x + 2) % 5) + 5 * y)]);
|
||||
}
|
||||
|
||||
for (int x = 0; x < 5; x++) {
|
||||
A[x + 5 * y] = chiC[x];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void iota(long[] A, int indexRound) {
|
||||
A[(((0) % 5) + 5 * ((0) % 5))] ^= KeccakRoundConstants[indexRound];
|
||||
}
|
||||
|
||||
private void KeccakAbsorb(byte[] byteState, byte[] data, int dataInBytes) {
|
||||
keccakPermutationAfterXor(byteState, data, dataInBytes);
|
||||
}
|
||||
|
||||
private void KeccakExtract1024bits(byte[] byteState, byte[] data) {
|
||||
System.arraycopy(byteState, 0, data, 0, 128);
|
||||
}
|
||||
|
||||
private void KeccakExtract(byte[] byteState, byte[] data, int laneCount) {
|
||||
System.arraycopy(byteState, 0, data, 0, laneCount * 8);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.vitorpamplona.quartz.ots.crypto;
|
||||
|
||||
/**
|
||||
* Interface for Memoable objects. Memoable objects allow the taking of a snapshot of their internal state
|
||||
* via the {@link #copy copy()} method and then resetting the object back to that state later using the
|
||||
* {@link #reset reset()} method.
|
||||
*/
|
||||
public interface Memoable {
|
||||
/**
|
||||
* Produce a copy of this object with its configuration and in its current state.
|
||||
* <p>
|
||||
* The returned object may be used simply to store the state, or may be used as a similar object
|
||||
* starting from the copied state.
|
||||
*
|
||||
* @return Memoable object
|
||||
*/
|
||||
Memoable copy();
|
||||
|
||||
/**
|
||||
* Restore a copied object state into this object.
|
||||
* <p>
|
||||
* Implementations of this method <em>should</em> try to avoid or minimise memory allocation to perform the reset.
|
||||
*
|
||||
* @param other an object originally {@link #copy() copied} from an object of the same type as this instance.
|
||||
* @throws ClassCastException if the provided object is not of the correct type.
|
||||
*/
|
||||
void reset(Memoable other);
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
package com.vitorpamplona.quartz.ots.crypto;
|
||||
|
||||
/**
|
||||
* Utility methods for converting byte arrays into ints and longs, and back again.
|
||||
*/
|
||||
public abstract class Pack
|
||||
{
|
||||
public static short bigEndianToShort(byte[] bs, int off)
|
||||
{
|
||||
int n = (bs[ off] & 0xff) << 8;
|
||||
n |= (bs[++off] & 0xff);
|
||||
return (short)n;
|
||||
}
|
||||
|
||||
public static int bigEndianToInt(byte[] bs, int off)
|
||||
{
|
||||
int n = bs[ off] << 24;
|
||||
n |= (bs[++off] & 0xff) << 16;
|
||||
n |= (bs[++off] & 0xff) << 8;
|
||||
n |= (bs[++off] & 0xff);
|
||||
return n;
|
||||
}
|
||||
|
||||
public static void bigEndianToInt(byte[] bs, int off, int[] ns)
|
||||
{
|
||||
for (int i = 0; i < ns.length; ++i)
|
||||
{
|
||||
ns[i] = bigEndianToInt(bs, off);
|
||||
off += 4;
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] intToBigEndian(int n)
|
||||
{
|
||||
byte[] bs = new byte[4];
|
||||
intToBigEndian(n, bs, 0);
|
||||
return bs;
|
||||
}
|
||||
|
||||
public static void intToBigEndian(int n, byte[] bs, int off)
|
||||
{
|
||||
bs[ off] = (byte)(n >>> 24);
|
||||
bs[++off] = (byte)(n >>> 16);
|
||||
bs[++off] = (byte)(n >>> 8);
|
||||
bs[++off] = (byte)(n );
|
||||
}
|
||||
|
||||
public static byte[] intToBigEndian(int[] ns)
|
||||
{
|
||||
byte[] bs = new byte[4 * ns.length];
|
||||
intToBigEndian(ns, bs, 0);
|
||||
return bs;
|
||||
}
|
||||
|
||||
public static void intToBigEndian(int[] ns, byte[] bs, int off)
|
||||
{
|
||||
for (int i = 0; i < ns.length; ++i)
|
||||
{
|
||||
intToBigEndian(ns[i], bs, off);
|
||||
off += 4;
|
||||
}
|
||||
}
|
||||
|
||||
public static long bigEndianToLong(byte[] bs, int off)
|
||||
{
|
||||
int hi = bigEndianToInt(bs, off);
|
||||
int lo = bigEndianToInt(bs, off + 4);
|
||||
return ((long)(hi & 0xffffffffL) << 32) | (long)(lo & 0xffffffffL);
|
||||
}
|
||||
|
||||
public static void bigEndianToLong(byte[] bs, int off, long[] ns)
|
||||
{
|
||||
for (int i = 0; i < ns.length; ++i)
|
||||
{
|
||||
ns[i] = bigEndianToLong(bs, off);
|
||||
off += 8;
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] longToBigEndian(long n)
|
||||
{
|
||||
byte[] bs = new byte[8];
|
||||
longToBigEndian(n, bs, 0);
|
||||
return bs;
|
||||
}
|
||||
|
||||
public static void longToBigEndian(long n, byte[] bs, int off)
|
||||
{
|
||||
intToBigEndian((int)(n >>> 32), bs, off);
|
||||
intToBigEndian((int)(n & 0xffffffffL), bs, off + 4);
|
||||
}
|
||||
|
||||
public static byte[] longToBigEndian(long[] ns)
|
||||
{
|
||||
byte[] bs = new byte[8 * ns.length];
|
||||
longToBigEndian(ns, bs, 0);
|
||||
return bs;
|
||||
}
|
||||
|
||||
public static void longToBigEndian(long[] ns, byte[] bs, int off)
|
||||
{
|
||||
for (int i = 0; i < ns.length; ++i)
|
||||
{
|
||||
longToBigEndian(ns[i], bs, off);
|
||||
off += 8;
|
||||
}
|
||||
}
|
||||
|
||||
public static short littleEndianToShort(byte[] bs, int off)
|
||||
{
|
||||
int n = bs[ off] & 0xff;
|
||||
n |= (bs[++off] & 0xff) << 8;
|
||||
return (short)n;
|
||||
}
|
||||
|
||||
public static int littleEndianToInt(byte[] bs, int off)
|
||||
{
|
||||
int n = bs[ off] & 0xff;
|
||||
n |= (bs[++off] & 0xff) << 8;
|
||||
n |= (bs[++off] & 0xff) << 16;
|
||||
n |= bs[++off] << 24;
|
||||
return n;
|
||||
}
|
||||
|
||||
public static void littleEndianToInt(byte[] bs, int off, int[] ns)
|
||||
{
|
||||
for (int i = 0; i < ns.length; ++i)
|
||||
{
|
||||
ns[i] = littleEndianToInt(bs, off);
|
||||
off += 4;
|
||||
}
|
||||
}
|
||||
|
||||
public static void littleEndianToInt(byte[] bs, int bOff, int[] ns, int nOff, int count)
|
||||
{
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
ns[nOff + i] = littleEndianToInt(bs, bOff);
|
||||
bOff += 4;
|
||||
}
|
||||
}
|
||||
|
||||
public static int[] littleEndianToInt(byte[] bs, int off, int count)
|
||||
{
|
||||
int[] ns = new int[count];
|
||||
for (int i = 0; i < ns.length; ++i)
|
||||
{
|
||||
ns[i] = littleEndianToInt(bs, off);
|
||||
off += 4;
|
||||
}
|
||||
return ns;
|
||||
}
|
||||
|
||||
public static byte[] shortToLittleEndian(short n)
|
||||
{
|
||||
byte[] bs = new byte[2];
|
||||
shortToLittleEndian(n, bs, 0);
|
||||
return bs;
|
||||
}
|
||||
|
||||
public static void shortToLittleEndian(short n, byte[] bs, int off)
|
||||
{
|
||||
bs[ off] = (byte)(n );
|
||||
bs[++off] = (byte)(n >>> 8);
|
||||
}
|
||||
|
||||
public static byte[] intToLittleEndian(int n)
|
||||
{
|
||||
byte[] bs = new byte[4];
|
||||
intToLittleEndian(n, bs, 0);
|
||||
return bs;
|
||||
}
|
||||
|
||||
public static void intToLittleEndian(int n, byte[] bs, int off)
|
||||
{
|
||||
bs[ off] = (byte)(n );
|
||||
bs[++off] = (byte)(n >>> 8);
|
||||
bs[++off] = (byte)(n >>> 16);
|
||||
bs[++off] = (byte)(n >>> 24);
|
||||
}
|
||||
|
||||
public static byte[] intToLittleEndian(int[] ns)
|
||||
{
|
||||
byte[] bs = new byte[4 * ns.length];
|
||||
intToLittleEndian(ns, bs, 0);
|
||||
return bs;
|
||||
}
|
||||
|
||||
public static void intToLittleEndian(int[] ns, byte[] bs, int off)
|
||||
{
|
||||
for (int i = 0; i < ns.length; ++i)
|
||||
{
|
||||
intToLittleEndian(ns[i], bs, off);
|
||||
off += 4;
|
||||
}
|
||||
}
|
||||
|
||||
public static long littleEndianToLong(byte[] bs, int off)
|
||||
{
|
||||
int lo = littleEndianToInt(bs, off);
|
||||
int hi = littleEndianToInt(bs, off + 4);
|
||||
return ((long)(hi & 0xffffffffL) << 32) | (long)(lo & 0xffffffffL);
|
||||
}
|
||||
|
||||
public static void littleEndianToLong(byte[] bs, int off, long[] ns)
|
||||
{
|
||||
for (int i = 0; i < ns.length; ++i)
|
||||
{
|
||||
ns[i] = littleEndianToLong(bs, off);
|
||||
off += 8;
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] longToLittleEndian(long n)
|
||||
{
|
||||
byte[] bs = new byte[8];
|
||||
longToLittleEndian(n, bs, 0);
|
||||
return bs;
|
||||
}
|
||||
|
||||
public static void longToLittleEndian(long n, byte[] bs, int off)
|
||||
{
|
||||
intToLittleEndian((int)(n & 0xffffffffL), bs, off);
|
||||
intToLittleEndian((int)(n >>> 32), bs, off + 4);
|
||||
}
|
||||
|
||||
public static byte[] longToLittleEndian(long[] ns)
|
||||
{
|
||||
byte[] bs = new byte[8 * ns.length];
|
||||
longToLittleEndian(ns, bs, 0);
|
||||
return bs;
|
||||
}
|
||||
|
||||
public static void longToLittleEndian(long[] ns, byte[] bs, int off)
|
||||
{
|
||||
for (int i = 0; i < ns.length; ++i)
|
||||
{
|
||||
longToLittleEndian(ns[i], bs, off);
|
||||
off += 8;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,442 @@
|
||||
package com.vitorpamplona.quartz.ots.crypto;
|
||||
|
||||
/**
|
||||
* Implementation of RIPEMD
|
||||
*
|
||||
* @see <a href="http://www.esat.kuleuven.ac.be/~bosselae/ripemd160.html">ripemd160</a>
|
||||
*/
|
||||
public class RIPEMD160Digest
|
||||
extends GeneralDigest
|
||||
{
|
||||
private static final int DIGEST_LENGTH = 20;
|
||||
|
||||
private int H0, H1, H2, H3, H4; // IV's
|
||||
|
||||
private int[] X = new int[16];
|
||||
private int xOff;
|
||||
|
||||
/**
|
||||
* Standard constructor
|
||||
*/
|
||||
public RIPEMD160Digest()
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy constructor. This will copy the state of the provided
|
||||
* message digest.
|
||||
* @param t RIPEMD160Digest
|
||||
*/
|
||||
public RIPEMD160Digest(RIPEMD160Digest t)
|
||||
{
|
||||
super(t);
|
||||
|
||||
copyIn(t);
|
||||
}
|
||||
|
||||
private void copyIn(RIPEMD160Digest t)
|
||||
{
|
||||
super.copyIn(t);
|
||||
|
||||
H0 = t.H0;
|
||||
H1 = t.H1;
|
||||
H2 = t.H2;
|
||||
H3 = t.H3;
|
||||
H4 = t.H4;
|
||||
|
||||
System.arraycopy(t.X, 0, X, 0, t.X.length);
|
||||
xOff = t.xOff;
|
||||
}
|
||||
|
||||
public String getAlgorithmName()
|
||||
{
|
||||
return "RIPEMD160";
|
||||
}
|
||||
|
||||
public int getDigestSize()
|
||||
{
|
||||
return DIGEST_LENGTH;
|
||||
}
|
||||
|
||||
protected void processWord(
|
||||
byte[] in,
|
||||
int inOff)
|
||||
{
|
||||
X[xOff++] = (in[inOff] & 0xff) | ((in[inOff + 1] & 0xff) << 8)
|
||||
| ((in[inOff + 2] & 0xff) << 16) | ((in[inOff + 3] & 0xff) << 24);
|
||||
|
||||
if (xOff == 16)
|
||||
{
|
||||
processBlock();
|
||||
}
|
||||
}
|
||||
|
||||
protected void processLength(
|
||||
long bitLength)
|
||||
{
|
||||
if (xOff > 14)
|
||||
{
|
||||
processBlock();
|
||||
}
|
||||
|
||||
X[14] = (int)(bitLength & 0xffffffff);
|
||||
X[15] = (int)(bitLength >>> 32);
|
||||
}
|
||||
|
||||
private void unpackWord(
|
||||
int word,
|
||||
byte[] out,
|
||||
int outOff)
|
||||
{
|
||||
out[outOff] = (byte)word;
|
||||
out[outOff + 1] = (byte)(word >>> 8);
|
||||
out[outOff + 2] = (byte)(word >>> 16);
|
||||
out[outOff + 3] = (byte)(word >>> 24);
|
||||
}
|
||||
|
||||
public int doFinal(
|
||||
byte[] out,
|
||||
int outOff)
|
||||
{
|
||||
finish();
|
||||
|
||||
unpackWord(H0, out, outOff);
|
||||
unpackWord(H1, out, outOff + 4);
|
||||
unpackWord(H2, out, outOff + 8);
|
||||
unpackWord(H3, out, outOff + 12);
|
||||
unpackWord(H4, out, outOff + 16);
|
||||
|
||||
reset();
|
||||
|
||||
return DIGEST_LENGTH;
|
||||
}
|
||||
|
||||
/**
|
||||
* reset the chaining variables to the IV values.
|
||||
*/
|
||||
public void reset()
|
||||
{
|
||||
super.reset();
|
||||
|
||||
H0 = 0x67452301;
|
||||
H1 = 0xefcdab89;
|
||||
H2 = 0x98badcfe;
|
||||
H3 = 0x10325476;
|
||||
H4 = 0xc3d2e1f0;
|
||||
|
||||
xOff = 0;
|
||||
|
||||
for (int i = 0; i != X.length; i++)
|
||||
{
|
||||
X[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* rotate int x left n bits.
|
||||
*/
|
||||
private int RL(
|
||||
int x,
|
||||
int n)
|
||||
{
|
||||
return (x << n) | (x >>> (32 - n));
|
||||
}
|
||||
|
||||
/*
|
||||
* f1,f2,f3,f4,f5 are the basic RIPEMD160 functions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* rounds 0-15
|
||||
*/
|
||||
private int f1(
|
||||
int x,
|
||||
int y,
|
||||
int z)
|
||||
{
|
||||
return x ^ y ^ z;
|
||||
}
|
||||
|
||||
/*
|
||||
* rounds 16-31
|
||||
*/
|
||||
private int f2(
|
||||
int x,
|
||||
int y,
|
||||
int z)
|
||||
{
|
||||
return (x & y) | (~x & z);
|
||||
}
|
||||
|
||||
/*
|
||||
* rounds 32-47
|
||||
*/
|
||||
private int f3(
|
||||
int x,
|
||||
int y,
|
||||
int z)
|
||||
{
|
||||
return (x | ~y) ^ z;
|
||||
}
|
||||
|
||||
/*
|
||||
* rounds 48-63
|
||||
*/
|
||||
private int f4(
|
||||
int x,
|
||||
int y,
|
||||
int z)
|
||||
{
|
||||
return (x & z) | (y & ~z);
|
||||
}
|
||||
|
||||
/*
|
||||
* rounds 64-79
|
||||
*/
|
||||
private int f5(
|
||||
int x,
|
||||
int y,
|
||||
int z)
|
||||
{
|
||||
return x ^ (y | ~z);
|
||||
}
|
||||
|
||||
protected void processBlock()
|
||||
{
|
||||
int a, aa;
|
||||
int b, bb;
|
||||
int c, cc;
|
||||
int d, dd;
|
||||
int e, ee;
|
||||
|
||||
a = aa = H0;
|
||||
b = bb = H1;
|
||||
c = cc = H2;
|
||||
d = dd = H3;
|
||||
e = ee = H4;
|
||||
|
||||
//
|
||||
// Rounds 1 - 16
|
||||
//
|
||||
// left
|
||||
a = RL(a + f1(b,c,d) + X[ 0], 11) + e; c = RL(c, 10);
|
||||
e = RL(e + f1(a,b,c) + X[ 1], 14) + d; b = RL(b, 10);
|
||||
d = RL(d + f1(e,a,b) + X[ 2], 15) + c; a = RL(a, 10);
|
||||
c = RL(c + f1(d,e,a) + X[ 3], 12) + b; e = RL(e, 10);
|
||||
b = RL(b + f1(c,d,e) + X[ 4], 5) + a; d = RL(d, 10);
|
||||
a = RL(a + f1(b,c,d) + X[ 5], 8) + e; c = RL(c, 10);
|
||||
e = RL(e + f1(a,b,c) + X[ 6], 7) + d; b = RL(b, 10);
|
||||
d = RL(d + f1(e,a,b) + X[ 7], 9) + c; a = RL(a, 10);
|
||||
c = RL(c + f1(d,e,a) + X[ 8], 11) + b; e = RL(e, 10);
|
||||
b = RL(b + f1(c,d,e) + X[ 9], 13) + a; d = RL(d, 10);
|
||||
a = RL(a + f1(b,c,d) + X[10], 14) + e; c = RL(c, 10);
|
||||
e = RL(e + f1(a,b,c) + X[11], 15) + d; b = RL(b, 10);
|
||||
d = RL(d + f1(e,a,b) + X[12], 6) + c; a = RL(a, 10);
|
||||
c = RL(c + f1(d,e,a) + X[13], 7) + b; e = RL(e, 10);
|
||||
b = RL(b + f1(c,d,e) + X[14], 9) + a; d = RL(d, 10);
|
||||
a = RL(a + f1(b,c,d) + X[15], 8) + e; c = RL(c, 10);
|
||||
|
||||
// right
|
||||
aa = RL(aa + f5(bb,cc,dd) + X[ 5] + 0x50a28be6, 8) + ee; cc = RL(cc, 10);
|
||||
ee = RL(ee + f5(aa,bb,cc) + X[14] + 0x50a28be6, 9) + dd; bb = RL(bb, 10);
|
||||
dd = RL(dd + f5(ee,aa,bb) + X[ 7] + 0x50a28be6, 9) + cc; aa = RL(aa, 10);
|
||||
cc = RL(cc + f5(dd,ee,aa) + X[ 0] + 0x50a28be6, 11) + bb; ee = RL(ee, 10);
|
||||
bb = RL(bb + f5(cc,dd,ee) + X[ 9] + 0x50a28be6, 13) + aa; dd = RL(dd, 10);
|
||||
aa = RL(aa + f5(bb,cc,dd) + X[ 2] + 0x50a28be6, 15) + ee; cc = RL(cc, 10);
|
||||
ee = RL(ee + f5(aa,bb,cc) + X[11] + 0x50a28be6, 15) + dd; bb = RL(bb, 10);
|
||||
dd = RL(dd + f5(ee,aa,bb) + X[ 4] + 0x50a28be6, 5) + cc; aa = RL(aa, 10);
|
||||
cc = RL(cc + f5(dd,ee,aa) + X[13] + 0x50a28be6, 7) + bb; ee = RL(ee, 10);
|
||||
bb = RL(bb + f5(cc,dd,ee) + X[ 6] + 0x50a28be6, 7) + aa; dd = RL(dd, 10);
|
||||
aa = RL(aa + f5(bb,cc,dd) + X[15] + 0x50a28be6, 8) + ee; cc = RL(cc, 10);
|
||||
ee = RL(ee + f5(aa,bb,cc) + X[ 8] + 0x50a28be6, 11) + dd; bb = RL(bb, 10);
|
||||
dd = RL(dd + f5(ee,aa,bb) + X[ 1] + 0x50a28be6, 14) + cc; aa = RL(aa, 10);
|
||||
cc = RL(cc + f5(dd,ee,aa) + X[10] + 0x50a28be6, 14) + bb; ee = RL(ee, 10);
|
||||
bb = RL(bb + f5(cc,dd,ee) + X[ 3] + 0x50a28be6, 12) + aa; dd = RL(dd, 10);
|
||||
aa = RL(aa + f5(bb,cc,dd) + X[12] + 0x50a28be6, 6) + ee; cc = RL(cc, 10);
|
||||
|
||||
//
|
||||
// Rounds 16-31
|
||||
//
|
||||
// left
|
||||
e = RL(e + f2(a,b,c) + X[ 7] + 0x5a827999, 7) + d; b = RL(b, 10);
|
||||
d = RL(d + f2(e,a,b) + X[ 4] + 0x5a827999, 6) + c; a = RL(a, 10);
|
||||
c = RL(c + f2(d,e,a) + X[13] + 0x5a827999, 8) + b; e = RL(e, 10);
|
||||
b = RL(b + f2(c,d,e) + X[ 1] + 0x5a827999, 13) + a; d = RL(d, 10);
|
||||
a = RL(a + f2(b,c,d) + X[10] + 0x5a827999, 11) + e; c = RL(c, 10);
|
||||
e = RL(e + f2(a,b,c) + X[ 6] + 0x5a827999, 9) + d; b = RL(b, 10);
|
||||
d = RL(d + f2(e,a,b) + X[15] + 0x5a827999, 7) + c; a = RL(a, 10);
|
||||
c = RL(c + f2(d,e,a) + X[ 3] + 0x5a827999, 15) + b; e = RL(e, 10);
|
||||
b = RL(b + f2(c,d,e) + X[12] + 0x5a827999, 7) + a; d = RL(d, 10);
|
||||
a = RL(a + f2(b,c,d) + X[ 0] + 0x5a827999, 12) + e; c = RL(c, 10);
|
||||
e = RL(e + f2(a,b,c) + X[ 9] + 0x5a827999, 15) + d; b = RL(b, 10);
|
||||
d = RL(d + f2(e,a,b) + X[ 5] + 0x5a827999, 9) + c; a = RL(a, 10);
|
||||
c = RL(c + f2(d,e,a) + X[ 2] + 0x5a827999, 11) + b; e = RL(e, 10);
|
||||
b = RL(b + f2(c,d,e) + X[14] + 0x5a827999, 7) + a; d = RL(d, 10);
|
||||
a = RL(a + f2(b,c,d) + X[11] + 0x5a827999, 13) + e; c = RL(c, 10);
|
||||
e = RL(e + f2(a,b,c) + X[ 8] + 0x5a827999, 12) + d; b = RL(b, 10);
|
||||
|
||||
// right
|
||||
ee = RL(ee + f4(aa,bb,cc) + X[ 6] + 0x5c4dd124, 9) + dd; bb = RL(bb, 10);
|
||||
dd = RL(dd + f4(ee,aa,bb) + X[11] + 0x5c4dd124, 13) + cc; aa = RL(aa, 10);
|
||||
cc = RL(cc + f4(dd,ee,aa) + X[ 3] + 0x5c4dd124, 15) + bb; ee = RL(ee, 10);
|
||||
bb = RL(bb + f4(cc,dd,ee) + X[ 7] + 0x5c4dd124, 7) + aa; dd = RL(dd, 10);
|
||||
aa = RL(aa + f4(bb,cc,dd) + X[ 0] + 0x5c4dd124, 12) + ee; cc = RL(cc, 10);
|
||||
ee = RL(ee + f4(aa,bb,cc) + X[13] + 0x5c4dd124, 8) + dd; bb = RL(bb, 10);
|
||||
dd = RL(dd + f4(ee,aa,bb) + X[ 5] + 0x5c4dd124, 9) + cc; aa = RL(aa, 10);
|
||||
cc = RL(cc + f4(dd,ee,aa) + X[10] + 0x5c4dd124, 11) + bb; ee = RL(ee, 10);
|
||||
bb = RL(bb + f4(cc,dd,ee) + X[14] + 0x5c4dd124, 7) + aa; dd = RL(dd, 10);
|
||||
aa = RL(aa + f4(bb,cc,dd) + X[15] + 0x5c4dd124, 7) + ee; cc = RL(cc, 10);
|
||||
ee = RL(ee + f4(aa,bb,cc) + X[ 8] + 0x5c4dd124, 12) + dd; bb = RL(bb, 10);
|
||||
dd = RL(dd + f4(ee,aa,bb) + X[12] + 0x5c4dd124, 7) + cc; aa = RL(aa, 10);
|
||||
cc = RL(cc + f4(dd,ee,aa) + X[ 4] + 0x5c4dd124, 6) + bb; ee = RL(ee, 10);
|
||||
bb = RL(bb + f4(cc,dd,ee) + X[ 9] + 0x5c4dd124, 15) + aa; dd = RL(dd, 10);
|
||||
aa = RL(aa + f4(bb,cc,dd) + X[ 1] + 0x5c4dd124, 13) + ee; cc = RL(cc, 10);
|
||||
ee = RL(ee + f4(aa,bb,cc) + X[ 2] + 0x5c4dd124, 11) + dd; bb = RL(bb, 10);
|
||||
|
||||
//
|
||||
// Rounds 32-47
|
||||
//
|
||||
// left
|
||||
d = RL(d + f3(e,a,b) + X[ 3] + 0x6ed9eba1, 11) + c; a = RL(a, 10);
|
||||
c = RL(c + f3(d,e,a) + X[10] + 0x6ed9eba1, 13) + b; e = RL(e, 10);
|
||||
b = RL(b + f3(c,d,e) + X[14] + 0x6ed9eba1, 6) + a; d = RL(d, 10);
|
||||
a = RL(a + f3(b,c,d) + X[ 4] + 0x6ed9eba1, 7) + e; c = RL(c, 10);
|
||||
e = RL(e + f3(a,b,c) + X[ 9] + 0x6ed9eba1, 14) + d; b = RL(b, 10);
|
||||
d = RL(d + f3(e,a,b) + X[15] + 0x6ed9eba1, 9) + c; a = RL(a, 10);
|
||||
c = RL(c + f3(d,e,a) + X[ 8] + 0x6ed9eba1, 13) + b; e = RL(e, 10);
|
||||
b = RL(b + f3(c,d,e) + X[ 1] + 0x6ed9eba1, 15) + a; d = RL(d, 10);
|
||||
a = RL(a + f3(b,c,d) + X[ 2] + 0x6ed9eba1, 14) + e; c = RL(c, 10);
|
||||
e = RL(e + f3(a,b,c) + X[ 7] + 0x6ed9eba1, 8) + d; b = RL(b, 10);
|
||||
d = RL(d + f3(e,a,b) + X[ 0] + 0x6ed9eba1, 13) + c; a = RL(a, 10);
|
||||
c = RL(c + f3(d,e,a) + X[ 6] + 0x6ed9eba1, 6) + b; e = RL(e, 10);
|
||||
b = RL(b + f3(c,d,e) + X[13] + 0x6ed9eba1, 5) + a; d = RL(d, 10);
|
||||
a = RL(a + f3(b,c,d) + X[11] + 0x6ed9eba1, 12) + e; c = RL(c, 10);
|
||||
e = RL(e + f3(a,b,c) + X[ 5] + 0x6ed9eba1, 7) + d; b = RL(b, 10);
|
||||
d = RL(d + f3(e,a,b) + X[12] + 0x6ed9eba1, 5) + c; a = RL(a, 10);
|
||||
|
||||
// right
|
||||
dd = RL(dd + f3(ee,aa,bb) + X[15] + 0x6d703ef3, 9) + cc; aa = RL(aa, 10);
|
||||
cc = RL(cc + f3(dd,ee,aa) + X[ 5] + 0x6d703ef3, 7) + bb; ee = RL(ee, 10);
|
||||
bb = RL(bb + f3(cc,dd,ee) + X[ 1] + 0x6d703ef3, 15) + aa; dd = RL(dd, 10);
|
||||
aa = RL(aa + f3(bb,cc,dd) + X[ 3] + 0x6d703ef3, 11) + ee; cc = RL(cc, 10);
|
||||
ee = RL(ee + f3(aa,bb,cc) + X[ 7] + 0x6d703ef3, 8) + dd; bb = RL(bb, 10);
|
||||
dd = RL(dd + f3(ee,aa,bb) + X[14] + 0x6d703ef3, 6) + cc; aa = RL(aa, 10);
|
||||
cc = RL(cc + f3(dd,ee,aa) + X[ 6] + 0x6d703ef3, 6) + bb; ee = RL(ee, 10);
|
||||
bb = RL(bb + f3(cc,dd,ee) + X[ 9] + 0x6d703ef3, 14) + aa; dd = RL(dd, 10);
|
||||
aa = RL(aa + f3(bb,cc,dd) + X[11] + 0x6d703ef3, 12) + ee; cc = RL(cc, 10);
|
||||
ee = RL(ee + f3(aa,bb,cc) + X[ 8] + 0x6d703ef3, 13) + dd; bb = RL(bb, 10);
|
||||
dd = RL(dd + f3(ee,aa,bb) + X[12] + 0x6d703ef3, 5) + cc; aa = RL(aa, 10);
|
||||
cc = RL(cc + f3(dd,ee,aa) + X[ 2] + 0x6d703ef3, 14) + bb; ee = RL(ee, 10);
|
||||
bb = RL(bb + f3(cc,dd,ee) + X[10] + 0x6d703ef3, 13) + aa; dd = RL(dd, 10);
|
||||
aa = RL(aa + f3(bb,cc,dd) + X[ 0] + 0x6d703ef3, 13) + ee; cc = RL(cc, 10);
|
||||
ee = RL(ee + f3(aa,bb,cc) + X[ 4] + 0x6d703ef3, 7) + dd; bb = RL(bb, 10);
|
||||
dd = RL(dd + f3(ee,aa,bb) + X[13] + 0x6d703ef3, 5) + cc; aa = RL(aa, 10);
|
||||
|
||||
//
|
||||
// Rounds 48-63
|
||||
//
|
||||
// left
|
||||
c = RL(c + f4(d,e,a) + X[ 1] + 0x8f1bbcdc, 11) + b; e = RL(e, 10);
|
||||
b = RL(b + f4(c,d,e) + X[ 9] + 0x8f1bbcdc, 12) + a; d = RL(d, 10);
|
||||
a = RL(a + f4(b,c,d) + X[11] + 0x8f1bbcdc, 14) + e; c = RL(c, 10);
|
||||
e = RL(e + f4(a,b,c) + X[10] + 0x8f1bbcdc, 15) + d; b = RL(b, 10);
|
||||
d = RL(d + f4(e,a,b) + X[ 0] + 0x8f1bbcdc, 14) + c; a = RL(a, 10);
|
||||
c = RL(c + f4(d,e,a) + X[ 8] + 0x8f1bbcdc, 15) + b; e = RL(e, 10);
|
||||
b = RL(b + f4(c,d,e) + X[12] + 0x8f1bbcdc, 9) + a; d = RL(d, 10);
|
||||
a = RL(a + f4(b,c,d) + X[ 4] + 0x8f1bbcdc, 8) + e; c = RL(c, 10);
|
||||
e = RL(e + f4(a,b,c) + X[13] + 0x8f1bbcdc, 9) + d; b = RL(b, 10);
|
||||
d = RL(d + f4(e,a,b) + X[ 3] + 0x8f1bbcdc, 14) + c; a = RL(a, 10);
|
||||
c = RL(c + f4(d,e,a) + X[ 7] + 0x8f1bbcdc, 5) + b; e = RL(e, 10);
|
||||
b = RL(b + f4(c,d,e) + X[15] + 0x8f1bbcdc, 6) + a; d = RL(d, 10);
|
||||
a = RL(a + f4(b,c,d) + X[14] + 0x8f1bbcdc, 8) + e; c = RL(c, 10);
|
||||
e = RL(e + f4(a,b,c) + X[ 5] + 0x8f1bbcdc, 6) + d; b = RL(b, 10);
|
||||
d = RL(d + f4(e,a,b) + X[ 6] + 0x8f1bbcdc, 5) + c; a = RL(a, 10);
|
||||
c = RL(c + f4(d,e,a) + X[ 2] + 0x8f1bbcdc, 12) + b; e = RL(e, 10);
|
||||
|
||||
// right
|
||||
cc = RL(cc + f2(dd,ee,aa) + X[ 8] + 0x7a6d76e9, 15) + bb; ee = RL(ee, 10);
|
||||
bb = RL(bb + f2(cc,dd,ee) + X[ 6] + 0x7a6d76e9, 5) + aa; dd = RL(dd, 10);
|
||||
aa = RL(aa + f2(bb,cc,dd) + X[ 4] + 0x7a6d76e9, 8) + ee; cc = RL(cc, 10);
|
||||
ee = RL(ee + f2(aa,bb,cc) + X[ 1] + 0x7a6d76e9, 11) + dd; bb = RL(bb, 10);
|
||||
dd = RL(dd + f2(ee,aa,bb) + X[ 3] + 0x7a6d76e9, 14) + cc; aa = RL(aa, 10);
|
||||
cc = RL(cc + f2(dd,ee,aa) + X[11] + 0x7a6d76e9, 14) + bb; ee = RL(ee, 10);
|
||||
bb = RL(bb + f2(cc,dd,ee) + X[15] + 0x7a6d76e9, 6) + aa; dd = RL(dd, 10);
|
||||
aa = RL(aa + f2(bb,cc,dd) + X[ 0] + 0x7a6d76e9, 14) + ee; cc = RL(cc, 10);
|
||||
ee = RL(ee + f2(aa,bb,cc) + X[ 5] + 0x7a6d76e9, 6) + dd; bb = RL(bb, 10);
|
||||
dd = RL(dd + f2(ee,aa,bb) + X[12] + 0x7a6d76e9, 9) + cc; aa = RL(aa, 10);
|
||||
cc = RL(cc + f2(dd,ee,aa) + X[ 2] + 0x7a6d76e9, 12) + bb; ee = RL(ee, 10);
|
||||
bb = RL(bb + f2(cc,dd,ee) + X[13] + 0x7a6d76e9, 9) + aa; dd = RL(dd, 10);
|
||||
aa = RL(aa + f2(bb,cc,dd) + X[ 9] + 0x7a6d76e9, 12) + ee; cc = RL(cc, 10);
|
||||
ee = RL(ee + f2(aa,bb,cc) + X[ 7] + 0x7a6d76e9, 5) + dd; bb = RL(bb, 10);
|
||||
dd = RL(dd + f2(ee,aa,bb) + X[10] + 0x7a6d76e9, 15) + cc; aa = RL(aa, 10);
|
||||
cc = RL(cc + f2(dd,ee,aa) + X[14] + 0x7a6d76e9, 8) + bb; ee = RL(ee, 10);
|
||||
|
||||
//
|
||||
// Rounds 64-79
|
||||
//
|
||||
// left
|
||||
b = RL(b + f5(c,d,e) + X[ 4] + 0xa953fd4e, 9) + a; d = RL(d, 10);
|
||||
a = RL(a + f5(b,c,d) + X[ 0] + 0xa953fd4e, 15) + e; c = RL(c, 10);
|
||||
e = RL(e + f5(a,b,c) + X[ 5] + 0xa953fd4e, 5) + d; b = RL(b, 10);
|
||||
d = RL(d + f5(e,a,b) + X[ 9] + 0xa953fd4e, 11) + c; a = RL(a, 10);
|
||||
c = RL(c + f5(d,e,a) + X[ 7] + 0xa953fd4e, 6) + b; e = RL(e, 10);
|
||||
b = RL(b + f5(c,d,e) + X[12] + 0xa953fd4e, 8) + a; d = RL(d, 10);
|
||||
a = RL(a + f5(b,c,d) + X[ 2] + 0xa953fd4e, 13) + e; c = RL(c, 10);
|
||||
e = RL(e + f5(a,b,c) + X[10] + 0xa953fd4e, 12) + d; b = RL(b, 10);
|
||||
d = RL(d + f5(e,a,b) + X[14] + 0xa953fd4e, 5) + c; a = RL(a, 10);
|
||||
c = RL(c + f5(d,e,a) + X[ 1] + 0xa953fd4e, 12) + b; e = RL(e, 10);
|
||||
b = RL(b + f5(c,d,e) + X[ 3] + 0xa953fd4e, 13) + a; d = RL(d, 10);
|
||||
a = RL(a + f5(b,c,d) + X[ 8] + 0xa953fd4e, 14) + e; c = RL(c, 10);
|
||||
e = RL(e + f5(a,b,c) + X[11] + 0xa953fd4e, 11) + d; b = RL(b, 10);
|
||||
d = RL(d + f5(e,a,b) + X[ 6] + 0xa953fd4e, 8) + c; a = RL(a, 10);
|
||||
c = RL(c + f5(d,e,a) + X[15] + 0xa953fd4e, 5) + b; e = RL(e, 10);
|
||||
b = RL(b + f5(c,d,e) + X[13] + 0xa953fd4e, 6) + a; d = RL(d, 10);
|
||||
|
||||
// right
|
||||
bb = RL(bb + f1(cc,dd,ee) + X[12], 8) + aa; dd = RL(dd, 10);
|
||||
aa = RL(aa + f1(bb,cc,dd) + X[15], 5) + ee; cc = RL(cc, 10);
|
||||
ee = RL(ee + f1(aa,bb,cc) + X[10], 12) + dd; bb = RL(bb, 10);
|
||||
dd = RL(dd + f1(ee,aa,bb) + X[ 4], 9) + cc; aa = RL(aa, 10);
|
||||
cc = RL(cc + f1(dd,ee,aa) + X[ 1], 12) + bb; ee = RL(ee, 10);
|
||||
bb = RL(bb + f1(cc,dd,ee) + X[ 5], 5) + aa; dd = RL(dd, 10);
|
||||
aa = RL(aa + f1(bb,cc,dd) + X[ 8], 14) + ee; cc = RL(cc, 10);
|
||||
ee = RL(ee + f1(aa,bb,cc) + X[ 7], 6) + dd; bb = RL(bb, 10);
|
||||
dd = RL(dd + f1(ee,aa,bb) + X[ 6], 8) + cc; aa = RL(aa, 10);
|
||||
cc = RL(cc + f1(dd,ee,aa) + X[ 2], 13) + bb; ee = RL(ee, 10);
|
||||
bb = RL(bb + f1(cc,dd,ee) + X[13], 6) + aa; dd = RL(dd, 10);
|
||||
aa = RL(aa + f1(bb,cc,dd) + X[14], 5) + ee; cc = RL(cc, 10);
|
||||
ee = RL(ee + f1(aa,bb,cc) + X[ 0], 15) + dd; bb = RL(bb, 10);
|
||||
dd = RL(dd + f1(ee,aa,bb) + X[ 3], 13) + cc; aa = RL(aa, 10);
|
||||
cc = RL(cc + f1(dd,ee,aa) + X[ 9], 11) + bb; ee = RL(ee, 10);
|
||||
bb = RL(bb + f1(cc,dd,ee) + X[11], 11) + aa; dd = RL(dd, 10);
|
||||
|
||||
dd += c + H1;
|
||||
H1 = H2 + d + ee;
|
||||
H2 = H3 + e + aa;
|
||||
H3 = H4 + a + bb;
|
||||
H4 = H0 + b + cc;
|
||||
H0 = dd;
|
||||
|
||||
//
|
||||
// reset the offset and clean out the word buffer.
|
||||
//
|
||||
xOff = 0;
|
||||
for (int i = 0; i != X.length; i++)
|
||||
{
|
||||
X[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public Memoable copy()
|
||||
{
|
||||
return new RIPEMD160Digest(this);
|
||||
}
|
||||
|
||||
public void reset(Memoable other)
|
||||
{
|
||||
RIPEMD160Digest d = (RIPEMD160Digest)other;
|
||||
|
||||
copyIn(d);
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package com.vitorpamplona.quartz.ots.exceptions;
|
||||
|
||||
public class CommitmentNotFoundException extends Exception {
|
||||
public CommitmentNotFoundException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package com.vitorpamplona.quartz.ots.exceptions;
|
||||
|
||||
public class DeserializationException extends Exception {
|
||||
public DeserializationException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package com.vitorpamplona.quartz.ots.exceptions;
|
||||
|
||||
public class ExceededSizeException extends Exception {
|
||||
public ExceededSizeException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.vitorpamplona.quartz.ots.exceptions;
|
||||
|
||||
public class UrlException extends Exception {
|
||||
public UrlException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package com.vitorpamplona.quartz.ots.exceptions;
|
||||
|
||||
public class VerificationException extends Exception {
|
||||
public VerificationException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
package com.vitorpamplona.quartz.ots.http;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
/**
|
||||
* For making an HTTP request.
|
||||
*/
|
||||
public class Request implements Callable<Response> {
|
||||
private URL url;
|
||||
private byte[] data;
|
||||
private Map<String, String> headers;
|
||||
private BlockingQueue<Response> queue;
|
||||
|
||||
public Request(URL url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public void setData(byte[] data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public void setHeaders(Map<String, String> headers) {
|
||||
this.headers = headers;
|
||||
}
|
||||
|
||||
public void setQueue(BlockingQueue<Response> queue) {
|
||||
this.queue = queue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response call() throws Exception {
|
||||
Response response = new Response();
|
||||
|
||||
try {
|
||||
HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
|
||||
httpURLConnection.setReadTimeout(10000);
|
||||
httpURLConnection.setConnectTimeout(10000);
|
||||
httpURLConnection.setRequestProperty("User-Agent", "OpenTimestamps Java");
|
||||
httpURLConnection.setRequestProperty("Accept", "application/json");
|
||||
httpURLConnection.setRequestProperty("Accept-Encoding", "gzip");
|
||||
|
||||
if (headers != null) {
|
||||
for (Map.Entry<String, String> entry : headers.entrySet()) {
|
||||
httpURLConnection.setRequestProperty(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
if (data != null) {
|
||||
httpURLConnection.setDoOutput(true);
|
||||
httpURLConnection.setRequestMethod("POST");
|
||||
httpURLConnection.setRequestProperty("Content-Length", "" + Integer.toString(this.data.length));
|
||||
DataOutputStream wr = new DataOutputStream(httpURLConnection.getOutputStream());
|
||||
wr.write(this.data, 0, this.data.length);
|
||||
wr.flush();
|
||||
wr.close();
|
||||
} else {
|
||||
httpURLConnection.setRequestMethod("GET");
|
||||
}
|
||||
|
||||
httpURLConnection.connect();
|
||||
|
||||
int responseCode = httpURLConnection.getResponseCode();
|
||||
|
||||
Log.i("OpenTimestamp", responseCode + " responseCode ");
|
||||
|
||||
response.setStatus(responseCode);
|
||||
response.setFromUrl(url.toString());
|
||||
InputStream is = httpURLConnection.getInputStream();
|
||||
if ("gzip".equals(httpURLConnection.getContentEncoding())) {
|
||||
is = new GZIPInputStream(is);
|
||||
}
|
||||
response.setStream(is);
|
||||
} catch (Exception e) {
|
||||
Log.w("OpenTimestamp", url.toString() + " exception " + e);
|
||||
} finally {
|
||||
if (queue != null) {
|
||||
queue.offer(response);
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
public static String urlEncodeUTF8(String s) {
|
||||
try {
|
||||
return URLEncoder.encode(s, "UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new UnsupportedOperationException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static String urlEncodeUTF8(Map<?, ?> map) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
for (Map.Entry<?, ?> entry : map.entrySet()) {
|
||||
if (sb.length() > 0) {
|
||||
sb.append("&");
|
||||
}
|
||||
|
||||
sb.append(String.format("%s=%s",
|
||||
urlEncodeUTF8(entry.getKey().toString()),
|
||||
urlEncodeUTF8(entry.getValue().toString())
|
||||
));
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package com.vitorpamplona.quartz.ots.http;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* Holds the response from an HTTP request.
|
||||
*/
|
||||
public class Response {
|
||||
private InputStream stream;
|
||||
private String fromUrl;
|
||||
private Integer status;
|
||||
|
||||
public Response() {
|
||||
}
|
||||
|
||||
public Response(InputStream stream) {
|
||||
this.stream = stream;
|
||||
}
|
||||
|
||||
public void setStream(InputStream stream) {
|
||||
this.stream = stream;
|
||||
}
|
||||
|
||||
public Integer getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(Integer status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public boolean isOk() {
|
||||
return getStatus() != null && 200 == getStatus();
|
||||
}
|
||||
|
||||
public String getFromUrl() {
|
||||
return fromUrl;
|
||||
}
|
||||
|
||||
public void setFromUrl(String fromUrl) {
|
||||
this.fromUrl = fromUrl;
|
||||
}
|
||||
|
||||
public InputStream getStream() {
|
||||
return this.stream;
|
||||
}
|
||||
|
||||
public String getString() throws IOException {
|
||||
return new String(getBytes(), StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
public byte[] getBytes() throws IOException {
|
||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||
int nRead;
|
||||
byte[] data = new byte[16384];
|
||||
|
||||
while ((nRead = this.stream.read(data, 0, data.length)) != -1) {
|
||||
buffer.write(data, 0, nRead);
|
||||
}
|
||||
|
||||
buffer.flush();
|
||||
|
||||
return buffer.toByteArray();
|
||||
}
|
||||
|
||||
public JSONObject getJson() throws IOException, JSONException {
|
||||
String jsonString = getString();
|
||||
JSONObject json = new JSONObject(jsonString);
|
||||
|
||||
return json;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
package com.vitorpamplona.quartz.ots.op;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.vitorpamplona.quartz.ots.StreamDeserializationContext;
|
||||
import com.vitorpamplona.quartz.ots.StreamSerializationContext;
|
||||
|
||||
import com.vitorpamplona.quartz.ots.exceptions.DeserializationException;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Operations are the edges in the timestamp tree, with each operation taking a message and zero or more arguments to produce a result.
|
||||
*/
|
||||
public abstract class Op implements Comparable<Op> {
|
||||
|
||||
/**
|
||||
* Maximum length of an com.vitorpamplona.quartz.ots.op.Op result
|
||||
* <p>
|
||||
* For a verifier, this limit is what limits the maximum amount of memory you
|
||||
* need at any one time to verify a particular timestamp path; while verifying
|
||||
* a particular commitment operation path previously calculated results can be
|
||||
* discarded.
|
||||
* <p>
|
||||
* Of course, if everything was a merkle tree you never need to append/prepend
|
||||
* anything near 4KiB of data; 64 bytes would be plenty even with SHA512. The
|
||||
* main need for this is compatibility with existing systems like Bitcoin
|
||||
* timestamps and Certificate Transparency servers. While the pathological
|
||||
* limits required by both are quite large - 1MB and 16MiB respectively - 4KiB
|
||||
* is perfectly adequate in both cases for more reasonable usage.
|
||||
* <p>
|
||||
* @see Op subclasses should set this limit even lower if doing so is appropriate
|
||||
* for them.
|
||||
*/
|
||||
public static int _MAX_RESULT_LENGTH = 4096;
|
||||
|
||||
/**
|
||||
* Maximum length of the message an com.vitorpamplona.quartz.ots.op.Op can be applied too.
|
||||
* <p>
|
||||
* Similar to the result length limit, this limit gives implementations a sane
|
||||
* constraint to work with; the maximum result-length limit implicitly
|
||||
* constrains maximum message length anyway.
|
||||
* <p>
|
||||
* com.vitorpamplona.quartz.ots.op.Op subclasses should set this limit even lower if doing so is appropriate
|
||||
* for them.
|
||||
*/
|
||||
public static int _MAX_MSG_LENGTH = 4096;
|
||||
|
||||
public static byte _TAG = (byte) 0x00;
|
||||
|
||||
public String _TAG_NAME() {
|
||||
return "";
|
||||
}
|
||||
|
||||
public byte _TAG() {
|
||||
return Op._TAG;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize operation from a buffer.
|
||||
*
|
||||
* @param ctx The stream deserialization context.
|
||||
* @return The subclass Operation.
|
||||
*/
|
||||
public static Op deserialize(StreamDeserializationContext ctx) throws DeserializationException {
|
||||
byte tag = ctx.readBytes(1)[0];
|
||||
|
||||
return Op.deserializeFromTag(ctx, tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize operation from a buffer.
|
||||
*
|
||||
* @param ctx The stream deserialization context.
|
||||
* @param tag The tag of the operation.
|
||||
* @return The subclass Operation.
|
||||
*/
|
||||
public static Op deserializeFromTag(StreamDeserializationContext ctx, byte tag)
|
||||
throws DeserializationException {
|
||||
if (tag == OpAppend._TAG) {
|
||||
return OpAppend.deserializeFromTag(ctx, tag);
|
||||
} else if (tag == OpPrepend._TAG) {
|
||||
return OpPrepend.deserializeFromTag(ctx, tag);
|
||||
} else if (tag == OpSHA1._TAG) {
|
||||
return OpSHA1.deserializeFromTag(ctx, tag);
|
||||
} else if (tag == OpSHA256._TAG) {
|
||||
return OpSHA256.deserializeFromTag(ctx, tag);
|
||||
} else if (tag == OpRIPEMD160._TAG) {
|
||||
return OpRIPEMD160.deserializeFromTag(ctx, tag);
|
||||
} else if (tag == OpKECCAK256._TAG) {
|
||||
return OpKECCAK256.deserializeFromTag(ctx, tag);
|
||||
} else {
|
||||
Log.e("OpenTimestamp", "Unknown operation tag: " + tag + " 0x" + String.format("%02x", tag));
|
||||
return null; // TODO: Is this OK? Won't it blow up later? Better to throw?
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize operation.
|
||||
*
|
||||
* @param ctx The stream serialization context.
|
||||
*/
|
||||
public void serialize(StreamSerializationContext ctx) {
|
||||
if (this._TAG() == 0x00) {
|
||||
Log.e("OpenTimestamp", "No valid serialized Op");
|
||||
// TODO: Is it OK to just log and carry on? Won't it blow up later? Better to throw?
|
||||
}
|
||||
|
||||
ctx.writeByte(this._TAG());
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the operation to a message.
|
||||
* Raises MsgValueError if the message value is invalid, such as it being
|
||||
* too long, or it causing the result to be too long.
|
||||
*
|
||||
* @param msg The message.
|
||||
* @return the msg after the operation has been applied
|
||||
*/
|
||||
public byte[] call(byte[] msg) {
|
||||
if (msg.length > _MAX_MSG_LENGTH) {
|
||||
Log.e("OpenTimestamp", "Error : Message too long;");
|
||||
return new byte[]{}; // TODO: Is this OK? Won't it blow up later? Better to throw?
|
||||
}
|
||||
|
||||
byte[] r = this.call(msg);
|
||||
|
||||
if (r.length > _MAX_RESULT_LENGTH) {
|
||||
Log.e("OpenTimestamp", "Error : Result too long;");
|
||||
// TODO: Is it OK to just log and carry on? Won't it blow up later? Better to throw?
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Op o) {
|
||||
return this._TAG() - o._TAG();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.vitorpamplona.quartz.ots.op;
|
||||
|
||||
import com.vitorpamplona.quartz.ots.StreamDeserializationContext;
|
||||
import com.vitorpamplona.quartz.ots.Utils;
|
||||
|
||||
import com.vitorpamplona.quartz.ots.exceptions.DeserializationException;
|
||||
import java.util.Arrays;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Append a suffix to a message.
|
||||
*
|
||||
* @see OpBinary
|
||||
*/
|
||||
public class OpAppend extends OpBinary {
|
||||
byte[] arg;
|
||||
|
||||
public static byte _TAG = (byte) 0xf0;
|
||||
|
||||
@Override
|
||||
public byte _TAG() {
|
||||
return OpAppend._TAG;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String _TAG_NAME() {
|
||||
return "append";
|
||||
}
|
||||
|
||||
public OpAppend() {
|
||||
super();
|
||||
this.arg = new byte[]{};
|
||||
}
|
||||
|
||||
public OpAppend(byte[] arg_) {
|
||||
super(arg_);
|
||||
this.arg = arg_;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] call(byte[] msg) {
|
||||
return Utils.arraysConcat(msg, this.arg);
|
||||
}
|
||||
|
||||
public static Op deserializeFromTag(StreamDeserializationContext ctx, byte tag)
|
||||
throws DeserializationException {
|
||||
return OpBinary.deserializeFromTag(ctx, tag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof OpAppend)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Arrays.equals(this.arg, ((OpAppend) obj).arg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return _TAG ^ Arrays.hashCode(this.arg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package com.vitorpamplona.quartz.ots.op;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.vitorpamplona.quartz.encoders.Hex;
|
||||
import com.vitorpamplona.quartz.ots.StreamDeserializationContext;
|
||||
import com.vitorpamplona.quartz.ots.StreamSerializationContext;
|
||||
import com.vitorpamplona.quartz.ots.Utils;
|
||||
|
||||
import com.vitorpamplona.quartz.ots.exceptions.DeserializationException;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Operations that act on a message and a single argument.
|
||||
*
|
||||
* @see OpUnary
|
||||
*/
|
||||
public abstract class OpBinary extends Op implements Comparable<Op> {
|
||||
|
||||
public byte[] arg;
|
||||
|
||||
@Override
|
||||
public String _TAG_NAME() {
|
||||
return "";
|
||||
}
|
||||
|
||||
public OpBinary() {
|
||||
super();
|
||||
this.arg = new byte[]{};
|
||||
}
|
||||
|
||||
public OpBinary(byte[] arg_) {
|
||||
super();
|
||||
this.arg = arg_;
|
||||
}
|
||||
|
||||
public static Op deserializeFromTag(StreamDeserializationContext ctx, byte tag)
|
||||
throws DeserializationException {
|
||||
byte[] arg = ctx.readVarbytes(_MAX_RESULT_LENGTH, 1);
|
||||
|
||||
if (tag == OpAppend._TAG) {
|
||||
return new OpAppend(arg);
|
||||
} else if (tag == OpPrepend._TAG) {
|
||||
return new OpPrepend(arg);
|
||||
} else {
|
||||
Log.e("OpenTimestamp", "Unknown operation tag: " + tag + " 0x" + String.format("%02x", tag));
|
||||
return null; // TODO: Is this OK? Won't it blow up later? Better to throw?
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialize(StreamSerializationContext ctx) {
|
||||
super.serialize(ctx);
|
||||
ctx.writeVarbytes(this.arg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this._TAG_NAME() + ' ' + Hex.encode(this.arg).toLowerCase();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Op o) {
|
||||
if (o instanceof OpBinary && this._TAG() == o._TAG()) {
|
||||
return Utils.compare(this.arg, ((OpBinary) o).arg);
|
||||
}
|
||||
|
||||
return this._TAG() - o._TAG();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return _TAG ^ Arrays.hashCode(this.arg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package com.vitorpamplona.quartz.ots.op;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.vitorpamplona.quartz.ots.StreamDeserializationContext;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
/**
|
||||
* Cryptographic transformations.
|
||||
* These transformations have the unique property that for any length message,
|
||||
* the size of the result they return is fixed. Additionally, they're the only
|
||||
* type of operation that can be applied directly to a stream.
|
||||
*
|
||||
* @see OpUnary
|
||||
*/
|
||||
public class OpCrypto extends OpUnary {
|
||||
|
||||
public String _TAG_NAME = "";
|
||||
|
||||
public String _HASHLIB_NAME() {
|
||||
return "";
|
||||
}
|
||||
|
||||
public int _DIGEST_LENGTH() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
OpCrypto() {
|
||||
super();
|
||||
}
|
||||
|
||||
public static Op deserializeFromTag(StreamDeserializationContext ctx, byte tag) {
|
||||
return OpUnary.deserializeFromTag(ctx, tag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] call(byte[] msg) {
|
||||
// For Sha1 & Sha256 use java.security.MessageDigest library
|
||||
try {
|
||||
MessageDigest digest = MessageDigest.getInstance(this._HASHLIB_NAME());
|
||||
byte[] hash = digest.digest(msg);
|
||||
|
||||
return hash;
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
Log.e("OpenTimestamp", "NoSuchAlgorithmException");
|
||||
e.printStackTrace();
|
||||
|
||||
return new byte[]{}; // TODO: Is this OK? Won't it blow up later? Better to throw?
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] hashFd(StreamDeserializationContext ctx) throws NoSuchAlgorithmException {
|
||||
MessageDigest digest = MessageDigest.getInstance(this._HASHLIB_NAME());
|
||||
byte[] chunk = ctx.read(1048576);
|
||||
|
||||
while (chunk != null && chunk.length > 0) {
|
||||
digest.update(chunk);
|
||||
chunk = ctx.read(1048576);
|
||||
}
|
||||
|
||||
byte[] hash = digest.digest();
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
public byte[] hashFd(File file) throws IOException, NoSuchAlgorithmException {
|
||||
return hashFd(new FileInputStream(file));
|
||||
}
|
||||
|
||||
public byte[] hashFd(byte[] bytes) throws IOException, NoSuchAlgorithmException {
|
||||
StreamDeserializationContext ctx = new StreamDeserializationContext(bytes);
|
||||
|
||||
return hashFd(ctx);
|
||||
}
|
||||
|
||||
public byte[] hashFd(InputStream inputStream) throws IOException, NoSuchAlgorithmException {
|
||||
MessageDigest digest = MessageDigest.getInstance(this._HASHLIB_NAME());
|
||||
byte[] chunk = new byte[1048576];
|
||||
int count = inputStream.read(chunk, 0, 1048576);
|
||||
|
||||
while (count >= 0) {
|
||||
digest.update(chunk, 0, count);
|
||||
count = inputStream.read(chunk, 0, 1048576);
|
||||
}
|
||||
|
||||
inputStream.close();
|
||||
byte[] hash = digest.digest();
|
||||
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package com.vitorpamplona.quartz.ots.op;
|
||||
|
||||
import com.vitorpamplona.quartz.ots.StreamDeserializationContext;
|
||||
import com.vitorpamplona.quartz.ots.Utils;
|
||||
import com.vitorpamplona.quartz.ots.crypto.KeccakDigest;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Cryptographic Keccak256 operation.
|
||||
* Cryptographic operation tag numbers taken from RFC4880, although it's not
|
||||
* guaranteed that they'll continue to match that RFC in the future.
|
||||
*
|
||||
* @see OpCrypto
|
||||
*/
|
||||
public class OpKECCAK256 extends OpCrypto {
|
||||
private KeccakDigest digest = new KeccakDigest(256);
|
||||
|
||||
public static byte _TAG = (byte) 103;
|
||||
|
||||
@Override
|
||||
public byte _TAG() {
|
||||
return OpKECCAK256._TAG;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String _TAG_NAME() {
|
||||
return "keccak256";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String _HASHLIB_NAME() {
|
||||
return "keccak256";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int _DIGEST_LENGTH() {
|
||||
return digest.getDigestSize();
|
||||
}
|
||||
|
||||
public OpKECCAK256() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] call(byte[] msg) {
|
||||
digest.update(msg, 0, msg.length);
|
||||
byte[] hash = new byte[digest.getDigestSize()];
|
||||
digest.doFinal(hash, 0);
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
public static Op deserializeFromTag(StreamDeserializationContext ctx, byte tag) {
|
||||
return OpCrypto.deserializeFromTag(ctx, tag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return (obj instanceof OpKECCAK256);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.vitorpamplona.quartz.ots.op;
|
||||
|
||||
import com.vitorpamplona.quartz.ots.StreamDeserializationContext;
|
||||
import com.vitorpamplona.quartz.ots.Utils;
|
||||
|
||||
import com.vitorpamplona.quartz.ots.exceptions.DeserializationException;
|
||||
import java.util.Arrays;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Prepend a prefix to a message.
|
||||
*
|
||||
* @see OpBinary
|
||||
*/
|
||||
public class OpPrepend extends OpBinary {
|
||||
|
||||
byte[] arg;
|
||||
|
||||
public static byte _TAG = (byte) 0xf1;
|
||||
|
||||
@Override
|
||||
public byte _TAG() {
|
||||
return OpPrepend._TAG;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String _TAG_NAME() {
|
||||
return "prepend";
|
||||
}
|
||||
|
||||
public OpPrepend() {
|
||||
super();
|
||||
this.arg = new byte[]{};
|
||||
}
|
||||
|
||||
public OpPrepend(byte[] arg_) {
|
||||
super(arg_);
|
||||
this.arg = arg_;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] call(byte[] msg) {
|
||||
return Utils.arraysConcat(this.arg, msg);
|
||||
}
|
||||
|
||||
public static Op deserializeFromTag(StreamDeserializationContext ctx, byte tag)
|
||||
throws DeserializationException {
|
||||
return OpBinary.deserializeFromTag(ctx, tag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof OpPrepend)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Arrays.equals(this.arg, ((OpPrepend) obj).arg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return _TAG ^ Arrays.hashCode(this.arg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.vitorpamplona.quartz.ots.op;
|
||||
|
||||
import com.vitorpamplona.quartz.ots.StreamDeserializationContext;
|
||||
import com.vitorpamplona.quartz.ots.Utils;
|
||||
import com.vitorpamplona.quartz.ots.crypto.RIPEMD160Digest;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Cryptographic RIPEMD160 operation.
|
||||
* Cryptographic operation tag numbers taken from RFC4880, although it's not
|
||||
* guaranteed that they'll continue to match that RFC in the future.
|
||||
*
|
||||
* @see OpCrypto
|
||||
*/
|
||||
public class OpRIPEMD160 extends OpCrypto {
|
||||
|
||||
public static byte _TAG = 0x03;
|
||||
|
||||
@Override
|
||||
public byte _TAG() {
|
||||
return OpRIPEMD160._TAG;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String _TAG_NAME() {
|
||||
return "ripemd160";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String _HASHLIB_NAME() {
|
||||
return "ripemd160";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int _DIGEST_LENGTH() {
|
||||
return 20;
|
||||
}
|
||||
|
||||
public OpRIPEMD160() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] call(byte[] msg) {
|
||||
RIPEMD160Digest digest = new RIPEMD160Digest();
|
||||
digest.update(msg, 0, msg.length);
|
||||
byte[] hash = new byte[digest.getDigestSize()];
|
||||
digest.doFinal(hash, 0);
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
public static Op deserializeFromTag(StreamDeserializationContext ctx, byte tag) {
|
||||
return OpCrypto.deserializeFromTag(ctx, tag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return (obj instanceof OpRIPEMD160);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return _TAG;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package com.vitorpamplona.quartz.ots.op;
|
||||
|
||||
import com.vitorpamplona.quartz.ots.StreamDeserializationContext;
|
||||
import com.vitorpamplona.quartz.ots.Utils;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Cryptographic SHA1 operation.
|
||||
* Cryptographic operation tag numbers taken from RFC4880, although it's not
|
||||
* guaranteed that they'll continue to match that RFC in the future.
|
||||
* Remember that for timestamping, hash algorithms with collision attacks
|
||||
* *are* secure! We've still proven that both messages existed prior to some
|
||||
* point in time - the fact that they both have the same hash digest doesn't
|
||||
* change that.
|
||||
* Heck, even md5 is still secure enough for timestamping... but that's
|
||||
* pushing our luck...
|
||||
*
|
||||
* @see OpCrypto
|
||||
*/
|
||||
public class OpSHA1 extends OpCrypto {
|
||||
|
||||
|
||||
public static byte _TAG = 0x02;
|
||||
|
||||
@Override
|
||||
public byte _TAG() {
|
||||
return OpSHA1._TAG;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String _TAG_NAME() {
|
||||
return "sha1";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String _HASHLIB_NAME() {
|
||||
return "SHA-1";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int _DIGEST_LENGTH() {
|
||||
return 20;
|
||||
}
|
||||
|
||||
public OpSHA1() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] call(byte[] msg) {
|
||||
return super.call(msg);
|
||||
}
|
||||
|
||||
public static Op deserializeFromTag(StreamDeserializationContext ctx, byte tag) {
|
||||
return OpCrypto.deserializeFromTag(ctx, tag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return (obj instanceof OpSHA1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return _TAG;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package com.vitorpamplona.quartz.ots.op;
|
||||
|
||||
import com.vitorpamplona.quartz.ots.StreamDeserializationContext;
|
||||
import com.vitorpamplona.quartz.ots.Utils;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Cryptographic SHA256 operation.
|
||||
* Cryptographic operation tag numbers taken from RFC4880, although it's not
|
||||
* guaranteed that they'll continue to match that RFC in the future.
|
||||
*
|
||||
* @see OpCrypto
|
||||
*/
|
||||
public class OpSHA256 extends OpCrypto {
|
||||
|
||||
|
||||
public static byte _TAG = 0x08;
|
||||
|
||||
@Override
|
||||
public byte _TAG() {
|
||||
return OpSHA256._TAG;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String _TAG_NAME() {
|
||||
return "sha256";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String _HASHLIB_NAME() {
|
||||
return "SHA-256";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int _DIGEST_LENGTH() {
|
||||
return 32;
|
||||
}
|
||||
|
||||
public OpSHA256() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] call(byte[] msg) {
|
||||
return super.call(msg);
|
||||
}
|
||||
|
||||
public static Op deserializeFromTag(StreamDeserializationContext ctx, byte tag) {
|
||||
return OpCrypto.deserializeFromTag(ctx, tag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return (obj instanceof OpSHA256);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return _TAG;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.vitorpamplona.quartz.ots.op;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.vitorpamplona.quartz.ots.StreamDeserializationContext;
|
||||
import com.vitorpamplona.quartz.ots.Utils;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Operations that act on a single message.
|
||||
*
|
||||
* @see Op
|
||||
*/
|
||||
public abstract class OpUnary extends Op {
|
||||
|
||||
@Override
|
||||
public String _TAG_NAME() {
|
||||
return "";
|
||||
}
|
||||
|
||||
public OpUnary() {
|
||||
super();
|
||||
}
|
||||
|
||||
public static Op deserializeFromTag(StreamDeserializationContext ctx, byte tag) {
|
||||
if (tag == OpSHA1._TAG) {
|
||||
return new OpSHA1();
|
||||
} else if (tag == OpSHA256._TAG) {
|
||||
return new OpSHA256();
|
||||
} else if (tag == OpRIPEMD160._TAG) {
|
||||
return new OpRIPEMD160();
|
||||
} else if (tag == OpKECCAK256._TAG) {
|
||||
return new OpKECCAK256();
|
||||
} else {
|
||||
Log.e("OpenTimestamp", "Unknown operation tag: " + tag);
|
||||
|
||||
return null; // TODO: Is this OK? Won't it blow up later? Better to throw?
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this._TAG_NAME();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user