Skip to content

Commit

Permalink
adaptor sigs are deniable! with a valid bip340sig, anybody can adapt …
Browse files Browse the repository at this point in the history
…it to an arbitrary point
  • Loading branch information
VzxPLnHqr authored and fiatjaf committed Jul 26, 2023
1 parent fd85b49 commit ff41b5d
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 8 deletions.
39 changes: 33 additions & 6 deletions shared/src/main/scala/AdaptorSig.scala
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ object AdaptorSig {
* @param tweakPoint
* the curve point by which to "tweak" the signature
* @return
* (R,s,T) as a 97-byte ByteVector
* (R,s,T) as `AdaptorSig` datatype
*/
def computeSchnorrAdaptorSignatureForPoint(
data: ByteVector32,
Expand All @@ -46,7 +46,7 @@ object AdaptorSig {
val r = PrivateKey(calculateBip340nonce(data, privateKey, None))
val pointR = r.publicKey
val pointRprime = pointR + tweakPoint
//require(pointRprime.isEven, "(R + T) must be even for adaptor signature to verify properly")

val pointP = privateKey.publicKey
val e = calculateBip340challenge(
data.bytes,
Expand All @@ -61,6 +61,35 @@ object AdaptorSig {
AdaptorSig(pointR, s, tweakPoint)
}

/**
* Tweak a valid schnorr signature `(R',s')` with a scalar value `t` to create
* an adaptor signature `(R' - t*G, s' - t, t*G). Anybody with knowledge of `t`
* will be able to repair the resulting adaptor signature to reconstruct the
* valid original signature. Because knowledge of the signing key was not
* necessary to create the adaptor signature, this shows that adaptor
* signatures posess a denaibility property. see:
* https://suredbits.com/schnorr-applications-scriptless-scripts/
*
* @param sig
* @param scalarTweak
* @return `AdaptorSig`
*/
def tweakSchnorrSignatureWithScalar(
bip340sig: ByteVector64,
scalarTweak: ByteVector32
): AdaptorSig = {
val (pointRprime, sPrime) = (
XOnlyPublicKey(ByteVector32(bip340sig.take(32))).publicKey,
PrivateKey(ByteVector32(bip340sig.drop(32)))
)
val pointT = PrivateKey(scalarTweak).publicKey
AdaptorSig(
pointR = pointRprime - pointT,
s = sPrime - PrivateKey(scalarTweak),
pointT = pointT
)
}

/** Verify an "Adaptor Signature." If verification is successful and the
* verifier knows the discrete logarithm (private key) for the `tweakPoint`,
* then verifier will be able to repair the adaptor signature into a complete
Expand All @@ -70,8 +99,7 @@ object AdaptorSig {
* https://suredbits.com/schnorr-applications-scriptless-scripts/
*
* @param adaptorSig
* a 97-byte `ByteVector` `(R,s,T)` where `R` and `s` are 32-bytes,
* and `T` is a 33-byte compressed public key.
* `AdaptorSig == (R,s,T)`
* `e = H(R + T || P || m)`
* `if R == R' = s*G - e*P, the adaptorSig is valid
* @param data
Expand Down Expand Up @@ -105,8 +133,7 @@ object AdaptorSig {
* https://suredbits.com/schnorr-applications-scriptless-scripts/
*
* @param adaptorSig
* a 97-byte `ByteVector` `(R,s,T)` where `R` and `s` are 32-bytes,
* and `T` is a 33-byte compressed public key.
* `AdaptorSig(R,s,T)`
* @param data
* the message which is signed (usually a hash of a bitcoin transaction)
* @param publicKey
Expand Down
40 changes: 38 additions & 2 deletions shared/src/test/scala/AdaptorSigsTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,7 @@ object AdaptorSigsTest extends TestSuite {
privateKey = priv,
tweakPoint = tweakPoint
)
//require(XOnlyPublicKey(ByteVector32(adaptorSig.take(32))) == PrivateKey(calculateBip340nonce(data = msg, privateKey = priv, auxrand32 = None)).publicKey.xonly, "R does not match")
//require(PublicKey(adaptorSig.drop(64)).value == tweakPoint.value, "tweakPoint does not match")

assert(verifySchnorrAdaptorSignature(adaptorSig, msg, priv.publicKey.xonly))
val repairedSig = repairSchnorrAdaptorSignature(
adaptorSig = adaptorSig,
Expand All @@ -113,5 +112,42 @@ object AdaptorSigsTest extends TestSuite {

}
}

test("a bunch of deniable adaptor sigs") {
import Crypto._, AdaptorSig._
val num_trials = 100
(0 until num_trials).foreach{ i =>
//println(s"index $i started")
val priv = PrivateKey(sha256(ByteVector(s"priv$i".getBytes)))
val msg = sha256(ByteVector(s"msg$i".getBytes))
val tweak = sha256(ByteVector(s"tweak$i".getBytes))
val sig = signSchnorr(data = msg, privateKey = priv, auxrand32 = None)

/**
* notice how we can also turn any valid bip340 signature into an
* an adaptor siganture for point t*G = T. Anybody with knowledge of `t`
* will be able to repair the resulting adaptor signature to reconstruct the
* valid original signature. Because knowledge of the signing key was not
* necessary to create the adaptor signature, this shows that adaptor
* signatures posess a denaibility property.
* */
val adaptorSig = tweakSchnorrSignatureWithScalar(
bip340sig = sig,
scalarTweak = tweak
)

assert(verifySchnorrAdaptorSignature(adaptorSig, msg, priv.publicKey.xonly))
val repairedSig = repairSchnorrAdaptorSignature(
adaptorSig = adaptorSig,
data = msg,
scalarTweak = tweak
)
assert(verifySignatureSchnorr(repairedSig, msg, priv.publicKey.xonly))

val extractedScalar = extractScalar(adaptorSig, repairedSig)
assert(extractedScalar == tweak)
//println(s"index $i succeeded!")
}
}
}
}

0 comments on commit ff41b5d

Please sign in to comment.