Validate announcement signatures on create/accept offer (#4071)

* Validate announcement signatures on create/accept offer

* unit tests
This commit is contained in:
rorp 2022-02-11 04:50:45 -08:00 committed by GitHub
parent 5366428fb2
commit bce58ba33d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 97 additions and 0 deletions

View file

@ -9,6 +9,7 @@ import org.bitcoins.core.protocol.tlv._
import org.bitcoins.core.wallet.fee.SatoshisPerVirtualByte
import org.bitcoins.core.wallet.utxo.TxoState
import org.bitcoins.crypto._
import org.bitcoins.dlc.wallet.DLCWallet.InvalidAnnouncementSignature
import org.bitcoins.dlc.wallet.internal.DLCDataManagement
import org.bitcoins.testkit.wallet.DLCWalletUtil._
import org.bitcoins.testkit.wallet.FundWalletUtil.FundedDLCWallet
@ -856,4 +857,50 @@ class WalletDLCSetupTest extends BitcoinSDualWalletTest {
} yield res
}
it must "fail to create an offer with an invalid announcement signature" in {
wallets =>
val walletA = wallets._1.wallet
val offerData: DLCOffer = DLCWalletUtil.invalidDLCOffer
for {
res <- recoverToSucceededIf[InvalidAnnouncementSignature](
walletA.createDLCOffer(
offerData.contractInfo,
offerData.totalCollateral,
Some(offerData.feeRate),
offerData.timeouts.contractMaturity.toUInt32,
UInt32.max
))
} yield {
res
}
}
it must "fail to accept an offer with an invalid announcement signature" in {
wallets =>
val walletA = wallets._1.wallet
val walletB = wallets._2.wallet
//https://test.oracle.suredbits.com/contract/enum/75b08299654dca23b80cf359db6afb6cfd6e55bc898b5397d3c0fe796dfc13f0/12fb3e5f091086329ed0d2a12c3fcfa80111a36ef3fc1ac9c2567076a57d6a73
val contractInfo = ContractInfoV0TLV.fromHex(
"fdd82eeb00000000000186a0fda71026030359455300000000000186a0024e4f0000000000000000056f746865720000000000000000fda712b5fdd824b1596ec40d0dae3fdf54d9795ad51ec069970c6863a02d244663d39fd6bedadc0070349e1ba2e17583ee2d1cb3ae6fffaaa1c45039b61c5c4f1d0d864221c461745d1bcfab252c6dd9edd7aea4c5eeeef138f7ff7346061ea40143a9f5ae80baa9fdd8224d0001fa5b84283852400b21a840d5d5ca1cc31867c37326ad521aa50bebf3df4eea1a60b03280fdd8060f000303594553024e4f056f74686572135465746865722d52657365727665732d363342")
val feeRateOpt = Some(SatoshisPerVirtualByte(Satoshis.one))
val totalCollateral = Satoshis(5000)
for {
offer <- walletA.createDLCOffer(contractInfoTLV = contractInfo,
collateral = totalCollateral,
feeRateOpt = feeRateOpt,
locktime = UInt32.zero,
refundLT = UInt32.one)
invalidOffer = offer.copy(contractInfo = invalidContractInfo)
res <- recoverToSucceededIf[InvalidAnnouncementSignature](
walletB.acceptDLCOffer(invalidOffer))
} yield {
res
}
}
}

View file

@ -30,6 +30,7 @@ import org.bitcoins.core.wallet.fee.SatoshisPerVirtualByte
import org.bitcoins.core.wallet.utxo._
import org.bitcoins.crypto._
import org.bitcoins.db.SafeDatabase
import org.bitcoins.dlc.wallet.DLCWallet.InvalidAnnouncementSignature
import org.bitcoins.dlc.wallet.internal._
import org.bitcoins.dlc.wallet.models._
import org.bitcoins.dlc.wallet.util.{
@ -269,6 +270,12 @@ abstract class DLCWallet
locktime: UInt32,
refundLocktime: UInt32): Future[DLCOffer] = {
logger.info("Creating DLC Offer")
if (!validateAnnouncementSignatures(contractInfo.oracleInfos)) {
return Future.failed(
InvalidAnnouncementSignature(
s"Contract info contains invalid announcement signature(s)"))
}
val announcements =
contractInfo.oracleInfos.head.singleOracleInfos.map(_.announcement)
@ -552,6 +559,10 @@ abstract class DLCWallet
*/
override def acceptDLCOffer(offer: DLCOffer): Future[DLCAccept] = {
logger.debug("Calculating relevant wallet data for DLC Accept")
if (!validateAnnouncementSignatures(offer.oracleInfos)) {
return Future.failed(InvalidAnnouncementSignature(
s"Offer ${offer.tempContractId.hex} contains invalid announcement signature(s)"))
}
val dlcId = calcDLCId(offer.fundingInputs.map(_.outPoint))
@ -575,6 +586,12 @@ abstract class DLCWallet
} yield dlcAccept
}
private def validateAnnouncementSignatures(
oracleInfos: Vector[OracleInfo]): Boolean = {
oracleInfos.forall(infos =>
infos.singleOracleInfos.forall(_.announcement.validateSignature))
}
private def fundDLCAcceptMsg(
offer: DLCOffer,
collateral: CurrencyUnit,
@ -1726,6 +1743,9 @@ abstract class DLCWallet
object DLCWallet extends WalletLogger {
case class InvalidAnnouncementSignature(message: String)
extends RuntimeException(message)
private case class DLCWalletImpl(
nodeApi: NodeApi,
chainQueryApi: ChainQueryApi,

View file

@ -67,12 +67,28 @@ object DLCWalletUtil extends Logging {
rValue,
sampleOutcomes.map(_._1))
lazy val invalidOracleInfo: EnumSingleOracleInfo = {
val info = EnumSingleOracleInfo.dummyForKeys(oraclePrivKey,
rValue,
sampleOutcomes.map(_._1))
val announcement = info.announcement.asInstanceOf[OracleAnnouncementV0TLV]
val invalidAnnouncement =
announcement.copy(announcementSignature = SchnorrDigitalSignature.dummy)
info.copy(announcement = invalidAnnouncement)
}
lazy val sampleContractOraclePair: ContractOraclePair.EnumPair =
ContractOraclePair.EnumPair(sampleContractDescriptor, sampleOracleInfo)
lazy val invalidContractOraclePair: ContractOraclePair.EnumPair =
ContractOraclePair.EnumPair(sampleContractDescriptor, invalidOracleInfo)
lazy val sampleContractInfo: ContractInfo =
SingleContractInfo(half, sampleContractOraclePair)
lazy val invalidContractInfo: ContractInfo =
SingleContractInfo(half, invalidContractOraclePair)
lazy val sampleOracleWinSig: SchnorrDigitalSignature =
oraclePrivKey.schnorrSignWithNonce(winHash.bytes, kValue)
@ -164,6 +180,20 @@ object DLCWalletUtil extends Logging {
timeouts = dummyTimeouts
)
lazy val invalidDLCOffer: DLCOffer = DLCOffer(
protocolVersionOpt = DLCOfferTLV.currentVersionOpt,
contractInfo = invalidContractInfo,
pubKeys = dummyDLCKeys,
totalCollateral = half,
fundingInputs = Vector(dummyFundingInputs.head),
changeAddress = dummyAddress,
payoutSerialId = sampleOfferPayoutSerialId,
changeSerialId = sampleOfferChangeSerialId,
fundOutputSerialId = sampleFundOutputSerialId,
feeRate = SatoshisPerVirtualByte(Satoshis(3)),
timeouts = dummyTimeouts
)
lazy val sampleMultiNonceDLCOffer: DLCOffer =
sampleDLCOffer.copy(contractInfo = multiNonceContractInfo)