Skip to content

Commit

Permalink
Channel: add support for claiming anchor outputs
Browse files Browse the repository at this point in the history
  • Loading branch information
aarani authored and knocte committed Mar 22, 2022
1 parent 0bf1351 commit d0de8a8
Show file tree
Hide file tree
Showing 3 changed files with 170 additions and 0 deletions.
115 changes: 115 additions & 0 deletions src/DotNetLightning.Core/Channel/CommitmentAnchorExtension.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
namespace DotNetLightning.Channel

open System

open NBitcoin
open NBitcoin.BuilderExtensions
open DotNetLightning.Utils
open DotNetLightning.Crypto

open ResultUtils
open ResultUtils.Portability

type CommitmentAnchorParameters =
{
FundingPubKey: FundingPubKey
}

static member TryExtractParameters (scriptPubKey: Script): Option<CommitmentAnchorParameters> =
let ops =
scriptPubKey.ToOps()
// we have to collect it into a list and convert back to a seq
// because the IEnumerable that NBitcoin gives us is internally
// mutable.
|> List.ofSeq
|> Seq.ofList
let checkOpCode(opcodeType: OpcodeType) =
seqParser<Op> {
let! op = SeqParser.next()
if op.Code = opcodeType then
return ()
else
return! SeqParser.abort()
}
let parseToCompletionResult =
SeqParser.parseToCompletion ops <| seqParser {
let! opFundingPubKey = SeqParser.next()
let! fundingPubKey = seqParser {
if isNull opFundingPubKey.PushData then
return! SeqParser.abort()
else
try
return FundingPubKey <| PubKey opFundingPubKey.PushData
with
| :? FormatException -> return! SeqParser.abort()
}
do! checkOpCode OpcodeType.OP_CHECKSIG
do! checkOpCode OpcodeType.OP_IFDUP
do! checkOpCode OpcodeType.OP_NOTIF
do! checkOpCode OpcodeType.OP_16
do! checkOpCode OpcodeType.OP_CHECKSEQUENCEVERIFY
do! checkOpCode OpcodeType.OP_ENDIF
return {
FundingPubKey = fundingPubKey
}
}
match parseToCompletionResult with
| Ok data -> Some data
| Error _consumeAllError -> None

type internal CommitmentAnchorExtension() =
inherit BuilderExtension()
override __.Match (coin: ICoin, _input: PSBTInput): bool =
(CommitmentAnchorParameters.TryExtractParameters (coin.GetScriptCode())).IsSome

override __.Sign(inputSigningContext: InputSigningContext, keyRepo: IKeyRepository, signer: ISigner) =
let scriptPubKey = inputSigningContext.Coin.GetScriptCode()

match keyRepo.FindKey scriptPubKey with
| :? PubKey as pubKey when not (isNull pubKey) ->
match signer.Sign pubKey with
| :? TransactionSignature as signature when not (isNull signature) ->
inputSigningContext.Input.PartialSigs.AddOrReplace(pubKey, signature)
| _ -> ()
| _ -> ()

override __.CanDeduceScriptPubKey(_scriptSig: Script): bool =
false

override __.DeduceScriptPubKey(_scriptSig: Script): Script =
raise <| NotSupportedException()

override __.CanEstimateScriptSigSize(coin: ICoin): bool =
(CommitmentAnchorParameters.TryExtractParameters (coin.GetScriptCode())).IsSome

override __.EstimateScriptSigSize(_coin: ICoin): int =
ChannelConstants.MaxSignatureSize

override __.IsCompatibleKey(pubKey: IPubKey, scriptPubKey: Script): bool =
match CommitmentAnchorParameters.TryExtractParameters scriptPubKey with
| None -> false
| Some parameters ->
parameters.FundingPubKey.RawPubKey() :> IPubKey = pubKey


override __.Finalize(inputSigningContext: InputSigningContext) =
let scriptPubKey = inputSigningContext.Coin.GetScriptCode()
let parameters =
match (CommitmentAnchorParameters.TryExtractParameters scriptPubKey) with
| Some parameters -> parameters
| None ->
failwith
"NBitcoin should not call this unless Match returns true"

let txIn = inputSigningContext.Input
if txIn.PartialSigs.Count <> 0 then
let keyAndSignatureOpt =
txIn.PartialSigs
|> Seq.tryExactlyOne
match keyAndSignatureOpt with
| Some keyAndSignature when keyAndSignature.Key = parameters.FundingPubKey.RawPubKey() ->
inputSigningContext.Input.FinalScriptSig <-
Script [
Op.GetPushOp (keyAndSignature.Value.ToBytes())
]
| _ -> ()
54 changes: 54 additions & 0 deletions src/DotNetLightning.Core/Channel/CommitmentsModule.fs
Original file line number Diff line number Diff line change
Expand Up @@ -664,4 +664,58 @@ module ForceCloseFundsRecovery =
)
)
}
//FIXME: should this be inside ForceCloseFundsRecovery module?
type LocalAnchorRecoveryError =
| InvalidCommitmentTx of ValidateCommitmentTxError
| AnchorNotFound
member this.Message: string =
match this with
| InvalidCommitmentTx validateCommitmentTxError ->
sprintf "invalid commitment tx: %s" validateCommitmentTxError.Message
| AnchorNotFound ->
"no anchor output found, balance might be below dust limit"

/// This function returns a TransactionBuilder with the necessary inputs
/// added to the transaction to claim the comitment transaction's anchor output.
/// It is the caller's responsibility to add outputs and fees to the TransactionBuilder
/// before building the transaction.
let tryClaimAnchorFromCommitmentTx (localChannelPrivKeys: ChannelPrivKeys)
(staticChannelConfig: StaticChannelConfig)
(transaction: Transaction)
: Result<TransactionBuilder, LocalAnchorRecoveryError> =
result {
// We do this just to make sure the input transaction is a valid commitment tx
let! _obscuredCommitmentNumber =
tryGetObscuredCommitmentNumber
staticChannelConfig.FundingScriptCoin.Outpoint
transaction
|> Result.mapError LocalAnchorRecoveryError.InvalidCommitmentTx

let fundingPubKey =
localChannelPrivKeys.ToChannelPubKeys().FundingPubKey

let anchorScriptPubKey =
Scripts.anchor fundingPubKey

let anchorIndexOpt =
let anchorWitScriptPubKey = anchorScriptPubKey.WitHash.ScriptPubKey
Seq.tryFindIndex
(fun (txOut: TxOut) -> txOut.ScriptPubKey = anchorWitScriptPubKey)
transaction.Outputs

let! anchorIndex =
match anchorIndexOpt with
| Some anchorIndex -> Ok anchorIndex
| None -> Error AnchorNotFound

let transactionBuilder = staticChannelConfig.Network.CreateTransactionBuilder()

transactionBuilder.Extensions.Add(CommitmentAnchorExtension())
return
transactionBuilder
.AddKeys(localChannelPrivKeys.FundingPrivKey.RawKey())
.AddCoin(
ScriptCoin(transaction, uint32 anchorIndex, anchorScriptPubKey)
)
}

1 change: 1 addition & 0 deletions src/DotNetLightning.Core/DotNetLightning.Core.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
<Compile Include="Channel\ChannelConstants.fs" />
<Compile Include="Channel\CommitmentToLocalExtension.fs" />
<Compile Include="Channel\CommitmentToDelayedRemoteExtension.fs" />
<Compile Include="Channel\CommitmentAnchorExtension.fs" />
<Compile Include="Channel\HTLCChannelType.fs" />
<Compile Include="Channel\ChannelOperations.fs" />
<Compile Include="Channel\Commitments.fs" />
Expand Down

0 comments on commit d0de8a8

Please sign in to comment.