Skip to content

Commit ba6db74

Browse files
committed
Prevent malicious taproot commitment
1 parent 76ba82f commit ba6db74

File tree

3 files changed

+46
-11
lines changed

3 files changed

+46
-11
lines changed

python/chilldkg_ref/chilldkg.py

+2
Original file line numberDiff line numberDiff line change
@@ -684,6 +684,7 @@ def recover(
684684
certeq_verify(hostpubkeys, eq_input, cert)
685685

686686
# Compute threshold pubkey and individual pubshares
687+
sum_coms, secshare_tweak = sum_coms.invalid_taproot_commit()
687688
threshold_pubkey = sum_coms.commitment_to_secret()
688689
pubshares = [sum_coms.pubshare(i) for i in range(n)]
689690

@@ -706,6 +707,7 @@ def recover(
706707
idx,
707708
enc_secshares[idx],
708709
)
710+
secshare += secshare_tweak
709711

710712
# This is just a sanity check. Our signature is valid, so we have done
711713
# this check already during the actual session.

python/chilldkg_ref/simplpedpop.py

+16-10
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from typing import List, NamedTuple, NewType, Tuple, Optional, NoReturn
33

44
from secp256k1proto.bip340 import schnorr_sign, schnorr_verify
5-
from secp256k1proto.secp256k1 import GE, Scalar
5+
from secp256k1proto.secp256k1 import G, GE, Scalar
66
from .util import (
77
BIP_TAG,
88
SecretKeyError,
@@ -112,6 +112,7 @@ class ParticipantBlameState(NamedTuple):
112112
n: int
113113
idx: int
114114
secshare: Scalar
115+
secshare_tweak: Scalar
115116
pubshare: GE
116117

117118

@@ -204,16 +205,20 @@ def participant_step2(
204205
i, "Participant sent invalid proof-of-knowledge"
205206
)
206207
sum_coms = assemble_sum_coms(coms_to_secrets, sum_coms_to_nonconst_terms)
207-
threshold_pubkey = sum_coms.commitment_to_secret()
208-
pubshare = sum_coms.pubshare(idx)
208+
sum_coms_tweaked, secshare_tweak = sum_coms.invalid_taproot_commit()
209+
secshare += secshare_tweak
210+
threshold_pubkey = sum_coms_tweaked.commitment_to_secret()
211+
pubshare = sum_coms_tweaked.pubshare(idx)
209212

210213
if not VSSCommitment.verify_secshare(secshare, pubshare):
211214
raise UnknownFaultyParticipantOrCoordinatorError(
212-
ParticipantBlameState(n, idx, secshare, pubshare),
215+
ParticipantBlameState(n, idx, secshare, secshare_tweak, pubshare),
213216
"Received invalid secshare, consider blaming to determine faulty party",
214217
)
215218

216-
pubshares = [sum_coms.pubshare(i) if i != idx else pubshare for i in range(n)]
219+
pubshares = [
220+
sum_coms_tweaked.pubshare(i) if i != idx else pubshare for i in range(n)
221+
]
217222
dkg_output = DKGOutput(
218223
secshare.to_bytes(),
219224
threshold_pubkey.to_bytes_compressed(),
@@ -228,13 +233,13 @@ def participant_blame(
228233
cblame: CoordinatorBlameMsg,
229234
partial_secshares: List[Scalar],
230235
) -> NoReturn:
231-
n, idx, secshare, pubshare = blame_state
236+
n, idx, secshare, secshare_tweak, pubshare = blame_state
232237
partial_pubshares = cblame.partial_pubshares
233238

234-
if GE.sum(*partial_pubshares) != pubshare:
239+
if GE.sum(*partial_pubshares) + secshare_tweak * G != pubshare:
235240
raise FaultyCoordinatorError("Sum of partial pubshares not equal to pubshare")
236241

237-
if Scalar.sum(*partial_secshares) != secshare:
242+
if Scalar.sum(*partial_secshares) + secshare_tweak != secshare:
238243
raise SecshareSumError("Sum of partial secshares not equal to secshare")
239244

240245
for i in range(n):
@@ -286,8 +291,9 @@ def coordinator_step(
286291
cmsg = CoordinatorMsg(coms_to_secrets, sum_coms_to_nonconst_terms, pops)
287292

288293
sum_coms = assemble_sum_coms(coms_to_secrets, sum_coms_to_nonconst_terms)
289-
threshold_pubkey = sum_coms.commitment_to_secret()
290-
pubshares = [sum_coms.pubshare(i) for i in range(n)]
294+
sum_coms_tweaked, secshare_tweak = sum_coms.invalid_taproot_commit()
295+
threshold_pubkey = sum_coms_tweaked.commitment_to_secret()
296+
pubshares = [sum_coms_tweaked.pubshare(i) for i in range(n)]
291297

292298
dkg_output = DKGOutput(
293299
None,

python/chilldkg_ref/vss.py

+28-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
from __future__ import annotations
22

3-
from typing import List
3+
from typing import List, Tuple
44

55
from secp256k1proto.secp256k1 import GE, G, Scalar
6+
from secp256k1proto.util import tagged_hash
67

78
from .util import tagged_hash_bip_dkg
89

@@ -74,6 +75,32 @@ def commitment_to_secret(self) -> GE:
7475
def commitment_to_nonconst_terms(self) -> List[GE]:
7576
return self.ges[1 : self.t()]
7677

78+
def invalid_taproot_commit(self) -> Tuple[VSSCommitment, Scalar]:
79+
# Create VSS commitment that Taproot-commits to a message that is
80+
# invalid for BIP 341 script path spends.
81+
#
82+
# Specifically, for a VSS commitment `com`, we have:
83+
# `com.add_invalid_taproot_commitment().commitment_to_secret() = com.commitment_to_secret() + t*G`.
84+
#
85+
# The tweak `t` commits to a message chosen such that the new
86+
# `commitment_to_secret()` is unspendable via a BIP 341 Taproot script
87+
# path.
88+
#
89+
# This prevents a malicious participant from secretly inserting a _valid_
90+
# Taproot commitment to a script path into the summed VSS commitment during
91+
# the DKG protocol. If the resulting threshold public key was used directly
92+
# in a BIP 341 Taproot output, the malicious participant would be able to
93+
# spend the output using their hidden script path.
94+
#
95+
# The function returns the updated VSS commitment and the tweak `t` which
96+
# must be added to all secret shares of the commitment.
97+
pk = self.commitment_to_secret()
98+
secshare_tweak = Scalar.from_bytes(
99+
tagged_hash("TapTweak", pk.to_bytes_compressed())
100+
)
101+
vss_tweak = VSSCommitment([secshare_tweak * G] + [GE()] * (self.t() - 1))
102+
return (self + vss_tweak, secshare_tweak)
103+
77104

78105
class VSS:
79106
f: Polynomial

0 commit comments

Comments
 (0)