mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-02-23 06:45:21 +01:00
Oracle Announcement TLVs (#2149)
* Oracle Announcement TLV * Add pubkey, event uri & descriptor * TLVParentFactory, EnumEventDescriptorTLV * Add trailing V0 to types * Make names match spec pr * Add range descriptor * Add num outcomes to enum descriptor
This commit is contained in:
parent
2cad89c1a0
commit
77e8cabf5b
3 changed files with 311 additions and 9 deletions
|
@ -8,7 +8,7 @@ class TLVTest extends BitcoinSUnitTest {
|
|||
implicit override val generatorDrivenConfig: PropertyCheckConfiguration =
|
||||
generatorDrivenConfigNewCode
|
||||
|
||||
"TLV" must "have serizliation symmetry" in {
|
||||
"TLV" must "have serialization symmetry" in {
|
||||
forAll(TLVGen.tlv) { tlv =>
|
||||
assert(TLV(tlv.bytes) == tlv)
|
||||
}
|
||||
|
@ -41,4 +41,46 @@ class TLVTest extends BitcoinSUnitTest {
|
|||
assert(TLV(pong.bytes) == pong)
|
||||
}
|
||||
}
|
||||
|
||||
"EventDescriptorTLV" must "have serialization symmetry" in {
|
||||
forAll(TLVGen.eventDescriptorTLV) { tlv =>
|
||||
assert(EventDescriptorTLV(tlv.bytes) == tlv)
|
||||
assert(TLV(tlv.bytes) == tlv)
|
||||
}
|
||||
}
|
||||
|
||||
"ExternalEventDescriptorTLV" must "have serialization symmetry" in {
|
||||
forAll(TLVGen.externalEventDescriptorV0TLV) { tlv =>
|
||||
assert(ExternalEventDescriptorV0TLV(tlv.bytes) == tlv)
|
||||
assert(TLV(tlv.bytes) == tlv)
|
||||
}
|
||||
}
|
||||
|
||||
"EnumEventDescriptorTLV" must "have serialization symmetry" in {
|
||||
forAll(TLVGen.enumEventDescriptorV0TLV) { tlv =>
|
||||
assert(EnumEventDescriptorV0TLV(tlv.bytes) == tlv)
|
||||
assert(TLV(tlv.bytes) == tlv)
|
||||
}
|
||||
}
|
||||
|
||||
"RangeEventDescriptorV0TLV" must "have serialization symmetry" in {
|
||||
forAll(TLVGen.rangeEventDescriptorV0TLV) { tlv =>
|
||||
assert(RangeEventDescriptorV0TLV(tlv.bytes) == tlv)
|
||||
assert(TLV(tlv.bytes) == tlv)
|
||||
}
|
||||
}
|
||||
|
||||
"OracleEventV0TLV" must "have serialization symmetry" in {
|
||||
forAll(TLVGen.oracleEventV0TLV) { tlv =>
|
||||
assert(OracleEventV0TLV(tlv.bytes) == tlv)
|
||||
assert(TLV(tlv.bytes) == tlv)
|
||||
}
|
||||
}
|
||||
|
||||
"OracleAnnouncementV0TLV" must "have serialization symmetry" in {
|
||||
forAll(TLVGen.oracleAnnouncementV0TLV) { tlv =>
|
||||
assert(OracleAnnouncementV0TLV(tlv.bytes) == tlv)
|
||||
assert(TLV(tlv.bytes) == tlv)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
package org.bitcoins.core.protocol.tlv
|
||||
|
||||
import org.bitcoins.core.number.UInt16
|
||||
import java.nio.charset.StandardCharsets
|
||||
|
||||
import org.bitcoins.core.number._
|
||||
import org.bitcoins.core.protocol.BigSizeUInt
|
||||
import org.bitcoins.core.protocol.script.ScriptPubKey
|
||||
import org.bitcoins.core.protocol.tlv.TLV.DecodeTLVResult
|
||||
import org.bitcoins.crypto.{Factory, NetworkElement}
|
||||
import org.bitcoins.crypto._
|
||||
import scodec.bits.ByteVector
|
||||
|
||||
sealed trait TLV extends NetworkElement {
|
||||
|
@ -19,7 +22,26 @@ sealed trait TLV extends NetworkElement {
|
|||
}
|
||||
}
|
||||
|
||||
object TLV extends Factory[TLV] {
|
||||
sealed trait TLVParentFactory[T <: TLV] extends Factory[T] {
|
||||
|
||||
def typeName: String
|
||||
|
||||
def allFactories: Vector[TLVFactory[T]]
|
||||
|
||||
lazy val knownTypes: Vector[BigSizeUInt] = allFactories.map(_.tpe)
|
||||
|
||||
override def fromBytes(bytes: ByteVector): T = {
|
||||
val DecodeTLVResult(tpe, _, value) = TLV.decodeTLV(bytes)
|
||||
|
||||
allFactories.find(_.tpe == tpe) match {
|
||||
case Some(tlvFactory) => tlvFactory.fromTLVValue(value)
|
||||
case None =>
|
||||
throw new IllegalArgumentException(s"Unknown $typeName type got $tpe")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object TLV extends TLVParentFactory[TLV] {
|
||||
|
||||
case class DecodeTLVResult(
|
||||
tpe: BigSizeUInt,
|
||||
|
@ -40,12 +62,17 @@ object TLV extends Factory[TLV] {
|
|||
DecodeTLVResult(tpe, length, value)
|
||||
}
|
||||
|
||||
private val allFactories: Vector[TLVFactory[TLV]] =
|
||||
Vector(ErrorTLV, PingTLV, PongTLV)
|
||||
val typeName = "TLV"
|
||||
|
||||
val knownTypes: Vector[BigSizeUInt] = allFactories.map(_.tpe)
|
||||
val allFactories: Vector[TLVFactory[TLV]] =
|
||||
Vector(ErrorTLV,
|
||||
PingTLV,
|
||||
PongTLV,
|
||||
OracleEventV0TLV,
|
||||
OracleAnnouncementV0TLV) ++ EventDescriptorTLV.allFactories
|
||||
|
||||
def fromBytes(bytes: ByteVector): TLV = {
|
||||
// Need to override to be able to default to Unknown
|
||||
override def fromBytes(bytes: ByteVector): TLV = {
|
||||
val DecodeTLVResult(tpe, _, value) = decodeTLV(bytes)
|
||||
|
||||
allFactories.find(_.tpe == tpe) match {
|
||||
|
@ -66,6 +93,39 @@ sealed trait TLVFactory[+T <: TLV] extends Factory[T] {
|
|||
|
||||
fromTLVValue(value)
|
||||
}
|
||||
|
||||
protected case class ValueIterator(value: ByteVector, var index: Int = 0) {
|
||||
|
||||
def current: ByteVector = {
|
||||
value.drop(index)
|
||||
}
|
||||
|
||||
def skip(numBytes: Long): Unit = {
|
||||
index += numBytes.toInt
|
||||
()
|
||||
}
|
||||
|
||||
def skip(bytes: NetworkElement): Unit = {
|
||||
skip(bytes.byteSize)
|
||||
}
|
||||
|
||||
def take(numBytes: Int): ByteVector = {
|
||||
val bytes = current.take(numBytes)
|
||||
skip(numBytes)
|
||||
bytes
|
||||
}
|
||||
|
||||
def takeBits(numBits: Int): ByteVector = {
|
||||
require(numBits % 8 == 0,
|
||||
s"Must take a round byte number of bits, got $numBits")
|
||||
take(numBytes = numBits / 8)
|
||||
}
|
||||
|
||||
def takeSPK(): ScriptPubKey = {
|
||||
val len = UInt16(takeBits(16)).toInt
|
||||
ScriptPubKey.fromAsmBytes(take(len))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case class UnknownTLV(tpe: BigSizeUInt, value: ByteVector) extends TLV {
|
||||
|
@ -146,3 +206,160 @@ object PongTLV extends TLVFactory[PongTLV] {
|
|||
new PongTLV(ignored)
|
||||
}
|
||||
}
|
||||
|
||||
sealed trait EventDescriptorTLV extends TLV
|
||||
|
||||
object EventDescriptorTLV extends TLVParentFactory[EventDescriptorTLV] {
|
||||
|
||||
val allFactories: Vector[TLVFactory[EventDescriptorTLV]] =
|
||||
Vector(ExternalEventDescriptorV0TLV,
|
||||
EnumEventDescriptorV0TLV,
|
||||
RangeEventDescriptorV0TLV)
|
||||
|
||||
override def typeName: String = "EventDescriptorTLV"
|
||||
}
|
||||
|
||||
case class ExternalEventDescriptorV0TLV(external_name: String)
|
||||
extends EventDescriptorTLV {
|
||||
override def tpe: BigSizeUInt = ExternalEventDescriptorV0TLV.tpe
|
||||
|
||||
override val value: ByteVector = CryptoUtil.serializeForHash(external_name)
|
||||
}
|
||||
|
||||
object ExternalEventDescriptorV0TLV
|
||||
extends TLVFactory[ExternalEventDescriptorV0TLV] {
|
||||
|
||||
override def apply(external_name: String): ExternalEventDescriptorV0TLV =
|
||||
new ExternalEventDescriptorV0TLV(external_name)
|
||||
|
||||
override val tpe: BigSizeUInt = BigSizeUInt(55300)
|
||||
|
||||
override def fromTLVValue(value: ByteVector): ExternalEventDescriptorV0TLV = {
|
||||
val external_name = new String(value.toArray, StandardCharsets.UTF_8)
|
||||
|
||||
ExternalEventDescriptorV0TLV(external_name)
|
||||
}
|
||||
}
|
||||
|
||||
case class EnumEventDescriptorV0TLV(outcomes: Vector[String])
|
||||
extends EventDescriptorTLV {
|
||||
override def tpe: BigSizeUInt = EnumEventDescriptorV0TLV.tpe
|
||||
|
||||
override val value: ByteVector = {
|
||||
val starting = UInt16(outcomes.size).bytes
|
||||
|
||||
outcomes.foldLeft(starting) { (accum, outcome) =>
|
||||
val outcomeBytes = CryptoUtil.serializeForHash(outcome)
|
||||
accum ++ UInt16(outcomeBytes.length).bytes ++ outcomeBytes
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object EnumEventDescriptorV0TLV extends TLVFactory[EnumEventDescriptorV0TLV] {
|
||||
|
||||
override val tpe: BigSizeUInt = BigSizeUInt(55302)
|
||||
|
||||
override def fromTLVValue(value: ByteVector): EnumEventDescriptorV0TLV = {
|
||||
val iter = ValueIterator(value)
|
||||
|
||||
val count = UInt16(iter.takeBits(16))
|
||||
|
||||
val builder = Vector.newBuilder[String]
|
||||
|
||||
while (iter.index < value.length) {
|
||||
val len = UInt16(iter.takeBits(16))
|
||||
val outcomeBytes = iter.take(len.toInt)
|
||||
val str = new String(outcomeBytes.toArray, StandardCharsets.UTF_8)
|
||||
builder.+=(str)
|
||||
}
|
||||
|
||||
val result = builder.result()
|
||||
|
||||
require(count.toInt == result.size,
|
||||
"Did not parse the expected number of outcomes")
|
||||
|
||||
EnumEventDescriptorV0TLV(result)
|
||||
}
|
||||
}
|
||||
|
||||
case class RangeEventDescriptorV0TLV(start: Int32, stop: Int32, step: UInt16)
|
||||
extends EventDescriptorTLV {
|
||||
override def tpe: BigSizeUInt = RangeEventDescriptorV0TLV.tpe
|
||||
|
||||
override val value: ByteVector = {
|
||||
start.bytes ++ stop.bytes ++ step.bytes
|
||||
}
|
||||
}
|
||||
|
||||
object RangeEventDescriptorV0TLV extends TLVFactory[RangeEventDescriptorV0TLV] {
|
||||
|
||||
override val tpe: BigSizeUInt = BigSizeUInt(55304)
|
||||
|
||||
override def fromTLVValue(value: ByteVector): RangeEventDescriptorV0TLV = {
|
||||
val iter = ValueIterator(value)
|
||||
|
||||
val start = Int32(iter.takeBits(32))
|
||||
val stop = Int32(iter.takeBits(32))
|
||||
val step = UInt16(iter.takeBits(16))
|
||||
|
||||
RangeEventDescriptorV0TLV(start, stop, step)
|
||||
}
|
||||
}
|
||||
|
||||
sealed trait OracleEventTLV extends TLV
|
||||
|
||||
case class OracleEventV0TLV(
|
||||
publicKey: SchnorrPublicKey,
|
||||
nonce: SchnorrNonce,
|
||||
eventMaturityEpoch: UInt32,
|
||||
eventDescriptor: EventDescriptorTLV,
|
||||
eventURI: String
|
||||
) extends OracleEventTLV {
|
||||
override def tpe: BigSizeUInt = OracleEventV0TLV.tpe
|
||||
|
||||
override val value: ByteVector = {
|
||||
val uriBytes = CryptoUtil.serializeForHash(eventURI)
|
||||
publicKey.bytes ++ nonce.bytes ++ eventMaturityEpoch.bytes ++ eventDescriptor.bytes ++ uriBytes
|
||||
}
|
||||
}
|
||||
|
||||
object OracleEventV0TLV extends TLVFactory[OracleEventV0TLV] {
|
||||
override val tpe: BigSizeUInt = BigSizeUInt(55330)
|
||||
|
||||
override def fromTLVValue(value: ByteVector): OracleEventV0TLV = {
|
||||
val iter = ValueIterator(value, 0)
|
||||
|
||||
val publicKey = SchnorrPublicKey(iter.take(32))
|
||||
val nonce = SchnorrNonce(iter.take(32))
|
||||
val eventMaturity = UInt32(iter.takeBits(32))
|
||||
val eventDescriptor = EventDescriptorTLV(iter.current)
|
||||
iter.skip(eventDescriptor.byteSize)
|
||||
val eventURI = new String(iter.current.toArray, StandardCharsets.UTF_8)
|
||||
|
||||
OracleEventV0TLV(publicKey, nonce, eventMaturity, eventDescriptor, eventURI)
|
||||
}
|
||||
}
|
||||
|
||||
sealed trait OracleAnnouncementTLV extends TLV
|
||||
|
||||
case class OracleAnnouncementV0TLV(
|
||||
announcementSignature: SchnorrDigitalSignature,
|
||||
eventTLV: OracleEventV0TLV)
|
||||
extends OracleAnnouncementTLV {
|
||||
override def tpe: BigSizeUInt = OracleAnnouncementV0TLV.tpe
|
||||
|
||||
override val value: ByteVector = announcementSignature.bytes ++ eventTLV.bytes
|
||||
}
|
||||
|
||||
object OracleAnnouncementV0TLV extends TLVFactory[OracleAnnouncementV0TLV] {
|
||||
override val tpe: BigSizeUInt = BigSizeUInt(55332)
|
||||
|
||||
override def fromTLVValue(value: ByteVector): OracleAnnouncementV0TLV = {
|
||||
val iter = ValueIterator(value, 0)
|
||||
|
||||
val sig = SchnorrDigitalSignature(iter.take(64))
|
||||
val eventTLV = OracleEventV0TLV(iter.current)
|
||||
|
||||
OracleAnnouncementV0TLV(sig, eventTLV)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,12 +41,55 @@ trait TLVGen {
|
|||
NumberGenerator.bytevector.map(PongTLV.forIgnored)
|
||||
}
|
||||
|
||||
def externalEventDescriptorV0TLV: Gen[ExternalEventDescriptorV0TLV] = {
|
||||
for {
|
||||
str <- StringGenerators.genString
|
||||
} yield ExternalEventDescriptorV0TLV(str)
|
||||
}
|
||||
|
||||
def enumEventDescriptorV0TLV: Gen[EnumEventDescriptorV0TLV] = {
|
||||
for {
|
||||
numOutcomes <- Gen.choose(2, 10)
|
||||
outcomes <- Gen.listOfN(numOutcomes, StringGenerators.genString)
|
||||
} yield EnumEventDescriptorV0TLV(outcomes.toVector)
|
||||
}
|
||||
|
||||
def rangeEventDescriptorV0TLV: Gen[RangeEventDescriptorV0TLV] = {
|
||||
for {
|
||||
start <- NumberGenerator.int32s
|
||||
stop <- NumberGenerator.int32s.suchThat(_ > start)
|
||||
step <- NumberGenerator.uInt16
|
||||
} yield RangeEventDescriptorV0TLV(start, stop, step)
|
||||
}
|
||||
|
||||
def eventDescriptorTLV: Gen[EventDescriptorTLV] =
|
||||
Gen.oneOf(externalEventDescriptorV0TLV, enumEventDescriptorV0TLV)
|
||||
|
||||
def oracleEventV0TLV: Gen[OracleEventV0TLV] = {
|
||||
for {
|
||||
pubkey <- CryptoGenerators.schnorrPublicKey
|
||||
nonce <- CryptoGenerators.schnorrNonce
|
||||
maturity <- NumberGenerator.uInt32s
|
||||
uri <- StringGenerators.genString
|
||||
desc <- eventDescriptorTLV
|
||||
} yield OracleEventV0TLV(pubkey, nonce, maturity, desc, uri)
|
||||
}
|
||||
|
||||
def oracleAnnouncementV0TLV: Gen[OracleAnnouncementV0TLV] = {
|
||||
for {
|
||||
sig <- CryptoGenerators.schnorrDigitalSignature
|
||||
eventTLV <- oracleEventV0TLV
|
||||
} yield OracleAnnouncementV0TLV(sig, eventTLV)
|
||||
}
|
||||
|
||||
def tlv: Gen[TLV] = {
|
||||
Gen.oneOf(
|
||||
unknownTLV,
|
||||
errorTLV,
|
||||
pingTLV,
|
||||
pongTLV
|
||||
pongTLV,
|
||||
oracleEventV0TLV,
|
||||
eventDescriptorTLV
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue