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:
Ben Carman 2020-10-08 22:47:40 -05:00 committed by GitHub
parent 2cad89c1a0
commit 77e8cabf5b
3 changed files with 311 additions and 9 deletions

View file

@ -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)
}
}
}

View file

@ -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)
}
}

View file

@ -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
)
}
}