Adds local cache to wait for OTS confirmations

Presents OTS pending and confirmations on the screen
Adds Menu item to OTS any post.
This commit is contained in:
Vitor Pamplona
2024-02-26 18:51:23 -05:00
parent bf67ff64b4
commit 8ebad524ff
12 changed files with 321 additions and 34 deletions
@@ -26,8 +26,8 @@ import com.vitorpamplona.quartz.events.Event
import com.vitorpamplona.quartz.events.OtsEvent
import com.vitorpamplona.quartz.signers.NostrSignerInternal
import junit.framework.TestCase.assertEquals
import junit.framework.TestCase.fail
import org.junit.Assert
import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import java.util.concurrent.CountDownLatch
@@ -60,9 +60,33 @@ class OtsTest {
val ots = Event.Companion.fromJson(otsPendingEvent) as OtsEvent
println(ots.info())
assertEquals(null, ots.verify())
val eventId =
ots.digestEvent() ?: run {
fail("Should not be null")
return
}
val upgraded = OtsEvent.upgrade(ots.content, eventId)
val signer = NostrSignerInternal(KeyPair())
var newOts: OtsEvent? = null
val countDownLatch = CountDownLatch(1)
OtsEvent.create(eventId, upgraded, signer) {
newOts = it
countDownLatch.countDown()
}
Assert.assertTrue(countDownLatch.await(1, TimeUnit.SECONDS))
println(newOts!!.toJson())
println(newOts!!.info())
assertEquals(1708879025L, newOts!!.verify())
}
@Ignore("Need to figure out a way to wait for Bitcoin confirmations to test thiss")
@Test
fun createOTSEventAndVerify() {
val signer = NostrSignerInternal(KeyPair())
@@ -70,7 +94,7 @@ class OtsTest {
val countDownLatch = CountDownLatch(1)
OtsEvent.create(otsEvent2Digest, signer) {
OtsEvent.create(otsEvent2Digest, OtsEvent.stamp(otsEvent2Digest), signer) {
ots = it
countDownLatch.countDown()
}
@@ -80,6 +104,6 @@ class OtsTest {
println(ots!!.toJson())
println(ots!!.info())
assertEquals(1706322179L, ots!!.verify())
assertEquals(null, ots!!.verify())
}
}
@@ -45,6 +45,9 @@ class OtsEvent(
content: String,
sig: HexKey,
) : Event(id, pubKey, createdAt, KIND, tags, content, sig) {
@Transient
var verifiedTime: Long? = null
fun digestEvent() = tags.firstOrNull { it.size > 1 && it[0] == "e" }?.get(1)
fun digest() = digestEvent()?.hexToByteArray()
@@ -53,25 +56,19 @@ class OtsEvent(
return Base64.getDecoder().decode(content)
}
fun verify(): Long? {
try {
val digest = digest() ?: return null
val detachedOts = DetachedTimestampFile.deserialize(otsByteArray())
val result = otsInstance.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 cacheVerify(): Long? {
return if (verifiedTime != null) {
verifiedTime
} else {
verifiedTime = verify()
verifiedTime
}
}
fun verify(): Long? {
return digestEvent()?.let { OtsEvent.verify(otsByteArray(), it) }
}
fun info(): String {
val detachedOts = DetachedTimestampFile.deserialize(otsByteArray())
return otsInstance.info(detachedOts)
@@ -83,27 +80,77 @@ class OtsEvent(
var otsInstance = OpenTimestamps(BlockstreamExplorer(), CalendarPureJavaBuilder())
fun stamp(eventId: HexKey): ByteArray {
fun stamp(eventId: HexKey): String {
val hash = Hash(eventId.hexToByteArray(), OpSHA256._TAG)
val file = DetachedTimestampFile.from(hash)
val timestamp = otsInstance.stamp(file)
val detachedToSerialize = DetachedTimestampFile(hash.getOp(), timestamp)
return detachedToSerialize.serialize()
return Base64.getEncoder().encodeToString(detachedToSerialize.serialize())
}
fun upgrade(
otsFile: String,
eventId: HexKey,
): String {
val detachedOts = DetachedTimestampFile.deserialize(Base64.getDecoder().decode(otsFile))
return if (otsInstance.upgrade(detachedOts)) {
// if the change is now verifiable.
if (verify(detachedOts, eventId) != null) {
Base64.getEncoder().encodeToString(detachedOts.serialize())
} else {
otsFile
}
} else {
otsFile
}
}
fun verify(
otsFile: String,
eventId: HexKey,
): Long? {
return verify(Base64.getDecoder().decode(otsFile), eventId)
}
fun verify(
otsFile: ByteArray,
eventId: HexKey,
): Long? {
return verify(DetachedTimestampFile.deserialize(otsFile), eventId)
}
fun verify(
detachedOts: DetachedTimestampFile,
eventId: HexKey,
): Long? {
try {
val result = otsInstance.verify(detachedOts, eventId.hexToByteArray())
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 create(
eventId: HexKey,
otsFileBase64: String,
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)
signer.sign(createdAt, KIND, tags, otsFileBase64, onReady)
}
}
}