Skip to content

Commit d0de8a8

Browse files
aaraniknocte
authored andcommitted
Channel: add support for claiming anchor outputs
1 parent 0bf1351 commit d0de8a8

File tree

3 files changed

+170
-0
lines changed

3 files changed

+170
-0
lines changed
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
namespace DotNetLightning.Channel
2+
3+
open System
4+
5+
open NBitcoin
6+
open NBitcoin.BuilderExtensions
7+
open DotNetLightning.Utils
8+
open DotNetLightning.Crypto
9+
10+
open ResultUtils
11+
open ResultUtils.Portability
12+
13+
type CommitmentAnchorParameters =
14+
{
15+
FundingPubKey: FundingPubKey
16+
}
17+
18+
static member TryExtractParameters (scriptPubKey: Script): Option<CommitmentAnchorParameters> =
19+
let ops =
20+
scriptPubKey.ToOps()
21+
// we have to collect it into a list and convert back to a seq
22+
// because the IEnumerable that NBitcoin gives us is internally
23+
// mutable.
24+
|> List.ofSeq
25+
|> Seq.ofList
26+
let checkOpCode(opcodeType: OpcodeType) =
27+
seqParser<Op> {
28+
let! op = SeqParser.next()
29+
if op.Code = opcodeType then
30+
return ()
31+
else
32+
return! SeqParser.abort()
33+
}
34+
let parseToCompletionResult =
35+
SeqParser.parseToCompletion ops <| seqParser {
36+
let! opFundingPubKey = SeqParser.next()
37+
let! fundingPubKey = seqParser {
38+
if isNull opFundingPubKey.PushData then
39+
return! SeqParser.abort()
40+
else
41+
try
42+
return FundingPubKey <| PubKey opFundingPubKey.PushData
43+
with
44+
| :? FormatException -> return! SeqParser.abort()
45+
}
46+
do! checkOpCode OpcodeType.OP_CHECKSIG
47+
do! checkOpCode OpcodeType.OP_IFDUP
48+
do! checkOpCode OpcodeType.OP_NOTIF
49+
do! checkOpCode OpcodeType.OP_16
50+
do! checkOpCode OpcodeType.OP_CHECKSEQUENCEVERIFY
51+
do! checkOpCode OpcodeType.OP_ENDIF
52+
return {
53+
FundingPubKey = fundingPubKey
54+
}
55+
}
56+
match parseToCompletionResult with
57+
| Ok data -> Some data
58+
| Error _consumeAllError -> None
59+
60+
type internal CommitmentAnchorExtension() =
61+
inherit BuilderExtension()
62+
override __.Match (coin: ICoin, _input: PSBTInput): bool =
63+
(CommitmentAnchorParameters.TryExtractParameters (coin.GetScriptCode())).IsSome
64+
65+
override __.Sign(inputSigningContext: InputSigningContext, keyRepo: IKeyRepository, signer: ISigner) =
66+
let scriptPubKey = inputSigningContext.Coin.GetScriptCode()
67+
68+
match keyRepo.FindKey scriptPubKey with
69+
| :? PubKey as pubKey when not (isNull pubKey) ->
70+
match signer.Sign pubKey with
71+
| :? TransactionSignature as signature when not (isNull signature) ->
72+
inputSigningContext.Input.PartialSigs.AddOrReplace(pubKey, signature)
73+
| _ -> ()
74+
| _ -> ()
75+
76+
override __.CanDeduceScriptPubKey(_scriptSig: Script): bool =
77+
false
78+
79+
override __.DeduceScriptPubKey(_scriptSig: Script): Script =
80+
raise <| NotSupportedException()
81+
82+
override __.CanEstimateScriptSigSize(coin: ICoin): bool =
83+
(CommitmentAnchorParameters.TryExtractParameters (coin.GetScriptCode())).IsSome
84+
85+
override __.EstimateScriptSigSize(_coin: ICoin): int =
86+
ChannelConstants.MaxSignatureSize
87+
88+
override __.IsCompatibleKey(pubKey: IPubKey, scriptPubKey: Script): bool =
89+
match CommitmentAnchorParameters.TryExtractParameters scriptPubKey with
90+
| None -> false
91+
| Some parameters ->
92+
parameters.FundingPubKey.RawPubKey() :> IPubKey = pubKey
93+
94+
95+
override __.Finalize(inputSigningContext: InputSigningContext) =
96+
let scriptPubKey = inputSigningContext.Coin.GetScriptCode()
97+
let parameters =
98+
match (CommitmentAnchorParameters.TryExtractParameters scriptPubKey) with
99+
| Some parameters -> parameters
100+
| None ->
101+
failwith
102+
"NBitcoin should not call this unless Match returns true"
103+
104+
let txIn = inputSigningContext.Input
105+
if txIn.PartialSigs.Count <> 0 then
106+
let keyAndSignatureOpt =
107+
txIn.PartialSigs
108+
|> Seq.tryExactlyOne
109+
match keyAndSignatureOpt with
110+
| Some keyAndSignature when keyAndSignature.Key = parameters.FundingPubKey.RawPubKey() ->
111+
inputSigningContext.Input.FinalScriptSig <-
112+
Script [
113+
Op.GetPushOp (keyAndSignature.Value.ToBytes())
114+
]
115+
| _ -> ()

src/DotNetLightning.Core/Channel/CommitmentsModule.fs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -664,4 +664,58 @@ module ForceCloseFundsRecovery =
664664
)
665665
)
666666
}
667+
//FIXME: should this be inside ForceCloseFundsRecovery module?
668+
type LocalAnchorRecoveryError =
669+
| InvalidCommitmentTx of ValidateCommitmentTxError
670+
| AnchorNotFound
671+
member this.Message: string =
672+
match this with
673+
| InvalidCommitmentTx validateCommitmentTxError ->
674+
sprintf "invalid commitment tx: %s" validateCommitmentTxError.Message
675+
| AnchorNotFound ->
676+
"no anchor output found, balance might be below dust limit"
677+
678+
/// This function returns a TransactionBuilder with the necessary inputs
679+
/// added to the transaction to claim the comitment transaction's anchor output.
680+
/// It is the caller's responsibility to add outputs and fees to the TransactionBuilder
681+
/// before building the transaction.
682+
let tryClaimAnchorFromCommitmentTx (localChannelPrivKeys: ChannelPrivKeys)
683+
(staticChannelConfig: StaticChannelConfig)
684+
(transaction: Transaction)
685+
: Result<TransactionBuilder, LocalAnchorRecoveryError> =
686+
result {
687+
// We do this just to make sure the input transaction is a valid commitment tx
688+
let! _obscuredCommitmentNumber =
689+
tryGetObscuredCommitmentNumber
690+
staticChannelConfig.FundingScriptCoin.Outpoint
691+
transaction
692+
|> Result.mapError LocalAnchorRecoveryError.InvalidCommitmentTx
693+
694+
let fundingPubKey =
695+
localChannelPrivKeys.ToChannelPubKeys().FundingPubKey
696+
697+
let anchorScriptPubKey =
698+
Scripts.anchor fundingPubKey
699+
700+
let anchorIndexOpt =
701+
let anchorWitScriptPubKey = anchorScriptPubKey.WitHash.ScriptPubKey
702+
Seq.tryFindIndex
703+
(fun (txOut: TxOut) -> txOut.ScriptPubKey = anchorWitScriptPubKey)
704+
transaction.Outputs
705+
706+
let! anchorIndex =
707+
match anchorIndexOpt with
708+
| Some anchorIndex -> Ok anchorIndex
709+
| None -> Error AnchorNotFound
710+
711+
let transactionBuilder = staticChannelConfig.Network.CreateTransactionBuilder()
712+
713+
transactionBuilder.Extensions.Add(CommitmentAnchorExtension())
714+
return
715+
transactionBuilder
716+
.AddKeys(localChannelPrivKeys.FundingPrivKey.RawKey())
717+
.AddCoin(
718+
ScriptCoin(transaction, uint32 anchorIndex, anchorScriptPubKey)
719+
)
720+
}
667721

src/DotNetLightning.Core/DotNetLightning.Core.fsproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
<Compile Include="Channel\ChannelConstants.fs" />
7676
<Compile Include="Channel\CommitmentToLocalExtension.fs" />
7777
<Compile Include="Channel\CommitmentToDelayedRemoteExtension.fs" />
78+
<Compile Include="Channel\CommitmentAnchorExtension.fs" />
7879
<Compile Include="Channel\HTLCChannelType.fs" />
7980
<Compile Include="Channel\ChannelOperations.fs" />
8081
<Compile Include="Channel\Commitments.fs" />

0 commit comments

Comments
 (0)