- Generalizing OTS Calendar access
- Developing OTS access points with OkHttp
This commit is contained in:
@@ -29,7 +29,11 @@ import android.util.Log
|
||||
import androidx.security.crypto.EncryptedSharedPreferences
|
||||
import coil.ImageLoader
|
||||
import coil.disk.DiskCache
|
||||
import com.vitorpamplona.amethyst.service.ots.OkHttpBlockstreamExplorer
|
||||
import com.vitorpamplona.amethyst.service.ots.OkHttpCalendarBuilder
|
||||
import com.vitorpamplona.amethyst.service.playback.VideoCache
|
||||
import com.vitorpamplona.quartz.events.OtsEvent
|
||||
import com.vitorpamplona.quartz.ots.OpenTimestamps
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
@@ -65,6 +69,8 @@ class Amethyst : Application() {
|
||||
super.onCreate()
|
||||
instance = this
|
||||
|
||||
OtsEvent.otsInstance = OpenTimestamps(OkHttpBlockstreamExplorer(), OkHttpCalendarBuilder())
|
||||
|
||||
if (BuildConfig.DEBUG) {
|
||||
StrictMode.setThreadPolicy(
|
||||
ThreadPolicy.Builder().detectAll().penaltyLog().build(),
|
||||
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* 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.amethyst.service.ots
|
||||
|
||||
import android.util.Log
|
||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||
import com.vitorpamplona.amethyst.BuildConfig
|
||||
import com.vitorpamplona.amethyst.service.HttpClientManager
|
||||
import com.vitorpamplona.quartz.ots.BitcoinExplorer
|
||||
import com.vitorpamplona.quartz.ots.BlockHeader
|
||||
import com.vitorpamplona.quartz.ots.exceptions.UrlException
|
||||
import okhttp3.Request
|
||||
|
||||
class OkHttpBlockstreamExplorer : BitcoinExplorer {
|
||||
/**
|
||||
* Retrieve the block information from the block hash.
|
||||
*
|
||||
* @param hash Hash of the block.
|
||||
* @return the blockheader of the hash
|
||||
* @throws Exception desc
|
||||
*/
|
||||
override fun block(hash: String): BlockHeader {
|
||||
val client = HttpClientManager.getHttpClient()
|
||||
val url = "$BLOCKSTREAM_API_URL/block/$hash"
|
||||
|
||||
val request =
|
||||
Request.Builder()
|
||||
.header("User-Agent", "Amethyst/${BuildConfig.VERSION_NAME}")
|
||||
.header("Accept", "application/json")
|
||||
.url(url)
|
||||
.get()
|
||||
.build()
|
||||
|
||||
client.newCall(request).execute().use {
|
||||
if (it.isSuccessful) {
|
||||
val jsonObject = jacksonObjectMapper().readTree(it.body.string())
|
||||
|
||||
val blockHeader = BlockHeader()
|
||||
blockHeader.merkleroot = jsonObject["merkle_root"].asText()
|
||||
blockHeader.setTime(jsonObject["timestamp"].asInt().toString())
|
||||
blockHeader.blockHash = hash
|
||||
Log.d("OkHttpBlockstreamExplorer", "$BLOCKSTREAM_API_URL/block/$hash")
|
||||
return blockHeader
|
||||
} else {
|
||||
throw UrlException("Couldn't open $url: " + it.message + " " + it.code)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
@Throws(Exception::class)
|
||||
override fun blockHash(height: Int): String {
|
||||
val client = HttpClientManager.getHttpClient()
|
||||
|
||||
val url = "$BLOCKSTREAM_API_URL/block-height/$height"
|
||||
|
||||
val request =
|
||||
Request.Builder()
|
||||
.header("User-Agent", "Amethyst/${BuildConfig.VERSION_NAME}")
|
||||
.url(url)
|
||||
.get()
|
||||
.build()
|
||||
|
||||
client.newCall(request).execute().use {
|
||||
if (it.isSuccessful) {
|
||||
val blockHash = it.body.string()
|
||||
|
||||
Log.d("OkHttpBlockstreamExplorer", "$url $blockHash")
|
||||
return blockHash
|
||||
} else {
|
||||
throw UrlException(it.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val BLOCKSTREAM_API_URL = "https://blockstream.info/api"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
/**
|
||||
* 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.amethyst.service.ots
|
||||
|
||||
import com.vitorpamplona.amethyst.BuildConfig
|
||||
import com.vitorpamplona.amethyst.service.HttpClientManager
|
||||
import com.vitorpamplona.quartz.encoders.Hex
|
||||
import com.vitorpamplona.quartz.ots.ICalendar
|
||||
import com.vitorpamplona.quartz.ots.StreamDeserializationContext
|
||||
import com.vitorpamplona.quartz.ots.Timestamp
|
||||
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 okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
|
||||
/**
|
||||
* Class representing remote calendar server interface.
|
||||
*/
|
||||
class OkHttpCalendar(val url: String) : ICalendar {
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
@Throws(ExceededSizeException::class, UrlException::class, DeserializationException::class)
|
||||
override fun submit(digest: ByteArray): Timestamp {
|
||||
try {
|
||||
val client = HttpClientManager.getHttpClient()
|
||||
val url = "$url/digest"
|
||||
|
||||
val mediaType = "application/x-www-form-urlencoded; charset=utf-8".toMediaType()
|
||||
val requestBody = digest.toRequestBody(mediaType)
|
||||
|
||||
val request =
|
||||
okhttp3.Request.Builder()
|
||||
.header("User-Agent", "Amethyst/${BuildConfig.VERSION_NAME}")
|
||||
.header("Accept", "application/vnd.opentimestamps.v1")
|
||||
.header("Content-Type", "application/x-www-form-urlencoded")
|
||||
.url(url)
|
||||
.post(requestBody)
|
||||
.build()
|
||||
|
||||
client.newCall(request).execute().use {
|
||||
if (it.isSuccessful) {
|
||||
val ctx = StreamDeserializationContext(it.body.bytes())
|
||||
return Timestamp.deserialize(ctx, digest)
|
||||
} else {
|
||||
throw UrlException("Failed to open $url")
|
||||
}
|
||||
}
|
||||
} catch (e: ExceededSizeException) {
|
||||
throw e
|
||||
} catch (e: DeserializationException) {
|
||||
throw e
|
||||
} catch (e: Exception) {
|
||||
throw UrlException(e.message)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
@Throws(
|
||||
DeserializationException::class,
|
||||
ExceededSizeException::class,
|
||||
CommitmentNotFoundException::class,
|
||||
UrlException::class,
|
||||
)
|
||||
override fun getTimestamp(commitment: ByteArray): Timestamp {
|
||||
try {
|
||||
val client = HttpClientManager.getHttpClient()
|
||||
val url = url + "/timestamp/" + Hex.encode(commitment)
|
||||
|
||||
val request =
|
||||
okhttp3.Request.Builder()
|
||||
.header("User-Agent", "Amethyst/${BuildConfig.VERSION_NAME}")
|
||||
.header("Accept", "application/vnd.opentimestamps.v1")
|
||||
.header("Content-Type", "application/x-www-form-urlencoded")
|
||||
.url(url)
|
||||
.get()
|
||||
.build()
|
||||
|
||||
client.newCall(request).execute().use {
|
||||
if (it.isSuccessful) {
|
||||
val ctx = StreamDeserializationContext(it.body.bytes())
|
||||
return Timestamp.deserialize(ctx, commitment)
|
||||
} else {
|
||||
throw CommitmentNotFoundException("Calendar response a status code != 200: " + it.code)
|
||||
}
|
||||
}
|
||||
} catch (e: DeserializationException) {
|
||||
throw e
|
||||
} catch (e: ExceededSizeException) {
|
||||
throw e
|
||||
} catch (e: CommitmentNotFoundException) {
|
||||
throw e
|
||||
} catch (e: Exception) {
|
||||
throw UrlException(e.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* 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.amethyst.service.ots
|
||||
|
||||
import com.vitorpamplona.amethyst.BuildConfig
|
||||
import com.vitorpamplona.amethyst.service.HttpClientManager
|
||||
import com.vitorpamplona.quartz.ots.ICalendarAsyncSubmit
|
||||
import com.vitorpamplona.quartz.ots.StreamDeserializationContext
|
||||
import com.vitorpamplona.quartz.ots.Timestamp
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import java.util.Optional
|
||||
import java.util.concurrent.BlockingQueue
|
||||
|
||||
/**
|
||||
* For making async calls to a calendar server
|
||||
*/
|
||||
class OkHttpCalendarAsyncSubmit(private val url: String, private val digest: ByteArray) : ICalendarAsyncSubmit {
|
||||
private var queue: BlockingQueue<Optional<Timestamp>>? = null
|
||||
|
||||
fun setQueue(queue: BlockingQueue<Optional<Timestamp>>?) {
|
||||
this.queue = queue
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
override fun call(): Optional<Timestamp> {
|
||||
val client = HttpClientManager.getHttpClient()
|
||||
val url = "$url/digest"
|
||||
|
||||
val mediaType = "application/x-www-form-urlencoded; charset=utf-8".toMediaType()
|
||||
val requestBody = digest.toRequestBody(mediaType)
|
||||
|
||||
val request =
|
||||
okhttp3.Request.Builder()
|
||||
.header("User-Agent", "Amethyst/${BuildConfig.VERSION_NAME}")
|
||||
.header("Accept", "application/vnd.opentimestamps.v1")
|
||||
.header("Content-Type", "application/x-www-form-urlencoded")
|
||||
.url(url)
|
||||
.post(requestBody)
|
||||
.build()
|
||||
|
||||
client.newCall(request).execute().use {
|
||||
if (it.isSuccessful) {
|
||||
val ctx = StreamDeserializationContext(it.body.bytes())
|
||||
val timestamp = Timestamp.deserialize(ctx, digest)
|
||||
val of = Optional.of(timestamp)
|
||||
queue!!.add(of)
|
||||
return of
|
||||
} else {
|
||||
queue!!.add(Optional.empty())
|
||||
return Optional.empty()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* 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.amethyst.service.ots
|
||||
|
||||
import com.vitorpamplona.quartz.ots.CalendarBuilder
|
||||
import com.vitorpamplona.quartz.ots.ICalendar
|
||||
import com.vitorpamplona.quartz.ots.ICalendarAsyncSubmit
|
||||
|
||||
class OkHttpCalendarBuilder : CalendarBuilder {
|
||||
override fun newSyncCalendar(url: String): ICalendar {
|
||||
return OkHttpCalendar(url)
|
||||
}
|
||||
|
||||
override fun newAsyncCalendar(
|
||||
url: String,
|
||||
digest: ByteArray,
|
||||
): ICalendarAsyncSubmit {
|
||||
return OkHttpCalendarAsyncSubmit(url, digest)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user