diff --git a/input/script_utils.go b/input/script_utils.go index 5054c57fe..7e1c4efb4 100644 --- a/input/script_utils.go +++ b/input/script_utils.go @@ -665,7 +665,7 @@ func senderHtlcTapScriptTree(senderHtlcKey, receiverHtlcKey, // SenderHTLCScriptTaproot constructs the taproot witness program (schnorr key) // for an outgoing HTLC on the sender's version of the commitment transaction. // This method returns the top level tweaked public key that commits to both -// the script paths. +// the script paths. This is also known as an offered HTLC. // // The returned key commits to a tapscript tree with two possible paths: // @@ -727,12 +727,21 @@ func SenderHTLCScriptTaprootRedeem(signer Signer, signDesc *SignDescriptor, // In addition to the signature and the witness/leaf script, we also // need to make a control block proof using the tapscript tree. - successTapLeafHash := txscript.NewBaseTapLeaf( - signDesc.WitnessScript, - ).TapHash() - successIdx := tapscriptTree.LeafProofIndex[successTapLeafHash] - successMerkleProof := tapscriptTree.LeafMerkleProofs[successIdx] - successControlBlock := successMerkleProof.ToControlBlock(revokeKey) + var ctrlBlock []byte + if signDesc.ControlBlock == nil { + successControlBlock := MakeTaprootCtrlBlock( + signDesc.WitnessScript, revokeKey, tapscriptTree, + ) + + ctrlBytes, err := successControlBlock.ToBytes() + if err != nil { + return nil, err + } + + ctrlBlock = ctrlBytes + } else { + ctrlBlock = signDesc.ControlBlock + } // The final witness stack is: // @@ -740,10 +749,7 @@ func SenderHTLCScriptTaprootRedeem(signer Signer, signDesc *SignDescriptor, witnessStack[0] = maybeAppendSighash(sweepSig, signDesc.HashType) witnessStack[1] = preimage witnessStack[2] = signDesc.WitnessScript - witnessStack[3], err = successControlBlock.ToBytes() - if err != nil { - return nil, err - } + witnessStack[3] = ctrlBlock return witnessStack, nil } @@ -765,12 +771,20 @@ func SenderHTLCScriptTaprootTimeout(receiverSig Signature, // With the sweep signature obtained, we'll obtain the control block // proof needed to perform a valid spend for the timeout path. - timeoutTapLeafHash := txscript.NewBaseTapLeaf( - signDesc.WitnessScript, - ).TapHash() - timeoutIdx := tapscriptTree.LeafProofIndex[timeoutTapLeafHash] - timeoutMerkleProof := tapscriptTree.LeafMerkleProofs[timeoutIdx] - timeoutControlBlock := timeoutMerkleProof.ToControlBlock(revokeKey) + var ctrlBlockBytes []byte + if signDesc.ControlBlock == nil { + timeoutControlBlock := MakeTaprootCtrlBlock( + signDesc.WitnessScript, revokeKey, tapscriptTree, + ) + ctrlBytes, err := timeoutControlBlock.ToBytes() + if err != nil { + return nil, err + } + + ctrlBlockBytes = ctrlBytes + } else { + ctrlBlockBytes = signDesc.ControlBlock + } // The final witness stack is: // @@ -778,10 +792,7 @@ func SenderHTLCScriptTaprootTimeout(receiverSig Signature, witnessStack[0] = maybeAppendSighash(receiverSig, receiverSigHash) witnessStack[1] = maybeAppendSighash(sweepSig, signDesc.HashType) witnessStack[2] = signDesc.WitnessScript - witnessStack[3], err = timeoutControlBlock.ToBytes() - if err != nil { - return nil, err - } + witnessStack[3] = ctrlBlockBytes return witnessStack, nil } @@ -1200,7 +1211,7 @@ func receiverHtlcTapScriptTree(senderHtlcKey, receiverHtlcKey, // ReceiverHTLCScriptTaproot constructs the taproot witness program (schnor // key) for an incoming HTLC on the receiver's version of the commitment // transaction. This method returns the top level tweaked public key that -// commits to both the script paths. From the PoV for the receiver, this is an +// commits to both the script paths. From the PoV of the receiver, this is an // accepted HTLC. // // The returned key commits to a tapscript tree with two possible paths: @@ -1257,12 +1268,20 @@ func ReceiverHTLCScriptTaprootRedeem(senderSig Signature, // In addition to the signature and the witness/leaf script, we also // need to make a control block proof using the tapscript tree. - successTapLeafHash := txscript.NewBaseTapLeaf( - signDesc.WitnessScript, - ).TapHash() - successIdx := tapscriptTree.LeafProofIndex[successTapLeafHash] - successMerkleProof := tapscriptTree.LeafMerkleProofs[successIdx] - successControlBlock := successMerkleProof.ToControlBlock(revokeKey) + var ctrlBlock []byte + if signDesc.ControlBlock == nil { + redeemControlBlock := MakeTaprootCtrlBlock( + signDesc.WitnessScript, revokeKey, tapscriptTree, + ) + ctrlBytes, err := redeemControlBlock.ToBytes() + if err != nil { + return nil, err + } + + ctrlBlock = ctrlBytes + } else { + ctrlBlock = signDesc.ControlBlock + } // The final witness stack is: // * @@ -1272,10 +1291,7 @@ func ReceiverHTLCScriptTaprootRedeem(senderSig Signature, witnessStack[1] = maybeAppendSighash(sweepSig, signDesc.HashType) witnessStack[2] = paymentPreimage witnessStack[3] = signDesc.WitnessScript - witnessStack[4], err = successControlBlock.ToBytes() - if err != nil { - return nil, err - } + witnessStack[4] = ctrlBlock return witnessStack, nil } @@ -1311,22 +1327,25 @@ func ReceiverHTLCScriptTaprootTimeout(signer Signer, signDesc *SignDescriptor, // In addition to the signature and the witness/leaf script, we also // need to make a control block proof using the tapscript tree. - timeoutTapLeafHash := txscript.NewBaseTapLeaf( - signDesc.WitnessScript, - ).TapHash() - timeoutIdx := tapscriptTree.LeafProofIndex[timeoutTapLeafHash] - timeoutMerkleProof := tapscriptTree.LeafMerkleProofs[timeoutIdx] - timeoutControlBlock := timeoutMerkleProof.ToControlBlock(revokeKey) + var ctrlBlock []byte + if signDesc.ControlBlock == nil { + timeoutControlBlock := MakeTaprootCtrlBlock( + signDesc.WitnessScript, revokeKey, tapscriptTree, + ) + ctrlBlock, err = timeoutControlBlock.ToBytes() + if err != nil { + return nil, err + } + } else { + ctrlBlock = signDesc.ControlBlock + } // The final witness is pretty simple, we just need to present a valid // signature for the script, and then provide the control block. witnessStack := make(wire.TxWitness, 3) witnessStack[0] = maybeAppendSighash(sweepSig, signDesc.HashType) witnessStack[1] = signDesc.WitnessScript - witnessStack[2], err = timeoutControlBlock.ToBytes() - if err != nil { - return nil, err - } + witnessStack[2] = ctrlBlock return witnessStack, nil } @@ -1542,7 +1561,7 @@ func TaprootHtlcSpendRevoke(signer Signer, signDesc *SignDescriptor, // NOTE: The caller MUST set the txn version, sequence number, and sign // descriptor's sig hash cache before invocation. func TaprootHtlcSpendSuccess(signer Signer, signDesc *SignDescriptor, - revokeKey *btcec.PublicKey, sweepTx *wire.MsgTx, + sweepTx *wire.MsgTx, revokeKey *btcec.PublicKey, tapscriptTree *txscript.IndexedTapScriptTree) (wire.TxWitness, error) { // First, we'll generate the sweep signature based on the populated @@ -1553,14 +1572,21 @@ func TaprootHtlcSpendSuccess(signer Signer, signDesc *SignDescriptor, return nil, err } - // Now that we have the sweep signature, we'll construct the control - // block needed to spend the script path. - redeemTapLeafHash := txscript.NewBaseTapLeaf( - signDesc.WitnessScript, - ).TapHash() - redeemIdx := tapscriptTree.LeafProofIndex[redeemTapLeafHash] - redeemMerkleProof := tapscriptTree.LeafMerkleProofs[redeemIdx] - redeemControlBlock := redeemMerkleProof.ToControlBlock(revokeKey) + var ctrlBlock []byte + if signDesc.ControlBlock == nil { + // Now that we have the sweep signature, we'll construct the control + // block needed to spend the script path. + redeemControlBlock := MakeTaprootCtrlBlock( + signDesc.WitnessScript, revokeKey, tapscriptTree, + ) + + ctrlBlock, err = redeemControlBlock.ToBytes() + if err != nil { + return nil, err + } + } else { + ctrlBlock = signDesc.ControlBlock + } // Now that we have the redeem control block, we can construct the // final witness needed to spend the script: @@ -1569,10 +1595,7 @@ func TaprootHtlcSpendSuccess(signer Signer, signDesc *SignDescriptor, witnessStack := make(wire.TxWitness, 3) witnessStack[0] = maybeAppendSighash(sweepSig, signDesc.HashType) witnessStack[1] = signDesc.WitnessScript - witnessStack[2], err = redeemControlBlock.ToBytes() - if err != nil { - return nil, err - } + witnessStack[2] = ctrlBlock return witnessStack, nil } @@ -1946,6 +1969,19 @@ func TaprootCommitScriptToSelf(csvTimeout uint32, return commitScriptTree.TaprootKey, nil } +// MakeTaprootSCtrlBlock takes a leaf script, the internal key (usually the +// revoke key), and a script tree and creates a valid control block for a spend +// of the leaf. +func MakeTaprootCtrlBlock(leafScript []byte, internalKey *btcec.PublicKey, + scriptTree *txscript.IndexedTapScriptTree) txscript.ControlBlock { + + tapLeafHash := txscript.NewBaseTapLeaf(leafScript).TapHash() + scriptIdx := scriptTree.LeafProofIndex[tapLeafHash] + settleMerkleProof := scriptTree.LeafMerkleProofs[scriptIdx] + + return settleMerkleProof.ToControlBlock(internalKey) +} + // TaprootCommitSpendSuccess constructs a valid witness allowing a node to // sweep the settled taproot output after the delay has passed for a force // close. @@ -1955,14 +1991,24 @@ func TaprootCommitSpendSuccess(signer Signer, signDesc *SignDescriptor, // First, we'll need to construct a valid control block to execute the // leaf script for sweep settlement. - settleTapleafHash := txscript.NewBaseTapLeaf( - signDesc.WitnessScript, - ).TapHash() - settleIdx := scriptTree.LeafProofIndex[settleTapleafHash] - settleMerkleProof := scriptTree.LeafMerkleProofs[settleIdx] - settleControlBlock := settleMerkleProof.ToControlBlock( - &TaprootNUMSKey, - ) + // + // TODO(roasbeef); make into closure instead? only need reovke key and + // scriptTree to make the ctrl block -- then default version that would + // take froms ign desc? + var ctrlBlockBytes []byte + if signDesc.ControlBlock == nil { + settleControlBlock := MakeTaprootCtrlBlock( + signDesc.WitnessScript, &TaprootNUMSKey, scriptTree, + ) + ctrlBytes, err := settleControlBlock.ToBytes() + if err != nil { + return nil, err + } + + ctrlBlockBytes = ctrlBytes + } else { + ctrlBlockBytes = signDesc.ControlBlock + } // With the control block created, we'll now generate the signature we // need to authorize the spend. @@ -1977,7 +2023,7 @@ func TaprootCommitSpendSuccess(signer Signer, signDesc *SignDescriptor, witnessStack := make(wire.TxWitness, 3) witnessStack[0] = maybeAppendSighash(sweepSig, signDesc.HashType) witnessStack[1] = signDesc.WitnessScript - witnessStack[2], err = settleControlBlock.ToBytes() + witnessStack[2] = ctrlBlockBytes if err != nil { return nil, err } @@ -1993,14 +2039,20 @@ func TaprootCommitSpendRevoke(signer Signer, signDesc *SignDescriptor, // First, we'll need to construct a valid control block to execute the // leaf script for revocation path. - revokeTapleafHash := txscript.NewBaseTapLeaf( - signDesc.WitnessScript, - ).TapHash() - revokeIdx := scriptTree.LeafProofIndex[revokeTapleafHash] - revokeMerkleProof := scriptTree.LeafMerkleProofs[revokeIdx] - revokeControlBlock := revokeMerkleProof.ToControlBlock( - &TaprootNUMSKey, - ) + var ctrlBlockBytes []byte + if signDesc.ControlBlock == nil { + revokeCtrlBlock := MakeTaprootCtrlBlock( + signDesc.WitnessScript, &TaprootNUMSKey, scriptTree, + ) + revokeBytes, err := revokeCtrlBlock.ToBytes() + if err != nil { + return nil, err + } + + ctrlBlockBytes = revokeBytes + } else { + ctrlBlockBytes = signDesc.ControlBlock + } // With the control block created, we'll now generate the signature we // need to authorize the spend. @@ -2015,10 +2067,7 @@ func TaprootCommitSpendRevoke(signer Signer, signDesc *SignDescriptor, witnessStack := make(wire.TxWitness, 3) witnessStack[0] = maybeAppendSighash(revokeSig, signDesc.HashType) witnessStack[1] = signDesc.WitnessScript - witnessStack[2], err = revokeControlBlock.ToBytes() - if err != nil { - return nil, err - } + witnessStack[2] = ctrlBlockBytes return witnessStack, nil } @@ -2307,12 +2356,20 @@ func TaprootCommitRemoteSpend(signer Signer, signDesc *SignDescriptor, // First, we'll need to construct a valid control block to execute the // leaf script for sweep settlement. - settleTapleafHash := txscript.NewBaseTapLeaf( - signDesc.WitnessScript, - ).TapHash() - settleIdx := scriptTree.LeafProofIndex[settleTapleafHash] - settleMerkleProof := scriptTree.LeafMerkleProofs[settleIdx] - settleControlBlock := settleMerkleProof.ToControlBlock(&TaprootNUMSKey) + var ctrlBlockBytes []byte + if signDesc.ControlBlock == nil { + settleControlBlock := MakeTaprootCtrlBlock( + signDesc.WitnessScript, &TaprootNUMSKey, scriptTree, + ) + ctrlBytes, err := settleControlBlock.ToBytes() + if err != nil { + return nil, err + } + + ctrlBlockBytes = ctrlBytes + } else { + ctrlBlockBytes = signDesc.ControlBlock + } // With the control block created, we'll now generate the signature we // need to authorize the spend. @@ -2327,10 +2384,7 @@ func TaprootCommitRemoteSpend(signer Signer, signDesc *SignDescriptor, witnessStack := make(wire.TxWitness, 3) witnessStack[0] = maybeAppendSighash(sweepSig, signDesc.HashType) witnessStack[1] = signDesc.WitnessScript - witnessStack[2], err = settleControlBlock.ToBytes() - if err != nil { - return nil, err - } + witnessStack[2] = ctrlBlockBytes return witnessStack, nil } @@ -2507,11 +2561,11 @@ func TaprootOutputKeyAnchor(key *btcec.PublicKey) (*btcec.PublicKey, error) { // TaprootAnchorSpend constructs a valid witness allowing a node to sweep their // anchor output. func TaprootAnchorSpend(signer Signer, signDesc *SignDescriptor, - revokeTx *wire.MsgTx) (wire.TxWitness, error) { + sweepTx *wire.MsgTx) (wire.TxWitness, error) { // For this spend type, we only need a single signature which'll be a - // keyspend using the revoke private key. - sweepSig, err := signer.SignOutputRaw(revokeTx, signDesc) + // keyspend using the anchor private key. + sweepSig, err := signer.SignOutputRaw(sweepTx, signDesc) if err != nil { return nil, err } diff --git a/input/signdescriptor.go b/input/signdescriptor.go index 17008288b..3ed18cf11 100644 --- a/input/signdescriptor.go +++ b/input/signdescriptor.go @@ -98,6 +98,13 @@ type SignDescriptor struct { // This MUST be set when spending Taproot outputs. PrevOutputFetcher txscript.PrevOutputFetcher + // ControlBlock is a fully serialized control block that contains the + // merkle proof necessary to spend a taproot output. This may + // optionally be set if the SignMethod is + // input.TaprootScriptSpendSignMethod. In which case, this should be an + // inclusion proof for the WitnessScript. + ControlBlock []byte + // InputIndex is the target input within the transaction that should be // signed. InputIndex int @@ -218,6 +225,8 @@ func WriteSignDescriptor(w io.Writer, sd *SignDescriptor) error { return err } + // TODO(roasbeef): also write ctrl block? + return nil } diff --git a/input/size_test.go b/input/size_test.go index b8d819f29..f1a65963e 100644 --- a/input/size_test.go +++ b/input/size_test.go @@ -998,7 +998,7 @@ var witnessSizeTests = []witnessSizeTest{ } witness, err := input.TaprootHtlcSpendSuccess( - signer, signDesc, revokeKey.PubKey(), testTx, + signer, signDesc, testTx, revokeKey.PubKey(), scriptTree, ) require.NoError(t, err) diff --git a/input/taproot_test.go b/input/taproot_test.go index b9e4b28be..3a0f85d41 100644 --- a/input/taproot_test.go +++ b/input/taproot_test.go @@ -1675,8 +1675,8 @@ func secondLevelHtlcSuccessWitGen(sigHash txscript.SigHashType, } return TaprootHtlcSpendSuccess( - signer, signDesc, scriptTree.revokeKey.PubKey(), - spendTx, scriptTree.scriptTree, + signer, signDesc, spendTx, + scriptTree.revokeKey.PubKey(), scriptTree.scriptTree, ) } } diff --git a/input/witnessgen.go b/input/witnessgen.go index 72c518d9b..4bb217052 100644 --- a/input/witnessgen.go +++ b/input/witnessgen.go @@ -515,6 +515,205 @@ func (wt StandardWitnessType) WitnessGenerator(signer Signer, case NestedWitnessKeyHash: return signer.ComputeInputScript(tx, desc) + case TaprootLocalCommitSpend: + // Ensure that the sign desc has the proper sign method + // set, and a valid prev output fetcher. + desc.SignMethod = TaprootScriptSpendSignMethod + + // The control block bytes must be set at this point. + if desc.ControlBlock == nil { + return nil, fmt.Errorf("control block must be " + + "set for taproot spend") + } + + witness, err := TaprootCommitSpendSuccess( + signer, desc, tx, nil, + ) + if err != nil { + return nil, err + } + + return &Script{ + Witness: witness, + }, nil + + case TaprootRemoteCommitSpend: + // Ensure that the sign desc has the proper sign method + // set, and a valid prev output fetcher. + desc.SignMethod = TaprootScriptSpendSignMethod + + // The control block bytes must be set at this point. + if desc.ControlBlock == nil { + return nil, fmt.Errorf("control block must be " + + "set for taproot spend") + } + + witness, err := TaprootCommitRemoteSpend( + signer, desc, tx, nil, + ) + if err != nil { + return nil, err + } + + return &Script{ + Witness: witness, + }, nil + + case TaprootAnchorSweepSpend: + // Ensure that the sign desc has the proper sign method + // set, and a valid prev output fetcher. + desc.SignMethod = TaprootKeySpendSignMethod + + // The tap tweak must be set at this point. + if desc.TapTweak == nil { + return nil, fmt.Errorf("tap tweak must be " + + "set for keyspend") + } + + witness, err := TaprootAnchorSpend( + signer, desc, tx, + ) + if err != nil { + return nil, err + } + + return &Script{ + Witness: witness, + }, nil + + case TaprootHtlcOfferedTimeoutSecondLevel, + TaprootHtlcAcceptedSuccessSecondLevel: + // Ensure that the sign desc has the proper sign method + // set, and a valid prev output fetcher. + desc.SignMethod = TaprootScriptSpendSignMethod + + // The control block bytes must be set at this point. + if desc.ControlBlock == nil { + return nil, fmt.Errorf("control block must be " + + "set for taproot spend") + } + + witness, err := TaprootHtlcSpendSuccess( + signer, desc, tx, nil, nil, + ) + if err != nil { + return nil, err + } + + return &Script{ + Witness: witness, + }, nil + + case TaprootHtlcSecondLevelRevoke: + // Ensure that the sign desc has the proper sign method + // set, and a valid prev output fetcher. + desc.SignMethod = TaprootKeySpendSignMethod + + // The tap tweak must be set at this point. + if desc.TapTweak == nil { + return nil, fmt.Errorf("tap tweak must be " + + "set for keyspend") + } + + witness, err := TaprootHtlcSpendRevoke( + signer, desc, tx, + ) + if err != nil { + return nil, err + } + + return &Script{ + Witness: witness, + }, nil + + case TaprootHtlcOfferedRevoke: + // Ensure that the sign desc has the proper sign method + // set, and a valid prev output fetcher. + desc.SignMethod = TaprootKeySpendSignMethod + + // The tap tweak must be set at this point. + if desc.TapTweak == nil { + return nil, fmt.Errorf("tap tweak must be " + + "set for keyspend") + } + + witness, err := SenderHTLCScriptTaprootRevoke( + signer, desc, tx, + ) + if err != nil { + return nil, err + } + + return &Script{ + Witness: witness, + }, nil + + case TaprootHtlcAcceptedRevoke: + // Ensure that the sign desc has the proper sign method + // set, and a valid prev output fetcher. + desc.SignMethod = TaprootKeySpendSignMethod + + // The tap tweak must be set at this point. + if desc.TapTweak == nil { + return nil, fmt.Errorf("tap tweak must be " + + "set for keyspend") + } + + witness, err := ReceiverHTLCScriptTaprootRevoke( + signer, desc, tx, + ) + if err != nil { + return nil, err + } + + return &Script{ + Witness: witness, + }, nil + + case TaprootHtlcOfferedRemoteTimeout: + // Ensure that the sign desc has the proper sign method + // set, and a valid prev output fetcher. + desc.SignMethod = TaprootScriptSpendSignMethod + + // The control block bytes must be set at this point. + if desc.ControlBlock == nil { + return nil, fmt.Errorf("control block must be " + + "set for taproot spend") + } + + witness, err := ReceiverHTLCScriptTaprootTimeout( + signer, desc, tx, -1, nil, nil, + ) + if err != nil { + return nil, err + } + + return &Script{ + Witness: witness, + }, nil + + case TaprootCommitmentRevoke: + // Ensure that the sign desc has the proper sign method + // set, and a valid prev output fetcher. + desc.SignMethod = TaprootScriptSpendSignMethod + + // The control block bytes must be set at this point. + if desc.ControlBlock == nil { + return nil, fmt.Errorf("control block " + + "must be set for taproot spend") + } + + witness, err := TaprootCommitSpendRevoke( + signer, desc, tx, nil, + ) + if err != nil { + return nil, err + } + + return &Script{ + Witness: witness, + }, nil + default: return nil, fmt.Errorf("unknown witness type: %v", wt) }