From d393848cc204b92edc3c200faab05c1f72bf3e75 Mon Sep 17 00:00:00 2001 From: Chris Stewart Date: Wed, 1 Dec 2021 13:30:18 -0600 Subject: [PATCH] 2021 11 30 issue 3847 (#3862) * Start pulling over accept serializers from dlc message spec PR * Get sign tlv json serialization working * Implement decodeaccept,decodesign in CoreRoutes * Move messages to DLCTestUtil, add documentation --- .../json/DLCAcceptJsonSerializerTest.scala | 60 +++++ .../json/DLCSignJsonSerializerTest.scala | 57 +++++ .../commons/serializers/Picklers.scala | 242 +++++++++++++++++- .../org/bitcoins/server/CoreRoutesSpec.scala | 46 ++++ .../org/bitcoins/server/CoreRoutes.scala | 17 +- .../bitcoins/server/ServerJsonModels.scala | 60 +++++ .../core/protocol/script/Script.scala | 1 + .../org/bitcoins/core/protocol/tlv/TLV.scala | 2 + .../core/serializers/PicklerKeys.scala | 87 +++++++ docs/applications/server.md | 4 + .../testkitcore/dlc/DLCTestUtil.scala | 99 +++++++ 11 files changed, 664 insertions(+), 11 deletions(-) create mode 100644 app-commons-test/src/test/scala/org/bitcoins/commons/json/DLCAcceptJsonSerializerTest.scala create mode 100644 app-commons-test/src/test/scala/org/bitcoins/commons/json/DLCSignJsonSerializerTest.scala create mode 100644 app/server-test/src/test/scala/org/bitcoins/server/CoreRoutesSpec.scala diff --git a/app-commons-test/src/test/scala/org/bitcoins/commons/json/DLCAcceptJsonSerializerTest.scala b/app-commons-test/src/test/scala/org/bitcoins/commons/json/DLCAcceptJsonSerializerTest.scala new file mode 100644 index 0000000000..eaaabc788d --- /dev/null +++ b/app-commons-test/src/test/scala/org/bitcoins/commons/json/DLCAcceptJsonSerializerTest.scala @@ -0,0 +1,60 @@ +package org.bitcoins.commons.json + +import org.bitcoins.commons.serializers.Picklers +import org.bitcoins.core.protocol.tlv.DLCAcceptTLV +import org.bitcoins.testkitcore.util.BitcoinSUnitTest + +class DLCAcceptJsonSerializerTest extends BitcoinSUnitTest { + + behavior of "DLCAcceptJsonSerializer" + + private val testString: String = + s""" + |{ + | "temporaryContractId": "bdc5286cd4b56c2b2525bc9e0e3dda1d6a2a130cc7fd8ca8a38d401ef9a5d3e7", + | "acceptCollateral": 100000000, + | "fundingPubkey": "0208dfffdda2a61c78c906f5d76afdb0b8fe0555e6a3644c41f53b511427f80f0a", + | "payoutSpk": "001463c84b34fcf37ee58566aa6daf0747f74e509970", + | "payoutSerialId": "11859227771650291066", + | "fundingInputs": [ + | { + | "inputSerialId": "6134040349072004330", + | "prevTx": "020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff03510101ffffffff0200f2052a01000000160014c87d38bcd3a468680e7c0abeeb7821d6302df9120000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + | "prevTxVout": 0, + | "sequence": 4294967295, + | "maxWitnessLen": 107, + | "redeemScript": "" + | } + | ], + | "changeSpk": "001481467abc1d30f5139fe8ff8c1ca7f7cb3f5bc031", + | "changeSerialId": "13987689245506757418", + | "cetAdaptorSignatures": { + | "ecdsaAdaptorSignatures": [ + | { + | "signature": "02b7dda1b4030e0f85a98eb15a6806f1ffb72b578a508f671f4e6bbd954aa2d5b9022a01536f70340da9ba2b0e034deec1a0b658cbec2432f2fa2a96de09000eafaafb586eec1375c85737bb7e9fde1cb7fcf3a8a970e698d0e5da55297ea64a45b8ffb2b705974b91e8e3d9b0e46573c648122fda1ef941980ba845ab09d1e7a43949add788ed79e4a23b832b1418c3b54251b0d3c83e791ce24c2e30544456d3b8" + | }, + | { + | "signature": "02d510a07687553f7dd26ff6124cdccbf73ccd8661ae9bd6a59a25cedac04727fd03fb8955cb520fde3dc30fbf095054a87f8c3e87f027238e3bc435b0dc6df2532af475c531e234ee045d6119d989a62d8b29bdfdbdcdf8d6761b0cb5fbd1eb6ef71fa04c7a993801e2d02d5659c08f629f7d5ef02173ce5b1fcec361d99b9aee79549a085c14bb3f7df507e891a35089b3d9886e7c81b7367cac8c75bbb9432861" + | }, + | { + | "signature": "0328ca81f2f281c39eda04fb69456f6b104f09d920d57def9e396aa60de6c0b38c03e73ec3891966e5d339ea154bf17e265f80cb75bfc922b83d79102f44479a69e281a485c8e99abc382aad656983f8f92a5836437156c783e261bfe0bf7eec9e3bb83ff8b4d948458b3d8fdfbc74b23cd02a72b06b84aa156a7bf6a578757c205257aa86a728b6b8022c303c9b01164f0332d6bc5aaa17014c7bb87d598b83af5d" + | }, + | { + | "signature": "0337e4ce2c5b3fb9d70663bac8797f8e7a1493af23f74ac9ace7449a35d59c757a02676f9cc3adf9d3d03ef5a4daf704c5d178dae12364f0297ab09982d5da08145010fc26c71166e6a2e651e4c351916f2e7d538c14adeccbd2f21c18f1d4ddea619120af9bcef67dcda0ed5b8d5c4dddee167107263becf05c91e0a13c2e653cd9722674531031610ef6b8cd80b205fc78834db55cf08e45d37616a6e218b7e09c" + | } + | ] + | }, + | "refundSignature": "304402202c9b25719f0a22d7372c54c36f916e44f445135809433585636417fe18b9316c0220125584711c7238d0adf4a494e30a166fef35edf3d0d1718955abd73b76a26e9d", + | "negotiationFields": null + | } + |""".stripMargin + + it must "have serialization symmetry for a accept json message" in { + val accept = upickle.default.read[DLCAcceptTLV](testString)( + Picklers.dlcAcceptTLVPickler) + val json: String = + upickle.default.write(accept)(Picklers.dlcAcceptTLVPickler) + assert(json == testString.replaceAll("\\s", "")) + } + +} diff --git a/app-commons-test/src/test/scala/org/bitcoins/commons/json/DLCSignJsonSerializerTest.scala b/app-commons-test/src/test/scala/org/bitcoins/commons/json/DLCSignJsonSerializerTest.scala new file mode 100644 index 0000000000..2e3947068a --- /dev/null +++ b/app-commons-test/src/test/scala/org/bitcoins/commons/json/DLCSignJsonSerializerTest.scala @@ -0,0 +1,57 @@ +package org.bitcoins.commons.json + +import org.bitcoins.commons.serializers.Picklers +import org.bitcoins.core.protocol.tlv.{DLCSignTLV, LnMessage} +import org.bitcoins.testkitcore.util.BitcoinSUnitTest + +class DLCSignJsonSerializerTest extends BitcoinSUnitTest { + behavior of "DLCAcceptJsonSerializer" + + private val testString: String = { + s""" + |{ + | "contractId": "afcd7e786c0b9085784a6d063c7f4e7291784f9be3ba23cd8734559040fd2202", + | "cetAdaptorSignatures": { + | "ecdsaAdaptorSignatures": [ + | { + | "signature": "02feee4c75948830549fd19efd93cd4e00d4f538041ecf2f2cb6820c78c0a57fc9034d9ce5849e0abe235b8c3137506745508aa17c7a3064ec4172e76c88e66ca27257c5ec09f99201c9bc95b8ab345aff9b32b89c328d21a6ca5553f287a650320162381ef95042d9ac57f66f39dc6904f17a472671d3828f75ef29fbddcd31119b69adb216dac9b5960b91b3e3d83b9310484ef667d19f4bed01964ff063a2acbd" + | }, + | { + | "signature": "025e840169038f0d046cc5005d9d622fd19ec2dd84d15a022f3eff4a781c6f404602cf8ec975191b73ecc3944786f5cca2a01ba3e71bbde9ea6932083c23591dfb6e3e06489cde6415c39cc9e187283f7a9b76227b157572dc62f1a5b130b4fc6b68c14ea07deddb2865d634a244839a87f158145c59d9aecb51b082c3a2ef0cb0201bc65252a3876f7253855ce4d3e72cd650bc275dd878f920162af74865b0346b" + | }, + | { + | "signature": "03ea132cd20738f1a6640f98dc5664d12952e000feb69bbb3c4929c9054b3ca965030b788e83098f906cd3b088ad297e8149d08eb02442f42bd2b9e4455d5a19a96fd329625e1f1d0db9b9f625a6242f1fc53f56ec8090575c704e364518bdf7d690386d0f4a7bfea6c263c5fbc91631b0f0323a92c51f43f3228c9649c5441b338f2e37cbad0d10ce0d80429e2a6a528e4a0e9700188b86c267246b5049ce7da031" + | }, + | { + | "signature": "02c2ec44af74653bdad7db89a38ab61d3dabf0646acdfab1bd5906d4a75f8d7a0c03051281626d4c8c84a5b7d09717c69f1c5340300be5ef6d94a4efe4d1baebbf820f8891a3b86d0f4a9a42d3fa0a44f7d711a5e65cc887b0424530e9b7e49f8d3a21d89762d535c7f121bc057b01b67372ea19762fad714580767ea6a091771808c3806b273382e0792573f624a90e15004a3b852e627f422c07209fd876c38169" + | } + | ] + | }, + | "refundSignature": "3044022005d4dcc449db1a5997b82f2aabaffb3414ad1e10c159fc3fcbd6dab121a3cb98022037583d39f66aa347a8170dba12cf88102997f460bb27d70365f7f85cd4ce5ee2", + | "fundingSignatures": { + | "fundingSignatures": [ + | { + | "witnessElements": [ + | { + | "witness": "304402204bbe45fd65402ad89784062c2af0f0c88a55f1de4509e4491f7933ada30fbab102207144fe9b8d621d2bef5c226d6f2f8ff43548f4c0bb9ea221a0c2a034a069c74f01" + | }, + | { + | "witness": "03bdb496d31a30d8e87ab7767fd26bfdfa7f6d06a0cfaea082a91af1ae1814f251" + | } + | ] + | } + | ] + | } + | } + |""".stripMargin + } + + it must "have serialization symmetry for dlc sign messages" in { + val sign = + upickle.default.read[DLCSignTLV](testString)(Picklers.dlcSignTLVPickler) + println(s"accept=${LnMessage(sign).hex}") + val json: String = + upickle.default.write(sign)(Picklers.dlcSignTLVPickler) + assert(json == testString.replaceAll("\\s", "")) + } +} diff --git a/app-commons/src/main/scala/org/bitcoins/commons/serializers/Picklers.scala b/app-commons/src/main/scala/org/bitcoins/commons/serializers/Picklers.scala index 607c857d75..9405304dd5 100644 --- a/app-commons/src/main/scala/org/bitcoins/commons/serializers/Picklers.scala +++ b/app-commons/src/main/scala/org/bitcoins/commons/serializers/Picklers.scala @@ -7,9 +7,15 @@ import org.bitcoins.core.crypto._ import org.bitcoins.core.currency.{Bitcoins, Satoshis} import org.bitcoins.core.dlc.accounting.DLCWalletAccounting import org.bitcoins.core.hd.AddressType -import org.bitcoins.core.number.UInt32 +import org.bitcoins.core.number.{UInt16, UInt32, UInt64} import org.bitcoins.core.protocol.dlc.models.DLCStatus._ import org.bitcoins.core.protocol.dlc.models._ +import org.bitcoins.core.protocol.script.{ + ScriptPubKey, + ScriptWitness, + ScriptWitnessV0, + WitnessScriptPubKey +} import org.bitcoins.core.protocol.tlv._ import org.bitcoins.core.protocol.transaction.{Transaction, TransactionOutPoint} import org.bitcoins.core.protocol.{BitcoinAddress, BlockStamp} @@ -123,16 +129,234 @@ object Picklers { implicit val lnMessageDLCOfferTLVPickler: ReadWriter[LnMessage[DLCOfferTLV]] = readwriter[String].bimap(_.hex, LnMessageFactory(DLCOfferTLV).fromHex) - implicit val dlcAcceptTLVPickler: ReadWriter[DLCAcceptTLV] = - readwriter[String].bimap(_.hex, DLCAcceptTLV.fromHex) + private def parseU64(str: ujson.Str): UInt64 = { + UInt64(BigInt(str.str)) + } + + private def parseFundingInput(obj: ujson.Obj): FundingInputTLV = { + val inputSerialId = parseU64(obj(PicklerKeys.inputSerialIdKey).str) + val prevTx = Transaction.fromHex(obj(PicklerKeys.prevTxKey).str) + val prevTxVout = obj(PicklerKeys.prevTxVoutKey).num.toLong + val sequence = UInt32(obj(PicklerKeys.sequenceKey).num.toLong) + val maxWitnessLen = UInt16(obj(PicklerKeys.maxWitnessLenKey).num.toLong) + val redeemScriptStr = obj(PicklerKeys.redeemScriptKey).str + val redeemScriptOpt = if (redeemScriptStr.nonEmpty) { + val spk = + WitnessScriptPubKey.fromAsmHex(obj(PicklerKeys.redeemScriptKey).str) + Some(spk) + } else { + None + } + + FundingInputV0TLV( + inputSerialId, + prevTx, + UInt32(prevTxVout), + sequence, + maxWitnessLen, + redeemScriptOpt + ) + } + + private def parseFundingInputs(arr: ujson.Arr): Vector[FundingInputTLV] = { + arr.value.toVector.map { + case inputObj: ujson.Obj => + parseFundingInput(inputObj) + case x: ujson.Value => + sys.error(s"Expected obj, got=$x") + } + } + + private def parseCetAdaptorSignatures(obj: ujson.Obj): CETSignaturesTLV = { + val ecAdaptorSignaturesArr = obj(PicklerKeys.ecdsaAdaptorSignaturesKey).arr + val adaptorSigs = parseAdaptorSignatures(ecAdaptorSignaturesArr) + CETSignaturesV0TLV(adaptorSigs) + } + + private def parseAdaptorSignatures( + arr: ujson.Arr): Vector[ECAdaptorSignature] = { + arr.value.toVector.map { + case obj: ujson.Obj => + ECAdaptorSignature.fromHex(obj(PicklerKeys.signatureKey).str) + case x: ujson.Value => + sys.error(s"Excpected string for ecdsa adaptor siganture, got obj=$x") + } + } + + private def writeAdaptorSignatures( + sigs: Vector[ECAdaptorSignature]): Vector[ujson.Obj] = { + sigs.map { sig => + ujson.Obj(PicklerKeys.signatureKey -> Str(sig.hex)) + } + } + + private def writeCetAdaptorSigs( + cetSignaturesTLV: CETSignaturesTLV): ujson.Obj = { + cetSignaturesTLV match { + case v0: CETSignaturesV0TLV => + val sigsVec = writeAdaptorSignatures(v0.sigs) + ujson.Obj( + PicklerKeys.ecdsaAdaptorSignaturesKey -> ujson.Arr.from(sigsVec)) + } + } + + private def readAcceptTLV(obj: ujson.Obj): DLCAcceptTLV = { + val tempContractId = + Sha256Digest.fromHex(obj(PicklerKeys.temporaryContractIdKey).str) + val acceptCollateral = Satoshis( + obj(PicklerKeys.acceptCollateralKey).num.toLong) + val fundingPubKey = + ECPublicKey.fromHex(obj(PicklerKeys.fundingPubKeyKey).str) + val payoutSpk = ScriptPubKey.fromAsmHex(obj(PicklerKeys.payoutSpkKey).str) + val payoutSerialId = parseU64(obj(PicklerKeys.payoutSerialIdKey).str) + val fundingInputs = parseFundingInputs( + obj(PicklerKeys.fundingInputsKey).arr) + val changeSpk = ScriptPubKey.fromAsmHex(obj(PicklerKeys.changeSpkKey).str) + val changeSerialId = parseU64(obj(PicklerKeys.changeSerialIdKey).str) + val cetAdaptorSigs = parseCetAdaptorSignatures( + obj(PicklerKeys.cetAdaptorSignaturesKey).obj) + val refundSignature = + ECDigitalSignature.fromHex(obj(PicklerKeys.refundSignatureKey).str) + val negotiationFields = { + obj(PicklerKeys.negotiationFieldsKey).strOpt match { + case Some(str) => + sys.error(s"Don't know how to parse negotiation fields, got=$str") + case None => NegotiationFieldsTLV.empty + } + } + + val acceptTLV = DLCAcceptTLV( + tempContractId = tempContractId, + totalCollateralSatoshis = acceptCollateral, + fundingPubKey = fundingPubKey, + payoutSPK = payoutSpk, + payoutSerialId = payoutSerialId, + fundingInputs = fundingInputs, + changeSPK = changeSpk, + changeSerialId = changeSerialId, + cetSignatures = cetAdaptorSigs, + refundSignature = refundSignature, + negotiationFields = negotiationFields + ) + + acceptTLV + } + + private def writeAcceptTLV(accept: DLCAcceptTLV): ujson.Obj = { + Obj( + PicklerKeys.tempContractIdKey -> Str(accept.tempContractId.hex), + PicklerKeys.acceptCollateralKey -> Num( + accept.totalCollateralSatoshis.toLong.toDouble), + PicklerKeys.fundingPubKeyKey -> Str(accept.fundingPubKey.hex), + PicklerKeys.payoutSpkKey -> Str(accept.payoutSPK.asmHex), + PicklerKeys.payoutSerialIdKey -> Str( + accept.payoutSerialId.toBigInt.toString()), + PicklerKeys.fundingInputsKey -> writeJs(accept.fundingInputs), + PicklerKeys.changeSpkKey -> Str(accept.changeSPK.asmHex), + PicklerKeys.changeSerialIdKey -> Str( + accept.changeSerialId.toBigInt.toString()), + PicklerKeys.cetAdaptorSignaturesKey -> writeCetAdaptorSigs( + accept.cetSignatures), + PicklerKeys.refundSignatureKey -> Str(accept.refundSignature.hex), + PicklerKeys.negotiationFieldsKey -> ujson.Null + ) + } + + private def parseFundingSignatures(obj: ujson.Obj): FundingSignaturesTLV = { + val fundingSignatures: Vector[ujson.Value] = obj( + PicklerKeys.fundingSignaturesKey).arr.toVector + val witV0 = paresFundingSignaturesArr(fundingSignatures) + FundingSignaturesV0TLV(witV0) + } + + private def paresFundingSignaturesArr( + arr: Vector[ujson.Value]): Vector[ScriptWitnessV0] = { + arr.map { + case obj: ujson.Obj => + val witnessElementsArr = obj(PicklerKeys.witnessElementsKey).arr + val witnesses: Vector[ByteVector] = { + parseWitnessElements(witnessElementsArr) + } + + val scriptWitnessV0 = ScriptWitness + .apply(witnesses.reverse) + .asInstanceOf[ScriptWitnessV0] + scriptWitnessV0 + case x => + sys.error(s"Expected array of objects for funding signatures, got=$x") + } + } + + private def parseWitnessElements(arr: ujson.Arr): Vector[ByteVector] = { + arr.value.toVector.map { + case obj: ujson.Obj => + val witnessStr = obj(PicklerKeys.witnessKey).str + ByteVector.fromValidHex(witnessStr) + case x: ujson.Value => + sys.error(s"Expected witness json object, got=$x") + } + } + + private def writeWitnessElements(witness: ScriptWitness): ujson.Obj = { + val vec: Vector[ujson.Obj] = witness.stack.reverse.map { w => + ujson.Obj(PicklerKeys.witnessKey -> Str(w.toHex)) + }.toVector + + ujson.Obj(PicklerKeys.witnessElementsKey -> ujson.Arr.from(vec)) + } + + private def writeFundingSignatures( + fundingSigs: FundingSignaturesTLV): ujson.Obj = { + val sigs: Vector[ujson.Obj] = fundingSigs match { + case v0: FundingSignaturesV0TLV => + val witnessJson: Vector[ujson.Obj] = + v0.witnesses.map(writeWitnessElements) + witnessJson + } + ujson.Obj( + PicklerKeys.fundingSignaturesKey -> ujson.Arr.from(sigs) + ) + } + + private def readSignTLV(obj: ujson.Obj): DLCSignTLV = { + val contractId = ByteVector.fromValidHex(obj(PicklerKeys.contractIdKey).str) + val adaptorSigs = parseCetAdaptorSignatures( + obj(PicklerKeys.cetAdaptorSignaturesKey).obj) + val refundSignature = + ECDigitalSignature.fromHex(obj(PicklerKeys.refundSignatureKey).str) + val fundingSignatures = parseFundingSignatures( + obj(PicklerKeys.fundingSignaturesKey).obj) + + val signTLV = + DLCSignTLV(contractId, adaptorSigs, refundSignature, fundingSignatures) + + signTLV + + } + + private def writeSignTLV(sign: DLCSignTLV): ujson.Obj = { + ujson.Obj( + PicklerKeys.contractIdKey -> sign.contractId.toHex, + PicklerKeys.cetAdaptorSignaturesKey -> writeCetAdaptorSigs( + sign.cetSignatures), + PicklerKeys.refundSignatureKey -> ujson.Str(sign.refundSignature.hex), + PicklerKeys.fundingSignaturesKey -> + writeFundingSignatures(sign.fundingSignatures) + ) + } + + implicit val dlcAcceptTLVPickler: ReadWriter[DLCAcceptTLV] = { + readwriter[ujson.Obj].bimap(writeAcceptTLV, readAcceptTLV) + } + + implicit val dlcSignTLVPickler: ReadWriter[DLCSignTLV] = { + readwriter[ujson.Obj].bimap(writeSignTLV, readSignTLV) + } implicit val lnMessageDLCAcceptTLVPickler: ReadWriter[ LnMessage[DLCAcceptTLV]] = readwriter[String].bimap(_.hex, LnMessageFactory(DLCAcceptTLV).fromHex) - implicit val dlcSignTLVPickler: ReadWriter[DLCSignTLV] = - readwriter[String].bimap(_.hex, DLCSignTLV.fromHex) - implicit val lnMessageDLCSignTLVPickler: ReadWriter[LnMessage[DLCSignTLV]] = readwriter[String].bimap(_.hex, LnMessageFactory(DLCSignTLV).fromHex) @@ -227,11 +451,11 @@ object Picklers { val redeemScriptJson = redeemScriptOpt match { case Some(rs) => Str(rs.hex) - case None => ujson.Null + case None => Str("") } Obj( - "inputSerialId" -> Num(inputSerialId.toBigInt.toDouble), + "inputSerialId" -> Str(inputSerialId.toBigInt.toString()), "prevTx" -> Str(prevTx.hex), "prevTxVout" -> Num(prevTxVout.toLong.toDouble), "sequence" -> Num(sequence.toLong.toDouble), @@ -433,7 +657,7 @@ object Picklers { totalCollateralSatoshis.toLong.toDouble), "fundingInputs" -> fundingInputs.map(i => writeJs(i)), "changeSPK" -> Str(changeSPK.hex), - "changeSerialId" -> Num(changeSerialId.toBigInt.toDouble), + "changeSerialId" -> Str(changeSerialId.toBigInt.toString()), "fundOutputSerialId" -> Num(fundOutputSerialId.toBigInt.toDouble), "feeRatePerVb" -> Num(feeRate.toLong.toDouble), "cetLocktime" -> Num(contractMaturityBound.toUInt32.toLong.toDouble), diff --git a/app/server-test/src/test/scala/org/bitcoins/server/CoreRoutesSpec.scala b/app/server-test/src/test/scala/org/bitcoins/server/CoreRoutesSpec.scala new file mode 100644 index 0000000000..f6e35f0006 --- /dev/null +++ b/app/server-test/src/test/scala/org/bitcoins/server/CoreRoutesSpec.scala @@ -0,0 +1,46 @@ +package org.bitcoins.server + +import akka.http.scaladsl.model.ContentTypes +import akka.http.scaladsl.testkit.ScalatestRouteTest +import org.bitcoins.server.routes.ServerCommand +import org.bitcoins.testkit.BitcoinSTestAppConfig +import org.bitcoins.testkitcore.dlc.DLCTestUtil +import org.scalamock.scalatest.MockFactory +import org.scalatest.wordspec.AnyWordSpec + +class CoreRoutesSpec + extends AnyWordSpec + with ScalatestRouteTest + with MockFactory { + + implicit val conf: BitcoinSAppConfig = + BitcoinSTestAppConfig.getSpvTestConfig() + val coreRoutes = CoreRoutes() + + "Core routes" should { + "decode an accept message" in { + val args = ujson.Arr(DLCTestUtil.acceptHex) + val route = + coreRoutes.handleCommand(ServerCommand("decodeaccept", args)) + + Post() ~> route ~> check { + assert(contentType == ContentTypes.`application/json`) + val actualJson = ujson.read(responseAs[String]) + assert(actualJson == DLCTestUtil.expectedAccept) + } + } + + "decode a sign message" in { + val args = ujson.Arr(DLCTestUtil.signHex) + val route = + coreRoutes.handleCommand(ServerCommand("decodesign", args)) + + Post() ~> route ~> check { + assert(contentType == ContentTypes.`application/json`) + val actualJson = ujson.read(responseAs[String]) + assert(actualJson == DLCTestUtil.expectedSign) + } + } + } + +} diff --git a/app/server/src/main/scala/org/bitcoins/server/CoreRoutes.scala b/app/server/src/main/scala/org/bitcoins/server/CoreRoutes.scala index a85de44558..b8287a63dc 100644 --- a/app/server/src/main/scala/org/bitcoins/server/CoreRoutes.scala +++ b/app/server/src/main/scala/org/bitcoins/server/CoreRoutes.scala @@ -24,7 +24,7 @@ case class CoreRoutes()(implicit system: ActorSystem, config: BitcoinSAppConfig) extends ServerRoute { import system.dispatcher - def handleCommand: PartialFunction[ServerCommand, Route] = { + override def handleCommand: PartialFunction[ServerCommand, Route] = { case ServerCommand("finalizepsbt", arr) => withValidServerCommand(FinalizePSBT.fromJsArr(arr)) { case FinalizePSBT(psbt) => @@ -153,7 +153,20 @@ case class CoreRoutes()(implicit system: ActorSystem, config: BitcoinSAppConfig) Server.httpSuccess(writeJs(offerTLV)) } } - + case ServerCommand("decodeaccept", arr) => + withValidServerCommand(DecodeAccept.fromJsArr(arr)) { + case DecodeAccept(accept) => + complete { + Server.httpSuccess(writeJs(accept)) + } + } + case ServerCommand("decodesign", arr) => + withValidServerCommand(DecodeSign.fromJsArr(arr)) { + case DecodeSign(accept) => + complete { + Server.httpSuccess(writeJs(accept)) + } + } case ServerCommand("decodecontractinfo", arr) => withValidServerCommand(DecodeContractInfo.fromJsArr(arr)) { case DecodeContractInfo(contractInfo) => diff --git a/app/server/src/main/scala/org/bitcoins/server/ServerJsonModels.scala b/app/server/src/main/scala/org/bitcoins/server/ServerJsonModels.scala index e5147bf6bc..9a4e40d5e8 100644 --- a/app/server/src/main/scala/org/bitcoins/server/ServerJsonModels.scala +++ b/app/server/src/main/scala/org/bitcoins/server/ServerJsonModels.scala @@ -735,6 +735,66 @@ object DecodeOffer extends ServerJsonModels { } } +case class DecodeAccept(accept: DLCAcceptTLV) + +object DecodeAccept extends ServerJsonModels { + + def fromJsArr(jsArr: ujson.Arr): Try[DecodeAccept] = { + jsArr.arr.toList match { + case acceptJs :: Nil => + Try { + val accept: LnMessage[DLCAcceptTLV] = + LnMessageFactory(DLCAcceptTLV).fromHex(acceptJs.str) + DecodeAccept(accept.tlv) + } match { + case Success(value) => + Success(value) + case Failure(_) => + Try { + val accept = DLCAcceptTLV.fromHex(acceptJs.str) + DecodeAccept(accept) + } + } + case Nil => + Failure(new IllegalArgumentException(s"Missing accept announcement")) + case other => + Failure( + new IllegalArgumentException( + s"Bad number of arguments: ${other.length} Expected: 1")) + } + } +} + +case class DecodeSign(sign: DLCSignTLV) + +object DecodeSign extends ServerJsonModels { + + def fromJsArr(jsArr: ujson.Arr): Try[DecodeSign] = { + jsArr.arr.toList match { + case signJs :: Nil => + Try { + val accept: LnMessage[DLCSignTLV] = + LnMessageFactory(DLCSignTLV).fromHex(signJs.str) + DecodeSign(accept.tlv) + } match { + case Success(value) => + Success(value) + case Failure(_) => + Try { + val sign = DLCSignTLV.fromHex(signJs.str) + DecodeSign(sign) + } + } + case Nil => + Failure(new IllegalArgumentException(s"Missing accept announcement")) + case other => + Failure( + new IllegalArgumentException( + s"Bad number of arguments: ${other.length} Expected: 1")) + } + } +} + case class DecodeAnnouncement(announcement: OracleAnnouncementTLV) object DecodeAnnouncement extends ServerJsonModels { diff --git a/core/src/main/scala/org/bitcoins/core/protocol/script/Script.scala b/core/src/main/scala/org/bitcoins/core/protocol/script/Script.scala index 6f6313461c..e64bcfe90a 100644 --- a/core/src/main/scala/org/bitcoins/core/protocol/script/Script.scala +++ b/core/src/main/scala/org/bitcoins/core/protocol/script/Script.scala @@ -32,4 +32,5 @@ abstract class Script extends NetworkElement { /** The full byte serialization for a script on the network */ override val bytes: ByteVector = compactSizeUInt.bytes ++ asmBytes + lazy val asmHex: String = asmBytes.toHex } diff --git a/core/src/main/scala/org/bitcoins/core/protocol/tlv/TLV.scala b/core/src/main/scala/org/bitcoins/core/protocol/tlv/TLV.scala index 3fb4c17c77..d10d2957e0 100644 --- a/core/src/main/scala/org/bitcoins/core/protocol/tlv/TLV.scala +++ b/core/src/main/scala/org/bitcoins/core/protocol/tlv/TLV.scala @@ -1535,6 +1535,8 @@ sealed trait NegotiationFieldsTLV extends DLCSetupPieceTLV object NegotiationFieldsTLV extends TLVParentFactory[NegotiationFieldsTLV] { + final val empty: NoNegotiationFieldsTLV.type = NoNegotiationFieldsTLV + override val allFactories: Vector[TLVFactory[NegotiationFieldsTLV]] = Vector(NoNegotiationFieldsTLVFactory, NegotiationFieldsV1TLV) diff --git a/core/src/main/scala/org/bitcoins/core/serializers/PicklerKeys.scala b/core/src/main/scala/org/bitcoins/core/serializers/PicklerKeys.scala index a310cb8207..b0ec05dfb7 100644 --- a/core/src/main/scala/org/bitcoins/core/serializers/PicklerKeys.scala +++ b/core/src/main/scala/org/bitcoins/core/serializers/PicklerKeys.scala @@ -21,9 +21,96 @@ object PicklerKeys { //offers final val protocolVersionKey: String = "protocolVersion" + //accepts + final val tempContractIdKey: String = "temporaryContractId" + final val fundingPubKeyKey: String = "fundingPubkey" + final val acceptCollateralKey: String = "acceptCollateral" + final val payoutSpkKey: String = "payoutSpk" + final val payoutSerialIdKey: String = "payoutSerialId" + final val fundingInputsKey: String = "fundingInputs" + final val changeSpkKey = "changeSpk" + final val changeSerialIdKey: String = "changeSerialId" + final val negotiationFieldsKey: String = "negotiationFields" + //contract info final val totalCollateralKey = "totalCollateral" final val contractDescriptorKey = "contractDescriptor" final val oracleInfoKey = "oracleInfo" final val pairsKey = "pairs" + + val contractFlagsKey = "contractFlags" + val chainHashKey = "chainHash" + + val contractInfoKey = "contractInfo" + val singleContractInfoKey = "singleContractInfo" + + val enumeratedContractDescriptorKey = "enumeratedContractDescriptor" + val numericOutcomeContractDescriptorKey = "numericOutcomeContractDescriptor" + val payoutsKey = "payouts" + + //numeric contract descriptor + val numDigitsKey = "numDigits" + val payFunctionKey = "payoutFunction" + val payoutFunctionPiecesKey = "payoutFunctionPieces" + val leftEndPointKey = "leftEndPoint" + val eventOutcomeKey = "eventOutcome" + val outcomePayoutKey = "outcomePayout" + + val payoutCurvePieceKey = "payoutCurvePiece" + val polynomialPayoutCurvePieceKey = "polynomialPayoutCurvePiece" + val payoutPointsKey = "payoutPoints" + + val lastEndpointKey = "lastEndpoint" + + val roundingIntervalsKey = "roundingIntervals" + val intervalsKey = "intervals" + val beginIntervalKey = "beginInterval" + val roundingModKey = "roundingMod" + + val singleKey = "single" + + val oracleAnnouncementKey = "oracleAnnouncement" + val announcementSignatureKey = "announcementSignature" + val oraclePublicKeyKey = "oraclePublicKey" + val oracleEventKey = "oracleEvent" + val oracleNoncesKey = "oracleNonces" + val eventMaturityEpochKey = "eventMaturityEpoch" + val eventDescriptorKey = "eventDescriptor" + val enumEventKey = "enumEvent" + + val digitDecompositionEventKey = "digitDecompositionEvent" + val baseKey = "base" + val isSignedKey = "isSigned" + val unitKey = "unit" + val precisionKey = "precision" + val nbDigitsKey = "nbDigits" + + val eventIdKey = "eventId" + + val offerCollateralKey = "offerCollateral" + + val fundOutputSerialIdKey = "fundOutputSerialId" + val feeRatePerKbKey = "feeRatePerVb" + val contractMaturityBoundKey = "contractMaturityBound" + val contractTimeoutKey = "contractTimeout" + + val acceptMessageKey = "accept_message" + val temporaryContractIdKey = "temporaryContractId" + val inputSerialIdKey = "inputSerialId" + val prevTxKey = "prevTx" + val prevTxVoutKey = "prevTxVout" + val sequenceKey = "sequence" + val maxWitnessLenKey = "maxWitnessLen" + val redeemScriptKey = "redeemScript" + val cetAdaptorSignaturesKey = "cetAdaptorSignatures" + val ecdsaAdaptorSignaturesKey = "ecdsaAdaptorSignatures" + val signatureKey = "signature" + val refundSignatureKey = "refundSignature" + + val signMessageKey = "sign_message" + val contractIdKey = "contractId" + val fundingSignaturesKey = "fundingSignatures" + val witnessElementsKey = "witnessElements" + val witnessKey = "witness" + val serializedKey = "serialized" } diff --git a/docs/applications/server.md b/docs/applications/server.md index b2cd79bd21..b9badbb146 100644 --- a/docs/applications/server.md +++ b/docs/applications/server.md @@ -241,6 +241,10 @@ the `-p 9999:9999` port mapping on the docker container to adjust for this. - `contractinfo` - Hex encoded contract info - `decodeoffer` `offer` - Decodes an offer message into json - `offer` - Hex encoded dlc offer message + - `decodeaccept` `accept` - Decodes an accept message into json + - `accept` - Hex encoded dlc accept message + - `decodesign` `sign` - Decodes a sign message into json + - `sign` - Hex encoded dlc sign message - `decodeannouncement` `announcement` - Decodes an oracle announcement message into json - `announcement` - Hex encoded oracle announcement message - `decodeattestments` `attestments` - Decodes an oracle attestments message into json diff --git a/testkit-core/src/main/scala/org/bitcoins/testkitcore/dlc/DLCTestUtil.scala b/testkit-core/src/main/scala/org/bitcoins/testkitcore/dlc/DLCTestUtil.scala index e892b38b08..60402f4d9b 100644 --- a/testkit-core/src/main/scala/org/bitcoins/testkitcore/dlc/DLCTestUtil.scala +++ b/testkit-core/src/main/scala/org/bitcoins/testkitcore/dlc/DLCTestUtil.scala @@ -99,4 +99,103 @@ object DLCTestUtil { val remoteInfo = info.flip(totalCollateral.satoshis) (info, remoteInfo) } + + val acceptHex: String = + "a71cd1dc7596d2a2f14a2542f2b7e563c149c910ba2238b59532b9fc1ebf6f8465f1000000000000ea60037dbfd3eb9296d1bd537c4008794989893821d48a163b0f3b7b01433599e8121100160014cb62b501146622502837f5cd898a657feba867eb9701e48dde0387770001fda714f4e2f7667f1f20d72700de02000000000101433df2df0297e1de67283fd8d369a16be5179f3a696fe36bf601337426408af50100000000000000000280f0fa0200000000160014d431e458cc852e7f2f21a6055751b5d2f30b7bcac190f30200000000160014aba424ac1cdf6c4ff53dcc76c1458c2e8bb7c71702473044022049911e914325d2c21f1f7a52d0bef9f50362cf130a87d73a30e61863fa72582502205338e9fa57b6172c821cb9e2530e7b0bf27812e2110ffa7adf277da1be31872d012103cf091ac1820aabeb4399d63bacb5b1c58310166a8b9466bc5efb4dbb678aba1e0000000000000000ffffffff006b0000001600145097a3a31e88036a7396728b6e3ea47c70780f8db68e98eec3ad4f26fda716fd01e70302834a87e2019a7c431db58235abd00ce62ca5a36e8b5e60bc6dd895045e9d936302e00d2ff673cf117f8bc3e6cbbbdc9efeb6b641d55c32c58ca46422c7279db2a3d07fa999360ba675ad6d342d21997e8644f60509a106df667f85f948f1d73ca5de082db23a50f07d9180e253bd8879567d1e4110f07de6f57ef75cf32206edc4d0656f6dcd738f0301d4baf73182aac102a1917b9688dcb7b74bcdcc85374b9203e9a2eb7600712af2236b80ebceb644d5bad65f3ccbcdcf8f2a649a48fd8a3b72023a6a694000be8df5519f7d5e11414b73274377300655be6ad1cc0da6c0eedf1fe36e4e82f3125c8ba40a1bcda444b4ec528d1674e6cee5e61d71f92586212ab2b5afdc90fe4744db5c9873eb240dfaf1952b41b3f3fa7e7a86def2a691bcbae5ad968459f712041a6ea8f39314c19545f8b5b501250aae6d09d0cfff7d9f79f403293e081859a95c05ab7a6fb6b5e960c928a199b89166188d4db5c4546fa8baef03a08004a37d041bcb10ceb94d3797814789ac192c2c79d9ddd41397cb0d0e6af84fcb15cdc885df164aaa81e2dbc95af76d4ca71017b81b2b20307c4a067d844c427f1e5c06da2082911203f61bc44d5983a203861f06e48241d407d56b72bb612792014ff77d8e2288699225241ca75d35823e563f71118851bd5f4f1aebf94c76d380d9f9315a822ef2054c1e234def6087ec7504e7423d51adcf6ed199a317199dbb561ee6ba7ecc736c5cd746fc17fa3609c6b0e1f19c4323d9e77ac67d92fdd82600" + + val expectedAcceptJsonString: String = { + s""" + |{ + | "result": + | { + | "temporaryContractId": "d1dc7596d2a2f14a2542f2b7e563c149c910ba2238b59532b9fc1ebf6f8465f1", + | "acceptCollateral": 60000, + | "fundingPubkey": "037dbfd3eb9296d1bd537c4008794989893821d48a163b0f3b7b01433599e81211", + | "payoutSpk": "0014cb62b501146622502837f5cd898a657feba867eb", + | "payoutSerialId": "10881229472670123895", + | "fundingInputs": [ + | { + | "inputSerialId": "16354653267988371239", + | "prevTx": "02000000000101433df2df0297e1de67283fd8d369a16be5179f3a696fe36bf601337426408af50100000000000000000280f0fa0200000000160014d431e458cc852e7f2f21a6055751b5d2f30b7bcac190f30200000000160014aba424ac1cdf6c4ff53dcc76c1458c2e8bb7c71702473044022049911e914325d2c21f1f7a52d0bef9f50362cf130a87d73a30e61863fa72582502205338e9fa57b6172c821cb9e2530e7b0bf27812e2110ffa7adf277da1be31872d012103cf091ac1820aabeb4399d63bacb5b1c58310166a8b9466bc5efb4dbb678aba1e00000000", + | "prevTxVout": 0, + | "sequence": 4294967295, + | "maxWitnessLen": 107, + | "redeemScript": "" + | } + | ], + | "changeSpk": "00145097a3a31e88036a7396728b6e3ea47c70780f8d", + | "changeSerialId": "13154619712848351014", + | "cetAdaptorSignatures": { + | "ecdsaAdaptorSignatures": [ + | { + | "signature": "02834a87e2019a7c431db58235abd00ce62ca5a36e8b5e60bc6dd895045e9d936302e00d2ff673cf117f8bc3e6cbbbdc9efeb6b641d55c32c58ca46422c7279db2a3d07fa999360ba675ad6d342d21997e8644f60509a106df667f85f948f1d73ca5de082db23a50f07d9180e253bd8879567d1e4110f07de6f57ef75cf32206edc4d0656f6dcd738f0301d4baf73182aac102a1917b9688dcb7b74bcdcc85374b92" + | }, + | { + | "signature": "03e9a2eb7600712af2236b80ebceb644d5bad65f3ccbcdcf8f2a649a48fd8a3b72023a6a694000be8df5519f7d5e11414b73274377300655be6ad1cc0da6c0eedf1fe36e4e82f3125c8ba40a1bcda444b4ec528d1674e6cee5e61d71f92586212ab2b5afdc90fe4744db5c9873eb240dfaf1952b41b3f3fa7e7a86def2a691bcbae5ad968459f712041a6ea8f39314c19545f8b5b501250aae6d09d0cfff7d9f79f4" + | }, + | { + | "signature": "03293e081859a95c05ab7a6fb6b5e960c928a199b89166188d4db5c4546fa8baef03a08004a37d041bcb10ceb94d3797814789ac192c2c79d9ddd41397cb0d0e6af84fcb15cdc885df164aaa81e2dbc95af76d4ca71017b81b2b20307c4a067d844c427f1e5c06da2082911203f61bc44d5983a203861f06e48241d407d56b72bb612792014ff77d8e2288699225241ca75d35823e563f71118851bd5f4f1aebf94c" + | } + | ] + | }, + | "refundSignature": "3044022076d380d9f9315a822ef2054c1e234def6087ec7504e7423d51adcf6ed199a3170220199dbb561ee6ba7ecc736c5cd746fc17fa3609c6b0e1f19c4323d9e77ac67d92", + | "negotiationFields": null + | }, + | + | "error": null + |} + |""".stripMargin + } + + val expectedAccept: ujson.Value = ujson.read(expectedAcceptJsonString) + + val signHex = + "a71eafcd7e786c0b9085784a6d063c7f4e7291784f9be3ba23cd8734559040fd2202fda716fd02890402feee4c75948830549fd19efd93cd4e00d4f538041ecf2f2cb6820c78c0a57fc9034d9ce5849e0abe235b8c3137506745508aa17c7a3064ec4172e76c88e66ca27257c5ec09f99201c9bc95b8ab345aff9b32b89c328d21a6ca5553f287a650320162381ef95042d9ac57f66f39dc6904f17a472671d3828f75ef29fbddcd31119b69adb216dac9b5960b91b3e3d83b9310484ef667d19f4bed01964ff063a2acbd025e840169038f0d046cc5005d9d622fd19ec2dd84d15a022f3eff4a781c6f404602cf8ec975191b73ecc3944786f5cca2a01ba3e71bbde9ea6932083c23591dfb6e3e06489cde6415c39cc9e187283f7a9b76227b157572dc62f1a5b130b4fc6b68c14ea07deddb2865d634a244839a87f158145c59d9aecb51b082c3a2ef0cb0201bc65252a3876f7253855ce4d3e72cd650bc275dd878f920162af74865b0346b03ea132cd20738f1a6640f98dc5664d12952e000feb69bbb3c4929c9054b3ca965030b788e83098f906cd3b088ad297e8149d08eb02442f42bd2b9e4455d5a19a96fd329625e1f1d0db9b9f625a6242f1fc53f56ec8090575c704e364518bdf7d690386d0f4a7bfea6c263c5fbc91631b0f0323a92c51f43f3228c9649c5441b338f2e37cbad0d10ce0d80429e2a6a528e4a0e9700188b86c267246b5049ce7da03102c2ec44af74653bdad7db89a38ab61d3dabf0646acdfab1bd5906d4a75f8d7a0c03051281626d4c8c84a5b7d09717c69f1c5340300be5ef6d94a4efe4d1baebbf820f8891a3b86d0f4a9a42d3fa0a44f7d711a5e65cc887b0424530e9b7e49f8d3a21d89762d535c7f121bc057b01b67372ea19762fad714580767ea6a091771808c3806b273382e0792573f624a90e15004a3b852e627f422c07209fd876c3816905d4dcc449db1a5997b82f2aabaffb3414ad1e10c159fc3fcbd6dab121a3cb9837583d39f66aa347a8170dba12cf88102997f460bb27d70365f7f85cd4ce5ee2fda71870000100020047304402204bbe45fd65402ad89784062c2af0f0c88a55f1de4509e4491f7933ada30fbab102207144fe9b8d621d2bef5c226d6f2f8ff43548f4c0bb9ea221a0c2a034a069c74f01002103bdb496d31a30d8e87ab7767fd26bfdfa7f6d06a0cfaea082a91af1ae1814f251" + + val expectedSignJsonString: String = { + s""" + |{ + | "result": + | + |{ + | "contractId": "afcd7e786c0b9085784a6d063c7f4e7291784f9be3ba23cd8734559040fd2202", + | "cetAdaptorSignatures": { + | "ecdsaAdaptorSignatures": [ + | { + | "signature": "02feee4c75948830549fd19efd93cd4e00d4f538041ecf2f2cb6820c78c0a57fc9034d9ce5849e0abe235b8c3137506745508aa17c7a3064ec4172e76c88e66ca27257c5ec09f99201c9bc95b8ab345aff9b32b89c328d21a6ca5553f287a650320162381ef95042d9ac57f66f39dc6904f17a472671d3828f75ef29fbddcd31119b69adb216dac9b5960b91b3e3d83b9310484ef667d19f4bed01964ff063a2acbd" + | }, + | { + | "signature": "025e840169038f0d046cc5005d9d622fd19ec2dd84d15a022f3eff4a781c6f404602cf8ec975191b73ecc3944786f5cca2a01ba3e71bbde9ea6932083c23591dfb6e3e06489cde6415c39cc9e187283f7a9b76227b157572dc62f1a5b130b4fc6b68c14ea07deddb2865d634a244839a87f158145c59d9aecb51b082c3a2ef0cb0201bc65252a3876f7253855ce4d3e72cd650bc275dd878f920162af74865b0346b" + | }, + | { + | "signature": "03ea132cd20738f1a6640f98dc5664d12952e000feb69bbb3c4929c9054b3ca965030b788e83098f906cd3b088ad297e8149d08eb02442f42bd2b9e4455d5a19a96fd329625e1f1d0db9b9f625a6242f1fc53f56ec8090575c704e364518bdf7d690386d0f4a7bfea6c263c5fbc91631b0f0323a92c51f43f3228c9649c5441b338f2e37cbad0d10ce0d80429e2a6a528e4a0e9700188b86c267246b5049ce7da031" + | }, + | { + | "signature": "02c2ec44af74653bdad7db89a38ab61d3dabf0646acdfab1bd5906d4a75f8d7a0c03051281626d4c8c84a5b7d09717c69f1c5340300be5ef6d94a4efe4d1baebbf820f8891a3b86d0f4a9a42d3fa0a44f7d711a5e65cc887b0424530e9b7e49f8d3a21d89762d535c7f121bc057b01b67372ea19762fad714580767ea6a091771808c3806b273382e0792573f624a90e15004a3b852e627f422c07209fd876c38169" + | } + | ] + | }, + | "refundSignature": "3044022005d4dcc449db1a5997b82f2aabaffb3414ad1e10c159fc3fcbd6dab121a3cb98022037583d39f66aa347a8170dba12cf88102997f460bb27d70365f7f85cd4ce5ee2", + | "fundingSignatures": { + | "fundingSignatures": [ + | { + | "witnessElements": [ + | { + | "witness": "304402204bbe45fd65402ad89784062c2af0f0c88a55f1de4509e4491f7933ada30fbab102207144fe9b8d621d2bef5c226d6f2f8ff43548f4c0bb9ea221a0c2a034a069c74f01" + | }, + | { + | "witness": "03bdb496d31a30d8e87ab7767fd26bfdfa7f6d06a0cfaea082a91af1ae1814f251" + | } + | ] + | } + | ] + | } + | }, + | + | "error": null + |} + |""".stripMargin + } + + val expectedSign: ujson.Value = ujson.read(expectedSignJsonString) }