Add annex to TaprootKeyPath (#4416)

* Add annex to TaprootKeyPath

* Add TaprootWitness.annexHashOpt

* Fix duplicate method
This commit is contained in:
Chris Stewart 2022-06-23 12:12:38 -05:00 committed by GitHub
parent 99b75d166f
commit 124cbe4b67
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 48 additions and 19 deletions

View file

@ -91,4 +91,13 @@ class TaprootWitnessTest extends BitcoinSUnitTest {
assert(taprootScriptPath.annexHashOpt.get.hex == expectedAnnexHash)
}
it must "construct a taproot keypath witness with an annex" in {
val vec = Vector(
"c2bdc23435c7bbdce741081181eecd31865f7d94fad6c49c8b1f4619aad72b83354530dbc9446243ff81e0dac2e77b2d437b9d53d279b535a23fb8c599454b3e02",
"50ba")
val stack = vec.map(ByteVector.fromValidHex(_))
val tr = TaprootWitness.fromStack(stack.reverse)
assert(tr.isInstanceOf[TaprootKeyPath])
assert(tr.annexOpt == Some(stack.last))
}
}

View file

@ -205,20 +205,37 @@ object ScriptWitness extends Factory[ScriptWitness] {
sealed trait TaprootWitness extends ScriptWitness {
override def bytes: ByteVector = RawScriptWitnessParser.write(this)
def annexOpt: Option[ByteVector]
/** As per bip341
* the SHA256 of (compact_size(size of annex) || annex), where annex includes the mandatory 0x50 prefix.
* @see https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#signature-validation-rules
*/
def annexHashOpt: Option[Sha256Digest] = {
annexOpt.map { annex =>
val cmpct = CompactSizeUInt.calc(annex)
CryptoUtil.sha256(cmpct.bytes ++ annex)
}
}
}
object TaprootWitness {
def fromStack(stack: Vector[ByteVector]): TaprootWitness = {
if (stack.length == 1) TaprootKeyPath.fromStack(stack)
else TaprootScriptPath(stack)
val hasAnnex = TaprootScriptPath.hasAnnex(stack)
if ((hasAnnex && stack.length == 2) || stack.length == 1) {
TaprootKeyPath.fromStack(stack)
} else TaprootScriptPath(stack)
}
}
/** Spending a taproot output via the key path spend */
case class TaprootKeyPath(
signature: SchnorrDigitalSignature,
hashType: HashType)
hashType: HashType,
annexOpt: Option[ByteVector])
extends TaprootWitness {
override val stack: Vector[ByteVector] = Vector(signature.bytes)
}
@ -226,21 +243,35 @@ case class TaprootKeyPath(
object TaprootKeyPath {
def fromStack(vec: Vector[ByteVector]): TaprootKeyPath = {
val hasAnnex = TaprootScriptPath.hasAnnex(vec)
require(
vec.length == 1,
s"Taproot keypath can only have one stack element, got=${vec.length}")
vec.length == 1 || (hasAnnex && vec.length == 2),
s"Taproot keypath can only have at most 2 stack elements, got=${vec.length}")
val sigBytes = vec.head
val annexOpt = {
if (hasAnnex) {
Some(vec.head)
} else {
None
}
}
val sigBytes = {
if (hasAnnex) {
vec(1)
} else {
vec.head
}
}
val keyPath = if (sigBytes.length == 64) {
//means SIGHASH_ALL is implicitly encoded
//see: https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#Common_signature_message
val sig = SchnorrDigitalSignature.fromBytes(sigBytes)
TaprootKeyPath(sig, HashType.sigHashAll)
TaprootKeyPath(sig, HashType.sigHashAll, annexOpt)
} else if (sigBytes.length == 65) {
val sig = SchnorrDigitalSignature.fromBytes(sigBytes.dropRight(1))
val hashType = HashType.fromByte(sigBytes.last)
TaprootKeyPath(sig, hashType)
TaprootKeyPath(sig, hashType, annexOpt)
} else {
sys.error(
s"Unknown sig bytes length, should be 64 or 65, got=${sigBytes.length}")
@ -300,17 +331,6 @@ case class TaprootScriptPath(stack: Vector[ByteVector]) extends TaprootWitness {
/** Let p = c[1:33] and let P = lift_x(int(p)) where lift_x and [:] are defined as in BIP340. Fail if this point is not on the curve.
*/
def p: XOnlyPubKey = controlBlock.p
/** As per bip341
* the SHA256 of (compact_size(size of annex) || annex), where annex includes the mandatory 0x50 prefix.
* @see https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#signature-validation-rules
*/
def annexHashOpt: Option[Sha256Digest] = {
annexOpt.map { annex =>
val cmpct = CompactSizeUInt.calc(annex)
CryptoUtil.sha256(cmpct.bytes ++ annex)
}
}
}
object TaprootScriptPath {