mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-03-03 10:46:42 +01:00
Validate announcement signatures on create/accept offer (#4071)
* Validate announcement signatures on create/accept offer * unit tests
This commit is contained in:
parent
5366428fb2
commit
bce58ba33d
3 changed files with 97 additions and 0 deletions
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue