From 9d790f8b67bc7e2857f628aa532d5c42ae75492c Mon Sep 17 00:00:00 2001 From: "Andres G. Aragoneses" Date: Wed, 4 Nov 2020 20:51:19 +0800 Subject: [PATCH 01/15] DotNetLightning.Kiss fork - LICENSE: change from MIT to AGPL (.Kiss fork) - Change package name suffix from .Core to .Kiss (skipping native build) --- .github/workflows/publish_master.yml | 7 +---- LICENSE.txt | 28 ++++++++----------- README.md | 12 ++------ .../DotNetLightning.Core.fsproj | 2 +- 4 files changed, 17 insertions(+), 32 deletions(-) diff --git a/.github/workflows/publish_master.yml b/.github/workflows/publish_master.yml index 2edbac2fd..cbab3ef92 100644 --- a/.github/workflows/publish_master.yml +++ b/.github/workflows/publish_master.yml @@ -33,9 +33,4 @@ jobs: if: startsWith(matrix.os, 'ubuntu') run: | dotnet pack ./src/DotNetLightning.Core -p:Configuration=Release --version-suffix date`date +%Y%m%d-%H%M`-git-`echo $GITHUB_SHA | head -c 7` -p:Portability=True - dotnet nuget push ./src/DotNetLightning.Core/bin/Release/DotNetLightning.1*.nupkg -k ${{ secrets.NUGET_API_KEY }} -s https://api.nuget.org/v3/index.json - - - name: Upload nuget packages (native) - run: | - bash -c "dotnet pack ./src/DotNetLightning.Core -p:Configuration=Release --version-suffix date$(date +%Y%m%d-%H%M).git-$(git rev-parse --short=7 HEAD)-${{ matrix.RID }}" - bash -c "dotnet nuget push ./src/DotNetLightning.Core/bin/Release/DotNetLightning.Core.1*.nupkg -k ${{ secrets.NUGET_API_KEY }} -s https://api.nuget.org/v3/index.json" + dotnet nuget push ./src/DotNetLightning.Core/bin/Release/DotNetLightning.Kiss.1*.nupkg -k ${{ secrets.NUGET_API_KEY }} -s https://api.nuget.org/v3/index.json diff --git a/LICENSE.txt b/LICENSE.txt index fb52ef9ff..9f67b0d06 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,21 +1,17 @@ -MIT License +AGPL License Copyright (c) 2020 Joe Miyamoto +Copyright (c) 2020 Node Effect Ltd -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as +published by the Free Software Foundation, either version 3 of the +License, or (at your option) any later version. -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . diff --git a/README.md b/README.md index da0345857..3c78e918e 100644 --- a/README.md +++ b/README.md @@ -4,17 +4,11 @@ The main API is in `DotNetLightning.Core` project/assembly. ## Installation -The package is compiled and published with two variants: +The package is compiled and published in nuget: -* [`DotNetLightning`](https://www.nuget.org/packages/DotNetLightning/) - * This does not use native bindings for cryptographic operations. - * This is the one you want to use if you run your code everywhere, but possibly slower than below. -* [`DotNetLightning.Core`](https://www.nuget.org/packages/DotNetLightning.Core/) - * This uses a pre-compiled `libsodium` for cryptographic operations. - * It only supports `windows`, `mac` and `linux` environments. - * This is what you want if you need performance and the environments above are the only ones you are planning to support. +* [`DotNetLightning.Kiss`](https://www.nuget.org/packages/DotNetLightning.Kiss/) -Run `dotnet add package` with the one you want. +It does not use native bindings for cryptographic operations. Currently it is in alpha, so you probably want to install a latest version by specifying it with `--version`. The version is prefixed with git commit hash and date. Please take a look at the nuget page. diff --git a/src/DotNetLightning.Core/DotNetLightning.Core.fsproj b/src/DotNetLightning.Core/DotNetLightning.Core.fsproj index f4dd32bf3..cf09a5607 100644 --- a/src/DotNetLightning.Core/DotNetLightning.Core.fsproj +++ b/src/DotNetLightning.Core/DotNetLightning.Core.fsproj @@ -12,7 +12,7 @@ - DotNetLightning + DotNetLightning.Kiss From 25c8ca9e31d9f3a478af183952a4577b3eb7fa2d Mon Sep 17 00:00:00 2001 From: Andrew Cann Date: Thu, 12 Nov 2020 16:38:55 +0800 Subject: [PATCH 02/15] Mono-hop unidirectional payments --- src/DotNetLightning.Core/Channel/Channel.fs | 34 ++++++++++++- .../Channel/ChannelError.fs | 50 ++++++++++++++++++- .../Channel/ChannelOperations.fs | 6 +++ .../Channel/ChannelTypes.fs | 46 +++++++++++++++++ .../Channel/ChannelValidation.fs | 7 +++ .../Channel/Commitments.fs | 36 +++++++++++++ src/DotNetLightning.Core/Crypto/ShaChain.fs | 7 ++- .../Serialization/Msgs/Msgs.fs | 23 +++++++++ .../Transactions/CommitmentSpec.fs | 25 +++++++++- src/DotNetLightning.Core/Utils/LNMoney.fs | 2 + 10 files changed, 230 insertions(+), 6 deletions(-) diff --git a/src/DotNetLightning.Core/Channel/Channel.fs b/src/DotNetLightning.Core/Channel/Channel.fs index 081038619..abeb75fe2 100644 --- a/src/DotNetLightning.Core/Channel/Channel.fs +++ b/src/DotNetLightning.Core/Channel/Channel.fs @@ -446,6 +446,32 @@ module Channel = [] |> Ok // ---------- normal operation --------- + | ChannelState.Normal state, MonoHopUnidirectionalPayment op when state.LocalShutdown.IsSome || state.RemoteShutdown.IsSome -> + sprintf "Could not send mono-hop unidirectional payment %A since shutdown is already in progress." op + |> apiMisuse + | ChannelState.Normal state, MonoHopUnidirectionalPayment op -> + result { + let payment: MonoHopUnidirectionalPaymentMsg = { + ChannelId = state.Commitments.ChannelId + Amount = op.Amount + } + let commitments1 = state.Commitments.AddLocalProposal(payment) + + let remoteCommit1 = + match commitments1.RemoteNextCommitInfo with + | RemoteNextCommitInfo.Waiting info -> info.NextRemoteCommit + | RemoteNextCommitInfo.Revoked _info -> commitments1.RemoteCommit + let! reduced = remoteCommit1.Spec.Reduce(commitments1.RemoteChanges.ACKed, commitments1.LocalChanges.Proposed) |> expectTransactionError + do! Validation.checkOurMonoHopUnidirectionalPaymentIsAcceptableWithCurrentSpec reduced commitments1 payment + return [ WeAcceptedOperationMonoHopUnidirectionalPayment(payment, commitments1) ] + } + | ChannelState.Normal state, ApplyMonoHopUnidirectionalPayment msg -> + result { + let commitments1 = state.Commitments.AddRemoteProposal(msg) + let! reduced = commitments1.LocalCommit.Spec.Reduce (commitments1.LocalChanges.ACKed, commitments1.RemoteChanges.Proposed) |> expectTransactionError + do! Validation.checkTheirMonoHopUnidirectionalPaymentIsAcceptableWithCurrentSpec reduced commitments1 msg + return [ WeAcceptedMonoHopUnidirectionalPayment commitments1 ] + } | ChannelState.Normal state, AddHTLC op when state.LocalShutdown.IsSome || state.RemoteShutdown.IsSome -> sprintf "Could not add new HTLC %A since shutdown is already in progress." op |> apiMisuse @@ -551,8 +577,8 @@ module Channel = RemoteCommit = theirNextCommit RemoteNextCommitInfo = RemoteNextCommitInfo.Revoked msg.NextPerCommitmentPoint RemotePerCommitmentSecrets = remotePerCommitmentSecrets } - let _result = Ok [ WeAcceptedRevokeAndACK commitments1 ] - failwith "needs update" + Console.WriteLine("WARNING: revocation is not implemented yet") + Ok [ WeAcceptedRevokeAndACK(commitments1) ] | ChannelState.Normal state, ChannelCommand.Close cmd -> let localSPK = cmd.ScriptPubKey |> Option.defaultValue (state.Commitments.LocalParams.DefaultFinalScriptPubKey) @@ -832,8 +858,12 @@ module Channel = { c with State = ChannelState.Normal data } // ----- normal operation -------- + | WeAcceptedOperationMonoHopUnidirectionalPayment(_, newCommitments), ChannelState.Normal normalData -> + { c with State = ChannelState.Normal({ normalData with Commitments = newCommitments }) } | WeAcceptedOperationAddHTLC(_, newCommitments), ChannelState.Normal d -> { c with State = ChannelState.Normal({ d with Commitments = newCommitments }) } + | WeAcceptedMonoHopUnidirectionalPayment(newCommitments), ChannelState.Normal normalData -> + { c with State = ChannelState.Normal({ normalData with Commitments = newCommitments }) } | WeAcceptedUpdateAddHTLC(newCommitments), ChannelState.Normal d -> { c with State = ChannelState.Normal({ d with Commitments = newCommitments }) } diff --git a/src/DotNetLightning.Core/Channel/ChannelError.fs b/src/DotNetLightning.Core/Channel/ChannelError.fs index aef958b2b..1c9252188 100644 --- a/src/DotNetLightning.Core/Channel/ChannelError.fs +++ b/src/DotNetLightning.Core/Channel/ChannelError.fs @@ -38,6 +38,7 @@ type ChannelError = // --- case they sent unacceptable msg --- | InvalidOpenChannel of InvalidOpenChannelError | InvalidAcceptChannel of InvalidAcceptChannelError + | InvalidMonoHopUnidirectionalPayment of InvalidMonoHopUnidirectionalPaymentError | InvalidUpdateAddHTLC of InvalidUpdateAddHTLCError | InvalidRevokeAndACK of InvalidRevokeAndACKError | InvalidUpdateFee of InvalidUpdateFeeError @@ -71,6 +72,7 @@ type ChannelError = | TheyCannotAffordFee (_, _, _) -> Close | InvalidOpenChannel _ -> DistrustPeer | InvalidAcceptChannel _ -> DistrustPeer + | InvalidMonoHopUnidirectionalPayment _ -> Close | InvalidUpdateAddHTLC _ -> Close | InvalidRevokeAndACK _ -> Close | InvalidUpdateFee _ -> Close @@ -141,6 +143,8 @@ type ChannelError = sprintf "Cannot close channel: %s" msg | InvalidOperationAddHTLC invalidOperationAddHTLCError -> sprintf "Invalid operation (add htlc): %s" invalidOperationAddHTLCError.Message + | InvalidMonoHopUnidirectionalPayment invalidMonoHopUnidirectionalPaymentError -> + sprintf "Invalid monohop-unidirectional payment: %s" invalidMonoHopUnidirectionalPaymentError.Message and ChannelConsumerAction = /// The error which should never happen. @@ -181,6 +185,18 @@ and InvalidAcceptChannelError = { member this.Message = String.concat "; " this.Errors +and InvalidMonoHopUnidirectionalPaymentError = { + NetworkMsg: MonoHopUnidirectionalPaymentMsg + Errors: string list +} + with + static member Create msg e = { + NetworkMsg = msg + Errors = e + } + member this.Message = + String.concat "; " this.Errors + and InvalidUpdateAddHTLCError = { NetworkMsg: UpdateAddHTLCMsg Errors: string list @@ -504,6 +520,22 @@ module internal AcceptChannelMsgValidation = (check1 |> Validation.ofResult) *^> check2 *^> check3 *^> check4 *^> check5 *^> check6 *^> check7 +module UpdateMonoHopUnidirectionalPaymentWithContext = + let internal checkWeHaveSufficientFunds (state: Commitments) (currentSpec) = + let fees = + if state.LocalParams.IsFunder then + Transactions.commitTxFee state.RemoteParams.DustLimitSatoshis currentSpec + else + Money.Zero + let missing = currentSpec.ToRemote.ToMoney() - state.RemoteParams.ChannelReserveSatoshis - fees + if missing < Money.Zero then + sprintf "We don't have sufficient funds to send mono-hop unidirectional payment. current to_remote amount is: %A. Remote Channel Reserve is: %A. and fee is %A" + (currentSpec.ToRemote.ToMoney()) + state.RemoteParams.ChannelReserveSatoshis + fees + |> Error + else + Ok() module UpdateAddHTLCValidation = let internal checkExpiryIsNotPast (current: BlockHeight) (expiry) = @@ -518,7 +550,23 @@ module UpdateAddHTLCValidation = let internal checkAmountIsLargerThanMinimum (htlcMinimum: LNMoney) (amount) = check (amount) (<) (htlcMinimum) "htlc value (%A) is too small. must be greater or equal to %A" - +module internal MonoHopUnidirectionalPaymentValidationWithContext = + let checkWeHaveSufficientFunds (state: Commitments) (currentSpec) = + let fees = + if state.LocalParams.IsFunder then + Transactions.commitTxFee state.RemoteParams.DustLimitSatoshis currentSpec + else + Money.Zero + let missing = currentSpec.ToRemote.ToMoney() - state.RemoteParams.ChannelReserveSatoshis - fees + if missing < Money.Zero then + sprintf "We don't have sufficient funds to send mono-hop unidirectional payment. current to_remote amount is: %A. Remote Channel Reserve is: %A. and fee is %A" + (currentSpec.ToRemote.ToMoney()) + state.RemoteParams.ChannelReserveSatoshis + fees + |> Error + else + Ok() + module internal UpdateAddHTLCValidationWithContext = let checkLessThanHTLCValueInFlightLimit (currentSpec: CommitmentSpec) (limit) (add: UpdateAddHTLCMsg) = let htlcValueInFlight = currentSpec.HTLCs |> Map.toSeq |> Seq.sumBy (fun (_, v) -> v.Add.Amount) diff --git a/src/DotNetLightning.Core/Channel/ChannelOperations.fs b/src/DotNetLightning.Core/Channel/ChannelOperations.fs index 349a43b8e..4dbcaa86b 100644 --- a/src/DotNetLightning.Core/Channel/ChannelOperations.fs +++ b/src/DotNetLightning.Core/Channel/ChannelOperations.fs @@ -15,6 +15,10 @@ open NBitcoin open ResultUtils open ResultUtils.Portability +type OperationMonoHopUnidirectionalPayment = { + Amount: LNMoney +} + type OperationAddHTLC = { Amount: LNMoney PaymentHash: PaymentHash @@ -203,6 +207,8 @@ type ChannelCommand = | CreateChannelReestablish // normal + | MonoHopUnidirectionalPayment of OperationMonoHopUnidirectionalPayment + | ApplyMonoHopUnidirectionalPayment of msg: MonoHopUnidirectionalPaymentMsg | AddHTLC of OperationAddHTLC | ApplyUpdateAddHTLC of msg: UpdateAddHTLCMsg * currentHeight: BlockHeight | FulfillHTLC of OperationFulfillHTLC diff --git a/src/DotNetLightning.Core/Channel/ChannelTypes.fs b/src/DotNetLightning.Core/Channel/ChannelTypes.fs index 702b0720c..19ec6caf5 100644 --- a/src/DotNetLightning.Core/Channel/ChannelTypes.fs +++ b/src/DotNetLightning.Core/Channel/ChannelTypes.fs @@ -276,6 +276,9 @@ type ChannelEvent = | BothFundingLocked of nextState: Data.NormalData // -------- normal operation ------ + | WeAcceptedOperationMonoHopUnidirectionalPayment of msg: MonoHopUnidirectionalPaymentMsg * newCommitments: Commitments + | WeAcceptedMonoHopUnidirectionalPayment of newCommitments: Commitments + | WeAcceptedOperationAddHTLC of msg: UpdateAddHTLCMsg * newCommitments: Commitments | WeAcceptedUpdateAddHTLC of newCommitments: Commitments @@ -368,6 +371,27 @@ type ChannelState = (fun v cc -> match cc with | Normal _ -> Normal v | _ -> cc ) + member this.ChannelId: Option = + match this with + | WaitForInitInternal + | WaitForOpenChannel _ + | WaitForAcceptChannel _ + | WaitForFundingTx _ + | WaitForFundingCreated _ -> None + | WaitForFundingSigned data -> Some data.ChannelId + | WaitForFundingConfirmed data -> Some data.ChannelId + | WaitForFundingLocked data -> Some data.ChannelId + | Normal data -> Some data.ChannelId + | Shutdown data -> Some data.ChannelId + | Negotiating data -> Some data.ChannelId + | Closing data -> Some data.ChannelId + | Closed _ + | Offline _ + | Syncing _ + | ErrFundingLost _ + | ErrFundingTimeOut _ + | ErrInformationLeak _ -> None + member this.Phase = match this with | WaitForInitInternal @@ -388,3 +412,25 @@ type ChannelState = | ErrFundingLost _ | ErrFundingTimeOut _ | ErrInformationLeak _ -> Abnormal + + member this.Commitments: Option = + match this with + | WaitForInitInternal + | WaitForOpenChannel _ + | WaitForAcceptChannel _ + | WaitForFundingTx _ + | WaitForFundingCreated _ + | WaitForFundingSigned _ -> None + | WaitForFundingConfirmed data -> Some (data :> IHasCommitments).Commitments + | WaitForFundingLocked data -> Some (data :> IHasCommitments).Commitments + | Normal data -> Some (data :> IHasCommitments).Commitments + | Shutdown data -> Some (data :> IHasCommitments).Commitments + | Negotiating data -> Some (data :> IHasCommitments).Commitments + | Closing data -> Some (data :> IHasCommitments).Commitments + | Closed _ + | Offline _ + | Syncing _ + | ErrFundingLost _ + | ErrFundingTimeOut _ + | ErrInformationLeak _ -> None + diff --git a/src/DotNetLightning.Core/Channel/ChannelValidation.fs b/src/DotNetLightning.Core/Channel/ChannelValidation.fs index 7a22cc13c..71b0fb3d3 100644 --- a/src/DotNetLightning.Core/Channel/ChannelValidation.fs +++ b/src/DotNetLightning.Core/Channel/ChannelValidation.fs @@ -197,6 +197,13 @@ module internal Validation = *> AcceptChannelMsgValidation.checkConfigPermits conf.PeerChannelConfigLimits msg |> Result.mapError(InvalidAcceptChannelError.Create msg >> InvalidAcceptChannel) + let checkOurMonoHopUnidirectionalPaymentIsAcceptableWithCurrentSpec (currentSpec) (state: Commitments) (payment: MonoHopUnidirectionalPaymentMsg) = + Validation.ofResult(MonoHopUnidirectionalPaymentValidationWithContext.checkWeHaveSufficientFunds state currentSpec) + |> Result.mapError(fun errs -> InvalidMonoHopUnidirectionalPayment { NetworkMsg = payment; Errors = errs }) + + let checkTheirMonoHopUnidirectionalPaymentIsAcceptableWithCurrentSpec (currentSpec) (state: Commitments) (payment: MonoHopUnidirectionalPaymentMsg) = + Validation.ofResult(MonoHopUnidirectionalPaymentValidationWithContext.checkWeHaveSufficientFunds state currentSpec) + |> Result.mapError(fun errs -> InvalidMonoHopUnidirectionalPayment { NetworkMsg = payment; Errors = errs }) let checkOperationAddHTLC (state: NormalData) (op: OperationAddHTLC) = Validation.ofResult(UpdateAddHTLCValidation.checkExpiryIsNotPast op.CurrentHeight op.Expiry) diff --git a/src/DotNetLightning.Core/Channel/Commitments.fs b/src/DotNetLightning.Core/Channel/Commitments.fs index 674e09951..bffaf84ff 100644 --- a/src/DotNetLightning.Core/Channel/Commitments.fs +++ b/src/DotNetLightning.Core/Channel/Commitments.fs @@ -236,3 +236,39 @@ type Commitments = { |> Money.Satoshis) - commitFee {Amounts.ToLocal = toLocalAmount; ToRemote = toRemoteAmount} + + member this.SpendableBalance(): LNMoney = + let remoteCommit = + match this.RemoteNextCommitInfo with + | RemoteNextCommitInfo.Waiting info -> info.NextRemoteCommit + | RemoteNextCommitInfo.Revoked _info -> this.RemoteCommit + let reducedRes = + remoteCommit.Spec.Reduce( + this.RemoteChanges.ACKed, + this.LocalChanges.Proposed + ) + let reduced = + match reducedRes with + | Error err -> + failwithf + "reducing commit failed even though we have not proposed any changes\ + error: %A" + err + | Ok reduced -> reduced + let fees = + if this.LocalParams.IsFunder then + Transactions.commitTxFee this.RemoteParams.DustLimitSatoshis reduced + |> LNMoney.FromMoney + else + LNMoney.Zero + let channelReserve = + this.RemoteParams.ChannelReserveSatoshis + |> LNMoney.FromMoney + let totalBalance = reduced.ToRemote + let untrimmedSpendableBalance = totalBalance - channelReserve - fees + let dustLimit = + this.RemoteParams.DustLimitSatoshis + |> LNMoney.FromMoney + let untrimmedMax = LNMoney.Min(untrimmedSpendableBalance, dustLimit) + let spendableBalance = LNMoney.Max(untrimmedMax, untrimmedSpendableBalance) + spendableBalance diff --git a/src/DotNetLightning.Core/Crypto/ShaChain.fs b/src/DotNetLightning.Core/Crypto/ShaChain.fs index 3a027ddb0..457f50e49 100644 --- a/src/DotNetLightning.Core/Crypto/ShaChain.fs +++ b/src/DotNetLightning.Core/Crypto/ShaChain.fs @@ -1,5 +1,7 @@ namespace DotNetLightning.Crypto +open System + type Node = { Value: byte[] Height: int32 @@ -16,8 +18,9 @@ module ShaChain = let flip (_input: byte[]) (_index: uint64): byte[] = failwith "Not implemented: ShaChain::flip" - let addHash (_receiver: ShaChain) (_hash: byte[]) (_index: uint64) = - failwith "Not implemented: ShaChain::addHash" + let addHash (receiver: ShaChain) (_hash: byte[]) (_index: uint64) = + Console.WriteLine("WARNING: Not implemented: ShaChain::addHash") + receiver let getHash (_receiver: ShaChain)(_index: uint64) = failwith "Not implemented: ShaChain::getHash" diff --git a/src/DotNetLightning.Core/Serialization/Msgs/Msgs.fs b/src/DotNetLightning.Core/Serialization/Msgs/Msgs.fs index 6ef781222..1b1cdbd6f 100644 --- a/src/DotNetLightning.Core/Serialization/Msgs/Msgs.fs +++ b/src/DotNetLightning.Core/Serialization/Msgs/Msgs.fs @@ -104,6 +104,8 @@ module internal TypeFlag = let ReplyChannelRange = 264us [] let GossipTimestampFilter = 265us + [] + let MonoHopUnidirectionalPayment = 42198us type ILightningMsg = interface end type ISetupMsg = inherit ILightningMsg @@ -195,6 +197,8 @@ module ILightningSerializable = deserialize(ls) :> ILightningMsg | TypeFlag.GossipTimestampFilter -> deserialize(ls) :> ILightningMsg + | TypeFlag.MonoHopUnidirectionalPayment -> + deserialize(ls) :> ILightningMsg | x -> raise <| FormatException(sprintf "Unknown message type %d" x) let serializeWithFlags (ls: LightningWriterStream) (data: ILightningMsg) = @@ -283,6 +287,9 @@ module ILightningSerializable = | :? GossipTimestampFilterMsg as d -> ls.Write(TypeFlag.GossipTimestampFilter, false) (d :> ILightningSerializable).Serialize(ls) + | :? MonoHopUnidirectionalPaymentMsg as d -> + ls.Write(TypeFlag.MonoHopUnidirectionalPayment, false) + (d :> ILightningSerializable).Serialize(ls) | x -> failwithf "%A is not known lightning message. This should never happen" x module LightningMsg = @@ -1572,3 +1579,19 @@ type GossipTimestampFilterMsg = { ls.Write(this.FirstTimestamp, false) ls.Write(this.TimestampRange, false) +[] +type MonoHopUnidirectionalPaymentMsg = { + mutable ChannelId: ChannelId + mutable Amount: LNMoney +} +with + interface IHTLCMsg + interface IUpdateMsg + interface ILightningSerializable with + member this.Deserialize(ls) = + this.ChannelId <- ls.ReadUInt256(true) |> ChannelId + this.Amount <- ls.ReadUInt64(false) |> LNMoney.MilliSatoshis + member this.Serialize(ls) = + ls.Write(this.ChannelId.Value.ToBytes()) + ls.Write(this.Amount.MilliSatoshi, false) + diff --git a/src/DotNetLightning.Core/Transactions/CommitmentSpec.fs b/src/DotNetLightning.Core/Transactions/CommitmentSpec.fs index 508d5ff8f..0052b892d 100644 --- a/src/DotNetLightning.Core/Transactions/CommitmentSpec.fs +++ b/src/DotNetLightning.Core/Transactions/CommitmentSpec.fs @@ -53,6 +53,11 @@ type CommitmentSpec = { member this.TotalFunds = this.ToLocal + this.ToRemote + (this.HTLCs |> Seq.sumBy(fun h -> h.Value.Add.Amount)) + member internal this.MonoHopUnidirectionalPayment(direction: Direction, update: MonoHopUnidirectionalPaymentMsg) = + match direction with + | Out -> { this with ToLocal = this.ToLocal - update.Amount; ToRemote = this.ToRemote + update.Amount } + | In -> { this with ToLocal = this.ToLocal + update.Amount; ToRemote = this.ToRemote - update.Amount } + member internal this.AddHTLC(direction: Direction, update: UpdateAddHTLCMsg) = let htlc = { DirectedHTLC.Direction = direction; Add = update } match direction with @@ -82,6 +87,24 @@ type CommitmentSpec = { UnknownHTLC htlcId |> Error member internal this.Reduce(localChanges: #IUpdateMsg list, remoteChanges: #IUpdateMsg list) = + let specMonoHopUnidirectionalPaymentLocal = + localChanges + |> List.fold(fun (acc: CommitmentSpec) updateMsg -> + match box updateMsg with + | :? MonoHopUnidirectionalPaymentMsg as u -> acc.MonoHopUnidirectionalPayment(Out, u) + | _ -> acc + ) + this + + let specMonoHopUnidirectionalPaymentRemote = + remoteChanges + |> List.fold(fun (acc: CommitmentSpec) updateMsg -> + match box updateMsg with + | :? MonoHopUnidirectionalPaymentMsg as u -> acc.MonoHopUnidirectionalPayment(In, u) + | _ -> acc + ) + specMonoHopUnidirectionalPaymentLocal + let spec1 = localChanges |> List.fold(fun (acc: CommitmentSpec) updateMsg -> @@ -89,7 +112,7 @@ type CommitmentSpec = { | :? UpdateAddHTLCMsg as u -> acc.AddHTLC(Out, u) | _ -> acc ) - this + specMonoHopUnidirectionalPaymentRemote let spec2 = remoteChanges diff --git a/src/DotNetLightning.Core/Utils/LNMoney.fs b/src/DotNetLightning.Core/Utils/LNMoney.fs index 9ecdfe80b..dfcef90ae 100644 --- a/src/DotNetLightning.Core/Utils/LNMoney.fs +++ b/src/DotNetLightning.Core/Utils/LNMoney.fs @@ -39,6 +39,8 @@ type LNMoney = | LNMoney of int64 with let satoshi = Checked.op_Multiply (amount) (decimal lnUnit) LNMoney(Checked.int64 satoshi) + static member FromMoney (money: Money) = + LNMoney.Satoshis money.Satoshi static member Coins(coins: decimal) = LNMoney.FromUnit(coins * (decimal LNMoneyUnit.BTC), LNMoneyUnit.MilliSatoshi) From c9731bf7429dcb7da78a72eee2de2edb1d744926 Mon Sep 17 00:00:00 2001 From: Afshin Arani Date: Wed, 9 Jun 2021 17:19:46 +0800 Subject: [PATCH 03/15] Core: return peer's closing_signed msg if accepted According to lightning spec, if the receiver agrees with the fee, it should reply with a closing_signed with the same fee_satoshis value so to transport this message we need to pass it inside MutualClosePerformed event, to be transported to the other peer. Source: https://github.com/lightningnetwork/lightning-rfc/blob/master/02-peer-protocol.md#requirements-6 Upstream PR: https://github.com/joemphilips/DotNetLightning/pull/168 --- src/DotNetLightning.Core/Channel/Channel.fs | 12 ++++++------ src/DotNetLightning.Core/Channel/ChannelTypes.fs | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/DotNetLightning.Core/Channel/Channel.fs b/src/DotNetLightning.Core/Channel/Channel.fs index abeb75fe2..922967039 100644 --- a/src/DotNetLightning.Core/Channel/Channel.fs +++ b/src/DotNetLightning.Core/Channel/Channel.fs @@ -104,10 +104,10 @@ module Channel = ((localClosingFee.Satoshi + remoteClosingFee.Satoshi) / 4L) * 2L |> Money.Satoshis - let handleMutualClose (closingTx: FinalizedTx, d: NegotiatingData) = + let handleMutualClose (closingTx: FinalizedTx, d: NegotiatingData, nextMessage: Option) = let nextData = ClosingData.Create (d.ChannelId, d.Commitments, None, DateTime.Now, (d.ClosingTxProposed |> List.collect id |> List.map (fun tx -> tx.UnsignedTx)), closingTx) - [ MutualClosePerformed (closingTx, nextData) ] + [ MutualClosePerformed (closingTx, nextData, nextMessage) ] |> Ok let claimCurrentLocalCommitTxOutputs (channelPrivKeys: ChannelPrivKeys, @@ -739,7 +739,7 @@ module Channel = let hasTooManyNegotiationDone = (state.ClosingTxProposed |> List.collect (id) |> List.length) >= cs.Config.PeerChannelConfigLimits.MaxClosingNegotiationIterations if (areWeInDeal || hasTooManyNegotiationDone) then - return! Closing.handleMutualClose (finalizedTx, { state with MaybeBestUnpublishedTx = Some(finalizedTx) }) + return! Closing.handleMutualClose (finalizedTx, { state with MaybeBestUnpublishedTx = Some(finalizedTx) }, None) else let lastLocalClosingFee = state.ClosingTxProposed |> List.tryHead |> Option.bind (List.tryHead) |> Option.map (fun txp -> txp.LocalClosingSigned.FeeSatoshis) let! localF = @@ -755,7 +755,7 @@ module Channel = let nextClosingFee = Closing.nextClosingFee (localF, msg.FeeSatoshis) if (Some nextClosingFee = lastLocalClosingFee) then - return! Closing.handleMutualClose (finalizedTx, { state with MaybeBestUnpublishedTx = Some(finalizedTx) }) + return! Closing.handleMutualClose (finalizedTx, { state with MaybeBestUnpublishedTx = Some(finalizedTx) }, None) else if (nextClosingFee = msg.FeeSatoshis) then // we have reached on agreement! let closingTxProposed1 = @@ -764,7 +764,7 @@ module Channel = newProposed :: state.ClosingTxProposed let negoData = { state with ClosingTxProposed = closingTxProposed1 MaybeBestUnpublishedTx = Some(finalizedTx) } - return! Closing.handleMutualClose (finalizedTx, negoData) + return! Closing.handleMutualClose (finalizedTx, negoData, Some closingSignedMsg) else let! closingTx, closingSignedMsg = Closing.makeClosingTx ( @@ -904,7 +904,7 @@ module Channel = { c with State = Negotiating nextState } | AcceptedShutdownWhenWeHavePendingHTLCs(nextState), ChannelState.Normal _d -> { c with State = Shutdown nextState } - | MutualClosePerformed (_txToPublish, nextState), ChannelState.Negotiating _d -> + | MutualClosePerformed (_txToPublish, nextState, _), ChannelState.Negotiating _d -> { c with State = Closing nextState } | WeProposedNewClosingSigned(_msg, nextState), ChannelState.Negotiating _d -> { c with State = Negotiating(nextState) } diff --git a/src/DotNetLightning.Core/Channel/ChannelTypes.fs b/src/DotNetLightning.Core/Channel/ChannelTypes.fs index 19ec6caf5..8fe5a5d22 100644 --- a/src/DotNetLightning.Core/Channel/ChannelTypes.fs +++ b/src/DotNetLightning.Core/Channel/ChannelTypes.fs @@ -306,7 +306,7 @@ type ChannelEvent = | AcceptedShutdownWhenWeHavePendingHTLCs of nextState: ShutdownData // ------ closing ------ - | MutualClosePerformed of txToPublish: FinalizedTx * nextState : ClosingData + | MutualClosePerformed of txToPublish: FinalizedTx * nextState : ClosingData * nextMsgToSend: Option | WeProposedNewClosingSigned of msgToSend: ClosingSignedMsg * nextState: NegotiatingData // -------- else --------- | Closed From d32c30c378d6aaae8f945def9d066a6d70464b9a Mon Sep 17 00:00:00 2001 From: Afshin Arani Date: Fri, 11 Jun 2021 15:12:34 +0430 Subject: [PATCH 04/15] Core: remove hmac from TLVPayload Hmac should be written during the packet serialization and not during the payload serialization --- src/DotNetLightning.Core/Serialization/OnionPayload.fs | 9 ++++----- tests/DotNetLightning.Core.Tests/Generators/Msgs.fs | 3 +-- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/DotNetLightning.Core/Serialization/OnionPayload.fs b/src/DotNetLightning.Core/Serialization/OnionPayload.fs index 78cd6d214..2b8f65092 100644 --- a/src/DotNetLightning.Core/Serialization/OnionPayload.fs +++ b/src/DotNetLightning.Core/Serialization/OnionPayload.fs @@ -31,7 +31,7 @@ type OnionRealm0HopData = { type OnionPayload = | Legacy of OnionRealm0HopData - | TLVPayload of tlvs: HopPayloadTLV array * hmac: uint256 + | TLVPayload of tlvs: HopPayloadTLV array with static member FromBytes(bytes: byte[]) = match bytes.[0] with @@ -46,14 +46,13 @@ type OnionPayload = let! tlvs = GenericTLV.TryCreateManyFromBytes(bytes.[0..(l - 1)]) |> Result.map (Array.map(HopPayloadTLV.FromGenericTLV)) - let hmac = uint256(bytes.[l..(l + 31)], false) - return (tlvs, hmac) |> TLVPayload + return TLVPayload tlvs } member this.ToBytes() = match this with | Legacy o -> o.ToBytes() - | TLVPayload (tlvs, hmac) -> + | TLVPayload tlvs-> let payloads = tlvs |> Seq.map(fun tlv -> tlv.ToGenericTLV().ToBytes()) |> Seq.concat |> Seq.toArray let length = payloads.LongLength.ToVarInt() - Array.concat [length; payloads; hmac.ToBytes(false)] + Array.concat [length; payloads] diff --git a/tests/DotNetLightning.Core.Tests/Generators/Msgs.fs b/tests/DotNetLightning.Core.Tests/Generators/Msgs.fs index 6f9b34b9e..362a20f9e 100644 --- a/tests/DotNetLightning.Core.Tests/Generators/Msgs.fs +++ b/tests/DotNetLightning.Core.Tests/Generators/Msgs.fs @@ -597,8 +597,7 @@ let private onionPayloadTlvGen = let tlvOnionPayloadGen = gen { let! tlv = Gen.nonEmptyListOf onionPayloadTlvGen |> Gen.map(List.toArray) - let! hmac = uint256Gen - return (tlv, hmac) |> OnionPayload.TLVPayload + return OnionPayload.TLVPayload tlv } let private legacyOnionPayloadGen = gen { From b2f17bcf8370ad940d14519681569bd5a790ec5a Mon Sep 17 00:00:00 2001 From: Afshin Arani Date: Fri, 11 Jun 2021 15:32:51 +0430 Subject: [PATCH 05/15] Support variable payload length on fillerGeneration Original implementation from https://github.com/ElementsProject/lightning/blob/ade10e7fc4dacbb9d635b05152c7dc38c0896ce7/contrib/pyln-proto/pyln/proto/onion.py#L497 --- src/DotNetLightning.Core/Crypto/Sphinx.fs | 46 +++++++++++++------ .../DotNetLightning.Core.Tests/SphinxTests.fs | 2 +- 2 files changed, 34 insertions(+), 14 deletions(-) diff --git a/src/DotNetLightning.Core/Crypto/Sphinx.fs b/src/DotNetLightning.Core/Crypto/Sphinx.fs index 9940a6d21..f1b04c0a6 100644 --- a/src/DotNetLightning.Core/Crypto/Sphinx.fs +++ b/src/DotNetLightning.Core/Crypto/Sphinx.fs @@ -21,6 +21,9 @@ module Sphinx = [] let PayloadLength = 33 + [] + let HopDataSize = 1300 + [] let MacLength = 32 @@ -88,18 +91,35 @@ module Sphinx = computeEphemeralPublicKeysAndSharedSecretsCore (sessionKey) (pubKeys |> List.tail) ([ephemeralPK0]) ([blindingFactor0]) ([secret0]) - let rec internal generateFiller (keyType: string) (sharedSecrets: Key list) (hopSize: int) (maxNumberOfHops: int option) = - let maxHopN = defaultArg maxNumberOfHops MaxHops - sharedSecrets - |> List.fold (fun (padding: byte[]) (secret: Key) -> - let key = generateKey(keyType, secret.ToBytes()) - let padding1 = Array.append padding (zeros hopSize) - let stream = - let s = generateStream(key, hopSize * (maxHopN + 1)) - s.[s.Length - padding1.Length .. s.Length - 1] // take padding1 from tale - assert (stream.Length = padding1.Length) - xor(padding1, stream) - ) [||] + let rec internal generateFiller (keyType: string) (payloads: byte[] list) (sharedSecrets: Key list) = + let filler_size = + payloads.[1..] |> + List.sumBy (fun payload -> payload.Length + MacLength) + + let rec fillInner (filler: array) + (i: int) + : array = + if i = payloads.Length - 1 then + filler + else + let filler_offset = + payloads.[..i-1] |> + List.sumBy (fun payload -> payload.Length + MacLength) + + + let filler_start = HopDataSize - filler_offset + let filler_end = HopDataSize + payloads.[i].Length + MacLength + let filler_len = filler_end - filler_start + + let key = generateKey(keyType, sharedSecrets.[i].ToBytes()) + let stream = + let s = generateStream(key, filler_end) + s.[filler_start..filler_end-1] + + let newFiller = [xor (Array.take filler_len filler, stream); Array.skip filler_len filler] |> Array.concat + fillInner newFiller (i + 1) + + fillInner (Array.zeroCreate filler_size) 0 type ParsedPacket = { Payload: byte[] @@ -183,7 +203,7 @@ module Sphinx = with static member Create (sessionKey: Key, pubKeys: PubKey list, payloads: byte[] list, ad: byte[]) = let (ephemeralPubKeys, sharedSecrets) = computeEphemeralPublicKeysAndSharedSecrets (sessionKey) (pubKeys) - let filler = generateFiller "rho" sharedSecrets.[0..sharedSecrets.Length - 2] (PayloadLength + MacLength) (Some MaxHops) + let filler = generateFiller "rho" payloads sharedSecrets let lastPacket = makeNextPacket(payloads |> List.last, ad, diff --git a/tests/DotNetLightning.Core.Tests/SphinxTests.fs b/tests/DotNetLightning.Core.Tests/SphinxTests.fs index 663a319a2..57392cccb 100644 --- a/tests/DotNetLightning.Core.Tests/SphinxTests.fs +++ b/tests/DotNetLightning.Core.Tests/SphinxTests.fs @@ -67,7 +67,7 @@ let bolt4Tests1 = testCase "generate filler" <| fun _ -> let (_, sharedSecrets) = computeEphemeralPublicKeysAndSharedSecrets sessionKey pubKeys - let filler = generateFiller "rho" sharedSecrets.[0..sharedSecrets.Length - 2] (PayloadLength + MacLength) (Some(20)) + let filler = generateFiller "rho" payloads sharedSecrets let expectedFiller = "c6b008cf6414ed6e4c42c291eb505e9f22f5fe7d0ecdd15a833f4d016ac974d33adc6ea3293e20859e87ebfb937ba406abd025d14af692b12e9c9c2adbe307a679779259676211c071e614fdb386d1ff02db223a5b2fae03df68d321c7b29f7c7240edd3fa1b7cb6903f89dc01abf41b2eb0b49b6b8d73bb0774b58204c0d0e96d3cce45ad75406be0bc009e327b3e712a4bd178609c00b41da2daf8a4b0e1319f07a492ab4efb056f0f599f75e6dc7e0d10ce1cf59088ab6e873de377343880f7a24f0e36731a0b72092f8d5bc8cd346762e93b2bf203d00264e4bc136fc142de8f7b69154deb05854ea88e2d7506222c95ba1aab065c8a851391377d3406a35a9af3ac" |> hex.DecodeData Expect.equal filler expectedFiller "" From 08b6971da166d02380c5046211a1ae2d716318b5 Mon Sep 17 00:00:00 2001 From: Afshin Arani Date: Fri, 11 Jun 2021 15:59:11 +0430 Subject: [PATCH 06/15] WIP: Support different initial packet filler All-zero initial packets should only be used for generating test vectors because of possible privacy leak. "Create packet (reference test vector)" testcase updated to new testcases provided by the RFC repo to meet this change of spec. See more: https://github.com/lightningnetwork/lightning-rfc/commit/8dd0b75809c9a7498bb9031a6674e5f58db509f4 https://github.com/lightningnetwork/lightning-rfc/blob/master/04-onion-routing.md#packet-construction --- src/DotNetLightning.Core/Crypto/Sphinx.fs | 18 ++++++++++++-- .../DotNetLightning.Core.Tests/SphinxTests.fs | 24 +++++++++---------- 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/src/DotNetLightning.Core/Crypto/Sphinx.fs b/src/DotNetLightning.Core/Crypto/Sphinx.fs index f1b04c0a6..8b2cf22ec 100644 --- a/src/DotNetLightning.Core/Crypto/Sphinx.fs +++ b/src/DotNetLightning.Core/Crypto/Sphinx.fs @@ -194,6 +194,20 @@ module Sphinx = HMAC = nextHmac } nextPacket + module PacketFiller = + // DeterministicPacketFiller is a packet filler that generates a deterministic + // set of filler bytes by using chacha20 with a key derived from the session + // key. + let DeterministicPacketFiller (sessionKey: Key) = + generateStream(generateKey("pad",sessionKey.ToBytes()), 1300) + + // BlankPacketFiller is a packet filler that doesn't attempt to fill out the + // packet at all. It should ONLY be used for generating test vectors or other + // instances that required deterministic packet generation. + [] + let BlankPacketFiller _= + Array.zeroCreate 1300 + type PacketAndSecrets = { Packet: OnionPacket /// Shared secrets (one per node in the route). Known (and needed) only if you're creating the @@ -201,7 +215,7 @@ module Sphinx = SharedSecrets: (Key * PubKey) list } with - static member Create (sessionKey: Key, pubKeys: PubKey list, payloads: byte[] list, ad: byte[]) = + static member Create (sessionKey: Key, pubKeys: PubKey list, payloads: byte[] list, ad: byte[], initialPacketFiller: Key -> byte[]) = let (ephemeralPubKeys, sharedSecrets) = computeEphemeralPublicKeysAndSharedSecrets (sessionKey) (pubKeys) let filler = generateFiller "rho" payloads sharedSecrets @@ -209,7 +223,7 @@ module Sphinx = ad, ephemeralPubKeys |> List.last, (sharedSecrets |> List.last |> fun ss -> ss.ToBytes()), - OnionPacket.LastPacket, + {OnionPacket.LastPacket with HopData = initialPacketFiller(sessionKey)}, Some(filler)) let rec loop (hopPayloads: byte[] list, ephKeys: PubKey list, ss: Key list, packet: OnionPacket) = if (hopPayloads.IsEmpty) then diff --git a/tests/DotNetLightning.Core.Tests/SphinxTests.fs b/tests/DotNetLightning.Core.Tests/SphinxTests.fs index 57392cccb..ff62d0280 100644 --- a/tests/DotNetLightning.Core.Tests/SphinxTests.fs +++ b/tests/DotNetLightning.Core.Tests/SphinxTests.fs @@ -30,10 +30,10 @@ let expectedPubKeys = [ "02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea |> List.map(hex.DecodeData >> PubKey) let payloads = [ "000000000000000000000000000000000000000000000000000000000000000000" - "000101010101010101000000010000000100000000000000000000000000000000" - "000202020202020202000000020000000200000000000000000000000000000000" - "000303030303030303000000030000000300000000000000000000000000000000" - "000404040404040404000000040000000400000000000000000000000000000000" ] + "000101010101010101000000000000000100000001000000000000000000000000" + "000202020202020202000000000000000200000002000000000000000000000000" + "000303030303030303000000000000000300000003000000000000000000000000" + "000404040404040404000000000000000400000004000000000000000000000000" ] |> List.map(hex.DecodeData) let associatedData = "4242424242424242424242424242424242424242424242424242424242424242" |> hex.DecodeData @@ -73,10 +73,10 @@ let bolt4Tests1 = testCase "Create packet (reference test vector)" <| fun _ -> let (onion, _ss) = - let p = PacketAndSecrets.Create (sessionKey, pubKeys, payloads, associatedData) + let p = PacketAndSecrets.Create (sessionKey, pubKeys, payloads, associatedData, PacketFiller.DeterministicPacketFiller) (p.Packet, p.SharedSecrets) let expectedPacket = - "0002eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619e5f14350c2a76fc232b5e46d421e9615471ab9e0bc887beff8c95fdb878f7b3a716a996c7845c93d90e4ecbb9bde4ece2f69425c99e4bc820e44485455f135edc0d10f7d61ab590531cf08000179a333a347f8b4072f216400406bdf3bf038659793d4a1fd7b246979e3150a0a4cb052c9ec69acf0f48c3d39cd55675fe717cb7d80ce721caad69320c3a469a202f1e468c67eaf7a7cd8226d0fd32f7b48084dca885d56047694762b67021713ca673929c163ec36e04e40ca8e1c6d17569419d3039d9a1ec866abe044a9ad635778b961fc0776dc832b3a451bd5d35072d2269cf9b040f6b7a7dad84fb114ed413b1426cb96ceaf83825665ed5a1d002c1687f92465b49ed4c7f0218ff8c6c7dd7221d589c65b3b9aaa71a41484b122846c7c7b57e02e679ea8469b70e14fe4f70fee4d87b910cf144be6fe48eef24da475c0b0bcc6565ae82cd3f4e3b24c76eaa5616c6111343306ab35c1fe5ca4a77c0e314ed7dba39d6f1e0de791719c241a939cc493bea2bae1c1e932679ea94d29084278513c77b899cc98059d06a27d171b0dbdf6bee13ddc4fc17a0c4d2827d488436b57baa167544138ca2e64a11b43ac8a06cd0c2fba2d4d900ed2d9205305e2d7383cc98dacb078133de5f6fb6bed2ef26ba92cea28aafc3b9948dd9ae5559e8bd6920b8cea462aa445ca6a95e0e7ba52961b181c79e73bd581821df2b10173727a810c92b83b5ba4a0403eb710d2ca10689a35bec6c3a708e9e92f7d78ff3c5d9989574b00c6736f84c199256e76e19e78f0c98a9d580b4a658c84fc8f2096c2fbea8f5f8c59d0fdacb3be2802ef802abbecb3aba4acaac69a0e965abd8981e9896b1f6ef9d60f7a164b371af869fd0e48073742825e9434fc54da837e120266d53302954843538ea7c6c3dbfb4ff3b2fdbe244437f2a153ccf7bdb4c92aa08102d4f3cff2ae5ef86fab4653595e6a5837fa2f3e29f27a9cde5966843fb847a4a61f1e76c281fe8bb2b0a181d096100db5a1a5ce7a910238251a43ca556712eaadea167fb4d7d75825e440f3ecd782036d7574df8bceacb397abefc5f5254d2722215c53ff54af8299aaaad642c6d72a14d27882d9bbd539e1cc7a527526ba89b8c037ad09120e98ab042d3e8652b31ae0e478516bfaf88efca9f3676ffe99d2819dcaeb7610a626695f53117665d267d3f7abebd6bbd6733f645c72c389f03855bdf1e4b8075b516569b118233a0f0971d24b83113c0b096f5216a207ca99a7cddc81c130923fe3d91e7508c9ac5f2e914ff5dccab9e558566fa14efb34ac98d878580814b94b73acbfde9072f30b881f7f0fff42d4045d1ace6322d86a97d164aa84d93a60498065cc7c20e636f5862dc81531a88c60305a2e59a985be327a6902e4bed986dbf4a0b50c217af0ea7fdf9ab37f9ea1a1aaa72f54cf40154ea9b269f1a7c09f9f43245109431a175d50e2db0132337baa0ef97eed0fcf20489da36b79a1172faccc2f7ded7c60e00694282d93359c4682135642bc81f433574aa8ef0c97b4ade7ca372c5ffc23c7eddd839bab4e0f14d6df15c9dbeab176bec8b5701cf054eb3072f6dadc98f88819042bf10c407516ee58bce33fbe3b3d86a54255e577db4598e30a135361528c101683a5fcde7e8ba53f3456254be8f45fe3a56120ae96ea3773631fcb3873aa3abd91bcff00bd38bd43697a2e789e00da6077482e7b1b1a677b5afae4c54e6cbdf7377b694eb7d7a5b913476a5be923322d3de06060fd5e819635232a2cf4f0731da13b8546d1d6d4f8d75b9fce6c2341a71b0ea6f780df54bfdb0dd5cd9855179f602f9172307c7268724c3618e6817abd793adc214a0dc0bc616816632f27ea336fb56dfd" + "0002eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619e5f14350c2a76fc232b5e46d421e9615471ab9e0bc887beff8c95fdb878f7b3a71e87f9aab8f6378c6ff744c1f34b393ad28d065b535c1a8668d85d3b34a1b3befd10f7d61ab590531cf08000178a333a347f8b4072e216400406bdf3bf038659793a1f9e7abc789266cc861cabd95818c0fc8efbdfdc14e3f7c2bc7eb8d6a79ef75ce721caad69320c3a469a202f3e468c67eaf7a7cda226d0fd32f7b48084dca885d014698cf05d742557763d9cb743faeae65dcc79dddaecf27fe5942be5380d15e9a1ec866abe044a9ad635778ba61fc0776dc832b39451bd5d35072d2269cf9b040a2a2fba158a0d8085926dc2e44f0c88bf487da56e13ef2d5e676a8589881b4869ed4c7f0218ff8c6c7dd7221d189c65b3b9aaa71a01484b122846c7c7b57e02e679ea8469b70e14fe4f70fee4d87b910cf144be6fe48eef24da475c0b0bcc6565a9f99728426ce2380a9580e2a9442481ceae7679906c30b1a0e21a10f26150e0645ab6edfdab1ce8f8bea7b1dee511c5fd38ac0e702c1c15bb86b52bca1b71e15b96982d262a442024c33ceb7dd8f949063c2e5e613e873250e2f8708bd4e1924abd45f65c2fa5617bfb10ee9e4a42d6b5811acc8029c16274f937dac9e8817c7e579fdb767ffe277f26d413ced06b620ede8362081da21cf67c2ca9d6f15fe5bc05f82f5bb93f8916bad3d63338ca824f3bbc11b57ce94a5fa1bc239533679903d6fec92a8c792fd86e2960188c14f21e399cfd72a50c620e10aefc6249360b463df9a89bf6836f4f26359207b765578e5ed76ae9f31b1cc48324be576e3d8e44d217445dba466f9b6293fdf05448584eb64f61e02903f834518622b7d4732471c6e0e22e22d1f45e31f0509eab39cdea5980a492a1da2aaac55a98a01216cd4bfe7abaa682af0fbff2dfed030ba28f1285df750e4d3477190dd193f8643b61d8ac1c427d590badb1f61a05d480908fbdc7c6f0502dd0c4abb51d725e92f95da2a8facb79881a844e2026911adcc659d1fb20a2fce63787c8bb0d9f6789c4b231c76da81c3f0718eb7156565a081d2be6b4170c0e0bcebddd459f53db2590c974bca0d705c055dee8c629bf854a5d58edc85228499ec6dde80cce4c8910b81b1e9e8b0f43bd39c8d69c3a80672729b7dc952dd9448688b6bd06afc2d2819cda80b66c57b52ccf7ac1a86601410d18d0c732f69de792e0894a9541684ef174de766fd4ce55efea8f53812867be6a391ac865802dbc26d93959df327ec2667c7256aa5a1d3c45a69a6158f285d6c97c3b8eedb09527848500517995a9eae4cd911df531544c77f5a9a2f22313e3eb72ca7a07dba243476bc926992e0d1e58b4a2fc8c7b01e0cad726237933ea319bad7537d39f3ed635d1e6c1d29e97b3d2160a09e30ee2b65ac5bce00996a73c008bcf351cecb97b6833b6d121dcf4644260b2946ea204732ac9954b228f0beaa15071930fd9583dfc466d12b5f0eeeba6dcf23d5ce8ae62ee5796359d97a4a15955c778d868d0ef9991d9f2833b5bb66119c5f8b396fd108baed7906cbb3cc376d13551caed97fece6f42a4c908ee279f1127fda1dd3ee77d8de0a6f3c135fa3f1cffe38591b6738dc97b55f0acc52be9753ce53e64d7e497bb00ca6123758df3b68fad99e35c04389f7514a8e36039f541598a417275e77869989782325a15b5342ac5011ff07af698584b476b35d941a4981eac590a07a092bb50342da5d3341f901aa07964a8d02b623c7b106dd0ae50bfa007a22d46c8772fa55558176602946cb1d11ea5460db7586fb89c6d3bcd3ab6dd20df4a4db63d2e7d52380800ad812b8640887e027e946df96488b47fbc4a4fadaa8beda4abe446fafea5403fae2ef" |> hex.DecodeData CheckArrayEqual (onion.ToBytes()) (expectedPacket) let { Payload = payload0; NextPacket = nextPacket0; SharedSecret = _ss0 }: ParsedPacket = @@ -99,16 +99,16 @@ let bolt4Tests1 = Expect.equal [payload0; payload1; payload2; payload3; payload4] payloads "" - Expect.equal nextPacket0.HMAC ("2bdc5227c8eb8ba5fcfc15cfc2aa578ff208c106646d0652cd289c0a37e445bb" |> hex.DecodeData |> uint256) "" - Expect.equal nextPacket1.HMAC ("28430b210c0af631ef80dc8594c08557ce4626bdd3593314624a588cc083a1d9" |> hex.DecodeData |> uint256) "" - Expect.equal nextPacket2.HMAC ("4e888d0cc6a90e7f857af18ac858834ac251d0d1c196d198df48a0c5bf816803" |> hex.DecodeData |> uint256) "" - Expect.equal nextPacket3.HMAC ("42c10947e06bda75b35ac2a9e38005479a6feac51468712e751c71a1dcf3e31b" |> hex.DecodeData |> uint256) "" + Expect.equal nextPacket0.HMAC ("a93aa4f40241cef3e764e24b28570a0db39af82ab5102c3a04e51bec8cca9394" |> hex.DecodeData |> uint256) "" + Expect.equal nextPacket1.HMAC ("5d1b11f1efeaa9be32eb1c74b113c0b46f056bb49e2a35a51ceaece6bd31332c" |> hex.DecodeData |> uint256) "" + Expect.equal nextPacket2.HMAC ("19ca6357b5552b28e50ae226854eec874bbbf7025cf290a34c06b4eff5d2bac0" |> hex.DecodeData |> uint256) "" + Expect.equal nextPacket3.HMAC ("16d4553c6084b369073d259381bb5b02c16bb2c590bbd9e69346cf7ebd563229" |> hex.DecodeData |> uint256) "" Expect.equal nextPacket4.HMAC ("0000000000000000000000000000000000000000000000000000000000000000" |> hex.DecodeData |> uint256) "" () testCase "last node replies with an error message" <| fun _ -> let (onion, ss) = - let p = PacketAndSecrets.Create (sessionKey, pubKeys, payloads, associatedData) + let p = PacketAndSecrets.Create (sessionKey, pubKeys, payloads, associatedData, PacketFiller.BlankPacketFiller) (p.Packet, p.SharedSecrets) let { NextPacket = packet1; SharedSecret = ss0 }: ParsedPacket = Sphinx.parsePacket (privKeys.[0]) (associatedData) (onion.ToBytes()) @@ -166,7 +166,7 @@ let bolt4Tests1 = testCase "Intermediate node replies with an error message" <| fun _ -> let { Packet = packet; SharedSecrets = ss } = - Sphinx.PacketAndSecrets.Create(sessionKey, pubKeys, payloads, associatedData) + Sphinx.PacketAndSecrets.Create(sessionKey, pubKeys, payloads, associatedData, PacketFiller.BlankPacketFiller) let { NextPacket = packet1; SharedSecret = ss0 } = Sphinx.parsePacket(privKeys.[0]) (associatedData) (packet.ToBytes()) |> Result.defaultWith(fun _ -> failwith "Fail: bolt4 intrm node replies with err msg defaultClosure0") From eccd3a103003148a1fbdf7cca63258f7c20e0585 Mon Sep 17 00:00:00 2001 From: Afshin Arani Date: Sat, 12 Jun 2021 00:19:53 +0430 Subject: [PATCH 07/15] Support for non fixed-size payloads when making packets --- src/DotNetLightning.Core/Crypto/Sphinx.fs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/DotNetLightning.Core/Crypto/Sphinx.fs b/src/DotNetLightning.Core/Crypto/Sphinx.fs index 8b2cf22ec..4fc0ecc86 100644 --- a/src/DotNetLightning.Core/Crypto/Sphinx.fs +++ b/src/DotNetLightning.Core/Crypto/Sphinx.fs @@ -170,12 +170,10 @@ module Sphinx = sharedSecret: byte[], packet: OnionPacket, routingInfoFiller: byte[] option) = - if (payload.Length <> PayloadLength) then - failwithf "Payload length is not %A" PayloadLength - else + let payloadLen = payload.Length let filler = defaultArg routingInfoFiller ([||]) let nextRoutingInfo = - let routingInfo1 = seq [ payload; packet.HMAC.ToBytes(); (packet.HopData |> Array.skipBack(PayloadLength + MacLength)) ] + let routingInfo1 = seq [ payload; packet.HMAC.ToBytes(); (packet.HopData |> Array.skipBack(payloadLen + MacLength)) ] |> Array.concat let routingInfo2 = let rho = generateKey("rho", sharedSecret) From 56d538bddf63e159c2f0e38809e4175d47dc7942 Mon Sep 17 00:00:00 2001 From: Afshin Arani Date: Sun, 13 Jun 2021 23:18:46 +0430 Subject: [PATCH 08/15] TLVs: HopPayloadTLV uses truncated integers See more here: https://github.com/lightningnetwork/lightning-rfc/blob/master/04-onion-routing.md#tlv_payload-format https://github.com/lightningnetwork/lightning-rfc/blob/master/01-messaging.md#fundamental-types --- .../Serialization/TLVs.fs | 13 ++-- src/DotNetLightning.Core/Utils/Extensions.fs | 74 +++++++++++++++++++ 2 files changed, 81 insertions(+), 6 deletions(-) diff --git a/src/DotNetLightning.Core/Serialization/TLVs.fs b/src/DotNetLightning.Core/Serialization/TLVs.fs index 811a2f699..aba6b3d81 100644 --- a/src/DotNetLightning.Core/Serialization/TLVs.fs +++ b/src/DotNetLightning.Core/Serialization/TLVs.fs @@ -2,6 +2,7 @@ namespace DotNetLightning.Serialization open DotNetLightning.Utils +open DotNetLightning.Core.Utils.Extensions open System open NBitcoin @@ -107,31 +108,31 @@ type HopPayloadTLV = static member FromGenericTLV(tlv: GenericTLV) = match tlv.Type with | 2UL -> - NBitcoin.Utils.ToUInt64(tlv.Value, false) + UInt64.FromTruncatedBytes(tlv.Value) |> LNMoney.MilliSatoshis |> AmountToForward | 4UL -> - NBitcoin.Utils.ToUInt32(tlv.Value, false) + UInt32.FromTruncatedBytes(tlv.Value) |> OutgoingCLTV | 6UL -> ShortChannelId.From8Bytes(tlv.Value) |> ShortChannelId | 8UL -> let secret = tlv.Value.[0..PaymentPreimage.LENGTH - 1] |> PaymentPreimage.Create - let totalMSat = NBitcoin.Utils.ToUInt64(tlv.Value.[PaymentPreimage.LENGTH..], false) |> LNMoney.MilliSatoshis + let totalMSat = UInt64.FromTruncatedBytes(tlv.Value.[PaymentPreimage.LENGTH..]) |> LNMoney.MilliSatoshis (secret, totalMSat) |> PaymentData | _ -> Unknown tlv member this.ToGenericTLV() = match this with | AmountToForward x -> - { Type = 2UL; Value = Utils.ToBytes(uint64 x.MilliSatoshi, false) } + { Type = 2UL; Value = (uint64 x.MilliSatoshi).GetTruncatedBytes() } | OutgoingCLTV x -> - { Type = 4UL; Value = Utils.ToBytes(x, false) } + { Type = 4UL; Value = (uint32 x).GetTruncatedBytes() } | ShortChannelId x -> { Type = 6UL; Value = x.ToBytes() } | PaymentData(secret, amount) -> - let value = Array.concat[ secret.ToByteArray(); Utils.ToBytes(uint64 amount.MilliSatoshi, false) ] + let value = Array.concat[ secret.ToByteArray(); (uint64 amount.MilliSatoshi).GetTruncatedBytes() ] { Type = 8UL; Value = value } | Unknown x -> x diff --git a/src/DotNetLightning.Core/Utils/Extensions.fs b/src/DotNetLightning.Core/Utils/Extensions.fs index 3d8f7024c..ea8e9545b 100644 --- a/src/DotNetLightning.Core/Utils/Extensions.fs +++ b/src/DotNetLightning.Core/Utils/Extensions.fs @@ -59,6 +59,47 @@ type System.UInt64 with buf.[8] <- (byte x) buf + member private x.GetLeadingZerosCount() = + if (x = 0UL) then + 8 + else if (x &&& 0xffffffffffffff00UL = 0UL) then + 7 + else if (x &&& 0xffffffffffff0000UL = 0UL) then + 6 + else if (x &&& 0xffffffffff000000UL = 0UL) then + 5 + else if (x &&& 0xffffffff00000000UL = 0UL) then + 4 + else if (x &&& 0xffffff0000000000UL = 0UL) then + 3 + else if (x &&& 0xffff000000000000UL = 0UL) then + 2 + else if (x &&& 0xff00000000000000UL = 0UL) then + 1 + else + 0 + + member x.GetTruncatedBytes() = + let numZeros = + x.GetLeadingZerosCount() + x.GetBytesBigEndian() |> Array.skip(numZeros) + + //TODO: better error handling? + static member FromTruncatedBytes(bytes: array): uint64 = + let len = Array.length bytes + if len > 8 then + failwith "incorrect truncated bytes length" + else + let num = + [Array.zeroCreate (8-len); bytes] + |> Array.concat + |> UInt64.FromBytesBigEndian + + if 8 - num.GetLeadingZerosCount() <> len then + failwith "truncated uint not minimally encoded" + + num + type System.UInt32 with member this.GetBytesBigEndian() = let d = BitConverter.GetBytes(this) @@ -72,6 +113,39 @@ type System.UInt32 with bytes4 BitConverter.ToUInt32(bytes, 0) + member private x.GetLeadingZerosCount() = + if (x = 0u) then + 4 + else if (x &&& 0xffffff00u = 0u) then + 3 + else if (x &&& 0xffff0000u = 0u) then + 2 + else if (x &&& 0xff000000u = 0u) then + 1 + else + 0 + + member x.GetTruncatedBytes() = + let numZeros = + x.GetLeadingZerosCount() + x.GetBytesBigEndian() |> Array.skip(numZeros) + + //TODO: better error handling? + static member FromTruncatedBytes(bytes: array): uint32 = + let len = Array.length bytes + if len > 4 then + failwith "incorrect truncated bytes length" + else + let num = + [Array.zeroCreate (4-len); bytes] + |> Array.concat + |> UInt32.FromBytesBigEndian + + if 4 - num.GetLeadingZerosCount() <> len then + failwith "truncated uint not minimally encoded" + + num + type System.UInt16 with member this.GetBytesBigEndian() = let d = BitConverter.GetBytes(this) From 04a92869a90db69e0c9a105ce9079f619816e060 Mon Sep 17 00:00:00 2001 From: Afshin Arani Date: Mon, 14 Jun 2021 00:06:14 +0430 Subject: [PATCH 09/15] Secret inside TLVPayload is PaymentSecret Secret field inside TLVPayload's PaymentData is PaymentSecret and not PaymentPreimage and It's used for mpp. --- .../Serialization/TLVs.fs | 6 ++--- src/DotNetLightning.Core/Utils/Primitives.fs | 27 +++++++++++++++++++ .../Generators/Msgs.fs | 2 +- 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/DotNetLightning.Core/Serialization/TLVs.fs b/src/DotNetLightning.Core/Serialization/TLVs.fs index aba6b3d81..881d357fa 100644 --- a/src/DotNetLightning.Core/Serialization/TLVs.fs +++ b/src/DotNetLightning.Core/Serialization/TLVs.fs @@ -102,7 +102,7 @@ type HopPayloadTLV = | AmountToForward of LNMoney | OutgoingCLTV of uint32 | ShortChannelId of ShortChannelId - | PaymentData of paymentSecret: PaymentPreimage * totalMsat: LNMoney + | PaymentData of paymentSecret: PaymentSecret * totalMsat: LNMoney | Unknown of GenericTLV with static member FromGenericTLV(tlv: GenericTLV) = @@ -118,8 +118,8 @@ type HopPayloadTLV = ShortChannelId.From8Bytes(tlv.Value) |> ShortChannelId | 8UL -> - let secret = tlv.Value.[0..PaymentPreimage.LENGTH - 1] |> PaymentPreimage.Create - let totalMSat = UInt64.FromTruncatedBytes(tlv.Value.[PaymentPreimage.LENGTH..]) |> LNMoney.MilliSatoshis + let secret = tlv.Value.[0..PaymentSecret.LENGTH - 1] |> PaymentSecret.FromByteArray + let totalMSat = UInt64.FromTruncatedBytes(tlv.Value.[PaymentSecret.LENGTH..]) |> LNMoney.MilliSatoshis (secret, totalMSat) |> PaymentData | _ -> Unknown tlv diff --git a/src/DotNetLightning.Core/Utils/Primitives.fs b/src/DotNetLightning.Core/Utils/Primitives.fs index a481e0526..00a3912b1 100644 --- a/src/DotNetLightning.Core/Utils/Primitives.fs +++ b/src/DotNetLightning.Core/Utils/Primitives.fs @@ -215,6 +215,33 @@ module Primitives = member this.ToPubKey() = this.ToPrivKey().PubKey + type PaymentSecret = + private PaymentSecret of uint256 + with + // as per BOLT-2: + static member LENGTH = 32 + + static member Create(secret: uint256) = + PaymentSecret secret + + static member FromByteArray(secretBytes: array) = + uint256(secretBytes, false) + |> PaymentSecret + + member this.Value = + let (PaymentSecret v) = this in v + + member this.ToHex() = + let h = NBitcoin.DataEncoders.HexEncoder() + let ba: byte[] = this.ToByteArray() + ba |> h.EncodeData + + member this.ToBytes() = + this.Value.ToBytes(false) + + member this.ToByteArray() = + this.Value.ToBytes(false) |> Array.ofSeq + let (|PaymentPreimage|) x = match x with | PaymentPreimage x -> x diff --git a/tests/DotNetLightning.Core.Tests/Generators/Msgs.fs b/tests/DotNetLightning.Core.Tests/Generators/Msgs.fs index 362a20f9e..90eeb694c 100644 --- a/tests/DotNetLightning.Core.Tests/Generators/Msgs.fs +++ b/tests/DotNetLightning.Core.Tests/Generators/Msgs.fs @@ -583,7 +583,7 @@ let gossipTimestampFilterGen: Gen = gen { let private hopPayloadTlvGen = let paymentDataGen: Gen<_ * _> = - ((PaymentPreimage.Create bytesOfNGen(32)), (lnMoneyGen)) + ((PaymentSecret.Create uint256Gen), (lnMoneyGen)) ||> Gen.map2(fun a b -> (a, b)) Gen.frequency [(1, AmountToForward lnMoneyGen) From 41dcc2c5b95c80881c0380dcc570a74211058690 Mon Sep 17 00:00:00 2001 From: Afshin Arani Date: Wed, 16 Jun 2021 11:48:59 +0430 Subject: [PATCH 10/15] WIP: Fix HTLC signing process --- .../Channel/CommitmentsModule.fs | 21 +-- .../Crypto/KeyExtensions.fs | 2 + .../DotNetLightning.Core.fsproj | 2 + .../Transactions/Transactions.fs | 2 + .../Utils/HTLCOfferedExtensions.fs | 147 +++++++++++++++++ .../Utils/HTLCReceivedExtensions.fs | 153 ++++++++++++++++++ 6 files changed, 317 insertions(+), 10 deletions(-) create mode 100644 src/DotNetLightning.Core/Utils/HTLCOfferedExtensions.fs create mode 100644 src/DotNetLightning.Core/Utils/HTLCReceivedExtensions.fs diff --git a/src/DotNetLightning.Core/Channel/CommitmentsModule.fs b/src/DotNetLightning.Core/Channel/CommitmentsModule.fs index 02bb2ad5a..b18879f2b 100644 --- a/src/DotNetLightning.Core/Channel/CommitmentsModule.fs +++ b/src/DotNetLightning.Core/Channel/CommitmentsModule.fs @@ -345,23 +345,24 @@ module internal Commitments = Transactions.checkTxFinalized signedCommitTx localCommitTx.WhichInput sigPair |> expectTransactionError let! finalizedCommitTx = tmp - let sortedHTLCTXs = Helpers.sortBothHTLCs htlcTimeoutTxs htlcSuccessTxs + let sortedHTLCTXs = Helpers.sortBothHTLCs htlcTimeoutTxs htlcSuccessTxs do! checkSignatureCountMismatch sortedHTLCTXs msg - let _localHTLCSigs, sortedHTLCTXs = - let localHtlcSigsAndHTLCTxs = - sortedHTLCTXs |> List.map(fun htlc -> - channelPrivKeys.SignHtlcTx htlc.Value localPerCommitmentPoint - ) - localHtlcSigsAndHTLCTxs |> List.map(fst), localHtlcSigsAndHTLCTxs |> List.map(snd) |> Seq.cast |> List.ofSeq + let HTLCTxsAndSignatures = + sortedHTLCTXs + |> List.zip (msg.HTLCSignatures) + |> List.map(fun (remoteSig, htlc) -> + channelPrivKeys.SignHtlcTx htlc.Value localPerCommitmentPoint |> fst, htlc, remoteSig + ) let remoteHTLCPubKey = localPerCommitmentPoint.DeriveHtlcPubKey remoteChannelKeys.HtlcBasepoint - let checkHTLCSig (htlc: IHTLCTx, remoteECDSASig: LNECDSASignature): Result<_, _> = + let checkHTLCSig (localSignature: TransactionSignature, htlc: IHTLCTx, remoteECDSASig: LNECDSASignature): Result<_, _> = let remoteS = TransactionSignature(remoteECDSASig.Value, SigHash.All) match htlc with | :? HTLCTimeoutTx -> - (Transactions.checkTxFinalized (htlc.Value) (0) (seq [(remoteHTLCPubKey.RawPubKey(), remoteS)])) + (htlc :?> HTLCTimeoutTx).Finalize(localSignature, remoteS) + |> Result.map(FinalizedTx) |> Result.map(box) // we cannot check that htlc-success tx are spendable because we need the payment preimage; thus we only check the remote sig | :? HTLCSuccessTx -> @@ -370,7 +371,7 @@ module internal Commitments = | _ -> failwith "Unreachable!" let! txList = - List.zip sortedHTLCTXs msg.HTLCSignatures + HTLCTxsAndSignatures |> List.map(checkHTLCSig) |> List.sequenceResultA |> expectTransactionErrors diff --git a/src/DotNetLightning.Core/Crypto/KeyExtensions.fs b/src/DotNetLightning.Core/Crypto/KeyExtensions.fs index c2f6f76a0..a105fa0cb 100644 --- a/src/DotNetLightning.Core/Crypto/KeyExtensions.fs +++ b/src/DotNetLightning.Core/Crypto/KeyExtensions.fs @@ -9,6 +9,7 @@ open DotNetLightning.Core.Utils.Extensions open ResultUtils open ResultUtils.Portability +open NBitcoin.BuilderExtensions [] module NBitcoinArithmethicExtensions = @@ -285,6 +286,7 @@ module KeyExtensions = : TransactionSignature * PSBT = let htlcPrivKey = perCommitmentPoint.DeriveHtlcPrivKey this.HtlcBasepointSecret let htlcPubKey = htlcPrivKey.HtlcPubKey() + psbt.Settings.CustomBuilderExtensions <- ([new HTLCReceivedExtensions() :> BuilderExtension; new HTLCOfferedExtensions():> BuilderExtension] |> Seq.ofList) psbt.SignWithKeys(htlcPrivKey.RawKey()) |> ignore match psbt.GetMatchingSig(htlcPubKey.RawPubKey()) with | Some signature -> (signature, psbt) diff --git a/src/DotNetLightning.Core/DotNetLightning.Core.fsproj b/src/DotNetLightning.Core/DotNetLightning.Core.fsproj index cf09a5607..4b48d086d 100644 --- a/src/DotNetLightning.Core/DotNetLightning.Core.fsproj +++ b/src/DotNetLightning.Core/DotNetLightning.Core.fsproj @@ -46,6 +46,8 @@ + + diff --git a/src/DotNetLightning.Core/Transactions/Transactions.fs b/src/DotNetLightning.Core/Transactions/Transactions.fs index 71122b97e..466db2da8 100644 --- a/src/DotNetLightning.Core/Transactions/Transactions.fs +++ b/src/DotNetLightning.Core/Transactions/Transactions.fs @@ -524,6 +524,7 @@ module Transactions = let dest = Scripts.toLocalDelayed localRevocationPubKey toLocalDelay localDelayedPaymentPubKey // we have already done dust limit check above txb.DustPrevention <- false + txb.Extensions.Add (new HTLCOfferedExtensions()) let tx = txb.AddCoins(scriptCoin) .Send(dest.WitHash, amount) .SendFees(fee) @@ -566,6 +567,7 @@ module Transactions = ScriptCoin(coin, redeem) let dest = Scripts.toLocalDelayed localRevocationPubKey toLocalDelay localDelayedPaymentPubKey // we have already done dust limit check above + txb.Extensions.Add (new HTLCReceivedExtensions()) txb.DustPrevention <- false let tx = txb.AddCoins(scriptCoin) .Send(dest.WitHash, amount) diff --git a/src/DotNetLightning.Core/Utils/HTLCOfferedExtensions.fs b/src/DotNetLightning.Core/Utils/HTLCOfferedExtensions.fs new file mode 100644 index 000000000..47d45a2f4 --- /dev/null +++ b/src/DotNetLightning.Core/Utils/HTLCOfferedExtensions.fs @@ -0,0 +1,147 @@ +namespace DotNetLightning.Utils + +open System +open NBitcoin +open NBitcoin.BuilderExtensions +open DotNetLightning.Utils + +open ResultUtils +open ResultUtils.Portability +open Newtonsoft.Json + +type HTLCOfferedParameters = { + LocalHtlcPubKey: HtlcPubKey + RemoteHtlcPubKey: HtlcPubKey +} + with + static member TryExtractParameters (scriptPubKey: Script): Option = + 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 + + Console.WriteLine(JsonConvert.SerializeObject(ops)) + let checkOpCode(opcodeType: OpcodeType) = seqParser { + let! op = SeqParser.next() + if op.Code = opcodeType then + return () + else + return! SeqParser.abort() + } + + let parseToCompletionResult = + SeqParser.parseToCompletion ops <| seqParser { + do! checkOpCode OpcodeType.OP_DUP + do! checkOpCode OpcodeType.OP_HASH160 + let! _opRevocationPubKey = SeqParser.next() + do! checkOpCode OpcodeType.OP_EQUAL + do! checkOpCode OpcodeType.OP_IF + do! checkOpCode OpcodeType.OP_CHECKSIG + do! checkOpCode OpcodeType.OP_ELSE + let! opRemoteHtlcPubKey = SeqParser.next() + let! remoteHtlcPubKey = seqParser { + match opRemoteHtlcPubKey.PushData with + | null -> return! SeqParser.abort() + | bytes -> + try + return bytes |> PubKey |> HtlcPubKey.HtlcPubKey + with + | :? FormatException -> return! SeqParser.abort() + } + do! checkOpCode OpcodeType.OP_SWAP + do! checkOpCode OpcodeType.OP_SIZE + let! _blabla = SeqParser.next() + do! checkOpCode OpcodeType.OP_EQUAL + do! checkOpCode OpcodeType.OP_NOTIF + do! checkOpCode OpcodeType.OP_DROP + do! checkOpCode OpcodeType.OP_2 + do! checkOpCode OpcodeType.OP_SWAP + let! opLocalHtlcPubKey = SeqParser.next() + let! localHtlcPubKey = seqParser { + match opLocalHtlcPubKey.PushData with + | null -> return! SeqParser.abort() + | bytes -> + try + return bytes |> PubKey |> HtlcPubKey.HtlcPubKey + with + | :? FormatException -> return! SeqParser.abort() + } + do! checkOpCode OpcodeType.OP_2 + do! checkOpCode OpcodeType.OP_CHECKMULTISIG + do! checkOpCode OpcodeType.OP_ELSE + do! checkOpCode OpcodeType.OP_HASH160 + let! _opPaymentHash = SeqParser.next() + do! checkOpCode OpcodeType.OP_EQUALVERIFY + do! checkOpCode OpcodeType.OP_CHECKSIG + do! checkOpCode OpcodeType.OP_ENDIF + do! checkOpCode OpcodeType.OP_ENDIF + + return { + LocalHtlcPubKey = localHtlcPubKey + RemoteHtlcPubKey = remoteHtlcPubKey + } + } + match parseToCompletionResult with + | Ok data -> Some data + | Error _consumeAllError -> None + +type internal HTLCOfferedExtensions() = + inherit BuilderExtension() + override self.CanGenerateScriptSig (scriptPubKey: Script): bool = + (HTLCOfferedParameters.TryExtractParameters scriptPubKey).IsSome + + override self.GenerateScriptSig(scriptPubKey: Script, keyRepo: IKeyRepository, signer: ISigner): Script = + let parameters = + match (HTLCOfferedParameters.TryExtractParameters scriptPubKey) with + | Some parameters -> parameters + | None -> + failwith + "NBitcoin should not call this unless CanGenerateScriptSig returns true" + let pubKey = keyRepo.FindKey scriptPubKey + // FindKey will return null if it can't find a key for + // scriptPubKey. If we can't find a valid key then this method + // should return null, indicating to NBitcoin that the sigScript + // could not be generated. + match pubKey with + | null -> null + | _ when pubKey = parameters.LocalHtlcPubKey.RawPubKey() -> + let localHtlcSig = signer.Sign (parameters.LocalHtlcPubKey.RawPubKey()) + Script [ + Op.GetPushOp (localHtlcSig.ToBytes()) + ] + | _ when pubKey = parameters.RemoteHtlcPubKey.RawPubKey() -> + let remoteHtlcSig = signer.Sign (parameters.RemoteHtlcPubKey.RawPubKey()) + Script [ + Op.GetPushOp (remoteHtlcSig.ToBytes()) + ] + | _ -> null + + override self.CanDeduceScriptPubKey(_scriptSig: Script): bool = + false + + override self.DeduceScriptPubKey(_scriptSig: Script): Script = + raise <| NotSupportedException() + + override self.CanEstimateScriptSigSize(_scriptPubKey: Script): bool = + false + + override self.EstimateScriptSigSize(_scriptPubKey: Script): int = + raise <| NotSupportedException() + + override self.CanCombineScriptSig(_scriptPubKey: Script, _a: Script, _b: Script): bool = + false + + override self.CombineScriptSig(_scriptPubKey: Script, _a: Script, _b: Script): Script = + raise <| NotSupportedException() + + override self.IsCompatibleKey(pubKey: PubKey, scriptPubKey: Script): bool = + match HTLCOfferedParameters.TryExtractParameters scriptPubKey with + | None -> false + | Some parameters -> + parameters.LocalHtlcPubKey.RawPubKey() = pubKey + || parameters.RemoteHtlcPubKey.RawPubKey() = pubKey + + diff --git a/src/DotNetLightning.Core/Utils/HTLCReceivedExtensions.fs b/src/DotNetLightning.Core/Utils/HTLCReceivedExtensions.fs new file mode 100644 index 000000000..15a1fc645 --- /dev/null +++ b/src/DotNetLightning.Core/Utils/HTLCReceivedExtensions.fs @@ -0,0 +1,153 @@ +namespace DotNetLightning.Utils + +open System +open NBitcoin +open NBitcoin.BuilderExtensions +open DotNetLightning.Utils + +open ResultUtils +open ResultUtils.Portability +open Newtonsoft.Json + +type HTLCReceivedParameters = { + LocalHtlcPubKey: HtlcPubKey + RemoteHtlcPubKey: HtlcPubKey +} + with + static member TryExtractParameters (scriptPubKey: Script): Option = + 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 + + Console.WriteLine(JsonConvert.SerializeObject(ops)) + let checkOpCode(opcodeType: OpcodeType) = seqParser { + let! op = SeqParser.next() + if op.Code = opcodeType then + return () + else + return! SeqParser.abort() + } + + let parseToCompletionResult = + SeqParser.parseToCompletion ops <| seqParser { + do! checkOpCode OpcodeType.OP_DUP + do! checkOpCode OpcodeType.OP_HASH160 + let! _opRevocationPubKey = SeqParser.next() + //not implemented + do! checkOpCode OpcodeType.OP_EQUAL + do! checkOpCode OpcodeType.OP_IF + do! checkOpCode OpcodeType.OP_CHECKSIG + do! checkOpCode OpcodeType.OP_ELSE + let! opRemoteHTLCPubKey = SeqParser.next() + let! remoteHTLCPubKey = seqParser { + match opRemoteHTLCPubKey.PushData with + | null -> return! SeqParser.abort() + | bytes -> + try + return bytes |> PubKey |> HtlcPubKey.HtlcPubKey + with + | :? FormatException -> return! SeqParser.abort() + } + do! checkOpCode OpcodeType.OP_SWAP + do! checkOpCode OpcodeType.OP_SIZE + let! _blabla = SeqParser.next() + do! checkOpCode OpcodeType.OP_EQUAL + do! checkOpCode OpcodeType.OP_IF + do! checkOpCode OpcodeType.OP_HASH160 + let! _opPaymentHash = SeqParser.next() + do! checkOpCode OpcodeType.OP_EQUALVERIFY + do! checkOpCode OpcodeType.OP_2 + do! checkOpCode OpcodeType.OP_SWAP + let! opLocalHtlcPubKey = SeqParser.next() + let! localHtlcPubKey = seqParser { + match opLocalHtlcPubKey.PushData with + | null -> return! SeqParser.abort() + | bytes -> + try + return bytes |> PubKey |> HtlcPubKey.HtlcPubKey + with + | :? FormatException -> return! SeqParser.abort() + } + do! checkOpCode OpcodeType.OP_2 + do! checkOpCode OpcodeType.OP_CHECKMULTISIG + do! checkOpCode OpcodeType.OP_ELSE + do! checkOpCode OpcodeType.OP_DROP + let! _locktime = SeqParser.next() + do! checkOpCode OpcodeType.OP_CHECKLOCKTIMEVERIFY + do! checkOpCode OpcodeType.OP_DROP + do! checkOpCode OpcodeType.OP_CHECKSIG + do! checkOpCode OpcodeType.OP_ENDIF + do! checkOpCode OpcodeType.OP_ENDIF + + + + return { + LocalHtlcPubKey = localHtlcPubKey + RemoteHtlcPubKey = remoteHTLCPubKey + } + } + match parseToCompletionResult with + | Ok data -> Some data + | Error _consumeAllError -> None + +type internal HTLCReceivedExtensions() = + inherit BuilderExtension() + override self.CanGenerateScriptSig (scriptPubKey: Script): bool = + (HTLCReceivedParameters.TryExtractParameters scriptPubKey).IsSome + + override self.GenerateScriptSig(scriptPubKey: Script, keyRepo: IKeyRepository, signer: ISigner): Script = + let parameters = + match (HTLCReceivedParameters.TryExtractParameters scriptPubKey) with + | Some parameters -> parameters + | None -> + failwith + "NBitcoin should not call this unless CanGenerateScriptSig returns true" + let pubKey = keyRepo.FindKey scriptPubKey + // FindKey will return null if it can't find a key for + // scriptPubKey. If we can't find a valid key then this method + // should return null, indicating to NBitcoin that the sigScript + // could not be generated. + match pubKey with + | null -> null + | _ when pubKey = parameters.LocalHtlcPubKey.RawPubKey() -> + let localHtlcSig = signer.Sign (parameters.LocalHtlcPubKey.RawPubKey()) + Script [ + Op.GetPushOp (localHtlcSig.ToBytes()) + ] + | _ when pubKey = parameters.RemoteHtlcPubKey.RawPubKey() -> + let remoteHtlcSig = signer.Sign (parameters.RemoteHtlcPubKey.RawPubKey()) + Script [ + Op.GetPushOp (remoteHtlcSig.ToBytes()) + ] + | _ -> null + + override self.CanDeduceScriptPubKey(_scriptSig: Script): bool = + false + + override self.DeduceScriptPubKey(_scriptSig: Script): Script = + raise <| NotSupportedException() + + override self.CanEstimateScriptSigSize(_scriptPubKey: Script): bool = + false + + override self.EstimateScriptSigSize(_scriptPubKey: Script): int = + raise <| NotSupportedException() + + override self.CanCombineScriptSig(_scriptPubKey: Script, _a: Script, _b: Script): bool = + false + + override self.CombineScriptSig(_scriptPubKey: Script, _a: Script, _b: Script): Script = + raise <| NotSupportedException() + + override self.IsCompatibleKey(pubKey: PubKey, scriptPubKey: Script): bool = + match HTLCReceivedParameters.TryExtractParameters scriptPubKey with + | None -> false + | Some parameters -> + parameters.LocalHtlcPubKey.RawPubKey() = pubKey + || parameters.RemoteHtlcPubKey.RawPubKey() = pubKey + + From ae083580d78a3f03e2f4925bb3ae0e6065a696af Mon Sep 17 00:00:00 2001 From: Afshin Arani Date: Mon, 14 Jun 2021 19:01:53 +0430 Subject: [PATCH 11/15] Typo: NowKnown -> NotKnown --- src/DotNetLightning.Core/Channel/ChannelError.fs | 2 +- src/DotNetLightning.Core/Channel/CommitmentsModule.fs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/DotNetLightning.Core/Channel/ChannelError.fs b/src/DotNetLightning.Core/Channel/ChannelError.fs index 1c9252188..67f725271 100644 --- a/src/DotNetLightning.Core/Channel/ChannelError.fs +++ b/src/DotNetLightning.Core/Channel/ChannelError.fs @@ -272,7 +272,7 @@ module internal ChannelError = let inline unknownHTLCId x = x |> UnknownHTLCId |> Error - let inline htlcOriginNowKnown x = + let inline htlcOriginNotKnown x = x |> HTLCOriginNotKnown |> Error let inline invalidFailureCode x = x |> InvalidFailureCode |> Error diff --git a/src/DotNetLightning.Core/Channel/CommitmentsModule.fs b/src/DotNetLightning.Core/Channel/CommitmentsModule.fs index b18879f2b..09246f556 100644 --- a/src/DotNetLightning.Core/Channel/CommitmentsModule.fs +++ b/src/DotNetLightning.Core/Channel/CommitmentsModule.fs @@ -176,7 +176,7 @@ module internal Commitments = match cm.OriginChannels.TryGetValue(msg.HTLCId) with | true, origin -> Ok origin | false, _ -> - msg.HTLCId |> htlcOriginNowKnown + msg.HTLCId |> htlcOriginNotKnown let nextC = cm.AddRemoteProposal(msg) return [WeAcceptedFailHTLC(o, htlc, nextC)] } @@ -214,7 +214,7 @@ module internal Commitments = match cm.OriginChannels.TryGetValue(msg.HTLCId) with | true, o -> Ok o | false, _ -> - msg.HTLCId |> htlcOriginNowKnown + msg.HTLCId |> htlcOriginNotKnown let nextC = cm.AddRemoteProposal(msg) return [WeAcceptedFailMalformedHTLC(o, htlc, nextC)] } From 554c557f5f0398130c50dd0e90d641bed6da13d2 Mon Sep 17 00:00:00 2001 From: Afshin Arani Date: Mon, 14 Jun 2021 19:06:07 +0430 Subject: [PATCH 12/15] HtlcId type converter to fix json serialization Newtonsoft.Json needs types used as key in dictionaries to be convertible to string so we need to create a type converter to HtlcId. --- src/DotNetLightning.Core/Utils/Primitives.fs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/DotNetLightning.Core/Utils/Primitives.fs b/src/DotNetLightning.Core/Utils/Primitives.fs index 00a3912b1..df6687d29 100644 --- a/src/DotNetLightning.Core/Utils/Primitives.fs +++ b/src/DotNetLightning.Core/Utils/Primitives.fs @@ -12,6 +12,7 @@ open DotNetLightning.Core.Utils.Extensions open ResultUtils open ResultUtils.Portability +open System.ComponentModel [] module Primitives = @@ -395,12 +396,29 @@ module Primitives = #if !NoDUsAsStructs [] #endif + [)>] type HTLCId = | HTLCId of uint64 with static member Zero = HTLCId(0UL) member x.Value = let (HTLCId v) = x in v static member (+) (a: HTLCId, b: uint64) = (a.Value + b) |> HTLCId + override self.ToString() = + self.Value.ToString() + + and private HTLCIdToStringTypeConverter() = + inherit TypeConverter() + override __.CanConvertFrom(_context, sourceType) = + sourceType = typeof + override __.ConvertFrom(_context, _culture, value) = + match value with + | :? string as stringValue -> + stringValue + |> UInt64.Parse + |> HTLCId.HTLCId + |> box + | _ -> failwith "should not happen" + #if !NoDUsAsStructs [] #endif From b54a9bc33f41c7ea786b6082a507d4df3726df3b Mon Sep 17 00:00:00 2001 From: Afshin Arani Date: Mon, 14 Jun 2021 19:07:42 +0430 Subject: [PATCH 13/15] [WIP] Change origin in HTLC messages to be an option I don't want to use DNL's routing types so I had to just make DNL support None as origin in all commands, which is interesting because DNL does accept option for addHtlc command but if you use it, receiveFulfill and all other htlc related functions crash or fail. --- src/DotNetLightning.Core/Channel/ChannelTypes.fs | 6 +++--- src/DotNetLightning.Core/Channel/CommitmentsModule.fs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/DotNetLightning.Core/Channel/ChannelTypes.fs b/src/DotNetLightning.Core/Channel/ChannelTypes.fs index 8fe5a5d22..d950cb844 100644 --- a/src/DotNetLightning.Core/Channel/ChannelTypes.fs +++ b/src/DotNetLightning.Core/Channel/ChannelTypes.fs @@ -283,13 +283,13 @@ type ChannelEvent = | WeAcceptedUpdateAddHTLC of newCommitments: Commitments | WeAcceptedOperationFulfillHTLC of msg: UpdateFulfillHTLCMsg * newCommitments: Commitments - | WeAcceptedFulfillHTLC of msg: UpdateFulfillHTLCMsg * origin: HTLCSource * htlc: UpdateAddHTLCMsg * newCommitments: Commitments + | WeAcceptedFulfillHTLC of msg: UpdateFulfillHTLCMsg * origin: Option * htlc: UpdateAddHTLCMsg * newCommitments: Commitments | WeAcceptedOperationFailHTLC of msg: UpdateFailHTLCMsg * newCommitments: Commitments - | WeAcceptedFailHTLC of origin: HTLCSource * msg: UpdateAddHTLCMsg * nextCommitments: Commitments + | WeAcceptedFailHTLC of origin: Option * msg: UpdateAddHTLCMsg * nextCommitments: Commitments | WeAcceptedOperationFailMalformedHTLC of msg: UpdateFailMalformedHTLCMsg * newCommitments: Commitments - | WeAcceptedFailMalformedHTLC of origin: HTLCSource * msg: UpdateAddHTLCMsg * newCommitments: Commitments + | WeAcceptedFailMalformedHTLC of origin: Option * msg: UpdateAddHTLCMsg * newCommitments: Commitments | WeAcceptedOperationUpdateFee of msg: UpdateFeeMsg * nextCommitments: Commitments | WeAcceptedUpdateFee of msg: UpdateFeeMsg * newCommitments: Commitments diff --git a/src/DotNetLightning.Core/Channel/CommitmentsModule.fs b/src/DotNetLightning.Core/Channel/CommitmentsModule.fs index 09246f556..5950e1597 100644 --- a/src/DotNetLightning.Core/Channel/CommitmentsModule.fs +++ b/src/DotNetLightning.Core/Channel/CommitmentsModule.fs @@ -137,7 +137,7 @@ module internal Commitments = match cm.GetHTLCCrossSigned(Direction.Out, msg.HTLCId) with | Some htlc when htlc.PaymentHash = msg.PaymentPreimage.Hash -> let commitments = cm.AddRemoteProposal(msg) - let origin = cm.OriginChannels |> Map.find(msg.HTLCId) + let origin = cm.OriginChannels |> Map.tryFind(msg.HTLCId) [WeAcceptedFulfillHTLC(msg, origin, htlc, commitments)] |> Ok | Some htlc -> (htlc.PaymentHash, msg.PaymentPreimage) @@ -178,7 +178,7 @@ module internal Commitments = | false, _ -> msg.HTLCId |> htlcOriginNotKnown let nextC = cm.AddRemoteProposal(msg) - return [WeAcceptedFailHTLC(o, htlc, nextC)] + return [WeAcceptedFailHTLC(Some o, htlc, nextC)] } | None -> msg.HTLCId |> unknownHTLCId @@ -216,7 +216,7 @@ module internal Commitments = | false, _ -> msg.HTLCId |> htlcOriginNotKnown let nextC = cm.AddRemoteProposal(msg) - return [WeAcceptedFailMalformedHTLC(o, htlc, nextC)] + return [WeAcceptedFailMalformedHTLC(Some o, htlc, nextC)] } | None -> msg.HTLCId |> unknownHTLCId From d0b91b76e7a0d35a8805b43e2e4c9cb44b0721ef Mon Sep 17 00:00:00 2001 From: Afshin Arani Date: Wed, 16 Jun 2021 12:17:53 +0430 Subject: [PATCH 14/15] nit fixes --- .../Channel/CommitmentsModule.fs | 8 +++--- .../Crypto/KeyExtensions.fs | 6 +++- src/DotNetLightning.Core/Crypto/Sphinx.fs | 9 +++--- .../DotNetLightning.Core.fsproj | 4 +-- .../Serialization/TLVs.fs | 8 +++--- .../Transactions/Transactions.fs | 4 +-- src/DotNetLightning.Core/Utils/Extensions.fs | 28 +++++++++---------- .../Utils/HTLCOfferedExtensions.fs | 25 +++++++++-------- .../Utils/HTLCReceivedExtensions.fs | 25 +++++++++-------- 9 files changed, 61 insertions(+), 56 deletions(-) diff --git a/src/DotNetLightning.Core/Channel/CommitmentsModule.fs b/src/DotNetLightning.Core/Channel/CommitmentsModule.fs index 5950e1597..90292a41a 100644 --- a/src/DotNetLightning.Core/Channel/CommitmentsModule.fs +++ b/src/DotNetLightning.Core/Channel/CommitmentsModule.fs @@ -137,7 +137,7 @@ module internal Commitments = match cm.GetHTLCCrossSigned(Direction.Out, msg.HTLCId) with | Some htlc when htlc.PaymentHash = msg.PaymentPreimage.Hash -> let commitments = cm.AddRemoteProposal(msg) - let origin = cm.OriginChannels |> Map.tryFind(msg.HTLCId) + let origin = cm.OriginChannels |> Map.tryFind msg.HTLCId [WeAcceptedFulfillHTLC(msg, origin, htlc, commitments)] |> Ok | Some htlc -> (htlc.PaymentHash, msg.PaymentPreimage) @@ -345,10 +345,10 @@ module internal Commitments = Transactions.checkTxFinalized signedCommitTx localCommitTx.WhichInput sigPair |> expectTransactionError let! finalizedCommitTx = tmp - let sortedHTLCTXs = Helpers.sortBothHTLCs htlcTimeoutTxs htlcSuccessTxs + let sortedHTLCTXs = Helpers.sortBothHTLCs htlcTimeoutTxs htlcSuccessTxs do! checkSignatureCountMismatch sortedHTLCTXs msg - let HTLCTxsAndSignatures = + let htlcTxsAndSignatures = sortedHTLCTXs |> List.zip (msg.HTLCSignatures) |> List.map(fun (remoteSig, htlc) -> @@ -371,7 +371,7 @@ module internal Commitments = | _ -> failwith "Unreachable!" let! txList = - HTLCTxsAndSignatures + htlcTxsAndSignatures |> List.map(checkHTLCSig) |> List.sequenceResultA |> expectTransactionErrors diff --git a/src/DotNetLightning.Core/Crypto/KeyExtensions.fs b/src/DotNetLightning.Core/Crypto/KeyExtensions.fs index a105fa0cb..ff534c4ff 100644 --- a/src/DotNetLightning.Core/Crypto/KeyExtensions.fs +++ b/src/DotNetLightning.Core/Crypto/KeyExtensions.fs @@ -286,7 +286,11 @@ module KeyExtensions = : TransactionSignature * PSBT = let htlcPrivKey = perCommitmentPoint.DeriveHtlcPrivKey this.HtlcBasepointSecret let htlcPubKey = htlcPrivKey.HtlcPubKey() - psbt.Settings.CustomBuilderExtensions <- ([new HTLCReceivedExtensions() :> BuilderExtension; new HTLCOfferedExtensions():> BuilderExtension] |> Seq.ofList) + psbt.Settings.CustomBuilderExtensions <- + [ + HtlcReceivedExtensions() :> BuilderExtension + HtlcOfferedExtensions() :> BuilderExtension + ] |> Seq.ofList psbt.SignWithKeys(htlcPrivKey.RawKey()) |> ignore match psbt.GetMatchingSig(htlcPubKey.RawPubKey()) with | Some signature -> (signature, psbt) diff --git a/src/DotNetLightning.Core/Crypto/Sphinx.fs b/src/DotNetLightning.Core/Crypto/Sphinx.fs index 4fc0ecc86..4c7e8b7dd 100644 --- a/src/DotNetLightning.Core/Crypto/Sphinx.fs +++ b/src/DotNetLightning.Core/Crypto/Sphinx.fs @@ -91,7 +91,7 @@ module Sphinx = computeEphemeralPublicKeysAndSharedSecretsCore (sessionKey) (pubKeys |> List.tail) ([ephemeralPK0]) ([blindingFactor0]) ([secret0]) - let rec internal generateFiller (keyType: string) (payloads: byte[] list) (sharedSecrets: Key list) = + let rec internal generateFiller (keyType: string) (payloads: list>) (sharedSecrets: list) = let filler_size = payloads.[1..] |> List.sumBy (fun payload -> payload.Length + MacLength) @@ -106,7 +106,6 @@ module Sphinx = payloads.[..i-1] |> List.sumBy (fun payload -> payload.Length + MacLength) - let filler_start = HopDataSize - filler_offset let filler_end = HopDataSize + payloads.[i].Length + MacLength let filler_len = filler_end - filler_start @@ -197,14 +196,14 @@ module Sphinx = // set of filler bytes by using chacha20 with a key derived from the session // key. let DeterministicPacketFiller (sessionKey: Key) = - generateStream(generateKey("pad",sessionKey.ToBytes()), 1300) + generateStream(generateKey("pad", sessionKey.ToBytes()), HopDataSize) // BlankPacketFiller is a packet filler that doesn't attempt to fill out the // packet at all. It should ONLY be used for generating test vectors or other // instances that required deterministic packet generation. [] let BlankPacketFiller _= - Array.zeroCreate 1300 + Array.zeroCreate HopDataSize type PacketAndSecrets = { Packet: OnionPacket @@ -213,7 +212,7 @@ module Sphinx = SharedSecrets: (Key * PubKey) list } with - static member Create (sessionKey: Key, pubKeys: PubKey list, payloads: byte[] list, ad: byte[], initialPacketFiller: Key -> byte[]) = + static member Create (sessionKey: Key, pubKeys: list, payloads: list>, ad: array, initialPacketFiller: Key -> array) = let (ephemeralPubKeys, sharedSecrets) = computeEphemeralPublicKeysAndSharedSecrets (sessionKey) (pubKeys) let filler = generateFiller "rho" payloads sharedSecrets diff --git a/src/DotNetLightning.Core/DotNetLightning.Core.fsproj b/src/DotNetLightning.Core/DotNetLightning.Core.fsproj index 4b48d086d..2915773bb 100644 --- a/src/DotNetLightning.Core/DotNetLightning.Core.fsproj +++ b/src/DotNetLightning.Core/DotNetLightning.Core.fsproj @@ -46,8 +46,8 @@ - - + + diff --git a/src/DotNetLightning.Core/Serialization/TLVs.fs b/src/DotNetLightning.Core/Serialization/TLVs.fs index 881d357fa..f06bbb24e 100644 --- a/src/DotNetLightning.Core/Serialization/TLVs.fs +++ b/src/DotNetLightning.Core/Serialization/TLVs.fs @@ -108,18 +108,18 @@ type HopPayloadTLV = static member FromGenericTLV(tlv: GenericTLV) = match tlv.Type with | 2UL -> - UInt64.FromTruncatedBytes(tlv.Value) + UInt64.FromTruncatedBytes tlv.Value |> LNMoney.MilliSatoshis |> AmountToForward | 4UL -> - UInt32.FromTruncatedBytes(tlv.Value) + UInt32.FromTruncatedBytes tlv.Value |> OutgoingCLTV | 6UL -> - ShortChannelId.From8Bytes(tlv.Value) + ShortChannelId.From8Bytes tlv.Value |> ShortChannelId | 8UL -> let secret = tlv.Value.[0..PaymentSecret.LENGTH - 1] |> PaymentSecret.FromByteArray - let totalMSat = UInt64.FromTruncatedBytes(tlv.Value.[PaymentSecret.LENGTH..]) |> LNMoney.MilliSatoshis + let totalMSat = UInt64.FromTruncatedBytes tlv.Value.[PaymentSecret.LENGTH..] |> LNMoney.MilliSatoshis (secret, totalMSat) |> PaymentData | _ -> Unknown tlv diff --git a/src/DotNetLightning.Core/Transactions/Transactions.fs b/src/DotNetLightning.Core/Transactions/Transactions.fs index 466db2da8..8e17270c9 100644 --- a/src/DotNetLightning.Core/Transactions/Transactions.fs +++ b/src/DotNetLightning.Core/Transactions/Transactions.fs @@ -524,7 +524,7 @@ module Transactions = let dest = Scripts.toLocalDelayed localRevocationPubKey toLocalDelay localDelayedPaymentPubKey // we have already done dust limit check above txb.DustPrevention <- false - txb.Extensions.Add (new HTLCOfferedExtensions()) + txb.Extensions.Add (HtlcOfferedExtensions()) let tx = txb.AddCoins(scriptCoin) .Send(dest.WitHash, amount) .SendFees(fee) @@ -567,7 +567,7 @@ module Transactions = ScriptCoin(coin, redeem) let dest = Scripts.toLocalDelayed localRevocationPubKey toLocalDelay localDelayedPaymentPubKey // we have already done dust limit check above - txb.Extensions.Add (new HTLCReceivedExtensions()) + txb.Extensions.Add (HtlcReceivedExtensions()) txb.DustPrevention <- false let tx = txb.AddCoins(scriptCoin) .Send(dest.WitHash, amount) diff --git a/src/DotNetLightning.Core/Utils/Extensions.fs b/src/DotNetLightning.Core/Utils/Extensions.fs index ea8e9545b..55afa2459 100644 --- a/src/DotNetLightning.Core/Utils/Extensions.fs +++ b/src/DotNetLightning.Core/Utils/Extensions.fs @@ -60,21 +60,21 @@ type System.UInt64 with buf member private x.GetLeadingZerosCount() = - if (x = 0UL) then + if x = 0UL then 8 - else if (x &&& 0xffffffffffffff00UL = 0UL) then + elif x &&& 0xffffffffffffff00UL = 0UL then 7 - else if (x &&& 0xffffffffffff0000UL = 0UL) then + elif x &&& 0xffffffffffff0000UL = 0UL then 6 - else if (x &&& 0xffffffffff000000UL = 0UL) then + elif x &&& 0xffffffffff000000UL = 0UL then 5 - else if (x &&& 0xffffffff00000000UL = 0UL) then + elif x &&& 0xffffffff00000000UL = 0UL then 4 - else if (x &&& 0xffffff0000000000UL = 0UL) then + elif x &&& 0xffffff0000000000UL = 0UL then 3 - else if (x &&& 0xffff000000000000UL = 0UL) then + elif x &&& 0xffff000000000000UL = 0UL then 2 - else if (x &&& 0xff00000000000000UL = 0UL) then + elif x &&& 0xff00000000000000UL = 0UL then 1 else 0 @@ -82,7 +82,7 @@ type System.UInt64 with member x.GetTruncatedBytes() = let numZeros = x.GetLeadingZerosCount() - x.GetBytesBigEndian() |> Array.skip(numZeros) + x.GetBytesBigEndian() |> Array.skip numZeros //TODO: better error handling? static member FromTruncatedBytes(bytes: array): uint64 = @@ -114,13 +114,13 @@ type System.UInt32 with BitConverter.ToUInt32(bytes, 0) member private x.GetLeadingZerosCount() = - if (x = 0u) then + if x = 0u then 4 - else if (x &&& 0xffffff00u = 0u) then + elif x &&& 0xffffff00u = 0u then 3 - else if (x &&& 0xffff0000u = 0u) then + elif x &&& 0xffff0000u = 0u then 2 - else if (x &&& 0xff000000u = 0u) then + elif x &&& 0xff000000u = 0u then 1 else 0 @@ -128,7 +128,7 @@ type System.UInt32 with member x.GetTruncatedBytes() = let numZeros = x.GetLeadingZerosCount() - x.GetBytesBigEndian() |> Array.skip(numZeros) + x.GetBytesBigEndian() |> Array.skip numZeros //TODO: better error handling? static member FromTruncatedBytes(bytes: array): uint32 = diff --git a/src/DotNetLightning.Core/Utils/HTLCOfferedExtensions.fs b/src/DotNetLightning.Core/Utils/HTLCOfferedExtensions.fs index 47d45a2f4..ce8805f93 100644 --- a/src/DotNetLightning.Core/Utils/HTLCOfferedExtensions.fs +++ b/src/DotNetLightning.Core/Utils/HTLCOfferedExtensions.fs @@ -9,12 +9,12 @@ open ResultUtils open ResultUtils.Portability open Newtonsoft.Json -type HTLCOfferedParameters = { +type HtlcOfferedParameters = { LocalHtlcPubKey: HtlcPubKey RemoteHtlcPubKey: HtlcPubKey } with - static member TryExtractParameters (scriptPubKey: Script): Option = + static member TryExtractParameters (scriptPubKey: Script): Option = let ops = scriptPubKey.ToOps() // we have to collect it into a list and convert back to a seq @@ -23,7 +23,6 @@ type HTLCOfferedParameters = { |> List.ofSeq |> Seq.ofList - Console.WriteLine(JsonConvert.SerializeObject(ops)) let checkOpCode(opcodeType: OpcodeType) = seqParser { let! op = SeqParser.next() if op.Code = opcodeType then @@ -88,31 +87,33 @@ type HTLCOfferedParameters = { | Ok data -> Some data | Error _consumeAllError -> None -type internal HTLCOfferedExtensions() = +type internal HtlcOfferedExtensions() = inherit BuilderExtension() override self.CanGenerateScriptSig (scriptPubKey: Script): bool = - (HTLCOfferedParameters.TryExtractParameters scriptPubKey).IsSome + (HtlcOfferedParameters.TryExtractParameters scriptPubKey).IsSome override self.GenerateScriptSig(scriptPubKey: Script, keyRepo: IKeyRepository, signer: ISigner): Script = let parameters = - match (HTLCOfferedParameters.TryExtractParameters scriptPubKey) with + match (HtlcOfferedParameters.TryExtractParameters scriptPubKey) with | Some parameters -> parameters | None -> failwith "NBitcoin should not call this unless CanGenerateScriptSig returns true" - let pubKey = keyRepo.FindKey scriptPubKey + let pubKeyOpt = + keyRepo.FindKey scriptPubKey + |> Option.ofObj // FindKey will return null if it can't find a key for // scriptPubKey. If we can't find a valid key then this method // should return null, indicating to NBitcoin that the sigScript // could not be generated. - match pubKey with - | null -> null - | _ when pubKey = parameters.LocalHtlcPubKey.RawPubKey() -> + match pubKeyOpt with + | None -> null + | Some pubKey when pubKey = parameters.LocalHtlcPubKey.RawPubKey() -> let localHtlcSig = signer.Sign (parameters.LocalHtlcPubKey.RawPubKey()) Script [ Op.GetPushOp (localHtlcSig.ToBytes()) ] - | _ when pubKey = parameters.RemoteHtlcPubKey.RawPubKey() -> + | Some pubKey when pubKey = parameters.RemoteHtlcPubKey.RawPubKey() -> let remoteHtlcSig = signer.Sign (parameters.RemoteHtlcPubKey.RawPubKey()) Script [ Op.GetPushOp (remoteHtlcSig.ToBytes()) @@ -138,7 +139,7 @@ type internal HTLCOfferedExtensions() = raise <| NotSupportedException() override self.IsCompatibleKey(pubKey: PubKey, scriptPubKey: Script): bool = - match HTLCOfferedParameters.TryExtractParameters scriptPubKey with + match HtlcOfferedParameters.TryExtractParameters scriptPubKey with | None -> false | Some parameters -> parameters.LocalHtlcPubKey.RawPubKey() = pubKey diff --git a/src/DotNetLightning.Core/Utils/HTLCReceivedExtensions.fs b/src/DotNetLightning.Core/Utils/HTLCReceivedExtensions.fs index 15a1fc645..fdb22415f 100644 --- a/src/DotNetLightning.Core/Utils/HTLCReceivedExtensions.fs +++ b/src/DotNetLightning.Core/Utils/HTLCReceivedExtensions.fs @@ -9,12 +9,12 @@ open ResultUtils open ResultUtils.Portability open Newtonsoft.Json -type HTLCReceivedParameters = { +type HtlcReceivedParameters = { LocalHtlcPubKey: HtlcPubKey RemoteHtlcPubKey: HtlcPubKey } with - static member TryExtractParameters (scriptPubKey: Script): Option = + static member TryExtractParameters (scriptPubKey: Script): Option = let ops = scriptPubKey.ToOps() // we have to collect it into a list and convert back to a seq @@ -23,7 +23,6 @@ type HTLCReceivedParameters = { |> List.ofSeq |> Seq.ofList - Console.WriteLine(JsonConvert.SerializeObject(ops)) let checkOpCode(opcodeType: OpcodeType) = seqParser { let! op = SeqParser.next() if op.Code = opcodeType then @@ -94,31 +93,33 @@ type HTLCReceivedParameters = { | Ok data -> Some data | Error _consumeAllError -> None -type internal HTLCReceivedExtensions() = +type internal HtlcReceivedExtensions() = inherit BuilderExtension() override self.CanGenerateScriptSig (scriptPubKey: Script): bool = - (HTLCReceivedParameters.TryExtractParameters scriptPubKey).IsSome + (HtlcReceivedParameters.TryExtractParameters scriptPubKey).IsSome override self.GenerateScriptSig(scriptPubKey: Script, keyRepo: IKeyRepository, signer: ISigner): Script = let parameters = - match (HTLCReceivedParameters.TryExtractParameters scriptPubKey) with + match (HtlcReceivedParameters.TryExtractParameters scriptPubKey) with | Some parameters -> parameters | None -> failwith "NBitcoin should not call this unless CanGenerateScriptSig returns true" - let pubKey = keyRepo.FindKey scriptPubKey + let pubKeyOpt = + keyRepo.FindKey scriptPubKey + |> Option.ofObj // FindKey will return null if it can't find a key for // scriptPubKey. If we can't find a valid key then this method // should return null, indicating to NBitcoin that the sigScript // could not be generated. - match pubKey with - | null -> null - | _ when pubKey = parameters.LocalHtlcPubKey.RawPubKey() -> + match pubKeyOpt with + | None -> null + | Some pubKey when pubKey = parameters.LocalHtlcPubKey.RawPubKey() -> let localHtlcSig = signer.Sign (parameters.LocalHtlcPubKey.RawPubKey()) Script [ Op.GetPushOp (localHtlcSig.ToBytes()) ] - | _ when pubKey = parameters.RemoteHtlcPubKey.RawPubKey() -> + | Some pubKey when pubKey = parameters.RemoteHtlcPubKey.RawPubKey() -> let remoteHtlcSig = signer.Sign (parameters.RemoteHtlcPubKey.RawPubKey()) Script [ Op.GetPushOp (remoteHtlcSig.ToBytes()) @@ -144,7 +145,7 @@ type internal HTLCReceivedExtensions() = raise <| NotSupportedException() override self.IsCompatibleKey(pubKey: PubKey, scriptPubKey: Script): bool = - match HTLCReceivedParameters.TryExtractParameters scriptPubKey with + match HtlcReceivedParameters.TryExtractParameters scriptPubKey with | None -> false | Some parameters -> parameters.LocalHtlcPubKey.RawPubKey() = pubKey From 6e36d902388b671c512704386ed58a3197b65431 Mon Sep 17 00:00:00 2001 From: Afshin Arani Date: Wed, 16 Jun 2021 13:57:38 +0430 Subject: [PATCH 15/15] Use sign function to sign HtlcTx PSBT sign function needs transaction builder for any non-predefined script template --- .../Channel/CommitmentsModule.fs | 4 +- .../Crypto/KeyExtensions.fs | 18 -- .../DotNetLightning.Core.fsproj | 2 - .../Transactions/Transactions.fs | 9 +- .../Utils/HTLCOfferedExtensions.fs | 148 ----------------- .../Utils/HTLCReceivedExtensions.fs | 154 ------------------ 6 files changed, 9 insertions(+), 326 deletions(-) delete mode 100644 src/DotNetLightning.Core/Utils/HTLCOfferedExtensions.fs delete mode 100644 src/DotNetLightning.Core/Utils/HTLCReceivedExtensions.fs diff --git a/src/DotNetLightning.Core/Channel/CommitmentsModule.fs b/src/DotNetLightning.Core/Channel/CommitmentsModule.fs index 90292a41a..85358e1f2 100644 --- a/src/DotNetLightning.Core/Channel/CommitmentsModule.fs +++ b/src/DotNetLightning.Core/Channel/CommitmentsModule.fs @@ -287,7 +287,7 @@ module internal Commitments = let htlcSigs = sortedHTLCTXs |> List.map( - (fun htlc -> channelPrivKeys.SignHtlcTx htlc.Value remoteNextPerCommitmentPoint) + (fun htlc -> (signHtlcTx htlc channelPrivKeys remoteNextPerCommitmentPoint)) >> fst >> (fun txSig -> txSig.Signature) ) @@ -352,7 +352,7 @@ module internal Commitments = sortedHTLCTXs |> List.zip (msg.HTLCSignatures) |> List.map(fun (remoteSig, htlc) -> - channelPrivKeys.SignHtlcTx htlc.Value localPerCommitmentPoint |> fst, htlc, remoteSig + signHtlcTx htlc channelPrivKeys localPerCommitmentPoint |> fst, htlc, remoteSig ) let remoteHTLCPubKey = localPerCommitmentPoint.DeriveHtlcPubKey remoteChannelKeys.HtlcBasepoint diff --git a/src/DotNetLightning.Core/Crypto/KeyExtensions.fs b/src/DotNetLightning.Core/Crypto/KeyExtensions.fs index ff534c4ff..8a1199e9c 100644 --- a/src/DotNetLightning.Core/Crypto/KeyExtensions.fs +++ b/src/DotNetLightning.Core/Crypto/KeyExtensions.fs @@ -281,24 +281,6 @@ module KeyExtensions = | Some signature -> (signature, psbt) | None -> failwithf "Failed to get signature for %A with funding pub key (%A). This should never happen" psbt fundingPubKey - member this.SignHtlcTx (psbt: PSBT) - (perCommitmentPoint: PerCommitmentPoint) - : TransactionSignature * PSBT = - let htlcPrivKey = perCommitmentPoint.DeriveHtlcPrivKey this.HtlcBasepointSecret - let htlcPubKey = htlcPrivKey.HtlcPubKey() - psbt.Settings.CustomBuilderExtensions <- - [ - HtlcReceivedExtensions() :> BuilderExtension - HtlcOfferedExtensions() :> BuilderExtension - ] |> Seq.ofList - psbt.SignWithKeys(htlcPrivKey.RawKey()) |> ignore - match psbt.GetMatchingSig(htlcPubKey.RawPubKey()) with - | Some signature -> (signature, psbt) - | None -> - failwithf - "failed to get htlc signature for %A. with htlc pubkey (%A) and perCommitmentPoint (%A)" - psbt htlcPubKey perCommitmentPoint - /// This is the node-wide master key which is also used for /// transport-level encryption. The channel's keys are derived from /// this via BIP32 key derivation where `channelIndex` is the child diff --git a/src/DotNetLightning.Core/DotNetLightning.Core.fsproj b/src/DotNetLightning.Core/DotNetLightning.Core.fsproj index 2915773bb..cf09a5607 100644 --- a/src/DotNetLightning.Core/DotNetLightning.Core.fsproj +++ b/src/DotNetLightning.Core/DotNetLightning.Core.fsproj @@ -46,8 +46,6 @@ - - diff --git a/src/DotNetLightning.Core/Transactions/Transactions.fs b/src/DotNetLightning.Core/Transactions/Transactions.fs index 8e17270c9..ca77acc1e 100644 --- a/src/DotNetLightning.Core/Transactions/Transactions.fs +++ b/src/DotNetLightning.Core/Transactions/Transactions.fs @@ -498,6 +498,13 @@ module Transactions = | Error e -> failwithf "%A" e let sign(tx, key) = signCore(tx, key, true) + let signHtlcTx (htlc: IHTLCTx) (channelPrivKeys: ChannelPrivKeys) (perCommitmentPoint: PerCommitmentPoint) = + let htlcPrivKey = + perCommitmentPoint.DeriveHtlcPrivKey + channelPrivKeys.HtlcBasepointSecret + + sign(htlc, htlcPrivKey.RawKey()) + let makeHTLCTimeoutTx (commitTx: Transaction) (localDustLimit: Money) (localRevocationPubKey: RevocationPubKey) @@ -524,7 +531,6 @@ module Transactions = let dest = Scripts.toLocalDelayed localRevocationPubKey toLocalDelay localDelayedPaymentPubKey // we have already done dust limit check above txb.DustPrevention <- false - txb.Extensions.Add (HtlcOfferedExtensions()) let tx = txb.AddCoins(scriptCoin) .Send(dest.WitHash, amount) .SendFees(fee) @@ -567,7 +573,6 @@ module Transactions = ScriptCoin(coin, redeem) let dest = Scripts.toLocalDelayed localRevocationPubKey toLocalDelay localDelayedPaymentPubKey // we have already done dust limit check above - txb.Extensions.Add (HtlcReceivedExtensions()) txb.DustPrevention <- false let tx = txb.AddCoins(scriptCoin) .Send(dest.WitHash, amount) diff --git a/src/DotNetLightning.Core/Utils/HTLCOfferedExtensions.fs b/src/DotNetLightning.Core/Utils/HTLCOfferedExtensions.fs deleted file mode 100644 index ce8805f93..000000000 --- a/src/DotNetLightning.Core/Utils/HTLCOfferedExtensions.fs +++ /dev/null @@ -1,148 +0,0 @@ -namespace DotNetLightning.Utils - -open System -open NBitcoin -open NBitcoin.BuilderExtensions -open DotNetLightning.Utils - -open ResultUtils -open ResultUtils.Portability -open Newtonsoft.Json - -type HtlcOfferedParameters = { - LocalHtlcPubKey: HtlcPubKey - RemoteHtlcPubKey: HtlcPubKey -} - with - static member TryExtractParameters (scriptPubKey: Script): Option = - 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 { - let! op = SeqParser.next() - if op.Code = opcodeType then - return () - else - return! SeqParser.abort() - } - - let parseToCompletionResult = - SeqParser.parseToCompletion ops <| seqParser { - do! checkOpCode OpcodeType.OP_DUP - do! checkOpCode OpcodeType.OP_HASH160 - let! _opRevocationPubKey = SeqParser.next() - do! checkOpCode OpcodeType.OP_EQUAL - do! checkOpCode OpcodeType.OP_IF - do! checkOpCode OpcodeType.OP_CHECKSIG - do! checkOpCode OpcodeType.OP_ELSE - let! opRemoteHtlcPubKey = SeqParser.next() - let! remoteHtlcPubKey = seqParser { - match opRemoteHtlcPubKey.PushData with - | null -> return! SeqParser.abort() - | bytes -> - try - return bytes |> PubKey |> HtlcPubKey.HtlcPubKey - with - | :? FormatException -> return! SeqParser.abort() - } - do! checkOpCode OpcodeType.OP_SWAP - do! checkOpCode OpcodeType.OP_SIZE - let! _blabla = SeqParser.next() - do! checkOpCode OpcodeType.OP_EQUAL - do! checkOpCode OpcodeType.OP_NOTIF - do! checkOpCode OpcodeType.OP_DROP - do! checkOpCode OpcodeType.OP_2 - do! checkOpCode OpcodeType.OP_SWAP - let! opLocalHtlcPubKey = SeqParser.next() - let! localHtlcPubKey = seqParser { - match opLocalHtlcPubKey.PushData with - | null -> return! SeqParser.abort() - | bytes -> - try - return bytes |> PubKey |> HtlcPubKey.HtlcPubKey - with - | :? FormatException -> return! SeqParser.abort() - } - do! checkOpCode OpcodeType.OP_2 - do! checkOpCode OpcodeType.OP_CHECKMULTISIG - do! checkOpCode OpcodeType.OP_ELSE - do! checkOpCode OpcodeType.OP_HASH160 - let! _opPaymentHash = SeqParser.next() - do! checkOpCode OpcodeType.OP_EQUALVERIFY - do! checkOpCode OpcodeType.OP_CHECKSIG - do! checkOpCode OpcodeType.OP_ENDIF - do! checkOpCode OpcodeType.OP_ENDIF - - return { - LocalHtlcPubKey = localHtlcPubKey - RemoteHtlcPubKey = remoteHtlcPubKey - } - } - match parseToCompletionResult with - | Ok data -> Some data - | Error _consumeAllError -> None - -type internal HtlcOfferedExtensions() = - inherit BuilderExtension() - override self.CanGenerateScriptSig (scriptPubKey: Script): bool = - (HtlcOfferedParameters.TryExtractParameters scriptPubKey).IsSome - - override self.GenerateScriptSig(scriptPubKey: Script, keyRepo: IKeyRepository, signer: ISigner): Script = - let parameters = - match (HtlcOfferedParameters.TryExtractParameters scriptPubKey) with - | Some parameters -> parameters - | None -> - failwith - "NBitcoin should not call this unless CanGenerateScriptSig returns true" - let pubKeyOpt = - keyRepo.FindKey scriptPubKey - |> Option.ofObj - // FindKey will return null if it can't find a key for - // scriptPubKey. If we can't find a valid key then this method - // should return null, indicating to NBitcoin that the sigScript - // could not be generated. - match pubKeyOpt with - | None -> null - | Some pubKey when pubKey = parameters.LocalHtlcPubKey.RawPubKey() -> - let localHtlcSig = signer.Sign (parameters.LocalHtlcPubKey.RawPubKey()) - Script [ - Op.GetPushOp (localHtlcSig.ToBytes()) - ] - | Some pubKey when pubKey = parameters.RemoteHtlcPubKey.RawPubKey() -> - let remoteHtlcSig = signer.Sign (parameters.RemoteHtlcPubKey.RawPubKey()) - Script [ - Op.GetPushOp (remoteHtlcSig.ToBytes()) - ] - | _ -> null - - override self.CanDeduceScriptPubKey(_scriptSig: Script): bool = - false - - override self.DeduceScriptPubKey(_scriptSig: Script): Script = - raise <| NotSupportedException() - - override self.CanEstimateScriptSigSize(_scriptPubKey: Script): bool = - false - - override self.EstimateScriptSigSize(_scriptPubKey: Script): int = - raise <| NotSupportedException() - - override self.CanCombineScriptSig(_scriptPubKey: Script, _a: Script, _b: Script): bool = - false - - override self.CombineScriptSig(_scriptPubKey: Script, _a: Script, _b: Script): Script = - raise <| NotSupportedException() - - override self.IsCompatibleKey(pubKey: PubKey, scriptPubKey: Script): bool = - match HtlcOfferedParameters.TryExtractParameters scriptPubKey with - | None -> false - | Some parameters -> - parameters.LocalHtlcPubKey.RawPubKey() = pubKey - || parameters.RemoteHtlcPubKey.RawPubKey() = pubKey - - diff --git a/src/DotNetLightning.Core/Utils/HTLCReceivedExtensions.fs b/src/DotNetLightning.Core/Utils/HTLCReceivedExtensions.fs deleted file mode 100644 index fdb22415f..000000000 --- a/src/DotNetLightning.Core/Utils/HTLCReceivedExtensions.fs +++ /dev/null @@ -1,154 +0,0 @@ -namespace DotNetLightning.Utils - -open System -open NBitcoin -open NBitcoin.BuilderExtensions -open DotNetLightning.Utils - -open ResultUtils -open ResultUtils.Portability -open Newtonsoft.Json - -type HtlcReceivedParameters = { - LocalHtlcPubKey: HtlcPubKey - RemoteHtlcPubKey: HtlcPubKey -} - with - static member TryExtractParameters (scriptPubKey: Script): Option = - 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 { - let! op = SeqParser.next() - if op.Code = opcodeType then - return () - else - return! SeqParser.abort() - } - - let parseToCompletionResult = - SeqParser.parseToCompletion ops <| seqParser { - do! checkOpCode OpcodeType.OP_DUP - do! checkOpCode OpcodeType.OP_HASH160 - let! _opRevocationPubKey = SeqParser.next() - //not implemented - do! checkOpCode OpcodeType.OP_EQUAL - do! checkOpCode OpcodeType.OP_IF - do! checkOpCode OpcodeType.OP_CHECKSIG - do! checkOpCode OpcodeType.OP_ELSE - let! opRemoteHTLCPubKey = SeqParser.next() - let! remoteHTLCPubKey = seqParser { - match opRemoteHTLCPubKey.PushData with - | null -> return! SeqParser.abort() - | bytes -> - try - return bytes |> PubKey |> HtlcPubKey.HtlcPubKey - with - | :? FormatException -> return! SeqParser.abort() - } - do! checkOpCode OpcodeType.OP_SWAP - do! checkOpCode OpcodeType.OP_SIZE - let! _blabla = SeqParser.next() - do! checkOpCode OpcodeType.OP_EQUAL - do! checkOpCode OpcodeType.OP_IF - do! checkOpCode OpcodeType.OP_HASH160 - let! _opPaymentHash = SeqParser.next() - do! checkOpCode OpcodeType.OP_EQUALVERIFY - do! checkOpCode OpcodeType.OP_2 - do! checkOpCode OpcodeType.OP_SWAP - let! opLocalHtlcPubKey = SeqParser.next() - let! localHtlcPubKey = seqParser { - match opLocalHtlcPubKey.PushData with - | null -> return! SeqParser.abort() - | bytes -> - try - return bytes |> PubKey |> HtlcPubKey.HtlcPubKey - with - | :? FormatException -> return! SeqParser.abort() - } - do! checkOpCode OpcodeType.OP_2 - do! checkOpCode OpcodeType.OP_CHECKMULTISIG - do! checkOpCode OpcodeType.OP_ELSE - do! checkOpCode OpcodeType.OP_DROP - let! _locktime = SeqParser.next() - do! checkOpCode OpcodeType.OP_CHECKLOCKTIMEVERIFY - do! checkOpCode OpcodeType.OP_DROP - do! checkOpCode OpcodeType.OP_CHECKSIG - do! checkOpCode OpcodeType.OP_ENDIF - do! checkOpCode OpcodeType.OP_ENDIF - - - - return { - LocalHtlcPubKey = localHtlcPubKey - RemoteHtlcPubKey = remoteHTLCPubKey - } - } - match parseToCompletionResult with - | Ok data -> Some data - | Error _consumeAllError -> None - -type internal HtlcReceivedExtensions() = - inherit BuilderExtension() - override self.CanGenerateScriptSig (scriptPubKey: Script): bool = - (HtlcReceivedParameters.TryExtractParameters scriptPubKey).IsSome - - override self.GenerateScriptSig(scriptPubKey: Script, keyRepo: IKeyRepository, signer: ISigner): Script = - let parameters = - match (HtlcReceivedParameters.TryExtractParameters scriptPubKey) with - | Some parameters -> parameters - | None -> - failwith - "NBitcoin should not call this unless CanGenerateScriptSig returns true" - let pubKeyOpt = - keyRepo.FindKey scriptPubKey - |> Option.ofObj - // FindKey will return null if it can't find a key for - // scriptPubKey. If we can't find a valid key then this method - // should return null, indicating to NBitcoin that the sigScript - // could not be generated. - match pubKeyOpt with - | None -> null - | Some pubKey when pubKey = parameters.LocalHtlcPubKey.RawPubKey() -> - let localHtlcSig = signer.Sign (parameters.LocalHtlcPubKey.RawPubKey()) - Script [ - Op.GetPushOp (localHtlcSig.ToBytes()) - ] - | Some pubKey when pubKey = parameters.RemoteHtlcPubKey.RawPubKey() -> - let remoteHtlcSig = signer.Sign (parameters.RemoteHtlcPubKey.RawPubKey()) - Script [ - Op.GetPushOp (remoteHtlcSig.ToBytes()) - ] - | _ -> null - - override self.CanDeduceScriptPubKey(_scriptSig: Script): bool = - false - - override self.DeduceScriptPubKey(_scriptSig: Script): Script = - raise <| NotSupportedException() - - override self.CanEstimateScriptSigSize(_scriptPubKey: Script): bool = - false - - override self.EstimateScriptSigSize(_scriptPubKey: Script): int = - raise <| NotSupportedException() - - override self.CanCombineScriptSig(_scriptPubKey: Script, _a: Script, _b: Script): bool = - false - - override self.CombineScriptSig(_scriptPubKey: Script, _a: Script, _b: Script): Script = - raise <| NotSupportedException() - - override self.IsCompatibleKey(pubKey: PubKey, scriptPubKey: Script): bool = - match HtlcReceivedParameters.TryExtractParameters scriptPubKey with - | None -> false - | Some parameters -> - parameters.LocalHtlcPubKey.RawPubKey() = pubKey - || parameters.RemoteHtlcPubKey.RawPubKey() = pubKey - -