From c0747c951a5b9cfd94180102346ae3419b00f57a Mon Sep 17 00:00:00 2001 From: "Andres G. Aragoneses" Date: Mon, 25 Apr 2022 23:39:47 +0800 Subject: [PATCH] Apply automatic fantomas formatting Fixes https://github.com/joemphilips/DotNetLightning/issues/58 --- .editorconfig | 127 + src/DotNetLightning.Core/AssemblyInfo.fs | 15 +- .../Chain/ChainInterface.fs | 7 +- .../Chain/KeysInterface.fs | 42 +- src/DotNetLightning.Core/Channel/Channel.fs | 2743 ++++++----- .../Channel/ChannelConstants.fs | 27 +- .../Channel/ChannelError.fs | 817 ++-- .../Channel/ChannelHelpers.fs | 215 +- .../Channel/ChannelOperations.fs | 247 +- .../Channel/ChannelTypes.fs | 81 +- .../Channel/ChannelValidation.fs | 530 ++- .../Channel/CommitmentToLocalExtension.fs | 241 +- .../Channel/Commitments.fs | 257 +- .../Channel/CommitmentsModule.fs | 496 +- .../Channel/HTLCChannelType.fs | 41 +- src/DotNetLightning.Core/Crypto/Aezeed.fs | 443 +- .../Crypto/CryptoUtils.fs | 449 +- .../Crypto/KeyExtensions.fs | 361 +- src/DotNetLightning.Core/Crypto/OnionUtils.fs | 1 - .../Crypto/PerCommitmentSecretStore.fs | 129 +- src/DotNetLightning.Core/Crypto/ShaChain.fs | 42 +- src/DotNetLightning.Core/Crypto/Sphinx.fs | 480 +- src/DotNetLightning.Core/Payment/Amount.fs | 33 +- .../Payment/LSAT/CaveatsExtensions.fs | 19 +- .../Payment/LSAT/Constants.fs | 2 +- .../Payment/LSAT/MacaroonIdentifier.fs | 77 +- .../Payment/LSAT/Satisfier.fs | 108 +- .../Payment/LSAT/Service.fs | 99 +- .../Payment/PaymentEvents.fs | 132 +- .../Payment/PaymentRequest.fs | 1327 ++++-- .../Payment/PaymentTypes.fs | 17 +- .../Peer/ChannelMonitor.fs | 2 +- src/DotNetLightning.Core/Peer/Peer.fs | 183 +- .../Peer/PeerChannelEncryptor.fs | 1214 +++-- src/DotNetLightning.Core/Peer/PeerError.fs | 9 +- src/DotNetLightning.Core/Peer/PeerTypes.fs | 20 +- src/DotNetLightning.Core/Routing/Graph.fs | 1090 +++-- .../Routing/NetworkStats.fs | 50 +- src/DotNetLightning.Core/Routing/Router.fs | 289 +- .../Routing/RouterPrimitives.fs | 209 +- .../Routing/RouterState.fs | 195 +- .../Routing/RouterTypes.fs | 13 +- .../Serialization/BitReader.fs | 73 +- .../Serialization/BitWriter.fs | 76 +- .../Serialization/EncodedTypes.fs | 81 +- .../Serialization/Encoding.fs | 209 +- .../Serialization/Features.fs | 329 +- .../Serialization/GenericTLV.fs | 76 +- .../Serialization/LightningStream.fs | 348 +- .../Serialization/Msgs/Msgs.fs | 2001 ++++---- .../Serialization/OnionPayload.fs | 113 +- .../Serialization/TLVs.fs | 201 +- .../Transactions/CommitmentSpec.fs | 248 +- .../Transactions/Scripts.fs | 178 +- .../Transactions/TransactionError.fs | 14 +- .../Transactions/Transactions.fs | 1204 +++-- src/DotNetLightning.Core/Utils/ChannelId.fs | 5 +- src/DotNetLightning.Core/Utils/Config.fs | 81 +- src/DotNetLightning.Core/Utils/Errors.fs | 102 +- src/DotNetLightning.Core/Utils/Extensions.fs | 282 +- src/DotNetLightning.Core/Utils/Keys.fs | 224 +- src/DotNetLightning.Core/Utils/LNMoney.fs | 129 +- .../Utils/NBitcoinExtensions.fs | 58 +- src/DotNetLightning.Core/Utils/Primitives.fs | 604 ++- .../Utils/PriorityQueue.fs | 265 +- src/DotNetLightning.Core/Utils/RouteType.fs | 30 +- src/DotNetLightning.Core/Utils/SeqParser.fs | 139 +- src/DotNetLightning.Core/Utils/TxId.fs | 7 +- src/DotNetLightning.Core/Utils/UInt48.fs | 135 +- src/DotNetLightning.Core/Utils/Utils.fs | 186 +- src/ResultUtils/List.fs | 42 +- src/ResultUtils/Option.fs | 12 +- src/ResultUtils/OptionCE.fs | 88 +- src/ResultUtils/Result.fs | 390 +- src/ResultUtils/ResultCE.fs | 182 +- src/ResultUtils/ResultOp.fs | 11 +- src/ResultUtils/ResultOption.fs | 37 +- src/ResultUtils/ResultOptionCE.fs | 23 +- src/ResultUtils/ResultOptionOp.fs | 15 +- src/ResultUtils/Validation.fs | 35 +- src/ResultUtils/ValidationOp.fs | 38 +- src/TaskUtils/Ply.fs | 820 ++-- src/TaskUtils/PlyCE.fs | 68 +- src/TaskUtils/Result.fs | 117 +- src/TaskUtils/Task.fs | 28 +- src/TaskUtils/TaskOp.fs | 11 +- src/TaskUtils/TaskResult.fs | 249 +- src/TaskUtils/TaskResultCE.fs | 2 +- src/TaskUtils/TaskResultOp.fs | 11 +- .../DotNetLightning.Core.Tests/AezeedTests.fs | 598 ++- .../AssemblyInfo.fs | 40 +- .../ClaimReceivedHTLCTests.fs | 38 +- .../CommitmentToLocalExtensionTests.fs | 63 +- .../EncryptDecrypt.fs | 82 +- .../FunctionalTests.fs | 4 +- .../Generators/Generators.fs | 137 +- .../Generators/Msgs.fs | 1318 +++--- .../Generators/Payments.fs | 168 +- .../Generators/Primitives.fs | 189 +- .../GeneratorsTests.fs | 93 +- .../DotNetLightning.Core.Tests/GraphTests.fs | 646 ++- .../KeyRepositoryTests.fs | 302 +- tests/DotNetLightning.Core.Tests/LSATTests.fs | 385 +- tests/DotNetLightning.Core.Tests/Main.fs | 13 +- .../PaymentPropertyTests.fs | 57 +- .../PaymentTests.fs | 1013 ++-- .../PeerChannelEncryptorTests.fs | 1097 +++-- .../PerCommitmentSecretStoreTests.fs | 552 ++- .../PrimitivesTests.fs | 60 +- .../RouteCalculationTests.fs | 4120 +++++++++++++---- .../Serialization.fs | 3042 ++++++++---- .../SerializationPropertyTests.fs | 282 +- .../DotNetLightning.Core.Tests/SphinxTests.fs | 623 ++- .../TLVSerialize.fs | 149 +- .../TransactionBolt3TestVectorTests.fs | 1053 +++-- .../TransactionTests.fs | 1827 +++++--- .../TxOutLexicographicCompareTests.fs | 134 +- .../DotNetLightning.Core.Tests/Utils/Utils.fs | 38 +- 118 files changed, 26830 insertions(+), 13749 deletions(-) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..c244f2262 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,127 @@ +[*.{fs,fsx}] +end_of_line=lf +indent_size=4 +max_line_length=80 +fsharp_semicolon_at_end_of_line=false +fsharp_space_before_parameter=false +fsharp_space_before_lowercase_invocation=false +fsharp_space_before_uppercase_invocation=false +fsharp_space_before_class_constructor=false +fsharp_space_before_member=false +fsharp_space_before_colon=false +fsharp_space_after_comma=true +fsharp_space_before_semicolon=false +fsharp_space_after_semicolon=true +fsharp_indent_on_try_with=false +fsharp_space_around_delimiter=true +fsharp_max_if_then_else_short_width=0 +fsharp_max_infix_operator_expression=80 +fsharp_max_record_width=0 +fsharp_max_function_binding_width=0 +fsharp_max_value_binding_width=80 +fsharp_multiline_block_brackets_on_same_column=true +fsharp_newline_between_type_definition_and_members=true +fsharp_keep_if_then_in_same_line=true +fsharp_strict_mode=false +fsharp_multi_line_lambda_closing_newline=true +fsharp_disable_elmish_syntax=true + +[*.cs] +#Core editorconfig formatting - indentation + +#use soft tabs (spaces) for indentation +indent_style = space + +#Formatting - new line options + +#place catch statements on a new line +csharp_new_line_before_catch = true +#place else statements on a new line +csharp_new_line_before_else = true +#require finally statements to be on a new line after the closing brace +csharp_new_line_before_finally = true +#require members of object intializers to be on separate lines +csharp_new_line_before_members_in_object_initializers = true +#require braces to be on a new line for methods, control_blocks, object_collection_array_initializers, and types (also known as "Allman" style) +csharp_new_line_before_open_brace = methods, control_blocks, object_collection_array_initializers, types + +#Formatting - organize using options + +#sort System.* using directives alphabetically, and place them before other usings +dotnet_sort_system_directives_first = true + +#Formatting - spacing options + +#require NO space between a cast and the value +csharp_space_after_cast = false +#require a space before the colon for bases or interfaces in a type declaration +csharp_space_after_colon_in_inheritance_clause = true +#require a space after a keyword in a control flow statement such as a for loop +csharp_space_after_keywords_in_control_flow_statements = true +#require a space before the colon for bases or interfaces in a type declaration +csharp_space_before_colon_in_inheritance_clause = true +#remove space within empty argument list parentheses +csharp_space_between_method_call_empty_parameter_list_parentheses = false +#remove space between method call name and opening parenthesis +csharp_space_between_method_call_name_and_opening_parenthesis = false +#do not place space characters after the opening parenthesis and before the closing parenthesis of a method call +csharp_space_between_method_call_parameter_list_parentheses = false +#place a space character after the opening parenthesis and before the closing parenthesis of a method declaration parameter list. +csharp_space_between_method_declaration_parameter_list_parentheses = false + +#Formatting - wrapping options + +#leave code block on single line +csharp_preserve_single_line_blocks = true + +#Style - Code block preferences + +#prefer no curly braces if allowed +csharp_prefer_braces = false:suggestion + +#Style - expression bodied member options + +#prefer block bodies for methods +csharp_style_expression_bodied_methods = false:suggestion +#prefer expression-bodied members for properties +csharp_style_expression_bodied_properties = true:suggestion + +#Style - Expression-level preferences + +#prefer default over default(T) +csharp_prefer_simple_default_expression = true:suggestion +#prefer objects to be initialized using object initializers when possible +dotnet_style_object_initializer = true:suggestion + +#Style - implicit and explicit types + +#prefer var over explicit type in all cases, unless overridden by another code style rule +csharp_style_var_elsewhere = true:suggestion +#prefer var is used to declare variables with built-in system types such as int +csharp_style_var_for_built_in_types = true:suggestion +#prefer explicit type over var when the type is already mentioned on the right-hand side of a declaration +csharp_style_var_when_type_is_apparent = false:suggestion + +#Style - language keyword and framework type options + +#prefer the language keyword for local variables, method parameters, and class members, instead of the type name, for types that have a keyword to represent them +dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion + +#Style - modifier options + +#prefer accessibility modifiers to be declared except for public interface members. This will currently not differ from always and will act as future proofing for if C# adds default interface methods. +dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion + +#Style - Modifier preferences + +#when this rule is set to a list of modifiers, prefer the specified ordering. +csharp_preferred_modifier_order = public,private,protected,static,override,readonly,abstract,async:suggestion + +#Style - qualification options + +#prefer fields not to be prefaced with this. or Me. in Visual Basic +dotnet_style_qualification_for_field = false:suggestion +#prefer methods not to be prefaced with this. or Me. in Visual Basic +dotnet_style_qualification_for_method = false:suggestion +#prefer properties not to be prefaced with this. or Me. in Visual Basic +dotnet_style_qualification_for_property = false:suggestion diff --git a/src/DotNetLightning.Core/AssemblyInfo.fs b/src/DotNetLightning.Core/AssemblyInfo.fs index c5d63cca9..077de6a12 100644 --- a/src/DotNetLightning.Core/AssemblyInfo.fs +++ b/src/DotNetLightning.Core/AssemblyInfo.fs @@ -3,13 +3,13 @@ module AssemblyInfo open System.Reflection open System.Runtime.CompilerServices -[] -[] -[] -[] -[] -[] -[] +[] +[] +[] +[] +[] +[] +[] [] [] [] @@ -34,4 +34,3 @@ module internal AssemblyVersionInformation = [] let AssemblyMetadata_ReleaseChannel = "release" - diff --git a/src/DotNetLightning.Core/Chain/ChainInterface.fs b/src/DotNetLightning.Core/Chain/ChainInterface.fs index 5e2294689..53338173a 100644 --- a/src/DotNetLightning.Core/Chain/ChainInterface.fs +++ b/src/DotNetLightning.Core/Chain/ChainInterface.fs @@ -1,4 +1,5 @@ namespace DotNetLightning.Chain + open System open NBitcoin open DotNetLightning.Utils @@ -15,7 +16,8 @@ type BlockChainInstanceId = BlockChainInstanceId of string /// We want transaction index number for channel id and such. /// So not using NBitcoin.Block directly -type BlockContent = BlockHeader * BlockHeight * (uint32 * Transaction) list +type BlockContent = BlockHeader * BlockHeight * list<(uint32 * Transaction)> + type RawOnChainEvent = | BlockConnected of chainId: BlockChainInstanceId * content: BlockContent | BlockDisconnected of chainId: BlockChainInstanceId * BlockHeader @@ -23,7 +25,7 @@ type RawOnChainEvent = type OnChainEvent = | BlockConnected of content: BlockContent /// value is a list of blocks which has disappeared from the blockchain. - | BlockDisconnected of header: BlockContent list + | BlockDisconnected of header: list type IBroadCaster = @@ -36,4 +38,3 @@ type ConfirmationTarget = type IFeeEstimator = abstract member GetEstSatPer1000Weight: (ConfirmationTarget) -> FeeRatePerKw - diff --git a/src/DotNetLightning.Core/Chain/KeysInterface.fs b/src/DotNetLightning.Core/Chain/KeysInterface.fs index ed97d7e1b..5a9a560ac 100644 --- a/src/DotNetLightning.Core/Chain/KeysInterface.fs +++ b/src/DotNetLightning.Core/Chain/KeysInterface.fs @@ -9,35 +9,38 @@ open DotNetLightning.Utils open DotNetLightning.Utils.NBitcoinExtensions /// OutPoint -type StaticOutput = { - outPoint: OutPoint - output: TxOut -} +type StaticOutput = + { + outPoint: OutPoint + output: TxOut + } /// Outpoint commits to p2wsh /// P2WSH should be spend by the following witness /// ` 0 ` (with input nSequence set to self_delay) /// Outputs from HTLC-Success/Timeout tx/commitment tx -type DynamicOutputP2WSH = { - outPoint: OutPoint - key: Key - witnessScript: Script - toSelfDelay: uint16 - output: TxOut -} +type DynamicOutputP2WSH = + { + outPoint: OutPoint + key: Key + witnessScript: Script + toSelfDelay: uint16 + output: TxOut + } /// Outpoint commits to a P2WPKH /// P2WPKH should be spend by the following witness. /// ` ` /// Outputs to_remote from a commitment tx -type DynamicOutputP2WPKH = { - /// Output spendable by user wallet - outpoint: OutPoint - /// localkey = payment_basepoint_secret + SHA256(per_commitment_point || payment_basepoint) - key: Key - /// The output which is reference by the given outpoint - output: TxOut -} +type DynamicOutputP2WPKH = + { + /// Output spendable by user wallet + outpoint: OutPoint + /// localkey = payment_basepoint_secret + SHA256(per_commitment_point || payment_basepoint) + key: Key + /// The output which is reference by the given outpoint + output: TxOut + } /// When on-chain outputs are created by DotNetLightning an event is generated which informs the user thereof. /// This enum describes the format of the output and provides the OutPoint. @@ -45,4 +48,3 @@ type SpendableOutputDescriptor = | StaticOutput of StaticOutput | DynamicOutputP2WSH of DynamicOutputP2WSH | DynamicOutputP2WPKH of DynamicOutputP2WPKH - diff --git a/src/DotNetLightning.Core/Channel/Channel.fs b/src/DotNetLightning.Core/Channel/Channel.fs index 1e6de9a76..0ddf85dda 100644 --- a/src/DotNetLightning.Core/Channel/Channel.fs +++ b/src/DotNetLightning.Core/Channel/Channel.fs @@ -13,341 +13,462 @@ open System open ResultUtils open ResultUtils.Portability -type ChannelWaitingForFundingSigned = { - StaticChannelConfig: StaticChannelConfig - ChannelOptions: ChannelOptions - ChannelPrivKeys: ChannelPrivKeys - NodeSecret: NodeSecret - ChannelId: ChannelId - FundingTx: FinalizedTx - LocalSpec: CommitmentSpec - LocalCommitTx: CommitTx - RemoteCommit: RemoteCommit -} with - member this.ApplyFundingSigned (msg: FundingSignedMsg) - : Result = result { - let! finalizedLocalCommitTx = - let theirFundingPk = this.StaticChannelConfig.RemoteChannelPubKeys.FundingPubKey.RawPubKey() - let _, signedLocalCommitTx = - this.ChannelPrivKeys.SignWithFundingPrivKey this.LocalCommitTx.Value - let remoteSigPairOfLocalTx = (theirFundingPk, TransactionSignature(msg.Signature.Value, SigHash.All)) - let sigPairs = seq [ remoteSigPairOfLocalTx; ] - Transactions.checkTxFinalized signedLocalCommitTx CommitTx.WhichInput sigPairs |> expectTransactionError - let commitments = { - ProposedLocalChanges = List.empty - ProposedRemoteChanges = List.empty - LocalNextHTLCId = HTLCId.Zero - RemoteNextHTLCId = HTLCId.Zero - OriginChannels = Map.empty - } - let channel = { - SavedChannelState = { - StaticChannelConfig = this.StaticChannelConfig - RemotePerCommitmentSecrets = PerCommitmentSecretStore() - ShortChannelId = None - LocalCommit = { - Index = CommitmentNumber.FirstCommitment - Spec = this.LocalSpec - PublishableTxs = { - PublishableTxs.CommitTx = finalizedLocalCommitTx - HTLCTxs = [] - } - PendingHTLCSuccessTxs = [] +type ChannelWaitingForFundingSigned = + { + StaticChannelConfig: StaticChannelConfig + ChannelOptions: ChannelOptions + ChannelPrivKeys: ChannelPrivKeys + NodeSecret: NodeSecret + ChannelId: ChannelId + FundingTx: FinalizedTx + LocalSpec: CommitmentSpec + LocalCommitTx: CommitTx + RemoteCommit: RemoteCommit + } + + member this.ApplyFundingSigned + (msg: FundingSignedMsg) + : Result = + result { + let! finalizedLocalCommitTx = + let theirFundingPk = + this.StaticChannelConfig.RemoteChannelPubKeys.FundingPubKey.RawPubKey + () + + let _, signedLocalCommitTx = + this.ChannelPrivKeys.SignWithFundingPrivKey + this.LocalCommitTx.Value + + let remoteSigPairOfLocalTx = + (theirFundingPk, + TransactionSignature(msg.Signature.Value, SigHash.All)) + + let sigPairs = seq [ remoteSigPairOfLocalTx ] + + Transactions.checkTxFinalized + signedLocalCommitTx + CommitTx.WhichInput + sigPairs + |> expectTransactionError + + let commitments = + { + ProposedLocalChanges = List.empty + ProposedRemoteChanges = List.empty + LocalNextHTLCId = HTLCId.Zero + RemoteNextHTLCId = HTLCId.Zero + OriginChannels = Map.empty } - RemoteCommit = this.RemoteCommit - LocalChanges = LocalChanges.Zero - RemoteChanges = RemoteChanges.Zero - } - ChannelOptions = this.ChannelOptions - ChannelPrivKeys = this.ChannelPrivKeys - NodeSecret = this.NodeSecret - RemoteNextCommitInfo = None - NegotiatingState = NegotiatingState.New() - Commitments = commitments + + let channel = + { + SavedChannelState = + { + StaticChannelConfig = this.StaticChannelConfig + RemotePerCommitmentSecrets = + PerCommitmentSecretStore() + ShortChannelId = None + LocalCommit = + { + Index = CommitmentNumber.FirstCommitment + Spec = this.LocalSpec + PublishableTxs = + { + PublishableTxs.CommitTx = + finalizedLocalCommitTx + HTLCTxs = [] + } + PendingHTLCSuccessTxs = [] + } + RemoteCommit = this.RemoteCommit + LocalChanges = LocalChanges.Zero + RemoteChanges = RemoteChanges.Zero + } + ChannelOptions = this.ChannelOptions + ChannelPrivKeys = this.ChannelPrivKeys + NodeSecret = this.NodeSecret + RemoteNextCommitInfo = None + NegotiatingState = NegotiatingState.New() + Commitments = commitments + } + + return this.FundingTx, channel } - return this.FundingTx, channel + +and ChannelWaitingForFundingCreated = + { + AnnounceChannel: bool + RemoteNodeId: NodeId + Network: Network + FundingTxMinimumDepth: BlockHeightOffset32 + LocalStaticShutdownScriptPubKey: Option + RemoteStaticShutdownScriptPubKey: Option + IsFunder: bool + ChannelOptions: ChannelOptions + ChannelPrivKeys: ChannelPrivKeys + NodeSecret: NodeSecret + LocalParams: LocalParams + RemoteParams: RemoteParams + RemoteChannelPubKeys: ChannelPubKeys + FundingSatoshis: Money + PushMSat: LNMoney + InitialFeeRatePerKw: FeeRatePerKw + RemoteFirstPerCommitmentPoint: PerCommitmentPoint } -and ChannelWaitingForFundingCreated = { - AnnounceChannel: bool - RemoteNodeId: NodeId - Network: Network - FundingTxMinimumDepth: BlockHeightOffset32 - LocalStaticShutdownScriptPubKey: Option - RemoteStaticShutdownScriptPubKey: Option - IsFunder: bool - ChannelOptions: ChannelOptions - ChannelPrivKeys: ChannelPrivKeys - NodeSecret: NodeSecret - LocalParams: LocalParams - RemoteParams: RemoteParams - RemoteChannelPubKeys: ChannelPubKeys - FundingSatoshis: Money - PushMSat: LNMoney - InitialFeeRatePerKw: FeeRatePerKw - RemoteFirstPerCommitmentPoint: PerCommitmentPoint -} with - member this.ApplyFundingCreated (msg: FundingCreatedMsg) - : Result = result { - let! (localSpec, localCommitTx, remoteSpec, remoteCommitTx) = - let firstPerCommitmentPoint = - this.ChannelPrivKeys.CommitmentSeed.DerivePerCommitmentPoint - CommitmentNumber.FirstCommitment - ChannelHelpers.makeFirstCommitTxs - false - (this.ChannelPrivKeys.ToChannelPubKeys()) - this.RemoteChannelPubKeys - this.LocalParams - this.RemoteParams - this.FundingSatoshis - this.PushMSat - this.InitialFeeRatePerKw - msg.FundingOutputIndex - msg.FundingTxId - firstPerCommitmentPoint - this.RemoteFirstPerCommitmentPoint - this.Network - assert (localCommitTx.Value.IsReadyToSign()) - let _s, signedLocalCommitTx = - this.ChannelPrivKeys.SignWithFundingPrivKey localCommitTx.Value - let remoteTxSig = TransactionSignature(msg.Signature.Value, SigHash.All) - let theirSigPair = (this.RemoteChannelPubKeys.FundingPubKey.RawPubKey(), remoteTxSig) - let sigPairs = seq [ theirSigPair ] - let! finalizedCommitTx = - Transactions.checkTxFinalized (signedLocalCommitTx) CommitTx.WhichInput sigPairs - |> expectTransactionError - let localSigOfRemoteCommit, _ = - this.ChannelPrivKeys.SignWithFundingPrivKey remoteCommitTx.Value - let fundingScriptCoin = - ChannelHelpers.getFundingScriptCoin - (this.ChannelPrivKeys.FundingPrivKey.FundingPubKey()) - this.RemoteChannelPubKeys.FundingPubKey - msg.FundingTxId - msg.FundingOutputIndex - this.FundingSatoshis - let commitments = { - ProposedLocalChanges = List.empty - ProposedRemoteChanges = List.empty - LocalNextHTLCId = HTLCId.Zero - RemoteNextHTLCId = HTLCId.Zero - OriginChannels = Map.empty - } - let staticChannelConfig = { - AnnounceChannel = this.AnnounceChannel - RemoteNodeId = this.RemoteNodeId - Network = this.Network - FundingTxMinimumDepth = this.FundingTxMinimumDepth - LocalStaticShutdownScriptPubKey = this.LocalStaticShutdownScriptPubKey - RemoteStaticShutdownScriptPubKey = this.RemoteStaticShutdownScriptPubKey - IsFunder = this.IsFunder - FundingScriptCoin = fundingScriptCoin - LocalParams = this.LocalParams - RemoteParams = this.RemoteParams - RemoteChannelPubKeys = this.RemoteChannelPubKeys - } - let channelId = staticChannelConfig.ChannelId() - let msgToSend: FundingSignedMsg = { - ChannelId = channelId - Signature = !>localSigOfRemoteCommit.Signature - } - let channel = { - SavedChannelState = { - StaticChannelConfig = staticChannelConfig - RemotePerCommitmentSecrets = PerCommitmentSecretStore() - ShortChannelId = None - LocalCommit = { - Index = CommitmentNumber.FirstCommitment - Spec = localSpec - PublishableTxs = { - PublishableTxs.CommitTx = finalizedCommitTx - HTLCTxs = [] - } - PendingHTLCSuccessTxs = [] + member this.ApplyFundingCreated + (msg: FundingCreatedMsg) + : Result = + result { + let! (localSpec, localCommitTx, remoteSpec, remoteCommitTx) = + let firstPerCommitmentPoint = + this.ChannelPrivKeys.CommitmentSeed.DerivePerCommitmentPoint + CommitmentNumber.FirstCommitment + + ChannelHelpers.makeFirstCommitTxs + false + (this.ChannelPrivKeys.ToChannelPubKeys()) + this.RemoteChannelPubKeys + this.LocalParams + this.RemoteParams + this.FundingSatoshis + this.PushMSat + this.InitialFeeRatePerKw + msg.FundingOutputIndex + msg.FundingTxId + firstPerCommitmentPoint + this.RemoteFirstPerCommitmentPoint + this.Network + + assert (localCommitTx.Value.IsReadyToSign()) + + let _s, signedLocalCommitTx = + this.ChannelPrivKeys.SignWithFundingPrivKey localCommitTx.Value + + let remoteTxSig = + TransactionSignature(msg.Signature.Value, SigHash.All) + + let theirSigPair = + (this.RemoteChannelPubKeys.FundingPubKey.RawPubKey(), + remoteTxSig) + + let sigPairs = seq [ theirSigPair ] + + let! finalizedCommitTx = + Transactions.checkTxFinalized + (signedLocalCommitTx) + CommitTx.WhichInput + sigPairs + |> expectTransactionError + + let localSigOfRemoteCommit, _ = + this.ChannelPrivKeys.SignWithFundingPrivKey remoteCommitTx.Value + + let fundingScriptCoin = + ChannelHelpers.getFundingScriptCoin + (this.ChannelPrivKeys.FundingPrivKey.FundingPubKey()) + this.RemoteChannelPubKeys.FundingPubKey + msg.FundingTxId + msg.FundingOutputIndex + this.FundingSatoshis + + let commitments = + { + ProposedLocalChanges = List.empty + ProposedRemoteChanges = List.empty + LocalNextHTLCId = HTLCId.Zero + RemoteNextHTLCId = HTLCId.Zero + OriginChannels = Map.empty } - RemoteCommit = { - Index = CommitmentNumber.FirstCommitment - Spec = remoteSpec - TxId = remoteCommitTx.Value.GetGlobalTransaction().GetTxId() - RemotePerCommitmentPoint = this.RemoteFirstPerCommitmentPoint + + let staticChannelConfig = + { + AnnounceChannel = this.AnnounceChannel + RemoteNodeId = this.RemoteNodeId + Network = this.Network + FundingTxMinimumDepth = this.FundingTxMinimumDepth + LocalStaticShutdownScriptPubKey = + this.LocalStaticShutdownScriptPubKey + RemoteStaticShutdownScriptPubKey = + this.RemoteStaticShutdownScriptPubKey + IsFunder = this.IsFunder + FundingScriptCoin = fundingScriptCoin + LocalParams = this.LocalParams + RemoteParams = this.RemoteParams + RemoteChannelPubKeys = this.RemoteChannelPubKeys } - LocalChanges = LocalChanges.Zero - RemoteChanges = RemoteChanges.Zero - } - ChannelOptions = this.ChannelOptions - ChannelPrivKeys = this.ChannelPrivKeys - NodeSecret = this.NodeSecret - RemoteNextCommitInfo = None - NegotiatingState = NegotiatingState.New() - Commitments = commitments + + let channelId = staticChannelConfig.ChannelId() + + let msgToSend: FundingSignedMsg = + { + ChannelId = channelId + Signature = !>localSigOfRemoteCommit.Signature + } + + let channel = + { + SavedChannelState = + { + StaticChannelConfig = staticChannelConfig + RemotePerCommitmentSecrets = + PerCommitmentSecretStore() + ShortChannelId = None + LocalCommit = + { + Index = CommitmentNumber.FirstCommitment + Spec = localSpec + PublishableTxs = + { + PublishableTxs.CommitTx = + finalizedCommitTx + HTLCTxs = [] + } + PendingHTLCSuccessTxs = [] + } + RemoteCommit = + { + Index = CommitmentNumber.FirstCommitment + Spec = remoteSpec + TxId = + remoteCommitTx + .Value + .GetGlobalTransaction() + .GetTxId() + RemotePerCommitmentPoint = + this.RemoteFirstPerCommitmentPoint + } + LocalChanges = LocalChanges.Zero + RemoteChanges = RemoteChanges.Zero + } + ChannelOptions = this.ChannelOptions + ChannelPrivKeys = this.ChannelPrivKeys + NodeSecret = this.NodeSecret + RemoteNextCommitInfo = None + NegotiatingState = NegotiatingState.New() + Commitments = commitments + } + + return msgToSend, channel } - return msgToSend, channel + +and ChannelWaitingForFundingTx = + { + AnnounceChannel: bool + ChannelOptions: ChannelOptions + ChannelPrivKeys: ChannelPrivKeys + RemoteNodeId: NodeId + NodeSecret: NodeSecret + Network: Network + LocalStaticShutdownScriptPubKey: Option + RemoteStaticShutdownScriptPubKey: Option + TemporaryChannelId: ChannelId + RemoteChannelPubKeys: ChannelPubKeys + FundingSatoshis: Money + PushMSat: LNMoney + InitFeeRatePerKw: FeeRatePerKw + LocalParams: LocalParams + RemoteFirstPerCommitmentPoint: PerCommitmentPoint + RemoteParams: RemoteParams + RemoteInit: InitMsg + FundingTxMinimumDepth: BlockHeightOffset32 } -and ChannelWaitingForFundingTx = { - AnnounceChannel: bool - ChannelOptions: ChannelOptions - ChannelPrivKeys: ChannelPrivKeys - RemoteNodeId: NodeId - NodeSecret: NodeSecret - Network: Network - LocalStaticShutdownScriptPubKey: Option - RemoteStaticShutdownScriptPubKey: Option - TemporaryChannelId: ChannelId - RemoteChannelPubKeys: ChannelPubKeys - FundingSatoshis: Money - PushMSat: LNMoney - InitFeeRatePerKw: FeeRatePerKw - LocalParams: LocalParams - RemoteFirstPerCommitmentPoint: PerCommitmentPoint - RemoteParams: RemoteParams - RemoteInit: InitMsg - FundingTxMinimumDepth: BlockHeightOffset32 -} with - member this.CreateFundingTx (fundingTx: FinalizedTx) - (outIndex: TxOutIndex) - : Result = result { - let localParams = this.LocalParams - let remoteParams = this.RemoteParams - let commitmentSpec = CommitmentSpec.Create (this.FundingSatoshis.ToLNMoney() - this.PushMSat) this.PushMSat this.InitFeeRatePerKw - let commitmentSeed = this.ChannelPrivKeys.CommitmentSeed - let fundingTxId = fundingTx.Value.GetTxId() - let! (_localSpec, localCommitTx, remoteSpec, remoteCommitTx) = - ChannelHelpers.makeFirstCommitTxs - true - (this.ChannelPrivKeys.ToChannelPubKeys()) - this.RemoteChannelPubKeys - localParams - remoteParams - this.FundingSatoshis - this.PushMSat - this.InitFeeRatePerKw - outIndex - fundingTxId - (commitmentSeed.DerivePerCommitmentPoint CommitmentNumber.FirstCommitment) - this.RemoteFirstPerCommitmentPoint - this.Network - let localSigOfRemoteCommit, _ = - this.ChannelPrivKeys.SignWithFundingPrivKey remoteCommitTx.Value - let nextMsg: FundingCreatedMsg = { - TemporaryChannelId = this.TemporaryChannelId - FundingTxId = fundingTxId - FundingOutputIndex = outIndex - Signature = !>localSigOfRemoteCommit.Signature - } - let fundingScriptCoin = - ChannelHelpers.getFundingScriptCoin - (this.ChannelPrivKeys.FundingPrivKey.FundingPubKey()) - this.RemoteChannelPubKeys.FundingPubKey - fundingTxId - outIndex - this.FundingSatoshis - let channelId = OutPoint(fundingTxId.Value, uint32 outIndex.Value).ToChannelId() - let channelWaitingForFundingSigned = { - StaticChannelConfig = { - AnnounceChannel = this.AnnounceChannel - RemoteNodeId = this.RemoteNodeId - Network = this.Network - FundingTxMinimumDepth = this.FundingTxMinimumDepth - LocalStaticShutdownScriptPubKey = this.LocalStaticShutdownScriptPubKey - RemoteStaticShutdownScriptPubKey = this.RemoteStaticShutdownScriptPubKey - IsFunder = true - FundingScriptCoin = fundingScriptCoin - LocalParams = localParams - RemoteParams = remoteParams - RemoteChannelPubKeys = this.RemoteChannelPubKeys - } - ChannelOptions = this.ChannelOptions - ChannelPrivKeys = this.ChannelPrivKeys - NodeSecret = this.NodeSecret - ChannelId = channelId - FundingTx = fundingTx - LocalSpec = commitmentSpec - LocalCommitTx = localCommitTx - RemoteCommit = { - RemoteCommit.Index = CommitmentNumber.FirstCommitment - Spec = remoteSpec - TxId = remoteCommitTx.Value.GetGlobalTransaction().GetTxId() - RemotePerCommitmentPoint = this.RemoteFirstPerCommitmentPoint - } + member this.CreateFundingTx + (fundingTx: FinalizedTx) + (outIndex: TxOutIndex) + : Result = + result { + let localParams = this.LocalParams + let remoteParams = this.RemoteParams + + let commitmentSpec = + CommitmentSpec.Create + (this.FundingSatoshis.ToLNMoney() - this.PushMSat) + this.PushMSat + this.InitFeeRatePerKw + + let commitmentSeed = this.ChannelPrivKeys.CommitmentSeed + let fundingTxId = fundingTx.Value.GetTxId() + + let! (_localSpec, localCommitTx, remoteSpec, remoteCommitTx) = + ChannelHelpers.makeFirstCommitTxs + true + (this.ChannelPrivKeys.ToChannelPubKeys()) + this.RemoteChannelPubKeys + localParams + remoteParams + this.FundingSatoshis + this.PushMSat + this.InitFeeRatePerKw + outIndex + fundingTxId + (commitmentSeed.DerivePerCommitmentPoint + CommitmentNumber.FirstCommitment) + this.RemoteFirstPerCommitmentPoint + this.Network + + let localSigOfRemoteCommit, _ = + this.ChannelPrivKeys.SignWithFundingPrivKey remoteCommitTx.Value + + let nextMsg: FundingCreatedMsg = + { + TemporaryChannelId = this.TemporaryChannelId + FundingTxId = fundingTxId + FundingOutputIndex = outIndex + Signature = !>localSigOfRemoteCommit.Signature + } + + let fundingScriptCoin = + ChannelHelpers.getFundingScriptCoin + (this.ChannelPrivKeys.FundingPrivKey.FundingPubKey()) + this.RemoteChannelPubKeys.FundingPubKey + fundingTxId + outIndex + this.FundingSatoshis + + let channelId = + OutPoint(fundingTxId.Value, uint32 outIndex.Value) + .ToChannelId() + + let channelWaitingForFundingSigned = + { + StaticChannelConfig = + { + AnnounceChannel = this.AnnounceChannel + RemoteNodeId = this.RemoteNodeId + Network = this.Network + FundingTxMinimumDepth = this.FundingTxMinimumDepth + LocalStaticShutdownScriptPubKey = + this.LocalStaticShutdownScriptPubKey + RemoteStaticShutdownScriptPubKey = + this.RemoteStaticShutdownScriptPubKey + IsFunder = true + FundingScriptCoin = fundingScriptCoin + LocalParams = localParams + RemoteParams = remoteParams + RemoteChannelPubKeys = this.RemoteChannelPubKeys + } + ChannelOptions = this.ChannelOptions + ChannelPrivKeys = this.ChannelPrivKeys + NodeSecret = this.NodeSecret + ChannelId = channelId + FundingTx = fundingTx + LocalSpec = commitmentSpec + LocalCommitTx = localCommitTx + RemoteCommit = + { + RemoteCommit.Index = + CommitmentNumber.FirstCommitment + Spec = remoteSpec + TxId = + remoteCommitTx + .Value + .GetGlobalTransaction() + .GetTxId() + RemotePerCommitmentPoint = + this.RemoteFirstPerCommitmentPoint + } + } + + return nextMsg, channelWaitingForFundingSigned } - return nextMsg, channelWaitingForFundingSigned + + +and ChannelWaitingForAcceptChannel = + { + AnnounceChannel: bool + ChannelOptions: ChannelOptions + ChannelHandshakeLimits: ChannelHandshakeLimits + ChannelPrivKeys: ChannelPrivKeys + RemoteNodeId: NodeId + NodeSecret: NodeSecret + Network: Network + LocalStaticShutdownScriptPubKey: Option + TemporaryChannelId: ChannelId + FundingSatoshis: Money + PushMSat: LNMoney + InitFeeRatePerKw: FeeRatePerKw + LocalParams: LocalParams + RemoteInit: InitMsg } + member this.ApplyAcceptChannel + (msg: AcceptChannelMsg) + : Result = + result { + do! + Validation.checkAcceptChannelMsgAcceptable + this.ChannelHandshakeLimits + this.FundingSatoshis + this.LocalParams.ChannelReserveSatoshis + this.LocalParams.DustLimitSatoshis + msg -and ChannelWaitingForAcceptChannel = { - AnnounceChannel: bool - ChannelOptions: ChannelOptions - ChannelHandshakeLimits: ChannelHandshakeLimits - ChannelPrivKeys: ChannelPrivKeys - RemoteNodeId: NodeId - NodeSecret: NodeSecret - Network: Network - LocalStaticShutdownScriptPubKey: Option - TemporaryChannelId: ChannelId - FundingSatoshis: Money - PushMSat: LNMoney - InitFeeRatePerKw: FeeRatePerKw - LocalParams: LocalParams - RemoteInit: InitMsg -} with - member this.ApplyAcceptChannel (msg: AcceptChannelMsg) - : Result = result { - do! - Validation.checkAcceptChannelMsgAcceptable - this.ChannelHandshakeLimits - this.FundingSatoshis - this.LocalParams.ChannelReserveSatoshis - this.LocalParams.DustLimitSatoshis msg - let redeem = - Scripts.funding - (this.ChannelPrivKeys.ToChannelPubKeys().FundingPubKey) - msg.FundingPubKey - let remoteChannelPubKeys = { - FundingPubKey = msg.FundingPubKey - RevocationBasepoint = msg.RevocationBasepoint - PaymentBasepoint = msg.PaymentBasepoint - DelayedPaymentBasepoint = msg.DelayedPaymentBasepoint - HtlcBasepoint = msg.HTLCBasepoint - } - let destination = redeem.WitHash :> IDestination - let amount = this.FundingSatoshis - let remoteParams = RemoteParams.FromAcceptChannel this.RemoteInit msg - let channelWaitingForFundingTx = { - AnnounceChannel = this.AnnounceChannel - ChannelOptions = this.ChannelOptions - ChannelPrivKeys = this.ChannelPrivKeys - RemoteNodeId = this.RemoteNodeId - NodeSecret = this.NodeSecret - Network = this.Network - LocalStaticShutdownScriptPubKey = this.LocalStaticShutdownScriptPubKey - RemoteStaticShutdownScriptPubKey = msg.ShutdownScriptPubKey() - TemporaryChannelId = msg.TemporaryChannelId - RemoteChannelPubKeys = remoteChannelPubKeys - FundingSatoshis = this.FundingSatoshis - PushMSat = this.PushMSat - InitFeeRatePerKw = this.InitFeeRatePerKw - LocalParams = this.LocalParams - RemoteInit = this.RemoteInit - RemoteFirstPerCommitmentPoint = msg.FirstPerCommitmentPoint - FundingTxMinimumDepth = msg.MinimumDepth - RemoteParams = remoteParams + let redeem = + Scripts.funding + (this + .ChannelPrivKeys + .ToChannelPubKeys() + .FundingPubKey) + msg.FundingPubKey + + let remoteChannelPubKeys = + { + FundingPubKey = msg.FundingPubKey + RevocationBasepoint = msg.RevocationBasepoint + PaymentBasepoint = msg.PaymentBasepoint + DelayedPaymentBasepoint = msg.DelayedPaymentBasepoint + HtlcBasepoint = msg.HTLCBasepoint + } + + let destination = redeem.WitHash :> IDestination + let amount = this.FundingSatoshis + + let remoteParams = + RemoteParams.FromAcceptChannel this.RemoteInit msg + + let channelWaitingForFundingTx = + { + AnnounceChannel = this.AnnounceChannel + ChannelOptions = this.ChannelOptions + ChannelPrivKeys = this.ChannelPrivKeys + RemoteNodeId = this.RemoteNodeId + NodeSecret = this.NodeSecret + Network = this.Network + LocalStaticShutdownScriptPubKey = + this.LocalStaticShutdownScriptPubKey + RemoteStaticShutdownScriptPubKey = + msg.ShutdownScriptPubKey() + TemporaryChannelId = msg.TemporaryChannelId + RemoteChannelPubKeys = remoteChannelPubKeys + FundingSatoshis = this.FundingSatoshis + PushMSat = this.PushMSat + InitFeeRatePerKw = this.InitFeeRatePerKw + LocalParams = this.LocalParams + RemoteInit = this.RemoteInit + RemoteFirstPerCommitmentPoint = msg.FirstPerCommitmentPoint + FundingTxMinimumDepth = msg.MinimumDepth + RemoteParams = remoteParams + } + + return destination, amount, channelWaitingForFundingTx } - return destination, amount, channelWaitingForFundingTx + +and Channel = + { + SavedChannelState: SavedChannelState + ChannelOptions: ChannelOptions + ChannelPrivKeys: ChannelPrivKeys + NodeSecret: NodeSecret + RemoteNextCommitInfo: Option + NegotiatingState: NegotiatingState + Commitments: Commitments } -and Channel = { - SavedChannelState: SavedChannelState - ChannelOptions: ChannelOptions - ChannelPrivKeys: ChannelPrivKeys - NodeSecret: NodeSecret - RemoteNextCommitInfo: Option - NegotiatingState: NegotiatingState - Commitments: Commitments - } - with - - member internal this.RemoteNextCommitInfoIfFundingLocked (operation: string) - : Result = + member internal this.RemoteNextCommitInfoIfFundingLocked + (operation: string) + : Result = match this.RemoteNextCommitInfo with | None -> sprintf @@ -356,93 +477,122 @@ and Channel = { |> apiMisuse | Some remoteNextCommitInfo -> Ok remoteNextCommitInfo - member internal this.RemoteNextCommitInfoIfFundingLockedNormal (operation: string) - : Result = + member internal this.RemoteNextCommitInfoIfFundingLockedNormal + (operation: string) + : Result = match this.SavedChannelState.ShortChannelId with | None -> sprintf "cannot perform operation %s because funding is not confirmed" operation |> apiMisuse - | Some _ -> - this.RemoteNextCommitInfoIfFundingLocked operation - - static member NewOutbound(channelHandshakeLimits: ChannelHandshakeLimits, - channelOptions: ChannelOptions, - announceChannel: bool, - nodeMasterPrivKey: NodeMasterPrivKey, - channelIndex: int, - network: Network, - remoteNodeId: NodeId, - shutdownScriptPubKey: Option, - temporaryChannelId: ChannelId, - fundingSatoshis: Money, - pushMSat: LNMoney, - initFeeRatePerKw: FeeRatePerKw, - localParams: LocalParams, - remoteInit: InitMsg - ): Result = + | Some _ -> this.RemoteNextCommitInfoIfFundingLocked operation + + static member NewOutbound + ( + channelHandshakeLimits: ChannelHandshakeLimits, + channelOptions: ChannelOptions, + announceChannel: bool, + nodeMasterPrivKey: NodeMasterPrivKey, + channelIndex: int, + network: Network, + remoteNodeId: NodeId, + shutdownScriptPubKey: Option, + temporaryChannelId: ChannelId, + fundingSatoshis: Money, + pushMSat: LNMoney, + initFeeRatePerKw: FeeRatePerKw, + localParams: LocalParams, + remoteInit: InitMsg + ) : Result = let channelPrivKeys = nodeMasterPrivKey.ChannelPrivKeys channelIndex - let openChannelMsgToSend: OpenChannelMsg = { - Chainhash = network.Consensus.HashGenesisBlock - TemporaryChannelId = temporaryChannelId - FundingSatoshis = fundingSatoshis - PushMSat = pushMSat - DustLimitSatoshis = localParams.DustLimitSatoshis - MaxHTLCValueInFlightMsat = localParams.MaxHTLCValueInFlightMSat - ChannelReserveSatoshis = localParams.ChannelReserveSatoshis - HTLCMinimumMsat = localParams.HTLCMinimumMSat - FeeRatePerKw = initFeeRatePerKw - ToSelfDelay = localParams.ToSelfDelay - MaxAcceptedHTLCs = localParams.MaxAcceptedHTLCs - FundingPubKey = channelPrivKeys.FundingPrivKey.FundingPubKey() - RevocationBasepoint = channelPrivKeys.RevocationBasepointSecret.RevocationBasepoint() - PaymentBasepoint = channelPrivKeys.PaymentBasepointSecret.PaymentBasepoint() - DelayedPaymentBasepoint = channelPrivKeys.DelayedPaymentBasepointSecret.DelayedPaymentBasepoint() - HTLCBasepoint = channelPrivKeys.HtlcBasepointSecret.HtlcBasepoint() - FirstPerCommitmentPoint = channelPrivKeys.CommitmentSeed.DerivePerCommitmentPoint CommitmentNumber.FirstCommitment - ChannelFlags = { - AnnounceChannel = announceChannel - } - TLVs = [| OpenChannelTLV.UpfrontShutdownScript shutdownScriptPubKey |] - } - result { - do! Validation.checkOurOpenChannelMsgAcceptable localParams openChannelMsgToSend - let channelPrivKeys = nodeMasterPrivKey.ChannelPrivKeys channelIndex - let nodeSecret = nodeMasterPrivKey.NodeSecret() - let channelWaitingForAcceptChannel = { - AnnounceChannel = announceChannel - ChannelHandshakeLimits = channelHandshakeLimits - ChannelOptions = channelOptions - ChannelPrivKeys = channelPrivKeys - RemoteNodeId = remoteNodeId - NodeSecret = nodeSecret - Network = network - LocalStaticShutdownScriptPubKey = shutdownScriptPubKey + + let openChannelMsgToSend: OpenChannelMsg = + { + Chainhash = network.Consensus.HashGenesisBlock TemporaryChannelId = temporaryChannelId FundingSatoshis = fundingSatoshis PushMSat = pushMSat - InitFeeRatePerKw = initFeeRatePerKw - LocalParams = localParams - RemoteInit = remoteInit + DustLimitSatoshis = localParams.DustLimitSatoshis + MaxHTLCValueInFlightMsat = localParams.MaxHTLCValueInFlightMSat + ChannelReserveSatoshis = localParams.ChannelReserveSatoshis + HTLCMinimumMsat = localParams.HTLCMinimumMSat + FeeRatePerKw = initFeeRatePerKw + ToSelfDelay = localParams.ToSelfDelay + MaxAcceptedHTLCs = localParams.MaxAcceptedHTLCs + FundingPubKey = channelPrivKeys.FundingPrivKey.FundingPubKey() + RevocationBasepoint = + channelPrivKeys.RevocationBasepointSecret.RevocationBasepoint + () + PaymentBasepoint = + channelPrivKeys.PaymentBasepointSecret.PaymentBasepoint() + DelayedPaymentBasepoint = + channelPrivKeys.DelayedPaymentBasepointSecret.DelayedPaymentBasepoint + () + HTLCBasepoint = + channelPrivKeys.HtlcBasepointSecret.HtlcBasepoint() + FirstPerCommitmentPoint = + channelPrivKeys.CommitmentSeed.DerivePerCommitmentPoint + CommitmentNumber.FirstCommitment + ChannelFlags = + { + AnnounceChannel = announceChannel + } + TLVs = + [| + OpenChannelTLV.UpfrontShutdownScript + shutdownScriptPubKey + |] } + + result { + do! + Validation.checkOurOpenChannelMsgAcceptable + localParams + openChannelMsgToSend + + let channelPrivKeys = nodeMasterPrivKey.ChannelPrivKeys channelIndex + let nodeSecret = nodeMasterPrivKey.NodeSecret() + + let channelWaitingForAcceptChannel = + { + AnnounceChannel = announceChannel + ChannelHandshakeLimits = channelHandshakeLimits + ChannelOptions = channelOptions + ChannelPrivKeys = channelPrivKeys + RemoteNodeId = remoteNodeId + NodeSecret = nodeSecret + Network = network + LocalStaticShutdownScriptPubKey = shutdownScriptPubKey + TemporaryChannelId = temporaryChannelId + FundingSatoshis = fundingSatoshis + PushMSat = pushMSat + InitFeeRatePerKw = initFeeRatePerKw + LocalParams = localParams + RemoteInit = remoteInit + } + return (openChannelMsgToSend, channelWaitingForAcceptChannel) } - static member NewInbound (channelHandshakeLimits: ChannelHandshakeLimits, - channelOptions: ChannelOptions, - announceChannel: bool, - nodeMasterPrivKey: NodeMasterPrivKey, - channelIndex: int, - network: Network, - remoteNodeId: NodeId, - minimumDepth: BlockHeightOffset32, - shutdownScriptPubKey: Option, - openChannelMsg: OpenChannelMsg, - localParams: LocalParams, - remoteInit: InitMsg): Result = + static member NewInbound + ( + channelHandshakeLimits: ChannelHandshakeLimits, + channelOptions: ChannelOptions, + announceChannel: bool, + nodeMasterPrivKey: NodeMasterPrivKey, + channelIndex: int, + network: Network, + remoteNodeId: NodeId, + minimumDepth: BlockHeightOffset32, + shutdownScriptPubKey: Option, + openChannelMsg: OpenChannelMsg, + localParams: LocalParams, + remoteInit: InitMsg + ) : Result = result { let channelPrivKeys = nodeMasterPrivKey.ChannelPrivKeys channelIndex + do! Validation.checkOpenChannelMsgAcceptable channelHandshakeLimits @@ -450,671 +600,968 @@ and Channel = { announceChannel localParams openChannelMsg - let firstPerCommitmentPoint = channelPrivKeys.CommitmentSeed.DerivePerCommitmentPoint CommitmentNumber.FirstCommitment - let acceptChannelMsg: AcceptChannelMsg = { - TemporaryChannelId = openChannelMsg.TemporaryChannelId - DustLimitSatoshis = localParams.DustLimitSatoshis - MaxHTLCValueInFlightMsat = localParams.MaxHTLCValueInFlightMSat - ChannelReserveSatoshis = localParams.ChannelReserveSatoshis - HTLCMinimumMSat = localParams.HTLCMinimumMSat - MinimumDepth = minimumDepth - ToSelfDelay = localParams.ToSelfDelay - MaxAcceptedHTLCs = localParams.MaxAcceptedHTLCs - FundingPubKey = channelPrivKeys.FundingPrivKey.FundingPubKey() - RevocationBasepoint = channelPrivKeys.RevocationBasepointSecret.RevocationBasepoint() - PaymentBasepoint = channelPrivKeys.PaymentBasepointSecret.PaymentBasepoint() - DelayedPaymentBasepoint = channelPrivKeys.DelayedPaymentBasepointSecret.DelayedPaymentBasepoint() - HTLCBasepoint = channelPrivKeys.HtlcBasepointSecret.HtlcBasepoint() - FirstPerCommitmentPoint = firstPerCommitmentPoint - TLVs = [| AcceptChannelTLV.UpfrontShutdownScript shutdownScriptPubKey |] - } - let remoteChannelPubKeys = { - FundingPubKey = openChannelMsg.FundingPubKey - RevocationBasepoint = openChannelMsg.RevocationBasepoint - PaymentBasepoint = openChannelMsg.PaymentBasepoint - DelayedPaymentBasepoint = openChannelMsg.DelayedPaymentBasepoint - HtlcBasepoint = openChannelMsg.HTLCBasepoint - } - let remoteParams = RemoteParams.FromOpenChannel remoteInit openChannelMsg + + let firstPerCommitmentPoint = + channelPrivKeys.CommitmentSeed.DerivePerCommitmentPoint + CommitmentNumber.FirstCommitment + + let acceptChannelMsg: AcceptChannelMsg = + { + TemporaryChannelId = openChannelMsg.TemporaryChannelId + DustLimitSatoshis = localParams.DustLimitSatoshis + MaxHTLCValueInFlightMsat = + localParams.MaxHTLCValueInFlightMSat + ChannelReserveSatoshis = localParams.ChannelReserveSatoshis + HTLCMinimumMSat = localParams.HTLCMinimumMSat + MinimumDepth = minimumDepth + ToSelfDelay = localParams.ToSelfDelay + MaxAcceptedHTLCs = localParams.MaxAcceptedHTLCs + FundingPubKey = + channelPrivKeys.FundingPrivKey.FundingPubKey() + RevocationBasepoint = + channelPrivKeys.RevocationBasepointSecret.RevocationBasepoint + () + PaymentBasepoint = + channelPrivKeys.PaymentBasepointSecret.PaymentBasepoint + () + DelayedPaymentBasepoint = + channelPrivKeys.DelayedPaymentBasepointSecret.DelayedPaymentBasepoint + () + HTLCBasepoint = + channelPrivKeys.HtlcBasepointSecret.HtlcBasepoint() + FirstPerCommitmentPoint = firstPerCommitmentPoint + TLVs = + [| + AcceptChannelTLV.UpfrontShutdownScript + shutdownScriptPubKey + |] + } + + let remoteChannelPubKeys = + { + FundingPubKey = openChannelMsg.FundingPubKey + RevocationBasepoint = openChannelMsg.RevocationBasepoint + PaymentBasepoint = openChannelMsg.PaymentBasepoint + DelayedPaymentBasepoint = + openChannelMsg.DelayedPaymentBasepoint + HtlcBasepoint = openChannelMsg.HTLCBasepoint + } + + let remoteParams = + RemoteParams.FromOpenChannel remoteInit openChannelMsg + let nodeSecret = nodeMasterPrivKey.NodeSecret() - let channelWaitingForFundingCreated = { - AnnounceChannel = openChannelMsg.ChannelFlags.AnnounceChannel - RemoteNodeId = remoteNodeId - Network = network - FundingTxMinimumDepth = minimumDepth - LocalStaticShutdownScriptPubKey = shutdownScriptPubKey - RemoteStaticShutdownScriptPubKey = openChannelMsg.ShutdownScriptPubKey() - IsFunder = false - ChannelOptions = channelOptions - ChannelPrivKeys = channelPrivKeys - NodeSecret = nodeSecret - LocalParams = localParams - RemoteParams = remoteParams - RemoteChannelPubKeys = remoteChannelPubKeys - FundingSatoshis = openChannelMsg.FundingSatoshis - PushMSat = openChannelMsg.PushMSat - InitialFeeRatePerKw = openChannelMsg.FeeRatePerKw - RemoteFirstPerCommitmentPoint = openChannelMsg.FirstPerCommitmentPoint - } + + let channelWaitingForFundingCreated = + { + AnnounceChannel = + openChannelMsg.ChannelFlags.AnnounceChannel + RemoteNodeId = remoteNodeId + Network = network + FundingTxMinimumDepth = minimumDepth + LocalStaticShutdownScriptPubKey = shutdownScriptPubKey + RemoteStaticShutdownScriptPubKey = + openChannelMsg.ShutdownScriptPubKey() + IsFunder = false + ChannelOptions = channelOptions + ChannelPrivKeys = channelPrivKeys + NodeSecret = nodeSecret + LocalParams = localParams + RemoteParams = remoteParams + RemoteChannelPubKeys = remoteChannelPubKeys + FundingSatoshis = openChannelMsg.FundingSatoshis + PushMSat = openChannelMsg.PushMSat + InitialFeeRatePerKw = openChannelMsg.FeeRatePerKw + RemoteFirstPerCommitmentPoint = + openChannelMsg.FirstPerCommitmentPoint + } + return (acceptChannelMsg, channelWaitingForFundingCreated) } - member this.CreateChannelReestablish (): ChannelReestablishMsg = + member this.CreateChannelReestablish() : ChannelReestablishMsg = let commitmentSeed = this.ChannelPrivKeys.CommitmentSeed - let ourChannelReestablish = { - ChannelId = this.SavedChannelState.StaticChannelConfig.ChannelId() - NextCommitmentNumber = - (this.SavedChannelState.RemotePerCommitmentSecrets.NextCommitmentNumber().NextCommitment()) - NextRevocationNumber = - this.SavedChannelState.RemotePerCommitmentSecrets.NextCommitmentNumber() - DataLossProtect = OptionalField.Some <| { - YourLastPerCommitmentSecret = - this.SavedChannelState.RemotePerCommitmentSecrets.MostRecentPerCommitmentSecret() - MyCurrentPerCommitmentPoint = - commitmentSeed.DerivePerCommitmentPoint this.SavedChannelState.RemoteCommit.Index + + let ourChannelReestablish = + { + ChannelId = + this.SavedChannelState.StaticChannelConfig.ChannelId() + NextCommitmentNumber = + (this + .SavedChannelState + .RemotePerCommitmentSecrets + .NextCommitmentNumber() + .NextCommitment()) + NextRevocationNumber = + this.SavedChannelState.RemotePerCommitmentSecrets.NextCommitmentNumber + () + DataLossProtect = + OptionalField.Some + <| { + YourLastPerCommitmentSecret = + this.SavedChannelState.RemotePerCommitmentSecrets.MostRecentPerCommitmentSecret + () + MyCurrentPerCommitmentPoint = + commitmentSeed.DerivePerCommitmentPoint + this.SavedChannelState.RemoteCommit.Index + } } - } + ourChannelReestablish - member this.ApplyFundingLocked (fundingLockedMsg: FundingLockedMsg) - : Result = result { - do! - match this.RemoteNextCommitInfo with - | None -> Ok () - | Some remoteNextCommitInfo -> - if remoteNextCommitInfo.PerCommitmentPoint() - <> fundingLockedMsg.NextPerCommitmentPoint then - Error <| InvalidFundingLocked { NetworkMsg = fundingLockedMsg } - else - Ok () - return { - this with - RemoteNextCommitInfo = - RemoteNextCommitInfo.Revoked fundingLockedMsg.NextPerCommitmentPoint - |> Some + member this.ApplyFundingLocked + (fundingLockedMsg: FundingLockedMsg) + : Result = + result { + do! + match this.RemoteNextCommitInfo with + | None -> Ok() + | Some remoteNextCommitInfo -> + if remoteNextCommitInfo.PerCommitmentPoint() + <> fundingLockedMsg.NextPerCommitmentPoint then + Error + <| InvalidFundingLocked + { + NetworkMsg = fundingLockedMsg + } + else + Ok() + + return + { this with + RemoteNextCommitInfo = + RemoteNextCommitInfo.Revoked + fundingLockedMsg.NextPerCommitmentPoint + |> Some + } } - } - member this.ApplyFundingConfirmedOnBC (height: BlockHeight) - (txindex: TxIndexInBlock) - (depth: BlockHeightOffset32) - : Result = result { - let requiredDepth = this.SavedChannelState.StaticChannelConfig.FundingTxMinimumDepth - if depth < requiredDepth then - return! Error <| InsufficientConfirmations (requiredDepth, depth) - else - let nextPerCommitmentPoint = - this.ChannelPrivKeys.CommitmentSeed.DerivePerCommitmentPoint - (CommitmentNumber.FirstCommitment.NextCommitment()) - let msgToSend: FundingLockedMsg = { - ChannelId = this.SavedChannelState.StaticChannelConfig.ChannelId() - NextPerCommitmentPoint = nextPerCommitmentPoint - } + member this.ApplyFundingConfirmedOnBC + (height: BlockHeight) + (txindex: TxIndexInBlock) + (depth: BlockHeightOffset32) + : Result = + result { + let requiredDepth = + this.SavedChannelState.StaticChannelConfig.FundingTxMinimumDepth - // This is temporary channel id that we will use in our - // channel_update message, the goal is to be able to use our - // channel as soon as it reaches NORMAL state, and before it is - // announced on the network (this id might be updated when the - // funding tx gets deeply buried, if there was a reorg in the - // meantime) this is not specified in BOLT. - let shortChannelId = { - ShortChannelId.BlockHeight = height; - BlockIndex = txindex - TxOutIndex = - this.SavedChannelState.StaticChannelConfig.FundingScriptCoin.Outpoint.N - |> uint16 - |> TxOutIndex - } - let savedChannelState = { - this.SavedChannelState with - ShortChannelId = Some shortChannelId - } - let channel = { - this with - SavedChannelState = savedChannelState - } - return channel, msgToSend - } + if depth < requiredDepth then + return! Error <| InsufficientConfirmations(requiredDepth, depth) + else + let nextPerCommitmentPoint = + this.ChannelPrivKeys.CommitmentSeed.DerivePerCommitmentPoint( + CommitmentNumber.FirstCommitment.NextCommitment() + ) + + let msgToSend: FundingLockedMsg = + { + ChannelId = + this.SavedChannelState.StaticChannelConfig.ChannelId + () + NextPerCommitmentPoint = nextPerCommitmentPoint + } + + // This is temporary channel id that we will use in our + // channel_update message, the goal is to be able to use our + // channel as soon as it reaches NORMAL state, and before it is + // announced on the network (this id might be updated when the + // funding tx gets deeply buried, if there was a reorg in the + // meantime) this is not specified in BOLT. + let shortChannelId = + { + ShortChannelId.BlockHeight = height + BlockIndex = txindex + TxOutIndex = + this.SavedChannelState.StaticChannelConfig.FundingScriptCoin.Outpoint.N + |> uint16 + |> TxOutIndex + } + + let savedChannelState = + { this.SavedChannelState with + ShortChannelId = Some shortChannelId + } + + let channel = + { this with + SavedChannelState = savedChannelState + } + + return channel, msgToSend + } + + member this.AddHTLC + (op: OperationAddHTLC) + : Result = + result { + if this.NegotiatingState.HasEnteredShutdown() then + return! + sprintf + "Could not add new HTLC %A since shutdown is already in progress." + op + |> apiMisuse + else + do! + Validation.checkOperationAddHTLC + this.SavedChannelState.StaticChannelConfig.RemoteParams + op + + let add: UpdateAddHTLCMsg = + { + ChannelId = + this.SavedChannelState.StaticChannelConfig.ChannelId + () + HTLCId = this.Commitments.LocalNextHTLCId + Amount = op.Amount + PaymentHash = op.PaymentHash + CLTVExpiry = op.Expiry + OnionRoutingPacket = op.Onion + } + + let commitments1 = + let commitments = + { this.Commitments.AddLocalProposal(add) with + LocalNextHTLCId = + this.Commitments.LocalNextHTLCId + 1UL + } + + match op.Origin with + | None -> commitments + | Some origin -> + { commitments with + OriginChannels = + this.Commitments.OriginChannels + |> Map.add add.HTLCId origin + } + + let! remoteNextCommitInfo = + this.RemoteNextCommitInfoIfFundingLockedNormal "AddHTLC" + // we need to base the next current commitment on the last sig we sent, even if we didn't yet receive their revocation + let remoteCommit1 = + match remoteNextCommitInfo with + | Waiting nextRemoteCommit -> nextRemoteCommit + | Revoked _info -> this.SavedChannelState.RemoteCommit + + let! reduced = + remoteCommit1.Spec.Reduce( + this.SavedChannelState.RemoteChanges.ACKed, + commitments1.ProposedLocalChanges + ) + |> expectTransactionError + + do! + Validation.checkOurUpdateAddHTLCIsAcceptableWithCurrentSpec + reduced + this.SavedChannelState.StaticChannelConfig + add + + let channel = + { this with + Commitments = commitments1 + } + + return channel, add + } + + member this.ApplyUpdateAddHTLC + (msg: UpdateAddHTLCMsg) + (height: BlockHeight) + : Result = + result { + do! + Validation.checkTheirUpdateAddHTLCIsAcceptable + this.Commitments + this.SavedChannelState.StaticChannelConfig.LocalParams + msg + height - member this.AddHTLC (op: OperationAddHTLC) - : Result = result { - if this.NegotiatingState.HasEnteredShutdown() then - return! - sprintf "Could not add new HTLC %A since shutdown is already in progress." op - |> apiMisuse - else - do! Validation.checkOperationAddHTLC this.SavedChannelState.StaticChannelConfig.RemoteParams op - let add: UpdateAddHTLCMsg = { - ChannelId = this.SavedChannelState.StaticChannelConfig.ChannelId() - HTLCId = this.Commitments.LocalNextHTLCId - Amount = op.Amount - PaymentHash = op.PaymentHash - CLTVExpiry = op.Expiry - OnionRoutingPacket = op.Onion - } let commitments1 = - let commitments = { - this.Commitments.AddLocalProposal(add) with - LocalNextHTLCId = this.Commitments.LocalNextHTLCId + 1UL - } - match op.Origin with - | None -> commitments - | Some origin -> { - commitments with - OriginChannels = - this.Commitments.OriginChannels - |> Map.add add.HTLCId origin + { this.Commitments.AddRemoteProposal(msg) with + RemoteNextHTLCId = this.Commitments.LocalNextHTLCId + 1UL } - let! remoteNextCommitInfo = - this.RemoteNextCommitInfoIfFundingLockedNormal "AddHTLC" - // we need to base the next current commitment on the last sig we sent, even if we didn't yet receive their revocation - let remoteCommit1 = - match remoteNextCommitInfo with - | Waiting nextRemoteCommit -> nextRemoteCommit - | Revoked _info -> this.SavedChannelState.RemoteCommit - let! reduced = remoteCommit1.Spec.Reduce(this.SavedChannelState.RemoteChanges.ACKed, commitments1.ProposedLocalChanges) |> expectTransactionError + let! reduced = + this.SavedChannelState.LocalCommit.Spec.Reduce( + this.SavedChannelState.LocalChanges.ACKed, + commitments1.ProposedRemoteChanges + ) + |> expectTransactionError + do! - Validation.checkOurUpdateAddHTLCIsAcceptableWithCurrentSpec + Validation.checkTheirUpdateAddHTLCIsAcceptableWithCurrentSpec reduced this.SavedChannelState.StaticChannelConfig - add - let channel = { - this with + msg + + return + { this with Commitments = commitments1 - } - return channel, add - } + } + } - member this.ApplyUpdateAddHTLC (msg: UpdateAddHTLCMsg) - (height: BlockHeight) - : Result = result { - do! - Validation.checkTheirUpdateAddHTLCIsAcceptable - this.Commitments - this.SavedChannelState.StaticChannelConfig.LocalParams - msg - height - let commitments1 = { - this.Commitments.AddRemoteProposal(msg) with - RemoteNextHTLCId = this.Commitments.LocalNextHTLCId + 1UL + member this.FulFillHTLC + (cmd: OperationFulfillHTLC) + : Result = + result { + let! remoteNextCommitInfo = + this.RemoteNextCommitInfoIfFundingLockedNormal "FulfillHTLC" + + let! updateFulfillHTLCMsg, newCommitments = + Commitments.sendFulfill + cmd + this.Commitments + this.SavedChannelState + remoteNextCommitInfo + + let channel = + { this with + Commitments = newCommitments + } + + return channel, updateFulfillHTLCMsg } - let! reduced = - this.SavedChannelState.LocalCommit.Spec.Reduce ( - this.SavedChannelState.LocalChanges.ACKed, - commitments1.ProposedRemoteChanges - ) |> expectTransactionError - do! - Validation.checkTheirUpdateAddHTLCIsAcceptableWithCurrentSpec - reduced - this.SavedChannelState.StaticChannelConfig - msg - return { - this with - Commitments = commitments1 + + member this.ApplyUpdateFulfillHTLC + (msg: UpdateFulfillHTLCMsg) + : Result = + result { + let! remoteNextCommitInfo = + this.RemoteNextCommitInfoIfFundingLockedNormal + "ApplyUpdateFulfullHTLC" + + let! newCommitments = + Commitments.receiveFulfill + msg + this.Commitments + this.SavedChannelState + remoteNextCommitInfo + + return + { this with + Commitments = newCommitments + } } - } - member this.FulFillHTLC (cmd: OperationFulfillHTLC) - : Result = result { - let! remoteNextCommitInfo = - this.RemoteNextCommitInfoIfFundingLockedNormal "FulfillHTLC" - let! updateFulfillHTLCMsg, newCommitments = - Commitments.sendFulfill - cmd - this.Commitments - this.SavedChannelState - remoteNextCommitInfo - - let channel = { - this with - Commitments = newCommitments + member this.FailHTLC + (op: OperationFailHTLC) + : Result = + result { + let! remoteNextCommitInfo = + this.RemoteNextCommitInfoIfFundingLockedNormal "FailHTLC" + + let! updateFailHTLCMsg, newCommitments = + Commitments.sendFail + this.NodeSecret + op + this.Commitments + this.SavedChannelState + remoteNextCommitInfo + + let channel = + { this with + Commitments = newCommitments + } + + return channel, updateFailHTLCMsg } - return channel, updateFulfillHTLCMsg - } - member this.ApplyUpdateFulfillHTLC (msg: UpdateFulfillHTLCMsg) - : Result = result { - let! remoteNextCommitInfo = - this.RemoteNextCommitInfoIfFundingLockedNormal "ApplyUpdateFulfullHTLC" - let! newCommitments = - Commitments.receiveFulfill - msg - this.Commitments - this.SavedChannelState - remoteNextCommitInfo - return { - this with - Commitments = newCommitments + member this.FailMalformedHTLC + (op: OperationFailMalformedHTLC) + : Result = + result { + let! remoteNextCommitInfo = + this.RemoteNextCommitInfoIfFundingLockedNormal + "FailMalformedHTLC" + + let! updateFailMalformedHTLCMsg, newCommitments = + Commitments.sendFailMalformed + op + this.Commitments + this.SavedChannelState + remoteNextCommitInfo + + let channel = + { this with + Commitments = newCommitments + } + + return channel, updateFailMalformedHTLCMsg } - } - member this.FailHTLC (op: OperationFailHTLC) - : Result = result { - let! remoteNextCommitInfo = - this.RemoteNextCommitInfoIfFundingLockedNormal "FailHTLC" - let! updateFailHTLCMsg, newCommitments = - Commitments.sendFail - this.NodeSecret - op - this.Commitments - this.SavedChannelState - remoteNextCommitInfo - let channel = { - this with - Commitments = newCommitments + member this.ApplyUpdateFailHTLC + (msg: UpdateFailHTLCMsg) + : Result = + result { + let! remoteNextCommitInfo = + this.RemoteNextCommitInfoIfFundingLockedNormal + "ApplyUpdateFailHTLC" + + let! newCommitments = + Commitments.receiveFail + msg + this.Commitments + this.SavedChannelState + remoteNextCommitInfo + + return + { this with + Commitments = newCommitments + } } - return channel, updateFailHTLCMsg - } - member this.FailMalformedHTLC (op: OperationFailMalformedHTLC) - : Result = result { - let! remoteNextCommitInfo = - this.RemoteNextCommitInfoIfFundingLockedNormal "FailMalformedHTLC" - let! updateFailMalformedHTLCMsg, newCommitments = - Commitments.sendFailMalformed - op - this.Commitments - this.SavedChannelState - remoteNextCommitInfo - let channel = { - this with - Commitments = newCommitments + member this.ApplyUpdateFailMalformedHTLC + (msg: UpdateFailMalformedHTLCMsg) + : Result = + result { + let! remoteNextCommitInfo = + this.RemoteNextCommitInfoIfFundingLockedNormal + "ApplyUpdateFailMalformedHTLC" + + let! newCommitments = + Commitments.receiveFailMalformed + msg + this.Commitments + this.SavedChannelState + remoteNextCommitInfo + + return + { this with + Commitments = newCommitments + } } - return channel, updateFailMalformedHTLCMsg - } - member this.ApplyUpdateFailHTLC (msg: UpdateFailHTLCMsg) - : Result = result { - let! remoteNextCommitInfo = - this.RemoteNextCommitInfoIfFundingLockedNormal "ApplyUpdateFailHTLC" - let! newCommitments = - Commitments.receiveFail - msg - this.Commitments - this.SavedChannelState - remoteNextCommitInfo - return { - this with - Commitments = newCommitments + member this.UpdateFee + (op: OperationUpdateFee) + : Result = + result { + let! _remoteNextCommitInfo = + this.RemoteNextCommitInfoIfFundingLockedNormal "UpdateFee" + + let! updateFeeMsg, newCommitments = + Commitments.sendFee op this.SavedChannelState this.Commitments + + let channel = + { this with + Commitments = newCommitments + } + + return channel, updateFeeMsg } - } - member this.ApplyUpdateFailMalformedHTLC (msg: UpdateFailMalformedHTLCMsg) - : Result = result { - let! remoteNextCommitInfo = - this.RemoteNextCommitInfoIfFundingLockedNormal "ApplyUpdateFailMalformedHTLC" - let! newCommitments = - Commitments.receiveFailMalformed - msg - this.Commitments - this.SavedChannelState - remoteNextCommitInfo - return { - this with - Commitments = newCommitments + member this.ApplyUpdateFee + (msg: UpdateFeeMsg) + : Result = + result { + let! _remoteNextCommitInfo = + this.RemoteNextCommitInfoIfFundingLockedNormal "ApplyUpdateFee" + + let localFeerate = + this.ChannelOptions.FeeEstimator.GetEstSatPer1000Weight( + ConfirmationTarget.HighPriority + ) + + let! newCommitments = + Commitments.receiveFee + this.ChannelOptions + localFeerate + msg + this.SavedChannelState + this.Commitments + + return + { this with + Commitments = newCommitments + } } - } - member this.UpdateFee (op: OperationUpdateFee) - : Result = result { - let! _remoteNextCommitInfo = - this.RemoteNextCommitInfoIfFundingLockedNormal "UpdateFee" - let! updateFeeMsg, newCommitments = - Commitments.sendFee op this.SavedChannelState this.Commitments - let channel = { - this with - Commitments = newCommitments + member this.SignCommitment + () + : Result = + result { + let! remoteNextCommitInfo = + this.RemoteNextCommitInfoIfFundingLockedNormal "SignCommit" + + match remoteNextCommitInfo with + | _ when (this.LocalHasChanges() |> not) -> + return! Error NoUpdatesToSign + | RemoteNextCommitInfo.Revoked _ -> + let! commitmentSignedMsg, channel = + this.sendCommit remoteNextCommitInfo + + return channel, commitmentSignedMsg + | RemoteNextCommitInfo.Waiting _ -> + return! Error CannotSignCommitmentBeforeRevocation } - return channel, updateFeeMsg - } - member this.ApplyUpdateFee (msg: UpdateFeeMsg) - : Result = result { - let! _remoteNextCommitInfo = - this.RemoteNextCommitInfoIfFundingLockedNormal "ApplyUpdateFee" - let localFeerate = this.ChannelOptions.FeeEstimator.GetEstSatPer1000Weight(ConfirmationTarget.HighPriority) - let! newCommitments = - Commitments.receiveFee - this.ChannelOptions - localFeerate - msg - this.SavedChannelState - this.Commitments - return { - this with - Commitments = newCommitments + member this.ApplyCommitmentSigned + (msg: CommitmentSignedMsg) + : Result = + result { + let! _remoteNextCommitInfo = + this.RemoteNextCommitInfoIfFundingLockedNormal + "ApplyCommitmentSigned" + + let! revokeAndACKMsg, channel = this.receiveCommit msg + return channel, revokeAndACKMsg } - } - member this.SignCommitment(): Result = result { - let! remoteNextCommitInfo = - this.RemoteNextCommitInfoIfFundingLockedNormal "SignCommit" - match remoteNextCommitInfo with - | _ when (this.LocalHasChanges() |> not) -> - return! Error NoUpdatesToSign - | RemoteNextCommitInfo.Revoked _ -> - let! commitmentSignedMsg, channel = - this.sendCommit remoteNextCommitInfo - return channel, commitmentSignedMsg - | RemoteNextCommitInfo.Waiting _ -> - return! Error CannotSignCommitmentBeforeRevocation - } + member this.ApplyRevokeAndACK + (msg: RevokeAndACKMsg) + : Result = + result { + let! remoteNextCommitInfo = + this.RemoteNextCommitInfoIfFundingLockedNormal + "ApplyRevokeAndACK" - member this.ApplyCommitmentSigned (msg: CommitmentSignedMsg) - : Result = result { - let! _remoteNextCommitInfo = - this.RemoteNextCommitInfoIfFundingLockedNormal "ApplyCommitmentSigned" - let! revokeAndACKMsg, channel = this.receiveCommit msg - return channel, revokeAndACKMsg - } + match remoteNextCommitInfo with + | RemoteNextCommitInfo.Waiting _ when + (msg.PerCommitmentSecret.PerCommitmentPoint() + <> this.SavedChannelState.RemoteCommit.RemotePerCommitmentPoint) + -> + let errorMsg = + sprintf + "Invalid revoke_and_ack %A; must be %A" + msg.PerCommitmentSecret + this.SavedChannelState.RemoteCommit.RemotePerCommitmentPoint - member this.ApplyRevokeAndACK (msg: RevokeAndACKMsg) - : Result = result { - let! remoteNextCommitInfo = - this.RemoteNextCommitInfoIfFundingLockedNormal "ApplyRevokeAndACK" - match remoteNextCommitInfo with - | RemoteNextCommitInfo.Waiting _ when (msg.PerCommitmentSecret.PerCommitmentPoint() <> this.SavedChannelState.RemoteCommit.RemotePerCommitmentPoint) -> - let errorMsg = sprintf "Invalid revoke_and_ack %A; must be %A" msg.PerCommitmentSecret this.SavedChannelState.RemoteCommit.RemotePerCommitmentPoint - return! Error <| invalidRevokeAndACK msg errorMsg - | RemoteNextCommitInfo.Revoked _ -> - let errorMsg = sprintf "Unexpected revocation" - return! Error <| invalidRevokeAndACK msg errorMsg - | RemoteNextCommitInfo.Waiting theirNextCommit -> - let remotePerCommitmentSecretsOpt = - this.SavedChannelState.RemotePerCommitmentSecrets.InsertPerCommitmentSecret - this.SavedChannelState.RemoteCommit.Index - msg.PerCommitmentSecret - match remotePerCommitmentSecretsOpt with - | Error err -> return! Error <| invalidRevokeAndACK msg err.Message - | Ok remotePerCommitmentSecrets -> - let savedChannelState = { - this.SavedChannelState with - RemotePerCommitmentSecrets = remotePerCommitmentSecrets - RemoteCommit = theirNextCommit - LocalChanges = { - this.SavedChannelState.LocalChanges with - Signed = []; - ACKed = this.SavedChannelState.LocalChanges.ACKed @ this.SavedChannelState.LocalChanges.Signed + return! Error <| invalidRevokeAndACK msg errorMsg + | RemoteNextCommitInfo.Revoked _ -> + let errorMsg = sprintf "Unexpected revocation" + return! Error <| invalidRevokeAndACK msg errorMsg + | RemoteNextCommitInfo.Waiting theirNextCommit -> + let remotePerCommitmentSecretsOpt = + this.SavedChannelState.RemotePerCommitmentSecrets.InsertPerCommitmentSecret + this.SavedChannelState.RemoteCommit.Index + msg.PerCommitmentSecret + + match remotePerCommitmentSecretsOpt with + | Error err -> + return! Error <| invalidRevokeAndACK msg err.Message + | Ok remotePerCommitmentSecrets -> + let savedChannelState = + { this.SavedChannelState with + RemotePerCommitmentSecrets = + remotePerCommitmentSecrets + RemoteCommit = theirNextCommit + LocalChanges = + { this.SavedChannelState.LocalChanges with + Signed = [] + ACKed = + this.SavedChannelState.LocalChanges.ACKed + @ this.SavedChannelState.LocalChanges.Signed + } + RemoteChanges = + { this.SavedChannelState.RemoteChanges with + Signed = [] + } } - RemoteChanges = { - this.SavedChannelState.RemoteChanges with - Signed = [] + + return + { this with + SavedChannelState = savedChannelState + RemoteNextCommitInfo = + Some + <| RemoteNextCommitInfo.Revoked + msg.NextPerCommitmentPoint } + } + + member this.Close + (localShutdownScriptPubKey: ShutdownScriptPubKey) + : Result = + result { + if this.NegotiatingState.LocalRequestedShutdown.IsSome then + do! + Error + <| cannotCloseChannel "shutdown is already in progress" + + do! + Validation.checkShutdownScriptPubKeyAcceptable + this.SavedChannelState.StaticChannelConfig.LocalStaticShutdownScriptPubKey + localShutdownScriptPubKey + + if (this.Commitments.LocalHasUnsignedOutgoingHTLCs()) then + do! + Error + <| cannotCloseChannel + "Cannot close with unsigned outgoing htlcs" + + let shutdownMsg: ShutdownMsg = + { + ChannelId = + this.SavedChannelState.StaticChannelConfig.ChannelId() + ScriptPubKey = localShutdownScriptPubKey } - return { - this with - SavedChannelState = savedChannelState - RemoteNextCommitInfo = - Some <| RemoteNextCommitInfo.Revoked msg.NextPerCommitmentPoint - } - } - member this.Close (localShutdownScriptPubKey: ShutdownScriptPubKey) - : Result = result { - if this.NegotiatingState.LocalRequestedShutdown.IsSome then - do! Error <| cannotCloseChannel "shutdown is already in progress" - do! - Validation.checkShutdownScriptPubKeyAcceptable - this.SavedChannelState.StaticChannelConfig.LocalStaticShutdownScriptPubKey - localShutdownScriptPubKey - if (this.Commitments.LocalHasUnsignedOutgoingHTLCs()) then - do! Error <| cannotCloseChannel "Cannot close with unsigned outgoing htlcs" - let shutdownMsg: ShutdownMsg = { - ChannelId = this.SavedChannelState.StaticChannelConfig.ChannelId() - ScriptPubKey = localShutdownScriptPubKey - } - let channel = { - this with - NegotiatingState = { - this.NegotiatingState with - LocalRequestedShutdown = Some localShutdownScriptPubKey + let channel = + { this with + NegotiatingState = + { this.NegotiatingState with + LocalRequestedShutdown = + Some localShutdownScriptPubKey + } } + + return channel, shutdownMsg } - return channel, shutdownMsg - } static member private Hex = NBitcoin.DataEncoders.HexEncoder() static member private Ascii = System.Text.ASCIIEncoding.ASCII - static member private DummyPrivKey = new Key(Channel.Hex.DecodeData("0101010101010101010101010101010101010101010101010101010101010101")) + + static member private DummyPrivKey = + new Key( + Channel.Hex.DecodeData( + "0101010101010101010101010101010101010101010101010101010101010101" + ) + ) + static member private DummySig = - "01010101010101010101010101010101" |> Channel.Ascii.GetBytes + "01010101010101010101010101010101" + |> Channel.Ascii.GetBytes |> uint256 |> fun m -> Channel.DummyPrivKey.SignCompact(m) |> fun d -> LNECDSASignature.FromBytesCompact(d, true) |> fun ecdsaSig -> TransactionSignature(ecdsaSig.Value, SigHash.All) - member internal this.MakeClosingTx (localSpk: ShutdownScriptPubKey) - (remoteSpk: ShutdownScriptPubKey) - (closingFee: Money) = result { - let channelPrivKeys = this.ChannelPrivKeys - let staticChannelConfig = this.SavedChannelState.StaticChannelConfig - let dustLimit = Money.Max(staticChannelConfig.LocalParams.DustLimitSatoshis, staticChannelConfig.RemoteParams.DustLimitSatoshis) - let! closingTx = Transactions.makeClosingTx staticChannelConfig.FundingScriptCoin localSpk remoteSpk staticChannelConfig.IsFunder dustLimit closingFee this.SavedChannelState.LocalCommit.Spec staticChannelConfig.Network - let localSignature, psbtUpdated = channelPrivKeys.SignWithFundingPrivKey closingTx.Value - let msg: ClosingSignedMsg = { - ChannelId = staticChannelConfig.ChannelId() - FeeSatoshis = closingFee - Signature = localSignature.Signature |> LNECDSASignature + member internal this.MakeClosingTx + (localSpk: ShutdownScriptPubKey) + (remoteSpk: ShutdownScriptPubKey) + (closingFee: Money) + = + result { + let channelPrivKeys = this.ChannelPrivKeys + let staticChannelConfig = this.SavedChannelState.StaticChannelConfig + + let dustLimit = + Money.Max( + staticChannelConfig.LocalParams.DustLimitSatoshis, + staticChannelConfig.RemoteParams.DustLimitSatoshis + ) + + let! closingTx = + Transactions.makeClosingTx + staticChannelConfig.FundingScriptCoin + localSpk + remoteSpk + staticChannelConfig.IsFunder + dustLimit + closingFee + this.SavedChannelState.LocalCommit.Spec + staticChannelConfig.Network + + let localSignature, psbtUpdated = + channelPrivKeys.SignWithFundingPrivKey closingTx.Value + + let msg: ClosingSignedMsg = + { + ChannelId = staticChannelConfig.ChannelId() + FeeSatoshis = closingFee + Signature = localSignature.Signature |> LNECDSASignature + } + + return (ClosingTx psbtUpdated, msg) } - return (ClosingTx psbtUpdated, msg) - } - member internal this.FirstClosingFee (localSpk: ShutdownScriptPubKey) - (remoteSpk: ShutdownScriptPubKey) = result { - let feeEst = this.ChannelOptions.FeeEstimator - let staticChannelConfig = this.SavedChannelState.StaticChannelConfig - let! dummyClosingTx = - Transactions.makeClosingTx - staticChannelConfig.FundingScriptCoin - localSpk - remoteSpk - staticChannelConfig.IsFunder - Money.Zero - Money.Zero - this.SavedChannelState.LocalCommit.Spec - staticChannelConfig.Network - let tx = dummyClosingTx.Value.GetGlobalTransaction() - tx.Inputs.[0].WitScript <- - let witness = seq [ Channel.DummySig.ToBytes(); Channel.DummySig.ToBytes(); dummyClosingTx.Value.Inputs.[0].WitnessScript.ToBytes() ] - WitScript(witness) - let feeRatePerKw = - FeeRatePerKw.Max ( - feeEst.GetEstSatPer1000Weight(ConfirmationTarget.HighPriority), - this.SavedChannelState.LocalCommit.Spec.FeeRatePerKw - ) - return feeRatePerKw.CalculateFeeFromVirtualSize(tx) - } + member internal this.FirstClosingFee + (localSpk: ShutdownScriptPubKey) + (remoteSpk: ShutdownScriptPubKey) + = + result { + let feeEst = this.ChannelOptions.FeeEstimator + let staticChannelConfig = this.SavedChannelState.StaticChannelConfig + + let! dummyClosingTx = + Transactions.makeClosingTx + staticChannelConfig.FundingScriptCoin + localSpk + remoteSpk + staticChannelConfig.IsFunder + Money.Zero + Money.Zero + this.SavedChannelState.LocalCommit.Spec + staticChannelConfig.Network + + let tx = dummyClosingTx.Value.GetGlobalTransaction() + + tx.Inputs.[0].WitScript <- let witness = + seq + [ + Channel.DummySig.ToBytes() + Channel.DummySig.ToBytes() + dummyClosingTx.Value.Inputs.[0] + .WitnessScript.ToBytes() + ] + + WitScript(witness) + + let feeRatePerKw = + FeeRatePerKw.Max( + feeEst.GetEstSatPer1000Weight( + ConfirmationTarget.HighPriority + ), + this.SavedChannelState.LocalCommit.Spec.FeeRatePerKw + ) + + return feeRatePerKw.CalculateFeeFromVirtualSize(tx) + } - static member internal NextClosingFee (localClosingFee: Money, remoteClosingFee: Money) = + static member internal NextClosingFee + ( + localClosingFee: Money, + remoteClosingFee: Money + ) = ((localClosingFee.Satoshi + remoteClosingFee.Satoshi) / 4L) * 2L |> Money.Satoshis - member this.RemoteShutdown (msg: ShutdownMsg) - (localShutdownScriptPubKey: ShutdownScriptPubKey) - : Result * Option, ChannelError> = result { - let remoteShutdownScriptPubKey = msg.ScriptPubKey - do! - Validation.checkShutdownScriptPubKeyAcceptable - this.SavedChannelState.StaticChannelConfig.LocalStaticShutdownScriptPubKey - localShutdownScriptPubKey - do! - Validation.checkShutdownScriptPubKeyAcceptable - this.SavedChannelState.StaticChannelConfig.RemoteStaticShutdownScriptPubKey - remoteShutdownScriptPubKey - let cm = this.Commitments - // They have pending unsigned htlcs => they violated the spec, close the channel - // they don't have pending unsigned htlcs - // We have pending unsigned htlcs - // We already sent a shutdown msg => spec violation (we can't send htlcs after having sent shutdown) - // We did not send a shutdown msg - // We are ready to sign => we stop sending further htlcs, we initiate a signature - // We are waiting for a rev => we stop sending further htlcs, we wait for their revocation, will resign immediately after, and then we will send our shutdown msg - // We have no pending unsigned htlcs - // we already sent a shutdown msg - // There are pending signed htlcs => send our shutdown msg, go to SHUTDOWN state - // there are no htlcs => send our shutdown msg, goto NEGOTIATING state - // We did not send a shutdown msg - // There are pending signed htlcs => go to SHUTDOWN state - // there are no HTLCs => go to NEGOTIATING state - - if (cm.RemoteHasUnsignedOutgoingHTLCs()) then - return! receivedShutdownWhenRemoteHasUnsignedOutgoingHTLCs msg - // Do we have Unsigned Outgoing HTLCs? - else if (cm.LocalHasUnsignedOutgoingHTLCs()) then - let channel = { - this with - NegotiatingState = { - this.NegotiatingState with - RemoteRequestedShutdown = Some remoteShutdownScriptPubKey - } - } - return channel, None, None - else - let localShutdownMsgOpt: Option = - match this.NegotiatingState.LocalRequestedShutdown with - | None -> - Some { - ChannelId = this.SavedChannelState.StaticChannelConfig.ChannelId() - ScriptPubKey = localShutdownScriptPubKey + member this.RemoteShutdown + (msg: ShutdownMsg) + (localShutdownScriptPubKey: ShutdownScriptPubKey) + : Result * Option, ChannelError> = + result { + let remoteShutdownScriptPubKey = msg.ScriptPubKey + + do! + Validation.checkShutdownScriptPubKeyAcceptable + this.SavedChannelState.StaticChannelConfig.LocalStaticShutdownScriptPubKey + localShutdownScriptPubKey + + do! + Validation.checkShutdownScriptPubKeyAcceptable + this.SavedChannelState.StaticChannelConfig.RemoteStaticShutdownScriptPubKey + remoteShutdownScriptPubKey + + let cm = this.Commitments + // They have pending unsigned htlcs => they violated the spec, close the channel + // they don't have pending unsigned htlcs + // We have pending unsigned htlcs + // We already sent a shutdown msg => spec violation (we can't send htlcs after having sent shutdown) + // We did not send a shutdown msg + // We are ready to sign => we stop sending further htlcs, we initiate a signature + // We are waiting for a rev => we stop sending further htlcs, we wait for their revocation, will resign immediately after, and then we will send our shutdown msg + // We have no pending unsigned htlcs + // we already sent a shutdown msg + // There are pending signed htlcs => send our shutdown msg, go to SHUTDOWN state + // there are no htlcs => send our shutdown msg, goto NEGOTIATING state + // We did not send a shutdown msg + // There are pending signed htlcs => go to SHUTDOWN state + // there are no HTLCs => go to NEGOTIATING state + + if (cm.RemoteHasUnsignedOutgoingHTLCs()) then + return! receivedShutdownWhenRemoteHasUnsignedOutgoingHTLCs msg + // Do we have Unsigned Outgoing HTLCs? + else if (cm.LocalHasUnsignedOutgoingHTLCs()) then + let channel = + { this with + NegotiatingState = + { this.NegotiatingState with + RemoteRequestedShutdown = + Some remoteShutdownScriptPubKey + } } - | Some _ -> None - let hasNoPendingHTLCs = - match this.RemoteNextCommitInfo with - | None -> true - | Some remoteNextCommitInfo -> - this.SavedChannelState.HasNoPendingHTLCs remoteNextCommitInfo - if hasNoPendingHTLCs then - // we have to send first closing_signed msg iif we are the funder - if this.SavedChannelState.StaticChannelConfig.IsFunder then - let! closingFee = + + return channel, None, None + else + let localShutdownMsgOpt: Option = + match this.NegotiatingState.LocalRequestedShutdown with + | None -> + Some + { + ChannelId = + this.SavedChannelState.StaticChannelConfig.ChannelId + () + ScriptPubKey = localShutdownScriptPubKey + } + | Some _ -> None + + let hasNoPendingHTLCs = + match this.RemoteNextCommitInfo with + | None -> true + | Some remoteNextCommitInfo -> + this.SavedChannelState.HasNoPendingHTLCs + remoteNextCommitInfo + + if hasNoPendingHTLCs then + // we have to send first closing_signed msg iif we are the funder + if this.SavedChannelState.StaticChannelConfig.IsFunder then + let! closingFee = + this.FirstClosingFee + localShutdownScriptPubKey + remoteShutdownScriptPubKey + |> expectTransactionError + + let! (_closingTx, closingSignedMsg) = + this.MakeClosingTx + localShutdownScriptPubKey + remoteShutdownScriptPubKey + closingFee + |> expectTransactionError + + let nextState = + { + LocalRequestedShutdown = + Some localShutdownScriptPubKey + RemoteRequestedShutdown = + Some remoteShutdownScriptPubKey + LocalClosingFeesProposed = [ closingFee ] + RemoteClosingFeeProposed = None + } + + let channel = + { this with + NegotiatingState = nextState + } + + return + channel, localShutdownMsgOpt, Some closingSignedMsg + else + let nextState = + { + LocalRequestedShutdown = + Some localShutdownScriptPubKey + RemoteRequestedShutdown = + Some remoteShutdownScriptPubKey + LocalClosingFeesProposed = [] + RemoteClosingFeeProposed = None + } + + let channel = + { this with + NegotiatingState = nextState + } + + return channel, localShutdownMsgOpt, None + else + let channel = + { this with + NegotiatingState = + { this.NegotiatingState with + LocalRequestedShutdown = + Some localShutdownScriptPubKey + RemoteRequestedShutdown = + Some remoteShutdownScriptPubKey + } + } + + return channel, localShutdownMsgOpt, None + } + + member this.ApplyClosingSigned + (msg: ClosingSignedMsg) + : Result = + result { + let! localShutdownScriptPubKey, remoteShutdownScriptPubKey = + match (this.NegotiatingState.LocalRequestedShutdown, + this.NegotiatingState.RemoteRequestedShutdown) + with + | (Some localShutdownScriptPubKey, + Some remoteShutdownScriptPubKey) -> + Ok(localShutdownScriptPubKey, remoteShutdownScriptPubKey) + // FIXME: these should be new channel errors + | (Some _, None) -> + Error ReceivedClosingSignedBeforeReceivingShutdown + | (None, Some _) -> + Error ReceivedClosingSignedBeforeSendingShutdown + | (None, None) -> + Error ReceivedClosingSignedBeforeSendingOrReceivingShutdown + + let remoteChannelKeys = + this.SavedChannelState.StaticChannelConfig.RemoteChannelPubKeys + + let lastCommitFeeSatoshi = + this.SavedChannelState.StaticChannelConfig.FundingScriptCoin.TxOut.Value + - (this.SavedChannelState.LocalCommit.PublishableTxs.CommitTx.Value.TotalOut) + + do! + checkRemoteProposedHigherFeeThanBaseFee + lastCommitFeeSatoshi + msg.FeeSatoshis + + do! + checkRemoteProposedFeeWithinNegotiatedRange + (List.tryHead this.NegotiatingState.LocalClosingFeesProposed) + (Option.map + (fun (fee, _sig) -> fee) + this.NegotiatingState.RemoteClosingFeeProposed) + msg.FeeSatoshis + + let! closingTx, closingSignedMsg = + this.MakeClosingTx + localShutdownScriptPubKey + remoteShutdownScriptPubKey + msg.FeeSatoshis + |> expectTransactionError + + let! finalizedTx = + Transactions.checkTxFinalized + closingTx.Value + closingTx.WhichInput + (seq + [ + remoteChannelKeys.FundingPubKey.RawPubKey(), + TransactionSignature( + msg.Signature.Value, + SigHash.All + ) + ]) + |> expectTransactionError + + let maybeLocalFee = + this.NegotiatingState.LocalClosingFeesProposed |> List.tryHead + + let areWeInDeal = Some(msg.FeeSatoshis) = maybeLocalFee + + let hasTooManyNegotiationDone = + (this.NegotiatingState.LocalClosingFeesProposed |> List.length) + >= this.ChannelOptions.MaxClosingNegotiationIterations + + if (areWeInDeal || hasTooManyNegotiationDone) then + return this, MutualClose(finalizedTx, None) + else + let lastLocalClosingFee = + this.NegotiatingState.LocalClosingFeesProposed + |> List.tryHead + + let! localF = + match lastLocalClosingFee with + | Some v -> Ok v + | None -> this.FirstClosingFee localShutdownScriptPubKey remoteShutdownScriptPubKey |> expectTransactionError - let! (_closingTx, closingSignedMsg) = + + let nextClosingFee = + Channel.NextClosingFee(localF, msg.FeeSatoshis) + + if (Some nextClosingFee = lastLocalClosingFee) then + return this, MutualClose(finalizedTx, None) + else if (nextClosingFee = msg.FeeSatoshis) then + // we have reached on agreement! + return this, MutualClose(finalizedTx, Some closingSignedMsg) + else + let! _closingTx, closingSignedMsg = this.MakeClosingTx localShutdownScriptPubKey remoteShutdownScriptPubKey - closingFee + nextClosingFee |> expectTransactionError - let nextState = { - LocalRequestedShutdown = Some localShutdownScriptPubKey - RemoteRequestedShutdown = Some remoteShutdownScriptPubKey - LocalClosingFeesProposed = [ closingFee ] - RemoteClosingFeeProposed = None - } - let channel = { - this with - NegotiatingState = nextState - } - return channel, localShutdownMsgOpt, Some closingSignedMsg - else - let nextState = { - LocalRequestedShutdown = Some localShutdownScriptPubKey - RemoteRequestedShutdown = Some remoteShutdownScriptPubKey - LocalClosingFeesProposed = [] - RemoteClosingFeeProposed = None - } - let channel = { - this with + + let nextState = + { this.NegotiatingState with + LocalClosingFeesProposed = + nextClosingFee + :: this.NegotiatingState.LocalClosingFeesProposed + RemoteClosingFeeProposed = + Some(msg.FeeSatoshis, msg.Signature) + } + + let channel = + { this with NegotiatingState = nextState - } - return channel, localShutdownMsgOpt, None - else - let channel = { - this with - NegotiatingState = { - this.NegotiatingState with - LocalRequestedShutdown = Some localShutdownScriptPubKey - RemoteRequestedShutdown = Some remoteShutdownScriptPubKey } - } - return channel, localShutdownMsgOpt, None - } - member this.ApplyClosingSigned (msg: ClosingSignedMsg) - : Result = result { - let! localShutdownScriptPubKey, remoteShutdownScriptPubKey = - match (this.NegotiatingState.LocalRequestedShutdown, this.NegotiatingState.RemoteRequestedShutdown) with - | (Some localShutdownScriptPubKey, Some remoteShutdownScriptPubKey) -> - Ok (localShutdownScriptPubKey, remoteShutdownScriptPubKey) - // FIXME: these should be new channel errors - | (Some _, None) -> - Error ReceivedClosingSignedBeforeReceivingShutdown - | (None, Some _) -> - Error ReceivedClosingSignedBeforeSendingShutdown - | (None, None) -> - Error ReceivedClosingSignedBeforeSendingOrReceivingShutdown - let remoteChannelKeys = this.SavedChannelState.StaticChannelConfig.RemoteChannelPubKeys - let lastCommitFeeSatoshi = - this.SavedChannelState.StaticChannelConfig.FundingScriptCoin.TxOut.Value - (this.SavedChannelState.LocalCommit.PublishableTxs.CommitTx.Value.TotalOut) - do! checkRemoteProposedHigherFeeThanBaseFee lastCommitFeeSatoshi msg.FeeSatoshis - do! - checkRemoteProposedFeeWithinNegotiatedRange - (List.tryHead this.NegotiatingState.LocalClosingFeesProposed) - (Option.map (fun (fee, _sig) -> fee) this.NegotiatingState.RemoteClosingFeeProposed) - msg.FeeSatoshis - - let! closingTx, closingSignedMsg = - this.MakeClosingTx - localShutdownScriptPubKey - remoteShutdownScriptPubKey - msg.FeeSatoshis - |> expectTransactionError - let! finalizedTx = - Transactions.checkTxFinalized - closingTx.Value - closingTx.WhichInput - (seq [ - remoteChannelKeys.FundingPubKey.RawPubKey(), - TransactionSignature(msg.Signature.Value, SigHash.All) - ]) - |> expectTransactionError - let maybeLocalFee = - this.NegotiatingState.LocalClosingFeesProposed - |> List.tryHead - let areWeInDeal = Some(msg.FeeSatoshis) = maybeLocalFee - let hasTooManyNegotiationDone = - (this.NegotiatingState.LocalClosingFeesProposed |> List.length) >= this.ChannelOptions.MaxClosingNegotiationIterations - if (areWeInDeal || hasTooManyNegotiationDone) then - return this, MutualClose (finalizedTx, None) - else - let lastLocalClosingFee = this.NegotiatingState.LocalClosingFeesProposed |> List.tryHead - let! localF = - match lastLocalClosingFee with - | Some v -> Ok v - | None -> - this.FirstClosingFee - localShutdownScriptPubKey - remoteShutdownScriptPubKey - |> expectTransactionError - let nextClosingFee = - Channel.NextClosingFee (localF, msg.FeeSatoshis) - if (Some nextClosingFee = lastLocalClosingFee) then - return this, MutualClose (finalizedTx, None) - else if (nextClosingFee = msg.FeeSatoshis) then - // we have reached on agreement! - return this, MutualClose (finalizedTx, Some closingSignedMsg) - else - let! _closingTx, closingSignedMsg = - this.MakeClosingTx - localShutdownScriptPubKey - remoteShutdownScriptPubKey - nextClosingFee - |> expectTransactionError - let nextState = { - this.NegotiatingState with - LocalClosingFeesProposed = - nextClosingFee :: this.NegotiatingState.LocalClosingFeesProposed - RemoteClosingFeeProposed = Some (msg.FeeSatoshis, msg.Signature) - } - let channel = { - this with - NegotiatingState = nextState - } - return channel, NewClosingSigned closingSignedMsg - } + return channel, NewClosingSigned closingSignedMsg + } member this.LocalHasChanges() = (not this.SavedChannelState.RemoteChanges.ACKed.IsEmpty) @@ -1124,88 +1571,132 @@ and Channel = { (not this.SavedChannelState.LocalChanges.ACKed.IsEmpty) || (not this.Commitments.ProposedRemoteChanges.IsEmpty) - member private this.sendCommit (remoteNextCommitInfo: RemoteNextCommitInfo) - : Result = + member private this.sendCommit + (remoteNextCommitInfo: RemoteNextCommitInfo) + : Result = let channelPrivKeys = this.ChannelPrivKeys let cm = this.Commitments let savedChannelState = this.SavedChannelState + match remoteNextCommitInfo with | RemoteNextCommitInfo.Revoked remoteNextPerCommitmentPoint -> result { // remote commitment will include all local changes + remote acked changes - let! spec = savedChannelState.RemoteCommit.Spec.Reduce(savedChannelState.RemoteChanges.ACKed, cm.ProposedLocalChanges) |> expectTransactionError + let! spec = + savedChannelState.RemoteCommit.Spec.Reduce( + savedChannelState.RemoteChanges.ACKed, + cm.ProposedLocalChanges + ) + |> expectTransactionError + let! (remoteCommitTx, htlcTimeoutTxs, htlcSuccessTxs) = - Commitments.Helpers.makeRemoteTxs savedChannelState.StaticChannelConfig - (savedChannelState.RemoteCommit.Index.NextCommitment()) - (channelPrivKeys.ToChannelPubKeys()) - (remoteNextPerCommitmentPoint) - (spec) + Commitments.Helpers.makeRemoteTxs + savedChannelState.StaticChannelConfig + (savedChannelState.RemoteCommit.Index.NextCommitment()) + (channelPrivKeys.ToChannelPubKeys()) + (remoteNextPerCommitmentPoint) + (spec) |> expectTransactionErrors - let signature,_ = channelPrivKeys.SignWithFundingPrivKey remoteCommitTx.Value - let sortedHTLCTXs = Commitments.Helpers.sortBothHTLCs htlcTimeoutTxs htlcSuccessTxs + + let signature, _ = + channelPrivKeys.SignWithFundingPrivKey remoteCommitTx.Value + + let sortedHTLCTXs = + Commitments.Helpers.sortBothHTLCs + htlcTimeoutTxs + htlcSuccessTxs + let htlcSigs = sortedHTLCTXs |> List.map( - (fun htlc -> channelPrivKeys.SignHtlcTx htlc.Value remoteNextPerCommitmentPoint) - >> fst - >> (fun txSig -> txSig.Signature) - ) - let msg = { - CommitmentSignedMsg.ChannelId = savedChannelState.StaticChannelConfig.ChannelId() - Signature = !> signature.Signature - HTLCSignatures = htlcSigs |> List.map (!>) - } - let nextRemoteCommitInfo = { - savedChannelState.RemoteCommit - with - Index = savedChannelState.RemoteCommit.Index.NextCommitment() + (fun htlc -> + channelPrivKeys.SignHtlcTx + htlc.Value + remoteNextPerCommitmentPoint + ) + >> fst + >> (fun txSig -> txSig.Signature) + ) + + let msg = + { + CommitmentSignedMsg.ChannelId = + savedChannelState.StaticChannelConfig.ChannelId() + Signature = !>signature.Signature + HTLCSignatures = htlcSigs |> List.map(!>) + } + + let nextRemoteCommitInfo = + { savedChannelState.RemoteCommit with + Index = + savedChannelState.RemoteCommit.Index.NextCommitment + () TxId = remoteCommitTx.GetTxId() Spec = spec RemotePerCommitmentPoint = remoteNextPerCommitmentPoint - } - let nextCommitments = { - cm with + } + + let nextCommitments = + { cm with ProposedLocalChanges = [] - } - let nextSavedChannelState = { - this.SavedChannelState with - LocalChanges = { - this.SavedChannelState.LocalChanges with + } + + let nextSavedChannelState = + { this.SavedChannelState with + LocalChanges = + { this.SavedChannelState.LocalChanges with Signed = cm.ProposedLocalChanges - } - RemoteChanges = { - this.SavedChannelState.RemoteChanges with + } + RemoteChanges = + { this.SavedChannelState.RemoteChanges with ACKed = [] - Signed = this.SavedChannelState.RemoteChanges.ACKed - } - } - let channel = { - this with + Signed = + this.SavedChannelState.RemoteChanges.ACKed + } + } + + let channel = + { this with Commitments = nextCommitments SavedChannelState = nextSavedChannelState RemoteNextCommitInfo = - Some <| RemoteNextCommitInfo.Waiting nextRemoteCommitInfo - } + Some + <| RemoteNextCommitInfo.Waiting nextRemoteCommitInfo + } + return msg, channel } - | RemoteNextCommitInfo.Waiting _ -> - CanNotSignBeforeRevocation |> Error + | RemoteNextCommitInfo.Waiting _ -> CanNotSignBeforeRevocation |> Error - member private this.receiveCommit (msg: CommitmentSignedMsg) - : Result = + member private this.receiveCommit + (msg: CommitmentSignedMsg) + : Result = let channelPrivKeys = this.ChannelPrivKeys let cm = this.Commitments let savedChannelState = this.SavedChannelState + if this.RemoteHasChanges() |> not then ReceivedCommitmentSignedWhenWeHaveNoPendingChanges |> Error else let commitmentSeed = channelPrivKeys.CommitmentSeed let localChannelKeys = channelPrivKeys.ToChannelPubKeys() - let remoteChannelKeys = savedChannelState.StaticChannelConfig.RemoteChannelPubKeys + + let remoteChannelKeys = + savedChannelState.StaticChannelConfig.RemoteChannelPubKeys + let nextI = savedChannelState.LocalCommit.Index.NextCommitment() + result { - let! spec = savedChannelState.LocalCommit.Spec.Reduce(savedChannelState.LocalChanges.ACKed, cm.ProposedRemoteChanges) |> expectTransactionError - let localPerCommitmentPoint = commitmentSeed.DerivePerCommitmentPoint nextI + let! spec = + savedChannelState.LocalCommit.Spec.Reduce( + savedChannelState.LocalChanges.ACKed, + cm.ProposedRemoteChanges + ) + |> expectTransactionError + + let localPerCommitmentPoint = + commitmentSeed.DerivePerCommitmentPoint nextI + let! (localCommitTx, htlcTimeoutTxs, htlcSuccessTxs) = Commitments.Helpers.makeLocalTXs savedChannelState.StaticChannelConfig @@ -1214,37 +1705,86 @@ and Channel = { localPerCommitmentPoint spec |> expectTransactionErrors - let signature, signedCommitTx = channelPrivKeys.SignWithFundingPrivKey localCommitTx.Value + + let signature, signedCommitTx = + channelPrivKeys.SignWithFundingPrivKey localCommitTx.Value let sigPair = - let localSigPair = seq [(localChannelKeys.FundingPubKey.RawPubKey(), signature)] - let remoteSigPair = seq[ (remoteChannelKeys.FundingPubKey.RawPubKey(), TransactionSignature(msg.Signature.Value, SigHash.All)) ] + let localSigPair = + seq + [ + (localChannelKeys.FundingPubKey.RawPubKey(), + signature) + ] + + let remoteSigPair = + seq[(remoteChannelKeys.FundingPubKey.RawPubKey(), + TransactionSignature( + msg.Signature.Value, + SigHash.All + ))] + Seq.append localSigPair remoteSigPair + let tmp = - Transactions.checkTxFinalized signedCommitTx CommitTx.WhichInput sigPair + Transactions.checkTxFinalized + signedCommitTx + CommitTx.WhichInput + sigPair |> expectTransactionError + let! finalizedCommitTx = tmp - let sortedHTLCTXs = Commitments.Helpers.sortBothHTLCs htlcTimeoutTxs htlcSuccessTxs + + let sortedHTLCTXs = + Commitments.Helpers.sortBothHTLCs + htlcTimeoutTxs + htlcSuccessTxs + do! Commitments.checkSignatureCountMismatch sortedHTLCTXs msg - + let _localHTLCSigs, sortedHTLCTXs = let localHtlcSigsAndHTLCTxs = - sortedHTLCTXs |> List.map(fun htlc -> - channelPrivKeys.SignHtlcTx htlc.Value localPerCommitmentPoint + sortedHTLCTXs + |> List.map(fun htlc -> + channelPrivKeys.SignHtlcTx + htlc.Value + localPerCommitmentPoint ) - localHtlcSigsAndHTLCTxs |> List.map(fst), localHtlcSigsAndHTLCTxs |> List.map(snd) |> Seq.cast |> List.ofSeq - let remoteHTLCPubKey = localPerCommitmentPoint.DeriveHtlcPubKey remoteChannelKeys.HtlcBasepoint + localHtlcSigsAndHTLCTxs |> List.map(fst), + localHtlcSigsAndHTLCTxs + |> List.map(snd) + |> Seq.cast + |> List.ofSeq + + let remoteHTLCPubKey = + localPerCommitmentPoint.DeriveHtlcPubKey + remoteChannelKeys.HtlcBasepoint + + let checkHTLCSig + ( + htlc: IHTLCTx, + remoteECDSASig: LNECDSASignature + ) : Result<_, _> = + let remoteS = + TransactionSignature(remoteECDSASig.Value, SigHash.All) - let checkHTLCSig (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)])) + (Transactions.checkTxFinalized + (htlc.Value) + (0) + (seq + [ + (remoteHTLCPubKey.RawPubKey(), remoteS) + ])) |> 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 -> - (Transactions.checkSigAndAdd (htlc) (remoteS) (remoteHTLCPubKey.RawPubKey())) + (Transactions.checkSigAndAdd + (htlc) + (remoteS) + (remoteHTLCPubKey.RawPubKey())) |> Result.map(box) | _ -> failwith "Unreachable!" @@ -1253,66 +1793,107 @@ and Channel = { |> List.map(checkHTLCSig) |> List.sequenceResultA |> expectTransactionErrors + let successTxs = - txList |> List.choose(fun o -> + txList + |> List.choose(fun o -> match o with | :? HTLCSuccessTx as tx -> Some tx | _ -> None ) + let finalizedTxs = - txList |> List.choose(fun o -> + txList + |> List.choose(fun o -> match o with | :? FinalizedTx as tx -> Some tx | _ -> None ) + let localPerCommitmentSecret = - channelPrivKeys.CommitmentSeed.DerivePerCommitmentSecret savedChannelState.LocalCommit.Index + channelPrivKeys.CommitmentSeed.DerivePerCommitmentSecret + savedChannelState.LocalCommit.Index + let localNextPerCommitmentPoint = let perCommitmentSecret = - channelPrivKeys.CommitmentSeed.DerivePerCommitmentSecret - (savedChannelState.LocalCommit.Index.NextCommitment().NextCommitment()) + channelPrivKeys.CommitmentSeed.DerivePerCommitmentSecret( + savedChannelState + .LocalCommit + .Index + .NextCommitment() + .NextCommitment() + ) + perCommitmentSecret.PerCommitmentPoint() - let nextMsg = { - RevokeAndACKMsg.ChannelId = savedChannelState.StaticChannelConfig.ChannelId() - PerCommitmentSecret = localPerCommitmentSecret - NextPerCommitmentPoint = localNextPerCommitmentPoint - } - - let localCommit1 = { LocalCommit.Index = savedChannelState.LocalCommit.Index.NextCommitment() - Spec = spec - PublishableTxs = { PublishableTxs.CommitTx = finalizedCommitTx - HTLCTxs = finalizedTxs } - PendingHTLCSuccessTxs = successTxs } - let nextSavedChannelState = { - savedChannelState with + let nextMsg = + { + RevokeAndACKMsg.ChannelId = + savedChannelState.StaticChannelConfig.ChannelId() + PerCommitmentSecret = localPerCommitmentSecret + NextPerCommitmentPoint = localNextPerCommitmentPoint + } + + let localCommit1 = + { + LocalCommit.Index = + savedChannelState.LocalCommit.Index.NextCommitment() + Spec = spec + PublishableTxs = + { + PublishableTxs.CommitTx = finalizedCommitTx + HTLCTxs = finalizedTxs + } + PendingHTLCSuccessTxs = successTxs + } + + let nextSavedChannelState = + { savedChannelState with LocalCommit = localCommit1 - LocalChanges = { - savedChannelState.LocalChanges with + LocalChanges = + { savedChannelState.LocalChanges with ACKed = [] - } - RemoteChanges = { - savedChannelState.RemoteChanges with - ACKed = (savedChannelState.RemoteChanges.ACKed @ cm.ProposedRemoteChanges) - } - } + } + RemoteChanges = + { savedChannelState.RemoteChanges with + ACKed = + (savedChannelState.RemoteChanges.ACKed + @ cm.ProposedRemoteChanges) + } + } + let nextCommitments = let completedOutgoingHTLCs = - let t1 = savedChannelState.LocalCommit.Spec.OutgoingHTLCs - |> Map.toSeq |> Seq.map (fun (k, _) -> k) |> Set.ofSeq - let t2 = localCommit1.Spec.OutgoingHTLCs - |> Map.toSeq |> Seq.map (fun (k, _) -> k) |> Set.ofSeq + let t1 = + savedChannelState.LocalCommit.Spec.OutgoingHTLCs + |> Map.toSeq + |> Seq.map(fun (k, _) -> k) + |> Set.ofSeq + + let t2 = + localCommit1.Spec.OutgoingHTLCs + |> Map.toSeq + |> Seq.map(fun (k, _) -> k) + |> Set.ofSeq + Set.difference t1 t2 - let originChannels1 = cm.OriginChannels |> Map.filter(fun k _ -> Set.contains k completedOutgoingHTLCs) - { - cm with - ProposedRemoteChanges = [] - OriginChannels = originChannels1 + + let originChannels1 = + cm.OriginChannels + |> Map.filter(fun k _ -> + Set.contains k completedOutgoingHTLCs + ) + + { cm with + ProposedRemoteChanges = [] + OriginChannels = originChannels1 } - let nextChannel = { - this with + + let nextChannel = + { this with SavedChannelState = nextSavedChannelState Commitments = nextCommitments - } + } + return nextMsg, nextChannel } diff --git a/src/DotNetLightning.Core/Channel/ChannelConstants.fs b/src/DotNetLightning.Core/Channel/ChannelConstants.fs index 4549aed68..2c6808345 100644 --- a/src/DotNetLightning.Core/Channel/ChannelConstants.fs +++ b/src/DotNetLightning.Core/Channel/ChannelConstants.fs @@ -1,14 +1,18 @@ namespace DotNetLightning.Channel + open DotNetLightning.Utils open NBitcoin -type ChannelValueStat = internal { - ValueToSelf: LNMoney; - ChannelValue: LNMoney; - ChannelReserve: LNMoney; - PendingOutboundHTLCsAmount: LNMoney; - PendingInBoundHTLCsAmount: LNMoney; - HoldingCellOutBoundAmount: LNMoney; -} + +type ChannelValueStat = + internal + { + ValueToSelf: LNMoney + ChannelValue: LNMoney + ChannelReserve: LNMoney + PendingOutboundHTLCsAmount: LNMoney + PendingInBoundHTLCsAmount: LNMoney + HoldingCellOutBoundAmount: LNMoney + } [] module ChannelConstants = @@ -22,7 +26,6 @@ module ChannelConstants = let MAX_FUNDING_SATOSHIS = Money.Satoshis(16777216m) // (1 << 24) [] - /// see refs: https://github.com/lightningnetwork/lightning-rfc/blob/master/07-routing-gossip.md#requirements let UNCONF_THRESHOLD = 6u /// The amount of time we require our counterparty wait to claim their money (i.e. time between when @@ -39,13 +42,14 @@ module ChannelConstants = [] let COMMITMENT_TX_BASE_WEIGHT = 724UL + [] let COMMITMENT_TX_WEIGHT_PER_HTLC = 172UL // prevout: 36, nSequence: 4, script len: 1, witness lengths: (3+1)/4, sig: 73/4, if-selector: 1, redeemScript: (6 ops + 2*33 pubkeys + 1*2 delay)/4 [] - let SPENDING_INPUT_FOR_A_OUTPUT_WEIGHT = 79UL + let SPENDING_INPUT_FOR_A_OUTPUT_WEIGHT = 79UL // prevout: 40, nSequence: 4, script len: 1, witness lengths: 3/4, sig: 73/4, pubkey: 33/4, output: 31 [] let B_OUTPUT_PLUS_SPENDING_INPUT_WEIGHT = 104UL @@ -53,6 +57,7 @@ module ChannelConstants = [] let ACCEPTED_HTLC_SCRIPT_WEIGHT = 139uy + [] let OFFERED_HTLC_SCRIPT_WEIGHT = 133uy @@ -60,4 +65,4 @@ module ChannelConstants = let HTLC_SUCCESS_TX_WEIGHT = 703UL [] - let HTLC_TIMEOUT_TX_WEIGHT = 663UL \ No newline at end of file + let HTLC_TIMEOUT_TX_WEIGHT = 663UL diff --git a/src/DotNetLightning.Core/Channel/ChannelError.fs b/src/DotNetLightning.Core/Channel/ChannelError.fs index e57787c47..c4bfab9a2 100644 --- a/src/DotNetLightning.Core/Channel/ChannelError.fs +++ b/src/DotNetLightning.Core/Channel/ChannelError.fs @@ -16,26 +16,34 @@ open ResultUtils.Portability type ChannelError = | CryptoError of CryptoError - | TransactionRelatedErrors of TransactionError list - + | TransactionRelatedErrors of list + | HTLCAlreadySent of HTLCId - | InvalidPaymentPreimage of expectedHash: PaymentHash * actualPreimage: PaymentPreimage + | InvalidPaymentPreimage of + expectedHash: PaymentHash * + actualPreimage: PaymentPreimage | UnknownHTLCId of HTLCId | HTLCOriginNotKnown of HTLCId | InvalidFailureCode of FailureCode | APIMisuse of string - | WeCannotAffordFee of localChannelReserve: Money * requiredFee: Money * missingAmount: Money + | WeCannotAffordFee of + localChannelReserve: Money * + requiredFee: Money * + missingAmount: Money | CanNotSignBeforeRevocation | ReceivedCommitmentSignedWhenWeHaveNoPendingChanges | SignatureCountMismatch of expected: int * actual: int /// protocol violation defined in BOLT 02 | ReceivedShutdownWhenRemoteHasUnsignedOutgoingHTLCs of msg: ShutdownMsg - + /// When we create the first commitment txs as fundee, /// There might be a case that their initial funding amount is too low that it /// cannot afford fee - | TheyCannotAffordFee of toRemote: LNMoney * fee: Money * channelReserve: Money - + | TheyCannotAffordFee of + toRemote: LNMoney * + fee: Money * + channelReserve: Money + // --- case they sent unacceptable msg --- | InvalidFundingLocked of InvalidFundingLockedError | InvalidOpenChannel of InvalidOpenChannelError @@ -44,29 +52,36 @@ type ChannelError = | InvalidRevokeAndACK of InvalidRevokeAndACKError | InvalidUpdateFee of InvalidUpdateFeeError // ------------------ - + /// Consumer of the api (usually, that is wallet) failed to give an funding tx | ReceivedClosingSignedBeforeReceivingShutdown | ReceivedClosingSignedBeforeSendingShutdown | ReceivedClosingSignedBeforeSendingOrReceivingShutdown | FundingTxNotGiven of msg: string - | OnceConfirmedFundingTxHasBecomeUnconfirmed of height: BlockHeight * depth: BlockHeightOffset32 + | OnceConfirmedFundingTxHasBecomeUnconfirmed of + height: BlockHeight * + depth: BlockHeightOffset32 | CannotCloseChannel of msg: string | RemoteProposedHigherFeeThanBaseFee of baseFee: Money * proposedFee: Money - | RemoteProposedFeeOutOfNegotiatedRange of ourPreviousFee: Money * theirPreviousFee: Money * theirNextFee: Money + | RemoteProposedFeeOutOfNegotiatedRange of + ourPreviousFee: Money * + theirPreviousFee: Money * + theirNextFee: Money | NoUpdatesToSign | CannotSignCommitmentBeforeRevocation - | InsufficientConfirmations of requiredDepth: BlockHeightOffset32 * currentDepth: BlockHeightOffset32 + | InsufficientConfirmations of + requiredDepth: BlockHeightOffset32 * + currentDepth: BlockHeightOffset32 // ---- invalid command ---- | InvalidOperationAddHTLC of InvalidOperationAddHTLCError // ------------------------- - + member this.RecommendedAction = match this with | CryptoError _ -> ReportAndCrash | TransactionRelatedErrors _ -> Close | HTLCAlreadySent _ -> Ignore - | InvalidPaymentPreimage (_, _) -> Close + | InvalidPaymentPreimage(_, _) -> Close | UnknownHTLCId _ -> Close | HTLCOriginNotKnown _ -> Close | InvalidFailureCode _ -> Close @@ -75,8 +90,8 @@ type ChannelError = | CanNotSignBeforeRevocation -> Close | ReceivedCommitmentSignedWhenWeHaveNoPendingChanges -> Close | ReceivedShutdownWhenRemoteHasUnsignedOutgoingHTLCs _ -> Close - | SignatureCountMismatch (_, _) -> Close - | TheyCannotAffordFee (_, _, _) -> Close + | SignatureCountMismatch(_, _) -> Close + | TheyCannotAffordFee(_, _, _) -> Close | InvalidFundingLocked _ -> DistrustPeer | InvalidOpenChannel _ -> DistrustPeer | InvalidAcceptChannel _ -> DistrustPeer @@ -95,65 +110,101 @@ type ChannelError = | InvalidOperationAddHTLC _ -> Ignore | RemoteProposedHigherFeeThanBaseFee(_, _) -> Close | RemoteProposedFeeOutOfNegotiatedRange(_, _, _) -> Close - + member this.Message = match this with | HTLCAlreadySent htlcId -> - sprintf "We have already sent a fail/fulfill for htlc id %i" htlcId.Value + sprintf + "We have already sent a fail/fulfill for htlc id %i" + htlcId.Value | InvalidPaymentPreimage(e, actual) -> - sprintf "Invalid HTLC PreImage %A. Hash (%A) does not match the one expected %A" - actual - actual.Hash - e + sprintf + "Invalid HTLC PreImage %A. Hash (%A) does not match the one expected %A" + actual + actual.Hash + e | InvalidFailureCode errorCode -> - sprintf "invalid failure code %A" (errorCode.GetOnionErrorDescription()) - | WeCannotAffordFee (channelReserve, fees, missing) -> + sprintf + "invalid failure code %A" + (errorCode.GetOnionErrorDescription()) + | WeCannotAffordFee(channelReserve, fees, missing) -> sprintf "Cannot afford fees. Missing Satoshis are: %A . Reserve Satoshis are %A . Fees are %A" - channelReserve fees (missing) + channelReserve + fees + (missing) | SignatureCountMismatch(expected, actual) -> - sprintf "Number of signatures went from the remote (%A) does not match the number expected (%A)" actual expected - | TheyCannotAffordFee (toRemote, fee, channelReserve) -> - sprintf "they are funder but cannot afford their fee. to_remote output is: %A; actual fee is %A; channel_reserve_satoshis is: %A" toRemote fee channelReserve + sprintf + "Number of signatures went from the remote (%A) does not match the number expected (%A)" + actual + expected + | TheyCannotAffordFee(toRemote, fee, channelReserve) -> + sprintf + "they are funder but cannot afford their fee. to_remote output is: %A; actual fee is %A; channel_reserve_satoshis is: %A" + toRemote + fee + channelReserve | InvalidFundingLocked invalidFundingLockedError -> - sprintf "Invalid funding_locked from the peer: %s" invalidFundingLockedError.Message + sprintf + "Invalid funding_locked from the peer: %s" + invalidFundingLockedError.Message | InvalidOpenChannel invalidOpenChannelError -> - sprintf "Invalid open_channel from the peer.: %s" invalidOpenChannelError.Message - | OnceConfirmedFundingTxHasBecomeUnconfirmed (height, depth) -> - sprintf "once confirmed funding tx has become less confirmed than threshold %A! This is probably caused by reorg. current depth is: %A " height depth + sprintf + "Invalid open_channel from the peer.: %s" + invalidOpenChannelError.Message + | OnceConfirmedFundingTxHasBecomeUnconfirmed(height, depth) -> + sprintf + "once confirmed funding tx has become less confirmed than threshold %A! This is probably caused by reorg. current depth is: %A " + height + depth | ReceivedShutdownWhenRemoteHasUnsignedOutgoingHTLCs msg -> - sprintf "They sent shutdown msg (%A) while they have pending unsigned HTLCs, this is protocol violation" msg + sprintf + "They sent shutdown msg (%A) while they have pending unsigned HTLCs, this is protocol violation" + msg | RemoteProposedHigherFeeThanBaseFee(baseFee, proposedFee) -> "remote proposed a closing fee higher than commitment fee of the final commitment transaction. " - + sprintf "commitment fee=%A; fee remote proposed=%A;" baseFee proposedFee - | RemoteProposedFeeOutOfNegotiatedRange(ourPreviousFee, theirPreviousFee, theirNextFee) -> + + sprintf + "commitment fee=%A; fee remote proposed=%A;" + baseFee + proposedFee + | RemoteProposedFeeOutOfNegotiatedRange(ourPreviousFee, + theirPreviousFee, + theirNextFee) -> "remote proposed a closing fee which was not strictly between the previous fee that \ we proposed and the previous fee that they proposed. " + sprintf "our previous fee = %A; their previous fee = %A; their next fee = %A" - ourPreviousFee theirPreviousFee theirNextFee + ourPreviousFee + theirPreviousFee + theirNextFee | CryptoError cryptoError -> sprintf "Crypto error: %s" cryptoError.Message | TransactionRelatedErrors transactionErrors -> - let getMessage(transactionError: TransactionError): string = + let getMessage(transactionError: TransactionError) : string = transactionError.Message - sprintf "Transaction errors: %s" (String.concat "; " (Seq.map getMessage transactionErrors)) - | UnknownHTLCId htlcId -> - sprintf "Unknown HTLC id (%i)" htlcId.Value + + sprintf + "Transaction errors: %s" + (String.concat "; " (Seq.map getMessage transactionErrors)) + | UnknownHTLCId htlcId -> sprintf "Unknown HTLC id (%i)" htlcId.Value | HTLCOriginNotKnown htlcId -> sprintf "Origin of HTLC %i not known" htlcId.Value - | APIMisuse error -> - sprintf "Internal error (API misuse): %s" error - | CanNotSignBeforeRevocation -> - "Cannot sign before revocation" + | APIMisuse error -> sprintf "Internal error (API misuse): %s" error + | CanNotSignBeforeRevocation -> "Cannot sign before revocation" | ReceivedCommitmentSignedWhenWeHaveNoPendingChanges -> "Received commitment signed when we have not pending changes" | InvalidAcceptChannel invalidAcceptChannelError -> - sprintf "Invalid accept_channel msg: %s" invalidAcceptChannelError.Message + sprintf + "Invalid accept_channel msg: %s" + invalidAcceptChannelError.Message | InvalidUpdateAddHTLC invalidUpdateAddHTLCError -> - sprintf "Invalid udpate_add_htlc msg: %s" invalidUpdateAddHTLCError.Message + sprintf + "Invalid udpate_add_htlc msg: %s" + invalidUpdateAddHTLCError.Message | InvalidRevokeAndACK invalidRevokeAndACKError -> - sprintf "Invalid revoke_and_ack msg: %s" invalidRevokeAndACKError.Message + sprintf + "Invalid revoke_and_ack msg: %s" + invalidRevokeAndACKError.Message | InvalidUpdateFee invalidUpdateFeeError -> sprintf "Invalid update_fee msg: %s" invalidUpdateFeeError.Message | ReceivedClosingSignedBeforeReceivingShutdown -> @@ -161,22 +212,22 @@ type ChannelError = | ReceivedClosingSignedBeforeSendingShutdown -> sprintf "received closing_signed before sending shutdown" | ReceivedClosingSignedBeforeSendingOrReceivingShutdown -> - sprintf "received closing_signed before sending or receiving shutdown" - | FundingTxNotGiven msg -> - sprintf "Funding tx not given: %s" msg - | CannotCloseChannel msg -> - sprintf "Cannot close channel: %s" msg - | NoUpdatesToSign -> - "No updates to sign" + sprintf + "received closing_signed before sending or receiving shutdown" + | FundingTxNotGiven msg -> sprintf "Funding tx not given: %s" msg + | CannotCloseChannel msg -> sprintf "Cannot close channel: %s" msg + | NoUpdatesToSign -> "No updates to sign" | CannotSignCommitmentBeforeRevocation -> "Cannot sign commitment before previous commitment is revoked" - | InsufficientConfirmations (requiredConfirmations, currentConfirmations) -> + | InsufficientConfirmations(requiredConfirmations, currentConfirmations) -> sprintf "Insufficient confirmations. %i required, only have %i" requiredConfirmations.Value currentConfirmations.Value | InvalidOperationAddHTLC invalidOperationAddHTLCError -> - sprintf "Invalid operation (add htlc): %s" invalidOperationAddHTLCError.Message + sprintf + "Invalid operation (add htlc): %s" + invalidOperationAddHTLCError.Message and ChannelConsumerAction = /// The error which should never happen. @@ -193,91 +244,104 @@ and ChannelConsumerAction = /// The error is not critical to the channel operation. /// But it maybe good to report the log message, or maybe lower the peer score. | Ignore -and InvalidFundingLockedError ={ - NetworkMsg: FundingLockedMsg -} - with + +and InvalidFundingLockedError = + { + NetworkMsg: FundingLockedMsg + } + member this.Message = "remote peer sent a second funding_locked message which does not match their first" -and InvalidOpenChannelError = { - NetworkMsg: OpenChannelMsg - Errors: string list -} - with - static member Create msg e = { - NetworkMsg = msg - Errors = e +and InvalidOpenChannelError = + { + NetworkMsg: OpenChannelMsg + Errors: list } - member this.Message = - String.concat "; " this.Errors - -and InvalidAcceptChannelError = { - NetworkMsg: AcceptChannelMsg - Errors: string list -} - with - static member Create msg e = { - NetworkMsg = msg - Errors = e + + static member Create msg e = + { + NetworkMsg = msg + Errors = e + } + + member this.Message = String.concat "; " this.Errors + +and InvalidAcceptChannelError = + { + NetworkMsg: AcceptChannelMsg + Errors: list } - member this.Message = - String.concat "; " this.Errors - -and InvalidUpdateAddHTLCError = { - NetworkMsg: UpdateAddHTLCMsg - Errors: string list -} - with - static member Create msg e = { - NetworkMsg = msg - Errors = e + + static member Create msg e = + { + NetworkMsg = msg + Errors = e + } + + member this.Message = String.concat "; " this.Errors + +and InvalidUpdateAddHTLCError = + { + NetworkMsg: UpdateAddHTLCMsg + Errors: list } - member this.Message = - String.concat "; " this.Errors - -and InvalidRevokeAndACKError = { - NetworkMsg: RevokeAndACKMsg - Errors: string list -} - with - static member Create msg e = { - NetworkMsg = msg - Errors = e + + static member Create msg e = + { + NetworkMsg = msg + Errors = e + } + + member this.Message = String.concat "; " this.Errors + +and InvalidRevokeAndACKError = + { + NetworkMsg: RevokeAndACKMsg + Errors: list } - member this.Message = - String.concat "; " this.Errors - -and InvalidUpdateFeeError = { - NetworkMsg: UpdateFeeMsg - Errors: string list -} - with - static member Create msg e = { - NetworkMsg = msg - Errors = e + + static member Create msg e = + { + NetworkMsg = msg + Errors = e + } + + member this.Message = String.concat "; " this.Errors + +and InvalidUpdateFeeError = + { + NetworkMsg: UpdateFeeMsg + Errors: list } - member this.Message = - String.concat "; " this.Errors - -and InvalidOperationAddHTLCError = { - Operation: OperationAddHTLC - Errors: string list -} - with - static member Create op e = { - Operation = op - Errors = e + + static member Create msg e = + { + NetworkMsg = msg + Errors = e + } + + member this.Message = String.concat "; " this.Errors + +and InvalidOperationAddHTLCError = + { + Operation: OperationAddHTLC + Errors: list } - member this.Message = - String.concat "; " this.Errors + + static member Create op e = + { + Operation = op + Errors = e + } + + member this.Message = String.concat "; " this.Errors [] module private ValidationHelper = let check left predicate right msg = if predicate left right then - sprintf msg left right - |> Error + sprintf msg left right |> Error else Ok() @@ -287,225 +351,318 @@ module internal ChannelError = let inline feeDeltaTooHigh msg (actualDelta, maxAccepted) = InvalidUpdateFeeError.Create msg - [sprintf "delta is %.2f%% . But it must be lower than %.2f%%" (actualDelta * 100.0) (maxAccepted * 100.0)] - |> InvalidUpdateFee |> Error - + [ + sprintf + "delta is %.2f%% . But it must be lower than %.2f%%" + (actualDelta * 100.0) + (maxAccepted * 100.0) + ] + |> InvalidUpdateFee + |> Error + let inline htlcAlreadySent htlcId = htlcId |> HTLCAlreadySent |> Error - - let inline invalidPaymentPreimage (e, a) = + + let inline invalidPaymentPreimage(e, a) = (e, a) |> InvalidPaymentPreimage |> Error - + let inline unknownHTLCId x = x |> UnknownHTLCId |> Error - + let inline htlcOriginNotKnown x = x |> HTLCOriginNotKnown |> Error + let inline invalidFailureCode x = x |> InvalidFailureCode |> Error - + let inline apiMisuse x = x |> APIMisuse |> Error - + let inline cannotAffordFee x = x |> WeCannotAffordFee |> Error - + let inline signatureCountMismatch x = x |> SignatureCountMismatch |> Error - + let inline theyCannotAffordFee x = x |> TheyCannotAffordFee |> Error - - let onceConfirmedFundingTxHasBecomeUnconfirmed (height, depth) = + + let onceConfirmedFundingTxHasBecomeUnconfirmed(height, depth) = (height, depth) |> OnceConfirmedFundingTxHasBecomeUnconfirmed |> Error - + let expectTransactionError result = Result.mapError (List.singleton >> TransactionRelatedErrors) result - + let expectTransactionErrors result = Result.mapError (TransactionRelatedErrors) result - + let expectFundingTxError msg = - Result.mapError(FundingTxNotGiven) msg - + Result.mapError (FundingTxNotGiven) msg + let invalidRevokeAndACK msg e = - InvalidRevokeAndACKError.Create msg ([e]) |> InvalidRevokeAndACK - + InvalidRevokeAndACKError.Create msg ([ e ]) |> InvalidRevokeAndACK + let cannotCloseChannel msg = msg |> CannotCloseChannel let receivedShutdownWhenRemoteHasUnsignedOutgoingHTLCs msg = msg |> ReceivedShutdownWhenRemoteHasUnsignedOutgoingHTLCs |> Error - + let checkRemoteProposedHigherFeeThanBaseFee baseFee proposedFee = if (baseFee < proposedFee) then RemoteProposedHigherFeeThanBaseFee(baseFee, proposedFee) |> Error else Ok() - let checkRemoteProposedFeeWithinNegotiatedRange (ourPreviousFeeOpt: Option) - (theirPreviousFeeOpt: Option) - (theirNextFee: Money) = + let checkRemoteProposedFeeWithinNegotiatedRange + (ourPreviousFeeOpt: Option) + (theirPreviousFeeOpt: Option) + (theirNextFee: Money) + = match (ourPreviousFeeOpt, theirPreviousFeeOpt) with | (Some ourPreviousFee, Some theirPreviousFee) -> let feeWithinRange = - ((theirNextFee < theirPreviousFee) && (theirNextFee >= ourPreviousFee)) || - ((theirNextFee <= ourPreviousFee) && (theirNextFee > theirPreviousFee)) + ((theirNextFee < theirPreviousFee) + && (theirNextFee >= ourPreviousFee)) + || ((theirNextFee <= ourPreviousFee) + && (theirNextFee > theirPreviousFee)) + if feeWithinRange then - Ok () + Ok() else RemoteProposedFeeOutOfNegotiatedRange( ourPreviousFee, theirPreviousFee, theirNextFee - ) |> Error - | _ -> Ok () + ) + |> Error + | _ -> Ok() module internal OpenChannelMsgValidation = - let checkMaxAcceptedHTLCs (msg: OpenChannelMsg) = + let checkMaxAcceptedHTLCs(msg: OpenChannelMsg) = if (msg.MaxAcceptedHTLCs < 1us) || (msg.MaxAcceptedHTLCs > 483us) then - sprintf "max_accepted_htlcs must be in between %d and %d. But it was %d" 1us 483us msg.MaxAcceptedHTLCs + sprintf + "max_accepted_htlcs must be in between %i and %i. But it was %i" + 1us + 483us + msg.MaxAcceptedHTLCs |> Error else Ok() - let checkFundingSatoshisLessThanMax (localParams: LocalParams) (isOurOpenChannelMsg: bool) (msg: OpenChannelMsg) = + let checkFundingSatoshisLessThanMax + (localParams: LocalParams) + (isOurOpenChannelMsg: bool) + (msg: OpenChannelMsg) + = // If we are validating our own open message we make sure we are forcing support for option_support_large_channel on the other peer let featureType = match isOurOpenChannelMsg with - | true -> - Some FeaturesSupport.Mandatory - | false -> - None - - if msg.FundingSatoshis >= ChannelConstants.MAX_FUNDING_SATOSHIS && - not (localParams.Features.HasFeature (Feature.OptionSupportLargeChannel, ?featureType = featureType)) then - sprintf - "funding_satoshis must be less than %A. It was %A, consider activating option_support_large_channel feature." - ChannelConstants.MAX_FUNDING_SATOSHIS + | true -> Some FeaturesSupport.Mandatory + | false -> None + + if + msg.FundingSatoshis >= ChannelConstants.MAX_FUNDING_SATOSHIS + && not + ( + localParams.Features.HasFeature( + Feature.OptionSupportLargeChannel, + ?featureType = featureType + ) + ) + then + sprintf + "funding_satoshis must be less than %A. It was %A, consider activating option_support_large_channel feature." + ChannelConstants.MAX_FUNDING_SATOSHIS msg.FundingSatoshis |> Error else Ok() - let checkChannelReserveSatohisLessThanFundingSatoshis (msg: OpenChannelMsg) = + + let checkChannelReserveSatohisLessThanFundingSatoshis(msg: OpenChannelMsg) = if (msg.ChannelReserveSatoshis > msg.FundingSatoshis) then sprintf "Bogus channel_reserve_satoshis (%A). Must be bigger than funding_satoshis(%A)" - msg.ChannelReserveSatoshis msg.FundingSatoshis + msg.ChannelReserveSatoshis + msg.FundingSatoshis |> Error else Ok() + let checkPushMSatLesserThanFundingValue msg = - let fundingValue =(msg.FundingSatoshis - msg.ChannelReserveSatoshis) + let fundingValue = (msg.FundingSatoshis - msg.ChannelReserveSatoshis) + if (msg.PushMSat.ToMoney() > fundingValue) then sprintf "push_msat(%A) larger than funding value(%A)" - msg.PushMSat fundingValue + msg.PushMSat + fundingValue |> Error else Ok() - let checkFundingSatoshisLessThanDustLimitSatoshis (msg: OpenChannelMsg) = + let checkFundingSatoshisLessThanDustLimitSatoshis(msg: OpenChannelMsg) = if (msg.DustLimitSatoshis > msg.FundingSatoshis) then sprintf "The dust limit (%A) is larger than the funding amount (%A)" - msg.FundingSatoshis msg.DustLimitSatoshis + msg.FundingSatoshis + msg.DustLimitSatoshis |> Error else Ok() - - let checkRemoteFee (feeEstimator: IFeeEstimator) - (remoteFeeRatePerKw: FeeRatePerKw) - (maxFeeRateMismatchRatio: float) = + + let checkRemoteFee + (feeEstimator: IFeeEstimator) + (remoteFeeRatePerKw: FeeRatePerKw) + (maxFeeRateMismatchRatio: float) + = let localFeeRatePerKw = feeEstimator.GetEstSatPer1000Weight(ConfirmationTarget.Background) + let diff = remoteFeeRatePerKw.MismatchRatio localFeeRatePerKw + if (diff > maxFeeRateMismatchRatio) then sprintf "Peer's feerate (%A) was unacceptably far from the estimated fee rate of %A" - remoteFeeRatePerKw localFeeRatePerKw + remoteFeeRatePerKw + localFeeRatePerKw |> Error else Ok() - let checkConfigPermits (config: ChannelHandshakeLimits) (msg: OpenChannelMsg) = + let checkConfigPermits + (config: ChannelHandshakeLimits) + (msg: OpenChannelMsg) + = let check1 = check - msg.FundingSatoshis (<) config.MinFundingSatoshis + msg.FundingSatoshis + (<) + config.MinFundingSatoshis "funding satoshis is less than the user specified limit. received: %A; limit: %A" + let check2 = check - (msg.HTLCMinimumMsat.ToMoney()) (>) (config.MinFundingSatoshis) + (msg.HTLCMinimumMsat.ToMoney()) + (>) + (config.MinFundingSatoshis) "htlc minimum msat is higher than the users specified limit. received %A; limit: %A" + let check3 = check - msg.MaxHTLCValueInFlightMsat (<) config.MinMaxHTLCValueInFlightMSat + msg.MaxHTLCValueInFlightMsat + (<) + config.MinMaxHTLCValueInFlightMSat "max htlc value in light msat is less than the user specified limit. received: %A; limit %A" + let check4 = check - msg.ChannelReserveSatoshis (>) config.MaxChannelReserveSatoshis + msg.ChannelReserveSatoshis + (>) + config.MaxChannelReserveSatoshis "channel reserve satoshis is higher than the user specified limit. received %A; limit: %A" + let check5 = check - msg.MaxAcceptedHTLCs (<) config.MinMaxAcceptedHTLCs - "max accepted htlcs is less than the user specified limit. received: %A; limit: %A" + msg.MaxAcceptedHTLCs + (<) + config.MinMaxAcceptedHTLCs + "max accepted htlcs is less than the user specified limit. received: %A; limit: %A" + let check6 = check - msg.DustLimitSatoshis (<) config.MinDustLimitSatoshis + msg.DustLimitSatoshis + (<) + config.MinDustLimitSatoshis "dust_limit_satoshis is less than the user specified limit. received: %A; limit: %A" + let check7 = check - msg.DustLimitSatoshis (>) config.MaxDustLimitSatoshis + msg.DustLimitSatoshis + (>) + config.MaxDustLimitSatoshis "dust_limit_satoshis is greater than the user specified limit. received: %A; limit: %A" + let check8 = check - msg.ToSelfDelay (>) (config.MaxToSelfDelay) + msg.ToSelfDelay + (>) + (config.MaxToSelfDelay) "They wanted our payments to be delayed by a needlessly long period (%A), configured maximum was (%A)" - Validation.ofResult(check1) *^> check2 *^> check3 *^> check4 *^> check5 *^> check6 *^> check7 *^> check8 - let checkChannelAnnouncementPreferenceAcceptable (channelHandshakeLimits: ChannelHandshakeLimits) - (announceChannel: bool) - (msg: OpenChannelMsg) = + Validation.ofResult(check1) + *^> check2 + *^> check3 + *^> check4 + *^> check5 + *^> check6 + *^> check7 + *^> check8 + + let checkChannelAnnouncementPreferenceAcceptable + (channelHandshakeLimits: ChannelHandshakeLimits) + (announceChannel: bool) + (msg: OpenChannelMsg) + = let theirAnnounce = msg.ChannelFlags.AnnounceChannel - if (channelHandshakeLimits.ForceChannelAnnouncementPreference) && announceChannel <> theirAnnounce then + + if (channelHandshakeLimits.ForceChannelAnnouncementPreference) + && announceChannel <> theirAnnounce then "Peer tried to open channel but their announcement preference is different from ours" |> Error else Ok() let checkIsAcceptableByCurrentFeeRate (feeEstimator: IFeeEstimator) msg = - let ourDustLimit = ChannelConstantHelpers.deriveOurDustLimitSatoshis feeEstimator - let ourChannelReserve = ChannelConstantHelpers.getOurChannelReserve (msg.FundingSatoshis) + let ourDustLimit = + ChannelConstantHelpers.deriveOurDustLimitSatoshis feeEstimator + + let ourChannelReserve = + ChannelConstantHelpers.getOurChannelReserve(msg.FundingSatoshis) + let check left predicate right msg = if predicate left right then - sprintf msg left right - |> Error + sprintf msg left right |> Error else Ok() let check1 = check - ourChannelReserve (<) ourDustLimit + ourChannelReserve + (<) + ourDustLimit "Funder's channel reserve (%A, dictated by the fundee) is less than the fundee's dust limit (%A). \ The funder must use a larger amount to open a channel." + let check2 = check - msg.ChannelReserveSatoshis (<) ourDustLimit + msg.ChannelReserveSatoshis + (<) + ourDustLimit "Fundee's channel reserve (%A, dictated by the funder) is less than the fundee's dust limit (%A). \ The funder must use a larger amount to open the channel, or require a smaller channel reserve." + let check3 = check - ourChannelReserve (<) msg.DustLimitSatoshis + ourChannelReserve + (<) + msg.DustLimitSatoshis "Funder's channel reserve (%A, dictated by the fundee) is less than the funder's dust limit (%A)." + Validation.ofResult(check1) *^> check2 *^> check3 let checkFunderCanAffordFee (feeRate: FeeRatePerKw) (msg: OpenChannelMsg) = - let fundersAmount = LNMoney.Satoshis(msg.FundingSatoshis.Satoshi) - msg.PushMSat + let fundersAmount = + LNMoney.Satoshis(msg.FundingSatoshis.Satoshi) - msg.PushMSat + let fee = feeRate.CalculateFeeFromWeight COMMITMENT_TX_BASE_WEIGHT + if fundersAmount.ToMoney() < fee then sprintf "funding amount (%A) minus push amount (%A) does not cover commitment tx fee (%A)" - msg.FundingSatoshis msg.PushMSat fee + msg.FundingSatoshis + msg.PushMSat + fee |> Error else Ok() @@ -513,108 +670,231 @@ module internal OpenChannelMsgValidation = module internal AcceptChannelMsgValidation = let private check left predicate right msg = if predicate left right then - sprintf msg left right - |> Error + sprintf msg left right |> Error else Ok() - let checkMaxAcceptedHTLCs (msg: AcceptChannelMsg) = + let checkMaxAcceptedHTLCs(msg: AcceptChannelMsg) = if (msg.MaxAcceptedHTLCs < 1us) || (msg.MaxAcceptedHTLCs > 483us) then - sprintf "max_accepted_htlcs must be in between %d and %d. But it was %d" 1us 483us msg.MaxAcceptedHTLCs + sprintf + "max_accepted_htlcs must be in between %i and %i. But it was %i" + 1us + 483us + msg.MaxAcceptedHTLCs |> Error else Ok() let checkDustLimit msg = if msg.DustLimitSatoshis > Money.Satoshis(21000000L * 100000L) then - sprintf "Peer never wants payout outputs? dust_limit_satoshis was: %A" msg.DustLimitSatoshis + sprintf + "Peer never wants payout outputs? dust_limit_satoshis was: %A" + msg.DustLimitSatoshis |> Error else Ok() - let checkChannelReserveSatoshis (fundingAmount: Money) - (channelReserveAmount: Money) - (dustLimit: Money) - (acceptChannelMsg: AcceptChannelMsg) = + let checkChannelReserveSatoshis + (fundingAmount: Money) + (channelReserveAmount: Money) + (dustLimit: Money) + (acceptChannelMsg: AcceptChannelMsg) + = if acceptChannelMsg.ChannelReserveSatoshis > fundingAmount then - sprintf "bogus channel_reserve_satoshis %A . Must be larger than funding_satoshis %A" (acceptChannelMsg.ChannelReserveSatoshis) fundingAmount + sprintf + "bogus channel_reserve_satoshis %A . Must be larger than funding_satoshis %A" + (acceptChannelMsg.ChannelReserveSatoshis) + fundingAmount |> Error else if acceptChannelMsg.DustLimitSatoshis > channelReserveAmount then - sprintf "Bogus channel_reserve and dust_limit. dust_limit: %A; channel_reserve %A" acceptChannelMsg.DustLimitSatoshis channelReserveAmount + sprintf + "Bogus channel_reserve and dust_limit. dust_limit: %A; channel_reserve %A" + acceptChannelMsg.DustLimitSatoshis + channelReserveAmount |> Error else if acceptChannelMsg.ChannelReserveSatoshis < dustLimit then - sprintf "Peer never wants payout outputs? channel_reserve_satoshis are %A; dust_limit_satoshis in our last sent msg is %A" acceptChannelMsg.ChannelReserveSatoshis dustLimit + sprintf + "Peer never wants payout outputs? channel_reserve_satoshis are %A; dust_limit_satoshis in our last sent msg is %A" + acceptChannelMsg.ChannelReserveSatoshis + dustLimit |> Error else Ok() - let checkDustLimitIsLargerThanOurChannelReserve (channelReserveAmount: Money) - (acceptChannelMsg: AcceptChannelMsg) = + let checkDustLimitIsLargerThanOurChannelReserve + (channelReserveAmount: Money) + (acceptChannelMsg: AcceptChannelMsg) + = check - acceptChannelMsg.DustLimitSatoshis (>) channelReserveAmount - "dust limit (%A) is bigger than our channel reserve (%A)" - - let checkMinimumHTLCValueIsAcceptable (fundingAmount: Money) - (acceptChannelMsg: AcceptChannelMsg) = - if acceptChannelMsg.HTLCMinimumMSat.ToMoney() >= (fundingAmount - acceptChannelMsg.ChannelReserveSatoshis) then - sprintf "Minimum HTLC value is greater than full channel value HTLCMinimum %A satoshi; funding_satoshis %A; channel_reserve: %A" (acceptChannelMsg.HTLCMinimumMSat.ToMoney()) (fundingAmount) (acceptChannelMsg.ChannelReserveSatoshis) + acceptChannelMsg.DustLimitSatoshis + (>) + channelReserveAmount + "dust limit (%A) is bigger than our channel reserve (%A)" + + let checkMinimumHTLCValueIsAcceptable + (fundingAmount: Money) + (acceptChannelMsg: AcceptChannelMsg) + = + if acceptChannelMsg.HTLCMinimumMSat.ToMoney() + >= (fundingAmount - acceptChannelMsg.ChannelReserveSatoshis) then + sprintf + "Minimum HTLC value is greater than full channel value HTLCMinimum %A satoshi; funding_satoshis %A; channel_reserve: %A" + (acceptChannelMsg.HTLCMinimumMSat.ToMoney()) + (fundingAmount) + (acceptChannelMsg.ChannelReserveSatoshis) |> Error else Ok() - let checkConfigPermits (config: ChannelHandshakeLimits) (msg: AcceptChannelMsg) = - let check1 = check msg.HTLCMinimumMSat (>) config.MaxHTLCMinimumMSat "HTLC Minimum msat in accept_channel (%A) is higher than the user specified limit (%A)" - let check2 = check msg.MaxHTLCValueInFlightMsat (<) config.MinMaxHTLCValueInFlightMSat "max htlc value in flight msat (%A) is less than the user specified limit (%A)" - let check3 = check msg.ChannelReserveSatoshis (>) config.MaxChannelReserveSatoshis "max reserve_satoshis (%A) is higher than the user specified limit (%A)" - let check4 = check msg.MaxAcceptedHTLCs (<) config.MinMaxAcceptedHTLCs "max accepted htlcs (%A) is less than the user specified limit (%A)" - let check5 = check msg.DustLimitSatoshis (<) config.MinDustLimitSatoshis "dust limit satoshis (%A) is less then the user specified limit (%A)" - let check6 = check msg.DustLimitSatoshis (>) config.MaxDustLimitSatoshis "dust limit satoshis (%A) is greater then the user specified limit (%A)" - let check7 = check (msg.MinimumDepth.Value) (>) (config.MaxMinimumDepth.Value |> uint32) "We consider the minimum depth (%A) to be unreasonably large. Our max minimum depth is (%A)" - let check8 = check msg.ToSelfDelay (>) (config.MaxToSelfDelay) "They wanted our payments to be delayed by a needlessly long period (%A), configured maximum was (%A)" + let checkConfigPermits + (config: ChannelHandshakeLimits) + (msg: AcceptChannelMsg) + = + let check1 = + check + msg.HTLCMinimumMSat + (>) + config.MaxHTLCMinimumMSat + "HTLC Minimum msat in accept_channel (%A) is higher than the user specified limit (%A)" + + let check2 = + check + msg.MaxHTLCValueInFlightMsat + (<) + config.MinMaxHTLCValueInFlightMSat + "max htlc value in flight msat (%A) is less than the user specified limit (%A)" + + let check3 = + check + msg.ChannelReserveSatoshis + (>) + config.MaxChannelReserveSatoshis + "max reserve_satoshis (%A) is higher than the user specified limit (%A)" + + let check4 = + check + msg.MaxAcceptedHTLCs + (<) + config.MinMaxAcceptedHTLCs + "max accepted htlcs (%A) is less than the user specified limit (%A)" + + let check5 = + check + msg.DustLimitSatoshis + (<) + config.MinDustLimitSatoshis + "dust limit satoshis (%A) is less then the user specified limit (%A)" + + let check6 = + check + msg.DustLimitSatoshis + (>) + config.MaxDustLimitSatoshis + "dust limit satoshis (%A) is greater then the user specified limit (%A)" + + let check7 = + check + (msg.MinimumDepth.Value) + (>) + (config.MaxMinimumDepth.Value |> uint32) + "We consider the minimum depth (%A) to be unreasonably large. Our max minimum depth is (%A)" + + let check8 = + check + msg.ToSelfDelay + (>) + (config.MaxToSelfDelay) + "They wanted our payments to be delayed by a needlessly long period (%A), configured maximum was (%A)" + + (check1 |> Validation.ofResult) + *^> check2 + *^> check3 + *^> check4 + *^> check5 + *^> check6 + *^> check7 + *^> check8 - (check1 |> Validation.ofResult) *^> check2 *^> check3 *^> check4 *^> check5 *^> check6 *^> check7 *^> check8 - module UpdateAddHTLCValidation = - let internal checkExpiryIsNotPast (current: BlockHeight) (expiry) = - check (expiry) (<=) (current) "AddHTLC's Expiry was %A but it must be larger than current height %A" + let internal checkExpiryIsNotPast (current: BlockHeight) expiry = + check + (expiry) + (<=) + (current) + "AddHTLC's Expiry was %A but it must be larger than current height %A" - let internal checkExpiryIsInAcceptableRange (current: BlockHeight) (expiry) = - let checkIsToSoon = check (expiry) (<=) (current + MIN_CLTV_EXPIRY) "Operation_ADD_HTLC.Expiry was %A but it was too close to current height. Minimum is: %A" - let checkIsToFar = check (expiry) (>=) (current + MAX_CLTV_EXPIRY) "Operation_ADD_HTLC.Expiry was %A but it was too far from current height. Maximum is: %A" + let internal checkExpiryIsInAcceptableRange (current: BlockHeight) expiry = + let checkIsToSoon = + check + (expiry) + (<=) + (current + MIN_CLTV_EXPIRY) + "Operation_ADD_HTLC.Expiry was %A but it was too close to current height. Minimum is: %A" + + let checkIsToFar = + check + (expiry) + (>=) + (current + MAX_CLTV_EXPIRY) + "Operation_ADD_HTLC.Expiry was %A but it was too far from current height. Maximum is: %A" + Validation.ofResult(checkIsToSoon) *^> checkIsToFar - let internal checkAmountIsLargerThanMinimum (htlcMinimum: LNMoney) (amount) = - check (amount) (<) (htlcMinimum) "htlc value (%A) is too small. must be greater or equal to %A" + let internal checkAmountIsLargerThanMinimum (htlcMinimum: LNMoney) amount = + check + (amount) + (<) + (htlcMinimum) + "htlc value (%A) is too small. must be greater or equal to %A" + - module internal UpdateAddHTLCValidationWithContext = - let checkLessThanHTLCValueInFlightLimit (currentSpec: CommitmentSpec) (limit) (add: UpdateAddHTLCMsg) = + let checkLessThanHTLCValueInFlightLimit + (currentSpec: CommitmentSpec) + limit + (add: UpdateAddHTLCMsg) + = let outgoingValue = currentSpec.OutgoingHTLCs |> Map.toSeq - |> Seq.sumBy (fun (_, v) -> v.Amount) + |> Seq.sumBy(fun (_, v) -> v.Amount) + let incomingValue = currentSpec.IncomingHTLCs |> Map.toSeq - |> Seq.sumBy (fun (_, v) -> v.Amount) + |> Seq.sumBy(fun (_, v) -> v.Amount) + let htlcValueInFlight = outgoingValue + incomingValue + if (htlcValueInFlight > limit) then - sprintf "Too much HTLC value is in flight. Current: %A. Limit: %A \n Could not add new one with value: %A" - htlcValueInFlight - limit - add.Amount + sprintf + "Too much HTLC value is in flight. Current: %A. Limit: %A \n Could not add new one with value: %A" + htlcValueInFlight + limit + add.Amount |> Error else Ok() - let checkLessThanMaxAcceptedHTLC (currentSpec: CommitmentSpec) (limit: uint16) = + let checkLessThanMaxAcceptedHTLC + (currentSpec: CommitmentSpec) + (limit: uint16) + = let acceptedHTLCs = currentSpec.IncomingHTLCs |> Map.count - check acceptedHTLCs (>) (int limit) "We have much number of HTLCs (%A). Limit specified by remote is (%A). So not going to relay" - let checkWeHaveSufficientFunds (staticChannelConfig: StaticChannelConfig) (currentSpec) = + check + acceptedHTLCs + (>) + (int limit) + "We have much number of HTLCs (%A). Limit specified by remote is (%A). So not going to relay" + + let checkWeHaveSufficientFunds + (staticChannelConfig: StaticChannelConfig) + currentSpec + = let fees = if staticChannelConfig.IsFunder then Transactions.commitTxFee @@ -622,21 +902,32 @@ module internal UpdateAddHTLCValidationWithContext = currentSpec else Money.Zero - let missing = currentSpec.ToRemote.ToMoney() - staticChannelConfig.RemoteParams.ChannelReserveSatoshis - fees + + let missing = + currentSpec.ToRemote.ToMoney() + - staticChannelConfig.RemoteParams.ChannelReserveSatoshis + - fees + if (missing < Money.Zero) then - sprintf "We don't have sufficient funds to send HTLC. current to_remote amount is: %A. Remote Channel Reserve is: %A. and fee is %A" - (currentSpec.ToRemote.ToMoney()) - (staticChannelConfig.RemoteParams.ChannelReserveSatoshis) - (fees) + sprintf + "We don't have sufficient funds to send HTLC. current to_remote amount is: %A. Remote Channel Reserve is: %A. and fee is %A" + (currentSpec.ToRemote.ToMoney()) + (staticChannelConfig.RemoteParams.ChannelReserveSatoshis) + (fees) |> Error else Ok() + module internal UpdateFeeValidation = - let checkFeeDiffTooHigh (msg: UpdateFeeMsg) (localFeeRatePerKw: FeeRatePerKw) (maxFeeRateMismatchRatio) = + let checkFeeDiffTooHigh + (msg: UpdateFeeMsg) + (localFeeRatePerKw: FeeRatePerKw) + maxFeeRateMismatchRatio + = let remoteFeeRatePerKw = msg.FeeRatePerKw let diff = remoteFeeRatePerKw.MismatchRatio localFeeRatePerKw + if (diff > maxFeeRateMismatchRatio) then - (diff, maxFeeRateMismatchRatio) - |> feeDeltaTooHigh msg - else - Ok () + (diff, maxFeeRateMismatchRatio) |> feeDeltaTooHigh msg + else + Ok() diff --git a/src/DotNetLightning.Core/Channel/ChannelHelpers.fs b/src/DotNetLightning.Core/Channel/ChannelHelpers.fs index 0bbf5d4da..0ee836872 100644 --- a/src/DotNetLightning.Core/Channel/ChannelHelpers.fs +++ b/src/DotNetLightning.Core/Channel/ChannelHelpers.fs @@ -15,15 +15,21 @@ open NBitcoin /// cousin of `ChannelHelpers` module which only includes very primitive function. module internal ChannelConstantHelpers = - let deriveOurDustLimitSatoshis (feeEstimator: IFeeEstimator): Money = - let (FeeRatePerKw atOpenBackGroundFee) = feeEstimator.GetEstSatPer1000Weight(ConfirmationTarget.Background) - (Money.Satoshis((uint64 atOpenBackGroundFee) * B_OUTPUT_PLUS_SPENDING_INPUT_WEIGHT / 1000UL), Money.Satoshis(546UL)) + let deriveOurDustLimitSatoshis(feeEstimator: IFeeEstimator) : Money = + let (FeeRatePerKw atOpenBackGroundFee) = + feeEstimator.GetEstSatPer1000Weight(ConfirmationTarget.Background) + + (Money.Satoshis( + (uint64 atOpenBackGroundFee) * B_OUTPUT_PLUS_SPENDING_INPUT_WEIGHT + / 1000UL + ), + Money.Satoshis(546UL)) |> Money.Max - - let getOurChannelReserve (channelValue: Money) = + + let getOurChannelReserve(channelValue: Money) = let q = channelValue / 100L Money.Min(channelValue, Money.Max(q, Money.Satoshis(1L))) - + module ClosingHelpers = let TxVersionNumberOfCommitmentTxs = 2u @@ -33,9 +39,11 @@ module ClosingHelpers = | TxHasMultipleInputs of int | DoesNotSpendChannelFunds of OutPoint | InvalidLockTimeAndSequenceForCommitmentTx of LockTime * Sequence + member this.Message: string = match this with - | InvalidTxVersionForCommitmentTx version -> sprintf "invalid tx version for commitment tx (%i)" version + | InvalidTxVersionForCommitmentTx version -> + sprintf "invalid tx version for commitment tx (%i)" version | TxHasNoInputs -> "tx has no inputs" | TxHasMultipleInputs n -> sprintf "tx has multiple inputs (%i)" n | DoesNotSpendChannelFunds outPoint -> @@ -43,7 +51,7 @@ module ClosingHelpers = "tx does not spend from the channel funds but spends from a different \ outpoint (%s)" (outPoint.ToString()) - | InvalidLockTimeAndSequenceForCommitmentTx (lockTime, sequence) -> + | InvalidLockTimeAndSequenceForCommitmentTx(lockTime, sequence) -> sprintf "invalid lock time and sequence for commitment tx \ (locktime = %s, sequence = %s)" @@ -64,30 +72,33 @@ module ClosingHelpers = (transaction: Transaction) : Result = result { - if transaction.Version - <> TxVersionNumberOfCommitmentTxs then + if transaction.Version <> TxVersionNumberOfCommitmentTxs then return! - Error - <| InvalidTxVersionForCommitmentTx transaction.Version + Error <| InvalidTxVersionForCommitmentTx transaction.Version if transaction.Inputs.Count = 0 then return! Error <| TxHasNoInputs if transaction.Inputs.Count > 1 then - return! - Error - <| TxHasMultipleInputs transaction.Inputs.Count + return! Error <| TxHasMultipleInputs transaction.Inputs.Count let txIn = Seq.exactlyOne transaction.Inputs if fundingOutPoint <> txIn.PrevOut then return! Error <| DoesNotSpendChannelFunds txIn.PrevOut - match ObscuredCommitmentNumber.TryFromLockTimeAndSequence transaction.LockTime txIn.Sequence with + match + ObscuredCommitmentNumber.TryFromLockTimeAndSequence + transaction.LockTime + txIn.Sequence + with | None -> return! Error - <| InvalidLockTimeAndSequenceForCommitmentTx(transaction.LockTime, txIn.Sequence) + <| InvalidLockTimeAndSequenceForCommitmentTx( + transaction.LockTime, + txIn.Sequence + ) | Some obscuredCommitmentNumber -> return obscuredCommitmentNumber } @@ -99,14 +110,16 @@ module ClosingHelpers = (remotePerCommitmentPoint: PerCommitmentPoint) = result { - let localChannelPubKeys = localChannelPrivKeys.ToChannelPubKeys() + let localChannelPubKeys = + localChannelPrivKeys.ToChannelPubKeys() let localPaymentPrivKey = remotePerCommitmentPoint.DerivePaymentPrivKey localChannelPrivKeys.PaymentBasepointSecret let localCommitmentPubKeys = - remotePerCommitmentPoint.DeriveCommitmentPubKeys localChannelPubKeys + remotePerCommitmentPoint.DeriveCommitmentPubKeys + localChannelPubKeys let toRemoteScriptPubKey = localCommitmentPubKeys @@ -116,7 +129,11 @@ module ClosingHelpers = .ScriptPubKey let toRemoteIndexOpt = - Seq.tryFindIndex (fun (txOut: TxOut) -> txOut.ScriptPubKey = toRemoteScriptPubKey) commitTx.Outputs + Seq.tryFindIndex + (fun (txOut: TxOut) -> + txOut.ScriptPubKey = toRemoteScriptPubKey + ) + commitTx.Outputs let! toRemoteIndex = match toRemoteIndexOpt with @@ -131,7 +148,9 @@ module ClosingHelpers = .AddKeys(localPaymentPrivKey.RawKey()) return - transactionBuilder.AddCoin(Coin(commitTx, uint32 toRemoteIndex)) + transactionBuilder.AddCoin( + Coin(commitTx, uint32 toRemoteIndex) + ) } let ClaimCommitTxOutputs @@ -143,7 +162,12 @@ module ClosingHelpers = assert (remoteCommit.TxId = closingTx.GetTxId()) { - MainOutput = ClaimMainOutput closingTx staticChannelConfig channelPrivKeys remoteCommit.RemotePerCommitmentPoint + MainOutput = + ClaimMainOutput + closingTx + staticChannelConfig + channelPrivKeys + remoteCommit.RemotePerCommitmentPoint } module LocalClose = @@ -154,17 +178,23 @@ module ClosingHelpers = (localChannelPrivKeys: ChannelPrivKeys) = result { - let localChannelPubKeys = localChannelPrivKeys.ToChannelPubKeys() - let remoteChannelPubKeys = staticChannelConfig.RemoteChannelPubKeys + let localChannelPubKeys = + localChannelPrivKeys.ToChannelPubKeys() + + let remoteChannelPubKeys = + staticChannelConfig.RemoteChannelPubKeys let perCommitmentPoint = - localChannelPrivKeys.CommitmentSeed.DerivePerCommitmentPoint commitmentNumber + localChannelPrivKeys.CommitmentSeed.DerivePerCommitmentPoint + commitmentNumber let localCommitmentPubKeys = - perCommitmentPoint.DeriveCommitmentPubKeys localChannelPubKeys + perCommitmentPoint.DeriveCommitmentPubKeys + localChannelPubKeys let remoteCommitmentPubKeys = - perCommitmentPoint.DeriveCommitmentPubKeys remoteChannelPubKeys + perCommitmentPoint.DeriveCommitmentPubKeys + remoteChannelPubKeys let transactionBuilder = staticChannelConfig.Network.CreateTransactionBuilder() @@ -176,10 +206,13 @@ module ClosingHelpers = localCommitmentPubKeys.DelayedPaymentPubKey let toLocalIndexOpt = - let toLocalWitScriptPubKey = toLocalScriptPubKey.WitHash.ScriptPubKey + let toLocalWitScriptPubKey = + toLocalScriptPubKey.WitHash.ScriptPubKey Seq.tryFindIndex - (fun (txOut: TxOut) -> txOut.ScriptPubKey = toLocalWitScriptPubKey) + (fun (txOut: TxOut) -> + txOut.ScriptPubKey = toLocalWitScriptPubKey + ) commitTx.Outputs let! toLocalIndex = @@ -188,7 +221,8 @@ module ClosingHelpers = | None -> Error BalanceBelowDustLimit let delayedPaymentPrivKey = - perCommitmentPoint.DeriveDelayedPaymentPrivKey localChannelPrivKeys.DelayedPaymentBasepointSecret + perCommitmentPoint.DeriveDelayedPaymentPrivKey + localChannelPrivKeys.DelayedPaymentBasepointSecret transactionBuilder .SetVersion(TxVersionNumberOfCommitmentTxs) @@ -198,11 +232,18 @@ module ClosingHelpers = transactionBuilder .AddKeys(delayedPaymentPrivKey.RawKey()) .AddCoin( - ScriptCoin(commitTx, uint32 toLocalIndex, toLocalScriptPubKey), + ScriptCoin( + commitTx, + uint32 toLocalIndex, + toLocalScriptPubKey + ), CoinOptions( Sequence = (Nullable - <| Sequence(uint32 staticChannelConfig.LocalParams.ToSelfDelay.Value)) + <| Sequence( + uint32 + staticChannelConfig.LocalParams.ToSelfDelay.Value + )) ) ) } @@ -231,7 +272,12 @@ module ClosingHelpers = remoteChannelPubKeys.PaymentBasepoint { - MainOutput = ClaimMainOutput closingTx commitmentNumber staticChannelConfig channelPrivKeys + MainOutput = + ClaimMainOutput + closingTx + commitmentNumber + staticChannelConfig + channelPrivKeys } | _ -> { @@ -244,30 +290,38 @@ module ClosingHelpers = (commitmentNumber: CommitmentNumber) (staticChannelConfig: StaticChannelConfig) (remotePerCommitmentSecret: Choice) - (localChannelPrivKeys: ChannelPrivKeys): Result - = + (localChannelPrivKeys: ChannelPrivKeys) + : Result = result { - let localChannelPubKeys = localChannelPrivKeys.ToChannelPubKeys() - let remoteChannelPubKeys = staticChannelConfig.RemoteChannelPubKeys + let localChannelPubKeys = + localChannelPrivKeys.ToChannelPubKeys() + + let remoteChannelPubKeys = + staticChannelConfig.RemoteChannelPubKeys let! perCommitmentSecret = match remotePerCommitmentSecret with - | Choice1Of2 remotePerCommitmentSecret -> Ok remotePerCommitmentSecret + | Choice1Of2 remotePerCommitmentSecret -> + Ok remotePerCommitmentSecret | Choice2Of2 remotePerCommitmentSecretStore -> let commitmentSecretOpt = - remotePerCommitmentSecretStore.GetPerCommitmentSecret commitmentNumber + remotePerCommitmentSecretStore.GetPerCommitmentSecret + commitmentNumber match commitmentSecretOpt with | Some commitmentSecret -> Ok commitmentSecret | None -> Error OutputClaimError.UnknownClosingTx - let perCommitmentPoint = perCommitmentSecret.PerCommitmentPoint() + let perCommitmentPoint = + perCommitmentSecret.PerCommitmentPoint() let localCommitmentPubKeys = - perCommitmentPoint.DeriveCommitmentPubKeys localChannelPubKeys + perCommitmentPoint.DeriveCommitmentPubKeys + localChannelPubKeys let remoteCommitmentPubKeys = - perCommitmentPoint.DeriveCommitmentPubKeys remoteChannelPubKeys + perCommitmentPoint.DeriveCommitmentPubKeys + remoteChannelPubKeys let transactionBuilder = staticChannelConfig.Network.CreateTransactionBuilder() @@ -285,45 +339,60 @@ module ClosingHelpers = staticChannelConfig.LocalParams.ToSelfDelay remoteCommitmentPubKeys.DelayedPaymentPubKey - let toLocalWitScriptPubKey = toLocalScriptPubKey.WitHash.ScriptPubKey + let toLocalWitScriptPubKey = + toLocalScriptPubKey.WitHash.ScriptPubKey let toRemoteIndexOpt = closingTx.Outputs - |> Seq.tryFindIndex (fun out -> out.ScriptPubKey = toRemoteScriptPubKey) + |> Seq.tryFindIndex(fun out -> + out.ScriptPubKey = toRemoteScriptPubKey + ) toRemoteIndexOpt - |> Option.iter - ( - fun toRemoteIndex -> - let localPaymentPrivKey = - perCommitmentPoint.DerivePaymentPrivKey - localChannelPrivKeys.PaymentBasepointSecret + |> Option.iter(fun toRemoteIndex -> + let localPaymentPrivKey = + perCommitmentPoint.DerivePaymentPrivKey + localChannelPrivKeys.PaymentBasepointSecret - transactionBuilder.SetVersion TxVersionNumberOfCommitmentTxs - |> ignore + transactionBuilder.SetVersion TxVersionNumberOfCommitmentTxs + |> ignore - transactionBuilder.AddKeys(localPaymentPrivKey.RawKey()) - |> ignore + transactionBuilder.AddKeys(localPaymentPrivKey.RawKey()) + |> ignore - transactionBuilder.AddCoin(Coin(closingTx, toRemoteIndex |> uint32)) |> ignore + transactionBuilder.AddCoin( + Coin(closingTx, toRemoteIndex |> uint32) ) + |> ignore + ) let toLocalIndexOpt = closingTx.Outputs - |> Seq.tryFindIndex (fun out -> out.ScriptPubKey = toLocalWitScriptPubKey) + |> Seq.tryFindIndex(fun out -> + out.ScriptPubKey = toLocalWitScriptPubKey + ) toLocalIndexOpt - |> Option.iter - (fun toLocalIndex -> - let revocationPrivKey = - perCommitmentSecret.DeriveRevocationPrivKey localChannelPrivKeys.RevocationBasepointSecret + |> Option.iter(fun toLocalIndex -> + let revocationPrivKey = + perCommitmentSecret.DeriveRevocationPrivKey + localChannelPrivKeys.RevocationBasepointSecret - transactionBuilder.Extensions.Add(CommitmentToLocalExtension()) + transactionBuilder.Extensions.Add( + CommitmentToLocalExtension() + ) - transactionBuilder - .AddKeys(revocationPrivKey.RawKey()) - .AddCoin(ScriptCoin(closingTx, toLocalIndex |> uint32, toLocalScriptPubKey)) - |> ignore) + transactionBuilder + .AddKeys(revocationPrivKey.RawKey()) + .AddCoin( + ScriptCoin( + closingTx, + toLocalIndex |> uint32, + toLocalScriptPubKey + ) + ) + |> ignore + ) // We should've retuned BalanceBelowDustLimit here // but because it's possible for old local commitment TXs to @@ -352,7 +421,8 @@ module ClosingHelpers = remotePerCommitmentSecret.PerCommitmentPoint() let localCommitmentPubKeys = - remotePerCommitmentPoint.DeriveCommitmentPubKeys localChannelPubKeys + remotePerCommitmentPoint.DeriveCommitmentPubKeys + localChannelPubKeys let remoteCommitmentPubKeys = remotePerCommitmentPoint.DeriveCommitmentPubKeys @@ -433,9 +503,12 @@ module ClosingHelpers = (savedChannelState: SavedChannelState) (remoteNextCommitInfoOpt: Option) (channelPrivKeys: ChannelPrivKeys) - (closingTx: Transaction) : ClosingResult = + (closingTx: Transaction) + : ClosingResult = let closingTxId = closingTx.GetTxId() - if closingTxId = savedChannelState.LocalCommit.PublishableTxs.CommitTx.Value.GetTxId() then + + if closingTxId = savedChannelState.LocalCommit.PublishableTxs.CommitTx.Value.GetTxId + () then LocalClose.ClaimCommitTxOutputs closingTx savedChannelState.StaticChannelConfig @@ -448,7 +521,9 @@ module ClosingHelpers = savedChannelState.RemoteCommit else match remoteNextCommitInfoOpt with - | Some (Waiting remoteNextCommit) when closingTxId = remoteNextCommit.TxId -> + | Some(Waiting remoteNextCommit) when + closingTxId = remoteNextCommit.TxId + -> RemoteClose.ClaimCommitTxOutputs closingTx savedChannelState.StaticChannelConfig @@ -459,4 +534,4 @@ module ClosingHelpers = closingTx savedChannelState.StaticChannelConfig savedChannelState.RemotePerCommitmentSecrets - channelPrivKeys \ No newline at end of file + channelPrivKeys diff --git a/src/DotNetLightning.Core/Channel/ChannelOperations.fs b/src/DotNetLightning.Core/Channel/ChannelOperations.fs index 2b2e2388b..4c0b0660d 100644 --- a/src/DotNetLightning.Core/Channel/ChannelOperations.fs +++ b/src/DotNetLightning.Core/Channel/ChannelOperations.fs @@ -15,120 +15,141 @@ open NBitcoin open ResultUtils open ResultUtils.Portability -type OperationAddHTLC = { - Amount: LNMoney - PaymentHash: PaymentHash - Expiry: BlockHeight - Onion: OnionPacket - Upstream: UpdateAddHTLCMsg option - Origin: HTLCSource option - CurrentHeight: BlockHeight -} - with - static member Create amountMSat paymentHash expiry onion upstream origin currentHeight = - { - Amount = amountMSat - PaymentHash = paymentHash - Expiry = expiry - Onion = onion - Upstream = upstream - Origin = origin - CurrentHeight = currentHeight - } - - -type OperationFulfillHTLC = { - Id: HTLCId - PaymentPreimage: PaymentPreimage - Commit: bool -} - -type OperationFailHTLC = { - Id: HTLCId - Reason: Choice -} - -type OperationFailMalformedHTLC = { - Id: HTLCId - Sha256OfOnion: uint256 - FailureCode: FailureCode -} - -type OperationUpdateFee = { - FeeRatePerKw: FeeRatePerKw -} - -type LocalParams = { - DustLimitSatoshis: Money - MaxHTLCValueInFlightMSat: LNMoney - ChannelReserveSatoshis: Money - HTLCMinimumMSat: LNMoney - ToSelfDelay: BlockHeightOffset16 - MaxAcceptedHTLCs: uint16 - Features: FeatureBits -} - -type RemoteParams = { - DustLimitSatoshis: Money - MaxHTLCValueInFlightMSat: LNMoney - ChannelReserveSatoshis: Money - HTLCMinimumMSat: LNMoney - ToSelfDelay: BlockHeightOffset16 - MaxAcceptedHTLCs: uint16 - Features: FeatureBits -} - with - static member FromAcceptChannel (remoteInit: InitMsg) (msg: AcceptChannelMsg) = - { - DustLimitSatoshis = msg.DustLimitSatoshis - MaxHTLCValueInFlightMSat = msg.MaxHTLCValueInFlightMsat - ChannelReserveSatoshis = msg.ChannelReserveSatoshis - HTLCMinimumMSat = msg.HTLCMinimumMSat - ToSelfDelay = msg.ToSelfDelay - MaxAcceptedHTLCs = msg.MaxAcceptedHTLCs - Features = remoteInit.Features - } - - static member FromOpenChannel (remoteInit: InitMsg) - (msg: OpenChannelMsg) - : RemoteParams = - { - DustLimitSatoshis = msg.DustLimitSatoshis - MaxHTLCValueInFlightMSat = msg.MaxHTLCValueInFlightMsat - ChannelReserveSatoshis = msg.ChannelReserveSatoshis - HTLCMinimumMSat = msg.HTLCMinimumMsat - ToSelfDelay = msg.ToSelfDelay - MaxAcceptedHTLCs = msg.MaxAcceptedHTLCs - Features = remoteInit.Features - } +type OperationAddHTLC = + { + Amount: LNMoney + PaymentHash: PaymentHash + Expiry: BlockHeight + Onion: OnionPacket + Upstream: option + Origin: option + CurrentHeight: BlockHeight + } + + static member Create + amountMSat + paymentHash + expiry + onion + upstream + origin + currentHeight + = + { + Amount = amountMSat + PaymentHash = paymentHash + Expiry = expiry + Onion = onion + Upstream = upstream + Origin = origin + CurrentHeight = currentHeight + } + + +type OperationFulfillHTLC = + { + Id: HTLCId + PaymentPreimage: PaymentPreimage + Commit: bool + } + +type OperationFailHTLC = + { + Id: HTLCId + Reason: Choice, FailureMsg> + } + +type OperationFailMalformedHTLC = + { + Id: HTLCId + Sha256OfOnion: uint256 + FailureCode: FailureCode + } + +type OperationUpdateFee = + { + FeeRatePerKw: FeeRatePerKw + } + +type LocalParams = + { + DustLimitSatoshis: Money + MaxHTLCValueInFlightMSat: LNMoney + ChannelReserveSatoshis: Money + HTLCMinimumMSat: LNMoney + ToSelfDelay: BlockHeightOffset16 + MaxAcceptedHTLCs: uint16 + Features: FeatureBits + } + +type RemoteParams = + { + DustLimitSatoshis: Money + MaxHTLCValueInFlightMSat: LNMoney + ChannelReserveSatoshis: Money + HTLCMinimumMSat: LNMoney + ToSelfDelay: BlockHeightOffset16 + MaxAcceptedHTLCs: uint16 + Features: FeatureBits + } + + static member FromAcceptChannel + (remoteInit: InitMsg) + (msg: AcceptChannelMsg) + = + { + DustLimitSatoshis = msg.DustLimitSatoshis + MaxHTLCValueInFlightMSat = msg.MaxHTLCValueInFlightMsat + ChannelReserveSatoshis = msg.ChannelReserveSatoshis + HTLCMinimumMSat = msg.HTLCMinimumMSat + ToSelfDelay = msg.ToSelfDelay + MaxAcceptedHTLCs = msg.MaxAcceptedHTLCs + Features = remoteInit.Features + } + + static member FromOpenChannel + (remoteInit: InitMsg) + (msg: OpenChannelMsg) + : RemoteParams = + { + DustLimitSatoshis = msg.DustLimitSatoshis + MaxHTLCValueInFlightMSat = msg.MaxHTLCValueInFlightMsat + ChannelReserveSatoshis = msg.ChannelReserveSatoshis + HTLCMinimumMSat = msg.HTLCMinimumMsat + ToSelfDelay = msg.ToSelfDelay + MaxAcceptedHTLCs = msg.MaxAcceptedHTLCs + Features = remoteInit.Features + } /// Channel config which is static, ie. config parameters which are established /// during the channel handshake and persist unchagned through the lifetime of /// the channel. -type StaticChannelConfig = { - AnnounceChannel: bool - RemoteNodeId: NodeId - Network: Network - FundingTxMinimumDepth: BlockHeightOffset32 - LocalStaticShutdownScriptPubKey: Option - RemoteStaticShutdownScriptPubKey: Option - IsFunder: bool - FundingScriptCoin: ScriptCoin - LocalParams: LocalParams - RemoteParams: RemoteParams - RemoteChannelPubKeys: ChannelPubKeys -} - with - member this.ChannelId(): ChannelId = - this.FundingScriptCoin.Outpoint.ToChannelId() - -type ChannelOptions = { - MaxFeeRateMismatchRatio: float - // Amount (in millionth of a satoshi) the channel will charge per transferred satoshi. - // This may be allowed to change at runtime in a later update, however doing so must result in - // update messages sent to notify all nodes of our updated relay fee. - FeeProportionalMillionths: uint32 - /// We don't exchange more than this many signatures when negotiating the closing fee - MaxClosingNegotiationIterations: int32 - FeeEstimator: IFeeEstimator - } +type StaticChannelConfig = + { + AnnounceChannel: bool + RemoteNodeId: NodeId + Network: Network + FundingTxMinimumDepth: BlockHeightOffset32 + LocalStaticShutdownScriptPubKey: Option + RemoteStaticShutdownScriptPubKey: Option + IsFunder: bool + FundingScriptCoin: ScriptCoin + LocalParams: LocalParams + RemoteParams: RemoteParams + RemoteChannelPubKeys: ChannelPubKeys + } + + member this.ChannelId() : ChannelId = + this.FundingScriptCoin.Outpoint.ToChannelId() + +type ChannelOptions = + { + MaxFeeRateMismatchRatio: float + // Amount (in millionth of a satoshi) the channel will charge per transferred satoshi. + // This may be allowed to change at runtime in a later update, however doing so must result in + // update messages sent to notify all nodes of our updated relay fee. + FeeProportionalMillionths: uint32 + /// We don't exchange more than this many signatures when negotiating the closing fee + MaxClosingNegotiationIterations: int32 + FeeEstimator: IFeeEstimator + } diff --git a/src/DotNetLightning.Core/Channel/ChannelTypes.fs b/src/DotNetLightning.Core/Channel/ChannelTypes.fs index a9f233cf4..1b293e9dc 100644 --- a/src/DotNetLightning.Core/Channel/ChannelTypes.fs +++ b/src/DotNetLightning.Core/Channel/ChannelTypes.fs @@ -23,68 +23,87 @@ open NBitcoin [] module Data = - type NegotiatingState = { - LocalRequestedShutdown: Option - RemoteRequestedShutdown: Option - LocalClosingFeesProposed: List - RemoteClosingFeeProposed: Option - } with - static member New(): NegotiatingState = { - LocalRequestedShutdown = None - RemoteRequestedShutdown = None - LocalClosingFeesProposed = List.empty - RemoteClosingFeeProposed = None + type NegotiatingState = + { + LocalRequestedShutdown: Option + RemoteRequestedShutdown: Option + LocalClosingFeesProposed: List + RemoteClosingFeeProposed: Option } - member this.HasEnteredShutdown(): bool = - this.LocalRequestedShutdown.IsSome && this.RemoteRequestedShutdown.IsSome + + static member New() : NegotiatingState = + { + LocalRequestedShutdown = None + RemoteRequestedShutdown = None + LocalClosingFeesProposed = List.empty + RemoteClosingFeeProposed = None + } + + member this.HasEnteredShutdown() : bool = + this.LocalRequestedShutdown.IsSome + && this.RemoteRequestedShutdown.IsSome type ClosingSignedResponse = | NewClosingSigned of ClosingSignedMsg | MutualClose of FinalizedTx * Option -type SavedChannelState = { - StaticChannelConfig: StaticChannelConfig - RemotePerCommitmentSecrets: PerCommitmentSecretStore - ShortChannelId: Option - LocalCommit: LocalCommit - RemoteCommit: RemoteCommit - LocalChanges: LocalChanges - RemoteChanges: RemoteChanges -} with - member internal this.HasNoPendingHTLCs (remoteNextCommitInfo: RemoteNextCommitInfo) = +type SavedChannelState = + { + StaticChannelConfig: StaticChannelConfig + RemotePerCommitmentSecrets: PerCommitmentSecretStore + ShortChannelId: Option + LocalCommit: LocalCommit + RemoteCommit: RemoteCommit + LocalChanges: LocalChanges + RemoteChanges: RemoteChanges + } + + member internal this.HasNoPendingHTLCs + (remoteNextCommitInfo: RemoteNextCommitInfo) + = this.LocalCommit.Spec.OutgoingHTLCs.IsEmpty && this.LocalCommit.Spec.IncomingHTLCs.IsEmpty && this.RemoteCommit.Spec.OutgoingHTLCs.IsEmpty && this.RemoteCommit.Spec.IncomingHTLCs.IsEmpty - && (remoteNextCommitInfo |> function Waiting _ -> false | Revoked _ -> true) + && (remoteNextCommitInfo + |> function + | Waiting _ -> false + | Revoked _ -> true) - member internal this.GetOutgoingHTLCCrossSigned (remoteNextCommitInfo: RemoteNextCommitInfo) - (htlcId: HTLCId) - : Option = + member internal this.GetOutgoingHTLCCrossSigned + (remoteNextCommitInfo: RemoteNextCommitInfo) + (htlcId: HTLCId) + : Option = let remoteSigned = Map.tryFind htlcId this.LocalCommit.Spec.OutgoingHTLCs + let localSigned = let remoteCommit = match remoteNextCommitInfo with | Revoked _ -> this.RemoteCommit | Waiting nextRemoteCommit -> nextRemoteCommit + Map.tryFind htlcId remoteCommit.Spec.IncomingHTLCs + match remoteSigned, localSigned with | Some _, Some htlcIn -> htlcIn |> Some | _ -> None - member internal this.GetIncomingHTLCCrossSigned (remoteNextCommitInfo: RemoteNextCommitInfo) - (htlcId: HTLCId) - : Option = + member internal this.GetIncomingHTLCCrossSigned + (remoteNextCommitInfo: RemoteNextCommitInfo) + (htlcId: HTLCId) + : Option = let remoteSigned = Map.tryFind htlcId this.LocalCommit.Spec.IncomingHTLCs + let localSigned = let remoteCommit = match remoteNextCommitInfo with | Revoked _ -> this.RemoteCommit | Waiting nextRemoteCommit -> nextRemoteCommit + Map.tryFind htlcId remoteCommit.Spec.OutgoingHTLCs + match remoteSigned, localSigned with | Some _, Some htlcIn -> htlcIn |> Some | _ -> None - diff --git a/src/DotNetLightning.Core/Channel/ChannelValidation.fs b/src/DotNetLightning.Core/Channel/ChannelValidation.fs index ab4996aa5..cbad8a81a 100644 --- a/src/DotNetLightning.Core/Channel/ChannelValidation.fs +++ b/src/DotNetLightning.Core/Channel/ChannelValidation.fs @@ -13,150 +13,232 @@ open ResultUtils open ResultUtils.Portability exception ChannelException of ChannelError + module internal ChannelHelpers = - let getFundingScriptCoin (ourFundingPubKey: FundingPubKey) - (theirFundingPubKey: FundingPubKey) - (TxId fundingTxId) - (TxOutIndex fundingOutputIndex) - (fundingAmount: Money) - : ScriptCoin = + let getFundingScriptCoin + (ourFundingPubKey: FundingPubKey) + (theirFundingPubKey: FundingPubKey) + (TxId fundingTxId) + (TxOutIndex fundingOutputIndex) + (fundingAmount: Money) + : ScriptCoin = let redeem = Scripts.funding ourFundingPubKey theirFundingPubKey - Coin(fundingTxId, uint32 fundingOutputIndex, fundingAmount, redeem.WitHash.ScriptPubKey) + + Coin( + fundingTxId, + uint32 fundingOutputIndex, + fundingAmount, + redeem.WitHash.ScriptPubKey + ) |> fun c -> ScriptCoin(c, redeem) - let private makeFlags (isNode1: bool, enable: bool) = - (if isNode1 then 1uy else 0uy) ||| ((if enable then 1uy else 0uy) <<< 1) - - let internal makeChannelUpdate (chainHash, - nodeSecret: NodeSecret, - remoteNodeId: NodeId, - shortChannelId, - cltvExpiryDelta, - htlcMinimum, - feeBase, - feeProportionalMillionths, - enabled: bool, - timestamp - ) = - let timestamp = defaultArg timestamp ((System.DateTime.UtcNow.ToUnixTimestamp()) |> uint32) + let private makeFlags(isNode1: bool, enable: bool) = + (if isNode1 then + 1uy + else + 0uy) + ||| ((if enable then + 1uy + else + 0uy) + <<< 1) + + let internal makeChannelUpdate + ( + chainHash, + nodeSecret: NodeSecret, + remoteNodeId: NodeId, + shortChannelId, + cltvExpiryDelta, + htlcMinimum, + feeBase, + feeProportionalMillionths, + enabled: bool, + timestamp + ) = + let timestamp = + defaultArg + timestamp + ((System.DateTime.UtcNow.ToUnixTimestamp()) |> uint32) + let isNodeOne = nodeSecret.NodeId() < remoteNodeId - let unsignedChannelUpdate = { - ChainHash = chainHash - ShortChannelId = shortChannelId - Timestamp = timestamp - ChannelFlags = makeFlags (isNodeOne, enabled) - MessageFlags = 0uy - CLTVExpiryDelta = cltvExpiryDelta - HTLCMinimumMSat = htlcMinimum - FeeBaseMSat = feeBase - FeeProportionalMillionths = feeProportionalMillionths - HTLCMaximumMSat = None - } + + let unsignedChannelUpdate = + { + ChainHash = chainHash + ShortChannelId = shortChannelId + Timestamp = timestamp + ChannelFlags = makeFlags(isNodeOne, enabled) + MessageFlags = 0uy + CLTVExpiryDelta = cltvExpiryDelta + HTLCMinimumMSat = htlcMinimum + FeeBaseMSat = feeBase + FeeProportionalMillionths = feeProportionalMillionths + HTLCMaximumMSat = None + } + let signature = unsignedChannelUpdate.ToBytes() |> Crypto.Hashes.SHA256 |> uint256 |> nodeSecret.RawKey().Sign |> LNECDSASignature + { ChannelUpdateMsg.Contents = unsignedChannelUpdate Signature = signature } /// gets the fee we'd want to charge for adding an HTLC output to this channel - let internal getOurFeeBase (feeEstimator: IFeeEstimator) (FeeRatePerKw feeRatePerKw) (isFunder: bool): LNMoney = + let internal getOurFeeBase + (feeEstimator: IFeeEstimator) + (FeeRatePerKw feeRatePerKw) + (isFunder: bool) + : LNMoney = // for lack of a better metric, we calculate waht it would cost to consolidate the new HTLC // output value back into a transaction with the regular channel output: // the fee cost of the HTLC-success/HTLC-Timout transaction - let mutable res = uint64 feeRatePerKw * (max (ChannelConstants.HTLC_TIMEOUT_TX_WEIGHT) (ChannelConstants.HTLC_TIMEOUT_TX_WEIGHT)) |> fun r -> r / 1000UL - if (isFunder) then - res <- res + uint64 feeRatePerKw * COMMITMENT_TX_WEIGHT_PER_HTLC / 1000UL + let mutable res = + uint64 feeRatePerKw + * (max + (ChannelConstants.HTLC_TIMEOUT_TX_WEIGHT) + (ChannelConstants.HTLC_TIMEOUT_TX_WEIGHT)) + |> fun r -> r / 1000UL + + if isFunder then + res <- + res + + uint64 feeRatePerKw * COMMITMENT_TX_WEIGHT_PER_HTLC / 1000UL //+ the marginal cost of an input which spends the HTLC-Success/HTLC-Timeout output: res <- - res + (uint64 (feeEstimator.GetEstSatPer1000Weight(ConfirmationTarget.Normal).Value) * SPENDING_INPUT_FOR_A_OUTPUT_WEIGHT) / 1000UL + res + + (uint64( + feeEstimator + .GetEstSatPer1000Weight( + ConfirmationTarget.Normal + ) + .Value + ) + * SPENDING_INPUT_FOR_A_OUTPUT_WEIGHT) + / 1000UL + res |> LNMoney.Satoshis - let makeFirstCommitTxs (localIsFunder: bool) - (localChannelPubKeys: ChannelPubKeys) - (remoteChannelPubKeys: ChannelPubKeys) - (localParams: LocalParams) - (remoteParams: RemoteParams) - (fundingAmount: Money) - (pushAmount: LNMoney) - (initialFeeRatePerKw: FeeRatePerKw) - (fundingOutputIndex: TxOutIndex) - (fundingTxId: TxId) - (localPerCommitmentPoint: PerCommitmentPoint) - (remotePerCommitmentPoint: PerCommitmentPoint) - (network: Network) - : Result = + let makeFirstCommitTxs + (localIsFunder: bool) + (localChannelPubKeys: ChannelPubKeys) + (remoteChannelPubKeys: ChannelPubKeys) + (localParams: LocalParams) + (remoteParams: RemoteParams) + (fundingAmount: Money) + (pushAmount: LNMoney) + (initialFeeRatePerKw: FeeRatePerKw) + (fundingOutputIndex: TxOutIndex) + (fundingTxId: TxId) + (localPerCommitmentPoint: PerCommitmentPoint) + (remotePerCommitmentPoint: PerCommitmentPoint) + (network: Network) + : Result = let toLocal = if localIsFunder then fundingAmount.ToLNMoney() - pushAmount else pushAmount + let toRemote = if localIsFunder then pushAmount else fundingAmount.ToLNMoney() - pushAmount + let localChannelKeys = localChannelPubKeys - let localSpec = CommitmentSpec.Create toLocal toRemote initialFeeRatePerKw - let remoteSpec = CommitmentSpec.Create toRemote toLocal initialFeeRatePerKw + + let localSpec = + CommitmentSpec.Create toLocal toRemote initialFeeRatePerKw + + let remoteSpec = + CommitmentSpec.Create toRemote toLocal initialFeeRatePerKw + let checkTheyCanAffordFee() = let toRemote = remoteSpec.ToLocal - let fees = Transactions.commitTxFee remoteParams.DustLimitSatoshis remoteSpec - let missing = toRemote.ToMoney() - localParams.ChannelReserveSatoshis - fees + + let fees = + Transactions.commitTxFee + remoteParams.DustLimitSatoshis + remoteSpec + + let missing = + toRemote.ToMoney() - localParams.ChannelReserveSatoshis - fees + if missing < Money.Zero then - theyCannotAffordFee(toRemote, fees, localParams.ChannelReserveSatoshis) + theyCannotAffordFee( + toRemote, + fees, + localParams.ChannelReserveSatoshis + ) else Ok() + let makeFirstCommitTxCore() = - let scriptCoin = getFundingScriptCoin localChannelKeys.FundingPubKey - remoteChannelPubKeys.FundingPubKey - fundingTxId - fundingOutputIndex - fundingAmount - let localPubKeysForLocalCommitment = localPerCommitmentPoint.DeriveCommitmentPubKeys localChannelKeys - let remotePubKeysForLocalCommitment = localPerCommitmentPoint.DeriveCommitmentPubKeys remoteChannelPubKeys + let scriptCoin = + getFundingScriptCoin + localChannelKeys.FundingPubKey + remoteChannelPubKeys.FundingPubKey + fundingTxId + fundingOutputIndex + fundingAmount + + let localPubKeysForLocalCommitment = + localPerCommitmentPoint.DeriveCommitmentPubKeys localChannelKeys + + let remotePubKeysForLocalCommitment = + localPerCommitmentPoint.DeriveCommitmentPubKeys + remoteChannelPubKeys let localCommitTx = - Transactions.makeCommitTx scriptCoin - CommitmentNumber.FirstCommitment - localChannelKeys.PaymentBasepoint - remoteChannelPubKeys.PaymentBasepoint - localIsFunder - localParams.DustLimitSatoshis - remotePubKeysForLocalCommitment.RevocationPubKey - remoteParams.ToSelfDelay - localPubKeysForLocalCommitment.DelayedPaymentPubKey - remotePubKeysForLocalCommitment.PaymentPubKey - localPubKeysForLocalCommitment.HtlcPubKey - remotePubKeysForLocalCommitment.HtlcPubKey - localSpec - network - - let localPubKeysForRemoteCommitment = remotePerCommitmentPoint.DeriveCommitmentPubKeys localChannelKeys - let remotePubKeysForRemoteCommitment = remotePerCommitmentPoint.DeriveCommitmentPubKeys remoteChannelPubKeys + Transactions.makeCommitTx + scriptCoin + CommitmentNumber.FirstCommitment + localChannelKeys.PaymentBasepoint + remoteChannelPubKeys.PaymentBasepoint + localIsFunder + localParams.DustLimitSatoshis + remotePubKeysForLocalCommitment.RevocationPubKey + remoteParams.ToSelfDelay + localPubKeysForLocalCommitment.DelayedPaymentPubKey + remotePubKeysForLocalCommitment.PaymentPubKey + localPubKeysForLocalCommitment.HtlcPubKey + remotePubKeysForLocalCommitment.HtlcPubKey + localSpec + network + + let localPubKeysForRemoteCommitment = + remotePerCommitmentPoint.DeriveCommitmentPubKeys + localChannelKeys + + let remotePubKeysForRemoteCommitment = + remotePerCommitmentPoint.DeriveCommitmentPubKeys + remoteChannelPubKeys let remoteCommitTx = - Transactions.makeCommitTx scriptCoin - CommitmentNumber.FirstCommitment - remoteChannelPubKeys.PaymentBasepoint - localChannelKeys.PaymentBasepoint - (not localIsFunder) - (remoteParams.DustLimitSatoshis) - localPubKeysForRemoteCommitment.RevocationPubKey - localParams.ToSelfDelay - remotePubKeysForRemoteCommitment.DelayedPaymentPubKey - localPubKeysForRemoteCommitment.PaymentPubKey - remotePubKeysForRemoteCommitment.HtlcPubKey - localPubKeysForRemoteCommitment.HtlcPubKey - remoteSpec - network + Transactions.makeCommitTx + scriptCoin + CommitmentNumber.FirstCommitment + remoteChannelPubKeys.PaymentBasepoint + localChannelKeys.PaymentBasepoint + (not localIsFunder) + (remoteParams.DustLimitSatoshis) + localPubKeysForRemoteCommitment.RevocationPubKey + localParams.ToSelfDelay + remotePubKeysForRemoteCommitment.DelayedPaymentPubKey + localPubKeysForRemoteCommitment.PaymentPubKey + remotePubKeysForRemoteCommitment.HtlcPubKey + localPubKeysForRemoteCommitment.HtlcPubKey + remoteSpec + network (localSpec, localCommitTx, remoteSpec, remoteCommitTx) |> Ok @@ -172,92 +254,204 @@ module internal ChannelHelpers = module internal Validation = open DotNetLightning.Channel - let checkOurOpenChannelMsgAcceptable (localParams: LocalParams) (msg: OpenChannelMsg) = - Validation.ofResult(OpenChannelMsgValidation.checkFundingSatoshisLessThanMax localParams true msg) - *^> OpenChannelMsgValidation.checkChannelReserveSatohisLessThanFundingSatoshis msg + + let checkOurOpenChannelMsgAcceptable + (localParams: LocalParams) + (msg: OpenChannelMsg) + = + Validation.ofResult( + OpenChannelMsgValidation.checkFundingSatoshisLessThanMax + localParams + true + msg + ) + *^> OpenChannelMsgValidation.checkChannelReserveSatohisLessThanFundingSatoshis + msg *^> OpenChannelMsgValidation.checkPushMSatLesserThanFundingValue msg - *^> OpenChannelMsgValidation.checkFundingSatoshisLessThanDustLimitSatoshis msg + *^> OpenChannelMsgValidation.checkFundingSatoshisLessThanDustLimitSatoshis + msg *^> OpenChannelMsgValidation.checkMaxAcceptedHTLCs msg - *^> OpenChannelMsgValidation.checkFunderCanAffordFee (msg.FeeRatePerKw) msg - |> Result.mapError((@)["open_channel msg is invalid"] >> InvalidOpenChannelError.Create msg >> InvalidOpenChannel) - - let internal checkOpenChannelMsgAcceptable (channelHandshakeLimits: ChannelHandshakeLimits) - (channelOptions: ChannelOptions) - (announceChannel: bool) - (localParams: LocalParams) - (msg: OpenChannelMsg) = - let feeRate = channelOptions.FeeEstimator.GetEstSatPer1000Weight(ConfirmationTarget.Background) - Validation.ofResult(OpenChannelMsgValidation.checkFundingSatoshisLessThanMax localParams false msg) - *^> OpenChannelMsgValidation.checkChannelReserveSatohisLessThanFundingSatoshis msg + *^> OpenChannelMsgValidation.checkFunderCanAffordFee + (msg.FeeRatePerKw) + msg + |> Result.mapError( + (@)["open_channel msg is invalid"] + >> InvalidOpenChannelError.Create msg + >> InvalidOpenChannel + ) + + let internal checkOpenChannelMsgAcceptable + (channelHandshakeLimits: ChannelHandshakeLimits) + (channelOptions: ChannelOptions) + (announceChannel: bool) + (localParams: LocalParams) + (msg: OpenChannelMsg) + = + let feeRate = + channelOptions.FeeEstimator.GetEstSatPer1000Weight( + ConfirmationTarget.Background + ) + + Validation.ofResult( + OpenChannelMsgValidation.checkFundingSatoshisLessThanMax + localParams + false + msg + ) + *^> OpenChannelMsgValidation.checkChannelReserveSatohisLessThanFundingSatoshis + msg *^> OpenChannelMsgValidation.checkPushMSatLesserThanFundingValue msg - *^> OpenChannelMsgValidation.checkFundingSatoshisLessThanDustLimitSatoshis msg - *^> OpenChannelMsgValidation.checkRemoteFee channelOptions.FeeEstimator msg.FeeRatePerKw channelOptions.MaxFeeRateMismatchRatio + *^> OpenChannelMsgValidation.checkFundingSatoshisLessThanDustLimitSatoshis + msg + *^> OpenChannelMsgValidation.checkRemoteFee + channelOptions.FeeEstimator + msg.FeeRatePerKw + channelOptions.MaxFeeRateMismatchRatio *^> OpenChannelMsgValidation.checkMaxAcceptedHTLCs msg - *> OpenChannelMsgValidation.checkConfigPermits channelHandshakeLimits msg - *^> OpenChannelMsgValidation.checkChannelAnnouncementPreferenceAcceptable channelHandshakeLimits announceChannel msg - *> OpenChannelMsgValidation.checkIsAcceptableByCurrentFeeRate channelOptions.FeeEstimator msg + *> OpenChannelMsgValidation.checkConfigPermits + channelHandshakeLimits + msg + *^> OpenChannelMsgValidation.checkChannelAnnouncementPreferenceAcceptable + channelHandshakeLimits + announceChannel + msg + *> OpenChannelMsgValidation.checkIsAcceptableByCurrentFeeRate + channelOptions.FeeEstimator + msg *^> OpenChannelMsgValidation.checkFunderCanAffordFee feeRate msg - |> Result.mapError((@)["rejected received open_channel msg"] >> InvalidOpenChannelError.Create msg >> InvalidOpenChannel) + |> Result.mapError( + (@)["rejected received open_channel msg"] + >> InvalidOpenChannelError.Create msg + >> InvalidOpenChannel + ) - let internal checkAcceptChannelMsgAcceptable (channelHandshakeLimits: ChannelHandshakeLimits) - (fundingAmount: Money) - (channelReserveAmount: Money) - (dustLimit: Money) - (acceptChannelMsg: AcceptChannelMsg) = - Validation.ofResult(AcceptChannelMsgValidation.checkMaxAcceptedHTLCs acceptChannelMsg) + let internal checkAcceptChannelMsgAcceptable + (channelHandshakeLimits: ChannelHandshakeLimits) + (fundingAmount: Money) + (channelReserveAmount: Money) + (dustLimit: Money) + (acceptChannelMsg: AcceptChannelMsg) + = + Validation.ofResult( + AcceptChannelMsgValidation.checkMaxAcceptedHTLCs acceptChannelMsg + ) *^> AcceptChannelMsgValidation.checkDustLimit acceptChannelMsg - *^> AcceptChannelMsgValidation.checkChannelReserveSatoshis fundingAmount channelReserveAmount dustLimit acceptChannelMsg - *^> AcceptChannelMsgValidation.checkDustLimitIsLargerThanOurChannelReserve channelReserveAmount acceptChannelMsg - *^> AcceptChannelMsgValidation.checkMinimumHTLCValueIsAcceptable fundingAmount acceptChannelMsg - *> AcceptChannelMsgValidation.checkConfigPermits channelHandshakeLimits acceptChannelMsg - |> Result.mapError(InvalidAcceptChannelError.Create acceptChannelMsg >> InvalidAcceptChannel) - - - let checkOperationAddHTLC (remoteParams: RemoteParams) (op: OperationAddHTLC) = - Validation.ofResult(UpdateAddHTLCValidation.checkExpiryIsNotPast op.CurrentHeight op.Expiry) - *> UpdateAddHTLCValidation.checkExpiryIsInAcceptableRange op.CurrentHeight op.Expiry - *^> UpdateAddHTLCValidation.checkAmountIsLargerThanMinimum remoteParams.HTLCMinimumMSat op.Amount - |> Result.mapError(InvalidOperationAddHTLCError.Create op >> InvalidOperationAddHTLC) - - let checkOurUpdateAddHTLCIsAcceptableWithCurrentSpec (currentSpec) - (staticChannelConfig: StaticChannelConfig) - (add: UpdateAddHTLCMsg) = + *^> AcceptChannelMsgValidation.checkChannelReserveSatoshis + fundingAmount + channelReserveAmount + dustLimit + acceptChannelMsg + *^> AcceptChannelMsgValidation.checkDustLimitIsLargerThanOurChannelReserve + channelReserveAmount + acceptChannelMsg + *^> AcceptChannelMsgValidation.checkMinimumHTLCValueIsAcceptable + fundingAmount + acceptChannelMsg + *> AcceptChannelMsgValidation.checkConfigPermits + channelHandshakeLimits + acceptChannelMsg + |> Result.mapError( + InvalidAcceptChannelError.Create acceptChannelMsg + >> InvalidAcceptChannel + ) + + + let checkOperationAddHTLC + (remoteParams: RemoteParams) + (op: OperationAddHTLC) + = + Validation.ofResult( + UpdateAddHTLCValidation.checkExpiryIsNotPast + op.CurrentHeight + op.Expiry + ) + *> UpdateAddHTLCValidation.checkExpiryIsInAcceptableRange + op.CurrentHeight + op.Expiry + *^> UpdateAddHTLCValidation.checkAmountIsLargerThanMinimum + remoteParams.HTLCMinimumMSat + op.Amount + |> Result.mapError( + InvalidOperationAddHTLCError.Create op >> InvalidOperationAddHTLC + ) + + let checkOurUpdateAddHTLCIsAcceptableWithCurrentSpec + currentSpec + (staticChannelConfig: StaticChannelConfig) + (add: UpdateAddHTLCMsg) + = Validation.ofResult( UpdateAddHTLCValidationWithContext.checkLessThanHTLCValueInFlightLimit currentSpec staticChannelConfig.RemoteParams.MaxHTLCValueInFlightMSat add ) - *^> UpdateAddHTLCValidationWithContext.checkLessThanMaxAcceptedHTLC currentSpec staticChannelConfig.RemoteParams.MaxAcceptedHTLCs - *^> UpdateAddHTLCValidationWithContext.checkWeHaveSufficientFunds staticChannelConfig currentSpec - |> Result.mapError(InvalidUpdateAddHTLCError.Create add >> InvalidUpdateAddHTLC) - - let checkTheirUpdateAddHTLCIsAcceptable (state: Commitments) - (localParams: LocalParams) - (add: UpdateAddHTLCMsg) - (currentHeight: BlockHeight) = - Validation.ofResult(ValidationHelper.check add.HTLCId (<>) state.RemoteNextHTLCId "Received Unexpected HTLCId (%A). Must be (%A)") - *^> UpdateAddHTLCValidation.checkExpiryIsNotPast currentHeight add.CLTVExpiry - *> UpdateAddHTLCValidation.checkExpiryIsInAcceptableRange currentHeight add.CLTVExpiry - *^> UpdateAddHTLCValidation.checkAmountIsLargerThanMinimum localParams.HTLCMinimumMSat add.Amount - |> Result.mapError(InvalidUpdateAddHTLCError.Create add >> InvalidUpdateAddHTLC) - - let checkTheirUpdateAddHTLCIsAcceptableWithCurrentSpec (currentSpec) - (staticChannelConfig: StaticChannelConfig) - (add: UpdateAddHTLCMsg) = - Validation.ofResult(UpdateAddHTLCValidationWithContext.checkLessThanHTLCValueInFlightLimit currentSpec staticChannelConfig.LocalParams.MaxHTLCValueInFlightMSat add) - *^> UpdateAddHTLCValidationWithContext.checkLessThanMaxAcceptedHTLC currentSpec staticChannelConfig.LocalParams.MaxAcceptedHTLCs - *^> UpdateAddHTLCValidationWithContext.checkWeHaveSufficientFunds staticChannelConfig currentSpec - |> Result.mapError(InvalidUpdateAddHTLCError.Create add >> InvalidUpdateAddHTLC) - - let checkShutdownScriptPubKeyAcceptable (staticShutdownScriptPubKey: Option) - (requestedShutdownScriptPubKey: ShutdownScriptPubKey) - : Result = + *^> UpdateAddHTLCValidationWithContext.checkLessThanMaxAcceptedHTLC + currentSpec + staticChannelConfig.RemoteParams.MaxAcceptedHTLCs + *^> UpdateAddHTLCValidationWithContext.checkWeHaveSufficientFunds + staticChannelConfig + currentSpec + |> Result.mapError( + InvalidUpdateAddHTLCError.Create add >> InvalidUpdateAddHTLC + ) + + let checkTheirUpdateAddHTLCIsAcceptable + (state: Commitments) + (localParams: LocalParams) + (add: UpdateAddHTLCMsg) + (currentHeight: BlockHeight) + = + Validation.ofResult( + ValidationHelper.check + add.HTLCId + (<>) + state.RemoteNextHTLCId + "Received Unexpected HTLCId (%A). Must be (%A)" + ) + *^> UpdateAddHTLCValidation.checkExpiryIsNotPast + currentHeight + add.CLTVExpiry + *> UpdateAddHTLCValidation.checkExpiryIsInAcceptableRange + currentHeight + add.CLTVExpiry + *^> UpdateAddHTLCValidation.checkAmountIsLargerThanMinimum + localParams.HTLCMinimumMSat + add.Amount + |> Result.mapError( + InvalidUpdateAddHTLCError.Create add >> InvalidUpdateAddHTLC + ) + + let checkTheirUpdateAddHTLCIsAcceptableWithCurrentSpec + currentSpec + (staticChannelConfig: StaticChannelConfig) + (add: UpdateAddHTLCMsg) + = + Validation.ofResult( + UpdateAddHTLCValidationWithContext.checkLessThanHTLCValueInFlightLimit + currentSpec + staticChannelConfig.LocalParams.MaxHTLCValueInFlightMSat + add + ) + *^> UpdateAddHTLCValidationWithContext.checkLessThanMaxAcceptedHTLC + currentSpec + staticChannelConfig.LocalParams.MaxAcceptedHTLCs + *^> UpdateAddHTLCValidationWithContext.checkWeHaveSufficientFunds + staticChannelConfig + currentSpec + |> Result.mapError( + InvalidUpdateAddHTLCError.Create add >> InvalidUpdateAddHTLC + ) + + let checkShutdownScriptPubKeyAcceptable + (staticShutdownScriptPubKey: Option) + (requestedShutdownScriptPubKey: ShutdownScriptPubKey) + : Result = match staticShutdownScriptPubKey with | Some scriptPubKey when scriptPubKey <> requestedShutdownScriptPubKey -> - Error <| - cannotCloseChannel - "requested shutdown script does not match shutdown \ + Error + <| cannotCloseChannel + "requested shutdown script does not match shutdown \ script in open/accept channel" - | _ -> Ok () + | _ -> Ok() diff --git a/src/DotNetLightning.Core/Channel/CommitmentToLocalExtension.fs b/src/DotNetLightning.Core/Channel/CommitmentToLocalExtension.fs index c6acf407b..113ccc872 100644 --- a/src/DotNetLightning.Core/Channel/CommitmentToLocalExtension.fs +++ b/src/DotNetLightning.Core/Channel/CommitmentToLocalExtension.fs @@ -9,13 +9,16 @@ open DotNetLightning.Crypto open ResultUtils open ResultUtils.Portability -type CommitmentToLocalParameters = { - RevocationPubKey: RevocationPubKey - ToSelfDelay: BlockHeightOffset16 - LocalDelayedPubKey: DelayedPaymentPubKey -} - with - static member TryExtractParameters (scriptPubKey: Script): Option = +type CommitmentToLocalParameters = + { + RevocationPubKey: RevocationPubKey + ToSelfDelay: BlockHeightOffset16 + LocalDelayedPubKey: DelayedPaymentPubKey + } + + 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,130 +26,172 @@ type CommitmentToLocalParameters = { // mutable. |> List.ofSeq |> Seq.ofList - let checkOpCode(opcodeType: OpcodeType) = seqParser { - let! op = SeqParser.next() + + 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 { + SeqParser.parseToCompletion ops + <| seqParser { do! checkOpCode OpcodeType.OP_IF let! opRevocationPubKey = SeqParser.next() - let! revocationPubKey = seqParser { - match opRevocationPubKey.PushData with - | null -> return! SeqParser.abort() - | bytes -> - try - return RevocationPubKey.FromBytes bytes - with - | :? FormatException -> return! SeqParser.abort() - } + + let! revocationPubKey = + seqParser { + match opRevocationPubKey.PushData with + | null -> return! SeqParser.abort() + | bytes -> + try + return RevocationPubKey.FromBytes bytes + with + | :? FormatException -> return! SeqParser.abort() + } + do! checkOpCode OpcodeType.OP_ELSE let! opToSelfDelay = SeqParser.next() - let! toSelfDelay = seqParser { - let nullableToSelfDelay = opToSelfDelay.GetLong() - if nullableToSelfDelay.HasValue then - try - return BlockHeightOffset16 (Convert.ToUInt16 nullableToSelfDelay.Value) - with - | :? OverflowException -> return! SeqParser.abort() - else - return! SeqParser.abort() - } + + let! toSelfDelay = + seqParser { + let nullableToSelfDelay = opToSelfDelay.GetLong() + + if nullableToSelfDelay.HasValue then + try + return + BlockHeightOffset16( + Convert.ToUInt16 + nullableToSelfDelay.Value + ) + with + | :? OverflowException -> return! SeqParser.abort() + else + return! SeqParser.abort() + } + do! checkOpCode OpcodeType.OP_CHECKSEQUENCEVERIFY do! checkOpCode OpcodeType.OP_DROP let! opLocalDelayedPubKey = SeqParser.next() - let! localDelayedPubKey = seqParser { - match opLocalDelayedPubKey.PushData with - | null -> return! SeqParser.abort() - | bytes -> - try - return DelayedPaymentPubKey.FromBytes bytes - with - | :? FormatException -> return! SeqParser.abort() - } + + let! localDelayedPubKey = + seqParser { + match opLocalDelayedPubKey.PushData with + | null -> return! SeqParser.abort() + | bytes -> + try + return DelayedPaymentPubKey.FromBytes bytes + with + | :? FormatException -> return! SeqParser.abort() + } + do! checkOpCode OpcodeType.OP_ENDIF do! checkOpCode OpcodeType.OP_CHECKSIG - return { - RevocationPubKey = revocationPubKey - ToSelfDelay = toSelfDelay - LocalDelayedPubKey = localDelayedPubKey - } + + return + { + RevocationPubKey = revocationPubKey + ToSelfDelay = toSelfDelay + LocalDelayedPubKey = localDelayedPubKey + } } + match parseToCompletionResult with | Ok data -> Some data | Error _consumeAllError -> None type internal CommitmentToLocalExtension() = inherit BuilderExtension() - override __.Match (coin: ICoin, _input: PSBTInput): bool = - (CommitmentToLocalParameters.TryExtractParameters (coin.GetScriptCode())).IsSome - - override __.Sign(inputSigningContext: InputSigningContext, keyRepo: IKeyRepository, signer: ISigner) = - let scriptPubKey = inputSigningContext.Coin.GetScriptCode() - - match keyRepo.FindKey scriptPubKey with - | :? PubKey as pubKey when pubKey |> isNull |> not -> - match signer.Sign pubKey with - | :? TransactionSignature as signature when signature |> isNull |> not -> - inputSigningContext.Input.PartialSigs.AddOrReplace(pubKey, signature) - | _ -> () + + override __.Match(coin: ICoin, _input: PSBTInput) : bool = + (CommitmentToLocalParameters.TryExtractParameters(coin.GetScriptCode())) + .IsSome + + override __.Sign + ( + inputSigningContext: InputSigningContext, + keyRepo: IKeyRepository, + signer: ISigner + ) = + let scriptPubKey = inputSigningContext.Coin.GetScriptCode() + + match keyRepo.FindKey scriptPubKey with + | :? PubKey as pubKey when pubKey |> isNull |> not -> + match signer.Sign pubKey with + | :? TransactionSignature as signature when + signature |> isNull |> not + -> + inputSigningContext.Input.PartialSigs.AddOrReplace( + pubKey, + signature + ) | _ -> () + | _ -> () - override __.CanDeduceScriptPubKey(_scriptSig: Script): bool = - false + override __.CanDeduceScriptPubKey(_scriptSig: Script) : bool = + false - override __.DeduceScriptPubKey(_scriptSig: Script): Script = - raise <| NotSupportedException() + override __.DeduceScriptPubKey(_scriptSig: Script) : Script = + raise <| NotSupportedException() - override __.CanEstimateScriptSigSize(coin: ICoin): bool = - (CommitmentToLocalParameters.TryExtractParameters (coin.GetScriptCode())).IsSome + override __.CanEstimateScriptSigSize(coin: ICoin) : bool = + (CommitmentToLocalParameters.TryExtractParameters(coin.GetScriptCode())) + .IsSome - override __.EstimateScriptSigSize(_coin: ICoin): int = - (* + override __.EstimateScriptSigSize(_coin: ICoin) : int = + (* Max script signature size = max signature size + op_true/op_false (1 byte) According to BIP 137: "Signatures are either 73, 72, or 71 bytes long, with probabilities approximately 25%, 50% and 25% respectively, although sizes even smaller than that are possible with exponentially decreasing probability" Reference: https://github.com/bitcoin/bips/blob/master/bip-0137.mediawiki#background-on-ecdsa-signatures *) - 73 + 1 - - override __.IsCompatibleKey(pubKey: IPubKey, scriptPubKey: Script): bool = - match CommitmentToLocalParameters.TryExtractParameters scriptPubKey with - | None -> false - | Some parameters -> - parameters.RevocationPubKey.RawPubKey() :> IPubKey = pubKey - || parameters.LocalDelayedPubKey.RawPubKey() :> IPubKey = pubKey - - - override __.Finalize(inputSigningContext: InputSigningContext) = - let scriptPubKey = inputSigningContext.Coin.GetScriptCode() - let parameters = - match (CommitmentToLocalParameters.TryExtractParameters scriptPubKey) with - | Some parameters -> parameters - | None -> - failwith - "NBitcoin should not call this unless Match returns true" - - let txIn = inputSigningContext.Input - if txIn.PartialSigs.Count <> 0 then - let keyAndSignatureOpt = - txIn.PartialSigs - |> Seq.tryExactlyOne - match keyAndSignatureOpt with - | Some keyAndSignature when keyAndSignature.Key = parameters.RevocationPubKey.RawPubKey() -> - inputSigningContext.Input.FinalScriptSig <- - Script [ - Op.GetPushOp (keyAndSignature.Value.ToBytes()) + 73 + 1 + + override __.IsCompatibleKey(pubKey: IPubKey, scriptPubKey: Script) : bool = + match CommitmentToLocalParameters.TryExtractParameters scriptPubKey with + | None -> false + | Some parameters -> + parameters.RevocationPubKey.RawPubKey() :> IPubKey = pubKey + || parameters.LocalDelayedPubKey.RawPubKey() :> IPubKey = pubKey + + + override __.Finalize(inputSigningContext: InputSigningContext) = + let scriptPubKey = inputSigningContext.Coin.GetScriptCode() + + let parameters = + match (CommitmentToLocalParameters.TryExtractParameters scriptPubKey) + with + | Some parameters -> parameters + | None -> + failwith + "NBitcoin should not call this unless Match returns true" + + let txIn = inputSigningContext.Input + + if txIn.PartialSigs.Count <> 0 then + let keyAndSignatureOpt = txIn.PartialSigs |> Seq.tryExactlyOne + + match keyAndSignatureOpt with + | Some keyAndSignature when + keyAndSignature.Key = parameters.RevocationPubKey.RawPubKey() + -> + inputSigningContext.Input.FinalScriptSig <- + Script + [ + Op.GetPushOp(keyAndSignature.Value.ToBytes()) Op.op_Implicit OpcodeType.OP_TRUE ] - | Some keyAndSignature when keyAndSignature.Key = parameters.LocalDelayedPubKey.RawPubKey() -> - inputSigningContext.Input.FinalScriptSig <- - Script [ - Op.GetPushOp (keyAndSignature.Value.ToBytes()) + | Some keyAndSignature when + keyAndSignature.Key = parameters.LocalDelayedPubKey.RawPubKey() + -> + inputSigningContext.Input.FinalScriptSig <- + Script + [ + Op.GetPushOp(keyAndSignature.Value.ToBytes()) Op.op_Implicit OpcodeType.OP_FALSE ] - | _ -> () + | _ -> () diff --git a/src/DotNetLightning.Core/Channel/Commitments.fs b/src/DotNetLightning.Core/Channel/Commitments.fs index 9b66a95ce..1d33a2209 100644 --- a/src/DotNetLightning.Core/Channel/Commitments.fs +++ b/src/DotNetLightning.Core/Channel/Commitments.fs @@ -10,124 +10,159 @@ open ResultUtils open ResultUtils.Portability open DotNetLightning.Transactions -type LocalChanges = { - Signed: IUpdateMsg list - ACKed: IUpdateMsg list -} - with - static member Zero = { Signed = []; ACKed = [] } - -type RemoteChanges = { - Signed: IUpdateMsg list - ACKed: IUpdateMsg list -} - with - static member Zero = { Signed = []; ACKed = [] } - -type PublishableTxs = { - CommitTx: FinalizedTx - HTLCTxs: FinalizedTx list -} - -type LocalCommit = { - Index: CommitmentNumber - Spec: CommitmentSpec - PublishableTxs: PublishableTxs - /// These are not redeemable on-chain until we get a corresponding preimage. - PendingHTLCSuccessTxs: HTLCSuccessTx list -} -type RemoteCommit = { - Index: CommitmentNumber - Spec: CommitmentSpec - TxId: TxId - RemotePerCommitmentPoint: PerCommitmentPoint -} +type LocalChanges = + { + Signed: list + ACKed: list + } + + static member Zero = + { + Signed = [] + ACKed = [] + } + +type RemoteChanges = + { + Signed: list + ACKed: list + } + + static member Zero = + { + Signed = [] + ACKed = [] + } + +type PublishableTxs = + { + CommitTx: FinalizedTx + HTLCTxs: list + } + +type LocalCommit = + { + Index: CommitmentNumber + Spec: CommitmentSpec + PublishableTxs: PublishableTxs + /// These are not redeemable on-chain until we get a corresponding preimage. + PendingHTLCSuccessTxs: list + } + +type RemoteCommit = + { + Index: CommitmentNumber + Spec: CommitmentSpec + TxId: TxId + RemotePerCommitmentPoint: PerCommitmentPoint + } type RemoteNextCommitInfo = | Waiting of RemoteCommit | Revoked of PerCommitmentPoint - with - member this.PerCommitmentPoint(): PerCommitmentPoint = - match this with - | Waiting remoteCommit -> remoteCommit.RemotePerCommitmentPoint - | Revoked perCommitmentPoint -> perCommitmentPoint -type Amounts = + member this.PerCommitmentPoint() : PerCommitmentPoint = + match this with + | Waiting remoteCommit -> remoteCommit.RemotePerCommitmentPoint + | Revoked perCommitmentPoint -> perCommitmentPoint + +type Amounts = { ToLocal: Money ToRemote: Money } -type Commitments = { - ProposedLocalChanges: list - ProposedRemoteChanges: list - LocalNextHTLCId: HTLCId - RemoteNextHTLCId: HTLCId - OriginChannels: Map -} - with - member this.AddLocalProposal(proposal: IUpdateMsg) = - { - this with - ProposedLocalChanges = proposal :: this.ProposedLocalChanges - } - - member this.AddRemoteProposal(proposal: IUpdateMsg) = - { - this with - ProposedRemoteChanges = proposal :: this.ProposedRemoteChanges - } - - member this.IncrLocalHTLCId() = { this with LocalNextHTLCId = this.LocalNextHTLCId + 1UL } - member this.IncrRemoteHTLCId() = { this with RemoteNextHTLCId = this.RemoteNextHTLCId + 1UL } - - member internal this.LocalHasUnsignedOutgoingHTLCs() = - this.ProposedLocalChanges |> List.exists(fun p -> match p with | :? UpdateAddHTLCMsg -> true | _ -> false) - - member internal this.RemoteHasUnsignedOutgoingHTLCs() = - this.ProposedRemoteChanges |> List.exists(fun p -> match p with | :? UpdateAddHTLCMsg -> true | _ -> false) - - - static member RemoteCommitAmount (isLocalFunder: bool) - (remoteParams: RemoteParams) - (remoteCommit: RemoteCommit) - : Amounts = - let commitFee = Transactions.commitTxFee - remoteParams.DustLimitSatoshis - remoteCommit.Spec - - let (toLocalAmount, toRemoteAmount) = - if isLocalFunder then - (remoteCommit.Spec.ToLocal.Satoshi - |> Money.Satoshis), - (remoteCommit.Spec.ToRemote.Satoshi - |> Money.Satoshis) - commitFee - else - (remoteCommit.Spec.ToLocal.Satoshi - |> Money.Satoshis) - commitFee, - (remoteCommit.Spec.ToRemote.Satoshi - |> Money.Satoshis) - - {Amounts.ToLocal = toLocalAmount; ToRemote = toRemoteAmount} - - static member LocalCommitAmount (isLocalFunder: bool) - (localParams: LocalParams) - (localCommit: LocalCommit) - : Amounts = - let commitFee = Transactions.commitTxFee - localParams.DustLimitSatoshis - localCommit.Spec - - let (toLocalAmount, toRemoteAmount) = - if isLocalFunder then - (localCommit.Spec.ToLocal.Satoshi - |> Money.Satoshis) - commitFee, - (localCommit.Spec.ToRemote.Satoshi - |> Money.Satoshis) - else - (localCommit.Spec.ToLocal.Satoshi - |> Money.Satoshis), - (localCommit.Spec.ToRemote.Satoshi - |> Money.Satoshis) - commitFee - - {Amounts.ToLocal = toLocalAmount; ToRemote = toRemoteAmount} +type Commitments = + { + ProposedLocalChanges: list + ProposedRemoteChanges: list + LocalNextHTLCId: HTLCId + RemoteNextHTLCId: HTLCId + OriginChannels: Map + } + + member this.AddLocalProposal(proposal: IUpdateMsg) = + { this with + ProposedLocalChanges = proposal :: this.ProposedLocalChanges + } + + member this.AddRemoteProposal(proposal: IUpdateMsg) = + { this with + ProposedRemoteChanges = proposal :: this.ProposedRemoteChanges + } + + member this.IncrLocalHTLCId() = + { this with + LocalNextHTLCId = this.LocalNextHTLCId + 1UL + } + + member this.IncrRemoteHTLCId() = + { this with + RemoteNextHTLCId = this.RemoteNextHTLCId + 1UL + } + + member internal this.LocalHasUnsignedOutgoingHTLCs() = + this.ProposedLocalChanges + |> List.exists(fun p -> + match p with + | :? UpdateAddHTLCMsg -> true + | _ -> false + ) + + member internal this.RemoteHasUnsignedOutgoingHTLCs() = + this.ProposedRemoteChanges + |> List.exists(fun p -> + match p with + | :? UpdateAddHTLCMsg -> true + | _ -> false + ) + + + static member RemoteCommitAmount + (isLocalFunder: bool) + (remoteParams: RemoteParams) + (remoteCommit: RemoteCommit) + : Amounts = + let commitFee = + Transactions.commitTxFee + remoteParams.DustLimitSatoshis + remoteCommit.Spec + + let (toLocalAmount, toRemoteAmount) = + if isLocalFunder then + (remoteCommit.Spec.ToLocal.Satoshi |> Money.Satoshis), + (remoteCommit.Spec.ToRemote.Satoshi |> Money.Satoshis) + - commitFee + else + (remoteCommit.Spec.ToLocal.Satoshi |> Money.Satoshis) + - commitFee, + (remoteCommit.Spec.ToRemote.Satoshi |> Money.Satoshis) + + { + Amounts.ToLocal = toLocalAmount + ToRemote = toRemoteAmount + } + + static member LocalCommitAmount + (isLocalFunder: bool) + (localParams: LocalParams) + (localCommit: LocalCommit) + : Amounts = + let commitFee = + Transactions.commitTxFee + localParams.DustLimitSatoshis + localCommit.Spec + + let (toLocalAmount, toRemoteAmount) = + if isLocalFunder then + (localCommit.Spec.ToLocal.Satoshi |> Money.Satoshis) - commitFee, + (localCommit.Spec.ToRemote.Satoshi |> Money.Satoshis) + else + (localCommit.Spec.ToLocal.Satoshi |> Money.Satoshis), + (localCommit.Spec.ToRemote.Satoshi |> Money.Satoshis) + - commitFee + + { + Amounts.ToLocal = toLocalAmount + ToRemote = toRemoteAmount + } diff --git a/src/DotNetLightning.Core/Channel/CommitmentsModule.fs b/src/DotNetLightning.Core/Channel/CommitmentsModule.fs index 170dc0dd9..6a9841522 100644 --- a/src/DotNetLightning.Core/Channel/CommitmentsModule.fs +++ b/src/DotNetLightning.Core/Channel/CommitmentsModule.fs @@ -13,52 +13,68 @@ open DotNetLightning.Serialization.Msgs open ResultUtils open ResultUtils.Portability -[] +[] module internal Commitments = module Helpers = - let isAlreadySent (htlc: UpdateAddHTLCMsg) (proposed: IUpdateMsg list) = + let isAlreadySent + (htlc: UpdateAddHTLCMsg) + (proposed: list) + = proposed - |> List.exists(fun p -> match p with - | :? UpdateFulfillHTLCMsg as u -> u.HTLCId = htlc.HTLCId - | :? UpdateFailHTLCMsg as u -> u.HTLCId = htlc.HTLCId - | :? UpdateFailMalformedHTLCMsg as u -> u.HTLCId = htlc.HTLCId - | _ -> false) + |> List.exists(fun p -> + match p with + | :? UpdateFulfillHTLCMsg as u -> u.HTLCId = htlc.HTLCId + | :? UpdateFailHTLCMsg as u -> u.HTLCId = htlc.HTLCId + | :? UpdateFailMalformedHTLCMsg as u -> u.HTLCId = htlc.HTLCId + | _ -> false + ) let makeRemoteTxs (staticChannelConfig: StaticChannelConfig) (commitTxNumber: CommitmentNumber) (localChannelPubKeys: ChannelPubKeys) (remotePerCommitmentPoint: PerCommitmentPoint) - (spec: CommitmentSpec) = - let localCommitmentPubKeys = remotePerCommitmentPoint.DeriveCommitmentPubKeys localChannelPubKeys - let remoteCommitmentPubKeys = remotePerCommitmentPoint.DeriveCommitmentPubKeys staticChannelConfig.RemoteChannelPubKeys + (spec: CommitmentSpec) + = + let localCommitmentPubKeys = + remotePerCommitmentPoint.DeriveCommitmentPubKeys + localChannelPubKeys + + let remoteCommitmentPubKeys = + remotePerCommitmentPoint.DeriveCommitmentPubKeys + staticChannelConfig.RemoteChannelPubKeys + let commitTx = - Transactions.makeCommitTx staticChannelConfig.FundingScriptCoin - commitTxNumber - staticChannelConfig.RemoteChannelPubKeys.PaymentBasepoint - localChannelPubKeys.PaymentBasepoint - (not staticChannelConfig.IsFunder) - (staticChannelConfig.RemoteParams.DustLimitSatoshis) - localCommitmentPubKeys.RevocationPubKey - staticChannelConfig.LocalParams.ToSelfDelay - remoteCommitmentPubKeys.DelayedPaymentPubKey - localCommitmentPubKeys.PaymentPubKey - remoteCommitmentPubKeys.HtlcPubKey - localCommitmentPubKeys.HtlcPubKey - spec - staticChannelConfig.Network + Transactions.makeCommitTx + staticChannelConfig.FundingScriptCoin + commitTxNumber + staticChannelConfig.RemoteChannelPubKeys.PaymentBasepoint + localChannelPubKeys.PaymentBasepoint + (not staticChannelConfig.IsFunder) + (staticChannelConfig.RemoteParams.DustLimitSatoshis) + localCommitmentPubKeys.RevocationPubKey + staticChannelConfig.LocalParams.ToSelfDelay + remoteCommitmentPubKeys.DelayedPaymentPubKey + localCommitmentPubKeys.PaymentPubKey + remoteCommitmentPubKeys.HtlcPubKey + localCommitmentPubKeys.HtlcPubKey + spec + staticChannelConfig.Network + result { - let! (htlcTimeoutTxs, htlcSuccessTxs) = - Transactions.makeHTLCTxs - (commitTx.Value.GetGlobalTransaction()) - (staticChannelConfig.RemoteParams.DustLimitSatoshis) - localCommitmentPubKeys.RevocationPubKey - (staticChannelConfig.LocalParams.ToSelfDelay) - remoteCommitmentPubKeys.DelayedPaymentPubKey - remoteCommitmentPubKeys.HtlcPubKey - localCommitmentPubKeys.HtlcPubKey - spec - staticChannelConfig.Network + let! (htlcTimeoutTxs, htlcSuccessTxs) = + Transactions.makeHTLCTxs + (commitTx.Value.GetGlobalTransaction()) + (staticChannelConfig.RemoteParams.DustLimitSatoshis) + localCommitmentPubKeys.RevocationPubKey + (staticChannelConfig.LocalParams.ToSelfDelay) + remoteCommitmentPubKeys.DelayedPaymentPubKey + remoteCommitmentPubKeys.HtlcPubKey + localCommitmentPubKeys.HtlcPubKey + spec + staticChannelConfig.Network + return (commitTx, htlcTimeoutTxs, htlcSuccessTxs) } @@ -68,237 +84,343 @@ module internal Commitments = (localChannelPubKeys: ChannelPubKeys) (localPerCommitmentPoint: PerCommitmentPoint) (spec: CommitmentSpec) - : Result<(CommitTx * HTLCTimeoutTx list * HTLCSuccessTx list), _> = - let localCommitmentPubKeys = localPerCommitmentPoint.DeriveCommitmentPubKeys localChannelPubKeys - let remoteCommitmentPubKeys = localPerCommitmentPoint.DeriveCommitmentPubKeys staticChannelConfig.RemoteChannelPubKeys + : Result<(CommitTx * list * list), _> = + let localCommitmentPubKeys = + localPerCommitmentPoint.DeriveCommitmentPubKeys + localChannelPubKeys + + let remoteCommitmentPubKeys = + localPerCommitmentPoint.DeriveCommitmentPubKeys + staticChannelConfig.RemoteChannelPubKeys let commitTx = - Transactions.makeCommitTx staticChannelConfig.FundingScriptCoin - commitTxNumber - localChannelPubKeys.PaymentBasepoint - staticChannelConfig.RemoteChannelPubKeys.PaymentBasepoint - staticChannelConfig.IsFunder - staticChannelConfig.LocalParams.DustLimitSatoshis - remoteCommitmentPubKeys.RevocationPubKey - staticChannelConfig.RemoteParams.ToSelfDelay - localCommitmentPubKeys.DelayedPaymentPubKey - remoteCommitmentPubKeys.PaymentPubKey - localCommitmentPubKeys.HtlcPubKey - remoteCommitmentPubKeys.HtlcPubKey - spec - staticChannelConfig.Network + Transactions.makeCommitTx + staticChannelConfig.FundingScriptCoin + commitTxNumber + localChannelPubKeys.PaymentBasepoint + staticChannelConfig.RemoteChannelPubKeys.PaymentBasepoint + staticChannelConfig.IsFunder + staticChannelConfig.LocalParams.DustLimitSatoshis + remoteCommitmentPubKeys.RevocationPubKey + staticChannelConfig.RemoteParams.ToSelfDelay + localCommitmentPubKeys.DelayedPaymentPubKey + remoteCommitmentPubKeys.PaymentPubKey + localCommitmentPubKeys.HtlcPubKey + remoteCommitmentPubKeys.HtlcPubKey + spec + staticChannelConfig.Network + result { let! (htlcTimeoutTxs, htlcSuccessTxs) = - Transactions.makeHTLCTxs (commitTx.Value.GetGlobalTransaction()) - (staticChannelConfig.LocalParams.DustLimitSatoshis) - remoteCommitmentPubKeys.RevocationPubKey - staticChannelConfig.RemoteParams.ToSelfDelay - localCommitmentPubKeys.DelayedPaymentPubKey - localCommitmentPubKeys.HtlcPubKey - remoteCommitmentPubKeys.HtlcPubKey - (spec) - staticChannelConfig.Network + Transactions.makeHTLCTxs + (commitTx.Value.GetGlobalTransaction()) + (staticChannelConfig.LocalParams.DustLimitSatoshis) + remoteCommitmentPubKeys.RevocationPubKey + staticChannelConfig.RemoteParams.ToSelfDelay + localCommitmentPubKeys.DelayedPaymentPubKey + localCommitmentPubKeys.HtlcPubKey + remoteCommitmentPubKeys.HtlcPubKey + (spec) + staticChannelConfig.Network + return (commitTx, htlcTimeoutTxs, htlcSuccessTxs) } - let sortBothHTLCs (htlcTimeoutTxs: HTLCTimeoutTx list) (htlcSuccessTxs: HTLCSuccessTx list) = + let sortBothHTLCs + (htlcTimeoutTxs: list) + (htlcSuccessTxs: list) + = let timeoutTXsV = (htlcTimeoutTxs |> Seq.cast) let successTXsV = (htlcSuccessTxs |> Seq.cast) + Seq.append timeoutTXsV successTXsV |> List.ofSeq - |> List.sortBy(fun htlc -> htlc.Value.GetGlobalTransaction().Inputs.[htlc.WhichInput].PrevOut.N) + |> List.sortBy(fun htlc -> + htlc.Value.GetGlobalTransaction().Inputs.[htlc.WhichInput] + .PrevOut + .N + ) - let checkUpdateFee (channelOptions: ChannelOptions) - (msg: UpdateFeeMsg) - (localFeeRate: FeeRatePerKw) = + let checkUpdateFee + (channelOptions: ChannelOptions) + (msg: UpdateFeeMsg) + (localFeeRate: FeeRatePerKw) + = let maxMismatch = channelOptions.MaxFeeRateMismatchRatio - UpdateFeeValidation.checkFeeDiffTooHigh (msg) (localFeeRate) (maxMismatch) - let sendFulfill (op: OperationFulfillHTLC) - (cm: Commitments) - (savedChannelState: SavedChannelState) - (remoteNextCommitInfo: RemoteNextCommitInfo) = - match savedChannelState.GetIncomingHTLCCrossSigned remoteNextCommitInfo op.Id with + UpdateFeeValidation.checkFeeDiffTooHigh + (msg) + (localFeeRate) + (maxMismatch) + + let sendFulfill + (op: OperationFulfillHTLC) + (cm: Commitments) + (savedChannelState: SavedChannelState) + (remoteNextCommitInfo: RemoteNextCommitInfo) + = + match + savedChannelState.GetIncomingHTLCCrossSigned + remoteNextCommitInfo + op.Id + with | Some htlc when (cm.ProposedLocalChanges |> Helpers.isAlreadySent htlc) -> htlc.HTLCId |> htlcAlreadySent | Some htlc when (htlc.PaymentHash = op.PaymentPreimage.Hash) -> - let msgToSend: UpdateFulfillHTLCMsg = { - ChannelId = savedChannelState.StaticChannelConfig.ChannelId() - HTLCId = op.Id - PaymentPreimage = op.PaymentPreimage - } + let msgToSend: UpdateFulfillHTLCMsg = + { + ChannelId = + savedChannelState.StaticChannelConfig.ChannelId() + HTLCId = op.Id + PaymentPreimage = op.PaymentPreimage + } + let newCommitments = cm.AddLocalProposal(msgToSend) (msgToSend, newCommitments) |> Ok | Some htlc -> - (htlc.PaymentHash, op.PaymentPreimage) - |> invalidPaymentPreimage - | None -> - op.Id - |> unknownHTLCId - - let receiveFulfill (msg: UpdateFulfillHTLCMsg) - (cm: Commitments) - (savedChannelState: SavedChannelState) - (remoteNextCommitInfo: RemoteNextCommitInfo) = - match savedChannelState.GetOutgoingHTLCCrossSigned remoteNextCommitInfo msg.HTLCId with + (htlc.PaymentHash, op.PaymentPreimage) |> invalidPaymentPreimage + | None -> op.Id |> unknownHTLCId + + let receiveFulfill + (msg: UpdateFulfillHTLCMsg) + (cm: Commitments) + (savedChannelState: SavedChannelState) + (remoteNextCommitInfo: RemoteNextCommitInfo) + = + match + savedChannelState.GetOutgoingHTLCCrossSigned + remoteNextCommitInfo + msg.HTLCId + with | Some htlc when htlc.PaymentHash = msg.PaymentPreimage.Hash -> let commitments = cm.AddRemoteProposal(msg) commitments |> Ok | Some htlc -> - (htlc.PaymentHash, msg.PaymentPreimage) - |> invalidPaymentPreimage - | None -> - msg.HTLCId - |> unknownHTLCId - - let sendFail (nodeSecret: NodeSecret) - (op: OperationFailHTLC) - (cm: Commitments) - (savedChannelState: SavedChannelState) - (remoteNextCommitInfo: RemoteNextCommitInfo) = - match savedChannelState.GetIncomingHTLCCrossSigned remoteNextCommitInfo op.Id with - | Some htlc when (cm.ProposedLocalChanges |> Helpers.isAlreadySent htlc) -> + (htlc.PaymentHash, msg.PaymentPreimage) |> invalidPaymentPreimage + | None -> msg.HTLCId |> unknownHTLCId + + let sendFail + (nodeSecret: NodeSecret) + (op: OperationFailHTLC) + (cm: Commitments) + (savedChannelState: SavedChannelState) + (remoteNextCommitInfo: RemoteNextCommitInfo) + = + match + savedChannelState.GetIncomingHTLCCrossSigned + remoteNextCommitInfo + op.Id + with + | Some htlc when (cm.ProposedLocalChanges |> Helpers.isAlreadySent htlc) -> htlc.HTLCId |> htlcAlreadySent | Some htlc -> let ad = htlc.PaymentHash.ToBytes() let rawPacket = htlc.OnionRoutingPacket.ToBytes() + Sphinx.parsePacket (nodeSecret.RawKey()) ad rawPacket |> Result.mapError(ChannelError.CryptoError) - >>= fun ({ SharedSecret = ss}) -> - let reason = - op.Reason - |> function Choice1Of2 b -> Sphinx.forwardErrorPacket(b, ss) | Choice2Of2 f -> Sphinx.ErrorPacket.Create(ss, f) - let f = { - UpdateFailHTLCMsg.ChannelId = savedChannelState.StaticChannelConfig.ChannelId() - HTLCId = op.Id - Reason = { Data = reason } - } - let nextCommitments = cm.AddLocalProposal(f) - Ok (f, nextCommitments) - | None -> - op.Id |> unknownHTLCId - - let receiveFail (msg: UpdateFailHTLCMsg) - (cm: Commitments) - (savedChannelState: SavedChannelState) - (remoteNextCommitInfo: RemoteNextCommitInfo) = - match savedChannelState.GetOutgoingHTLCCrossSigned remoteNextCommitInfo msg.HTLCId with + >>= fun ({ + SharedSecret = ss + }) -> + let reason = + op.Reason + |> function + | Choice1Of2 b -> Sphinx.forwardErrorPacket(b, ss) + | Choice2Of2 f -> Sphinx.ErrorPacket.Create(ss, f) + + let f = + { + UpdateFailHTLCMsg.ChannelId = + savedChannelState.StaticChannelConfig.ChannelId + () + HTLCId = op.Id + Reason = + { + Data = reason + } + } + + let nextCommitments = cm.AddLocalProposal(f) + Ok(f, nextCommitments) + | None -> op.Id |> unknownHTLCId + + let receiveFail + (msg: UpdateFailHTLCMsg) + (cm: Commitments) + (savedChannelState: SavedChannelState) + (remoteNextCommitInfo: RemoteNextCommitInfo) + = + match + savedChannelState.GetOutgoingHTLCCrossSigned + remoteNextCommitInfo + msg.HTLCId + with | Some _htlc -> result { let! _origin = match cm.OriginChannels.TryGetValue(msg.HTLCId) with | true, origin -> Ok origin - | false, _ -> - msg.HTLCId |> htlcOriginNotKnown + | false, _ -> msg.HTLCId |> htlcOriginNotKnown + let nextC = cm.AddRemoteProposal(msg) return nextC } - | None -> - msg.HTLCId |> unknownHTLCId + | None -> msg.HTLCId |> unknownHTLCId - let sendFailMalformed (op: OperationFailMalformedHTLC) - (cm: Commitments) - (savedChannelState: SavedChannelState) - (remoteNextCommitInfo: RemoteNextCommitInfo) = + let sendFailMalformed + (op: OperationFailMalformedHTLC) + (cm: Commitments) + (savedChannelState: SavedChannelState) + (remoteNextCommitInfo: RemoteNextCommitInfo) + = // BADONION bit must be set in failure code if (op.FailureCode.Value &&& OnionError.BADONION) = 0us then op.FailureCode |> invalidFailureCode else - match savedChannelState.GetIncomingHTLCCrossSigned remoteNextCommitInfo op.Id with - | Some htlc when (cm.ProposedLocalChanges |> Helpers.isAlreadySent htlc) -> + match + savedChannelState.GetIncomingHTLCCrossSigned + remoteNextCommitInfo + op.Id + with + | Some htlc when + (cm.ProposedLocalChanges |> Helpers.isAlreadySent htlc) + -> htlc.HTLCId |> htlcAlreadySent | Some _htlc -> - let msg = { - UpdateFailMalformedHTLCMsg.ChannelId = savedChannelState.StaticChannelConfig.ChannelId() - HTLCId = op.Id - Sha256OfOnion = op.Sha256OfOnion - FailureCode = op.FailureCode - } + let msg = + { + UpdateFailMalformedHTLCMsg.ChannelId = + savedChannelState.StaticChannelConfig.ChannelId() + HTLCId = op.Id + Sha256OfOnion = op.Sha256OfOnion + FailureCode = op.FailureCode + } + let nextCommitments = cm.AddLocalProposal(msg) - Ok (msg, nextCommitments) - | None -> - op.Id |> unknownHTLCId - - let receiveFailMalformed (msg: UpdateFailMalformedHTLCMsg) - (cm: Commitments) - (savedChannelState: SavedChannelState) - (remoteNextCommitInfo: RemoteNextCommitInfo) = + Ok(msg, nextCommitments) + | None -> op.Id |> unknownHTLCId + + let receiveFailMalformed + (msg: UpdateFailMalformedHTLCMsg) + (cm: Commitments) + (savedChannelState: SavedChannelState) + (remoteNextCommitInfo: RemoteNextCommitInfo) + = if msg.FailureCode.Value &&& OnionError.BADONION = 0us then msg.FailureCode |> invalidFailureCode else - match savedChannelState.GetOutgoingHTLCCrossSigned remoteNextCommitInfo msg.HTLCId with + match + savedChannelState.GetOutgoingHTLCCrossSigned + remoteNextCommitInfo + msg.HTLCId + with | Some _htlc -> result { let! _origin = match cm.OriginChannels.TryGetValue(msg.HTLCId) with | true, o -> Ok o - | false, _ -> - msg.HTLCId |> htlcOriginNotKnown + | false, _ -> msg.HTLCId |> htlcOriginNotKnown + let nextC = cm.AddRemoteProposal(msg) return nextC } - | None -> - msg.HTLCId |> unknownHTLCId - - let sendFee (op: OperationUpdateFee) - (savedChannelState: SavedChannelState) - (cm: Commitments) = - if (not savedChannelState.StaticChannelConfig.IsFunder) then - "Local is Fundee so it cannot send update fee" |> apiMisuse - else - let fee = { - UpdateFeeMsg.ChannelId = savedChannelState.StaticChannelConfig.ChannelId() + | None -> msg.HTLCId |> unknownHTLCId + + let sendFee + (op: OperationUpdateFee) + (savedChannelState: SavedChannelState) + (cm: Commitments) + = + if (not savedChannelState.StaticChannelConfig.IsFunder) then + "Local is Fundee so it cannot send update fee" |> apiMisuse + else + let fee = + { + UpdateFeeMsg.ChannelId = + savedChannelState.StaticChannelConfig.ChannelId() FeeRatePerKw = op.FeeRatePerKw } - let c1 = cm.AddLocalProposal(fee) - result { - let! reduced = - savedChannelState.RemoteCommit.Spec.Reduce(savedChannelState.RemoteChanges.ACKed, c1.ProposedLocalChanges) |> expectTransactionError - // A node cannot spend pending incoming htlcs, and need to keep funds above the reserve required by - // the counter party, after paying the fee, we look from remote's point of view, so if local is funder - // remote doesn't pay the fees. - let fees = Transactions.commitTxFee(savedChannelState.StaticChannelConfig.RemoteParams.DustLimitSatoshis) reduced - let missing = reduced.ToRemote.ToMoney() - savedChannelState.StaticChannelConfig.RemoteParams.ChannelReserveSatoshis - fees - if (missing < Money.Zero) then - return! - (savedChannelState.StaticChannelConfig.LocalParams.ChannelReserveSatoshis, fees, (-1 * missing)) - |> cannotAffordFee - else - return fee, c1 - } - let receiveFee (channelOptions: ChannelOptions) - (localFeerate) - (msg: UpdateFeeMsg) - (savedChannelState: SavedChannelState) - (cm: Commitments) = + let c1 = cm.AddLocalProposal(fee) + + result { + let! reduced = + savedChannelState.RemoteCommit.Spec.Reduce( + savedChannelState.RemoteChanges.ACKed, + c1.ProposedLocalChanges + ) + |> expectTransactionError + // A node cannot spend pending incoming htlcs, and need to keep funds above the reserve required by + // the counter party, after paying the fee, we look from remote's point of view, so if local is funder + // remote doesn't pay the fees. + let fees = + Transactions.commitTxFee + (savedChannelState.StaticChannelConfig.RemoteParams.DustLimitSatoshis) + reduced + + let missing = + reduced.ToRemote.ToMoney() + - savedChannelState.StaticChannelConfig.RemoteParams.ChannelReserveSatoshis + - fees + + if (missing < Money.Zero) then + return! + (savedChannelState.StaticChannelConfig.LocalParams.ChannelReserveSatoshis, + fees, + (-1 * missing)) + |> cannotAffordFee + else + return fee, c1 + } + + let receiveFee + (channelOptions: ChannelOptions) + localFeerate + (msg: UpdateFeeMsg) + (savedChannelState: SavedChannelState) + (cm: Commitments) + = if savedChannelState.StaticChannelConfig.IsFunder then "Remote is Fundee so it cannot send update fee" |> apiMisuse else result { do! Helpers.checkUpdateFee channelOptions msg localFeerate let nextCommitments = cm.AddRemoteProposal(msg) + let! reduced = savedChannelState.LocalCommit.Spec.Reduce( savedChannelState.LocalChanges.ACKed, nextCommitments.ProposedRemoteChanges - ) |> expectTransactionError - - let fees = Transactions.commitTxFee(savedChannelState.StaticChannelConfig.RemoteParams.DustLimitSatoshis) reduced - let missing = reduced.ToRemote.ToMoney() - savedChannelState.StaticChannelConfig.RemoteParams.ChannelReserveSatoshis - fees + ) + |> expectTransactionError + + let fees = + Transactions.commitTxFee + (savedChannelState.StaticChannelConfig.RemoteParams.DustLimitSatoshis) + reduced + + let missing = + reduced.ToRemote.ToMoney() + - savedChannelState.StaticChannelConfig.RemoteParams.ChannelReserveSatoshis + - fees + if (missing < Money.Zero) then return! - (savedChannelState.StaticChannelConfig.LocalParams.ChannelReserveSatoshis, fees, (-1 * missing)) + (savedChannelState.StaticChannelConfig.LocalParams.ChannelReserveSatoshis, + fees, + (-1 * missing)) |> cannotAffordFee else return nextCommitments } - let checkSignatureCountMismatch(sortedHTLCTXs: IHTLCTx list) (msg) = + let checkSignatureCountMismatch (sortedHTLCTXs: list) msg = if (sortedHTLCTXs.Length <> msg.HTLCSignatures.Length) then - signatureCountMismatch (sortedHTLCTXs.Length, msg.HTLCSignatures.Length) + signatureCountMismatch( + sortedHTLCTXs.Length, + msg.HTLCSignatures.Length + ) else Ok() - diff --git a/src/DotNetLightning.Core/Channel/HTLCChannelType.fs b/src/DotNetLightning.Core/Channel/HTLCChannelType.fs index 1a75b3598..c3b7f732e 100644 --- a/src/DotNetLightning.Core/Channel/HTLCChannelType.fs +++ b/src/DotNetLightning.Core/Channel/HTLCChannelType.fs @@ -1,44 +1,47 @@ namespace DotNetLightning.Channel + open NBitcoin open DotNetLightning.Utils open DotNetLightning.Serialization.Msgs open DotNetLightning.Utils.OnionError -type PendingForwardHTLCInfo = { - OnionPacket: OnionPacket option - IncomingSharedSecret: uint8 - PaymentHash: uint256 - ShortChannelId: ShortChannelId - AmountToForward: LNMoney - OutgoingCLTVValue: uint32 -} +type PendingForwardHTLCInfo = + { + OnionPacket: option + IncomingSharedSecret: uint8 + PaymentHash: uint256 + ShortChannelId: ShortChannelId + AmountToForward: LNMoney + OutgoingCLTVValue: uint32 + } type HTLCFailureMsg = | Relay of UpdateFailHTLCMsg | Malformed of UpdateFailMalformedHTLCMsg -type PendingHTLCStatus = - | Forward of PendingForwardHTLCInfo +type PendingHTLCStatus = Forward of PendingForwardHTLCInfo type HTLCPreviousHopData = | ShortChannelId of ShortChannelId | HTLCId of HTLCId | IncomingPacketSharedSecret of PaymentPreimage -type OutboundRoute = { - Route: Route - SessionPriv: Key - FirstHopHTLC: LNMoney -} +type OutboundRoute = + { + Route: Route + SessionPriv: Key + FirstHopHTLC: LNMoney + } type HTLCSource = | PreviousHopData of HTLCPreviousHopData | OutboundRoute of OutboundRoute -type HTLCFailureData = { - Data: byte[] - FailureCode: FailureCode -} +type HTLCFailureData = + { + Data: array + FailureCode: FailureCode + } type HTLCFailReason = | ErrorPacket of OnionErrorPacket diff --git a/src/DotNetLightning.Core/Crypto/Aezeed.fs b/src/DotNetLightning.Core/Crypto/Aezeed.fs index 5c1aa74cf..f299eef8f 100644 --- a/src/DotNetLightning.Core/Crypto/Aezeed.fs +++ b/src/DotNetLightning.Core/Crypto/Aezeed.fs @@ -26,168 +26,254 @@ module internal AEZConstants = /// for the Bitcoin Days Genesis timestamp, and 16 bytes for entropy. It also governs how the cipher seed [] let CIPHER_SEED_VERSION = 0uy - + [] let DECIPHERED_CIPHER_SEED_SIZE = 19 - + [] let ENCIPHERED_CIPHER_SEED_SIZE = 33 - + [] let CIPHER_TEXT_EXPANSION = 4 - + [] let ENTROPY_SIZE = 16 - + [] let NUM_MNEMONIC_WORDS = 24 [] let SALT_SIZE = 5 - + [] let AD_SIZE = 6 - + [] let CHECKSUM_SIZE = 4 - + [] let KEY_LEN = 32 - + [] let BITS_PER_WORD = 11 - + let SALT_OFFSET = ENCIPHERED_CIPHER_SEED_SIZE - CHECKSUM_SIZE - SALT_SIZE - + let CHECKSUM_OFFSET = ENCIPHERED_CIPHER_SEED_SIZE - CHECKSUM_SIZE - - let ENCIPHERED_SEED_SIZE = DECIPHERED_CIPHER_SEED_SIZE + CIPHER_TEXT_EXPANSION - + + let ENCIPHERED_SEED_SIZE = + DECIPHERED_CIPHER_SEED_SIZE + CIPHER_TEXT_EXPANSION + /// We have to change this value in the test, thus we make it mutable. let mutable V0_SCRYPT_N = 32768 - + [] let V0_SCRYPT_R = 8 - + [] let V0_SCRYPT_P = 1 - - let DEFAULT_PASPHRASE = - UTF8Encoding.UTF8.GetBytes("aezeed") - - let BITCOIN_GENESIS_DATE = NBitcoin.Network.Main.GetGenesis().Header.BlockTime - + + let DEFAULT_PASPHRASE = UTF8Encoding.UTF8.GetBytes("aezeed") + + let BITCOIN_GENESIS_DATE = + NBitcoin + .Network + .Main + .GetGenesis() + .Header + .BlockTime + let convertBits(data, fromBits, toBits, pad: bool) = InternalBech32Encoder.Instance.ConvertBits(data, fromBits, toBits, pad) - - let getScryptKey(pass: byte[], salt: byte[]) = - NBitcoin.Crypto.SCrypt.ComputeDerivedKey(pass, salt, V0_SCRYPT_N, V0_SCRYPT_R, V0_SCRYPT_P, Nullable(), KEY_LEN) + + let getScryptKey(pass: array, salt: array) = + NBitcoin.Crypto.SCrypt.ComputeDerivedKey( + pass, + salt, + V0_SCRYPT_N, + V0_SCRYPT_R, + V0_SCRYPT_P, + Nullable(), + KEY_LEN + ) + type AezeedError = | UnsupportedVersion of uint8 | InvalidPass of CryptographicException /// Returns when the checksum does not match. | IncorrectMnemonic of expectedCheckSum: uint32 * actualChecksum: uint32 - + [] -module internal AezeedHelpers = - let private extractAD(encipheredSeed: byte[]) = +module internal AezeedHelpers = + let private extractAD(encipheredSeed: array) = let ad = Array.zeroCreate AD_SIZE ad.[0] <- encipheredSeed.[0] - let a = encipheredSeed.[SALT_OFFSET..CHECKSUM_OFFSET - 1] + let a = encipheredSeed.[SALT_OFFSET .. CHECKSUM_OFFSET - 1] Array.blit a 0 ad 1 (AD_SIZE - 1) ad - - let decipherCipherSeed(cipherSeedBytes: byte[], password: byte[]) = + + let decipherCipherSeed + ( + cipherSeedBytes: array, + password: array + ) = Debug.Assert(cipherSeedBytes.Length = ENCIPHERED_CIPHER_SEED_SIZE) - if cipherSeedBytes.[0] <> CIPHER_SEED_VERSION then Error(AezeedError.UnsupportedVersion cipherSeedBytes.[0]) else - let salt = cipherSeedBytes.[SALT_OFFSET..SALT_OFFSET + SALT_SIZE - 1] - let cipherSeed = cipherSeedBytes.[1..SALT_OFFSET - 1] - let checkSum = cipherSeedBytes.[CHECKSUM_OFFSET..] |> UInt32.FromBytesBigEndian - let freshChecksum = - AEZ.Crc32.Crc32CAlgorithm.Compute(cipherSeedBytes.[..CHECKSUM_OFFSET - 1]) - if (freshChecksum <> checkSum) then Error(AezeedError.IncorrectMnemonic(freshChecksum, checkSum)) else - let key = getScryptKey(password, salt) - let ad = extractAD(cipherSeedBytes) - try - let r = (AEZ.AEZ.Decrypt(ReadOnlySpan(key), ReadOnlySpan.Empty ,[|ad|], CIPHER_TEXT_EXPANSION, ReadOnlySpan(cipherSeed), Span.Empty)) - Ok(r.ToArray()) - with - | :? CryptographicException as e -> - Error(AezeedError.InvalidPass e) - - let mnemonicToCipherText(mnemonic: string[], lang: Wordlist option) = + + if cipherSeedBytes.[0] <> CIPHER_SEED_VERSION then + Error(AezeedError.UnsupportedVersion cipherSeedBytes.[0]) + else + let salt = + cipherSeedBytes.[SALT_OFFSET .. SALT_OFFSET + SALT_SIZE - 1] + + let cipherSeed = cipherSeedBytes.[1 .. SALT_OFFSET - 1] + + let checkSum = + cipherSeedBytes.[CHECKSUM_OFFSET..] |> UInt32.FromBytesBigEndian + + let freshChecksum = + AEZ.Crc32.Crc32CAlgorithm.Compute( + cipherSeedBytes.[.. CHECKSUM_OFFSET - 1] + ) + + if (freshChecksum <> checkSum) then + Error(AezeedError.IncorrectMnemonic(freshChecksum, checkSum)) + else + let key = getScryptKey(password, salt) + let ad = extractAD(cipherSeedBytes) + + try + let r = + (AEZ.AEZ.Decrypt( + ReadOnlySpan(key), + ReadOnlySpan.Empty, + [| ad |], + CIPHER_TEXT_EXPANSION, + ReadOnlySpan(cipherSeed), + Span.Empty + )) + + Ok(r.ToArray()) + with + | :? CryptographicException as e -> + Error(AezeedError.InvalidPass e) + + let mnemonicToCipherText(mnemonic: array, lang: option) = let lang = Option.defaultValue NBitcoin.Wordlist.English lang let indices = lang.ToIndices mnemonic let cipherBits = BitWriter() + for i in indices do cipherBits.Write(uint32 i, BITS_PER_WORD) + cipherBits.ToBytes() - - let internal cipherTextToMnemonic(cipherText: byte[], lang: Wordlist option) = + + let internal cipherTextToMnemonic + ( + cipherText: array, + lang: option + ) = Debug.Assert(cipherText.Length = ENCIPHERED_CIPHER_SEED_SIZE) let lang = Option.defaultValue NBitcoin.Wordlist.English lang let writer = BitWriter() writer.Write(cipherText) let wordInt = writer.ToIntegers() wordInt |> lang.GetWords - + /// CipherSeed is a fully decoded instance of the aezeed scheme. At a high level, the encoded cipherseed is the /// enciphering off /// 1. 1 byte version byte /// 2. 2 bytes timestamp -/// 3. -[] -type CipherSeed = { - InternalVersion: uint8 - Birthday: uint16 - Entropy: byte[] - Salt: byte[] -} - with +/// 3. +[] +type CipherSeed = + { + InternalVersion: uint8 + Birthday: uint16 + Entropy: array + Salt: array + } + override this.GetHashCode() = - int (AEZ.Crc32.Crc32CAlgorithm.Compute(this.ToBytes())) + int(AEZ.Crc32.Crc32CAlgorithm.Compute(this.ToBytes())) + override this.Equals(o: obj) = match o with - | :? CipherSeed as other -> (this :> IEquatable).Equals(other) + | :? CipherSeed as other -> + (this :> IEquatable).Equals(other) | _ -> false + interface IEquatable with member this.Equals(o: CipherSeed) = - this.InternalVersion = o.InternalVersion && - this.Birthday = o.Birthday && - this.Entropy.ToHexString() = o.Entropy.ToHexString() + this.InternalVersion = o.InternalVersion + && this.Birthday = o.Birthday + && this.Entropy.ToHexString() = o.Entropy.ToHexString() + static member Create() = CipherSeed.Create(CIPHER_SEED_VERSION) + static member Create(internalVersion: uint8) = CipherSeed.Create(internalVersion, DateTimeOffset.Now) + static member Create(internalVersion: uint8, now: DateTimeOffset) = CipherSeed.Create(internalVersion, None, now) - static member Create(internalVersion: uint8, entropy: byte[], now: DateTimeOffset) = + + static member Create + ( + internalVersion: uint8, + entropy: array, + now: DateTimeOffset + ) = CipherSeed.Create(internalVersion, Some entropy, now) - static member Create(internalVersion: uint8, entropy: byte[] option, now: DateTimeOffset) = - let entropy = Option.defaultWith (fun _ -> RandomUtils.GetBytes(ENTROPY_SIZE)) entropy - if entropy.Length < ENTROPY_SIZE then raise <| ArgumentException(sprintf "entropy size must be at least %d! it was %d" ENTROPY_SIZE entropy.Length) else - if (now < BITCOIN_GENESIS_DATE) then raise <| ArgumentOutOfRangeException(sprintf "You shall not create CipherSeed older than BitcoinGenesis!") else - let seed = Array.zeroCreate ENTROPY_SIZE - Array.blit entropy 0 seed 0 ENTROPY_SIZE - - let birthDate = uint16((now - BITCOIN_GENESIS_DATE).Days) - { - CipherSeed.InternalVersion = internalVersion - Birthday = birthDate - Entropy = seed - Salt = RandomUtils.GetBytes SALT_SIZE - } - + + static member Create + ( + internalVersion: uint8, + entropy: option>, + now: DateTimeOffset + ) = + let entropy = + Option.defaultWith + (fun _ -> RandomUtils.GetBytes(ENTROPY_SIZE)) + entropy + + if entropy.Length < ENTROPY_SIZE then + raise + <| ArgumentException( + sprintf + "entropy size must be at least %i! it was %i" + ENTROPY_SIZE + entropy.Length + ) + else if (now < BITCOIN_GENESIS_DATE) then + raise + <| ArgumentOutOfRangeException( + sprintf + "You shall not create CipherSeed older than BitcoinGenesis!" + ) + else + let seed = Array.zeroCreate ENTROPY_SIZE + Array.blit entropy 0 seed 0 ENTROPY_SIZE + + let birthDate = uint16((now - BITCOIN_GENESIS_DATE).Days) + + { + CipherSeed.InternalVersion = internalVersion + Birthday = birthDate + Entropy = seed + Salt = RandomUtils.GetBytes SALT_SIZE + } + member private this.Serialize(ls: LightningWriterStream) = ls.Write(this.InternalVersion) ls.Write(this.Birthday, false) ls.Write(this.Entropy) + member internal this.ToBytes() = use ms = new MemoryStream() use ls = new LightningWriterStream(ms) this.Serialize ls ms.ToArray() - + static member private Deserialize(ls: LightningReaderStream) = { InternalVersion = ls.ReadByte() @@ -195,118 +281,203 @@ type CipherSeed = { Entropy = ls.ReadBytes(ENTROPY_SIZE) Salt = Array.zeroCreate SALT_SIZE } - + member private this.GetADBytes() = - let res = Array.zeroCreate (SALT_SIZE + 1) + let res = Array.zeroCreate(SALT_SIZE + 1) res.[0] <- byte CIPHER_SEED_VERSION Array.blit this.Salt 0 res 1 this.Salt.Length res - - static member internal FromBytes(b: byte[]) = + + static member internal FromBytes(b: array) = use ms = new MemoryStream(b) use ls = new LightningReaderStream(ms) CipherSeed.Deserialize ls - member this.Encipher() = this.Encipher(None) - - member this.Encipher([]password: byte[]): byte[] = - let pass = if isNull password then None else Some(password) + member this.Encipher() = + this.Encipher(None) + + member this.Encipher([] password: array) : array = + let pass = + if isNull password then + None + else + Some(password) + this.Encipher(pass) + /// Takes a fully populated cipherseed instance, and enciphers the /// encoded seed, then appends a randomly generated seed used to stretch th /// passphrase out into an appropriate key, then computes a checksum over the /// preceding. Returns 33 bytes enciphered cipherseed - member this.Encipher(password: byte[] option): byte[] = + member this.Encipher(password: option>) : array = let passphrase = match password with | Some p when p.Length = 0 -> DEFAULT_PASPHRASE | Some p -> p | None -> DEFAULT_PASPHRASE + let key = getScryptKey(passphrase, this.Salt) - + let seedBytes = this.ToBytes() let ad = this.GetADBytes() - let cipherText = (AEZ.AEZ.Encrypt(ReadOnlySpan(key), ReadOnlySpan.Empty, [| ad |], CIPHER_TEXT_EXPANSION, ReadOnlySpan(seedBytes), Span.Empty)).ToArray() - + + let cipherText = + (AEZ.AEZ.Encrypt( + ReadOnlySpan(key), + ReadOnlySpan.Empty, + [| ad |], + CIPHER_TEXT_EXPANSION, + ReadOnlySpan(seedBytes), + Span.Empty + )) + .ToArray() + let result = Array.zeroCreate ENCIPHERED_CIPHER_SEED_SIZE result.[0] <- byte CIPHER_SEED_VERSION Array.blit cipherText 0 result 1 (SALT_OFFSET - 1) Array.blit this.Salt 0 result SALT_OFFSET SALT_SIZE - + let checkSum = - AEZ.Crc32.Crc32CAlgorithm.Compute(result.[0..CHECKSUM_OFFSET - 1]) + AEZ.Crc32.Crc32CAlgorithm.Compute(result.[0 .. CHECKSUM_OFFSET - 1]) |> fun ch -> ch.GetBytesBigEndian() + Array.blit checkSum 0 result CHECKSUM_OFFSET CHECKSUM_SIZE result - - member this.ToMnemonicWords(password: byte[] option, lang: Wordlist option) = + + member this.ToMnemonicWords + ( + password: option>, + lang: option + ) = let cipherText = this.Encipher(password) cipherTextToMnemonic(cipherText, lang) - + member this.ToMnemonicWords([] password, [] lang) = - let pass = if isNull password then None else Some(password) - let lang = if isNull lang then None else Some(lang) + let pass = + if isNull password then + None + else + Some(password) + + let lang = + if isNull lang then + None + else + Some(lang) + this.ToMnemonicWords(pass, lang) - - member this.ToMnemonic(password: byte[] option, lang: Wordlist option) = - this.ToMnemonicWords(password, lang) |> Seq.fold(fun x acc -> x + " " + acc) "" |> Mnemonic - - member this.ToMnemonic([]password, []lang) = - let pass = if isNull password then None else Some(password) - let lang = if isNull lang then None else Some(lang) + + member this.ToMnemonic + ( + password: option>, + lang: option + ) = + this.ToMnemonicWords(password, lang) + |> Seq.fold (fun x acc -> x + " " + acc) "" + |> Mnemonic + + member this.ToMnemonic([] password, [] lang) = + let pass = + if isNull password then + None + else + Some(password) + + let lang = + if isNull lang then + None + else + Some(lang) + this.ToMnemonic(pass, lang) - + member this.GetBirthdayTime() = let offset = TimeSpan.FromDays(float this.Birthday) BITCOIN_GENESIS_DATE + offset - -[] + +[] type MnemonicExtensions = /// Attempts to map the mnemonic to the original cipher text byte slice. /// Then It will attempt to decrypt the ciphertext using aez with the passed passphrase, /// using the last 5 bytes of the ciphertext as a salt for the KDF. - static member ToCipherSeed(this: Mnemonic, []password: string) = + static member ToCipherSeed + ( + this: Mnemonic, + [] password: string + ) = password - |> fun x -> if isNull x then [||] else Encoding.UTF8.GetBytes(password) + |> fun x -> + if isNull x then + [||] + else + Encoding.UTF8.GetBytes(password) |> fun b -> this.ToCipherSeed(b, null) - + /// Attempts to map the mnemonic to the original cipher text byte slice. /// Then It will attempt to decrypt the ciphertext using aez with the passed passphrase, /// using the last 5 bytes of the ciphertext as a salt for the KDF. - static member ToCipherSeed(this: Mnemonic, []password: byte[]) = + static member ToCipherSeed + ( + this: Mnemonic, + [] password: array + ) = this.ToCipherSeed(password, null) + [] - /// Attempts to map the mnemonic to the original cipher text byte slice. - /// Then It will attempt to decrypt the ciphertext using aez with the passed passphrase, - /// using the last 5 bytes of the ciphertext as a salt for the KDF. - static member ToCipherSeed(this: Mnemonic, []password: byte[], []lang: Wordlist) = - let pass = if isNull password then None else Some(password) - let lang = if isNull lang then None else Some(lang) + static member ToCipherSeed + ( + this: Mnemonic, + [] password: array, + [] lang: Wordlist + ) = + let pass = + if isNull password then + None + else + Some(password) + + let lang = + if isNull lang then + None + else + Some(lang) + MnemonicExtensions.ToCipherSeed(this, pass, lang) - + [] - /// Attempts to map the mnemonic to the original cipher text byte slice. - /// Then It will attempt to decrypt the ciphertext using aez with the passed passphrase, - /// using the last 5 bytes of the ciphertext as a salt for the KDF. - static member ToCipherSeed(this: Mnemonic, password: byte[] option, lang: Wordlist option) = - this.Decipher(password, lang) - |> Result.map CipherSeed.FromBytes - + static member ToCipherSeed + ( + this: Mnemonic, + password: option>, + lang: option + ) = + this.Decipher(password, lang) |> Result.map CipherSeed.FromBytes + [] - - static member internal Decipher(this: Mnemonic, password: byte[] option, lang: Wordlist option) = + + static member internal Decipher + ( + this: Mnemonic, + password: option>, + lang: option + ) = let pass = match password with | Some p when p.Length = 0 -> DEFAULT_PASPHRASE | Some p -> p | None -> DEFAULT_PASPHRASE - - let cipherText = mnemonicToCipherText (this.Words, lang) + + let cipherText = mnemonicToCipherText(this.Words, lang) decipherCipherSeed(cipherText, pass) - + [] - static member ChangePass(this: Mnemonic, oldPass: byte[], newPass: byte[], []lang: Wordlist) = + static member ChangePass + ( + this: Mnemonic, + oldPass: array, + newPass: array, + [] lang: Wordlist + ) = this.ToCipherSeed(oldPass, lang) |> Result.map(fun x -> x.ToMnemonic newPass) - diff --git a/src/DotNetLightning.Core/Crypto/CryptoUtils.fs b/src/DotNetLightning.Core/Crypto/CryptoUtils.fs index bf91b01ff..eeeb71803 100644 --- a/src/DotNetLightning.Core/Crypto/CryptoUtils.fs +++ b/src/DotNetLightning.Core/Crypto/CryptoUtils.fs @@ -18,21 +18,25 @@ open ResultUtils.Portability type CryptoError = | BadMac | InvalidErrorPacketLength of expected: int * actual: int - | InvalidPublicKey of byte[] + | InvalidPublicKey of array | InvalidMessageLength of int - | FailedToParseErrorPacket of packet: byte[] * sharedSecrets: (byte[] * PubKey) list - + | FailedToParseErrorPacket of + packet: array * + sharedSecrets: list<(array * PubKey)> + member this.Message = match this with | BadMac -> "Bad MAC" - | InvalidErrorPacketLength (expected, actual) -> - sprintf "Invalid error packet length. Expected %i, got %i" expected actual + | InvalidErrorPacketLength(expected, actual) -> + sprintf + "Invalid error packet length. Expected %i, got %i" + expected + actual | InvalidPublicKey publicKeyBytes -> sprintf "Invalid public key: %s" (publicKeyBytes.ToHexString()) | InvalidMessageLength length -> sprintf "Invalid message length (%i)" length - | FailedToParseErrorPacket _ -> - "failed to parse error packet." + | FailedToParseErrorPacket _ -> "failed to parse error packet." module Secret = let FromKeyPair(pub: PubKey, priv: Key) = @@ -41,26 +45,56 @@ module Secret = type ISecp256k1 = inherit IDisposable + /// Create uncompressed public key(64 bytes) from private key (32 bytes) - abstract member PublicKeyCreate: privateKeyInput: ReadOnlySpan -> bool * byte[] + abstract member PublicKeyCreate: + privateKeyInput: ReadOnlySpan -> bool * array + /// Convert secp256k1 compatible style pubkey (64 bytes) into compressed form (33 bytes) - abstract member PublicKeySerializeCompressed: publicKey: ReadOnlySpan -> bool * byte[] + abstract member PublicKeySerializeCompressed: + publicKey: ReadOnlySpan -> bool * array + /// Convert compressed pubkey (33 bytes) to secp256k1 compatible style (64 bytes). - abstract member PublicKeyParse: serializedPublicKey: ReadOnlySpan -> bool * publicKeyOutput: byte[] + abstract member PublicKeyParse: + serializedPublicKey: ReadOnlySpan -> bool * array + /// Combine 2 public keys. Input can be either 33 bytes or 64 bytes. So normalization by PublicKeyParse is required. - abstract member PublicKeyCombine: inputPubKey1: Span * inputPubKey2: Span -> bool * pubkeyOutput: byte[] + abstract member PublicKeyCombine: + inputPubKey1: Span * inputPubKey2: Span -> + bool * array + /// Add a tweak (32 bytes) to a private key. Causes a mutation to `privateKeyToMutate`. - abstract member PrivateKeyTweakAdd: tweak: ReadOnlySpan * privateKeyToMutate: Span -> bool + abstract member PrivateKeyTweakAdd: + tweak: ReadOnlySpan * privateKeyToMutate: Span -> bool + /// Assume tweak (32 bytes) as a scalar and add `G` (basepoint) for tweak times. Causes a mutation to `privateKeyToMutate` - abstract member PrivateKeyTweakMultiply: tweak: ReadOnlySpan * privKeyToMutate: Span -> bool + abstract member PrivateKeyTweakMultiply: + tweak: ReadOnlySpan * privKeyToMutate: Span -> bool + /// Add a public key (64 bytes) to itself for tweak times. Causes a mutation to `pubKeyToMutate` - abstract member PublicKeyTweakMultiply: tweak: ReadOnlySpan * pubKeyToMutate: Span -> bool + abstract member PublicKeyTweakMultiply: + tweak: ReadOnlySpan * pubKeyToMutate: Span -> bool type ICryptoImpl = - abstract member decryptWithAD: nonce: uint64 * key: uint256 * ad: byte[] * cipherText: ReadOnlySpan -> Result - abstract member encryptWithAD: nonce: uint64 * key: uint256 * ad: ReadOnlySpan * plainText: ReadOnlySpan -> byte[] + abstract member decryptWithAD: + nonce: uint64 * + key: uint256 * + ad: array * + cipherText: ReadOnlySpan -> + Result, CryptoError> + + abstract member encryptWithAD: + nonce: uint64 * + key: uint256 * + ad: ReadOnlySpan * + plainText: ReadOnlySpan -> + array + /// This is used for filler generation in onion routing (BOLT 4) - abstract member encryptWithoutAD: nonce: uint64 * key: byte[] * plainText: ReadOnlySpan -> byte[] + abstract member encryptWithoutAD: + nonce: uint64 * key: array * plainText: ReadOnlySpan -> + array + abstract member newSecp256k1: unit -> ISecp256k1 #if !BouncyCastle @@ -68,84 +102,129 @@ module Sodium = type NBitcoinSecp256k1() = let context = NBitcoin.Secp256k1.Context() - + member this.PublicKeyParse serializedPublicKey = - match context.TryCreatePubKey(serializedPublicKey, compressed = ref false) with + match + context.TryCreatePubKey + ( + serializedPublicKey, + compressed = ref false + ) + with | true, pk -> let pk64 = Array.zeroCreate 65 - pk.WriteToSpan(compressed = false, output = pk64.AsSpan()) |> ignore + + pk.WriteToSpan(compressed = false, output = pk64.AsSpan()) + |> ignore + (true, pk64.[1..]) - | false, _ -> - false, [||] - + | false, _ -> false, [||] + /// Normalize any representation of PublicKey into secp256k1 internal representation. (64 bytes) - member private this.NormalizePubKey (pk: ReadOnlySpan) = + member private this.NormalizePubKey(pk: ReadOnlySpan) = if pk.Length = 64 then pk else if pk.Length = 65 then pk.Slice(1) else if pk.Length = 33 then - match this.PublicKeyParse (pk) with - | false, _ -> - let hex = pk.ToArray().ToHexString() - raise <| ArgumentException(sprintf "Failed to parse public key %s" hex) - ReadOnlySpan.Empty // to avoid weird compiler error - | true, pk64 -> ReadOnlySpan(pk64) + match this.PublicKeyParse(pk) with + | false, _ -> + let hex = pk.ToArray().ToHexString() + + raise + <| ArgumentException( + sprintf "Failed to parse public key %s" hex + ) + + ReadOnlySpan.Empty // to avoid weird compiler error + | true, pk64 -> ReadOnlySpan(pk64) else - raise <| ArgumentException(sprintf "invalid public key with length %d" pk.Length) + raise + <| ArgumentException( + sprintf "invalid public key with length %i" pk.Length + ) + ReadOnlySpan.Empty // to avoid weird compiler error - + member private this.NormalizePubKey(pk: ECPubKey) = - this.NormalizePubKey (ReadOnlySpan(pk.ToBytes())) - + this.NormalizePubKey(ReadOnlySpan(pk.ToBytes())) + interface ISecp256k1 with member this.PublicKeyCreate privKey = match context.TryCreateECPrivKey(privKey) with | true, priv -> - true, this.NormalizePubKey(priv.CreatePubKey()).ToArray() + true, + this + .NormalizePubKey(priv.CreatePubKey()) + .ToArray() | false, _ -> false, [||] + member this.PublicKeySerializeCompressed pubKey = let pk = this.NormalizePubKey pubKey - match NBitcoin.Secp256k1.ECPubKey.TryCreateRawFormat(pk, context) with - | true, pk -> - (true, pk.ToBytes(true)) - | false, _ -> - false, [||] + + match + NBitcoin.Secp256k1.ECPubKey.TryCreateRawFormat + ( + pk, + context + ) + with + | true, pk -> (true, pk.ToBytes(true)) + | false, _ -> false, [||] + member this.PublicKeyParse serializedPublicKey = this.PublicKeyParse serializedPublicKey - + member this.PublicKeyCombine(pk1, pk2) = - let pk1 = this.NormalizePubKey(Span.op_Implicit(pk1)) - let pk2 = this.NormalizePubKey(Span.op_Implicit(pk2)) - match ECPubKey.TryCreateRawFormat(pk1, context), ECPubKey.TryCreateRawFormat(pk2, context) with + let pk1 = this.NormalizePubKey(Span.op_Implicit (pk1)) + let pk2 = this.NormalizePubKey(Span.op_Implicit (pk2)) + + match ECPubKey.TryCreateRawFormat(pk1, context), + ECPubKey.TryCreateRawFormat(pk2, context) + with | (true, pk1), (true, pk2) -> - match NBitcoin.Secp256k1.ECPubKey.TryCombine(context, seq {yield pk1; yield pk2}) with - | true, pk -> - true, this.NormalizePubKey(pk).ToArray() - | false, _ -> - false, [||] - | _ -> failwithf "failed to combine public key %s and %s" (pk1.ToArray().ToHexString()) (pk2.ToArray().ToHexString()) - + match + NBitcoin.Secp256k1.ECPubKey.TryCombine + ( + context, + seq { + yield pk1 + yield pk2 + } + ) + with + | true, pk -> true, this.NormalizePubKey(pk).ToArray() + | false, _ -> false, [||] + | _ -> + failwithf + "failed to combine public key %s and %s" + (pk1.ToArray().ToHexString()) + (pk2.ToArray().ToHexString()) + member this.PrivateKeyTweakAdd(tweak, privateKey) = - use ecPrivateKey = context.CreateECPrivKey(Span.op_Implicit( privateKey)) + use ecPrivateKey = + context.CreateECPrivKey(Span.op_Implicit (privateKey)) + match ecPrivateKey.TryTweakAdd tweak with | true, r -> r.WriteToSpan privateKey true - | false, _ -> - false - + | false, _ -> false + member this.PrivateKeyTweakMultiply(tweak, privateKey) = - use ecPrivateKey = context.CreateECPrivKey(Span.op_Implicit(privateKey)) + use ecPrivateKey = + context.CreateECPrivKey(Span.op_Implicit (privateKey)) + match ecPrivateKey.TryTweakMul tweak with | true, r -> r.WriteToSpan privateKey true | false, _ -> false - + member this.PublicKeyTweakMultiply(tweak, publicKey) = - let pk: ReadOnlySpan = Span.op_Implicit(publicKey) + let pk: ReadOnlySpan = Span.op_Implicit (publicKey) let pk = this.NormalizePubKey pk + match ECPubKey.TryCreateRawFormat(pk, context) with | true, ecPubKey -> /// This function must update publicKey object which is 64 bytes. @@ -153,128 +232,221 @@ module Sodium = /// So We must first convert into uncompressed serialized form (65 bytes) /// And normalize that by removing header byte. let tweaked = ecPubKey.MultTweak tweak + let tweaked = this.NormalizePubKey(tweaked) tweaked.CopyTo publicKey true - | false, _ -> - false - + | false, _ -> false + member this.Dispose() = () - - let internal getNonce (n: uint64) = - let nonceBytes = ReadOnlySpan(Array.concat[| Array.zeroCreate 4; BitConverter.GetBytes n |]) // little endian + + let internal getNonce(n: uint64) = + let nonceBytes = + ReadOnlySpan( + Array.concat + [| + Array.zeroCreate 4 + BitConverter.GetBytes n + |] + ) // little endian + NSec.Cryptography.Nonce(nonceBytes, 0) - let internal chacha20AD = NSec.Cryptography.ChaCha20Poly1305.ChaCha20Poly1305 + let internal chacha20AD = + NSec.Cryptography.ChaCha20Poly1305.ChaCha20Poly1305 + let internal chacha20 = NSec.Experimental.ChaCha20.ChaCha20 + type CryptoImpl() = interface ICryptoImpl with member this.newSecp256k1() = new NBitcoinSecp256k1() :> ISecp256k1 - member this.decryptWithAD(n: uint64, key: uint256, ad: byte[], cipherText: ReadOnlySpan): Result = + + member this.decryptWithAD + ( + n: uint64, + key: uint256, + ad: array, + cipherText: ReadOnlySpan + ) : Result, CryptoError> = let nonce = getNonce n - let keySpan = ReadOnlySpan (key.ToBytes()) + let keySpan = ReadOnlySpan(key.ToBytes()) let adSpan = ReadOnlySpan ad let blobF = NSec.Cryptography.KeyBlobFormat.RawSymmetricKey - let chachaKey = NSec.Cryptography.Key.Import(chacha20AD, keySpan, blobF) - match chacha20AD.Decrypt(chachaKey, &nonce, adSpan, cipherText) with + + let chachaKey = + NSec.Cryptography.Key.Import(chacha20AD, keySpan, blobF) + + match chacha20AD.Decrypt(chachaKey, &nonce, adSpan, cipherText) + with | true, plainText -> Ok plainText | false, _ -> Error(BadMac) - member this.encryptWithoutAD(n: uint64, key: byte[], plainText: ReadOnlySpan) = + member this.encryptWithoutAD + ( + n: uint64, + key: array, + plainText: ReadOnlySpan + ) = let nonce = getNonce n let keySpan = ReadOnlySpan key let blobF = NSec.Cryptography.KeyBlobFormat.RawSymmetricKey - use chachaKey = NSec.Cryptography.Key.Import(chacha20, keySpan, blobF) + + use chachaKey = + NSec.Cryptography.Key.Import(chacha20, keySpan, blobF) + let res = chacha20.XOr(chachaKey, &nonce, plainText) res - member this.encryptWithAD(n: uint64, key: uint256, ad: ReadOnlySpan, plainText: ReadOnlySpan) = + member this.encryptWithAD + ( + n: uint64, + key: uint256, + ad: ReadOnlySpan, + plainText: ReadOnlySpan + ) = let nonce = getNonce n - let keySpan = ReadOnlySpan (key.ToBytes()) + let keySpan = ReadOnlySpan(key.ToBytes()) let blobF = NSec.Cryptography.KeyBlobFormat.RawSymmetricKey - use chachaKey = NSec.Cryptography.Key.Import(chacha20AD, keySpan, blobF) + + use chachaKey = + NSec.Cryptography.Key.Import(chacha20AD, keySpan, blobF) + chacha20AD.Encrypt(chachaKey, &nonce, ad, plainText) - + #else -type internal Operation = Mul | Add +type internal Operation = + | Mul + | Add type internal BouncySecp256k1() = - let parameters: Org.BouncyCastle.Asn1.X9.X9ECParameters = Org.BouncyCastle.Asn1.Sec.SecNamedCurves.GetByName "secp256k1" - let ecParams = ECDomainParameters(parameters.Curve, parameters.G, parameters.N, parameters.H) - let bcBigint (x: byte[]) = Org.BouncyCastle.Math.BigInteger(1, x) - let tweakKey (op: Operation) (tweak: ReadOnlySpan) (keyToMutate: Span) = + let parameters: Org.BouncyCastle.Asn1.X9.X9ECParameters = + Org.BouncyCastle.Asn1.Sec.SecNamedCurves.GetByName "secp256k1" + + let ecParams = + ECDomainParameters( + parameters.Curve, + parameters.G, + parameters.N, + parameters.H + ) + + let bcBigint(x: array) = + Org.BouncyCastle.Math.BigInteger(1, x) + + let tweakKey + (op: Operation) + (tweak: ReadOnlySpan) + (keyToMutate: Span) + = let k = bcBigint <| keyToMutate.ToArray() let tweakInt = bcBigint <| tweak.ToArray() - let tweaked = match op with - | Mul -> k.Multiply tweakInt - | Add -> k.Add tweakInt + + let tweaked = + match op with + | Mul -> k.Multiply tweakInt + | Add -> k.Add tweakInt + let tweakedByteArray = tweaked.Mod(parameters.N).ToByteArrayUnsigned() let offset = 32 - tweakedByteArray.Length - tweakedByteArray.AsSpan().CopyTo (keyToMutate.Slice offset) + + tweakedByteArray + .AsSpan() + .CopyTo(keyToMutate.Slice offset) + keyToMutate.Slice(0, offset).Fill 0uy true + interface IDisposable with - member this.Dispose() = () + member this.Dispose() = + () + interface ISecp256k1 with member this.PublicKeyCreate privKey = let privInt = bcBigint <| privKey.ToArray() true, ecParams.G.Multiply(privInt).GetEncoded true + member this.PublicKeySerializeCompressed publicKey = let p = parameters.Curve.DecodePoint <| publicKey.ToArray() true, p.GetEncoded true + member this.PublicKeyParse serializedPubKey = let p = parameters.Curve.DecodePoint <| serializedPubKey.ToArray() true, p.GetEncoded true - member this.PublicKeyCombine (pubkey1, pubkey2) = + + member this.PublicKeyCombine(pubkey1, pubkey2) = let p1 = parameters.Curve.DecodePoint <| pubkey1.ToArray() let p2 = parameters.Curve.DecodePoint <| pubkey2.ToArray() true, p1.Add(p2).Normalize().GetEncoded true - member this.PrivateKeyTweakAdd (tweak, privKeyToMutate) = + + member this.PrivateKeyTweakAdd(tweak, privKeyToMutate) = tweakKey Add tweak privKeyToMutate - member this.PrivateKeyTweakMultiply (tweak, privKeyToMutate) = + + member this.PrivateKeyTweakMultiply(tweak, privKeyToMutate) = tweakKey Mul tweak privKeyToMutate - member this.PublicKeyTweakMultiply (tweak, publicKeyToMutate) = + + member this.PublicKeyTweakMultiply(tweak, publicKeyToMutate) = let p = parameters.Curve.DecodePoint <| publicKeyToMutate.ToArray() let tweakInt = bcBigint <| tweak.ToArray() let tweaked = p.Multiply tweakInt - tweaked.Normalize().GetEncoded(true).AsSpan().CopyTo publicKeyToMutate + + tweaked + .Normalize() + .GetEncoded(true) + .AsSpan() + .CopyTo publicKeyToMutate + true module BouncyCastle = - type internal Mode = Encrypt | Decrypt + type internal Mode = + | Encrypt + | Decrypt - let internal encryptOrDecrypt (mode: Mode) (inp: byte[]) (key: byte[]) (nonce: byte[]) (skip1block: bool): byte[] = + let internal encryptOrDecrypt + (mode: Mode) + (inp: array) + (key: array) + (nonce: array) + (skip1block: bool) + : array = let eng = Org.BouncyCastle.Crypto.Engines.ChaCha7539Engine() eng.Init((mode = Encrypt), ParametersWithIV(KeyParameter key, nonce)) let out = Array.zeroCreate inp.Length + if skip1block then let dummy = Array.zeroCreate 64 eng.ProcessBytes(Array.zeroCreate 64, 0, 64, dummy, 0) + eng.ProcessBytes(inp, 0, inp.Length, out, 0) out - let internal pad (mac: Poly1305) (length: int): unit = + let internal pad (mac: Poly1305) (length: int) : unit = match length % 16 with | 0 -> () | n -> let padding = Array.zeroCreate <| 16 - n mac.BlockUpdate(padding, 0, padding.Length) - let internal writeLE (mac: Poly1305) (length: int): unit = + let internal writeLE (mac: Poly1305) (length: int) : unit = let serialized = BitConverter.GetBytes(uint64 length) + if not BitConverter.IsLittleEndian then Array.Reverse serialized + mac.BlockUpdate(serialized, 0, 8) - let internal writeSpan (mac: Poly1305) (span: ReadOnlySpan): unit = + let internal writeSpan (mac: Poly1305) (span: ReadOnlySpan) : unit = let byteArray = span.ToArray() mac.BlockUpdate(byteArray, 0, byteArray.Length) - let internal calcMac key nonce ciphertext ad: byte[] = + let internal calcMac key nonce ciphertext ad : array = let mac = Poly1305() - let polyKey = encryptOrDecrypt Encrypt (Array.zeroCreate 32) key nonce false + + let polyKey = + encryptOrDecrypt Encrypt (Array.zeroCreate 32) key nonce false + mac.Init <| KeyParameter polyKey writeSpan mac ad pad mac ad.Length @@ -282,39 +454,96 @@ module BouncyCastle = pad mac ciphertext.Length writeLE mac ad.Length writeLE mac ciphertext.Length - let tag: byte[] = Array.zeroCreate 16 + let tag: array = Array.zeroCreate 16 let macreslen = mac.DoFinal(tag, 0) assert (macreslen = 16) tag type CryptoImpl() = interface ICryptoImpl with - member this.newSecp256k1() = new BouncySecp256k1() :> ISecp256k1 - member this.encryptWithAD(n: uint64, key: uint256, ad: ReadOnlySpan, plainText: ReadOnlySpan) = + member this.newSecp256k1() = + new BouncySecp256k1() :> ISecp256k1 + + member this.encryptWithAD + ( + n: uint64, + key: uint256, + ad: ReadOnlySpan, + plainText: ReadOnlySpan + ) = let key = key.ToBytes() - let nonce = Array.concat [| Array.zeroCreate 4; BitConverter.GetBytes n |] + + let nonce = + Array.concat + [| + Array.zeroCreate 4 + BitConverter.GetBytes n + |] + let plainTextBytes = plainText.ToArray() - let ciphertext = encryptOrDecrypt Encrypt plainTextBytes key nonce true + + let ciphertext = + encryptOrDecrypt Encrypt plainTextBytes key nonce true + let tag = calcMac key nonce ciphertext ad Array.concat [| ciphertext; tag |] - member this.decryptWithAD(n: uint64, key: uint256, ad: byte[], ciphertext: ReadOnlySpan) = + member this.decryptWithAD + ( + n: uint64, + key: uint256, + ad: array, + ciphertext: ReadOnlySpan + ) = if ciphertext.Length < 16 then CryptoError.InvalidMessageLength ciphertext.Length |> Error else let key = key.ToBytes() - let nonce = Array.concat[| Array.zeroCreate 4; BitConverter.GetBytes n |] - let ciphertextWithoutMac = ciphertext.Slice(0, ciphertext.Length - 16).ToArray() - let macToValidate = ciphertext.Slice(ciphertext.Length - 16).ToArray() - let correctMac = calcMac key nonce ciphertextWithoutMac (ReadOnlySpan ad) + + let nonce = + Array.concat + [| + Array.zeroCreate 4 + BitConverter.GetBytes n + |] + + let ciphertextWithoutMac = + ciphertext + .Slice(0, ciphertext.Length - 16) + .ToArray() + + let macToValidate = + ciphertext.Slice(ciphertext.Length - 16).ToArray() + + let correctMac = + calcMac key nonce ciphertextWithoutMac (ReadOnlySpan ad) + if correctMac <> macToValidate then Error(BadMac) else - let plaintext = encryptOrDecrypt Decrypt ciphertextWithoutMac key nonce true + let plaintext = + encryptOrDecrypt + Decrypt + ciphertextWithoutMac + key + nonce + true + Ok plaintext - member this.encryptWithoutAD(n: uint64, key: byte[], plainText: ReadOnlySpan) = - let nonce = Array.concat [| Array.zeroCreate 4; BitConverter.GetBytes n |] + member this.encryptWithoutAD + ( + n: uint64, + key: array, + plainText: ReadOnlySpan + ) = + let nonce = + Array.concat + [| + Array.zeroCreate 4 + BitConverter.GetBytes n + |] + encryptOrDecrypt Encrypt (plainText.ToArray()) key nonce false #endif diff --git a/src/DotNetLightning.Core/Crypto/KeyExtensions.fs b/src/DotNetLightning.Core/Crypto/KeyExtensions.fs index c2f6f76a0..67fe137ee 100644 --- a/src/DotNetLightning.Core/Crypto/KeyExtensions.fs +++ b/src/DotNetLightning.Core/Crypto/KeyExtensions.fs @@ -21,244 +21,349 @@ module NBitcoinArithmethicExtensions = let Secp256k1 = CryptoUtils.impl.newSecp256k1() type Key with - static member Mul(lhs: Key, rhs: Key): Key = + + static member Mul(lhs: Key, rhs: Key) : Key = let lhsBytes = lhs.ToBytes() let rhsBytes = rhs.ToBytes() let tweak = ReadOnlySpan(lhsBytes) - match Secp256k1.PrivateKeyTweakMultiply(tweak, rhsBytes.AsSpan()) with + + match Secp256k1.PrivateKeyTweakMultiply(tweak, rhsBytes.AsSpan()) + with | true -> new Key(rhsBytes) | false -> failwith "failed to multiply Keys" - static member Add(lhs: Key, rhs: Key): Key = + static member Add(lhs: Key, rhs: Key) : Key = let lhsBytes = lhs.ToBytes() let rhsBytes = rhs.ToBytes() let tweak = ReadOnlySpan(lhsBytes) + match Secp256k1.PrivateKeyTweakAdd(tweak, rhsBytes.AsSpan()) with | true -> new Key(rhsBytes) | false -> failwithf "failed to add Keys" type PubKey with - member private this.ExpandToBytes(): array = + + member private this.ExpandToBytes() : array = let compressedPubKeyBytes = this.ToBytes() let compressedPubKeyBytesSpan = ReadOnlySpan(compressedPubKeyBytes) + match Secp256k1.PublicKeyParse(compressedPubKeyBytesSpan) with | true, expandedPubKeyBytes -> expandedPubKeyBytes | false, _ -> failwithf "Failed to expand public key %A" this - static member private CompressFromBytes(expandedPubKeyBytes: array): PubKey = + static member private CompressFromBytes + (expandedPubKeyBytes: array) + : PubKey = let expandedPubKeyBytesSpan = ReadOnlySpan(expandedPubKeyBytes) - match Secp256k1.PublicKeySerializeCompressed(expandedPubKeyBytesSpan) with + + match + Secp256k1.PublicKeySerializeCompressed(expandedPubKeyBytesSpan) + with | true, compressedPubKeyBytes -> PubKey compressedPubKeyBytes | false, _ -> failwith "Failed to compress pubkey" - static member Mul(pubKey: PubKey, key: Key): PubKey = + static member Mul(pubKey: PubKey, key: Key) : PubKey = let keyBytes = key.ToBytes() let pubKeyBytes = pubKey.ExpandToBytes() let tweak = ReadOnlySpan(keyBytes) - match Secp256k1.PublicKeyTweakMultiply(tweak, pubKeyBytes.AsSpan()) with + + match Secp256k1.PublicKeyTweakMultiply(tweak, pubKeyBytes.AsSpan()) + with | true -> PubKey.CompressFromBytes pubKeyBytes | false -> failwith "failed to multiplying PubKey by Key" - static member Add(lhs: PubKey, rhs: PubKey): PubKey = + static member Add(lhs: PubKey, rhs: PubKey) : PubKey = let lhsBytes = lhs.ToBytes() let rhsBytes = rhs.ToBytes() - match Secp256k1.PublicKeyCombine(lhsBytes.AsSpan(), rhsBytes.AsSpan()) with + + match + Secp256k1.PublicKeyCombine + ( + lhsBytes.AsSpan(), + rhsBytes.AsSpan() + ) + with | true, result -> PubKey.CompressFromBytes result | false, _ -> failwith "failed to add PubKeys" [] module KeyExtensions = type PerCommitmentPoint with - member this.DerivePrivKey (basepointSecret: Key): Key = + + member this.DerivePrivKey(basepointSecret: Key) : Key = let basepointBytes = let basepoint = basepointSecret.PubKey basepoint.ToBytes() + let perCommitmentPointBytes = this.ToBytes() + let tweak = - Key.FromHashOf <| Array.append perCommitmentPointBytes basepointBytes + Key.FromHashOf + <| Array.append perCommitmentPointBytes basepointBytes + Key.Add(basepointSecret, tweak) - member this.DerivePubKey (basepoint: PubKey): PubKey = + member this.DerivePubKey(basepoint: PubKey) : PubKey = let basepointBytes = basepoint.ToBytes() let perCommitmentPointBytes = this.ToBytes() + let tweak = - Key.FromHashOf <| Array.append perCommitmentPointBytes basepointBytes + Key.FromHashOf + <| Array.append perCommitmentPointBytes basepointBytes + PubKey.Add(basepoint, tweak.PubKey) - member this.DerivePaymentPrivKey (paymentBasepointSecret: PaymentBasepointSecret) - : PaymentPrivKey = - PaymentPrivKey <| - this.DerivePrivKey (paymentBasepointSecret.RawKey()) - - member this.DerivePaymentPubKey (paymentBasepoint: PaymentBasepoint) - : PaymentPubKey = - PaymentPubKey <| - this.DerivePubKey (paymentBasepoint.RawPubKey()) - - member this.DeriveDelayedPaymentPrivKey (delayedPaymentBasepointSecret: DelayedPaymentBasepointSecret) - : DelayedPaymentPrivKey = - DelayedPaymentPrivKey <| - this.DerivePrivKey (delayedPaymentBasepointSecret.RawKey()) - - member this.DeriveDelayedPaymentPubKey (delayedPaymentBasepoint: DelayedPaymentBasepoint) - : DelayedPaymentPubKey = - DelayedPaymentPubKey <| - this.DerivePubKey (delayedPaymentBasepoint.RawPubKey()) - - member this.DeriveHtlcPrivKey (htlcBasepointSecret: HtlcBasepointSecret) - : HtlcPrivKey = - HtlcPrivKey <| - this.DerivePrivKey (htlcBasepointSecret.RawKey()) - - member this.DeriveHtlcPubKey (htlcBasepoint: HtlcBasepoint) - : HtlcPubKey = - HtlcPubKey <| - this.DerivePubKey (htlcBasepoint.RawPubKey()) - - member this.DeriveRevocationPubKey (revocationBasepoint: RevocationBasepoint) - : RevocationPubKey = + member this.DerivePaymentPrivKey + (paymentBasepointSecret: PaymentBasepointSecret) + : PaymentPrivKey = + PaymentPrivKey + <| this.DerivePrivKey(paymentBasepointSecret.RawKey()) + + member this.DerivePaymentPubKey + (paymentBasepoint: PaymentBasepoint) + : PaymentPubKey = + PaymentPubKey <| this.DerivePubKey(paymentBasepoint.RawPubKey()) + + member this.DeriveDelayedPaymentPrivKey + (delayedPaymentBasepointSecret: DelayedPaymentBasepointSecret) + : DelayedPaymentPrivKey = + DelayedPaymentPrivKey + <| this.DerivePrivKey(delayedPaymentBasepointSecret.RawKey()) + + member this.DeriveDelayedPaymentPubKey + (delayedPaymentBasepoint: DelayedPaymentBasepoint) + : DelayedPaymentPubKey = + DelayedPaymentPubKey + <| this.DerivePubKey(delayedPaymentBasepoint.RawPubKey()) + + member this.DeriveHtlcPrivKey + (htlcBasepointSecret: HtlcBasepointSecret) + : HtlcPrivKey = + HtlcPrivKey <| this.DerivePrivKey(htlcBasepointSecret.RawKey()) + + member this.DeriveHtlcPubKey + (htlcBasepoint: HtlcBasepoint) + : HtlcPubKey = + HtlcPubKey <| this.DerivePubKey(htlcBasepoint.RawPubKey()) + + member this.DeriveRevocationPubKey + (revocationBasepoint: RevocationBasepoint) + : RevocationPubKey = let revocationBasepointBytes = revocationBasepoint.ToBytes() let perCommitmentPointBytes = this.ToBytes() + let revocationBasepointTweak = - Key.FromHashOf <| Array.append revocationBasepointBytes perCommitmentPointBytes - let perCommitmentPointTweak = - Key.FromHashOf <| Array.append perCommitmentPointBytes revocationBasepointBytes + Key.FromHashOf + <| Array.append revocationBasepointBytes perCommitmentPointBytes - RevocationPubKey <| PubKey.Add( - PubKey.Mul(revocationBasepoint.RawPubKey(), revocationBasepointTweak), + let perCommitmentPointTweak = + Key.FromHashOf + <| Array.append perCommitmentPointBytes revocationBasepointBytes + + RevocationPubKey + <| PubKey.Add( + PubKey.Mul( + revocationBasepoint.RawPubKey(), + revocationBasepointTweak + ), PubKey.Mul(this.RawPubKey(), perCommitmentPointTweak) ) - member this.DeriveCommitmentPubKeys (channelPubKeys: ChannelPubKeys) - : CommitmentPubKeys = { - RevocationPubKey = - this.DeriveRevocationPubKey channelPubKeys.RevocationBasepoint - PaymentPubKey = - this.DerivePaymentPubKey channelPubKeys.PaymentBasepoint - DelayedPaymentPubKey = - this.DeriveDelayedPaymentPubKey channelPubKeys.DelayedPaymentBasepoint - HtlcPubKey = - this.DeriveHtlcPubKey channelPubKeys.HtlcBasepoint - } + member this.DeriveCommitmentPubKeys + (channelPubKeys: ChannelPubKeys) + : CommitmentPubKeys = + { + RevocationPubKey = + this.DeriveRevocationPubKey + channelPubKeys.RevocationBasepoint + PaymentPubKey = + this.DerivePaymentPubKey channelPubKeys.PaymentBasepoint + DelayedPaymentPubKey = + this.DeriveDelayedPaymentPubKey + channelPubKeys.DelayedPaymentBasepoint + HtlcPubKey = this.DeriveHtlcPubKey channelPubKeys.HtlcBasepoint + } type CommitmentNumber with - static member ObscureFactor (isFunder: bool) - (localPaymentBasepoint: PaymentBasepoint) - (remotePaymentBasepoint: PaymentBasepoint) - : UInt48 = + + static member ObscureFactor + (isFunder: bool) + (localPaymentBasepoint: PaymentBasepoint) + (remotePaymentBasepoint: PaymentBasepoint) + : UInt48 = let pubKeysHash = if isFunder then let ba = - Array.concat - (seq [ yield localPaymentBasepoint.ToBytes(); yield remotePaymentBasepoint.ToBytes() ]) + Array.concat( + seq + [ + yield localPaymentBasepoint.ToBytes() + yield remotePaymentBasepoint.ToBytes() + ] + ) + Hashes.SHA256 ba else let ba = - Array.concat - (seq [ yield remotePaymentBasepoint.ToBytes(); yield localPaymentBasepoint.ToBytes() ]) + Array.concat( + seq + [ + yield remotePaymentBasepoint.ToBytes() + yield localPaymentBasepoint.ToBytes() + ] + ) + Hashes.SHA256 ba + UInt48.FromBytesBigEndian pubKeysHash.[26..] - member this.Subsumes(other: CommitmentNumber): bool = + member this.Subsumes(other: CommitmentNumber) : bool = let trailingZeros = this.Index().TrailingZeros() (this.Index() >>> trailingZeros) = (other.Index() >>> trailingZeros) - member this.PreviousUnsubsumed(): Option = + member this.PreviousUnsubsumed() : Option = let trailingZeros = this.Index().TrailingZeros() let prev = this.Index().UInt64 + (1UL <<< trailingZeros) + if prev > UInt48.MaxValue.UInt64 then None else Some <| CommitmentNumber(UInt48.FromUInt64 prev) - member this.Obscure (isFunder: bool) - (localPaymentBasepoint: PaymentBasepoint) - (remotePaymentBasepoint: PaymentBasepoint) - : ObscuredCommitmentNumber = + member this.Obscure + (isFunder: bool) + (localPaymentBasepoint: PaymentBasepoint) + (remotePaymentBasepoint: PaymentBasepoint) + : ObscuredCommitmentNumber = let obscureFactor = CommitmentNumber.ObscureFactor isFunder localPaymentBasepoint remotePaymentBasepoint - ObscuredCommitmentNumber((UInt48.MaxValue - this.Index()) ^^^ obscureFactor) + + ObscuredCommitmentNumber( + (UInt48.MaxValue - this.Index()) ^^^ obscureFactor + ) type ObscuredCommitmentNumber with + member this.LockTime: LockTime = - Array.concat [| [| 0x20uy |]; this.ObscuredIndex().GetBytesBigEndian().[3..] |] + Array.concat + [| + [| 0x20uy |] + this.ObscuredIndex().GetBytesBigEndian().[3..] + |] |> System.UInt32.FromBytesBigEndian |> LockTime member this.Sequence: Sequence = - Array.concat [| [| 0x80uy |]; this.ObscuredIndex().GetBytesBigEndian().[..2] |] + Array.concat + [| + [| 0x80uy |] + this.ObscuredIndex().GetBytesBigEndian().[..2] + |] |> System.UInt32.FromBytesBigEndian |> Sequence - static member TryFromLockTimeAndSequence (lockTime: LockTime) - (sequence: Sequence) - : Option = + static member TryFromLockTimeAndSequence + (lockTime: LockTime) + (sequence: Sequence) + : Option = let lockTimeBytes = lockTime.Value.GetBytesBigEndian() let sequenceBytes = sequence.Value.GetBytesBigEndian() + if lockTimeBytes.[0] <> 0x20uy || sequenceBytes.[0] <> 0x80uy then None else - Array.concat [| sequenceBytes.[1..]; lockTimeBytes.[1..] |] + Array.concat + [| + sequenceBytes.[1..] + lockTimeBytes.[1..] + |] |> UInt48.FromBytesBigEndian |> ObscuredCommitmentNumber |> Some - member this.Unobscure (isFunder: bool) - (localPaymentBasepoint: PaymentBasepoint) - (remotePaymentBasepoint: PaymentBasepoint) - : CommitmentNumber = + member this.Unobscure + (isFunder: bool) + (localPaymentBasepoint: PaymentBasepoint) + (remotePaymentBasepoint: PaymentBasepoint) + : CommitmentNumber = let obscureFactor = CommitmentNumber.ObscureFactor isFunder localPaymentBasepoint remotePaymentBasepoint - CommitmentNumber(UInt48.MaxValue - (this.ObscuredIndex() ^^^ obscureFactor)) + + CommitmentNumber( + UInt48.MaxValue - (this.ObscuredIndex() ^^^ obscureFactor) + ) type PerCommitmentSecret with - member this.DeriveChild (thisCommitmentNumber: CommitmentNumber) - (childCommitmentNumber: CommitmentNumber) - : Option = + + member this.DeriveChild + (thisCommitmentNumber: CommitmentNumber) + (childCommitmentNumber: CommitmentNumber) + : Option = if thisCommitmentNumber.Subsumes childCommitmentNumber then let commonBits = thisCommitmentNumber.Index().TrailingZeros() let index = childCommitmentNumber.Index() let mutable secret = this.ToBytes() + for bit in (commonBits - 1) .. -1 .. 0 do if (index >>> bit) &&& UInt48.One = UInt48.One then let byteIndex = bit / 8 let bitIndex = bit % 8 - secret.[byteIndex] <- secret.[byteIndex] ^^^ (1uy <<< bitIndex) + + secret.[byteIndex] <- + secret.[byteIndex] ^^^ (1uy <<< bitIndex) + secret <- Hashes.SHA256 secret + Some <| PerCommitmentSecret(new Key(secret)) else None - member this.DeriveRevocationPrivKey (revocationBasepointSecret: RevocationBasepointSecret) - : RevocationPrivKey = + member this.DeriveRevocationPrivKey + (revocationBasepointSecret: RevocationBasepointSecret) + : RevocationPrivKey = let revocationBasepointBytes = - let revocationBasepoint = revocationBasepointSecret.RevocationBasepoint() + let revocationBasepoint = + revocationBasepointSecret.RevocationBasepoint() + revocationBasepoint.ToBytes() + let perCommitmentPointBytes = let perCommitmentPoint = this.PerCommitmentPoint() perCommitmentPoint.ToBytes() + let revocationBasepointSecretTweak = - Key.FromHashOf <| Array.append revocationBasepointBytes perCommitmentPointBytes - let perCommitmentSecretTweak = - Key.FromHashOf <| Array.append perCommitmentPointBytes revocationBasepointBytes + Key.FromHashOf + <| Array.append revocationBasepointBytes perCommitmentPointBytes - RevocationPrivKey <| Key.Add( - Key.Mul(revocationBasepointSecret.RawKey(), revocationBasepointSecretTweak), + let perCommitmentSecretTweak = + Key.FromHashOf + <| Array.append perCommitmentPointBytes revocationBasepointBytes + + RevocationPrivKey + <| Key.Add( + Key.Mul( + revocationBasepointSecret.RawKey(), + revocationBasepointSecretTweak + ), Key.Mul(this.RawKey(), perCommitmentSecretTweak) ) type CommitmentSeed with - member this.DerivePerCommitmentSecret (commitmentNumber: CommitmentNumber): PerCommitmentSecret = + + member this.DerivePerCommitmentSecret + (commitmentNumber: CommitmentNumber) + : PerCommitmentSecret = let res = this.LastPerCommitmentSecret().DeriveChild CommitmentNumber.LastCommitment commitmentNumber + match res with | Some perCommitmentSecret -> perCommitmentSecret | None -> @@ -266,39 +371,57 @@ module KeyExtensions = "The final per commitment secret should be able to derive the \ commitment secret for all prior commitments. This is a bug." - member this.DerivePerCommitmentPoint (commitmentNumber: CommitmentNumber): PerCommitmentPoint = - let perCommitmentSecret = this.DerivePerCommitmentSecret commitmentNumber + member this.DerivePerCommitmentPoint + (commitmentNumber: CommitmentNumber) + : PerCommitmentPoint = + let perCommitmentSecret = + this.DerivePerCommitmentSecret commitmentNumber + perCommitmentSecret.PerCommitmentPoint() /// Set of lightning keys needed to operate a channel as describe in BOLT 3 type ChannelPrivKeys with - member this.SignWithFundingPrivKey (psbt: PSBT) - : TransactionSignature * PSBT = + + member this.SignWithFundingPrivKey + (psbt: PSBT) + : TransactionSignature * PSBT = let fundingPubKey = this.FundingPrivKey.FundingPubKey() psbt.SignWithKeys(this.FundingPrivKey.RawKey()) |> ignore + match psbt.GetMatchingSig(fundingPubKey.RawPubKey()) with | Some signature -> (signature, psbt) - | None -> failwithf "Failed to get signature for %A with funding pub key (%A). This should never happen" psbt fundingPubKey + | 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 - member this.SignHtlcTx (psbt: PSBT) - (perCommitmentPoint: PerCommitmentPoint) - : TransactionSignature * PSBT = - let htlcPrivKey = perCommitmentPoint.DeriveHtlcPrivKey this.HtlcBasepointSecret let htlcPubKey = htlcPrivKey.HtlcPubKey() 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 + 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 /// index used to derive the channel's master key. type NodeMasterPrivKey with - member this.ChannelPrivKeys (channelIndex: int): ChannelPrivKeys = + + member this.ChannelPrivKeys(channelIndex: int) : ChannelPrivKeys = let channelMasterKey = this.RawExtKey().Derive(channelIndex, true) // TODO: make use of these keys or remove them @@ -313,16 +436,21 @@ module KeyExtensions = channelMasterKey.Derive(4, true).PrivateKey |> FundingPrivKey let revocationBasepointSecret = - channelMasterKey.Derive(5, true).PrivateKey |> RevocationBasepointSecret + channelMasterKey.Derive(5, true).PrivateKey + |> RevocationBasepointSecret let paymentBasepointSecret = - channelMasterKey.Derive(6, true).PrivateKey |> PaymentBasepointSecret + channelMasterKey.Derive(6, true).PrivateKey + |> PaymentBasepointSecret let delayedPaymentBasepointSecret = - channelMasterKey.Derive(7, true).PrivateKey |> DelayedPaymentBasepointSecret + channelMasterKey.Derive(7, true).PrivateKey + |> DelayedPaymentBasepointSecret let htlcBasepointSecret = - channelMasterKey.Derive(8, true).PrivateKey |> HtlcBasepointSecret + channelMasterKey.Derive(8, true).PrivateKey + |> HtlcBasepointSecret + { FundingPrivKey = fundingPrivKey RevocationBasepointSecret = revocationBasepointSecret @@ -331,4 +459,3 @@ module KeyExtensions = HtlcBasepointSecret = htlcBasepointSecret CommitmentSeed = commitmentSeed } - diff --git a/src/DotNetLightning.Core/Crypto/OnionUtils.fs b/src/DotNetLightning.Core/Crypto/OnionUtils.fs index 2af281a98..d4a35d493 100644 --- a/src/DotNetLightning.Core/Crypto/OnionUtils.fs +++ b/src/DotNetLightning.Core/Crypto/OnionUtils.fs @@ -1,2 +1 @@ namespace DotNetLightning.Crypto - diff --git a/src/DotNetLightning.Core/Crypto/PerCommitmentSecretStore.fs b/src/DotNetLightning.Core/Crypto/PerCommitmentSecretStore.fs index 9da3f34bb..276ba5749 100644 --- a/src/DotNetLightning.Core/Crypto/PerCommitmentSecretStore.fs +++ b/src/DotNetLightning.Core/Crypto/PerCommitmentSecretStore.fs @@ -8,35 +8,46 @@ open ResultUtils open ResultUtils.Portability type InsertPerCommitmentSecretError = - | UnexpectedCommitmentNumber of got: CommitmentNumber * expected: CommitmentNumber - | SecretMismatch of previousCommitmentNumber: CommitmentNumber * newCommitmentNumber: CommitmentNumber - with - member this.Message: string = - match this with - | UnexpectedCommitmentNumber(got, expected) -> - sprintf - "Unexpected commitment number. Got %s, expected %s" - (got.ToString()) - (expected.ToString()) - | SecretMismatch(previousCommitmentNumber, newCommitmentNumber) -> - sprintf - "Per commitment secret for commitment %s derives a secret \ + | UnexpectedCommitmentNumber of + got: CommitmentNumber * + expected: CommitmentNumber + | SecretMismatch of + previousCommitmentNumber: CommitmentNumber * + newCommitmentNumber: CommitmentNumber + + member this.Message: string = + match this with + | UnexpectedCommitmentNumber(got, expected) -> + sprintf + "Unexpected commitment number. Got %s, expected %s" + (got.ToString()) + (expected.ToString()) + | SecretMismatch(previousCommitmentNumber, newCommitmentNumber) -> + sprintf + "Per commitment secret for commitment %s derives a secret \ for commitment %s which does not match the recorded secret" - (newCommitmentNumber.ToString()) - (previousCommitmentNumber.ToString()) + (newCommitmentNumber.ToString()) + (previousCommitmentNumber.ToString()) -type PerCommitmentSecretStore private (secrets: list) = +type PerCommitmentSecretStore + private + ( + secrets: list + ) = new() = PerCommitmentSecretStore(List.empty) member this.Secrets = secrets - static member FromSecrets (secrets: list): PerCommitmentSecretStore = - let rec sanityCheck (commitmentNumbers: list): bool = + static member FromSecrets + (secrets: list) + : PerCommitmentSecretStore = + let rec sanityCheck(commitmentNumbers: list) : bool = if commitmentNumbers.IsEmpty then true else let commitmentNumber = commitmentNumbers.Head let tail = commitmentNumbers.Tail + match commitmentNumber.PreviousUnsubsumed() with | None -> tail.IsEmpty | Some expectedCommitmentNumber -> @@ -44,63 +55,101 @@ type PerCommitmentSecretStore private (secrets: list expectedCommitmentNumber then false else sanityCheck tail + let commitmentNumbers, _ = List.unzip secrets - if not (sanityCheck commitmentNumbers) then - failwithf "commitment number list is malformed: %A" commitmentNumbers + + if not(sanityCheck commitmentNumbers) then + failwithf + "commitment number list is malformed: %A" + commitmentNumbers + PerCommitmentSecretStore secrets - member this.NextCommitmentNumber(): CommitmentNumber = + member this.NextCommitmentNumber() : CommitmentNumber = if this.Secrets.IsEmpty then CommitmentNumber.FirstCommitment else let prevCommitmentNumber, _ = this.Secrets.Head prevCommitmentNumber.NextCommitment() - member this.InsertPerCommitmentSecret (commitmentNumber: CommitmentNumber) - (perCommitmentSecret: PerCommitmentSecret) - : Result = + member this.InsertPerCommitmentSecret + (commitmentNumber: CommitmentNumber) + (perCommitmentSecret: PerCommitmentSecret) + : Result = let nextCommitmentNumber = this.NextCommitmentNumber() + if commitmentNumber <> nextCommitmentNumber then - Error <| UnexpectedCommitmentNumber (commitmentNumber, nextCommitmentNumber) + Error + <| UnexpectedCommitmentNumber( + commitmentNumber, + nextCommitmentNumber + ) else - let rec fold (secrets: list) - : Result = + let rec fold + (secrets: list) + : Result = if secrets.IsEmpty then - let res = [commitmentNumber, perCommitmentSecret] + let res = + [ + commitmentNumber, perCommitmentSecret + ] + Ok <| PerCommitmentSecretStore res else - let storedCommitmentNumber, storedPerCommitmentSecret = secrets.Head - match perCommitmentSecret.DeriveChild commitmentNumber storedCommitmentNumber with + let storedCommitmentNumber, storedPerCommitmentSecret = + secrets.Head + + match + perCommitmentSecret.DeriveChild + commitmentNumber + storedCommitmentNumber + with | Some derivedPerCommitmentSecret -> - if derivedPerCommitmentSecret <> storedPerCommitmentSecret then - Error <| SecretMismatch (storedCommitmentNumber, commitmentNumber) + if derivedPerCommitmentSecret + <> storedPerCommitmentSecret then + Error + <| SecretMismatch( + storedCommitmentNumber, + commitmentNumber + ) else fold secrets.Tail | None -> - let res = (commitmentNumber, perCommitmentSecret) :: secrets + let res = + (commitmentNumber, perCommitmentSecret) :: secrets + Ok <| PerCommitmentSecretStore res + fold this.Secrets - member this.GetPerCommitmentSecret (commitmentNumber: CommitmentNumber) - : Option = - let rec fold (secrets: list) = + member this.GetPerCommitmentSecret + (commitmentNumber: CommitmentNumber) + : Option = + let rec fold(secrets: list) = if secrets.IsEmpty then None else - let storedCommitmentNumber, storedPerCommitmentSecret = secrets.Head - match storedPerCommitmentSecret.DeriveChild storedCommitmentNumber commitmentNumber with + let storedCommitmentNumber, storedPerCommitmentSecret = + secrets.Head + + match + storedPerCommitmentSecret.DeriveChild + storedCommitmentNumber + commitmentNumber + with | Some perCommitmentSecret -> Some perCommitmentSecret | None -> fold secrets.Tail + fold this.Secrets - member this.MostRecentPerCommitmentSecret(): Option = + member this.MostRecentPerCommitmentSecret() : Option = if this.Secrets.IsEmpty then None else let _, perCommitmentSecret = this.Secrets.Head Some perCommitmentSecret - diff --git a/src/DotNetLightning.Core/Crypto/ShaChain.fs b/src/DotNetLightning.Core/Crypto/ShaChain.fs index 3a027ddb0..082af696d 100644 --- a/src/DotNetLightning.Core/Crypto/ShaChain.fs +++ b/src/DotNetLightning.Core/Crypto/ShaChain.fs @@ -1,30 +1,38 @@ namespace DotNetLightning.Crypto -type Node = { - Value: byte[] - Height: int32 - Parent: Node option -} - -type ShaChain = { - KnownHashes: Map - LastIndex: uint64 option -} - with - static member Zero = { KnownHashes = Map.empty; LastIndex = None } +type Node = + { + Value: array + Height: int32 + Parent: option + } + +type ShaChain = + { + KnownHashes: Map, array> + LastIndex: option + } + + static member Zero = + { + KnownHashes = Map.empty + LastIndex = None + } + module ShaChain = - let flip (_input: byte[]) (_index: uint64): byte[] = + let flip (_input: array) (_index: uint64) : array = failwith "Not implemented: ShaChain::flip" - let addHash (_receiver: ShaChain) (_hash: byte[]) (_index: uint64) = + let addHash (_receiver: ShaChain) (_hash: array) (_index: uint64) = failwith "Not implemented: ShaChain::addHash" - let getHash (_receiver: ShaChain)(_index: uint64) = + let getHash (_receiver: ShaChain) (_index: uint64) = failwith "Not implemented: ShaChain::getHash" type ShaChain with - member this.AddHash(hash: byte[], index: uint64): ShaChain = + + member this.AddHash(hash: array, index: uint64) : ShaChain = ShaChain.addHash this hash index - member this.GetHash(index: uint64): ShaChain = + member this.GetHash(index: uint64) : ShaChain = ShaChain.getHash this index diff --git a/src/DotNetLightning.Core/Crypto/Sphinx.fs b/src/DotNetLightning.Core/Crypto/Sphinx.fs index 4255d7408..44b758de9 100644 --- a/src/DotNetLightning.Core/Crypto/Sphinx.fs +++ b/src/DotNetLightning.Core/Crypto/Sphinx.fs @@ -28,29 +28,30 @@ module Sphinx = let MaxHops = 20 [] - let PACKET_LENGTH = 1366 // 1 + 33 + MacLength + MaxHops * (PayloadLength + MacLength) + let PACKET_LENGTH = 1366 // 1 + 33 + MacLength + MaxHops * (PayloadLength + MacLength) let private hex = NBitcoin.DataEncoders.HexEncoder() let private ascii = NBitcoin.DataEncoders.ASCIIEncoder() - let private mac (key, msg) = Hashes.HMACSHA256(key, msg) |> uint256 + let private mac(key, msg) = + Hashes.HMACSHA256(key, msg) |> uint256 - let private xor (a: byte[], b: byte[]) = - Array.zip a b - |> Array.map(fun (abyte, bbyte) -> (abyte ^^^ bbyte)) + let private xor(a: array, b: array) = + Array.zip a b |> Array.map(fun (abyte, bbyte) -> (abyte ^^^ bbyte)) - let private generateKey (key, secret) = + let private generateKey(key, secret) = let kb = key |> ascii.DecodeData - Hashes.HMACSHA256 (kb, secret) + Hashes.HMACSHA256(kb, secret) - let private zeros (l) = Array.zeroCreate l + let private zeros l = + Array.zeroCreate l - let private generateStream (key, l) : byte[] = + let private generateStream(key, l) : array = crypto.encryptWithoutAD(0UL, key, ReadOnlySpan(Array.zeroCreate l)) let private computeSharedSecret = Secret.FromKeyPair - let private computeBlindingFactor(pk: PubKey) (secret: Key) = + let private computeBlindingFactor (pk: PubKey) (secret: Key) = [| pk.ToBytes(); secret.ToBytes() |] |> Array.concat |> Crypto.Hashes.SHA256 @@ -59,86 +60,164 @@ module Sphinx = let private blind (pk: PubKey) (secret: Key) = pk.GetSharedPubkey(secret) - let private blindMulti (pk: PubKey) (secrets: Key seq) = + let private blindMulti (pk: PubKey) (secrets: seq) = Seq.fold (blind) pk secrets // computes ephemeral public keys and shared secretes for all nodes on the route let rec private computeEphemeralPublicKeysAndSharedSecretsCore (sessionKey: Key) - (pubKeys: PubKey list) - (ephemeralPubKeys: PubKey list) - (blindingFactors: Key list) - (sharedSecrets: Key list) = + (pubKeys: list) + (ephemeralPubKeys: list) + (blindingFactors: list) + (sharedSecrets: list) + = if (pubKeys.Length = 0) then (ephemeralPubKeys, sharedSecrets) else - let ephemeralPubKey = blind (ephemeralPubKeys |> List.last) (blindingFactors |> List.last) - let secret = computeSharedSecret (blindMulti (pubKeys.[0]) (blindingFactors), sessionKey) |> fun h -> new Key(h) - let blindingFactor = computeBlindingFactor(ephemeralPubKey) (secret) + let ephemeralPubKey = + blind + (ephemeralPubKeys |> List.last) + (blindingFactors |> List.last) + + let secret = + computeSharedSecret( + blindMulti (pubKeys.[0]) (blindingFactors), + sessionKey + ) + |> fun h -> new Key(h) + + let blindingFactor = + computeBlindingFactor (ephemeralPubKey) (secret) + computeEphemeralPublicKeysAndSharedSecretsCore - sessionKey (pubKeys |> List.tail) - (ephemeralPubKeys @ [ephemeralPubKey]) - (blindingFactors @ [blindingFactor]) - (sharedSecrets @ [secret]) + sessionKey + (pubKeys |> List.tail) + (ephemeralPubKeys @ [ ephemeralPubKey ]) + (blindingFactors @ [ blindingFactor ]) + (sharedSecrets @ [ secret ]) - let rec internal computeEphemeralPublicKeysAndSharedSecrets(sessionKey: Key) (pubKeys: PubKey list) = + let rec internal computeEphemeralPublicKeysAndSharedSecrets + (sessionKey: Key) + (pubKeys: list) + = let ephemeralPK0 = sessionKey.PubKey - let secret0 = computeSharedSecret(pubKeys.[0], sessionKey) |> fun h -> new Key(h) - let blindingFactor0 = computeBlindingFactor(ephemeralPK0) (secret0) - computeEphemeralPublicKeysAndSharedSecretsCore - (sessionKey) (pubKeys |> List.tail) ([ephemeralPK0]) ([blindingFactor0]) ([secret0]) - let rec internal generateFiller (keyType: string) (sharedSecrets: Key list) (hopSize: int) (maxNumberOfHops: int option) = + let secret0 = + computeSharedSecret(pubKeys.[0], sessionKey) |> fun h -> new Key(h) + + let blindingFactor0 = computeBlindingFactor (ephemeralPK0) (secret0) + + computeEphemeralPublicKeysAndSharedSecretsCore + (sessionKey) + (pubKeys |> List.tail) + ([ ephemeralPK0 ]) + ([ blindingFactor0 ]) + ([ secret0 ]) + + let rec internal generateFiller + (keyType: string) + (sharedSecrets: list) + (hopSize: int) + (maxNumberOfHops: 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) - ) [||] - - type ParsedPacket = { - Payload: byte[] - NextPacket: OnionPacket - SharedSecret: byte[] - } - let parsePacket (nodePrivateKey: Key) (ad: byte[]) (rawPacket: byte[]): Result = + |> List.fold + (fun (padding: array) (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) + ) + [||] + + type ParsedPacket = + { + Payload: array + NextPacket: OnionPacket + SharedSecret: array + } + + let parsePacket + (nodePrivateKey: Key) + (ad: array) + (rawPacket: array) + : Result = if (rawPacket.Length <> PACKET_LENGTH) then - CryptoError.InvalidErrorPacketLength (PACKET_LENGTH, rawPacket.Length) + CryptoError.InvalidErrorPacketLength( + PACKET_LENGTH, + rawPacket.Length + ) |> Error else - let packet = ILightningSerializable.fromBytes(rawPacket) + let packet = + ILightningSerializable.fromBytes(rawPacket) + match PubKey.TryCreatePubKey packet.PublicKey with - | false, _ -> - InvalidPublicKey(packet.PublicKey) |> Error + | false, _ -> InvalidPublicKey(packet.PublicKey) |> Error | true, publicKey -> let ss = computeSharedSecret(publicKey, nodePrivateKey) let mu = generateKey("mu", ss) + let check = - let msg = Array.concat (seq [ packet.HopData; ad ]) + let msg = Array.concat(seq [ packet.HopData; ad ]) mac(mu, msg) + if check <> packet.HMAC then CryptoError.BadMac |> Error else let rho = generateKey("rho", ss) + let bin = - let d = Array.concat (seq [packet.HopData; zeros(PayloadLength + MacLength)]) - let dataLength = PayloadLength + MacLength + MaxHops * (PayloadLength + MacLength) + let d = + Array.concat( + seq + [ + packet.HopData + zeros(PayloadLength + MacLength) + ] + ) + + let dataLength = + PayloadLength + + MacLength + + MaxHops * (PayloadLength + MacLength) + xor(d, generateStream(rho, dataLength)) - let payload = bin.[0..PayloadLength - 1] - let hmac = bin.[PayloadLength .. PayloadLength + MacLength - 1] |> uint256 - let nextRouteInfo = bin.[PayloadLength + MacLength..] + let payload = bin.[0 .. PayloadLength - 1] + + let hmac = + bin.[PayloadLength .. PayloadLength + MacLength - 1] + |> uint256 + + let nextRouteInfo = bin.[PayloadLength + MacLength ..] + let nextPubKey = use sharedSecret = new Key(ss) - blind publicKey (computeBlindingFactor publicKey sharedSecret) - { ParsedPacket.Payload = payload - NextPacket = { Version = VERSION; PublicKey = nextPubKey.ToBytes(); HMAC= hmac; HopData = nextRouteInfo } - SharedSecret = ss } |> Ok + + blind + publicKey + (computeBlindingFactor publicKey sharedSecret) + + { + ParsedPacket.Payload = payload + NextPacket = + { + Version = VERSION + PublicKey = nextPubKey.ToBytes() + HMAC = hmac + HopData = nextRouteInfo + } + SharedSecret = ss + } + |> Ok /// Compute the next packet from the current packet and node parameters. /// Packets are constructed in reverse order: @@ -146,135 +225,220 @@ module Sphinx = /// - then you call makeNextPacket(...) until you've build the final onion packet /// that will be sent to the first node let internal makeNextPacket - (payload: byte[], - ad: byte[], - ephemeralPubKey: PubKey, - sharedSecret: byte[], - packet: OnionPacket, - routingInfoFiller: byte[] option) = + ( + payload: array, + ad: array, + ephemeralPubKey: PubKey, + sharedSecret: array, + packet: OnionPacket, + routingInfoFiller: option> + ) = if (payload.Length <> PayloadLength) then failwithf "Payload length is not %A" PayloadLength else let filler = defaultArg routingInfoFiller ([||]) + let nextRoutingInfo = - let routingInfo1 = seq [ payload; packet.HMAC.ToBytes(); (packet.HopData |> Array.skipBack(PayloadLength + MacLength)) ] - |> Array.concat + let routingInfo1 = + seq + [ + payload + packet.HMAC.ToBytes() + (packet.HopData + |> Array.skipBack(PayloadLength + MacLength)) + ] + |> Array.concat + let routingInfo2 = let rho = generateKey("rho", sharedSecret) let numHops = MaxHops * (PayloadLength + MacLength) xor(routingInfo1, generateStream(rho, numHops)) - Array.append (routingInfo2 |> Array.skipBack filler.Length) filler - - let nextHmac = + Array.append + (routingInfo2 |> Array.skipBack filler.Length) + filler + + let nextHmac = let macKey = generateKey("mu", sharedSecret) let macMsg = (Array.append nextRoutingInfo ad) mac(macKey, macMsg) - let nextPacket ={ OnionPacket.Version = VERSION - PublicKey = ephemeralPubKey.ToBytes() - HopData = nextRoutingInfo - HMAC = nextHmac } + + let nextPacket = + { + OnionPacket.Version = VERSION + PublicKey = ephemeralPubKey.ToBytes() + HopData = nextRoutingInfo + HMAC = nextHmac + } + nextPacket - type PacketAndSecrets = { - Packet: OnionPacket - /// Shared secrets (one per node in the route). Known (and needed) only if you're creating the - /// packet. Empty if you're just forwarding the packet to the next node - SharedSecrets: (Key * PubKey) list - } - 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 lastPacket = makeNextPacket(payloads |> List.last, - ad, - ephemeralPubKeys |> List.last, - (sharedSecrets |> List.last |> fun ss -> ss.ToBytes()), - OnionPacket.LastPacket, - Some(filler)) - let rec loop (hopPayloads: byte[] list, ephKeys: PubKey list, ss: Key list, packet: OnionPacket) = - if (hopPayloads.IsEmpty) then - packet - else - let nextPacket = makeNextPacket(hopPayloads |> List.last, - ad, - ephKeys |> List.last, - (ss |> List.last |> fun (s: Key) -> s.ToBytes()), - packet, - None) - loop (hopPayloads.[0..hopPayloads.Length - 2], ephKeys.[0..ephKeys.Length - 2], ss.[0..ss.Length - 2], nextPacket) - let p = loop (payloads.[0..payloads.Length - 2], ephemeralPubKeys.[0..ephemeralPubKeys.Length - 2], sharedSecrets.[0..sharedSecrets.Length - 2], lastPacket) - { PacketAndSecrets.Packet = p; SharedSecrets = List.zip sharedSecrets pubKeys } - - let [] MAX_ERROR_PAYLOAD_LENGTH = 256 - let [] ERROR_PACKET_LENGTH = 292 // MacLength + MAX_ERROR_PAYLOAD_LENGTH + 2 + 2 - - let forwardErrorPacket (packet: byte[], ss: byte[]) = - assert(packet.Length = ERROR_PACKET_LENGTH) + type PacketAndSecrets = + { + Packet: OnionPacket + /// Shared secrets (one per node in the route). Known (and needed) only if you're creating the + /// packet. Empty if you're just forwarding the packet to the next node + SharedSecrets: list<(Key * PubKey)> + } + + static member Create + ( + sessionKey: Key, + pubKeys: list, + payloads: list>, + ad: array + ) = + let (ephemeralPubKeys, sharedSecrets) = + computeEphemeralPublicKeysAndSharedSecrets + (sessionKey) + (pubKeys) + + let filler = + generateFiller + "rho" + sharedSecrets.[0 .. sharedSecrets.Length - 2] + (PayloadLength + MacLength) + (Some MaxHops) + + let lastPacket = + makeNextPacket( + payloads |> List.last, + ad, + ephemeralPubKeys |> List.last, + (sharedSecrets |> List.last |> (fun ss -> ss.ToBytes())), + OnionPacket.LastPacket, + Some(filler) + ) + + let rec loop + ( + hopPayloads: list>, + ephKeys: list, + ss: list, + packet: OnionPacket + ) = + if hopPayloads.IsEmpty then + packet + else + let nextPacket = + makeNextPacket( + hopPayloads |> List.last, + ad, + ephKeys |> List.last, + (ss |> List.last |> (fun (s: Key) -> s.ToBytes())), + packet, + None + ) + + loop( + hopPayloads.[0 .. hopPayloads.Length - 2], + ephKeys.[0 .. ephKeys.Length - 2], + ss.[0 .. ss.Length - 2], + nextPacket + ) + + let p = + loop( + payloads.[0 .. payloads.Length - 2], + ephemeralPubKeys.[0 .. ephemeralPubKeys.Length - 2], + sharedSecrets.[0 .. sharedSecrets.Length - 2], + lastPacket + ) + + { + PacketAndSecrets.Packet = p + SharedSecrets = List.zip sharedSecrets pubKeys + } + + [] + let MAX_ERROR_PAYLOAD_LENGTH = 256 + + [] + let ERROR_PACKET_LENGTH = 292 // MacLength + MAX_ERROR_PAYLOAD_LENGTH + 2 + 2 + + let forwardErrorPacket(packet: array, ss: array) = + assert (packet.Length = ERROR_PACKET_LENGTH) let k = generateKey("ammag", ss) let s = generateStream(k, ERROR_PACKET_LENGTH) xor(packet, s) - let private checkMac(ss: byte[], packet: byte[]): bool = + let private checkMac(ss: array, packet: array) : bool = let (macV, payload) = packet |> Array.splitAt(MacLength) let um = generateKey("um", ss) (macV |> uint256) = mac(um, payload) - let private extractFailureMessage (packet: byte[]) = + let private extractFailureMessage(packet: array) = if (packet.Length <> ERROR_PACKET_LENGTH) then InvalidErrorPacketLength(ERROR_PACKET_LENGTH, packet.Length) |> Error else let (_mac, payload) = packet |> Array.splitAt(MacLength) let len = Utils.ToUInt16(payload.[0..1], false) |> int + if (len < 0 || (len > MAX_ERROR_PAYLOAD_LENGTH)) then - InvalidMessageLength len - |> Error + InvalidMessageLength len |> Error else - let msg = payload.[2..2 + len - 1] + let msg = payload.[2 .. 2 + len - 1] ILightningSerializable.fromBytes(msg) |> Ok - type ErrorPacket = { - OriginNode: NodeId - FailureMsg: FailureMsg - } - with - static member Create (ss: byte[], msg: FailureMsg) = - let msgB = msg.ToBytes() - assert (msgB.Length <= MAX_ERROR_PAYLOAD_LENGTH) - let um = generateKey("um", ss) - let padLen = MAX_ERROR_PAYLOAD_LENGTH - msgB.Length - let payload = - use ms = new System.IO.MemoryStream() - use st = new LightningWriterStream(ms) - st.Write(uint16 msgB.Length, false) - st.Write(msgB) - st.Write(uint16 padLen, false) - st.Write(zeros padLen) - ms.ToArray() - forwardErrorPacket(Array.append (mac(um, payload).ToBytes()) payload, ss) - - static member TryParse(packet: byte[], ss: (Key * PubKey) list) = - let ssB = ss |> List.map(fun (k, pk) -> (k.ToBytes(), pk)) - ErrorPacket.TryParse(packet, ssB) - - static member TryParse(packet: byte[], ss: (byte[] * PubKey) list): Result = - if (packet.Length <> ERROR_PACKET_LENGTH) then - InvalidErrorPacketLength (ERROR_PACKET_LENGTH, packet.Length) |> Error - else - let rec loop (packet: byte[], ss: (byte[] * PubKey) list) = - match ss with - | [] -> - FailedToParseErrorPacket (packet, ss) - |> Error - | (secret, pk)::tail -> - let packet1 = forwardErrorPacket(packet, secret) - if ((checkMac(secret, packet1))) then - extractFailureMessage packet1 - >>= fun msg -> - { OriginNode = pk |> NodeId - FailureMsg = msg } - |> Ok - else - loop (packet1, tail) - loop(packet, ss) + + type ErrorPacket = + { + OriginNode: NodeId + FailureMsg: FailureMsg + } + + static member Create(ss: array, msg: FailureMsg) = + let msgB = msg.ToBytes() + assert (msgB.Length <= MAX_ERROR_PAYLOAD_LENGTH) + let um = generateKey("um", ss) + let padLen = MAX_ERROR_PAYLOAD_LENGTH - msgB.Length + + let payload = + use ms = new System.IO.MemoryStream() + use st = new LightningWriterStream(ms) + st.Write(uint16 msgB.Length, false) + st.Write(msgB) + st.Write(uint16 padLen, false) + st.Write(zeros padLen) + ms.ToArray() + + forwardErrorPacket( + Array.append (mac(um, payload).ToBytes()) payload, + ss + ) + + static member TryParse(packet: array, ss: list<(Key * PubKey)>) = + let ssB = ss |> List.map(fun (k, pk) -> (k.ToBytes(), pk)) + ErrorPacket.TryParse(packet, ssB) + + static member TryParse + ( + packet: array, + ss: list<(array * PubKey)> + ) : Result = + if (packet.Length <> ERROR_PACKET_LENGTH) then + InvalidErrorPacketLength(ERROR_PACKET_LENGTH, packet.Length) + |> Error + else + let rec loop + ( + packet: array, + ss: list<(array * PubKey)> + ) = + match ss with + | [] -> FailedToParseErrorPacket(packet, ss) |> Error + | (secret, pk) :: tail -> + let packet1 = forwardErrorPacket(packet, secret) + + if ((checkMac(secret, packet1))) then + extractFailureMessage packet1 + >>= fun msg -> + { + OriginNode = pk |> NodeId + FailureMsg = msg + } + |> Ok + else + loop(packet1, tail) + + loop(packet, ss) diff --git a/src/DotNetLightning.Core/Payment/Amount.fs b/src/DotNetLightning.Core/Payment/Amount.fs index 309f2f62d..6b3a61530 100644 --- a/src/DotNetLightning.Core/Payment/Amount.fs +++ b/src/DotNetLightning.Core/Payment/Amount.fs @@ -8,39 +8,44 @@ open ResultUtils.Portability [] module Amount = - let unit (amount: LNMoney): char = + let unit(amount: LNMoney) : char = match amount.MilliSatoshi * 10L with | pico when pico % 1000L > 0L -> 'p' | nano when nano % 1000000L > 0L -> 'n' | micro when micro % 1000000000L > 0L -> 'u' | _ -> 'm' - - let decode (input: string): Result = + + let decode(input: string) : Result = let parseCore (a: string) multi = match a |> Int64.TryParse with - | true, aValue -> - LNMoney.MilliSatoshis(aValue * multi) |> Ok + | true, aValue -> LNMoney.MilliSatoshis(aValue * multi) |> Ok | false, _ -> sprintf "Could not parse %s into Int64" a |> Error + match input with | "" -> Error "Empty input" | a when a.[a.Length - 1] = 'p' -> (parseCore (a.Substring(0, a.Length - 1)) 1L) // 1 milli-satoshis == 10 pico-bitcoin, so we must divide it here. - |> Result.map(fun lnMoney -> (lnMoney.MilliSatoshi / 10L) |> LNMoney.MilliSatoshis) + |> Result.map(fun lnMoney -> + (lnMoney.MilliSatoshi / 10L) |> LNMoney.MilliSatoshis + ) | a when a.[a.Length - 1] = 'n' -> parseCore (a.Substring(0, a.Length - 1)) 100L | a when a.[a.Length - 1] = 'u' -> parseCore (a.Substring(0, a.Length - 1)) 100000L | a when a.[a.Length - 1] = 'm' -> parseCore (a.Substring(0, a.Length - 1)) 100000000L - | a -> - parseCore a 100000000000L - - let encode (amount: LNMoney option) = + | a -> parseCore a 100000000000L + + let encode(amount: option) = match amount with | None -> "" - | Some(amt) when unit(amt) = 'p' -> sprintf "%dp" (amt.MilliSatoshi * 10L) - | Some(amt) when unit(amt) = 'n' -> sprintf "%dn" (amt.MilliSatoshi / 100L ) - | Some(amt) when unit(amt) = 'u' -> sprintf "%du" (amt.MilliSatoshi / 100000L) - | Some(amt) when unit(amt) = 'm' -> sprintf "%dm" (amt.MilliSatoshi / 100000000L) + | Some amt when unit(amt) = 'p' -> + sprintf "%ip" (amt.MilliSatoshi * 10L) + | Some amt when unit(amt) = 'n' -> + sprintf "%in" (amt.MilliSatoshi / 100L) + | Some amt when unit(amt) = 'u' -> + sprintf "%iu" (amt.MilliSatoshi / 100000L) + | Some amt when unit(amt) = 'm' -> + sprintf "%im" (amt.MilliSatoshi / 100000000L) | x -> failwithf "Unreachable! %A" x diff --git a/src/DotNetLightning.Core/Payment/LSAT/CaveatsExtensions.fs b/src/DotNetLightning.Core/Payment/LSAT/CaveatsExtensions.fs index 199a4aaa6..82b066358 100644 --- a/src/DotNetLightning.Core/Payment/LSAT/CaveatsExtensions.fs +++ b/src/DotNetLightning.Core/Payment/LSAT/CaveatsExtensions.fs @@ -6,20 +6,25 @@ open System.Runtime.CompilerServices open ResultUtils open ResultUtils.Portability -[] +[] type CaveatsExtensions() = - + /// 'Value' is right hand of '=' in caveats, if it does not contain '=', it will return Error [] static member TryGetValue(caveat: Caveat) = let s = caveat.ToString().Split('=') - if (s.Length <> 2) then Error(sprintf "invalid caveat for lsat %s" (caveat.ToString())) else - Ok(s.[1].Trim()) - + + if (s.Length <> 2) then + Error(sprintf "invalid caveat for lsat %s" (caveat.ToString())) + else + Ok(s.[1].Trim()) + /// 'Condition' is left hand of '=' in caveats, if it does not contain '=', it will return Error [] static member TryGetCondition(caveat: Caveat) = let s = caveat.ToString().Split('=') - if (s.Length <> 2) then Error(sprintf "invalid caveat for lsat %s" (caveat.ToString())) else - Ok(s.[0].Trim()) + if (s.Length <> 2) then + Error(sprintf "invalid caveat for lsat %s" (caveat.ToString())) + else + Ok(s.[0].Trim()) diff --git a/src/DotNetLightning.Core/Payment/LSAT/Constants.fs b/src/DotNetLightning.Core/Payment/LSAT/Constants.fs index 8fdece164..074ca0f0d 100644 --- a/src/DotNetLightning.Core/Payment/LSAT/Constants.fs +++ b/src/DotNetLightning.Core/Payment/LSAT/Constants.fs @@ -2,4 +2,4 @@ namespace DotNetLightning.Payment.LSAT module Constants = [] - let CAPABILITIES_CONDITION_PREFIX = "_capabilities" + let CAPABILITIES_CONDITION_PREFIX = "_capabilities" diff --git a/src/DotNetLightning.Core/Payment/LSAT/MacaroonIdentifier.fs b/src/DotNetLightning.Core/Payment/LSAT/MacaroonIdentifier.fs index 3bedb85b5..6f43142dc 100644 --- a/src/DotNetLightning.Core/Payment/LSAT/MacaroonIdentifier.fs +++ b/src/DotNetLightning.Core/Payment/LSAT/MacaroonIdentifier.fs @@ -11,13 +11,14 @@ open ResultUtils open ResultUtils.Portability module private Helpers = - let hex = DataEncoders.HexEncoder() - -type MacaroonIdentifierV0 = { - PaymentHash: PaymentHash - TokenId: uint256 -} - with + let hex = DataEncoders.HexEncoder() + +type MacaroonIdentifierV0 = + { + PaymentHash: PaymentHash + TokenId: uint256 + } + static member Create(p: PaymentHash) = { PaymentHash = p @@ -27,26 +28,31 @@ type MacaroonIdentifierV0 = { /// see ref: https://github.com/lightninglabs/LSAT/blob/master/macaroons.md#macaroon-identifier type MacaroonIdentifier = | V0 of MacaroonIdentifierV0 - | UnknownVersion of version: uint16 * contents: byte[] - with + | UnknownVersion of version: uint16 * contents: array + static member CreateV0 hash = MacaroonIdentifierV0.Create hash |> V0 - static member TryCreateFromBytes(b: byte[]) = - let e = Error(sprintf "Invalid bytes for macaroon identifier %A" b) - if (b.Length < 2) then e else - match UInt16.FromBytesBigEndian(b.[0..1]) with - | 0us -> - if (b.Length <> 2 + 32 + 32) then e else - { - PaymentHash = PaymentHash.PaymentHash(uint256(b.[2..33], false)) - TokenId = uint256(b.[34..], false) - } - |> V0 - |> Ok - | x -> - UnknownVersion(x, b.[2..]) - |> Ok - + + static member TryCreateFromBytes(b: array) = + let e = Error(sprintf "Invalid bytes for macaroon identifier %A" b) + + if (b.Length < 2) then + e + else + match UInt16.FromBytesBigEndian(b.[0..1]) with + | 0us -> + if (b.Length <> 2 + 32 + 32) then + e + else + { + PaymentHash = + PaymentHash.PaymentHash(uint256(b.[2..33], false)) + TokenId = uint256(b.[34..], false) + } + |> V0 + |> Ok + | x -> UnknownVersion(x, b.[2..]) |> Ok + member this.ToBytes() = match this with | V0 i -> @@ -55,15 +61,22 @@ type MacaroonIdentifier = Array.blit (i.PaymentHash.ToBytes(false)) 0 r 2 32 Array.blit (i.TokenId.ToBytes(false)) 0 r 34 32 r - | UnknownVersion (v, bytes) -> - Array.concat (seq { yield v.GetBytesBigEndian(); yield bytes } ) - + | UnknownVersion(v, bytes) -> + Array.concat( + seq { + yield v.GetBytesBigEndian() + yield bytes + } + ) + static member TryParse(str: string) = str |> Helpers.hex.DecodeData |> MacaroonIdentifier.TryCreateFromBytes + member this.ToHex() = this.ToBytes() |> Helpers.hex.EncodeData - + member this.Hash = - this.ToBytes() |> Hashes.SHA256 |> fun x -> uint256(x, false) - override this.ToString() = this.ToHex() - + this.ToBytes() |> Hashes.SHA256 |> (fun x -> uint256(x, false)) + + override this.ToString() = + this.ToHex() diff --git a/src/DotNetLightning.Core/Payment/LSAT/Satisfier.fs b/src/DotNetLightning.Core/Payment/LSAT/Satisfier.fs index e706544c4..2abe09ef7 100644 --- a/src/DotNetLightning.Core/Payment/LSAT/Satisfier.fs +++ b/src/DotNetLightning.Core/Payment/LSAT/Satisfier.fs @@ -18,92 +18,142 @@ open ResultUtils.Portability type ISatisfier = /// This is the left side of caveat equation. e.g. for caveat "service=my_awesome_service", it is "service" abstract Condition: string + /// ensures a caveat is in accordance with a previous one with the same condition. This is needed since caveats /// of the same condition can be used multiple times as long as they enforce more permissions than the previous. /// /// For example, we have a caveat that only allows us to use an LSAT for 7 more days. we can add another caveat /// that only allows for 3 more days of use and lend it to another party. abstract SatisfyPrevious: Caveat * Caveat -> Result + /// Satisfies the final caveat of an LSAT. If multiple caveats with the same condition exist, this will only /// be executed once all previous caveats are also satisfied. abstract SatisfyFinal: Caveat -> Result type ServiceSatisfier(targetService: string) = - do - checkNull "targetService" targetService - + do checkNull "targetService" targetService + interface ISatisfier with member this.Condition = "service" + member this.SatisfyPrevious(prev, curr) = result { let! prevServiceValue = prev.TryGetValue() - let! prevServices = Service.ParseMany(prevServiceValue.ToString()) + + let! prevServices = + Service.ParseMany(prevServiceValue.ToString()) + let prevServices = prevServices |> HashSet - + let! currentServiceValue = curr.TryGetValue() - let! currentServices = Service.ParseMany(currentServiceValue.ToString()) + + let! currentServices = + Service.ParseMany(currentServiceValue.ToString()) + for s in currentServices do if not <| prevServices.Contains(s) then - return! Error(sprintf "Service (%s) was not previously allowed!" s.Name) + return! + Error( + sprintf + "Service (%s) was not previously allowed!" + s.Name + ) else () } - member this.SatisfyFinal(finalCaveat) = + + member this.SatisfyFinal finalCaveat = result { let! serviceValue = finalCaveat.TryGetValue() let! services = Service.ParseMany(serviceValue.ToString()) + if services |> Seq.exists(fun s -> s.Name = targetService) then return () else - return! Error(sprintf "Target service %s not found" targetService) + return! + Error( + sprintf "Target service %s not found" targetService + ) } - + type CapabilitiesSatisfier(service: string, targetCapability: string) = do checkNull "service" service checkNull "targetCapability" targetCapability - member val Condition = sprintf "%s%s" service Constants.CAPABILITIES_CONDITION_PREFIX with get - + + member val Condition = + sprintf "%s%s" service Constants.CAPABILITIES_CONDITION_PREFIX + interface ISatisfier with member this.Condition = this.Condition + member this.SatisfyPrevious(prev, curr) = result { let! prevValue = prev.TryGetValue() let prevCapabilities = prevValue.Split ',' |> HashSet let! currentValue = curr.TryGetValue() let currentCapabilities = currentValue.Split ',' + for c in currentCapabilities do if (not <| prevCapabilities.Contains(c)) then - return! Error(sprintf "Capability (%A) was not previously allowed!" c) + return! + Error( + sprintf + "Capability (%A) was not previously allowed!" + c + ) + () } - member this.SatisfyFinal(finalCaveat) = + + member this.SatisfyFinal finalCaveat = result { let! caps = finalCaveat.TryGetValue() let caps = caps.Split ',' - if (caps |> Seq.exists((=)targetCapability)) then + + if (caps |> Seq.exists((=) targetCapability)) then return () - else return! Error(sprintf "target capability (%A) not found" targetCapability) + else + return! + Error( + sprintf + "target capability (%A) not found" + targetCapability + ) } - -[] + +[] type MacaroonExtensions() = /// Verifies that the macaroon is compliant with LSAT. [] - static member VerifyLSATCaveats(macaroon: Macaroon, satisfiers: IList, secret: string): VerificationResult = + static member VerifyLSATCaveats + ( + macaroon: Macaroon, + satisfiers: IList, + secret: string + ) : VerificationResult = macaroon.VerifyLSATCaveats(macaroon.Caveats, satisfiers, secret) - + /// Verifies that the macaroon is compliant with LSAT. [] - static member VerifyLSATCaveats(macaroon: Macaroon, caveats: IList, satisfiers: IList, secret: string): VerificationResult = + static member VerifyLSATCaveats + ( + macaroon: Macaroon, + caveats: IList, + satisfiers: IList, + secret: string + ) : VerificationResult = result { let caveatSatisfiers = Dictionary() + for s in satisfiers do caveatSatisfiers.AddOrReplace(s.Condition, s) - + let relevantCaveats = Dictionary>() + for c in caveats do let! condition = c.TryGetCondition() + if (caveatSatisfiers.ContainsKey(condition)) then match relevantCaveats.TryGetValue(condition) with | true, caveatsSoFar -> @@ -113,23 +163,25 @@ type MacaroonExtensions() = let cs = ResizeArray() cs.Add(c) relevantCaveats.Add(condition, cs) - + for kv in relevantCaveats do let condition, caveats = kv.Key, kv.Value let s = caveatSatisfiers.[condition] - for i in 0..(caveats.Count - 2) do + + for i in 0 .. (caveats.Count - 2) do let prev = caveats.[i] let curr = caveats.[i + 1] do! s.SatisfyPrevious(prev, curr) + do! s.SatisfyFinal(caveats |> Seq.last) } - |> - function + |> function | Ok _ -> // finally check each caveats independently let v = Verifier() + for c in caveats do v.SatisfyExact(c.CId) + macaroon.Verify(v, secret) - | Error e -> - VerificationResult(e) + | Error e -> VerificationResult(e) diff --git a/src/DotNetLightning.Core/Payment/LSAT/Service.fs b/src/DotNetLightning.Core/Payment/LSAT/Service.fs index 0e69770dc..87b39644f 100644 --- a/src/DotNetLightning.Core/Payment/LSAT/Service.fs +++ b/src/DotNetLightning.Core/Payment/LSAT/Service.fs @@ -12,44 +12,79 @@ open ResultUtils open ResultUtils.Portability /// See: https://github.com/lightninglabs/LSAT/blob/master/macaroons.md#target-services -type Service = { - Name: string - ServiceTier: uint8 - Price: LNMoney option -} - with +type Service = + { + Name: string + ServiceTier: uint8 + Price: option + } + static member Create(name, tier) = { Name = name ServiceTier = tier Price = None } + static member ParseMany(s: string) = - if (String.IsNullOrEmpty(s)) then Error("empty service") else - let rawServices = s.Split ',' - result { - let mutable res = ResizeArray() - for rawService in rawServices do - let serviceInfo = rawService.Split ':' - if serviceInfo.Length <> 2 then return! Error(sprintf "Invalid value for service %A" serviceInfo) else - let (name, tier) = serviceInfo.[0], serviceInfo.[1] - let! t = - match Byte.TryParse tier with - | true, t -> Ok t - | false, _ -> Error(sprintf "invalid service caveat value %s Service tier must be uint8 (%s)" s tier) - res.Add(Service.Create(name, t)) - return res - } - - static member EncodeToCaveat(services: IList): Result = + if (String.IsNullOrEmpty(s)) then + Error("empty service") + else + let rawServices = s.Split ',' + + result { + let mutable res = ResizeArray() + + for rawService in rawServices do + let serviceInfo = rawService.Split ':' + + if serviceInfo.Length <> 2 then + return! + Error( + sprintf + "Invalid value for service %A" + serviceInfo + ) + else + let (name, tier) = serviceInfo.[0], serviceInfo.[1] + + let! t = + match Byte.TryParse tier with + | true, t -> Ok t + | false, _ -> + Error( + sprintf + "invalid service caveat value %s Service tier must be uint8 (%s)" + s + tier + ) + + res.Add(Service.Create(name, t)) + + return res + } + + static member EncodeToCaveat(services: IList) : Result = result { - if (services.Count = 0) then return! Error("empty service") else - if (String.IsNullOrEmpty services.[0].Name) then return! Error ("Missing service name") else - let sb = StringBuilder() - sb.Append("services=") |> ignore - sb.Append(sprintf "%s:%d" services.[0].Name services.[0].ServiceTier) |> ignore - for s in services do - if (String.IsNullOrEmpty s.Name) then return! Error ("Missing service name") else - sb.Append(sprintf ",%s:%d" s.Name s.ServiceTier) |> ignore - return sb.ToString() |> Caveat + if (services.Count = 0) then + return! Error("empty service") + else if (String.IsNullOrEmpty services.[0].Name) then + return! Error("Missing service name") + else + let sb = StringBuilder() + sb.Append("services=") |> ignore + + sb.Append( + sprintf "%s:%i" services.[0].Name services.[0].ServiceTier + ) + |> ignore + + for s in services do + if (String.IsNullOrEmpty s.Name) then + return! Error("Missing service name") + else + sb.Append(sprintf ",%s:%i" s.Name s.ServiceTier) + |> ignore + + return sb.ToString() |> Caveat } diff --git a/src/DotNetLightning.Core/Payment/PaymentEvents.fs b/src/DotNetLightning.Core/Payment/PaymentEvents.fs index 1f4989b6d..a878d1972 100644 --- a/src/DotNetLightning.Core/Payment/PaymentEvents.fs +++ b/src/DotNetLightning.Core/Payment/PaymentEvents.fs @@ -11,10 +11,10 @@ type PaymentFailure = /// A failure happened locally, preventing the payment from being sent (e.g. no route found) | LocalFailure of ChannelError /// A remote node failed the payment and we were able to decrypt the onion failure packet - | RemoteFailure of route: RouteHop list * err: Sphinx.ErrorPacket + | RemoteFailure of route: list * err: Sphinx.ErrorPacket /// A remote node failed the payment but we couldn't decrypt the failure /// (e.g. a malicious node tampered with the message) - | UnreadableRemoteFailure of route: RouteHop list + | UnreadableRemoteFailure of route: list type PaymentEvent = | PaymentSent of PaymentSentEvent @@ -22,64 +22,80 @@ type PaymentEvent = | PaymentRelayed of PaymentRelayedEvent | PaymentReceived of PaymentReceivedEvent | PaymentSettlingOnChain of PaymentSettlingOnChainEvent -and PaymentSentEvent = { - Id: PaymentId - PaymentHash: PaymentHash - PaymentPreimage: PaymentPreimage - Timestamp: DateTimeOffset - Amount: LNMoney - Parts: PartialPayment - FeesPaid: LNMoney -} -and PartialPayment = { - Id: PaymentId - Amount: LNMoney - FeesPaid: LNMoney - ToChannelId: ChannelId - Route: RouteHop list option - Timestamp: DateTimeOffset -} -and PaymentFailedEvent = { - Id: PaymentId - PaymentHash: PaymentHash - Failures: PaymentFailure list -} +and PaymentSentEvent = + { + Id: PaymentId + PaymentHash: PaymentHash + PaymentPreimage: PaymentPreimage + Timestamp: DateTimeOffset + Amount: LNMoney + Parts: PartialPayment + FeesPaid: LNMoney + } + +and PartialPayment = + { + Id: PaymentId + Amount: LNMoney + FeesPaid: LNMoney + ToChannelId: ChannelId + Route: option> + Timestamp: DateTimeOffset + } -and PaymentRelayedEvent = { - PaymentHash: PaymentHash - Timestamp: DateTimeOffset - AmountIn: LNMoney - AmountOut: LNMoney - FromChannelId: ChannelId - ToChannelId: ChannelId -} -and PaymentReceivedEvent = { - PartialPayment: ReceivedPartialPayment -} -and ReceivedPartialPayment = private { - Amount: LNMoney - FromChannelId: ChannelId - Timestamp: DateTimeOffset -} - with - static member Create(amount, fromChannelId) = { - Amount = amount - FromChannelId = fromChannelId - Timestamp = DateTimeOffset.Now +and PaymentFailedEvent = + { + Id: PaymentId + PaymentHash: PaymentHash + Failures: list } -and PaymentSettlingOnChainEvent = private { - Id: PaymentId - Amount: LNMoney - PaymentHash: PaymentHash - Timestamp: DateTimeOffset -} - with - static member Create(id, amount, paymentHash) = { - Id = id - Amount = amount - PaymentHash = paymentHash - Timestamp = DateTimeOffset.Now + +and PaymentRelayedEvent = + { + PaymentHash: PaymentHash + Timestamp: DateTimeOffset + AmountIn: LNMoney + AmountOut: LNMoney + FromChannelId: ChannelId + ToChannelId: ChannelId + } + +and PaymentReceivedEvent = + { + PartialPayment: ReceivedPartialPayment } - + +and ReceivedPartialPayment = + private + { + Amount: LNMoney + FromChannelId: ChannelId + Timestamp: DateTimeOffset + } + + static member Create(amount, fromChannelId) = + { + Amount = amount + FromChannelId = fromChannelId + Timestamp = DateTimeOffset.Now + } + +and PaymentSettlingOnChainEvent = + private + { + Id: PaymentId + Amount: LNMoney + PaymentHash: PaymentHash + Timestamp: DateTimeOffset + } + + static member Create(id, amount, paymentHash) = + { + Id = id + Amount = amount + PaymentHash = paymentHash + Timestamp = DateTimeOffset.Now + } + type OurPaymentResult = Result diff --git a/src/DotNetLightning.Core/Payment/PaymentRequest.fs b/src/DotNetLightning.Core/Payment/PaymentRequest.fs index 2175812aa..b86781aa5 100644 --- a/src/DotNetLightning.Core/Payment/PaymentRequest.fs +++ b/src/DotNetLightning.Core/Payment/PaymentRequest.fs @@ -27,30 +27,41 @@ module private Helpers = try base58check.DecodeData data |> Ok with - | :? FormatException as fex -> - fex.ToString() |> Error + | :? FormatException as fex -> fex.ToString() |> Error + let parseBitcoinAddress addr n = try BitcoinAddress.Create(addr, n) |> Ok with - | :? FormatException as fex -> - fex.ToString() |> Error + | :? FormatException as fex -> fex.ToString() |> Error + + let tryGetP2WPKHAddressEncoder(network: Network) = + let maybeEncoder = + network.GetBech32Encoder(Bech32Type.WITNESS_PUBKEY_ADDRESS, false) - let tryGetP2WPKHAddressEncoder (network: Network) = - let maybeEncoder = network.GetBech32Encoder(Bech32Type.WITNESS_PUBKEY_ADDRESS, false) - if isNull maybeEncoder then Error("Failed to get p2wpkh encoder") else Ok maybeEncoder + if isNull maybeEncoder then + Error("Failed to get p2wpkh encoder") + else + Ok maybeEncoder - let tryGetP2WSHAddressEncoder (network: Network) = - let maybeEncoder = network.GetBech32Encoder(Bech32Type.WITNESS_SCRIPT_ADDRESS, false) - if isNull maybeEncoder then Error("Failed to get p2wsh encoder") else Ok maybeEncoder + let tryGetP2WSHAddressEncoder(network: Network) = + let maybeEncoder = + network.GetBech32Encoder(Bech32Type.WITNESS_SCRIPT_ADDRESS, false) + + if isNull maybeEncoder then + Error("Failed to get p2wsh encoder") + else + Ok maybeEncoder let decodeBech32 s = try - InternalBech32Encoder.Instance.DecodeData(s).ToTuple() + InternalBech32Encoder + .Instance + .DecodeData(s) + .ToTuple() |> Ok with - | :? FormatException as fex -> - fex.ToString() |> Error + | :? FormatException as fex -> fex.ToString() |> Error let encodeBech32 hrp s = InternalBech32Encoder.Instance.EncodeData(hrp, s, 0, s.Length) @@ -78,14 +89,27 @@ module private Helpers = /// The values for prefix are sorted by its length, in this way we assure that we try to match the longest /// value first, so that e.g. when we have "lnbcrt" it will not match "lnbc" - let sortedPrefixValues = prefixes |> Map.toSeq |> Seq.map(fun (_, v) -> v) |> Seq.sortByDescending(fun x -> x.Length) - let checkAndGetPrefixFromHrp (hrp: string) = - let maybePrefix = sortedPrefixValues |> Seq.filter(fun p -> hrp.StartsWith(p)) |> Seq.tryHead + let sortedPrefixValues = + prefixes + |> Map.toSeq + |> Seq.map(fun (_, v) -> v) + |> Seq.sortByDescending(fun x -> x.Length) + + let checkAndGetPrefixFromHrp(hrp: string) = + let maybePrefix = + sortedPrefixValues + |> Seq.filter(fun p -> hrp.StartsWith(p)) + |> Seq.tryHead + match maybePrefix with | None -> - Error(sprintf "Unknown prefix type %s! hrp must be either of %A" hrp sortedPrefixValues) - | Some(prefix) -> - Ok(prefix) + Error( + sprintf + "Unknown prefix type %s! hrp must be either of %A" + hrp + sortedPrefixValues + ) + | Some prefix -> Ok(prefix) /// maxInvoiceLength is the maximum total length an invoice can have. /// This is chosen to be the maximum number of bytes that can fit into a @@ -93,24 +117,33 @@ module private Helpers = [] let maxInvoiceLength = 7089 - let checkMaxInvoiceLength (invoice: string) = + let checkMaxInvoiceLength(invoice: string) = if invoice.Length <= maxInvoiceLength then Ok() else - Error(sprintf "Invoice length too large! max size is %d but it was %d" maxInvoiceLength invoice.Length) + Error( + sprintf + "Invoice length too large! max size is %i but it was %i" + maxInvoiceLength + invoice.Length + ) + + let uint64ToBase32(num: uint64) : array = + if num = 0UL then + [||] + else - let uint64ToBase32(num: uint64):byte[] = - if num = 0UL then [||] else + let mutable numMutable = num + // To fit an uint64, we need at most is ceil (64 / 5) = 13 groups. + let arr = Array.zeroCreate 13 + let mutable i = 13 - let mutable numMutable = num - // To fit an uint64, we need at most is ceil (64 / 5) = 13 groups. - let arr = Array.zeroCreate 13 - let mutable i = 13 - while numMutable > 0UL do - i <- i - 1 - arr.[i] <- byte(numMutable &&& 0b11111UL) - numMutable <- numMutable >>> 5 - arr.[i..] // we only return non-zero leading groups + while numMutable > 0UL do + i <- i - 1 + arr.[i] <- byte(numMutable &&& 0b11111UL) + numMutable <- numMutable >>> 5 + + arr.[i..] // we only return non-zero leading groups let convertBits(data, fromBits, toBits, pad: bool) = InternalBech32Encoder.Instance.ConvertBits(data, fromBits, toBits, pad) @@ -124,77 +157,152 @@ module private Helpers = type IMessageSigner = /// take serialized msg hash and returns 65 bytes signature for it.(1byte header + 32 bytes r + 32 bytes s) /// The header byte must be (recovery id + 27uy + 4uy). - abstract member SignMessage: uint256 -> byte[] + abstract member SignMessage: uint256 -> array /// To make it network-agnostic, it holds data directly in bytes rather than `NBitcoin.BitcoinAddress` -type FallbackAddress = private { - Version: uint8 - Data: byte[] -} - with +type FallbackAddress = + private + { + Version: uint8 + Data: array + } + static member FromBase58Address(addr: string) = result { let! d = Helpers.base58CheckDecode(addr) let (prefix, addressHash) = d |> Array.splitAt(1) + match prefix.[0] with - | Helpers.PREFIX_ADDRESS_PUBKEYHASH | Helpers.PREFIX_ADDRESS_PUBKEYHASH_TESTNET -> - return { Version = 17uy; Data = addressHash } - | Helpers.PREFIX_ADDRESS_SCRIPTHASH | Helpers.PREFIX_ADDRESS_SCRIPTHASH_TESTNET -> - return { Version = 18uy; Data = addressHash } - | x -> - return! Error(sprintf "Unknown address prefix %d" x) + | Helpers.PREFIX_ADDRESS_PUBKEYHASH + | Helpers.PREFIX_ADDRESS_PUBKEYHASH_TESTNET -> + return + { + Version = 17uy + Data = addressHash + } + | Helpers.PREFIX_ADDRESS_SCRIPTHASH + | Helpers.PREFIX_ADDRESS_SCRIPTHASH_TESTNET -> + return + { + Version = 18uy + Data = addressHash + } + | x -> return! Error(sprintf "Unknown address prefix %i" x) } static member FromBech32Address(addrStr: string, network: Network) = - match Helpers.tryGetP2WPKHAddressEncoder network with + match Helpers.tryGetP2WPKHAddressEncoder network with + | Ok encoder -> + match encoder.Decode(addrStr) with + | decoded, witVersion -> + { + Version = witVersion + Data = decoded + } + |> Ok + | Error e -> + match Helpers.tryGetP2WSHAddressEncoder network with | Ok encoder -> match encoder.Decode(addrStr) with | decoded, witVersion -> - { Version = witVersion; Data = decoded } |> Ok - | Error e -> - match Helpers.tryGetP2WSHAddressEncoder network with - | Ok encoder -> - match encoder.Decode(addrStr) with - | decoded, witVersion -> { Version = witVersion; Data = decoded } |> Ok - | Error e2 -> - e + e2 |> Error + { + Version = witVersion + Data = decoded + } + |> Ok + | Error e2 -> e + e2 |> Error static member FromAddress(s: BitcoinAddress) = match s with - | :? BitcoinWitScriptAddress as p2wsh -> FallbackAddress.FromBech32Address(p2wsh.ToString(), s.Network) - | :? BitcoinWitPubKeyAddress as p2wpkh -> FallbackAddress.FromBech32Address(p2wpkh.ToString(), s.Network) - | :? BitcoinPubKeyAddress as p2pkh -> FallbackAddress.FromBase58Address(p2pkh.ToString()) - | :? BitcoinScriptAddress as p2pkh -> FallbackAddress.FromBase58Address(p2pkh.ToString()) + | :? BitcoinWitScriptAddress as p2wsh -> + FallbackAddress.FromBech32Address(p2wsh.ToString(), s.Network) + | :? BitcoinWitPubKeyAddress as p2wpkh -> + FallbackAddress.FromBech32Address(p2wpkh.ToString(), s.Network) + | :? BitcoinPubKeyAddress as p2pkh -> + FallbackAddress.FromBase58Address(p2pkh.ToString()) + | :? BitcoinScriptAddress as p2pkh -> + FallbackAddress.FromBase58Address(p2pkh.ToString()) | _ -> Error(sprintf "Unknown type of address: %s" (s.ToString())) - static member TryCreate(version, data: byte[]) = + static member TryCreate(version, data: array) = match version with // p2pkh | 17uy when data.Length = 20 -> - { Version = version; Data = data } |> Ok + { + Version = version + Data = data + } + |> Ok // p2sh | 18uy when data.Length = 20 -> - { Version = version; Data = data } |> Ok + { + Version = version + Data = data + } + |> Ok // p2wpkh or p2wsh | v when (data.Length = 20 || data.Length = 32) -> - { Version = v; Data = data } |> Ok + { + Version = v + Data = data + } + |> Ok | v -> - sprintf "Unknown combination of bitcoin address version and length! version %d. length: %d" v data.Length + sprintf + "Unknown combination of bitcoin address version and length! version %i. length: %i" + v + data.Length |> Error member this.ToAddress(prefix: string) = match this.Version with | 17uy when prefix = "lnbc" -> - let data = Array.concat (seq { yield [| Helpers.PREFIX_ADDRESS_PUBKEYHASH |]; yield this.Data }) + let data = + Array.concat( + seq { + yield [| Helpers.PREFIX_ADDRESS_PUBKEYHASH |] + yield this.Data + } + ) + Helpers.base58check.EncodeData(data) | 18uy when prefix = "lnbc" -> - let data = Array.concat (seq { yield [| Helpers.PREFIX_ADDRESS_SCRIPTHASH |]; yield this.Data }) + let data = + Array.concat( + seq { + yield [| Helpers.PREFIX_ADDRESS_SCRIPTHASH |] + yield this.Data + } + ) + Helpers.base58check.EncodeData(data) | 17uy when prefix = "lntb" || prefix = "lnbcrt" -> - let data = Array.concat (seq { yield [| Helpers.PREFIX_ADDRESS_PUBKEYHASH_TESTNET |]; yield this.Data }) + let data = + Array.concat( + seq { + yield + [| + Helpers.PREFIX_ADDRESS_PUBKEYHASH_TESTNET + |] + + yield this.Data + } + ) + Helpers.base58check.EncodeData(data) | 18uy when prefix = "lnbc" || prefix = "lnbcrt" -> - let data = Array.concat (seq { yield [| Helpers.PREFIX_ADDRESS_SCRIPTHASH_TESTNET |]; yield this.Data }) + let data = + Array.concat( + seq { + yield + [| + Helpers.PREFIX_ADDRESS_SCRIPTHASH_TESTNET + |] + + yield this.Data + } + ) + Helpers.base58check.EncodeData(data) | v when prefix = "lnbc" -> let encoder = Bech32Encoder(Encoders.ASCII.DecodeData("bc")) @@ -205,27 +313,40 @@ type FallbackAddress = private { | v when prefix = "lnbcrt" -> let encoder = Bech32Encoder(Encoders.ASCII.DecodeData("bcrt")) encoder.Encode(v, this.Data) - | v -> - failwithf "Unreachable! Unexpected version %A" v - -type ExtraHop = internal { - NodeId: NodeId - ShortChannelId: ShortChannelId - FeeBase: LNMoney - FeeProportionalMillionths: uint32 - CLTVExpiryDelta: BlockHeightOffset16 -} - with - static member Size = 264 + 64 + 32 + 32 + 16 - static member TryCreate(nodeId, shortChannelId, feeBase, feeProportionalMillionths, cltvExpiryDelta: BlockHeightOffset16) = - if cltvExpiryDelta.Value = 0us then Error ("CLTVExpiryDelta in ExtraHop should not be 0") else - Ok { - NodeId = nodeId - ShortChannelId = shortChannelId - FeeBase = feeBase - FeeProportionalMillionths = feeProportionalMillionths - CLTVExpiryDelta = cltvExpiryDelta + | v -> failwithf "Unreachable! Unexpected version %A" v + +type ExtraHop = + internal + { + NodeId: NodeId + ShortChannelId: ShortChannelId + FeeBase: LNMoney + FeeProportionalMillionths: uint32 + CLTVExpiryDelta: BlockHeightOffset16 } + + static member Size = 264 + 64 + 32 + 32 + 16 + + static member TryCreate + ( + nodeId, + shortChannelId, + feeBase, + feeProportionalMillionths, + cltvExpiryDelta: BlockHeightOffset16 + ) = + if cltvExpiryDelta.Value = 0us then + Error("CLTVExpiryDelta in ExtraHop should not be 0") + else + Ok + { + NodeId = nodeId + ShortChannelId = shortChannelId + FeeBase = feeBase + FeeProportionalMillionths = feeProportionalMillionths + CLTVExpiryDelta = cltvExpiryDelta + } + member this.NodeIdValue = this.NodeId member this.ShortChannelIdValue = this.ShortChannelId member this.FeeBaseValue = this.FeeBase @@ -241,11 +362,11 @@ type TaggedField = /// long description, an invoice, | DescriptionHashTaggedField of uint256 | FallbackAddressTaggedField of FallbackAddress - | RoutingInfoTaggedField of ExtraHop list + | RoutingInfoTaggedField of list | ExpiryTaggedField of DateTimeOffset | MinFinalCltvExpiryTaggedField of BlockHeightOffset32 | FeaturesTaggedField of FeatureBits - with + member this.Type = match this with | PaymentHashTaggedField _ -> 1uy @@ -259,16 +380,22 @@ type TaggedField = | RoutingInfoTaggedField _ -> 3uy | FeaturesTaggedField _ -> 5uy - member private this.WriteField(writer: BinaryWriter, data: byte[]) = - let mutable dataLengthInBase32 = Helpers.uint64ToBase32(data.Length |> uint64) + member private this.WriteField(writer: BinaryWriter, data: array) = + let mutable dataLengthInBase32 = + Helpers.uint64ToBase32(data.Length |> uint64) // data length must be exactly 10 bits if (dataLengthInBase32.Length < 2) then - dataLengthInBase32 <- Array.append ([|0uy|]) dataLengthInBase32 + dataLengthInBase32 <- Array.append ([| 0uy |]) dataLengthInBase32 if (dataLengthInBase32.Length <> 2) then - raise <| FormatException (sprintf "data length too-big to fit in 10 bits. It was %d" dataLengthInBase32.Length) + raise + <| FormatException( + sprintf + "data length too-big to fit in 10 bits. It was %i" + dataLengthInBase32.Length + ) - writer.Write([|this.Type|]) + writer.Write([| this.Type |]) writer.Write(dataLengthInBase32) writer.Write(data) @@ -276,7 +403,7 @@ type TaggedField = match this with | PaymentHashTaggedField p -> this.WriteField(writer, Helpers.convert8BitsTo5(p.ToBytes())) - | PaymentSecretTaggedField p -> + | PaymentSecretTaggedField p -> this.WriteField(writer, Helpers.convert8BitsTo5(p.ToBytes(false))) | DescriptionTaggedField d -> let dBase32 = d |> Helpers.utf8.GetBytes |> Helpers.convert8BitsTo5 @@ -287,195 +414,500 @@ type TaggedField = | NodeIdTaggedField(NodeId pk) -> let dBase32 = pk.ToBytes() |> Helpers.convert8BitsTo5 this.WriteField(writer, dBase32) - | MinFinalCltvExpiryTaggedField (BlockHeightOffset32 c) -> + | MinFinalCltvExpiryTaggedField(BlockHeightOffset32 c) -> let dBase32 = c |> uint64 |> Helpers.uint64ToBase32 this.WriteField(writer, dBase32) | ExpiryTaggedField x -> - let dBase32 = ((x.ToUnixTimeSeconds() |> uint64) - timestamp) |> Helpers.uint64ToBase32 + let dBase32 = + ((x.ToUnixTimeSeconds() |> uint64) - timestamp) + |> Helpers.uint64ToBase32 + this.WriteField(writer, dBase32) | FallbackAddressTaggedField f -> let d = let dBase32 = f.Data |> Helpers.convert8BitsTo5 - Array.append [|f.Version|] dBase32 + Array.append [| f.Version |] dBase32 + this.WriteField(writer, d) | RoutingInfoTaggedField r -> - let routeInfoBase256 = ResizeArray() + let routeInfoBase256 = ResizeArray() + for hopInfo in r do - let hopInfoBase256 = Array.zeroCreate (51) // 51 is the number of bytes needed to encode the single route - Array.blit (hopInfo.NodeId.Value.ToBytes()) 0 hopInfoBase256 0 33 - Array.blit (hopInfo.ShortChannelId.ToBytes()) 0 hopInfoBase256 33 8 - Array.blit ((hopInfo.FeeBase.MilliSatoshi |> uint32).GetBytesBigEndian()) 0 hopInfoBase256 41 4 - Array.blit ((hopInfo.FeeProportionalMillionths |> uint32).GetBytesBigEndian()) 0 hopInfoBase256 45 4 - Array.blit (hopInfo.CLTVExpiryDelta.Value.GetBytesBigEndian()) 0 hopInfoBase256 49 2 + let hopInfoBase256 = Array.zeroCreate(51) // 51 is the number of bytes needed to encode the single route + + Array.blit + (hopInfo.NodeId.Value.ToBytes()) + 0 + hopInfoBase256 + 0 + 33 + + Array.blit + (hopInfo.ShortChannelId.ToBytes()) + 0 + hopInfoBase256 + 33 + 8 + + Array.blit + ((hopInfo.FeeBase.MilliSatoshi |> uint32) + .GetBytesBigEndian()) + 0 + hopInfoBase256 + 41 + 4 + + Array.blit + ((hopInfo.FeeProportionalMillionths |> uint32) + .GetBytesBigEndian()) + 0 + hopInfoBase256 + 45 + 4 + + Array.blit + (hopInfo.CLTVExpiryDelta.Value.GetBytesBigEndian()) + 0 + hopInfoBase256 + 49 + 2 + routeInfoBase256.Add(hopInfoBase256) - let routeInfoBase32 = routeInfoBase256 |> Array.concat |> Helpers.convert8BitsTo5 + + let routeInfoBase32 = + routeInfoBase256 |> Array.concat |> Helpers.convert8BitsTo5 + this.WriteField(writer, routeInfoBase32) | FeaturesTaggedField f -> let dBase32 = let mutable byteArray = f.BitArray.ToByteArray() + while (byteArray.Length * 8) % 5 <> 0 do byteArray <- Array.concat [ [| 0uy |]; byteArray ] - byteArray |> Helpers.convert8BitsTo5 |> Array.skipWhile((=)0uy) + + byteArray |> Helpers.convert8BitsTo5 |> Array.skipWhile((=) 0uy) + this.WriteField(writer, dBase32) -type TaggedFields = { - Fields: TaggedField list -} - with +type TaggedFields = + { + Fields: list + } + static member Zero = - { Fields = [] } + { + Fields = [] + } + member this.FallbackAddresses = - this.Fields |> List.choose(function FallbackAddressTaggedField a -> Some(a) | _ -> None) + this.Fields + |> List.choose( + function + | FallbackAddressTaggedField a -> Some(a) + | _ -> None + ) member this.ExplicitNodeId = - this.Fields |> Seq.choose(function NodeIdTaggedField x -> Some x | _ -> None) |> Seq.tryExactlyOne + this.Fields + |> Seq.choose( + function + | NodeIdTaggedField x -> Some x + | _ -> None + ) + |> Seq.tryExactlyOne member this.FeatureBits = - this.Fields |> Seq.choose(function FeaturesTaggedField fb -> Some fb | _ -> None) |> Seq.tryExactlyOne + this.Fields + |> Seq.choose( + function + | FeaturesTaggedField fb -> Some fb + | _ -> None + ) + |> Seq.tryExactlyOne member this.PaymentSecret = - this.Fields |> Seq.choose(function PaymentSecretTaggedField ps -> Some ps | _ -> None) |> Seq.tryExactlyOne + this.Fields + |> Seq.choose( + function + | PaymentSecretTaggedField ps -> Some ps + | _ -> None + ) + |> Seq.tryExactlyOne + member this.CheckSanity() = - let pHashes = this.Fields |> List.choose(function PaymentHashTaggedField x -> Some x | _ -> None) - if (pHashes.Length > 1) then "duplicate 'p' field" |> Error else - if (pHashes.Length < 1) then "no payment hash" |> Error else - let secrets = this.Fields |> List.choose(function PaymentSecretTaggedField x -> Some (x) | _ -> None) - if (secrets.Length > 1) then "duplicate 's' field" |> Error else - if secrets.Length = 1 && this.FeatureBits.IsNone then - sprintf "secret (%A) is set but there were no feature bits" secrets.[0] |> Error - elif secrets.Length = 1 && not <| this.FeatureBits.Value.HasFeature Feature.PaymentSecret then - let fb = this.FeatureBits.Value - sprintf "secret (%A) is set but feature bit (%s) is not set (%A)" (secrets.[0]) (fb.ToString()) (fb) - |> Error else - if secrets.Length = 0 && this.FeatureBits.IsSome && this.FeatureBits.Value.HasFeature(Feature.PaymentSecret, Mandatory) then - Error "feature bit for payment secret is set but payment secret is not set" else - let descriptions = this.Fields |> List.choose(function DescriptionTaggedField d -> Some d | _ -> None) - if (descriptions.Length > 1) then Error("duplicate 'd' field") else - let dHashes = this.Fields |> List.choose(function DescriptionHashTaggedField x -> Some x | _ -> None) - if (dHashes.Length > 1) then Error ("duplicate 'h' field") else - if (descriptions.Length = 1 && dHashes.Length = 1) then Error("both 'h' and 'd' field exists") else - if (descriptions.Length <> 1 && dHashes.Length <> 1) then Error("must have either description hash or description") else - if (descriptions.Length = 1 && descriptions.[0] |> String.IsNullOrEmpty) then Error("Description should not be an empty string") else - if (this.Fields |> List.filter(function | TaggedField.RoutingInfoTaggedField _ -> true | _ -> false ) |> List.length > 2) then - Error("Extra Routing hint information can not be longer than 2.") - else - let features = this.Fields |> List.choose(function FeaturesTaggedField x -> Some x | _ -> None) |> Seq.tryExactlyOne - match features with - | Some x when x.BitArray.Length = 0 || x.ToByteArray() |> Seq.forall((=)0uy) -> - Error("Empty Features are not allowed for PaymentRequest") - | _ -> - let maybeMinFinalExp = + let pHashes = this.Fields - |> List.choose(function | MinFinalCltvExpiryTaggedField x-> Some x | _ -> None) - |> List.tryExactlyOne - match maybeMinFinalExp with - | Some(BlockHeightOffset32 v) when v = 0u -> - Error("MinFinalExpiry field should not be zero") - | _ -> - () |> Ok - |> Result.mapError(fun s -> "Invalid BOLT11! " + s) - -type private Bolt11Data = { - Timestamp: DateTimeOffset - TaggedFields: TaggedFields - // byte is recovery id - Signature: (LNECDSASignature * byte) option -} - with - static member FromBytes(b: byte[]): Result = + |> List.choose( + function + | PaymentHashTaggedField x -> Some x + | _ -> None + ) + + if (pHashes.Length > 1) then + "duplicate 'p' field" |> Error + else if (pHashes.Length < 1) then + "no payment hash" |> Error + else + let secrets = + this.Fields + |> List.choose( + function + | PaymentSecretTaggedField x -> Some(x) + | _ -> None + ) + + if (secrets.Length > 1) then + "duplicate 's' field" |> Error + else if secrets.Length = 1 && this.FeatureBits.IsNone then + sprintf + "secret (%A) is set but there were no feature bits" + secrets.[0] + |> Error + elif secrets.Length = 1 + && not + <| this.FeatureBits.Value.HasFeature Feature.PaymentSecret then + let fb = this.FeatureBits.Value + + sprintf + "secret (%A) is set but feature bit (%s) is not set (%A)" + (secrets.[0]) + (fb.ToString()) + (fb) + |> Error + else if + secrets.Length = 0 && this.FeatureBits.IsSome + && this.FeatureBits.Value.HasFeature + ( + Feature.PaymentSecret, + Mandatory + ) + then + Error + "feature bit for payment secret is set but payment secret is not set" + else + let descriptions = + this.Fields + |> List.choose( + function + | DescriptionTaggedField d -> Some d + | _ -> None + ) + + if (descriptions.Length > 1) then + Error("duplicate 'd' field") + else + let dHashes = + this.Fields + |> List.choose( + function + | DescriptionHashTaggedField x -> Some x + | _ -> None + ) + + if (dHashes.Length > 1) then + Error("duplicate 'h' field") + else if (descriptions.Length = 1 && dHashes.Length = 1) then + Error("both 'h' and 'd' field exists") + else if (descriptions.Length <> 1 && dHashes.Length <> 1) then + Error( + "must have either description hash or description" + ) + else if (descriptions.Length = 1 + && descriptions.[0] |> String.IsNullOrEmpty) then + Error("Description should not be an empty string") + else if (this.Fields + |> List.filter( + function + | TaggedField.RoutingInfoTaggedField _ -> true + | _ -> false + ) + |> List.length > 2) then + Error( + "Extra Routing hint information can not be longer than 2." + ) + else + let features = + this.Fields + |> List.choose( + function + | FeaturesTaggedField x -> Some x + | _ -> None + ) + |> Seq.tryExactlyOne + + match features with + | Some x when + x.BitArray.Length = 0 + || x.ToByteArray() |> Seq.forall((=) 0uy) + -> + Error( + "Empty Features are not allowed for PaymentRequest" + ) + | _ -> + let maybeMinFinalExp = + this.Fields + |> List.choose( + function + | MinFinalCltvExpiryTaggedField x -> Some x + | _ -> None + ) + |> List.tryExactlyOne + + match maybeMinFinalExp with + | Some(BlockHeightOffset32 v) when v = 0u -> + Error("MinFinalExpiry field should not be zero") + | _ -> () |> Ok + |> Result.mapError(fun s -> "Invalid BOLT11! " + s) + +type private Bolt11Data = + { + Timestamp: DateTimeOffset + TaggedFields: TaggedFields + // byte is recovery id + Signature: option<(LNECDSASignature * byte)> + } + + static member FromBytes(b: array) : Result = result { let bitArray = BitArray.From5BitEncoding(b) let reader = BitReader(bitArray) reader.Position <- reader.Count - 520 - 30 + if (reader.Position < 0) then - return! sprintf "Invalid BOLT11: Invalid size. reader.Position was (%d)" reader.Position |> Error + return! + sprintf + "Invalid BOLT11: Invalid size. reader.Position was (%i)" + reader.Position + |> Error else if (not <| reader.CanConsume(65)) then - return! "Invalid BOLT11: Invalid size. could not consume 65" |> Error + return! + "Invalid BOLT11: Invalid size. could not consume 65" + |> Error else let rs = reader.ReadBytes(65) - let signature = LNECDSASignature.FromBytesCompact(rs.[0..rs.Length - 2]) + + let signature = + LNECDSASignature.FromBytesCompact(rs.[0 .. rs.Length - 2]) + let recvId = rs.[rs.Length - 1] reader.Position <- 0 let timestamp = Utils.UnixTimeToDateTime(reader.ReadULongBE(35)) + let checkSize (r: BitReader) c = if (not <| r.CanConsume(c)) then - Error(sprintf "Invalid BOLT11: Invalid size. could not consume %d" c) + Error( + sprintf + "Invalid BOLT11: Invalid size. could not consume %i" + c + ) else Ok() + let rec loop (r: BitReader) (acc: TaggedFields) (skipTo: int) = result { do! r.SkipTo(skipTo) + match (r.Position <> r.Count - 520 - 30) with - | false -> - return acc + | false -> return acc | true -> do! checkSize r (5 + 10) let tag = r.ReadULongBE(5) let mutable size = (r.ReadULongBE(10) * 5UL) |> int do! checkSize r (size) let afterReadPosition = r.Position + size + match tag with - | 1UL -> // payment hash + | 1UL -> // payment hash if (size <> 52 * 5) then return! loop r acc afterReadPosition // we must omit instead of returning an error (according to the BOLT11) else - let ph = r.ReadBytes(32) |> fun x -> uint256(x, false) |> PaymentHash |> PaymentHashTaggedField - return! loop r ({ acc with Fields = ph :: acc.Fields}) afterReadPosition + let ph = + r.ReadBytes(32) + |> fun x -> + uint256(x, false) + |> PaymentHash + |> PaymentHashTaggedField + + return! + loop + r + ({ acc with + Fields = ph :: acc.Fields + }) + afterReadPosition | 16UL -> // payment secret if (size <> 52 * 5) then - return! loop r acc afterReadPosition // we must omit instead of returning an error (according to the BOLT11) + return! loop r acc afterReadPosition // we must omit instead of returning an error (according to the BOLT11) else - let ps = r.ReadBytes(32) |> fun x -> uint256(x, false) |> PaymentSecretTaggedField - return! loop r { acc with Fields = ps :: acc.Fields } afterReadPosition + let ps = + r.ReadBytes(32) + |> fun x -> + uint256(x, false) + |> PaymentSecretTaggedField + + return! + loop + r + { acc with + Fields = ps :: acc.Fields + } + afterReadPosition | 6UL -> // expiry - let expiryDate = timestamp + TimeSpan.FromSeconds(r.ReadULongBE(size) |> float) |> ExpiryTaggedField - - return! loop r { acc with Fields = expiryDate :: acc.Fields } afterReadPosition + let expiryDate = + timestamp + + TimeSpan.FromSeconds( + r.ReadULongBE(size) |> float + ) + |> ExpiryTaggedField + + return! + loop + r + { acc with + Fields = expiryDate :: acc.Fields + } + afterReadPosition | 13UL -> // description let bytesCount = size / 8 let bytes = r.ReadBytes(bytesCount) + try - let shortDesc = Helpers.utf8.GetString(bytes, 0 , bytesCount) |> DescriptionTaggedField - return! loop r ({ acc with Fields = shortDesc :: acc.Fields }) afterReadPosition + let shortDesc = + Helpers.utf8.GetString( + bytes, + 0, + bytesCount + ) + |> DescriptionTaggedField + + return! + loop + r + ({ acc with + Fields = + shortDesc :: acc.Fields + }) + afterReadPosition with - | _ -> - return! loop r acc afterReadPosition + | _ -> return! loop r acc afterReadPosition | 19UL -> // pubkey for node id if (size <> 53 * 5) then return! loop r acc afterReadPosition // we must omit instead of returning an error (according to the BOLT11) else - let pk = r.ReadBytes(33) |> PubKey |> NodeId |> NodeIdTaggedField - return! loop r ({ acc with Fields = pk :: acc.Fields }) afterReadPosition + let pk = + r.ReadBytes(33) + |> PubKey + |> NodeId + |> NodeIdTaggedField + + return! + loop + r + ({ acc with + Fields = pk :: acc.Fields + }) + afterReadPosition | 24UL -> // min_final_cltv_expiry let v = r.ReadULongBE(size) + if (v > (UInt32.MaxValue |> uint64)) then return! loop r acc afterReadPosition else - let minFinalCltvExpiry = v |> uint32 |> BlockHeightOffset32 |> MinFinalCltvExpiryTaggedField - return! loop r { acc with Fields = minFinalCltvExpiry :: acc.Fields } afterReadPosition + let minFinalCltvExpiry = + v + |> uint32 + |> BlockHeightOffset32 + |> MinFinalCltvExpiryTaggedField + + return! + loop + r + { acc with + Fields = + minFinalCltvExpiry + :: acc.Fields + } + afterReadPosition | 9UL -> // fallback address if (size < 5) then return! loop r acc afterReadPosition else let version = r.ReadULongBE(5) + match version with | 0UL -> if (size = 5 + (20 * 8)) then - let! addr = FallbackAddress.TryCreate(0uy, r.ReadBytes(20)) - return! loop r { acc with Fields = (FallbackAddressTaggedField addr) :: acc.Fields } afterReadPosition + let! addr = + FallbackAddress.TryCreate( + 0uy, + r.ReadBytes(20) + ) + + return! + loop + r + { acc with + Fields = + (FallbackAddressTaggedField + addr) + :: acc.Fields + } + afterReadPosition else if (size = (5 + (32 * 8) + 4)) then - let! addr = FallbackAddress.TryCreate(0uy, r.ReadBytes(32)) - return! loop r { acc with Fields = (FallbackAddressTaggedField addr) :: acc.Fields } afterReadPosition + let! addr = + FallbackAddress.TryCreate( + 0uy, + r.ReadBytes(32) + ) + + return! + loop + r + { acc with + Fields = + (FallbackAddressTaggedField + addr) + :: acc.Fields + } + afterReadPosition else return! loop r acc afterReadPosition | 17UL when (size = 5 + (20 * 8)) -> - let! addr = FallbackAddress.TryCreate(17uy, r.ReadBytes(20)) - return! loop r { acc with Fields = (FallbackAddressTaggedField addr) :: acc.Fields } afterReadPosition + let! addr = + FallbackAddress.TryCreate( + 17uy, + r.ReadBytes(20) + ) + + return! + loop + r + { acc with + Fields = + (FallbackAddressTaggedField + addr) + :: acc.Fields + } + afterReadPosition | 18UL when (size = 5 + (20 * 8)) -> - let! addr = FallbackAddress.TryCreate(18uy, r.ReadBytes(20)) - return! loop r { acc with Fields = (FallbackAddressTaggedField addr) :: acc.Fields } afterReadPosition + let! addr = + FallbackAddress.TryCreate( + 18uy, + r.ReadBytes(20) + ) + + return! + loop + r + { acc with + Fields = + (FallbackAddressTaggedField + addr) + :: acc.Fields + } + afterReadPosition | _ -> // in case of unknown version, we should just ignore for future compatibility return! loop r acc afterReadPosition @@ -483,94 +915,166 @@ type private Bolt11Data = { if (size <> 52 * 5) then return! loop r acc afterReadPosition // we must omit instead of returning an error (according to the BOLT11) else - let dHash = r.ReadBytes(32) |> fun x -> uint256(x, false) |> DescriptionHashTaggedField - return! loop r ({ acc with Fields = dHash :: acc.Fields }) afterReadPosition + let dHash = + r.ReadBytes(32) + |> fun x -> + uint256(x, false) + |> DescriptionHashTaggedField + + return! + loop + r + ({ acc with + Fields = dHash :: acc.Fields + }) + afterReadPosition | 3UL -> // routing info if (size < ExtraHop.Size) then - return! sprintf "Unexpected length for routing info (%d)" size |> Error + return! + sprintf + "Unexpected length for routing info (%i)" + size + |> Error else - let hopInfosR = ResizeArray>() + let hopInfosR = ResizeArray>() + while (size >= ExtraHop.Size) do - let nodeId = r.ReadBytes(264 / 8) |> PubKey |> NodeId - let schId = r.ReadULongBE(64) |> ShortChannelId.FromUInt64 - let feeBase = r.ReadULongBE(32) |> LNMoney.MilliSatoshis - let feeProportional = r.ReadULongBE(32) |> uint32 - let cltvExpiryDelta = r.ReadULongBE(16) |> uint16 |> BlockHeightOffset16 - let hopInfo = ExtraHop.TryCreate(nodeId, schId, feeBase, feeProportional, cltvExpiryDelta) + let nodeId = + r.ReadBytes(264 / 8) + |> PubKey + |> NodeId + + let schId = + r.ReadULongBE(64) + |> ShortChannelId.FromUInt64 + + let feeBase = + r.ReadULongBE(32) + |> LNMoney.MilliSatoshis + + let feeProportional = + r.ReadULongBE(32) |> uint32 + + let cltvExpiryDelta = + r.ReadULongBE(16) + |> uint16 + |> BlockHeightOffset16 + + let hopInfo = + ExtraHop.TryCreate( + nodeId, + schId, + feeBase, + feeProportional, + cltvExpiryDelta + ) + hopInfosR.Add(hopInfo) size <- size - ExtraHop.Size - let! hopInfos= + + let! hopInfos = hopInfosR |> Seq.toList |> List.traverseResultM(id) - let hopInfoList = hopInfos |> Seq.toList |> RoutingInfoTaggedField - return! loop r { acc with Fields = hopInfoList :: acc.Fields } afterReadPosition + + let hopInfoList = + hopInfos + |> Seq.toList + |> RoutingInfoTaggedField + + return! + loop + r + { acc with + Fields = + hopInfoList :: acc.Fields + } + afterReadPosition | 5UL -> // feature bits let bits = r.ReadBits(size) + let! fb = bits |> FeatureBits.TryCreate |> Result.mapError(fun x -> - "Invalid BOLT11! Feature field is bogus " + x.ToString()) + "Invalid BOLT11! Feature field is bogus " + + x.ToString() + ) + let features = fb |> FeaturesTaggedField - return! loop r { acc with Fields = features :: acc.Fields } afterReadPosition + + return! + loop + r + { acc with + Fields = features :: acc.Fields + } + afterReadPosition | _ -> // we must skip unknown field return! loop r acc afterReadPosition } - let! taggedField = loop reader (TaggedFields.Zero) reader.Position + + let! taggedField = + loop reader (TaggedFields.Zero) reader.Position + do! taggedField.CheckSanity() - return { - Timestamp = timestamp - TaggedFields = taggedField - Signature = (signature, recvId) |> Some - } - } + + return + { + Timestamp = timestamp + TaggedFields = taggedField + Signature = (signature, recvId) |> Some + } + } member this.ToBytesBase32() = use ms = new MemoryStream() use writer = new BinaryWriter(ms) - let timestamp = - Utils.DateTimeToUnixTime(this.Timestamp) - |> uint64 - let timestampBase32 = - timestamp - |> Helpers.uint64ToBase32 - Debug.Assert(timestampBase32.Length <= (35 / 5), sprintf "Timestamp too big: %d" timestampBase32.Length) - let padding: byte[] = Array.zeroCreate(7 - timestampBase32.Length) + let timestamp = Utils.DateTimeToUnixTime(this.Timestamp) |> uint64 + let timestampBase32 = timestamp |> Helpers.uint64ToBase32 + + Debug.Assert( + timestampBase32.Length <= (35 / 5), + sprintf "Timestamp too big: %i" timestampBase32.Length + ) + + let padding: array = Array.zeroCreate(7 - timestampBase32.Length) writer.Write(padding) writer.Write(timestampBase32) this.TaggedFields.Fields |> List.rev - |> List.iter(fun f -> - f.WriteTo(writer, timestamp) - ) + |> List.iter(fun f -> f.WriteTo(writer, timestamp)) this.Signature |> Option.iter(fun (s, recv) -> - let sigBase32 = Array.concat [ s.ToBytesCompact(); [|recv|]] |> Helpers.convert8BitsTo5 + let sigBase32 = + Array.concat [ s.ToBytesCompact(); [| recv |] ] + |> Helpers.convert8BitsTo5 + writer.Write(sigBase32) - ) + ) + ms.ToArray() /// Returns binary representation for signing. member this.ToBytes() = - this.ToBytesBase32() - |> Helpers.convert5BitsTo8 - - -[] -/// a.k.a bolt11-invoice, lightning-invoice -type PaymentRequest = private { - Prefix: string - Amount: LNMoney option - Timestamp: DateTimeOffset - NodeId: NodeId - Tags: TaggedFields - Signature: (LNECDSASignature * byte) option -} - with + this.ToBytesBase32() |> Helpers.convert5BitsTo8 + + +[] +type PaymentRequest = + private + { + Prefix: string + Amount: option + Timestamp: DateTimeOffset + NodeId: NodeId + Tags: TaggedFields + Signature: option<(LNECDSASignature * byte)> + } + member this.PrefixValue = this.Prefix member this.AmountValue = this.Amount member this.TimestampValue = this.Timestamp @@ -580,189 +1084,360 @@ type PaymentRequest = private { member this.PaymentHash = this.Tags.Fields - |> Seq.choose(function TaggedField.PaymentHashTaggedField p -> Some p | _ -> None) + |> Seq.choose( + function + | TaggedField.PaymentHashTaggedField p -> Some p + | _ -> None + ) |> Seq.exactlyOne // we assured in constructor that it has only one member this.PaymentSecret = this.Tags.Fields - |> Seq.choose(function TaggedField.PaymentSecretTaggedField ps -> Some ps | _ -> None) + |> Seq.choose( + function + | TaggedField.PaymentSecretTaggedField ps -> Some ps + | _ -> None + ) |> Seq.tryExactlyOne member this.Description = this.Tags.Fields - |> Seq.choose(function - | DescriptionTaggedField d -> Some(Choice1Of2 d) - | DescriptionHashTaggedField d -> Some(Choice2Of2 d) - | _ -> None) - |> Seq.exactlyOne + |> Seq.choose( + function + | DescriptionTaggedField d -> Some(Choice1Of2 d) + | DescriptionHashTaggedField d -> Some(Choice2Of2 d) + | _ -> None + ) + |> Seq.exactlyOne member this.FallbackAddresses = this.Tags.Fields - |> List.choose(function FallbackAddressTaggedField f -> Some f | _ -> None) + |> List.choose( + function + | FallbackAddressTaggedField f -> Some f + | _ -> None + ) |> List.map(fun fallbackAddr -> fallbackAddr.ToAddress(this.Prefix)) member this.RoutingInfo = this.Tags.Fields - |> List.choose(function RoutingInfoTaggedField r -> Some r | _ -> None) + |> List.choose( + function + | RoutingInfoTaggedField r -> Some r + | _ -> None + ) /// absolute expiry date. member this.Expiry = this.Tags.Fields - |> Seq.choose(function ExpiryTaggedField e -> Some (e) | _ -> None) + |> Seq.choose( + function + | ExpiryTaggedField e -> Some(e) + | _ -> None + ) |> Seq.tryExactlyOne - |> Option.defaultValue (this.Timestamp + PaymentConstants.DEFAULT_EXPIRY_SECONDS) + |> Option.defaultValue( + this.Timestamp + PaymentConstants.DEFAULT_EXPIRY_SECONDS + ) member this.MinFinalCLTVExpiryDelta = this.Tags.Fields - |> Seq.choose(function MinFinalCltvExpiryTaggedField cltvE -> Some (cltvE) | _ -> None) + |> Seq.choose( + function + | MinFinalCltvExpiryTaggedField cltvE -> Some(cltvE) + | _ -> None + ) |> Seq.tryExactlyOne - |> Option.defaultValue (PaymentConstants.DEFAULT_MINIMUM_CLTVEXPIRY) + |> Option.defaultValue(PaymentConstants.DEFAULT_MINIMUM_CLTVEXPIRY) member this.Features = this.Tags.Fields - |> Seq.choose(function FeaturesTaggedField f -> Some f | _ -> None) + |> Seq.choose( + function + | FeaturesTaggedField f -> Some f + | _ -> None + ) |> Seq.tryExactlyOne - member this.IsExpired = - this.Expiry <= DateTimeOffset.UtcNow + member this.IsExpired = this.Expiry <= DateTimeOffset.UtcNow member this.HumanReadablePart = (sprintf "%s%s" (this.Prefix) (Amount.encode(this.Amount))) + member private this.Hash = - let hrp = - this.HumanReadablePart |> Helpers.utf8.GetBytes + let hrp = this.HumanReadablePart |> Helpers.utf8.GetBytes + + let data = + { + Bolt11Data.Timestamp = this.Timestamp + TaggedFields = this.Tags + Signature = None + } - let data = { Bolt11Data.Timestamp = this.Timestamp - TaggedFields = this.Tags - Signature = None } let bin = data.ToBytes() - let msg = Array.concat(seq { yield hrp; yield bin }) + + let msg = + Array.concat( + seq { + yield hrp + yield bin + } + ) + Hashes.SHA256(msg) |> uint256 member this.Sign(privKey: Key, ?forceLowR: bool) = let forceLowR = Option.defaultValue true forceLowR let ecdsaSig = privKey.SignCompact(this.Hash, forceLowR) let recvId = ecdsaSig.[0] - 27uy - 4uy - let sig64 = ecdsaSig |> fun s -> LNECDSASignature.FromBytesCompact(s, true) - { this with Signature = (sig64, recvId) |> Some } + + let sig64 = + ecdsaSig |> fun s -> LNECDSASignature.FromBytesCompact(s, true) + + { this with + Signature = (sig64, recvId) |> Some + } override this.ToString() = let hrp = this.HumanReadablePart + let data = - { Bolt11Data.TaggedFields = this.Tags - Timestamp = this.TimestampValue - Signature = this.Signature } + { + Bolt11Data.TaggedFields = this.Tags + Timestamp = this.TimestampValue + Signature = this.Signature + } + data.ToBytesBase32() |> Helpers.encodeBech32 hrp - member private this.ToString(signature65bytes: byte[]) = + member private this.ToString(signature65bytes: array) = let hrp = this.HumanReadablePart + let data = let recvId = signature65bytes.[0] - 27uy - 4uy - let signature = signature65bytes |> fun s -> LNECDSASignature.FromBytesCompact(s, true) - { Bolt11Data.TaggedFields = this.Tags - Timestamp = this.Timestamp - Signature = (signature, recvId) |> Some } + + let signature = + signature65bytes + |> fun s -> LNECDSASignature.FromBytesCompact(s, true) + + { + Bolt11Data.TaggedFields = this.Tags + Timestamp = this.Timestamp + Signature = (signature, recvId) |> Some + } + data.ToBytesBase32() |> Helpers.encodeBech32 hrp member this.ToString(signer: IMessageSigner) = let sign = signer.SignMessage(this.Hash) this.ToString(sign) - static member Parse(str: string): Result = + static member Parse(str: string) : Result = result { - do! Helpers.checkMaxInvoiceLength (str) // for DoS protection + do! Helpers.checkMaxInvoiceLength(str) // for DoS protection let mutable s = str.ToLowerInvariant() // assure reference transparency + if (s.StartsWith("lightning:", StringComparison.OrdinalIgnoreCase)) then s <- s.Substring("lightning:".Length) + let! (hrp, data) = Helpers.decodeBech32(s) let! prefix = Helpers.checkAndGetPrefixFromHrp hrp - let maybeAmount = Amount.decode(hrp.Substring(prefix.Length)) |> function Ok s -> Some s | Error _ -> None + + let maybeAmount = + Amount.decode(hrp.Substring(prefix.Length)) + |> function + | Ok s -> Some s + | Error _ -> None let! bolt11Data = Bolt11Data.FromBytes(data) let (sigCompact, recv) = bolt11Data.Signature.Value + let nodeId = match (bolt11Data.TaggedFields.ExplicitNodeId) with | Some n -> n | None -> let msg = let bitArray = BitArray.From5BitEncoding(data) - let reader = BitReader(bitArray, bitArray.Count - 520 - 30) - let remainder = ref 0; - let mutable byteCount = Math.DivRem(reader.Count, 8, remainder) + + let reader = + BitReader(bitArray, bitArray.Count - 520 - 30) + + let remainder = ref 0 + + let mutable byteCount = + Math.DivRem(reader.Count, 8, remainder) + if (!remainder <> 0) then byteCount <- byteCount + 1 - seq { yield (hrp |> Helpers.utf8.GetBytes); yield (reader.ReadBytes(byteCount)) } + seq { + yield (hrp |> Helpers.utf8.GetBytes) + yield (reader.ReadBytes(byteCount)) + } |> Array.concat + let signatureInNBitcoinFormat = Array.zeroCreate(65) - Array.blit (sigCompact.ToBytesCompact()) 0 signatureInNBitcoinFormat 1 64 + + Array.blit + (sigCompact.ToBytesCompact()) + 0 + signatureInNBitcoinFormat + 1 + 64 + signatureInNBitcoinFormat.[0] <- (recv + 27uy + 4uy) let r = Hashes.SHA256(msg) |> uint256 - PubKey.RecoverCompact(r, signatureInNBitcoinFormat).Compress() + + PubKey + .RecoverCompact(r, signatureInNBitcoinFormat) + .Compress() |> NodeId - return { - PaymentRequest.Amount = maybeAmount - Prefix = prefix - Timestamp = bolt11Data.Timestamp - NodeId = nodeId - Tags = bolt11Data.TaggedFields - Signature = (sigCompact, recv) |> Some - } + return + { + PaymentRequest.Amount = maybeAmount + Prefix = prefix + Timestamp = bolt11Data.Timestamp + NodeId = nodeId + Tags = bolt11Data.TaggedFields + Signature = (sigCompact, recv) |> Some + } } - static member TryCreate(network: Network, amount: LNMoney option, timestamp, tags: TaggedFields, nodeSecret: Key) = + + static member TryCreate + ( + network: Network, + amount: option, + timestamp, + tags: TaggedFields, + nodeSecret: Key + ) = let prefix = Helpers.prefixes.[network.GenesisHash] PaymentRequest.TryCreate(prefix, amount, timestamp, tags, nodeSecret) - static member TryCreate(prefix: string, amount: LNMoney option, timestamp, tags: TaggedFields, nodeSecret: Key) = + static member TryCreate + ( + prefix: string, + amount: option, + timestamp, + tags: TaggedFields, + nodeSecret: Key + ) = let nodeId = nodeSecret.PubKey |> NodeId - PaymentRequest.TryCreate(prefix, amount, timestamp, nodeId, tags, nodeSecret) - static member TryCreate(prefix: string, amount: LNMoney option, timestamp, nodeId: NodeId, tags: TaggedFields, nodeSecret: Key) = - if (nodeId.Value <> nodeSecret.PubKey) then Error "Wrong Node secret" else - let msgSigner = { new IMessageSigner - with - member this.SignMessage(data) = nodeSecret.SignCompact(data, false) } - PaymentRequest.TryCreate(prefix, amount, timestamp, nodeId, tags, msgSigner) + PaymentRequest.TryCreate( + prefix, + amount, + timestamp, + nodeId, + tags, + nodeSecret + ) + + static member TryCreate + ( + prefix: string, + amount: option, + timestamp, + nodeId: NodeId, + tags: TaggedFields, + nodeSecret: Key + ) = + if (nodeId.Value <> nodeSecret.PubKey) then + Error "Wrong Node secret" + else + let msgSigner = + { new IMessageSigner with + member this.SignMessage data = + nodeSecret.SignCompact(data, false) + } + + PaymentRequest.TryCreate( + prefix, + amount, + timestamp, + nodeId, + tags, + msgSigner + ) /// signer must sign by node_secret which corresponds to node_id - static member TryCreate (prefix: string, amount: LNMoney option, timestamp, nodeId, tags: TaggedFields, signer: IMessageSigner) = + static member TryCreate + ( + prefix: string, + amount: option, + timestamp, + nodeId, + tags: TaggedFields, + signer: IMessageSigner + ) = result { - do! amount |> function None -> Ok() | Some a -> Result.requireTrue "amount must be larger than 0" (a > LNMoney.Zero) + do! + amount + |> function + | None -> Ok() + | Some a -> + Result.requireTrue + "amount must be larger than 0" + (a > LNMoney.Zero) + do! tags.CheckSanity() + match tags.ExplicitNodeId with - | Some n when n <> nodeId -> - return! Error("NodeId mismatch!") + | Some n when n <> nodeId -> return! Error("NodeId mismatch!") | _ -> - let maybeExpiryDate = tags.Fields |> List.choose(function | ExpiryTaggedField x -> Some x | _ -> None) |> List.tryExactlyOne - match maybeExpiryDate with - | Some e when e < timestamp -> - return! Error(sprintf "You can not newly create already expired invoice! timestamp: %A. expiryDate: %A" (timestamp) (e)) - | _ -> - let r = { - Prefix = prefix - Amount = amount - Timestamp = timestamp - NodeId = nodeId - Tags = tags - Signature = None - } - let signature65bytes = signer.SignMessage (r.Hash) - let recvId = signature65bytes.[0] - 27uy - 4uy - let signature = signature65bytes |> fun s -> LNECDSASignature.FromBytesCompact(s, true) - return { r with Signature = Some(signature, recvId) } + let maybeExpiryDate = + tags.Fields + |> List.choose( + function + | ExpiryTaggedField x -> Some x + | _ -> None + ) + |> List.tryExactlyOne + + match maybeExpiryDate with + | Some e when e < timestamp -> + return! + Error( + sprintf + "You can not newly create already expired invoice! timestamp: %A. expiryDate: %A" + (timestamp) + (e) + ) + | _ -> + let r = + { + Prefix = prefix + Amount = amount + Timestamp = timestamp + NodeId = nodeId + Tags = tags + Signature = None + } + + let signature65bytes = signer.SignMessage(r.Hash) + let recvId = signature65bytes.[0] - 27uy - 4uy + + let signature = + signature65bytes + |> fun s -> LNECDSASignature.FromBytesCompact(s, true) + + return + { r with + Signature = Some(signature, recvId) + } } - override this.Equals(o) = + override this.Equals o = match o with - | :? PaymentRequest as other -> (this :> IEquatable).Equals(other) + | :? PaymentRequest as other -> + (this :> IEquatable).Equals(other) | :? String as other -> this.ToString().Equals(other) | _ -> false override this.GetHashCode() = - this.ToString() - |> hash + this.ToString() |> hash interface IEquatable with - member this.Equals(other) = + member this.Equals other = this.ToString().Equals(other.ToString()) diff --git a/src/DotNetLightning.Core/Payment/PaymentTypes.fs b/src/DotNetLightning.Core/Payment/PaymentTypes.fs index 70e3d9cec..2f2d2a73e 100644 --- a/src/DotNetLightning.Core/Payment/PaymentTypes.fs +++ b/src/DotNetLightning.Core/Payment/PaymentTypes.fs @@ -1,12 +1,15 @@ namespace DotNetLightning.Payment + open System -type PaymentId = private PaymentId of string - with - static member Create() = - PaymentId (Guid.NewGuid().ToString()) - +type PaymentId = + private + | PaymentId of string + + static member Create() = + PaymentId(Guid.NewGuid().ToString()) + [] module PaymentType = - let (|PaymentId|) (PaymentId x) = x - + let (|PaymentId|)(PaymentId x) = + x diff --git a/src/DotNetLightning.Core/Peer/ChannelMonitor.fs b/src/DotNetLightning.Core/Peer/ChannelMonitor.fs index bd05d233b..9fbed45ce 100644 --- a/src/DotNetLightning.Core/Peer/ChannelMonitor.fs +++ b/src/DotNetLightning.Core/Peer/ChannelMonitor.fs @@ -1,8 +1,8 @@ namespace DotNetLightning.LN + open NBitcoin open DotNetLightning.Utils.Primitives open Microsoft.Extensions.Logging open NBitcoin open System open DotNetLightning.Utils.Aether - diff --git a/src/DotNetLightning.Core/Peer/Peer.fs b/src/DotNetLightning.Core/Peer/Peer.fs index ec7a527d7..675a13f8f 100644 --- a/src/DotNetLightning.Core/Peer/Peer.fs +++ b/src/DotNetLightning.Core/Peer/Peer.fs @@ -10,11 +10,12 @@ open DotNetLightning.Serialization.Msgs open ResultUtils open ResultUtils.Portability -type PeerHandleError = { - /// Used to indicate that we probably can't make any future connections to this peer, implying - /// we should go ahead and force-close any channels we have with it. - NoConnectionPossible: bool -} +type PeerHandleError = + { + /// Used to indicate that we probably can't make any future connections to this peer, implying + /// we should go ahead and force-close any channels we have with it. + NoConnectionPossible: bool + } exception PeerHandleException of PeerHandleError @@ -27,110 +28,148 @@ module private PeerConstants = [] let INITIAL_SYNCS_TO_SEND = 5u -type Peer = { - ChannelEncryptor: PeerChannelEncryptor - IsOutBound : bool - TheirNodeId: NodeId option - TheirFeatures: FeatureBits option - SyncStatus: InitSyncTracker -} - with - static member CreateInbound(ourNodeSecret) = { +type Peer = + { + ChannelEncryptor: PeerChannelEncryptor + IsOutBound: bool + TheirNodeId: option + TheirFeatures: option + SyncStatus: InitSyncTracker + } + + static member CreateInbound ourNodeSecret = + { ChannelEncryptor = PeerChannelEncryptor.newInBound(ourNodeSecret) IsOutBound = false TheirNodeId = None TheirFeatures = None SyncStatus = NoSyncRequested } - - static member CreateOutbound(theirNodeId, ourNodeSecret: Key) = { - ChannelEncryptor = PeerChannelEncryptor.newOutBound(theirNodeId, ourNodeSecret) + + static member CreateOutbound(theirNodeId, ourNodeSecret: Key) = + { + ChannelEncryptor = + PeerChannelEncryptor.newOutBound(theirNodeId, ourNodeSecret) IsOutBound = true TheirNodeId = Some theirNodeId TheirFeatures = None SyncStatus = NoSyncRequested } - /// Returns true if the channel announcements/updates for the given channel should be - /// Forwarded to this peer. - /// If we are sending our routing table to this peer and we have not yet sent channel - /// announceents/updates for the given channel_id then we will send it when we get to that - /// point and we shouldn't send it yet to avoid sending - member this.ShouldForwardChannel(channelId: ChannelId) = - match this.SyncStatus with - | NoSyncRequested -> true - | ChannelsSyncing i -> i < channelId - | NodeSyncing _ -> true - + + /// Returns true if the channel announcements/updates for the given channel should be + /// Forwarded to this peer. + /// If we are sending our routing table to this peer and we have not yet sent channel + /// announceents/updates for the given channel_id then we will send it when we get to that + /// point and we shouldn't send it yet to avoid sending + member this.ShouldForwardChannel(channelId: ChannelId) = + match this.SyncStatus with + | NoSyncRequested -> true + | ChannelsSyncing i -> i < channelId + | NodeSyncing _ -> true + module Peer = let executeCommand (state: Peer) (cmd: PeerCommand) = let noiseStep = state.ChannelEncryptor.GetNoiseStep() + match noiseStep, cmd with - | ActOne, ProcessActOne (actOne, ourNodeSecret) -> + | ActOne, ProcessActOne(actOne, ourNodeSecret) -> result { - let! actTwo = state.ChannelEncryptor |> PeerChannelEncryptor.processActOneWithKey actOne ourNodeSecret - return actTwo |> ActOneProcessed |> List.singleton + let! actTwo = + state.ChannelEncryptor + |> PeerChannelEncryptor.processActOneWithKey + actOne + ourNodeSecret + + return actTwo |> ActOneProcessed |> List.singleton } - | ActTwo, ProcessActTwo (actTwo, ourNodeSecret) -> + | ActTwo, ProcessActTwo(actTwo, ourNodeSecret) -> result { - let! actThree = state.ChannelEncryptor |> PeerChannelEncryptor.processActTwo actTwo ourNodeSecret + let! actThree = + state.ChannelEncryptor + |> PeerChannelEncryptor.processActTwo actTwo ourNodeSecret + return actThree |> (ActTwoProcessed >> List.singleton) } - | ActThree, ProcessActThree(actThree) -> + | ActThree, ProcessActThree actThree -> result { - let! theirNodeId = state.ChannelEncryptor |> PeerChannelEncryptor.processActThree actThree + let! theirNodeId = + state.ChannelEncryptor + |> PeerChannelEncryptor.processActThree actThree + return theirNodeId |> (ActThreeProcessed >> List.singleton) } - | NoiseComplete, DecodeCipherPacket (lengthPacket, reader) -> + | NoiseComplete, DecodeCipherPacket(lengthPacket, reader) -> result { let! (len, pce) = - PeerChannelEncryptor.decryptLengthHeader lengthPacket state.ChannelEncryptor - - let packet = reader (int len + 16) - let! (b, newPCE) = pce |> PeerChannelEncryptor.decryptMessage packet - let! msg = LightningMsg.fromBytes b |> Result.mapError(PeerError.P2PMessageDecodeError) + PeerChannelEncryptor.decryptLengthHeader + lengthPacket + state.ChannelEncryptor + + let packet = reader(int len + 16) + + let! (b, newPCE) = + pce |> PeerChannelEncryptor.decryptMessage packet + + let! msg = + LightningMsg.fromBytes b + |> Result.mapError(PeerError.P2PMessageDecodeError) + return match msg with - | :? ErrorMsg as msg -> - [ReceivedError (msg, newPCE)] - | :? PingMsg as msg -> - [ReceivedPing (msg, newPCE)] - | :? PongMsg as msg -> - [ReceivedPong (msg, newPCE)] - | :? InitMsg as msg -> - [ReceivedInit (msg, newPCE)] + | :? ErrorMsg as msg -> [ ReceivedError(msg, newPCE) ] + | :? PingMsg as msg -> [ ReceivedPing(msg, newPCE) ] + | :? PongMsg as msg -> [ ReceivedPong(msg, newPCE) ] + | :? InitMsg as msg -> [ ReceivedInit(msg, newPCE) ] | :? IRoutingMsg as msg -> - [ReceivedRoutingMsg (msg, newPCE)] + [ ReceivedRoutingMsg(msg, newPCE) ] | :? IChannelMsg as msg -> - [ReceivedChannelMsg (msg, newPCE)] - | _ -> - failwithf "unreachable %A" msg + [ ReceivedChannelMsg(msg, newPCE) ] + | _ -> failwithf "unreachable %A" msg } - | NoiseComplete, EncodeMsg (msg) -> - state.ChannelEncryptor |> PeerChannelEncryptor.encryptMessage (msg.ToBytes()) + | NoiseComplete, EncodeMsg msg -> + state.ChannelEncryptor + |> PeerChannelEncryptor.encryptMessage(msg.ToBytes()) |> (MsgEncoded >> List.singleton >> Ok) | _, cmd -> - failwithf "Peer does not know how to handle %A while in noise step %A" cmd noiseStep - - let applyEvent (state: Peer) (event: PeerEvent): Peer = + failwithf + "Peer does not know how to handle %A while in noise step %A" + cmd + noiseStep + + let applyEvent (state: Peer) (event: PeerEvent) : Peer = let noiseStep = state.ChannelEncryptor.GetNoiseStep() + match noiseStep, event with - | ActOne, ActOneProcessed (_, pce) -> - { state with ChannelEncryptor = pce } + | ActOne, ActOneProcessed(_, pce) -> + { state with + ChannelEncryptor = pce + } | ActTwo, ActTwoProcessed((_, nodeId), pce) -> - { state with ChannelEncryptor = pce; TheirNodeId = Some nodeId } + { state with + ChannelEncryptor = pce + TheirNodeId = Some nodeId + } | ActThree, ActThreeProcessed(nodeId, pce) -> - { state with ChannelEncryptor = pce; TheirNodeId = Some nodeId } - | NoiseComplete, ReceivedError (_, pce) - | NoiseComplete, ReceivedPing (_, pce) - | NoiseComplete, ReceivedPong (_, pce) - | NoiseComplete, ReceivedRoutingMsg (_, pce) - | NoiseComplete, ReceivedChannelMsg (_, pce) -> - { state with ChannelEncryptor = pce; } - | NoiseComplete, ReceivedInit (initMsg, pce) -> { state with - ChannelEncryptor = pce; - TheirFeatures = Some initMsg.Features } + ChannelEncryptor = pce + TheirNodeId = Some nodeId + } + | NoiseComplete, ReceivedError(_, pce) + | NoiseComplete, ReceivedPing(_, pce) + | NoiseComplete, ReceivedPong(_, pce) + | NoiseComplete, ReceivedRoutingMsg(_, pce) + | NoiseComplete, ReceivedChannelMsg(_, pce) -> + { state with + ChannelEncryptor = pce + } + | NoiseComplete, ReceivedInit(initMsg, pce) -> + { state with + ChannelEncryptor = pce + TheirFeatures = Some initMsg.Features + } | NoiseComplete, MsgEncoded(_, pce) -> - { state with ChannelEncryptor = pce } + { state with + ChannelEncryptor = pce + } | state, e -> failwithf "Unreachable! applied event %A while in state %A" e state diff --git a/src/DotNetLightning.Core/Peer/PeerChannelEncryptor.fs b/src/DotNetLightning.Core/Peer/PeerChannelEncryptor.fs index 633e10601..cf60a2a99 100644 --- a/src/DotNetLightning.Core/Peer/PeerChannelEncryptor.fs +++ b/src/DotNetLightning.Core/Peer/PeerChannelEncryptor.fs @@ -16,19 +16,89 @@ module PeerChannelEncryptor = let crypto = CryptoUtils.impl type BitConverter with + static member GetBytesBE(data: uint16) = let buf = Array.zeroCreate 2 - buf.[0] <- (byte (data >>> 8)) + buf.[0] <- (byte(data >>> 8)) buf.[1] <- byte data buf - static member ToUint16BE(data: byte[]) = - ((uint16 data.[0]) <<< 8 ||| uint16 (data.[1])) + + static member ToUint16BE(data: array) = + ((uint16 data.[0]) <<< 8 ||| uint16(data.[1])) // Sha256("Noise_XK_secp256k1_ChaChaPoly_SHA256") - let NOISE_CK = [|0x26uy; 0x40uy; 0xf5uy; 0x2euy; 0xebuy; 0xcduy; 0x9euy; 0x88uy; 0x29uy; 0x58uy; 0x95uy; 0x1cuy; 0x79uy; 0x42uy; 0x50uy; 0xeeuy; 0xdbuy; 0x28uy; 0x00uy; 0x2cuy; 0x05uy; 0xd7uy; 0xdcuy; 0x2euy; 0xa0uy; 0xf1uy; 0x95uy; 0x40uy; 0x60uy; 0x42uy; 0xcauy; 0xf1uy|] + let NOISE_CK = + [| + 0x26uy + 0x40uy + 0xf5uy + 0x2euy + 0xebuy + 0xcduy + 0x9euy + 0x88uy + 0x29uy + 0x58uy + 0x95uy + 0x1cuy + 0x79uy + 0x42uy + 0x50uy + 0xeeuy + 0xdbuy + 0x28uy + 0x00uy + 0x2cuy + 0x05uy + 0xd7uy + 0xdcuy + 0x2euy + 0xa0uy + 0xf1uy + 0x95uy + 0x40uy + 0x60uy + 0x42uy + 0xcauy + 0xf1uy + |] // Sha256(NOISE_CK || "lightning") - let NOISE_H = [|0xd1uy; 0xfbuy; 0xf6uy; 0xdeuy; 0xe4uy; 0xf6uy; 0x86uy; 0xf1uy; 0x32uy; 0xfduy; 0x70uy; 0x2cuy; 0x4auy; 0xbfuy; 0x8fuy; 0xbauy; 0x4buy; 0xb4uy; 0x20uy; 0xd8uy; 0x9duy; 0x2auy; 0x04uy; 0x8auy; 0x3cuy; 0x4fuy; 0x4cuy; 0x09uy; 0x2euy; 0x37uy; 0xb6uy; 0x76uy|] + let NOISE_H = + [| + 0xd1uy + 0xfbuy + 0xf6uy + 0xdeuy + 0xe4uy + 0xf6uy + 0x86uy + 0xf1uy + 0x32uy + 0xfduy + 0x70uy + 0x2cuy + 0x4auy + 0xbfuy + 0x8fuy + 0xbauy + 0x4buy + 0xb4uy + 0x20uy + 0xd8uy + 0x9duy + 0x2auy + 0x04uy + 0x8auy + 0x3cuy + 0x4fuy + 0x4cuy + 0x09uy + 0x2euy + 0x37uy + 0xb6uy + 0x76uy + |] type NextNoiseStep = | ActOne @@ -41,159 +111,208 @@ module PeerChannelEncryptor = | PostActOne | PostActTwo - type BidirectionalNoiseState = { - /// The handshake hash. This value is the accumulated hash of all handhsake data that has been sent - /// and received so far during the handshake process. - H: uint256 - /// The chaining key. This value is the accumulated hash of all previous ECDH outputs. - /// At the end of the handshake, it is used for deriving the encryption keys for Lightning Messages. - CK: uint256 - } + type BidirectionalNoiseState = + { + /// The handshake hash. This value is the accumulated hash of all handhsake data that has been sent + /// and received so far during the handshake process. + H: uint256 + /// The chaining key. This value is the accumulated hash of all previous ECDH outputs. + /// At the end of the handshake, it is used for deriving the encryption keys for Lightning Messages. + CK: uint256 + } type DirectionalNoisestate = | OutBound of OutBound | InBound of InBound - and OutBound = { IE: Key } + and OutBound = + { + IE: Key + } - and InBound = { - IE: PubKey option // Some if state >= PostActOne - RE: Key option // Some if state >= PostActTwo - TempK2: uint256 option // Some if state >= PostActTwo - } + and InBound = + { + IE: option // Some if state >= PostActOne + RE: option // Some if state >= PostActTwo + TempK2: option // Some if state >= PostActTwo + } type NoiseState = | InProgress of InProgressNoiseState | Finished of FinishedNoiseState - and InProgressNoiseState = { - State: NoiseStep - DirectionalState: DirectionalNoisestate - BidirectionalState: BidirectionalNoiseState - } - - and FinishedNoiseState = { - /// Sending key - SK: uint256 - /// Sending nonce - SN: uint64 - SCK: uint256 - /// Reading key - RK: uint256 - /// Reading nonce - RN: uint64 - RCK: uint256 - } - - type PeerChannelEncryptor = internal { - TheirNodeId: NodeId option - NoiseState: NoiseState - } - with - member this.IsReadyForEncryption() = - match this.NoiseState with - | Finished _ -> true - | _ -> false - - member this.GetNoiseStep() = - match this.NoiseState with - | InProgress d -> - match d.State with - | NoiseStep.PreActOne -> NextNoiseStep.ActOne - | NoiseStep.PostActOne -> NextNoiseStep.ActTwo - | NoiseStep.PostActTwo -> NextNoiseStep.ActThree - | Finished _ -> NextNoiseStep.NoiseComplete + and InProgressNoiseState = + { + State: NoiseStep + DirectionalState: DirectionalNoisestate + BidirectionalState: BidirectionalNoiseState + } + + and FinishedNoiseState = + { + /// Sending key + SK: uint256 + /// Sending nonce + SN: uint64 + SCK: uint256 + /// Reading key + RK: uint256 + /// Reading nonce + RN: uint64 + RCK: uint256 + } + + type PeerChannelEncryptor = + internal + { + TheirNodeId: option + NoiseState: NoiseState + } + + member this.IsReadyForEncryption() = + match this.NoiseState with + | Finished _ -> true + | _ -> false + + member this.GetNoiseStep() = + match this.NoiseState with + | InProgress d -> + match d.State with + | NoiseStep.PreActOne -> NextNoiseStep.ActOne + | NoiseStep.PostActOne -> NextNoiseStep.ActTwo + | NoiseStep.PostActTwo -> NextNoiseStep.ActThree + | Finished _ -> NextNoiseStep.NoiseComplete module PeerChannelEncryptor = - let newOutBound (NodeId theirNodeId, ourNodeSecret: Key) = - let hashInput = Array.concat[| NOISE_H; theirNodeId.ToBytes()|] + let newOutBound(NodeId theirNodeId, ourNodeSecret: Key) = + let hashInput = Array.concat [| NOISE_H; theirNodeId.ToBytes() |] let h = uint256(Hashes.SHA256(hashInput)) + { TheirNodeId = Some(NodeId theirNodeId) - NoiseState = InProgress { - State = PreActOne - DirectionalState = OutBound ({ IE = ourNodeSecret }) - BidirectionalState = {H = h; CK = uint256(NOISE_CK)} - } + NoiseState = + InProgress + { + State = PreActOne + DirectionalState = + OutBound( + { + IE = ourNodeSecret + } + ) + BidirectionalState = + { + H = h + CK = uint256(NOISE_CK) + } + } } - let newInBound (ourNodeSecret: Key) = - let hashInput = Array.concat[|NOISE_H; ourNodeSecret.PubKey.ToBytes()|] + let newInBound(ourNodeSecret: Key) = + let hashInput = + Array.concat + [| + NOISE_H + ourNodeSecret.PubKey.ToBytes() + |] + let h = uint256(Hashes.SHA256(hashInput)) { TheirNodeId = None - NoiseState = InProgress { - State = PreActOne - DirectionalState = InBound { IE = None; RE = None; TempK2 = None} - BidirectionalState = {H = h; CK = uint256(NOISE_CK)} - } + NoiseState = + InProgress + { + State = PreActOne + DirectionalState = + InBound + { + IE = None + RE = None + TempK2 = None + } + BidirectionalState = + { + H = h + CK = uint256(NOISE_CK) + } + } } - let internal decryptWithAD(n: uint64, key: uint256, ad: byte[], cipherText: ReadOnlySpan): Result = + + let internal decryptWithAD + ( + n: uint64, + key: uint256, + ad: array, + cipherText: ReadOnlySpan + ) : Result, _> = crypto.decryptWithAD(n, key, ad, cipherText) |> Result.mapError(PeerError.CryptoError) - let internal hkdfExtractExpand(salt: byte[], ikm: byte[]) = + let internal hkdfExtractExpand(salt: array, ikm: array) = let prk = Hashes.HMACSHA256(salt, ikm) - let t1 = Hashes.HMACSHA256(prk, [|1uy|]) - let t2 = Hashes.HMACSHA256(prk, Array.append t1 [|2uy|]) + let t1 = Hashes.HMACSHA256(prk, [| 1uy |]) + let t2 = Hashes.HMACSHA256(prk, Array.append t1 [| 2uy |]) (t1, t2) - let private hkdf (sharedSecret: byte[]) (state: PeerChannelEncryptor) = + let private hkdf + (sharedSecret: array) + (state: PeerChannelEncryptor) + = match state.NoiseState with | InProgress inProgressState -> let (t1, t2) = let ck = inProgressState.BidirectionalState.CK hkdfExtractExpand(ck.ToBytes(), sharedSecret) + uint256 t2, - { - state - with - NoiseState = - InProgress { - inProgressState - with - BidirectionalState = - { - inProgressState.BidirectionalState - with - CK = uint256 t1 - } + { state with + NoiseState = + InProgress + { inProgressState with + BidirectionalState = + { inProgressState.BidirectionalState with + CK = uint256 t1 } - } - | _ -> - failwith "Invalid state when calculating hdkf" - - let private updateHWith (state: PeerChannelEncryptor) (item: byte[]) = - match state.NoiseState with - | InProgress inProgressNoiseState -> - { - state - with - NoiseState = - InProgress { - inProgressNoiseState - with - BidirectionalState = - { - inProgressNoiseState.BidirectionalState - with - H = - [| - inProgressNoiseState.BidirectionalState.H.ToBytes() - item - |] - |> Array.concat - |> Hashes.SHA256 - |> uint256 - } - } - } - | _ -> failwith "Invalid state when updating H" + } + } + | _ -> failwith "Invalid state when calculating hdkf" - let private outBoundNoiseAct (state: PeerChannelEncryptor, ourKey: Key, theirKey: PubKey) = + let private updateHWith + (state: PeerChannelEncryptor) + (item: array) + = + match state.NoiseState with + | InProgress inProgressNoiseState -> + { state with + NoiseState = + InProgress + { inProgressNoiseState with + BidirectionalState = + { inProgressNoiseState.BidirectionalState with + H = + [| + inProgressNoiseState.BidirectionalState.H.ToBytes + () + item + |] + |> Array.concat + |> Hashes.SHA256 + |> uint256 + } + } + } + | _ -> failwith "Invalid state when updating H" + + let private outBoundNoiseAct + ( + state: PeerChannelEncryptor, + ourKey: Key, + theirKey: PubKey + ) = let ourPub = ourKey.PubKey + let tempK, s3 = let s2 = updateHWith state (ourPub.ToBytes()) let ss = Secret.FromKeyPair(theirKey, ourKey) @@ -202,211 +321,322 @@ module PeerChannelEncryptor = let c = let h = match s3.NoiseState with - | InProgress { BidirectionalState = bState } -> - Some bState.H - | _ -> - None - crypto.encryptWithAD (0UL, tempK, ReadOnlySpan(h.Value.ToBytes()), ReadOnlySpan([||])) - let resultToSend = Array.concat (seq [ [|0uy|]; ourPub.ToBytes(); c ]) + | InProgress { + BidirectionalState = bState + } -> Some bState.H + | _ -> None + + crypto.encryptWithAD( + 0UL, + tempK, + ReadOnlySpan(h.Value.ToBytes()), + ReadOnlySpan([||]) + ) + + let resultToSend = + Array.concat(seq [ [| 0uy |]; ourPub.ToBytes(); c ]) + let newState = updateHWith s3 c (resultToSend, tempK), newState - let private inBoundNoiseAct (state: PeerChannelEncryptor, act: byte[], ourKey: Key): Result<(PubKey * uint256) * PeerChannelEncryptor, PeerError> = - if (act.Length <> 50) then raise <| ArgumentException(sprintf "Invalid act length: %d" (act.Length)) + let private inBoundNoiseAct + ( + state: PeerChannelEncryptor, + act: array, + ourKey: Key + ) : Result<(PubKey * uint256) * PeerChannelEncryptor, PeerError> = + if (act.Length <> 50) then + raise + <| ArgumentException( + sprintf "Invalid act length: %i" (act.Length) + ) + if (act.[0] <> 0uy) then - Error(UnknownHandshakeVersionNumber (act.[0])) + Error(UnknownHandshakeVersionNumber(act.[0])) else let publicKeyBytes = act.[1..33] + match PubKey.TryCreatePubKey publicKeyBytes with | false, _ -> - Error(PeerError.CryptoError(InvalidPublicKey(publicKeyBytes))) + Error( + PeerError.CryptoError(InvalidPublicKey(publicKeyBytes)) + ) | true, theirPub -> let tempK, s3 = let ss = Secret.FromKeyPair(theirPub, ourKey) let s2 = updateHWith state (theirPub.ToBytes()) hkdf ss s2 + let currentH = match s3.NoiseState with - | InProgress { BidirectionalState = bState } -> - Some bState.H - | _ -> - None + | InProgress { + BidirectionalState = bState + } -> Some bState.H + | _ -> None + result { - let! _ = decryptWithAD (0UL, tempK, currentH.Value.ToBytes(), ReadOnlySpan(act.[34..])) - let s4 = updateHWith s3 act.[34..] + let! _ = + decryptWithAD( + 0UL, + tempK, + currentH.Value.ToBytes(), + ReadOnlySpan(act.[34..]) + ) + + let s4 = updateHWith s3 act.[34..] return ((theirPub, tempK), s4) } - let getActOne (pce: PeerChannelEncryptor) : byte[] * PeerChannelEncryptor = + let getActOne + (pce: PeerChannelEncryptor) + : array * PeerChannelEncryptor = match pce.NoiseState with - | InProgress { State = state; DirectionalState = dState} -> + | InProgress { + State = state + DirectionalState = dState + } -> match dState with - | OutBound { IE = ie } -> + | OutBound { + IE = ie + } -> if state <> PreActOne then failwith "Requested act at wrong step" else - outBoundNoiseAct (pce, ie, pce.TheirNodeId.Value.Value) + outBoundNoiseAct(pce, ie, pce.TheirNodeId.Value.Value) |> fun ((res, _), pce2) -> match pce2.NoiseState with | InProgress inProgressNoiseState -> res, - { - pce2 - with - NoiseState = - InProgress { - inProgressNoiseState with - State = - NoiseStep.PostActOne - } + { pce2 with + NoiseState = + InProgress + { inProgressNoiseState with + State = NoiseStep.PostActOne + } } | _ -> failwith "Invalid state for updating state" | _ -> failwith "Wrong Direction for Act" - | _ -> failwith "Cannot get act one after noise handshake completes" - - let processActOneWithEphemeralKey (actOne: byte[]) (ourNodeSecret: Key) (ourEphemeral: Key) (pce: PeerChannelEncryptor): Result = + | _ -> + failwith "Cannot get act one after noise handshake completes" + + let processActOneWithEphemeralKey + (actOne: array) + (ourNodeSecret: Key) + (ourEphemeral: Key) + (pce: PeerChannelEncryptor) + : Result * _, PeerError> = match pce.NoiseState with - | InProgress { State = state; DirectionalState = dState; } -> + | InProgress { + State = state + DirectionalState = dState + } -> match dState with | InBound _ -> - if state <> PreActOne then + if state <> PreActOne then failwith "Requested act at wrong step" inBoundNoiseAct(pce, actOne, ourNodeSecret) >>= fun res -> - let (theirPub, _), pce2 = res - let pce3 = - match pce2.NoiseState with - | InProgress inProgressState -> - match inProgressState.DirectionalState with - | InBound inBoundState -> - { - pce2 - with - NoiseState = - InProgress { - inProgressState - with - DirectionalState = - InBound { - inBoundState - with - IE = Some theirPub - RE = Some ourEphemeral - } + let (theirPub, _), pce2 = res + + let pce3 = + match pce2.NoiseState with + | InProgress inProgressState -> + match inProgressState.DirectionalState with + | InBound inBoundState -> + { pce2 with + NoiseState = + InProgress + { inProgressState with + DirectionalState = + InBound + { inBoundState with + IE = + Some + theirPub + RE = + Some + ourEphemeral + } } - } - | _ -> failwith "Invalid directional state" - | _ -> failwith "Invalid noise state" - let (res, tempK), pce4 = outBoundNoiseAct (pce3, ourEphemeral, theirPub) - let pce5 = - match pce4.NoiseState with - | InProgress inProgressState -> - match inProgressState.DirectionalState with - | InBound inBoundState -> - { - pce4 - with - NoiseState = - InProgress { - inProgressState - with - DirectionalState = - InBound { - inBoundState - with - TempK2 = Some tempK - } - State = NoiseStep.PostActTwo + } + | _ -> failwith "Invalid directional state" + | _ -> failwith "Invalid noise state" + + let (res, tempK), pce4 = + outBoundNoiseAct(pce3, ourEphemeral, theirPub) + + let pce5 = + match pce4.NoiseState with + | InProgress inProgressState -> + match inProgressState.DirectionalState with + | InBound inBoundState -> + { pce4 with + NoiseState = + InProgress + { inProgressState with + DirectionalState = + InBound + { inBoundState with + TempK2 = + Some + tempK + } + State = + NoiseStep.PostActTwo } - } - | _ -> failwith "Invalid directional state" - | _ -> failwith "Invalid noise state" + } + | _ -> failwith "Invalid directional state" + | _ -> failwith "Invalid noise state" - Ok (res, pce5) + Ok(res, pce5) | _ -> failwith "Requested act at wrong step" - | _ -> - failwith "Cannot get acg one after noise handshake completes" - - let processActOneWithKey (actOne: byte[]) (ourNodeSecret: Key) (pce: PeerChannelEncryptor): Result = - if (actOne.Length <> 50) then raise <| ArgumentException(sprintf "invalid actOne length: %d" (actOne.Length)) + | _ -> failwith "Cannot get acg one after noise handshake completes" + + let processActOneWithKey + (actOne: array) + (ourNodeSecret: Key) + (pce: PeerChannelEncryptor) + : Result * _, PeerError> = + if (actOne.Length <> 50) then + raise + <| ArgumentException( + sprintf "invalid actOne length: %i" (actOne.Length) + ) let ephemeralKey = new Key() processActOneWithEphemeralKey actOne ourNodeSecret ephemeralKey pce - let processActTwo (actTwo: byte[]) (ourNodeSecret: Key) (pce: PeerChannelEncryptor): Result<(byte[] * NodeId) * PeerChannelEncryptor, PeerError> = + let processActTwo + (actTwo: array) + (ourNodeSecret: Key) + (pce: PeerChannelEncryptor) + : Result<(array * NodeId) * PeerChannelEncryptor, PeerError> = match pce.NoiseState with - | InProgress {State = state; DirectionalState = dState } -> + | InProgress { + State = state + DirectionalState = dState + } -> match dState with - | OutBound {IE = ie} -> + | OutBound { + IE = ie + } -> if state <> NoiseStep.PostActOne then failwith "Requested act at wrong step" - inBoundNoiseAct (pce, actTwo, ie) + + inBoundNoiseAct(pce, actTwo, ie) >>= fun ((re, tempK2), pce2) -> - let ourNodeId = ourNodeSecret.PubKey - let encryptedRes1 = - let currentH = - match pce2.NoiseState with - | InProgress { BidirectionalState = bState } -> - Some bState.H - | _ -> - None - crypto.encryptWithAD (1UL, tempK2, ReadOnlySpan(currentH.Value.ToBytes()), ReadOnlySpan(ourNodeId.ToBytes())) - let pce3 = updateHWith pce2 encryptedRes1 - let (tempK, pce4) = - let ss = Secret.FromKeyPair(re, ourNodeSecret) - hkdf ss pce3 - - let encryptedRes2 = - let currentH = - match pce4.NoiseState with - | InProgress { BidirectionalState = bState } -> - Some bState.H - | _ -> - None - crypto.encryptWithAD (0UL, tempK, ReadOnlySpan(currentH.Value.ToBytes()), ReadOnlySpan([||])) - - let (sk, rk) = - let currentCK = - match pce4.NoiseState with - | InProgress { BidirectionalState = bState } -> - Some bState.CK - | _ -> - None - hkdfExtractExpand(currentCK.Value.ToBytes(), [||]) - - let newPce = - let ck = - match pce4.NoiseState with - | InProgress { BidirectionalState = bState } -> - Some bState.CK - | _ -> - None - { - pce4 - with - NoiseState = - Finished { - FinishedNoiseState.SK = uint256 sk + let ourNodeId = ourNodeSecret.PubKey + + let encryptedRes1 = + let currentH = + match pce2.NoiseState with + | InProgress { + BidirectionalState = bState + } -> Some bState.H + | _ -> None + + crypto.encryptWithAD( + 1UL, + tempK2, + ReadOnlySpan(currentH.Value.ToBytes()), + ReadOnlySpan(ourNodeId.ToBytes()) + ) + + let pce3 = updateHWith pce2 encryptedRes1 + + let (tempK, pce4) = + let ss = Secret.FromKeyPair(re, ourNodeSecret) + hkdf ss pce3 + + let encryptedRes2 = + let currentH = + match pce4.NoiseState with + | InProgress { + BidirectionalState = bState + } -> Some bState.H + | _ -> None + + crypto.encryptWithAD( + 0UL, + tempK, + ReadOnlySpan(currentH.Value.ToBytes()), + ReadOnlySpan([||]) + ) + + let (sk, rk) = + let currentCK = + match pce4.NoiseState with + | InProgress { + BidirectionalState = bState + } -> Some bState.CK + | _ -> None + + hkdfExtractExpand( + currentCK.Value.ToBytes(), + [||] + ) + + let newPce = + let ck = + match pce4.NoiseState with + | InProgress { + BidirectionalState = bState + } -> Some bState.CK + | _ -> None + + { pce4 with + NoiseState = + Finished + { + FinishedNoiseState.SK = + uint256 sk SN = 0UL SCK = ck.Value RK = uint256 rk RN = 0UL RCK = ck.Value } - } - let resultToSend = Array.concat (seq [ [|0uy|]; encryptedRes1; encryptedRes2 ]) - Ok ((resultToSend, newPce.TheirNodeId.Value), newPce) + } + + let resultToSend = + Array.concat( + seq + [ + [| 0uy |] + encryptedRes1 + encryptedRes2 + ] + ) + + Ok((resultToSend, newPce.TheirNodeId.Value), newPce) | _ -> failwith "Wrong direction with act" | _ -> failwith "Cannot get act one after noise handshake completes" - let processActThree (actThree: byte[]) (pce: PeerChannelEncryptor): Result = - if (actThree.Length <> 66) then raise <| ArgumentException (sprintf "actThree must be 66 bytes, but it was %d" (actThree.Length)) + let processActThree + (actThree: array) + (pce: PeerChannelEncryptor) + : Result = + if (actThree.Length <> 66) then + raise + <| ArgumentException( + sprintf + "actThree must be 66 bytes, but it was %i" + (actThree.Length) + ) + match pce.NoiseState with - | InProgress { State = state; DirectionalState = dState;} -> + | InProgress { + State = state + DirectionalState = dState + } -> match dState with - | InBound { IE = _; RE = re; TempK2 = tempk2 } -> + | InBound { + IE = _ + RE = re + TempK2 = tempk2 + } -> if (state <> PostActTwo) then failwith "Requested act at wrong step" else if (actThree.[0] <> 0uy) then @@ -414,178 +644,257 @@ module PeerChannelEncryptor = else let h = match pce.NoiseState with - | InProgress { BidirectionalState = bState } -> - Some bState.H - | _ -> - None - decryptWithAD (1UL, tempk2.Value, h.Value.ToBytes(), ReadOnlySpan(actThree.[1..49])) + | InProgress { + BidirectionalState = bState + } -> Some bState.H + | _ -> None + + decryptWithAD( + 1UL, + tempk2.Value, + h.Value.ToBytes(), + ReadOnlySpan(actThree.[1..49]) + ) >>= fun theirNodeId -> - match PubKey.TryCreatePubKey theirNodeId with - | false, _ -> - Error(PeerError.CryptoError(CryptoError.InvalidPublicKey(theirNodeId))) - | true, theirNodeIdPubKey -> - let tempK, pce3 = - let pce2 = - let pceWithUpdatedH = - pce - |> fun p -> updateHWith p (actThree.[1..49]) - { - pceWithUpdatedH - with + match PubKey.TryCreatePubKey theirNodeId with + | false, _ -> + Error( + PeerError.CryptoError( + CryptoError.InvalidPublicKey( + theirNodeId + ) + ) + ) + | true, theirNodeIdPubKey -> + let tempK, pce3 = + let pce2 = + let pceWithUpdatedH = + pce + |> fun p -> + updateHWith + p + (actThree.[1..49]) + + { pceWithUpdatedH with TheirNodeId = - theirNodeIdPubKey |> NodeId |> Some - } - - let ss = Secret.FromKeyPair(pce2.TheirNodeId.Value.Value, re.Value) - hkdf ss pce2 - - let h = - match pce3.NoiseState with - | InProgress { BidirectionalState = bState } -> - Some bState.H - | _ -> - None - decryptWithAD(0UL, tempK, h.Value.ToBytes(), ReadOnlySpan(actThree.[50..])) - >>= fun _ -> - let ck = + theirNodeIdPubKey + |> NodeId + |> Some + } + + let ss = + Secret.FromKeyPair( + pce2.TheirNodeId.Value.Value, + re.Value + ) + + hkdf ss pce2 + + let h = match pce3.NoiseState with - | InProgress { BidirectionalState = bState } -> - bState.CK - | _ -> - failwith "Invalid state when reading CK" - let (rk, sk) = hkdfExtractExpand(ck.ToBytes(), [||]) - let newPce = - { - pce3 - with + | InProgress { + BidirectionalState = bState + } -> Some bState.H + | _ -> None + + decryptWithAD( + 0UL, + tempK, + h.Value.ToBytes(), + ReadOnlySpan(actThree.[50..]) + ) + >>= fun _ -> + let ck = + match pce3.NoiseState with + | InProgress { + BidirectionalState = bState + } -> bState.CK + | _ -> + failwith + "Invalid state when reading CK" + + let (rk, sk) = + hkdfExtractExpand( + ck.ToBytes(), + [||] + ) + + let newPce = + { pce3 with NoiseState = - Finished { - FinishedNoiseState.SK = sk |> uint256 - SN = 0UL - SCK = ck - RK = rk |> uint256 - RN= 0UL - RCK = ck - } - } - Ok(newPce.TheirNodeId.Value, newPce) + Finished + { + FinishedNoiseState.SK = + sk + |> uint256 + SN = 0UL + SCK = ck + RK = + rk + |> uint256 + RN = 0UL + RCK = ck + } + } + + Ok(newPce.TheirNodeId.Value, newPce) | _ -> failwith "wrong direction for act" | _ -> failwith "Cannot get act one after noise handshake completes" - let private encryptCore (msg: byte[]) (pceLocal) = + let private encryptCore (msg: array) pceLocal = let sn, sk = match pceLocal.NoiseState with - | Finished { SN = sn; SK = sk} -> - sn, sk - | _ -> - failwith "Invalid state when reading SK" + | Finished { + SN = sn + SK = sk + } -> sn, sk + | _ -> failwith "Invalid state when reading SK" + let lengthBytes = BitConverter.GetBytesBE(uint16 msg.Length) // 2 byte length + 16 bytes MAC - let cipherLength = crypto.encryptWithAD (sn, sk, ReadOnlySpan([||]) , ReadOnlySpan(lengthBytes)) + let cipherLength = + crypto.encryptWithAD( + sn, + sk, + ReadOnlySpan([||]), + ReadOnlySpan(lengthBytes) + ) + let newSN = (sn + 1UL) + let pce3 = match pceLocal.NoiseState with | Finished finishedNoiseState -> - { - pceLocal with - NoiseState = - Finished { - finishedNoiseState - with - SN = newSN + { pceLocal with + NoiseState = + Finished + { finishedNoiseState with + SN = newSN } } | _ -> failwith "Invalid state when incrementing SN" - let cipherText = crypto.encryptWithAD (newSN, sk, ReadOnlySpan[||], ReadOnlySpan(msg)) + + let cipherText = + crypto.encryptWithAD( + newSN, + sk, + ReadOnlySpan [||], + ReadOnlySpan(msg) + ) + let pce4 = match pce3.NoiseState with | Finished finishedNoiseState -> - { - pce3 with - NoiseState = - Finished { - finishedNoiseState - with - SN = newSN + 1UL + { pce3 with + NoiseState = + Finished + { finishedNoiseState with + SN = newSN + 1UL } } | _ -> failwith "Invalid state when incrementing SN" - Array.concat (seq [ cipherLength; cipherText ]), pce4 - let encryptMessage (msg: byte[]) (pce: PeerChannelEncryptor): byte[] * PeerChannelEncryptor = - if msg.Length > 65535 then failwith "Attempted encrypt message longer thaan 65535 bytes" + Array.concat(seq [ cipherLength; cipherText ]), pce4 + + let encryptMessage + (msg: array) + (pce: PeerChannelEncryptor) + : array * PeerChannelEncryptor = + if msg.Length > 65535 then + failwith "Attempted encrypt message longer thaan 65535 bytes" + match pce.NoiseState with - | Finished { SK = sk; SN = sn; SCK = sck } -> - let refreshState(pceLocal: PeerChannelEncryptor) = - let (newSCK, newSK) = hkdfExtractExpand(sck.ToBytes(), sk.ToBytes()) + | Finished { + SK = sk + SN = sn + SCK = sck + } -> + let refreshState(pceLocal: PeerChannelEncryptor) = + let (newSCK, newSK) = + hkdfExtractExpand(sck.ToBytes(), sk.ToBytes()) + match pceLocal.NoiseState with | Finished finishedNoiseState -> - { - pceLocal with - NoiseState = - Finished { - finishedNoiseState - with - SCK = uint256 newSCK - SK = uint256 newSK - SN = 0UL + { pceLocal with + NoiseState = + Finished + { finishedNoiseState with + SCK = uint256 newSCK + SK = uint256 newSK + SN = 0UL } } | _ -> failwith "Invalid state when encrypting" if sn >= 1000UL then let newState = refreshState(pce) - encryptCore(msg) newState + encryptCore (msg) newState else encryptCore (msg) pce - | _ -> failwith "Tried to encrypt a message prior to noise handshake completion" + | _ -> + failwith + "Tried to encrypt a message prior to noise handshake completion" - let private decryptCore (msg) (localPce) = + let private decryptCore msg localPce = let newRK = match localPce.NoiseState with - | Finished { RK = rk } -> - rk - | _ -> - failwith "Invalid state when reading RK" + | Finished { + RK = rk + } -> rk + | _ -> failwith "Invalid state when reading RK" + let newRN = match localPce.NoiseState with - | Finished { RN = rn } -> - rn - | _ -> - failwith "Invalid state when reading RN" + | Finished { + RN = rn + } -> rn + | _ -> failwith "Invalid state when reading RN" + decryptWithAD(newRN, newRK, [||], ReadOnlySpan(msg)) >>= fun plainText -> - let newPCE = - match localPce.NoiseState with - | Finished finishedNoiseState -> - { - localPce with + let newPCE = + match localPce.NoiseState with + | Finished finishedNoiseState -> + { localPce with NoiseState = - Finished { - finishedNoiseState - with - RN = finishedNoiseState.RN + 1UL - } - } - | _ -> failwith "Invalid state when incrementing RN" - (BitConverter.ToUint16BE(plainText), newPCE) |> Ok - let decryptLengthHeader (msg: byte[]) (pce): Result = - if (msg.Length <> 16 + 2) then raise <| ArgumentException(sprintf "Invalid length of message %d" msg.Length) + Finished + { finishedNoiseState with + RN = finishedNoiseState.RN + 1UL + } + } + | _ -> failwith "Invalid state when incrementing RN" + + (BitConverter.ToUint16BE(plainText), newPCE) |> Ok + + let decryptLengthHeader + (msg: array) + pce + : Result = + if (msg.Length <> 16 + 2) then + raise + <| ArgumentException( + sprintf "Invalid length of message %i" msg.Length + ) + match pce.NoiseState with - | Finished { RK = rk; RN = rn; RCK = rck } -> - let refreshState (localPce) = - let (newRCK, newRK) = hkdfExtractExpand(rck.ToBytes(), rk.ToBytes()) + | Finished { + RK = rk + RN = rn + RCK = rck + } -> + let refreshState localPce = + let (newRCK, newRK) = + hkdfExtractExpand(rck.ToBytes(), rk.ToBytes()) + match localPce.NoiseState with | Finished finishedNoiseState -> - { - localPce with - NoiseState = - Finished { - finishedNoiseState - with - RCK = uint256 newRCK - RK = uint256 newRK - RN = 0UL + { localPce with + NoiseState = + Finished + { finishedNoiseState with + RCK = uint256 newRCK + RK = uint256 newRK + RN = 0UL } } | _ -> failwith "Invalid state when decrypting" @@ -596,113 +905,152 @@ module PeerChannelEncryptor = else decryptCore msg (pce) - | _ -> failwith "Tried to encrypt a message prior to noies handshake completion" + | _ -> + failwith + "Tried to encrypt a message prior to noies handshake completion" + + let decryptMessage (msg: array) pce = + if (msg.Length > 16 + 65535) then + raise + <| ArgumentException( + sprintf "Invalid length of message %i" msg.Length + ) - let decryptMessage (msg: byte[]) (pce) = - if (msg.Length > 16 + 65535) then raise <| ArgumentException(sprintf "Invalid length of message %d" msg.Length) match pce.NoiseState with - | Finished { RK = rk; RN = rn;} -> + | Finished { + RK = rk + RN = rn + } -> decryptWithAD(rn, rk, [||], ReadOnlySpan(msg)) >>= fun plainText -> - let newPCE = - match pce.NoiseState with - | Finished finishedNoiseState -> - { - pce with + let newPCE = + match pce.NoiseState with + | Finished finishedNoiseState -> + { pce with NoiseState = - Finished { - finishedNoiseState - with - RN = finishedNoiseState.RN + 1UL - } - } - | _ -> failwith "Invalid state when incrementing RN" - Ok(plainText, newPCE) - | _ -> failwith "Tried to encyrpt a message prior to noise handshake completion" + Finished + { finishedNoiseState with + RN = finishedNoiseState.RN + 1UL + } + } + | _ -> failwith "Invalid state when incrementing RN" + + Ok(plainText, newPCE) + | _ -> + failwith + "Tried to encyrpt a message prior to noise handshake completion" /// Might remove in the future [] module PeerChannelEncryptorMonad = type PeerChannelEncryptorComputation<'a> = - PeerChannelEncryptorComputation of (PeerChannelEncryptor -> Result<'a * PeerChannelEncryptor, PeerError>) + | PeerChannelEncryptorComputation of + (PeerChannelEncryptor -> Result<'a * PeerChannelEncryptor, PeerError>) let runP pcec initialState = - let (PeerChannelEncryptorComputation innerFn)= pcec + let (PeerChannelEncryptorComputation innerFn) = pcec innerFn initialState let returnP x = - let innerFn state = + let innerFn state = Ok(x, state) + PeerChannelEncryptorComputation innerFn - let bindP (f: 'a -> PeerChannelEncryptorComputation<'b>) (xT: PeerChannelEncryptorComputation<'a>): PeerChannelEncryptorComputation<'b> = + let bindP + (f: 'a -> PeerChannelEncryptorComputation<'b>) + (xT: PeerChannelEncryptorComputation<'a>) + : PeerChannelEncryptorComputation<'b> = let innerFn state = runP xT state >>= fun (res, state2) -> - let h = runP (f res) state2 - h + let h = runP (f res) state2 + h + PeerChannelEncryptorComputation innerFn let mapP f = - bindP (f >> returnP) + bindP(f >> returnP) /// Lift non-failable function to monadic world - let fromPlainFunction (f: PeerChannelEncryptor -> 'a * PeerChannelEncryptor) = + let fromPlainFunction + (f: PeerChannelEncryptor -> 'a * PeerChannelEncryptor) + = let innerFn state = let (result, newState) = f state Ok(result, newState) + PeerChannelEncryptorComputation innerFn /// Lift non-failable reader function to monadic world - let fromReaderFunction (f: PeerChannelEncryptor -> 'a) = + let fromReaderFunction(f: PeerChannelEncryptor -> 'a) = let innerFn state = let result = f state result, state + fromPlainFunction innerFn /// Lift non-failable writer function to monadic world let fromWriterFunction f = let f2 state = (), f state + fromPlainFunction f2 - let fromFailableFunction (f: PeerChannelEncryptor -> Result<'a * PeerChannelEncryptor, _>) = + let fromFailableFunction + (f: PeerChannelEncryptor -> Result<'a * PeerChannelEncryptor, _>) + = PeerChannelEncryptorComputation f - let fromFailableReaderFunction (f: PeerChannelEncryptor -> Result<'a, _>) = + let fromFailableReaderFunction(f: PeerChannelEncryptor -> Result<'a, _>) = let innerFn state = - f state - >>= fun result -> - Ok (result, state) + f state >>= fun result -> Ok(result, state) + PeerChannelEncryptorComputation innerFn - let processActOneWithKey (actOne) (key) = - fromFailableFunction(PeerChannelEncryptor.processActOneWithKey actOne key) + let processActOneWithKey actOne key = + fromFailableFunction( + PeerChannelEncryptor.processActOneWithKey actOne key + ) - let processActOneWithEphemeralKey (actOne) (key) (ourEphemeral) = - fromFailableFunction(PeerChannelEncryptor.processActOneWithEphemeralKey actOne key ourEphemeral) + let processActOneWithEphemeralKey actOne key ourEphemeral = + fromFailableFunction( + PeerChannelEncryptor.processActOneWithEphemeralKey + actOne + key + ourEphemeral + ) - let processActTwo (actTwo) (ourNodeSecret) = - fromFailableFunction (PeerChannelEncryptor.processActTwo actTwo ourNodeSecret) + let processActTwo actTwo ourNodeSecret = + fromFailableFunction( + PeerChannelEncryptor.processActTwo actTwo ourNodeSecret + ) - let processActThree (actThree) = - fromFailableFunction (PeerChannelEncryptor.processActThree actThree) + let processActThree actThree = + fromFailableFunction(PeerChannelEncryptor.processActThree actThree) - let encryptMessage (msg) = + let encryptMessage msg = fromPlainFunction(PeerChannelEncryptor.encryptMessage msg) - let decryptMessage (msg) = + let decryptMessage msg = fromFailableFunction(PeerChannelEncryptor.decryptMessage msg) - let decryptLengthHeader (msg) = + let decryptLengthHeader msg = fromFailableFunction(PeerChannelEncryptor.decryptLengthHeader msg) type PeerChannelEncryptorComputationBuilder() = - member this.Return(x) = returnP x - member this.ReturnFrom(x) = x - member this.Bind(x, f) = bindP f x - member this.Zero() = returnP () + member this.Return x = + returnP x + + member this.ReturnFrom x = + x + + member this.Bind(x, f) = + bindP f x + + member this.Zero() = + returnP() let noise = PeerChannelEncryptorComputationBuilder() diff --git a/src/DotNetLightning.Core/Peer/PeerError.fs b/src/DotNetLightning.Core/Peer/PeerError.fs index ea5886c97..ffbf30d80 100644 --- a/src/DotNetLightning.Core/Peer/PeerError.fs +++ b/src/DotNetLightning.Core/Peer/PeerError.fs @@ -9,9 +9,9 @@ open DotNetLightning.Serialization.Msgs /// TODO: fix type PeerError = | CryptoError of CryptoError - + | P2PMessageDecodeError of P2PDecodeError - + // ---- handshake logic errors ---- | UnknownHandshakeVersionNumber of uint8 @@ -22,5 +22,6 @@ type PeerError = | P2PMessageDecodeError p2pDecodeError -> sprintf "deserialization error: %s" p2pDecodeError.Message | UnknownHandshakeVersionNumber version -> - sprintf "unknown handshake version number. expected 0, got %i." version - + sprintf + "unknown handshake version number. expected 0, got %i." + version diff --git a/src/DotNetLightning.Core/Peer/PeerTypes.fs b/src/DotNetLightning.Core/Peer/PeerTypes.fs index e0a2194a5..1c03af593 100644 --- a/src/DotNetLightning.Core/Peer/PeerTypes.fs +++ b/src/DotNetLightning.Core/Peer/PeerTypes.fs @@ -6,10 +6,10 @@ open NBitcoin type PeerEvent = // --- initialization --- - | ActOneProcessed of actTwo: byte[] * newPCE: PeerChannelEncryptor - | ActTwoProcessed of (byte[] * NodeId) * newPCE: PeerChannelEncryptor + | ActOneProcessed of actTwo: array * newPCE: PeerChannelEncryptor + | ActTwoProcessed of (array * NodeId) * newPCE: PeerChannelEncryptor | ActThreeProcessed of theirNodeId: NodeId * newPCE: PeerChannelEncryptor - + // --- else --- // --- receiving --- | ReceivedError of error: ErrorMsg * newPCE: PeerChannelEncryptor @@ -18,14 +18,16 @@ type PeerEvent = | ReceivedInit of initMsg: InitMsg * newPCE: PeerChannelEncryptor | ReceivedRoutingMsg of msg: IRoutingMsg * newPCE: PeerChannelEncryptor | ReceivedChannelMsg of msg: IChannelMsg * newPCE: PeerChannelEncryptor - + // --- sending --- - | MsgEncoded of msg: byte[] * newPCE: PeerChannelEncryptor + | MsgEncoded of msg: array * newPCE: PeerChannelEncryptor | FailedToBroadcastTransaction of tx: Transaction type PeerCommand = - | ProcessActOne of actOne: byte[] * ourNodeSecret: Key - | ProcessActTwo of actTwo: byte[] * ourNodeSecret: Key - | ProcessActThree of actThree: byte[] - | DecodeCipherPacket of lengthHeader: byte[] * reader: (int -> byte[]) + | ProcessActOne of actOne: array * ourNodeSecret: Key + | ProcessActTwo of actTwo: array * ourNodeSecret: Key + | ProcessActThree of actThree: array + | DecodeCipherPacket of + lengthHeader: array * + reader: (int -> array) | EncodeMsg of msg: ILightningMsg diff --git a/src/DotNetLightning.Core/Routing/Graph.fs b/src/DotNetLightning.Core/Routing/Graph.fs index 4cfaf218c..f273b2306 100644 --- a/src/DotNetLightning.Core/Routing/Graph.fs +++ b/src/DotNetLightning.Core/Routing/Graph.fs @@ -13,96 +13,129 @@ open ResultUtils.Portability module Graph = /// The cumulative weight of a set of edges (path in the graph). - [] - type RichWeight = { - Weight: double - Cost: LNMoney - Length: int - CLTV: BlockHeightOffset16 - } - with + [] + type RichWeight = + { + Weight: double + Cost: LNMoney + Length: int + CLTV: BlockHeightOffset16 + } + override this.GetHashCode() = this.Weight.GetHashCode() + member this.Equals(o: RichWeight) = this.Weight.Equals(o.Weight) + override this.Equals(o: obj) = match o with | :? RichWeight as other -> this.Equals(other) | _ -> false + member this.CompareTo(other: RichWeight) = this.Weight.CompareTo(other.Weight) - + interface IComparable with - member this.CompareTo(other:obj) = + member this.CompareTo(other: obj) = match other with | :? RichWeight as o -> this.CompareTo(o) | _ -> -1 - + /// We use heuristics to calculate the weight of an edge based on channel age, cltv delta and capacity. /// We favor older channels, with bigger capacity and small cltv delta - type WeightRatios = private { - CLTVDeltaFactor: double - AgeFactor: double - CapacityFactor: double - } - with + type WeightRatios = + private + { + CLTVDeltaFactor: double + AgeFactor: double + CapacityFactor: double + } + static member TryCreate(cltvDeltaFactor, ageFactor, capacityFactor) = let s = cltvDeltaFactor + ageFactor + capacityFactor - if (s <= 0.|| 1. < s) then - sprintf "sum of CLTVDeltaFactor + ageFactor + capacityFactor must in between 0 to 1. it was %f" s + + if (s <= 0. || 1. < s) then + sprintf + "sum of CLTVDeltaFactor + ageFactor + capacityFactor must in between 0 to 1. it was %f" + s |> Error else { CLTVDeltaFactor = cltvDeltaFactor AgeFactor = ageFactor CapacityFactor = capacityFactor - } |> Ok - - [] - type WeightedNode = { - Id: NodeId - Weight: RichWeight - } - with + } + |> Ok + + [] + type WeightedNode = + { + Id: NodeId + Weight: RichWeight + } + member this.Equals(o: WeightedNode) = this.Id.Equals(o.Id) + override this.Equals(o: obj) = match o with | :? WeightedNode as other -> this.Equals(other) | _ -> false + override this.GetHashCode() = let mutable num = 0 - num <- -1640531527 + this.Id.GetHashCode() + ((num <<< 6) + (num >>> 2)) + + num <- + -1640531527 + + this.Id.GetHashCode() + + ((num <<< 6) + (num >>> 2)) + num + interface IEquatable with - member this.Equals o = this.Equals o + member this.Equals o = + this.Equals o + member this.CompareTo(other: WeightedNode) = let weightCmp = this.Weight.CompareTo other.Weight - if (weightCmp <> 0) then weightCmp else - this.Id.Value.ToHex().CompareTo(other.Id.Value.ToHex()) + + if (weightCmp <> 0) then + weightCmp + else + this + .Id + .Value + .ToHex() + .CompareTo(other.Id.Value.ToHex()) + interface IComparable with member this.CompareTo(o: obj) = match o with | :? WeightedNode as wn -> this.CompareTo(wn) | _ -> -1 - - type ChannelDesc = { - ShortChannelId: ShortChannelId - A: NodeId - B: NodeId - } - - type PublicChannel = private { - Announcement: UnsignedChannelAnnouncementMsg - FundingTxId: TxId - Capacity: Money - Update1Opt: UnsignedChannelUpdateMsg option - Update2Opt: UnsignedChannelUpdateMsg option - } - with + + type ChannelDesc = + { + ShortChannelId: ShortChannelId + A: NodeId + B: NodeId + } + + type PublicChannel = + private + { + Announcement: UnsignedChannelAnnouncementMsg + FundingTxId: TxId + Capacity: Money + Update1Opt: option + Update2Opt: option + } + member this.Ann = this.Announcement - static member Create (a, fundingTxId, cap, u1, u2) = + + static member Create(a, fundingTxId, cap, u1, u2) = { Announcement = a FundingTxId = fundingTxId @@ -110,255 +143,459 @@ module Graph = Update1Opt = u1 Update2Opt = u2 } - type GraphLabel = { Desc: ChannelDesc; Update: UnsignedChannelUpdateMsg } - with - static member Create (d, u) = { Desc = d; Update = u } + + type GraphLabel = + { + Desc: ChannelDesc + Update: UnsignedChannelUpdateMsg + } + + static member Create(d, u) = + { + Desc = d + Update = u + } + module GraphEdge = - let hasZeroFee (l: GraphLabel) = + let hasZeroFee(l: GraphLabel) = let u = l.Update u.FeeBaseMSat = LNMoney.Zero && u.FeeProportionalMillionths = 0u - - - [] - type WeightedPath = { - Path: GraphLabel seq - Weight: RichWeight - } - with + + + [] + type WeightedPath = + { + Path: seq + Weight: RichWeight + } + override this.GetHashCode() = let mutable num = 0 - num <- -1640531527 + this.Weight.GetHashCode() + ((num <<< 6) + (num >>> 2)) + + num <- + -1640531527 + + this.Weight.GetHashCode() + + ((num <<< 6) + (num >>> 2)) + for i in this.Path do - num <- -1640531527 + i.GetHashCode() + ((num <<< 6) + (num >>> 2)) + num <- + -1640531527 + i.GetHashCode() + ((num <<< 6) + (num >>> 2)) + num - member this.Equals (o: WeightedPath) = + + member this.Equals(o: WeightedPath) = this.Path.Equals(o.Path) && this.Weight.Equals(o.Weight) + override this.Equals o = match o with | :? WeightedPath as other -> this.Equals other | _ -> false - + interface IEquatable with - member this.Equals o = this.Equals o - member this.CompareTo(other: WeightedPath)= + member this.Equals o = + this.Equals o + + member this.CompareTo(other: WeightedPath) = this.Weight.CompareTo other.Weight - + interface IComparable with member this.CompareTo o = match o with | :? WeightedPath as x -> this.CompareTo(x) | _ -> -1 - - let internal getDesc (u: UnsignedChannelUpdateMsg, ann: UnsignedChannelAnnouncementMsg): ChannelDesc = + + let internal getDesc + ( + u: UnsignedChannelUpdateMsg, + ann: UnsignedChannelAnnouncementMsg + ) : ChannelDesc = let isNode1 = (u.ChannelFlags &&& 1uy) = 0uy - let a, b = if (isNode1) then ann.NodeId1, ann.NodeId2 else ann.NodeId2, ann.NodeId1 - { ShortChannelId = u.ShortChannelId; A = a; B = b } - + + let a, b = + if isNode1 then + ann.NodeId1, ann.NodeId2 + else + ann.NodeId2, ann.NodeId1 + + { + ShortChannelId = u.ShortChannelId + A = a + B = b + } + [] - type DirectedLNGraph = private DirectedLNGraph of Map - with + type DirectedLNGraph = + private + | DirectedLNGraph of Map> + static member Create() = Map.empty |> DirectedLNGraph - - member this.Value = let (DirectedLNGraph v) = this in v + + member this.Value = let (DirectedLNGraph v) = this in v + member this.AddVertex(key: NodeId) = match this.Value |> Map.tryFind key with | None -> this.Value |> Map.add key [] |> DirectedLNGraph | Some _ -> this - - member this.AddEdge({Desc = d}: GraphLabel as e) = + + member this.AddEdge + ({ + Desc = d + }: GraphLabel as e) + = let vertIn = d.A let vertOut = d.B + if (this.ContainsEdge(d)) then this.RemoveEdge(d).AddEdge e else let withVertices = this.AddVertex(vertIn).AddVertex(vertOut) + withVertices.Value |> Map.add (vertOut) (e :: withVertices.Value.[vertOut]) |> DirectedLNGraph - + member this.AddEdge(desc, update) = - this.AddEdge({ Desc = desc; Update = update }) - - member this.AddEdges(edges: seq) = + this.AddEdge( + { + Desc = desc + Update = update + } + ) + + member this.AddEdges + (edges: seq) + = edges |> Seq.fold - (fun (acc: DirectedLNGraph) (desc, update) -> - acc.AddEdge({ Desc = desc; Update = update })) - this + (fun (acc: DirectedLNGraph) (desc, update) -> + acc.AddEdge( + { + Desc = desc + Update = update + } + ) + ) + this + member this.AddEdges(edges: seq) = edges |> Seq.map(fun e -> e.Desc, e.Update) |> this.AddEdges - member this.ContainsEdge({ A = a; B = b; ShortChannelId = id }) = + + member this.ContainsEdge + ({ + A = a + B = b + ShortChannelId = id + }) + = match this.Value |> Map.tryFind b with | None -> false - | Some (adj) -> adj |> List.exists(fun { Desc = d; } -> d.ShortChannelId = id && d.A = a) - - - member this.VertexSet() = this.Value |> Seq.map(fun kvp -> kvp.Key) |> Seq.toList - member this.EdgeSet() = this.Value |> Seq.collect(fun kvp -> kvp.Value) |> Seq.toList - + | Some adj -> + adj + |> List.exists(fun { + Desc = d + } -> d.ShortChannelId = id && d.A = a + ) + + + member this.VertexSet() = + this.Value |> Seq.map(fun kvp -> kvp.Key) |> Seq.toList + + member this.EdgeSet() = + this.Value |> Seq.collect(fun kvp -> kvp.Value) |> Seq.toList + member this.OutgoingEdgesOf(v: NodeId) = - this.EdgeSet() |> List.filter(fun {Desc = d} -> d.A = v) - + this.EdgeSet() + |> List.filter(fun { + Desc = d + } -> d.A = v + ) + member this.IncomingEdgesOf(v: NodeId) = this.Value |> Map.tryFind v |> Option.defaultValue [] - - member this.RemoveEdge({B = b}: ChannelDesc as desc): DirectedLNGraph = + + member this.RemoveEdge + ({ + B = b + }: ChannelDesc as desc) + : DirectedLNGraph = match this.ContainsEdge(desc) with | true -> this.Value - |> Map.add (b) (this.Value |> Map.find b |> List.filter(fun x -> x.Desc <> desc)) + |> Map.add + (b) + (this.Value + |> Map.find b + |> List.filter(fun x -> x.Desc <> desc)) |> DirectedLNGraph | false -> this + member this.RemoveEdges(descriptions: #seq) = - descriptions |> Seq.fold(fun (acc: DirectedLNGraph) d -> acc.RemoveEdge(d)) this - + descriptions + |> Seq.fold (fun (acc: DirectedLNGraph) d -> acc.RemoveEdge(d)) this + member this.TryGetEdge(desc: ChannelDesc) = this.Value - |> Map.tryFind (desc.B) - |> Option.bind(fun adj -> adj |> List.tryFind(fun e -> e.Desc.ShortChannelId = desc.ShortChannelId && e.Desc.A = desc.A)) - - member this.TryGetEdge({ Desc = d }) = + |> Map.tryFind(desc.B) + |> Option.bind(fun adj -> + adj + |> List.tryFind(fun e -> + e.Desc.ShortChannelId = desc.ShortChannelId + && e.Desc.A = desc.A + ) + ) + + member this.TryGetEdge + ({ + Desc = d + }) + = this.TryGetEdge(d) - + member this.GetEdgesBetween(keyA: NodeId, keyB: NodeId) = match this.Value |> Map.tryFind keyB with | None -> List.empty | Some adj -> adj |> List.filter(fun e -> e.Desc.A = keyA) - + member this.ContainsVertex v = this.Value |> Map.containsKey v - + member this.RemoveVertex(key: NodeId) = let ds = this.IncomingEdgesOf(key) |> List.map(fun x -> x.Desc) this.RemoveEdges(ds).Value |> Map.remove key |> DirectedLNGraph - + /// This is the recommended way of creating the network graph. /// We don't include private channels: they would bloat the graph without providing any value(if they are /// private they probably don't want to be involved in routing other people's payments). /// The only private channels we know are ours: we should check them to see if our destination can be reached /// in a single hop via private channel before using the public network graph - static member MakeGraph(channels: Map): DirectedLNGraph= - let result = Dictionary() - + static member MakeGraph + (channels: Map) + : DirectedLNGraph = + let result = Dictionary>() + let addDescToDict(desc: ChannelDesc, u: UnsignedChannelUpdateMsg) = - let previousV = result.TryGetValue(desc.B) |> function true, v -> v | false, _ -> List.empty - result.AddOrReplace(desc.B, GraphLabel.Create(desc, u) :: previousV) + let previousV = + result.TryGetValue(desc.B) + |> function + | true, v -> v + | false, _ -> List.empty + + result.AddOrReplace( + desc.B, + GraphLabel.Create(desc, u) :: previousV + ) + match result.TryGetValue desc.A with | false, _ -> result.Add(desc.A, List.empty) |> ignore | true, _ -> () + channels |> Map.iter(fun _k channel -> channel.Update1Opt |> Option.iter(fun u1 -> let desc1 = getDesc(u1, channel.Announcement) addDescToDict(desc1, u1) - ) + ) + channel.Update2Opt |> Option.iter(fun u2 -> let desc2 = getDesc(u2, channel.Announcement) addDescToDict(desc2, u2) - ) ) - + ) + DirectedLNGraph(result |> Seq.map(|KeyValue|) |> Map.ofSeq) - + member this.PrettyPrint = this.Value - |> Seq.fold(fun acc (kvp) -> - let (v, adj) = kvp.Key, kvp.Value - sprintf "%s[%A]: %A\n" acc (v.Value.ToHex().[0..6]) (adj |> List.map(fun x -> ("--> " + x.Desc.B.Value.ToHex().[0..6]))) - ) "" - + |> Seq.fold + (fun acc kvp -> + let (v, adj) = kvp.Key, kvp.Value + + sprintf + "%s[%A]: %A\n" + acc + (v.Value.ToHex().[0..6]) + (adj + |> List.map(fun x -> + ("--> " + x.Desc.B.Value.ToHex().[0..6]) + )) + ) + "" + module internal RoutingHeuristics = let BLOCK_TIME_TWO_MONTHS = 8640us |> BlockHeightOffset16 let CAPACITY_CHANNEL_LOW = LNMoney.Satoshis(1000L) - let CAPACITY_CHANNEL_HIGH = DotNetLightning.Channel.ChannelConstants.MAX_FUNDING_SATOSHIS.Satoshi |> LNMoney.Satoshis - + + let CAPACITY_CHANNEL_HIGH = + DotNetLightning.Channel.ChannelConstants.MAX_FUNDING_SATOSHIS.Satoshi + |> LNMoney.Satoshis + [] let CLTV_LOW = 9L + [] let CLTV_HIGH = 2016 - - let normalize(v, min, max): double = - if (v <= min) then 0.00001 else - if (v > max) then 0.99999 else - (v - min) / (max - min) - - let internal nodeFee(baseFee: LNMoney, proportionalFee: int64, paymentAmount: LNMoney) = + + let normalize(v, min, max) : double = + if (v <= min) then + 0.00001 + else if (v > max) then + 0.99999 + else + (v - min) / (max - min) + + let internal nodeFee + ( + baseFee: LNMoney, + proportionalFee: int64, + paymentAmount: LNMoney + ) = baseFee + ((paymentAmount * proportionalFee) / 1000000L) - + /// This forces channel_update(s) with fees = 0 to have a minimum of 1msat for the baseFee. Note that /// the update is not being modified and the result of the route computation will still have the update /// with fees=0 which is what will be used to build the onion. - let private edgeFeeCost (edge: GraphLabel, amountWithFees: LNMoney) = - if (GraphEdge.hasZeroFee(edge)) then amountWithFees + nodeFee(LNMoney.One, 0L, amountWithFees) else - let ({Update = update}) = edge - amountWithFees + nodeFee(update.FeeBaseMSat, (int64 update.FeeProportionalMillionths), amountWithFees) - + let private edgeFeeCost(edge: GraphLabel, amountWithFees: LNMoney) = + if (GraphEdge.hasZeroFee(edge)) then + amountWithFees + nodeFee(LNMoney.One, 0L, amountWithFees) + else + let ({ + Update = update + }) = + edge + + amountWithFees + + nodeFee( + update.FeeBaseMSat, + (int64 update.FeeProportionalMillionths), + amountWithFees + ) + /// Computes the compound weight for the given @param edge, the weight is cumulative let private edgeWeight (edge: GraphLabel) (prev: RichWeight) (isNeighborTarget: bool) (currentBlockHeight: BlockHeight) - (weightRatios: WeightRatios option): RichWeight = + (weightRatios: option) + : RichWeight = match weightRatios with | None -> - let edgeCost = if (isNeighborTarget) then prev.Cost else edgeFeeCost(edge, prev.Cost) - { RichWeight.Cost = edgeCost; Length = prev.Length + 1; CLTV = prev.CLTV + edge.Update.CLTVExpiryDelta; Weight = edgeCost.MilliSatoshi |> double } + let edgeCost = + if isNeighborTarget then + prev.Cost + else + edgeFeeCost(edge, prev.Cost) + + { + RichWeight.Cost = edgeCost + Length = prev.Length + 1 + CLTV = prev.CLTV + edge.Update.CLTVExpiryDelta + Weight = edgeCost.MilliSatoshi |> double + } | Some wr -> - let ({ Update = update; Desc = desc }) = edge + let ({ + Update = update + Desc = desc + }) = + edge + let channelBlockHeight = desc.ShortChannelId.BlockHeight.Value // every edge is weighted by funding block height where older blocks add less weight, let ageFactor = - RoutingHeuristics.normalize(channelBlockHeight |> double, - (currentBlockHeight - RoutingHeuristics.BLOCK_TIME_TWO_MONTHS).Value |> double, - currentBlockHeight.Value |> double) + RoutingHeuristics.normalize( + channelBlockHeight |> double, + (currentBlockHeight + - RoutingHeuristics.BLOCK_TIME_TWO_MONTHS) + .Value + |> double, + currentBlockHeight.Value |> double + ) // Every edge is weighted by channel capacity, larger channels and less weight let edgeMaxCapacity = - update.HTLCMaximumMSat |> Option.defaultValue (RoutingHeuristics.CAPACITY_CHANNEL_LOW) + update.HTLCMaximumMSat + |> Option.defaultValue(RoutingHeuristics.CAPACITY_CHANNEL_LOW) + let capFactor = - 1. - RoutingHeuristics.normalize(edgeMaxCapacity.MilliSatoshi |> double, - RoutingHeuristics.CAPACITY_CHANNEL_LOW.MilliSatoshi |> double, - RoutingHeuristics.CAPACITY_CHANNEL_HIGH.MilliSatoshi |> double) + 1. + - RoutingHeuristics.normalize( + edgeMaxCapacity.MilliSatoshi |> double, + RoutingHeuristics.CAPACITY_CHANNEL_LOW.MilliSatoshi + |> double, + RoutingHeuristics.CAPACITY_CHANNEL_HIGH.MilliSatoshi + |> double + ) // Every edge is weighted by cltv-delta value, normalized. let channelCLTVDelta = update.CLTVExpiryDelta + let cltvFactor = - RoutingHeuristics.normalize(channelCLTVDelta.Value |> double, - RoutingHeuristics.CLTV_LOW |> double, - RoutingHeuristics.CLTV_HIGH |> double) - let edgeCost = if (isNeighborTarget) then prev.Cost else edgeFeeCost(edge, prev.Cost) - - let factor = (cltvFactor * wr.CLTVDeltaFactor) + (ageFactor * wr.AgeFactor) + (capFactor * wr.CapacityFactor) + RoutingHeuristics.normalize( + channelCLTVDelta.Value |> double, + RoutingHeuristics.CLTV_LOW |> double, + RoutingHeuristics.CLTV_HIGH |> double + ) + + let edgeCost = + if isNeighborTarget then + prev.Cost + else + edgeFeeCost(edge, prev.Cost) + + let factor = + (cltvFactor * wr.CLTVDeltaFactor) + + (ageFactor * wr.AgeFactor) + + (capFactor * wr.CapacityFactor) + let edgeWeight = - if (isNeighborTarget) then prev.Weight else - prev.Weight + ((edgeCost.MilliSatoshi |> double) * factor) - { RichWeight.Cost = edgeCost; Length = prev.Length + 1; Weight = edgeWeight; CLTV = prev.CLTV + channelCLTVDelta } - + if isNeighborTarget then + prev.Weight + else + prev.Weight + ((edgeCost.MilliSatoshi |> double) * factor) + + { + RichWeight.Cost = edgeCost + Length = prev.Length + 1 + Weight = edgeWeight + CLTV = prev.CLTV + channelCLTVDelta + } + /// Calculates the total cost of a path (amount + fees), /// direct channels with the source will have a cost of 0 (pay no fees) let pathWeight - (path: GraphLabel seq) + (path: seq) (amount: LNMoney) (isPartial: bool) (currentBlockHeight: BlockHeight) - (wr: WeightRatios option) = - let zero = - { RichWeight.Cost = amount - Weight = 0. - Length = 0 - CLTV = BlockHeightOffset16.Zero } - if path |> Seq.length = 0 then zero else - path - |> Seq.skip(if isPartial then 0 else 1) - |> Seq.fold - (fun (acc: RichWeight) (edge: GraphLabel) -> - edgeWeight(edge) (acc) (false) (currentBlockHeight) (wr) - ) + (wr: option) + = + let zero = + { + RichWeight.Cost = amount + Weight = 0. + Length = 0 + CLTV = BlockHeightOffset16.Zero + } + + if path |> Seq.length = 0 then zero - + else + path + |> Seq.skip( + if isPartial then + 0 + else + 1 + ) + |> Seq.fold + (fun (acc: RichWeight) (edge: GraphLabel) -> + edgeWeight (edge) (acc) (false) (currentBlockHeight) (wr) + ) + zero + open System.Linq + /// Finds the shortest path in the graph, uses a modified version of Dijkstra's algorithm that computes /// the shortest path from the target to the source (this is because we ) /// @@ -380,214 +617,327 @@ module Graph = (initialWeight: RichWeight) (boundaries: RichWeight -> bool) (currentBlockHeight: BlockHeight) - (wr: WeightRatios option) : GraphLabel seq = + (wr: option) + : seq = // The graph does not contain source/destination nodes - if (not <| g.ContainsVertex sourceNode) then Seq.empty else - if (not <| g.ContainsVertex targetNode && (not <| extraEdges.IsEmpty) && not <| extraEdges.Any(fun x -> x.Desc.B = targetNode)) then Seq.empty else - - let maxMapSize = 100 // conservative estimation to avoid over allocating memory - let weight = Dictionary(maxMapSize) - let prev = Dictionary(maxMapSize) - // TODO: mutable and while loop is ugly. Refactor. - let mutable vertexQueue = - // Ideally, we should use Fibonacci heap for the sake of performance, - // but to make things easy, we just use regular heap. - // isDescending is false, which means root of the heap is the smallest value, - // so that we can pop the min-cost node with constant time. - Heap.empty(false) - |> PriorityQueue.insert({ WeightedNode.Id = targetNode; Weight = initialWeight }) - - weight.Add(targetNode, initialWeight) - let mutable targetFound = false - while (not <| vertexQueue.IsEmpty && not <| targetFound) do - let current, _vq = vertexQueue.Pop() - vertexQueue <- _vq - if (current.Id = sourceNode) then - targetFound <- true - else - // build the neighbors with optional extra edges - let currentNeighbors = - if extraEdges.IsEmpty then - g.IncomingEdgesOf(current.Id) |> List.toSeq - else - let extraNeighbors = - extraEdges - |> Seq.filter(fun x -> x.Desc.B = current.Id) - // the resulting set must have only one element per shortChannelId - let incoming = - g.IncomingEdgesOf(current.Id) - |> Seq.filter(fun (e) -> not <| extraEdges.Any(fun x -> x.Desc.ShortChannelId = e.Desc.ShortChannelId)) - seq { yield! incoming; yield! extraNeighbors } - let currentWeight = - match weight.TryGetValue current.Id with - | true, t -> t - | _ -> failwithf "Unreachable! Failed to get value %A \n from %A" current.Id (weight |> Seq.map(|KeyValue|)) - - currentNeighbors - |> Seq.iter ( fun edge -> - let neighbor = edge.Desc.A - let newMinimumKnownWeight = - edgeWeight (edge) - (currentWeight) - (initialWeight.Length = 0 && neighbor = sourceNode) - (currentBlockHeight) - (wr) - if (edge.Update.HTLCMaximumMSat |> Option.forall(fun x -> newMinimumKnownWeight.Cost <= x)) && - newMinimumKnownWeight.Cost >= edge.Update.HTLCMinimumMSat && - boundaries (newMinimumKnownWeight) && - (not <| ignoredEdges.Contains(edge.Desc)) && (not <| ignoredVertices.Contains(neighbor)) then - let neighborCost = - match weight.TryGetValue(neighbor) with - | true, s -> s - | false, _ -> - { RichWeight.Cost = LNMoney.MaxValue - Weight = Double.MaxValue - Length = Int32.MaxValue - CLTV = BlockHeightOffset16.MaxValue } - // if this neighbor has a shorter distance than previously known - if (newMinimumKnownWeight.Weight < neighborCost.Weight) then - // update the visiting tree - prev.AddOrReplace(neighbor, edge) - // update the queue - vertexQueue <- vertexQueue |> PriorityQueue.insert({ WeightedNode.Id = neighbor; Weight = newMinimumKnownWeight }) - // update the minimum known distance array - weight.AddOrReplace(neighbor, newMinimumKnownWeight) - () + if (not <| g.ContainsVertex sourceNode) then + Seq.empty + else if (not <| g.ContainsVertex targetNode + && (not <| extraEdges.IsEmpty) + && not <| extraEdges.Any(fun x -> x.Desc.B = targetNode)) then + Seq.empty + else + + let maxMapSize = 100 // conservative estimation to avoid over allocating memory + let weight = Dictionary(maxMapSize) + let prev = Dictionary(maxMapSize) + // TODO: mutable and while loop is ugly. Refactor. + let mutable vertexQueue = + // Ideally, we should use Fibonacci heap for the sake of performance, + // but to make things easy, we just use regular heap. + // isDescending is false, which means root of the heap is the smallest value, + // so that we can pop the min-cost node with constant time. + Heap.empty(false) + |> PriorityQueue.insert( + { + WeightedNode.Id = targetNode + Weight = initialWeight + } ) - match targetFound with - | false -> Seq.empty - | true -> - let edgePath = ResizeArray() - let mutable found, current = prev.TryGetValue(sourceNode) - while found do - edgePath.Add(current) - let f, c = prev.TryGetValue(current.Desc.B) - found <- f - current <- c - edgePath :> seq<_> - - - - let yenKShortestPaths (g: DirectedLNGraph) - (sourceNode: NodeId) - (targetNode: NodeId) - (amount: LNMoney) - (ignoredEdges: Set) - (ignoredVertices: Set) - (extraEdges: Set) - (pathsToFind: int) - (wr: WeightRatios option) - (currentBlockHeight: BlockHeight) - (boundaries: RichWeight -> bool): ResizeArray = + + weight.Add(targetNode, initialWeight) + let mutable targetFound = false + + while (not <| vertexQueue.IsEmpty && not <| targetFound) do + let current, _vq = vertexQueue.Pop() + vertexQueue <- _vq + + if (current.Id = sourceNode) then + targetFound <- true + else + // build the neighbors with optional extra edges + let currentNeighbors = + if extraEdges.IsEmpty then + g.IncomingEdgesOf(current.Id) |> List.toSeq + else + let extraNeighbors = + extraEdges + |> Seq.filter(fun x -> x.Desc.B = current.Id) + // the resulting set must have only one element per shortChannelId + let incoming = + g.IncomingEdgesOf(current.Id) + |> Seq.filter(fun e -> + not + <| extraEdges.Any(fun x -> + x.Desc.ShortChannelId = e.Desc.ShortChannelId + ) + ) + + seq { + yield! incoming + yield! extraNeighbors + } + + let currentWeight = + match weight.TryGetValue current.Id with + | true, t -> t + | _ -> + failwithf + "Unreachable! Failed to get value %A \n from %A" + current.Id + (weight |> Seq.map(|KeyValue|)) + + currentNeighbors + |> Seq.iter(fun edge -> + let neighbor = edge.Desc.A + + let newMinimumKnownWeight = + edgeWeight + (edge) + (currentWeight) + (initialWeight.Length = 0 + && neighbor = sourceNode) + (currentBlockHeight) + (wr) + + if (edge.Update.HTLCMaximumMSat + |> Option.forall(fun x -> + newMinimumKnownWeight.Cost <= x + )) + && newMinimumKnownWeight.Cost + >= edge.Update.HTLCMinimumMSat + && boundaries(newMinimumKnownWeight) + && (not <| ignoredEdges.Contains(edge.Desc)) + && (not <| ignoredVertices.Contains(neighbor)) then + let neighborCost = + match weight.TryGetValue(neighbor) with + | true, s -> s + | false, _ -> + { + RichWeight.Cost = LNMoney.MaxValue + Weight = Double.MaxValue + Length = Int32.MaxValue + CLTV = BlockHeightOffset16.MaxValue + } + // if this neighbor has a shorter distance than previously known + if (newMinimumKnownWeight.Weight < neighborCost.Weight) then + // update the visiting tree + prev.AddOrReplace(neighbor, edge) + // update the queue + vertexQueue <- + vertexQueue + |> PriorityQueue.insert( + { + WeightedNode.Id = neighbor + Weight = newMinimumKnownWeight + } + ) + // update the minimum known distance array + weight.AddOrReplace( + neighbor, + newMinimumKnownWeight + ) + + () + ) + + match targetFound with + | false -> Seq.empty + | true -> + let edgePath = ResizeArray() + let mutable found, current = prev.TryGetValue(sourceNode) + + while found do + edgePath.Add(current) + let f, c = prev.TryGetValue(current.Desc.B) + found <- f + current <- c + + edgePath :> seq<_> + + + + let yenKShortestPaths + (g: DirectedLNGraph) + (sourceNode: NodeId) + (targetNode: NodeId) + (amount: LNMoney) + (ignoredEdges: Set) + (ignoredVertices: Set) + (extraEdges: Set) + (pathsToFind: int) + (wr: option) + (currentBlockHeight: BlockHeight) + (boundaries: RichWeight -> bool) + : ResizeArray = let mutable allSpurPathsFound = false // Stores tha shortest paths let shortestPaths = ResizeArray() // Stores the candidates for k(K+1) shortest paths // we instantiate by isDescending=false, so `Pop` should return the lowest cost element let mutable candidates = Heap.empty false - + // find the shortest path, k = 0 - let initialWeight = { RichWeight.Cost = amount; - Weight = 0. - Length = 0 - CLTV = BlockHeightOffset16.Zero } + let initialWeight = + { + RichWeight.Cost = amount + Weight = 0. + Length = 0 + CLTV = BlockHeightOffset16.Zero + } + let shortestPath = - dijkstraShortestPath g - sourceNode - targetNode - ignoredEdges - ignoredVertices - extraEdges - initialWeight - boundaries - currentBlockHeight - wr + dijkstraShortestPath + g + sourceNode + targetNode + ignoredEdges + ignoredVertices + extraEdges + initialWeight + boundaries + currentBlockHeight + wr + shortestPaths.Add( - { WeightedPath.Path = shortestPath |> Seq.toList - Weight = pathWeight(shortestPath) (amount) false currentBlockHeight wr } - ) - if ((shortestPath.Count()) = 0) then ResizeArray() else - for k in 1..(pathsToFind - 1) do - if (not <| allSpurPathsFound) then - let edgeNum = shortestPaths.[k - 1].Path.Count() - /// for each edge in the path - for i in 0..(edgeNum - 1) do - let prevShortestPath = shortestPaths.[k - 1].Path - // select the spur node as the i-th element of the k-the previous shortest path (k - 1) - let spurEdge = prevShortestPath |> Seq.item i - // select the sub-path from the source to the spur node of the k-th previous shortest path - let rootPathEdges = - if (i = 0) then prevShortestPath |> Seq.head |> List.singleton else - prevShortestPath |> Seq.truncate i |> Seq.toList - let rootPathWeight = - pathWeight - (rootPathEdges) - (amount) - true - currentBlockHeight - wr - - // links to be removed that are part of the previous shortest path and which share the - // same root path - let edgesToIgnore = - seq { - for weightedPath in shortestPaths do - if (weightedPath.Path |> Seq.isEmpty) then () else - if (i = 0 && (weightedPath.Path |> Seq.head |> List.singleton = rootPathEdges)) || - (weightedPath.Path |> Seq.truncate i |> Seq.toList = rootPathEdges) then - yield (weightedPath.Path |> Seq.item i).Desc - else - yield! [] - } - - // remove any link that can lead back to the previous vertex to avoid going back from where - // we arrived(previous iteration) - let returningEdges = - rootPathEdges - |> List.tryLast - |> Option.map(fun last -> g.GetEdgesBetween(last.Desc.B, last.Desc.A)) - |> Option.toList - |> Seq.collect id - |> Seq.map(fun x -> x.Desc) - - // find the "spur" path, a sub-path going from the spur edge to the target avoiding previously - // found sub-paths - let spurPath = - let ignoredE = ignoredEdges |> Set.union (Set(edgesToIgnore)) |> Set.union(Set(returningEdges)) - dijkstraShortestPath - (g) - spurEdge.Desc.A - targetNode - ignoredE - ignoredVertices - extraEdges - rootPathWeight - boundaries - currentBlockHeight - wr - |> List.ofSeq - - // if there wasn't a path the spur will be empty - if (spurPath.Count() <> 0) then - // candidate k-shortest path is made of the rootPath and the new spurPath - let totalPath = - if rootPathEdges.Head.Desc.A = (spurPath |> Seq.head).Desc.A then - // if the heads are the same node, drop it from the rootPath - let t = List.concat[ (rootPathEdges |> List.tail); spurPath ] - t + { + WeightedPath.Path = shortestPath |> Seq.toList + Weight = + pathWeight + (shortestPath) + (amount) + false + currentBlockHeight + wr + } + ) + + if ((shortestPath.Count()) = 0) then + ResizeArray() + else + for k in 1 .. (pathsToFind - 1) do + if (not <| allSpurPathsFound) then + let edgeNum = shortestPaths.[k - 1].Path.Count() + + for i in 0 .. (edgeNum - 1) do + let prevShortestPath = shortestPaths.[k - 1].Path + // select the spur node as the i-th element of the k-the previous shortest path (k - 1) + let spurEdge = prevShortestPath |> Seq.item i + // select the sub-path from the source to the spur node of the k-th previous shortest path + let rootPathEdges = + if (i = 0) then + prevShortestPath |> Seq.head |> List.singleton else - List.concat [rootPathEdges; spurPath] - let candidatePath = { WeightedPath.Path = totalPath; Weight = pathWeight(totalPath) (amount) false currentBlockHeight wr } - if (boundaries(candidatePath.Weight) && - (not <| shortestPaths.Contains(candidatePath)) && - (not <| (candidates |> Seq.exists((=)candidatePath)))) then - candidates <- candidates.Insert(candidatePath) - if (candidates.IsEmpty) then - // handles the case of having exhausted all possible spur paths and it's impossible to - // reach the target from the source - allSpurPathsFound <- true - else - let (best, c) = candidates.Uncons() - candidates <- c - shortestPaths.Add(best) - shortestPaths + prevShortestPath |> Seq.truncate i |> Seq.toList + + let rootPathWeight = + pathWeight + (rootPathEdges) + (amount) + true + currentBlockHeight + wr + + // links to be removed that are part of the previous shortest path and which share the + // same root path + let edgesToIgnore = + seq { + for weightedPath in shortestPaths do + if (weightedPath.Path |> Seq.isEmpty) then + () + else if (i = 0 + && (weightedPath.Path + |> Seq.head + |> List.singleton = rootPathEdges)) + || (weightedPath.Path + |> Seq.truncate i + |> Seq.toList = rootPathEdges) then + yield + (weightedPath.Path |> Seq.item i) + .Desc + else + yield! [] + } + + // remove any link that can lead back to the previous vertex to avoid going back from where + // we arrived(previous iteration) + let returningEdges = + rootPathEdges + |> List.tryLast + |> Option.map(fun last -> + g.GetEdgesBetween(last.Desc.B, last.Desc.A) + ) + |> Option.toList + |> Seq.collect id + |> Seq.map(fun x -> x.Desc) + + // find the "spur" path, a sub-path going from the spur edge to the target avoiding previously + // found sub-paths + let spurPath = + let ignoredE = + ignoredEdges + |> Set.union(Set(edgesToIgnore)) + |> Set.union(Set(returningEdges)) + + dijkstraShortestPath + (g) + spurEdge.Desc.A + targetNode + ignoredE + ignoredVertices + extraEdges + rootPathWeight + boundaries + currentBlockHeight + wr + |> List.ofSeq + + // if there wasn't a path the spur will be empty + if (spurPath.Count() <> 0) then + // candidate k-shortest path is made of the rootPath and the new spurPath + let totalPath = + if rootPathEdges.Head.Desc.A = (spurPath + |> Seq.head) + .Desc + .A then + // if the heads are the same node, drop it from the rootPath + let t = + List.concat + [ + (rootPathEdges |> List.tail) + spurPath + ] + + t + else + List.concat [ rootPathEdges; spurPath ] + + let candidatePath = + { + WeightedPath.Path = totalPath + Weight = + pathWeight + totalPath + amount + false + currentBlockHeight + wr + } + + if (boundaries(candidatePath.Weight) + && (not <| shortestPaths.Contains(candidatePath)) + && (not + <| (candidates + |> Seq.exists((=) candidatePath)))) then + candidates <- candidates.Insert(candidatePath) + + if candidates.IsEmpty then + // handles the case of having exhausted all possible spur paths and it's impossible to + // reach the target from the source + allSpurPathsFound <- true + else + let (best, c) = candidates.Uncons() + candidates <- c + shortestPaths.Add(best) + + shortestPaths diff --git a/src/DotNetLightning.Core/Routing/NetworkStats.fs b/src/DotNetLightning.Core/Routing/NetworkStats.fs index 96fb42882..3bdba6d7a 100644 --- a/src/DotNetLightning.Core/Routing/NetworkStats.fs +++ b/src/DotNetLightning.Core/Routing/NetworkStats.fs @@ -7,28 +7,34 @@ open NBitcoin open ResultUtils open ResultUtils.Portability -type Stats<'T> = { - Median: 'T - Percentile5: 'T - Percentile10: 'T - Percentile25: 'T - Percentile75: 'T - Percentile90: 'T - Percentile95: 'T -} -type NetworkStats = { - Channels: int - Nodes: int - Cap: Stats - CLTVExpiryDelta: Stats - FeeBase: Stats - FeeProportional: Stats -} - with - member this.ComputeStats(publicChannels: PublicChannel seq): NetworkStats option = - if (publicChannels |> Seq.isEmpty || publicChannels |> Seq.collect(fun pc -> pc.GetChannelUpdateField(fun _ -> true)) |> Seq.isEmpty) then +type Stats<'T> = + { + Median: 'T + Percentile5: 'T + Percentile10: 'T + Percentile25: 'T + Percentile75: 'T + Percentile90: 'T + Percentile95: 'T + } + +type NetworkStats = + { + Channels: int + Nodes: int + Cap: Stats + CLTVExpiryDelta: Stats + FeeBase: Stats + FeeProportional: Stats + } + + member this.ComputeStats + (publicChannels: seq) + : option = + if (publicChannels |> Seq.isEmpty + || publicChannels + |> Seq.collect(fun pc -> pc.GetChannelUpdateField(fun _ -> true)) + |> Seq.isEmpty) then None else failwith "TODO" - - diff --git a/src/DotNetLightning.Core/Routing/Router.fs b/src/DotNetLightning.Core/Routing/Router.fs index e3977b008..fde56af1b 100644 --- a/src/DotNetLightning.Core/Routing/Router.fs +++ b/src/DotNetLightning.Core/Routing/Router.fs @@ -16,16 +16,40 @@ open ResultUtils.Portability module Routing = /// This method is used after a payment failed, and we want to exclude some nodes that we know are failing - let getIgnoredChannelDesc(channels: Map) (ignoredNodes: Set) = + let getIgnoredChannelDesc + (channels: Map) + (ignoredNodes: Set) + = let desc = - if (ignoredNodes.IsEmpty) then Seq.empty else - channels - |> Seq.map(fun kvp -> kvp.Value) - |> Seq.filter(fun (channelData: PublicChannel) -> ignoredNodes.Contains(channelData.Ann.NodeId1) || ignoredNodes.Contains(channelData.Ann.NodeId2)) - |> Seq.collect(fun pc -> seq { - yield { ChannelDesc.ShortChannelId = pc.Ann.ShortChannelId; A = pc.Ann.NodeId1; B = pc.Ann.NodeId2 } - yield { ChannelDesc.ShortChannelId = pc.Ann.ShortChannelId; A = pc.Ann.NodeId2; B = pc.Ann.NodeId1 } - }) + if ignoredNodes.IsEmpty then + Seq.empty + else + channels + |> Seq.map(fun kvp -> kvp.Value) + |> Seq.filter(fun (channelData: PublicChannel) -> + ignoredNodes.Contains(channelData.Ann.NodeId1) + || ignoredNodes.Contains(channelData.Ann.NodeId2) + ) + |> Seq.collect(fun pc -> + seq { + yield + { + ChannelDesc.ShortChannelId = + pc.Ann.ShortChannelId + A = pc.Ann.NodeId1 + B = pc.Ann.NodeId2 + } + + yield + { + ChannelDesc.ShortChannelId = + pc.Ann.ShortChannelId + A = pc.Ann.NodeId2 + B = pc.Ann.NodeId1 + } + } + ) + desc // ----- helpers ----- /// BOLT11: "For each entry, the pubkey is the node ID of the start of the channel", and the last node is the destination @@ -33,23 +57,49 @@ module Routing = /// should be able to route the payment, so we'll compute an htlcMaximumMSat accordingly. /// We could also get the channel capacity from the blockchain (since we have the shortChannelId) but that's more expensive /// We also need to make sure the channel isn't excluded by our heuristics. - let internal toAssistedChannels (targetNode: NodeId) (amount: LNMoney)(extraRoute: ExtraHop seq) : seq = - let lastChannelCap = LNMoney.Max(amount, RoutingHeuristics.CAPACITY_CHANNEL_LOW) + let internal toAssistedChannels + (targetNode: NodeId) + (amount: LNMoney) + (extraRoute: seq) + : seq = + let lastChannelCap = + LNMoney.Max(amount, RoutingHeuristics.CAPACITY_CHANNEL_LOW) + let nextNodeIds = seq { yield! - if (extraRoute |> Seq.isEmpty) then Seq.empty else - (extraRoute |> Seq.map(fun x -> x.NodeIdValue) |> Seq.skip 1) + if (extraRoute |> Seq.isEmpty) then + Seq.empty + else + (extraRoute + |> Seq.map(fun x -> x.NodeIdValue) + |> Seq.skip 1) + yield targetNode } + extraRoute |> Seq.zip nextNodeIds |> Seq.rev |> Seq.fold (fun (amount: LNMoney, acs) (nextNodeId, extraHop) -> - let nextAmount = amount + Graph.nodeFee(extraHop.FeeBaseValue, int64 extraHop.FeeProportionalMillionthsValue, amount) + let nextAmount = + amount + + Graph.nodeFee( + extraHop.FeeBaseValue, + int64 extraHop.FeeProportionalMillionthsValue, + amount + ) + (nextAmount, - acs |> Map.add(extraHop.ShortChannelIdValue) ({ AssistedChannel.ExtraHop = extraHop; NextNodeId = nextNodeId; HTLCMaximum = nextAmount })) + acs + |> Map.add + (extraHop.ShortChannelIdValue) + ({ + AssistedChannel.ExtraHop = extraHop + NextNodeId = nextNodeId + HTLCMaximum = nextAmount + })) ) (lastChannelCap, Map.empty) |> snd @@ -59,6 +109,7 @@ module Routing = let ROUTE_MAX_LENGTH = 20 /// Max allowed cltv for a route let DEFAULT_ROUTE_MAX_CLTV = BlockHeightOffset16(1008us) + /// The default number of routes we'll search for when findRoute is called with randomize = true [] let private DEFAULT_ROUTES_COUNT = 3 @@ -75,86 +126,160 @@ module Routing = (ignoredE: Set) (ignoredV: Set) (routeParams: RouteParams) - (currentBlockHeight: BlockHeight): Result, RouterError> = - - if (local = target) then routeFindingError("Cannot route to yourself") else - let feeBaseOk(fee: LNMoney) = - fee <= routeParams.MaxFeeBase - let feePctOk (fee: LNMoney, amount: LNMoney) = - let f, a = (fee.MilliSatoshi |> double), (amount.MilliSatoshi |> double) - let maxFee = (a) * routeParams.MaxFeePCT - f <= maxFee - - let feeOk(f, a) = feeBaseOk(f) || feePctOk(f, a) - - let lengthOk (l: int) = - let limit = Math.Min(routeParams.RouteMaxLength, ROUTE_MAX_LENGTH) - l <= limit - - let cltvOk (cltv: BlockHeightOffset16) = - cltv <= routeParams.RouteMaxCLTV - let boundaries = - fun (weight: RichWeight) -> - feeOk(weight.Cost - amount, amount) && lengthOk(weight.Length) && cltvOk(weight.CLTV) - - Graph.yenKShortestPaths (g) (local) (target) amount ignoredE ignoredV extraE numRoutes routeParams.Ratios currentBlockHeight boundaries - |> Seq.toList - |> function - // if not found within the constraints we relax and repeat the search - | [] when routeParams.RouteMaxLength < ROUTE_MAX_LENGTH -> - findRoute - (g) - local - target - amount - numRoutes - (extraE) - (ignoredE) - (ignoredV) - { routeParams with RouteMaxLength = ROUTE_MAX_LENGTH } - currentBlockHeight - | [] -> routeFindingError "Route not found!" - | routes -> - routes - |> if (routeParams.Randomize) then (List.sortBy(fun _ -> Guid.NewGuid())) else id - |> List.head - |> fun x -> (x.Path |> Seq.map ChannelHop.FromGraphEdge) |> Ok - - let private toFakeUpdate(extraHop: ExtraHop) (htlcMaximum: LNMoney): UnsignedChannelUpdateMsg = + (currentBlockHeight: BlockHeight) + : Result, RouterError> = + + if (local = target) then + routeFindingError("Cannot route to yourself") + else + let feeBaseOk(fee: LNMoney) = + fee <= routeParams.MaxFeeBase + + let feePctOk(fee: LNMoney, amount: LNMoney) = + let f, a = + (fee.MilliSatoshi |> double), + (amount.MilliSatoshi |> double) + + let maxFee = (a) * routeParams.MaxFeePCT + f <= maxFee + + let feeOk(f, a) = + feeBaseOk(f) || feePctOk(f, a) + + let lengthOk(l: int) = + let limit = + Math.Min(routeParams.RouteMaxLength, ROUTE_MAX_LENGTH) + + l <= limit + + let cltvOk(cltv: BlockHeightOffset16) = + cltv <= routeParams.RouteMaxCLTV + + let boundaries = + fun (weight: RichWeight) -> + feeOk(weight.Cost - amount, amount) + && lengthOk(weight.Length) + && cltvOk(weight.CLTV) + + Graph.yenKShortestPaths + (g) + (local) + (target) + amount + ignoredE + ignoredV + extraE + numRoutes + routeParams.Ratios + currentBlockHeight + boundaries + |> Seq.toList + |> function + // if not found within the constraints we relax and repeat the search + | [] when routeParams.RouteMaxLength < ROUTE_MAX_LENGTH -> + findRoute + (g) + local + target + amount + numRoutes + (extraE) + (ignoredE) + (ignoredV) + { routeParams with + RouteMaxLength = ROUTE_MAX_LENGTH + } + currentBlockHeight + | [] -> routeFindingError "Route not found!" + | routes -> + routes + |> if routeParams.Randomize then + (List.sortBy(fun _ -> Guid.NewGuid())) + else + id + |> List.head + |> fun x -> + (x.Path |> Seq.map ChannelHop.FromGraphEdge) |> Ok + + let private toFakeUpdate + (extraHop: ExtraHop) + (htlcMaximum: LNMoney) + : UnsignedChannelUpdateMsg = // the `direction` bit in flags will not be accurate but it doesn't matter because it is not used // what matters is that the `disable` bit is 0 so that this update doesn't get filtered out - { UnsignedChannelUpdateMsg.ShortChannelId = extraHop.ShortChannelIdValue; - ChainHash = uint256.Zero - Timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds() |> uint32 - MessageFlags = 1uy - ChannelFlags = 0uy - CLTVExpiryDelta = extraHop.CLTVExpiryDeltaValue - HTLCMinimumMSat = LNMoney.Zero - FeeBaseMSat = extraHop.FeeBaseValue - FeeProportionalMillionths = extraHop.FeeProportionalMillionthsValue - HTLCMaximumMSat = Some(htlcMaximum) } + { + UnsignedChannelUpdateMsg.ShortChannelId = + extraHop.ShortChannelIdValue + ChainHash = uint256.Zero + Timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds() |> uint32 + MessageFlags = 1uy + ChannelFlags = 0uy + CLTVExpiryDelta = extraHop.CLTVExpiryDeltaValue + HTLCMinimumMSat = LNMoney.Zero + FeeBaseMSat = extraHop.FeeBaseValue + FeeProportionalMillionths = extraHop.FeeProportionalMillionthsValue + HTLCMaximumMSat = Some(htlcMaximum) + } // ----- ------ let executeCommand (state: RouterState) (cmd: RouterCommand) = match state, cmd with - | RouterState.Normal _d, (NetworkEvent (ChannelUpdateReceived (_update))) -> - failwith "Not implemented: Routing::executeCommand when state,cmd = Normal,NetworkEvent ChannelUpdateRecv" - | RouterState.Normal d, (NetworkCommand (CalculateRoute (routeRequest))) -> - let { Source = start; Target = t; Amount = a; AssistedRoutes = assistedRoutes; IgnoredNodes = ignoredV } = + | RouterState.Normal _d, (NetworkEvent(ChannelUpdateReceived _update)) -> + failwith + "Not implemented: Routing::executeCommand when state,cmd = Normal,NetworkEvent ChannelUpdateRecv" + | RouterState.Normal d, (NetworkCommand(CalculateRoute routeRequest)) -> + let { + Source = start + Target = t + Amount = a + AssistedRoutes = assistedRoutes + IgnoredNodes = ignoredV + } = routeRequest + let assistedChannels: Map = assistedRoutes |> Seq.collect(toAssistedChannels t a) |> Map.ofSeq + let extraEdges = assistedChannels |> Seq.map(fun kvp -> let ac = kvp.Value - { GraphLabel.Desc = { ShortChannelId = ac.ExtraHop.ShortChannelIdValue; A = ac.ExtraHop.NodeIdValue; B = ac.NextNodeId } - Update = toFakeUpdate(ac.ExtraHop) (ac.HTLCMaximum) } - ) + + { + GraphLabel.Desc = + { + ShortChannelId = ac.ExtraHop.ShortChannelIdValue + A = ac.ExtraHop.NodeIdValue + B = ac.NextNodeId + } + Update = toFakeUpdate (ac.ExtraHop) (ac.HTLCMaximum) + } + ) |> Set - let ignoredEdges = routeRequest.IgnoredChannels |> Set.union d.ExcludedChannels + + let ignoredEdges = + routeRequest.IgnoredChannels |> Set.union d.ExcludedChannels + let routeParams = routeRequest.RouteParams - let routesToFind = if routeParams.Randomize then DEFAULT_ROUTES_COUNT else 1 - findRoute(d.Graph) (start) t a routesToFind extraEdges ignoredEdges ignoredV routeParams d.CurrentBlockHeight - | _ -> failwith "Not implemented: Routing::executeCommand for some unknown state,cmd tuple" + + let routesToFind = + if routeParams.Randomize then + DEFAULT_ROUTES_COUNT + else + 1 + + findRoute + (d.Graph) + (start) + t + a + routesToFind + extraEdges + ignoredEdges + ignoredV + routeParams + d.CurrentBlockHeight + | _ -> + failwith + "Not implemented: Routing::executeCommand for some unknown state,cmd tuple" diff --git a/src/DotNetLightning.Core/Routing/RouterPrimitives.fs b/src/DotNetLightning.Core/Routing/RouterPrimitives.fs index 8b0dce6ac..1fa4b3437 100644 --- a/src/DotNetLightning.Core/Routing/RouterPrimitives.fs +++ b/src/DotNetLightning.Core/Routing/RouterPrimitives.fs @@ -12,117 +12,186 @@ open ResultUtils.Portability [] module RouterPrimitives = - let checkUpdate(x: ChannelUpdateMsg option, msg ) = + let checkUpdate(x: option, msg) = match x with | Some x -> Result.requireTrue msg (x.IsNode1) | None -> Ok() - + let isNode1(localNodeId: NodeId, remoteNodeId: NodeId) = localNodeId > remoteNodeId - - type RouterConf = { - RandomizeRouterSelection: bool - ChannelExcludeDuration: TimeSpan - RouterBroadcastInterval: TimeSpan - SearchMaxFeeBase: LNMoney - SearchMaxFeePct: double - SearchMaxRouteLength: int - SearchMaxCLTV: BlockHeightOffset16 - SearchHeuristicsEnabled: bool - SearchRatioCLTV: double - SearchRatioChannelAge: double - SearchRatioChannelCapacity: double - } - - type PublicChannel = private { - Announce: ChannelAnnouncementMsg - FundingTxId: TxId - Cap: Money - MaybeUpdate1: ChannelUpdateMsg option - MaybeUpdate2: ChannelUpdateMsg option - } - with - static member TryCreate (a, f, c, ?update1: ChannelUpdateMsg, ?update2: ChannelUpdateMsg) = + + type RouterConf = + { + RandomizeRouterSelection: bool + ChannelExcludeDuration: TimeSpan + RouterBroadcastInterval: TimeSpan + SearchMaxFeeBase: LNMoney + SearchMaxFeePct: double + SearchMaxRouteLength: int + SearchMaxCLTV: BlockHeightOffset16 + SearchHeuristicsEnabled: bool + SearchRatioCLTV: double + SearchRatioChannelAge: double + SearchRatioChannelCapacity: double + } + + type PublicChannel = + private + { + Announce: ChannelAnnouncementMsg + FundingTxId: TxId + Cap: Money + MaybeUpdate1: option + MaybeUpdate2: option + } + + static member TryCreate + ( + a, + f, + c, + ?update1: ChannelUpdateMsg, + ?update2: ChannelUpdateMsg + ) = result { - do! checkUpdate(update1, "Update 1 must be node 1 according to bolt 7 definition") - do! checkUpdate(update2, "Update 2 must be node 2 according to bolt 7 definition") - return { Announce = a; FundingTxId = f; Cap = c; MaybeUpdate1 = update1; MaybeUpdate2 = update2 } + do! + checkUpdate( + update1, + "Update 1 must be node 1 according to bolt 7 definition" + ) + + do! + checkUpdate( + update2, + "Update 2 must be node 2 according to bolt 7 definition" + ) + + return + { + Announce = a + FundingTxId = f + Cap = c + MaybeUpdate1 = update1 + MaybeUpdate2 = update2 + } } member this.GetNodeIdSameSideAs(u: ChannelUpdateMsg) = - if (u.IsNode1) then this.Announce.Contents.NodeId1 else this.Announce.Contents.NodeId2 - + if u.IsNode1 then + this.Announce.Contents.NodeId1 + else + this.Announce.Contents.NodeId2 + member this.GetChannelUpdateSameSideAs(u: ChannelUpdateMsg) = - if (u.IsNode1) then this.MaybeUpdate1 else this.MaybeUpdate2 - + if u.IsNode1 then + this.MaybeUpdate1 + else + this.MaybeUpdate2 + member this.UpdateChannelUpdateSameSideAs(u: ChannelUpdateMsg) = - if (u.IsNode1) then { this with MaybeUpdate1 = Some u } else { this with MaybeUpdate2 = Some u } - - member this.GetChannelUpdateField<'T> (f: ChannelUpdateMsg -> 'T): seq<'T> = + if u.IsNode1 then + { this with + MaybeUpdate1 = Some u + } + else + { this with + MaybeUpdate2 = Some u + } + + member this.GetChannelUpdateField<'T> + (f: ChannelUpdateMsg -> 'T) + : seq<'T> = seq { yield! this.MaybeUpdate1 |> Option.toList yield! this.MaybeUpdate2 |> Option.toList - } |> Seq.map f - - type PrivateChannel = { - LocalNodeId: NodeId - RemoteNodeId: NodeId - MaybeUpdate1: ChannelUpdateMsg option - MaybeUpdate2: ChannelUpdateMsg option - } - - with + } + |> Seq.map f + + type PrivateChannel = + { + LocalNodeId: NodeId + RemoteNodeId: NodeId + MaybeUpdate1: option + MaybeUpdate2: option + } + member this.GetNodeIdSameSideAs(u: ChannelUpdateMsg) = let node1, node2 = if (isNode1(this.LocalNodeId, this.RemoteNodeId)) then (this.LocalNodeId, this.RemoteNodeId) - else (this.RemoteNodeId, this.LocalNodeId) - if (u.IsNode1) then node1 else node2 - + else + (this.RemoteNodeId, this.LocalNodeId) + + if u.IsNode1 then + node1 + else + node2 + member this.GetChannelUpdateSameSideAs(u: ChannelUpdateMsg) = - if (u.IsNode1) then this.MaybeUpdate1 else this.MaybeUpdate2 + if u.IsNode1 then + this.MaybeUpdate1 + else + this.MaybeUpdate2 + member this.UpdateChannelUpdateSameSideAs(u: ChannelUpdateMsg) = - if (u.IsNode1) then { this with MaybeUpdate1 = Some u } else { this with MaybeUpdate2 = Some u } - - type AssistedChannel = { - ExtraHop: ExtraHop - NextNodeId: NodeId - HTLCMaximum: LNMoney - } + if u.IsNode1 then + { this with + MaybeUpdate1 = Some u + } + else + { this with + MaybeUpdate2 = Some u + } + + type AssistedChannel = + { + ExtraHop: ExtraHop + NextNodeId: NodeId + HTLCMaximum: LNMoney + } + type IHop = abstract member NodeId: NodeId abstract member NextNodeId: NodeId abstract member CLTVExpiryDelta: BlockHeightOffset16 abstract member Fee: amount: LNMoney -> LNMoney - + /// A directed hop between two connected nodes using a specific channel. - type ChannelHop = private { - /// The id of the start node - NodeId: NodeId - /// The id of the end node - NextNodeId: NodeId - CLTVExpiryDelta: BlockHeightOffset16 - LastUpdate: UnsignedChannelUpdateMsg - } - with + type ChannelHop = + private + { + /// The id of the start node + NodeId: NodeId + /// The id of the end node + NextNodeId: NodeId + CLTVExpiryDelta: BlockHeightOffset16 + LastUpdate: UnsignedChannelUpdateMsg + } + member this.LastUpdateValue = this.LastUpdate member this.NodeIdValue = this.NodeId member this.NextNodeIdValue = this.NextNodeId - static member Create (a, b, u: UnsignedChannelUpdateMsg) = + + static member Create(a, b, u: UnsignedChannelUpdateMsg) = { NodeId = a NextNodeId = b CLTVExpiryDelta = u.CLTVExpiryDelta LastUpdate = u } + static member FromGraphEdge(g: GraphLabel) = ChannelHop.Create(g.Desc.A, g.Desc.B, g.Update) + interface IHop with member this.NodeId = this.NodeId member this.NextNodeId = this.NextNodeId member this.CLTVExpiryDelta = this.CLTVExpiryDelta + member this.Fee amount = let u = this.LastUpdate - (u.FeeBaseMSat.MilliSatoshi + (int64 u.FeeProportionalMillionths * amount.MilliSatoshi) / 1000000L) + + (u.FeeBaseMSat.MilliSatoshi + + (int64 u.FeeProportionalMillionths * amount.MilliSatoshi) + / 1000000L) |> LNMoney.MilliSatoshis - diff --git a/src/DotNetLightning.Core/Routing/RouterState.fs b/src/DotNetLightning.Core/Routing/RouterState.fs index 0d42830e0..f5201d240 100644 --- a/src/DotNetLightning.Core/Routing/RouterState.fs +++ b/src/DotNetLightning.Core/Routing/RouterState.fs @@ -9,15 +9,16 @@ open Graph open ResultUtils open ResultUtils.Portability -type RouteParams = { - Randomize: bool - MaxFeeBase: LNMoney - MaxFeePCT: double - RouteMaxLength: int - RouteMaxCLTV: BlockHeightOffset16 - Ratios: WeightRatios option -} - with +type RouteParams = + { + Randomize: bool + MaxFeeBase: LNMoney + MaxFeePCT: double + RouteMaxLength: int + RouteMaxCLTV: BlockHeightOffset16 + Ratios: option + } + static member GetDefault(conf: RouterConf) = { Randomize = conf.RandomizeRouterSelection @@ -29,25 +30,44 @@ type RouteParams = { match conf.SearchHeuristicsEnabled with | false -> None | true -> - match WeightRatios.TryCreate(conf.SearchRatioCLTV, conf.SearchRatioChannelAge, conf.SearchRatioChannelCapacity) with + match + WeightRatios.TryCreate + ( + conf.SearchRatioCLTV, + conf.SearchRatioChannelAge, + conf.SearchRatioChannelCapacity + ) + with | Ok s -> Some s | Error _ -> None } -type RouteRequest = private { - Source: NodeId - Target: NodeId - Amount: LNMoney - AssistedRoutes: ExtraHop seq seq - IgnoredNodes: Set - IgnoredChannels: Set - RouteParams: RouteParams -} - with - static member Create(source, target, amount, routeParams, ?assistedRoutes, ?ignoredNodes, ?ignoredChannels) = +type RouteRequest = + private + { + Source: NodeId + Target: NodeId + Amount: LNMoney + AssistedRoutes: seq> + IgnoredNodes: Set + IgnoredChannels: Set + RouteParams: RouteParams + } + + static member Create + ( + source, + target, + amount, + routeParams, + ?assistedRoutes, + ?ignoredNodes, + ?ignoredChannels + ) = let a = Option.defaultValue [] assistedRoutes let iN = Option.defaultValue Set.empty ignoredNodes let iC = Option.defaultValue Set.empty ignoredChannels + { Source = source Target = target @@ -57,72 +77,97 @@ type RouteRequest = private { IgnoredChannels = iC RouteParams = routeParams } - -type FinalizeRoute = private { - Hops: NodeId seq - AssistedRoutes: ExtraHop seq seq -} - with + +type FinalizeRoute = + private + { + Hops: seq + AssistedRoutes: seq> + } + static member Create(hops, ?assistedRoutes) = { Hops = hops AssistedRoutes = Option.defaultValue [] assistedRoutes } -type RouteResponse = private { - Hops: ChannelHop seq - IgnoredNodes: Set - IgnoredChannels: Set -} - with - static member TryCreate (hops: ChannelHop seq, ignoredNodes, ignoredChannels, ?allowEmpty) = - let allowEmpty = Option.defaultValue false allowEmpty - if allowEmpty || (hops |> Seq.isEmpty |> not) then Error("Route cannot be empty") else +type RouteResponse = + private { - Hops = hops - IgnoredNodes = ignoredNodes - IgnoredChannels = ignoredChannels - } |> Ok + Hops: seq + IgnoredNodes: Set + IgnoredChannels: Set + } + + static member TryCreate + ( + hops: seq, + ignoredNodes, + ignoredChannels, + ?allowEmpty + ) = + let allowEmpty = Option.defaultValue false allowEmpty + + if allowEmpty || (hops |> Seq.isEmpty |> not) then + Error("Route cannot be empty") + else + { + Hops = hops + IgnoredNodes = ignoredNodes + IgnoredChannels = ignoredChannels + } + |> Ok -type RoutingState = { - Channels: PublicChannel seq - Nodes: NodeAnnouncementMsg seq -} +type RoutingState = + { + Channels: seq + Nodes: seq + } type GossipOrigin = | Remote of PeerId | Local -type Stash = { - Updates: Map> - Nodes: Map> -} -type ReBroadcast = { - Channels: Map> - Updates: Map> - Nodes: Map> -} - -type Sync = { - Pending: IRoutingMsg seq - Total: int -} - -type RouterData = private { - Nodes: Map - Channels: SortedDictionary - Stats: NetworkStats - ReBroadcast: ReBroadcast - Awaiting: Map> - PrivateChannels: Map - ExcludedChannels: Set - Graph: DirectedLNGraph - Sync: Map - CurrentBlockHeight: BlockHeight -} - with + +type Stash = + { + Updates: Map> + Nodes: Map> + } + +type ReBroadcast = + { + Channels: Map> + Updates: Map> + Nodes: Map> + } + +type Sync = + { + Pending: seq + Total: int + } + +type RouterData = + private + { + Nodes: Map + Channels: SortedDictionary + Stats: NetworkStats + ReBroadcast: ReBroadcast + Awaiting: Map> + PrivateChannels: Map + ExcludedChannels: Set + Graph: DirectedLNGraph + Sync: Map + CurrentBlockHeight: BlockHeight + } + member this.NetworkStats = this.Stats + member this.RoutingState = - { RoutingState.Channels = this.Channels.Values - Nodes = this.Nodes |> Seq.map(fun kvp -> kvp.Value) } - + { + RoutingState.Channels = this.Channels.Values + Nodes = this.Nodes |> Seq.map(fun kvp -> kvp.Value) + } + type RouterState = Normal of RouterData diff --git a/src/DotNetLightning.Core/Routing/RouterTypes.fs b/src/DotNetLightning.Core/Routing/RouterTypes.fs index 5d0dbe88c..104d65308 100644 --- a/src/DotNetLightning.Core/Routing/RouterTypes.fs +++ b/src/DotNetLightning.Core/Routing/RouterTypes.fs @@ -16,7 +16,7 @@ type NetworkEvent = | NodeDiscovered of msg: NodeAnnouncementMsg | NodeUpdated of msg: NodeAnnouncementMsg | NodeLost of nodeId: NodeId - | ChannelDiscovered of msg: ChannelAnnouncementMsg * capacity : Money + | ChannelDiscovered of msg: ChannelAnnouncementMsg * capacity: Money | ChannelUpdateReceived of msg: ChannelUpdateMsg // when funding tx on the blockchain has been spent, we must consider that the channel is closed in a graph view | FundingTxSpent of ShortChannelId @@ -27,13 +27,14 @@ type NetworkCommand = /// channel to recover (note that exclusions are directed) | ExcludeChannel of desc: ChannelDesc | CalculateRoute of RouteRequest - -type RouterError = - | RouteFindingError of string + +type RouterError = RouteFindingError of string + [] module internal RouterError = - let routeFindingError msg = RouteFindingError msg |> Error + let routeFindingError msg = + RouteFindingError msg |> Error + type RouterCommand = | NetworkEvent of NetworkEvent | NetworkCommand of NetworkCommand - diff --git a/src/DotNetLightning.Core/Serialization/BitReader.fs b/src/DotNetLightning.Core/Serialization/BitReader.fs index 7b59841ff..3f4cfdf51 100644 --- a/src/DotNetLightning.Core/Serialization/BitReader.fs +++ b/src/DotNetLightning.Core/Serialization/BitReader.fs @@ -9,11 +9,11 @@ open ResultUtils open ResultUtils.Portability type BitReader(ba: BitArray, bitCount: int) = - - member val Count = bitCount with get + + member val Count = bitCount member val Position = 0 with get, set - new (ba) = BitReader(ba, ba.Count) - + new(ba) = BitReader(ba, ba.Count) + member this.Read() = let v = ba.Get(this.Position) this.Position <- this.Position + 1 @@ -21,51 +21,82 @@ type BitReader(ba: BitArray, bitCount: int) = member this.ReadULongBE(bitCount: int) = let mutable value = 0UL - for i in 0..(bitCount - 1) do - let v = if this.Read() then 1UL else 0UL + + for i in 0 .. (bitCount - 1) do + let v = + if this.Read() then + 1UL + else + 0UL + value <- value + (v <<< (bitCount - i - 1)) + value - + member this.ReadBytes(byteSize: int) = - let bytes: byte[] = Array.zeroCreate byteSize + let bytes: array = Array.zeroCreate byteSize let mutable maxRead = this.Count - this.Position let mutable byteIndex = 0 + while (byteIndex < byteSize && maxRead <> 0) do let mutable value = 0uy + for i in 0..7 do if (maxRead <> 0) then - let v = if this.Read() then 1UL else 0UL - value <- value + (byte (v <<< (8 - i - 1))) + let v = + if this.Read() then + 1UL + else + 0UL + + value <- value + (byte(v <<< (8 - i - 1))) maxRead <- maxRead - 1 - + bytes.[byteIndex] <- value byteIndex <- byteIndex + 1 + bytes - + member this.ReadBits(bitCount: int) = - let result = Array.zeroCreate (bitCount) - for i in 0..bitCount - 1 do + let result = Array.zeroCreate(bitCount) + + for i in 0 .. bitCount - 1 do result.[i] <- this.Read() + BitArray(result) - + member this.Consume(count: int) = this.Position <- this.Position + count - + member this.SkipTo(position: int) = let skip = position - this.Position + if skip < 0 then - sprintf "Could not skip BitReader from %d to %d" this.Position position |> Error + sprintf + "Could not skip BitReader from %i to %i" + this.Position + position + |> Error else this.Consume(skip) Ok() - + member this.CanConsume(bitCount: int) = this.Position + bitCount <= this.Count - + override this.ToString() = let sb = StringBuilder(ba.Length) - for i in 0..(this.Count - 1) do + + for i in 0 .. (this.Count - 1) do if (i <> 0 && i % 8 = 0) then sb.Append(' ') |> ignore - sb.Append(if ba.Get(i) then "1" else "0") |> ignore + + sb.Append( + if ba.Get(i) then + "1" + else + "0" + ) + |> ignore + sb.ToString() diff --git a/src/DotNetLightning.Core/Serialization/BitWriter.fs b/src/DotNetLightning.Core/Serialization/BitWriter.fs index c60e451c3..1195ecd34 100644 --- a/src/DotNetLightning.Core/Serialization/BitWriter.fs +++ b/src/DotNetLightning.Core/Serialization/BitWriter.fs @@ -12,37 +12,43 @@ open ResultUtils.Portability // Based on: https://github.com/MetacoSA/NBitcoin/blob/d822f191441b2da5abdd3ab4765cf82296dbea18/NBitcoin/BitWriter.cs type BitWriter() = let values = ResizeArray() - + member this.Count = values.Count - + member val Position = 0 with get, set - + member this.Write(v: bool) = values.Insert(this.Position, v) this.Position <- this.Position + 1 - member this.Write(bytes: byte[]) = + member this.Write(bytes: array) = this.Write(bytes, bytes.Length * 8) - - static member private SwapEndianBytes(bytes: byte[]) = + + static member private SwapEndianBytes(bytes: array) = let output = Array.zeroCreate(bytes.Length) - for i in 0..(output.Length - 1) do + + for i in 0 .. (output.Length - 1) do let mutable newByte = 0uy + for ib in 0..7 do - newByte <- newByte + byte (((bytes.[i] >>> ib) &&& 1uy) <<< (7 - ib)) + newByte <- + newByte + byte(((bytes.[i] >>> ib) &&& 1uy) <<< (7 - ib)) + output.[i] <- newByte + output - - member this.Write(bytes: byte[], bitCount: int) = + + member this.Write(bytes: array, bitCount: int) = let bytes = BitWriter.SwapEndianBytes(bytes) let array = BitArray(bytes) values.InsertRange(this.Position, array.OfType().Take(bitCount)) this.Position <- this.Position + bitCount - + member this.Write(value: uint32, bitCount: int) = let mutable v = value let mutable count = bitCount v <- v <<< (32 - bitCount) + while (count > 0) do this.Write((v >>> (32 - 1) = 1u)) v <- v <<< 1 @@ -50,36 +56,54 @@ type BitWriter() = static member private ToByteArray(bits: BitArray) = let mutable arrayLength = bits.Length / 8 + if (bits.Length % 8 <> 0) then arrayLength <- arrayLength + 1 + let array = Array.zeroCreate arrayLength - for i in 0..bits.Length - 1 do - let b = i /8 + + for i in 0 .. bits.Length - 1 do + let b = i / 8 let offset = i % 8 - array.[b] <- array.[b] ||| if (bits.Get i) then byte(1 <<< offset) else 0uy + + array.[b] <- + array.[b] + ||| if (bits.Get i) then + byte(1 <<< offset) + else + 0uy + array - member this.ToBytes(): byte[] = - this.ToBitArray() - |> BitWriter.ToByteArray - |> BitWriter.SwapEndianBytes - - member this.ToBitArray(): BitArray = + + member this.ToBytes() : array = + this.ToBitArray() |> BitWriter.ToByteArray |> BitWriter.SwapEndianBytes + + member this.ToBitArray() : BitArray = values.ToArray() |> BitArray - + member this.ToIntegers() = this.ToBitArray() |> NBitcoin.Wordlist.ToIntegers - + member this.Write(ba: BitArray) = this.Write(ba, ba.Length) - + member this.Write(ba: BitArray, bitCount) = - for i in 0..(bitCount - 1) do + for i in 0 .. (bitCount - 1) do this.Write(ba.Get(i)) override this.ToString() = let sb = StringBuilder(values.Count) - for i in 0 ..(this.Count - 1) do + + for i in 0 .. (this.Count - 1) do if (i <> 0 && i % 8 = 0) then sb.Append(' ') |> ignore - sb.Append(if values.[i] then "1" else "0") |> ignore + + sb.Append( + if values.[i] then + "1" + else + "0" + ) + |> ignore + sb.ToString() diff --git a/src/DotNetLightning.Core/Serialization/EncodedTypes.fs b/src/DotNetLightning.Core/Serialization/EncodedTypes.fs index b39a69962..e642b126d 100644 --- a/src/DotNetLightning.Core/Serialization/EncodedTypes.fs +++ b/src/DotNetLightning.Core/Serialization/EncodedTypes.fs @@ -5,52 +5,73 @@ open System.IO open ResultUtils open ResultUtils.Portability -type QueryFlags = private QueryFlags of uint8 - with - static member Create (data) = QueryFlags(data) +type QueryFlags = + private + | QueryFlags of uint8 + + static member Create data = + QueryFlags(data) + static member TryCreate(data: uint64) = if data > 0xfcUL then - Error(sprintf "Too large query flag! It must be represented as 1 byte, but it was %A" data) + Error( + sprintf + "Too large query flag! It must be represented as 1 byte, but it was %A" + data + ) else QueryFlags(uint8 data) |> Ok + member private x.Value = let (QueryFlags v) = x in v + member this.RequiresChannelAnnouncement = (this.Value &&& 0b00000001uy) = 1uy - + member this.RequiresChannelUpdateForNode1 = (this.Value &&& 0b00000010uy) = 1uy - + member this.RequiresChannelUpdateForNode2 = (this.Value &&& 0b00000100uy) = 1uy + member this.RequiresNodeAnnouncementForNode1 = (this.Value &&& 0b00001000uy) = 1uy + member this.RequiresNodeAnnouncementForNode2 = (this.Value &&& 0b00010000uy) = 1uy - + member this.ToBytes() = - [|(byte)this.Value|] - -type QueryOption = private QueryOption of uint8 - with - static member Create (data) = QueryOption(data) + [| (byte) this.Value |] + +type QueryOption = + private + | QueryOption of uint8 + + static member Create data = + QueryOption(data) + static member TryCreate(data: uint64) = if data > 0xfcUL then - Error(sprintf "Too large query flag! It must be represented as 1 byte, but it was %A" data) + Error( + sprintf + "Too large query flag! It must be represented as 1 byte, but it was %A" + data + ) else QueryFlags(uint8 data) |> Ok + member private x.Value = let (QueryOption v) = x in v - member this.SenderWantsTimestamps = - (this.Value &&& 0b00000001uy) = 1uy - member this.SenderWantsChecksums = - (this.Value &&& 0b00000010uy) = 1uy + member this.SenderWantsTimestamps = (this.Value &&& 0b00000001uy) = 1uy + member this.SenderWantsChecksums = (this.Value &&& 0b00000010uy) = 1uy + member this.ToBytes() = - [|(byte)this.Value|] - -type TwoTimestamps = { - NodeId1: uint32 - NodeId2: uint32 -} - with + [| (byte) this.Value |] + +type TwoTimestamps = + { + NodeId1: uint32 + NodeId2: uint32 + } + member this.ToBytes() = use ms = new MemoryStream() use ls = new LightningWriterStream(ms) @@ -58,15 +79,15 @@ type TwoTimestamps = { ls.Write(this.NodeId2, false) ms.ToArray() -type TwoChecksums = { - NodeId1: uint32 - NodeId2: uint32 -} - with +type TwoChecksums = + { + NodeId1: uint32 + NodeId2: uint32 + } + member this.ToBytes() = use ms = new MemoryStream() use ls = new LightningWriterStream(ms) ls.Write(this.NodeId1, false) ls.Write(this.NodeId2, false) ms.ToArray() - diff --git a/src/DotNetLightning.Core/Serialization/Encoding.fs b/src/DotNetLightning.Core/Serialization/Encoding.fs index 866e27152..45c4a4662 100644 --- a/src/DotNetLightning.Core/Serialization/Encoding.fs +++ b/src/DotNetLightning.Core/Serialization/Encoding.fs @@ -13,61 +13,75 @@ open ResultUtils open ResultUtils.Portability module Decoder = - let private tryDecode (encodingType: EncodingType) (bytes : byte[]) = - if bytes.Length = 0 then bytes |> Ok else - let data = bytes - match encodingType with - | EncodingType.SortedPlain -> - data |> Ok - | EncodingType.ZLib -> - use ms = new MemoryStream(data) - ms.Position <- 0L - use ds = new DeflateStream(ms, CompressionMode.Decompress, true) - use outputMs = new MemoryStream() - ds.CopyTo(outputMs) - outputMs.ToArray() |> Ok - | x -> - Error(sprintf "Unknown encoding type %A" x) - + let private tryDecode (encodingType: EncodingType) (bytes: array) = + if bytes.Length = 0 then + bytes |> Ok + else + let data = bytes + + match encodingType with + | EncodingType.SortedPlain -> data |> Ok + | EncodingType.ZLib -> + use ms = new MemoryStream(data) + ms.Position <- 0L + use ds = new DeflateStream(ms, CompressionMode.Decompress, true) + use outputMs = new MemoryStream() + ds.CopyTo(outputMs) + outputMs.ToArray() |> Ok + | x -> Error(sprintf "Unknown encoding type %A" x) + let private unwrap b = - b |> function Ok x -> x | Error msg -> raise <| FormatException(msg) - - let private bytesToShortIds (data: byte[]) = + b + |> function + | Ok x -> x + | Error msg -> raise <| FormatException(msg) + + let private bytesToShortIds(data: array) = result { - if data.Length = 0 then return [||] else - let count, remainder = Math.DivRem(data.Length, 8) - if (remainder <> 0) then - return! - sprintf "Bogus encoded_ item! length of short_channel_ids must be multiple of 8. it was %d" data.Length - |> Error + if data.Length = 0 then + return [||] else - return - data - |> Array.splitInto count - |> Array.map(ShortChannelId.From8Bytes) + let count, remainder = Math.DivRem(data.Length, 8) + + if (remainder <> 0) then + return! + sprintf + "Bogus encoded_ item! length of short_channel_ids must be multiple of 8. it was %i" + data.Length + |> Error + else + return + data + |> Array.splitInto count + |> Array.map(ShortChannelId.From8Bytes) } + let tryDecodeShortChannelIds encodingType d = result { let! bytes = tryDecode encodingType d return! bytes |> bytesToShortIds } - + let decodeShortChannelIds e d = tryDecodeShortChannelIds e d |> unwrap - - let decodeShortChannelIdsFromBytes (d: byte[]) = - let encodingType = LanguagePrimitives.EnumOfValue(d.[0]) + + let decodeShortChannelIdsFromBytes(d: array) = + let encodingType = + LanguagePrimitives.EnumOfValue(d.[0]) + decodeShortChannelIds encodingType (d.[1..]) - - let private bytesToQueryFlags (data: byte[]) = + + let private bytesToQueryFlags(data: array) = use ms = new MemoryStream(data) use ls = new LightningReaderStream(ms) + let flags = ls.ReadAllAsBigSize() |> Array.toList - |> List.map (QueryFlags.TryCreate) + |> List.map(QueryFlags.TryCreate) |> List.sequenceResultM |> Result.map List.toArray + flags let tryDecodeQueryFlags encodingType d = @@ -75,72 +89,97 @@ module Decoder = let! bytes = tryDecode encodingType d return! bytes |> bytesToQueryFlags } - + let decodeQueryFlags encodingType d = tryDecodeQueryFlags encodingType d |> unwrap - - let decodeQueryFlagsFromBytes (d: byte[]) = - let encodingType = LanguagePrimitives.EnumOfValue(d.[0]) + + let decodeQueryFlagsFromBytes(d: array) = + let encodingType = + LanguagePrimitives.EnumOfValue(d.[0]) + decodeQueryFlags encodingType (d.[1..]) - - let private bytesToUint32Pair (data : byte[]) = + + let private bytesToUint32Pair(data: array) = result { - if data.Length = 0 then return ([||]) else - let div, rem = Math.DivRem(data.Length, 8) - if (rem <> 0) then return! Error(sprintf "bogus timestamps! %A" data) else - use ms = new MemoryStream(data) - use ls = new LightningReaderStream(ms) - let res = Array.zeroCreate div - for i in 0..(div - 1) do - let a = ls.ReadUInt32(false) - let b = ls.ReadUInt32(false) - res.[i] <- (a, b) - return res + if data.Length = 0 then + return ([||]) + else + let div, rem = Math.DivRem(data.Length, 8) + + if (rem <> 0) then + return! Error(sprintf "bogus timestamps! %A" data) + else + use ms = new MemoryStream(data) + use ls = new LightningReaderStream(ms) + let res = Array.zeroCreate div + + for i in 0 .. (div - 1) do + let a = ls.ReadUInt32(false) + let b = ls.ReadUInt32(false) + res.[i] <- (a, b) + + return res } - + let private bytesToTimestampPair x = bytesToUint32Pair x - |> Result.map(Array.map(fun (a, b) -> { TwoTimestamps.NodeId1 = a; NodeId2 = b })) - + |> Result.map( + Array.map(fun (a, b) -> + { + TwoTimestamps.NodeId1 = a + NodeId2 = b + } + ) + ) + let tryBytesToChecksumPair x = bytesToUint32Pair x - |> Result.map(Array.map(fun (a, b) -> { TwoChecksums.NodeId1 = a; NodeId2 = b })) - - let bytesToChecksumPair = - tryBytesToChecksumPair >> unwrap - - let tryDecodeTimestampPairs e d = + |> Result.map( + Array.map(fun (a, b) -> + { + TwoChecksums.NodeId1 = a + NodeId2 = b + } + ) + ) + + let bytesToChecksumPair = tryBytesToChecksumPair >> unwrap + + let tryDecodeTimestampPairs e d = result { let! bytes = tryDecode e d return! bytes |> bytesToTimestampPair } - let decodeTimestampPairs encodingType (d: byte[]) = + + let decodeTimestampPairs encodingType (d: array) = tryDecodeTimestampPairs encodingType d |> unwrap - + module Encoder = - let private encode(ty: EncodingType) (value:byte[]): byte[] = - if value.Length = 0 then [||] else - match ty with - | EncodingType.SortedPlain -> - value - | EncodingType.ZLib -> - use outputMs = new MemoryStream() - use ds = new DeflateStream(outputMs, CompressionMode.Compress) - ds.Write(value, 0, value.Length) - ds.Flush() - ds.Close() - outputMs.ToArray() - | x -> - failwithf "Unreachable! Unknown encoding type %A" x - - let encodeShortChannelIds (ty) (shortIds: ShortChannelId[]) = + let private encode (ty: EncodingType) (value: array) : array = + if value.Length = 0 then + [||] + else + match ty with + | EncodingType.SortedPlain -> value + | EncodingType.ZLib -> + use outputMs = new MemoryStream() + use ds = new DeflateStream(outputMs, CompressionMode.Compress) + ds.Write(value, 0, value.Length) + ds.Flush() + ds.Close() + outputMs.ToArray() + | x -> failwithf "Unreachable! Unknown encoding type %A" x + + let encodeShortChannelIds ty (shortIds: array) = let b = shortIds |> Array.map(fun i -> i.ToBytes()) |> Array.concat encode (ty) (b) - - let encodeQueryFlags (ty) (flags: QueryFlags[]) = + + let encodeQueryFlags ty (flags: array) = let b = flags |> Array.map(fun i -> i.ToBytes()) |> Array.concat encode (ty) (b) - - let encodeTimestampsPairs (ty) (timestampPairs: TwoTimestamps[]) = - let b = timestampPairs |> Array.map(fun i -> i.ToBytes()) |> Array.concat + + let encodeTimestampsPairs ty (timestampPairs: array) = + let b = + timestampPairs |> Array.map(fun i -> i.ToBytes()) |> Array.concat + encode ty b diff --git a/src/DotNetLightning.Core/Serialization/Features.fs b/src/DotNetLightning.Core/Serialization/Features.fs index 3cfb16535..59bfaf10d 100644 --- a/src/DotNetLightning.Core/Serialization/Features.fs +++ b/src/DotNetLightning.Core/Serialization/Features.fs @@ -16,6 +16,7 @@ type FeaturesSupport = type FeatureError = | UnknownRequiredFeature of msg: string | BogusFeatureDependency of msg: string + member this.Message = match this with | UnknownRequiredFeature msg @@ -23,101 +24,126 @@ type FeatureError = /// Feature bits specified in BOLT 9. /// It has no constructors, use its static members to instantiate -type Feature = private { - RfcName: string - Mandatory: int -} - with +type Feature = + private + { + RfcName: string + Mandatory: int + } + member this.MandatoryBitPosition = this.Mandatory member this.OptionalBitPosition = this.Mandatory + 1 + member this.BitPosition(support: FeaturesSupport) = match support with | Mandatory -> this.MandatoryBitPosition | Optional -> this.OptionalBitPosition - override this.ToString() = this.RfcName - - static member OptionDataLossProtect = { - RfcName = "option_data_loss_protect" - Mandatory = 0 - } - - static member InitialRoutingSync = { - RfcName = "initial_routing_sync" - Mandatory = 2 - } - - static member OptionUpfrontShutdownScript = { - RfcName = "option_upfront_shutdown_script" - Mandatory = 4 - } - - static member ChannelRangeQueries = { - RfcName = "gossip_queries" - Mandatory = 6 - } - - static member VariableLengthOnion = { - RfcName = "var_onion_optin" - Mandatory = 8 - } - - static member ChannelRangeQueriesExtended = { - RfcName = "gossip_queries_ex" - Mandatory = 10 - } - - static member OptionStaticRemoteKey = { - RfcName = "option_static_remotekey" - Mandatory = 12 - } - - static member PaymentSecret = { - RfcName = "payment_secret" - Mandatory = 14 - } - - static member BasicMultiPartPayment = { - RfcName = "basic_mpp" - Mandatory = 16 - } - - static member OptionSupportLargeChannel = { - RfcName = "option_support_large_channel" - Mandatory = 18 - } - + override this.ToString() = + this.RfcName + + static member OptionDataLossProtect = + { + RfcName = "option_data_loss_protect" + Mandatory = 0 + } + + static member InitialRoutingSync = + { + RfcName = "initial_routing_sync" + Mandatory = 2 + } + + static member OptionUpfrontShutdownScript = + { + RfcName = "option_upfront_shutdown_script" + Mandatory = 4 + } + + static member ChannelRangeQueries = + { + RfcName = "gossip_queries" + Mandatory = 6 + } + + static member VariableLengthOnion = + { + RfcName = "var_onion_optin" + Mandatory = 8 + } + + static member ChannelRangeQueriesExtended = + { + RfcName = "gossip_queries_ex" + Mandatory = 10 + } + + static member OptionStaticRemoteKey = + { + RfcName = "option_static_remotekey" + Mandatory = 12 + } + + static member PaymentSecret = + { + RfcName = "payment_secret" + Mandatory = 14 + } + + static member BasicMultiPartPayment = + { + RfcName = "basic_mpp" + Mandatory = 16 + } + + static member OptionSupportLargeChannel = + { + RfcName = "option_support_large_channel" + Mandatory = 18 + } + module internal Feature = /// Features may depend on other features, as specified in BOLT 9 let private featuresDependency = Map.empty - |> Map.add (Feature.ChannelRangeQueriesExtended) ([Feature.ChannelRangeQueries]) - |> Map.add (Feature.BasicMultiPartPayment) ([Feature.PaymentSecret]) - |> Map.add (Feature.PaymentSecret) ([Feature.VariableLengthOnion]) - - let isFeatureOn(features: BitArray) (bit: int) = + |> Map.add + (Feature.ChannelRangeQueriesExtended) + ([ Feature.ChannelRangeQueries ]) + |> Map.add (Feature.BasicMultiPartPayment) ([ Feature.PaymentSecret ]) + |> Map.add (Feature.PaymentSecret) ([ Feature.VariableLengthOnion ]) + + let isFeatureOn (features: BitArray) (bit: int) = (features.Length > bit) && features.Reverse().[bit] - - let hasFeature(features: BitArray) (f: Feature) (support: FeaturesSupport option) = + + let hasFeature + (features: BitArray) + (f: Feature) + (support: option) + = match support with - | Some(Mandatory) -> - isFeatureOn(features) (f.Mandatory) - | Some(Optional) -> - isFeatureOn(features) (f.OptionalBitPosition) + | Some(Mandatory) -> isFeatureOn (features) (f.Mandatory) + | Some(Optional) -> isFeatureOn (features) (f.OptionalBitPosition) | None -> - isFeatureOn(features) (f.OptionalBitPosition) || isFeatureOn(features) (f.Mandatory) - - let private printDeps (deps: #seq) (features) = + isFeatureOn (features) (f.OptionalBitPosition) + || isFeatureOn (features) (f.Mandatory) + + let private printDeps (deps: #seq) features = deps - |> Seq.filter(fun d -> not <| (hasFeature(features) (d) (None) )) + |> Seq.filter(fun d -> not <| (hasFeature (features) (d) (None))) |> Seq.map(fun d -> d.ToString()) |> String.concat " and " + let validateFeatureGraph(features: BitArray) = result { for kvp in featuresDependency do let f = kvp.Key let deps = kvp.Value - if hasFeature(features) (f) (None) && deps |> List.exists(fun d -> not <| hasFeature(features) (d) (None)) then + + if hasFeature (features) (f) (None) + && deps + |> List.exists(fun d -> + not <| hasFeature (features) (d) (None) + ) then return! sprintf "%s sets %s but is missing a dependency %s " @@ -129,38 +155,41 @@ module internal Feature = else return () } - + let private supportedMandatoryFeatures = - seq { yield Feature.OptionDataLossProtect - yield Feature.InitialRoutingSync - yield Feature.OptionUpfrontShutdownScript - yield Feature.ChannelRangeQueries - yield Feature.VariableLengthOnion - // TODO: support this feature - // Feature.ChannelRangeQueriesExtended - yield Feature.OptionStaticRemoteKey - yield Feature.PaymentSecret - // TODO: support this feature - // Feature.BasicMultiPartPayment - yield Feature.OptionSupportLargeChannel - } + seq { + yield Feature.OptionDataLossProtect + yield Feature.InitialRoutingSync + yield Feature.OptionUpfrontShutdownScript + yield Feature.ChannelRangeQueries + yield Feature.VariableLengthOnion + // TODO: support this feature + // Feature.ChannelRangeQueriesExtended + yield Feature.OptionStaticRemoteKey + yield Feature.PaymentSecret + // TODO: support this feature + // Feature.BasicMultiPartPayment + yield Feature.OptionSupportLargeChannel + } |> Seq.map(fun f -> f.Mandatory) |> Set + /// Check that the features that we understand are correctly specified, and that there are no mandatory features /// we don't understand let areSupported(features: BitArray) = - + let reversed = features.Reverse() + seq { - for i in 0..reversed.Length - 1 do + for i in 0 .. reversed.Length - 1 do if (i % 2 = 0) then yield i } |> Seq.exists(fun i -> reversed.[i] && not <| supportedMandatoryFeatures.Contains(i) - ) + ) |> not - + let allFeatures = seq { yield Feature.OptionDataLossProtect @@ -175,68 +204,85 @@ module internal Feature = yield Feature.OptionSupportLargeChannel } |> Set - - + + [] type FeatureBits private (bitArray: BitArray) = - member internal __.BitArray - with get() = - bitArray - member internal this.ByteArray - with get() = - this.BitArray.ToByteArray() + member __.BitArray + with internal get () = bitArray + + member this.ByteArray + with internal get () = this.BitArray.ToByteArray() + static member TryCreate(ba: BitArray) = result { do! Feature.validateFeatureGraph(ba) + if not <| Feature.areSupported(ba) then return! - sprintf "feature bits (%s) contains a mandatory flag that we don't know!" (ba.PrintBits()) + sprintf + "feature bits (%s) contains a mandatory flag that we don't know!" + (ba.PrintBits()) |> FeatureError.UnknownRequiredFeature |> Error else return FeatureBits ba } + static member Zero = - let b: bool array = [||] + let b: array = [||] b |> BitArray |> FeatureBits - static member TryCreate(bytes: byte[]) = + + static member TryCreate(bytes: array) = FeatureBits.TryCreate(BitArray.FromBytes bytes) static member TryCreate(v: int64) = BitArray.FromInt64 v |> FeatureBits.TryCreate - + static member CreateUnsafe(v: int64) = BitArray.FromInt64 v |> FeatureBits.CreateUnsafe - + static member private Unwrap(r: Result) = match r with - | Error(FeatureError.UnknownRequiredFeature(e)) - | Error(FeatureError.BogusFeatureDependency(e)) -> raise <| FormatException(e) + | Error(FeatureError.UnknownRequiredFeature e) + | Error(FeatureError.BogusFeatureDependency e) -> + raise <| FormatException(e) | Ok fb -> fb + /// Throws FormatException /// TODO: ugliness of this method is caused by binary serialization throws error instead of returning Result /// We should refactor serialization altogether at some point - static member CreateUnsafe(bytes: byte[]) = + static member CreateUnsafe(bytes: array) = FeatureBits.TryCreate bytes |> FeatureBits.Unwrap - + static member CreateUnsafe(ba: BitArray) = FeatureBits.TryCreate ba |> FeatureBits.Unwrap + static member TryParse(str: string) = result { let! ba = BitArray.TryParse str - return! ba |> FeatureBits.TryCreate |> Result.mapError(fun fe -> fe.ToString()) + + return! + ba + |> FeatureBits.TryCreate + |> Result.mapError(fun fe -> fe.ToString()) } - + override this.ToString() = this.BitArray.PrintBits() - - member this.SetFeature(feature: Feature) (support: FeaturesSupport) (on: bool): FeatureBits = + + member this.SetFeature + (feature: Feature) + (support: FeaturesSupport) + (on: bool) + : FeatureBits = let index = feature.BitPosition support let length = bitArray.Length let bits = Array.zeroCreate length //FIXME: will clone() work here? this.BitArray.CopyTo(bits, 0) let newBitArray = BitArray bits + if length <= index then newBitArray.Length <- index + 1 @@ -253,26 +299,32 @@ type FeatureBits private (bitArray: BitArray) = // RightShift sets the leading bits to zero. for i in 0 .. (index - length) do newBitArray.[i] <- false + newBitArray.[newBitArray.Length - index - 1] <- on FeatureBits newBitArray member this.HasFeature(f, ?featureType) = Feature.hasFeature this.BitArray (f) (featureType) - + member this.PrettyPrint = let sb = StringBuilder() let reversed = this.BitArray.Reverse() + for f in Feature.allFeatures do - if (reversed.Length > f.MandatoryBitPosition) && (reversed.[f.MandatoryBitPosition]) then + if (reversed.Length > f.MandatoryBitPosition) + && (reversed.[f.MandatoryBitPosition]) then sb.Append(sprintf "%s is mandatory. " f.RfcName) |> ignore - else if (reversed.Length > f.OptionalBitPosition) && (reversed.[f.OptionalBitPosition]) then + else if (reversed.Length > f.OptionalBitPosition) + && (reversed.[f.OptionalBitPosition]) then sb.Append(sprintf "%s is optional. " f.RfcName) |> ignore else sb.Append(sprintf "%s is non supported. " f.RfcName) |> ignore + sb.ToString() - - member this.ToByteArray() = this.ByteArray - + + member this.ToByteArray() = + this.ByteArray + // --- equality and comparison members ---- member this.Equals(o: FeatureBits) = this.ByteArray = o.ByteArray @@ -285,26 +337,39 @@ type FeatureBits private (bitArray: BitArray) = match other with | :? FeatureBits as o -> this.Equals o | _ -> false - + override this.GetHashCode() = let mutable num = 0 + for i in this.BitArray do num <- -1640531527 + i.GetHashCode() + ((num <<< 6) + (num >>> 2)) + num - + member this.CompareTo(o: FeatureBits) = - if (this.BitArray.Length > o.BitArray.Length) then -1 else - if (this.BitArray.Length < o.BitArray.Length) then 1 else - let mutable result = 0 - for i in 0..this.BitArray.Length - 1 do - if (this.BitArray.[i] > o.BitArray.[i]) then - result <- -1 - else if (this.BitArray.[i] < o.BitArray.[i]) then - result <- 1 - result + if (this.BitArray.Length > o.BitArray.Length) then + -1 + else if (this.BitArray.Length < o.BitArray.Length) then + 1 + else + let mutable result = 0 + + for i in 0 .. this.BitArray.Length - 1 do + if (this.BitArray.[i] > o.BitArray.[i]) then + result <- -1 + else if (this.BitArray.[i] < o.BitArray.[i]) then + result <- 1 + + result + interface IComparable with - member this.CompareTo(o) = + member this.CompareTo o = match o with | :? FeatureBits as fb -> this.CompareTo fb - | _ -> raise <| ArgumentException ("Comparison should be done against same type (FeatureBits)", "o") - // -------- + | _ -> + raise + <| ArgumentException( + "Comparison should be done against same type (FeatureBits)", + "o" + ) +// -------- diff --git a/src/DotNetLightning.Core/Serialization/GenericTLV.fs b/src/DotNetLightning.Core/Serialization/GenericTLV.fs index 116db7d8d..226c261a7 100644 --- a/src/DotNetLightning.Core/Serialization/GenericTLV.fs +++ b/src/DotNetLightning.Core/Serialization/GenericTLV.fs @@ -6,37 +6,63 @@ open DotNetLightning.Core.Utils.Extensions open ResultUtils open ResultUtils.Portability -type GenericTLV = { - Type: uint64 - Value: byte[] -} - with - static member TryCreateFromBytes(b: byte[]) = +type GenericTLV = + { + Type: uint64 + Value: array + } + + static member TryCreateFromBytes(b: array) = result { let! ty, b = b.TryPopVarInt() let! l, b = b.TryPopVarInt() - if (l > (uint64 Int32.MaxValue)) then return! Error(sprintf "length for tlv is too long %A" l) else - let l = l |> int32 - if b.Length < l then return! Error (sprintf "malformed Generic TLV! bytes (%A) are shorter than specified length (%A)" b l) else - let value = b.[0..(l - 1)] - return { Type = ty; Value = value }, b.[l..] + + if (l > (uint64 Int32.MaxValue)) then + return! Error(sprintf "length for tlv is too long %A" l) + else + let l = l |> int32 + + if b.Length < l then + return! + Error( + sprintf + "malformed Generic TLV! bytes (%A) are shorter than specified length (%A)" + b + l + ) + else + let value = b.[0 .. (l - 1)] + + return + { + Type = ty + Value = value + }, + b.[l..] } - + /// consumes all bytes. - static member TryCreateManyFromBytes(bytes: byte[]) = + static member TryCreateManyFromBytes(bytes: array) = result { - let result = ResizeArray() - let mutable b = bytes - let mutable cont = true - while cont do - let! tlv, b2 = GenericTLV.TryCreateFromBytes(b) - result.Add(tlv) - b <- b2 - cont <- b.Length > 1 - return result.ToArray() + let result = ResizeArray() + let mutable b = bytes + let mutable cont = true + + while cont do + let! tlv, b2 = GenericTLV.TryCreateFromBytes(b) + result.Add(tlv) + b <- b2 + cont <- b.Length > 1 + + return result.ToArray() } - + member this.ToBytes() = let ty = this.Type.ToVarInt() - Array.concat[ty; this.Value.LongLength.ToVarInt(); this.Value] - \ No newline at end of file + + Array.concat + [ + ty + this.Value.LongLength.ToVarInt() + this.Value + ] diff --git a/src/DotNetLightning.Core/Serialization/LightningStream.fs b/src/DotNetLightning.Core/Serialization/LightningStream.fs index 67e90cd77..aedb8f2c5 100644 --- a/src/DotNetLightning.Core/Serialization/LightningStream.fs +++ b/src/DotNetLightning.Core/Serialization/LightningStream.fs @@ -12,56 +12,71 @@ type Scope(openAction: Action, closeAction: Action) = let _close = closeAction do openAction.Invoke() member private this.Close = _close + interface IDisposable with - member this.Dispose() = this.Close.Invoke() + member this.Dispose() = + this.Close.Invoke() type O = OptionalArgumentAttribute type D = System.Runtime.InteropServices.DefaultParameterValueAttribute + /// Simple Wrapper stream for serializing Lightning Network p2p messages. /// Why not use BitWriter/Reader? Because it does not support big endian and /// We might want to extend it in the future. [] -type LightningStream(inner: Stream) = +type LightningStream(inner: Stream) = inherit Stream() let _inner = inner member this.Inner = _inner override this.CanSeek = this.Inner.CanSeek - override this.Flush() = this.Inner.Flush() + + override this.Flush() = + this.Inner.Flush() + override this.Length = this.Inner.Length + override this.Position - with get() = this.Inner.Position - and set (v) = this.Inner.Position <- v + with get () = this.Inner.Position + and set v = this.Inner.Position <- v + override this.Seek(offset: int64, origin: SeekOrigin) = this.Inner.Seek(offset, origin) + override this.SetLength(_value: int64) = - raise (NotSupportedException("SetLength is not supported for LigningStream")) + raise + <| NotSupportedException("SetLength is not supported for LigningStream") + override this.Close() = this.Inner.Close() - override this.Write(buffer: byte[], offset: int, count: int) = + override this.Write(buffer: array, offset: int, count: int) = this.Inner.Write(buffer, offset, count) - - member this.Write(buffer: byte[]) = - this.Write(buffer, 0 , buffer.Length) + + member this.Write(buffer: array) = + this.Write(buffer, 0, buffer.Length) member this.Write(b: byte) = this.WriteByte(b) - override this.Read(buf: byte[], offset: int, count: int) = + override this.Read(buf: array, offset: int, count: int) = this.Inner.Read(buf, offset, count) + type LightningWriterStream(inner: Stream) = inherit LightningStream(inner) - do if not (inner.CanWrite) then invalidArg "inner" "inner stream must be writable" + do + if not(inner.CanWrite) then + invalidArg "inner" "inner stream must be writable" + override this.CanWrite = true override this.CanRead = this.Inner.CanRead - override this.Write(buffer: byte[], offset: int, count: int) = + override this.Write(buffer: array, offset: int, count: int) = this.Inner.Write(buffer, offset, count) - member this.Write(buf: byte[]) = + member this.Write(buf: array) = this.Write(buf, 0, buf.Length) member this.Write(b: byte) = @@ -73,48 +88,54 @@ type LightningWriterStream(inner: Stream) = member this.Write(data: uint16, lendian: bool) = let mutable buf = Array.zeroCreate 2 + if lendian then buf.[0] <- (byte) data - buf.[1] <- (byte (data >>> 8)) + buf.[1] <- (byte(data >>> 8)) else - buf.[0] <- (byte (data >>> 8)) + buf.[0] <- (byte(data >>> 8)) buf.[1] <- byte data + this.Write(buf, 0, 2) member this.Write(data: uint32, lendian: bool) = let mutable buf = Array.zeroCreate 4 + if lendian then buf.[0] <- (byte) data - buf.[1] <- (byte (data >>> 8)) - buf.[2] <- (byte (data >>> 16)) - buf.[3] <- (byte (data >>> 24)) + buf.[1] <- (byte(data >>> 8)) + buf.[2] <- (byte(data >>> 16)) + buf.[3] <- (byte(data >>> 24)) else - buf.[0] <- (byte (data >>> 24)) - buf.[1] <- (byte (data >>> 16)) - buf.[2] <- (byte (data >>> 8)) + buf.[0] <- (byte(data >>> 24)) + buf.[1] <- (byte(data >>> 16)) + buf.[2] <- (byte(data >>> 8)) buf.[3] <- byte data + this.Write(buf, 0, 4) member this.Write(data: uint64, lendian: bool) = let mutable buf = Array.zeroCreate 8 + if lendian then buf.[0] <- (byte) data - buf.[1] <- (byte (data >>> 8)) - buf.[2] <- (byte (data >>> 16)) - buf.[3] <- (byte (data >>> 24)) - buf.[4] <- (byte (data >>> 32)) - buf.[5] <- (byte (data >>> 40)) - buf.[6] <- (byte (data >>> 48)) - buf.[7] <- (byte (data >>> 56)) + buf.[1] <- (byte(data >>> 8)) + buf.[2] <- (byte(data >>> 16)) + buf.[3] <- (byte(data >>> 24)) + buf.[4] <- (byte(data >>> 32)) + buf.[5] <- (byte(data >>> 40)) + buf.[6] <- (byte(data >>> 48)) + buf.[7] <- (byte(data >>> 56)) else - buf.[0] <- (byte (data >>> 56)) - buf.[1] <- (byte (data >>> 48)) - buf.[2] <- (byte (data >>> 40)) - buf.[3] <- (byte (data >>> 32)) - buf.[4] <- (byte (data >>> 24)) - buf.[5] <- (byte (data >>> 16)) - buf.[6] <- (byte (data >>> 8)) + buf.[0] <- (byte(data >>> 56)) + buf.[1] <- (byte(data >>> 48)) + buf.[2] <- (byte(data >>> 40)) + buf.[3] <- (byte(data >>> 32)) + buf.[4] <- (byte(data >>> 24)) + buf.[5] <- (byte(data >>> 16)) + buf.[6] <- (byte(data >>> 8)) buf.[7] <- byte data + this.Write(buf, 0, 8) member this.Write(data: int64, lendian: bool) = @@ -123,35 +144,45 @@ type LightningWriterStream(inner: Stream) = member this.Write(data: int32, lendian: bool) = this.Write(uint32 data, lendian) - member this.Write (data: byte[] option) = + member this.Write(data: option>) = match data with | Some d -> this.Write(d, 0, d.Length) | None -> () + member this.Write(data: ShortChannelId) = let d = data.ToBytes() this.Write(d, 0, d.Length) + member this.Write(data: uint256, lendian: bool) = let d = data.ToBytes(lendian) this.Write(d, 0, d.Length) + member this.Write(data: PubKey) = let d = data.ToBytes() this.Write(d, 0, d.Length) + member this.Write(data: LNECDSASignature) = let d = data.ToBytesCompact() this.Write(d, 0, d.Length) + member this.Write(data: RGB) = this.Inner.WriteByte(data.Red) this.Inner.WriteByte(data.Green) this.Inner.WriteByte(data.Blue) + member this.Write(commitmentNumber: CommitmentNumber) = - this.Write((UInt48.MaxValue - commitmentNumber.Index()).UInt64, false) + this.Write( + (UInt48.MaxValue - commitmentNumber.Index()) + .UInt64, + false + ) - member this.WriteWithLen(data: byte[]) = + member this.WriteWithLen(data: array) = let length = data.Length - this.Write((uint16)length, false) + this.Write((uint16) length, false) this.Write(data, 0, length) - member this.WriteWithLen(data: byte[] option) = + member this.WriteWithLen(data: option>) = match data with | Some d -> this.WriteWithLen(d) | None -> () @@ -168,28 +199,32 @@ type LightningWriterStream(inner: Stream) = else this.Write(0xffuy) this.Write(x, false) - + member this.WriteTLV(tlv: GenericTLV) = this.WriteBigSize(tlv.Type) this.WriteBigSize(uint64 tlv.Value.LongLength) this.Write(tlv.Value) - + member this.WriteTLVStream(tlvs: #seq) = tlvs |> Seq.iter(fun tlv -> this.WriteTLV(tlv)) - + type LightningReaderStream(inner: Stream) = inherit LightningStream(inner) - do if (not inner.CanRead) then invalidArg "inner" "inner stream must be readable" - let MaxArraySize = 1024*1024 + + do + if (not inner.CanRead) then + invalidArg "inner" "inner stream must be readable" + + let MaxArraySize = 1024 * 1024 let mutable m_buffer = Array.zeroCreate MaxArraySize override this.CanRead = true override this.CanWrite = this.Inner.CanWrite - member this.ReadAll(): byte[] = + member this.ReadAll() : array = this.ReadBytes(int32 this.Length - int32 this.Position) - member this.TryReadAll(): byte[] option = + member this.TryReadAll() : option> = if (this.Length > this.Position) then this.ReadAll() |> Some else @@ -198,21 +233,33 @@ type LightningReaderStream(inner: Stream) = member private this.FillBuffer(numBytes: int) = if (isNull m_buffer) then failwith "m_buffer was null" + if (numBytes < 0 || (numBytes > m_buffer.Length)) then - raise (ArgumentOutOfRangeException(sprintf "numBytes was %d" numBytes)) + raise + <| ArgumentOutOfRangeException(sprintf "numBytes was %i" numBytes) let mutable n = 0 + if (numBytes = 1) then n <- this.Inner.ReadByte() + if (n = -1) then - raise (EndOfStreamException "Inner Stream for LightningReaderStream has been consumed") + raise + <| EndOfStreamException + "Inner Stream for LightningReaderStream has been consumed" + m_buffer.[0] <- byte n else let mutable bytesRead = 0 + while (bytesRead < numBytes) do n <- this.Inner.Read(m_buffer, bytesRead, numBytes - bytesRead) + if (n = 0) then - raise (EndOfStreamException "Inner Stream for LightningReaderStream has been consumed") + raise + <| EndOfStreamException + "Inner Stream for LightningReaderStream has been consumed" + bytesRead <- bytesRead + n // ##### Methods for reading @@ -220,22 +267,26 @@ type LightningReaderStream(inner: Stream) = this.FillBuffer(1) m_buffer.[0] <> 0uy - member this.ReadByte(): byte = + member this.ReadByte() : byte = let b = this.Inner.ReadByte() + if (b = -1) then - raise (EndOfStreamException "Inner Stream for LightningReaderStream has been consumed") + raise + <| EndOfStreamException + "Inner Stream for LightningReaderStream has been consumed" else (byte b) - + member this.ReadUInt8() = - uint8 (this.ReadByte()) - + uint8(this.ReadByte()) + member this.ReadSByte() = this.FillBuffer(1) (sbyte m_buffer.[0]) member this.ReadInt16(lendian: bool) = this.FillBuffer(2) + if lendian then ((int16 m_buffer.[0]) ||| (int16 m_buffer.[1] <<< 8)) else @@ -243,6 +294,7 @@ type LightningReaderStream(inner: Stream) = member this.ReadUInt16(lendian: bool) = this.FillBuffer(2) + if lendian then ((uint16 m_buffer.[0]) ||| (uint16 m_buffer.[1] <<< 8)) else @@ -250,183 +302,203 @@ type LightningReaderStream(inner: Stream) = member this.ReadInt32(lendian: bool) = this.FillBuffer(4) + if lendian then - ((int32 m_buffer.[0]) ||| - (int32 m_buffer.[1] <<< 8) ||| - (int32 m_buffer.[2] <<< 16) ||| - (int32 m_buffer.[3] <<< 24)) + ((int32 m_buffer.[0]) + ||| (int32 m_buffer.[1] <<< 8) + ||| (int32 m_buffer.[2] <<< 16) + ||| (int32 m_buffer.[3] <<< 24)) else - ((int32 m_buffer.[0] <<< 24) ||| - (int32 m_buffer.[1] <<< 16) ||| - (int32 m_buffer.[2] <<< 8) ||| - (int32 m_buffer.[3])) + ((int32 m_buffer.[0] <<< 24) + ||| (int32 m_buffer.[1] <<< 16) + ||| (int32 m_buffer.[2] <<< 8) + ||| (int32 m_buffer.[3])) member this.ReadUInt32(lendian: bool) = this.FillBuffer(4) + if lendian then - ((uint32 m_buffer.[0]) ||| - (uint32 m_buffer.[1] <<< 8) ||| - (uint32 m_buffer.[2] <<< 16) ||| - (uint32 m_buffer.[3] <<< 24)) + ((uint32 m_buffer.[0]) + ||| (uint32 m_buffer.[1] <<< 8) + ||| (uint32 m_buffer.[2] <<< 16) + ||| (uint32 m_buffer.[3] <<< 24)) else - ((uint32 m_buffer.[0] <<< 24) ||| - (uint32 m_buffer.[1] <<< 16) ||| - (uint32 m_buffer.[2] <<< 8) ||| - (uint32 m_buffer.[3])) + ((uint32 m_buffer.[0] <<< 24) + ||| (uint32 m_buffer.[1] <<< 16) + ||| (uint32 m_buffer.[2] <<< 8) + ||| (uint32 m_buffer.[3])) + member this.ReadInt64(lendian: bool) = this.FillBuffer(8) + if lendian then - ((int64 m_buffer.[0]) ||| - (int64 m_buffer.[1] <<< 8) ||| - (int64 m_buffer.[2] <<< 16) ||| - (int64 m_buffer.[3] <<< 24) ||| - (int64 m_buffer.[4] <<< 32) ||| - (int64 m_buffer.[5] <<< 40) ||| - (int64 m_buffer.[6] <<< 48) ||| - (int64 m_buffer.[7] <<< 56)) + ((int64 m_buffer.[0]) + ||| (int64 m_buffer.[1] <<< 8) + ||| (int64 m_buffer.[2] <<< 16) + ||| (int64 m_buffer.[3] <<< 24) + ||| (int64 m_buffer.[4] <<< 32) + ||| (int64 m_buffer.[5] <<< 40) + ||| (int64 m_buffer.[6] <<< 48) + ||| (int64 m_buffer.[7] <<< 56)) else - ((int64 m_buffer.[0]) <<< 56 ||| - (int64 m_buffer.[1] <<< 48) ||| - (int64 m_buffer.[2] <<< 40) ||| - (int64 m_buffer.[3] <<< 32) ||| - (int64 m_buffer.[4] <<< 24) ||| - (int64 m_buffer.[5] <<< 16) ||| - (int64 m_buffer.[6] <<< 8) ||| - (int64 m_buffer.[7])) + ((int64 m_buffer.[0]) <<< 56 + ||| (int64 m_buffer.[1] <<< 48) + ||| (int64 m_buffer.[2] <<< 40) + ||| (int64 m_buffer.[3] <<< 32) + ||| (int64 m_buffer.[4] <<< 24) + ||| (int64 m_buffer.[5] <<< 16) + ||| (int64 m_buffer.[6] <<< 8) + ||| (int64 m_buffer.[7])) member this.ReadUInt64(lendian: bool) = this.FillBuffer(8) + if lendian then - ((uint64 m_buffer.[0]) ||| - (uint64 m_buffer.[1] <<< 8) ||| - (uint64 m_buffer.[2] <<< 16) ||| - (uint64 m_buffer.[3] <<< 24) ||| - (uint64 m_buffer.[4] <<< 32) ||| - (uint64 m_buffer.[5] <<< 40) ||| - (uint64 m_buffer.[6] <<< 48) ||| - (uint64 m_buffer.[7] <<< 56)) + ((uint64 m_buffer.[0]) + ||| (uint64 m_buffer.[1] <<< 8) + ||| (uint64 m_buffer.[2] <<< 16) + ||| (uint64 m_buffer.[3] <<< 24) + ||| (uint64 m_buffer.[4] <<< 32) + ||| (uint64 m_buffer.[5] <<< 40) + ||| (uint64 m_buffer.[6] <<< 48) + ||| (uint64 m_buffer.[7] <<< 56)) else - ((uint64 m_buffer.[0]) <<< 56 ||| - (uint64 m_buffer.[1] <<< 48) ||| - (uint64 m_buffer.[2] <<< 40) ||| - (uint64 m_buffer.[3] <<< 32) ||| - (uint64 m_buffer.[4] <<< 24) ||| - (uint64 m_buffer.[5] <<< 16) ||| - (uint64 m_buffer.[6] <<< 8) ||| - (uint64 m_buffer.[7])) - - member this.ReadUInt256([]lendian: bool): uint256 = + ((uint64 m_buffer.[0]) <<< 56 + ||| (uint64 m_buffer.[1] <<< 48) + ||| (uint64 m_buffer.[2] <<< 40) + ||| (uint64 m_buffer.[3] <<< 32) + ||| (uint64 m_buffer.[4] <<< 24) + ||| (uint64 m_buffer.[5] <<< 16) + ||| (uint64 m_buffer.[6] <<< 8) + ||| (uint64 m_buffer.[7])) + + member this.ReadUInt256([] lendian: bool) : uint256 = let b = this.ReadBytes(32) uint256(b, lendian) - member this.ReadWithLen(): array = + member this.ReadWithLen() : array = let len = this.ReadUInt16(false) this.ReadBytes(int32 len) - member this.ReadChannelFlags(): ChannelFlags = + member this.ReadChannelFlags() : ChannelFlags = let flags = this.ReadUInt8() ChannelFlags.FromUInt8 flags - member this.ReadKey(): Key = + member this.ReadKey() : Key = let bytes: array = this.ReadBytes Key.BytesLength new Key(bytes) - member this.ReadPubKey(): PubKey = + member this.ReadPubKey() : PubKey = let bytes = this.ReadBytes PubKey.BytesLength + match PubKey.TryCreatePubKey bytes with - | true, pubKey -> - pubKey - | false, _ -> - raise (FormatException("Invalid Pubkey encoding")) + | true, pubKey -> pubKey + | false, _ -> raise <| FormatException("Invalid Pubkey encoding") - member this.ReadPerCommitmentSecret(): PerCommitmentSecret = + member this.ReadPerCommitmentSecret() : PerCommitmentSecret = PerCommitmentSecret <| this.ReadKey() - member this.ReadPerCommitmentPoint(): PerCommitmentPoint = + member this.ReadPerCommitmentPoint() : PerCommitmentPoint = PerCommitmentPoint <| this.ReadPubKey() - member this.ReadFundingPubKey(): FundingPubKey = + member this.ReadFundingPubKey() : FundingPubKey = FundingPubKey <| this.ReadPubKey() - member this.ReadRevocationBasepoint(): RevocationBasepoint = + member this.ReadRevocationBasepoint() : RevocationBasepoint = RevocationBasepoint <| this.ReadPubKey() - member this.ReadPaymentBasepoint(): PaymentBasepoint = + member this.ReadPaymentBasepoint() : PaymentBasepoint = PaymentBasepoint <| this.ReadPubKey() - member this.ReadDelayedPaymentBasepoint(): DelayedPaymentBasepoint = + member this.ReadDelayedPaymentBasepoint() : DelayedPaymentBasepoint = DelayedPaymentBasepoint <| this.ReadPubKey() - member this.ReadHtlcBasepoint(): HtlcBasepoint = + member this.ReadHtlcBasepoint() : HtlcBasepoint = HtlcBasepoint <| this.ReadPubKey() - member this.ReadCommitmentNumber(): CommitmentNumber = + member this.ReadCommitmentNumber() : CommitmentNumber = let n = this.ReadUInt64 false CommitmentNumber <| (UInt48.MaxValue - (UInt48.FromUInt64 n)) - member this.ReadECDSACompact(): LNECDSASignature = + member this.ReadECDSACompact() : LNECDSASignature = let data = this.ReadBytes(64) LNECDSASignature.FromBytesCompact(data) - member this.ReadScript(): Script = + member this.ReadScript() : Script = let d = this.ReadWithLen() Script.FromBytesUnsafe(d) - member this.ReadRGB(): RGB = + member this.ReadRGB() : RGB = let r = this.ReadUInt8() let g = this.ReadUInt8() let b = this.ReadUInt8() + { Red = r Green = g Blue = b } - - member this.ReadBigSize(): uint64 = + + member this.ReadBigSize() : uint64 = let x = this.ReadUInt8() + if x < 0xfduy then uint64 x else if x = 0xfduy then let v = this.ReadUInt16(false) |> uint64 + if (v < 0xfdUL || 0x10000UL <= v) then raise <| FormatException("decoded varint is not canonical") + v else if x = 0xfeuy then let v = this.ReadUInt32(false) |> uint64 + if (v < 0x10000UL || 0x100000000UL <= v) then raise <| FormatException("decoded varint is not canonical") + v else let v = this.ReadUInt64(false) + if (v < 0x100000000UL) then raise <| FormatException("decoded varint is not canonical") + v - - member this.ReadAllAsBigSize(): array = + + member this.ReadAllAsBigSize() : array = let mutable rest = int32 this.Length - int32 this.Position let result = ResizeArray() + while rest > 0 do result.Add(this.ReadBigSize()) rest <- int32 this.Length - int32 this.Position + result.ToArray() - - member this.ReadTLV(): GenericTLV = + + member this.ReadTLV() : GenericTLV = let ty = this.ReadBigSize() let length = this.ReadBigSize() let value = this.ReadBytes(int32 length) - { GenericTLV.Type = ty; Value = value } - - member this.ReadTLVStream(): array = + + { + GenericTLV.Type = ty + Value = value + } + + member this.ReadTLVStream() : array = let mutable rest = int32 this.Length - int32 this.Position let result = ResizeArray() + while rest > 0 do result.Add(this.ReadTLV()) rest <- int32 this.Length - int32 this.Position + result |> Seq.toArray - - member this.ReadShutdownScriptPubKey(): ShutdownScriptPubKey = + + member this.ReadShutdownScriptPubKey() : ShutdownScriptPubKey = let script = this.ReadScript() + match ShutdownScriptPubKey.TryFromScript script with | Ok shutdownScript -> shutdownScript - | Error errorMsg -> - raise <| FormatException(errorMsg) + | Error errorMsg -> raise <| FormatException(errorMsg) diff --git a/src/DotNetLightning.Core/Serialization/Msgs/Msgs.fs b/src/DotNetLightning.Core/Serialization/Msgs/Msgs.fs index f02c4e6b6..98c279b58 100644 --- a/src/DotNetLightning.Core/Serialization/Msgs/Msgs.fs +++ b/src/DotNetLightning.Core/Serialization/Msgs/Msgs.fs @@ -26,23 +26,20 @@ type P2PDecodeError = | BadLengthDescriptor | UnexpectedEndOfStream of EndOfStreamException | IO of IOException - + member this.Message = match this with - | UnknownVersion -> - "Unknown version" + | UnknownVersion -> "Unknown version" | FeatureError featureError -> sprintf "Feature error: %s" featureError.Message - | InvalidValue -> - "Invalid value" - | ExtraAddressesPerType -> - "Extra address per type" - | BadLengthDescriptor -> - "Bad length descriptor" + | InvalidValue -> "Invalid value" + | ExtraAddressesPerType -> "Extra address per type" + | BadLengthDescriptor -> "Bad length descriptor" | UnexpectedEndOfStream endOfStreamException -> - sprintf "Unexpected end of stream: %s" (endOfStreamException.Message) - | IO ioException -> - sprintf "IO error: %s" ioException.Message + sprintf + "Unexpected end of stream: %s" + (endOfStreamException.Message) + | IO ioException -> sprintf "IO error: %s" ioException.Message type UnknownVersionException(msg) = inherit FormatException(msg) @@ -50,70 +47,111 @@ type UnknownVersionException(msg) = module internal TypeFlag = [] let Init = 16us + [] let Error = 17us + [] let Ping = 18us + [] let Pong = 19us + [] let OpenChannel = 32us + [] let AcceptChannel = 33us + [] let FundingCreated = 34us + [] let FundingSigned = 35us + [] let FundingLocked = 36us + [] let Shutdown = 38us + [] let ClosingSigned = 39us + [] let UpdateAddHTLC = 128us + [] let UpdateFulfillHTLC = 130us + [] let UpdateFailHTLC = 131us + [] let UpdateFailMalformedHTLC = 135us + [] let ChannelReestablish = 136us + [] let CommitmentSigned = 132us + [] let RevokeAndACK = 133us + [] let UpdateFee = 134us + [] let AnnouncementSignatures = 259us + [] let ChannelAnnouncement = 256us + [] let NodeAnnouncement = 257us + [] let ChannelUpdate = 258us + [] let QueryShortChannelIds = 261us + [] let ReplyShortChannelIdsEnd = 262us + [] let QueryChannelRange = 263us + [] let ReplyChannelRange = 264us + [] let GossipTimestampFilter = 265us -type ILightningMsg = interface end -type ISetupMsg = inherit ILightningMsg -type IChannelMsg = inherit ILightningMsg -type IHTLCMsg = inherit IChannelMsg -type IUpdateMsg = inherit IChannelMsg -type IRoutingMsg = inherit ILightningMsg -type IQueryMsg = inherit IRoutingMsg +type ILightningMsg = + interface + end + +type ISetupMsg = + inherit ILightningMsg + +type IChannelMsg = + inherit ILightningMsg -// #endregion +type IHTLCMsg = + inherit IChannelMsg + +type IUpdateMsg = + inherit IChannelMsg + +type IRoutingMsg = + inherit ILightningMsg + +type IQueryMsg = + inherit IRoutingMsg + +// #endregion /// Should throw `FormatException` when it fails type ILightningSerializable<'T when 'T: (new: unit -> 'T) and 'T :> ILightningSerializable<'T>> = @@ -121,32 +159,37 @@ type ILightningSerializable<'T when 'T: (new: unit -> 'T) and 'T :> ILightningSe abstract Serialize: LightningWriterStream -> unit module ILightningSerializable = - let internal fromBytes<'T when 'T :(new :unit -> 'T) and 'T :> ILightningSerializable<'T>>(data: byte[]) = + let internal fromBytes<'T when 'T: (new: unit -> 'T) and 'T :> ILightningSerializable<'T>> + (data: array) + = use ms = new MemoryStream(data) use ls = new LightningReaderStream(ms) let instance = new 'T() instance.Deserialize(ls) instance - let internal init<'T when 'T :(new :unit -> 'T) and 'T :> ILightningSerializable<'T>>() = + let internal init<'T when 'T: (new: unit -> 'T) and 'T :> ILightningSerializable<'T>> + () + = new 'T() - let internal deserialize<'T when 'T :(new :unit -> 'T) and 'T :> ILightningSerializable<'T>>(ls: LightningReaderStream) = + let internal deserialize<'T when 'T: (new: unit -> 'T) and 'T :> ILightningSerializable<'T>> + (ls: LightningReaderStream) + = let instance = new 'T() instance.Deserialize(ls) instance - let internal deserializeWithFlag(ls: LightningReaderStream): ILightningMsg = + let internal deserializeWithFlag + (ls: LightningReaderStream) + : ILightningMsg = let t = ls.ReadUInt16(false) + match t with - | TypeFlag.Init -> - deserialize(ls) :> ILightningMsg - | TypeFlag.Error -> - deserialize(ls) :> ILightningMsg - | TypeFlag.Ping -> - deserialize(ls) :> ILightningMsg - | TypeFlag.Pong -> - deserialize(ls) :> ILightningMsg + | TypeFlag.Init -> deserialize(ls) :> ILightningMsg + | TypeFlag.Error -> deserialize(ls) :> ILightningMsg + | TypeFlag.Ping -> deserialize(ls) :> ILightningMsg + | TypeFlag.Pong -> deserialize(ls) :> ILightningMsg | TypeFlag.OpenChannel -> deserialize(ls) :> ILightningMsg | TypeFlag.AcceptChannel -> @@ -157,8 +200,7 @@ module ILightningSerializable = deserialize(ls) :> ILightningMsg | TypeFlag.FundingLocked -> deserialize(ls) :> ILightningMsg - | TypeFlag.Shutdown -> - deserialize(ls) :> ILightningMsg + | TypeFlag.Shutdown -> deserialize(ls) :> ILightningMsg | TypeFlag.ClosingSigned -> deserialize(ls) :> ILightningMsg | TypeFlag.UpdateAddHTLC -> @@ -175,8 +217,7 @@ module ILightningSerializable = deserialize(ls) :> ILightningMsg | TypeFlag.RevokeAndACK -> deserialize(ls) :> ILightningMsg - | TypeFlag.UpdateFee -> - deserialize(ls) :> ILightningMsg + | TypeFlag.UpdateFee -> deserialize(ls) :> ILightningMsg | TypeFlag.AnnouncementSignatures -> deserialize(ls) :> ILightningMsg | TypeFlag.ChannelAnnouncement -> @@ -195,103 +236,163 @@ module ILightningSerializable = deserialize(ls) :> ILightningMsg | TypeFlag.GossipTimestampFilter -> deserialize(ls) :> ILightningMsg - | x -> - raise <| FormatException(sprintf "Unknown message type %d" x) + | x -> raise <| FormatException(sprintf "Unknown message type %i" x) + let serializeWithFlags (ls: LightningWriterStream) (data: ILightningMsg) = match data with | :? InitMsg as d -> ls.Write(TypeFlag.Init, false) - (d :> ILightningSerializable).Serialize(ls) + + (d :> ILightningSerializable) + .Serialize(ls) | :? ErrorMsg as d -> ls.Write(TypeFlag.Error, false) - (d :> ILightningSerializable).Serialize(ls) + + (d :> ILightningSerializable) + .Serialize(ls) | :? PingMsg as d -> ls.Write(TypeFlag.Ping, false) - (d :> ILightningSerializable).Serialize(ls) + + (d :> ILightningSerializable) + .Serialize(ls) | :? PongMsg as d -> ls.Write(TypeFlag.Pong, false) - (d :> ILightningSerializable).Serialize(ls) + + (d :> ILightningSerializable) + .Serialize(ls) | :? OpenChannelMsg as d -> ls.Write(TypeFlag.OpenChannel, false) - (d :> ILightningSerializable).Serialize(ls) + + (d :> ILightningSerializable) + .Serialize(ls) | :? AcceptChannelMsg as d -> ls.Write(TypeFlag.AcceptChannel, false) - (d :> ILightningSerializable).Serialize(ls) + + (d :> ILightningSerializable) + .Serialize(ls) | :? FundingCreatedMsg as d -> ls.Write(TypeFlag.FundingCreated, false) - (d :> ILightningSerializable).Serialize(ls) + + (d :> ILightningSerializable) + .Serialize(ls) | :? FundingSignedMsg as d -> ls.Write(TypeFlag.FundingSigned, false) - (d :> ILightningSerializable).Serialize(ls) + + (d :> ILightningSerializable) + .Serialize(ls) | :? FundingLockedMsg as d -> ls.Write(TypeFlag.FundingLocked, false) - (d :> ILightningSerializable).Serialize(ls) + + (d :> ILightningSerializable) + .Serialize(ls) | :? ShutdownMsg as d -> ls.Write(TypeFlag.Shutdown, false) - (d :> ILightningSerializable).Serialize(ls) + + (d :> ILightningSerializable) + .Serialize(ls) | :? ClosingSignedMsg as d -> ls.Write(TypeFlag.ClosingSigned, false) - (d :> ILightningSerializable).Serialize(ls) + + (d :> ILightningSerializable) + .Serialize(ls) | :? UpdateAddHTLCMsg as d -> ls.Write(TypeFlag.UpdateAddHTLC, false) - (d :> ILightningSerializable).Serialize(ls) + + (d :> ILightningSerializable) + .Serialize(ls) | :? UpdateFulfillHTLCMsg as d -> ls.Write(TypeFlag.UpdateFulfillHTLC, false) - (d :> ILightningSerializable).Serialize(ls) + + (d :> ILightningSerializable) + .Serialize(ls) | :? UpdateFailHTLCMsg as d -> ls.Write(TypeFlag.UpdateFailHTLC, false) - (d :> ILightningSerializable).Serialize(ls) + + (d :> ILightningSerializable) + .Serialize(ls) | :? UpdateFailMalformedHTLCMsg as d -> ls.Write(TypeFlag.UpdateFailMalformedHTLC, false) - (d :> ILightningSerializable).Serialize(ls) + + (d :> ILightningSerializable) + .Serialize(ls) | :? ChannelReestablishMsg as d -> ls.Write(TypeFlag.ChannelReestablish, false) - (d :> ILightningSerializable).Serialize(ls) + + (d :> ILightningSerializable) + .Serialize(ls) | :? CommitmentSignedMsg as d -> ls.Write(TypeFlag.CommitmentSigned, false) - (d :> ILightningSerializable).Serialize(ls) + + (d :> ILightningSerializable) + .Serialize(ls) | :? RevokeAndACKMsg as d -> ls.Write(TypeFlag.RevokeAndACK, false) - (d :> ILightningSerializable).Serialize(ls) + + (d :> ILightningSerializable) + .Serialize(ls) | :? UpdateFeeMsg as d -> ls.Write(TypeFlag.UpdateFee, false) - (d :> ILightningSerializable).Serialize(ls) + + (d :> ILightningSerializable) + .Serialize(ls) | :? AnnouncementSignaturesMsg as d -> ls.Write(TypeFlag.AnnouncementSignatures, false) - (d :> ILightningSerializable).Serialize(ls) + + (d :> ILightningSerializable) + .Serialize(ls) | :? ChannelAnnouncementMsg as d -> ls.Write(TypeFlag.ChannelAnnouncement, false) - (d :> ILightningSerializable).Serialize(ls) + + (d :> ILightningSerializable) + .Serialize(ls) | :? NodeAnnouncementMsg as d -> ls.Write(TypeFlag.NodeAnnouncement, false) - (d :> ILightningSerializable).Serialize(ls) + + (d :> ILightningSerializable) + .Serialize(ls) | :? ChannelUpdateMsg as d -> ls.Write(TypeFlag.ChannelUpdate, false) - (d :> ILightningSerializable).Serialize(ls) + + (d :> ILightningSerializable) + .Serialize(ls) | :? QueryShortChannelIdsMsg as d -> ls.Write(TypeFlag.QueryShortChannelIds, false) - (d :> ILightningSerializable).Serialize(ls) + + (d :> ILightningSerializable) + .Serialize(ls) | :? ReplyShortChannelIdsEndMsg as d -> ls.Write(TypeFlag.ReplyShortChannelIdsEnd, false) - (d :> ILightningSerializable).Serialize(ls) + + (d :> ILightningSerializable) + .Serialize(ls) | :? QueryChannelRangeMsg as d -> ls.Write(TypeFlag.QueryChannelRange, false) - (d :> ILightningSerializable).Serialize(ls) + + (d :> ILightningSerializable) + .Serialize(ls) | :? ReplyChannelRangeMsg as d -> ls.Write(TypeFlag.ReplyChannelRange, false) - (d :> ILightningSerializable).Serialize(ls) + + (d :> ILightningSerializable) + .Serialize(ls) | :? GossipTimestampFilterMsg as d -> ls.Write(TypeFlag.GossipTimestampFilter, false) - (d :> ILightningSerializable).Serialize(ls) - | x -> failwithf "%A is not known lightning message. This should never happen" x + + (d :> ILightningSerializable) + .Serialize(ls) + | x -> + failwithf + "%A is not known lightning message. This should never happen" + x module LightningMsg = - let fromBytes<'T when 'T :> ILightningMsg>(b: byte[]): Result<_, P2PDecodeError> = + let fromBytes<'T when 'T :> ILightningMsg> + (b: array) + : Result<_, P2PDecodeError> = try use ms = new MemoryStream(b) use ls = new LightningReaderStream(ms) - ILightningSerializable.deserializeWithFlag ls - |> Ok + ILightningSerializable.deserializeWithFlag ls |> Ok with | :? EndOfStreamException as ex -> UnexpectedEndOfStream ex |> Error | :? System.IO.IOException as ex -> P2PDecodeError.IO ex |> Error @@ -315,7 +416,11 @@ type ILightningSerializableExtension() = ms.ToArray() [] - static member SerializeWithLen(this: ILightningSerializable<'T>, w: LightningWriterStream) = + static member SerializeWithLen + ( + this: ILightningSerializable<'T>, + w: LightningWriterStream + ) = let d = this.ToBytes() w.WriteWithLen(d) @@ -333,192 +438,224 @@ type ILightningSerializableExtension() = // ---------- network message primitives -type OptionalField<'T> = 'T option +type OptionalField<'T> = option<'T> -[] +[] type OnionPacket = { mutable Version: uint8 /// This might be 33 bytes of 0uy in case of last packet /// So we are not using `PubKey` to represent pubkey - mutable PublicKey: byte[] - mutable HopData: byte[] + mutable PublicKey: array + mutable HopData: array mutable HMAC: uint256 } - with - - static member LastPacket = - let o = ILightningSerializable.init() - o.Version <- 0uy - o.PublicKey <- Array.zeroCreate 33 - o.HopData <- Array.zeroCreate 1300 - o.HMAC <- uint256.Zero - o - - member this.IsLastPacket = - this.HMAC = uint256.Zero - - interface ILightningSerializable with - member this.Deserialize(ls: LightningReaderStream) = - this.Version <- - let v = ls.ReadUInt8() - if (v <> 0uy) then - raise <| UnknownVersionException("Unknown version byte for OnionPacket") - else - v - this.PublicKey <- ls.ReadBytes(33) - this.HopData <- ls.ReadBytes(1300) - this.HMAC <- ls.ReadUInt256(true) - member this.Serialize(ls) = - ls.Write(this.Version) - ls.Write(this.PublicKey) - ls.Write(this.HopData) - ls.Write(this.HMAC, true) + static member LastPacket = + let o = ILightningSerializable.init() + o.Version <- 0uy + o.PublicKey <- Array.zeroCreate 33 + o.HopData <- Array.zeroCreate 1300 + o.HMAC <- uint256.Zero + o + + member this.IsLastPacket = this.HMAC = uint256.Zero + + interface ILightningSerializable with + member this.Deserialize(ls: LightningReaderStream) = + this.Version <- + let v = ls.ReadUInt8() + + if (v <> 0uy) then + raise + <| UnknownVersionException( + "Unknown version byte for OnionPacket" + ) + else + v -type OnionErrorPacket = { - Data: byte[] -} + this.PublicKey <- ls.ReadBytes(33) + this.HopData <- ls.ReadBytes(1300) + this.HMAC <- ls.ReadUInt256(true) + + member this.Serialize ls = + ls.Write(this.Version) + ls.Write(this.PublicKey) + ls.Write(this.HopData) + ls.Write(this.HMAC, true) + +type OnionErrorPacket = + { + Data: array + } [] type InitMsg = { mutable Features: FeatureBits - mutable TLVStream: InitTLV array + mutable TLVStream: array } - with - interface ISetupMsg - interface ILightningSerializable with - member this.Deserialize(ls: LightningReaderStream) = - // For backwards compatibility reason, we must consider legacy - // `global features` section. (see bolt 1) - let globalFeatures = ls.ReadWithLen() - let localFeatures = ls.ReadWithLen() - let oredFeatures = - let len = Math.Max(globalFeatures.Length, localFeatures.Length) - let oredFeatures = Array.zeroCreate len - for index in 0 .. (len - 1) do - let globalFeaturesByte = - if index < globalFeatures.Length then - globalFeatures.[globalFeatures.Length - 1 - index] - else - 0uy - let localFeaturesByte = - if index < localFeatures.Length then - localFeatures.[localFeatures.Length - 1 - index] - else - 0uy - oredFeatures.[len - 1 - index] <- globalFeaturesByte ||| localFeaturesByte - oredFeatures - this.Features <- oredFeatures |> FeatureBits.CreateUnsafe - this.TLVStream <- ls.ReadTLVStream() |> Array.map(InitTLV.FromGenericTLV) - - member this.Serialize(ls) = - // For legacy compatiblity we treat the VariableLengthOnion feature specially. - // Older versions of the lightning protocol spec had a - // distinction between global and local features, of which - // VariableLengthOnion was the only global feature. Although - // this distinction has since been removed, older clients still - // expect VariableLengthOnion to appear in the global_features - // field of an init message and other features to appear only - // in the local_features field. This is still compatible with - // newer clients since those clients simply OR the two feature fields. - let localFeatures = - this.Features.ToByteArray() + + interface ISetupMsg + + interface ILightningSerializable with + member this.Deserialize(ls: LightningReaderStream) = + // For backwards compatibility reason, we must consider legacy + // `global features` section. (see bolt 1) + let globalFeatures = ls.ReadWithLen() + let localFeatures = ls.ReadWithLen() + + let oredFeatures = + let len = Math.Max(globalFeatures.Length, localFeatures.Length) + let oredFeatures = Array.zeroCreate len + + for index in 0 .. (len - 1) do + let globalFeaturesByte = + if index < globalFeatures.Length then + globalFeatures.[globalFeatures.Length - 1 - index] + else + 0uy + + let localFeaturesByte = + if index < localFeatures.Length then + localFeatures.[localFeatures.Length - 1 - index] + else + 0uy + + oredFeatures.[len - 1 - index] <- + globalFeaturesByte ||| localFeaturesByte + + oredFeatures + + this.Features <- oredFeatures |> FeatureBits.CreateUnsafe + + this.TLVStream <- + ls.ReadTLVStream() |> Array.map(InitTLV.FromGenericTLV) + + member this.Serialize ls = + // For legacy compatiblity we treat the VariableLengthOnion feature specially. + // Older versions of the lightning protocol spec had a + // distinction between global and local features, of which + // VariableLengthOnion was the only global feature. Although + // this distinction has since been removed, older clients still + // expect VariableLengthOnion to appear in the global_features + // field of an init message and other features to appear only + // in the local_features field. This is still compatible with + // newer clients since those clients simply OR the two feature fields. + let localFeatures = this.Features.ToByteArray() + + let globalFeatures = + let mandatory = + this.Features.HasFeature( + Feature.VariableLengthOnion, + FeaturesSupport.Mandatory + ) + + let optional = + this.Features.HasFeature( + Feature.VariableLengthOnion, + FeaturesSupport.Optional + ) + let globalFeatures = - let mandatory = - this.Features.HasFeature( - Feature.VariableLengthOnion, + let zero = FeatureBits.Zero + + if mandatory then + zero.SetFeature + Feature.VariableLengthOnion FeaturesSupport.Mandatory - ) - let optional = - this.Features.HasFeature( - Feature.VariableLengthOnion, + true + elif optional then + zero.SetFeature + Feature.VariableLengthOnion FeaturesSupport.Optional - ) - - let globalFeatures = - let zero = FeatureBits.Zero - if mandatory then - zero.SetFeature - Feature.VariableLengthOnion - FeaturesSupport.Mandatory - true - elif optional then - zero.SetFeature - Feature.VariableLengthOnion - FeaturesSupport.Optional - true - else - zero - globalFeatures.ToByteArray() - ls.WriteWithLen(globalFeatures) - ls.WriteWithLen(localFeatures) - ls.WriteTLVStream(this.TLVStream |> Array.map(fun tlv -> tlv.ToGenericTLV())) + true + else + zero + + globalFeatures.ToByteArray() + + ls.WriteWithLen(globalFeatures) + ls.WriteWithLen(localFeatures) + + ls.WriteTLVStream( + this.TLVStream |> Array.map(fun tlv -> tlv.ToGenericTLV()) + ) [] -type PingMsg = { - mutable PongLen: uint16 - mutable BytesLen: uint16 -} -with +type PingMsg = + { + mutable PongLen: uint16 + mutable BytesLen: uint16 + } + interface ISetupMsg + interface ILightningSerializable with - member this.Deserialize(ls) = + member this.Deserialize ls = this.PongLen <- ls.ReadUInt16(false) this.BytesLen <- ls.ReadUInt16(false) ls.ReadBytes(int32 this.BytesLen) |> ignore - member this.Serialize(ls) = + + member this.Serialize ls = ls.Write(this.PongLen, false) ls.Write(this.BytesLen, false) - ls.Write(Array.zeroCreate ((int)this.BytesLen)) + ls.Write(Array.zeroCreate((int) this.BytesLen)) [] -type PongMsg = { - mutable BytesLen: uint16 -} -with +type PongMsg = + { + mutable BytesLen: uint16 + } + interface ISetupMsg + interface ILightningSerializable with - member this.Deserialize(ls) = + member this.Deserialize ls = this.BytesLen <- ls.ReadUInt16(false) ls.ReadBytes(int32 this.BytesLen) |> ignore - member this.Serialize(ls) = + + member this.Serialize ls = ls.Write(this.BytesLen, false) - ls.Write(Array.zeroCreate ((int)this.BytesLen)) + ls.Write(Array.zeroCreate((int) this.BytesLen)) [] -type OpenChannelMsg = { - mutable Chainhash: uint256 - mutable TemporaryChannelId: ChannelId - mutable FundingSatoshis: Money - mutable PushMSat: LNMoney - mutable DustLimitSatoshis: Money - mutable MaxHTLCValueInFlightMsat: LNMoney - mutable ChannelReserveSatoshis: Money - mutable HTLCMinimumMsat: LNMoney - mutable FeeRatePerKw: FeeRatePerKw - mutable ToSelfDelay: BlockHeightOffset16 - mutable MaxAcceptedHTLCs: uint16 - mutable FundingPubKey: FundingPubKey - mutable RevocationBasepoint: RevocationBasepoint - mutable PaymentBasepoint: PaymentBasepoint - mutable DelayedPaymentBasepoint: DelayedPaymentBasepoint - mutable HTLCBasepoint: HtlcBasepoint - mutable FirstPerCommitmentPoint: PerCommitmentPoint - mutable ChannelFlags: ChannelFlags - mutable TLVs: array -} -with +type OpenChannelMsg = + { + mutable Chainhash: uint256 + mutable TemporaryChannelId: ChannelId + mutable FundingSatoshis: Money + mutable PushMSat: LNMoney + mutable DustLimitSatoshis: Money + mutable MaxHTLCValueInFlightMsat: LNMoney + mutable ChannelReserveSatoshis: Money + mutable HTLCMinimumMsat: LNMoney + mutable FeeRatePerKw: FeeRatePerKw + mutable ToSelfDelay: BlockHeightOffset16 + mutable MaxAcceptedHTLCs: uint16 + mutable FundingPubKey: FundingPubKey + mutable RevocationBasepoint: RevocationBasepoint + mutable PaymentBasepoint: PaymentBasepoint + mutable DelayedPaymentBasepoint: DelayedPaymentBasepoint + mutable HTLCBasepoint: HtlcBasepoint + mutable FirstPerCommitmentPoint: PerCommitmentPoint + mutable ChannelFlags: ChannelFlags + mutable TLVs: array + } + interface IChannelMsg + interface ILightningSerializable with - member this.Deserialize(ls) = + member this.Deserialize ls = this.Chainhash <- ls.ReadUInt256(true) this.TemporaryChannelId <- ChannelId(ls.ReadUInt256(true)) this.FundingSatoshis <- Money.Satoshis(ls.ReadUInt64(false)) this.PushMSat <- LNMoney.MilliSatoshis(ls.ReadUInt64(false)) this.DustLimitSatoshis <- Money.Satoshis(ls.ReadUInt64(false)) - this.MaxHTLCValueInFlightMsat <- LNMoney.MilliSatoshis(ls.ReadInt64(false)) + + this.MaxHTLCValueInFlightMsat <- + LNMoney.MilliSatoshis(ls.ReadInt64(false)) + this.ChannelReserveSatoshis <- Money.Satoshis(ls.ReadInt64(false)) this.HTLCMinimumMsat <- LNMoney.MilliSatoshis(ls.ReadInt64(false)) this.FeeRatePerKw <- FeeRatePerKw(ls.ReadUInt32(false)) @@ -531,11 +668,11 @@ with this.HTLCBasepoint <- ls.ReadHtlcBasepoint() this.FirstPerCommitmentPoint <- ls.ReadPerCommitmentPoint() this.ChannelFlags <- ls.ReadChannelFlags() + this.TLVs <- - ls.ReadTLVStream() - |> Array.map OpenChannelTLV.FromGenericTLV + ls.ReadTLVStream() |> Array.map OpenChannelTLV.FromGenericTLV - member this.Serialize(ls) = + member this.Serialize ls = ls.Write(this.Chainhash, true) ls.Write(this.TemporaryChannelId.Value, true) ls.Write(this.FundingSatoshis.Satoshi, false) @@ -554,45 +691,56 @@ with ls.Write(this.HTLCBasepoint.ToBytes()) ls.Write(this.FirstPerCommitmentPoint.ToBytes()) ls.Write(this.ChannelFlags.IntoUInt8()) - this.TLVs |> Array.map(fun tlv -> tlv.ToGenericTLV()) |> ls.WriteTLVStream + + this.TLVs + |> Array.map(fun tlv -> tlv.ToGenericTLV()) + |> ls.WriteTLVStream member this.ShutdownScriptPubKey() : Option = Seq.choose (function - | OpenChannelTLV.UpfrontShutdownScript script -> Some script - | _ -> None - ) + | OpenChannelTLV.UpfrontShutdownScript script -> Some script + | _ -> None) this.TLVs |> Seq.tryExactlyOne |> Option.flatten [] -type AcceptChannelMsg = { - mutable TemporaryChannelId: ChannelId - mutable DustLimitSatoshis: Money - mutable MaxHTLCValueInFlightMsat: LNMoney - mutable ChannelReserveSatoshis: Money - mutable HTLCMinimumMSat: LNMoney - mutable MinimumDepth: BlockHeightOffset32 - mutable ToSelfDelay: BlockHeightOffset16 - mutable MaxAcceptedHTLCs: uint16 - mutable FundingPubKey: FundingPubKey - mutable RevocationBasepoint: RevocationBasepoint - mutable PaymentBasepoint: PaymentBasepoint - mutable DelayedPaymentBasepoint: DelayedPaymentBasepoint - mutable HTLCBasepoint: HtlcBasepoint - mutable FirstPerCommitmentPoint: PerCommitmentPoint - mutable TLVs: array -} -with +type AcceptChannelMsg = + { + mutable TemporaryChannelId: ChannelId + mutable DustLimitSatoshis: Money + mutable MaxHTLCValueInFlightMsat: LNMoney + mutable ChannelReserveSatoshis: Money + mutable HTLCMinimumMSat: LNMoney + mutable MinimumDepth: BlockHeightOffset32 + mutable ToSelfDelay: BlockHeightOffset16 + mutable MaxAcceptedHTLCs: uint16 + mutable FundingPubKey: FundingPubKey + mutable RevocationBasepoint: RevocationBasepoint + mutable PaymentBasepoint: PaymentBasepoint + mutable DelayedPaymentBasepoint: DelayedPaymentBasepoint + mutable HTLCBasepoint: HtlcBasepoint + mutable FirstPerCommitmentPoint: PerCommitmentPoint + mutable TLVs: array + } + interface IChannelMsg + interface ILightningSerializable with - member this.Deserialize(ls) = + member this.Deserialize ls = this.TemporaryChannelId <- ChannelId(ls.ReadUInt256(true)) this.DustLimitSatoshis <- ls.ReadUInt64(false) |> Money.Satoshis - this.MaxHTLCValueInFlightMsat <- ls.ReadUInt64(false) |> LNMoney.MilliSatoshis - this.ChannelReserveSatoshis <- ls.ReadUInt64(false) |> Money.Satoshis - this.HTLCMinimumMSat <- ls.ReadUInt64(false) |> LNMoney.MilliSatoshis + + this.MaxHTLCValueInFlightMsat <- + ls.ReadUInt64(false) |> LNMoney.MilliSatoshis + + this.ChannelReserveSatoshis <- + ls.ReadUInt64(false) |> Money.Satoshis + + this.HTLCMinimumMSat <- + ls.ReadUInt64(false) |> LNMoney.MilliSatoshis + this.MinimumDepth <- ls.ReadUInt32(false) |> BlockHeightOffset32 this.ToSelfDelay <- ls.ReadUInt16(false) |> BlockHeightOffset16 this.MaxAcceptedHTLCs <- ls.ReadUInt16(false) @@ -602,10 +750,11 @@ with this.DelayedPaymentBasepoint <- ls.ReadDelayedPaymentBasepoint() this.HTLCBasepoint <- ls.ReadHtlcBasepoint() this.FirstPerCommitmentPoint <- ls.ReadPerCommitmentPoint() + this.TLVs <- - ls.ReadTLVStream() - |> Array.map AcceptChannelTLV.FromGenericTLV - member this.Serialize(ls) = + ls.ReadTLVStream() |> Array.map AcceptChannelTLV.FromGenericTLV + + member this.Serialize ls = ls.Write(this.TemporaryChannelId.Value.ToBytes()) ls.Write(this.DustLimitSatoshis.Satoshi, false) ls.Write(this.MaxHTLCValueInFlightMsat.MilliSatoshi, false) @@ -620,306 +769,376 @@ with ls.Write(this.DelayedPaymentBasepoint.ToBytes()) ls.Write(this.HTLCBasepoint.ToBytes()) ls.Write(this.FirstPerCommitmentPoint.ToBytes()) - this.TLVs |> Array.map(fun tlv -> tlv.ToGenericTLV()) |> ls.WriteTLVStream + + this.TLVs + |> Array.map(fun tlv -> tlv.ToGenericTLV()) + |> ls.WriteTLVStream member this.ShutdownScriptPubKey() : Option = Seq.choose (function - | AcceptChannelTLV.UpfrontShutdownScript script -> Some script - | _ -> None - ) + | AcceptChannelTLV.UpfrontShutdownScript script -> Some script + | _ -> None) this.TLVs |> Seq.tryExactlyOne |> Option.flatten [] -type FundingCreatedMsg = { - mutable TemporaryChannelId: ChannelId - mutable FundingTxId: TxId - mutable FundingOutputIndex: TxOutIndex - mutable Signature: LNECDSASignature -} -with +type FundingCreatedMsg = + { + mutable TemporaryChannelId: ChannelId + mutable FundingTxId: TxId + mutable FundingOutputIndex: TxOutIndex + mutable Signature: LNECDSASignature + } + interface IChannelMsg + interface ILightningSerializable with - member this.Deserialize(ls) = + member this.Deserialize ls = this.TemporaryChannelId <- ls.ReadUInt256(true) |> ChannelId this.FundingTxId <- ls.ReadUInt256(true) |> TxId this.FundingOutputIndex <- ls.ReadUInt16(false) |> TxOutIndex this.Signature <- ls.ReadECDSACompact() - member this.Serialize(ls) = + + member this.Serialize ls = ls.Write(this.TemporaryChannelId.Value.ToBytes()) ls.Write(this.FundingTxId.Value.ToBytes()) ls.Write(this.FundingOutputIndex.Value, false) ls.Write(this.Signature) [] -type FundingSignedMsg = { - mutable ChannelId: ChannelId - mutable Signature: LNECDSASignature -} -with +type FundingSignedMsg = + { + mutable ChannelId: ChannelId + mutable Signature: LNECDSASignature + } + interface IChannelMsg + interface ILightningSerializable with - member this.Deserialize(ls) = + member this.Deserialize ls = this.ChannelId <- ChannelId(ls.ReadUInt256(true)) this.Signature <- ls.ReadECDSACompact() - member this.Serialize(ls) = + + member this.Serialize ls = ls.Write(this.ChannelId.Value.ToBytes()) ls.Write(this.Signature) [] -type FundingLockedMsg = { - mutable ChannelId: ChannelId - mutable NextPerCommitmentPoint: PerCommitmentPoint -} -with +type FundingLockedMsg = + { + mutable ChannelId: ChannelId + mutable NextPerCommitmentPoint: PerCommitmentPoint + } + interface IChannelMsg + interface ILightningSerializable with - member this.Deserialize(ls) = + member this.Deserialize ls = this.ChannelId <- ls.ReadUInt256(true) |> ChannelId this.NextPerCommitmentPoint <- ls.ReadPerCommitmentPoint() - member this.Serialize(ls) = + + member this.Serialize ls = ls.Write(this.ChannelId.Value.ToBytes()) ls.Write(this.NextPerCommitmentPoint.ToBytes()) [] -type ShutdownMsg = { - mutable ChannelId: ChannelId - mutable ScriptPubKey: ShutdownScriptPubKey -} -with +type ShutdownMsg = + { + mutable ChannelId: ChannelId + mutable ScriptPubKey: ShutdownScriptPubKey + } + interface IChannelMsg + interface ILightningSerializable with - member this.Deserialize(ls) = + member this.Deserialize ls = this.ChannelId <- ls.ReadUInt256(true) |> ChannelId this.ScriptPubKey <- ls.ReadShutdownScriptPubKey() - member this.Serialize(ls) = + + member this.Serialize ls = ls.Write(this.ChannelId.Value.ToBytes()) ls.WriteWithLen(this.ScriptPubKey.ToBytes()) [] -type ClosingSignedMsg = { - mutable ChannelId: ChannelId - mutable FeeSatoshis: Money - mutable Signature: LNECDSASignature -} -with +type ClosingSignedMsg = + { + mutable ChannelId: ChannelId + mutable FeeSatoshis: Money + mutable Signature: LNECDSASignature + } + interface IChannelMsg + interface ILightningSerializable with - member this.Deserialize(ls) = + member this.Deserialize ls = this.ChannelId <- ls.ReadUInt256(true) |> ChannelId this.FeeSatoshis <- ls.ReadUInt64(false) |> Money.Satoshis this.Signature <- ls.ReadECDSACompact() - member this.Serialize(ls) = + + member this.Serialize ls = ls.Write(this.ChannelId.Value.ToBytes()) ls.Write(this.FeeSatoshis.Satoshi, false) ls.Write(this.Signature) -[] -type UpdateAddHTLCMsg = { - mutable ChannelId: ChannelId - mutable HTLCId: HTLCId - mutable Amount: LNMoney - mutable PaymentHash: PaymentHash - mutable CLTVExpiry: BlockHeight - mutable OnionRoutingPacket: OnionPacket -} -with +[] +type UpdateAddHTLCMsg = + { + mutable ChannelId: ChannelId + mutable HTLCId: HTLCId + mutable Amount: LNMoney + mutable PaymentHash: PaymentHash + mutable CLTVExpiry: BlockHeight + mutable OnionRoutingPacket: OnionPacket + } + interface IHTLCMsg interface IUpdateMsg + interface ILightningSerializable with - member this.Deserialize(ls) = + member this.Deserialize ls = this.ChannelId <- ls.ReadUInt256(true) |> ChannelId this.HTLCId <- ls.ReadUInt64(false) |> HTLCId this.Amount <- ls.ReadUInt64(false) |> LNMoney.MilliSatoshis this.PaymentHash <- ls.ReadUInt256(false) |> PaymentHash this.CLTVExpiry <- ls.ReadUInt32(false) |> BlockHeight - this.OnionRoutingPacket <- ILightningSerializable.deserialize(ls) - member this.Serialize(ls) = + + this.OnionRoutingPacket <- + ILightningSerializable.deserialize(ls) + + member this.Serialize ls = ls.Write(this.ChannelId.Value.ToBytes()) ls.Write(this.HTLCId.Value, false) ls.Write(this.Amount.MilliSatoshi, false) ls.Write(this.PaymentHash.ToBytes()) ls.Write(this.CLTVExpiry.Value, false) - (this.OnionRoutingPacket :> ILightningSerializable).Serialize(ls) + + (this.OnionRoutingPacket :> ILightningSerializable) + .Serialize(ls) [] -type UpdateFulfillHTLCMsg = { - mutable ChannelId: ChannelId - mutable HTLCId: HTLCId - mutable PaymentPreimage: PaymentPreimage -} -with +type UpdateFulfillHTLCMsg = + { + mutable ChannelId: ChannelId + mutable HTLCId: HTLCId + mutable PaymentPreimage: PaymentPreimage + } + interface IHTLCMsg interface IUpdateMsg + interface ILightningSerializable with - member this.Deserialize(ls) = + member this.Deserialize ls = this.ChannelId <- ls.ReadUInt256(true) |> ChannelId this.HTLCId <- ls.ReadUInt64(false) |> HTLCId - this.PaymentPreimage <- ls.ReadBytes PaymentPreimage.LENGTH |> PaymentPreimage.Create - member this.Serialize(ls) = + + this.PaymentPreimage <- + ls.ReadBytes PaymentPreimage.LENGTH |> PaymentPreimage.Create + + member this.Serialize ls = ls.Write(this.ChannelId.Value.ToBytes()) ls.Write(this.HTLCId.Value, false) ls.Write(this.PaymentPreimage.ToByteArray()) [] -type UpdateFailHTLCMsg = { - mutable ChannelId: ChannelId - mutable HTLCId: HTLCId - mutable Reason: OnionErrorPacket -} -with +type UpdateFailHTLCMsg = + { + mutable ChannelId: ChannelId + mutable HTLCId: HTLCId + mutable Reason: OnionErrorPacket + } + interface IHTLCMsg interface IUpdateMsg + interface ILightningSerializable with - member this.Deserialize(ls) = + member this.Deserialize ls = this.ChannelId <- ls.ReadUInt256(true) |> ChannelId this.HTLCId <- ls.ReadUInt64(false) |> HTLCId - this.Reason <- { Data = ls.ReadWithLen() } - member this.Serialize(ls) = + + this.Reason <- + { + Data = ls.ReadWithLen() + } + + member this.Serialize ls = ls.Write(this.ChannelId.Value.ToBytes()) ls.Write(this.HTLCId.Value, false) ls.WriteWithLen(this.Reason.Data) [] -type UpdateFailMalformedHTLCMsg = { - mutable ChannelId: ChannelId - mutable HTLCId: HTLCId - mutable Sha256OfOnion: uint256 - mutable FailureCode: FailureCode -} -with +type UpdateFailMalformedHTLCMsg = + { + mutable ChannelId: ChannelId + mutable HTLCId: HTLCId + mutable Sha256OfOnion: uint256 + mutable FailureCode: FailureCode + } + interface IHTLCMsg interface IUpdateMsg + interface ILightningSerializable with - member this.Deserialize(ls) = + member this.Deserialize ls = this.ChannelId <- ls.ReadUInt256(true) |> ChannelId this.HTLCId <- ls.ReadUInt64(false) |> HTLCId this.Sha256OfOnion <- ls.ReadUInt256(true) this.FailureCode <- ls.ReadUInt16(false) |> OnionError.FailureCode - member this.Serialize(ls) = + + member this.Serialize ls = ls.Write(this.ChannelId.Value.ToBytes()) ls.Write(this.HTLCId.Value, false) ls.Write(this.Sha256OfOnion, true) ls.Write(this.FailureCode.Value, false) [] -type CommitmentSignedMsg = { - mutable ChannelId: ChannelId - mutable Signature: LNECDSASignature - mutable HTLCSignatures: LNECDSASignature list -} -with +type CommitmentSignedMsg = + { + mutable ChannelId: ChannelId + mutable Signature: LNECDSASignature + mutable HTLCSignatures: list + } + interface IHTLCMsg + interface ILightningSerializable with - member this.Deserialize(ls) = + member this.Deserialize ls = this.ChannelId <- ls.ReadUInt256(true) |> ChannelId this.Signature <- ls.ReadECDSACompact() - this.HTLCSignatures <- + + this.HTLCSignatures <- let len = ls.ReadUInt16(false) - [ 1us..len ] |> List.map(fun _ -> ls.ReadECDSACompact()) - member this.Serialize(ls) = + [ 1us .. len ] |> List.map(fun _ -> ls.ReadECDSACompact()) + + member this.Serialize ls = ls.Write(this.ChannelId.Value.ToBytes()) ls.Write(this.Signature) - ls.Write((uint16)this.HTLCSignatures.Length, false) - this.HTLCSignatures |> List.iter (ls.Write) + ls.Write((uint16) this.HTLCSignatures.Length, false) + this.HTLCSignatures |> List.iter(ls.Write) [] -type RevokeAndACKMsg = { - mutable ChannelId: ChannelId - mutable PerCommitmentSecret: PerCommitmentSecret - mutable NextPerCommitmentPoint: PerCommitmentPoint -} -with +type RevokeAndACKMsg = + { + mutable ChannelId: ChannelId + mutable PerCommitmentSecret: PerCommitmentSecret + mutable NextPerCommitmentPoint: PerCommitmentPoint + } + interface IHTLCMsg + interface ILightningSerializable with - member this.Deserialize(ls) = + member this.Deserialize ls = this.ChannelId <- ls.ReadUInt256(true) |> ChannelId this.PerCommitmentSecret <- ls.ReadPerCommitmentSecret() this.NextPerCommitmentPoint <- ls.ReadPerCommitmentPoint() - member this.Serialize(ls) = + + member this.Serialize ls = ls.Write(this.ChannelId.Value.ToBytes()) ls.Write(this.PerCommitmentSecret.ToBytes()) ls.Write(this.NextPerCommitmentPoint.ToBytes()) [] -type UpdateFeeMsg = { - mutable ChannelId: ChannelId - mutable FeeRatePerKw: FeeRatePerKw -} -with +type UpdateFeeMsg = + { + mutable ChannelId: ChannelId + mutable FeeRatePerKw: FeeRatePerKw + } + interface IChannelMsg interface IUpdateMsg + interface ILightningSerializable with - member this.Deserialize(ls) = + member this.Deserialize ls = this.ChannelId <- ls.ReadUInt256(true) |> ChannelId this.FeeRatePerKw <- ls.ReadUInt32(false) |> FeeRatePerKw - member this.Serialize(ls) = + + member this.Serialize ls = ls.Write(this.ChannelId.Value.ToBytes()) ls.Write(this.FeeRatePerKw.Value, false) [] -type DataLossProtect = { - mutable YourLastPerCommitmentSecret: Option - mutable MyCurrentPerCommitmentPoint: PerCommitmentPoint -} - with - interface ILightningSerializable with - member this.Deserialize(ls: LightningReaderStream) = - this.YourLastPerCommitmentSecret <- - let bytes = ls.ReadBytes PerCommitmentSecret.BytesLength - if bytes.All(fun b -> b = 0uy) then - None - else - Some <| PerCommitmentSecret.FromBytes bytes - this.MyCurrentPerCommitmentPoint <- ls.ReadPerCommitmentPoint() - member this.Serialize(ls: LightningWriterStream): unit = - match this.YourLastPerCommitmentSecret with - | Some perCommitmentSecret -> ls.Write(perCommitmentSecret.ToBytes()) - | None -> ls.Write(Array.zeroCreate PerCommitmentSecret.BytesLength) - ls.Write(this.MyCurrentPerCommitmentPoint.ToBytes()) +type DataLossProtect = + { + mutable YourLastPerCommitmentSecret: Option + mutable MyCurrentPerCommitmentPoint: PerCommitmentPoint + } + + interface ILightningSerializable with + member this.Deserialize(ls: LightningReaderStream) = + this.YourLastPerCommitmentSecret <- + let bytes = ls.ReadBytes PerCommitmentSecret.BytesLength + + if bytes.All(fun b -> b = 0uy) then + None + else + Some <| PerCommitmentSecret.FromBytes bytes + + this.MyCurrentPerCommitmentPoint <- ls.ReadPerCommitmentPoint() + + member this.Serialize(ls: LightningWriterStream) : unit = + match this.YourLastPerCommitmentSecret with + | Some perCommitmentSecret -> + ls.Write(perCommitmentSecret.ToBytes()) + | None -> ls.Write(Array.zeroCreate PerCommitmentSecret.BytesLength) + + ls.Write(this.MyCurrentPerCommitmentPoint.ToBytes()) [] -type ChannelReestablishMsg = { - mutable ChannelId: ChannelId - mutable NextCommitmentNumber: CommitmentNumber - mutable NextRevocationNumber: CommitmentNumber - mutable DataLossProtect: OptionalField -} -with +type ChannelReestablishMsg = + { + mutable ChannelId: ChannelId + mutable NextCommitmentNumber: CommitmentNumber + mutable NextRevocationNumber: CommitmentNumber + mutable DataLossProtect: OptionalField + } + interface IChannelMsg + interface ILightningSerializable with - member this.Deserialize(ls) = + member this.Deserialize ls = this.ChannelId <- ls.ReadUInt256(true) |> ChannelId this.NextCommitmentNumber <- ls.ReadCommitmentNumber() this.NextRevocationNumber <- ls.ReadCommitmentNumber() - this.DataLossProtect <- ls.TryReadAll() |> Option.map ILightningSerializable.fromBytes - member this.Serialize(ls) = + + this.DataLossProtect <- + ls.TryReadAll() + |> Option.map ILightningSerializable.fromBytes + + member this.Serialize ls = ls.Write(this.ChannelId.Value.ToBytes()) ls.Write this.NextCommitmentNumber ls.Write this.NextRevocationNumber + match this.DataLossProtect with | None -> () - | Some dataLossProtect -> (dataLossProtect :> ILightningSerializable).Serialize ls + | Some dataLossProtect -> + (dataLossProtect :> ILightningSerializable) + .Serialize ls [] -type AnnouncementSignaturesMsg = { - mutable ChannelId: ChannelId - mutable ShortChannelId: ShortChannelId - mutable NodeSignature: LNECDSASignature - mutable BitcoinSignature: LNECDSASignature -} -with +type AnnouncementSignaturesMsg = + { + mutable ChannelId: ChannelId + mutable ShortChannelId: ShortChannelId + mutable NodeSignature: LNECDSASignature + mutable BitcoinSignature: LNECDSASignature + } + interface IRoutingMsg + interface ILightningSerializable with - member this.Deserialize(ls) = + member this.Deserialize ls = this.ChannelId <- ls.ReadUInt256(true) |> ChannelId - this.ShortChannelId <- ls.ReadUInt64(false) |> ShortChannelId.FromUInt64 + + this.ShortChannelId <- + ls.ReadUInt64(false) |> ShortChannelId.FromUInt64 + this.NodeSignature <- ls.ReadECDSACompact() this.BitcoinSignature <- ls.ReadECDSACompact() - member this.Serialize(ls) = + + member this.Serialize ls = ls.Write(this.ChannelId.Value.ToBytes()) ls.Write(this.ShortChannelId) ls.Write(this.NodeSignature) @@ -938,15 +1157,16 @@ type NetAddress = | OnionV2 _ -> 3uy | OnionV3 _ -> 4uy - member this.Length with get() = - match this with - | IPv4 _ -> 6us - | IPv6 _ -> 18us - | OnionV2 _ -> 12us - | OnionV3 _ -> 37us + member this.Length = + match this with + | IPv4 _ -> 6us + | IPv6 _ -> 18us + | OnionV2 _ -> 12us + | OnionV3 _ -> 37us member this.WriteTo(ls: LightningWriterStream) = ls.Write(this.GetId()) + match this with | IPv4 d -> ls.Write(d.Addr) @@ -963,55 +1183,80 @@ type NetAddress = ls.Write(d.Version) ls.Write(d.Port, false) - static member ReadFrom(ls: LightningReaderStream): NetAdddrSerilizationResult = + static member ReadFrom + (ls: LightningReaderStream) + : NetAdddrSerilizationResult = let id = ls.ReadUInt8() + match id with | 1uy -> let addr = ls.ReadBytes(4) let port = ls.ReadUInt16(false) - IPv4 { Addr = addr; Port = port } + + IPv4 + { + Addr = addr + Port = port + } |> Ok | 2uy -> let addr = ls.ReadBytes(16) let port = ls.ReadUInt16((false)) - IPv6 { Addr = addr; Port = port } + + IPv6 + { + Addr = addr + Port = port + } |> Ok | 3uy -> let addr = ls.ReadBytes(10) let port = ls.ReadUInt16(false) - OnionV2 { Addr = addr; Port = port } + + OnionV2 + { + Addr = addr + Port = port + } |> Ok | 4uy -> let ed25519PK = ls.ReadBytes(32) let checkSum = ls.ReadUInt16(false) let v = ls.ReadUInt8() let port = ls.ReadUInt16(false) - OnionV3 { - OnionV3EndPoint.ed25519PubKey = ed25519PK - CheckSum = checkSum - Version = v - Port = port - } + + OnionV3 + { + OnionV3EndPoint.ed25519PubKey = ed25519PK + CheckSum = checkSum + Version = v + Port = port + } |> Ok - | unknown -> - Result.Error (unknown) -and IPv4Or6Data = { - /// 4 byte in case of IPv4. 16 byes in case of IPv6 - Addr: byte[] - Port: uint16 -} - -and OnionV2EndPoint = { - /// 10 bytes - Addr: byte[] - Port: uint16 -} -and OnionV3EndPoint = { - ed25519PubKey: byte[] - CheckSum: uint16 - Version: uint8 - Port: uint16 -} + | unknown -> Result.Error(unknown) + +and IPv4Or6Data = + { + /// 4 byte in case of IPv4. 16 byes in case of IPv6 + Addr: array + Port: uint16 + } + +and OnionV2EndPoint = + { + /// 10 bytes + Addr: array + Port: uint16 + } + +and OnionV3EndPoint = + { + ed25519PubKey: array + CheckSum: uint16 + Version: uint8 + Port: uint16 + } + and NetAdddrSerilizationResult = Result and UnknownNetAddr = byte @@ -1019,124 +1264,174 @@ and UnknownNetAddr = byte /// Only exposed as broadcast of node_announcement should be filtered by node_id /// The unsigned part of node_anouncement [] -type UnsignedNodeAnnouncementMsg = { - mutable Features: FeatureBits - mutable Timestamp: uint32 - mutable NodeId: NodeId - mutable RGB: RGB - mutable Alias: uint256 - mutable Addresses: NetAddress [] - mutable ExcessAddressData: byte[] - mutable ExcessData: byte[] -} -with +type UnsignedNodeAnnouncementMsg = + { + mutable Features: FeatureBits + mutable Timestamp: uint32 + mutable NodeId: NodeId + mutable RGB: RGB + mutable Alias: uint256 + mutable Addresses: array + mutable ExcessAddressData: array + mutable ExcessData: array + } + interface ILightningSerializable with - member this.Deserialize(ls) = + member this.Deserialize ls = this.Features <- ls.ReadWithLen() |> FeatureBits.CreateUnsafe this.Timestamp <- ls.ReadUInt32(false) this.NodeId <- ls.ReadPubKey() |> NodeId this.RGB <- ls.ReadRGB() this.Alias <- ls.ReadUInt256(true) let addrLen = ls.ReadUInt16(false) - let mutable addresses: NetAddress list = [] + let mutable addresses: list = [] let mutable addr_readPos = 0us let mutable foundUnknown = false let mutable excessAddressDataByte = 0uy + this.Addresses <- while addr_readPos < addrLen && (not foundUnknown) do let addr = NetAddress.ReadFrom ls - ignore <| match addr with - | Ok (IPv4 _) -> - if addresses.Length > 0 then - raise <| FormatException(sprintf "Extra Address per type %A" addresses) - | Ok (IPv6 _) -> - if addresses.Length > 1 || (addresses.Length = 1 && addresses.[0].GetId() <> 1uy) then - raise <| FormatException(sprintf "Extra Address per type %A" addresses) - | Ok(OnionV2 _) -> - if addresses.Length > 2 || (addresses.Length > 0 && addresses.[0].GetId() > 2uy) then - raise <| FormatException(sprintf "Extra Address per type %A" addresses) - | Ok(OnionV3 _) -> - if addresses.Length > 3 || (addresses.Length > 0 && addresses.[0].GetId() > 3uy) then - raise <| FormatException(sprintf "Extra Address per type %A" addresses) - | Result.Error v -> - excessAddressDataByte <- v - foundUnknown <- true - addr_readPos <- addr_readPos + 1us + + ignore + <| match addr with + | Ok(IPv4 _) -> + if addresses.Length > 0 then + raise + <| FormatException( + sprintf "Extra Address per type %A" addresses + ) + | Ok(IPv6 _) -> + if addresses.Length > 1 + || (addresses.Length = 1 + && addresses.[0].GetId() <> 1uy) then + raise + <| FormatException( + sprintf "Extra Address per type %A" addresses + ) + | Ok(OnionV2 _) -> + if addresses.Length > 2 + || (addresses.Length > 0 + && addresses.[0].GetId() > 2uy) then + raise + <| FormatException( + sprintf "Extra Address per type %A" addresses + ) + | Ok(OnionV3 _) -> + if addresses.Length > 3 + || (addresses.Length > 0 + && addresses.[0].GetId() > 3uy) then + raise + <| FormatException( + sprintf "Extra Address per type %A" addresses + ) + | Result.Error v -> + excessAddressDataByte <- v + foundUnknown <- true + addr_readPos <- addr_readPos + 1us + if (not foundUnknown) then match addr with | Ok addr -> addr_readPos <- addr_readPos + (1us + addr.Length) addresses <- addr :: addresses | Result.Error _ -> failwith "Unreachable" + addresses |> List.rev |> Array.ofList + this.ExcessAddressData <- if addr_readPos < addrLen then if foundUnknown then - Array.append [|excessAddressDataByte|] (ls.ReadBytes(int (addrLen - addr_readPos))) + Array.append + [| excessAddressDataByte |] + (ls.ReadBytes(int(addrLen - addr_readPos))) else - (ls.ReadBytes(int (addrLen - addr_readPos))) + (ls.ReadBytes(int(addrLen - addr_readPos))) + else if foundUnknown then + [| excessAddressDataByte |] else - if foundUnknown then - [|excessAddressDataByte|] - else - [||] + [||] + + this.ExcessData <- + match ls.TryReadAll() with + | Some b -> b + | None -> [||] - this.ExcessData <- match ls.TryReadAll() with Some b -> b | None -> [||] - member this.Serialize(ls) = + member this.Serialize ls = ls.WriteWithLen(this.Features.ToByteArray()) ls.Write(this.Timestamp, false) ls.Write(this.NodeId.Value) ls.Write(this.RGB) ls.Write(this.Alias, true) - let mutable addrLen:uint16 = (this.Addresses |> Array.sumBy(fun addr -> addr.Length + 1us)) // 1 byte for type field + + let mutable addrLen: uint16 = + (this.Addresses |> Array.sumBy(fun addr -> addr.Length + 1us)) // 1 byte for type field + let excessAddrLen = (uint16 this.ExcessAddressData.Length) addrLen <- excessAddrLen + addrLen ls.Write(addrLen, false) - this.Addresses - |> Array.iter(fun addr -> addr.WriteTo(ls)) + this.Addresses |> Array.iter(fun addr -> addr.WriteTo(ls)) ls.Write(this.ExcessAddressData) ls.Write(this.ExcessData) [] -type NodeAnnouncementMsg = { - mutable Signature: LNECDSASignature - mutable Contents: UnsignedNodeAnnouncementMsg -} -with +type NodeAnnouncementMsg = + { + mutable Signature: LNECDSASignature + mutable Contents: UnsignedNodeAnnouncementMsg + } + interface IRoutingMsg + interface ILightningSerializable with - member this.Deserialize(ls) = + member this.Deserialize ls = this.Signature <- ls.ReadECDSACompact() - this.Contents <- ILightningSerializable.deserialize(ls) - member this.Serialize(ls) = + + this.Contents <- + ILightningSerializable.deserialize( + ls + ) + + member this.Serialize ls = ls.Write(this.Signature) - (this.Contents :> ILightningSerializable).Serialize(ls) - - -[] -type UnsignedChannelAnnouncementMsg = { - mutable Features: FeatureBits - mutable ChainHash: uint256 - mutable ShortChannelId: ShortChannelId - mutable NodeId1: NodeId - mutable NodeId2: NodeId - mutable BitcoinKey1: ComparablePubKey - mutable BitcoinKey2: ComparablePubKey - mutable ExcessData: byte[] -} -with + + (this.Contents + :> ILightningSerializable) + .Serialize(ls) + + +[] +type UnsignedChannelAnnouncementMsg = + { + mutable Features: FeatureBits + mutable ChainHash: uint256 + mutable ShortChannelId: ShortChannelId + mutable NodeId1: NodeId + mutable NodeId2: NodeId + mutable BitcoinKey1: ComparablePubKey + mutable BitcoinKey2: ComparablePubKey + mutable ExcessData: array + } + interface ILightningSerializable with - member this.Deserialize(ls) = - this.Features <- - ls.ReadWithLen() |> FeatureBits.CreateUnsafe + member this.Deserialize ls = + this.Features <- ls.ReadWithLen() |> FeatureBits.CreateUnsafe this.ChainHash <- ls.ReadUInt256(true) - this.ShortChannelId <- ls.ReadUInt64(false) |> ShortChannelId.FromUInt64 + + this.ShortChannelId <- + ls.ReadUInt64(false) |> ShortChannelId.FromUInt64 + this.NodeId1 <- ls.ReadPubKey() |> NodeId this.NodeId2 <- ls.ReadPubKey() |> NodeId this.BitcoinKey1 <- ls.ReadPubKey() |> ComparablePubKey this.BitcoinKey2 <- ls.ReadPubKey() |> ComparablePubKey - this.ExcessData <- match ls.TryReadAll() with Some b -> b | None -> [||] - member this.Serialize(ls) = + + this.ExcessData <- + match ls.TryReadAll() with + | Some b -> b + | None -> [||] + + member this.Serialize ls = ls.WriteWithLen(this.Features.ToByteArray()) ls.Write(this.ChainHash, true) ls.Write(this.ShortChannelId) @@ -1147,61 +1442,83 @@ with ls.Write(this.ExcessData) [] -type ChannelAnnouncementMsg = { - mutable NodeSignature1: LNECDSASignature - mutable NodeSignature2: LNECDSASignature - mutable BitcoinSignature1: LNECDSASignature - mutable BitcoinSignature2: LNECDSASignature - mutable Contents: UnsignedChannelAnnouncementMsg -} -with +type ChannelAnnouncementMsg = + { + mutable NodeSignature1: LNECDSASignature + mutable NodeSignature2: LNECDSASignature + mutable BitcoinSignature1: LNECDSASignature + mutable BitcoinSignature2: LNECDSASignature + mutable Contents: UnsignedChannelAnnouncementMsg + } + interface IRoutingMsg + interface ILightningSerializable with - member this.Deserialize(ls) = + member this.Deserialize ls = this.NodeSignature1 <- ls.ReadECDSACompact() this.NodeSignature2 <- ls.ReadECDSACompact() this.BitcoinSignature1 <- ls.ReadECDSACompact() this.BitcoinSignature2 <- ls.ReadECDSACompact() - this.Contents <- ILightningSerializable.deserialize(ls) - member this.Serialize(ls) = + + this.Contents <- + ILightningSerializable.deserialize( + ls + ) + + member this.Serialize ls = ls.Write(this.NodeSignature1) ls.Write(this.NodeSignature2) ls.Write(this.BitcoinSignature1) ls.Write(this.BitcoinSignature2) - (this.Contents :> ILightningSerializable).Serialize(ls) + + (this.Contents + :> ILightningSerializable) + .Serialize(ls) [] -type UnsignedChannelUpdateMsg = { - mutable ChainHash: uint256 - mutable ShortChannelId: ShortChannelId - mutable Timestamp: uint32 - mutable MessageFlags: uint8 - mutable ChannelFlags: uint8 - mutable CLTVExpiryDelta: BlockHeightOffset16 - mutable HTLCMinimumMSat: LNMoney - mutable FeeBaseMSat: LNMoney - mutable FeeProportionalMillionths: uint32 - mutable HTLCMaximumMSat: OptionalField -} - with +type UnsignedChannelUpdateMsg = + { + mutable ChainHash: uint256 + mutable ShortChannelId: ShortChannelId + mutable Timestamp: uint32 + mutable MessageFlags: uint8 + mutable ChannelFlags: uint8 + mutable CLTVExpiryDelta: BlockHeightOffset16 + mutable HTLCMinimumMSat: LNMoney + mutable FeeBaseMSat: LNMoney + mutable FeeProportionalMillionths: uint32 + mutable HTLCMaximumMSat: OptionalField + } + interface IRoutingMsg - interface ILightningSerializable with - member this.Deserialize(ls: LightningReaderStream): unit = + + interface ILightningSerializable with + member this.Deserialize(ls: LightningReaderStream) : unit = this.ChainHash <- ls.ReadUInt256(true) - this.ShortChannelId <- ls.ReadUInt64(false) |> ShortChannelId.FromUInt64 + + this.ShortChannelId <- + ls.ReadUInt64(false) |> ShortChannelId.FromUInt64 + this.Timestamp <- ls.ReadUInt32(false) this.MessageFlags <- ls.ReadByte() this.ChannelFlags <- ls.ReadByte() this.CLTVExpiryDelta <- ls.ReadUInt16(false) |> BlockHeightOffset16 - this.HTLCMinimumMSat <- ls.ReadUInt64(false) |> LNMoney.MilliSatoshis - this.FeeBaseMSat <- ls.ReadUInt32(false) |> uint64 |> LNMoney.MilliSatoshis + + this.HTLCMinimumMSat <- + ls.ReadUInt64(false) |> LNMoney.MilliSatoshis + + this.FeeBaseMSat <- + ls.ReadUInt32(false) |> uint64 |> LNMoney.MilliSatoshis + this.FeeProportionalMillionths <- ls.ReadUInt32(false) + this.HTLCMaximumMSat <- if ((this.MessageFlags &&& 0b00000001uy) = 1uy) then ls.ReadUInt64(false) |> LNMoney.MilliSatoshis |> Some else None - member this.Serialize(ls: LightningWriterStream): unit = + + member this.Serialize(ls: LightningWriterStream) : unit = ls.Write(this.ChainHash, true) ls.Write(this.ShortChannelId) ls.Write(this.Timestamp, false) @@ -1211,27 +1528,34 @@ type UnsignedChannelUpdateMsg = { ls.Write(this.HTLCMinimumMSat.MilliSatoshi, false) ls.Write(uint32 this.FeeBaseMSat.MilliSatoshi, false) ls.Write(uint32 this.FeeProportionalMillionths, false) + match this.HTLCMaximumMSat with | Some s -> ls.Write(s.MilliSatoshi, false) | None -> () [] -type ChannelUpdateMsg = { - mutable Signature: LNECDSASignature - mutable Contents: UnsignedChannelUpdateMsg -} -with - member this.IsNode1 = - (this.Contents.ChannelFlags &&& 1uy) = 0uy +type ChannelUpdateMsg = + { + mutable Signature: LNECDSASignature + mutable Contents: UnsignedChannelUpdateMsg + } + + member this.IsNode1 = (this.Contents.ChannelFlags &&& 1uy) = 0uy interface IRoutingMsg + interface ILightningSerializable with - member this.Deserialize(ls) = + member this.Deserialize ls = this.Signature <- ls.ReadECDSACompact() - this.Contents <- ILightningSerializable.deserialize(ls) - member this.Serialize(ls) = + + this.Contents <- + ILightningSerializable.deserialize(ls) + + member this.Serialize ls = ls.Write(this.Signature) - (this.Contents :> ILightningSerializable).Serialize(ls) + + (this.Contents :> ILightningSerializable) + .Serialize(ls) type FailureMsgData = | InvalidRealm @@ -1248,7 +1572,7 @@ type FailureMsgData = | AmountBelowMinimum of amount: LNMoney * update: ChannelUpdateMsg | FeeInsufficient of amount: LNMoney * update: ChannelUpdateMsg | ChannelDisabled of Flags: uint16 * update: ChannelUpdateMsg - | IncorrectCLTVExpiry of expiry: BlockHeight * update: ChannelUpdateMsg + | IncorrectCLTVExpiry of expiry: BlockHeight * update: ChannelUpdateMsg | UnknownPaymentHash | IncorrectPaymentAmount | ExpiryTooSoon of update: ChannelUpdateMsg @@ -1256,110 +1580,118 @@ type FailureMsgData = | FinalIncorrectCLTVExpiry of expiry: BlockHeight | FinalIncorrectCLTVAmount of amountMSat: LNMoney | ExpiryTooFar - | Unknown of byte[] + | Unknown of array [] -type FailureMsg = { - mutable Data: FailureMsgData - mutable Code: FailureCode -} - with - interface ILightningSerializable with - member this.Deserialize(r: LightningReaderStream): unit = - let t = r.ReadUInt16(false) - this.Code <- t |> FailureCode - match t with - | (INVALID_REALM) -> - this.Data <- InvalidRealm - | (TEMPORARY_NODE_FAILURE) -> this.Data <- TemporaryNodeFailure - | (PERMANENT_NODE_FAILURE) -> this.Data <- PermanentNodeFailure - | (REQUIRED_NODE_FEATURE_MISSING) -> this.Data <- RequiredNodeFeatureMissing - | (INVALID_ONION_VERSION) -> - let v = r.ReadUInt256(true) - this.Data <- InvalidOnionVersion(v) - | (INVALID_ONION_HMAC) -> - this.Data <- r.ReadUInt256(true) |> InvalidOnionHmac - | (INVALID_ONION_KEY) -> - this.Data <- r.ReadUInt256(true) |> InvalidOnionKey - | (TEMPORARY_CHANNEL_FAILURE) -> - let d = ILightningSerializable.deserialize(r) - this.Data <- d |> TemporaryChannelFailure - | (PERMANENT_CHANNEL_FAILURE) -> - this.Data <- PermanentChannelFailure - | (REQUIRED_CHANNEL_FEATURE_MISSING) -> - this.Data <- RequiredChannelFeatureMissing - | (UNKNOWN_NEXT_PEER) -> - this.Data <- UnknownNextPeer - | (AMOUNT_BELOW_MINIMUM) -> - let amountMSat = r.ReadUInt64(false) |> LNMoney.MilliSatoshis - let d = ILightningSerializable.init() - (d :> ILightningSerializable).Deserialize(r) - this.Data <- (amountMSat, d) |> AmountBelowMinimum - | (FEE_INSUFFICIENT) -> - let amountMSat = r.ReadUInt64(false) |> LNMoney.MilliSatoshis - let d = ILightningSerializable.init() - (d :> ILightningSerializable).Deserialize(r) - this.Data <- (amountMSat, d) |> FeeInsufficient - | (CHANNEL_DISABLED) -> - let flags = r.ReadUInt16(false) - let d = ILightningSerializable.deserialize(r) - this.Data <- (flags, d ) |> ChannelDisabled - | (INOCCORRECT_CLTV_EXPIRY) -> - let expiry = r.ReadUInt32(false) |> BlockHeight - let d = ILightningSerializable.deserialize(r) - this.Data <- (expiry, d) |> IncorrectCLTVExpiry - | (UNKNOWN_PAYMENT_HASH) -> - this.Data <- UnknownPaymentHash - | (INCORRECT_PAYMENT_AMOUNT) -> - this.Data <- IncorrectPaymentAmount - | (EXPIRY_TOO_SOON) -> - let d = ILightningSerializable.deserialize(r) - this.Data <- d |> ExpiryTooSoon - | FINAL_EXPIRY_TOO_SOON -> - this.Data <- FinalExpiryTooSoon - | (FINAL_INCORRECT_CLTV_EXPIRY) -> - let expiry = r.ReadUInt32(false) |> BlockHeight - this.Data <- expiry |> FinalIncorrectCLTVExpiry - | (FINAL_INCORRECT_HTLC_AMOUNT) -> - let expiry = r.ReadUInt64(false) |> LNMoney.MilliSatoshis - this.Data <- expiry |> FinalIncorrectCLTVAmount - | (EXPIRY_TOO_FAR) -> - this.Data <- ExpiryTooFar - | _ -> - this.Data <- r.ReadAll() |> Unknown - member this.Serialize(w: LightningWriterStream): unit = - w.Write(this.Code.Value, false) - match this.Data with - | InvalidOnionVersion onionHash -> - w.Write(onionHash, false) - | InvalidOnionHmac onionHash -> - w.Write(onionHash, false) - | InvalidOnionKey onionHash -> - w.Write(onionHash, false) - | TemporaryChannelFailure update -> - (update :> ILightningSerializable).SerializeWithLen(w) - | AmountBelowMinimum (amount, update) -> - w.Write(uint64 amount.Value, false) - (update :> ILightningSerializable).SerializeWithLen(w) - | FeeInsufficient (amount, update) -> - w.Write(uint64 amount.Value, false) - (update :> ILightningSerializable).SerializeWithLen(w) - | ChannelDisabled (flags, update) -> - w.Write(flags, false) - (update :> ILightningSerializable).SerializeWithLen(w) - | IncorrectCLTVExpiry (expiry, update) -> - w.Write(expiry.Value, false) - (update :> ILightningSerializable).SerializeWithLen(w) - | ExpiryTooSoon (update) -> - (update :> ILightningSerializable).SerializeWithLen(w) - | FinalIncorrectCLTVExpiry (expiry) -> - w.Write(expiry.Value, false) - | FinalIncorrectCLTVAmount (amountMSat) -> - w.Write(amountMSat.Value, false) - | FailureMsgData.Unknown b -> - w.Write(b) - | _ -> - () +type FailureMsg = + { + mutable Data: FailureMsgData + mutable Code: FailureCode + } + + interface ILightningSerializable with + member this.Deserialize(r: LightningReaderStream) : unit = + let t = r.ReadUInt16(false) + this.Code <- t |> FailureCode + + match t with + | (INVALID_REALM) -> this.Data <- InvalidRealm + | (TEMPORARY_NODE_FAILURE) -> this.Data <- TemporaryNodeFailure + | (PERMANENT_NODE_FAILURE) -> this.Data <- PermanentNodeFailure + | (REQUIRED_NODE_FEATURE_MISSING) -> + this.Data <- RequiredNodeFeatureMissing + | (INVALID_ONION_VERSION) -> + let v = r.ReadUInt256(true) + this.Data <- InvalidOnionVersion(v) + | (INVALID_ONION_HMAC) -> + this.Data <- r.ReadUInt256(true) |> InvalidOnionHmac + | (INVALID_ONION_KEY) -> + this.Data <- r.ReadUInt256(true) |> InvalidOnionKey + | (TEMPORARY_CHANNEL_FAILURE) -> + let d = ILightningSerializable.deserialize(r) + this.Data <- d |> TemporaryChannelFailure + | (PERMANENT_CHANNEL_FAILURE) -> + this.Data <- PermanentChannelFailure + | (REQUIRED_CHANNEL_FEATURE_MISSING) -> + this.Data <- RequiredChannelFeatureMissing + | (UNKNOWN_NEXT_PEER) -> this.Data <- UnknownNextPeer + | (AMOUNT_BELOW_MINIMUM) -> + let amountMSat = r.ReadUInt64(false) |> LNMoney.MilliSatoshis + let d = ILightningSerializable.init() + + (d :> ILightningSerializable) + .Deserialize(r) + + this.Data <- (amountMSat, d) |> AmountBelowMinimum + | (FEE_INSUFFICIENT) -> + let amountMSat = r.ReadUInt64(false) |> LNMoney.MilliSatoshis + let d = ILightningSerializable.init() + + (d :> ILightningSerializable) + .Deserialize(r) + + this.Data <- (amountMSat, d) |> FeeInsufficient + | (CHANNEL_DISABLED) -> + let flags = r.ReadUInt16(false) + let d = ILightningSerializable.deserialize(r) + this.Data <- (flags, d) |> ChannelDisabled + | (INOCCORRECT_CLTV_EXPIRY) -> + let expiry = r.ReadUInt32(false) |> BlockHeight + let d = ILightningSerializable.deserialize(r) + this.Data <- (expiry, d) |> IncorrectCLTVExpiry + | (UNKNOWN_PAYMENT_HASH) -> this.Data <- UnknownPaymentHash + | (INCORRECT_PAYMENT_AMOUNT) -> this.Data <- IncorrectPaymentAmount + | (EXPIRY_TOO_SOON) -> + let d = ILightningSerializable.deserialize(r) + this.Data <- d |> ExpiryTooSoon + | FINAL_EXPIRY_TOO_SOON -> this.Data <- FinalExpiryTooSoon + | (FINAL_INCORRECT_CLTV_EXPIRY) -> + let expiry = r.ReadUInt32(false) |> BlockHeight + this.Data <- expiry |> FinalIncorrectCLTVExpiry + | (FINAL_INCORRECT_HTLC_AMOUNT) -> + let expiry = r.ReadUInt64(false) |> LNMoney.MilliSatoshis + this.Data <- expiry |> FinalIncorrectCLTVAmount + | (EXPIRY_TOO_FAR) -> this.Data <- ExpiryTooFar + | _ -> this.Data <- r.ReadAll() |> Unknown + + member this.Serialize(w: LightningWriterStream) : unit = + w.Write(this.Code.Value, false) + + match this.Data with + | InvalidOnionVersion onionHash -> w.Write(onionHash, false) + | InvalidOnionHmac onionHash -> w.Write(onionHash, false) + | InvalidOnionKey onionHash -> w.Write(onionHash, false) + | TemporaryChannelFailure update -> + (update :> ILightningSerializable) + .SerializeWithLen(w) + | AmountBelowMinimum(amount, update) -> + w.Write(uint64 amount.Value, false) + + (update :> ILightningSerializable) + .SerializeWithLen(w) + | FeeInsufficient(amount, update) -> + w.Write(uint64 amount.Value, false) + + (update :> ILightningSerializable) + .SerializeWithLen(w) + | ChannelDisabled(flags, update) -> + w.Write(flags, false) + + (update :> ILightningSerializable) + .SerializeWithLen(w) + | IncorrectCLTVExpiry(expiry, update) -> + w.Write(expiry.Value, false) + + (update :> ILightningSerializable) + .SerializeWithLen(w) + | ExpiryTooSoon update -> + (update :> ILightningSerializable) + .SerializeWithLen(w) + | FinalIncorrectCLTVExpiry expiry -> w.Write(expiry.Value, false) + | FinalIncorrectCLTVAmount amountMSat -> + w.Write(amountMSat.Value, false) + | FailureMsgData.Unknown b -> w.Write(b) + | _ -> () @@ -1367,223 +1699,336 @@ type FailureMsg = { type ErrorMsg = { mutable ChannelId: WhichChannel - mutable Data: byte[] + mutable Data: array } - with - interface ISetupMsg - interface ILightningSerializable with - member this.Deserialize(ls) = - match ls.ReadUInt256(true) with - | id when id = uint256.Zero -> - this.ChannelId <- All - | id -> - this.ChannelId <- SpecificChannel(ChannelId id) - this.Data <- ls.ReadWithLen() - member this.Serialize(ls) = - match this.ChannelId with - | SpecificChannel (ChannelId id) -> ls.Write(id.ToBytes()) - | All -> ls.Write(Array.zeroCreate 32) - ls.WriteWithLen(this.Data) - - member this.GetFailureMsgData() = - let minPrintableAsciiChar = 32uy - let isPrintableAsciiChar (asciiChar: byte) = - asciiChar >= minPrintableAsciiChar - let isPrintableAsciiString = - not <| Seq.exists - (fun asciiChar -> not (isPrintableAsciiChar asciiChar)) - this.Data - if isPrintableAsciiString then - System.Text.ASCIIEncoding.ASCII.GetString this.Data - else - Seq.fold - (fun msg (asciiChar: byte) -> sprintf "%s %02x" msg asciiChar) - ":" - this.Data + + interface ISetupMsg + + interface ILightningSerializable with + member this.Deserialize ls = + match ls.ReadUInt256(true) with + | id when id = uint256.Zero -> this.ChannelId <- All + | id -> this.ChannelId <- SpecificChannel(ChannelId id) + + this.Data <- ls.ReadWithLen() + + member this.Serialize ls = + match this.ChannelId with + | SpecificChannel(ChannelId id) -> ls.Write(id.ToBytes()) + | All -> ls.Write(Array.zeroCreate 32) + + ls.WriteWithLen(this.Data) + + member this.GetFailureMsgData() = + let minPrintableAsciiChar = 32uy + + let isPrintableAsciiChar(asciiChar: byte) = + asciiChar >= minPrintableAsciiChar + + let isPrintableAsciiString = + not + <| Seq.exists + (fun asciiChar -> not(isPrintableAsciiChar asciiChar)) + this.Data + + if isPrintableAsciiString then + System.Text.ASCIIEncoding.ASCII.GetString this.Data + else + Seq.fold + (fun msg (asciiChar: byte) -> sprintf "%s %02x" msg asciiChar) + ":" + this.Data and WhichChannel = | SpecificChannel of ChannelId | All -type ErrorAction = - | DisconnectPeer of ErrorMsg option +type ErrorAction = + | DisconnectPeer of option | IgnoreError | SendErrorMessage of ErrorMsg -type HandleError = { - Error: string - Action: ErrorAction option -} +type HandleError = + { + Error: string + Action: option + } /// Struct used to return valeus from revoke_and_ack messages, cotaining a bunch of commitment /// transaction updates if they were pending. -type CommitmentUpdate = { - UpdateAddHTLCs: UpdateAddHTLCMsg list - UpdateFulfillHTLCs: UpdateFulfillHTLCMsg list - UpdateFailHTLCs: UpdateFailHTLCMsg list - UpdateFailMalformedHTLCs: UpdateFailMalformedHTLCMsg list - UpdateFee: UpdateFeeMsg option - CommitmentSigned: CommitmentSignedMsg -} +type CommitmentUpdate = + { + UpdateAddHTLCs: list + UpdateFulfillHTLCs: list + UpdateFailHTLCs: list + UpdateFailMalformedHTLCs: list + UpdateFee: option + CommitmentSigned: CommitmentSignedMsg + } /// The information we received from a peer along the route of a payment we originated. This is /// returned by ChannelMessageHandler.HandleUpdateFailHTLC to be passed into /// RoutingMessageHandler.HandleHTLCFailChannelUpdate to update our network map. -type HTLCFailChannelUpdate = { - ChannelUpdateMessage: ChannelUpdateMsg - ChannelClosed: ChannelClosed - NodeFailure: NodeFailure -} - -and ChannelClosed = { - ShortChannelId: ShortChannelId - /// when this true, this channel should be permanently removed from the - /// consideration. Otherwiser, this channel can be restored as new ChannelUpdateMsg is received - IsPermanent: bool -} -and NodeFailure = { - NodeId: NodeId - IsPermanent: bool -} +type HTLCFailChannelUpdate = + { + ChannelUpdateMessage: ChannelUpdateMsg + ChannelClosed: ChannelClosed + NodeFailure: NodeFailure + } + +and ChannelClosed = + { + ShortChannelId: ShortChannelId + /// when this true, this channel should be permanently removed from the + /// consideration. Otherwiser, this channel can be restored as new ChannelUpdateMsg is received + IsPermanent: bool + } + +and NodeFailure = + { + NodeId: NodeId + IsPermanent: bool + } [] -type QueryShortChannelIdsMsg = { - mutable ChainHash: uint256 - mutable ShortIdsEncodingType: EncodingType - mutable ShortIds: ShortChannelId [] - mutable TLVs: QueryShortChannelIdsTLV [] -} - with +type QueryShortChannelIdsMsg = + { + mutable ChainHash: uint256 + mutable ShortIdsEncodingType: EncodingType + mutable ShortIds: array + mutable TLVs: array + } + interface IQueryMsg + interface ILightningSerializable with member this.Deserialize(ls: LightningReaderStream) = this.ChainHash <- ls.ReadUInt256(true) let shortIdsWithFlag = ls.ReadWithLen() - this.ShortIdsEncodingType <- LanguagePrimitives.EnumOfValue(shortIdsWithFlag.[0]) + + this.ShortIdsEncodingType <- + LanguagePrimitives.EnumOfValue( + shortIdsWithFlag.[0] + ) + let shortIds = - Decoder.decodeShortChannelIds this.ShortIdsEncodingType (shortIdsWithFlag.[1..]) + Decoder.decodeShortChannelIds + this.ShortIdsEncodingType + (shortIdsWithFlag.[1..]) + let tlvs = ls.ReadTLVStream() |> Array.map(QueryShortChannelIdsTLV.FromGenericTLV) + let queryFlags = tlvs - |> Seq.choose(function QueryShortChannelIdsTLV.QueryFlags (_, y) -> Some (y) | _ -> None) + |> Seq.choose( + function + | QueryShortChannelIdsTLV.QueryFlags(_, y) -> Some(y) + | _ -> None + ) |> Seq.tryExactlyOne + match queryFlags with | None -> this.ShortIds <- shortIds this.TLVs <- tlvs | Some flags -> if (shortIds.Length <> (flags |> Seq.length)) then - raise <| FormatException(sprintf "query_short_channel_ids have different length for short_ids(%A) and query_flags! (%A)" shortIds flags) + raise + <| FormatException( + sprintf + "query_short_channel_ids have different length for short_ids(%A) and query_flags! (%A)" + shortIds + flags + ) + this.ShortIds <- shortIds this.TLVs <- tlvs - member this.Serialize(ls) = + + member this.Serialize ls = ls.Write(this.ChainHash, true) - let encodedIds = this.ShortIds |> Encoder.encodeShortChannelIds (this.ShortIdsEncodingType) - [[|(byte)this.ShortIdsEncodingType|]; encodedIds] + + let encodedIds = + this.ShortIds + |> Encoder.encodeShortChannelIds(this.ShortIdsEncodingType) + + [ + [| (byte) this.ShortIdsEncodingType |] + encodedIds + ] |> Array.concat |> ls.WriteWithLen - this.TLVs |> Array.map(fun tlv -> tlv.ToGenericTLV()) |> ls.WriteTLVStream + + this.TLVs + |> Array.map(fun tlv -> tlv.ToGenericTLV()) + |> ls.WriteTLVStream [] -type ReplyShortChannelIdsEndMsg = { - mutable ChainHash: uint256 - mutable Complete: bool -} - with +type ReplyShortChannelIdsEndMsg = + { + mutable ChainHash: uint256 + mutable Complete: bool + } + interface IQueryMsg + interface ILightningSerializable with - member this.Deserialize(ls) = + member this.Deserialize ls = this.ChainHash <- ls.ReadUInt256(true) + this.Complete <- let b = ls.ReadByte() - if (b = 0uy) then false else - if (b = 1uy) then true else - raise <| FormatException(sprintf "reply_short_channel_ids has unknown byte in `complete` field %A" b) - member this.Serialize(ls) = + + if (b = 0uy) then + false + else if (b = 1uy) then + true + else + raise + <| FormatException( + sprintf + "reply_short_channel_ids has unknown byte in `complete` field %A" + b + ) + + member this.Serialize ls = ls.Write(this.ChainHash, true) - ls.Write(if (this.Complete) then 1uy else 0uy) + + ls.Write( + if this.Complete then + 1uy + else + 0uy + ) + [] -type QueryChannelRangeMsg = { - mutable ChainHash: uint256 - mutable FirstBlockNum: BlockHeight - mutable NumberOfBlocks: uint32 - mutable TLVs: QueryChannelRangeTLV [] -} - with +type QueryChannelRangeMsg = + { + mutable ChainHash: uint256 + mutable FirstBlockNum: BlockHeight + mutable NumberOfBlocks: uint32 + mutable TLVs: array + } + interface IQueryMsg + interface ILightningSerializable with - member this.Deserialize(ls) = + member this.Deserialize ls = this.ChainHash <- ls.ReadUInt256(true) this.FirstBlockNum <- ls.ReadUInt32(false) |> BlockHeight this.NumberOfBlocks <- ls.ReadUInt32(false) + this.TLVs <- let r = ls.ReadTLVStream() - r - |> Array.map(QueryChannelRangeTLV.FromGenericTLV) - member this.Serialize(ls) = + r |> Array.map(QueryChannelRangeTLV.FromGenericTLV) + + member this.Serialize ls = ls.Write(this.ChainHash, true) ls.Write(this.FirstBlockNum.Value, false) ls.Write(this.NumberOfBlocks, false) - this.TLVs |> Array.map(fun tlv -> tlv.ToGenericTLV()) |> ls.WriteTLVStream + + this.TLVs + |> Array.map(fun tlv -> tlv.ToGenericTLV()) + |> ls.WriteTLVStream [] -type ReplyChannelRangeMsg = { - mutable ChainHash: uint256 - mutable FirstBlockNum: BlockHeight - mutable NumOfBlocks: uint32 - mutable Complete: bool - mutable ShortIdsEncodingType: EncodingType - mutable ShortIds: ShortChannelId[] - mutable TLVs: ReplyChannelRangeTLV[] -} - with +type ReplyChannelRangeMsg = + { + mutable ChainHash: uint256 + mutable FirstBlockNum: BlockHeight + mutable NumOfBlocks: uint32 + mutable Complete: bool + mutable ShortIdsEncodingType: EncodingType + mutable ShortIds: array + mutable TLVs: array + } + interface IQueryMsg + interface ILightningSerializable with - member this.Deserialize(ls) = + member this.Deserialize ls = this.ChainHash <- ls.ReadUInt256(true) this.FirstBlockNum <- ls.ReadUInt32(false) |> BlockHeight this.NumOfBlocks <- ls.ReadUInt32(false) + this.Complete <- - let b =ls.ReadByte() - if b = 0uy then false else - if b = 1uy then true else - raise <| FormatException(sprintf "reply_channel_range has unknown byte in `complete` field %A" b) + let b = ls.ReadByte() + + if b = 0uy then + false + else if b = 1uy then + true + else + raise + <| FormatException( + sprintf + "reply_channel_range has unknown byte in `complete` field %A" + b + ) + let shortIdsWithFlag = ls.ReadWithLen() - this.ShortIdsEncodingType <- LanguagePrimitives.EnumOfValue(shortIdsWithFlag.[0]) + + this.ShortIdsEncodingType <- + LanguagePrimitives.EnumOfValue( + shortIdsWithFlag.[0] + ) + this.ShortIds <- - Decoder.decodeShortChannelIds this.ShortIdsEncodingType (shortIdsWithFlag.[1..]) + Decoder.decodeShortChannelIds + this.ShortIdsEncodingType + (shortIdsWithFlag.[1..]) + this.TLVs <- - ls.ReadTLVStream() |> Array.map(ReplyChannelRangeTLV.FromGenericTLV) - member this.Serialize(ls) = + ls.ReadTLVStream() + |> Array.map(ReplyChannelRangeTLV.FromGenericTLV) + + member this.Serialize ls = ls.Write(this.ChainHash, true) ls.Write(this.FirstBlockNum.Value, false) ls.Write(this.NumOfBlocks, false) - ls.Write(if this.Complete then 1uy else 0uy) - let encodedIds = this.ShortIds |> Encoder.encodeShortChannelIds (this.ShortIdsEncodingType) - [[|(byte)this.ShortIdsEncodingType|]; encodedIds] + + ls.Write( + if this.Complete then + 1uy + else + 0uy + ) + + let encodedIds = + this.ShortIds + |> Encoder.encodeShortChannelIds(this.ShortIdsEncodingType) + + [ + [| (byte) this.ShortIdsEncodingType |] + encodedIds + ] |> Array.concat |> ls.WriteWithLen - this.TLVs |> Array.map(fun x -> x.ToGenericTLV()) |> ls.WriteTLVStream - + + this.TLVs + |> Array.map(fun x -> x.ToGenericTLV()) + |> ls.WriteTLVStream + [] -type GossipTimestampFilterMsg = { - mutable ChainHash: uint256 - mutable FirstTimestamp: uint32 - mutable TimestampRange: uint32 -} - with +type GossipTimestampFilterMsg = + { + mutable ChainHash: uint256 + mutable FirstTimestamp: uint32 + mutable TimestampRange: uint32 + } + interface IQueryMsg + interface ILightningSerializable with - member this.Deserialize(ls) = + member this.Deserialize ls = this.ChainHash <- ls.ReadUInt256(true) this.FirstTimestamp <- ls.ReadUInt32(false) this.TimestampRange <- ls.ReadUInt32(false) - member this.Serialize(ls) = + + member this.Serialize ls = ls.Write(this.ChainHash, true) ls.Write(this.FirstTimestamp, false) ls.Write(this.TimestampRange, false) - diff --git a/src/DotNetLightning.Core/Serialization/OnionPayload.fs b/src/DotNetLightning.Core/Serialization/OnionPayload.fs index 78cd6d214..9aebc7bd7 100644 --- a/src/DotNetLightning.Core/Serialization/OnionPayload.fs +++ b/src/DotNetLightning.Core/Serialization/OnionPayload.fs @@ -8,52 +8,97 @@ open DotNetLightning.Core.Utils.Extensions open ResultUtils open ResultUtils.Portability -type OnionRealm0HopData = { - ShortChannelId: ShortChannelId - AmtToForward: LNMoney - OutgoingCLTVValue: uint32 -} - with - static member FromBytes(bytes: byte[]) = - if bytes.Length < 20 then Error(sprintf "%A is not valid legacy payload format " bytes) else - let schId = bytes.[0..7] |> ShortChannelId.From8Bytes - let amountToForward = bytes.[8..15] |> fun x -> Utils.ToUInt64(x, false) |> LNMoney.MilliSatoshis - let outgoingCLTV = bytes.[16..19] |> fun x -> Utils.ToUInt32(x, false) - { ShortChannelId = schId; AmtToForward = amountToForward; OutgoingCLTVValue = outgoingCLTV } - |> Ok - +type OnionRealm0HopData = + { + ShortChannelId: ShortChannelId + AmtToForward: LNMoney + OutgoingCLTVValue: uint32 + } + + static member FromBytes(bytes: array) = + if bytes.Length < 20 then + Error(sprintf "%A is not valid legacy payload format " bytes) + else + let schId = bytes.[0..7] |> ShortChannelId.From8Bytes + + let amountToForward = + bytes.[8..15] + |> fun x -> Utils.ToUInt64(x, false) |> LNMoney.MilliSatoshis + + let outgoingCLTV = + bytes.[16..19] |> fun x -> Utils.ToUInt32(x, false) + + { + ShortChannelId = schId + AmtToForward = amountToForward + OutgoingCLTVValue = outgoingCLTV + } + |> Ok + member this.ToBytes() = let schid = this.ShortChannelId.ToBytes() - let amt = this.AmtToForward.MilliSatoshi |> uint64 |> fun x -> Utils.ToBytes(x, false) - let outgoingCLTV = this.OutgoingCLTVValue |> fun x -> Utils.ToBytes(x, false) + + let amt = + this.AmtToForward.MilliSatoshi + |> uint64 + |> fun x -> Utils.ToBytes(x, false) + + let outgoingCLTV = + this.OutgoingCLTVValue |> fun x -> Utils.ToBytes(x, false) + let pad = Array.zeroCreate 12 - Array.concat [ [|0uy|]; schid; amt; outgoingCLTV; pad ] + + Array.concat + [ + [| 0uy |] + schid + amt + outgoingCLTV + pad + ] type OnionPayload = | Legacy of OnionRealm0HopData - | TLVPayload of tlvs: HopPayloadTLV array * hmac: uint256 - with - static member FromBytes(bytes: byte[]) = + | TLVPayload of tlvs: array * hmac: uint256 + + static member FromBytes(bytes: array) = match bytes.[0] with - | 0uy -> - OnionRealm0HopData.FromBytes(bytes.[1..]) - |> Result.map(Legacy) + | 0uy -> OnionRealm0HopData.FromBytes(bytes.[1..]) |> Result.map(Legacy) | _ -> result { let! l, bytes = bytes.TryPopVarInt() - if (l > (uint64 Int32.MaxValue)) then return! Error(sprintf "length for onion paylaod is too long %A" l) else - let l = int32 l - 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 + + if (l > (uint64 Int32.MaxValue)) then + return! + Error( + sprintf "length for onion paylaod is too long %A" l + ) + else + let l = int32 l + + 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 } - + member this.ToBytes() = match this with | Legacy o -> o.ToBytes() - | TLVPayload (tlvs, hmac) -> - let payloads = tlvs |> Seq.map(fun tlv -> tlv.ToGenericTLV().ToBytes()) |> Seq.concat |> Seq.toArray + | TLVPayload(tlvs, hmac) -> + 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 + hmac.ToBytes(false) + ] diff --git a/src/DotNetLightning.Core/Serialization/TLVs.fs b/src/DotNetLightning.Core/Serialization/TLVs.fs index 0f302def2..bbe178778 100644 --- a/src/DotNetLightning.Core/Serialization/TLVs.fs +++ b/src/DotNetLightning.Core/Serialization/TLVs.fs @@ -10,35 +10,54 @@ open ResultUtils.Portability type InitTLV = /// genesis chain hash that the node is interested in - | Networks of uint256 array + | Networks of array | Unknown of GenericTLV - with + static member FromGenericTLV(tlv: GenericTLV) = match tlv.Type with | 1UL -> let n, rem = Math.DivRem(tlv.Value.Length, 32) - if rem <> 0 then raise <| FormatException(sprintf "Bogus length for TLV in init message (%d), remainder was (%d)" tlv.Value.Length rem) else - let result = Array.zeroCreate n - for i in 0..n - 1 do - result.[i] <- tlv.Value.[(i * 32)..((i * 32) + 31)] |> fun x -> uint256(x, true) - result |> Networks - | _ -> Unknown (tlv) - + + if rem <> 0 then + raise + <| FormatException( + sprintf + "Bogus length for TLV in init message (%i), remainder was (%i)" + tlv.Value.Length + rem + ) + else + let result = Array.zeroCreate n + + for i in 0 .. n - 1 do + result.[i] <- + tlv.Value.[(i * 32) .. ((i * 32) + 31)] + |> fun x -> uint256(x, true) + + result |> Networks + | _ -> Unknown(tlv) + member this.ToGenericTLV() = match this with | Networks networks -> - let v = networks |> Array.map(fun x -> x.ToBytes(true)) |> Array.concat - { GenericTLV.Type = 1UL; Value = v } + let v = + networks |> Array.map(fun x -> x.ToBytes(true)) |> Array.concat + + { + GenericTLV.Type = 1UL + Value = v + } | Unknown tlv -> tlv type OpenChannelTLV = | UpfrontShutdownScript of Option | Unknown of GenericTLV - with + static member FromGenericTLV(tlv: GenericTLV) = match tlv.Type with | 0UL -> let script = Script tlv.Value + let shutdownScript = if script = Script.Empty then None @@ -46,7 +65,11 @@ type OpenChannelTLV = match ShutdownScriptPubKey.TryFromScript script with | Ok shutdownScript -> Some shutdownScript | Error err -> - raise <| FormatException(sprintf "invalid script for shutdown script: %s" err) + raise + <| FormatException( + sprintf "invalid script for shutdown script: %s" err + ) + UpfrontShutdownScript shutdownScript | _ -> Unknown tlv @@ -65,11 +88,12 @@ type OpenChannelTLV = type AcceptChannelTLV = | UpfrontShutdownScript of Option | Unknown of GenericTLV - with + static member FromGenericTLV(tlv: GenericTLV) = match tlv.Type with | 0UL -> let script = Script tlv.Value + let shutdownScript = if script = Script.Empty then None @@ -77,7 +101,11 @@ type AcceptChannelTLV = match ShutdownScriptPubKey.TryFromScript script with | Ok shutdownScript -> Some shutdownScript | Error err -> - raise <| FormatException(sprintf "invalid script for shutdown script: %s" err) + raise + <| FormatException( + sprintf "invalid script for shutdown script: %s" err + ) + UpfrontShutdownScript shutdownScript | _ -> Unknown tlv @@ -94,55 +122,78 @@ type AcceptChannelTLV = | Unknown tlv -> tlv type QueryShortChannelIdsTLV = - | QueryFlags of encodingType: EncodingType * encodedQueryFlags: QueryFlags[] + | QueryFlags of + encodingType: EncodingType * + encodedQueryFlags: array | Unknown of GenericTLV - with + static member FromGenericTLV(tlv: GenericTLV) = match tlv.Type with | 1UL -> - let encodingType = LanguagePrimitives.EnumOfValue(tlv.Value.[0]) + let encodingType = + LanguagePrimitives.EnumOfValue( + tlv.Value.[0] + ) + let data = tlv.Value.[1..] let flags = Decoder.decodeQueryFlags encodingType data QueryFlags(encodingType, flags) - | _ -> QueryShortChannelIdsTLV.Unknown (tlv) - + | _ -> QueryShortChannelIdsTLV.Unknown(tlv) + member this.ToGenericTLV() = match this with - | QueryFlags (t, flags) -> - let encodedFlags: byte[] = - flags |> Encoder.encodeQueryFlags t - let v = Array.concat(seq { yield [|(uint8)t|]; yield encodedFlags }) - { Type = 1UL; Value = v } + | QueryFlags(t, flags) -> + let encodedFlags: array = flags |> Encoder.encodeQueryFlags t + + let v = + Array.concat( + seq { + yield [| (uint8) t |] + yield encodedFlags + } + ) + + { + Type = 1UL + Value = v + } | Unknown tlv -> tlv - + type QueryChannelRangeTLV = | Opt of QueryOption | Unknown of GenericTLV - with + static member FromGenericTLV(tlv: GenericTLV) = match tlv.Type with | 1UL -> let op = tlv.Value.[0] |> QueryOption.Create Opt op - | _ -> - Unknown tlv - + | _ -> Unknown tlv + member this.ToGenericTLV() = match this with - | Opt(opt) -> - { Type = 1UL; Value = opt.ToBytes() } - | Unknown tlv -> - tlv - + | Opt opt -> + { + Type = 1UL + Value = opt.ToBytes() + } + | Unknown tlv -> tlv + type ReplyChannelRangeTLV = - | Timestamp of encodingType: EncodingType * encodedTimestamps: TwoTimestamps[] - | Checksums of TwoChecksums[] + | Timestamp of + encodingType: EncodingType * + encodedTimestamps: array + | Checksums of array | Unknown of GenericTLV - with + static member FromGenericTLV(tlv: GenericTLV) = match tlv.Type with | 1UL -> - let encodingType = LanguagePrimitives.EnumOfValue(tlv.Value.[0]) + let encodingType = + LanguagePrimitives.EnumOfValue( + tlv.Value.[0] + ) + let data = tlv.Value.[1..] let timestamps = Decoder.decodeTimestampPairs encodingType data Timestamp(encodingType, timestamps) @@ -150,53 +201,83 @@ type ReplyChannelRangeTLV = let checksums = Decoder.bytesToChecksumPair tlv.Value Checksums(checksums) | _ -> Unknown tlv - + member this.ToGenericTLV() = match this with | Timestamp(e, ts) -> let bytes = Encoder.encodeTimestampsPairs e ts - let value = Array.concat[| [|(byte)e|]; bytes |] - { Type = 1UL; Value = value } + let value = Array.concat [| [| (byte) e |]; bytes |] + + { + Type = 1UL + Value = value + } | Checksums checksums -> let b = checksums |> Array.map(fun i -> i.ToBytes()) |> Array.concat - { Type = 3UL; Value = b } + + { + Type = 3UL + Value = b + } | Unknown x -> x - + type HopPayloadTLV = | AmountToForward of LNMoney | OutgoingCLTV of uint32 | ShortChannelId of ShortChannelId | PaymentData of paymentSecret: PaymentPreimage * totalMsat: LNMoney | Unknown of GenericTLV - with + static member FromGenericTLV(tlv: GenericTLV) = match tlv.Type with | 2UL -> NBitcoin.Utils.ToUInt64(tlv.Value, false) |> LNMoney.MilliSatoshis |> AmountToForward - | 4UL -> - NBitcoin.Utils.ToUInt32(tlv.Value, false) - |> OutgoingCLTV - | 6UL -> - ShortChannelId.From8Bytes(tlv.Value) - |> ShortChannelId + | 4UL -> NBitcoin.Utils.ToUInt32(tlv.Value, false) |> 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 secret = + tlv.Value.[0 .. PaymentPreimage.LENGTH - 1] + |> PaymentPreimage.Create + + let totalMSat = + NBitcoin.Utils.ToUInt64( + tlv.Value.[PaymentPreimage.LENGTH ..], + false + ) + |> 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 = Utils.ToBytes(uint64 x.MilliSatoshi, false) + } | OutgoingCLTV x -> - { Type = 4UL; Value = Utils.ToBytes(x, false) } + { + Type = 4UL + Value = Utils.ToBytes(x, false) + } | ShortChannelId x -> - { Type = 6UL; Value = x.ToBytes() } + { + Type = 6UL + Value = x.ToBytes() + } | PaymentData(secret, amount) -> - let value = Array.concat[ secret.ToByteArray(); Utils.ToBytes(uint64 amount.MilliSatoshi, false) ] - { Type = 8UL; Value = value } + let value = + Array.concat + [ + secret.ToByteArray() + Utils.ToBytes(uint64 amount.MilliSatoshi, false) + ] + + { + Type = 8UL + Value = value + } | Unknown x -> x - diff --git a/src/DotNetLightning.Core/Transactions/CommitmentSpec.fs b/src/DotNetLightning.Core/Transactions/CommitmentSpec.fs index eab5931d6..6f255dd64 100644 --- a/src/DotNetLightning.Core/Transactions/CommitmentSpec.fs +++ b/src/DotNetLightning.Core/Transactions/CommitmentSpec.fs @@ -7,115 +7,145 @@ open DotNetLightning.Utils open ResultUtils open ResultUtils.Portability -type CommitmentSpec = { - OutgoingHTLCs: Map - IncomingHTLCs: Map - FeeRatePerKw: FeeRatePerKw - ToLocal: LNMoney - ToRemote: LNMoney -} - with - static member Create (toLocal) (toRemote) (feeRate) = - { - OutgoingHTLCs = Map.empty - IncomingHTLCs = Map.empty - FeeRatePerKw = feeRate - ToLocal = toLocal - ToRemote = toRemote +type CommitmentSpec = + { + OutgoingHTLCs: Map + IncomingHTLCs: Map + FeeRatePerKw: FeeRatePerKw + ToLocal: LNMoney + ToRemote: LNMoney + } + + static member Create toLocal toRemote feeRate = + { + OutgoingHTLCs = Map.empty + IncomingHTLCs = Map.empty + FeeRatePerKw = feeRate + ToLocal = toLocal + ToRemote = toRemote + } + + member internal this.AddOutgoingHTLC(update: UpdateAddHTLCMsg) = + { this with + ToLocal = (this.ToLocal - update.Amount) + OutgoingHTLCs = this.OutgoingHTLCs.Add(update.HTLCId, update) + } + + member internal this.AddIncomingHTLC(update: UpdateAddHTLCMsg) = + { this with + ToRemote = this.ToRemote - update.Amount + IncomingHTLCs = this.IncomingHTLCs.Add(update.HTLCId, update) + } + + member internal this.FulfillOutgoingHTLC(htlcId: HTLCId) = + match this.IncomingHTLCs |> Map.tryFind htlcId with + | Some htlc -> + { this with + ToLocal = this.ToLocal + htlc.Amount + IncomingHTLCs = this.IncomingHTLCs.Remove htlcId + } + |> Ok + | None -> UnknownHTLC htlcId |> Error + + member internal this.FulfillIncomingHTLC(htlcId: HTLCId) = + match this.OutgoingHTLCs |> Map.tryFind htlcId with + | Some htlc -> + { this with + ToRemote = this.ToRemote + htlc.Amount + OutgoingHTLCs = this.OutgoingHTLCs.Remove htlcId + } + |> Ok + | None -> UnknownHTLC htlcId |> Error + + member internal this.FailOutgoingHTLC(htlcId: HTLCId) = + match this.OutgoingHTLCs |> Map.tryFind htlcId with + | Some htlc -> + { this with + ToRemote = this.ToRemote + htlc.Amount + OutgoingHTLCs = this.OutgoingHTLCs.Remove htlcId } + |> Ok + | None -> UnknownHTLC htlcId |> Error - member internal this.AddOutgoingHTLC(update: UpdateAddHTLCMsg) = - { this with ToLocal = (this.ToLocal - update.Amount); OutgoingHTLCs = this.OutgoingHTLCs.Add(update.HTLCId, update)} - - member internal this.AddIncomingHTLC(update: UpdateAddHTLCMsg) = - { this with ToRemote = this.ToRemote - update.Amount; IncomingHTLCs = this.IncomingHTLCs.Add(update.HTLCId, update)} - - member internal this.FulfillOutgoingHTLC(htlcId: HTLCId) = - match this.IncomingHTLCs |> Map.tryFind htlcId with - | Some htlc -> - { this with ToLocal = this.ToLocal + htlc.Amount; IncomingHTLCs = this.IncomingHTLCs.Remove htlcId } - |> Ok - | None -> - UnknownHTLC htlcId |> Error - - member internal this.FulfillIncomingHTLC(htlcId: HTLCId) = - match this.OutgoingHTLCs |> Map.tryFind htlcId with - | Some htlc -> - { this with ToRemote = this.ToRemote + htlc.Amount; OutgoingHTLCs = this.OutgoingHTLCs.Remove htlcId } - |> Ok - | None -> - UnknownHTLC htlcId |> Error - - member internal this.FailOutgoingHTLC(htlcId: HTLCId) = - match this.OutgoingHTLCs |> Map.tryFind htlcId with - | Some htlc -> - { this with ToRemote = this.ToRemote + htlc.Amount; OutgoingHTLCs = this.OutgoingHTLCs.Remove htlcId } - |> Ok - | None -> - UnknownHTLC htlcId |> Error - - member internal this.FailIncomingHTLC(htlcId: HTLCId) = - match this.IncomingHTLCs |> Map.tryFind htlcId with - | Some htlc -> - { this with ToLocal = this.ToLocal + htlc.Amount; IncomingHTLCs = this.IncomingHTLCs.Remove htlcId } - |> Ok - | None -> - UnknownHTLC htlcId |> Error - - member internal this.Reduce(localChanges: #IUpdateMsg list, remoteChanges: #IUpdateMsg list) = - let spec1 = - localChanges - |> List.fold(fun (acc: CommitmentSpec) updateMsg -> - match box updateMsg with - | :? UpdateAddHTLCMsg as u -> acc.AddOutgoingHTLC u - | _ -> acc - ) - this - - let spec2 = - remoteChanges - |> List.fold(fun (acc: CommitmentSpec) updateMsg -> - match box updateMsg with - | :? UpdateAddHTLCMsg as u -> acc.AddIncomingHTLC u - | _ -> acc - ) - spec1 - - let spec3RR = - localChanges - |> List.fold(fun (acc: Result) updateMsg -> - match box updateMsg with - | :? UpdateFulfillHTLCMsg as u -> - acc >>= fun a -> a.FulfillOutgoingHTLC u.HTLCId - | :? UpdateFailHTLCMsg as u -> - acc >>= fun a -> a.FailOutgoingHTLC u.HTLCId - | :? UpdateFailMalformedHTLCMsg as u -> - acc >>= fun a -> a.FailOutgoingHTLC u.HTLCId - | _ -> acc + member internal this.FailIncomingHTLC(htlcId: HTLCId) = + match this.IncomingHTLCs |> Map.tryFind htlcId with + | Some htlc -> + { this with + ToLocal = this.ToLocal + htlc.Amount + IncomingHTLCs = this.IncomingHTLCs.Remove htlcId + } + |> Ok + | None -> UnknownHTLC htlcId |> Error + + member internal this.Reduce + ( + localChanges: list<#IUpdateMsg>, + remoteChanges: list<#IUpdateMsg> + ) = + let spec1 = + localChanges + |> List.fold + (fun (acc: CommitmentSpec) updateMsg -> + match box updateMsg with + | :? UpdateAddHTLCMsg as u -> acc.AddOutgoingHTLC u + | _ -> acc + ) + this + + let spec2 = + remoteChanges + |> List.fold + (fun (acc: CommitmentSpec) updateMsg -> + match box updateMsg with + | :? UpdateAddHTLCMsg as u -> acc.AddIncomingHTLC u + | _ -> acc + ) + spec1 + + let spec3RR = + localChanges + |> List.fold + (fun (acc: Result) updateMsg -> + match box updateMsg with + | :? UpdateFulfillHTLCMsg as u -> + acc >>= fun a -> a.FulfillOutgoingHTLC u.HTLCId + | :? UpdateFailHTLCMsg as u -> + acc >>= fun a -> a.FailOutgoingHTLC u.HTLCId + | :? UpdateFailMalformedHTLCMsg as u -> + acc >>= fun a -> a.FailOutgoingHTLC u.HTLCId + | _ -> acc + ) + (Ok spec2) + + let spec4RR = + remoteChanges + |> List.fold + (fun (acc: Result) updateMsg -> + match box updateMsg with + | :? UpdateFulfillHTLCMsg as u -> + acc >>= fun a -> a.FulfillIncomingHTLC u.HTLCId + | :? UpdateFailHTLCMsg as u -> + acc >>= fun a -> a.FailIncomingHTLC u.HTLCId + | :? UpdateFailMalformedHTLCMsg as u -> + acc >>= fun a -> a.FailIncomingHTLC u.HTLCId + | _ -> acc + ) + spec3RR + + let spec5 = + (localChanges @ remoteChanges) + |> List.fold + (fun acc updateMsg -> + match box updateMsg with + | :? UpdateFeeMsg as u -> + (fun a -> + { a with + CommitmentSpec.FeeRatePerKw = u.FeeRatePerKw + } ) - (Ok spec2) - - let spec4RR = - remoteChanges - |> List.fold(fun (acc: Result) updateMsg -> - match box updateMsg with - | :? UpdateFulfillHTLCMsg as u -> - acc >>= fun a -> a.FulfillIncomingHTLC u.HTLCId - | :? UpdateFailHTLCMsg as u -> - acc >>= fun a -> a.FailIncomingHTLC u.HTLCId - | :? UpdateFailMalformedHTLCMsg as u -> - acc >>= fun a -> a.FailIncomingHTLC u.HTLCId - | _ -> acc - ) - spec3RR - - let spec5 = - (localChanges @ remoteChanges) - |> List.fold(fun (acc) updateMsg -> - match box updateMsg with - | :? UpdateFeeMsg as u -> - (fun a -> { a with CommitmentSpec.FeeRatePerKw = u.FeeRatePerKw }) acc - | _ -> acc - ) - spec4RR - spec5 + acc + | _ -> acc + ) + spec4RR + + spec5 diff --git a/src/DotNetLightning.Core/Transactions/Scripts.fs b/src/DotNetLightning.Core/Transactions/Scripts.fs index 5570f6d00..ec9698c3b 100644 --- a/src/DotNetLightning.Core/Transactions/Scripts.fs +++ b/src/DotNetLightning.Core/Transactions/Scripts.fs @@ -10,110 +10,120 @@ open ResultUtils.Portability module Scripts = - let inline private encodeInt (n) = + let inline private encodeInt n = Op.GetPushOp(int64 n).ToString() - let funding (fundingPubKey0: FundingPubKey) (fundingPubKey1: FundingPubKey) = + let funding + (fundingPubKey0: FundingPubKey) + (fundingPubKey1: FundingPubKey) + = PayToMultiSigTemplate.Instance.GenerateScriptPubKey( 2, true, - [| fundingPubKey0.RawPubKey(); fundingPubKey1.RawPubKey() |] + [| + fundingPubKey0.RawPubKey() + fundingPubKey1.RawPubKey() + |] ) - let toLocalDelayed (revocationPubKey: RevocationPubKey) - (BlockHeightOffset16 toSelfDelay) - (localDelayedPaymentPubKey: DelayedPaymentPubKey) - : Script = + let toLocalDelayed + (revocationPubKey: RevocationPubKey) + (BlockHeightOffset16 toSelfDelay) + (localDelayedPaymentPubKey: DelayedPaymentPubKey) + : Script = let opList = ResizeArray() - opList.Add(!> OpcodeType.OP_IF) + opList.Add(!>OpcodeType.OP_IF) opList.Add(Op.GetPushOp(revocationPubKey.ToBytes())) - opList.Add(!> OpcodeType.OP_ELSE) + opList.Add(!>OpcodeType.OP_ELSE) opList.Add(Op.GetPushOp(int64 toSelfDelay)) - opList.Add(!> OpcodeType.OP_CHECKSEQUENCEVERIFY) - opList.Add(!> OpcodeType.OP_DROP) + opList.Add(!>OpcodeType.OP_CHECKSEQUENCEVERIFY) + opList.Add(!>OpcodeType.OP_DROP) opList.Add(Op.GetPushOp(localDelayedPaymentPubKey.ToBytes())) - opList.Add(!> OpcodeType.OP_ENDIF) - opList.Add(!> OpcodeType.OP_CHECKSIG) + opList.Add(!>OpcodeType.OP_ENDIF) + opList.Add(!>OpcodeType.OP_CHECKSIG) Script(opList) - let htlcOffered (localHtlcPubKey: HtlcPubKey) - (remoteHtlcPubKey: HtlcPubKey) - (revocationPubKey: RevocationPubKey) - (ph: PaymentHash) - : Script = + let htlcOffered + (localHtlcPubKey: HtlcPubKey) + (remoteHtlcPubKey: HtlcPubKey) + (revocationPubKey: RevocationPubKey) + (ph: PaymentHash) + : Script = let revocationPubKeyHash = let p = revocationPubKey.ToBytes() Hashes.Hash160(p, 0, p.Length) + let paymentHashHash = ph.GetRIPEMD160() - let opList = new ResizeArray(); - opList.Add(!> OpcodeType.OP_DUP); - opList.Add(!> OpcodeType.OP_HASH160); - opList.Add(Op.GetPushOp(revocationPubKeyHash.ToBytes())); - opList.Add(!> OpcodeType.OP_EQUAL); - opList.Add(!> OpcodeType.OP_IF); - opList.Add(!> OpcodeType.OP_CHECKSIG); - opList.Add(!> OpcodeType.OP_ELSE); - opList.Add(Op.GetPushOp(remoteHtlcPubKey.ToBytes())); - opList.Add(!> OpcodeType.OP_SWAP); - opList.Add(!> OpcodeType.OP_SIZE); - opList.Add(Op.GetPushOp(32L)); - opList.Add(!> OpcodeType.OP_EQUAL); - opList.Add(!> OpcodeType.OP_NOTIF); - opList.Add(!> OpcodeType.OP_DROP); - opList.Add(!> OpcodeType.OP_2); - opList.Add(!> OpcodeType.OP_SWAP); - opList.Add(Op.GetPushOp(localHtlcPubKey.ToBytes())); - opList.Add(!> OpcodeType.OP_2); - opList.Add(!> OpcodeType.OP_CHECKMULTISIG); - opList.Add(!> OpcodeType.OP_ELSE); - opList.Add(!> OpcodeType.OP_HASH160); - opList.Add(Op.GetPushOp(paymentHashHash)); - opList.Add(!> OpcodeType.OP_EQUALVERIFY); - opList.Add(!> OpcodeType.OP_CHECKSIG); - opList.Add(!> OpcodeType.OP_ENDIF); - opList.Add(!> OpcodeType.OP_ENDIF); - Script(opList); + let opList = new ResizeArray() + opList.Add(!>OpcodeType.OP_DUP) + opList.Add(!>OpcodeType.OP_HASH160) + opList.Add(Op.GetPushOp(revocationPubKeyHash.ToBytes())) + opList.Add(!>OpcodeType.OP_EQUAL) + opList.Add(!>OpcodeType.OP_IF) + opList.Add(!>OpcodeType.OP_CHECKSIG) + opList.Add(!>OpcodeType.OP_ELSE) + opList.Add(Op.GetPushOp(remoteHtlcPubKey.ToBytes())) + opList.Add(!>OpcodeType.OP_SWAP) + opList.Add(!>OpcodeType.OP_SIZE) + opList.Add(Op.GetPushOp(32L)) + opList.Add(!>OpcodeType.OP_EQUAL) + opList.Add(!>OpcodeType.OP_NOTIF) + opList.Add(!>OpcodeType.OP_DROP) + opList.Add(!>OpcodeType.OP_2) + opList.Add(!>OpcodeType.OP_SWAP) + opList.Add(Op.GetPushOp(localHtlcPubKey.ToBytes())) + opList.Add(!>OpcodeType.OP_2) + opList.Add(!>OpcodeType.OP_CHECKMULTISIG) + opList.Add(!>OpcodeType.OP_ELSE) + opList.Add(!>OpcodeType.OP_HASH160) + opList.Add(Op.GetPushOp(paymentHashHash)) + opList.Add(!>OpcodeType.OP_EQUALVERIFY) + opList.Add(!>OpcodeType.OP_CHECKSIG) + opList.Add(!>OpcodeType.OP_ENDIF) + opList.Add(!>OpcodeType.OP_ENDIF) + Script(opList) - let htlcReceived (localHTLCPubKey: HtlcPubKey) - (remoteHTLCPubKey: HtlcPubKey) - (revocationPubKey: RevocationPubKey) - (ph: PaymentHash) - (lockTime: uint32) - : Script = + let htlcReceived + (localHTLCPubKey: HtlcPubKey) + (remoteHTLCPubKey: HtlcPubKey) + (revocationPubKey: RevocationPubKey) + (ph: PaymentHash) + (lockTime: uint32) + : Script = let revocationPubKeyHash = let p = revocationPubKey.ToBytes() Hashes.Hash160(p, 0, p.Length) + let paymentHashHash = ph.GetRIPEMD160() - let opList = ResizeArray(); - - opList.Add(!> OpcodeType.OP_DUP); - opList.Add(!> OpcodeType.OP_HASH160); - opList.Add(Op.GetPushOp(revocationPubKeyHash.ToBytes())); - opList.Add(!> OpcodeType.OP_EQUAL); - opList.Add(!> OpcodeType.OP_IF); - opList.Add(!> OpcodeType.OP_CHECKSIG); - opList.Add(!> OpcodeType.OP_ELSE); - opList.Add(Op.GetPushOp(remoteHTLCPubKey.ToBytes())); - opList.Add(!> OpcodeType.OP_SWAP); - opList.Add(!> OpcodeType.OP_SIZE); - opList.Add(Op.GetPushOp(32L)); - opList.Add(!> OpcodeType.OP_EQUAL); - opList.Add(!> OpcodeType.OP_IF); - opList.Add(!> OpcodeType.OP_HASH160); - opList.Add(Op.GetPushOp(paymentHashHash)); - opList.Add(!> OpcodeType.OP_EQUALVERIFY); - opList.Add(!> OpcodeType.OP_2); - opList.Add(!> OpcodeType.OP_SWAP); - opList.Add(Op.GetPushOp(localHTLCPubKey.ToBytes())); - opList.Add(!> OpcodeType.OP_2); - opList.Add(!> OpcodeType.OP_CHECKMULTISIG); - opList.Add(!> OpcodeType.OP_ELSE); - opList.Add(!> OpcodeType.OP_DROP); - opList.Add(Op.GetPushOp(int64 lockTime)); - opList.Add(!> OpcodeType.OP_CHECKLOCKTIMEVERIFY); - opList.Add(!> OpcodeType.OP_DROP); - opList.Add(!> OpcodeType.OP_CHECKSIG); - opList.Add(!> OpcodeType.OP_ENDIF); - opList.Add(!> OpcodeType.OP_ENDIF); - Script(opList); + let opList = ResizeArray() + opList.Add(!>OpcodeType.OP_DUP) + opList.Add(!>OpcodeType.OP_HASH160) + opList.Add(Op.GetPushOp(revocationPubKeyHash.ToBytes())) + opList.Add(!>OpcodeType.OP_EQUAL) + opList.Add(!>OpcodeType.OP_IF) + opList.Add(!>OpcodeType.OP_CHECKSIG) + opList.Add(!>OpcodeType.OP_ELSE) + opList.Add(Op.GetPushOp(remoteHTLCPubKey.ToBytes())) + opList.Add(!>OpcodeType.OP_SWAP) + opList.Add(!>OpcodeType.OP_SIZE) + opList.Add(Op.GetPushOp(32L)) + opList.Add(!>OpcodeType.OP_EQUAL) + opList.Add(!>OpcodeType.OP_IF) + opList.Add(!>OpcodeType.OP_HASH160) + opList.Add(Op.GetPushOp(paymentHashHash)) + opList.Add(!>OpcodeType.OP_EQUALVERIFY) + opList.Add(!>OpcodeType.OP_2) + opList.Add(!>OpcodeType.OP_SWAP) + opList.Add(Op.GetPushOp(localHTLCPubKey.ToBytes())) + opList.Add(!>OpcodeType.OP_2) + opList.Add(!>OpcodeType.OP_CHECKMULTISIG) + opList.Add(!>OpcodeType.OP_ELSE) + opList.Add(!>OpcodeType.OP_DROP) + opList.Add(Op.GetPushOp(int64 lockTime)) + opList.Add(!>OpcodeType.OP_CHECKLOCKTIMEVERIFY) + opList.Add(!>OpcodeType.OP_DROP) + opList.Add(!>OpcodeType.OP_CHECKSIG) + opList.Add(!>OpcodeType.OP_ENDIF) + opList.Add(!>OpcodeType.OP_ENDIF) + Script(opList) diff --git a/src/DotNetLightning.Core/Transactions/TransactionError.fs b/src/DotNetLightning.Core/Transactions/TransactionError.fs index b08989b65..b23bbeef3 100644 --- a/src/DotNetLightning.Core/Transactions/TransactionError.fs +++ b/src/DotNetLightning.Core/Transactions/TransactionError.fs @@ -10,17 +10,17 @@ type TransactionError = | AmountBelowDustLimit of amount: Money /// We tried to close the channel, but there are remaining HTLC we must do something /// before closing it - | HTLCNotClean of remainingHTLCs: HTLCId list - + | HTLCNotClean of remainingHTLCs: list + member this.Message = match this with - | UnknownHTLC htlcId -> - sprintf "Unknown htlc id (%i)" htlcId.Value + | UnknownHTLC htlcId -> sprintf "Unknown htlc id (%i)" htlcId.Value | FailedToFinalizeScript errorMsg -> sprintf "Failed to finalize script: %s" errorMsg - | InvalidSignature _ -> - "Invalid signature" + | InvalidSignature _ -> "Invalid signature" | AmountBelowDustLimit amount -> sprintf "Amount (%s) is below dust limit" (amount.ToString()) | HTLCNotClean remainingHTLCs -> - sprintf "Attempted to close channel with %i remaining htlcs" remainingHTLCs.Length + sprintf + "Attempted to close channel with %i remaining htlcs" + remainingHTLCs.Length diff --git a/src/DotNetLightning.Core/Transactions/Transactions.fs b/src/DotNetLightning.Core/Transactions/Transactions.fs index 2fd8e419a..c99d43152 100644 --- a/src/DotNetLightning.Core/Transactions/Transactions.fs +++ b/src/DotNetLightning.Core/Transactions/Transactions.fs @@ -28,222 +28,286 @@ type IHTLCTx = inherit ILightningTx -type CommitTx = { - Value: PSBT -} - with - interface ILightningTx with - member this.Value: PSBT = - this.Value - member this.WhichInput: int = 0 - - static member WhichInput: int = 0 - member this.GetTxId() = - this.Value.GetGlobalTransaction().GetTxId() +type CommitTx = + { + Value: PSBT + } + + interface ILightningTx with + member this.Value: PSBT = this.Value + member this.WhichInput: int = 0 + + static member WhichInput: int = 0 + + member this.GetTxId() = + this.Value.GetGlobalTransaction().GetTxId() module private HTLCHelper = - let createHTLCWitScript(localSig: TransactionSignature, remoteSig: TransactionSignature, witScript: Script, paymentPreimage: PaymentPreimage option) = + let createHTLCWitScript + ( + localSig: TransactionSignature, + remoteSig: TransactionSignature, + witScript: Script, + paymentPreimage: option + ) = let l = ResizeArray() - l.Add(!> OpcodeType.OP_0) + l.Add(!>OpcodeType.OP_0) l.Add(Op.GetPushOp(remoteSig.ToBytes())) l.Add(Op.GetPushOp(localSig.ToBytes())) + match paymentPreimage with | Some p -> l.Add(Op.GetPushOp(p.ToByteArray())) - | None -> l.Add(!> OpcodeType.OP_0) + | None -> l.Add(!>OpcodeType.OP_0) + l.Add(Op.GetPushOp(witScript.ToBytes())) let s = Script(l) s.ToWitScript() - + /// PSBT does not support finalizing script with payment preimage. /// (And it never does unless scripts in BOLT3 follows some other static analysis scheme such as Miniscript.) /// We must finalize manually here. - let finalize(htlcTx: IHTLCTx, localSig, remoteSig, paymentPreimage: PaymentPreimage option) = + let finalize + ( + htlcTx: IHTLCTx, + localSig, + remoteSig, + paymentPreimage: option + ) = // Clone this to make the operation atomic. we don't want to mutate `this` in case of failure - let psbt = htlcTx.Value.Clone() + let psbt = htlcTx.Value.Clone() let psbtIn = psbt.Inputs.[htlcTx.WhichInput] let witScript = psbtIn.WitnessScript - let finalWit = createHTLCWitScript(localSig, remoteSig, witScript, paymentPreimage) + + let finalWit = + createHTLCWitScript(localSig, remoteSig, witScript, paymentPreimage) + psbtIn.FinalScriptWitness <- finalWit let coin = psbtIn.GetCoin() - let txIn = psbt.GetGlobalTransaction().Inputs.FindIndexedInput(coin.Outpoint) + + let txIn = + psbt + .GetGlobalTransaction() + .Inputs.FindIndexedInput(coin.Outpoint) + txIn.WitScript <- finalWit let errors = ref ScriptError.OK + match txIn.VerifyScript(coin, ScriptVerify.Standard, errors) with | true -> htlcTx.Value.Inputs.[htlcTx.WhichInput].FinalScriptWitness <- finalWit htlcTx.Value.Inputs.[htlcTx.WhichInput].WitnessScript <- null - htlcTx.Value.Inputs.[htlcTx.WhichInput].PartialSigs.Clear() - htlcTx.Value.ExtractTransaction() - |> Ok + + htlcTx.Value.Inputs.[htlcTx.WhichInput] + .PartialSigs.Clear() + + htlcTx.Value.ExtractTransaction() |> Ok | false -> sprintf "Failed to finalize PSBT. error (%A) " errors |> FailedToFinalizeScript |> Error - -type HTLCSuccessTx = { - Value: PSBT - PaymentHash: PaymentHash -} - with - static member WhichInput: int = 0 - - member this.GetRedeem() = - this.Value.Inputs.[HTLCSuccessTx.WhichInput].WitnessScript - - member this.Finalize(localPubkey, remotePubKey, paymentPreimage) = - let localSig = this.Value.Inputs.[HTLCSuccessTx.WhichInput].PartialSigs.[localPubkey] - let remoteSig = this.Value.Inputs.[HTLCSuccessTx.WhichInput].PartialSigs.[remotePubKey] - this.Finalize(localSig, remoteSig, paymentPreimage) - - member this.Finalize(localSig, remoteSig, paymentPreimage: PaymentPreimage) = - HTLCHelper.finalize(this, localSig, remoteSig, Some paymentPreimage) - - interface IHTLCTx - with - member this.Value = this.Value - member this.WhichInput = HTLCSuccessTx.WhichInput - -type HTLCTimeoutTx = { - Value: PSBT -} - with - static member WhichInput: int = 0 - - member this.Finalize(localSig: TransactionSignature, remoteSig: TransactionSignature) = - HTLCHelper.finalize(this, localSig, remoteSig, None) - - interface IHTLCTx - with - member this.Value = this.Value - member this.WhichInput = HTLCTimeoutTx.WhichInput - - -type ClaimHTLCSuccessTx = ClaimHTLCSuccessTx of PSBT - with - interface ILightningTx with - member this.Value = - this.Value - member this.WhichInput: int = - 0 - member this.Value = let (ClaimHTLCSuccessTx v) = this in v; -type ClaimHTLCTimeoutTx = ClaimHTLCTimeoutTx of PSBT - with - interface ILightningTx with - member this.Value = - this.Value - member this.WhichInput: int = - 0 - member this.Value = let (ClaimHTLCTimeoutTx v) = this in v; -type ClaimP2WPKHOutputTx = ClaimP2WPKHOutputTx of PSBT - with - interface ILightningTx with - member this.Value = - this.Value - member this.WhichInput: int = - 0 - member this.Value = let (ClaimP2WPKHOutputTx v) = this in v; -type ClaimDelayedOutputTx = ClaimDelayedOutputTx of PSBT - with - interface ILightningTx with - member this.Value = - this.Value - member this.WhichInput: int = - 0 - member this.Value = let (ClaimDelayedOutputTx v) = this in v; -type MainPenaltyTx = MainPenaltyTx of PSBT - with - interface ILightningTx with - member this.Value = - this.Value - member this.WhichInput: int = - 0 - member this.Value = let (MainPenaltyTx v) = this in v; -type HTLCPenaltyTx = HTLCPenaltyTx of PSBT - with - interface ILightningTx with - member this.Value = - this.Value - member this.WhichInput: int = - 0 - member this.Value = let (HTLCPenaltyTx v) = this in v; -type ClosingTx = ClosingTx of PSBT - with - member this.Value = let (ClosingTx v) = this in v; - member this.WhichInput = 0 - - interface ILightningTx with - member this.Value = - this.Value - member this.WhichInput: int = - this.WhichInput + +type HTLCSuccessTx = + { + Value: PSBT + PaymentHash: PaymentHash + } + + static member WhichInput: int = 0 + + member this.GetRedeem() = + this.Value.Inputs.[HTLCSuccessTx.WhichInput] + .WitnessScript + + member this.Finalize(localPubkey, remotePubKey, paymentPreimage) = + let localSig = + this.Value.Inputs.[HTLCSuccessTx.WhichInput] + .PartialSigs.[localPubkey] + + let remoteSig = + this.Value.Inputs.[HTLCSuccessTx.WhichInput] + .PartialSigs.[remotePubKey] + + this.Finalize(localSig, remoteSig, paymentPreimage) + + member this.Finalize + ( + localSig, + remoteSig, + paymentPreimage: PaymentPreimage + ) = + HTLCHelper.finalize(this, localSig, remoteSig, Some paymentPreimage) + + interface IHTLCTx with + member this.Value = this.Value + member this.WhichInput = HTLCSuccessTx.WhichInput + +type HTLCTimeoutTx = + { + Value: PSBT + } + + static member WhichInput: int = 0 + + member this.Finalize + ( + localSig: TransactionSignature, + remoteSig: TransactionSignature + ) = + HTLCHelper.finalize(this, localSig, remoteSig, None) + + interface IHTLCTx with + member this.Value = this.Value + member this.WhichInput = HTLCTimeoutTx.WhichInput + + +type ClaimHTLCSuccessTx = + | ClaimHTLCSuccessTx of PSBT + + interface ILightningTx with + member this.Value = this.Value + member this.WhichInput: int = 0 + + member this.Value = let (ClaimHTLCSuccessTx v) = this in v + +type ClaimHTLCTimeoutTx = + | ClaimHTLCTimeoutTx of PSBT + + interface ILightningTx with + member this.Value = this.Value + member this.WhichInput: int = 0 + + member this.Value = let (ClaimHTLCTimeoutTx v) = this in v + +type ClaimP2WPKHOutputTx = + | ClaimP2WPKHOutputTx of PSBT + + interface ILightningTx with + member this.Value = this.Value + member this.WhichInput: int = 0 + + member this.Value = let (ClaimP2WPKHOutputTx v) = this in v + +type ClaimDelayedOutputTx = + | ClaimDelayedOutputTx of PSBT + + interface ILightningTx with + member this.Value = this.Value + member this.WhichInput: int = 0 + + member this.Value = let (ClaimDelayedOutputTx v) = this in v + +type MainPenaltyTx = + | MainPenaltyTx of PSBT + + interface ILightningTx with + member this.Value = this.Value + member this.WhichInput: int = 0 + + member this.Value = let (MainPenaltyTx v) = this in v + +type HTLCPenaltyTx = + | HTLCPenaltyTx of PSBT + + interface ILightningTx with + member this.Value = this.Value + member this.WhichInput: int = 0 + + member this.Value = let (HTLCPenaltyTx v) = this in v + +type ClosingTx = + | ClosingTx of PSBT + + member this.Value = let (ClosingTx v) = this in v + member this.WhichInput = 0 + + interface ILightningTx with + member this.Value = this.Value + member this.WhichInput: int = this.WhichInput /// Tx already verified and it can be published anytime type FinalizedTx = - FinalizedTx of Transaction - with - member this.Value = let (FinalizedTx v) = this in v + | FinalizedTx of Transaction -type InputInfo = { - OutPoint: OutPoint - RedeemScript: Script -} + member this.Value = let (FinalizedTx v) = this in v + +type InputInfo = + { + OutPoint: OutPoint + RedeemScript: Script + } // Write -[] -type SortableTxOut = { - TxOut: TxOut - UpdateAddHTLC: UpdateAddHTLCMsg option +[] +type SortableTxOut = + { + TxOut: TxOut + UpdateAddHTLC: option } - with - override this.Equals(other: obj): bool = - match other with - | :? SortableTxOut as o -> (this :> IEquatable).Equals(o) - | _ -> false - - override this.GetHashCode() = - match this.UpdateAddHTLC with - | None -> this.TxOut.GetHashCode() - | Some htlc -> Array.append (this.TxOut.ToBytes()) (htlc.ToBytes()) |> hash - - interface IEquatable with - member this.Equals(other) = - this.TxOut.ScriptPubKey.Equals(other.TxOut.ScriptPubKey) - && this.TxOut.Value.Equals(other.TxOut.Value) - && this.UpdateAddHTLC.Equals(other.UpdateAddHTLC) - - interface IComparable with - member this.CompareTo(obj: obj): int = - match obj with - | :? SortableTxOut as other -> (this :> IComparable).CompareTo(other) - | _ -> 1 - - interface IComparable with - member this.CompareTo(obj: SortableTxOut) = - let (txout1) = this.TxOut - let (txout2) = obj.TxOut - let c1 = txout1.Value.CompareTo(txout2.Value) - if (c1 <> 0) then - c1 + + override this.Equals(other: obj) : bool = + match other with + | :? SortableTxOut as o -> (this :> IEquatable).Equals(o) + | _ -> false + + override this.GetHashCode() = + match this.UpdateAddHTLC with + | None -> this.TxOut.GetHashCode() + | Some htlc -> + Array.append (this.TxOut.ToBytes()) (htlc.ToBytes()) |> hash + + interface IEquatable with + member this.Equals other = + this.TxOut.ScriptPubKey.Equals(other.TxOut.ScriptPubKey) + && this.TxOut.Value.Equals(other.TxOut.Value) + && this.UpdateAddHTLC.Equals(other.UpdateAddHTLC) + + interface IComparable with + member this.CompareTo(obj: obj) : int = + match obj with + | :? SortableTxOut as other -> + (this :> IComparable) + .CompareTo(other) + | _ -> 1 + + interface IComparable with + member this.CompareTo(obj: SortableTxOut) = + let txout1 = this.TxOut + let txout2 = obj.TxOut + let c1 = txout1.Value.CompareTo(txout2.Value) + + if (c1 <> 0) then + c1 + else + let c2 = + BytesComparer.Instance.Compare( + txout1.ScriptPubKey.ToBytes(), + txout2.ScriptPubKey.ToBytes() + ) + + if (c2 <> 0) then + c2 else - let c2 = BytesComparer.Instance.Compare(txout1.ScriptPubKey.ToBytes(), txout2.ScriptPubKey.ToBytes()) - if (c2 <> 0) then - c2 - else - // tie-breaker - match (this.UpdateAddHTLC), (obj.UpdateAddHTLC) with - | None, _ -> 0 - | _, None -> 0 - | Some a, Some b -> - let c3 = a.CLTVExpiry.Value.CompareTo(b.CLTVExpiry.Value) - if c3 <> 0 then - c3 + // tie-breaker + match (this.UpdateAddHTLC), (obj.UpdateAddHTLC) with + | None, _ -> 0 + | _, None -> 0 + | Some a, Some b -> + let c3 = + a.CLTVExpiry.Value.CompareTo(b.CLTVExpiry.Value) + + if c3 <> 0 then + c3 + else + let c4 = + a.PaymentHash.Value.CompareTo( + b.PaymentHash.Value + ) + + if (c4 <> 0) then + c4 else - let c4 = a.PaymentHash.Value.CompareTo(b.PaymentHash.Value) - if (c4 <> 0) then - c4 - else - 0 + 0 module Transactions = @@ -291,7 +355,7 @@ module Transactions = // prevout: 36, nSequence: 4, script len: 1, witness lengths: (3+1)/4, sig: 73/4, if-selector: 1, redeemScript: (6 ops + 2*33 pubkeys + 1*2 delay)/4 [] - let SPENDING_INPUT_FOR_A_OUTPUT_WEIGHT = 79UL + let SPENDING_INPUT_FOR_A_OUTPUT_WEIGHT = 79UL // prevout: 40, nSequence: 4, script len: 1, witness lengths: 3/4, sig: 73/4, pubkey: 33/4, output: 31 [] let B_OUTPUT_PLUS_SPENDING_INPUT_WEIGHT = 104UL @@ -301,10 +365,11 @@ module Transactions = [] let ACCEPTED_HTLC_SCRIPT_WEIGHT = 139uy + [] let OFFERED_HTLC_SCRIPT_WEIGHT = 133uy - let internal createDeterministicTransactionBuilder (network: Network) = + let internal createDeterministicTransactionBuilder(network: Network) = let txb = network.CreateTransactionBuilder() txb.ShuffleOutputs <- false txb.ShuffleInputs <- false @@ -313,95 +378,168 @@ module Transactions = let UINT32_MAX = 0xffffffffu - let private trimOfferedHTLCs (dustLimit: Money) (spec: CommitmentSpec): list = - let htlcTimeoutFee = spec.FeeRatePerKw.CalculateFeeFromWeight(HTLC_TIMEOUT_WEIGHT) + let private trimOfferedHTLCs + (dustLimit: Money) + (spec: CommitmentSpec) + : list = + let htlcTimeoutFee = + spec.FeeRatePerKw.CalculateFeeFromWeight(HTLC_TIMEOUT_WEIGHT) + spec.OutgoingHTLCs - |> Map.toList - |> List.map snd - |> List.filter(fun v -> (v.Amount.ToMoney()) >= (dustLimit + htlcTimeoutFee)) + |> Map.toList + |> List.map snd + |> List.filter(fun v -> + (v.Amount.ToMoney()) >= (dustLimit + htlcTimeoutFee) + ) + + let private trimReceivedHTLCs + (dustLimit: Money) + (spec: CommitmentSpec) + : list = + let htlcSuccessFee = + spec.FeeRatePerKw.CalculateFeeFromWeight(HTLC_SUCCESS_WEIGHT) - let private trimReceivedHTLCs (dustLimit: Money) (spec: CommitmentSpec) : list = - let htlcSuccessFee = spec.FeeRatePerKw.CalculateFeeFromWeight(HTLC_SUCCESS_WEIGHT) spec.IncomingHTLCs - |> Map.toList - |> List.map snd - |> List.filter(fun v -> (v.Amount.ToMoney()) >= (dustLimit + htlcSuccessFee)) + |> Map.toList + |> List.map snd + |> List.filter(fun v -> + (v.Amount.ToMoney()) >= (dustLimit + htlcSuccessFee) + ) - let internal commitTxFee (dustLimit: Money) (spec: CommitmentSpec): Money = + let internal commitTxFee (dustLimit: Money) (spec: CommitmentSpec) : Money = let trimmedOfferedHTLCs = trimOfferedHTLCs (dustLimit) (spec) let trimmedReceivedHTLCs = trimReceivedHTLCs dustLimit spec - let weight = COMMIT_WEIGHT + 172UL * (uint64 trimmedOfferedHTLCs.Length + uint64 trimmedReceivedHTLCs.Length) + + let weight = + COMMIT_WEIGHT + + 172UL + * (uint64 trimmedOfferedHTLCs.Length + + uint64 trimmedReceivedHTLCs.Length) + spec.FeeRatePerKw.CalculateFeeFromWeight(weight) - let getCommitTxNumber (commitTx: Transaction) - (isFunder: bool) - (localPaymentBasepoint: PaymentBasepoint) - (remotePaymentBasepoint: PaymentBasepoint) - : Option = + let getCommitTxNumber + (commitTx: Transaction) + (isFunder: bool) + (localPaymentBasepoint: PaymentBasepoint) + (remotePaymentBasepoint: PaymentBasepoint) + : Option = let obscuredCommitmentNumberOpt = ObscuredCommitmentNumber.TryFromLockTimeAndSequence commitTx.LockTime commitTx.Inputs.[0].Sequence + match obscuredCommitmentNumberOpt with | None -> None | Some obscuredCommitmentNumber -> - Some <| obscuredCommitmentNumber.Unobscure + Some + <| obscuredCommitmentNumber.Unobscure isFunder localPaymentBasepoint remotePaymentBasepoint /// Sort by BOLT 3: Compliant way (i.e. BIP69 + CLTV order) - let sortTxOut (txOutsWithMeta: (TxOut * Option) list) = + let sortTxOut(txOutsWithMeta: list<(TxOut * Option)>) = txOutsWithMeta - |> List.sortBy(fun txom -> { SortableTxOut.TxOut = (fst txom);UpdateAddHTLC = (snd txom) }) + |> List.sortBy(fun txom -> + { + SortableTxOut.TxOut = (fst txom) + UpdateAddHTLC = (snd txom) + } + ) |> List.map(fst) - let makeCommitTx (inputInfo: ScriptCoin) - (commitmentNumber: CommitmentNumber) - (localPaymentBasepoint: PaymentBasepoint) - (remotePaymentBasepoint: PaymentBasepoint) - (localIsFunder: bool) - (localDustLimit: Money) - (localRevocationPubKey: RevocationPubKey) - (toLocalDelay: BlockHeightOffset16) - (localDelayedPaymentPubKey: DelayedPaymentPubKey) - (remotePaymentPubkey: PaymentPubKey) - (localHTLCPubKey: HtlcPubKey) - (remoteHTLCPubkey: HtlcPubKey) - (spec: CommitmentSpec) - (network: Network) - = + let makeCommitTx + (inputInfo: ScriptCoin) + (commitmentNumber: CommitmentNumber) + (localPaymentBasepoint: PaymentBasepoint) + (remotePaymentBasepoint: PaymentBasepoint) + (localIsFunder: bool) + (localDustLimit: Money) + (localRevocationPubKey: RevocationPubKey) + (toLocalDelay: BlockHeightOffset16) + (localDelayedPaymentPubKey: DelayedPaymentPubKey) + (remotePaymentPubkey: PaymentPubKey) + (localHTLCPubKey: HtlcPubKey) + (remoteHTLCPubkey: HtlcPubKey) + (spec: CommitmentSpec) + (network: Network) + = let commitFee = commitTxFee localDustLimit spec + let (toLocalAmount, toRemoteAmount) = - if (localIsFunder) then - (spec.ToLocal.Satoshi |> Money.Satoshis) - commitFee, spec.ToRemote.Satoshi |> Money.Satoshis + if localIsFunder then + (spec.ToLocal.Satoshi |> Money.Satoshis) - commitFee, + spec.ToRemote.Satoshi |> Money.Satoshis else - (spec.ToLocal.Satoshi |> Money.Satoshis), (spec.ToRemote.Satoshi |> Money.Satoshis) - commitFee + (spec.ToLocal.Satoshi |> Money.Satoshis), + (spec.ToRemote.Satoshi |> Money.Satoshis) - commitFee let toLocalDelayedOutput_opt = if (toLocalAmount >= localDustLimit) then - Some (TxOut(toLocalAmount, (Scripts.toLocalDelayed(localRevocationPubKey) (toLocalDelay) (localDelayedPaymentPubKey)).WitHash.ScriptPubKey)) + Some( + TxOut( + toLocalAmount, + (Scripts.toLocalDelayed + (localRevocationPubKey) + (toLocalDelay) + (localDelayedPaymentPubKey)) + .WitHash + .ScriptPubKey + ) + ) else None + let toRemoteOutput_opt = - if (toRemoteAmount >= localDustLimit) then - Some(TxOut(toRemoteAmount, (remotePaymentPubkey.RawPubKey().WitHash.ScriptPubKey))) + if (toRemoteAmount >= localDustLimit) then + Some( + TxOut( + toRemoteAmount, + (remotePaymentPubkey + .RawPubKey() + .WitHash + .ScriptPubKey) + ) + ) else None let htlcOfferedOutputsWithMetadata = trimOfferedHTLCs (localDustLimit) (spec) |> List.map(fun htlc -> - let redeem = Scripts.htlcOffered (localHTLCPubKey) (remoteHTLCPubkey) localRevocationPubKey (htlc.PaymentHash) - (TxOut(htlc.Amount.ToMoney(), redeem.WitHash.ScriptPubKey)), Some htlc) + let redeem = + Scripts.htlcOffered + (localHTLCPubKey) + (remoteHTLCPubkey) + localRevocationPubKey + (htlc.PaymentHash) + + (TxOut(htlc.Amount.ToMoney(), redeem.WitHash.ScriptPubKey)), + Some htlc + ) + let htlcReceivedOutputsWithMetadata = - trimReceivedHTLCs(localDustLimit) (spec) + trimReceivedHTLCs (localDustLimit) (spec) |> List.map(fun htlc -> - let redeem = Scripts.htlcReceived (localHTLCPubKey) (remoteHTLCPubkey) (localRevocationPubKey) (htlc.PaymentHash) (htlc.CLTVExpiry.Value) - TxOut(htlc.Amount.ToMoney(), redeem.WitHash.ScriptPubKey), Some htlc) - + let redeem = + Scripts.htlcReceived + (localHTLCPubKey) + (remoteHTLCPubkey) + (localRevocationPubKey) + (htlc.PaymentHash) + (htlc.CLTVExpiry.Value) + + TxOut(htlc.Amount.ToMoney(), redeem.WitHash.ScriptPubKey), + Some htlc + ) + let obscuredCommitmentNumber = - commitmentNumber.Obscure localIsFunder localPaymentBasepoint remotePaymentBasepoint + commitmentNumber.Obscure + localIsFunder + localPaymentBasepoint + remotePaymentBasepoint + let sequence = obscuredCommitmentNumber.Sequence let lockTime = obscuredCommitmentNumber.LockTime @@ -414,55 +552,81 @@ module Transactions = .SetLockTime(lockTime) .AddCoin( inputInfo, - CoinOptions ( - Sequence = Nullable sequence - ) + CoinOptions(Sequence = Nullable sequence) ) + let txOuts = - ([toLocalDelayedOutput_opt; toRemoteOutput_opt] |> List.choose id |> List.map(fun x -> x, None)) + ([ + toLocalDelayedOutput_opt + toRemoteOutput_opt + ] + |> List.choose id + |> List.map(fun x -> x, None)) @ (htlcOfferedOutputsWithMetadata) - @ htlcReceivedOutputsWithMetadata + @ htlcReceivedOutputsWithMetadata + List.fold - ( - fun ((txb, outAmount): TransactionBuilder * Money) (txOut: TxOut) -> - txb.Send(txOut.ScriptPubKey, txOut.Value) |> ignore - (txb, outAmount + txOut.Value) + (fun ((txb, outAmount): TransactionBuilder * Money) (txOut: TxOut) -> + txb.Send(txOut.ScriptPubKey, txOut.Value) |> ignore + (txb, outAmount + txOut.Value) ) (txb, Money 0UL) (sortTxOut txOuts) let actualFee = inputInfo.Amount - outAmount - txb.SendFees(actualFee) - .BuildTransaction true + txb.SendFees(actualFee).BuildTransaction true let psbt = let p = PSBT.FromTransaction(tx, network) p.AddCoins(inputInfo) - { CommitTx.Value = psbt } + + { + CommitTx.Value = psbt + } - let private findScriptPubKeyIndex(tx: Transaction) (spk: Script) = - tx.Outputs |> List.ofSeq |> List.findIndex(fun o -> o.ScriptPubKey = spk) + let private findScriptPubKeyIndex (tx: Transaction) (spk: Script) = + tx.Outputs + |> List.ofSeq + |> List.findIndex(fun o -> o.ScriptPubKey = spk) - let checkTxFinalized (psbt: PSBT) (inputIndex: int) (additionalKnownSigs: (PubKey * TransactionSignature) seq): Result = - let checkTxFinalizedCore (psbt: PSBT): Result<_, _> = + let checkTxFinalized + (psbt: PSBT) + (inputIndex: int) + (additionalKnownSigs: seq<(PubKey * TransactionSignature)>) + : Result = + let checkTxFinalizedCore(psbt: PSBT) : Result<_, _> = match psbt.TryFinalize() with | false, e -> - (sprintf "failed to finalize psbt Errors: %A \n PSBTInput: %A \n base64 PSBT: %s" e psbt.Inputs.[inputIndex] (psbt.ToBase64())) + (sprintf + "failed to finalize psbt Errors: %A \n PSBTInput: %A \n base64 PSBT: %s" + e + psbt.Inputs.[inputIndex] + (psbt.ToBase64())) |> FailedToFinalizeScript |> Error - | true, _ -> - psbt.ExtractTransaction() |> FinalizedTx |> Ok + | true, _ -> psbt.ExtractTransaction() |> FinalizedTx |> Ok + match psbt.Inputs.[inputIndex].GetTxOut() with - | null -> failwithf "Bug: prevout does not exist in %d for psbt: %A" inputIndex (psbt) + | null -> + failwithf + "Bug: prevout does not exist in %d for psbt: %A" + inputIndex + (psbt) | _ -> - additionalKnownSigs |> Seq.iter (fun kv -> - psbt.Inputs.[inputIndex].PartialSigs.AddOrReplace(kv) + additionalKnownSigs + |> Seq.iter(fun kv -> + psbt.Inputs.[inputIndex] + .PartialSigs.AddOrReplace(kv) ) checkTxFinalizedCore psbt - let checkSigAndAdd (tx: ILightningTx) (signature: TransactionSignature) (pk: PubKey) = + let checkSigAndAdd + (tx: ILightningTx) + (signature: TransactionSignature) + (pk: PubKey) + = if (tx.Value.IsAllFinalized()) then Ok tx else @@ -470,15 +634,28 @@ module Transactions = let spentOutput = psbt.Inputs.[tx.WhichInput].GetTxOut() let scriptCode = psbt.Inputs.[tx.WhichInput].WitnessScript let globalTx = psbt.GetGlobalTransaction() - let checker = TransactionChecker(globalTx, tx.WhichInput, spentOutput) - let sigHash = checker.Transaction.GetSignatureHash(scriptCode, checker.Index, signature.SigHash, checker.SpentOutput, HashVersion.WitnessV0, checker.PrecomputedTransactionData); - if pk.Verify (sigHash, signature.Signature) then - tx.Value.Inputs.[tx.WhichInput].PartialSigs.AddOrReplace(pk, signature) + let checker = + TransactionChecker(globalTx, tx.WhichInput, spentOutput) + + let sigHash = + checker.Transaction.GetSignatureHash( + scriptCode, + checker.Index, + signature.SigHash, + checker.SpentOutput, + HashVersion.WitnessV0, + checker.PrecomputedTransactionData + ) + + if pk.Verify(sigHash, signature.Signature) then + tx.Value.Inputs.[tx.WhichInput] + .PartialSigs.AddOrReplace(pk, signature) + tx |> Ok else InvalidSignature signature |> Error - + /// Sign psbt inside ILightningTx and returns /// 1. Newly created signature /// 2. ILightningTx updated @@ -488,261 +665,401 @@ module Transactions = let psbt = tx.Value let psbtIn = psbt.Inputs.[tx.WhichInput] let coin = ScriptCoin(psbtIn.GetCoin(), psbtIn.WitnessScript) + let txIn = let globalTx = psbt.GetGlobalTransaction() + globalTx.Inputs.AsIndexedInputs() |> Seq.indexed |> Seq.find(fun (i, _txIn) -> i = tx.WhichInput) |> snd - let txSig = txIn.Sign(key, coin, SigningOptions (EnforceLowR = enforceLowR, SigHash = SigHash.All)) + + let txSig = + txIn.Sign( + key, + coin, + SigningOptions(EnforceLowR = enforceLowR, SigHash = SigHash.All) + ) + match checkSigAndAdd tx txSig key.PubKey with - | Ok txWithSig -> - (txSig, txWithSig) + | Ok txWithSig -> (txSig, txWithSig) | Error(InvalidSignature signature) -> - failwithf "Failed to check signature. (%A) This should never happen." signature + failwithf + "Failed to check signature. (%A) This should never happen." + signature | Error err -> failwithf "%A" err - let sign(tx, key) = signCore(tx, key, true) - let makeHTLCTimeoutTx (commitTx: Transaction) - (localDustLimit: Money) - (localRevocationPubKey: RevocationPubKey) - (toLocalDelay: BlockHeightOffset16) - (localDelayedPaymentPubKey: DelayedPaymentPubKey) - (localHTLCPubKey: HtlcPubKey) - (remoteHTLCPubKey: HtlcPubKey) - (feeratePerKw: FeeRatePerKw) - (htlc: UpdateAddHTLCMsg) - (network: Network) - = + let sign(tx, key) = + signCore(tx, key, true) + + let makeHTLCTimeoutTx + (commitTx: Transaction) + (localDustLimit: Money) + (localRevocationPubKey: RevocationPubKey) + (toLocalDelay: BlockHeightOffset16) + (localDelayedPaymentPubKey: DelayedPaymentPubKey) + (localHTLCPubKey: HtlcPubKey) + (remoteHTLCPubKey: HtlcPubKey) + (feeratePerKw: FeeRatePerKw) + (htlc: UpdateAddHTLCMsg) + (network: Network) + = let fee = feeratePerKw.CalculateFeeFromWeight(HTLC_TIMEOUT_WEIGHT) - let redeem = Scripts.htlcOffered(localHTLCPubKey) (remoteHTLCPubKey) (localRevocationPubKey) (htlc.PaymentHash) + + let redeem = + Scripts.htlcOffered + (localHTLCPubKey) + (remoteHTLCPubKey) + (localRevocationPubKey) + (htlc.PaymentHash) + let spk = redeem.WitHash.ScriptPubKey let spkIndex = findScriptPubKeyIndex commitTx spk let amount = htlc.Amount.ToMoney() - fee + if (amount < localDustLimit) then AmountBelowDustLimit amount |> Error else - let psbt = + let psbt = let txb = createDeterministicTransactionBuilder network - let indexedTxOut = commitTx.Outputs.AsIndexedOutputs().ElementAt(spkIndex) + + let indexedTxOut = + commitTx + .Outputs + .AsIndexedOutputs() + .ElementAt(spkIndex) + let scriptCoin = ScriptCoin(indexedTxOut, redeem) - let dest = Scripts.toLocalDelayed localRevocationPubKey toLocalDelay localDelayedPaymentPubKey + + let dest = + Scripts.toLocalDelayed + localRevocationPubKey + toLocalDelay + localDelayedPaymentPubKey // we have already done dust limit check above txb.DustPrevention <- false - let tx = txb.AddCoins(scriptCoin) - .Send(dest.WitHash, amount) - .SendFees(fee) - .SetLockTime(!> htlc.CLTVExpiry.Value) - .BuildTransaction(false) + + let tx = + txb + .AddCoins(scriptCoin) + .Send(dest.WitHash, amount) + .SendFees(fee) + .SetLockTime(!>htlc.CLTVExpiry.Value) + .BuildTransaction(false) + tx.Version <- 2u - - /// We must set 0 to sequence for HTLC-success/timeout (defined in bolt3) + for i in tx.Inputs do i.Sequence <- Sequence(0) - PSBT.FromTransaction(tx, network) + PSBT + .FromTransaction(tx, network) .AddCoins scriptCoin - { HTLCTimeoutTx.Value = psbt } |> Ok - - let makeHTLCSuccessTx (commitTx: Transaction) - (localDustLimit: Money) - (localRevocationPubKey: RevocationPubKey) - (toLocalDelay: BlockHeightOffset16) - (localDelayedPaymentPubKey: DelayedPaymentPubKey) - (localHTLCPubKey: HtlcPubKey) - (remoteHTLCPubKey: HtlcPubKey) - (feeratePerKw: FeeRatePerKw) - (htlc: UpdateAddHTLCMsg) - (network: Network) - = + + { + HTLCTimeoutTx.Value = psbt + } + |> Ok + + let makeHTLCSuccessTx + (commitTx: Transaction) + (localDustLimit: Money) + (localRevocationPubKey: RevocationPubKey) + (toLocalDelay: BlockHeightOffset16) + (localDelayedPaymentPubKey: DelayedPaymentPubKey) + (localHTLCPubKey: HtlcPubKey) + (remoteHTLCPubKey: HtlcPubKey) + (feeratePerKw: FeeRatePerKw) + (htlc: UpdateAddHTLCMsg) + (network: Network) + = let fee = feeratePerKw.CalculateFeeFromWeight(HTLC_SUCCESS_WEIGHT) - let redeem = Scripts.htlcReceived (localHTLCPubKey) (remoteHTLCPubKey) (localRevocationPubKey) (htlc.PaymentHash) (htlc.CLTVExpiry.Value) + + let redeem = + Scripts.htlcReceived + (localHTLCPubKey) + (remoteHTLCPubKey) + (localRevocationPubKey) + (htlc.PaymentHash) + (htlc.CLTVExpiry.Value) + let spk = redeem.WitHash.ScriptPubKey let spkIndex = findScriptPubKeyIndex commitTx spk let amount = htlc.Amount.ToMoney() - fee + if (amount < localDustLimit) then AmountBelowDustLimit amount |> Error else - let psbt = + let psbt = let txb = createDeterministicTransactionBuilder network + let scriptCoin = - let coin = commitTx.Outputs.AsIndexedOutputs().ElementAt(spkIndex) + let coin = + commitTx + .Outputs + .AsIndexedOutputs() + .ElementAt(spkIndex) + ScriptCoin(coin, redeem) - let dest = Scripts.toLocalDelayed localRevocationPubKey toLocalDelay localDelayedPaymentPubKey + + let dest = + Scripts.toLocalDelayed + localRevocationPubKey + toLocalDelay + localDelayedPaymentPubKey // we have already done dust limit check above txb.DustPrevention <- false - let tx = txb.AddCoins(scriptCoin) - .Send(dest.WitHash, amount) - .SendFees(fee) - .SetLockTime(!> 0u) - .BuildTransaction(false) + + let tx = + txb + .AddCoins(scriptCoin) + .Send(dest.WitHash, amount) + .SendFees(fee) + .SetLockTime(!> 0u) + .BuildTransaction(false) + tx.Version <- 2u - - /// We must set 0 to sequence for HTLC-success/timeout (defined in bolt3) + for i in tx.Inputs do i.Sequence <- Sequence(0) - PSBT.FromTransaction(tx, network) + PSBT + .FromTransaction(tx, network) .AddCoins scriptCoin - { HTLCSuccessTx.Value = psbt; PaymentHash = htlc.PaymentHash } |> Ok - - let makeHTLCTxs (commitTx: Transaction) - (localDustLimit: Money) - (localRevocationPubKey: RevocationPubKey) - (toLocalDelay) - (toLocalDelayedPaymentPubKey: DelayedPaymentPubKey) - (localHTLCPubKey) - (remoteHTLCPubKey) - (spec: CommitmentSpec) - (network: Network) - : Result<(HTLCTimeoutTx list) * (HTLCSuccessTx list), TransactionError list> = - let htlcTimeoutTxs = (trimOfferedHTLCs localDustLimit spec) - |> List.map(fun htlc -> makeHTLCTimeoutTx commitTx - localDustLimit - localRevocationPubKey - toLocalDelay - toLocalDelayedPaymentPubKey - localHTLCPubKey - remoteHTLCPubKey - spec.FeeRatePerKw - htlc - network - ) - |> List.sequenceResultA - - let htlcSuccessTxs = (trimReceivedHTLCs localDustLimit spec) - |> List.map(fun htlc -> makeHTLCSuccessTx commitTx - localDustLimit - localRevocationPubKey - toLocalDelay - toLocalDelayedPaymentPubKey - localHTLCPubKey - remoteHTLCPubKey - spec.FeeRatePerKw - htlc - network - ) - |> List.sequenceResultA + + { + HTLCSuccessTx.Value = psbt + PaymentHash = htlc.PaymentHash + } + |> Ok + + let makeHTLCTxs + (commitTx: Transaction) + (localDustLimit: Money) + (localRevocationPubKey: RevocationPubKey) + toLocalDelay + (toLocalDelayedPaymentPubKey: DelayedPaymentPubKey) + localHTLCPubKey + remoteHTLCPubKey + (spec: CommitmentSpec) + (network: Network) + : Result<(list) * (list), list> = + let htlcTimeoutTxs = + (trimOfferedHTLCs localDustLimit spec) + |> List.map(fun htlc -> + makeHTLCTimeoutTx + commitTx + localDustLimit + localRevocationPubKey + toLocalDelay + toLocalDelayedPaymentPubKey + localHTLCPubKey + remoteHTLCPubKey + spec.FeeRatePerKw + htlc + network + ) + |> List.sequenceResultA + + let htlcSuccessTxs = + (trimReceivedHTLCs localDustLimit spec) + |> List.map(fun htlc -> + makeHTLCSuccessTx + commitTx + localDustLimit + localRevocationPubKey + toLocalDelay + toLocalDelayedPaymentPubKey + localHTLCPubKey + remoteHTLCPubKey + spec.FeeRatePerKw + htlc + network + ) + |> List.sequenceResultA + (fun a b -> (a, b)) htlcTimeoutTxs <*> htlcSuccessTxs - let makeClaimHTLCSuccessTx (commitTx: Transaction) - (localDustLimit: Money) - (localHTLCPubKey: HtlcPubKey) - (remoteHTLCPubKey: HtlcPubKey) - (remoteRevocationPubKey: RevocationPubKey) - (localFinalScriptPubKey: Script) - (htlc: UpdateAddHTLCMsg) - (feeRatePerKw: FeeRatePerKw) - (network: Network) - : Result = + let makeClaimHTLCSuccessTx + (commitTx: Transaction) + (localDustLimit: Money) + (localHTLCPubKey: HtlcPubKey) + (remoteHTLCPubKey: HtlcPubKey) + (remoteRevocationPubKey: RevocationPubKey) + (localFinalScriptPubKey: Script) + (htlc: UpdateAddHTLCMsg) + (feeRatePerKw: FeeRatePerKw) + (network: Network) + : Result = let fee = feeRatePerKw.CalculateFeeFromWeight(CLAIM_HTLC_SUCCESS_WEIGHT) - let redeem = Scripts.htlcOffered(remoteHTLCPubKey) (localHTLCPubKey) (remoteRevocationPubKey) (htlc.PaymentHash) + + let redeem = + Scripts.htlcOffered + (remoteHTLCPubKey) + (localHTLCPubKey) + (remoteRevocationPubKey) + (htlc.PaymentHash) + let spk = redeem.WitHash.ScriptPubKey let spkIndex = findScriptPubKeyIndex commitTx spk let amount = htlc.Amount.ToMoney() - fee + if (amount < localDustLimit) then AmountBelowDustLimit amount |> Error else - let psbt = + let psbt = let txb = createDeterministicTransactionBuilder network - let coin = Coin(commitTx.Outputs.AsIndexedOutputs().ElementAt(spkIndex)) - let tx = txb.AddCoins(coin) - .Send(localFinalScriptPubKey, amount) - .SendFees(fee) - .SetLockTime(!> 0u) - .BuildTransaction(false) + + let coin = + Coin( + commitTx + .Outputs + .AsIndexedOutputs() + .ElementAt(spkIndex) + ) + + let tx = + txb + .AddCoins(coin) + .Send(localFinalScriptPubKey, amount) + .SendFees(fee) + .SetLockTime(!> 0u) + .BuildTransaction(false) + tx.Version <- 2u - tx.Inputs.[0].Sequence <- !> UINT32_MAX - PSBT.FromTransaction(tx, network) - .AddCoins(coin) + tx.Inputs.[0].Sequence <- !>UINT32_MAX + PSBT.FromTransaction(tx, network).AddCoins(coin) + psbt |> ClaimHTLCSuccessTx |> Ok - let makeClaimHTLCTimeoutTx (commitTx: Transaction) - (localDustLimit: Money) - (localHTLCPubKey: HtlcPubKey) - (remoteHTLCPubKey: HtlcPubKey) - (remoteRevocationPubKey: RevocationPubKey) - (localFinalScriptPubKey: Script) - (htlc: UpdateAddHTLCMsg) - (feeRatePerKw: FeeRatePerKw) - (network: Network) - : Result<_, _> = + let makeClaimHTLCTimeoutTx + (commitTx: Transaction) + (localDustLimit: Money) + (localHTLCPubKey: HtlcPubKey) + (remoteHTLCPubKey: HtlcPubKey) + (remoteRevocationPubKey: RevocationPubKey) + (localFinalScriptPubKey: Script) + (htlc: UpdateAddHTLCMsg) + (feeRatePerKw: FeeRatePerKw) + (network: Network) + : Result<_, _> = let fee = feeRatePerKw.CalculateFeeFromWeight(CLAIM_HTLC_TIMEOUT_WEIGHT) - let redeem = Scripts.htlcReceived remoteHTLCPubKey localHTLCPubKey remoteRevocationPubKey htlc.PaymentHash htlc.CLTVExpiry.Value + + let redeem = + Scripts.htlcReceived + remoteHTLCPubKey + localHTLCPubKey + remoteRevocationPubKey + htlc.PaymentHash + htlc.CLTVExpiry.Value + let spk = redeem.WitHash.ScriptPubKey let spkIndex = findScriptPubKeyIndex commitTx spk let amount = htlc.Amount.ToMoney() - fee + if (amount < localDustLimit) then AmountBelowDustLimit amount |> Error else - let psbt = - let coin = Coin(commitTx.Outputs.AsIndexedOutputs().ElementAt(spkIndex)) - let tx = (createDeterministicTransactionBuilder network) - .AddCoins(coin) - .Send(localFinalScriptPubKey, amount) - .SendFees(fee) - .SetLockTime(!> 0u) - .BuildTransaction(false) + let psbt = + let coin = + Coin( + commitTx + .Outputs + .AsIndexedOutputs() + .ElementAt(spkIndex) + ) + + let tx = + (createDeterministicTransactionBuilder network) + .AddCoins(coin) + .Send(localFinalScriptPubKey, amount) + .SendFees(fee) + .SetLockTime(!> 0u) + .BuildTransaction(false) + tx.Version <- 2u - tx.Inputs.[0].Sequence <- !> UINT32_MAX - PSBT.FromTransaction(tx, network) - .AddCoins(coin) + tx.Inputs.[0].Sequence <- !>UINT32_MAX + PSBT.FromTransaction(tx, network).AddCoins(coin) + psbt |> ClaimHTLCTimeoutTx |> Ok - let makeMainPenaltyTx (commitTx: Transaction) - (localDustLimit: Money) - (remoteRevocationKey: RevocationPubKey) - (localFinalDestination: IDestination) - (toRemoteDelay: BlockHeightOffset16) - (remoteDelayedPaymentPubKey: DelayedPaymentPubKey) - (feeRatePerKw: FeeRatePerKw) - (network: Network) - : Result = + let makeMainPenaltyTx + (commitTx: Transaction) + (localDustLimit: Money) + (remoteRevocationKey: RevocationPubKey) + (localFinalDestination: IDestination) + (toRemoteDelay: BlockHeightOffset16) + (remoteDelayedPaymentPubKey: DelayedPaymentPubKey) + (feeRatePerKw: FeeRatePerKw) + (network: Network) + : Result = let fee = feeRatePerKw.CalculateFeeFromWeight(MAIN_PENALTY_WEIGHT) - let redeem = Scripts.toLocalDelayed remoteRevocationKey toRemoteDelay remoteDelayedPaymentPubKey + + let redeem = + Scripts.toLocalDelayed + remoteRevocationKey + toRemoteDelay + remoteDelayedPaymentPubKey + let spk = redeem.WitHash.ScriptPubKey let spkIndex = findScriptPubKeyIndex commitTx spk - let outPut = commitTx.Outputs.AsIndexedOutputs().ElementAt(spkIndex) + + let outPut = + commitTx + .Outputs + .AsIndexedOutputs() + .ElementAt(spkIndex) + let amount = (outPut).TxOut.Value - fee + if (amount < localDustLimit) then AmountBelowDustLimit amount |> Error else - let psbt = + let psbt = let coin = Coin(outPut) let txb = createDeterministicTransactionBuilder network // we have already done dust limit check above txb.DustPrevention <- false - let tx = txb - .AddCoins(coin) - .Send(localFinalDestination, amount) - .SendFees(fee) - .SetLockTime(!> 0u) - .BuildTransaction(false) + + let tx = + txb + .AddCoins(coin) + .Send(localFinalDestination, amount) + .SendFees(fee) + .SetLockTime(!> 0u) + .BuildTransaction(false) + tx.Version <- 2u - tx.Inputs.[0].Sequence <- !> UINT32_MAX - PSBT.FromTransaction(tx, network) - .AddCoins(coin) + tx.Inputs.[0].Sequence <- !>UINT32_MAX + PSBT.FromTransaction(tx, network).AddCoins(coin) + psbt |> MainPenaltyTx |> Ok - - let makeHTLCPenaltyTx (_commitTx: Transaction) (_localDustLimit: Money): HTLCPenaltyTx = + + let makeHTLCPenaltyTx + (_commitTx: Transaction) + (_localDustLimit: Money) + : HTLCPenaltyTx = raise <| NotImplementedException() - let makeClosingTx (commitTxInput: ScriptCoin) - (localDestination: ShutdownScriptPubKey) - (remoteDestination: ShutdownScriptPubKey) - (localIsFunder: bool) - (dustLimit: Money) - (closingFee: Money) - (spec: CommitmentSpec) - (network: Network) - : Result = + let makeClosingTx + (commitTxInput: ScriptCoin) + (localDestination: ShutdownScriptPubKey) + (remoteDestination: ShutdownScriptPubKey) + (localIsFunder: bool) + (dustLimit: Money) + (closingFee: Money) + (spec: CommitmentSpec) + (network: Network) + : Result = if (not spec.OutgoingHTLCs.IsEmpty) || (not spec.IncomingHTLCs.IsEmpty) then - HTLCNotClean - ((spec.OutgoingHTLCs |> Map.toList |> List.map(fst)) @ (spec.IncomingHTLCs |> Map.toList |> List.map(fst))) + HTLCNotClean( + (spec.OutgoingHTLCs |> Map.toList |> List.map(fst)) + @ (spec.IncomingHTLCs |> Map.toList |> List.map(fst)) + ) |> Error else let toLocalAmount, toRemoteAmount = - if (localIsFunder) then + if localIsFunder then spec.ToLocal.ToMoney() - closingFee, spec.ToRemote.ToMoney() else spec.ToLocal.ToMoney(), spec.ToRemote.ToMoney() - closingFee @@ -750,22 +1067,37 @@ module Transactions = let outputs = seq { if toLocalAmount >= dustLimit then - yield TxOut(toLocalAmount, localDestination.ScriptPubKey()) + yield + TxOut( + toLocalAmount, + localDestination.ScriptPubKey() + ) + if toRemoteAmount >= dustLimit then - yield TxOut(toRemoteAmount, remoteDestination.ScriptPubKey()) + yield + TxOut( + toRemoteAmount, + remoteDestination.ScriptPubKey() + ) } |> Seq.sortWith TxOut.LexicographicCompare - let psbt = - let txb = (createDeterministicTransactionBuilder network) - .AddCoins(commitTxInput) - .SendFees(closingFee) - .SetLockTime(!> 0u) + + let psbt = + let txb = + (createDeterministicTransactionBuilder network) + .AddCoins(commitTxInput) + .SendFees(closingFee) + .SetLockTime(!> 0u) + for txOut in outputs do txb.Send(txOut.ScriptPubKey, txOut.Value) |> ignore + let tx = txb.BuildTransaction(false) tx.Version <- 2u - tx.Inputs.[0].Sequence <- !> UINT32_MAX - PSBT.FromTransaction(tx, network) + tx.Inputs.[0].Sequence <- !>UINT32_MAX + + PSBT + .FromTransaction(tx, network) .AddCoins(commitTxInput) - psbt |> ClosingTx |> Ok + psbt |> ClosingTx |> Ok diff --git a/src/DotNetLightning.Core/Utils/ChannelId.fs b/src/DotNetLightning.Core/Utils/ChannelId.fs index 76a0de5d4..6c330b829 100644 --- a/src/DotNetLightning.Core/Utils/ChannelId.fs +++ b/src/DotNetLightning.Core/Utils/ChannelId.fs @@ -2,8 +2,9 @@ namespace DotNetLightning.Utils open NBitcoin -type ChannelId = | ChannelId of uint256 with +type ChannelId = + | ChannelId of uint256 + member this.Value = let (ChannelId v) = this in v static member Zero = uint256.Zero |> ChannelId - diff --git a/src/DotNetLightning.Core/Utils/Config.fs b/src/DotNetLightning.Core/Utils/Config.fs index 93f84568a..180436db6 100644 --- a/src/DotNetLightning.Core/Utils/Config.fs +++ b/src/DotNetLightning.Core/Utils/Config.fs @@ -1,45 +1,46 @@ namespace DotNetLightning.Utils + open System open NBitcoin /// Optional Channel limits which are applied during channel creation. /// These limits are only applied to our counterparty's limits, not our own -type ChannelHandshakeLimits = { - MinFundingSatoshis: Money - MaxHTLCMinimumMSat: LNMoney - /// The remote node sets a limit on the maximum value of pending HTLCs to them at any given time - /// to limit their funds exposure to HTLCs. This allows you to set a minimum such value. - MinMaxHTLCValueInFlightMSat: LNMoney - /// The remote node will require we keep a certain amount in direct payment to ourselves at all - /// time, ensuring that we are able to be punished if we broadcast an old state. This allows to - /// you limit the amount which we will have to keep to ourselves (and cannot use for HTLCs). - MaxChannelReserveSatoshis: Money - /// The remote node sets a limit on the maximum number of pending HTLCs to them at any given - /// time. This allows you to set a minimum such value. - MinMaxAcceptedHTLCs: uint16 - /// HTLCs below this amount plus HTLC transaction fees are not enforceable on-chain. - /// This settings allows you to set a minimum dust limit for their commitment TXs, - /// Defaults to 546 , or the current dust limit on the Bitcoin network. - MinDustLimitSatoshis: Money +type ChannelHandshakeLimits = + { + MinFundingSatoshis: Money + MaxHTLCMinimumMSat: LNMoney + /// The remote node sets a limit on the maximum value of pending HTLCs to them at any given time + /// to limit their funds exposure to HTLCs. This allows you to set a minimum such value. + MinMaxHTLCValueInFlightMSat: LNMoney + /// The remote node will require we keep a certain amount in direct payment to ourselves at all + /// time, ensuring that we are able to be punished if we broadcast an old state. This allows to + /// you limit the amount which we will have to keep to ourselves (and cannot use for HTLCs). + MaxChannelReserveSatoshis: Money + /// The remote node sets a limit on the maximum number of pending HTLCs to them at any given + /// time. This allows you to set a minimum such value. + MinMaxAcceptedHTLCs: uint16 + /// HTLCs below this amount plus HTLC transaction fees are not enforceable on-chain. + /// This settings allows you to set a minimum dust limit for their commitment TXs, + /// Defaults to 546 , or the current dust limit on the Bitcoin network. + MinDustLimitSatoshis: Money - /// Maximum allowed threshold above which outputs will not be generated in their commitment - /// Transactions. - /// HTLCs below this amount plus HTLC tx fees are not enforceable on-chain. - MaxDustLimitSatoshis: Money - /// before a channel is usable the funding TX will need to be confirmed by at least a certain number - /// of blocks, specified by the node which is not the funder (as the funder can assume they aren't - /// going to double-spend themselves). - /// This config allows you to set a limit on the maximum amount of time to wait. Defaults to 144 - /// blocks or roughly one day and only applies to outbound channels. - MaxMinimumDepth: BlockHeightOffset32 - /// Set to force the incoming channel to match our announced channel preference in ChannelConfig. - /// Defaults to true to make the default that no announced channels are possible (which is - /// appropriate for any nodes which are not online very reliably) - ForceChannelAnnouncementPreference: bool - MaxToSelfDelay: BlockHeightOffset16 - } + /// Maximum allowed threshold above which outputs will not be generated in their commitment + /// Transactions. + /// HTLCs below this amount plus HTLC tx fees are not enforceable on-chain. + MaxDustLimitSatoshis: Money + /// before a channel is usable the funding TX will need to be confirmed by at least a certain number + /// of blocks, specified by the node which is not the funder (as the funder can assume they aren't + /// going to double-spend themselves). + /// This config allows you to set a limit on the maximum amount of time to wait. Defaults to 144 + /// blocks or roughly one day and only applies to outbound channels. + MaxMinimumDepth: BlockHeightOffset32 + /// Set to force the incoming channel to match our announced channel preference in ChannelConfig. + /// Defaults to true to make the default that no announced channels are possible (which is + /// appropriate for any nodes which are not online very reliably) + ForceChannelAnnouncementPreference: bool + MaxToSelfDelay: BlockHeightOffset16 + } - with static member Zero = { MinFundingSatoshis = Money.Satoshis(1000m) @@ -57,11 +58,16 @@ type ChannelHandshakeLimits = { type RouterConfig() = member val RandomizeRouteSelection: bool = true with get, set - member val ChannelExcludeDuration: DateTimeOffset = Unchecked.defaultof with get, set - member val RouterBroadcastInterval: DateTimeOffset = Unchecked.defaultof with get, set + + member val ChannelExcludeDuration: DateTimeOffset = + Unchecked.defaultof with get, set + + member val RouterBroadcastInterval: DateTimeOffset = + Unchecked.defaultof with get, set + member val NetworkStatsRefreshInterval = Unchecked.defaultof member val RequestNodeAnnouncement = false - + member val ChannelRangeChunkSize = 0 member val SearchMaxRouteLength: int = 6 with get, set // max acceptable cltv expiry fo the payment (1008 ~ 1 week) @@ -78,4 +84,3 @@ type RouterConfig() = member val SearchRatioCLTV: float = 0.15 with get, set member val SearchRatioChannelAge: float = 0.35 with get, set member val SearchRatioChannelCapacity: float = 0.5 with get, set - diff --git a/src/DotNetLightning.Core/Utils/Errors.fs b/src/DotNetLightning.Core/Utils/Errors.fs index 568068971..d7956aacd 100644 --- a/src/DotNetLightning.Core/Utils/Errors.fs +++ b/src/DotNetLightning.Core/Utils/Errors.fs @@ -1,4 +1,5 @@ namespace DotNetLightning.Utils + open System /// Indicates an error on the client's part ( usually some variant of attempting to use too-low or @@ -20,17 +21,24 @@ type APIError = /// attempted action to fail. | MonitorUpdateFailed -and FeeRateTooHighContent = { Msg: string; FeeRate: FeeRatePerKw } +and FeeRateTooHighContent = + { + Msg: string + FeeRate: FeeRatePerKw + } /// TODO: rename? module OnionError = [] let BADONION = 0x8000us + [] let PERM = 0x4000us + [] let NODE = 0x2000us + [] let UPDATE = 0x1000us @@ -104,31 +112,77 @@ module OnionError = #if !NoDUsAsStructs [] #endif - type FailureCode = | FailureCode of uint16 with + type FailureCode = + | FailureCode of uint16 member this.Value = let (FailureCode v) = this in v + member this.GetOnionErrorDescription() = match this.Value with - | (INVALID_REALM) -> ("The realm byte was not understood by the processing node", "invalid_realm") - | (TEMPORARY_NODE_FAILURE) -> ("Node indicated temporary node failure", "temporary_node_failure") - | (PERMANENT_NODE_FAILURE) -> ("Node indicated permanent node failure", "permanent_node_failure") - | (REQUIRED_NODE_FEATURE_MISSING) -> ("Node indicated the required node feature is missing in the onion", "required_node_feature_missing") - | (INVALID_ONION_VERSION) -> ("Node indicated the version by is not understood", "invalid_onion_version") - | (INVALID_ONION_HMAC) -> ("Node indicated the HMAC of the onion is incorrect", "invalid_onion_hmac") - | (INVALID_ONION_KEY) -> ("Node indicated the ephemeral public keys is not parseable", "invalid_onion_key") - | (TEMPORARY_CHANNEL_FAILURE) -> ("Node indicated the outgoing channel is unable to handle the HTLC temporarily", "temporary_channel_failure") - | (PERMANENT_CHANNEL_FAILURE) -> ("Node indicated the outgoing channel is unable to handle the HTLC permanently", "permanent_channel_failure") - | (REQUIRED_CHANNEL_FEATURE_MISSING) -> ("Node indicated the required feature for the outgoing channel is not satisfied", "required_channel_feature_missing") - | (UNKNOWN_NEXT_PEER) -> ("Node indicated the outbound channel is not found for the specified short_channel_id in the onion packet", "unknown_next_peer") - | (AMOUNT_BELOW_MINIMUM) -> ("Node indicated the HTLC amount was below the required minimum for the outbound channel", "amount_below_minimum") - | (FEE_INSUFFICIENT) -> ("Node indicated the fee amount does not meet the required level", "fee_insufficient") - | (INOCCORRECT_CLTV_EXPIRY) -> ("Node indicated the cltv_expiry does not comply with the cltv_expiry_delta required by the outgoing channel", "incorrect_cltv_expiry") - | (EXPIRY_TOO_SOON) -> ("Node indicated the CLTV expiry too close to the current block height for safe handling", "expiry_too_soon") - | (UNKNOWN_PAYMENT_HASH) -> ("The final node indicated the payment hash is unknown or amount is incorrect", "incorrect_or_unknown_payment_details") - | (INCORRECT_PAYMENT_AMOUNT) -> ("The final node indicated the payment amount is incorrect", "incorrect_payment_amount") - | FINAL_EXPIRY_TOO_SOON -> ("The final node indicated the CLTV expiry is too close to the current block height for safe handling", "final_expiry_too_soon") - | (FINAL_INCORRECT_CLTV_EXPIRY) -> ("The final node indicated the CLTV expiry in the HTLC does not match the value in the onion", "final_incorrect_cltv_expiry") - | (FINAL_INCORRECT_HTLC_AMOUNT) -> ("The final node indicated the amount in the HTLC does not match the value in the onion", "final_incorrect_htlc_amount") - | (EXPIRY_TOO_FAR) -> ("Node indicated the CLTV expiry in the HTLC is too far in the future", "expiry_too_far") - | (CHANNEL_DISABLED) -> ("Node indicated the outbound channel has been disabled", "channel_disabled") + | (INVALID_REALM) -> + ("The realm byte was not understood by the processing node", + "invalid_realm") + | (TEMPORARY_NODE_FAILURE) -> + ("Node indicated temporary node failure", + "temporary_node_failure") + | (PERMANENT_NODE_FAILURE) -> + ("Node indicated permanent node failure", + "permanent_node_failure") + | (REQUIRED_NODE_FEATURE_MISSING) -> + ("Node indicated the required node feature is missing in the onion", + "required_node_feature_missing") + | (INVALID_ONION_VERSION) -> + ("Node indicated the version by is not understood", + "invalid_onion_version") + | (INVALID_ONION_HMAC) -> + ("Node indicated the HMAC of the onion is incorrect", + "invalid_onion_hmac") + | (INVALID_ONION_KEY) -> + ("Node indicated the ephemeral public keys is not parseable", + "invalid_onion_key") + | (TEMPORARY_CHANNEL_FAILURE) -> + ("Node indicated the outgoing channel is unable to handle the HTLC temporarily", + "temporary_channel_failure") + | (PERMANENT_CHANNEL_FAILURE) -> + ("Node indicated the outgoing channel is unable to handle the HTLC permanently", + "permanent_channel_failure") + | (REQUIRED_CHANNEL_FEATURE_MISSING) -> + ("Node indicated the required feature for the outgoing channel is not satisfied", + "required_channel_feature_missing") + | (UNKNOWN_NEXT_PEER) -> + ("Node indicated the outbound channel is not found for the specified short_channel_id in the onion packet", + "unknown_next_peer") + | (AMOUNT_BELOW_MINIMUM) -> + ("Node indicated the HTLC amount was below the required minimum for the outbound channel", + "amount_below_minimum") + | (FEE_INSUFFICIENT) -> + ("Node indicated the fee amount does not meet the required level", + "fee_insufficient") + | (INOCCORRECT_CLTV_EXPIRY) -> + ("Node indicated the cltv_expiry does not comply with the cltv_expiry_delta required by the outgoing channel", + "incorrect_cltv_expiry") + | (EXPIRY_TOO_SOON) -> + ("Node indicated the CLTV expiry too close to the current block height for safe handling", + "expiry_too_soon") + | (UNKNOWN_PAYMENT_HASH) -> + ("The final node indicated the payment hash is unknown or amount is incorrect", + "incorrect_or_unknown_payment_details") + | (INCORRECT_PAYMENT_AMOUNT) -> + ("The final node indicated the payment amount is incorrect", + "incorrect_payment_amount") + | FINAL_EXPIRY_TOO_SOON -> + ("The final node indicated the CLTV expiry is too close to the current block height for safe handling", + "final_expiry_too_soon") + | (FINAL_INCORRECT_CLTV_EXPIRY) -> + ("The final node indicated the CLTV expiry in the HTLC does not match the value in the onion", + "final_incorrect_cltv_expiry") + | (FINAL_INCORRECT_HTLC_AMOUNT) -> + ("The final node indicated the amount in the HTLC does not match the value in the onion", + "final_incorrect_htlc_amount") + | (EXPIRY_TOO_FAR) -> + ("Node indicated the CLTV expiry in the HTLC is too far in the future", + "expiry_too_far") + | (CHANNEL_DISABLED) -> + ("Node indicated the outbound channel has been disabled", + "channel_disabled") | _ -> ("Unknown", "") diff --git a/src/DotNetLightning.Core/Utils/Extensions.fs b/src/DotNetLightning.Core/Utils/Extensions.fs index 1bd72bac9..ee3272b10 100644 --- a/src/DotNetLightning.Core/Utils/Extensions.fs +++ b/src/DotNetLightning.Core/Utils/Extensions.fs @@ -12,83 +12,110 @@ open ResultUtils open ResultUtils.Portability module Dict = - let tryGetValue key (dict: IDictionary<_,_>)= - match dict.TryGetValue key with - | true, v -> Some v - | false, _ -> None + let tryGetValue key (dict: IDictionary<_, _>) = + match dict.TryGetValue key with + | true, v -> Some v + | false, _ -> None + type System.UInt64 with + member this.GetBytesBigEndian() = let d = BitConverter.GetBytes(this) - if BitConverter.IsLittleEndian then (d |> Array.rev) else d - static member FromBytesBigEndian(bytes8: array): uint64 = + if BitConverter.IsLittleEndian then + (d |> Array.rev) + else + d + + static member FromBytesBigEndian(bytes8: array) : uint64 = let bytes = if BitConverter.IsLittleEndian then Array.rev bytes8 else bytes8 + BitConverter.ToUInt64(bytes, 0) member this.ToVarInt() = if this < 0xfdUL then - [|uint8 this|] + [| uint8 this |] elif this < 0x10000UL then let buf = Array.zeroCreate(3) buf.[0] <- (0xfduy) - buf.[1] <- (byte (this >>> 8)) + buf.[1] <- (byte(this >>> 8)) buf.[2] <- byte this buf elif this < 0x100000000UL then let buf = Array.zeroCreate(5) buf.[0] <- (0xfeuy) - buf.[1] <- (byte (this >>> 24)) - buf.[2] <- (byte (this >>> 16)) - buf.[3] <- (byte (this >>> 8)) + buf.[1] <- (byte(this >>> 24)) + buf.[2] <- (byte(this >>> 16)) + buf.[3] <- (byte(this >>> 8)) buf.[4] <- (byte this) buf else let buf = Array.zeroCreate(9) buf.[0] <- (0xffuy) - buf.[1] <- (byte (this >>> 56)) - buf.[2] <- (byte (this >>> 48)) - buf.[3] <- (byte (this >>> 40)) - buf.[4] <- (byte (this >>> 32)) - buf.[5] <- (byte (this >>> 24)) - buf.[6] <- (byte (this >>> 16)) - buf.[7] <- (byte (this >>> 8)) + buf.[1] <- (byte(this >>> 56)) + buf.[2] <- (byte(this >>> 48)) + buf.[3] <- (byte(this >>> 40)) + buf.[4] <- (byte(this >>> 32)) + buf.[5] <- (byte(this >>> 24)) + buf.[6] <- (byte(this >>> 16)) + buf.[7] <- (byte(this >>> 8)) buf.[8] <- (byte this) buf - + type System.UInt32 with + member this.GetBytesBigEndian() = let d = BitConverter.GetBytes(this) - if BitConverter.IsLittleEndian then (d |> Array.rev) else d - static member FromBytesBigEndian(bytes4: array): uint32 = + if BitConverter.IsLittleEndian then + (d |> Array.rev) + else + d + + static member FromBytesBigEndian(bytes4: array) : uint32 = let bytes = if BitConverter.IsLittleEndian then Array.rev bytes4 else bytes4 + BitConverter.ToUInt32(bytes, 0) type System.UInt16 with + member this.GetBytesBigEndian() = let d = BitConverter.GetBytes(this) - if BitConverter.IsLittleEndian then (d |> Array.rev) else d - static member FromBytesBigEndian(value: byte[]) = + + if BitConverter.IsLittleEndian then + (d |> Array.rev) + else + d + + static member FromBytesBigEndian(value: array) = ((uint16 value.[0]) <<< 8 ||| (uint16 value.[1])) + type System.Int64 with - member this.ToVarInt() = (uint64 this).ToVarInt() -type System.Byte - with + + member this.ToVarInt() = + (uint64 this).ToVarInt() + +type System.Byte with + member a.FlipBit() = - ((a &&& 0x1uy) <<< 7) ||| ((a &&& 0x2uy) <<< 5) ||| - ((a &&& 0x4uy) <<< 3) ||| ((a &&& 0x8uy) <<< 1) ||| - ((a &&& 0x10uy) >>> 1) ||| ((a &&& 0x20uy) >>> 3) ||| - ((a &&& 0x40uy) >>> 5) ||| ((a &&& 0x80uy) >>> 7) - -[] + ((a &&& 0x1uy) <<< 7) + ||| ((a &&& 0x2uy) <<< 5) + ||| ((a &&& 0x4uy) <<< 3) + ||| ((a &&& 0x8uy) <<< 1) + ||| ((a &&& 0x10uy) >>> 1) + ||| ((a &&& 0x20uy) >>> 3) + ||| ((a &&& 0x40uy) >>> 5) + ||| ((a &&& 0x80uy) >>> 7) + +[] type BitArrayExtensions() = [] static member ToHex(this: #seq) = @@ -96,130 +123,183 @@ type BitArrayExtensions() = db.Append("0x") |> ignore this |> Seq.iter(fun b -> sprintf "%X" b |> db.Append |> ignore) db.ToString() - + [] - static member TryPopVarInt(this: byte array) = - let e b = sprintf "Decoded VarInt is not canonical %A" b |> Error + static member TryPopVarInt(this: array) = + let e b = + sprintf "Decoded VarInt is not canonical %A" b |> Error + let x = this.[0] + if x < 0xfduy then ((uint64 x), this.[1..]) |> Ok else if x = 0xfduy then - if (this.Length < 2) then e this else - let v = this.[1..2] |> UInt16.FromBytesBigEndian |> uint64 - if (v < 0xfdUL || 0x10000UL <= v) then e this else - (v, this.[3..]) |> Ok + if (this.Length < 2) then + e this + else + let v = this.[1..2] |> UInt16.FromBytesBigEndian |> uint64 + + if (v < 0xfdUL || 0x10000UL <= v) then + e this + else + (v, this.[3..]) |> Ok else if x = 0xfeuy then - if (this.Length < 4) then e this else - let v = this.[1..4] |> fun x -> NBitcoin.Utils.ToUInt32(x, false) |> uint64 - if (v < 0x10000UL || 0x100000000UL <= v) then e this else - (v, this.[5..]) |> Ok + if (this.Length < 4) then + e this + else + let v = + this.[1..4] + |> fun x -> NBitcoin.Utils.ToUInt32(x, false) |> uint64 + + if (v < 0x10000UL || 0x100000000UL <= v) then + e this + else + (v, this.[5..]) |> Ok + else if (this.Length < 8) then + e this else - if (this.Length < 8) then e this else let v = this.[1..8] |> fun x -> NBitcoin.Utils.ToUInt64(x, false) - if (v < 0x100000000UL) then e this else - (v, this.[9..]) |> Ok - + + if (v < 0x100000000UL) then + e this + else + (v, this.[9..]) |> Ok + type System.Collections.BitArray with + member this.ToByteArray() = - if this.Length = 0 then [||] else - - let leadingZeros = - match (Seq.tryFindIndex (fun b -> b) (Seq.cast this)) with - | Some i -> i - | None -> this.Length - let trueLength = this.Length - leadingZeros - let desiredLength = ((trueLength + 7) / 8) * 8 - let difference = desiredLength - this.Length - let bitArray = - if difference < 0 then - // Drop zeroes from the front of the array until we have a multiple of 8 bits - let shortenedBitArray = BitArray(desiredLength) - for i in 0 .. (desiredLength - 1) do - shortenedBitArray.[i] <- this.[i - difference] - shortenedBitArray - else if difference > 0 then - // Push zeroes to the front of the array until we have a multiple of 8 bits - let lengthenedBitArray = BitArray(desiredLength) - for i in 0 .. (this.Length - 1) do - lengthenedBitArray.[i + difference] <- this.[i] - lengthenedBitArray - else - this - - // Copy the bit array to a byte array, then flip the bytes. - let byteArray: byte[] = Array.zeroCreate(desiredLength / 8) - bitArray.CopyTo(byteArray, 0) - byteArray |> Array.map (fun b -> b.FlipBit()) - - static member From5BitEncoding(b: byte[]) = + if this.Length = 0 then + [||] + else + + let leadingZeros = + match (Seq.tryFindIndex (fun b -> b) (Seq.cast this)) with + | Some i -> i + | None -> this.Length + + let trueLength = this.Length - leadingZeros + let desiredLength = ((trueLength + 7) / 8) * 8 + let difference = desiredLength - this.Length + + let bitArray = + if difference < 0 then + // Drop zeroes from the front of the array until we have a multiple of 8 bits + let shortenedBitArray = BitArray(desiredLength) + + for i in 0 .. (desiredLength - 1) do + shortenedBitArray.[i] <- this.[i - difference] + + shortenedBitArray + else if difference > 0 then + // Push zeroes to the front of the array until we have a multiple of 8 bits + let lengthenedBitArray = BitArray(desiredLength) + + for i in 0 .. (this.Length - 1) do + lengthenedBitArray.[i + difference] <- this.[i] + + lengthenedBitArray + else + this + + // Copy the bit array to a byte array, then flip the bytes. + let byteArray: array = Array.zeroCreate(desiredLength / 8) + bitArray.CopyTo(byteArray, 0) + byteArray |> Array.map(fun b -> b.FlipBit()) + + static member From5BitEncoding(b: array) = let bitArray = System.Collections.BitArray(b.Length * 5) - for di in 0..(b.Length - 1) do + + for di in 0 .. (b.Length - 1) do bitArray.Set(di * 5 + 0, ((b.[di] >>> 4) &&& 0x01uy) = 1uy) bitArray.Set(di * 5 + 1, ((b.[di] >>> 3) &&& 0x01uy) = 1uy) bitArray.Set(di * 5 + 2, ((b.[di] >>> 2) &&& 0x01uy) = 1uy) bitArray.Set(di * 5 + 3, ((b.[di] >>> 1) &&& 0x01uy) = 1uy) bitArray.Set(di * 5 + 4, ((b.[di] >>> 0) &&& 0x01uy) = 1uy) + bitArray - + static member Concat(ba: #seq) = - let a = ba |> Seq.map(fun b -> b.Cast()) |> Seq.concat |> Seq.toArray + let a = + ba |> Seq.map(fun b -> b.Cast()) |> Seq.concat |> Seq.toArray + BitArray(a) + static member FromUInt32(d: uint32) = let b = d.GetBytesBigEndian() BitArray.From5BitEncoding(b) - + member this.Reverse() = - let boolArray: array = Array.ofSeq (Seq.cast this) + let boolArray: array = Array.ofSeq(Seq.cast this) Array.Reverse boolArray BitArray(boolArray) member this.PrintBits() = let sb = StringBuilder() + for b in this do - (if b then "1" else "0") |> sb.Append |> ignore + (if b then + "1" + else + "0") + |> sb.Append + |> ignore + sb.ToString() - + static member FromInt64(value: int64) = let mutable v = value let array = Array.zeroCreate 64 - for i in 0..(64 - 1) do + + for i in 0 .. (64 - 1) do array.[i] <- (v &&& 1L) = 1L v <- v >>> 1 + BitArray(array |> Array.rev) + static member TryParse(str: string) = let mutable str = str.Trim().Clone() :?> string + if str.StartsWith("0b", StringComparison.OrdinalIgnoreCase) then str <- str.Substring("0b".Length) + let array = Array.zeroCreate(str.Length) let mutable hasFunnyChar = -1 - for i in 0..str.Length - 1 do - if hasFunnyChar <> -1 then () else - if str.[i] = '0' then array.[i] <- false else - if str.[i] = '1' then array.[i] <- true else - hasFunnyChar <- i + + for i in 0 .. str.Length - 1 do + if hasFunnyChar <> -1 then + () + else if str.[i] = '0' then + array.[i] <- false + else if str.[i] = '1' then + array.[i] <- true + else + hasFunnyChar <- i + if hasFunnyChar <> -1 then - sprintf "Failed to parse BitArray! it must have only '0' or '1' but we found %A" str.[hasFunnyChar] + sprintf + "Failed to parse BitArray! it must have only '0' or '1' but we found %A" + str.[hasFunnyChar] |> Error else BitArray(array) |> Ok - + /// This flips bits for each byte before passing to the BitArray constructor. /// This is necessary for representing bolt 9 feature bits as BitArray - static member FromBytes(ba: byte[]) = + static member FromBytes(ba: array) = ba |> Array.map(fun b -> b.FlipBit()) |> BitArray - + member this.ToHex() = this.ToByteArray().ToHex() - -[] + +[] type DictionaryExtensions() = [] static member TryGetValueOption(this: IDictionary<_, _>, key) = Dict.tryGetValue key this - + module Seq = - let skipSafe num = - Seq.zip (Seq.initInfinite id) - >> Seq.skipWhile (fun (i, _) -> i < num) + let skipSafe num = + Seq.zip(Seq.initInfinite id) + >> Seq.skipWhile(fun (i, _) -> i < num) >> Seq.map snd diff --git a/src/DotNetLightning.Core/Utils/Keys.fs b/src/DotNetLightning.Core/Utils/Keys.fs index 78345743a..d7ebf0112 100644 --- a/src/DotNetLightning.Core/Utils/Keys.fs +++ b/src/DotNetLightning.Core/Utils/Keys.fs @@ -8,12 +8,12 @@ open ResultUtils.Portability type FundingPubKey = | FundingPubKey of PubKey - with - member this.RawPubKey(): PubKey = + + member this.RawPubKey() : PubKey = let (FundingPubKey pubKey) = this pubKey - member this.ToBytes(): array = + member this.ToBytes() : array = this.RawPubKey().ToBytes() override this.ToString() = @@ -21,22 +21,22 @@ type FundingPubKey = type FundingPrivKey = | FundingPrivKey of Key - with - member this.RawKey(): Key = + + member this.RawKey() : Key = let (FundingPrivKey key) = this key - member this.FundingPubKey(): FundingPubKey = + member this.FundingPubKey() : FundingPubKey = FundingPubKey(this.RawKey().PubKey) type RevocationBasepoint = | RevocationBasepoint of PubKey - with - member this.RawPubKey(): PubKey = + + member this.RawPubKey() : PubKey = let (RevocationBasepoint pubKey) = this pubKey - member this.ToBytes(): array = + member this.ToBytes() : array = this.RawPubKey().ToBytes() override this.ToString() = @@ -44,28 +44,28 @@ type RevocationBasepoint = type RevocationBasepointSecret = | RevocationBasepointSecret of Key - with - member this.RawKey(): Key = + + member this.RawKey() : Key = let (RevocationBasepointSecret key) = this key - member this.RevocationBasepoint(): RevocationBasepoint = + member this.RevocationBasepoint() : RevocationBasepoint = RevocationBasepoint(this.RawKey().PubKey) - member this.ToBytes(): array = + member this.ToBytes() : array = this.RawKey().ToBytes() type RevocationPubKey = | RevocationPubKey of PubKey - with - member this.RawPubKey(): PubKey = + + member this.RawPubKey() : PubKey = let (RevocationPubKey pubKey) = this pubKey - static member FromBytes(bytes: array): RevocationPubKey = + static member FromBytes(bytes: array) : RevocationPubKey = RevocationPubKey <| PubKey bytes - member this.ToBytes(): array = + member this.ToBytes() : array = this.RawPubKey().ToBytes() override this.ToString() = @@ -73,22 +73,22 @@ type RevocationPubKey = type RevocationPrivKey = | RevocationPrivKey of Key - with - member this.RawKey(): Key = + + member this.RawKey() : Key = let (RevocationPrivKey key) = this key - member this.ToBytes(): array = + member this.ToBytes() : array = this.RawKey().ToBytes() type PaymentBasepoint = | PaymentBasepoint of PubKey - with - member this.RawPubKey(): PubKey = + + member this.RawPubKey() : PubKey = let (PaymentBasepoint pubKey) = this pubKey - member this.ToBytes(): array = + member this.ToBytes() : array = this.RawPubKey().ToBytes() override this.ToString() = @@ -96,22 +96,22 @@ type PaymentBasepoint = type PaymentBasepointSecret = | PaymentBasepointSecret of Key - with - member this.RawKey(): Key = + + member this.RawKey() : Key = let (PaymentBasepointSecret key) = this key - member this.PaymentBasepoint(): PaymentBasepoint = + member this.PaymentBasepoint() : PaymentBasepoint = PaymentBasepoint(this.RawKey().PubKey) type PaymentPubKey = | PaymentPubKey of PubKey - with - member this.RawPubKey(): PubKey = + + member this.RawPubKey() : PubKey = let (PaymentPubKey pubKey) = this pubKey - member this.ToBytes(): array = + member this.ToBytes() : array = this.RawPubKey().ToBytes() override this.ToString() = @@ -119,22 +119,22 @@ type PaymentPubKey = type PaymentPrivKey = | PaymentPrivKey of Key - with - member this.RawKey(): Key = + + member this.RawKey() : Key = let (PaymentPrivKey key) = this key - member this.PaymentPubKey(): PaymentPubKey = + member this.PaymentPubKey() : PaymentPubKey = PaymentPubKey <| this.RawKey().PubKey type DelayedPaymentBasepoint = | DelayedPaymentBasepoint of PubKey - with - member this.RawPubKey(): PubKey = + + member this.RawPubKey() : PubKey = let (DelayedPaymentBasepoint pubKey) = this pubKey - member this.ToBytes(): array = + member this.ToBytes() : array = this.RawPubKey().ToBytes() override this.ToString() = @@ -142,25 +142,25 @@ type DelayedPaymentBasepoint = type DelayedPaymentBasepointSecret = | DelayedPaymentBasepointSecret of Key - with - member this.RawKey(): Key = + + member this.RawKey() : Key = let (DelayedPaymentBasepointSecret key) = this key - member this.DelayedPaymentBasepoint(): DelayedPaymentBasepoint = + member this.DelayedPaymentBasepoint() : DelayedPaymentBasepoint = DelayedPaymentBasepoint(this.RawKey().PubKey) type DelayedPaymentPubKey = | DelayedPaymentPubKey of PubKey - with - member this.RawPubKey(): PubKey = + + member this.RawPubKey() : PubKey = let (DelayedPaymentPubKey pubKey) = this pubKey - static member FromBytes(bytes: array): DelayedPaymentPubKey = + static member FromBytes(bytes: array) : DelayedPaymentPubKey = DelayedPaymentPubKey <| PubKey bytes - member this.ToBytes(): array = + member this.ToBytes() : array = this.RawPubKey().ToBytes() override this.ToString() = @@ -168,22 +168,22 @@ type DelayedPaymentPubKey = type DelayedPaymentPrivKey = | DelayedPaymentPrivKey of Key - with - member this.RawKey(): Key = + + member this.RawKey() : Key = let (DelayedPaymentPrivKey key) = this key - member this.DelayedPaymentPubKey(): DelayedPaymentPubKey = + member this.DelayedPaymentPubKey() : DelayedPaymentPubKey = DelayedPaymentPubKey <| this.RawKey().PubKey type HtlcBasepoint = | HtlcBasepoint of PubKey - with - member this.RawPubKey(): PubKey = + + member this.RawPubKey() : PubKey = let (HtlcBasepoint pubKey) = this pubKey - member this.ToBytes(): array = + member this.ToBytes() : array = this.RawPubKey().ToBytes() override this.ToString() = @@ -191,22 +191,22 @@ type HtlcBasepoint = type HtlcBasepointSecret = | HtlcBasepointSecret of Key - with - member this.RawKey(): Key = + + member this.RawKey() : Key = let (HtlcBasepointSecret key) = this key - member this.HtlcBasepoint(): HtlcBasepoint = + member this.HtlcBasepoint() : HtlcBasepoint = HtlcBasepoint(this.RawKey().PubKey) type HtlcPubKey = | HtlcPubKey of PubKey - with - member this.RawPubKey(): PubKey = + + member this.RawPubKey() : PubKey = let (HtlcPubKey pubKey) = this pubKey - member this.ToBytes(): array = + member this.ToBytes() : array = this.RawPubKey().ToBytes() override this.ToString() = @@ -214,53 +214,55 @@ type HtlcPubKey = type HtlcPrivKey = | HtlcPrivKey of Key - with - member this.RawKey(): Key = + + member this.RawKey() : Key = let (HtlcPrivKey key) = this key - member this.HtlcPubKey(): HtlcPubKey = + member this.HtlcPubKey() : HtlcPubKey = HtlcPubKey <| this.RawKey().PubKey type NodeSecret = | NodeSecret of Key - with - member this.RawKey(): Key = + + member this.RawKey() : Key = let (NodeSecret key) = this key - member this.NodeId(): NodeId = - NodeId (this.RawKey().PubKey) + member this.NodeId() : NodeId = + NodeId(this.RawKey().PubKey) /// In usual operation we should not hold secrets on memory. So only hold pubkey -type ChannelPubKeys = { - FundingPubKey: FundingPubKey - RevocationBasepoint: RevocationBasepoint - PaymentBasepoint: PaymentBasepoint - DelayedPaymentBasepoint: DelayedPaymentBasepoint - HtlcBasepoint: HtlcBasepoint -} - -type CommitmentPubKeys = { - RevocationPubKey: RevocationPubKey - PaymentPubKey: PaymentPubKey - DelayedPaymentPubKey: DelayedPaymentPubKey - HtlcPubKey: HtlcPubKey -} +type ChannelPubKeys = + { + FundingPubKey: FundingPubKey + RevocationBasepoint: RevocationBasepoint + PaymentBasepoint: PaymentBasepoint + DelayedPaymentBasepoint: DelayedPaymentBasepoint + HtlcBasepoint: HtlcBasepoint + } + +type CommitmentPubKeys = + { + RevocationPubKey: RevocationPubKey + PaymentPubKey: PaymentPubKey + DelayedPaymentPubKey: DelayedPaymentPubKey + HtlcPubKey: HtlcPubKey + } type PerCommitmentPoint = | PerCommitmentPoint of PubKey - with - member this.RawPubKey(): PubKey = + + member this.RawPubKey() : PubKey = let (PerCommitmentPoint pubKey) = this pubKey static member BytesLength: int = PubKey.BytesLength - static member FromBytes(bytes: array): PerCommitmentPoint = + static member FromBytes(bytes: array) : PerCommitmentPoint = PerCommitmentPoint <| PubKey bytes - member this.ToBytes(): array = + member this.ToBytes() : array = this.RawPubKey().ToBytes() #if !NoDUsAsStructs @@ -268,13 +270,16 @@ type PerCommitmentPoint = #endif type CommitmentNumber = | CommitmentNumber of UInt48 - with + member this.Index() = let (CommitmentNumber index) = this index override this.ToString() = - sprintf "%012x (#%i)" (this.Index().UInt64) (UInt48.MaxValue - this.Index()).UInt64 + sprintf + "%012x (#%i)" + (this.Index().UInt64) + (UInt48.MaxValue - this.Index()).UInt64 static member LastCommitment: CommitmentNumber = CommitmentNumber UInt48.Zero @@ -282,10 +287,10 @@ type CommitmentNumber = static member FirstCommitment: CommitmentNumber = CommitmentNumber UInt48.MaxValue - member this.PreviousCommitment(): CommitmentNumber = + member this.PreviousCommitment() : CommitmentNumber = CommitmentNumber(this.Index() + UInt48.One) - member this.NextCommitment(): CommitmentNumber = + member this.NextCommitment() : CommitmentNumber = CommitmentNumber(this.Index() - UInt48.One) #if !NoDUsAsStructs @@ -293,8 +298,8 @@ type CommitmentNumber = #endif type ObscuredCommitmentNumber = | ObscuredCommitmentNumber of UInt48 - with - member this.ObscuredIndex(): UInt48 = + + member this.ObscuredIndex() : UInt48 = let (ObscuredCommitmentNumber obscuredIndex) = this obscuredIndex @@ -303,44 +308,48 @@ type ObscuredCommitmentNumber = type PerCommitmentSecret = | PerCommitmentSecret of Key - with - member this.RawKey(): Key = + + member this.RawKey() : Key = let (PerCommitmentSecret key) = this key static member BytesLength: int = Key.BytesLength - static member FromBytes(bytes: array): PerCommitmentSecret = + static member FromBytes(bytes: array) : PerCommitmentSecret = PerCommitmentSecret <| new Key(bytes) - member this.ToBytes(): array = + member this.ToBytes() : array = this.RawKey().ToBytes() - member this.PerCommitmentPoint(): PerCommitmentPoint = + member this.PerCommitmentPoint() : PerCommitmentPoint = PerCommitmentPoint <| this.RawKey().PubKey type CommitmentSeed = | CommitmentSeed of PerCommitmentSecret - with + member this.LastPerCommitmentSecret() = let (CommitmentSeed lastPerCommitmentSecret) = this lastPerCommitmentSecret /// Set of lightning keys needed to operate a channel as describe in BOLT 3 -type ChannelPrivKeys = { - FundingPrivKey: FundingPrivKey - RevocationBasepointSecret: RevocationBasepointSecret - PaymentBasepointSecret: PaymentBasepointSecret - DelayedPaymentBasepointSecret: DelayedPaymentBasepointSecret - HtlcBasepointSecret: HtlcBasepointSecret - CommitmentSeed: CommitmentSeed -} with - member this.ToChannelPubKeys(): ChannelPubKeys = +type ChannelPrivKeys = + { + FundingPrivKey: FundingPrivKey + RevocationBasepointSecret: RevocationBasepointSecret + PaymentBasepointSecret: PaymentBasepointSecret + DelayedPaymentBasepointSecret: DelayedPaymentBasepointSecret + HtlcBasepointSecret: HtlcBasepointSecret + CommitmentSeed: CommitmentSeed + } + + member this.ToChannelPubKeys() : ChannelPubKeys = { FundingPubKey = this.FundingPrivKey.FundingPubKey() - RevocationBasepoint = this.RevocationBasepointSecret.RevocationBasepoint() + RevocationBasepoint = + this.RevocationBasepointSecret.RevocationBasepoint() PaymentBasepoint = this.PaymentBasepointSecret.PaymentBasepoint() - DelayedPaymentBasepoint = this.DelayedPaymentBasepointSecret.DelayedPaymentBasepoint() + DelayedPaymentBasepoint = + this.DelayedPaymentBasepointSecret.DelayedPaymentBasepoint() HtlcBasepoint = this.HtlcBasepointSecret.HtlcBasepoint() } @@ -350,14 +359,13 @@ type ChannelPrivKeys = { /// index used to derive the channel's master key. type NodeMasterPrivKey = | NodeMasterPrivKey of ExtKey - with - member this.RawExtKey(): ExtKey = + + member this.RawExtKey() : ExtKey = let (NodeMasterPrivKey extKey) = this extKey - member this.NodeSecret(): NodeSecret = - NodeSecret (this.RawExtKey().PrivateKey) + member this.NodeSecret() : NodeSecret = + NodeSecret(this.RawExtKey().PrivateKey) - member this.NodeId(): NodeId = + member this.NodeId() : NodeId = this.NodeSecret().NodeId() - diff --git a/src/DotNetLightning.Core/Utils/LNMoney.fs b/src/DotNetLightning.Core/Utils/LNMoney.fs index 9ecdfe80b..992582351 100644 --- a/src/DotNetLightning.Core/Utils/LNMoney.fs +++ b/src/DotNetLightning.Core/Utils/LNMoney.fs @@ -1,4 +1,5 @@ namespace DotNetLightning.Utils + open System open System.Globalization open NBitcoin @@ -23,16 +24,23 @@ type LNMoneyUnit = #if !NoDUsAsStructs [] #endif -type LNMoney = | LNMoney of int64 with +type LNMoney = + | LNMoney of int64 static member private BitcoinStyle = - NumberStyles.AllowLeadingWhite ||| NumberStyles.AllowTrailingWhite ||| - NumberStyles.AllowLeadingSign ||| NumberStyles.AllowDecimalPoint + NumberStyles.AllowLeadingWhite + ||| NumberStyles.AllowTrailingWhite + ||| NumberStyles.AllowLeadingSign + ||| NumberStyles.AllowDecimalPoint // --- constructors ----- static member private CheckMoneyUnit(v: LNMoneyUnit, paramName: string) = let typeOfMoneyUnit = typeof - if not (Enum.IsDefined(typeOfMoneyUnit, v)) then - raise (ArgumentException(sprintf "Invalid value for MoneyUnit %s" paramName)) + + if not(Enum.IsDefined(typeOfMoneyUnit, v)) then + raise + <| ArgumentException( + sprintf "Invalid value for MoneyUnit %s" paramName + ) static member private FromUnit(amount: decimal, lnUnit: LNMoneyUnit) = LNMoney.CheckMoneyUnit(lnUnit, "unit") |> ignore @@ -41,15 +49,21 @@ type LNMoney = | LNMoney of int64 with static member Coins(coins: decimal) = - LNMoney.FromUnit(coins * (decimal LNMoneyUnit.BTC), LNMoneyUnit.MilliSatoshi) + LNMoney.FromUnit( + coins * (decimal LNMoneyUnit.BTC), + LNMoneyUnit.MilliSatoshi + ) static member Satoshis(satoshis: decimal) = - LNMoney.FromUnit(satoshis * (decimal LNMoneyUnit.Satoshi), LNMoneyUnit.MilliSatoshi) + LNMoney.FromUnit( + satoshis * (decimal LNMoneyUnit.Satoshi), + LNMoneyUnit.MilliSatoshi + ) static member Satoshis(sats: int64) = LNMoney.MilliSatoshis(Checked.op_Multiply 1000L sats) - static member inline Satoshis(sats) = + static member inline Satoshis sats = LNMoney.Satoshis(int64 sats) static member Satoshis(sats: uint64) = @@ -58,7 +72,7 @@ type LNMoney = | LNMoney of int64 with static member MilliSatoshis(sats: int64) = LNMoney(sats) - static member inline MilliSatoshis(sats) = + static member inline MilliSatoshis sats = LNMoney(int64 sats) static member MilliSatoshis(sats: uint64) = @@ -66,56 +80,111 @@ type LNMoney = | LNMoney of int64 with static member Zero = LNMoney(0L) static member One = LNMoney(1L) + static member TryParse(bitcoin: string, result: outref) = - match Decimal.TryParse(bitcoin, LNMoney.BitcoinStyle, CultureInfo.InvariantCulture) with + match + Decimal.TryParse + ( + bitcoin, + LNMoney.BitcoinStyle, + CultureInfo.InvariantCulture + ) + with | false, _ -> false | true, v -> try result <- LNMoney.FromUnit(v, LNMoneyUnit.BTC) true with - | :? OverflowException -> false + | :? OverflowException -> false + static member Parse(bitcoin: string) = match LNMoney.TryParse(bitcoin) with | true, v -> v - | _ -> raise (FormatException("Impossible to parse the string in a bitcoin amount")) + | _ -> + raise + <| FormatException( + "Impossible to parse the string in a bitcoin amount" + ) // -------- Arithmetic operations - static member (+) (LNMoney a, LNMoney b) = LNMoney(a + b) - static member (-) (LNMoney a, LNMoney b) = LNMoney(a - b) - static member (*) (LNMoney a, LNMoney b) = LNMoney(a * b) - static member (/) (LNMoney a, LNMoney b) = LNMoney(a / b) - static member inline (/) (LNMoney a, b) = LNMoney(a / (int64 b)) - static member inline (+) (LNMoney a, b) = LNMoney(a + (int64 b)) - static member inline (-) (LNMoney a, b) = LNMoney(a - (int64 b)) - static member inline (*) (LNMoney a, b) = LNMoney(a * (int64 b)) - static member Max(LNMoney a, LNMoney b) = if a >= b then LNMoney a else LNMoney b - static member Min(LNMoney a, LNMoney b) = if a <= b then LNMoney a else LNMoney b - + static member (+)(LNMoney a, LNMoney b) = + LNMoney(a + b) + + static member (-)(LNMoney a, LNMoney b) = + LNMoney(a - b) + + static member (*)(LNMoney a, LNMoney b) = + LNMoney(a * b) + + static member (/)(LNMoney a, LNMoney b) = + LNMoney(a / b) + + static member inline (/)(LNMoney a, b) = + LNMoney(a / (int64 b)) + + static member inline (+)(LNMoney a, b) = + LNMoney(a + (int64 b)) + + static member inline (-)(LNMoney a, b) = + LNMoney(a - (int64 b)) + + static member inline (*)(LNMoney a, b) = + LNMoney(a * (int64 b)) + + static member Max(LNMoney a, LNMoney b) = + if a >= b then + LNMoney a + else + LNMoney b + + static member Min(LNMoney a, LNMoney b) = + if a <= b then + LNMoney a + else + LNMoney b + static member MaxValue = let maxSatoshis = 21000000UL * (uint64 Money.COIN) LNMoney.Satoshis maxSatoshis - static member op_Implicit (money: Money) = LNMoney.Satoshis(money.Satoshi) + static member op_Implicit(money: Money) = + LNMoney.Satoshis(money.Satoshi) // --------- Utilities member this.Abs() = - if this < LNMoney.Zero then LNMoney(-this.Value) else this + if this < LNMoney.Zero then + LNMoney(-this.Value) + else + this member this.MilliSatoshi = let (LNMoney v) = this in v member this.Satoshi = this.MilliSatoshi / 1000L member this.BTC = this.MilliSatoshi / (int64 LNMoneyUnit.BTC) member this.Value = this.MilliSatoshi - member this.ToMoney() = this.Satoshi |> Money - member this.Split(parts: int): LNMoney seq = + member this.ToMoney() = + this.Satoshi |> Money + + member this.Split(parts: int) : seq = if parts <= 0 then - raise (ArgumentOutOfRangeException("parts")) + raise <| ArgumentOutOfRangeException("parts") else let mutable remain = 0L let res = Math.DivRem(this.MilliSatoshi, int64 parts, &remain) + seq { - for _ in 0..(parts - 1) do - yield LNMoney.Satoshis (decimal (res + (if remain > 0L then 1L else 0L))) + for _ in 0 .. (parts - 1) do + yield + LNMoney.Satoshis( + decimal( + res + + (if remain > 0L then + 1L + else + 0L) + ) + ) + remain <- remain - 1L } diff --git a/src/DotNetLightning.Core/Utils/NBitcoinExtensions.fs b/src/DotNetLightning.Core/Utils/NBitcoinExtensions.fs index 3971b03ca..40c6ab3ea 100644 --- a/src/DotNetLightning.Core/Utils/NBitcoinExtensions.fs +++ b/src/DotNetLightning.Core/Utils/NBitcoinExtensions.fs @@ -8,26 +8,33 @@ open NBitcoin.Crypto [] module NBitcoinExtensions = module FeeRate = - let FeePerKWeight (feeRate: FeeRate) = + let FeePerKWeight(feeRate: FeeRate) = Money.Satoshis(feeRate.FeePerK.Satoshi / 4L) type NBitcoin.Transaction with - member this.GetTxId() = TxId (this.GetHash()) + + member this.GetTxId() = + TxId(this.GetHash()) type Money with - member this.ToLNMoney() = LNMoney.Satoshis(this.Satoshi) + + member this.ToLNMoney() = + LNMoney.Satoshis(this.Satoshi) type OutPoint with - member this.ToChannelId(): ChannelId = + + member this.ToChannelId() : ChannelId = let mutable res = this.Clone().Hash.ToBytes() - res.[30] <- res.[30] ^^^ (uint8 (this.N >>> 8) &&& 0xffuy) - res.[31] <- res.[31] ^^^ (uint8 (this.N >>> 0) &&& 0xffuy) - res |> uint256 |> ChannelId + res.[30] <- res.[30] ^^^ (uint8(this.N >>> 8) &&& 0xffuy) + res.[31] <- res.[31] ^^^ (uint8(this.N >>> 0) &&& 0xffuy) + res |> uint256 |> ChannelId type TxOut with - static member LexicographicCompare (txOut0: TxOut) - (txOut1: TxOut) - : int = + + static member LexicographicCompare + (txOut0: TxOut) + (txOut1: TxOut) + : int = if txOut0.Value < txOut1.Value then -1 elif txOut0.Value > txOut1.Value then @@ -35,7 +42,8 @@ module NBitcoinExtensions = else let script0 = txOut0.ScriptPubKey.ToBytes() let script1 = txOut1.ScriptPubKey.ToBytes() - let rec compare (index: int) = + + let rec compare(index: int) = if script0.Length = index && script1.Length = index then 0 elif script0.Length = index then @@ -47,30 +55,42 @@ module NBitcoinExtensions = elif script0.[index] > script1.[index] then 1 else - compare (index + 1) + compare(index + 1) + compare 0 type PSBT with + member this.GetMatchingSig(pubkey: PubKey) = this.Inputs - |> Seq.collect (fun i -> i.PartialSigs) - |> Seq.choose(fun kv -> if kv.Key = pubkey then Some kv.Value else None) + |> Seq.collect(fun i -> i.PartialSigs) + |> Seq.choose(fun kv -> + if kv.Key = pubkey then + Some kv.Value + else + None + ) |> Seq.tryExactlyOne member this.GetTxId() = this.GetGlobalTransaction().GetTxId() type Key with + static member BytesLength: int = 32 - static member FromHashOf(preimage: array): Key = - new Key (Hashes.SHA256 preimage) + static member FromHashOf(preimage: array) : Key = + new Key(Hashes.SHA256 preimage) type PubKey with + static member BytesLength: int = 33 type DateTimeOffset with - member this.IsValidUnixTime(): bool = - let unixRef = DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero); + + member this.IsValidUnixTime() : bool = + let unixRef = DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero) let dt = this.ToUniversalTime() - (unixRef <= dt) && ((dt - unixRef).TotalSeconds <= float UInt32.MaxValue) + + (unixRef <= dt) + && ((dt - unixRef).TotalSeconds <= float UInt32.MaxValue) diff --git a/src/DotNetLightning.Core/Utils/Primitives.fs b/src/DotNetLightning.Core/Utils/Primitives.fs index c70ec0a43..7e4f9aeab 100644 --- a/src/DotNetLightning.Core/Utils/Primitives.fs +++ b/src/DotNetLightning.Core/Utils/Primitives.fs @@ -17,44 +17,52 @@ open ResultUtils.Portability module Primitives = type NBitcoin.Utils with - static member ToUInt16(b: byte [], lendian: bool): uint16 = + + static member ToUInt16(b: array, lendian: bool) : uint16 = if lendian then - uint16 (b.[0]) + (uint16 (b.[1]) <<< 8) + uint16(b.[0]) + (uint16(b.[1]) <<< 8) else - uint16 (b.[1]) + (uint16 (b.[0]) <<< 8) + uint16(b.[1]) + (uint16(b.[0]) <<< 8) - static member ToBytes(d: uint16, lendian: bool): byte [] = + static member ToBytes(d: uint16, lendian: bool) : array = let mutable output = Array.zeroCreate 2 + if lendian then output.[0] <- byte d - output.[1] <- byte (d >>> 8) + output.[1] <- byte(d >>> 8) else - output.[0] <- byte (d >>> 8) + output.[0] <- byte(d >>> 8) output.[1] <- byte d + output /// Absolute block height #if !NoDUsAsStructs [] #endif - type BlockHeight = | BlockHeight of uint32 with + type BlockHeight = + | BlockHeight of uint32 + static member Zero = 0u |> BlockHeight static member One = 1u |> BlockHeight member this.Value = let (BlockHeight v) = this in v + member this.AsOffset() = this.Value |> Checked.uint16 |> BlockHeightOffset16 - static member (+) (a: BlockHeight, b: BlockHeightOffset16) = - a.Value + (uint32 b.Value ) |> BlockHeight - static member (+) (a: BlockHeight, b: BlockHeightOffset32) = - a.Value + b.Value |> BlockHeight + static member (+)(a: BlockHeight, b: BlockHeightOffset16) = + a.Value + (uint32 b.Value) |> BlockHeight - static member (-) (a: BlockHeight, b: BlockHeightOffset16) = + static member (+)(a: BlockHeight, b: BlockHeightOffset32) = + a.Value + b.Value |> BlockHeight + + static member (-)(a: BlockHeight, b: BlockHeightOffset16) = a.Value - (uint32 b.Value) |> BlockHeight - static member (-) (a: BlockHeight, b: BlockHeightOffset32) = + + static member (-)(a: BlockHeight, b: BlockHeightOffset32) = a.Value - b.Value |> BlockHeight - - static member (-) (a: BlockHeight, b: BlockHeight) = + + static member (-)(a: BlockHeight, b: BlockHeight) = a.Value - (b.Value) |> BlockHeightOffset32 /// **Description** @@ -62,67 +70,83 @@ module Primitives = /// 16bit relative block height used for `OP_CSV` locks, /// Since OP_CSV allow only block number of 0 ~ 65535, it is safe /// to restrict into the range smaller than BlockHeight - and -#if !NoDUsAsStructs - [] -#endif - BlockHeightOffset16 = | BlockHeightOffset16 of uint16 with + and [] BlockHeightOffset16 = + | BlockHeightOffset16 of uint16 + member this.Value = let (BlockHeightOffset16 v) = this in v static member ofBlockHeightOffset32(bho32: BlockHeightOffset32) = - BlockHeightOffset16 (uint16 bho32.Value) - static member op_Implicit (v: uint16) = + BlockHeightOffset16(uint16 bho32.Value) + + static member op_Implicit(v: uint16) = BlockHeightOffset16 v + static member One = BlockHeightOffset16(1us) static member Zero = BlockHeightOffset16(0us) static member MaxValue = UInt16.MaxValue |> BlockHeightOffset16 - static member (+) (a: BlockHeightOffset16, b: BlockHeightOffset16) = + + static member (+)(a: BlockHeightOffset16, b: BlockHeightOffset16) = a.Value + b.Value |> BlockHeightOffset16 - static member (-) (a: BlockHeightOffset16, b: BlockHeightOffset16) = + + static member (-)(a: BlockHeightOffset16, b: BlockHeightOffset16) = a.Value - b.Value |> BlockHeightOffset16 /// **Description** /// /// 32bit relative block height. For `OP_CSV` locks, BlockHeightOffset16 /// should be used instead. - and -#if !NoDUsAsStructs - [] -#endif - BlockHeightOffset32 = | BlockHeightOffset32 of uint32 with + and [] BlockHeightOffset32 = + | BlockHeightOffset32 of uint32 + member this.Value = let (BlockHeightOffset32 v) = this in v static member ofBlockHeightOffset16(bho16: BlockHeightOffset16) = - BlockHeightOffset32 (uint32 bho16.Value) - static member op_Implicit (v: uint32) = + BlockHeightOffset32(uint32 bho16.Value) + + static member op_Implicit(v: uint32) = BlockHeightOffset32 v + static member One = BlockHeightOffset32(1u) static member Zero = BlockHeightOffset32(0u) static member MaxValue = UInt32.MaxValue |> BlockHeightOffset32 - static member (+) (a: BlockHeightOffset32, b: BlockHeightOffset32) = + + static member (+)(a: BlockHeightOffset32, b: BlockHeightOffset32) = a.Value + b.Value |> BlockHeightOffset32 - static member (-) (a: BlockHeightOffset32, b: BlockHeightOffset32) = + + static member (-)(a: BlockHeightOffset32, b: BlockHeightOffset32) = a.Value - b.Value |> BlockHeightOffset32 /// Wrapper around NBitcoin's ECDSASignature type for convenience. It has following difference /// 1. It is equatable /// 2. Some Convenience methods for serialization /// 3. Custom `ToString` - [] - type LNECDSASignature = LNECDSASignature of ECDSASignature | Empty with - member this.Value = match this with LNECDSASignature s -> s | Empty -> failwith "Unreachable!" - override this.GetHashCode() = hash this.Value + [] + type LNECDSASignature = + | LNECDSASignature of ECDSASignature + | Empty + + member this.Value = + match this with + | LNECDSASignature s -> s + | Empty -> failwith "Unreachable!" + + override this.GetHashCode() = + hash this.Value + override this.Equals(obj: obj) = match obj with - | :? LNECDSASignature as o -> (this :> IEquatable).Equals(o) + | :? LNECDSASignature as o -> + (this :> IEquatable).Equals(o) | _ -> false + interface IEquatable with member this.Equals(o: LNECDSASignature) = Utils.ArrayEqual(o.ToBytesCompact(), this.ToBytesCompact()) - + override this.ToString() = sprintf "LNECDSASignature (%A)" (this.ToBytesCompact()) + member this.AsString = this.ToString() /// ** Description ** @@ -138,41 +162,56 @@ module Primitives = /// (serialized R value + S value) in byte array. member this.ToBytesCompact() = this.Value.ToCompact() - + /// Logic does not really matter here. This is just for making life easier by enabling automatic implementation /// of `StructuralComparison` for wrapper types. member this.CompareTo(e: LNECDSASignature) = let a = this.ToBytesCompact() |> fun x -> Utils.ToUInt64(x, true) - let b = e.ToBytesCompact() |> fun x -> Utils.ToUInt64(x, true) + let b = e.ToBytesCompact() |> fun x -> Utils.ToUInt64(x, true) a.CompareTo(b) + interface IComparable with member this.CompareTo(o: obj) = match o with | :? LNECDSASignature as e -> this.CompareTo(e) | _ -> -1 - + member this.ToDER() = this.Value.ToDER() - + /// Read 64 bytes as r(32 bytes) and s(32 bytes) value /// If `withRecId` is `true`, skip first 1 byte - static member FromBytesCompact(bytes: byte [], ?withRecId: bool) = + static member FromBytesCompact(bytes: array, ?withRecId: bool) = let withRecId = defaultArg withRecId false + if withRecId && bytes.Length <> 65 then - invalidArg "bytes" "ECDSASignature specified to have recovery id, but it was not 65 bytes length" + invalidArg + "bytes" + "ECDSASignature specified to have recovery id, but it was not 65 bytes length" else if (not withRecId) && bytes.Length <> 64 then - invalidArg "bytes" "ECDSASignature was not specified to have recovery id, but it was not 64 bytes length." + invalidArg + "bytes" + "ECDSASignature was not specified to have recovery id, but it was not 64 bytes length." else - let data = if withRecId then bytes.[1..] else bytes + let data = + if withRecId then + bytes.[1..] + else + bytes + match ECDSASignature.TryParseFromCompact data with | true, x -> LNECDSASignature x - | _ -> failwithf "failed to parse compact ecdsa signature %A" data + | _ -> + failwithf "failed to parse compact ecdsa signature %A" data - static member op_Implicit (ec: ECDSASignature) = + static member op_Implicit(ec: ECDSASignature) = ec |> LNECDSASignature - type PaymentHash = | PaymentHash of uint256 with + type PaymentHash = + | PaymentHash of uint256 + member this.Value = let (PaymentHash v) = this in v + member this.ToBytes(?lEndian) = let e = defaultArg lEndian false this.Value.ToBytes e @@ -182,161 +221,206 @@ module Primitives = Crypto.Hashes.RIPEMD160(b, b.Length) type PaymentPreimage = - private PaymentPreimage of seq - with - // as per BOLT-2: - static member LENGTH = 32 + private + | PaymentPreimage of seq + // as per BOLT-2: + static member LENGTH = 32 - static member Create(data: seq) = - if data.Count() <> PaymentPreimage.LENGTH then - raise <| ArgumentException(sprintf "Payment preimage length should be %i" PaymentPreimage.LENGTH) - PaymentPreimage data + static member Create(data: seq) = + if data.Count() <> PaymentPreimage.LENGTH then + raise + <| ArgumentException( + sprintf + "Payment preimage length should be %i" + PaymentPreimage.LENGTH + ) - member this.Value = - let (PaymentPreimage v) = this in v + PaymentPreimage data - member this.ToHex() = - let h = NBitcoin.DataEncoders.HexEncoder() - let ba: byte[] = this.ToByteArray() - ba |> h.EncodeData - - member this.ToBytes() = - this.Value + member this.Value = let (PaymentPreimage v) = this in v - member this.ToByteArray() = - this.Value |> Array.ofSeq + member this.ToHex() = + let h = NBitcoin.DataEncoders.HexEncoder() + let ba: array = this.ToByteArray() + ba |> h.EncodeData - member this.Hash = - this.ToByteArray() |> Crypto.Hashes.SHA256 |> fun x -> uint256(x, false) |> PaymentHash + member this.ToBytes() = + this.Value - member this.ToPrivKey() = - this.ToByteArray() |> fun ba -> new Key(ba) + member this.ToByteArray() = + this.Value |> Array.ofSeq - member this.ToPubKey() = - this.ToPrivKey().PubKey + member this.Hash = + this.ToByteArray() + |> Crypto.Hashes.SHA256 + |> fun x -> uint256(x, false) |> PaymentHash + + member this.ToPrivKey() = + this.ToByteArray() |> fun ba -> new Key(ba) + + member this.ToPubKey() = + this.ToPrivKey().PubKey let (|PaymentPreimage|) x = match x with | PaymentPreimage x -> x - + type ConnectionId = ConnectionId of Guid - [] - type PeerId = PeerId of EndPoint - with + + [] + type PeerId = + | PeerId of EndPoint + member this.Value = let (PeerId ep) = this in ep - - override this.GetHashCode() = this.Value.GetHashCode() + + override this.GetHashCode() = + this.Value.GetHashCode() + member this.Equals(o: PeerId) = - this.Value.ToEndpointString().Equals(o.Value.ToEndpointString()) + this + .Value + .ToEndpointString() + .Equals(o.Value.ToEndpointString()) + override this.Equals(o: obj) = match o with | :? PeerId as p -> this.Equals(p) | _ -> false + interface IEquatable with - member this.Equals o = this.Equals(o) + member this.Equals o = + this.Equals(o) + member this.CompareTo(o: PeerId) = - this.Value.ToEndpointString().CompareTo(o.Value.ToEndpointString()) - + this + .Value + .ToEndpointString() + .CompareTo(o.Value.ToEndpointString()) + interface IComparable with member this.CompareTo(o: obj) = match o with | :? PeerId as p -> this.CompareTo(p) | _ -> -1 - [] - type ComparablePubKey = ComparablePubKey of PubKey with + [] + type ComparablePubKey = + | ComparablePubKey of PubKey + member this.Value = let (ComparablePubKey v) = this in v + interface IComparable with - override this.CompareTo(other) = + override this.CompareTo other = match other with | :? ComparablePubKey as n -> this.Value.CompareTo(n.Value) | _ -> -1 - override this.GetHashCode() = this.Value.GetHashCode() - override this.Equals(other) = + + override this.GetHashCode() = + this.Value.GetHashCode() + + override this.Equals other = match other with | :? ComparablePubKey as n -> this.Value.Equals(n.Value) - | _ -> false - static member op_Implicit (pk: PubKey) = + | _ -> false + + static member op_Implicit(pk: PubKey) = pk |> ComparablePubKey - - [] - type NodeId = | NodeId of PubKey with + + [] + type NodeId = + | NodeId of PubKey + member this.Value = let (NodeId v) = this in v + interface IComparable with - override this.CompareTo(other) = + override this.CompareTo other = match other with | :? NodeId as n -> this.Value.CompareTo(n.Value) | _ -> -1 - override this.Equals(other) = + + override this.Equals other = match other with | :? NodeId as n -> this.Value.Equals(n.Value) - | _ -> false + | _ -> false + override this.GetHashCode() = this.Value.GetHashCode() /// Small wrapper for NBitcoin's OutPoint type /// So that it supports comparison and equality constraints - [] - type LNOutPoint = LNOutPoint of OutPoint with + [] + type LNOutPoint = + | LNOutPoint of OutPoint + member this.Value = let (LNOutPoint v) = this in v - + member this.CompareTo(other: LNOutPoint) = if this.Value.Hash > other.Value.Hash then 1 else if this.Value.Hash < other.Value.Hash then -1 + else if this.Value.N > other.Value.N then + 1 + else if this.Value.N < other.Value.N then + -1 else - if this.Value.N > other.Value.N then - 1 - else if this.Value.N < other.Value.N then - -1 - else - 0 - + 0 + member this.Equals(other: LNOutPoint) = - (this.Value.Hash = other.Value.Hash) && - (this.Value.N = other.Value.N) - + (this.Value.Hash = other.Value.Hash) + && (this.Value.N = other.Value.N) + override this.Equals(other: obj) = - if isNull other then false else - if not <| other :? LNOutPoint then false else - this.Equals((other :?> LNOutPoint)) - - override this.GetHashCode() = hash (this.Value.ToBytes()) - + if isNull other then + false + else if not <| other :? LNOutPoint then + false + else + this.Equals((other :?> LNOutPoint)) + + override this.GetHashCode() = + hash(this.Value.ToBytes()) + interface IComparable with - member this.CompareTo(other) = - if isNull other then 1 else - if not <| other :? LNOutPoint then 1 else - this.CompareTo(other :?> LNOutPoint) - + member this.CompareTo other = + if isNull other then + 1 + else if not <| other :? LNOutPoint then + 1 + else + this.CompareTo(other :?> LNOutPoint) + interface IEquatable with - member this.Equals(other) = this.Equals(other) + member this.Equals other = + this.Equals(other) /// feerate per kilo weight - type FeeRatePerKw = | FeeRatePerKw of uint32 with + type FeeRatePerKw = + | FeeRatePerKw of uint32 + member this.Value = let (FeeRatePerKw v) = this in v + static member FromFee(fee: Money, weight: uint64) = - (((uint64 fee.Satoshi) * 1000UL) / weight) - |> uint32 - |> FeeRatePerKw - + (((uint64 fee.Satoshi) * 1000UL) / weight) |> uint32 |> FeeRatePerKw + static member FromFeeAndVSize(fee: Money, vsize: uint64) = FeeRatePerKw.FromFee(fee, vsize * 4UL) - member this.CalculateFeeFromWeight(weight) = + member this.CalculateFeeFromWeight weight = Money.Satoshis(uint64 this.Value * weight / 1000UL) - - member this.CalculateFeeFromVirtualSize(vSize) = - this.CalculateFeeFromWeight (vSize * 4UL) + + member this.CalculateFeeFromVirtualSize vSize = + this.CalculateFeeFromWeight(vSize * 4UL) + member this.CalculateFeeFromVirtualSize(tx: Transaction) = for i in tx.Inputs do if isNull i.WitScript || i.WitScript = WitScript.Empty then invalidArg "tx" "Should never hold non-segwit input." - this.CalculateFeeFromVirtualSize(uint64 (tx.GetVirtualSize())) - + + this.CalculateFeeFromVirtualSize(uint64(tx.GetVirtualSize())) + member this.AsNBitcoinFeeRate() = - this.Value |> uint64 |> (*)4UL |> Money.Satoshis |> FeeRate + this.Value |> uint64 |> (*) 4UL |> Money.Satoshis |> FeeRate /// /// Suppose remote = 3.0 and local = 1.0. This formula will calculate the mismatch "ratio" to be: @@ -349,58 +433,82 @@ module Primitives = /// See this wikipedia entry for more info on ways of calculating relative differences: /// https://en.wikipedia.org/wiki/Relative_change_and_difference /// - member this.MismatchRatio (other: FeeRatePerKw) = + member this.MismatchRatio(other: FeeRatePerKw) = let local = double this.Value let remote = double other.Value - abs (2.0 * (remote - local) / (remote + local)) + abs(2.0 * (remote - local) / (remote + local)) static member Max(a: FeeRatePerKw, b: FeeRatePerKw) = - if (a.Value >= b.Value) then a else b - static member (+) (a: FeeRatePerKw, b: uint32) = + if (a.Value >= b.Value) then + a + else + b + + static member (+)(a: FeeRatePerKw, b: uint32) = (a.Value + b) |> FeeRatePerKw - static member (*) (a: FeeRatePerKw, b: uint32) = + + static member (*)(a: FeeRatePerKw, b: uint32) = (a.Value * b) |> FeeRatePerKw /// Block Hash - type BlockId = | BlockId of uint256 with + type BlockId = + | BlockId of uint256 + member this.Value = let (BlockId v) = this in v #if !NoDUsAsStructs [] #endif - type HTLCId = | HTLCId of uint64 with + type HTLCId = + | HTLCId of uint64 + static member Zero = HTLCId(0UL) member this.Value = let (HTLCId v) = this in v - static member (+) (a: HTLCId, b: uint64) = (a.Value + b) |> HTLCId + static member (+)(a: HTLCId, b: uint64) = + (a.Value + b) |> HTLCId #if !NoDUsAsStructs [] #endif - type TxOutIndex = | TxOutIndex of uint16 with + type TxOutIndex = + | TxOutIndex of uint16 + member this.Value = let (TxOutIndex v) = this in v #if !NoDUsAsStructs [] #endif - type TxIndexInBlock = | TxIndexInBlock of uint32 with + type TxIndexInBlock = + | TxIndexInBlock of uint32 + member this.Value = let (TxIndexInBlock v) = this in v #if !NoDUsAsStructs - [] + [] #else [] #endif - type ShortChannelId = { - BlockHeight: BlockHeight - BlockIndex: TxIndexInBlock - TxOutIndex: TxOutIndex - } - with - - static member From8Bytes(b: byte[]): ShortChannelId = - let bh = NBitcoin.Utils.ToUInt32 (Array.concat [| [| 0uy |]; b.[0..2]; |], false) - let bi = NBitcoin.Utils.ToUInt32 (Array.concat [| [| 0uy |]; b.[3..5]; |], false) + type ShortChannelId = + { + BlockHeight: BlockHeight + BlockIndex: TxIndexInBlock + TxOutIndex: TxOutIndex + } + + static member From8Bytes(b: array) : ShortChannelId = + let bh = + NBitcoin.Utils.ToUInt32( + Array.concat [| [| 0uy |]; b.[0..2] |], + false + ) + + let bi = + NBitcoin.Utils.ToUInt32( + Array.concat [| [| 0uy |]; b.[3..5] |], + false + ) + let txOutIndex = NBitcoin.Utils.ToUInt16(b.[6..7], false) { @@ -408,111 +516,159 @@ module Primitives = BlockIndex = bi |> TxIndexInBlock TxOutIndex = txOutIndex |> TxOutIndex } - + static member FromUInt64(rawData: uint64) = NBitcoin.Utils.ToBytes(rawData, false) |> ShortChannelId.From8Bytes - - member this.ToBytes(): byte [] = - Array.concat [| - NBitcoin.Utils.ToBytes(this.BlockHeight.Value, false).[1..3] - NBitcoin.Utils.ToBytes(this.BlockIndex.Value, false).[1..3] - NBitcoin.Utils.ToBytes(this.TxOutIndex.Value, false) - |] + + member this.ToBytes() : array = + Array.concat + [| + NBitcoin.Utils.ToBytes(this.BlockHeight.Value, false).[1..3] + NBitcoin.Utils.ToBytes(this.BlockIndex.Value, false).[1..3] + NBitcoin.Utils.ToBytes(this.TxOutIndex.Value, false) + |] member this.ToUInt64() = - this.ToBytes() - |> fun b -> NBitcoin.Utils.ToUInt64(b, false) + this.ToBytes() |> fun b -> NBitcoin.Utils.ToUInt64(b, false) override this.ToString() = - sprintf "%dx%dx%d" this.BlockHeight.Value this.BlockIndex.Value this.TxOutIndex.Value - + sprintf + "%ix%ix%i" + this.BlockHeight.Value + this.BlockIndex.Value + this.TxOutIndex.Value + member this.AsString = this.ToString() - + static member TryParse(s: string) = let items = s.Split('x') - let err = Error (sprintf "Failed to parse %s" s) - if (items.Length <> 3) then err else - match (items.[0] |> UInt32.TryParse), (items.[1] |> UInt32.TryParse), (items.[2] |> UInt16.TryParse) with - | (true, h), (true, blockI), (true, outputI) -> - { - BlockHeight = h |> BlockHeight - BlockIndex = blockI |> TxIndexInBlock - TxOutIndex = outputI |> TxOutIndex - } |> Ok - | _ -> err + let err = Error(sprintf "Failed to parse %s" s) + + if (items.Length <> 3) then + err + else + match (items.[0] |> UInt32.TryParse), + (items.[1] |> UInt32.TryParse), + (items.[2] |> UInt16.TryParse) + with + | (true, h), (true, blockI), (true, outputI) -> + { + BlockHeight = h |> BlockHeight + BlockIndex = blockI |> TxIndexInBlock + TxOutIndex = outputI |> TxOutIndex + } + |> Ok + | _ -> err + static member ParseUnsafe(s: string) = ShortChannelId.TryParse s - |> Result.defaultWith (fun _ -> raise <| FormatException(sprintf "Failed to parse %s" s)) + |> Result.defaultWith(fun _ -> + raise <| FormatException(sprintf "Failed to parse %s" s) + ) type UserId = UserId of uint64 - type Delimiter = - Delimiter of byte [] - - type RGB = { - Red: uint8 - Green: uint8 - Blue: uint8 - } + type Delimiter = Delimiter of array + + type RGB = + { + Red: uint8 + Green: uint8 + Blue: uint8 + } + type EncodingType = | SortedPlain = 0uy | ZLib = 1uy - type ShutdownScriptPubKey = private { - ShutdownScript: Script - } with - static member FromPubKeyP2wpkh (pubKey: PubKey) = + type ShutdownScriptPubKey = + private + { + ShutdownScript: Script + } + + static member FromPubKeyP2wpkh(pubKey: PubKey) = let script = pubKey.WitHash.ScriptPubKey - { ShutdownScript = script } - static member FromPubKeyP2pkh (pubKey: PubKey) = + { + ShutdownScript = script + } + + static member FromPubKeyP2pkh(pubKey: PubKey) = let script = pubKey.Hash.ScriptPubKey - { ShutdownScript = script } - static member FromScriptP2sh (script: Script) = + { + ShutdownScript = script + } + + static member FromScriptP2sh(script: Script) = let scriptPubKey = script.Hash.ScriptPubKey - { ShutdownScript = scriptPubKey } - static member FromScriptP2wsh (script: Script) = + { + ShutdownScript = scriptPubKey + } + + static member FromScriptP2wsh(script: Script) = let scriptPubKey = script.WitHash.ScriptPubKey - { ShutdownScript = scriptPubKey } - static member TryFromScript (scriptPubKey: Script) - : Result = + { + ShutdownScript = scriptPubKey + } + + static member TryFromScript + (scriptPubKey: Script) + : Result = let isValidFinalScriptPubKey = - (PayToPubkeyHashTemplate.Instance.CheckScriptPubKey(scriptPubKey)) - || (PayToScriptHashTemplate.Instance.CheckScriptPubKey(scriptPubKey)) - || (PayToWitPubKeyHashTemplate.Instance.CheckScriptPubKey(scriptPubKey)) - || (PayToWitScriptHashTemplate.Instance.CheckScriptPubKey(scriptPubKey)) + (PayToPubkeyHashTemplate.Instance.CheckScriptPubKey( + scriptPubKey + )) + || (PayToScriptHashTemplate.Instance.CheckScriptPubKey( + scriptPubKey + )) + || (PayToWitPubKeyHashTemplate.Instance.CheckScriptPubKey( + scriptPubKey + )) + || (PayToWitScriptHashTemplate.Instance.CheckScriptPubKey( + scriptPubKey + )) + if isValidFinalScriptPubKey then - Ok { ShutdownScript = scriptPubKey } + Ok + { + ShutdownScript = scriptPubKey + } else - sprintf "Invalid final script pubkey(%A). it must be one of p2pkh, p2sh, p2wpkh, p2wsh" scriptPubKey - |> Error + sprintf + "Invalid final script pubkey(%A). it must be one of p2pkh, p2sh, p2wpkh, p2wsh" + scriptPubKey + |> Error - member this.ScriptPubKey(): Script = + member this.ScriptPubKey() : Script = this.ShutdownScript - member this.ToBytes(): array = + member this.ToBytes() : array = this.ShutdownScript.ToBytes() - type ChannelFlags = { - // Set to announce the channel publicly and notify all nodes that they - // can route via this channel. This should only be set to true for - // nodes which expect to be online reliably. As the node which funds a - // channel picks this value this will only apply for new outbound - // channels unless - // `ChannelHandshakeLimits.ForceAnnouncedChannelPreferences` is set. - AnnounceChannel: bool - } with + type ChannelFlags = + { + // Set to announce the channel publicly and notify all nodes that they + // can route via this channel. This should only be set to true for + // nodes which expect to be online reliably. As the node which funds a + // channel picks this value this will only apply for new outbound + // channels unless + // `ChannelHandshakeLimits.ForceAnnouncedChannelPreferences` is set. + AnnounceChannel: bool + } + static member private AnnounceChannelMask: uint8 = 1uy - static member FromUInt8(flags: uint8): ChannelFlags = { - AnnounceChannel = (flags &&& ChannelFlags.AnnounceChannelMask) = ChannelFlags.AnnounceChannelMask - } + static member FromUInt8(flags: uint8) : ChannelFlags = + { + AnnounceChannel = + (flags &&& ChannelFlags.AnnounceChannelMask) = ChannelFlags.AnnounceChannelMask + } - member this.IntoUInt8(): uint8 = + member this.IntoUInt8() : uint8 = if this.AnnounceChannel then ChannelFlags.AnnounceChannelMask else 0uy - diff --git a/src/DotNetLightning.Core/Utils/PriorityQueue.fs b/src/DotNetLightning.Core/Utils/PriorityQueue.fs index 5047a4b12..7e90cbb6e 100644 --- a/src/DotNetLightning.Core/Utils/PriorityQueue.fs +++ b/src/DotNetLightning.Core/Utils/PriorityQueue.fs @@ -5,18 +5,18 @@ open System.Collections.Generic /// Based on FSharpx's PriorityQueue /// refs: https://github.com/fsprojects/FSharpx.Collections/blob/master/src/FSharpx.Collections/PriorityQueue.fs -type IPriorityQueue<'T when 'T : comparison> = +type IPriorityQueue<'T when 'T: comparison> = inherit IEnumerable inherit IEnumerable<'T> - abstract member IsEmpty : bool with get - abstract member Insert : 'T -> IPriorityQueue<'T> + abstract member IsEmpty: bool + abstract member Insert: 'T -> IPriorityQueue<'T> - abstract member TryPeek : unit -> 'T option + abstract member TryPeek: unit -> option<'T> abstract member Peek: unit -> 'T abstract member Length: int - abstract member TryPop : unit -> ('T * IPriorityQueue<'T>) option - abstract member Pop : unit -> 'T * IPriorityQueue<'T> + abstract member TryPop: unit -> option<('T * IPriorityQueue<'T>)> + abstract member Pop: unit -> 'T * IPriorityQueue<'T> /// Heap is an ordered linear structure where the ordering is ascending or descending. /// "head" inspects the first element in the ordering, "tail" takes the remaining structure @@ -24,7 +24,12 @@ type IPriorityQueue<'T when 'T : comparison> = /// as an alternate interface. /// According to Okasaki the time complexity of the heap functions in this Heap implementation /// (based on the "pairing" heap) have resisted time complexity analysis. -type Heap<'T when 'T : comparison>(isDescending: bool, length: int, data: HeapData<'T>) = +type Heap<'T when 'T: comparison> + ( + isDescending: bool, + length: int, + data: HeapData<'T> + ) = let mutable hashCode = None member internal this.HeapData = data member internal this.HeapLength = length @@ -35,65 +40,97 @@ type Heap<'T when 'T : comparison>(isDescending: bool, length: int, data: HeapDa match hashCode with | None -> let mutable hash = 1 + for x in this do hash <- 31 * hash * Unchecked.hash x + hashCode <- Some hash hash | Some hash -> hash - override this.Equals(other) = + override this.Equals other = match other with | :? Heap<'T> as y -> - if this.Length <> y.Length then false + if this.Length <> y.Length then + false + else if this.GetHashCode() <> y.GetHashCode() then + false else - if this.GetHashCode() <> y.GetHashCode() then false - else Seq.forall2 (Unchecked.equals) this y + Seq.forall2 (Unchecked.equals) this y | _ -> false - static member private merge isDescending newLength (h1: HeapData<'T>) (h2: HeapData<'T>): Heap<'T> = + static member private merge + isDescending + newLength + (h1: HeapData<'T>) + (h2: HeapData<'T>) + : Heap<'T> = match h1, h2 with | E, h -> Heap(isDescending, newLength, h) | h, E -> Heap(isDescending, newLength, h) | T(x, xs), T(y, ys) -> if isDescending then - if x <= y then Heap(isDescending, newLength, T(y, h1 :: ys)) else Heap(isDescending, newLength, T(x, h2::xs)) + if x <= y then + Heap(isDescending, newLength, T(y, h1 :: ys)) + else + Heap(isDescending, newLength, T(x, h2 :: xs)) + else if x <= y then + Heap(isDescending, newLength, T(x, h2 :: xs)) else - if x <= y then Heap(isDescending, newLength, T(x, h2 :: xs)) else Heap(isDescending, newLength, T(y, h1::ys)) + Heap(isDescending, newLength, T(y, h1 :: ys)) - static member private foldHeap nodeF leafV (h: HeapData<'T> list) = - let rec loop (h: HeapData<'T> list) cont = + static member private foldHeap nodeF leafV (h: list>) = + let rec loop (h: list>) cont = match h with - | T(a, h')::tl -> loop h' (fun lacc -> - loop tl (fun racc -> cont (nodeF a lacc racc))) + | T(a, h') :: tl -> + loop + h' + (fun lacc -> loop tl (fun racc -> cont(nodeF a lacc racc))) | _ -> cont leafV + loop h (id) - static member private inOrder (h: HeapData<'T> list) = (Heap.foldHeap (fun x l r acc -> l (x:: (r acc))) id h) [] - static member ofSeq (isDescending: bool) (s: 'T seq): Heap<'T> = - if Seq.isEmpty s then Heap(isDescending, 0, E) + static member private inOrder(h: list>) = + (Heap.foldHeap (fun x l r acc -> l(x :: (r acc))) id h) [] + + static member ofSeq (isDescending: bool) (s: seq<'T>) : Heap<'T> = + if Seq.isEmpty s then + Heap(isDescending, 0, E) else let len, h' = - Seq.fold(fun (lnth, (h: 'T HeapData)) x -> - match h with - | E -> 1, T (x, []) - | T (y, ys) -> - if isDescending then - if x <= y then (lnth + 1), T(y, T(x, []):: ys) else (lnth + 1), T(x, T(y, ys)::[]) - else - if x <= y then (lnth + 1), T(x, T(y, ys)::[]) else (lnth + 1), T(y, T(x, [])::ys)) - (0, E) s + Seq.fold + (fun (lnth, (h: HeapData<'T>)) x -> + match h with + | E -> 1, T(x, []) + | T(y, ys) -> + if isDescending then + if x <= y then + (lnth + 1), T(y, T(x, []) :: ys) + else + (lnth + 1), T(x, T(y, ys) :: []) + else if x <= y then + (lnth + 1), T(x, T(y, ys) :: []) + else + (lnth + 1), T(y, T(x, []) :: ys) + ) + (0, E) + s + Heap(isDescending, len, h') + member this.Head() = match data with | E -> failwith "Heap is empty" | T(x, _) -> x + member this.TryHead() = match data with | E -> None - | T(x, _) -> Some (x) + | T(x, _) -> Some(x) + member this.Insert x = - Heap.merge(isDescending) (length + 1) (T(x, [])) data + Heap.merge (isDescending) (length + 1) (T(x, [])) data member this.IsEmpty = match data with @@ -101,16 +138,25 @@ type Heap<'T when 'T : comparison>(isDescending: bool, length: int, data: HeapDa | _ -> false member this.IsDescending = isDescending - member this.Merge (xs: Heap<'T>) = - if isDescending = xs.IsDescending then Heap.merge isDescending (length + xs.HeapLength) data xs.HeapData - else failwith "heaps to merge have different sort orders" - member this.TryMerge (xs: Heap<'T>) = - if isDescending = xs.IsDescending then Heap.merge isDescending (length + xs.HeapLength) data xs.HeapData |> Some - else None + + member this.Merge(xs: Heap<'T>) = + if isDescending = xs.IsDescending then + Heap.merge isDescending (length + xs.HeapLength) data xs.HeapData + else + failwith "heaps to merge have different sort orders" + + member this.TryMerge(xs: Heap<'T>) = + if isDescending = xs.IsDescending then + Heap.merge isDescending (length + xs.HeapLength) data xs.HeapData + |> Some + else + None member this.Rev() = - if isDescending then Heap<'T>.ofSeq false (this :> seq<'T>) - else Heap<'T>.ofSeq true (this :> 'T seq) + if isDescending then + Heap<'T>.ofSeq false (this :> seq<'T>) + else + Heap<'T>.ofSeq true (this :> seq<'T>) member this.Tail() = let mergeData (h1: HeapData<'T>) (h2: HeapData<'T>) : HeapData<'T> = @@ -119,27 +165,33 @@ type Heap<'T when 'T : comparison>(isDescending: bool, length: int, data: HeapDa | h, E -> h | T(x, xs), T(y, ys) -> if isDescending then - if x <= y then T(y, h1::ys) else T(x, h2::xs) + if x <= y then + T(y, h1 :: ys) + else + T(x, h2 :: xs) + else if x <= y then + T(x, h2 :: xs) else - if x <= y then T(x, h2::xs) else T(y, h1::ys) + T(y, h1 :: ys) + match data with | E -> failwith "Heap is empty" - | T (_x, xs) -> + | T(_x, xs) -> let combinePairs state item = match state with - | Some p, l -> - (None, (mergeData item p)::l) - | None, l -> - (Some item, l) + | Some p, l -> (None, (mergeData item p) :: l) + | None, l -> (Some item, l) let tail = xs |> List.fold combinePairs (None, []) |> function - | Some i, l -> i::l + | Some i, l -> i :: l | None, l -> l |> List.fold mergeData E + Heap(isDescending, (length - 1), tail) + member this.TryTail() = match data with | E -> None @@ -160,105 +212,142 @@ type Heap<'T when 'T : comparison>(isDescending: bool, length: int, data: HeapDa member this.GetEnumerator() = let e = let listH = data :: [] - if isDescending - then Heap.inOrder listH |> List.sort |> List.rev |> List.toSeq - else Heap.inOrder listH |> List.sort |> List.toSeq + + if isDescending then + Heap.inOrder listH |> List.sort |> List.rev |> List.toSeq + else + Heap.inOrder listH |> List.sort |> List.toSeq + e.GetEnumerator() - member this.GetEnumerator() = (this :> _ seq).GetEnumerator() :> IEnumerator + member this.GetEnumerator() = + (this :> seq<_>).GetEnumerator() :> IEnumerator interface IPriorityQueue<'T> with - member this.Insert(element: 'T): IPriorityQueue<'T> = + member this.Insert(element: 'T) : IPriorityQueue<'T> = this.Insert element :> IPriorityQueue<_> + member this.IsEmpty: bool = this.IsEmpty - member this.Length: int = this.Length - member this.Peek(): 'T = this.Head() - member this.TryPeek(): 'T option = this.TryHead() - member this.Pop(): 'T * IPriorityQueue<'T> = + member this.Length: int = this.Length + + member this.Peek() : 'T = + this.Head() + + member this.TryPeek() : option<'T> = + this.TryHead() + + member this.Pop() : 'T * IPriorityQueue<'T> = let elem, newHeap = this.Uncons() elem, (newHeap :> IPriorityQueue<_>) - member this.TryPop(): ('T * IPriorityQueue<'T>) option = + + member this.TryPop() : option<('T * IPriorityQueue<'T>)> = match this.TryUncons() with - | Some (elem, newHeap) -> Some(elem, newHeap :> IPriorityQueue<_>) + | Some(elem, newHeap) -> Some(elem, newHeap :> IPriorityQueue<_>) | None -> None -and HeapData<'T when 'T : comparison> = +and HeapData<'T when 'T: comparison> = | E - | T of 'T * HeapData<'T> list + | T of 'T * list> [] -module Heap = +module Heap = //pattern discriminator - let (|Cons|Nil|) (h: Heap<'T>) = match h.TryUncons() with Some(a,b) -> Cons(a,b) | None -> Nil - + let (|Cons|Nil|)(h: Heap<'T>) = + match h.TryUncons() with + | Some(a, b) -> Cons(a, b) + | None -> Nil + ///O(1). Returns a empty heap. - let empty<'T when 'T : comparison> (isDescending: bool) = Heap<'T>(isDescending, 0, E) + let empty<'T when 'T: comparison>(isDescending: bool) = + Heap<'T>(isDescending, 0, E) ///O(1) worst case. Returns the min or max element. - let inline head (xs: Heap<'T>) = xs.Head + let inline head(xs: Heap<'T>) = + xs.Head ///O(1) worst case. Returns option first min or max element. - let inline tryHead (xs: Heap<'T>) = xs.TryHead + let inline tryHead(xs: Heap<'T>) = + xs.TryHead ///O(log n) amortized time. Returns a new heap with the element inserted. - let inline insert x (xs: Heap<'T>) = xs.Insert x + let inline insert x (xs: Heap<'T>) = + xs.Insert x ///O(1). Returns true if the heap has no elements. - let inline isEmpty (xs: Heap<'T>) = xs.IsEmpty + let inline isEmpty(xs: Heap<'T>) = + xs.IsEmpty ///O(1). Returns true if the heap has max element at head. - let inline isDescending (xs: Heap<'T>) = xs.IsDescending + let inline isDescending(xs: Heap<'T>) = + xs.IsDescending ///O(n). Returns the count of elememts. - let inline length (xs: Heap<'T>) = xs.Length + let inline length(xs: Heap<'T>) = + xs.Length ///O(log n) amortized time. Returns heap from merging two heaps, both must have same descending. - let inline merge (xs: Heap<'T>) (ys: Heap<'T>) = xs.Merge ys + let inline merge (xs: Heap<'T>) (ys: Heap<'T>) = + xs.Merge ys ///O(log n) amortized time. Returns heap option from merging two heaps. - let inline tryMerge (xs: Heap<'T>) (ys: Heap<'T>) = xs.TryMerge ys + let inline tryMerge (xs: Heap<'T>) (ys: Heap<'T>) = + xs.TryMerge ys ///O(n log n). Returns heap, bool isDescending, from the sequence. - let ofSeq isDescending s = Heap<'T>.ofSeq isDescending s - + let ofSeq isDescending s = + Heap<'T>.ofSeq isDescending s + ///O(n). Returns heap reversed. - let inline rev (xs: Heap<'T>) = xs.Rev() + let inline rev(xs: Heap<'T>) = + xs.Rev() ///O(log n) amortized time. Returns a new heap of the elements trailing the head. - let inline tail (xs: Heap<'T>) = xs.Tail() + let inline tail(xs: Heap<'T>) = + xs.Tail() ///O(log n) amortized time. Returns option heap of the elements trailing the head. - let inline tryTail (xs: Heap<'T>) = xs.TryTail() + let inline tryTail(xs: Heap<'T>) = + xs.TryTail() ///O(n). Views the given heap as a sequence. - let inline toSeq (xs: Heap<'T>) = xs :> seq<'T> + let inline toSeq(xs: Heap<'T>) = + xs :> seq<'T> ///O(log n) amortized time. Returns the head element and tail. - let inline uncons (xs: Heap<'T>) = xs.Uncons() + let inline uncons(xs: Heap<'T>) = + xs.Uncons() ///O(log n) amortized time. Returns option head element and tail. - let inline tryUncons (xs: Heap<'T>) = xs.TryUncons() + let inline tryUncons(xs: Heap<'T>) = + xs.TryUncons() [] module PriorityQueue = ///O(1). Returns a empty queue, with indicated ordering. - let empty<'T when 'T : comparison> isDescending = Heap.empty isDescending :> IPriorityQueue<'T> + let empty<'T when 'T: comparison> isDescending = + Heap.empty isDescending :> IPriorityQueue<'T> ///O(1). Returns true if the queue has no elements. - let inline isEmpty (pq:IPriorityQueue<'T>) = pq.IsEmpty + let inline isEmpty(pq: IPriorityQueue<'T>) = + pq.IsEmpty ///O(log n) amortized time. Returns a new queue with the element added to the end. - let inline insert element (pq:IPriorityQueue<'T>) = pq.Insert element + let inline insert element (pq: IPriorityQueue<'T>) = + pq.Insert element ///O(1). Returns option first element. - let inline tryPeek (pq:IPriorityQueue<'T>) = pq.TryPeek() + let inline tryPeek(pq: IPriorityQueue<'T>) = + pq.TryPeek() ///O(1). Returns the first element. - let inline peek (pq:IPriorityQueue<'T>) = pq.Peek() + let inline peek(pq: IPriorityQueue<'T>) = + pq.Peek() ///O(log n) amortized time. Returns the option first element and tail. - let inline tryPop (pq:IPriorityQueue<'T>) = pq.TryPop() + let inline tryPop(pq: IPriorityQueue<'T>) = + pq.TryPop() ///O(log n) amortized time. Returns the first element and tail. - let inline pop (pq:IPriorityQueue<'T>) = pq.Pop() + let inline pop(pq: IPriorityQueue<'T>) = + pq.Pop() diff --git a/src/DotNetLightning.Core/Utils/RouteType.fs b/src/DotNetLightning.Core/Utils/RouteType.fs index cb81794ab..9620d5037 100644 --- a/src/DotNetLightning.Core/Utils/RouteType.fs +++ b/src/DotNetLightning.Core/Utils/RouteType.fs @@ -3,19 +3,21 @@ namespace DotNetLightning.Utils open NBitcoin /// A hop in route -type RouteHop = { - /// The node_id of the node at this hop. - PubKey: PubKey - /// The channel that should be used from the previous hop to reach this node. - ShortChannelId: ShortChannelId - /// The fee of this hop. FOr the last hop, this should be the full value of the payment. - Fee: LNMoney - /// The CLTV delta added for this hop. For the last hop, this should be the full CLTV value - /// expected at the destination, in excess of the current block height. - CLTVExpiryDelta: uint32 -} +type RouteHop = + { + /// The node_id of the node at this hop. + PubKey: PubKey + /// The channel that should be used from the previous hop to reach this node. + ShortChannelId: ShortChannelId + /// The fee of this hop. FOr the last hop, this should be the full value of the payment. + Fee: LNMoney + /// The CLTV delta added for this hop. For the last hop, this should be the full CLTV value + /// expected at the destination, in excess of the current block height. + CLTVExpiryDelta: uint32 + } /// This should not exceed 20 (as a protocol rule). -type Route = Route of RouteHop list - with - member this.Value = let (Route r) = this in r \ No newline at end of file +type Route = + | Route of list + + member this.Value = let (Route r) = this in r diff --git a/src/DotNetLightning.Core/Utils/SeqParser.fs b/src/DotNetLightning.Core/Utils/SeqParser.fs index 2fd7bafa7..3fb5f24e4 100644 --- a/src/DotNetLightning.Core/Utils/SeqParser.fs +++ b/src/DotNetLightning.Core/Utils/SeqParser.fs @@ -7,103 +7,122 @@ open ResultUtils.Portability /// sequence and optionally returns a value successfully-parsed from the start of /// the sequence along with the rest of the sequence. If parsing fails it returns /// None. -/// +/// /// You can construct a SeqParser using the seqParser compuation expression. /// The bind operation of the computation expression will return the value parsed /// from the sequence and advance the sequence to the position where the next value /// can be parsed from. For example, given a parser parseValue, you can construct a /// parser which parses three values like this: -/// +/// /// seqParser { /// let! value0 = parseValue() /// let! value1 = parseValue() /// let! value2 = parseValue() /// return (value0, value1, value2) /// } -/// +/// /// You can also call SeqParser.next and SeqParser.abort from within a /// seqParser to pop the next element of the sequence or to abort parsing /// respectively. -/// +/// /// The function SeqParser.parseToCompletion takes a sequence and a SeqParser /// and will attempt to parse the sequence to completion. [] module SeqParserCE = - type SeqParser<'SeqElement, 'Value> = { - Parse: seq<'SeqElement> -> Option * 'Value> - } - - type SeqParserBuilder<'SeqElement>() = - member __.Bind<'Arg, 'Return>(seqParser0: SeqParser<'SeqElement, 'Arg>, - func: 'Arg -> SeqParser<'SeqElement, 'Return> - ): SeqParser<'SeqElement, 'Return> = { - Parse = fun (sequence0: seq<'SeqElement>) -> - match seqParser0.Parse sequence0 with - | None -> None - | Some (sequence1, value0) -> - let seqParser1 = func value0 - seqParser1.Parse sequence1 - } - - member __.Return<'Value>(value: 'Value) - : SeqParser<'SeqElement, 'Value> = { - Parse = fun (sequence: seq<'SeqElement>) -> Some (sequence, value) + type SeqParser<'SeqElement, 'Value> = + { + Parse: seq<'SeqElement> -> Option * 'Value> } - member __.ReturnFrom<'Value>(seqParser: SeqParser<'SeqElement, 'Value>) - : SeqParser<'SeqElement, 'Value> = + type SeqParserBuilder<'SeqElement>() = + member __.Bind<'Arg, 'Return> + ( + seqParser0: SeqParser<'SeqElement, 'Arg>, + func: 'Arg -> SeqParser<'SeqElement, 'Return> + ) : SeqParser<'SeqElement, 'Return> = + { + Parse = + fun (sequence0: seq<'SeqElement>) -> + match seqParser0.Parse sequence0 with + | None -> None + | Some(sequence1, value0) -> + let seqParser1 = func value0 + seqParser1.Parse sequence1 + } + + member __.Return<'Value> + (value: 'Value) + : SeqParser<'SeqElement, 'Value> = + { + Parse = + fun (sequence: seq<'SeqElement>) -> Some(sequence, value) + } + + member __.ReturnFrom<'Value> + (seqParser: SeqParser<'SeqElement, 'Value>) + : SeqParser<'SeqElement, 'Value> = seqParser - member __.Zero(): SeqParser<'SeqElement, unit> = { - Parse = fun (sequence: seq<'SeqElement>) -> Some (sequence, ()) - } - - member __.Delay<'Value>(delayedSeqParser: unit -> SeqParser<'SeqElement, 'Value>) - : SeqParser<'SeqElement, 'Value> = { - Parse = fun (sequence: seq<'SeqElement>) -> - (delayedSeqParser ()).Parse sequence - } - - member __.TryWith<'Value>(seqParser: SeqParser<'SeqElement, 'Value>, - onException: exn -> SeqParser<'SeqElement, 'Value> - ): SeqParser<'SeqElement, 'Value> = { - Parse = fun (sequence: seq<'SeqElement>) -> - try - seqParser.Parse sequence - with - | ex -> - let subSeqParser = onException ex - subSeqParser.Parse sequence - } + member __.Zero() : SeqParser<'SeqElement, unit> = + { + Parse = fun (sequence: seq<'SeqElement>) -> Some(sequence, ()) + } + + member __.Delay<'Value> + (delayedSeqParser: unit -> SeqParser<'SeqElement, 'Value>) + : SeqParser<'SeqElement, 'Value> = + { + Parse = + fun (sequence: seq<'SeqElement>) -> + (delayedSeqParser()).Parse sequence + } + + member __.TryWith<'Value> + ( + seqParser: SeqParser<'SeqElement, 'Value>, + onException: exn -> SeqParser<'SeqElement, 'Value> + ) : SeqParser<'SeqElement, 'Value> = + { + Parse = + fun (sequence: seq<'SeqElement>) -> + try + seqParser.Parse sequence + with + | ex -> + let subSeqParser = onException ex + subSeqParser.Parse sequence + } let seqParser<'SeqElement> = SeqParserBuilder<'SeqElement>() [] module SeqParser = - let next<'SeqElement>(): SeqParser<'SeqElement, 'SeqElement> = { - Parse = fun (sequence: seq<'SeqElement>) -> - Seq.tryHead sequence - |> Option.map (fun value -> (Seq.tail sequence, value)) - } + let next<'SeqElement>() : SeqParser<'SeqElement, 'SeqElement> = + { + Parse = + fun (sequence: seq<'SeqElement>) -> + Seq.tryHead sequence + |> Option.map(fun value -> (Seq.tail sequence, value)) + } - let abort<'SeqElement, 'Value>(): SeqParser<'SeqElement, 'Value> = { - Parse = fun (_sequence: seq<'SeqElement>) -> None - } + let abort<'SeqElement, 'Value>() : SeqParser<'SeqElement, 'Value> = + { + Parse = fun (_sequence: seq<'SeqElement>) -> None + } type ParseToCompletionError = | SequenceEndedTooEarly | SequenceNotReadToEnd - let parseToCompletion<'SeqElement, 'Value> (sequence: seq<'SeqElement>) - (seqParser: SeqParser<'SeqElement, 'Value>) - : Result<'Value, ParseToCompletionError> = + let parseToCompletion<'SeqElement, 'Value> + (sequence: seq<'SeqElement>) + (seqParser: SeqParser<'SeqElement, 'Value>) + : Result<'Value, ParseToCompletionError> = match seqParser.Parse sequence with | None -> Error SequenceEndedTooEarly - | Some (consumedSequence, value) -> + | Some(consumedSequence, value) -> if Seq.isEmpty consumedSequence then Ok value else Error SequenceNotReadToEnd - - diff --git a/src/DotNetLightning.Core/Utils/TxId.fs b/src/DotNetLightning.Core/Utils/TxId.fs index e59662e12..0e507f495 100644 --- a/src/DotNetLightning.Core/Utils/TxId.fs +++ b/src/DotNetLightning.Core/Utils/TxId.fs @@ -2,8 +2,9 @@ namespace DotNetLightning.Utils open NBitcoin -[] -type TxId = | TxId of uint256 with +[] +type TxId = + | TxId of uint256 + member this.Value = let (TxId v) = this in v static member Zero = uint256.Zero |> TxId - diff --git a/src/DotNetLightning.Core/Utils/UInt48.fs b/src/DotNetLightning.Core/Utils/UInt48.fs index 85b466f5e..cdcc2c0f5 100644 --- a/src/DotNetLightning.Core/Utils/UInt48.fs +++ b/src/DotNetLightning.Core/Utils/UInt48.fs @@ -6,33 +6,44 @@ open DotNetLightning.Core.Utils.Extensions #if !NoDUsAsStructs [] #endif -type UInt48 = { - UInt64: uint64 -} with +type UInt48 = + { + UInt64: uint64 + } + static member private BitMask: uint64 = 0x0000ffffffffffffUL - static member Zero: UInt48 = { - UInt64 = 0UL - } + static member Zero: UInt48 = + { + UInt64 = 0UL + } - static member One: UInt48 = { - UInt64 = 1UL - } + static member One: UInt48 = + { + UInt64 = 1UL + } - static member MaxValue: UInt48 = { - UInt64 = UInt48.BitMask - } + static member MaxValue: UInt48 = + { + UInt64 = UInt48.BitMask + } - static member FromUInt64(x: uint64): UInt48 = + static member FromUInt64(x: uint64) : UInt48 = if x > UInt48.BitMask then - raise <| ArgumentOutOfRangeException("x", "value is out of range for a UInt48") + raise + <| ArgumentOutOfRangeException( + "x", + "value is out of range for a UInt48" + ) else - { UInt64 = x } + { + UInt64 = x + } override this.ToString() = this.UInt64.ToString() - member this.GetBytesBigEndian(): array = + member this.GetBytesBigEndian() : array = this.UInt64.GetBytesBigEndian().[2..] static member FromBytesBigEndian(bytes6: array) = @@ -40,49 +51,61 @@ type UInt48 = { failwith "UInt48.FromBytesBigEndian expects a 6 byte array" else let bytes8 = Array.concat [| [| 0uy; 0uy |]; bytes6 |] - { UInt64 = System.UInt64.FromBytesBigEndian bytes8 } - - static member (+) (a: UInt48, b: UInt48): UInt48 = { - UInt64 = ((a.UInt64 <<< 8) + (b.UInt64 <<< 8)) >>> 8 - } - - static member (+) (a: UInt48, b: uint32): UInt48 = { - UInt64 = ((a.UInt64 <<< 8) + ((uint64 b) <<< 8)) >>> 8 - } - - static member (-) (a: UInt48, b: UInt48): UInt48 = { - UInt64 = ((a.UInt64 <<< 8) - (b.UInt64 <<< 8)) >>> 8 - } - - static member (-) (a: UInt48, b: uint32): UInt48 = { - UInt64 = ((a.UInt64 <<< 8) - ((uint64 b) <<< 8)) >>> 8 - } - static member (*) (a: UInt48, b: UInt48): UInt48 = { - UInt64 = ((a.UInt64 <<< 4) * (b.UInt64 <<< 4)) >>> 8 - } - - static member (*) (a: UInt48, b: uint32): UInt48 = { - UInt64 = ((a.UInt64 <<< 4) * ((uint64 b) <<< 4)) >>> 8 - } - - static member (&&&) (a: UInt48, b: UInt48): UInt48 = { - UInt64 = a.UInt64 &&& b.UInt64 - } - - static member (^^^) (a: UInt48, b: UInt48): UInt48 = { - UInt64 = a.UInt64 ^^^ b.UInt64 - } - - static member (>>>) (a: UInt48, b: int): UInt48 = { - UInt64 = (a.UInt64 >>> b) &&& UInt48.BitMask - } - - member this.TrailingZeros(): int = - let rec count (acc: int) (x: uint64): int = + { + UInt64 = System.UInt64.FromBytesBigEndian bytes8 + } + + static member (+)(a: UInt48, b: UInt48) : UInt48 = + { + UInt64 = ((a.UInt64 <<< 8) + (b.UInt64 <<< 8)) >>> 8 + } + + static member (+)(a: UInt48, b: uint32) : UInt48 = + { + UInt64 = ((a.UInt64 <<< 8) + ((uint64 b) <<< 8)) >>> 8 + } + + static member (-)(a: UInt48, b: UInt48) : UInt48 = + { + UInt64 = ((a.UInt64 <<< 8) - (b.UInt64 <<< 8)) >>> 8 + } + + static member (-)(a: UInt48, b: uint32) : UInt48 = + { + UInt64 = ((a.UInt64 <<< 8) - ((uint64 b) <<< 8)) >>> 8 + } + + static member (*)(a: UInt48, b: UInt48) : UInt48 = + { + UInt64 = ((a.UInt64 <<< 4) * (b.UInt64 <<< 4)) >>> 8 + } + + static member (*)(a: UInt48, b: uint32) : UInt48 = + { + UInt64 = ((a.UInt64 <<< 4) * ((uint64 b) <<< 4)) >>> 8 + } + + static member (&&&)(a: UInt48, b: UInt48) : UInt48 = + { + UInt64 = a.UInt64 &&& b.UInt64 + } + + static member (^^^)(a: UInt48, b: UInt48) : UInt48 = + { + UInt64 = a.UInt64 ^^^ b.UInt64 + } + + static member (>>>)(a: UInt48, b: int) : UInt48 = + { + UInt64 = (a.UInt64 >>> b) &&& UInt48.BitMask + } + + member this.TrailingZeros() : int = + let rec count (acc: int) (x: uint64) : int = if acc = 48 || x &&& 1UL = 1UL then acc else count (acc + 1) (x >>> 1) - count 0 this.UInt64 + count 0 this.UInt64 diff --git a/src/DotNetLightning.Core/Utils/Utils.fs b/src/DotNetLightning.Core/Utils/Utils.fs index 3c2131bc1..5d34c6a3b 100644 --- a/src/DotNetLightning.Core/Utils/Utils.fs +++ b/src/DotNetLightning.Core/Utils/Utils.fs @@ -1,4 +1,5 @@ namespace DotNetLightning.Utils + open System open System.Threading.Tasks @@ -6,80 +7,133 @@ open System.Runtime.CompilerServices [] module Utils = - let inline checkNull name (o) = - if isNull (box <| o) then nullArg (name) else () - let inline (!>) (x: ^a): ^b = ((^a or ^b): (static member op_Implicit: ^a -> ^b) x) + let inline checkNull name o = + if isNull(box <| o) then + nullArg(name) + else + () + + let inline (!>)(x: ^a) : ^b = + ((^a or ^b): (static member op_Implicit: ^a -> ^b) x) + + let inline curry2 f a b = + f(a, b) + + let inline uncurry2 f t = + let (a, b) = t in f a b + + let inline curry3 f a b c = + f(a, b, c) + + let inline uncurry3 f t = + let (a, b, c) = t in f a b c + + let inline curry4 f a b c d = + f(a, b, c, d) + + let inline uncurry4 f t = + let (a, b, c, d) = t in f a b c d - let inline curry2 f a b = f (a, b) - let inline uncurry2 f t = let (a, b) = t in f a b + let inline curry5 f a b c d e = + f(a, b, c, d, e) - let inline curry3 f a b c = f (a, b, c) - let inline uncurry3 f t = let (a, b, c) = t in f a b c + let inline uncurry5 f t = + let (a, b, c, d, e) = t in f a b c d e - let inline curry4 f a b c d = f (a, b, c, d) - let inline uncurry4 f t = let (a, b, c, d) = t in f a b c d + let inline curry6 f a b c d e g = + f(a, b, c, d, e, g) - let inline curry5 f a b c d e = f (a, b, c, d, e) - let inline uncurry5 f t = let (a, b, c, d, e) = t in f a b c d e + let inline uncurry6 f t = + let (a, b, c, d, e, g) = t in f a b c d e g - let inline curry6 f a b c d e g = f (a, b, c, d, e, g) - let inline uncurry6 f t = let (a, b, c, d, e, g) = t in f a b c d e g + let inline curry7 f a b c d e g h = + f(a, b, c, d, e, g, h) - let inline curry7 f a b c d e g h = f (a, b, c, d, e, g, h) - let inline uncurry7 f t = let (a, b, c, d, e, g, h) = t in f a b c d e g h + let inline uncurry7 f t = + let (a, b, c, d, e, g, h) = t in f a b c d e g h - let inline curry8 f a b c d e g h i = f (a, b, c, d, e, g, h, i) - let inline uncurry8 f t = let (a, b, c, d, e, g, h, i) = t in f a b c d e g h i + let inline curry8 f a b c d e g h i = + f(a, b, c, d, e, g, h, i) - let inline curry9 f a b c d e g h i j = f (a, b, c, d, e, g, h, i, j) - let inline uncurry9 f t = let (a, b, c, d, e, g, h, i, j) = t in f a b c d e g h i j + let inline uncurry8 f t = + let (a, b, c, d, e, g, h, i) = t in f a b c d e g h i - let inline curry10 f a b c d e g h i j k = f (a, b, c, d, e, g, h, i, j, k) - let inline uncurry10 f t = let (a, b, c, d, e, g, h, i, j, k) = t in f a b c d e g h i j k + let inline curry9 f a b c d e g h i j = + f(a, b, c, d, e, g, h, i, j) - let inline curry11 f a b c d e g h i j k l = f (a, b, c, d, e, g, h, i, j, k, l) - let inline uncurry11 f t = let (a, b, c, d, e, g, h, i, j, k, l) = t in f a b c d e g h i j k l + let inline uncurry9 f t = + let (a, b, c, d, e, g, h, i, j) = t in f a b c d e g h i j + let inline curry10 f a b c d e g h i j k = + f(a, b, c, d, e, g, h, i, j, k) - let inline max a b = if b > a then b else a - let inline min a b = if a > b then b else a + let inline uncurry10 f t = + let (a, b, c, d, e, g, h, i, j, k) = t in f a b c d e g h i j k + + let inline curry11 f a b c d e g h i j k l = + f(a, b, c, d, e, g, h, i, j, k, l) + + let inline uncurry11 f t = + let (a, b, c, d, e, g, h, i, j, k, l) = t in f a b c d e g h i j k l + + + let inline max a b = + if b > a then + b + else + a + + let inline min a b = + if a > b then + b + else + a [] type Extensions() = [] - static member ToHexString(this: byte []) = - "0x" + BitConverter.ToString(this).Replace("-", "").ToLowerInvariant() + static member ToHexString(this: array) = + "0x" + + BitConverter + .ToString(this) + .Replace("-", "") + .ToLowerInvariant() module Async = let result = async.Return - - let map f value = async { - let! v = value - return! f v - } - let bind f xAsync = async { - let! x = xAsync - return! f x - } + let map f value = + async { + let! v = value + return! f v + } + + let bind f xAsync = + async { + let! x = xAsync + return! f x + } let withTimeout timeoutMillis op = async { let! child = Async.StartChild(op, timeoutMillis) + try let! result = child return Some result - with :? TimeoutException -> - return None + with + | :? TimeoutException -> return None + } + + let apply fAsync xAsync = + async { + let! fChild = Async.StartChild fAsync + let! xChild = Async.StartChild xAsync + let! f = fChild + let! x = xChild + return f x } - let apply fAsync xAsync = async { - let! fChild = Async.StartChild fAsync - let! xChild = Async.StartChild xAsync - let! f = fChild - let! x = xChild - return f x - } let lift2 f x y = apply (apply (result f) x) y @@ -95,49 +149,63 @@ module Async = module Operators = - let inline (>>=) m f = bind f m + let inline (>>=) m f = + bind f m - let inline (=<<) f m = bind f m + let inline (=<<) f m = + bind f m - let inline (<*>) f m = apply f m + let inline (<*>) f m = + apply f m - let inline () f m = map f m + let inline () f m = + map f m - let inline ( *> ) m1 m2 = lift2 (fun _ x -> x) m1 m2 + let inline ( *> ) m1 m2 = + lift2 (fun _ x -> x) m1 m2 - let inline (<*) m1 m2 = lift2 (fun x _ -> x) m1 m2 + let inline (<*) m1 m2 = + lift2 (fun x _ -> x) m1 m2 /// extend default async builder to enable awaiting task without Async.AwaitTask type AsyncBuilder with - member __.Bind(t: Task<'T>, f: 'T -> Async<'R>): Async<'R> = + + member __.Bind(t: Task<'T>, f: 'T -> Async<'R>) : Async<'R> = async.Bind(Async.AwaitTask t, f) - member __.Bind(t: Task, f: unit -> Async<'R>): Async<'R> = + member __.Bind(t: Task, f: unit -> Async<'R>) : Async<'R> = async.Bind(Async.AwaitTask t, f) + module Set = let first (predicate: 'a -> bool) (items: Set<'a>) = let res = items |> Set.filter predicate + if (Seq.isEmpty res) then None else - Some (res |> Set.toList |> fun x -> x.[0]) + Some(res |> Set.toList |> (fun x -> x.[0])) module Array = - let skipBack (length: int) (a: 'a array) = - a.[0..a.Length - 1 - length] + let skipBack (length: int) (a: array<'a>) = + a.[0 .. a.Length - 1 - length] module List = /// Map a Async producing function over a list to get a new Async using /// applicative style. ('a -> Async<'b>) -> 'a list -> Async<'b list> let rec traverseAsyncA f list = let (<*>) = Async.apply - let cons head tail = head :: tail + + let cons head tail = + head :: tail + let initState = Async.result [] + let folder head tail = Async.result cons <*> (f head) <*> tail + List.foldBack folder list initState /// Transform a "list" into a "Async" and collect the results /// using apply. - let sequenceAsyncA x = traverseAsyncA id x - + let sequenceAsyncA x = + traverseAsyncA id x diff --git a/src/ResultUtils/List.fs b/src/ResultUtils/List.fs index 9846caef4..2b194da90 100644 --- a/src/ResultUtils/List.fs +++ b/src/ResultUtils/List.fs @@ -4,23 +4,28 @@ open ResultUtils.Portability [] module List = - let rec private traverseResultM' (state: Result<_, _>) (f: _ -> Result<_, _>) xs = + let rec private traverseResultM' + (state: Result<_, _>) + (f: _ -> Result<_, _>) + xs + = match xs with - | [] -> - state + | [] -> state | x :: xs -> - let r = result { - let! y = f x - let! ys = state - return ys @ [y] - } + let r = + result { + let! y = f x + let! ys = state + return ys @ [ y ] + } + match r with | Ok _ -> traverseResultM' r f xs | Error _ -> r - + let traverseResultM f xs = traverseResultM' (Ok []) f xs - + let sequenceResultM xs = traverseResultM id xs @@ -28,17 +33,16 @@ module List = match xs with | [] -> state | x :: xs -> - let fR = - f x |> Result.mapError List.singleton + let fR = f x |> Result.mapError List.singleton + match state, fR with - | Ok ys, Ok y -> - traverseResultA' (Ok (ys @ [y])) f xs - | Error errs, Error e -> - traverseResultA' (Error(errs @ e)) f xs - | Ok _, Error e | Error e , Ok _ -> - traverseResultA' (Error e) f xs + | Ok ys, Ok y -> traverseResultA' (Ok(ys @ [ y ])) f xs + | Error errs, Error e -> traverseResultA' (Error(errs @ e)) f xs + | Ok _, Error e + | Error e, Ok _ -> traverseResultA' (Error e) f xs let traverseResultA f xs = traverseResultA' (Ok []) f xs + let sequenceResultA xs = - traverseResultA id xs \ No newline at end of file + traverseResultA id xs diff --git a/src/ResultUtils/Option.fs b/src/ResultUtils/Option.fs index e99266289..41ea9b058 100644 --- a/src/ResultUtils/Option.fs +++ b/src/ResultUtils/Option.fs @@ -2,10 +2,10 @@ namespace ResultUtils [] module Option = - let traverseResult f opt = - match opt with - | None -> Ok None - | Some v -> f v |> Result.map Some + let traverseResult f opt = + match opt with + | None -> Ok None + | Some v -> f v |> Result.map Some - let sequenceResult opt = - traverseResult id opt \ No newline at end of file + let sequenceResult opt = + traverseResult id opt diff --git a/src/ResultUtils/OptionCE.fs b/src/ResultUtils/OptionCE.fs index f8f1eef0b..386750dae 100644 --- a/src/ResultUtils/OptionCE.fs +++ b/src/ResultUtils/OptionCE.fs @@ -5,69 +5,97 @@ open System [] module OptionCE = type OptionBuilder() = - member __.Return<'T>(value: 'T): Option<'T> = + member __.Return<'T>(value: 'T) : Option<'T> = Some value - member __.ReturnFrom<'T>(opt: Option<'T>): Option<'T> = + member __.ReturnFrom<'T>(opt: Option<'T>) : Option<'T> = opt - member this.Zero(): Option = - Some () + member this.Zero() : Option = + Some() - member __.Bind<'T, 'U>(opt: Option<'T>, binder: 'T -> Option<'U>) - : Option<'U> = + member __.Bind<'T, 'U> + ( + opt: Option<'T>, + binder: 'T -> Option<'U> + ) : Option<'U> = Option.bind binder opt - member __.Delay<'T>(continuation: unit -> Option<'T>) - : unit -> Option<'T> = + member __.Delay<'T> + (continuation: unit -> Option<'T>) + : unit -> Option<'T> = continuation - member __.Run<'T>(continuation: unit -> Option<'T>) - : Option<'T> = + member __.Run<'T>(continuation: unit -> Option<'T>) : Option<'T> = continuation() - member this.Combine<'T>(opt: Option, binder: unit -> Option<'T>) - : Option<'T> = + member this.Combine<'T> + ( + opt: Option, + binder: unit -> Option<'T> + ) : Option<'T> = this.Bind(opt, binder) - member this.TryWith<'T>(generator: unit -> Option<'T>, handler: exn -> Option<'T>) - : Option<'T> = + member this.TryWith<'T> + ( + generator: unit -> Option<'T>, + handler: exn -> Option<'T> + ) : Option<'T> = try this.Run generator with | e -> handler e - member this.TryFinally<'T>(generator: unit -> Option<'T>, final: unit -> unit) - : Option<'T> = + member this.TryFinally<'T> + ( + generator: unit -> Option<'T>, + final: unit -> unit + ) : Option<'T> = try this.Run generator finally final() - member this.Using<'T, 'U when 'T :> IDisposable>(resource: 'T, binder: 'T -> Option<'U>) - : Option<'U> = - this.TryFinally ( + member this.Using<'T, 'U when 'T :> IDisposable> + ( + resource: 'T, + binder: 'T -> Option<'U> + ) : Option<'U> = + this.TryFinally( (fun () -> binder resource), (fun () -> if not <| obj.ReferenceEquals(resource, null) then - resource.Dispose () + resource.Dispose() ) ) - member this.While(guard: unit -> bool, generator: unit -> Option) - : Option = - if not <| guard () then - this.Zero () + member this.While + ( + guard: unit -> bool, + generator: unit -> Option + ) : Option = + if not <| guard() then + this.Zero() else - this.Bind(this.Run generator, fun () -> this.While (guard, generator)) + this.Bind( + this.Run generator, + fun () -> this.While(guard, generator) + ) - member this.For<'T, 'Sequence when 'Sequence :> seq<'T> >(sequence: 'Sequence, binder: 'T -> Option) - : Option = - this.Using(sequence.GetEnumerator (), fun enumerator -> - this.While(enumerator.MoveNext, this.Delay(fun () -> binder enumerator.Current)) + member this.For<'T, 'Sequence when 'Sequence :> seq<'T>> + ( + sequence: 'Sequence, + binder: 'T -> Option + ) : Option = + this.Using( + sequence.GetEnumerator(), + fun enumerator -> + this.While( + enumerator.MoveNext, + this.Delay(fun () -> binder enumerator.Current) + ) ) [] module OptionCEExtensions = let option = OptionBuilder() - diff --git a/src/ResultUtils/Result.fs b/src/ResultUtils/Result.fs index 9720a7c53..0b9b9be82 100644 --- a/src/ResultUtils/Result.fs +++ b/src/ResultUtils/Result.fs @@ -3,7 +3,7 @@ namespace ResultUtils.Portability #if NoDUsAsStructs [] [] -type Result<'T,'TError> = +type Result<'T, 'TError> = | Ok of ResultValue: 'T | Error of ErrorValue: 'TError #endif @@ -16,187 +16,215 @@ open ResultUtils.Portability [] module Result = - let ToFSharpCoreResult res = + let ToFSharpCoreResult res = #if NoDUsAsStructs - match res with - | Ok o -> FSharp.Core.Result.Ok o - | Error e -> FSharp.Core.Result.Error e + match res with + | Ok o -> FSharp.Core.Result.Ok o + | Error e -> FSharp.Core.Result.Error e #else - res + res #endif - let isOk x = - match x with - | Ok _ -> true - | Error _ -> false - - let isError x = - isOk x |> not - - let either okF errorF x = - match x with - | Ok x -> okF x - | Error err -> errorF err - - let eitherMap okF errorF x = - either (okF >> Ok) (errorF >> Error) x - - let bind binder result = - match result with Error e -> Error e | Ok x -> binder x - - let map mapping result = - match result with Error e -> Error e | Ok x -> Ok (mapping x) - - let mapError mapping result = - match result with Error x -> Error (mapping x) | Ok v -> Ok v - - let apply f x = - bind (fun f' -> - bind (fun x' -> Ok (f' x')) x) f - - let map2 f x y = - (apply (apply (Ok f) x) y) - - let map3 f x y z = - apply (map2 f x y) z - - let fold onOk onError r = - match r with - | Ok x -> onOk x - | Error y -> onError y - - let ofChoice c = - match c with - | Choice1Of2 x -> Ok x - | Choice2Of2 x -> Error x - - let inline tryCreate fieldName (x : 'a) : Result< ^b, (string * 'c)> = - let tryCreate' x = - (^b : (static member TryCreate : 'a -> Result< ^b, 'c>) x) - tryCreate' x - |> mapError (fun z -> (fieldName, z)) - - /// Replaces the wrapped value with unit - let ignore result = - result |> map ignore - - /// Returns the specified error if the value is false. - let requireTrue error value = - if value then Ok () else Error error - - /// Returns the specified error if the value is true. - let requireFalse error value = - if not value then Ok () else Error error - - /// Converts an Option to a Result, using the given error if None. - let requireSome error option = - match option with - | Some x -> Ok x - | None -> Error error - - /// Converts an Option to a Result, using the given error if Some. - let requireNone error option = - match option with - | Some _ -> Error error - | None -> Ok () - - /// Converts a nullable value into a Result, using the given error if null - let requireNotNull error value = - match value with - | null -> Error error - | nonnull -> Ok nonnull - - /// Returns Ok if the two values are equal, or the specified error if not. - /// Same as requireEqual, but with a signature that fits piping better than - /// normal function application. - let requireEqualTo other err this = - if this = other then Ok () else Error err - - /// Returns Ok if the two values are equal, or the specified error if not. - /// Same as requireEqualTo, but with a signature that fits normal function - /// application better than piping. - let requireEqual x1 x2 error = - if x1 = x2 then Ok () else Error error - - /// Returns Ok if the sequence is empty, or the specified error if not. - let requireEmpty error xs = - if Seq.isEmpty xs then Ok () else Error error - - /// Returns the specified error if the sequence is empty, or Ok if not. - let requireNotEmpty error xs = - if Seq.isEmpty xs then Error error else Ok () - - /// Returns the first item of the sequence if it exists, or the specified - /// error if the sequence is empty - let requireHead error xs = - match Seq.tryHead xs with - | Some x -> Ok x - | None -> Error error - - /// Replaces an error value with a custom error value. - let setError error result = - result |> mapError (fun _ -> error) - - /// Replaces a unit error value with a custom error value. Safer than setError - /// since you're not losing any information. - let withError error result = - result |> mapError (fun () -> error) - - /// Returns the contained value if Ok, otherwise returns ifError. - let defaultValue ifError result = - match result with - | Ok x -> x - | Error _ -> ifError - - /// Returns the contained value if Ok, otherwise evaluates ifErrorThunk and - /// returns the result. - let defaultWith ifErrorThunk result = - match result with - | Ok x -> x - | Error _ -> ifErrorThunk () - - let deref result = - match result with - | Ok x -> x - | Error e -> failwithf "Failed to dereference Result (%A)" e - /// Same as defaultValue for a result where the Ok value is unit. The name - /// describes better what is actually happening in this case. - let ignoreError result = - defaultValue () result - - /// If the result is Ok and the predicate returns true, executes the function - /// on the Ok value. Passes through the input value. - let teeIf predicate f result = - match result with - | Ok x -> - if predicate x then f x - | Error _ -> () - result - - /// If the result is Error and the predicate returns true, executes the - /// function on the Error value. Passes through the input value. - let teeErrorIf predicate f result = - match result with - | Ok _ -> () - | Error x -> - if predicate x then f x - result - - /// If the result is Ok, executes the function on the Ok value. Passes through - /// the input value. - let tee f result = - teeIf (fun _ -> true) f result - - /// If the result is Error, executes the function on the Error value. Passes - /// through the input value. - let teeError f result = - teeErrorIf (fun _ -> true) f result - - let sequenceAsync (resAsync: Result, 'b>) : Async> = - async { - match resAsync with - | Ok asnc -> - let! x = asnc - return Ok x - | Error err -> return Error err - } \ No newline at end of file + let isOk x = + match x with + | Ok _ -> true + | Error _ -> false + + let isError x = + isOk x |> not + + let either okF errorF x = + match x with + | Ok x -> okF x + | Error err -> errorF err + + let eitherMap okF errorF x = + either (okF >> Ok) (errorF >> Error) x + + let bind binder result = + match result with + | Error e -> Error e + | Ok x -> binder x + + let map mapping result = + match result with + | Error e -> Error e + | Ok x -> Ok(mapping x) + + let mapError mapping result = + match result with + | Error x -> Error(mapping x) + | Ok v -> Ok v + + let apply f x = + bind (fun f' -> bind (fun x' -> Ok(f' x')) x) f + + let map2 f x y = + (apply (apply (Ok f) x) y) + + let map3 f x y z = + apply (map2 f x y) z + + let fold onOk onError r = + match r with + | Ok x -> onOk x + | Error y -> onError y + + let ofChoice c = + match c with + | Choice1Of2 x -> Ok x + | Choice2Of2 x -> Error x + + let inline tryCreate fieldName (x: 'a) : Result< ^b, (string * 'c) > = + let tryCreate' x = + (^b: (static member TryCreate: 'a -> Result< ^b, 'c >) x) + + tryCreate' x |> mapError(fun z -> (fieldName, z)) + + /// Replaces the wrapped value with unit + let ignore result = + result |> map ignore + + /// Returns the specified error if the value is false. + let requireTrue error value = + if value then + Ok() + else + Error error + + /// Returns the specified error if the value is true. + let requireFalse error value = + if not value then + Ok() + else + Error error + + /// Converts an Option to a Result, using the given error if None. + let requireSome error option = + match option with + | Some x -> Ok x + | None -> Error error + + /// Converts an Option to a Result, using the given error if Some. + let requireNone error option = + match option with + | Some _ -> Error error + | None -> Ok() + + /// Converts a nullable value into a Result, using the given error if null + let requireNotNull error value = + match value with + | null -> Error error + | nonnull -> Ok nonnull + + /// Returns Ok if the two values are equal, or the specified error if not. + /// Same as requireEqual, but with a signature that fits piping better than + /// normal function application. + let requireEqualTo other err this = + if this = other then + Ok() + else + Error err + + /// Returns Ok if the two values are equal, or the specified error if not. + /// Same as requireEqualTo, but with a signature that fits normal function + /// application better than piping. + let requireEqual x1 x2 error = + if x1 = x2 then + Ok() + else + Error error + + /// Returns Ok if the sequence is empty, or the specified error if not. + let requireEmpty error xs = + if Seq.isEmpty xs then + Ok() + else + Error error + + /// Returns the specified error if the sequence is empty, or Ok if not. + let requireNotEmpty error xs = + if Seq.isEmpty xs then + Error error + else + Ok() + + /// Returns the first item of the sequence if it exists, or the specified + /// error if the sequence is empty + let requireHead error xs = + match Seq.tryHead xs with + | Some x -> Ok x + | None -> Error error + + /// Replaces an error value with a custom error value. + let setError error result = + result |> mapError(fun _ -> error) + + /// Replaces a unit error value with a custom error value. Safer than setError + /// since you're not losing any information. + let withError error result = + result |> mapError(fun () -> error) + + /// Returns the contained value if Ok, otherwise returns ifError. + let defaultValue ifError result = + match result with + | Ok x -> x + | Error _ -> ifError + + /// Returns the contained value if Ok, otherwise evaluates ifErrorThunk and + /// returns the result. + let defaultWith ifErrorThunk result = + match result with + | Ok x -> x + | Error _ -> ifErrorThunk() + + let deref result = + match result with + | Ok x -> x + | Error e -> failwithf "Failed to dereference Result (%A)" e + + /// Same as defaultValue for a result where the Ok value is unit. The name + /// describes better what is actually happening in this case. + let ignoreError result = + defaultValue () result + + /// If the result is Ok and the predicate returns true, executes the function + /// on the Ok value. Passes through the input value. + let teeIf predicate f result = + match result with + | Ok x -> + if predicate x then + f x + | Error _ -> () + + result + + /// If the result is Error and the predicate returns true, executes the + /// function on the Error value. Passes through the input value. + let teeErrorIf predicate f result = + match result with + | Ok _ -> () + | Error x -> + if predicate x then + f x + + result + + /// If the result is Ok, executes the function on the Ok value. Passes through + /// the input value. + let tee f result = + teeIf (fun _ -> true) f result + + /// If the result is Error, executes the function on the Error value. Passes + /// through the input value. + let teeError f result = + teeErrorIf (fun _ -> true) f result + + let sequenceAsync(resAsync: Result, 'b>) : Async> = + async { + match resAsync with + | Ok asnc -> + let! x = asnc + return Ok x + | Error err -> return Error err + } diff --git a/src/ResultUtils/ResultCE.fs b/src/ResultUtils/ResultCE.fs index 85d6a21f9..4db955910 100644 --- a/src/ResultUtils/ResultCE.fs +++ b/src/ResultUtils/ResultCE.fs @@ -7,85 +7,121 @@ open ResultUtils.Portability [] module ResultCE = - type ResultBuilder() = - member __.Return (v: 'T) : Result<'T, 'TError> = - Ok v - - member __.ReturnFrom (result: Result<'T, 'TError>) : Result<'T, 'TError> = - result - - member this.Zero () : Result = - this.Return () - - member __.Bind - (result: Result<'T, 'TError>, binder: 'T -> Result<'U, 'TError>) - : Result<'U, 'TError> = - Result.bind binder result - - member __.Delay - (generator: unit -> Result<'T, 'TError>) - : unit -> Result<'T, 'TError> = - generator - - member __.Run - (generator: unit -> Result<'T, 'TError>) - : Result<'T, 'TError> = - generator () - - member this.Combine - (result: Result, binder: unit -> Result<'T, 'TError>) - : Result<'T, 'TError> = - this.Bind(result, binder) - - member this.TryWith - (generator: unit -> Result<'T, 'TError>, - handler: exn -> Result<'T, 'TError>) - : Result<'T, 'TError> = - try this.Run generator with | e -> handler e - - member this.TryFinally - (generator: unit -> Result<'T, 'TError>, compensation: unit -> unit) - : Result<'T, 'TError> = - try this.Run generator finally compensation () - - member this.Using - (resource: 'T when 'T :> IDisposable, binder: 'T -> Result<'U, 'TError>) - : Result<'U, 'TError> = - this.TryFinally ( - (fun () -> binder resource), - (fun () -> if not <| obj.ReferenceEquals(resource, null) then resource.Dispose ()) - ) - - member this.While - (guard: unit -> bool, generator: unit -> Result) - : Result = - if not <| guard () then this.Zero () - else this.Bind(this.Run generator, fun () -> this.While (guard, generator)) - - member this.For - (sequence: #seq<'T>, binder: 'T -> Result) - : Result = - this.Using(sequence.GetEnumerator (), fun enum -> - this.While(enum.MoveNext, - this.Delay(fun () -> binder enum.Current))) + type ResultBuilder() = + member __.Return(v: 'T) : Result<'T, 'TError> = + Ok v + + member __.ReturnFrom + (result: Result<'T, 'TError>) + : Result<'T, 'TError> = + result + + member this.Zero() : Result = + this.Return() + + member __.Bind + ( + result: Result<'T, 'TError>, + binder: 'T -> Result<'U, 'TError> + ) : Result<'U, 'TError> = + Result.bind binder result + + member __.Delay + (generator: unit -> Result<'T, 'TError>) + : unit -> Result<'T, 'TError> = + generator + + member __.Run + (generator: unit -> Result<'T, 'TError>) + : Result<'T, 'TError> = + generator() + + member this.Combine + ( + result: Result, + binder: unit -> Result<'T, 'TError> + ) : Result<'T, 'TError> = + this.Bind(result, binder) + + member this.TryWith + ( + generator: unit -> Result<'T, 'TError>, + handler: exn -> Result<'T, 'TError> + ) : Result<'T, 'TError> = + try + this.Run generator + with + | e -> handler e + + member this.TryFinally + ( + generator: unit -> Result<'T, 'TError>, + compensation: unit -> unit + ) : Result<'T, 'TError> = + try + this.Run generator + finally + compensation() + + member this.Using + ( + resource: 'T :> IDisposable, + binder: 'T -> Result<'U, 'TError> + ) : Result<'U, 'TError> = + this.TryFinally( + (fun () -> binder resource), + (fun () -> + if not <| obj.ReferenceEquals(resource, null) then + resource.Dispose() + ) + ) + + member this.While + ( + guard: unit -> bool, + generator: unit -> Result + ) : Result = + if not <| guard() then + this.Zero() + else + this.Bind( + this.Run generator, + fun () -> this.While(guard, generator) + ) + + member this.For + ( + sequence: #seq<'T>, + binder: 'T -> Result + ) : Result = + this.Using( + sequence.GetEnumerator(), + fun enum -> + this.While( + enum.MoveNext, + this.Delay(fun () -> binder enum.Current) + ) + ) [] module ResultCEExtensions = - // Having Choice<_> members as extensions gives them lower priority in - // overload resolution and allows skipping more type annotations. - type ResultBuilder with + // Having Choice<_> members as extensions gives them lower priority in + // overload resolution and allows skipping more type annotations. + type ResultBuilder with - member __.ReturnFrom (result: Choice<'T, 'TError>) : Result<'T, 'TError> = - Result.ofChoice result + member __.ReturnFrom + (result: Choice<'T, 'TError>) + : Result<'T, 'TError> = + Result.ofChoice result - member __.Bind - (result: Choice<'T, 'TError>, binder: 'T -> Result<'U, 'TError>) - : Result<'U, 'TError> = - result - |> Result.ofChoice - |> Result.bind binder + member __.Bind + ( + result: Choice<'T, 'TError>, + binder: 'T -> Result<'U, 'TError> + ) : Result<'U, 'TError> = + result |> Result.ofChoice |> Result.bind binder - let result = ResultBuilder() \ No newline at end of file + let result = ResultBuilder() diff --git a/src/ResultUtils/ResultOp.fs b/src/ResultUtils/ResultOp.fs index 2b77b736b..bca6c9af2 100644 --- a/src/ResultUtils/ResultOp.fs +++ b/src/ResultUtils/ResultOp.fs @@ -2,6 +2,11 @@ namespace ResultUtils [] module ResultOp = - let inline () f x = Result.map f x - let inline (<*>) f x = Result.apply f x - let inline (>>=) x f = Result.bind f x + let inline () f x = + Result.map f x + + let inline (<*>) f x = + Result.apply f x + + let inline (>>=) x f = + Result.bind f x diff --git a/src/ResultUtils/ResultOption.fs b/src/ResultUtils/ResultOption.fs index baa46b445..8d49c46d7 100644 --- a/src/ResultUtils/ResultOption.fs +++ b/src/ResultUtils/ResultOption.fs @@ -5,25 +5,28 @@ open ResultUtils.Portability [] module ResultOption = - let map f ro = - Result.map(Option.map f) ro + let map f ro = + Result.map (Option.map f) ro - let bind f ro = - Result.bind (function | Some x -> f x | None -> Ok None) ro + let bind f ro = + Result.bind + (function + | Some x -> f x + | None -> Ok None) + ro - let retn x = - Ok (Some x) + let retn x = + Ok(Some x) - let apply f x = - bind (fun f' -> - bind (f' >> retn) x) f + let apply f x = + bind (fun f' -> bind (f' >> retn) x) f - let map2 f x y = - (apply (apply (retn f) x) y) - - let map3 f x y z = - apply (map2 f x y) z + let map2 f x y = + (apply (apply (retn f) x) y) - /// Replaces the wrapped value with unit - let ignore ro = - ro |> map ignore \ No newline at end of file + let map3 f x y z = + apply (map2 f x y) z + + /// Replaces the wrapped value with unit + let ignore ro = + ro |> map ignore diff --git a/src/ResultUtils/ResultOptionCE.fs b/src/ResultUtils/ResultOptionCE.fs index b3a1373dd..3266ffcd9 100644 --- a/src/ResultUtils/ResultOptionCE.fs +++ b/src/ResultUtils/ResultOptionCE.fs @@ -6,18 +6,21 @@ open ResultUtils.Portability [] module ResultOptionCE = - type ResultOptionBuilder() = - member __.Return value = ResultOption.retn value - member __.ReturnFrom value = value + type ResultOptionBuilder() = + member __.Return value = + ResultOption.retn value - member __.Bind (resultOpt, binder) = - ResultOption.bind binder resultOpt + member __.ReturnFrom value = + value - member __.Combine(r1, r2) = - r1 - |> ResultOption.bind (fun _ -> r2) + member __.Bind(resultOpt, binder) = + ResultOption.bind binder resultOpt - member __.Delay f = f () + member __.Combine(r1, r2) = + r1 |> ResultOption.bind(fun _ -> r2) + member __.Delay f = + f() - let resultOption = ResultOptionBuilder() \ No newline at end of file + + let resultOption = ResultOptionBuilder() diff --git a/src/ResultUtils/ResultOptionOp.fs b/src/ResultUtils/ResultOptionOp.fs index dde2d9fbc..2b7b40110 100644 --- a/src/ResultUtils/ResultOptionOp.fs +++ b/src/ResultUtils/ResultOptionOp.fs @@ -3,7 +3,14 @@ namespace ResultUtils [] module ResultOptionOp = - let inline () f x = ResultOption.map f x - let inline (<*>) f x = ResultOption.apply f x - let inline (<*^>) f x = ResultOption.apply f (Result.map Some x) - let inline (>>=) x f = ResultOption.bind f x \ No newline at end of file + let inline () f x = + ResultOption.map f x + + let inline (<*>) f x = + ResultOption.apply f x + + let inline (<*^>) f x = + ResultOption.apply f (Result.map Some x) + + let inline (>>=) x f = + ResultOption.bind f x diff --git a/src/ResultUtils/Validation.fs b/src/ResultUtils/Validation.fs index 0d273f268..ac3725df3 100644 --- a/src/ResultUtils/Validation.fs +++ b/src/ResultUtils/Validation.fs @@ -4,23 +4,28 @@ open ResultUtils.Portability [] module Validation = - let ofResult x = - Result.mapError List.singleton x + let ofResult x = + Result.mapError List.singleton x - let apply f x = - match f, x with - | Ok f, Ok x -> Ok (f x) - | Error errs, Ok _ | Ok _, Error errs -> Error errs - | Error errors1, Error errors2 -> Error (errors1 @ errors2) + let apply f x = + match f, x with + | Ok f, Ok x -> Ok(f x) + | Error errs, Ok _ + | Ok _, Error errs -> Error errs + | Error errors1, Error errors2 -> Error(errors1 @ errors2) - let retn x = ofResult (Ok x) + let retn x = + ofResult(Ok x) - let map2 f x y = - apply (apply (retn f ) x ) y + let map2 f x y = + apply (apply (retn f) x) y - let map3 f x y z = - apply (map2 f x y) z - - let inline lift2 (f: 'a -> 'b -> 'c) (x: Result<'a, _>) (y: Result<'b, _>): Result<'c, _> = - apply (apply (Ok f) x) y + let map3 f x y z = + apply (map2 f x y) z + let inline lift2 + (f: 'a -> 'b -> 'c) + (x: Result<'a, _>) + (y: Result<'b, _>) + : Result<'c, _> = + apply (apply (Ok f) x) y diff --git a/src/ResultUtils/ValidationOp.fs b/src/ResultUtils/ValidationOp.fs index 5ab4ed806..04c031843 100644 --- a/src/ResultUtils/ValidationOp.fs +++ b/src/ResultUtils/ValidationOp.fs @@ -4,17 +4,27 @@ open ResultUtils.Portability [] module ValidationOp = - let inline () f (x: Result<'a, 'b list>) = Result.map f x - let inline () f x = - x - |> Result.mapError List.singleton - |> Result.map f - let inline (<*>) f x = Validation.apply f x - let inline (<*^>) f x = Validation.apply f (Validation.ofResult x) - - - let inline ( *> ) m1 m2 = Validation.lift2 (fun _ y -> y) m1 m2 - let inline ( <* ) m1 m2 = Validation.lift2 (fun x _ -> x) m1 m2 - - let inline ( *^> ) m1 m2 = Validation.lift2 (fun _ y -> y) m1 (Validation.ofResult m2) - let inline ( <*^ ) m1 m2 = Validation.lift2 (fun x _ -> x) m1 (Validation.ofResult m2) + let inline () f (x: Result<'a, list<'b>>) = + Result.map f x + + let inline () f x = + x |> Result.mapError List.singleton |> Result.map f + + let inline (<*>) f x = + Validation.apply f x + + let inline (<*^>) f x = + Validation.apply f (Validation.ofResult x) + + + let inline ( *> ) m1 m2 = + Validation.lift2 (fun _ y -> y) m1 m2 + + let inline (<*) m1 m2 = + Validation.lift2 (fun x _ -> x) m1 m2 + + let inline ( *^> ) m1 m2 = + Validation.lift2 (fun _ y -> y) m1 (Validation.ofResult m2) + + let inline (<*^) m1 m2 = + Validation.lift2 (fun x _ -> x) m1 (Validation.ofResult m2) diff --git a/src/TaskUtils/Ply.fs b/src/TaskUtils/Ply.fs index c91f7f257..7b49734da 100644 --- a/src/TaskUtils/Ply.fs +++ b/src/TaskUtils/Ply.fs @@ -4,6 +4,7 @@ // Distributed under the MIT License (https://opensource.org/licenses/MIT). namespace rec Ply + open System open System.Runtime.CompilerServices open System.Runtime.InteropServices @@ -12,80 +13,111 @@ open System.Diagnostics open System.Runtime.ExceptionServices module Internal = - type [] Awaitable<'u>() = - abstract member Await<'t when 't :> IAwaitingMachine> : machine: byref<'t> -> bool + [] + type Awaitable<'u>() = + abstract member Await<'t when 't :> IAwaitingMachine> : + machine: byref<'t> -> bool + abstract member GetNext: unit -> Ply<'u> - and IAwaitingMachine = - abstract member AwaitUnsafeOnCompleted<'awt when 'awt :> ICriticalNotifyCompletion> : awt: byref<'awt> -> unit -#if NETSTANDARD2_0 -type [] Ply<'u> = -#else -type [] Ply<'u> = + and IAwaitingMachine = + abstract member AwaitUnsafeOnCompleted<'awt when 'awt :> ICriticalNotifyCompletion> : + awt: byref<'awt> -> unit + +#if NETSTANDARD2_0 +[] +type Ply<'u> = +#else +[] +type Ply<'u> = #endif - val internal value : 'u - val internal awaitable : Internal.Awaitable<'u> - new(result: 'u) = { value = result; awaitable = null } - new(await: Internal.Awaitable<'u>) = { value = Unchecked.defaultof<_>; awaitable = await } - member this.IsCompletedSuccessfully = Object.ReferenceEquals(this.awaitable, null) - member this.Result = if this.IsCompletedSuccessfully then this.value else this.awaitable.GetNext().Result + val internal value: 'u + val internal awaitable: Internal.Awaitable<'u> + + new(result: 'u) = + { + value = result + awaitable = null + } + + new(await: Internal.Awaitable<'u>) = + { + value = Unchecked.defaultof<_> + awaitable = await + } + + member this.IsCompletedSuccessfully = + Object.ReferenceEquals(this.awaitable, null) + + member this.Result = + if this.IsCompletedSuccessfully then + this.value + else + this.awaitable.GetNext().Result [] -/// Entrypoint for generated code module TplPrimitives = open Internal - - type IAwaiterMethods<'awt, 'res when 'awt :> ICriticalNotifyCompletion> = + + type IAwaiterMethods<'awt, 'res when 'awt :> ICriticalNotifyCompletion> = abstract member IsCompleted: byref<'awt> -> bool abstract member GetResult: byref<'awt> -> 'res - let inline createBuilder() = + let inline createBuilder() = #if NETSTANDARD2_0 AsyncTaskMethodBuilder<_>() -#else +#else AsyncValueTaskMethodBuilder<_>() -#endif +#endif let inline defaultof<'T> = Unchecked.defaultof<'T> - let inline isNull x = Object.ReferenceEquals(x, null) - let inline isNotNull x = not (isNull x) - - let inline throwPreserve ex = + let inline isNull x = + Object.ReferenceEquals(x, null) + + let inline isNotNull x = + not(isNull x) + + let inline throwPreserve ex = (ExceptionDispatchInfo.Capture ex).Throw() Unchecked.defaultof<_> - let ret x = Ply(result = x) - let zero = ret () + let ret x = + Ply(result = x) - type []ContinuationStateMachine<'u> = -#if NETSTANDARD2_0 - val Builder : AsyncTaskMethodBuilder<'u> + let zero = ret() + + [] + type ContinuationStateMachine<'u> = +#if NETSTANDARD2_0 + val Builder: AsyncTaskMethodBuilder<'u> #else - val Builder : AsyncValueTaskMethodBuilder<'u> + val Builder: AsyncValueTaskMethodBuilder<'u> #endif val mutable private next: Ply<'u> val mutable private inspect: bool val mutable private continuation: unit -> Ply<'u> - new(continuation) = { - Builder = createBuilder() - continuation = continuation - next = defaultof<_> - inspect = true - } - - new(ply) = { - Builder = createBuilder() - continuation = defaultof<_> - next = ply - inspect = true - } + new(continuation) = + { + Builder = createBuilder() + continuation = continuation + next = defaultof<_> + inspect = true + } + + new(ply) = + { + Builder = createBuilder() + continuation = defaultof<_> + next = ply + inspect = true + } member private this.UnsafeExecuteToFirstYield() = this.next <- this.continuation() this.continuation <- defaultof<_> - + interface IAwaitingMachine with [] [] @@ -94,23 +126,27 @@ module TplPrimitives = interface IAsyncStateMachine with // This method is effectively deprecated on .NET Core so only .NET Fx will still call this. - member this.SetStateMachine(csm) = + member this.SetStateMachine csm = this.Builder.SetStateMachine(csm) - - member this.MoveNext() = + + member this.MoveNext() = let mutable ex = defaultof + try - if isNotNull this.continuation then this.UnsafeExecuteToFirstYield() + if isNotNull this.continuation then + this.UnsafeExecuteToFirstYield() let mutable fin = false + while not fin do if this.inspect then let next = this.next + if this.next.IsCompletedSuccessfully then fin <- true - this.Builder.SetResult(this.next.value) - else - this.inspect <- false + this.Builder.SetResult(this.next.value) + else + this.inspect <- false let yielded = next.awaitable.Await(&this) // MoveNext will be called again by the builder once await is done. if yielded then @@ -118,68 +154,89 @@ module TplPrimitives = else this.inspect <- true this.next <- this.next.awaitable.GetNext() - with exn -> - ex <- exn - - if isNotNull ex then + with + | exn -> ex <- exn + + if isNotNull ex then this.Builder.SetException(ex) and [] TplAwaitable<'methods, 'awt, 't, 'u when 'methods :> IAwaiterMethods<'awt, 't> and 'awt :> ICriticalNotifyCompletion> = inherit Awaitable<'u> - + val private awaiterMethods: 'methods val mutable private awaiter: 'awt val private continuation: 't -> Ply<'u> - - new(awaiterMethods, awaiter, continuation) = { - awaiterMethods = awaiterMethods - awaiter = awaiter - continuation = continuation - } - - override this.Await(csm) = + + new(awaiterMethods, awaiter, continuation) = + { + awaiterMethods = awaiterMethods + awaiter = awaiter + continuation = continuation + } + + override this.Await csm = if this.awaiterMethods.IsCompleted &this.awaiter then false else csm.AwaitUnsafeOnCompleted(&this.awaiter) |> ignore - true + true - override this.GetNext() = - Debug.Assert(this.awaiterMethods.IsCompleted &this.awaiter || (typeof<'awt> = typeof), "Forcing an async here") - this.continuation (this.awaiterMethods.GetResult &this.awaiter) - - and [] PlyAwaitable<'t, 'u> (awaitable: Awaitable<'t>, continuation: 't -> Ply<'u>) = + override this.GetNext() = + Debug.Assert( + this.awaiterMethods.IsCompleted &this.awaiter + || (typeof<'awt> = typeof), + "Forcing an async here" + ) + + this.continuation(this.awaiterMethods.GetResult &this.awaiter) + + and [] PlyAwaitable<'t, 'u> + ( + awaitable: Awaitable<'t>, + continuation: 't -> Ply<'u> + ) = inherit Awaitable<'u>() let mutable awaitable = awaitable - override __.Await(csm) = awaitable.Await(&csm) + override __.Await csm = + awaitable.Await(&csm) - override this.GetNext() = + override this.GetNext() = let next = awaitable.GetNext() + if next.IsCompletedSuccessfully then - continuation (next.value) + continuation(next.value) else awaitable <- next.awaitable Ply(await = this) - and [] LoopAwaitable(initialAwaitable : Awaitable, cond: unit -> bool, body : unit -> Ply) = + and [] LoopAwaitable + ( + initialAwaitable: Awaitable, + cond: unit -> bool, + body: unit -> Ply + ) = inherit Awaitable() - let mutable awaitable : Awaitable = initialAwaitable + let mutable awaitable: Awaitable = initialAwaitable member private this.RepeatBody() = if cond() then let next = body() - if next.IsCompletedSuccessfully then + + if next.IsCompletedSuccessfully then this.RepeatBody() else awaitable <- next.awaitable Ply(await = this) - else zero - - override __.Await(csm) = awaitable.Await(&csm) + else + zero + + override __.Await csm = + awaitable.Await(&csm) override this.GetNext() = let next = awaitable.GetNext() + if next.IsCompletedSuccessfully then this.RepeatBody() else @@ -187,13 +244,13 @@ module TplPrimitives = Ply(await = this) #if !NETSTANDARD2_0 - let run (f: unit -> Ply<'u>) : ValueTask<'u> = + let run(f: unit -> Ply<'u>) : ValueTask<'u> = // ContinuationStateMachine contains a mutable struct so we need to prevent struct copies. let mutable x = ContinuationStateMachine<_>(f) x.Builder.Start(&x) x.Builder.Task - let runPly (ply: Ply<'u>) : ValueTask<'u> = + let runPly(ply: Ply<'u>) : ValueTask<'u> = let mutable x = ContinuationStateMachine<_>(ply) x.Builder.Start(&x) x.Builder.Task @@ -201,17 +258,18 @@ module TplPrimitives = // This won't correctly prevent AsyncLocal leakage or SyncContext switches but it does save us the closure alloc // Making only this version completely alloc free for the fast path... // Read more here https://github.com/dotnet/coreclr/blob/027a9105/src/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncMethodBuilder.cs#L954 - let inline runUnwrapped (f: unit -> Ply<'u>) : ValueTask<'u> = + let inline runUnwrapped(f: unit -> Ply<'u>) : ValueTask<'u> = let next = f() - if next.IsCompletedSuccessfully then + + if next.IsCompletedSuccessfully then let mutable b = createBuilder() b.SetResult(next.Result) b.Task - else + else runPly next #endif - let runAsTask (f: unit -> Ply<'u>) : Task<'u> = + let runAsTask(f: unit -> Ply<'u>) : Task<'u> = // ContinuationStateMachine contains a mutable struct so we need to prevent struct copies. let mutable x = ContinuationStateMachine<_>(f) x.Builder.Start(&x) @@ -221,7 +279,7 @@ module TplPrimitives = x.Builder.Task.AsTask() #endif - let runPlyAsTask (ply: Ply<'u>) : Task<'u> = + let runPlyAsTask(ply: Ply<'u>) : Task<'u> = let mutable x = ContinuationStateMachine<_>(ply) x.Builder.Start(&x) #if NETSTANDARD2_0 @@ -233,9 +291,10 @@ module TplPrimitives = // This won't correctly prevent AsyncLocal leakage or SyncContext switches but it does save us the closure alloc // Making only this version completely alloc free for the fast path... // Read more here https://github.com/dotnet/coreclr/blob/027a9105/src/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncMethodBuilder.cs#L954 - let inline runUnwrappedAsTask (f: unit -> Ply<'u>) : Task<'u> = + let inline runUnwrappedAsTask(f: unit -> Ply<'u>) : Task<'u> = let next = f() - if next.IsCompletedSuccessfully then + + if next.IsCompletedSuccessfully then let mutable b = createBuilder() b.SetResult(next.Result) #if NETSTANDARD2_0 @@ -243,121 +302,192 @@ module TplPrimitives = #else b.Task.AsTask() #endif - else + else runPlyAsTask next - let combine (ply : Ply) (continuation : unit -> Ply<'b>) = - if ply.IsCompletedSuccessfully then - continuation() - else + let combine (ply: Ply) (continuation: unit -> Ply<'b>) = + if ply.IsCompletedSuccessfully then + continuation() + else Ply(await = PlyAwaitable(ply.awaitable, continuation)) - let rec whileLoop (cond : unit -> bool) (body : unit -> Ply) = + let rec whileLoop (cond: unit -> bool) (body: unit -> Ply) = // As long as we never yield loops are allocation free if cond() then let next = body() - if next.IsCompletedSuccessfully then + + if next.IsCompletedSuccessfully then whileLoop cond body else Ply(await = LoopAwaitable(next.awaitable, cond, body)) - else zero - - let tryWith(continuation : unit -> Ply<'u>) (catch : exn -> Ply<'u>) = + else + zero + + let tryWith (continuation: unit -> Ply<'u>) (catch: exn -> Ply<'u>) = try let next = continuation() - if next.IsCompletedSuccessfully then next else + + if next.IsCompletedSuccessfully then + next + else let mutable awaitable = next.awaitable - Ply(await = { new Awaitable<'u>() with - override __.Await(csm) = awaitable.Await(&csm) - override this.GetNext() = - try - let next = awaitable.GetNext() - if next.IsCompletedSuccessfully then next else - awaitable <- next.awaitable - Ply(await = this) - with ex -> catch ex - }) - with ex -> catch ex - - let tryFinally (continuation : unit -> Ply<'u>) (finallyBody : unit -> unit) = - let inline withFinally f = - try f() - with ex -> + + Ply( + await = + { new Awaitable<'u>() with + override __.Await csm = + awaitable.Await(&csm) + + override this.GetNext() = + try + let next = awaitable.GetNext() + + if next.IsCompletedSuccessfully then + next + else + awaitable <- next.awaitable + Ply(await = this) + with + | ex -> catch ex + } + ) + with + | ex -> catch ex + + let tryFinally (continuation: unit -> Ply<'u>) (finallyBody: unit -> unit) = + let inline withFinally f = + try + f() + with + | ex -> finallyBody() throwPreserve ex let next = withFinally continuation - if next.IsCompletedSuccessfully then + + if next.IsCompletedSuccessfully then finallyBody() - next - else + next + else let mutable awaitable = next.awaitable - Ply(await = { new Awaitable<'u>() with - override __.Await(csm) = awaitable.Await(&csm) - override this.GetNext() = - let next = withFinally awaitable.GetNext - if next.IsCompletedSuccessfully then - finallyBody() - next - else - awaitable <- next.awaitable - Ply(await = this) - }) - - let using (disposable : #IDisposable) (body : #IDisposable -> Ply<'u>) = - tryFinally - (fun () -> body disposable) - (fun () -> if isNotNull disposable then disposable.Dispose()) - - let forLoop (sequence : 'a seq) (body : 'a -> Ply) = - using (sequence.GetEnumerator()) (fun e -> whileLoop e.MoveNext (fun () -> body e.Current)) - - type []TaskAwaiterMethods<'t> = - interface IAwaiterMethods, 't> with - member __.IsCompleted awt = awt.IsCompleted - member __.GetResult awt = awt.GetResult() - and []UnitTaskAwaiterMethods<'t> = - interface IAwaiterMethods with - member __.IsCompleted awt = awt.IsCompleted - member __.GetResult awt = awt.GetResult(); defaultof<_> // Always unit - - and []ConfiguredTaskAwaiterMethods<'t> = - interface IAwaiterMethods.ConfiguredTaskAwaiter, 't> with - member __.IsCompleted awt = awt.IsCompleted - member __.GetResult awt = awt.GetResult() - and []ConfiguredUnitTaskAwaiterMethods<'t> = - interface IAwaiterMethods with - member __.IsCompleted awt = awt.IsCompleted - member __.GetResult awt = awt.GetResult(); defaultof<_> // Always unit - - and []YieldAwaiterMethods<'t> = - interface IAwaiterMethods with - member __.IsCompleted awt = awt.IsCompleted - member __.GetResult awt = awt.GetResult(); defaultof<_> // Always unit - - and []GenericAwaiterMethods<'awt, 't when 'awt :> ICriticalNotifyCompletion> = - interface IAwaiterMethods<'awt, 't> with - member __.IsCompleted awt = false // Always await, this way we don't have to specialize per awaiter - member __.GetResult awt = defaultof<_> // Always unit because we wrap this continuation to always be unit -> Ply<'u> - -#if !NETSTANDARD2_0 - and []ValueTaskAwaiterMethods<'t> = - interface IAwaiterMethods, 't> with - member __.IsCompleted awt = awt.IsCompleted - member __.GetResult awt = awt.GetResult() - and []UnitValueTaskAwaiterMethods<'t> = - interface IAwaiterMethods with - member __.IsCompleted awt = awt.IsCompleted - member __.GetResult awt = awt.GetResult(); defaultof<_> // Always unit - - and []ConfiguredValueTaskAwaiterMethods<'t> = - interface IAwaiterMethods.ConfiguredValueTaskAwaiter, 't> with - member __.IsCompleted awt = awt.IsCompleted - member __.GetResult awt = awt.GetResult() - and []ConfiguredUnitValueTaskAwaiterMethods<'t> = - interface IAwaiterMethods with - member __.IsCompleted awt = awt.IsCompleted - member __.GetResult awt = awt.GetResult(); defaultof<_> // Always unit + + Ply( + await = + { new Awaitable<'u>() with + override __.Await csm = + awaitable.Await(&csm) + + override this.GetNext() = + let next = withFinally awaitable.GetNext + + if next.IsCompletedSuccessfully then + finallyBody() + next + else + awaitable <- next.awaitable + Ply(await = this) + } + ) + + let using (disposable: #IDisposable) (body: #IDisposable -> Ply<'u>) = + tryFinally + (fun () -> body disposable) + (fun () -> + if isNotNull disposable then + disposable.Dispose() + ) + + let forLoop (sequence: seq<'a>) (body: 'a -> Ply) = + using + (sequence.GetEnumerator()) + (fun e -> whileLoop e.MoveNext (fun () -> body e.Current)) + + [] + type TaskAwaiterMethods<'t> = + interface IAwaiterMethods, 't> with + member __.IsCompleted awt = + awt.IsCompleted + + member __.GetResult awt = + awt.GetResult() + + and [] UnitTaskAwaiterMethods<'t> = + interface IAwaiterMethods with + member __.IsCompleted awt = + awt.IsCompleted + + member __.GetResult awt = + awt.GetResult() + defaultof<_> // Always unit + + and [] ConfiguredTaskAwaiterMethods<'t> = + interface IAwaiterMethods.ConfiguredTaskAwaiter, 't> with + member __.IsCompleted awt = + awt.IsCompleted + + member __.GetResult awt = + awt.GetResult() + + and [] ConfiguredUnitTaskAwaiterMethods<'t> = + interface IAwaiterMethods with + member __.IsCompleted awt = + awt.IsCompleted + + member __.GetResult awt = + awt.GetResult() + defaultof<_> // Always unit + + and [] YieldAwaiterMethods<'t> = + interface IAwaiterMethods with + member __.IsCompleted awt = + awt.IsCompleted + + member __.GetResult awt = + awt.GetResult() + defaultof<_> // Always unit + + and [] GenericAwaiterMethods<'awt, 't when 'awt :> ICriticalNotifyCompletion> = + interface IAwaiterMethods<'awt, 't> with + member __.IsCompleted awt = + false // Always await, this way we don't have to specialize per awaiter + + member __.GetResult awt = + defaultof<_> // Always unit because we wrap this continuation to always be unit -> Ply<'u> + +#if !NETSTANDARD2_0 + and [] ValueTaskAwaiterMethods<'t> = + interface IAwaiterMethods, 't> with + member __.IsCompleted awt = + awt.IsCompleted + + member __.GetResult awt = + awt.GetResult() + + and [] UnitValueTaskAwaiterMethods<'t> = + interface IAwaiterMethods with + member __.IsCompleted awt = + awt.IsCompleted + + member __.GetResult awt = + awt.GetResult() + defaultof<_> // Always unit + + and [] ConfiguredValueTaskAwaiterMethods<'t> = + interface IAwaiterMethods.ConfiguredValueTaskAwaiter, 't> with + member __.IsCompleted awt = + awt.IsCompleted + + member __.GetResult awt = + awt.GetResult() + + and [] ConfiguredUnitValueTaskAwaiterMethods<'t> = + interface IAwaiterMethods with + member __.IsCompleted awt = + awt.IsCompleted + + member __.GetResult awt = + awt.GetResult() + defaultof<_> // Always unit #endif type Binder<'u>() = @@ -366,112 +496,278 @@ module TplPrimitives = // but it does help delay 'cont' from allocating until we really need it as an FSharpFunc. // The IsCompleted branch works fine without the alloc because it inlines all the way up the CE. // It's a mess really... - + // Secondly, for every GetResult — because all calls to bind overloads are wrapped by TaskBuilder.Run — we are - // already running within our own Excecution context bubble. No need to be careful calling GetResult. + // already running within our own Excecution context bubble. No need to be careful calling GetResult. // We keep Await non inline to protect internals to maximize binary compatibility. - static member Await<'methods, 'awt, 't when 'methods :> IAwaiterMethods<'awt, 't>>(awt: byref<'awt>, cont: 't -> Ply<'u>) = + static member Await<'methods, 'awt, 't when 'methods :> IAwaiterMethods<'awt, 't>> + ( + awt: byref<'awt>, + cont: 't -> Ply<'u> + ) = Ply(await = TplAwaitable(defaultof<'methods>, awt, cont)) - static member inline Specialized<'methods, ^awt, 't - when 'methods :> IAwaiterMethods< ^awt, 't> - and ^awt :> ICriticalNotifyCompletion - and ^awt : (member get_IsCompleted: unit -> bool) - and ^awt : (member GetResult: unit -> 't) > - (awt: ^awt, cont: 't -> Ply<'u>) = - if (^awt : (member get_IsCompleted: unit -> bool) (awt)) then - cont (^awt : (member GetResult: unit -> 't) (awt)) + static member inline Specialized<'methods, ^awt, 't when 'methods :> IAwaiterMethods< ^awt, 't > and ^awt :> ICriticalNotifyCompletion and ^awt: (member get_IsCompleted: + unit -> bool) and ^awt: (member GetResult: unit -> 't)> + ( + awt: ^awt, + cont: 't -> Ply<'u> + ) = + if (^awt: (member get_IsCompleted: unit -> bool) (awt)) then + cont(^awt: (member GetResult: unit -> 't) (awt)) else let mutable mutAwt = awt - Binder<'u>.Await<'methods,_,_>(&mutAwt, (fun x -> cont x)) + + Binder<'u> + .Await<'methods, _, _>(&mutAwt, (fun x -> cont x)) // We have special treatment for unknown taskLike types where we wrap the continuation in a unit func // This allows us to use a single GenericAwaiterMethods type (zero alloc, small drop in perf) instead of an object expression. static member inline Generic(task: ^taskLike, cont: 't -> Ply<'u>) = - let awt = (^taskLike : (member GetAwaiter: unit -> ^awt) (task)) - if (^awt : (member get_IsCompleted: unit -> bool) (awt)) then - cont (^awt : (member GetResult: unit -> 't) (awt)) + let awt = (^taskLike: (member GetAwaiter: unit -> ^awt) (task)) + + if (^awt: (member get_IsCompleted: unit -> bool) (awt)) then + cont(^awt: (member GetResult: unit -> 't) (awt)) else // Leave original awt symbol immutable, otherwise it'll cost us an FsharpRef due to the capture. let mutable mutAwt = awt // This continuation closure is actually also just one alloc as the compiler simplifies the 'would be' cont into this one. - Binder<'u>.Await,_,_>(&mutAwt, (fun () -> cont (^awt : (member GetResult : unit -> 't) (awt)))) - - static member PlyAwait(ply: Ply<'t>, cont: 't -> Ply<'u>) = + Binder<'u> + .Await, _, _>( + &mutAwt, + (fun () -> + cont(^awt: (member GetResult: unit -> 't) (awt)) + ) + ) + + static member PlyAwait(ply: Ply<'t>, cont: 't -> Ply<'u>) = Ply(await = PlyAwaitable(ply.awaitable, (fun x -> cont x))) - static member inline Ply(ply: Ply<'t>, cont: 't -> Ply<'u>) = - if ply.IsCompletedSuccessfully then - cont ply.Result - else - Binder<'u>.PlyAwait(ply, (fun x -> cont x)) + static member inline Ply(ply: Ply<'t>, cont: 't -> Ply<'u>) = + if ply.IsCompletedSuccessfully then + cont ply.Result + else + Binder<'u>.PlyAwait (ply, (fun x -> cont x)) // Supporting types to have the compiler do what we want with respect to overload resolution. - type Id<'t> = class end - type Default2() = class end - type Default1() = inherit Default2() + type Id<'t> = + class + end - type Bind() = - inherit Default1() + type Default2() = + class + end - static member inline Invoke (task, cont: 't -> Ply<'u>) = - let inline call_2 (task: ^b, cont, a: ^a) = ((^a or ^b) : (static member Bind : _*_*_ -> Ply<'u>) task, cont, a) - let inline call (task: 'b, cont, a: 'a) = call_2 (task, cont, a) - call(task, cont, defaultof) - - static member inline Bind(task: ^taskLike, cont: 't -> Ply<'u>, []_impl:Default2) = - Binder<'u>.Generic(task, cont) + type Default1() = + inherit Default2() - static member inline Bind(task: Task, cont: unit -> Ply<'u>, []_impl:Default1) = - Binder<'u>.Specialized,_,_>(task.GetAwaiter(), cont) - - static member inline Bind(task: Task<'t>, cont: 't -> Ply<'u>, []_impl:Bind) = - Binder<'u>.Specialized,_,_>(task.GetAwaiter(), cont) - - static member inline Bind(task: ConfiguredTaskAwaitable<'t>, cont: 't -> Ply<'u>, []_impl:Bind) = - Binder<'u>.Specialized,_,_>(task.GetAwaiter(), cont) - - static member inline Bind(task: ConfiguredTaskAwaitable, cont: unit -> Ply<'u>, []_impl:Bind) = - Binder<'u>.Specialized,_,_>(task.GetAwaiter(), cont) + type Bind() = + inherit Default1() - static member inline Bind(task: YieldAwaitable, cont: unit -> Ply<'u>, []_impl:Bind) = - Binder<'u>.Specialized,_,_>(task.GetAwaiter(), cont) + static member inline Invoke(task, cont: 't -> Ply<'u>) = + let inline call_2(task: ^b, cont, a: ^a) = + ((^a or ^b): (static member Bind: _ * _ * _ -> Ply<'u>) task, + cont, + a) - static member inline Bind(async: Async<'t>, cont: 't -> Ply<'u>, []_impl:Bind) = - Binder<'u>.Specialized,_,_>((Async.StartAsTask async).GetAwaiter(), cont) + let inline call(task: 'b, cont, a: 'a) = + call_2(task, cont, a) - static member inline Bind(ply: Ply<'t>, cont: 't -> Ply<'u>, []_impl:Bind) = - Binder<'u>.Ply(ply, cont) + call(task, cont, defaultof) - static member inline Bind(_: Id<'t>, _: 't -> Ply<'u>, []_impl:Bind) = + static member inline Bind + ( + task: ^taskLike, + cont: 't -> Ply<'u>, + [] _impl: Default2 + ) = + Binder<'u>.Generic (task, cont) + + static member inline Bind + ( + task: Task, + cont: unit -> Ply<'u>, + [] _impl: Default1 + ) = + Binder<'u> + .Specialized, _, _>( + task.GetAwaiter(), + cont + ) + + static member inline Bind + ( + task: Task<'t>, + cont: 't -> Ply<'u>, + [] _impl: Bind + ) = + Binder<'u> + .Specialized, _, _>( + task.GetAwaiter(), + cont + ) + + static member inline Bind + ( + task: ConfiguredTaskAwaitable<'t>, + cont: 't -> Ply<'u>, + [] _impl: Bind + ) = + Binder<'u> + .Specialized, _, _>( + task.GetAwaiter(), + cont + ) + + static member inline Bind + ( + task: ConfiguredTaskAwaitable, + cont: unit -> Ply<'u>, + [] _impl: Bind + ) = + Binder<'u> + .Specialized, _, _>( + task.GetAwaiter(), + cont + ) + + static member inline Bind + ( + task: YieldAwaitable, + cont: unit -> Ply<'u>, + [] _impl: Bind + ) = + Binder<'u> + .Specialized, _, _>( + task.GetAwaiter(), + cont + ) + + static member inline Bind + ( + async: Async<'t>, + cont: 't -> Ply<'u>, + [] _impl: Bind + ) = + Binder<'u> + .Specialized, _, _>( + (Async.StartAsTask async).GetAwaiter(), + cont + ) + + static member inline Bind + ( + ply: Ply<'t>, + cont: 't -> Ply<'u>, + [] _impl: Bind + ) = + Binder<'u>.Ply (ply, cont) + + static member inline Bind + ( + _: Id<'t>, + _: 't -> Ply<'u>, + [] _impl: Bind + ) = failwith "Used for forcing delayed resolution." -#if !NETSTANDARD2_0 - static member inline Bind(task: ValueTask<'t>, cont: 't -> Ply<'u>, []_impl:Bind) = - Binder<'u>.Specialized,_,_>(task.GetAwaiter(), cont) - - static member inline Bind(task: ValueTask, cont: unit -> Ply<'u>, []_impl:Bind) = - Binder<'u>.Specialized,_,_>(task.GetAwaiter(), cont) - - static member inline Bind(task: ConfiguredValueTaskAwaitable<'t>, cont: 't -> Ply<'u>, []_impl:Bind) = - Binder<'u>.Specialized,_,_>(task.GetAwaiter(), cont) - - static member inline Bind(task: ConfiguredValueTaskAwaitable, cont: unit -> Ply<'u>, []_impl:Bind) = - Binder<'u>.Specialized,_,_>(task.GetAwaiter(), cont) +#if !NETSTANDARD2_0 + static member inline Bind + ( + task: ValueTask<'t>, + cont: 't -> Ply<'u>, + [] _impl: Bind + ) = + Binder<'u> + .Specialized, _, _>( + task.GetAwaiter(), + cont + ) + + static member inline Bind + ( + task: ValueTask, + cont: unit -> Ply<'u>, + [] _impl: Bind + ) = + Binder<'u> + .Specialized, _, _>( + task.GetAwaiter(), + cont + ) + + static member inline Bind + ( + task: ConfiguredValueTaskAwaitable<'t>, + cont: 't -> Ply<'u>, + [] _impl: Bind + ) = + Binder<'u> + .Specialized, _, _>( + task.GetAwaiter(), + cont + ) + + static member inline Bind + ( + task: ConfiguredValueTaskAwaitable, + cont: unit -> Ply<'u>, + [] _impl: Bind + ) = + Binder<'u> + .Specialized, _, _>( + task.GetAwaiter(), + cont + ) #endif type AwaitableBuilder() = - member inline __.Delay(body : unit -> Ply<'t>) = body - member inline __.Return(x) = ret x - member inline __.Zero() = zero - - member inline __.ReturnFrom(task: ^taskLike) = Bind.Invoke(task, ret) - member inline __.Bind(task: ^taskLike, continuation: 't -> Ply<'u>) = Bind.Invoke(task, continuation) - - member inline __.Combine(ply : Ply, continuation: unit -> Ply<'t>) = combine ply continuation - member inline __.While(condition : unit -> bool, body : unit -> Ply) = whileLoop condition body - member inline __.TryWith(body : unit -> Ply<'t>, catch : exn -> Ply<'t>) = tryWith body catch - member inline __.TryFinally(body : unit -> Ply<'t>, finallyBody : unit -> unit) = tryFinally body finallyBody - member inline __.Using(disposable : #IDisposable, body : #IDisposable -> Ply<'u>) = using disposable body - member inline __.For(sequence : seq<_>, body : _ -> Ply) = forLoop sequence body + member inline __.Delay(body: unit -> Ply<'t>) = + body + + member inline __.Return x = + ret x + + member inline __.Zero() = + zero + + member inline __.ReturnFrom(task: ^taskLike) = + Bind.Invoke(task, ret) + + member inline __.Bind(task: ^taskLike, continuation: 't -> Ply<'u>) = + Bind.Invoke(task, continuation) + + member inline __.Combine + ( + ply: Ply, + continuation: unit -> Ply<'t> + ) = + combine ply continuation + + member inline __.While + ( + condition: unit -> bool, + body: unit -> Ply + ) = + whileLoop condition body + + member inline __.TryWith(body: unit -> Ply<'t>, catch: exn -> Ply<'t>) = + tryWith body catch + + member inline __.TryFinally + ( + body: unit -> Ply<'t>, + finallyBody: unit -> unit + ) = + tryFinally body finallyBody + + member inline __.Using + ( + disposable: #IDisposable, + body: #IDisposable -> Ply<'u> + ) = + using disposable body + + member inline __.For(sequence: seq<_>, body: _ -> Ply) = + forLoop sequence body diff --git a/src/TaskUtils/PlyCE.fs b/src/TaskUtils/PlyCE.fs index 77d3b126c..854bdf06d 100644 --- a/src/TaskUtils/PlyCE.fs +++ b/src/TaskUtils/PlyCE.fs @@ -11,21 +11,28 @@ open Ply.TplPrimitives open System.Threading.Tasks [] -module Builders = +module Builders = type TaskBuilder() = inherit AwaitableBuilder() - member inline __.Run(f : unit -> Ply<'u>) : Task<'u> = runAsTask f + + member inline __.Run(f: unit -> Ply<'u>) : Task<'u> = + runAsTask f let task = TaskBuilder() type UnitTaskBuilder() = inherit AwaitableBuilder() - member inline __.Run(f : unit -> Ply<'u>) = -#if NETSTANDARD2_0 + + member inline __.Run(f: unit -> Ply<'u>) = +#if NETSTANDARD2_0 (runAsTask f) :> Task -#else +#else let t = run f - if t.IsCompletedSuccessfully then Task.CompletedTask else t.AsTask() :> Task + + if t.IsCompletedSuccessfully then + Task.CompletedTask + else + t.AsTask() :> Task #endif let unitTask = UnitTaskBuilder() @@ -33,34 +40,48 @@ module Builders = #if !NETSTANDARD2_0 type ValueTaskBuilder() = inherit AwaitableBuilder() - member inline __.Run(f : unit -> Ply<'u>) = run f - + + member inline __.Run(f: unit -> Ply<'u>) = + run f + let vtask = ValueTaskBuilder() type UnitValueTaskBuilder() = inherit AwaitableBuilder() - member inline __.Run(f : unit -> Ply<'u>) = + + member inline __.Run(f: unit -> Ply<'u>) = let t = run f - if t.IsCompletedSuccessfully then ValueTask() else ValueTask(t.AsTask() :> Task) + + if t.IsCompletedSuccessfully then + ValueTask() + else + ValueTask(t.AsTask() :> Task) let unitVtask = UnitValueTaskBuilder() #endif - module Unsafe = + module Unsafe = type UnsafePlyBuilder() = inherit AwaitableBuilder() - member inline __.Run(f : unit -> Ply<'u>) = f() + + member inline __.Run(f: unit -> Ply<'u>) = + f() let uply = UnsafePlyBuilder() type UnsafeUnitTaskBuilder() = inherit AwaitableBuilder() - member inline __.Run(f : unit -> Ply<'u>) = -#if NETSTANDARD2_0 + + member inline __.Run(f: unit -> Ply<'u>) = +#if NETSTANDARD2_0 (runUnwrappedAsTask f) :> Task -#else +#else let t = runUnwrapped f - if t.IsCompletedSuccessfully then Task.CompletedTask else t.AsTask() :> Task + + if t.IsCompletedSuccessfully then + Task.CompletedTask + else + t.AsTask() :> Task #endif let uunitTask = UnsafeUnitTaskBuilder() @@ -68,15 +89,22 @@ module Builders = #if !NETSTANDARD2_0 type UnsafeValueTaskBuilder() = inherit AwaitableBuilder() - member inline __.Run(f : unit -> Ply<'u>) = runUnwrapped f + + member inline __.Run(f: unit -> Ply<'u>) = + runUnwrapped f let uvtask = UnsafeValueTaskBuilder() - + type UnsafeUnitValueTaskBuilder() = inherit AwaitableBuilder() - member inline __.Run(f : unit -> Ply<'u>) = + + member inline __.Run(f: unit -> Ply<'u>) = let t = runUnwrapped f - if t.IsCompletedSuccessfully then ValueTask() else ValueTask(t.AsTask() :> Task) + + if t.IsCompletedSuccessfully then + ValueTask() + else + ValueTask(t.AsTask() :> Task) let uunitVtask = UnsafeUnitValueTaskBuilder() #endif diff --git a/src/TaskUtils/Result.fs b/src/TaskUtils/Result.fs index 2977b8efe..63dac976f 100644 --- a/src/TaskUtils/Result.fs +++ b/src/TaskUtils/Result.fs @@ -8,65 +8,66 @@ open ResultUtils.Portability [] module Result = - let sequenceTask (resTask: Result, 'b>) = + let sequenceTask(resTask: Result, 'b>) = - task { - match resTask with - | Ok t -> - let! x = t - return Ok x - | Error e -> return Error e - } + task { + match resTask with + | Ok t -> + let! x = t + return Ok x + | Error e -> return Error e + } [] module List = - let private traverseTaskResultM' (f: 'c -> Task>) (xs: 'c list) = - let mutable state = Ok [] - let mutable index = 0 - let xs = xs |> List.toArray - task { - while state |> Result.isOk && index < xs.Length do - let! r = xs |> Array.item index |> f - index <- index + 1 - match (r, state) with - | Ok y, Ok ys -> - state <- Ok (y :: ys) - | Error e, _ -> - state <- Error e - | _, _ -> - () - return - state - |> Result.map List.rev - } - let traverseTaskResultM f xs = - traverseTaskResultM' f xs - - let sequenceTaskResultM xs = - traverseTaskResultM id xs - - let private traverseTaskResultA' (f : 'c -> Task>) (xs : 'c list) = - let mutable state = Ok [] - - task { - for x in xs do - let! r = f x - match (r, state) with - | Ok y, Ok ys -> - state <- Ok (y :: ys) - | Error e, Error errs -> - state <- Error (e :: errs) - | Ok _, Error e -> - state <- Error e - | Error e , Ok _ -> - state <- Error [e] - return - state - |> Result.eitherMap List.rev List.rev - } - - let traverseTaskResultA f xs = - traverseTaskResultA' f xs - - let sequenceTaskResultA xs = - traverseTaskResultA id xs \ No newline at end of file + let private traverseTaskResultM' + (f: 'c -> Task>) + (xs: list<'c>) + = + let mutable state = Ok [] + let mutable index = 0 + let xs = xs |> List.toArray + + task { + while state |> Result.isOk && index < xs.Length do + let! r = xs |> Array.item index |> f + index <- index + 1 + + match (r, state) with + | Ok y, Ok ys -> state <- Ok(y :: ys) + | Error e, _ -> state <- Error e + | _, _ -> () + + return state |> Result.map List.rev + } + + let traverseTaskResultM f xs = + traverseTaskResultM' f xs + + let sequenceTaskResultM xs = + traverseTaskResultM id xs + + let private traverseTaskResultA' + (f: 'c -> Task>) + (xs: list<'c>) + = + let mutable state = Ok [] + + task { + for x in xs do + let! r = f x + + match (r, state) with + | Ok y, Ok ys -> state <- Ok(y :: ys) + | Error e, Error errs -> state <- Error(e :: errs) + | Ok _, Error e -> state <- Error e + | Error e, Ok _ -> state <- Error [ e ] + + return state |> Result.eitherMap List.rev List.rev + } + + let traverseTaskResultA f xs = + traverseTaskResultA' f xs + + let sequenceTaskResultA xs = + traverseTaskResultA id xs diff --git a/src/TaskUtils/Task.fs b/src/TaskUtils/Task.fs index a77783f9f..4592c1297 100644 --- a/src/TaskUtils/Task.fs +++ b/src/TaskUtils/Task.fs @@ -6,21 +6,23 @@ open FSharp.Control.Tasks [] module Task = - let singleton v = v |> Task.FromResult + let singleton v = + v |> Task.FromResult - let bind (f: 'a -> Task<'b>) (x: Task<'a>) = task { - let! r = x - return! f r - } + let bind (f: 'a -> Task<'b>) (x: Task<'a>) = + task { + let! r = x + return! f r + } - let apply f x = - bind (fun f' -> bind(f' >> singleton) x) f + let apply f x = + bind (fun f' -> bind (f' >> singleton) x) f - let map f x = - x |> bind (f >> singleton) + let map f x = + x |> bind(f >> singleton) - let map2 f x y = - (apply (apply (singleton f) x) y) + let map2 f x y = + (apply (apply (singleton f) x) y) - let map3 f x y z = - apply(map2 f x y) z \ No newline at end of file + let map3 f x y z = + apply (map2 f x y) z diff --git a/src/TaskUtils/TaskOp.fs b/src/TaskUtils/TaskOp.fs index f21620d27..8e6fc760e 100644 --- a/src/TaskUtils/TaskOp.fs +++ b/src/TaskUtils/TaskOp.fs @@ -2,6 +2,11 @@ namespace TaskUtils [] module TaskOp = - let inline () f x = Task.map f x - let inline (<*>) f x = Task.apply f x - let inline (>>=) x f = Task.bind f x \ No newline at end of file + let inline () f x = + Task.map f x + + let inline (<*>) f x = + Task.apply f x + + let inline (>>=) x f = + Task.bind f x diff --git a/src/TaskUtils/TaskResult.fs b/src/TaskUtils/TaskResult.fs index f8dce0b6c..29b392f58 100644 --- a/src/TaskUtils/TaskResult.fs +++ b/src/TaskUtils/TaskResult.fs @@ -5,128 +5,127 @@ open System.Threading.Tasks [] module TaskResult = - open ResultUtils - let map f tr = - Task.map(Result.map f) tr - - let bind f (tr: Task<_>) = task { - let! result = tr - let t = - match result with - | Ok x -> f x - | Error e -> task { return Error e } - return! t - } - - - let foldResult onSuccess onError tr = - Task.map (Result.fold onSuccess onError) tr - - let ofAsync aAsync = - aAsync - |> Async.Catch - |> Async.StartAsTask - |> Task.map Result.ofChoice - - let retn x = - Ok x - |> Task.singleton - - let returnError x = - Error x - |> Task.singleton - - let map2 f xTR yTR = - Task.map2 (Result.map2 f) xTR yTR - - let map3 f xTR yTR zTR = - Task.map3 (Result.map3 f) xTR yTR zTR - - let apply fTR xTR = - map2 (fun f x -> f x) fTR xTR - - /// Replaces the wrapped value with unit - let ignore tr = - tr |> map ignore - - /// Returns the specified error if the task-wrapped value is false. - let requireTrue error value = - value |> Task.map (Result.requireTrue error) - - /// Returns the specified error if the task-wrapped value is true. - let requireFalse error value = - value |> Task.map (Result.requireFalse error) - - // Converts an task-wrapped Option to a Result, using the given error if None. - let requireSome error option = - option |> Task.map (Result.requireSome error) - - // Converts an task-wrapped Option to a Result, using the given error if Some. - let requireNone error option = - option |> Task.map (Result.requireNone error) - - /// Returns Ok if the task-wrapped value and the provided value are equal, or the specified error if not. - let requireEqual x1 x2 error = - x2 |> Task.map (fun x2' -> Result.requireEqual x1 x2' error) - - /// Returns Ok if the two values are equal, or the specified error if not. - let requireEqualTo other error this = - this |> Task.map (Result.requireEqualTo other error) - - /// Returns Ok if the task-wrapped sequence is empty, or the specified error if not. - let requireEmpty error xs = - xs |> Task.map (Result.requireEmpty error) - - /// Returns Ok if the task-wrapped sequence is not-empty, or the specified error if not. - let requireNotEmpty error xs = - xs |> Task.map (Result.requireNotEmpty error) - - /// Returns the first item of the task-wrapped sequence if it exists, or the specified - /// error if the sequence is empty - let requireHead error xs = - xs |> Task.map (Result.requireHead error) - - /// Replaces an error value of an task-wrapped result with a custom error - /// value. - let setError error taskResult = - taskResult |> Task.map (Result.setError error) - - /// Replaces a unit error value of an task-wrapped result with a custom - /// error value. Safer than setError since you're not losing any information. - let withError error taskResult = - taskResult |> Task.map (Result.withError error) - - /// Extracts the contained value of an task-wrapped result if Ok, otherwise - /// uses ifError. - let defaultValue ifError taskResult = - taskResult |> Task.map (Result.defaultValue ifError) - - /// Extracts the contained value of an task-wrapped result if Ok, otherwise - /// evaluates ifErrorThunk and uses the result. - let defaultWith ifErrorThunk taskResult = - taskResult |> Task.map (Result.defaultWith ifErrorThunk) - - /// Same as defaultValue for a result where the Ok value is unit. The name - /// describes better what is actually happening in this case. - let ignoreError taskResult = - defaultValue () taskResult - - /// If the task-wrapped result is Ok, executes the function on the Ok value. - /// Passes through the input value. - let tee f taskResult = - taskResult |> Task.map (Result.tee f) - - /// If the task-wrapped result is Ok and the predicate returns true, executes - /// the function on the Ok value. Passes through the input value. - let teeIf predicate f taskResult = - taskResult |> Task.map (Result.teeIf predicate f) - - /// If the task-wrapped result is Error, executes the function on the Error - /// value. Passes through the input value. - let teeError f taskResult = - taskResult |> Task.map (Result.teeError f) - - /// If the task-wrapped result is Error and the predicate returns true, - /// executes the function on the Error value. Passes through the input value. - let teeErrorIf predicate f taskResult = - taskResult |> Task.map (Result.teeErrorIf predicate f) + open ResultUtils + + let map f tr = + Task.map (Result.map f) tr + + let bind f (tr: Task<_>) = + task { + let! result = tr + + let t = + match result with + | Ok x -> f x + | Error e -> task { return Error e } + + return! t + } + + + let foldResult onSuccess onError tr = + Task.map (Result.fold onSuccess onError) tr + + let ofAsync aAsync = + aAsync |> Async.Catch |> Async.StartAsTask |> Task.map Result.ofChoice + + let retn x = + Ok x |> Task.singleton + + let returnError x = + Error x |> Task.singleton + + let map2 f xTR yTR = + Task.map2 (Result.map2 f) xTR yTR + + let map3 f xTR yTR zTR = + Task.map3 (Result.map3 f) xTR yTR zTR + + let apply fTR xTR = + map2 (fun f x -> f x) fTR xTR + + /// Replaces the wrapped value with unit + let ignore tr = + tr |> map ignore + + /// Returns the specified error if the task-wrapped value is false. + let requireTrue error value = + value |> Task.map(Result.requireTrue error) + + /// Returns the specified error if the task-wrapped value is true. + let requireFalse error value = + value |> Task.map(Result.requireFalse error) + + // Converts an task-wrapped Option to a Result, using the given error if None. + let requireSome error option = + option |> Task.map(Result.requireSome error) + + // Converts an task-wrapped Option to a Result, using the given error if Some. + let requireNone error option = + option |> Task.map(Result.requireNone error) + + /// Returns Ok if the task-wrapped value and the provided value are equal, or the specified error if not. + let requireEqual x1 x2 error = + x2 |> Task.map(fun x2' -> Result.requireEqual x1 x2' error) + + /// Returns Ok if the two values are equal, or the specified error if not. + let requireEqualTo other error this = + this |> Task.map(Result.requireEqualTo other error) + + /// Returns Ok if the task-wrapped sequence is empty, or the specified error if not. + let requireEmpty error xs = + xs |> Task.map(Result.requireEmpty error) + + /// Returns Ok if the task-wrapped sequence is not-empty, or the specified error if not. + let requireNotEmpty error xs = + xs |> Task.map(Result.requireNotEmpty error) + + /// Returns the first item of the task-wrapped sequence if it exists, or the specified + /// error if the sequence is empty + let requireHead error xs = + xs |> Task.map(Result.requireHead error) + + /// Replaces an error value of an task-wrapped result with a custom error + /// value. + let setError error taskResult = + taskResult |> Task.map(Result.setError error) + + /// Replaces a unit error value of an task-wrapped result with a custom + /// error value. Safer than setError since you're not losing any information. + let withError error taskResult = + taskResult |> Task.map(Result.withError error) + + /// Extracts the contained value of an task-wrapped result if Ok, otherwise + /// uses ifError. + let defaultValue ifError taskResult = + taskResult |> Task.map(Result.defaultValue ifError) + + /// Extracts the contained value of an task-wrapped result if Ok, otherwise + /// evaluates ifErrorThunk and uses the result. + let defaultWith ifErrorThunk taskResult = + taskResult |> Task.map(Result.defaultWith ifErrorThunk) + + /// Same as defaultValue for a result where the Ok value is unit. The name + /// describes better what is actually happening in this case. + let ignoreError taskResult = + defaultValue () taskResult + + /// If the task-wrapped result is Ok, executes the function on the Ok value. + /// Passes through the input value. + let tee f taskResult = + taskResult |> Task.map(Result.tee f) + + /// If the task-wrapped result is Ok and the predicate returns true, executes + /// the function on the Ok value. Passes through the input value. + let teeIf predicate f taskResult = + taskResult |> Task.map(Result.teeIf predicate f) + + /// If the task-wrapped result is Error, executes the function on the Error + /// value. Passes through the input value. + let teeError f taskResult = + taskResult |> Task.map(Result.teeError f) + + /// If the task-wrapped result is Error and the predicate returns true, + /// executes the function on the Error value. Passes through the input value. + let teeErrorIf predicate f taskResult = + taskResult |> Task.map(Result.teeErrorIf predicate f) diff --git a/src/TaskUtils/TaskResultCE.fs b/src/TaskUtils/TaskResultCE.fs index 248384476..44dbc120a 100644 --- a/src/TaskUtils/TaskResultCE.fs +++ b/src/TaskUtils/TaskResultCE.fs @@ -1,3 +1,3 @@ namespace TaskUtils -// not implemented for now. \ No newline at end of file +// not implemented for now. diff --git a/src/TaskUtils/TaskResultOp.fs b/src/TaskUtils/TaskResultOp.fs index 75431e87b..699f93cd5 100644 --- a/src/TaskUtils/TaskResultOp.fs +++ b/src/TaskUtils/TaskResultOp.fs @@ -4,6 +4,11 @@ namespace TaskUtils [] module TaskResultOp = - let inline () f x = TaskResult.map f x - let inline (<*>) f x = TaskResult.apply f x - let inline (>>=) x f = TaskResult.bind f x \ No newline at end of file + let inline () f x = + TaskResult.map f x + + let inline (<*>) f x = + TaskResult.apply f x + + let inline (>>=) x f = + TaskResult.bind f x diff --git a/tests/DotNetLightning.Core.Tests/AezeedTests.fs b/tests/DotNetLightning.Core.Tests/AezeedTests.fs index e41e1e10f..a1eb502d8 100644 --- a/tests/DotNetLightning.Core.Tests/AezeedTests.fs +++ b/tests/DotNetLightning.Core.Tests/AezeedTests.fs @@ -12,206 +12,420 @@ open PrimitiveGenerators open ResultUtils open ResultUtils.Portability -type TestVector = { - Version: byte - Time: DateTimeOffset - Entropy: byte[] - Salt: byte[] - Password: byte[] - ExpectedMnemonic: Mnemonic - ExpectedBirthday: uint16 - MaybeExpectedCipherText: byte[] option -} +type TestVector = + { + Version: byte + Time: DateTimeOffset + Entropy: array + Salt: array + Password: array + ExpectedMnemonic: Mnemonic + ExpectedBirthday: uint16 + MaybeExpectedCipherText: option> + } type CipherSeedGenerator = - static member Bytes33() : Arbitrary = + static member Bytes33() : Arbitrary> = Gen.arrayOfLength 33 (Arb.generate) |> Arb.fromGen - + static member CipherSeed() : Arbitrary = cipherSeedGen |> Arb.fromGen + /// Based on: https://github.com/lightningnetwork/lnd/blob/b4bf4b2906c066ec0d0b8d7183c720b1d7d19220/aezeed/cipherseed_test.go [] let tests = AEZConstants.V0_SCRYPT_N <- 16 - let propConfig = { - FsCheckConfig.defaultConfig - with - arbitrary = [typeof;] - maxTest = 15; - } - testList "aezeed unit test ported from lnd" [ - let testEntropy = - [| - 0x81; 0xb6; 0x37; 0xd8; - 0x63; 0x59; 0xe6; 0x96 - 0x0d; 0xe7; 0x95; 0xe4; - 0x1e; 0x0b; 0x4c; 0xfd; - |] |> Array.map byte - - let testSalt = [|0x73uy; 0x61uy; 0x6cuy; 0x74uy; 0x31uy|] - let version0TestVectors = [| - { - Version = 0uy - Time = Network.Main.GetGenesis().Header.BlockTime - Entropy = testEntropy - Salt = testSalt - Password = [||] - ExpectedMnemonic = - [ - "ability"; "liquid"; "travel"; "stem"; "barely"; "drastic"; - "pact"; "cupboard"; "apple"; "thrive"; "morning"; "oak"; - "feature"; "tissue"; "couch"; "old"; "math"; "inform"; - "success"; "suggest"; "drink"; "motion"; "know"; "royal"; - ] |> Seq.fold(fun word acc -> word + " " + acc) "" |> Mnemonic - ExpectedBirthday = 0us - MaybeExpectedCipherText = - [|0; 48; 75; 158; 106; 161; 40; 132; 167; 169; 174; 10; 188; 38; 63; 203; 229; 67; 197; 140; 60; 208; 137; 14; 115; 97; 108; 116; 49; 32; 158; 253; 229;|] - |> Array.map(byte) - |> Some - } - { - Version = 0uy - Time = DateTimeOffset.FromUnixTimeSeconds(1521799345L) // 02/23/2018 @ 10:02am (UTC) - Entropy = testEntropy - Salt = testSalt - Password = Encoding.UTF8.GetBytes("!very_safe_55345_password*") - ExpectedMnemonic = - [ - "able"; "tree"; "stool"; "crush"; "transfer"; "cloud"; - "cross"; "three"; "profit"; "outside"; "hen"; "citizen"; - "plate"; "ride"; "require"; "leg"; "siren"; "drum"; - "success"; "suggest"; "drink"; "require"; "fiscal"; "upgrade"; - ] |> Seq.fold(fun word acc -> word + " " + acc) "" |> Mnemonic - ExpectedBirthday = 3365us - MaybeExpectedCipherText = - [|0; 92; 255; 89; 154; 142; 114; 87; 205; 7; 8; 171; 211; 177; 172; 148; 170; 99; 114; 237; 195; 250; 201; 104; 115; 97; 108; 116; 49; 110; 21; 231; 117|] - |> Array.map(byte) - |> Some - } - |] - testCase "aed v0 test vectors" <| fun _ -> - for v in version0TestVectors do - let cipherSeed = { - CipherSeed.Create(v.Version, Some(v.Entropy), v.Time) - with Salt = testSalt // salt is usually generated randomly, so we will overwrite it here + + let propConfig = + { FsCheckConfig.defaultConfig with + arbitrary = [ typeof ] + maxTest = 15 + } + + testList + "aezeed unit test ported from lnd" + [ + let testEntropy = + [| + 0x81 + 0xb6 + 0x37 + 0xd8 + 0x63 + 0x59 + 0xe6 + 0x96 + 0x0d + 0xe7 + 0x95 + 0xe4 + 0x1e + 0x0b + 0x4c + 0xfd + |] + |> Array.map byte + + let testSalt = + [| + 0x73uy + 0x61uy + 0x6cuy + 0x74uy + 0x31uy + |] + + let version0TestVectors = + [| + { + Version = 0uy + Time = Network.Main.GetGenesis().Header.BlockTime + Entropy = testEntropy + Salt = testSalt + Password = [||] + ExpectedMnemonic = + [ + "ability" + "liquid" + "travel" + "stem" + "barely" + "drastic" + "pact" + "cupboard" + "apple" + "thrive" + "morning" + "oak" + "feature" + "tissue" + "couch" + "old" + "math" + "inform" + "success" + "suggest" + "drink" + "motion" + "know" + "royal" + ] + |> Seq.fold (fun word acc -> word + " " + acc) "" + |> Mnemonic + ExpectedBirthday = 0us + MaybeExpectedCipherText = + [| + 0 + 48 + 75 + 158 + 106 + 161 + 40 + 132 + 167 + 169 + 174 + 10 + 188 + 38 + 63 + 203 + 229 + 67 + 197 + 140 + 60 + 208 + 137 + 14 + 115 + 97 + 108 + 116 + 49 + 32 + 158 + 253 + 229 + |] + |> Array.map(byte) + |> Some } - - Expect.equal (cipherSeed.Birthday) v.ExpectedBirthday "unmatched birthday" - v.MaybeExpectedCipherText - |> Option.iter(fun expectedC -> - let actualC = cipherSeed.Encipher(Some v.Password) - Expect.sequenceEqual (actualC) expectedC "" + { + Version = 0uy + Time = DateTimeOffset.FromUnixTimeSeconds(1521799345L) // 02/23/2018 @ 10:02am (UTC) + Entropy = testEntropy + Salt = testSalt + Password = + Encoding.UTF8.GetBytes("!very_safe_55345_password*") + ExpectedMnemonic = + [ + "able" + "tree" + "stool" + "crush" + "transfer" + "cloud" + "cross" + "three" + "profit" + "outside" + "hen" + "citizen" + "plate" + "ride" + "require" + "leg" + "siren" + "drum" + "success" + "suggest" + "drink" + "require" + "fiscal" + "upgrade" + ] + |> Seq.fold (fun word acc -> word + " " + acc) "" + |> Mnemonic + ExpectedBirthday = 3365us + MaybeExpectedCipherText = + [| + 0 + 92 + 255 + 89 + 154 + 142 + 114 + 87 + 205 + 7 + 8 + 171 + 211 + 177 + 172 + 148 + 170 + 99 + 114 + 237 + 195 + 250 + 201 + 104 + 115 + 97 + 108 + 116 + 49 + 110 + 21 + 231 + 117 + |] + |> Array.map(byte) + |> Some + } + |] + + testCase "aed v0 test vectors" + <| fun _ -> + for v in version0TestVectors do + let cipherSeed = + { CipherSeed.Create(v.Version, Some(v.Entropy), v.Time) with + Salt = testSalt // salt is usually generated randomly, so we will overwrite it here + } + + Expect.equal + (cipherSeed.Birthday) + v.ExpectedBirthday + "unmatched birthday" + + v.MaybeExpectedCipherText + |> Option.iter(fun expectedC -> + let actualC = cipherSeed.Encipher(Some v.Password) + Expect.sequenceEqual (actualC) expectedC "" ) - let mnemonic = cipherSeed.ToMnemonic(v.Password) - Expect.equal (mnemonic.ToString()) (v.ExpectedMnemonic.ToString()) "unmatched mnemonic" - - testCase "test empty passphrase derivation" <| fun _ -> - let pass = [||] - // We'll now create a new cipher seed with an internal version of zero - // to simulate a wallet that just adopted the scheme. - let cipherSeed = CipherSeed.Create(0uy, Some testEntropy, DateTimeOffset.Now) - - // Now that the seed has been created, we'll attempt to convert it to a - // valid mnemonic. - let mnemonic = cipherSeed.ToMnemonic(pass) - - // Next, we'll try to decrypt the mnemonic with the passphrase that we used. - let cipherSeed2 = mnemonic.ToCipherSeed(pass) |> Result.deref - Expect.equal cipherSeed cipherSeed2 "" - - testCase "it should generate entropy when user did not pass it" <| fun _ -> - let pass = [||] - let cipherSeed = CipherSeed.Create(0uy, None, DateTimeOffset.Now) - let mnemonic = cipherSeed.ToMnemonic() - let cipherSeed2 = mnemonic.ToCipherSeed(pass) |> Result.deref - Expect.equal cipherSeed cipherSeed2 "" - - testCase "should reject invalid passphrase" <| fun _ -> - let pass = Encoding.UTF8.GetBytes("test") - let cipherSeed = CipherSeed.Create(0uy, Some testEntropy, DateTimeOffset.Now) - let mnemonic = cipherSeed.ToMnemonic(pass) - let wrongPass = Encoding.UTF8.GetBytes "kek" - let e = mnemonic.ToCipherSeed(wrongPass) - match e with - | Error(AezeedError.InvalidPass _ ) -> () - | x -> failwithf "it should return invalid pass. it was %A" x - - testCase "test raw encipher/decipher" <| fun _ -> - let pass = Encoding.UTF8.GetBytes("test") - let cipherSeed = { CipherSeed.Create(0uy, Some testEntropy, BITCOIN_GENESIS_DATE) with Salt = testSalt} - let cipherText = cipherSeed.Encipher(pass) - let mnemonic = - AezeedHelpers.cipherTextToMnemonic(cipherText, None) - |> (Seq.fold(fun word acc -> word + " " + acc) "") - |> Mnemonic - let plainSeedBytes = mnemonic.Decipher(Some pass, None) |> Result.deref - let newSeed = CipherSeed.FromBytes(plainSeedBytes) - Expect.equal newSeed cipherSeed "" - - testCase "test invalid external version" <| fun _ -> - let cipherSeed = CipherSeed.Create(0uy, testEntropy, DateTimeOffset.Now) - let pass = Encoding.UTF8.GetBytes("newpasswhodis") - let cipherText = cipherSeed.Encipher(pass) - cipherText.[0] <- 44uy - - let wrongPass = Encoding.UTF8.GetBytes("kek") - let r = AezeedHelpers.decipherCipherSeed(cipherText, wrongPass) - match r with - | Error(AezeedError.UnsupportedVersion _) -> () - | x -> failwithf "Unexpected %A" x - - testCase "test changing passphrase" <| fun _ -> - let pass = Encoding.UTF8.GetBytes("test") - let cipherSeed = CipherSeed.Create(0uy, testEntropy, DateTimeOffset.Now) - let mnemonic = cipherSeed.ToMnemonic(pass) - let newPass = Encoding.UTF8.GetBytes("strongerpassyeh!") - let newMnemonic = mnemonic.ChangePass(pass, newPass) |> Result.deref - let newCipherSeed = newMnemonic.ToCipherSeed(newPass) |> Result.deref - Expect.equal cipherSeed newCipherSeed "unmatched cipherSeed" - - testCase "test change passphrase with wrong pass" <| fun _ -> - let pass = Encoding.UTF8.GetBytes "test" - let cipherSeed = CipherSeed.Create(0uy, testEntropy, DateTimeOffset.Now) - let mnemonic = cipherSeed.ToMnemonic(pass) - let wrongPass = Encoding.UTF8.GetBytes "kek" - let newPass = Encoding.UTF8.GetBytes "strongerpassyeh!" - let r = mnemonic.ChangePass(wrongPass, newPass) - match r with - | Error(AezeedError.InvalidPass _) -> () - | x -> failwithf "expected invalid pass error. Got: %A" x - - testPropertyWithConfig propConfig "test mnemonic encoding" <| fun (bytes33: byte[]) -> - let mnemonic = AezeedHelpers.cipherTextToMnemonic(bytes33, None) - let newCipher = AezeedHelpers.mnemonicToCipherText(mnemonic, None) - Expect.equal newCipher bytes33 "" - - testPropertyWithConfig propConfig "test encipher-decipher" <| fun (cipherSeed: CipherSeed, pass: NonNull) -> - let mnemonic = cipherSeed.ToMnemonic(pass.Get) - let cipherSeed2 = mnemonic.ToCipherSeed(pass.Get) |> Result.deref - Expect.equal (cipherSeed) (cipherSeed2) "" - - testPropertyWithConfig propConfig "test seed encode-decode" <| fun (cipherSeed: CipherSeed) -> - let b = cipherSeed.ToBytes() - let newSeed = CipherSeed.FromBytes b - - Expect.equal cipherSeed newSeed "" - Expect.equal (cipherSeed.GetHashCode()) (newSeed.GetHashCode()) "" - - testCase "test Decipher incorrect mnemonic" <| fun _ -> - let cipherSeed = CipherSeed.Create(0uy, testEntropy, DateTimeOffset.Now) - let pass = Encoding.UTF8.GetBytes "test" - let mnemonicW = cipherSeed.ToMnemonicWords(pass) - - let i1 = 9 - let i2 = 13 - let tmp = mnemonicW.[i1].Clone() - mnemonicW.[i1] <- mnemonicW.[i2] - mnemonicW.[i2] <- (tmp.ToString()) - - let mnemonic = - mnemonicW |> Seq.fold(fun word acc -> word + " " + acc) "" |> Mnemonic - let r = mnemonic.ToCipherSeed() - match r with - | Error(AezeedError.IncorrectMnemonic _ ) -> () - | x -> failwithf "Expecting IncorrectMnemonic. got %A" x - ] + + let mnemonic = cipherSeed.ToMnemonic(v.Password) + + Expect.equal + (mnemonic.ToString()) + (v.ExpectedMnemonic.ToString()) + "unmatched mnemonic" + + testCase "test empty passphrase derivation" + <| fun _ -> + let pass = [||] + // We'll now create a new cipher seed with an internal version of zero + // to simulate a wallet that just adopted the scheme. + let cipherSeed = + CipherSeed.Create(0uy, Some testEntropy, DateTimeOffset.Now) + + // Now that the seed has been created, we'll attempt to convert it to a + // valid mnemonic. + let mnemonic = cipherSeed.ToMnemonic(pass) + + // Next, we'll try to decrypt the mnemonic with the passphrase that we used. + let cipherSeed2 = mnemonic.ToCipherSeed(pass) |> Result.deref + Expect.equal cipherSeed cipherSeed2 "" + + testCase "it should generate entropy when user did not pass it" + <| fun _ -> + let pass = [||] + + let cipherSeed = + CipherSeed.Create(0uy, None, DateTimeOffset.Now) + + let mnemonic = cipherSeed.ToMnemonic() + let cipherSeed2 = mnemonic.ToCipherSeed(pass) |> Result.deref + Expect.equal cipherSeed cipherSeed2 "" + + testCase "should reject invalid passphrase" + <| fun _ -> + let pass = Encoding.UTF8.GetBytes("test") + + let cipherSeed = + CipherSeed.Create(0uy, Some testEntropy, DateTimeOffset.Now) + + let mnemonic = cipherSeed.ToMnemonic(pass) + let wrongPass = Encoding.UTF8.GetBytes "kek" + let e = mnemonic.ToCipherSeed(wrongPass) + + match e with + | Error(AezeedError.InvalidPass _) -> () + | x -> failwithf "it should return invalid pass. it was %A" x + + testCase "test raw encipher/decipher" + <| fun _ -> + let pass = Encoding.UTF8.GetBytes("test") + + let cipherSeed = + { CipherSeed.Create( + 0uy, + Some testEntropy, + BITCOIN_GENESIS_DATE + ) with + Salt = testSalt + } + + let cipherText = cipherSeed.Encipher(pass) + + let mnemonic = + AezeedHelpers.cipherTextToMnemonic(cipherText, None) + |> (Seq.fold (fun word acc -> word + " " + acc) "") + |> Mnemonic + + let plainSeedBytes = + mnemonic.Decipher(Some pass, None) |> Result.deref + + let newSeed = CipherSeed.FromBytes(plainSeedBytes) + Expect.equal newSeed cipherSeed "" + + testCase "test invalid external version" + <| fun _ -> + let cipherSeed = + CipherSeed.Create(0uy, testEntropy, DateTimeOffset.Now) + + let pass = Encoding.UTF8.GetBytes("newpasswhodis") + let cipherText = cipherSeed.Encipher(pass) + cipherText.[0] <- 44uy + + let wrongPass = Encoding.UTF8.GetBytes("kek") + let r = AezeedHelpers.decipherCipherSeed(cipherText, wrongPass) + + match r with + | Error(AezeedError.UnsupportedVersion _) -> () + | x -> failwithf "Unexpected %A" x + + testCase "test changing passphrase" + <| fun _ -> + let pass = Encoding.UTF8.GetBytes("test") + + let cipherSeed = + CipherSeed.Create(0uy, testEntropy, DateTimeOffset.Now) + + let mnemonic = cipherSeed.ToMnemonic(pass) + let newPass = Encoding.UTF8.GetBytes("strongerpassyeh!") + + let newMnemonic = + mnemonic.ChangePass(pass, newPass) |> Result.deref + + let newCipherSeed = + newMnemonic.ToCipherSeed(newPass) |> Result.deref + + Expect.equal cipherSeed newCipherSeed "unmatched cipherSeed" + + testCase "test change passphrase with wrong pass" + <| fun _ -> + let pass = Encoding.UTF8.GetBytes "test" + + let cipherSeed = + CipherSeed.Create(0uy, testEntropy, DateTimeOffset.Now) + + let mnemonic = cipherSeed.ToMnemonic(pass) + let wrongPass = Encoding.UTF8.GetBytes "kek" + let newPass = Encoding.UTF8.GetBytes "strongerpassyeh!" + let r = mnemonic.ChangePass(wrongPass, newPass) + + match r with + | Error(AezeedError.InvalidPass _) -> () + | x -> failwithf "expected invalid pass error. Got: %A" x + + testPropertyWithConfig propConfig "test mnemonic encoding" + <| fun (bytes33: array) -> + let mnemonic = AezeedHelpers.cipherTextToMnemonic(bytes33, None) + + let newCipher = + AezeedHelpers.mnemonicToCipherText(mnemonic, None) + + Expect.equal newCipher bytes33 "" + + testPropertyWithConfig propConfig "test encipher-decipher" + <| fun (cipherSeed: CipherSeed, pass: NonNull>) -> + let mnemonic = cipherSeed.ToMnemonic(pass.Get) + + let cipherSeed2 = + mnemonic.ToCipherSeed(pass.Get) |> Result.deref + + Expect.equal (cipherSeed) (cipherSeed2) "" + + testPropertyWithConfig propConfig "test seed encode-decode" + <| fun (cipherSeed: CipherSeed) -> + let b = cipherSeed.ToBytes() + let newSeed = CipherSeed.FromBytes b + + Expect.equal cipherSeed newSeed "" + + Expect.equal + (cipherSeed.GetHashCode()) + (newSeed.GetHashCode()) + "" + + testCase "test Decipher incorrect mnemonic" + <| fun _ -> + let cipherSeed = + CipherSeed.Create(0uy, testEntropy, DateTimeOffset.Now) + + let pass = Encoding.UTF8.GetBytes "test" + let mnemonicW = cipherSeed.ToMnemonicWords(pass) + + let i1 = 9 + let i2 = 13 + let tmp = mnemonicW.[i1].Clone() + mnemonicW.[i1] <- mnemonicW.[i2] + mnemonicW.[i2] <- (tmp.ToString()) + + let mnemonic = + mnemonicW + |> Seq.fold (fun word acc -> word + " " + acc) "" + |> Mnemonic + + let r = mnemonic.ToCipherSeed() + + match r with + | Error(AezeedError.IncorrectMnemonic _) -> () + | x -> failwithf "Expecting IncorrectMnemonic. got %A" x + ] diff --git a/tests/DotNetLightning.Core.Tests/AssemblyInfo.fs b/tests/DotNetLightning.Core.Tests/AssemblyInfo.fs index 1c84af9ec..d00f64c86 100644 --- a/tests/DotNetLightning.Core.Tests/AssemblyInfo.fs +++ b/tests/DotNetLightning.Core.Tests/AssemblyInfo.fs @@ -1,26 +1,44 @@ // Auto-Generated by FAKE; do not edit namespace System + open System.Reflection open Microsoft.Extensions.Configuration.UserSecrets [] [] [] -[] +[] [] [] -[] -[] +[] +[] [] do () module internal AssemblyVersionInformation = - let [] AssemblyTitle = "DotNetLightning.Tests" - let [] AssemblyProduct = "DotNetLightning" - let [] AssemblyVersion = "0.1.0" - let [] AssemblyMetadata_ReleaseDate = "2017-03-17T00:00:00.0000000" - let [] AssemblyFileVersion = "0.1.0" - let [] AssemblyInformationalVersion = "0.1.0" - let [] AssemblyMetadata_ReleaseChannel = "release" - let [] AssemblyMetadata_GitHash = "bb8964b54bee133e9af64d316dc2cfee16df7f72" + [] + let AssemblyTitle = "DotNetLightning.Tests" + + [] + let AssemblyProduct = "DotNetLightning" + + [] + let AssemblyVersion = "0.1.0" + + [] + let AssemblyMetadata_ReleaseDate = "2017-03-17T00:00:00.0000000" + + [] + let AssemblyFileVersion = "0.1.0" + + [] + let AssemblyInformationalVersion = "0.1.0" + + [] + let AssemblyMetadata_ReleaseChannel = "release" + + [] + let AssemblyMetadata_GitHash = "bb8964b54bee133e9af64d316dc2cfee16df7f72" diff --git a/tests/DotNetLightning.Core.Tests/ClaimReceivedHTLCTests.fs b/tests/DotNetLightning.Core.Tests/ClaimReceivedHTLCTests.fs index 2ec3f768c..0c860ad50 100644 --- a/tests/DotNetLightning.Core.Tests/ClaimReceivedHTLCTests.fs +++ b/tests/DotNetLightning.Core.Tests/ClaimReceivedHTLCTests.fs @@ -14,24 +14,32 @@ let base58 = Encoders.Base58 let n = Network.TestNet let alice = - let commitKey = "cVuzKWCszfvjkoJyUasvsrRdECriz8hSd1BDinRNzytwnXmX7m1g" |> fun d -> Key.Parse(d, n) - let finalKey = "cRUfvpbRtMSqCFD1ADdvgPn5HfRLYuHCFYAr2noWnaRDNger2AoA" |> fun d -> Key.Parse(d, n) + let commitKey = + "cVuzKWCszfvjkoJyUasvsrRdECriz8hSd1BDinRNzytwnXmX7m1g" + |> fun d -> Key.Parse(d, n) + + let finalKey = + "cRUfvpbRtMSqCFD1ADdvgPn5HfRLYuHCFYAr2noWnaRDNger2AoA" + |> fun d -> Key.Parse(d, n) + let r = Hashes.SHA256("This is Alice's R" |> utf8.GetBytes) let revokeCommit = "Alice foo" |> utf8.GetBytes + {| - CommitKey = commitKey - FinalKey = finalKey - CommitPubKey = commitKey.PubKey - finalPubKey = finalKey.PubKey - R = r - RHash = Hashes.SHA256(r) - RevokeCommit = revokeCommit - RevokeCommitHash = Hashes.SHA256(revokeCommit) - |} + CommitKey = commitKey + FinalKey = finalKey + CommitPubKey = commitKey.PubKey + finalPubKey = finalKey.PubKey + R = r + RHash = Hashes.SHA256(r) + RevokeCommit = revokeCommit + RevokeCommitHash = Hashes.SHA256(revokeCommit) + |} [] let tests = - testList "Claim Received HTLC Tests" [ - testCase "Should accept accept_channel" <| fun _ -> - () - ] + testList + "Claim Received HTLC Tests" + [ + testCase "Should accept accept_channel" <| fun _ -> () + ] diff --git a/tests/DotNetLightning.Core.Tests/CommitmentToLocalExtensionTests.fs b/tests/DotNetLightning.Core.Tests/CommitmentToLocalExtensionTests.fs index c87431125..102f965e2 100644 --- a/tests/DotNetLightning.Core.Tests/CommitmentToLocalExtensionTests.fs +++ b/tests/DotNetLightning.Core.Tests/CommitmentToLocalExtensionTests.fs @@ -12,27 +12,46 @@ open ResultUtils.Portability [] let commitmentToLocalExtensionTests = - testList "CommitmentToLocalExtensionTests" [ - testCase "can extract parameters" <| fun _ -> - let rand = Random() - let revocationPubKey = - let key = new Key() - let pubKey = key.PubKey - RevocationPubKey pubKey - let localDelayedPaymentPubKey = - let key = new Key() - let pubKey = key.PubKey - DelayedPaymentPubKey pubKey - let toSelfDelay = - BlockHeightOffset16 (rand.Next(1, 1000) |> uint16) - let scriptPubKey = - Scripts.toLocalDelayed - revocationPubKey + testList + "CommitmentToLocalExtensionTests" + [ + testCase "can extract parameters" + <| fun _ -> + let rand = Random() + + let revocationPubKey = + let key = new Key() + let pubKey = key.PubKey + RevocationPubKey pubKey + + let localDelayedPaymentPubKey = + let key = new Key() + let pubKey = key.PubKey + DelayedPaymentPubKey pubKey + + let toSelfDelay = + BlockHeightOffset16(rand.Next(1, 1000) |> uint16) + + let scriptPubKey = + Scripts.toLocalDelayed + revocationPubKey + toSelfDelay + localDelayedPaymentPubKey + + let parametersOpt = + CommitmentToLocalParameters.TryExtractParameters + scriptPubKey + + Expect.isSome parametersOpt "failed to extract parameters" + let parameters = parametersOpt.Value + + Expect.equal + parameters.ToSelfDelay toSelfDelay + "to_self_delay mismatch" + + Expect.equal + parameters.LocalDelayedPubKey localDelayedPaymentPubKey - let parametersOpt = CommitmentToLocalParameters.TryExtractParameters scriptPubKey - Expect.isSome parametersOpt "failed to extract parameters" - let parameters = parametersOpt.Value - Expect.equal parameters.ToSelfDelay toSelfDelay "to_self_delay mismatch" - Expect.equal parameters.LocalDelayedPubKey localDelayedPaymentPubKey "local delayed pubkey mismatch" - ] + "local delayed pubkey mismatch" + ] diff --git a/tests/DotNetLightning.Core.Tests/EncryptDecrypt.fs b/tests/DotNetLightning.Core.Tests/EncryptDecrypt.fs index 6bb633ca6..062575b88 100644 --- a/tests/DotNetLightning.Core.Tests/EncryptDecrypt.fs +++ b/tests/DotNetLightning.Core.Tests/EncryptDecrypt.fs @@ -11,29 +11,87 @@ open ResultUtils.Portability let hex = NBitcoin.DataEncoders.HexEncoder() let encryptTest(cryptoImpl: ICryptoImpl) = - let key = NBitcoin.uint256(ReadOnlySpan (hex.DecodeData "e68f69b7f096d7917245f5e5cf8ae1595febe4d4644333c99f9c4a1282031c9f")) - let nonce = (hex.DecodeData "000000000000000000000000", 0) |> BitConverter.ToUInt64 - let ad = ReadOnlySpan(hex.DecodeData "9e0e7de8bb75554f21db034633de04be41a2b8a18da7a319a03c803bf02b396c") + let key = + NBitcoin.uint256( + ReadOnlySpan( + hex.DecodeData + "e68f69b7f096d7917245f5e5cf8ae1595febe4d4644333c99f9c4a1282031c9f" + ) + ) + + let nonce = + (hex.DecodeData "000000000000000000000000", 0) |> BitConverter.ToUInt64 + + let ad = + ReadOnlySpan( + hex.DecodeData + "9e0e7de8bb75554f21db034633de04be41a2b8a18da7a319a03c803bf02b396c" + ) + let plaintext = ReadOnlySpan(Array.zeroCreate 0) - Expect.equal (cryptoImpl.encryptWithAD(nonce, key, ad, plaintext)) (hex.DecodeData "0df6086551151f58b8afe6c195782c6a") "empty plaintext encrypts correctly" + + Expect.equal + (cryptoImpl.encryptWithAD(nonce, key, ad, plaintext)) + (hex.DecodeData "0df6086551151f58b8afe6c195782c6a") + "empty plaintext encrypts correctly" let decryptTest(cryptoImpl: ICryptoImpl) = - let key = NBitcoin.uint256(ReadOnlySpan(hex.DecodeData "e68f69b7f096d7917245f5e5cf8ae1595febe4d4644333c99f9c4a1282031c9f")) + let key = + NBitcoin.uint256( + ReadOnlySpan( + hex.DecodeData + "e68f69b7f096d7917245f5e5cf8ae1595febe4d4644333c99f9c4a1282031c9f" + ) + ) + let nonce = uint64 0 - let ad = hex.DecodeData "9e0e7de8bb75554f21db034633de04be41a2b8a18da7a319a03c803bf02b396c" - let ciphertext = ReadOnlySpan(hex.DecodeData "0df6086551151f58b8afe6c195782c6a") - Expect.equal (cryptoImpl.decryptWithAD(nonce, key, ad, ciphertext)) (Ok [||]) "decryption returns empty plaintext" + + let ad = + hex.DecodeData + "9e0e7de8bb75554f21db034633de04be41a2b8a18da7a319a03c803bf02b396c" + + let ciphertext = + ReadOnlySpan(hex.DecodeData "0df6086551151f58b8afe6c195782c6a") + + Expect.equal + (cryptoImpl.decryptWithAD(nonce, key, ad, ciphertext)) + (Ok [||]) + "decryption returns empty plaintext" [] let tests = let cryptoImpl = CryptoUtils.impl let encrypt = testCase "encrypt" <| fun _ -> encryptTest cryptoImpl let decrypt = testCase "decrypt" <| fun _ -> decryptTest cryptoImpl + let encryptComposedDecryptIsId = - testProperty "decrypt after encrypt for any plaintext equals the original plaintext" <| fun (nonce: uint64) (shortKey: uint64) (ad: byte[]) (plaintext: byte[]) -> - let encrypted = cryptoImpl.encryptWithAD(nonce, NBitcoin.uint256 shortKey, ReadOnlySpan ad, ReadOnlySpan plaintext) - let decryption = cryptoImpl.decryptWithAD(nonce, NBitcoin.uint256 shortKey, ad, ReadOnlySpan encrypted) + testProperty + "decrypt after encrypt for any plaintext equals the original plaintext" + <| fun (nonce: uint64) (shortKey: uint64) (ad: array) (plaintext: array) -> + let encrypted = + cryptoImpl.encryptWithAD( + nonce, + NBitcoin.uint256 shortKey, + ReadOnlySpan ad, + ReadOnlySpan plaintext + ) + + let decryption = + cryptoImpl.decryptWithAD( + nonce, + NBitcoin.uint256 shortKey, + ad, + ReadOnlySpan encrypted + ) + match decryption with | Result.Ok x -> x = plaintext | Result.Error _ -> false - testList "BOLT-08 tests" [ encrypt; decrypt; encryptComposedDecryptIsId ] + + testList + "BOLT-08 tests" + [ + encrypt + decrypt + encryptComposedDecryptIsId + ] diff --git a/tests/DotNetLightning.Core.Tests/FunctionalTests.fs b/tests/DotNetLightning.Core.Tests/FunctionalTests.fs index 4d0b1cdb3..0cafd6507 100644 --- a/tests/DotNetLightning.Core.Tests/FunctionalTests.fs +++ b/tests/DotNetLightning.Core.Tests/FunctionalTests.fs @@ -3,6 +3,4 @@ module FunctionalTests open Expecto [] -let tests = - testList "test from rust-lightning" [ - ] \ No newline at end of file +let tests = testList "test from rust-lightning" [] diff --git a/tests/DotNetLightning.Core.Tests/Generators/Generators.fs b/tests/DotNetLightning.Core.Tests/Generators/Generators.fs index 40b789eec..d6e23adec 100644 --- a/tests/DotNetLightning.Core.Tests/Generators/Generators.fs +++ b/tests/DotNetLightning.Core.Tests/Generators/Generators.fs @@ -14,18 +14,20 @@ type PrimitiveGenerators = static member ECDSASignature() : Arbitrary = Arb.fromGen(signatureGen) - static member UInt256(): Arbitrary = + static member UInt256() : Arbitrary = Arb.fromGen(uint256Gen) - static member PubKey() = Arb.fromGen(pubKeyGen) + static member PubKey() = + Arb.fromGen(pubKeyGen) - static member NodeId() = Arb.fromGen(NodeId pubKeyGen) + static member NodeId() = + Arb.fromGen(NodeId pubKeyGen) type P2PMsgGenerators = static member Init() : Arbitrary = Arb.fromGen(initGen) - static member ErrorMsg(): Arbitrary = + static member ErrorMsg() : Arbitrary = Arb.fromGen(errorMsgGen) static member Ping() : Arbitrary = @@ -34,112 +36,123 @@ type P2PMsgGenerators = static member Pong() : Arbitrary = Arb.fromGen(pongGen) - static member OpenChannel(): Arbitrary = + static member OpenChannel() : Arbitrary = Arb.fromGen(openChannelGen) - static member AcceptChannel(): Arbitrary = + static member AcceptChannel() : Arbitrary = Arb.fromGen(acceptChannelGen) - static member FundingCreated(): Arbitrary = + static member FundingCreated() : Arbitrary = Arb.fromGen(fundingCreatedGen) - static member FundingSigned(): Arbitrary = + static member FundingSigned() : Arbitrary = Arb.fromGen(fundingSignedGen) - static member FundingLocked(): Arbitrary = + static member FundingLocked() : Arbitrary = Arb.fromGen(fundingLockedGen) - static member Shutdown(): Arbitrary = + static member Shutdown() : Arbitrary = Arb.fromGen(shutdownGen) - static member ClosingSigned(): Arbitrary = + static member ClosingSigned() : Arbitrary = Arb.fromGen(closingSignedGen) - static member OnionPacket(): Arbitrary = + static member OnionPacket() : Arbitrary = Arb.fromGen(onionPacketGen) - static member UpdateAddHTLC(): Arbitrary = + static member UpdateAddHTLC() : Arbitrary = Arb.fromGen(updateAddHTLCGen) - static member UpdateFulfillHTLC(): Arbitrary = + static member UpdateFulfillHTLC() : Arbitrary = Arb.fromGen(updateFulfillHTLCGen) - static member UpdateFailHTLC(): Arbitrary = + static member UpdateFailHTLC() : Arbitrary = Arb.fromGen(updateFailHTLCGen) - static member UpdateFailMalformedHTLC(): Arbitrary = + static member UpdateFailMalformedHTLC + () + : Arbitrary = Arb.fromGen(updateFailMalformedHTLCGen) - static member CommitmentSigned(): Arbitrary = + static member CommitmentSigned() : Arbitrary = Arb.fromGen(commitmentSignedGen) - static member RevokeAndACK(): Arbitrary = + static member RevokeAndACK() : Arbitrary = Arb.fromGen(revokeAndACKGen) - static member UpdateFee(): Arbitrary = + static member UpdateFee() : Arbitrary = Arb.fromGen(updateFeeGen) - static member ChannelReestablish(): Arbitrary = + static member ChannelReestablish() : Arbitrary = Arb.fromGen(channelReestablishGen) - static member AnnouncementSignatures(): Arbitrary = + static member AnnouncementSignatures + () + : Arbitrary = Arb.fromGen(announcementSignaturesGen) - static member UnsignedNodeAnnouncement(): Arbitrary = + static member UnsignedNodeAnnouncement + () + : Arbitrary = Arb.fromGen unsignedNodeAnnouncementGen - static member NodeAnnouncement(): Arbitrary = + static member NodeAnnouncement() : Arbitrary = Arb.fromGen nodeAnnouncementGen - static member ChannelAnnouncement(): Arbitrary = + static member ChannelAnnouncement() : Arbitrary = Arb.fromGen channelAnnouncementGen - static member ChannelUpdate(): Arbitrary = + static member ChannelUpdate() : Arbitrary = Arb.fromGen channelUpdateGen - static member QueryShortChannelIds(): Arbitrary = + static member QueryShortChannelIds() : Arbitrary = Arb.fromGen queryShortChannelIdsGen static member ReplyShortChannelIds() = Arb.fromGen(replyShortChannelIdsEndGen) - static member QueryChannelRange() = Arb.fromGen queryChannelRangeGen + static member QueryChannelRange() = + Arb.fromGen queryChannelRangeGen static member ReplyChannelRange = Arb.fromGen replyChannelRangeGen static member GossipTimestampFilter = Arb.fromGen gossipTimestampFilterGen - static member OnionPayload() = Arb.fromGen(onionPayloadGen) - - static member P2PMsg(): Arbitrary = - Gen.oneof [ - initGen |> Gen.map(fun i -> i :> ILightningMsg) - errorMsgGen |> Gen.map(fun i -> i :> ILightningMsg) - pingGen |> Gen.map(fun i -> i :> ILightningMsg) - pongGen |> Gen.map(fun i -> i :> ILightningMsg) - openChannelGen |> Gen.map(fun i -> i :> ILightningMsg) - acceptChannelGen |> Gen.map(fun i -> i :> ILightningMsg) - fundingCreatedGen |> Gen.map(fun i -> i :> ILightningMsg) - fundingSignedGen |> Gen.map(fun i -> i :> ILightningMsg) - fundingLockedGen |> Gen.map(fun i -> i :> ILightningMsg) - shutdownGen |> Gen.map(fun i -> i :> ILightningMsg) - closingSignedGen |> Gen.map(fun i -> i :> ILightningMsg) - updateAddHTLCGen |> Gen.map(fun i -> i :> ILightningMsg) - updateFulfillHTLCGen |> Gen.map(fun i -> i :> ILightningMsg) - updateFailHTLCGen |> Gen.map(fun i -> i :> ILightningMsg) - updateFailMalformedHTLCGen |> Gen.map(fun i -> i :> ILightningMsg) - commitmentSignedGen |> Gen.map(fun i -> i :> ILightningMsg) - revokeAndACKGen |> Gen.map(fun i -> i :> ILightningMsg) - updateFeeGen |> Gen.map(fun i -> i :> ILightningMsg) - channelReestablishGen |> Gen.map(fun i -> i :> ILightningMsg) - announcementSignaturesGen |> Gen.map(fun i -> i :> ILightningMsg) - nodeAnnouncementGen |> Gen.map(fun i -> i :> ILightningMsg) - channelAnnouncementGen |> Gen.map(fun i -> i :> ILightningMsg) - channelUpdateGen |> Gen.map(fun i -> i :> ILightningMsg) - queryShortChannelIdsGen |> Gen.map(fun i -> i :> ILightningMsg) - replyShortChannelIdsEndGen |> Gen.map(fun i -> i :> ILightningMsg) - queryChannelRangeGen |> Gen.map(fun i -> i :> ILightningMsg) - replyChannelRangeGen |> Gen.map(fun i -> i :> ILightningMsg) - gossipTimestampFilterGen |> Gen.map(fun i -> i :> ILightningMsg) - ] - |> Arb.fromGen - + static member OnionPayload() = + Arb.fromGen(onionPayloadGen) + + static member P2PMsg() : Arbitrary = + Gen.oneof + [ + initGen |> Gen.map(fun i -> i :> ILightningMsg) + errorMsgGen |> Gen.map(fun i -> i :> ILightningMsg) + pingGen |> Gen.map(fun i -> i :> ILightningMsg) + pongGen |> Gen.map(fun i -> i :> ILightningMsg) + openChannelGen |> Gen.map(fun i -> i :> ILightningMsg) + acceptChannelGen |> Gen.map(fun i -> i :> ILightningMsg) + fundingCreatedGen |> Gen.map(fun i -> i :> ILightningMsg) + fundingSignedGen |> Gen.map(fun i -> i :> ILightningMsg) + fundingLockedGen |> Gen.map(fun i -> i :> ILightningMsg) + shutdownGen |> Gen.map(fun i -> i :> ILightningMsg) + closingSignedGen |> Gen.map(fun i -> i :> ILightningMsg) + updateAddHTLCGen |> Gen.map(fun i -> i :> ILightningMsg) + updateFulfillHTLCGen |> Gen.map(fun i -> i :> ILightningMsg) + updateFailHTLCGen |> Gen.map(fun i -> i :> ILightningMsg) + updateFailMalformedHTLCGen + |> Gen.map(fun i -> i :> ILightningMsg) + commitmentSignedGen |> Gen.map(fun i -> i :> ILightningMsg) + revokeAndACKGen |> Gen.map(fun i -> i :> ILightningMsg) + updateFeeGen |> Gen.map(fun i -> i :> ILightningMsg) + channelReestablishGen |> Gen.map(fun i -> i :> ILightningMsg) + announcementSignaturesGen + |> Gen.map(fun i -> i :> ILightningMsg) + nodeAnnouncementGen |> Gen.map(fun i -> i :> ILightningMsg) + channelAnnouncementGen |> Gen.map(fun i -> i :> ILightningMsg) + channelUpdateGen |> Gen.map(fun i -> i :> ILightningMsg) + queryShortChannelIdsGen |> Gen.map(fun i -> i :> ILightningMsg) + replyShortChannelIdsEndGen + |> Gen.map(fun i -> i :> ILightningMsg) + queryChannelRangeGen |> Gen.map(fun i -> i :> ILightningMsg) + replyChannelRangeGen |> Gen.map(fun i -> i :> ILightningMsg) + gossipTimestampFilterGen |> Gen.map(fun i -> i :> ILightningMsg) + ] + |> Arb.fromGen diff --git a/tests/DotNetLightning.Core.Tests/Generators/Msgs.fs b/tests/DotNetLightning.Core.Tests/Generators/Msgs.fs index 6a61d015e..25fdd76dd 100644 --- a/tests/DotNetLightning.Core.Tests/Generators/Msgs.fs +++ b/tests/DotNetLightning.Core.Tests/Generators/Msgs.fs @@ -15,13 +15,17 @@ let completeFeaturesGen = let bitsGen = seq { for f in Feature.allFeatures do - Gen.oneof(seq [ - Gen.constant(1L <<< f.MandatoryBitPosition) - Gen.constant(1L <<< f.OptionalBitPosition) - Gen.constant(0L) - ]) + Gen.oneof( + seq + [ + Gen.constant(1L <<< f.MandatoryBitPosition) + Gen.constant(1L <<< f.OptionalBitPosition) + Gen.constant(0L) + ] + ) } |> Gen.sequence + bitsGen |> Gen.map(Seq.reduce(|||)) |> Gen.map(FeatureBits.TryCreate) @@ -29,603 +33,859 @@ let completeFeaturesGen = |> Gen.map(ResultUtils.Result.deref) let private featuresGen = - Gen.constant (1L <<< Feature.InitialRoutingSync.OptionalBitPosition) + Gen.constant(1L <<< Feature.InitialRoutingSync.OptionalBitPosition) |> Gen.map FeatureBits.CreateUnsafe + let private chainHashGen = - Gen.oneof(seq { - yield Gen.constant NBitcoin.Consensus.Main.HashGenesisBlock; - yield Gen.constant NBitcoin.Consensus.TestNet.HashGenesisBlock; - yield Gen.constant NBitcoin.Consensus.RegTest.HashGenesisBlock; - }) + Gen.oneof( + seq { + yield Gen.constant NBitcoin.Consensus.Main.HashGenesisBlock + yield Gen.constant NBitcoin.Consensus.TestNet.HashGenesisBlock + yield Gen.constant NBitcoin.Consensus.RegTest.HashGenesisBlock + } + ) -let genericTLVGen (known: uint64 list) = +let genericTLVGen(known: list) = gen { let! t = Arb.generate - |> Gen.filter(fun v -> not <| (List.exists(fun knownInt -> v = knownInt) known)) - let! v = Arb.generate> - return { GenericTLV.Type = t; Value = v.Get } + |> Gen.filter(fun v -> + not <| (List.exists (fun knownInt -> v = knownInt) known) + ) + + let! v = Arb.generate>> + + return + { + GenericTLV.Type = t + Value = v.Get + } } + let initTLVGen = - Gen.frequency[| - (1, chainHashGen |> Gen.map(Array.singleton >> InitTLV.Networks)) - (1, genericTLVGen([1UL]) |> Gen.map(InitTLV.Unknown)) - |] + Gen.frequency + [| + (1, chainHashGen |> Gen.map(Array.singleton >> InitTLV.Networks)) + (1, genericTLVGen([ 1UL ]) |> Gen.map(InitTLV.Unknown)) + |] let initGen = - Gen.map2 (fun f tlvS -> { Features = f; TLVStream = tlvS }) + Gen.map2 + (fun f tlvS -> + { + Features = f + TLVStream = tlvS + } + ) featuresGen (initTLVGen |> Gen.map(Array.singleton)) -let errorMsgGen = gen { - let specificC = SpecificChannel (ChannelId uint256Gen) - let allC = Gen.constant WhichChannel.All - let! c = Gen.oneof [specificC; allC] - let! d = bytesGen - return {ChannelId = c; Data = d} -} +let errorMsgGen = + gen { + let specificC = SpecificChannel (ChannelId uint256Gen) + let allC = Gen.constant WhichChannel.All + let! c = Gen.oneof [ specificC; allC ] + let! d = bytesGen + + return + { + ChannelId = c + Data = d + } + } let pingGen = - Gen.map2(fun pLen bLen -> { PongLen = pLen; BytesLen = bLen }) + Gen.map2 + (fun pLen bLen -> + { + PongLen = pLen + BytesLen = bLen + } + ) Arb.generate Arb.generate let pongGen = - Gen.map(fun bLen -> { BytesLen = bLen }) + Gen.map + (fun bLen -> + { + BytesLen = bLen + } + ) Arb.generate let openChannelGen = - let constructor = fun arg1 arg2 arg3 arg4 arg5 arg6 arg7 arg8 arg9 arg10 arg11 arg12 arg13 arg14 arg15 arg16 arg17 arg18 arg19 -> { - OpenChannelMsg.Chainhash = arg1 - TemporaryChannelId = arg2 - FundingSatoshis = arg3 - PushMSat = arg4 - DustLimitSatoshis = arg5 - MaxHTLCValueInFlightMsat = arg6 - ChannelReserveSatoshis = arg7 - HTLCMinimumMsat = arg8 - FeeRatePerKw = arg9 - ToSelfDelay = arg10 - MaxAcceptedHTLCs = arg11 - FundingPubKey = arg12 - RevocationBasepoint = arg13 - PaymentBasepoint = arg14 - DelayedPaymentBasepoint = arg15 - HTLCBasepoint = arg16 - FirstPerCommitmentPoint = arg17 - ChannelFlags = arg18 - TLVs = arg19 - } - constructor - (uint256Gen) - <*> (temporaryChannelGen) - <*> (moneyGen) - <*> (lnMoneyGen) - <*> (moneyGen) - <*> (lnMoneyGen) - <*> (moneyGen) - <*> (lnMoneyGen) - <*> (FeeRatePerKw Arb.generate) - <*> (BlockHeightOffset16 Arb.generate) - <*> Arb.generate - <*> fundingPubKeyGen - <*> revocationBasepointGen - <*> paymentBasepointGen - <*> delayedPaymentBasepointGen - <*> htlcBasepointGen - <*> perCommitmentPointGen - <*> channelFlagsGen - <*> Gen.oneof [ - gen { - let! genericTLV = genericTLVGen [0UL] - return [| OpenChannelTLV.Unknown genericTLV |] - } - gen { - let! shutdownScript = (Gen.optionOf shutdownScriptPubKeyGen) - return [| OpenChannelTLV.UpfrontShutdownScript shutdownScript |] + let constructor = + fun arg1 arg2 arg3 arg4 arg5 arg6 arg7 arg8 arg9 arg10 arg11 arg12 arg13 arg14 arg15 arg16 arg17 arg18 arg19 -> + { + OpenChannelMsg.Chainhash = arg1 + TemporaryChannelId = arg2 + FundingSatoshis = arg3 + PushMSat = arg4 + DustLimitSatoshis = arg5 + MaxHTLCValueInFlightMsat = arg6 + ChannelReserveSatoshis = arg7 + HTLCMinimumMsat = arg8 + FeeRatePerKw = arg9 + ToSelfDelay = arg10 + MaxAcceptedHTLCs = arg11 + FundingPubKey = arg12 + RevocationBasepoint = arg13 + PaymentBasepoint = arg14 + DelayedPaymentBasepoint = arg15 + HTLCBasepoint = arg16 + FirstPerCommitmentPoint = arg17 + ChannelFlags = arg18 + TLVs = arg19 } - ] -let acceptChannelGen = - let constructor = fun a b c d e f g h i j k l m n o -> - { - TemporaryChannelId = a - DustLimitSatoshis = b - MaxHTLCValueInFlightMsat = c - ChannelReserveSatoshis = d - HTLCMinimumMSat = e - MinimumDepth = f - ToSelfDelay = g - MaxAcceptedHTLCs = h - FundingPubKey = i - RevocationBasepoint = j - PaymentBasepoint = k - DelayedPaymentBasepoint = l - HTLCBasepoint = m - FirstPerCommitmentPoint = n - TLVs = o - } + constructor (uint256Gen) + <*> (temporaryChannelGen) + <*> (moneyGen) + <*> (lnMoneyGen) + <*> (moneyGen) + <*> (lnMoneyGen) + <*> (moneyGen) + <*> (lnMoneyGen) + <*> (FeeRatePerKw Arb.generate) + <*> (BlockHeightOffset16 Arb.generate) + <*> Arb.generate + <*> fundingPubKeyGen + <*> revocationBasepointGen + <*> paymentBasepointGen + <*> delayedPaymentBasepointGen + <*> htlcBasepointGen + <*> perCommitmentPointGen + <*> channelFlagsGen + <*> Gen.oneof + [ + gen { + let! genericTLV = genericTLVGen [ 0UL ] + return [| OpenChannelTLV.Unknown genericTLV |] + } + gen { + let! shutdownScript = (Gen.optionOf shutdownScriptPubKeyGen) + + return + [| + OpenChannelTLV.UpfrontShutdownScript shutdownScript + |] + } + ] - constructor - temporaryChannelGen - <*> moneyGen - <*> lnMoneyGen - <*> moneyGen - <*> lnMoneyGen - <*> (Arb.generate |> Gen.map(BlockHeightOffset32)) - <*> (BlockHeightOffset16 Arb.generate) - <*> Arb.generate - <*> fundingPubKeyGen - <*> revocationBasepointGen - <*> paymentBasepointGen - <*> delayedPaymentBasepointGen - <*> htlcBasepointGen - <*> perCommitmentPointGen - <*> Gen.oneof [ - gen { - let! genericTLV = genericTLVGen [0UL] - return [| AcceptChannelTLV.Unknown genericTLV |] - }; - gen { - let! shutdownScript = (Gen.optionOf shutdownScriptPubKeyGen) - return [| AcceptChannelTLV.UpfrontShutdownScript shutdownScript |] +let acceptChannelGen = + let constructor = + fun a b c d e f g h i j k l m n o -> + { + TemporaryChannelId = a + DustLimitSatoshis = b + MaxHTLCValueInFlightMsat = c + ChannelReserveSatoshis = d + HTLCMinimumMSat = e + MinimumDepth = f + ToSelfDelay = g + MaxAcceptedHTLCs = h + FundingPubKey = i + RevocationBasepoint = j + PaymentBasepoint = k + DelayedPaymentBasepoint = l + HTLCBasepoint = m + FirstPerCommitmentPoint = n + TLVs = o } - ] + + constructor temporaryChannelGen + <*> moneyGen + <*> lnMoneyGen + <*> moneyGen + <*> lnMoneyGen + <*> (Arb.generate |> Gen.map(BlockHeightOffset32)) + <*> (BlockHeightOffset16 Arb.generate) + <*> Arb.generate + <*> fundingPubKeyGen + <*> revocationBasepointGen + <*> paymentBasepointGen + <*> delayedPaymentBasepointGen + <*> htlcBasepointGen + <*> perCommitmentPointGen + <*> Gen.oneof + [ + gen { + let! genericTLV = genericTLVGen [ 0UL ] + + return + [| + AcceptChannelTLV.Unknown genericTLV + |] + } + gen { + let! shutdownScript = (Gen.optionOf shutdownScriptPubKeyGen) + + return + [| + AcceptChannelTLV.UpfrontShutdownScript + shutdownScript + |] + } + ] let fundingCreatedGen = - let constructor = fun a b c d -> - { - TemporaryChannelId = a - FundingTxId = b - FundingOutputIndex = c - Signature = d - } + let constructor = + fun a b c d -> + { + TemporaryChannelId = a + FundingTxId = b + FundingOutputIndex = c + Signature = d + } + + constructor temporaryChannelGen + <*> (TxId uint256Gen) + <*> (TxOutIndex Arb.generate) + <*> signatureGen + +let fundingSignedGen = + gen { + let! c = ChannelId uint256Gen + let! s = signatureGen + + return + { + FundingSignedMsg.ChannelId = c + Signature = s + } + } + +let fundingLockedGen = + gen { + let! c = ChannelId uint256Gen + let! pk = perCommitmentPointGen + + return + { + ChannelId = c + NextPerCommitmentPoint = pk + } + } + +let shutdownGen = + gen { + let! c = ChannelId uint256Gen + let! sc = shutdownScriptPubKeyGen - constructor - temporaryChannelGen - <*> (TxId uint256Gen) - <*> (TxOutIndex Arb.generate) - <*> signatureGen - -let fundingSignedGen = gen { - let! c = ChannelId uint256Gen - let! s = signatureGen - return { - FundingSignedMsg.ChannelId = c - Signature = s - } -} - -let fundingLockedGen = gen { - let! c = ChannelId uint256Gen - let! pk = perCommitmentPointGen - return {ChannelId = c; NextPerCommitmentPoint = pk} -} - -let shutdownGen = gen { - let! c = ChannelId uint256Gen - let! sc = shutdownScriptPubKeyGen - return { ChannelId = c; ScriptPubKey = sc } -} - -let closingSignedGen = gen { - let! c = ChannelId uint256Gen - let! m = moneyGen - let! s = signatureGen - return { ChannelId=c; FeeSatoshis=m; Signature=s} -} - -let onionPacketGen = gen { - // let! v = Arb.generate - let! pk = pubKeyGen |> Gen.map(fun pk -> pk.ToBytes()) - let! hopData = bytesOfNGen(1300) - let! hmac = uint256Gen - return { Version = 0uy; PublicKey=pk; HopData=hopData; HMAC=hmac } -} - -let updateAddHTLCGen = gen { - let! c = ChannelId uint256Gen - let! htlc = HTLCId Arb.generate - let! amount = lnMoneyGen - let! paymentHash = PaymentHash uint256Gen - let! cltvE = Arb.generate - let! onionRoutingPacket = onionPacketGen - return { - ChannelId = c - HTLCId = htlc - Amount = amount - PaymentHash = paymentHash - CLTVExpiry = cltvE |> BlockHeight - OnionRoutingPacket = onionRoutingPacket - } -} - -let updateFulfillHTLCGen = gen { - let! c = ChannelId uint256Gen - let! htlc = HTLCId Arb.generate - let! paymentPreimage = PaymentPreimage.Create bytesOfNGen PaymentPreimage.LENGTH - return { ChannelId = c; HTLCId = htlc; PaymentPreimage = paymentPreimage } -} - - -let updateFailHTLCGen = gen { - let! c = ChannelId uint256Gen - let! htlc = HTLCId Arb.generate - let! reason = bytesGen |> Gen.map (fun bs -> { Data = bs}) - return { - ChannelId = c - HTLCId = htlc - Reason = reason - } -} - -let updateFailMalformedHTLCGen = gen { - let! c = ChannelId uint256Gen - let! htlc = HTLCId Arb.generate - let! sha256 = uint256Gen - let! ec = FailureCode Arb.generate - return { - ChannelId = c - HTLCId = htlc - Sha256OfOnion = sha256 - FailureCode = ec - } -} - -let commitmentSignedGen = gen { - let! c = ChannelId uint256Gen - let! s = signatureGen - let! n = Arb.generate - let! ss = Gen.listOfLength (int n) signatureGen - return { - ChannelId = c - Signature = s - HTLCSignatures = ss - } -} - -let revokeAndACKGen = gen { - let! c = ChannelId uint256Gen - let! perCommitmentSecret = perCommitmentSecretGen - let! pk = perCommitmentPointGen - return { - ChannelId = c - PerCommitmentSecret = perCommitmentSecret - NextPerCommitmentPoint = pk - } -} -let updateFeeGen = gen { - let! c = ChannelId uint256Gen - let! fr = FeeRatePerKw Arb.generate - return { - ChannelId = c - FeeRatePerKw = fr - } -} - -let private dataLossProtectGen = gen { - let! perCommitmentSecret = perCommitmentSecretGen - let! pk = perCommitmentPointGen - return { - YourLastPerCommitmentSecret = Some perCommitmentSecret - MyCurrentPerCommitmentPoint = pk - } -} - -let channelReestablishGen = gen { - let! c = ChannelId uint256Gen - let! n1 = commitmentNumberGen - let! n2 = commitmentNumberGen - let! d = Gen.optionOf dataLossProtectGen - - return { - ChannelId = c - NextCommitmentNumber = n1 - NextRevocationNumber = n2 - DataLossProtect = d - } -} - -let announcementSignaturesGen = gen { - let! c = ChannelId uint256Gen - let! s = ShortChannelId.FromUInt64 Arb.generate - let! ns = signatureGen - let! bs = signatureGen - return { - ChannelId = c - ShortChannelId = s - NodeSignature = ns - BitcoinSignature = bs - } -} - -let private ipV4AddressGen = gen { - let! addr = bytesOfNGen(4) - let! port = Arb.generate - return NetAddress.IPv4 { IPv4Or6Data.Addr = addr; Port = port } -} - -let private ipV6AddressGen = gen { - let! addr = bytesOfNGen(16) - let! port = Arb.generate - return NetAddress.IPv6 { IPv4Or6Data.Addr = addr; Port = port } -} - -let private onionV2AddressGen = gen { - let! addr = bytesOfNGen(10) - let! port = Arb.generate - return NetAddress.OnionV2 { OnionV2EndPoint.Addr = addr; Port = port } -} - -let private onionV3AddressGen = gen { - let! cs = Arb.generate - let! pk = bytesOfNGen(32) - let! port = Arb.generate - let! v = Arb.generate - return NetAddress.OnionV3 { OnionV3EndPoint.CheckSum = cs - ed25519PubKey = pk - Version = v - Port = port } -} - - -let private netAddressesGen = gen { - let! ipv4 = Gen.optionOf(ipV4AddressGen) - let! ipv6 = Gen.optionOf(ipV6AddressGen) - let! onionv2 = Gen.optionOf(onionV2AddressGen) - let! onionv3 = Gen.optionOf(onionV3AddressGen) - return [|ipv4; ipv6; onionv2; onionv3|] -} - -let unsignedNodeAnnouncementGen = gen { - let! f = featuresGen - let! t = Arb.generate - let! nodeId = NodeId pubKeyGen - let! rgb = (fun r g b -> {Red = r; Green = g; Blue = b}) - Arb.generate - <*> Arb.generate - <*> Arb.generate - - let! a = uint256Gen - let! addrs = netAddressesGen |> Gen.map (Array.choose id) - let! eAddrs = bytesGen |> Gen.filter(fun b -> b.Length = 0 || (b.[0] <> 1uy && b.[0] <> 2uy && b.[0] <> 3uy && b.[0] <> 4uy)) - let! ed = bytesGen - - return { - Features = f - Timestamp = t - NodeId = nodeId - RGB = rgb - Alias = a - Addresses = addrs - ExcessAddressData = eAddrs - ExcessData = ed - } -} - -let nodeAnnouncementGen = gen { - let! c = unsignedNodeAnnouncementGen - let! s = signatureGen - return { - NodeAnnouncementMsg.Contents = c - Signature = s - } -} - -let private unsignedChannelAnnouncementGen = gen { - let! g = featuresGen - let! ch = uint256Gen - let! s = ShortChannelId.FromUInt64 Arb.generate - let! n1 = NodeId pubKeyGen - let! n2 = NodeId pubKeyGen - let! b1 = pubKeyGen |> Gen.map ComparablePubKey - let! b2 = pubKeyGen |> Gen.map ComparablePubKey - let! e = bytesGen - - return { - Features = g - ChainHash = ch - ShortChannelId = s - NodeId1 = n1 - NodeId2 = n2 - BitcoinKey1 = b1 - BitcoinKey2 = b2 - ExcessData = e - } -} - -let channelAnnouncementGen = gen { - let! n1 = signatureGen - let! n2 = signatureGen - let! b1 = signatureGen - let! b2 = signatureGen - let! c = unsignedChannelAnnouncementGen - return { - NodeSignature1 = n1 - NodeSignature2 = n2 - BitcoinSignature1 = b1 - BitcoinSignature2 = b2 - Contents = c - } -} - -let private unsignedChannelUpdateGen = gen { - let! ch = uint256Gen - let! s = ShortChannelId.FromUInt64 Arb.generate - let! ts = Arb.generate - let! messageFlags = Arb.generate - let! channelFlags = Arb.generate - let! cltvE = BlockHeightOffset16 Arb.generate - let! htlcMin = lnMoneyGen - let! feeBase = lnMoneyGen - let! feePM = Arb.generate - let! maximum = - if ((messageFlags &&& 0b00000001uy) = 1uy) then - lnMoneyGen |> Gen.map Some - else - Gen.constant None - - return { - ChainHash = ch - ShortChannelId = s - Timestamp = ts - MessageFlags = messageFlags - ChannelFlags = channelFlags - CLTVExpiryDelta = cltvE - HTLCMinimumMSat = htlcMin - FeeBaseMSat = feeBase - FeeProportionalMillionths = feePM - HTLCMaximumMSat = maximum - } -} - -let channelUpdateGen = gen { - let! s = signatureGen - let! c = unsignedChannelUpdateGen - - return { - Signature = s - Contents = c - } -} + return + { + ChannelId = c + ScriptPubKey = sc + } + } + +let closingSignedGen = + gen { + let! c = ChannelId uint256Gen + let! m = moneyGen + let! s = signatureGen + + return + { + ChannelId = c + FeeSatoshis = m + Signature = s + } + } + +let onionPacketGen = + gen { + // let! v = Arb.generate + let! pk = pubKeyGen |> Gen.map(fun pk -> pk.ToBytes()) + let! hopData = bytesOfNGen(1300) + let! hmac = uint256Gen + + return + { + Version = 0uy + PublicKey = pk + HopData = hopData + HMAC = hmac + } + } + +let updateAddHTLCGen = + gen { + let! c = ChannelId uint256Gen + let! htlc = HTLCId Arb.generate + let! amount = lnMoneyGen + let! paymentHash = PaymentHash uint256Gen + let! cltvE = Arb.generate + let! onionRoutingPacket = onionPacketGen + + return + { + ChannelId = c + HTLCId = htlc + Amount = amount + PaymentHash = paymentHash + CLTVExpiry = cltvE |> BlockHeight + OnionRoutingPacket = onionRoutingPacket + } + } + +let updateFulfillHTLCGen = + gen { + let! c = ChannelId uint256Gen + let! htlc = HTLCId Arb.generate + + let! paymentPreimage = + PaymentPreimage.Create bytesOfNGen PaymentPreimage.LENGTH + + return + { + ChannelId = c + HTLCId = htlc + PaymentPreimage = paymentPreimage + } + } + + +let updateFailHTLCGen = + gen { + let! c = ChannelId uint256Gen + let! htlc = HTLCId Arb.generate + + let! reason = + bytesGen + |> Gen.map(fun bs -> + { + Data = bs + } + ) + + return + { + ChannelId = c + HTLCId = htlc + Reason = reason + } + } + +let updateFailMalformedHTLCGen = + gen { + let! c = ChannelId uint256Gen + let! htlc = HTLCId Arb.generate + let! sha256 = uint256Gen + let! ec = FailureCode Arb.generate + + return + { + ChannelId = c + HTLCId = htlc + Sha256OfOnion = sha256 + FailureCode = ec + } + } + +let commitmentSignedGen = + gen { + let! c = ChannelId uint256Gen + let! s = signatureGen + let! n = Arb.generate + let! ss = Gen.listOfLength (int n) signatureGen + + return + { + ChannelId = c + Signature = s + HTLCSignatures = ss + } + } + +let revokeAndACKGen = + gen { + let! c = ChannelId uint256Gen + let! perCommitmentSecret = perCommitmentSecretGen + let! pk = perCommitmentPointGen + + return + { + ChannelId = c + PerCommitmentSecret = perCommitmentSecret + NextPerCommitmentPoint = pk + } + } + +let updateFeeGen = + gen { + let! c = ChannelId uint256Gen + let! fr = FeeRatePerKw Arb.generate + + return + { + ChannelId = c + FeeRatePerKw = fr + } + } + +let private dataLossProtectGen = + gen { + let! perCommitmentSecret = perCommitmentSecretGen + let! pk = perCommitmentPointGen + + return + { + YourLastPerCommitmentSecret = Some perCommitmentSecret + MyCurrentPerCommitmentPoint = pk + } + } + +let channelReestablishGen = + gen { + let! c = ChannelId uint256Gen + let! n1 = commitmentNumberGen + let! n2 = commitmentNumberGen + let! d = Gen.optionOf dataLossProtectGen + + return + { + ChannelId = c + NextCommitmentNumber = n1 + NextRevocationNumber = n2 + DataLossProtect = d + } + } + +let announcementSignaturesGen = + gen { + let! c = ChannelId uint256Gen + let! s = ShortChannelId.FromUInt64 Arb.generate + let! ns = signatureGen + let! bs = signatureGen + + return + { + ChannelId = c + ShortChannelId = s + NodeSignature = ns + BitcoinSignature = bs + } + } + +let private ipV4AddressGen = + gen { + let! addr = bytesOfNGen(4) + let! port = Arb.generate + + return + NetAddress.IPv4 + { + IPv4Or6Data.Addr = addr + Port = port + } + } + +let private ipV6AddressGen = + gen { + let! addr = bytesOfNGen(16) + let! port = Arb.generate + + return + NetAddress.IPv6 + { + IPv4Or6Data.Addr = addr + Port = port + } + } + +let private onionV2AddressGen = + gen { + let! addr = bytesOfNGen(10) + let! port = Arb.generate + + return + NetAddress.OnionV2 + { + OnionV2EndPoint.Addr = addr + Port = port + } + } + +let private onionV3AddressGen = + gen { + let! cs = Arb.generate + let! pk = bytesOfNGen(32) + let! port = Arb.generate + let! v = Arb.generate + + return + NetAddress.OnionV3 + { + OnionV3EndPoint.CheckSum = cs + ed25519PubKey = pk + Version = v + Port = port + } + } + + +let private netAddressesGen = + gen { + let! ipv4 = Gen.optionOf(ipV4AddressGen) + let! ipv6 = Gen.optionOf(ipV6AddressGen) + let! onionv2 = Gen.optionOf(onionV2AddressGen) + let! onionv3 = Gen.optionOf(onionV3AddressGen) + return [| ipv4; ipv6; onionv2; onionv3 |] + } + +let unsignedNodeAnnouncementGen = + gen { + let! f = featuresGen + let! t = Arb.generate + let! nodeId = NodeId pubKeyGen + + let! rgb = + (fun r g b -> + { + Red = r + Green = g + Blue = b + } + ) + Arb.generate + <*> Arb.generate + <*> Arb.generate + + let! a = uint256Gen + let! addrs = netAddressesGen |> Gen.map(Array.choose id) + + let! eAddrs = + bytesGen + |> Gen.filter(fun b -> + b.Length = 0 + || (b.[0] <> 1uy && b.[0] <> 2uy && b.[0] <> 3uy && b.[0] <> 4uy) + ) + + let! ed = bytesGen + + return + { + Features = f + Timestamp = t + NodeId = nodeId + RGB = rgb + Alias = a + Addresses = addrs + ExcessAddressData = eAddrs + ExcessData = ed + } + } + +let nodeAnnouncementGen = + gen { + let! c = unsignedNodeAnnouncementGen + let! s = signatureGen + + return + { + NodeAnnouncementMsg.Contents = c + Signature = s + } + } + +let private unsignedChannelAnnouncementGen = + gen { + let! g = featuresGen + let! ch = uint256Gen + let! s = ShortChannelId.FromUInt64 Arb.generate + let! n1 = NodeId pubKeyGen + let! n2 = NodeId pubKeyGen + let! b1 = pubKeyGen |> Gen.map ComparablePubKey + let! b2 = pubKeyGen |> Gen.map ComparablePubKey + let! e = bytesGen + + return + { + Features = g + ChainHash = ch + ShortChannelId = s + NodeId1 = n1 + NodeId2 = n2 + BitcoinKey1 = b1 + BitcoinKey2 = b2 + ExcessData = e + } + } + +let channelAnnouncementGen = + gen { + let! n1 = signatureGen + let! n2 = signatureGen + let! b1 = signatureGen + let! b2 = signatureGen + let! c = unsignedChannelAnnouncementGen + + return + { + NodeSignature1 = n1 + NodeSignature2 = n2 + BitcoinSignature1 = b1 + BitcoinSignature2 = b2 + Contents = c + } + } + +let private unsignedChannelUpdateGen = + gen { + let! ch = uint256Gen + let! s = ShortChannelId.FromUInt64 Arb.generate + let! ts = Arb.generate + let! messageFlags = Arb.generate + let! channelFlags = Arb.generate + let! cltvE = BlockHeightOffset16 Arb.generate + let! htlcMin = lnMoneyGen + let! feeBase = lnMoneyGen + let! feePM = Arb.generate + + let! maximum = + if ((messageFlags &&& 0b00000001uy) = 1uy) then + lnMoneyGen |> Gen.map Some + else + Gen.constant None + + return + { + ChainHash = ch + ShortChannelId = s + Timestamp = ts + MessageFlags = messageFlags + ChannelFlags = channelFlags + CLTVExpiryDelta = cltvE + HTLCMinimumMSat = htlcMin + FeeBaseMSat = feeBase + FeeProportionalMillionths = feePM + HTLCMaximumMSat = maximum + } + } + +let channelUpdateGen = + gen { + let! s = signatureGen + let! c = unsignedChannelUpdateGen + + return + { + Signature = s + Contents = c + } + } let private encodingTypeGen = - Gen.oneof[ - Gen.constant(EncodingType.SortedPlain) - Gen.constant(EncodingType.ZLib) - ] + Gen.oneof + [ + Gen.constant(EncodingType.SortedPlain) + Gen.constant(EncodingType.ZLib) + ] let private queryFlagGen: Gen = Arb.generate |> Gen.filter(fun x -> 0xfduy > x) // query flags should be represented as 1 byte in VarInt encoding |> Gen.map QueryFlags.Create -let private queryFlagsGen (length: int): Gen = +let private queryFlagsGen(length: int) : Gen = gen { let! ty = encodingTypeGen let! flags = Gen.arrayOfLength length queryFlagGen return QueryShortChannelIdsTLV.QueryFlags(ty, flags) } -let queryShortChannelIdsGen = gen { - let! n = Arb.generate - let! s = chainHashGen - let! ty = encodingTypeGen - let! ids = Gen.arrayOfLength n.Get shortChannelIdsGen - let! knownTLVs = queryFlagsGen n.Get |> Gen.map Array.singleton - let! unknownTLVs = (genericTLVGen([1UL]) |> Gen.map(QueryShortChannelIdsTLV.Unknown)) |> Gen.optionOf |> Gen.map(Option.toArray) - let tlvs = [knownTLVs; unknownTLVs] |> Array.concat - return { QueryShortChannelIdsMsg.ChainHash = s - ShortIdsEncodingType = ty - ShortIds = ids - TLVs = tlvs } -} - -let replyShortChannelIdsEndGen = gen { - let! b = Arb.generate - let! chainHash = chainHashGen - return { - ReplyShortChannelIdsEndMsg.Complete = b - ChainHash = chainHash - } -} +let queryShortChannelIdsGen = + gen { + let! n = Arb.generate + let! s = chainHashGen + let! ty = encodingTypeGen + let! ids = Gen.arrayOfLength n.Get shortChannelIdsGen + let! knownTLVs = queryFlagsGen n.Get |> Gen.map Array.singleton + + let! unknownTLVs = + (genericTLVGen([ 1UL ]) |> Gen.map(QueryShortChannelIdsTLV.Unknown)) + |> Gen.optionOf + |> Gen.map(Option.toArray) + + let tlvs = [ knownTLVs; unknownTLVs ] |> Array.concat + + return + { + QueryShortChannelIdsMsg.ChainHash = s + ShortIdsEncodingType = ty + ShortIds = ids + TLVs = tlvs + } + } + +let replyShortChannelIdsEndGen = + gen { + let! b = Arb.generate + let! chainHash = chainHashGen + + return + { + ReplyShortChannelIdsEndMsg.Complete = b + ChainHash = chainHash + } + } let private queryChannelRangeTLVGen = - Gen.frequency[| - (1, Arb.generate |> Gen.filter(fun x -> 0xfduy > x) |> Gen.map(QueryOption.Create >> QueryChannelRangeTLV.Opt)) - (1, genericTLVGen([1UL]) |> Gen.map(QueryChannelRangeTLV.Unknown)) - |] -let queryChannelRangeGen: Gen = gen { - let! chainHash = chainHashGen - let! firstBlockNum = Arb.generate |> Gen.map(BlockHeight) - let! nBlocks = Arb.generate - let! tlvs = queryChannelRangeTLVGen |> Gen.arrayOf - return { - QueryChannelRangeMsg.ChainHash = chainHash - FirstBlockNum = firstBlockNum - NumberOfBlocks = nBlocks - TLVs = tlvs - } -} - -let private timestampPairsGen (length: int): Gen = + Gen.frequency + [| + (1, + Arb.generate + |> Gen.filter(fun x -> 0xfduy > x) + |> Gen.map(QueryOption.Create >> QueryChannelRangeTLV.Opt)) + (1, genericTLVGen([ 1UL ]) |> Gen.map(QueryChannelRangeTLV.Unknown)) + |] + +let queryChannelRangeGen: Gen = + gen { + let! chainHash = chainHashGen + let! firstBlockNum = Arb.generate |> Gen.map(BlockHeight) + let! nBlocks = Arb.generate + let! tlvs = queryChannelRangeTLVGen |> Gen.arrayOf + + return + { + QueryChannelRangeMsg.ChainHash = chainHash + FirstBlockNum = firstBlockNum + NumberOfBlocks = nBlocks + TLVs = tlvs + } + } + +let private timestampPairsGen(length: int) : Gen = gen { let! ty = encodingTypeGen + let! timestampPairs = (Arb.generate, Arb.generate) - ||> Gen.map2(fun a b -> { TwoTimestamps.NodeId1 = a; NodeId2 = b }) + ||> Gen.map2(fun a b -> + { + TwoTimestamps.NodeId1 = a + NodeId2 = b + } + ) |> Gen.arrayOfLength length + return ReplyChannelRangeTLV.Timestamp(ty, timestampPairs) } -let private checksumPairsGen n = gen { - let! pairs = - (Arb.generate, Arb.generate) - ||> Gen.map2(fun a b -> { TwoChecksums.NodeId1 = a; NodeId2 = b }) - |> Gen.arrayOfLength n - return ReplyChannelRangeTLV.Checksums(pairs) -} +let private checksumPairsGen n = + gen { + let! pairs = + (Arb.generate, Arb.generate) + ||> Gen.map2(fun a b -> + { + TwoChecksums.NodeId1 = a + NodeId2 = b + } + ) + |> Gen.arrayOfLength n + + return ReplyChannelRangeTLV.Checksums(pairs) + } let private replyChannelRangeTLVGen n = - Gen.frequency [ - (1, timestampPairsGen n) - (1, checksumPairsGen n) - (1, genericTLVGen([1UL; 3UL]) |> Gen.map(ReplyChannelRangeTLV.Unknown)) - ] -let replyChannelRangeGen: Gen = gen { - let! ch = chainHashGen - let! firstBlockNum = Arb.generate |> Gen.map(BlockHeight) - let! nBlocks = Arb.generate - let! complete = Arb.generate - let! n = Arb.generate - let! eType = encodingTypeGen - let! shortChannelIds = shortChannelIdsGen |> Gen.arrayOfLength n.Get - let! tlvs = replyChannelRangeTLVGen n.Get |> Gen.map(Array.singleton) - return { - ReplyChannelRangeMsg.ChainHash = ch - FirstBlockNum = firstBlockNum - NumOfBlocks = nBlocks - Complete = complete - ShortIdsEncodingType = eType - ShortIds = shortChannelIds - TLVs = tlvs - } -} - -let gossipTimestampFilterGen: Gen = gen { - let! ch = chainHashGen - let! tsFirst = Arb.generate - let! tsRange = Arb.generate - return { ChainHash = ch; FirstTimestamp = tsFirst; TimestampRange = tsRange } -} + Gen.frequency + [ + (1, timestampPairsGen n) + (1, checksumPairsGen n) + (1, + genericTLVGen([ 1UL; 3UL ]) + |> Gen.map(ReplyChannelRangeTLV.Unknown)) + ] + +let replyChannelRangeGen: Gen = + gen { + let! ch = chainHashGen + let! firstBlockNum = Arb.generate |> Gen.map(BlockHeight) + let! nBlocks = Arb.generate + let! complete = Arb.generate + let! n = Arb.generate + let! eType = encodingTypeGen + let! shortChannelIds = shortChannelIdsGen |> Gen.arrayOfLength n.Get + let! tlvs = replyChannelRangeTLVGen n.Get |> Gen.map(Array.singleton) + + return + { + ReplyChannelRangeMsg.ChainHash = ch + FirstBlockNum = firstBlockNum + NumOfBlocks = nBlocks + Complete = complete + ShortIdsEncodingType = eType + ShortIds = shortChannelIds + TLVs = tlvs + } + } + +let gossipTimestampFilterGen: Gen = + gen { + let! ch = chainHashGen + let! tsFirst = Arb.generate + let! tsRange = Arb.generate + + return + { + ChainHash = ch + FirstTimestamp = tsFirst + TimestampRange = tsRange + } + } let private hopPayloadTlvGen = let paymentDataGen: Gen<_ * _> = ((PaymentPreimage.Create bytesOfNGen(32)), (lnMoneyGen)) ||> Gen.map2(fun a b -> (a, b)) + Gen.frequency - [(1, AmountToForward lnMoneyGen) - (1, OutgoingCLTV Arb.generate) - (1, HopPayloadTLV.ShortChannelId shortChannelIdsGen) - (1, PaymentData paymentDataGen)] + [ + (1, AmountToForward lnMoneyGen) + (1, OutgoingCLTV Arb.generate) + (1, HopPayloadTLV.ShortChannelId shortChannelIdsGen) + (1, PaymentData paymentDataGen) + ] + let private onionPayloadTlvGen = Gen.frequency - [(1, genericTLVGen([2UL; 4UL; 6UL; 8UL]) |> Gen.map(HopPayloadTLV.Unknown)) - (4, hopPayloadTlvGen)] + [ + (1, + genericTLVGen([ 2UL; 4UL; 6UL; 8UL ]) + |> Gen.map(HopPayloadTLV.Unknown)) + (4, hopPayloadTlvGen) + ] + let tlvOnionPayloadGen = gen { - let! tlv = Gen.nonEmptyListOf onionPayloadTlvGen |> Gen.map(List.toArray) + let! tlv = + Gen.nonEmptyListOf onionPayloadTlvGen |> Gen.map(List.toArray) + let! hmac = uint256Gen return (tlv, hmac) |> OnionPayload.TLVPayload } + let private legacyOnionPayloadGen = gen { let! scid = shortChannelIdsGen let! amt = lnMoneyGen let! cltv = Arb.generate - return { ShortChannelId = scid; AmtToForward = amt; OutgoingCLTVValue = cltv } |> OnionPayload.Legacy + + return + { + ShortChannelId = scid + AmtToForward = amt + OutgoingCLTVValue = cltv + } + |> OnionPayload.Legacy } + let onionPayloadGen = Gen.frequency - [(3, tlvOnionPayloadGen) - (1, legacyOnionPayloadGen)] + [ + (3, tlvOnionPayloadGen) + (1, legacyOnionPayloadGen) + ] diff --git a/tests/DotNetLightning.Core.Tests/Generators/Payments.fs b/tests/DotNetLightning.Core.Tests/Generators/Payments.fs index aad1dc0f1..bd96f9b15 100644 --- a/tests/DotNetLightning.Core.Tests/Generators/Payments.fs +++ b/tests/DotNetLightning.Core.Tests/Generators/Payments.fs @@ -21,76 +21,131 @@ let controlStrGen = let descTagGen = let ascii = ASCIIEncoder() - let s = seq [ - controlStrGen |> Gen.map(TaggedField.DescriptionTaggedField) - controlStrGen |> Gen.map(ascii.DecodeData >> Hashes.DoubleSHA256 >> TaggedField.DescriptionHashTaggedField) - ] + + let s = + seq + [ + controlStrGen |> Gen.map(TaggedField.DescriptionTaggedField) + controlStrGen + |> Gen.map( + ascii.DecodeData + >> Hashes.DoubleSHA256 + >> TaggedField.DescriptionHashTaggedField + ) + ] + s |> Gen.oneof let fallbackAddrGen = - seq [ - Gen.constant(BitcoinAddress.Create("2Myzv4XfqMyNZFKHUq3Jqv7zbw1AAHY3a2X", Network.RegTest)) - Gen.constant (BitcoinAddress.Create("bcrt1q5a75p74lj2269jcj83tlrzcl6vf50atn3dnt7p", Network.RegTest)) - ] + seq + [ + Gen.constant( + BitcoinAddress.Create( + "2Myzv4XfqMyNZFKHUq3Jqv7zbw1AAHY3a2X", + Network.RegTest + ) + ) + Gen.constant( + BitcoinAddress.Create( + "bcrt1q5a75p74lj2269jcj83tlrzcl6vf50atn3dnt7p", + Network.RegTest + ) + ) + ] |> Gen.oneof |> Gen.map(FallbackAddress.FromAddress) - |> Gen.filter(Result.isOk) |> Gen.map(Result.deref) + |> Gen.filter(Result.isOk) + |> Gen.map(Result.deref) |> Gen.map(TaggedField.FallbackAddressTaggedField) let routingInfoGen = - let g = gen { - let! nodeId = pubKeyGen |> Gen.map(NodeId) - let! shortChannelId = shortChannelIdsGen - let! feeBase = lnMoneyGen - let! feeProportional = Arb.generate - let! expiryDelta = Arb.generate - return ExtraHop.TryCreate(nodeId, shortChannelId, feeBase, feeProportional, BlockHeightOffset16 expiryDelta) - } + let g = + gen { + let! nodeId = pubKeyGen |> Gen.map(NodeId) + let! shortChannelId = shortChannelIdsGen + let! feeBase = lnMoneyGen + let! feeProportional = Arb.generate + let! expiryDelta = Arb.generate + + return + ExtraHop.TryCreate( + nodeId, + shortChannelId, + feeBase, + feeProportional, + BlockHeightOffset16 expiryDelta + ) + } + let g = g |> Gen.filter(Result.isOk) |> Gen.map(Result.deref) - seq [ - (20, g |> Gen.listOfLength 1) - (20, g |> Gen.listOfLength 2) - (1, g |> Gen.listOfLength 3) - ] + + seq + [ + (20, g |> Gen.listOfLength 1) + (20, g |> Gen.listOfLength 2) + (1, g |> Gen.listOfLength 3) + ] |> Gen.frequency |> Gen.map(TaggedField.RoutingInfoTaggedField) let paymentRequestGen = let taggedFieldGen = - seq [ - descTagGen - uint256Gen |> Gen.map(PaymentHash >> PaymentHashTaggedField) - Arb.generate + seq + [ + descTagGen + uint256Gen |> Gen.map(PaymentHash >> PaymentHashTaggedField) + Arb.generate |> Gen.filter(fun d -> d.IsValidUnixTime()) |> Gen.map(TaggedField.ExpiryTaggedField) - completeFeaturesGen |> Gen.map(FeaturesTaggedField) - fallbackAddrGen - routingInfoGen - uint256Gen |> Gen.map PaymentSecretTaggedField - Arb.generate |> Gen.map(BlockHeightOffset32 >> MinFinalCltvExpiryTaggedField) - ] + completeFeaturesGen |> Gen.map(FeaturesTaggedField) + fallbackAddrGen + routingInfoGen + uint256Gen |> Gen.map PaymentSecretTaggedField + Arb.generate + |> Gen.map(BlockHeightOffset32 >> MinFinalCltvExpiryTaggedField) + ] |> Gen.sequence - let taggedFieldsGen = gen { - let! f = taggedFieldGen - return { TaggedFields.Fields = f } - } - let prefixGen = Gen.oneof[ - Gen.constant "lnbcrt" - Gen.constant "lntb" - Gen.constant "lnbc" - ] + + let taggedFieldsGen = + gen { + let! f = taggedFieldGen + + return + { + TaggedFields.Fields = f + } + } + + let prefixGen = + Gen.oneof + [ + Gen.constant "lnbcrt" + Gen.constant "lntb" + Gen.constant "lnbc" + ] + gen { let! p = prefixGen let! m = moneyGen |> Gen.map(fun m -> m.ToLNMoney()) |> Gen.optionOf - let! t = Arb.generate |> Gen.filter(fun d -> d.IsValidUnixTime()) + + let! t = + Arb.generate + |> Gen.filter(fun d -> d.IsValidUnixTime()) + let! nodeSecret = keyGen let! tags = taggedFieldsGen let! shouldUseExplicitNodeId = Arb.generate + let tags = if shouldUseExplicitNodeId then - { tags with Fields = NodeIdTaggedField(nodeSecret.PubKey |> NodeId) :: tags.Fields } + { tags with + Fields = + NodeIdTaggedField(nodeSecret.PubKey |> NodeId) + :: tags.Fields + } else tags + return PaymentRequest.TryCreate(p, m, t, tags, nodeSecret) } |> Gen.filter(Result.isOk) @@ -104,22 +159,31 @@ open PrimitiveGenerators let private macaroonIdV1Gen = (uint256Gen |> Gen.map(PaymentHash.PaymentHash), uint256Gen) - ||> Gen.map2(fun p u -> { MacaroonIdentifierV0.PaymentHash = p - TokenId = u }) + ||> Gen.map2(fun p u -> + { + MacaroonIdentifierV0.PaymentHash = p + TokenId = u + } + ) |> Gen.map(MacaroonIdentifier.V0) -let private macaroonUnknownIdGen(knownVersions: uint16 seq) = + +let private macaroonUnknownIdGen(knownVersions: seq) = gen { let! t = Arb.generate - |> Gen.filter(fun v -> not <| (Seq.exists(fun knownInt -> v = knownInt) knownVersions)) - let! v = Arb.generate> + |> Gen.filter(fun v -> + not <| (Seq.exists (fun knownInt -> v = knownInt) knownVersions) + ) + + let! v = Arb.generate>> return MacaroonIdentifier.UnknownVersion(t, v.Get) } let macaroonIdGen: Gen = - Gen.oneof [ - macaroonIdV1Gen - macaroonUnknownIdGen([0us]) - ] + Gen.oneof + [ + macaroonIdV1Gen + macaroonUnknownIdGen([ 0us ]) + ] #endif diff --git a/tests/DotNetLightning.Core.Tests/Generators/Primitives.fs b/tests/DotNetLightning.Core.Tests/Generators/Primitives.fs index c042bbfe5..21489d25f 100644 --- a/tests/DotNetLightning.Core.Tests/Generators/Primitives.fs +++ b/tests/DotNetLightning.Core.Tests/Generators/Primitives.fs @@ -1,4 +1,5 @@ module PrimitiveGenerators + open FsCheck open NBitcoin open DotNetLightning.Utils.Primitives @@ -7,100 +8,130 @@ open DotNetLightning.Crypto open System open DotNetLightning.Crypto -let boolGen = Gen.oneof [ gen { return true }; gen { return false } ] +let boolGen = + Gen.oneof + [ + gen { return true } + gen { return false } + ] + let byteGen = byte Gen.choose(0, 127) let bytesGen = Gen.listOf(byteGen) |> Gen.map(List.toArray) -let bytesOfNGen(n) = Gen.listOfLength n byteGen |> Gen.map(List.toArray) -let uint48Gen = bytesOfNGen(6) |> Gen.map(fun bs -> UInt48.FromBytesBigEndian bs) + +let bytesOfNGen n = + Gen.listOfLength n byteGen |> Gen.map(List.toArray) + +let uint48Gen = + bytesOfNGen(6) |> Gen.map(fun bs -> UInt48.FromBytesBigEndian bs) + let uint256Gen = bytesOfNGen(32) |> Gen.map(fun bs -> uint256(bs)) let temporaryChannelGen = uint256Gen |> Gen.map ChannelId let moneyGen = Arb.generate |> Gen.map(Money.Satoshis) let lnMoneyGen = Arb.generate |> Gen.map(LNMoney.MilliSatoshis) -let shortChannelIdsGen = Arb.generate |> Gen.map(ShortChannelId.FromUInt64) +let shortChannelIdsGen = + Arb.generate |> Gen.map(ShortChannelId.FromUInt64) // crypto stuffs -let keyGen = Gen.fresh (fun () -> new Key()) - -let pubKeyGen = gen { - let! key = keyGen - return key.PubKey -} - -let perCommitmentSecretGen = gen { - let! key = keyGen - return PerCommitmentSecret key -} - -let perCommitmentPointGen = gen { - let! pubKey = pubKeyGen - return PerCommitmentPoint pubKey -} - -let fundingPubKeyGen = gen { - let! pubKey = pubKeyGen - return FundingPubKey pubKey -} - -let paymentBasepointGen = gen { - let! pubKey = pubKeyGen - return PaymentBasepoint pubKey -} - -let revocationBasepointGen = gen { - let! pubKey = pubKeyGen - return RevocationBasepoint pubKey -} - -let delayedPaymentBasepointGen = gen { - let! pubKey = pubKeyGen - return DelayedPaymentBasepoint pubKey -} - -let htlcBasepointGen = gen { - let! pubKey = pubKeyGen - return HtlcBasepoint pubKey -} - -let commitmentNumberGen = gen { - let! n = uint48Gen - return CommitmentNumber n -} - -let signatureGen: Gen = gen { - let! h = uint256Gen - let! k = keyGen - return k.Sign(h, false) |> LNECDSASignature -} - -let channelFlagsGen = gen { - let! announceChannel = boolGen - return { - AnnounceChannel = announceChannel +let keyGen = Gen.fresh(fun () -> new Key()) + +let pubKeyGen = + gen { + let! key = keyGen + return key.PubKey + } + +let perCommitmentSecretGen = + gen { + let! key = keyGen + return PerCommitmentSecret key + } + +let perCommitmentPointGen = + gen { + let! pubKey = pubKeyGen + return PerCommitmentPoint pubKey + } + +let fundingPubKeyGen = + gen { + let! pubKey = pubKeyGen + return FundingPubKey pubKey + } + +let paymentBasepointGen = + gen { + let! pubKey = pubKeyGen + return PaymentBasepoint pubKey + } + +let revocationBasepointGen = + gen { + let! pubKey = pubKeyGen + return RevocationBasepoint pubKey + } + +let delayedPaymentBasepointGen = + gen { + let! pubKey = pubKeyGen + return DelayedPaymentBasepoint pubKey + } + +let htlcBasepointGen = + gen { + let! pubKey = pubKeyGen + return HtlcBasepoint pubKey + } + +let commitmentNumberGen = + gen { + let! n = uint48Gen + return CommitmentNumber n + } + +let signatureGen: Gen = + gen { + let! h = uint256Gen + let! k = keyGen + return k.Sign(h, false) |> LNECDSASignature + } + +let channelFlagsGen = + gen { + let! announceChannel = boolGen + + return + { + AnnounceChannel = announceChannel + } } -} // scripts let pushOnlyOpcodeGen = bytesOfNGen(4) |> Gen.map(Op.GetPushOp) let pushOnlyOpcodesGen = Gen.listOf pushOnlyOpcodeGen -let pushScriptGen = Gen.nonEmptyListOf pushOnlyOpcodeGen |> Gen.map(fun ops -> Script(ops)) -let shutdownScriptPubKeyGen = gen { - let! pubKey = pubKeyGen - return ShutdownScriptPubKey.FromPubKeyP2pkh pubKey -} - -let cipherSeedGen = gen { - let! v = Arb.generate - let! now = Arb.generate - let! entropy = bytesOfNGen 16 - let! salt = bytesOfNGen 5 - return { - CipherSeed.InternalVersion = v - Birthday = now - Entropy = entropy - Salt = salt +let pushScriptGen = + Gen.nonEmptyListOf pushOnlyOpcodeGen |> Gen.map(fun ops -> Script(ops)) + +let shutdownScriptPubKeyGen = + gen { + let! pubKey = pubKeyGen + return ShutdownScriptPubKey.FromPubKeyP2pkh pubKey } -} +let cipherSeedGen = + gen { + let! v = Arb.generate + let! now = Arb.generate + let! entropy = bytesOfNGen 16 + let! salt = bytesOfNGen 5 + + return + { + CipherSeed.InternalVersion = v + Birthday = now + Entropy = entropy + Salt = salt + } + } diff --git a/tests/DotNetLightning.Core.Tests/GeneratorsTests.fs b/tests/DotNetLightning.Core.Tests/GeneratorsTests.fs index d4f70611e..8259ac6a7 100644 --- a/tests/DotNetLightning.Core.Tests/GeneratorsTests.fs +++ b/tests/DotNetLightning.Core.Tests/GeneratorsTests.fs @@ -8,6 +8,7 @@ open DotNetLightning.Crypto let newSecp256k1 = DotNetLightning.Crypto.CryptoUtils.impl.newSecp256k1 let hex = DataEncoders.HexEncoder() + let baseSecret = "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f" |> hex.DecodeData @@ -28,40 +29,64 @@ let revocationBasepointSecret = baseSecret |> RevocationBasepointSecret let perCommitmentPoint = "025f7117a78150fe2ef97db7cfc83bd57b2e2c0d0dd25eaf467a4a1c2a45ce1486" - |> PubKey |> PerCommitmentPoint + |> PubKey + |> PerCommitmentPoint [] let tests = - testList "key generator tests" [ - testCase "derivation key from basepoint and per-commitment-point" <| fun _ -> - use ctx = newSecp256k1() - let localkey = perCommitmentPoint.DerivePubKey basePoint - let expected = - "0235f2dbfaa89b57ec7b055afe29849ef7ddfeb1cefdb9ebdc43f5494984db29e5" - |> PubKey - Expect.equal (localkey.ToBytes()) (expected.ToBytes()) "" - - testCase "derivation of secret key from basepoint secret and per-commitment-secret" <| fun _ -> - use ctx = newSecp256k1() - let localPrivkey = perCommitmentPoint.DerivePrivKey baseSecret - let expected = - "cbced912d3b21bf196a766651e436aff192362621ce317704ea2f75d87e7be0f" - |> hex.DecodeData - Expect.equal (localPrivkey.ToBytes()) (expected) "" - - testCase "derivation of revocation key from basepoint and per_commitment_point" <| fun _ -> - use ctx = newSecp256k1() - let revocationKey = perCommitmentPoint.DeriveRevocationPubKey revocationBasepoint - let expected = - "02916e326636d19c33f13e8c0c3a03dd157f332f3e99c317c141dd865eb01f8ff0" - |> hex.DecodeData - Expect.equal (revocationKey.ToBytes()) expected "" - - testCase "derivation of revocation secret from basepoint-secret and per-commitment-secret" <| fun _ -> - use ctx = newSecp256k1() - let actual = perCommitmentSecret.DeriveRevocationPrivKey revocationBasepointSecret - let expected = - "d09ffff62ddb2297ab000cc85bcb4283fdeb6aa052affbc9dddcf33b61078110" - |> hex.DecodeData - Expect.equal (actual.ToBytes()) expected "" - ] + testList + "key generator tests" + [ + testCase "derivation key from basepoint and per-commitment-point" + <| fun _ -> + use ctx = newSecp256k1() + let localkey = perCommitmentPoint.DerivePubKey basePoint + + let expected = + "0235f2dbfaa89b57ec7b055afe29849ef7ddfeb1cefdb9ebdc43f5494984db29e5" + |> PubKey + + Expect.equal (localkey.ToBytes()) (expected.ToBytes()) "" + + testCase + "derivation of secret key from basepoint secret and per-commitment-secret" + <| fun _ -> + use ctx = newSecp256k1() + let localPrivkey = perCommitmentPoint.DerivePrivKey baseSecret + + let expected = + "cbced912d3b21bf196a766651e436aff192362621ce317704ea2f75d87e7be0f" + |> hex.DecodeData + + Expect.equal (localPrivkey.ToBytes()) (expected) "" + + testCase + "derivation of revocation key from basepoint and per_commitment_point" + <| fun _ -> + use ctx = newSecp256k1() + + let revocationKey = + perCommitmentPoint.DeriveRevocationPubKey + revocationBasepoint + + let expected = + "02916e326636d19c33f13e8c0c3a03dd157f332f3e99c317c141dd865eb01f8ff0" + |> hex.DecodeData + + Expect.equal (revocationKey.ToBytes()) expected "" + + testCase + "derivation of revocation secret from basepoint-secret and per-commitment-secret" + <| fun _ -> + use ctx = newSecp256k1() + + let actual = + perCommitmentSecret.DeriveRevocationPrivKey + revocationBasepointSecret + + let expected = + "d09ffff62ddb2297ab000cc85bcb4283fdeb6aa052affbc9dddcf33b61078110" + |> hex.DecodeData + + Expect.equal (actual.ToBytes()) expected "" + ] diff --git a/tests/DotNetLightning.Core.Tests/GraphTests.fs b/tests/DotNetLightning.Core.Tests/GraphTests.fs index be862cc61..ee71b9b02 100644 --- a/tests/DotNetLightning.Core.Tests/GraphTests.fs +++ b/tests/DotNetLightning.Core.Tests/GraphTests.fs @@ -13,78 +13,129 @@ let hex = Encoders.Hex module Constants = let ascii = System.Text.ASCIIEncoding.ASCII + let signMessageWith (privKey: Key) (msgHash: string) = let msgBytes = msgHash |> ascii.GetBytes - privKey.SignCompact(msgBytes |> uint256, false) |> fun d -> LNECDSASignature.FromBytesCompact(d, true) + + privKey.SignCompact(msgBytes |> uint256, false) + |> fun d -> LNECDSASignature.FromBytesCompact(d, true) + let DEFAULT_AMOUNT_MSAT = LNMoney.MilliSatoshis(10000000L) - let DEFAULT_ROUTE_PARAMS = { RouteParams.Randomize = false - MaxFeeBase = LNMoney.MilliSatoshis(21000L) - MaxFeePCT = 0.03 - RouteMaxCLTV = 2016us |> BlockHeightOffset16 - RouteMaxLength = 6 - Ratios = None } - let privKey1 = new Key(hex.DecodeData("0101010101010101010101010101010101010101010101010101010101010101")) - + + let DEFAULT_ROUTE_PARAMS = + { + RouteParams.Randomize = false + MaxFeeBase = LNMoney.MilliSatoshis(21000L) + MaxFeePCT = 0.03 + RouteMaxCLTV = 2016us |> BlockHeightOffset16 + RouteMaxLength = 6 + Ratios = None + } + + let privKey1 = + new Key( + hex.DecodeData( + "0101010101010101010101010101010101010101010101010101010101010101" + ) + ) + let DUMMY_SIG = signMessageWith privKey1 "01010101010101010101010101010101" - + /// Taken from eclair-core let pks = [ - "02999fa724ec3c244e4da52b4a91ad421dc96c9a810587849cd4b2469313519c73"; //a - "03f1cb1af20fe9ccda3ea128e27d7c39ee27375c8480f11a87c17197e97541ca6a"; //b - "0358e32d245ff5f5a3eb14c78c6f69c67cea7846bdf9aeeb7199e8f6fbb0306484"; //c - "029e059b6780f155f38e83601969919aae631ddf6faed58fe860c72225eb327d7c"; //d - "02f38f4e37142cc05df44683a83e22dea608cf4691492829ff4cf99888c5ec2d3a"; //e - "03fc5b91ce2d857f146fd9b986363374ffe04dc143d8bcd6d7664c8873c463cdfc"; //f - "03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f"; //g + "02999fa724ec3c244e4da52b4a91ad421dc96c9a810587849cd4b2469313519c73" //a + "03f1cb1af20fe9ccda3ea128e27d7c39ee27375c8480f11a87c17197e97541ca6a" //b + "0358e32d245ff5f5a3eb14c78c6f69c67cea7846bdf9aeeb7199e8f6fbb0306484" //c + "029e059b6780f155f38e83601969919aae631ddf6faed58fe860c72225eb327d7c" //d + "02f38f4e37142cc05df44683a83e22dea608cf4691492829ff4cf99888c5ec2d3a" //e + "03fc5b91ce2d857f146fd9b986363374ffe04dc143d8bcd6d7664c8873c463cdfc" //f + "03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f" //g ] - |> List.map (hex.DecodeData >> PubKey >> NodeId) -let a, b, c, d, e, f, g = pks.[0], pks.[1], pks.[2], pks.[3], pks.[4], pks.[5], pks.[6] - - -let makeUpdateCore(shortChannelId: ShortChannelId, - nodeid1: NodeId, - nodeid2: NodeId, - feeBase: LNMoney, - feeProportionalMillions: uint32, - minHtlc: LNMoney option, - maxHtlc: LNMoney option, - cltvDelta: BlockHeightOffset16 option - ): (ChannelDesc * UnsignedChannelUpdateMsg) = + |> List.map(hex.DecodeData >> PubKey >> NodeId) + +let a, b, c, d, e, f, g = + pks.[0], pks.[1], pks.[2], pks.[3], pks.[4], pks.[5], pks.[6] + + +let makeUpdateCore + ( + shortChannelId: ShortChannelId, + nodeid1: NodeId, + nodeid2: NodeId, + feeBase: LNMoney, + feeProportionalMillions: uint32, + minHtlc: option, + maxHtlc: option, + cltvDelta: option + ) : (ChannelDesc * UnsignedChannelUpdateMsg) = let minHtlc = Option.defaultValue Constants.DEFAULT_AMOUNT_MSAT minHtlc let cltvDelta = Option.defaultValue (BlockHeightOffset16(0us)) cltvDelta - let desc = { ChannelDesc.ShortChannelId = shortChannelId - A = nodeid1 - B = nodeid2 } + + let desc = + { + ChannelDesc.ShortChannelId = shortChannelId + A = nodeid1 + B = nodeid2 + } + let update = - { UnsignedChannelUpdateMsg.MessageFlags = - match maxHtlc with Some _ -> 1uy | _ -> 0uy - ChannelFlags = 0uy - ChainHash = Network.RegTest.GenesisHash - ShortChannelId = shortChannelId - Timestamp = 0u - CLTVExpiryDelta = cltvDelta - HTLCMinimumMSat = minHtlc - FeeBaseMSat = feeBase - FeeProportionalMillionths = feeProportionalMillions - HTLCMaximumMSat = maxHtlc } + { + UnsignedChannelUpdateMsg.MessageFlags = + match maxHtlc with + | Some _ -> 1uy + | _ -> 0uy + ChannelFlags = 0uy + ChainHash = Network.RegTest.GenesisHash + ShortChannelId = shortChannelId + Timestamp = 0u + CLTVExpiryDelta = cltvDelta + HTLCMinimumMSat = minHtlc + FeeBaseMSat = feeBase + FeeProportionalMillionths = feeProportionalMillions + HTLCMaximumMSat = maxHtlc + } + desc, update - -let makeUpdate (shortChannelId: uint64, - nodeid1: NodeId, - nodeid2: NodeId, - feeBase: LNMoney, - feeProportionalMillions: uint32, - minHtlc: LNMoney option, - maxHtlc: LNMoney option, - cltvDelta: BlockHeightOffset16 option ): (ChannelDesc * UnsignedChannelUpdateMsg) = - makeUpdateCore(shortChannelId |> ShortChannelId.FromUInt64, nodeid1, nodeid2, feeBase, feeProportionalMillions, minHtlc, maxHtlc, cltvDelta) -let makeUpdate2 (s, a, b, feeBase, feeProp, minHTLC, maxHTLC, cltvDelta) = - makeUpdateCore(ShortChannelId.ParseUnsafe(s), a, b, feeBase, feeProp, minHTLC, maxHTLC, cltvDelta) - -let makeUpdateSimple (shortChannelId, a, b) = + +let makeUpdate + ( + shortChannelId: uint64, + nodeid1: NodeId, + nodeid2: NodeId, + feeBase: LNMoney, + feeProportionalMillions: uint32, + minHtlc: option, + maxHtlc: option, + cltvDelta: option + ) : (ChannelDesc * UnsignedChannelUpdateMsg) = + makeUpdateCore( + shortChannelId |> ShortChannelId.FromUInt64, + nodeid1, + nodeid2, + feeBase, + feeProportionalMillions, + minHtlc, + maxHtlc, + cltvDelta + ) + +let makeUpdate2(s, a, b, feeBase, feeProp, minHTLC, maxHTLC, cltvDelta) = + makeUpdateCore( + ShortChannelId.ParseUnsafe(s), + a, + b, + feeBase, + feeProp, + minHTLC, + maxHTLC, + cltvDelta + ) + +let makeUpdateSimple(shortChannelId, a, b) = makeUpdate(shortChannelId, a, b, LNMoney.Zero, 0u, None, None, None) -let makeTestGraph(): DirectedLNGraph = + +let makeTestGraph() : DirectedLNGraph = let updates = [ makeUpdateSimple(1UL, a, b) @@ -94,162 +145,333 @@ let makeTestGraph(): DirectedLNGraph = makeUpdateSimple(5UL, c, e) makeUpdateSimple(6UL, b, e) ] + DirectedLNGraph.Create().AddEdges(updates) - + let descFromNodes(id, a: NodeId, b: NodeId) = - { ShortChannelId = ShortChannelId.FromUInt64(uint64 id); A = a; B = b } + { + ShortChannelId = ShortChannelId.FromUInt64(uint64 id) + A = a + B = b + } + [] let graphTests = - testList "GraphTests from eclair" [ - testCase "Instantiate a graph, with vertices and then add edges" <| fun _ -> - let g = - DirectedLNGraph.Create() - .AddVertex(a) - .AddVertex(b) - .AddVertex(c) - .AddVertex(d) - .AddVertex(e) - Expect.isTrue(g.ContainsVertex(a) && g.ContainsVertex(e)) "" - Expect.equal (g.VertexSet().Length) 5 "" - let otherGraph = g.AddVertex(a) - Expect.equal (otherGraph.VertexSet().Length) 5 "" - let descAB, updateAB = makeUpdate(1UL, a, b, LNMoney.Zero, 0u, None, None, None) - let descBC, updateBC = makeUpdate(2UL, b, c, LNMoney.Zero, 0u, None, None, None) - let descAD, updateAD = makeUpdate(3UL, a, d, LNMoney.Zero, 0u, None, None, None) - let descDC, updateDC = makeUpdate(4UL, d, c, LNMoney.Zero, 0u, None, None, None) - let descCE, updateCE = makeUpdate(5UL, c, e, LNMoney.Zero, 0u, None, None, None) - let graphWithEdges = - g - .AddEdge({ Update = updateAB; Desc = descAB }) - .AddEdge({ Update = updateBC; Desc = descBC }) - .AddEdge({ Update = updateAD; Desc = descAD }) - .AddEdge({ Update = updateDC; Desc = descDC }) - .AddEdge({ Update = updateCE; Desc = descCE }) - Expect.equal (graphWithEdges.OutgoingEdgesOf(a).Length) 2 "" - Expect.equal (graphWithEdges.OutgoingEdgesOf(b).Length) 1 "" - Expect.equal (graphWithEdges.OutgoingEdgesOf(c).Length) 1 "" - Expect.equal (graphWithEdges.OutgoingEdgesOf(d).Length) 1 "" - Expect.equal (graphWithEdges.OutgoingEdgesOf(e).Length) 0 "" - Expect.isEmpty (graphWithEdges.OutgoingEdgesOf(f)) "" - - let withRemovedEdges = graphWithEdges.RemoveEdge(descAD) - Expect.equal (withRemovedEdges.OutgoingEdgesOf(d).Length) 1 "" - - testCase "instantiate a graph adding edges only" <| fun _ -> - let labelAB = - makeUpdateSimple(1UL, a, b) - |> fun (a, b) -> { Desc = a; Update = b } - let labelBC = makeUpdateSimple(2UL, b, c) |> fun (a, b) -> { Desc = a; Update = b } - let labelAD = makeUpdateSimple(3UL, a, d) |> fun (a, b) -> { Desc = a; Update = b } - let labelDC = makeUpdateSimple(4UL, d, c) |> fun (a, b) -> { Desc = a; Update = b } - let labelCE = makeUpdateSimple(5UL, c, e) |> fun (a, b) -> { Desc = a; Update = b } - let labelBE = makeUpdateSimple(6UL, b, e) |> fun (a, b) -> { Desc = a; Update = b } - let g = - DirectedLNGraph.Create() - .AddEdge(labelAB) - .AddEdge(labelBC) - .AddEdge(labelAD) - .AddEdge(labelDC) - .AddEdge(labelCE) - .AddEdge(labelBE) - Expect.equal (g.VertexSet().Length) 5 "" - Expect.equal (g.OutgoingEdgesOf(c).Length) 1 "" - Expect.equal (g.IncomingEdgesOf(c).Length) 2 "" - Expect.equal (g.EdgeSet().Length) 6 "" - - testCase "containsEdge should return true if the graph contains that edge, false otherwise" <| fun _ -> - let updates = - seq { + testList + "GraphTests from eclair" + [ + testCase "Instantiate a graph, with vertices and then add edges" + <| fun _ -> + let g = + DirectedLNGraph + .Create() + .AddVertex(a) + .AddVertex(b) + .AddVertex(c) + .AddVertex(d) + .AddVertex(e) + + Expect.isTrue (g.ContainsVertex(a) && g.ContainsVertex(e)) "" + Expect.equal (g.VertexSet().Length) 5 "" + let otherGraph = g.AddVertex(a) + Expect.equal (otherGraph.VertexSet().Length) 5 "" + + let descAB, updateAB = + makeUpdate(1UL, a, b, LNMoney.Zero, 0u, None, None, None) + + let descBC, updateBC = + makeUpdate(2UL, b, c, LNMoney.Zero, 0u, None, None, None) + + let descAD, updateAD = + makeUpdate(3UL, a, d, LNMoney.Zero, 0u, None, None, None) + + let descDC, updateDC = + makeUpdate(4UL, d, c, LNMoney.Zero, 0u, None, None, None) + + let descCE, updateCE = + makeUpdate(5UL, c, e, LNMoney.Zero, 0u, None, None, None) + + let graphWithEdges = + g + .AddEdge( + { + Update = updateAB + Desc = descAB + } + ) + .AddEdge( + { + Update = updateBC + Desc = descBC + } + ) + .AddEdge( + { + Update = updateAD + Desc = descAD + } + ) + .AddEdge( + { + Update = updateDC + Desc = descDC + } + ) + .AddEdge( + { + Update = updateCE + Desc = descCE + } + ) + + Expect.equal (graphWithEdges.OutgoingEdgesOf(a).Length) 2 "" + Expect.equal (graphWithEdges.OutgoingEdgesOf(b).Length) 1 "" + Expect.equal (graphWithEdges.OutgoingEdgesOf(c).Length) 1 "" + Expect.equal (graphWithEdges.OutgoingEdgesOf(d).Length) 1 "" + Expect.equal (graphWithEdges.OutgoingEdgesOf(e).Length) 0 "" + Expect.isEmpty (graphWithEdges.OutgoingEdgesOf(f)) "" + + let withRemovedEdges = graphWithEdges.RemoveEdge(descAD) + Expect.equal (withRemovedEdges.OutgoingEdgesOf(d).Length) 1 "" + + testCase "instantiate a graph adding edges only" + <| fun _ -> + let labelAB = makeUpdateSimple(1UL, a, b) + |> fun (a, b) -> + { + Desc = a + Update = b + } + + let labelBC = makeUpdateSimple(2UL, b, c) - makeUpdateSimple(3UL, c, d) - makeUpdateSimple(4UL, d, e) - } - - let graph = DirectedLNGraph.Create().AddEdges(updates) - - Expect.isTrue (graph.ContainsEdge(descFromNodes(1, a, b))) "" - Expect.isTrue (graph.ContainsEdge(descFromNodes(2, b, c))) "" - Expect.isTrue (graph.ContainsEdge(descFromNodes(3, c, d))) "" - Expect.isTrue (graph.ContainsEdge(descFromNodes(4, d, e))) "" - // same with channel desc - Expect.isTrue (graph.ContainsEdge({ ChannelDesc.ShortChannelId = ShortChannelId.FromUInt64(4UL); A = d; B = e })) "" - Expect.isFalse (graph.ContainsEdge({ ChannelDesc.ShortChannelId = ShortChannelId.FromUInt64(4UL); A = a; B = g })) "" - Expect.isFalse (graph.ContainsEdge(descFromNodes(50, a, e))) "" - Expect.isFalse (graph.ContainsEdge(descFromNodes(66, c, f))) "" - - testCase "Should remove a set of edges" <| fun _ -> - let graph: DirectedLNGraph = makeTestGraph() - let (descBE, _) = makeUpdateSimple(6UL, b, e) - let (descCE, _) = makeUpdateSimple(5UL, c, e) - let (descAD, _) = makeUpdateSimple(3UL, a, d) - let (descDC, _) = makeUpdateSimple(4UL, d, c) - Expect.equal (graph.EdgeSet().Length) 6 "" - Expect.isTrue (graph.ContainsEdge(descBE)) "" - - let withRemovedEdge = graph.RemoveEdge(descBE) - Expect.equal (withRemovedEdge.EdgeSet().Length) 5 "" - - let withRemovedList = graph.RemoveEdges(seq { descAD; descDC }) - Expect.equal (withRemovedList.EdgeSet().Length) 4 "" - - let withoutAnyIncomingEdgeInE = graph.RemoveEdges(seq { descBE; descCE }) - Expect.isTrue (withoutAnyIncomingEdgeInE.ContainsVertex(e)) "" - Expect.isEmpty (withoutAnyIncomingEdgeInE.OutgoingEdgesOf(e)) "" - - testCase "should get an edge given two vertices" <| fun _ -> - let updates = seq { makeUpdateSimple(1UL, a, b); makeUpdateSimple(2UL, b, c) } - let graph = DirectedLNGraph.Create().AddEdges(updates) - let edgesAB = graph.GetEdgesBetween(a, b) - Expect.equal (edgesAB.Length) 1 "" - Expect.equal (edgesAB.Head.Desc.A) a "" - Expect.equal (edgesAB.Head.Desc.B) b "" - - let bIncoming = graph.IncomingEdgesOf(b) - Expect.equal bIncoming.Length 1 "" - Expect.contains (bIncoming |> List.map (fun x -> x.Desc.A)) a "" - Expect.contains (bIncoming |> List.map (fun x -> x.Desc.B)) b "" - - let bOutgoing = graph.OutgoingEdgesOf b - Expect.equal bOutgoing.Length 1 "" - Expect.contains (bOutgoing |> List.map(fun x -> x.Desc.A)) b "" - Expect.contains (bOutgoing |> List.map(fun x -> x.Desc.B)) c "" - () - - testCase "there can be multiple edges between the same vertices" <| fun _ -> - let graph: DirectedLNGraph = makeTestGraph() - // A --> B, A --> D - Expect.equal (graph.OutgoingEdgesOf(a).Length) 2 "" - - // add a new edge a --> b but with different channel update and a different ShortChannelId - let newEdgeForNewChannel = - makeUpdate(15UL, a, b, LNMoney.MilliSatoshis(20L), 0u, None, None, None) - |> GraphLabel.Create - let mutatedGraph = graph.AddEdge(newEdgeForNewChannel) - - Expect.equal (mutatedGraph.OutgoingEdgesOf(a).Length) 3 "" - - // if the ShortChannelId is the same we replace the edge and the update - // this edge have an update with a different 'feeBaseMSat' - let edgeForTheSameChannel = - makeUpdate(15UL, a, b, LNMoney.MilliSatoshis(30L), 0u, None, None, None) - |> GraphLabel.Create - let mutatedGraph2 = mutatedGraph.AddEdge(edgeForTheSameChannel) - Expect.equal (mutatedGraph2.OutgoingEdgesOf a).Length 3 "" - Expect.equal (mutatedGraph2.GetEdgesBetween(a, b).Length) 2 "" - Expect.equal - (mutatedGraph2.TryGetEdge(edgeForTheSameChannel.Desc).Value.Update.FeeBaseMSat) - (LNMoney.MilliSatoshis(30L)) "" - - testCase "remove a vertex with incoming edges and check those edges are removed too" <| fun _ -> - let graph = makeTestGraph() - Expect.equal (graph.VertexSet().Length) 5 "" - Expect.isTrue (graph.ContainsVertex(e)) "" - Expect.isTrue (graph.ContainsEdge(descFromNodes(5, c, e))) "" - Expect.isTrue (graph.ContainsEdge(descFromNodes(6, b, e))) "" - - let withoutE = graph.RemoveVertex(e) - Expect.equal (withoutE.VertexSet().Length) 4 "" - Expect.isFalse (withoutE.ContainsEdge(descFromNodes(5, c, e))) "" - Expect.isFalse (withoutE.ContainsEdge(descFromNodes(6, b, e))) "" - ] + |> fun (a, b) -> + { + Desc = a + Update = b + } + + let labelAD = + makeUpdateSimple(3UL, a, d) + |> fun (a, b) -> + { + Desc = a + Update = b + } + + let labelDC = + makeUpdateSimple(4UL, d, c) + |> fun (a, b) -> + { + Desc = a + Update = b + } + + let labelCE = + makeUpdateSimple(5UL, c, e) + |> fun (a, b) -> + { + Desc = a + Update = b + } + + let labelBE = + makeUpdateSimple(6UL, b, e) + |> fun (a, b) -> + { + Desc = a + Update = b + } + + let g = + DirectedLNGraph + .Create() + .AddEdge(labelAB) + .AddEdge(labelBC) + .AddEdge(labelAD) + .AddEdge(labelDC) + .AddEdge(labelCE) + .AddEdge(labelBE) + + Expect.equal (g.VertexSet().Length) 5 "" + Expect.equal (g.OutgoingEdgesOf(c).Length) 1 "" + Expect.equal (g.IncomingEdgesOf(c).Length) 2 "" + Expect.equal (g.EdgeSet().Length) 6 "" + + testCase + "containsEdge should return true if the graph contains that edge, false otherwise" + <| fun _ -> + let updates = + seq { + makeUpdateSimple(1UL, a, b) + makeUpdateSimple(2UL, b, c) + makeUpdateSimple(3UL, c, d) + makeUpdateSimple(4UL, d, e) + } + + let graph = DirectedLNGraph.Create().AddEdges(updates) + + Expect.isTrue (graph.ContainsEdge(descFromNodes(1, a, b))) "" + Expect.isTrue (graph.ContainsEdge(descFromNodes(2, b, c))) "" + Expect.isTrue (graph.ContainsEdge(descFromNodes(3, c, d))) "" + Expect.isTrue (graph.ContainsEdge(descFromNodes(4, d, e))) "" + // same with channel desc + Expect.isTrue + (graph.ContainsEdge( + { + ChannelDesc.ShortChannelId = + ShortChannelId.FromUInt64(4UL) + A = d + B = e + } + )) + "" + + Expect.isFalse + (graph.ContainsEdge( + { + ChannelDesc.ShortChannelId = + ShortChannelId.FromUInt64(4UL) + A = a + B = g + } + )) + "" + + Expect.isFalse (graph.ContainsEdge(descFromNodes(50, a, e))) "" + Expect.isFalse (graph.ContainsEdge(descFromNodes(66, c, f))) "" + + testCase "Should remove a set of edges" + <| fun _ -> + let graph: DirectedLNGraph = makeTestGraph() + let (descBE, _) = makeUpdateSimple(6UL, b, e) + let (descCE, _) = makeUpdateSimple(5UL, c, e) + let (descAD, _) = makeUpdateSimple(3UL, a, d) + let (descDC, _) = makeUpdateSimple(4UL, d, c) + Expect.equal (graph.EdgeSet().Length) 6 "" + Expect.isTrue (graph.ContainsEdge(descBE)) "" + + let withRemovedEdge = graph.RemoveEdge(descBE) + Expect.equal (withRemovedEdge.EdgeSet().Length) 5 "" + + let withRemovedList = + graph.RemoveEdges( + seq { + descAD + descDC + } + ) + + Expect.equal (withRemovedList.EdgeSet().Length) 4 "" + + let withoutAnyIncomingEdgeInE = + graph.RemoveEdges( + seq { + descBE + descCE + } + ) + + Expect.isTrue (withoutAnyIncomingEdgeInE.ContainsVertex(e)) "" + Expect.isEmpty (withoutAnyIncomingEdgeInE.OutgoingEdgesOf(e)) "" + + testCase "should get an edge given two vertices" + <| fun _ -> + let updates = + seq { + makeUpdateSimple(1UL, a, b) + makeUpdateSimple(2UL, b, c) + } + + let graph = DirectedLNGraph.Create().AddEdges(updates) + let edgesAB = graph.GetEdgesBetween(a, b) + Expect.equal (edgesAB.Length) 1 "" + Expect.equal (edgesAB.Head.Desc.A) a "" + Expect.equal (edgesAB.Head.Desc.B) b "" + + let bIncoming = graph.IncomingEdgesOf(b) + Expect.equal bIncoming.Length 1 "" + Expect.contains (bIncoming |> List.map(fun x -> x.Desc.A)) a "" + Expect.contains (bIncoming |> List.map(fun x -> x.Desc.B)) b "" + + let bOutgoing = graph.OutgoingEdgesOf b + Expect.equal bOutgoing.Length 1 "" + Expect.contains (bOutgoing |> List.map(fun x -> x.Desc.A)) b "" + Expect.contains (bOutgoing |> List.map(fun x -> x.Desc.B)) c "" + () + + testCase "there can be multiple edges between the same vertices" + <| fun _ -> + let graph: DirectedLNGraph = makeTestGraph() + // A --> B, A --> D + Expect.equal (graph.OutgoingEdgesOf(a).Length) 2 "" + + // add a new edge a --> b but with different channel update and a different ShortChannelId + let newEdgeForNewChannel = + makeUpdate( + 15UL, + a, + b, + LNMoney.MilliSatoshis(20L), + 0u, + None, + None, + None + ) + |> GraphLabel.Create + + let mutatedGraph = graph.AddEdge(newEdgeForNewChannel) + + Expect.equal (mutatedGraph.OutgoingEdgesOf(a).Length) 3 "" + + // if the ShortChannelId is the same we replace the edge and the update + // this edge have an update with a different 'feeBaseMSat' + let edgeForTheSameChannel = + makeUpdate( + 15UL, + a, + b, + LNMoney.MilliSatoshis(30L), + 0u, + None, + None, + None + ) + |> GraphLabel.Create + + let mutatedGraph2 = mutatedGraph.AddEdge(edgeForTheSameChannel) + Expect.equal (mutatedGraph2.OutgoingEdgesOf a).Length 3 "" + Expect.equal (mutatedGraph2.GetEdgesBetween(a, b).Length) 2 "" + + Expect.equal + (mutatedGraph2 + .TryGetEdge( + edgeForTheSameChannel.Desc + ) + .Value + .Update + .FeeBaseMSat) + (LNMoney.MilliSatoshis(30L)) + "" + + testCase + "remove a vertex with incoming edges and check those edges are removed too" + <| fun _ -> + let graph = makeTestGraph() + Expect.equal (graph.VertexSet().Length) 5 "" + Expect.isTrue (graph.ContainsVertex(e)) "" + Expect.isTrue (graph.ContainsEdge(descFromNodes(5, c, e))) "" + Expect.isTrue (graph.ContainsEdge(descFromNodes(6, b, e))) "" + + let withoutE = graph.RemoveVertex(e) + Expect.equal (withoutE.VertexSet().Length) 4 "" + + Expect.isFalse + (withoutE.ContainsEdge(descFromNodes(5, c, e))) + "" + + Expect.isFalse + (withoutE.ContainsEdge(descFromNodes(6, b, e))) + "" + ] diff --git a/tests/DotNetLightning.Core.Tests/KeyRepositoryTests.fs b/tests/DotNetLightning.Core.Tests/KeyRepositoryTests.fs index 51498be9e..3ad49a88d 100644 --- a/tests/DotNetLightning.Core.Tests/KeyRepositoryTests.fs +++ b/tests/DotNetLightning.Core.Tests/KeyRepositoryTests.fs @@ -15,142 +15,198 @@ let n = Network.RegTest /// same with bolt 3 let paymentPreImages = - let _s = ([ + let _s = + ([ ("0000000000000000000000000000000000000000000000000000000000000000") ("0101010101010101010101010101010101010101010101010101010101010101") ("0202020202020202020202020202020202020202020202020202020202020202") ("0303030303030303030303030303030303030303030303030303030303030303") ("0404040404040404040404040404040404040404040404040404040404040404") ]) + _s |> List.map(hex.DecodeData) |> List.map(PaymentPreimage.Create) - + /// same with bolt 3 -let incomingHtlcs = [ - { - UpdateAddHTLCMsg.ChannelId = ChannelId.Zero; - HTLCId = HTLCId.Zero; - Amount = LNMoney.MilliSatoshis 1000000L - PaymentHash = paymentPreImages.[0].Hash - CLTVExpiry = 500u |> BlockHeight; - OnionRoutingPacket = OnionPacket.LastPacket - } - { - UpdateAddHTLCMsg.ChannelId = ChannelId.Zero; - HTLCId = HTLCId(1UL); - Amount = LNMoney.MilliSatoshis 2000000L - PaymentHash = paymentPreImages.[1].Hash - CLTVExpiry = 501u |> BlockHeight; - OnionRoutingPacket = OnionPacket.LastPacket - } - { - UpdateAddHTLCMsg.ChannelId = ChannelId.Zero; - HTLCId = HTLCId(4UL); - Amount = LNMoney.MilliSatoshis 4000000L - PaymentHash = paymentPreImages.[4].Hash - CLTVExpiry = 504u |> BlockHeight; - OnionRoutingPacket = OnionPacket.LastPacket - } -] -let outgoingHtlcs = [ - { - UpdateAddHTLCMsg.ChannelId = ChannelId.Zero; - HTLCId = HTLCId(2UL); - Amount = LNMoney.MilliSatoshis 2000000L - PaymentHash = paymentPreImages.[2].Hash - CLTVExpiry = 502u |> BlockHeight; - OnionRoutingPacket = OnionPacket.LastPacket - } - { - UpdateAddHTLCMsg.ChannelId = ChannelId.Zero; - HTLCId = HTLCId(3UL); - Amount = LNMoney.MilliSatoshis 3000000L - PaymentHash = paymentPreImages.[3].Hash - CLTVExpiry = 503u |> BlockHeight; - OnionRoutingPacket = OnionPacket.LastPacket - } -] +let incomingHtlcs = + [ + { + UpdateAddHTLCMsg.ChannelId = ChannelId.Zero + HTLCId = HTLCId.Zero + Amount = LNMoney.MilliSatoshis 1000000L + PaymentHash = paymentPreImages.[0].Hash + CLTVExpiry = 500u |> BlockHeight + OnionRoutingPacket = OnionPacket.LastPacket + } + { + UpdateAddHTLCMsg.ChannelId = ChannelId.Zero + HTLCId = HTLCId(1UL) + Amount = LNMoney.MilliSatoshis 2000000L + PaymentHash = paymentPreImages.[1].Hash + CLTVExpiry = 501u |> BlockHeight + OnionRoutingPacket = OnionPacket.LastPacket + } + { + UpdateAddHTLCMsg.ChannelId = ChannelId.Zero + HTLCId = HTLCId(4UL) + Amount = LNMoney.MilliSatoshis 4000000L + PaymentHash = paymentPreImages.[4].Hash + CLTVExpiry = 504u |> BlockHeight + OnionRoutingPacket = OnionPacket.LastPacket + } + ] + +let outgoingHtlcs = + [ + { + UpdateAddHTLCMsg.ChannelId = ChannelId.Zero + HTLCId = HTLCId(2UL) + Amount = LNMoney.MilliSatoshis 2000000L + PaymentHash = paymentPreImages.[2].Hash + CLTVExpiry = 502u |> BlockHeight + OnionRoutingPacket = OnionPacket.LastPacket + } + { + UpdateAddHTLCMsg.ChannelId = ChannelId.Zero + HTLCId = HTLCId(3UL) + Amount = LNMoney.MilliSatoshis 3000000L + PaymentHash = paymentPreImages.[3].Hash + CLTVExpiry = 503u |> BlockHeight + OnionRoutingPacket = OnionPacket.LastPacket + } + ] let incomingHtlcMap = incomingHtlcs |> List.map(fun htlc -> htlc.HTLCId, htlc) |> Map.ofList + let outgoingHtlcMap = outgoingHtlcs |> List.map(fun htlc -> htlc.HTLCId, htlc) |> Map.ofList [] let tests = - testList "KeyRepository tests" [ - testCase "should create valid signature" <| fun _ -> - let fundingTxId = [| for _ in 0..31 -> 1uy |] |> uint256 |> TxId - let fundingAmount = Money.Satoshis 10000000L - - let localNodeMasterPrivKey = NodeMasterPrivKey <| ExtKey("00000000000000000000000000000000") - let localChannelIndex = 0 - let localPrivKeys = localNodeMasterPrivKey.ChannelPrivKeys localChannelIndex - let localPubKeys = localPrivKeys.ToChannelPubKeys() - - let remoteNodeMasterPrivKey = NodeMasterPrivKey <| ExtKey("88888888888888888888888888888888") - let remoteChannelIndex = 1 - let remotePrivKeys = remoteNodeMasterPrivKey.ChannelPrivKeys remoteChannelIndex - let remotePubKeys = remotePrivKeys.ToChannelPubKeys() - - let fundingScriptCoin = - ChannelHelpers.getFundingScriptCoin - localPubKeys.FundingPubKey - remotePubKeys.FundingPubKey - fundingTxId - (TxOutIndex 0us) - fundingAmount - - let localDustLimit = Money.Satoshis(546L) - let toLocalDelay = 200us |> BlockHeightOffset16 - let specBase = { - CommitmentSpec.IncomingHTLCs = incomingHtlcMap - CommitmentSpec.OutgoingHTLCs = outgoingHtlcMap - FeeRatePerKw = 15000u |> FeeRatePerKw - ToLocal = LNMoney.MilliSatoshis(6988000000L) - ToRemote = 3000000000L |> LNMoney.MilliSatoshis - } - let commitTx = - Transactions.makeCommitTx fundingScriptCoin - CommitmentNumber.FirstCommitment - localPubKeys.PaymentBasepoint - remotePubKeys.PaymentBasepoint - (true) - localDustLimit - (RevocationPubKey <| localPubKeys.RevocationBasepoint.RawPubKey()) // FIXME: basepoint being used as pubkey here? - toLocalDelay - (DelayedPaymentPubKey <| localPubKeys.DelayedPaymentBasepoint.RawPubKey()) // FIXME: basepoint being used as pubkey here? - (PaymentPubKey <| remotePubKeys.PaymentBasepoint.RawPubKey()) // FIXME: basepoint being used as pubkey here? - (HtlcPubKey <| localPubKeys.HtlcBasepoint.RawPubKey()) - (HtlcPubKey <| remotePubKeys.HtlcBasepoint.RawPubKey()) - specBase - n - let _remoteSigForLocalCommit, commitTx2 = remotePrivKeys.SignWithFundingPrivKey commitTx.Value - let _localSigForLocalCommit, commitTx3 = localPrivKeys.SignWithFundingPrivKey commitTx2 - commitTx3.Finalize() |> ignore - Expect.isTrue (commitTx3.CanExtractTransaction()) (sprintf "failed to finalize commitTx %A" commitTx3) - - let remoteDustLimit = Money.Satoshis(1000L) - let remoteDelay = 160us |> BlockHeightOffset16 - let remoteCommitTx = - Transactions.makeCommitTx fundingScriptCoin - CommitmentNumber.FirstCommitment - remotePubKeys.PaymentBasepoint - localPubKeys.PaymentBasepoint - false - remoteDustLimit - (RevocationPubKey <| remotePubKeys.RevocationBasepoint.RawPubKey()) // FIXME: basepoint being used as pubkey here? - remoteDelay - (DelayedPaymentPubKey <| remotePubKeys.DelayedPaymentBasepoint.RawPubKey()) // FIXME: basepoint being used as pubkey here? - (PaymentPubKey <| localPubKeys.PaymentBasepoint.RawPubKey()) // FIXME: basepoint being used as pubkey here? - (HtlcPubKey <| remotePubKeys.HtlcBasepoint.RawPubKey()) - (HtlcPubKey <| localPubKeys.HtlcBasepoint.RawPubKey()) - specBase - n - - let _remoteSigForRemoteCommit, remoteCommitTx2 = remotePrivKeys.SignWithFundingPrivKey remoteCommitTx.Value - let localSigForRemoteCommit, _commitTx3 = localPrivKeys.SignWithFundingPrivKey remoteCommitTx2 - - let localSigs = seq [(localPubKeys.FundingPubKey.RawPubKey(), TransactionSignature(localSigForRemoteCommit.Signature, SigHash.All))] - let _finalizedTx = Transactions.checkTxFinalized remoteCommitTx2 0 localSigs |> Result.deref - () - ] + testList + "KeyRepository tests" + [ + testCase "should create valid signature" + <| fun _ -> + let fundingTxId = [| for _ in 0..31 -> 1uy |] |> uint256 |> TxId + let fundingAmount = Money.Satoshis 10000000L + + let localNodeMasterPrivKey = + NodeMasterPrivKey + <| ExtKey("00000000000000000000000000000000") + + let localChannelIndex = 0 + + let localPrivKeys = + localNodeMasterPrivKey.ChannelPrivKeys localChannelIndex + + let localPubKeys = localPrivKeys.ToChannelPubKeys() + + let remoteNodeMasterPrivKey = + NodeMasterPrivKey + <| ExtKey("88888888888888888888888888888888") + + let remoteChannelIndex = 1 + + let remotePrivKeys = + remoteNodeMasterPrivKey.ChannelPrivKeys remoteChannelIndex + + let remotePubKeys = remotePrivKeys.ToChannelPubKeys() + + let fundingScriptCoin = + ChannelHelpers.getFundingScriptCoin + localPubKeys.FundingPubKey + remotePubKeys.FundingPubKey + fundingTxId + (TxOutIndex 0us) + fundingAmount + + let localDustLimit = Money.Satoshis(546L) + let toLocalDelay = 200us |> BlockHeightOffset16 + + let specBase = + { + CommitmentSpec.IncomingHTLCs = incomingHtlcMap + CommitmentSpec.OutgoingHTLCs = outgoingHtlcMap + FeeRatePerKw = 15000u |> FeeRatePerKw + ToLocal = LNMoney.MilliSatoshis(6988000000L) + ToRemote = 3000000000L |> LNMoney.MilliSatoshis + } + + let commitTx = + Transactions.makeCommitTx + fundingScriptCoin + CommitmentNumber.FirstCommitment + localPubKeys.PaymentBasepoint + remotePubKeys.PaymentBasepoint + (true) + localDustLimit + (RevocationPubKey + <| localPubKeys.RevocationBasepoint.RawPubKey()) // FIXME: basepoint being used as pubkey here? + toLocalDelay + (DelayedPaymentPubKey + <| localPubKeys.DelayedPaymentBasepoint.RawPubKey()) // FIXME: basepoint being used as pubkey here? + (PaymentPubKey + <| remotePubKeys.PaymentBasepoint.RawPubKey()) // FIXME: basepoint being used as pubkey here? + (HtlcPubKey <| localPubKeys.HtlcBasepoint.RawPubKey()) + (HtlcPubKey <| remotePubKeys.HtlcBasepoint.RawPubKey()) + specBase + n + + let _remoteSigForLocalCommit, commitTx2 = + remotePrivKeys.SignWithFundingPrivKey commitTx.Value + + let _localSigForLocalCommit, commitTx3 = + localPrivKeys.SignWithFundingPrivKey commitTx2 + + commitTx3.Finalize() |> ignore + + Expect.isTrue + (commitTx3.CanExtractTransaction()) + (sprintf "failed to finalize commitTx %A" commitTx3) + + let remoteDustLimit = Money.Satoshis(1000L) + let remoteDelay = 160us |> BlockHeightOffset16 + + let remoteCommitTx = + Transactions.makeCommitTx + fundingScriptCoin + CommitmentNumber.FirstCommitment + remotePubKeys.PaymentBasepoint + localPubKeys.PaymentBasepoint + false + remoteDustLimit + (RevocationPubKey + <| remotePubKeys.RevocationBasepoint.RawPubKey()) // FIXME: basepoint being used as pubkey here? + remoteDelay + (DelayedPaymentPubKey + <| remotePubKeys.DelayedPaymentBasepoint.RawPubKey()) // FIXME: basepoint being used as pubkey here? + (PaymentPubKey + <| localPubKeys.PaymentBasepoint.RawPubKey()) // FIXME: basepoint being used as pubkey here? + (HtlcPubKey <| remotePubKeys.HtlcBasepoint.RawPubKey()) + (HtlcPubKey <| localPubKeys.HtlcBasepoint.RawPubKey()) + specBase + n + + let _remoteSigForRemoteCommit, remoteCommitTx2 = + remotePrivKeys.SignWithFundingPrivKey remoteCommitTx.Value + + let localSigForRemoteCommit, _commitTx3 = + localPrivKeys.SignWithFundingPrivKey remoteCommitTx2 + + let localSigs = + seq + [ + (localPubKeys.FundingPubKey.RawPubKey(), + TransactionSignature( + localSigForRemoteCommit.Signature, + SigHash.All + )) + ] + + let _finalizedTx = + Transactions.checkTxFinalized remoteCommitTx2 0 localSigs + |> Result.deref + + () + ] diff --git a/tests/DotNetLightning.Core.Tests/LSATTests.fs b/tests/DotNetLightning.Core.Tests/LSATTests.fs index 3f79f3edb..26c4c3480 100644 --- a/tests/DotNetLightning.Core.Tests/LSATTests.fs +++ b/tests/DotNetLightning.Core.Tests/LSATTests.fs @@ -12,124 +12,279 @@ open ResultUtils.Portability [] let lsatTests = - testList "LSAT tests" [ - testCase "service decode tests" <| fun _ -> - let r = Service.ParseMany("a:0") - Expect.isOk (Result.ToFSharpCoreResult r) "can parse single service" - Expect.equal 1 (r |> Result.deref).Count "" - Expect.equal "a" (r |> Result.deref).[0].Name "" - let r = Service.ParseMany("a:0,b:1,c:0") - Expect.isOk (Result.ToFSharpCoreResult r) "can parse multiple service" - Expect.equal 3 (r |> Result.deref).Count "" - Expect.equal "c" (r |> Result.deref).[2].Name "" - Expect.equal 0uy (r |> Result.deref).[2].ServiceTier "" - let r = Service.ParseMany "" - Expect.isError (Result.ToFSharpCoreResult r) "can not parse empty service" - let r = Service.ParseMany ":a" - Expect.isError (Result.ToFSharpCoreResult r) "can not parse service missing name" - let r = Service.ParseMany "a" - Expect.isError (Result.ToFSharpCoreResult r) "can not parse service missing tier" - let r = Service.ParseMany "a:" - Expect.isError (Result.ToFSharpCoreResult r) "can not parse service with empty tier" - let r = Service.ParseMany ",," - Expect.isError (Result.ToFSharpCoreResult r) "can not parse empty services" - () - - testList "check macaroon verification works in LSAT compliant way" [ - testCase "successful verification" <| fun _ -> - let secret = "My secret key" - let identifier = "my macaroon identifier" - let m = Macaroon("http://my.awesome.service", secret, identifier) - let caveats = ResizeArray() - caveats.Add(Caveat("service=my-service-name:0")) - let satisfiers = ResizeArray() - satisfiers.Add(ServiceSatisfier("my-service-name") :> ISatisfier) - let v = m.VerifyLSATCaveats(caveats, satisfiers, secret) - Expect.isTrue(v.Success) (sprintf "%A" v.Messages) + testList + "LSAT tests" + [ + testCase "service decode tests" + <| fun _ -> + let r = Service.ParseMany("a:0") + + Expect.isOk + (Result.ToFSharpCoreResult r) + "can parse single service" + + Expect.equal 1 (r |> Result.deref).Count "" + Expect.equal "a" (r |> Result.deref).[0].Name "" + let r = Service.ParseMany("a:0,b:1,c:0") + + Expect.isOk + (Result.ToFSharpCoreResult r) + "can parse multiple service" + + Expect.equal 3 (r |> Result.deref).Count "" + Expect.equal "c" (r |> Result.deref).[2].Name "" + Expect.equal 0uy (r |> Result.deref).[2].ServiceTier "" + let r = Service.ParseMany "" + + Expect.isError + (Result.ToFSharpCoreResult r) + "can not parse empty service" + + let r = Service.ParseMany ":a" + + Expect.isError + (Result.ToFSharpCoreResult r) + "can not parse service missing name" + + let r = Service.ParseMany "a" + + Expect.isError + (Result.ToFSharpCoreResult r) + "can not parse service missing tier" + + let r = Service.ParseMany "a:" + + Expect.isError + (Result.ToFSharpCoreResult r) + "can not parse service with empty tier" + + let r = Service.ParseMany ",," + + Expect.isError + (Result.ToFSharpCoreResult r) + "can not parse empty services" + () - testCase "successful verification with unknown service name" <| fun _ -> - let secret = "My secret key" - let identifier = "my macaroon identifier" - let m = Macaroon("http://my.awesome.service", secret, identifier) - let caveats = ResizeArray() - caveats.Add(Caveat("service=my-service-name:0,another-service-name:0")) - caveats.Add(Caveat("service=my-service-name:0")) - let satisfiers = ResizeArray() - satisfiers.Add(ServiceSatisfier("my-service-name") :> ISatisfier) - let v = m.VerifyLSATCaveats(caveats, satisfiers, secret) - Expect.isTrue(v.Success) (sprintf "%A" v.Messages) - - testCase "successful verification with capabilities satisfier" <| fun _ -> - let secret = "My secret key" - let identifier = "my macaroon identifier" - let m = Macaroon("http://my.awesome.service", secret, identifier) - let caveats = ResizeArray() - caveats.Add(Caveat("service=my-service-name:0")) - caveats.Add(Caveat("my-service-name_capabilities=read")) - let satisfiers = ResizeArray() - satisfiers.Add(ServiceSatisfier("my-service-name") :> ISatisfier) - satisfiers.Add(CapabilitiesSatisfier("my-service-name", "read") :> ISatisfier) - let v = m.VerifyLSATCaveats(caveats, satisfiers, secret) - Expect.isTrue(v.Success) (sprintf "%A" v.Messages) - - testCase "verification succeeds when caveats includes required capabilities" <| fun _ -> - let secret = "My secret key" - let identifier = "my macaroon identifier" - let m = Macaroon("http://my.awesome.service", secret, identifier) - let caveats = ResizeArray() - caveats.Add(Caveat("my-service-name_capabilities=read,write")) - let satisfiers = ResizeArray() - satisfiers.Add(CapabilitiesSatisfier("my-service-name", "read") :> ISatisfier) - let v = m.VerifyLSATCaveats(caveats, satisfiers, secret) - Expect.isTrue(v.Success) (sprintf "%A" v.Messages) - - testCase "failure case: different secret" <| fun _ -> - let secret = "My secret key" - let identifier = "my macaroon identifier" - let m = Macaroon("http://my.awesome.service", secret, identifier) - let caveats = ResizeArray() - caveats.Add(Caveat("service=unknown-service-name:0")) - let satisfiers = ResizeArray() - satisfiers.Add(ServiceSatisfier("my-service-name") :> ISatisfier) - let v = m.VerifyLSATCaveats(caveats, satisfiers, "wrong secret key") - Expect.isFalse(v.Success) "" - - testCase "failure case: different service name" <| fun _ -> - let secret = "My secret key" - let identifier = "my macaroon identifier" - let m = Macaroon("http://my.awesome.service", secret, identifier) - let caveats = ResizeArray() - caveats.Add(Caveat("service=unknown-service-name:0")) - let satisfiers = ResizeArray() - satisfiers.Add(ServiceSatisfier("my-service-name") :> ISatisfier) - let v = m.VerifyLSATCaveats(caveats, satisfiers, secret) - Expect.isFalse(v.Success) "" - - testCase "failure case: verification fails if restriction gets loose then before" <| fun _ -> - let secret = "My secret key" - let identifier = "my macaroon identifier" - let m = Macaroon("http://my.awesome.service", secret, identifier) - let satisfiers = ResizeArray() - satisfiers.Add(ServiceSatisfier("my-service-name") :> ISatisfier) - - let caveats = ResizeArray() - // latter caveats has more power here. which is invalid for lsat. - caveats.Add(Caveat("service=my-service-name:0")) - caveats.Add(Caveat("service=my-service-name:0,another-service-name:0")) - - let v = m.VerifyLSATCaveats(caveats, satisfiers, secret) - Expect.isFalse(v.Success) "" - - satisfiers.Add(CapabilitiesSatisfier("my-service-name", "read") :> ISatisfier) - let caveats = ResizeArray() - // latter caveats has more power here. which is invalid for lsat. - caveats.Add(Caveat("my-service-name_capabilities = read")) - caveats.Add(Caveat("my-service-name_capabilities = read,write")) - let v = m.VerifyLSATCaveats(caveats, satisfiers, secret) - Expect.isFalse(v.Success) "" + testList + "check macaroon verification works in LSAT compliant way" + [ + testCase "successful verification" + <| fun _ -> + let secret = "My secret key" + let identifier = "my macaroon identifier" + + let m = + Macaroon( + "http://my.awesome.service", + secret, + identifier + ) + + let caveats = ResizeArray() + caveats.Add(Caveat("service=my-service-name:0")) + let satisfiers = ResizeArray() + + satisfiers.Add( + ServiceSatisfier("my-service-name") :> ISatisfier + ) + + let v = m.VerifyLSATCaveats(caveats, satisfiers, secret) + Expect.isTrue (v.Success) (sprintf "%A" v.Messages) + () + + testCase "successful verification with unknown service name" + <| fun _ -> + let secret = "My secret key" + let identifier = "my macaroon identifier" + + let m = + Macaroon( + "http://my.awesome.service", + secret, + identifier + ) + + let caveats = ResizeArray() + + caveats.Add( + Caveat( + "service=my-service-name:0,another-service-name:0" + ) + ) + + caveats.Add(Caveat("service=my-service-name:0")) + let satisfiers = ResizeArray() + + satisfiers.Add( + ServiceSatisfier("my-service-name") :> ISatisfier + ) + + let v = m.VerifyLSATCaveats(caveats, satisfiers, secret) + Expect.isTrue (v.Success) (sprintf "%A" v.Messages) + + testCase + "successful verification with capabilities satisfier" + <| fun _ -> + let secret = "My secret key" + let identifier = "my macaroon identifier" + + let m = + Macaroon( + "http://my.awesome.service", + secret, + identifier + ) + + let caveats = ResizeArray() + caveats.Add(Caveat("service=my-service-name:0")) + caveats.Add(Caveat("my-service-name_capabilities=read")) + let satisfiers = ResizeArray() + + satisfiers.Add( + ServiceSatisfier("my-service-name") :> ISatisfier + ) + + satisfiers.Add( + CapabilitiesSatisfier("my-service-name", "read") + :> ISatisfier + ) + + let v = m.VerifyLSATCaveats(caveats, satisfiers, secret) + Expect.isTrue (v.Success) (sprintf "%A" v.Messages) + + testCase + "verification succeeds when caveats includes required capabilities" + <| fun _ -> + let secret = "My secret key" + let identifier = "my macaroon identifier" + + let m = + Macaroon( + "http://my.awesome.service", + secret, + identifier + ) + + let caveats = ResizeArray() + + caveats.Add( + Caveat("my-service-name_capabilities=read,write") + ) + + let satisfiers = ResizeArray() + + satisfiers.Add( + CapabilitiesSatisfier("my-service-name", "read") + :> ISatisfier + ) + + let v = m.VerifyLSATCaveats(caveats, satisfiers, secret) + Expect.isTrue (v.Success) (sprintf "%A" v.Messages) + + testCase "failure case: different secret" + <| fun _ -> + let secret = "My secret key" + let identifier = "my macaroon identifier" + + let m = + Macaroon( + "http://my.awesome.service", + secret, + identifier + ) + + let caveats = ResizeArray() + caveats.Add(Caveat("service=unknown-service-name:0")) + let satisfiers = ResizeArray() + + satisfiers.Add( + ServiceSatisfier("my-service-name") :> ISatisfier + ) + + let v = + m.VerifyLSATCaveats( + caveats, + satisfiers, + "wrong secret key" + ) + + Expect.isFalse (v.Success) "" + + testCase "failure case: different service name" + <| fun _ -> + let secret = "My secret key" + let identifier = "my macaroon identifier" + + let m = + Macaroon( + "http://my.awesome.service", + secret, + identifier + ) + + let caveats = ResizeArray() + caveats.Add(Caveat("service=unknown-service-name:0")) + let satisfiers = ResizeArray() + + satisfiers.Add( + ServiceSatisfier("my-service-name") :> ISatisfier + ) + + let v = m.VerifyLSATCaveats(caveats, satisfiers, secret) + Expect.isFalse (v.Success) "" + + testCase + "failure case: verification fails if restriction gets loose then before" + <| fun _ -> + let secret = "My secret key" + let identifier = "my macaroon identifier" + + let m = + Macaroon( + "http://my.awesome.service", + secret, + identifier + ) + + let satisfiers = ResizeArray() + + satisfiers.Add( + ServiceSatisfier("my-service-name") :> ISatisfier + ) + + let caveats = ResizeArray() + // latter caveats has more power here. which is invalid for lsat. + caveats.Add(Caveat("service=my-service-name:0")) + + caveats.Add( + Caveat( + "service=my-service-name:0,another-service-name:0" + ) + ) + + let v = m.VerifyLSATCaveats(caveats, satisfiers, secret) + Expect.isFalse (v.Success) "" + + satisfiers.Add( + CapabilitiesSatisfier("my-service-name", "read") + :> ISatisfier + ) + + let caveats = ResizeArray() + // latter caveats has more power here. which is invalid for lsat. + caveats.Add( + Caveat("my-service-name_capabilities = read") + ) + + caveats.Add( + Caveat("my-service-name_capabilities = read,write") + ) + + let v = m.VerifyLSATCaveats(caveats, satisfiers, secret) + Expect.isFalse (v.Success) "" + ] ] - ] #endif diff --git a/tests/DotNetLightning.Core.Tests/Main.fs b/tests/DotNetLightning.Core.Tests/Main.fs index 9c17188ef..0454c0394 100644 --- a/tests/DotNetLightning.Core.Tests/Main.fs +++ b/tests/DotNetLightning.Core.Tests/Main.fs @@ -1,6 +1,7 @@ -module ExpectoTemplate -open Expecto - -[] -let main argv = - Tests.runTestsInAssembly defaultConfig argv +module ExpectoTemplate + +open Expecto + +[] +let main argv = + Tests.runTestsInAssembly defaultConfig argv diff --git a/tests/DotNetLightning.Core.Tests/PaymentPropertyTests.fs b/tests/DotNetLightning.Core.Tests/PaymentPropertyTests.fs index a51b335e5..48b1eb899 100644 --- a/tests/DotNetLightning.Core.Tests/PaymentPropertyTests.fs +++ b/tests/DotNetLightning.Core.Tests/PaymentPropertyTests.fs @@ -8,27 +8,38 @@ open PaymentGenerators open FsCheck open Generators open ResultUtils + type PaymentRequestGenerators = static member PaymentRequest() = - paymentRequestGen - |> Arb.fromGen + paymentRequestGen |> Arb.fromGen [] let tests = - let config = { - FsCheckConfig.defaultConfig with + let config = + { FsCheckConfig.defaultConfig with arbitrary = [ typeof ] maxTest = 100 - } - testList "PaymentRequest property tests" [ - testPropertyWithConfig config "PaymentRequest Serialization" <| fun (p: PaymentRequest) -> - let p2 = p.ToString() |> PaymentRequest.Parse |> Result.deref - Expect.equal p (p2) (sprintf "PaymentRequest before/after serialization is not equal %A" p) - ] + } + + testList + "PaymentRequest property tests" + [ + testPropertyWithConfig config "PaymentRequest Serialization" + <| fun (p: PaymentRequest) -> + let p2 = p.ToString() |> PaymentRequest.Parse |> Result.deref + + Expect.equal + p + (p2) + (sprintf + "PaymentRequest before/after serialization is not equal %A" + p) + ] #if BouncyCastle open DotNetLightning.Payment.LSAT + type PaymentGenerators = static member MacaroonIdentifier: Arbitrary = macaroonIdGen |> Arb.fromGen @@ -37,14 +48,22 @@ type PaymentGenerators = let lsatTests = let config = { FsCheckConfig.defaultConfig with - arbitrary = [ typeof; ] - maxTest = 300 - } - testList "Macaroon identifier" [ - testPropertyWithConfig config "macaroon identifier serialize/deserialize" <| fun (i: MacaroonIdentifier) -> - let i2 = MacaroonIdentifier.TryCreateFromBytes(i.ToBytes()) |> Result.deref - Expect.equal i i2 "failed to de/serialize macaroon id" - ] + arbitrary = [ typeof ] + maxTest = 300 + } -#endif + testList + "Macaroon identifier" + [ + testPropertyWithConfig + config + "macaroon identifier serialize/deserialize" + <| fun (i: MacaroonIdentifier) -> + let i2 = + MacaroonIdentifier.TryCreateFromBytes(i.ToBytes()) + |> Result.deref + Expect.equal i i2 "failed to de/serialize macaroon id" + ] + +#endif diff --git a/tests/DotNetLightning.Core.Tests/PaymentTests.fs b/tests/DotNetLightning.Core.Tests/PaymentTests.fs index 574e3de2a..a682af438 100644 --- a/tests/DotNetLightning.Core.Tests/PaymentTests.fs +++ b/tests/DotNetLightning.Core.Tests/PaymentTests.fs @@ -17,280 +17,763 @@ open ResultUtils.Portability let tests = let hex = NBitcoin.DataEncoders.HexEncoder() let ascii = System.Text.ASCIIEncoding.ASCII - let priv = "e126f68f7eafcc8b74f54d269fe206be715000f94dac067d1c04a8ca3b2db734" |> hex.DecodeData |> fun h -> new Key(h) - let msgSigner = { new IMessageSigner - with - member this.SignMessage(data) = let signature = priv.SignCompact(data, false) in signature } - - testList "BOLT-11 tests" [ - testCase "check minimal unit is used" <| fun _ -> - Expect.equal 'p' (Amount.unit(LNMoney.MilliSatoshis(1L))) "" - Expect.equal 'p' (Amount.unit(LNMoney.MilliSatoshis(99L))) "" - Expect.equal 'n' (Amount.unit(LNMoney.MilliSatoshis(100L))) "" - Expect.equal 'p' (Amount.unit(LNMoney.MilliSatoshis(101L))) "" - Expect.equal 'n' (Amount.unit(LNMoney.Satoshis(1L))) "" - Expect.equal 'u' (Amount.unit(LNMoney.Satoshis(100L))) "" - Expect.equal 'n' (Amount.unit(LNMoney.Satoshis(101L))) "" - Expect.equal 'u' (Amount.unit(LNMoney.Satoshis(1155400L))) "" - Expect.equal 'm' (Amount.unit(LNMoney.Coins(1m / 1000m))) "" - Expect.equal 'm' (Amount.unit(LNMoney.Coins(10m / 1000m))) "" - Expect.equal 'm' (Amount.unit(LNMoney.Coins(1m))) "" - - testCase "check that we can still decode non-minimal amount encoding" <| fun _ -> - Expect.equal (Amount.decode("1000u")) (Ok(LNMoney.MilliSatoshis(100000000L))) "" - Expect.equal (Amount.decode("1000000n")) (Ok(LNMoney.MilliSatoshis(100000000L))) "" - Expect.equal (Amount.decode("1000000000p")) (Ok(LNMoney.MilliSatoshis(100000000L))) "" - - testCase "Please make a donation of any amount using payment_hash 0001020304050607080900010203040506070809000102030405060708090102 to me @03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad" <| fun _ -> - let data = "lnbc1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpl2pkx2ctnv5sxxmmwwd5kgetjypeh2ursdae8g6twvus8g6rfwvs8qun0dfjkxaq8rkx3yf5tcsyz3d73gafnh3cax9rn449d9p5uxz9ezhhypd0elx87sjle52x86fux2ypatgddc6k63n7erqz25le42c4u4ecky03ylcqca784w" - let d = PaymentRequest.Parse(data) |> Result.deref - Expect.equal (d.PrefixValue) ("lnbc") "" - Expect.isTrue (d.AmountValue.IsNone) "" - Expect.equal (d.PaymentHash) (PaymentHash(uint256.Parse("0001020304050607080900010203040506070809000102030405060708090102"))) "" - Expect.equal (d.TimestampValue.ToUnixTimeSeconds()) (1496314658L) "" - Expect.equal (d.NodeIdValue) ("03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad" |> hex.DecodeData |> PubKey |> NodeId) "" - Expect.equal (d.Description) (Choice1Of2 "Please consider supporting this project") "" - Expect.equal (d.TagsValue.Fields.Length) (2) "" - Expect.equal (d.ToString()) data "" - Expect.equal (d.ToString(msgSigner)) data "" - - testCase "Please send $3 for a cup of coffee to the same peer, within one minute" <| fun _ -> - let data = "lnbc2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsxqzpuaztrnwngzn3kdzw5hydlzf03qdgm2hdq27cqv3agm2awhz5se903vruatfhq77w3ls4evs3ch9zw97j25emudupq63nyw24cg27h2rspfj9srp" - let pr = PaymentRequest.Parse(data) |> Result.deref - Expect.equal pr.PrefixValue "lnbc" "" - Expect.equal pr.AmountValue (Some(250000000L |> LNMoney.MilliSatoshis)) "" - Expect.equal pr.PaymentHash.Value ("0001020304050607080900010203040506070809000102030405060708090102" |> uint256.Parse) "" - Expect.equal pr.TimestampValue (DateTimeOffset.FromUnixTimeSeconds 1496314658L) "" - Expect.equal pr.NodeIdValue ("03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad" |> hex.DecodeData |> PubKey |> NodeId) "" - Expect.equal pr.Description (Choice1Of2 "1 cup coffee") "" - Expect.equal pr.FallbackAddresses ([]) "" - Expect.equal (pr.TagsValue.Fields.Length) 3 "" - Expect.equal (pr.ToString()) data "" - Expect.equal (pr.Sign(priv, false).ToString()) data "" - Expect.equal (pr.ToString(msgSigner)) data "" - - testCase "Please send 0.0025 BTC for a cup of nonsense (ナンセンス 1杯) to the same peer, within one minute" <| fun _ -> - let data = "lnbc2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpquwpc4curk03c9wlrswe78q4eyqc7d8d0xqzpuyk0sg5g70me25alkluzd2x62aysf2pyy8edtjeevuv4p2d5p76r4zkmneet7uvyakky2zr4cusd45tftc9c5fh0nnqpnl2jfll544esqchsrny" - let pr = PaymentRequest.Parse(data) |> Result.deref - Expect.equal (pr.Description) (Choice1Of2 "ナンセンス 1杯") "" - Expect.equal (pr.ToString()) data "" - Expect.equal (pr.ToString(msgSigner)) data "" - () - - testCase "Now send $24 for an entire list of things (hashed)" <| fun _ -> - let data = "lnbc20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqscc6gd6ql3jrc5yzme8v4ntcewwz5cnw92tz0pc8qcuufvq7khhr8wpald05e92xw006sq94mg8v2ndf4sefvf9sygkshp5zfem29trqq2yxxz7" - let pr = PaymentRequest.Parse(data) |> Result.deref - Expect.equal pr.PrefixValue "lnbc" "" - Expect.equal pr.AmountValue (2000000000L |> LNMoney.MilliSatoshis |> Some) "" - Expect.equal pr.PaymentHash ("0001020304050607080900010203040506070809000102030405060708090102" |> uint256.Parse |> PaymentHash) "" - Expect.equal pr.TimestampValue (DateTimeOffset.FromUnixTimeSeconds 1496314658L) "" - Expect.equal pr.NodeIdValue ("03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad" |> hex.DecodeData |> PubKey |> NodeId) "" - Expect.equal pr.Description ("One piece of chocolate cake, one icecream cone, one pickle, one slice of swiss cheese, one slice of salami, one lollypop, one piece of cherry pie, one sausage, one cupcake, and one slice of watermelon" |> ascii.GetBytes |> Hashes.SHA256 |> fun x -> uint256(x, false) |> Choice2Of2) "" - Expect.equal pr.FallbackAddresses [] "" - Expect.equal pr.TagsValue.Fields.Length 2 "" - Expect.equal (pr.ToString()) data "" - Expect.equal (pr.ToString(msgSigner)) data "" - - testCase "The same, on testnet, with a fallback address mk2QpYatsKicvFVuTAQLBryyccRXMUaGHP" <| fun _ -> - let data = "lntb20m1pvjluezhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqspp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqfpp3x9et2e20v6pu37c5d9vax37wxq72un98kmzzhznpurw9sgl2v0nklu2g4d0keph5t7tj9tcqd8rexnd07ux4uv2cjvcqwaxgj7v4uwn5wmypjd5n69z2xm3xgksg28nwht7f6zspwp3f9t" - let pr = PaymentRequest.Parse(data) |> Result.deref - Expect.equal pr.PrefixValue "lntb" "" - Expect.equal pr.AmountValue (2000000000L |> LNMoney.MilliSatoshis |> Some) "" - Expect.equal pr.PaymentHash ("0001020304050607080900010203040506070809000102030405060708090102" |> uint256.Parse |> PaymentHash) "" - Expect.equal (pr.TimestampValue.ToUnixTimeSeconds()) 1496314658L "" - Expect.equal (pr.NodeIdValue) ("03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad" |> hex.DecodeData |> PubKey |> NodeId) "" - Expect.equal (pr.FallbackAddresses) (["mk2QpYatsKicvFVuTAQLBryyccRXMUaGHP"]) "" - Expect.equal (pr.TagsValue.Fields.Length) 3 "" - Expect.equal (pr.ToString()) data "" - Expect.equal (pr.ToString(msgSigner)) data "" - - testCase "On mainnet, with fallback address 1RustyRX2oai4EYYDpQGWvEL62BBGqN9T with extra routing info to go via nodes 029e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255 then 039e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255" <| fun _ -> - let data = "lnbc20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqsfpp3qjmp7lwpagxun9pygexvgpjdc4jdj85fr9yq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqpqqqqq9qqqvpeuqafqxu92d8lr6fvg0r5gv0heeeqgcrqlnm6jhphu9y00rrhy4grqszsvpcgpy9qqqqqqgqqqqq7qqzqj9n4evl6mr5aj9f58zp6fyjzup6ywn3x6sk8akg5v4tgn2q8g4fhx05wf6juaxu9760yp46454gpg5mtzgerlzezqcqvjnhjh8z3g2qqdhhwkj" - let pr = PaymentRequest.Parse(data) |> Result.deref - Expect.equal pr.PrefixValue "lnbc" "" - Expect.equal pr.AmountValue (2000000000L |> LNMoney.MilliSatoshis |> Some) "" - Expect.equal pr.PaymentHash ("0001020304050607080900010203040506070809000102030405060708090102" |> uint256.Parse |> PaymentHash) "" - Expect.equal (pr.TimestampValue.ToUnixTimeSeconds()) 1496314658L "" - Expect.equal (pr.NodeIdValue) ("03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad" |> hex.DecodeData |> PubKey |> NodeId) "" - Expect.equal (pr.FallbackAddresses) (["1RustyRX2oai4EYYDpQGWvEL62BBGqN9T"]) "" - let routingInfo = - [ { ExtraHop.NodeId = "029e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255" |> hex.DecodeData |> PubKey |> NodeId - ShortChannelId = ShortChannelId.FromUInt64(72623859790382856UL) - FeeBase = 1L |> LNMoney.MilliSatoshis - FeeProportionalMillionths = 20u - CLTVExpiryDelta = 3us |> BlockHeightOffset16 } - { ExtraHop.NodeId = "039e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255" |> hex.DecodeData |> PubKey |> NodeId - ShortChannelId = 217304205466536202UL |> ShortChannelId.FromUInt64 - FeeBase = 2L |> LNMoney.MilliSatoshis - FeeProportionalMillionths = 30u - CLTVExpiryDelta = 4us |> BlockHeightOffset16 } - ] - Expect.equal pr.RoutingInfo ([routingInfo]) "" - Expect.equal pr.TagsValue.Fields.Length 4 "" - Expect.equal (pr.ToString()) data "" - Expect.equal (pr.ToString(msgSigner)) data "" - - testCase "On mainnet, with fallback (P2SH) address 3EktnHQD7RiAE6uzMj2ZifT9YgRrkSgzQX" <| fun _ -> - let data = "lnbc20m1pvjluezhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqspp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqfppj3a24vwu6r8ejrss3axul8rxldph2q7z9kmrgvr7xlaqm47apw3d48zm203kzcq357a4ls9al2ea73r8jcceyjtya6fu5wzzpe50zrge6ulk4nvjcpxlekvmxl6qcs9j3tz0469gq5g658y" - let pr = PaymentRequest.Parse(data) |> Result.deref - Expect.equal pr.PrefixValue "lnbc" "" - Expect.equal pr.PaymentHash ("0001020304050607080900010203040506070809000102030405060708090102" |> uint256.Parse |> PaymentHash) "" - Expect.equal (pr.TimestampValue.ToUnixTimeSeconds()) 1496314658L "" - Expect.equal (pr.NodeIdValue) ("03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad" |> hex.DecodeData |> PubKey |> NodeId) "" - Expect.equal (pr.Description) ("One piece of chocolate cake, one icecream cone, one pickle, one slice of swiss cheese, one slice of salami, one lollypop, one piece of cherry pie, one sausage, one cupcake, and one slice of watermelon" |> ascii.GetBytes |> Hashes.SHA256 |> fun x -> uint256(x, false) |> Choice2Of2) "" - Expect.equal (pr.FallbackAddresses) (["3EktnHQD7RiAE6uzMj2ZifT9YgRrkSgzQX"]) "" - Expect.equal (pr.TagsValue.Fields.Length) 3 "" - Expect.equal (pr.ToString()) data "" - Expect.equal (pr.ToString(msgSigner)) data "" - - testCase "On mainnet, with fallback (P2WPKH) address bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4" <| fun _ -> - let data = "lnbc20m1pvjluezhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqspp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqfppqw508d6qejxtdg4y5r3zarvary0c5xw7kepvrhrm9s57hejg0p662ur5j5cr03890fa7k2pypgttmh4897d3raaq85a293e9jpuqwl0rnfuwzam7yr8e690nd2ypcq9hlkdwdvycqa0qza8" - let pr = PaymentRequest.Parse(data) |> Result.deref - Expect.equal pr.PrefixValue "lnbc" "" - Expect.equal pr.PaymentHash ("0001020304050607080900010203040506070809000102030405060708090102" |> uint256.Parse |> PaymentHash) "" - Expect.equal (pr.TimestampValue.ToUnixTimeSeconds()) 1496314658L "" - Expect.equal (pr.NodeIdValue) ("03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad" |> hex.DecodeData |> PubKey |> NodeId) "" - Expect.equal (pr.Description) ("One piece of chocolate cake, one icecream cone, one pickle, one slice of swiss cheese, one slice of salami, one lollypop, one piece of cherry pie, one sausage, one cupcake, and one slice of watermelon" |> ascii.GetBytes |> Hashes.SHA256 |> fun x -> uint256(x, false) |> Choice2Of2) "" - Expect.equal (pr.FallbackAddresses) (["bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4"]) "" - Expect.isNone (pr.Features) "" - Expect.equal (pr.ToString()) data "" - Expect.equal (pr.ToString(msgSigner)) data "" - - testCase "On mainnet, with fallback (P2WSH) address bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3" <| fun _ -> - let data = "lnbc20m1pvjluezhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqspp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqfp4qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q28j0v3rwgy9pvjnd48ee2pl8xrpxysd5g44td63g6xcjcu003j3qe8878hluqlvl3km8rm92f5stamd3jw763n3hck0ct7p8wwj463cql26ava" - let pr = PaymentRequest.Parse(data) |> Result.deref - Expect.equal pr.PrefixValue "lnbc" "" - Expect.equal pr.PaymentHash ("0001020304050607080900010203040506070809000102030405060708090102" |> uint256.Parse |> PaymentHash) "" - Expect.equal (pr.TimestampValue.ToUnixTimeSeconds()) 1496314658L "" - Expect.equal (pr.NodeIdValue) ("03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad" |> hex.DecodeData |> PubKey |> NodeId) "" - Expect.equal (pr.Description) ("One piece of chocolate cake, one icecream cone, one pickle, one slice of swiss cheese, one slice of salami, one lollypop, one piece of cherry pie, one sausage, one cupcake, and one slice of watermelon" |> ascii.GetBytes |> Hashes.SHA256 |> fun x -> uint256(x, false) |> Choice2Of2) "" - Expect.equal (pr.FallbackAddresses) (["bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3"]) "" - Expect.equal (pr.ToString()) data "" - Expect.equal (pr.ToString(msgSigner)) data "" - - testCase "Please send $30 for coffee beans to the same peer, which supports features 9, 15 and 99, using secret 0x1111111111111111111111111111111111111111111111111111111111111111" <| fun _ -> - let data = "lnbc25m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5vdhkven9v5sxyetpdeessp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9q5sqqqqqqqqqqqqqqqpqsq67gye39hfg3zd8rgc80k32tvy9xk2xunwm5lzexnvpx6fd77en8qaq424dxgt56cag2dpt359k3ssyhetktkpqh24jqnjyw6uqd08sgptq44qu" - let pr = PaymentRequest.Parse(data) |> Result.deref - Expect.equal pr.PrefixValue "lnbc" "pr.PrefixValue mismatch" - Expect.equal pr.PaymentHash ("0001020304050607080900010203040506070809000102030405060708090102" |> uint256.Parse |> PaymentHash) "pr.PaymentHash mismatch" - Expect.equal (pr.TimestampValue.ToUnixTimeSeconds()) 1496314658L "pr.TimestampValue mismatch" - Expect.equal (pr.NodeIdValue) ("03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad" |> hex.DecodeData |> PubKey |> NodeId) "pr.NodeIdValue mismatch" - Expect.equal (pr.Description) (Choice1Of2 "coffee beans") "pr.Description mismatch" - Expect.isEmpty pr.FallbackAddresses "pr.FallbackAddress mismatch" - Expect.isSome (pr.Features) "pr.Features mismatch" - Expect.isTrue(pr.Features.Value.HasFeature(Feature.PaymentSecret, Optional)) "pr.Features.Value (PaymentSecret) mismatch" - Expect.isTrue(pr.Features.Value.HasFeature(Feature.VariableLengthOnion, Optional)) "pr.Features.Value (VariableLengthOnion) mismatch" - Expect.equal (pr.ToString()) data "pr.ToString() mismatch" - Expect.equal (pr.ToString(msgSigner)) data "pr.ToString(msgSigned) mismatch" - - testCase "Same, but adding invalid unknown feature 100" <| fun _ -> - let data = "lnbc25m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5vdhkven9v5sxyetpdeessp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9q4psqqqqqqqqqqqqqqqpqsqq40wa3khl49yue3zsgm26jrepqr2eghqlx86rttutve3ugd05em86nsefzh4pfurpd9ek9w2vp95zxqnfe2u7ckudyahsa52q66tgzcp6t2dyk" - let pr = PaymentRequest.Parse(data) - Expect.isError (Result.ToFSharpCoreResult pr) "" - ] + + let priv = + "e126f68f7eafcc8b74f54d269fe206be715000f94dac067d1c04a8ca3b2db734" + |> hex.DecodeData + |> fun h -> new Key(h) + + let msgSigner = + { new IMessageSigner with + member this.SignMessage data = + let signature = priv.SignCompact(data, false) in signature + } + + testList + "BOLT-11 tests" + [ + testCase "check minimal unit is used" + <| fun _ -> + Expect.equal 'p' (Amount.unit(LNMoney.MilliSatoshis(1L))) "" + Expect.equal 'p' (Amount.unit(LNMoney.MilliSatoshis(99L))) "" + Expect.equal 'n' (Amount.unit(LNMoney.MilliSatoshis(100L))) "" + Expect.equal 'p' (Amount.unit(LNMoney.MilliSatoshis(101L))) "" + Expect.equal 'n' (Amount.unit(LNMoney.Satoshis(1L))) "" + Expect.equal 'u' (Amount.unit(LNMoney.Satoshis(100L))) "" + Expect.equal 'n' (Amount.unit(LNMoney.Satoshis(101L))) "" + Expect.equal 'u' (Amount.unit(LNMoney.Satoshis(1155400L))) "" + Expect.equal 'm' (Amount.unit(LNMoney.Coins(1m / 1000m))) "" + Expect.equal 'm' (Amount.unit(LNMoney.Coins(10m / 1000m))) "" + Expect.equal 'm' (Amount.unit(LNMoney.Coins(1m))) "" + + testCase + "check that we can still decode non-minimal amount encoding" + <| fun _ -> + Expect.equal + (Amount.decode("1000u")) + (Ok(LNMoney.MilliSatoshis(100000000L))) + "" + + Expect.equal + (Amount.decode("1000000n")) + (Ok(LNMoney.MilliSatoshis(100000000L))) + "" + + Expect.equal + (Amount.decode("1000000000p")) + (Ok(LNMoney.MilliSatoshis(100000000L))) + "" + + testCase + "Please make a donation of any amount using payment_hash 0001020304050607080900010203040506070809000102030405060708090102 to me @03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad" + <| fun _ -> + let data = + "lnbc1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpl2pkx2ctnv5sxxmmwwd5kgetjypeh2ursdae8g6twvus8g6rfwvs8qun0dfjkxaq8rkx3yf5tcsyz3d73gafnh3cax9rn449d9p5uxz9ezhhypd0elx87sjle52x86fux2ypatgddc6k63n7erqz25le42c4u4ecky03ylcqca784w" + + let d = PaymentRequest.Parse(data) |> Result.deref + Expect.equal (d.PrefixValue) ("lnbc") "" + Expect.isTrue (d.AmountValue.IsNone) "" + + Expect.equal + (d.PaymentHash) + (PaymentHash( + uint256.Parse( + "0001020304050607080900010203040506070809000102030405060708090102" + ) + )) + "" + + Expect.equal + (d.TimestampValue.ToUnixTimeSeconds()) + (1496314658L) + "" + + Expect.equal + (d.NodeIdValue) + ("03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad" + |> hex.DecodeData + |> PubKey + |> NodeId) + "" + + Expect.equal + (d.Description) + (Choice1Of2 "Please consider supporting this project") + "" + + Expect.equal (d.TagsValue.Fields.Length) (2) "" + Expect.equal (d.ToString()) data "" + Expect.equal (d.ToString(msgSigner)) data "" + + testCase + "Please send $3 for a cup of coffee to the same peer, within one minute" + <| fun _ -> + let data = + "lnbc2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsxqzpuaztrnwngzn3kdzw5hydlzf03qdgm2hdq27cqv3agm2awhz5se903vruatfhq77w3ls4evs3ch9zw97j25emudupq63nyw24cg27h2rspfj9srp" + + let pr = PaymentRequest.Parse(data) |> Result.deref + Expect.equal pr.PrefixValue "lnbc" "" + + Expect.equal + pr.AmountValue + (Some(250000000L |> LNMoney.MilliSatoshis)) + "" + + Expect.equal + pr.PaymentHash.Value + ("0001020304050607080900010203040506070809000102030405060708090102" + |> uint256.Parse) + "" + + Expect.equal + pr.TimestampValue + (DateTimeOffset.FromUnixTimeSeconds 1496314658L) + "" + + Expect.equal + pr.NodeIdValue + ("03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad" + |> hex.DecodeData + |> PubKey + |> NodeId) + "" + + Expect.equal pr.Description (Choice1Of2 "1 cup coffee") "" + Expect.equal pr.FallbackAddresses ([]) "" + Expect.equal (pr.TagsValue.Fields.Length) 3 "" + Expect.equal (pr.ToString()) data "" + Expect.equal (pr.Sign(priv, false).ToString()) data "" + Expect.equal (pr.ToString(msgSigner)) data "" + + testCase + "Please send 0.0025 BTC for a cup of nonsense (ナンセンス 1杯) to the same peer, within one minute" + <| fun _ -> + let data = + "lnbc2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpquwpc4curk03c9wlrswe78q4eyqc7d8d0xqzpuyk0sg5g70me25alkluzd2x62aysf2pyy8edtjeevuv4p2d5p76r4zkmneet7uvyakky2zr4cusd45tftc9c5fh0nnqpnl2jfll544esqchsrny" + + let pr = PaymentRequest.Parse(data) |> Result.deref + Expect.equal (pr.Description) (Choice1Of2 "ナンセンス 1杯") "" + Expect.equal (pr.ToString()) data "" + Expect.equal (pr.ToString(msgSigner)) data "" + () + + testCase "Now send $24 for an entire list of things (hashed)" + <| fun _ -> + let data = + "lnbc20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqscc6gd6ql3jrc5yzme8v4ntcewwz5cnw92tz0pc8qcuufvq7khhr8wpald05e92xw006sq94mg8v2ndf4sefvf9sygkshp5zfem29trqq2yxxz7" + + let pr = PaymentRequest.Parse(data) |> Result.deref + Expect.equal pr.PrefixValue "lnbc" "" + + Expect.equal + pr.AmountValue + (2000000000L |> LNMoney.MilliSatoshis |> Some) + "" + + Expect.equal + pr.PaymentHash + ("0001020304050607080900010203040506070809000102030405060708090102" + |> uint256.Parse + |> PaymentHash) + "" + + Expect.equal + pr.TimestampValue + (DateTimeOffset.FromUnixTimeSeconds 1496314658L) + "" + + Expect.equal + pr.NodeIdValue + ("03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad" + |> hex.DecodeData + |> PubKey + |> NodeId) + "" + + Expect.equal + pr.Description + ("One piece of chocolate cake, one icecream cone, one pickle, one slice of swiss cheese, one slice of salami, one lollypop, one piece of cherry pie, one sausage, one cupcake, and one slice of watermelon" + |> ascii.GetBytes + |> Hashes.SHA256 + |> fun x -> uint256(x, false) |> Choice2Of2) + "" + + Expect.equal pr.FallbackAddresses [] "" + Expect.equal pr.TagsValue.Fields.Length 2 "" + Expect.equal (pr.ToString()) data "" + Expect.equal (pr.ToString(msgSigner)) data "" + + testCase + "The same, on testnet, with a fallback address mk2QpYatsKicvFVuTAQLBryyccRXMUaGHP" + <| fun _ -> + let data = + "lntb20m1pvjluezhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqspp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqfpp3x9et2e20v6pu37c5d9vax37wxq72un98kmzzhznpurw9sgl2v0nklu2g4d0keph5t7tj9tcqd8rexnd07ux4uv2cjvcqwaxgj7v4uwn5wmypjd5n69z2xm3xgksg28nwht7f6zspwp3f9t" + + let pr = PaymentRequest.Parse(data) |> Result.deref + Expect.equal pr.PrefixValue "lntb" "" + + Expect.equal + pr.AmountValue + (2000000000L |> LNMoney.MilliSatoshis |> Some) + "" + + Expect.equal + pr.PaymentHash + ("0001020304050607080900010203040506070809000102030405060708090102" + |> uint256.Parse + |> PaymentHash) + "" + + Expect.equal + (pr.TimestampValue.ToUnixTimeSeconds()) + 1496314658L + "" + + Expect.equal + (pr.NodeIdValue) + ("03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad" + |> hex.DecodeData + |> PubKey + |> NodeId) + "" + + Expect.equal + (pr.FallbackAddresses) + ([ "mk2QpYatsKicvFVuTAQLBryyccRXMUaGHP" ]) + "" + + Expect.equal (pr.TagsValue.Fields.Length) 3 "" + Expect.equal (pr.ToString()) data "" + Expect.equal (pr.ToString(msgSigner)) data "" + + testCase + "On mainnet, with fallback address 1RustyRX2oai4EYYDpQGWvEL62BBGqN9T with extra routing info to go via nodes 029e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255 then 039e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255" + <| fun _ -> + let data = + "lnbc20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqsfpp3qjmp7lwpagxun9pygexvgpjdc4jdj85fr9yq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqpqqqqq9qqqvpeuqafqxu92d8lr6fvg0r5gv0heeeqgcrqlnm6jhphu9y00rrhy4grqszsvpcgpy9qqqqqqgqqqqq7qqzqj9n4evl6mr5aj9f58zp6fyjzup6ywn3x6sk8akg5v4tgn2q8g4fhx05wf6juaxu9760yp46454gpg5mtzgerlzezqcqvjnhjh8z3g2qqdhhwkj" + + let pr = PaymentRequest.Parse(data) |> Result.deref + Expect.equal pr.PrefixValue "lnbc" "" + + Expect.equal + pr.AmountValue + (2000000000L |> LNMoney.MilliSatoshis |> Some) + "" + + Expect.equal + pr.PaymentHash + ("0001020304050607080900010203040506070809000102030405060708090102" + |> uint256.Parse + |> PaymentHash) + "" + + Expect.equal + (pr.TimestampValue.ToUnixTimeSeconds()) + 1496314658L + "" + + Expect.equal + (pr.NodeIdValue) + ("03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad" + |> hex.DecodeData + |> PubKey + |> NodeId) + "" + + Expect.equal + (pr.FallbackAddresses) + ([ "1RustyRX2oai4EYYDpQGWvEL62BBGqN9T" ]) + "" + + let routingInfo = + [ + { + ExtraHop.NodeId = + "029e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255" + |> hex.DecodeData + |> PubKey + |> NodeId + ShortChannelId = + ShortChannelId.FromUInt64(72623859790382856UL) + FeeBase = 1L |> LNMoney.MilliSatoshis + FeeProportionalMillionths = 20u + CLTVExpiryDelta = 3us |> BlockHeightOffset16 + } + { + ExtraHop.NodeId = + "039e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255" + |> hex.DecodeData + |> PubKey + |> NodeId + ShortChannelId = + 217304205466536202UL + |> ShortChannelId.FromUInt64 + FeeBase = 2L |> LNMoney.MilliSatoshis + FeeProportionalMillionths = 30u + CLTVExpiryDelta = 4us |> BlockHeightOffset16 + } + ] + + Expect.equal pr.RoutingInfo ([ routingInfo ]) "" + Expect.equal pr.TagsValue.Fields.Length 4 "" + Expect.equal (pr.ToString()) data "" + Expect.equal (pr.ToString(msgSigner)) data "" + + testCase + "On mainnet, with fallback (P2SH) address 3EktnHQD7RiAE6uzMj2ZifT9YgRrkSgzQX" + <| fun _ -> + let data = + "lnbc20m1pvjluezhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqspp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqfppj3a24vwu6r8ejrss3axul8rxldph2q7z9kmrgvr7xlaqm47apw3d48zm203kzcq357a4ls9al2ea73r8jcceyjtya6fu5wzzpe50zrge6ulk4nvjcpxlekvmxl6qcs9j3tz0469gq5g658y" + + let pr = PaymentRequest.Parse(data) |> Result.deref + Expect.equal pr.PrefixValue "lnbc" "" + + Expect.equal + pr.PaymentHash + ("0001020304050607080900010203040506070809000102030405060708090102" + |> uint256.Parse + |> PaymentHash) + "" + + Expect.equal + (pr.TimestampValue.ToUnixTimeSeconds()) + 1496314658L + "" + + Expect.equal + (pr.NodeIdValue) + ("03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad" + |> hex.DecodeData + |> PubKey + |> NodeId) + "" + + Expect.equal + (pr.Description) + ("One piece of chocolate cake, one icecream cone, one pickle, one slice of swiss cheese, one slice of salami, one lollypop, one piece of cherry pie, one sausage, one cupcake, and one slice of watermelon" + |> ascii.GetBytes + |> Hashes.SHA256 + |> fun x -> uint256(x, false) |> Choice2Of2) + "" + + Expect.equal + (pr.FallbackAddresses) + ([ "3EktnHQD7RiAE6uzMj2ZifT9YgRrkSgzQX" ]) + "" + + Expect.equal (pr.TagsValue.Fields.Length) 3 "" + Expect.equal (pr.ToString()) data "" + Expect.equal (pr.ToString(msgSigner)) data "" + + testCase + "On mainnet, with fallback (P2WPKH) address bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4" + <| fun _ -> + let data = + "lnbc20m1pvjluezhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqspp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqfppqw508d6qejxtdg4y5r3zarvary0c5xw7kepvrhrm9s57hejg0p662ur5j5cr03890fa7k2pypgttmh4897d3raaq85a293e9jpuqwl0rnfuwzam7yr8e690nd2ypcq9hlkdwdvycqa0qza8" + + let pr = PaymentRequest.Parse(data) |> Result.deref + Expect.equal pr.PrefixValue "lnbc" "" + + Expect.equal + pr.PaymentHash + ("0001020304050607080900010203040506070809000102030405060708090102" + |> uint256.Parse + |> PaymentHash) + "" + + Expect.equal + (pr.TimestampValue.ToUnixTimeSeconds()) + 1496314658L + "" + + Expect.equal + (pr.NodeIdValue) + ("03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad" + |> hex.DecodeData + |> PubKey + |> NodeId) + "" + + Expect.equal + (pr.Description) + ("One piece of chocolate cake, one icecream cone, one pickle, one slice of swiss cheese, one slice of salami, one lollypop, one piece of cherry pie, one sausage, one cupcake, and one slice of watermelon" + |> ascii.GetBytes + |> Hashes.SHA256 + |> fun x -> uint256(x, false) |> Choice2Of2) + "" + + Expect.equal + (pr.FallbackAddresses) + ([ + "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4" + ]) + "" + + Expect.isNone (pr.Features) "" + Expect.equal (pr.ToString()) data "" + Expect.equal (pr.ToString(msgSigner)) data "" + + testCase + "On mainnet, with fallback (P2WSH) address bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3" + <| fun _ -> + let data = + "lnbc20m1pvjluezhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqspp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqfp4qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q28j0v3rwgy9pvjnd48ee2pl8xrpxysd5g44td63g6xcjcu003j3qe8878hluqlvl3km8rm92f5stamd3jw763n3hck0ct7p8wwj463cql26ava" + + let pr = PaymentRequest.Parse(data) |> Result.deref + Expect.equal pr.PrefixValue "lnbc" "" + + Expect.equal + pr.PaymentHash + ("0001020304050607080900010203040506070809000102030405060708090102" + |> uint256.Parse + |> PaymentHash) + "" + + Expect.equal + (pr.TimestampValue.ToUnixTimeSeconds()) + 1496314658L + "" + + Expect.equal + (pr.NodeIdValue) + ("03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad" + |> hex.DecodeData + |> PubKey + |> NodeId) + "" + + Expect.equal + (pr.Description) + ("One piece of chocolate cake, one icecream cone, one pickle, one slice of swiss cheese, one slice of salami, one lollypop, one piece of cherry pie, one sausage, one cupcake, and one slice of watermelon" + |> ascii.GetBytes + |> Hashes.SHA256 + |> fun x -> uint256(x, false) |> Choice2Of2) + "" + + Expect.equal + (pr.FallbackAddresses) + ([ + "bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3" + ]) + "" + + Expect.equal (pr.ToString()) data "" + Expect.equal (pr.ToString(msgSigner)) data "" + + testCase + "Please send $30 for coffee beans to the same peer, which supports features 9, 15 and 99, using secret 0x1111111111111111111111111111111111111111111111111111111111111111" + <| fun _ -> + let data = + "lnbc25m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5vdhkven9v5sxyetpdeessp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9q5sqqqqqqqqqqqqqqqpqsq67gye39hfg3zd8rgc80k32tvy9xk2xunwm5lzexnvpx6fd77en8qaq424dxgt56cag2dpt359k3ssyhetktkpqh24jqnjyw6uqd08sgptq44qu" + + let pr = PaymentRequest.Parse(data) |> Result.deref + Expect.equal pr.PrefixValue "lnbc" "pr.PrefixValue mismatch" + + Expect.equal + pr.PaymentHash + ("0001020304050607080900010203040506070809000102030405060708090102" + |> uint256.Parse + |> PaymentHash) + "pr.PaymentHash mismatch" + + Expect.equal + (pr.TimestampValue.ToUnixTimeSeconds()) + 1496314658L + "pr.TimestampValue mismatch" + + Expect.equal + (pr.NodeIdValue) + ("03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad" + |> hex.DecodeData + |> PubKey + |> NodeId) + "pr.NodeIdValue mismatch" + + Expect.equal + (pr.Description) + (Choice1Of2 "coffee beans") + "pr.Description mismatch" + + Expect.isEmpty + pr.FallbackAddresses + "pr.FallbackAddress mismatch" + + Expect.isSome (pr.Features) "pr.Features mismatch" + + Expect.isTrue + (pr.Features.Value.HasFeature( + Feature.PaymentSecret, + Optional + )) + "pr.Features.Value (PaymentSecret) mismatch" + + Expect.isTrue + (pr.Features.Value.HasFeature( + Feature.VariableLengthOnion, + Optional + )) + "pr.Features.Value (VariableLengthOnion) mismatch" + + Expect.equal (pr.ToString()) data "pr.ToString() mismatch" + + Expect.equal + (pr.ToString(msgSigner)) + data + "pr.ToString(msgSigned) mismatch" + + testCase "Same, but adding invalid unknown feature 100" + <| fun _ -> + let data = + "lnbc25m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5vdhkven9v5sxyetpdeessp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9q4psqqqqqqqqqqqqqqqpqsqq40wa3khl49yue3zsgm26jrepqr2eghqlx86rttutve3ugd05em86nsefzh4pfurpd9ek9w2vp95zxqnfe2u7ckudyahsa52q66tgzcp6t2dyk" + + let pr = PaymentRequest.Parse(data) + Expect.isError (Result.ToFSharpCoreResult pr) "" + ] [] let unitTest = let utf8 = System.Text.UTF8Encoding() let hex = NBitcoin.DataEncoders.HexEncoder() - testList "PaymentRequest Unit tests" [ - testCase "Can create PaymentRequest correctly" <| fun _ -> - let h = - "af919878bd5d09dc58e86689a8cd3a6a03dabc37a1d9445eb413ea7837c50ac3" - |> uint256.Parse - |> PaymentHash - |> TaggedField.PaymentHashTaggedField - let nodeSecret = - "897469da69a4aae063b98454bdb5dce9efb71a6001b09ff9aaf32d730a127bfc" - |> hex.DecodeData - |> fun h -> new Key(h) - let nodeId = - "02b8042a54520cb3228d7b0aa3de81ffcb424e1dcd958821285696ce088d4486e4" - |> PubKey - |> NodeId - - let nodeIdt = - nodeId - |> TaggedField.NodeIdTaggedField - let d = "this is description" - let dht = d |> utf8.GetBytes |> Hashes.SHA256 |> uint256 |> TaggedField.DescriptionHashTaggedField - let dt = d |> TaggedField.DescriptionTaggedField - let taggedFields = {TaggedFields.Fields = [h; nodeIdt; dht]} - let msgSigner = { new IMessageSigner - with - member this.SignMessage(data) = nodeSecret.SignCompact(data, false) } - let r = PaymentRequest.TryCreate("lnbc", None, DateTimeOffset.UnixEpoch, nodeId, taggedFields, msgSigner) - Expect.isOk (Result.ToFSharpCoreResult r) "" - let r2 = PaymentRequest.Parse(r |> Result.deref |> fun x -> x.ToString()) - Expect.isOk (Result.ToFSharpCoreResult r2) "" - Expect.equal r r2 "Should not change by de/serializing it" - let r3 = PaymentRequest.TryCreate("lnbc", None, DateTimeOffset.UnixEpoch, nodeId, {Fields = [dht; dt]}, msgSigner) - Expect.isError (Result.ToFSharpCoreResult r3) "Field contains both description and description hash! this must be invalid" - testCase "PaymentSecret can get correct PaymentHash by its .Hash field" <| fun _ -> - let p = Primitives.PaymentPreimage.Create(hex.DecodeData "60ba77a7f0174a3dd0f4fc8c1b28cda6aa9fab0e87c87e936af40b34cca40883") - let h = p.Hash - let expectedHash = PaymentHash.PaymentHash (uint256.Parse "b1d9fa54b693576947e3b78eaf8a2595b5b6288b283c7c75f51ee0fe5bb82cba") - Expect.equal expectedHash h "" - () - - testCase "encode/decode invoice with payment_secret" <| fun _ -> - let invoice = "lnbcrt1m1psy8qzcpp5az829pxjk7dxmaa080vjgstjanr7lqcrthxunqw5e985cpyvmyhsdql2djkuepqw3hjqsj5gvsxzerywfjhxuccqzptsp5zt5m0yv2hk5lkjcdu36a0r2mt2wtten7zp5xt47px26dxqdlaggq9qyyssqrqxsvhqvd6aynuyv0vsds6kxktg5kg60m68qdv87mu38290cfgzrxrqtntl5n29c57dt2man6parlgl9ua945e4yfehdsttgs7hqlpsphcn7uy" - let expected = PubKey("0268692bc886c37a10ef4990f1ad1538bd9c50f0d74a80b9498e7b8a152ee2355b") - let p = PaymentRequest.Parse(invoice) |> Result.deref - Expect.equal p.NodeIdValue.Value expected "" - Expect.isNone p.TagsValue.ExplicitNodeId "" - let expectedPaymentSecret = "12e9b7918abda9fb4b0de475d78d5b5a9cb5e67e106865d7c132b4d301bfea10" - Expect.equal p.TagsValue.PaymentSecret.Value (uint256(expectedPaymentSecret)) "" - Expect.isSome p.SignatureValue "" - let p2 = p.ToString() |> PaymentRequest.Parse |> Result.deref - Expect.equal p.SignatureValue p2.SignatureValue "" - Expect.equal p.NodeIdValue p2.NodeIdValue "" - () - - // edge testcases found by property tests - yield! [ - testCase "Should not throw exn when parsing empty description field" <| fun _ -> - let invoiceR = - let key = new Key() - let nodeId = key.PubKey |> NodeId - let tags = { - TaggedFields.Fields = [ - TaggedField.PaymentHashTaggedField(PaymentHash uint256.Zero) - TaggedField.DescriptionTaggedField(String.Empty) - ] + + testList + "PaymentRequest Unit tests" + [ + testCase "Can create PaymentRequest correctly" + <| fun _ -> + let h = + "af919878bd5d09dc58e86689a8cd3a6a03dabc37a1d9445eb413ea7837c50ac3" + |> uint256.Parse + |> PaymentHash + |> TaggedField.PaymentHashTaggedField + + let nodeSecret = + "897469da69a4aae063b98454bdb5dce9efb71a6001b09ff9aaf32d730a127bfc" + |> hex.DecodeData + |> fun h -> new Key(h) + + let nodeId = + "02b8042a54520cb3228d7b0aa3de81ffcb424e1dcd958821285696ce088d4486e4" + |> PubKey + |> NodeId + + let nodeIdt = nodeId |> TaggedField.NodeIdTaggedField + let d = "this is description" + + let dht = + d + |> utf8.GetBytes + |> Hashes.SHA256 + |> uint256 + |> TaggedField.DescriptionHashTaggedField + + let dt = d |> TaggedField.DescriptionTaggedField + + let taggedFields = + { + TaggedFields.Fields = [ h; nodeIdt; dht ] } - PaymentRequest.TryCreate("lnbc", None, DateTimeOffset.Now, nodeId, tags, key) - Expect.isTrue(invoiceR |> Result.isError) "Should return error when Description is an empty string" - () - testCase "Should be unable to create already expired invoice" <| fun _ -> - let invoiceR = - let key = new Key() - let nodeId = key.PubKey |> NodeId - let tags = { - TaggedFields.Fields = [ - TaggedField.PaymentHashTaggedField(PaymentHash uint256.Zero) - TaggedField.DescriptionTaggedField("1") - ExpiryTaggedField(DateTimeOffset.UtcNow - TimeSpan.FromSeconds 20.) - ] + let msgSigner = + { new IMessageSigner with + member this.SignMessage data = + nodeSecret.SignCompact(data, false) } - PaymentRequest.TryCreate("lnbc", None, DateTimeOffset.UtcNow, nodeId, tags, key) - Expect.isTrue(invoiceR |> Result.isError) "Should be unable to create already expired invoice" + + let r = + PaymentRequest.TryCreate( + "lnbc", + None, + DateTimeOffset.UnixEpoch, + nodeId, + taggedFields, + msgSigner + ) + + Expect.isOk (Result.ToFSharpCoreResult r) "" + + let r2 = + PaymentRequest.Parse( + r |> Result.deref |> (fun x -> x.ToString()) + ) + + Expect.isOk (Result.ToFSharpCoreResult r2) "" + Expect.equal r r2 "Should not change by de/serializing it" + + let r3 = + PaymentRequest.TryCreate( + "lnbc", + None, + DateTimeOffset.UnixEpoch, + nodeId, + { + Fields = [ dht; dt ] + }, + msgSigner + ) + + Expect.isError + (Result.ToFSharpCoreResult r3) + "Field contains both description and description hash! this must be invalid" + testCase + "PaymentSecret can get correct PaymentHash by its .Hash field" + <| fun _ -> + let p = + Primitives.PaymentPreimage.Create( + hex.DecodeData + "60ba77a7f0174a3dd0f4fc8c1b28cda6aa9fab0e87c87e936af40b34cca40883" + ) + + let h = p.Hash + + let expectedHash = + PaymentHash.PaymentHash( + uint256.Parse + "b1d9fa54b693576947e3b78eaf8a2595b5b6288b283c7c75f51ee0fe5bb82cba" + ) + + Expect.equal expectedHash h "" () - testCase "Should fail to create with wrong node id" <| fun _ -> - let invoiceR = - let key = new Key() - let nodeId = key.PubKey |> NodeId - let tags = { - TaggedFields.Fields = [ - TaggedField.PaymentHashTaggedField(PaymentHash uint256.Zero) - TaggedField.DescriptionTaggedField("1") - NodeIdTaggedField((new Key()).PubKey |> NodeId) - ] - } - PaymentRequest.TryCreate("lnbc", None, DateTimeOffset.UtcNow, nodeId, tags, key) - Expect.isTrue(invoiceR |> Result.isError) "Should fail to create with wrong node id" + testCase "encode/decode invoice with payment_secret" + <| fun _ -> + let invoice = + "lnbcrt1m1psy8qzcpp5az829pxjk7dxmaa080vjgstjanr7lqcrthxunqw5e985cpyvmyhsdql2djkuepqw3hjqsj5gvsxzerywfjhxuccqzptsp5zt5m0yv2hk5lkjcdu36a0r2mt2wtten7zp5xt47px26dxqdlaggq9qyyssqrqxsvhqvd6aynuyv0vsds6kxktg5kg60m68qdv87mu38290cfgzrxrqtntl5n29c57dt2man6parlgl9ua945e4yfehdsttgs7hqlpsphcn7uy" + + let expected = + PubKey( + "0268692bc886c37a10ef4990f1ad1538bd9c50f0d74a80b9498e7b8a152ee2355b" + ) + + let p = PaymentRequest.Parse(invoice) |> Result.deref + Expect.equal p.NodeIdValue.Value expected "" + Expect.isNone p.TagsValue.ExplicitNodeId "" + + let expectedPaymentSecret = + "12e9b7918abda9fb4b0de475d78d5b5a9cb5e67e106865d7c132b4d301bfea10" + + Expect.equal + p.TagsValue.PaymentSecret.Value + (uint256(expectedPaymentSecret)) + "" + + Expect.isSome p.SignatureValue "" + let p2 = p.ToString() |> PaymentRequest.Parse |> Result.deref + Expect.equal p.SignatureValue p2.SignatureValue "" + Expect.equal p.NodeIdValue p2.NodeIdValue "" () + + // edge testcases found by property tests + yield! + [ + testCase + "Should not throw exn when parsing empty description field" + <| fun _ -> + let invoiceR = + let key = new Key() + let nodeId = key.PubKey |> NodeId + + let tags = + { + TaggedFields.Fields = + [ + TaggedField.PaymentHashTaggedField( + PaymentHash uint256.Zero + ) + TaggedField.DescriptionTaggedField( + String.Empty + ) + ] + } + + PaymentRequest.TryCreate( + "lnbc", + None, + DateTimeOffset.Now, + nodeId, + tags, + key + ) + + Expect.isTrue + (invoiceR |> Result.isError) + "Should return error when Description is an empty string" + + () + + testCase + "Should be unable to create already expired invoice" + <| fun _ -> + let invoiceR = + let key = new Key() + let nodeId = key.PubKey |> NodeId + + let tags = + { + TaggedFields.Fields = + [ + TaggedField.PaymentHashTaggedField( + PaymentHash uint256.Zero + ) + TaggedField.DescriptionTaggedField( + "1" + ) + ExpiryTaggedField( + DateTimeOffset.UtcNow + - TimeSpan.FromSeconds 20. + ) + ] + } + + PaymentRequest.TryCreate( + "lnbc", + None, + DateTimeOffset.UtcNow, + nodeId, + tags, + key + ) + + Expect.isTrue + (invoiceR |> Result.isError) + "Should be unable to create already expired invoice" + + () + + testCase "Should fail to create with wrong node id" + <| fun _ -> + let invoiceR = + let key = new Key() + let nodeId = key.PubKey |> NodeId + + let tags = + { + TaggedFields.Fields = + [ + TaggedField.PaymentHashTaggedField( + PaymentHash uint256.Zero + ) + TaggedField.DescriptionTaggedField( + "1" + ) + NodeIdTaggedField( + (new Key()).PubKey |> NodeId + ) + ] + } + + PaymentRequest.TryCreate( + "lnbc", + None, + DateTimeOffset.UtcNow, + nodeId, + tags, + key + ) + + Expect.isTrue + (invoiceR |> Result.isError) + "Should fail to create with wrong node id" + + () + ] ] - ] diff --git a/tests/DotNetLightning.Core.Tests/PeerChannelEncryptorTests.fs b/tests/DotNetLightning.Core.Tests/PeerChannelEncryptorTests.fs index ca3e3b88e..db09ef82f 100644 --- a/tests/DotNetLightning.Core.Tests/PeerChannelEncryptorTests.fs +++ b/tests/DotNetLightning.Core.Tests/PeerChannelEncryptorTests.fs @@ -14,305 +14,840 @@ open ResultUtils.Portability let hex = NBitcoin.DataEncoders.HexEncoder() let logger = Log.create "PeerChannelEncryptor tests" -let getOutBoundPeerForInitiatorTestVectors () = - let theirNodeId = PubKey("028d7500dd4c12685d1f568b4c2b5048e8534b873319f3a8daa612b469132ec7f7") +let getOutBoundPeerForInitiatorTestVectors() = + let theirNodeId = + PubKey( + "028d7500dd4c12685d1f568b4c2b5048e8534b873319f3a8daa612b469132ec7f7" + ) let outboundPeer = - let ie = (new Key(hex.DecodeData("1212121212121212121212121212121212121212121212121212121212121212"))) + let ie = + (new Key( + hex.DecodeData( + "1212121212121212121212121212121212121212121212121212121212121212" + ) + )) + PeerChannelEncryptor.newOutBound(NodeId theirNodeId, ie) |> fun c -> let expectedIE = match c.NoiseState with | InProgress inProgressNoiseState -> match inProgressNoiseState.DirectionalState with - | OutBound outbound -> - Some outbound.IE + | OutBound outbound -> Some outbound.IE | _ -> None | _ -> None + Expect.equal expectedIE (Some ie) String.Empty c let actual, result = outboundPeer |> PeerChannelEncryptor.getActOne - let expected = hex.DecodeData("00036360e856310ce5d294e8be33fc807077dc56ac80d95d9cd4ddbd21325eff73f70df6086551151f58b8afe6c195782c6a") + + let expected = + hex.DecodeData( + "00036360e856310ce5d294e8be33fc807077dc56ac80d95d9cd4ddbd21325eff73f70df6086551151f58b8afe6c195782c6a" + ) + Expect.equal actual expected "" result [] let peerChannelEncryptorTests = - testList "PeerChannelEncryptorTests" [ - testCase "noise initiator test vectors" <| fun _ -> - let ourNodeId = new Key(hex.DecodeData("1111111111111111111111111111111111111111111111111111111111111111")) - - let testCase1() = - let outboundPeer = getOutBoundPeerForInitiatorTestVectors() - let actTwo = hex.DecodeData("0002466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276e2470b93aac583c9ef6eafca3f730ae") - let res = outboundPeer |> PeerChannelEncryptor.processActTwo actTwo ourNodeId - Expect.isOk (Result.ToFSharpCoreResult res) "" - - let (actual, _nodeid), nextPCE = res |> Result.deref - let expected = "0x00b9e3a702e93e3a9948c2ed6e5fd7590a6e1c3a0344cfc9d5b57357049aa22355361aa02e55a8fc28fef5bd6d71ad0c38228dc68b1c466263b47fdf31e560e139ba" - Expect.equal (actual.ToHexString()) (expected) "" - - match nextPCE.NoiseState with - | Finished {SK = sk; SN=sn; SCK = sck; RK = rk; RN = rn; RCK = rck } -> - Expect.equal (sk.ToBytes()) (hex.DecodeData("969ab31b4d288cedf6218839b27a3e2140827047f2c0f01bf5c04435d43511a9")) "sk does not match" - Expect.equal (sn) (0UL) "sn does not match" - Expect.equal (sck.ToBytes()) (hex.DecodeData("919219dbb2920afa8db80f9a51787a840bcf111ed8d588caf9ab4be716e42b01")) "sck does not match" - Expect.equal (rk.ToBytes()) (hex.DecodeData("bb9020b8965f4df047e07f955f3c4b88418984aadc5cdb35096b9ea8fa5c3442")) "rk does not match" - Expect.equal (rn) (0UL) "rn does not match" - Expect.equal (rck.ToBytes()) (hex.DecodeData("919219dbb2920afa8db80f9a51787a840bcf111ed8d588caf9ab4be716e42b01")) "" - | s -> failwithf "not in finished state %A" s - - testCase1() - - /// Transport-initiator act3 short read test - let testCase2 () = - let outboundPeer = getOutBoundPeerForInitiatorTestVectors() - let actTwo = hex.DecodeData("0002466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276e2470b93aac583c9ef6eafca3f730") - Expect.throws (fun _ -> outboundPeer |> PeerChannelEncryptor.processActTwo actTwo ourNodeId |> ignore) "" - - testCase2() - - /// Trnsport-initiator act2 bad version test - let testCase3() = - let outboundPeer = getOutBoundPeerForInitiatorTestVectors() - let actTwo = hex.DecodeData("0102466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276e2470b93aac583c9ef6eafca3f730ae") - Expect.isError (Result.ToFSharpCoreResult (outboundPeer |> PeerChannelEncryptor.processActTwo actTwo ourNodeId)) "" - - testCase3() - - /// Transport-initiator act2 bad key serialization test - let testCase4() = - let outboundPeer = getOutBoundPeerForInitiatorTestVectors() - let actTwo = hex.DecodeData("0004466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276e2470b93aac583c9ef6eafca3f730ae") - Expect.isError (Result.ToFSharpCoreResult (outboundPeer |> PeerChannelEncryptor.processActTwo actTwo ourNodeId)) "" - testCase4() - - /// transport-initiator act2 bad MAC test - let testCase5() = - let outboundPeer = getOutBoundPeerForInitiatorTestVectors() - let actTwo = hex.DecodeData("0002466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276e2470b93aac583c9ef6eafca3f730af") - Expect.isError(Result.ToFSharpCoreResult (outboundPeer |> PeerChannelEncryptor.processActTwo actTwo ourNodeId)) "" - testCase5() - - testCase "noise responder test vectors" <| fun _ -> - let ourNodeSecret = hex.DecodeData("2121212121212121212121212121212121212121212121212121212121212121") |> fun h -> new Key(h) - let ourEphemeral = hex.DecodeData("2222222222222222222222222222222222222222222222222222222222222222") |> fun h -> new Key(h) - - /// Transport - responder successful handshake - let _testCase1 = - let inboundPeer1 = ourNodeSecret |> PeerChannelEncryptor.newInBound - let actOne = hex.DecodeData("00036360e856310ce5d294e8be33fc807077dc56ac80d95d9cd4ddbd21325eff73f70df6086551151f58b8afe6c195782c6a") - let actTwoExpected = hex.DecodeData("0002466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276e2470b93aac583c9ef6eafca3f730ae") - let actualR = inboundPeer1 |> PeerChannelEncryptor.processActOneWithEphemeralKey actOne ourNodeSecret ourEphemeral - Expect.isOk (Result.ToFSharpCoreResult actualR) "" - let actual, inboundPeer2 = actualR |> Result.deref - Expect.equal (actual) (actTwoExpected) "" - - let actThree = hex.DecodeData("00b9e3a702e93e3a9948c2ed6e5fd7590a6e1c3a0344cfc9d5b57357049aa22355361aa02e55a8fc28fef5bd6d71ad0c38228dc68b1c466263b47fdf31e560e139ba") - let theirNodeIdExpected = hex.DecodeData("034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa") |> PubKey |> NodeId - let actualRR = inboundPeer2 |> PeerChannelEncryptor.processActThree(actThree) - Expect.isOk (Result.ToFSharpCoreResult actualRR) "" - let actual, nextState = actualRR |> Result.deref - Expect.equal (actual) (theirNodeIdExpected) "" - match nextState.NoiseState with - | Finished { SK = sk; SN = sn; SCK = sck; RK = rk; RN = rn; RCK = rck } -> - Expect.equal (sk) (hex.DecodeData("bb9020b8965f4df047e07f955f3c4b88418984aadc5cdb35096b9ea8fa5c3442") |> uint256) "" - Expect.equal sn 0UL "" - Expect.equal (sck) (hex.DecodeData("919219dbb2920afa8db80f9a51787a840bcf111ed8d588caf9ab4be716e42b01") |> uint256) "" - Expect.equal (rk) (hex.DecodeData("969ab31b4d288cedf6218839b27a3e2140827047f2c0f01bf5c04435d43511a9") |> uint256) "" - Expect.equal (rn) (0UL) "" - Expect.equal (rck) (hex.DecodeData("919219dbb2920afa8db80f9a51787a840bcf111ed8d588caf9ab4be716e42b01") |> uint256) "" - | _ -> failwith "Fail: nextState.NoiseState not finished" - - /// Transport-responder act1 short read test - let _testCase2 = - let inboundPeer = PeerChannelEncryptor.newInBound( ourNodeSecret) - let actOne = hex.DecodeData("00036360e856310ce5d294e8be33fc807077dc56ac80d95d9cd4ddbd21325eff73f70df6086551151f58b8afe6c195782c") - Expect.throwsT(fun _ -> PeerChannelEncryptor.processActOneWithKey actOne ourNodeSecret inboundPeer |> ignore) "did not throw error" - - /// Transport-responder act1 bad version test - let _testCase3 = - let inboundPeer = PeerChannelEncryptor.newInBound( ourNodeSecret) - let actOne = "01036360e856310ce5d294e8be33fc807077dc56ac80d95d9cd4ddbd21325eff73f70df6086551151f58b8afe6c195782c6a" |> hex.DecodeData - let actualR = inboundPeer |> PeerChannelEncryptor.processActOneWithEphemeralKey actOne ourNodeSecret ourEphemeral - Expect.isError (Result.ToFSharpCoreResult actualR) "" - - /// Transport responder act1 babd key serialization test - let _testCase4 = - let inboundPeer = ourNodeSecret |> PeerChannelEncryptor.newInBound - let actOne = hex.DecodeData("00046360e856310ce5d294e8be33fc807077dc56ac80d95d9cd4ddbd21325eff73f70df6086551151f58b8afe6c195782c6a") - let actualR = inboundPeer |> PeerChannelEncryptor.processActOneWithEphemeralKey actOne ourNodeSecret ourEphemeral - Expect.isError (Result.ToFSharpCoreResult actualR) "" - - /// Transport-responder act1 bad MAC test - let _testCase5 = - let inboundPeer = ourNodeSecret |> PeerChannelEncryptor.newInBound - let actOne = hex.DecodeData("00036360e856310ce5d294e8be33fc807077dc56ac80d95d9cd4ddbd21325eff73f70df6086551151f58b8afe6c195782c6b") - let actualRR = inboundPeer |> PeerChannelEncryptor.processActOneWithEphemeralKey actOne ourNodeSecret ourEphemeral - Expect.isError (Result.ToFSharpCoreResult actualRR) "" - - /// Transport responder act3 bad version test - let _testCase6 = - let inboundPeer = ourNodeSecret |> PeerChannelEncryptor.newInBound - let actOne = hex.DecodeData("00036360e856310ce5d294e8be33fc807077dc56ac80d95d9cd4ddbd21325eff73f70df6086551151f58b8afe6c195782c6a") - let _actual1, inboundPeer2 = inboundPeer |> PeerChannelEncryptor.processActOneWithEphemeralKey actOne ourNodeSecret ourEphemeral |> Result.deref - - let actThree = hex.DecodeData("01b9e3a702e93e3a9948c2ed6e5fd7590a6e1c3a0344cfc9d5b57357049aa22355361aa02e55a8fc28fef5bd6d71ad0c38228dc68b1c466263b47fdf31e560e139ba") - let actualR = inboundPeer2 |> PeerChannelEncryptor.processActThree actThree - Expect.isError (Result.ToFSharpCoreResult actualR) "" - - /// Transport responder act3 short read test - let _testCase7 = - let inboundPeer = PeerChannelEncryptor.newInBound( ourNodeSecret) - let actOne = hex.DecodeData("00036360e856310ce5d294e8be33fc807077dc56ac80d95d9cd4ddbd21325eff73f70df6086551151f58b8afe6c195782c6a") - let _, inboundPeer2 = inboundPeer |> PeerChannelEncryptor.processActOneWithEphemeralKey actOne ourNodeSecret ourEphemeral |> Result.deref - let actThree = hex.DecodeData("00b9e3a702e93e3a9948c2ed6e5fd7590a6e1c3a0344cfc9d5b57357049aa22355361aa02e55a8fc28fef5bd6d71ad0c38228dc68b1c466263b47fdf31e560e139") - Expect.throwsT(fun _ -> - inboundPeer2 |> PeerChannelEncryptor.processActThree actThree |> ignore - ) "did not throw error" - - /// Transport-responder act3 bad MAC for ciphertext - let _testCase8 = - let inboundPeer = PeerChannelEncryptor.newInBound ourNodeSecret - let actOne = hex.DecodeData("00036360e856310ce5d294e8be33fc807077dc56ac80d95d9cd4ddbd21325eff73f70df6086551151f58b8afe6c195782c6a") - let expectedActOneResult = hex.DecodeData("0002466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276e2470b93aac583c9ef6eafca3f730ae") - let actualActOneResult, inboundPeer2 = inboundPeer |> PeerChannelEncryptor.processActOneWithEphemeralKey actOne ourNodeSecret ourEphemeral |> Result.deref - Expect.equal actualActOneResult expectedActOneResult "" - let actThree = hex.DecodeData("00c9e3a702e93e3a9948c2ed6e5fd7590a6e1c3a0344cfc9d5b57357049aa22355361aa02e55a8fc28fef5bd6d71ad0c38228dc68b1c466263b47fdf31e560e139ba") - let r = PeerChannelEncryptor.processActThree actThree inboundPeer2 - Expect.isError (Result.ToFSharpCoreResult r) "" - - /// transport-responder act3 bad rx - let _testCase9 = - let inboundPeer = PeerChannelEncryptor.newInBound( ourNodeSecret) - let actOne = hex.DecodeData("00036360e856310ce5d294e8be33fc807077dc56ac80d95d9cd4ddbd21325eff73f70df6086551151f58b8afe6c195782c6a") - let actualActOneResult, inboundPeer2 = inboundPeer |> PeerChannelEncryptor.processActOneWithEphemeralKey actOne ourNodeSecret ourEphemeral |> Result.deref - let expectedActOneResult = hex.DecodeData("0002466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276e2470b93aac583c9ef6eafca3f730ae") - Expect.equal (actualActOneResult) (expectedActOneResult) "" - - let actThree = hex.DecodeData("00bfe3a702e93e3a9948c2ed6e5fd7590a6e1c3a0344cfc9d5b57357049aa2235536ad09a8ee351870c2bb7f78b754a26c6cef79a98d25139c856d7efd252c2ae73c") - let r = PeerChannelEncryptor.processActThree actThree inboundPeer2 - Expect.isError (Result.ToFSharpCoreResult r) "" - - /// transport-responder act3 abd MAC text - let _testCase10 = - let inboundPeer = PeerChannelEncryptor.newInBound( ourNodeSecret) - let actOne = hex.DecodeData("00036360e856310ce5d294e8be33fc807077dc56ac80d95d9cd4ddbd21325eff73f70df6086551151f58b8afe6c195782c6a") - let expectedActOneResult = hex.DecodeData("0002466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276e2470b93aac583c9ef6eafca3f730ae") - let actualActOneResult, inboundPeer2 = inboundPeer |> PeerChannelEncryptor.processActOneWithEphemeralKey actOne ourNodeSecret ourEphemeral |> Result.deref - Expect.equal (actualActOneResult) (expectedActOneResult) "" - - let actThree = hex.DecodeData("00b9e3a702e93e3a9948c2ed6e5fd7590a6e1c3a0344cfc9d5b57357049aa22355361aa02e55a8fc28fef5bd6d71ad0c38228dc68b1c466263b47fdf31e560e139bb") - let r = PeerChannelEncryptor.processActThree actThree inboundPeer2 - Expect.isError (Result.ToFSharpCoreResult r) "" - () - - testCase "message encryption decryption test vectors" <| fun _ -> - let mutable outboundPeer = getOutBoundPeerForInitiatorTestVectors() - let _testCase1 = - let ourNodeId = "1111111111111111111111111111111111111111111111111111111111111111" |> hex.DecodeData |> fun h -> new Key(h) - let actTwo = "0002466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276e2470b93aac583c9ef6eafca3f730ae" |> hex.DecodeData - let (actualActTwoResult, _), outboundPeer2 = outboundPeer |> PeerChannelEncryptor.processActTwo actTwo ourNodeId |> Result.deref - let expectedActTwoResult = "00b9e3a702e93e3a9948c2ed6e5fd7590a6e1c3a0344cfc9d5b57357049aa22355361aa02e55a8fc28fef5bd6d71ad0c38228dc68b1c466263b47fdf31e560e139ba" |> hex.DecodeData - Expect.equal actualActTwoResult expectedActTwoResult "" - match outboundPeer2.NoiseState with - | Finished { SK = sk; SN = sn; SCK = sck; RK = rk; RN = rn; RCK = rck } -> - Expect.equal sk (hex.DecodeData("969ab31b4d288cedf6218839b27a3e2140827047f2c0f01bf5c04435d43511a9") |> uint256) "" - Expect.equal sn (0UL) "" - Expect.equal sck ("919219dbb2920afa8db80f9a51787a840bcf111ed8d588caf9ab4be716e42b01" |> hex.DecodeData |> uint256) "" - Expect.equal rk ("bb9020b8965f4df047e07f955f3c4b88418984aadc5cdb35096b9ea8fa5c3442" |> hex.DecodeData |> uint256) "" - Expect.equal rn 0UL "" - Expect.equal rck ("919219dbb2920afa8db80f9a51787a840bcf111ed8d588caf9ab4be716e42b01" |> hex.DecodeData |> uint256) "" - outboundPeer <- outboundPeer2 - | _ -> failwith "Fail: outboundPeer2.NoiseState not Finished" - - let ourNodeId = hex.DecodeData("2121212121212121212121212121212121212121212121212121212121212121") |> fun h -> new Key(h) - let mutable inboundPeer = PeerChannelEncryptor.newInBound ourNodeId - /// transport-responder successful handshake - let _testCase2 = - let ourEphemeral = hex.DecodeData("2222222222222222222222222222222222222222222222222222222222222222") |> fun h -> new Key(h) - let actOne = hex.DecodeData("00036360e856310ce5d294e8be33fc807077dc56ac80d95d9cd4ddbd21325eff73f70df6086551151f58b8afe6c195782c6a") - let expectedActOneResult = "0002466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276e2470b93aac583c9ef6eafca3f730ae" |> hex.DecodeData - let actualActOneResult, inboundPeer2 = inboundPeer |> PeerChannelEncryptor.processActOneWithEphemeralKey actOne ourNodeId ourEphemeral |> Result.deref - Expect.equal (actualActOneResult) (expectedActOneResult) "" - let actThree = "00b9e3a702e93e3a9948c2ed6e5fd7590a6e1c3a0344cfc9d5b57357049aa22355361aa02e55a8fc28fef5bd6d71ad0c38228dc68b1c466263b47fdf31e560e139ba" |> hex.DecodeData - let expectedActThreeResult = "034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa" |> hex.DecodeData |> PubKey |> NodeId - let actualActThreeResult, inboundPeer3 = PeerChannelEncryptor.processActThree (actThree) inboundPeer2 |> Result.deref - Expect.equal actualActThreeResult expectedActThreeResult "" - - match inboundPeer3.NoiseState with - | Finished { SK = sk; SN = sn; SCK = sck; RK= rk; RN = rn; RCK = rck} -> - Expect.equal sk (hex.DecodeData("bb9020b8965f4df047e07f955f3c4b88418984aadc5cdb35096b9ea8fa5c3442") |> uint256) "" - Expect.equal sn 0UL "" - Expect.equal sck ("919219dbb2920afa8db80f9a51787a840bcf111ed8d588caf9ab4be716e42b01" |> hex.DecodeData |> uint256) "" - Expect.equal rk ("969ab31b4d288cedf6218839b27a3e2140827047f2c0f01bf5c04435d43511a9" |> hex.DecodeData |> uint256) "" - Expect.equal rn 0UL "" - Expect.equal rck ("919219dbb2920afa8db80f9a51787a840bcf111ed8d588caf9ab4be716e42b01" |> hex.DecodeData |> uint256 ) "" - inboundPeer <- inboundPeer3 - | _ -> failwith "Fail: inboundPeer3.NoiseState not Finished" - - let log = fun _s -> () - // eventX >> logger.info - let rec loop (i: int) (localOutBound) (localInbound) = - log (sprintf "%d th iteration ----\n\n" i) - log (sprintf "outbound is %A" localOutBound) - log (sprintf "inbound is %A" localInbound) - log (sprintf "----\n\n") - if i > 1005 then - () - else - let msg = [| 0x68uy; 0x65uy; 0x6cuy; 0x6cuy; 0x6fuy |] - let res, newOutBound = - let instruction = noise { - return! encryptMessage msg; - } - runP instruction localOutBound |> Result.deref - - Expect.equal (res.Length) (5 + 2 * 16 + 2) "" - log(sprintf "new outbound is %A" newOutBound) - let lengthHeader = res.[0..2+16 - 1] - let actualLengthR = - let instruction = noise { - let! header = decryptLengthHeader (lengthHeader) - return header - } - runP instruction localInbound - Expect.isOk (Result.ToFSharpCoreResult actualLengthR) "" - let actualLength, inbound2 = actualLengthR |> Result.deref - log (sprintf "new inbound is %A" inbound2) - let expectedLength = uint16 msg.Length - Expect.equal actualLength (expectedLength) "" + testList + "PeerChannelEncryptorTests" + [ + testCase "noise initiator test vectors" + <| fun _ -> + let ourNodeId = + new Key( + hex.DecodeData( + "1111111111111111111111111111111111111111111111111111111111111111" + ) + ) + + let testCase1() = + let outboundPeer = getOutBoundPeerForInitiatorTestVectors() + + let actTwo = + hex.DecodeData( + "0002466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276e2470b93aac583c9ef6eafca3f730ae" + ) + + let res = + outboundPeer + |> PeerChannelEncryptor.processActTwo actTwo ourNodeId + + Expect.isOk (Result.ToFSharpCoreResult res) "" + + let (actual, _nodeid), nextPCE = res |> Result.deref + + let expected = + "0x00b9e3a702e93e3a9948c2ed6e5fd7590a6e1c3a0344cfc9d5b57357049aa22355361aa02e55a8fc28fef5bd6d71ad0c38228dc68b1c466263b47fdf31e560e139ba" + + Expect.equal (actual.ToHexString()) (expected) "" + + match nextPCE.NoiseState with + | Finished { + SK = sk + SN = sn + SCK = sck + RK = rk + RN = rn + RCK = rck + } -> + Expect.equal + (sk.ToBytes()) + (hex.DecodeData( + "969ab31b4d288cedf6218839b27a3e2140827047f2c0f01bf5c04435d43511a9" + )) + "sk does not match" + + Expect.equal (sn) (0UL) "sn does not match" + + Expect.equal + (sck.ToBytes()) + (hex.DecodeData( + "919219dbb2920afa8db80f9a51787a840bcf111ed8d588caf9ab4be716e42b01" + )) + "sck does not match" + + Expect.equal + (rk.ToBytes()) + (hex.DecodeData( + "bb9020b8965f4df047e07f955f3c4b88418984aadc5cdb35096b9ea8fa5c3442" + )) + "rk does not match" + + Expect.equal (rn) (0UL) "rn does not match" + + Expect.equal + (rck.ToBytes()) + (hex.DecodeData( + "919219dbb2920afa8db80f9a51787a840bcf111ed8d588caf9ab4be716e42b01" + )) + "" + | s -> failwithf "not in finished state %A" s + + testCase1() + + /// Transport-initiator act3 short read test + let testCase2() = + let outboundPeer = getOutBoundPeerForInitiatorTestVectors() + + let actTwo = + hex.DecodeData( + "0002466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276e2470b93aac583c9ef6eafca3f730" + ) + + Expect.throws + (fun _ -> + outboundPeer + |> PeerChannelEncryptor.processActTwo + actTwo + ourNodeId + |> ignore + ) + "" + + testCase2() + + /// Trnsport-initiator act2 bad version test + let testCase3() = + let outboundPeer = getOutBoundPeerForInitiatorTestVectors() + + let actTwo = + hex.DecodeData( + "0102466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276e2470b93aac583c9ef6eafca3f730ae" + ) + + Expect.isError + (Result.ToFSharpCoreResult( + outboundPeer + |> PeerChannelEncryptor.processActTwo + actTwo + ourNodeId + )) + "" + + testCase3() + + /// Transport-initiator act2 bad key serialization test + let testCase4() = + let outboundPeer = getOutBoundPeerForInitiatorTestVectors() + + let actTwo = + hex.DecodeData( + "0004466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276e2470b93aac583c9ef6eafca3f730ae" + ) + + Expect.isError + (Result.ToFSharpCoreResult( + outboundPeer + |> PeerChannelEncryptor.processActTwo + actTwo + ourNodeId + )) + "" + + testCase4() + + /// transport-initiator act2 bad MAC test + let testCase5() = + let outboundPeer = getOutBoundPeerForInitiatorTestVectors() + + let actTwo = + hex.DecodeData( + "0002466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276e2470b93aac583c9ef6eafca3f730af" + ) + + Expect.isError + (Result.ToFSharpCoreResult( + outboundPeer + |> PeerChannelEncryptor.processActTwo + actTwo + ourNodeId + )) + "" + + testCase5() + + testCase "noise responder test vectors" + <| fun _ -> + let ourNodeSecret = + hex.DecodeData( + "2121212121212121212121212121212121212121212121212121212121212121" + ) + |> fun h -> new Key(h) + + let ourEphemeral = + hex.DecodeData( + "2222222222222222222222222222222222222222222222222222222222222222" + ) + |> fun h -> new Key(h) + + /// Transport - responder successful handshake + let _testCase1 = + let inboundPeer1 = + ourNodeSecret |> PeerChannelEncryptor.newInBound + + let actOne = + hex.DecodeData( + "00036360e856310ce5d294e8be33fc807077dc56ac80d95d9cd4ddbd21325eff73f70df6086551151f58b8afe6c195782c6a" + ) + + let actTwoExpected = + hex.DecodeData( + "0002466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276e2470b93aac583c9ef6eafca3f730ae" + ) + + let actualR = + inboundPeer1 + |> PeerChannelEncryptor.processActOneWithEphemeralKey + actOne + ourNodeSecret + ourEphemeral + + Expect.isOk (Result.ToFSharpCoreResult actualR) "" + let actual, inboundPeer2 = actualR |> Result.deref + Expect.equal (actual) (actTwoExpected) "" + + let actThree = + hex.DecodeData( + "00b9e3a702e93e3a9948c2ed6e5fd7590a6e1c3a0344cfc9d5b57357049aa22355361aa02e55a8fc28fef5bd6d71ad0c38228dc68b1c466263b47fdf31e560e139ba" + ) + + let theirNodeIdExpected = + hex.DecodeData( + "034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa" + ) + |> PubKey + |> NodeId + + let actualRR = + inboundPeer2 + |> PeerChannelEncryptor.processActThree(actThree) + + Expect.isOk (Result.ToFSharpCoreResult actualRR) "" + let actual, nextState = actualRR |> Result.deref + Expect.equal (actual) (theirNodeIdExpected) "" + + match nextState.NoiseState with + | Finished { + SK = sk + SN = sn + SCK = sck + RK = rk + RN = rn + RCK = rck + } -> + Expect.equal + (sk) + (hex.DecodeData( + "bb9020b8965f4df047e07f955f3c4b88418984aadc5cdb35096b9ea8fa5c3442" + ) + |> uint256) + "" + + Expect.equal sn 0UL "" + + Expect.equal + (sck) + (hex.DecodeData( + "919219dbb2920afa8db80f9a51787a840bcf111ed8d588caf9ab4be716e42b01" + ) + |> uint256) + "" + + Expect.equal + (rk) + (hex.DecodeData( + "969ab31b4d288cedf6218839b27a3e2140827047f2c0f01bf5c04435d43511a9" + ) + |> uint256) + "" + + Expect.equal (rn) (0UL) "" + + Expect.equal + (rck) + (hex.DecodeData( + "919219dbb2920afa8db80f9a51787a840bcf111ed8d588caf9ab4be716e42b01" + ) + |> uint256) + "" + | _ -> failwith "Fail: nextState.NoiseState not finished" + + /// Transport-responder act1 short read test + let _testCase2 = + let inboundPeer = + PeerChannelEncryptor.newInBound(ourNodeSecret) + + let actOne = + hex.DecodeData( + "00036360e856310ce5d294e8be33fc807077dc56ac80d95d9cd4ddbd21325eff73f70df6086551151f58b8afe6c195782c" + ) + + Expect.throwsT + (fun _ -> + PeerChannelEncryptor.processActOneWithKey + actOne + ourNodeSecret + inboundPeer + |> ignore + ) + "did not throw error" + + /// Transport-responder act1 bad version test + let _testCase3 = + let inboundPeer = + PeerChannelEncryptor.newInBound(ourNodeSecret) + + let actOne = + "01036360e856310ce5d294e8be33fc807077dc56ac80d95d9cd4ddbd21325eff73f70df6086551151f58b8afe6c195782c6a" + |> hex.DecodeData + + let actualR = + inboundPeer + |> PeerChannelEncryptor.processActOneWithEphemeralKey + actOne + ourNodeSecret + ourEphemeral + + Expect.isError (Result.ToFSharpCoreResult actualR) "" + + /// Transport responder act1 babd key serialization test + let _testCase4 = + let inboundPeer = + ourNodeSecret |> PeerChannelEncryptor.newInBound + + let actOne = + hex.DecodeData( + "00046360e856310ce5d294e8be33fc807077dc56ac80d95d9cd4ddbd21325eff73f70df6086551151f58b8afe6c195782c6a" + ) + + let actualR = + inboundPeer + |> PeerChannelEncryptor.processActOneWithEphemeralKey + actOne + ourNodeSecret + ourEphemeral + + Expect.isError (Result.ToFSharpCoreResult actualR) "" + + /// Transport-responder act1 bad MAC test + let _testCase5 = + let inboundPeer = + ourNodeSecret |> PeerChannelEncryptor.newInBound + + let actOne = + hex.DecodeData( + "00036360e856310ce5d294e8be33fc807077dc56ac80d95d9cd4ddbd21325eff73f70df6086551151f58b8afe6c195782c6b" + ) let actualRR = - let instruction = noise { - let! msg = decryptMessage (res.[2 + 16..]) - return msg - } - runP instruction inbound2 - let actual, inbound3 = actualRR |> Result.deref - Expect.equal (actual) (msg) "" - - if i = 0 then - let expected = hex.DecodeData("cf2b30ddf0cf3f80e7c35a6e6730b59fe802473180f396d88a8fb0db8cbcf25d2f214cf9ea1d95") - Expect.equal res expected "" - if i = 1 then - let expected = hex.DecodeData("72887022101f0b6753e0c7de21657d35a4cb2a1f5cde2650528bbc8f837d0f0d7ad833b1a256a1") - Expect.equal res expected "" - if i = 500 then - let expected = hex.DecodeData("178cb9d7387190fa34db9c2d50027d21793c9bc2d40b1e14dcf30ebeeeb220f48364f7a4c68bf8") - Expect.equal res expected "" - if i = 501 then - let expected = hex.DecodeData("1b186c57d44eb6de4c057c49940d79bb838a145cb528d6e8fd26dbe50a60ca2c104b56b60e45bd") - Expect.equal res expected "" - if i = 1000 then - let expected = hex.DecodeData("4a2f3cc3b5e78ddb83dcb426d9863d9d9a723b0337c89dd0b005d89f8d3c05c52b76b29b740f09") - Expect.equal res expected "" - if i = 1001 then - let expected = hex.DecodeData("2ecd8c8a5629d0d02ab457a0fdd0f7b90a192cd46be5ecb6ca570bfc5e268338b1a16cf4ef2d36") - Expect.equal res expected "" - - loop (i + 1) newOutBound inbound3 - - loop 0 outboundPeer inboundPeer - ] \ No newline at end of file + inboundPeer + |> PeerChannelEncryptor.processActOneWithEphemeralKey + actOne + ourNodeSecret + ourEphemeral + + Expect.isError (Result.ToFSharpCoreResult actualRR) "" + + /// Transport responder act3 bad version test + let _testCase6 = + let inboundPeer = + ourNodeSecret |> PeerChannelEncryptor.newInBound + + let actOne = + hex.DecodeData( + "00036360e856310ce5d294e8be33fc807077dc56ac80d95d9cd4ddbd21325eff73f70df6086551151f58b8afe6c195782c6a" + ) + + let _actual1, inboundPeer2 = + inboundPeer + |> PeerChannelEncryptor.processActOneWithEphemeralKey + actOne + ourNodeSecret + ourEphemeral + |> Result.deref + + let actThree = + hex.DecodeData( + "01b9e3a702e93e3a9948c2ed6e5fd7590a6e1c3a0344cfc9d5b57357049aa22355361aa02e55a8fc28fef5bd6d71ad0c38228dc68b1c466263b47fdf31e560e139ba" + ) + + let actualR = + inboundPeer2 + |> PeerChannelEncryptor.processActThree actThree + + Expect.isError (Result.ToFSharpCoreResult actualR) "" + + /// Transport responder act3 short read test + let _testCase7 = + let inboundPeer = + PeerChannelEncryptor.newInBound(ourNodeSecret) + + let actOne = + hex.DecodeData( + "00036360e856310ce5d294e8be33fc807077dc56ac80d95d9cd4ddbd21325eff73f70df6086551151f58b8afe6c195782c6a" + ) + + let _, inboundPeer2 = + inboundPeer + |> PeerChannelEncryptor.processActOneWithEphemeralKey + actOne + ourNodeSecret + ourEphemeral + |> Result.deref + + let actThree = + hex.DecodeData( + "00b9e3a702e93e3a9948c2ed6e5fd7590a6e1c3a0344cfc9d5b57357049aa22355361aa02e55a8fc28fef5bd6d71ad0c38228dc68b1c466263b47fdf31e560e139" + ) + + Expect.throwsT + (fun _ -> + inboundPeer2 + |> PeerChannelEncryptor.processActThree actThree + |> ignore + ) + "did not throw error" + + /// Transport-responder act3 bad MAC for ciphertext + let _testCase8 = + let inboundPeer = + PeerChannelEncryptor.newInBound ourNodeSecret + + let actOne = + hex.DecodeData( + "00036360e856310ce5d294e8be33fc807077dc56ac80d95d9cd4ddbd21325eff73f70df6086551151f58b8afe6c195782c6a" + ) + + let expectedActOneResult = + hex.DecodeData( + "0002466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276e2470b93aac583c9ef6eafca3f730ae" + ) + + let actualActOneResult, inboundPeer2 = + inboundPeer + |> PeerChannelEncryptor.processActOneWithEphemeralKey + actOne + ourNodeSecret + ourEphemeral + |> Result.deref + + Expect.equal actualActOneResult expectedActOneResult "" + + let actThree = + hex.DecodeData( + "00c9e3a702e93e3a9948c2ed6e5fd7590a6e1c3a0344cfc9d5b57357049aa22355361aa02e55a8fc28fef5bd6d71ad0c38228dc68b1c466263b47fdf31e560e139ba" + ) + + let r = + PeerChannelEncryptor.processActThree + actThree + inboundPeer2 + + Expect.isError (Result.ToFSharpCoreResult r) "" + + /// transport-responder act3 bad rx + let _testCase9 = + let inboundPeer = + PeerChannelEncryptor.newInBound(ourNodeSecret) + + let actOne = + hex.DecodeData( + "00036360e856310ce5d294e8be33fc807077dc56ac80d95d9cd4ddbd21325eff73f70df6086551151f58b8afe6c195782c6a" + ) + + let actualActOneResult, inboundPeer2 = + inboundPeer + |> PeerChannelEncryptor.processActOneWithEphemeralKey + actOne + ourNodeSecret + ourEphemeral + |> Result.deref + + let expectedActOneResult = + hex.DecodeData( + "0002466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276e2470b93aac583c9ef6eafca3f730ae" + ) + + Expect.equal (actualActOneResult) (expectedActOneResult) "" + + let actThree = + hex.DecodeData( + "00bfe3a702e93e3a9948c2ed6e5fd7590a6e1c3a0344cfc9d5b57357049aa2235536ad09a8ee351870c2bb7f78b754a26c6cef79a98d25139c856d7efd252c2ae73c" + ) + + let r = + PeerChannelEncryptor.processActThree + actThree + inboundPeer2 + + Expect.isError (Result.ToFSharpCoreResult r) "" + + /// transport-responder act3 abd MAC text + let _testCase10 = + let inboundPeer = + PeerChannelEncryptor.newInBound(ourNodeSecret) + + let actOne = + hex.DecodeData( + "00036360e856310ce5d294e8be33fc807077dc56ac80d95d9cd4ddbd21325eff73f70df6086551151f58b8afe6c195782c6a" + ) + + let expectedActOneResult = + hex.DecodeData( + "0002466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276e2470b93aac583c9ef6eafca3f730ae" + ) + + let actualActOneResult, inboundPeer2 = + inboundPeer + |> PeerChannelEncryptor.processActOneWithEphemeralKey + actOne + ourNodeSecret + ourEphemeral + |> Result.deref + + Expect.equal (actualActOneResult) (expectedActOneResult) "" + + let actThree = + hex.DecodeData( + "00b9e3a702e93e3a9948c2ed6e5fd7590a6e1c3a0344cfc9d5b57357049aa22355361aa02e55a8fc28fef5bd6d71ad0c38228dc68b1c466263b47fdf31e560e139bb" + ) + + let r = + PeerChannelEncryptor.processActThree + actThree + inboundPeer2 + + Expect.isError (Result.ToFSharpCoreResult r) "" + + () + + testCase "message encryption decryption test vectors" + <| fun _ -> + let mutable outboundPeer = + getOutBoundPeerForInitiatorTestVectors() + + let _testCase1 = + let ourNodeId = + "1111111111111111111111111111111111111111111111111111111111111111" + |> hex.DecodeData + |> fun h -> new Key(h) + + let actTwo = + "0002466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276e2470b93aac583c9ef6eafca3f730ae" + |> hex.DecodeData + + let (actualActTwoResult, _), outboundPeer2 = + outboundPeer + |> PeerChannelEncryptor.processActTwo actTwo ourNodeId + |> Result.deref + + let expectedActTwoResult = + "00b9e3a702e93e3a9948c2ed6e5fd7590a6e1c3a0344cfc9d5b57357049aa22355361aa02e55a8fc28fef5bd6d71ad0c38228dc68b1c466263b47fdf31e560e139ba" + |> hex.DecodeData + + Expect.equal actualActTwoResult expectedActTwoResult "" + + match outboundPeer2.NoiseState with + | Finished { + SK = sk + SN = sn + SCK = sck + RK = rk + RN = rn + RCK = rck + } -> + Expect.equal + sk + (hex.DecodeData( + "969ab31b4d288cedf6218839b27a3e2140827047f2c0f01bf5c04435d43511a9" + ) + |> uint256) + "" + + Expect.equal sn (0UL) "" + + Expect.equal + sck + ("919219dbb2920afa8db80f9a51787a840bcf111ed8d588caf9ab4be716e42b01" + |> hex.DecodeData + |> uint256) + "" + + Expect.equal + rk + ("bb9020b8965f4df047e07f955f3c4b88418984aadc5cdb35096b9ea8fa5c3442" + |> hex.DecodeData + |> uint256) + "" + + Expect.equal rn 0UL "" + + Expect.equal + rck + ("919219dbb2920afa8db80f9a51787a840bcf111ed8d588caf9ab4be716e42b01" + |> hex.DecodeData + |> uint256) + "" + + outboundPeer <- outboundPeer2 + | _ -> + failwith "Fail: outboundPeer2.NoiseState not Finished" + + let ourNodeId = + hex.DecodeData( + "2121212121212121212121212121212121212121212121212121212121212121" + ) + |> fun h -> new Key(h) + + let mutable inboundPeer = + PeerChannelEncryptor.newInBound ourNodeId + + /// transport-responder successful handshake + let _testCase2 = + let ourEphemeral = + hex.DecodeData( + "2222222222222222222222222222222222222222222222222222222222222222" + ) + |> fun h -> new Key(h) + + let actOne = + hex.DecodeData( + "00036360e856310ce5d294e8be33fc807077dc56ac80d95d9cd4ddbd21325eff73f70df6086551151f58b8afe6c195782c6a" + ) + + let expectedActOneResult = + "0002466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276e2470b93aac583c9ef6eafca3f730ae" + |> hex.DecodeData + + let actualActOneResult, inboundPeer2 = + inboundPeer + |> PeerChannelEncryptor.processActOneWithEphemeralKey + actOne + ourNodeId + ourEphemeral + |> Result.deref + + Expect.equal (actualActOneResult) (expectedActOneResult) "" + + let actThree = + "00b9e3a702e93e3a9948c2ed6e5fd7590a6e1c3a0344cfc9d5b57357049aa22355361aa02e55a8fc28fef5bd6d71ad0c38228dc68b1c466263b47fdf31e560e139ba" + |> hex.DecodeData + + let expectedActThreeResult = + "034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa" + |> hex.DecodeData + |> PubKey + |> NodeId + + let actualActThreeResult, inboundPeer3 = + PeerChannelEncryptor.processActThree + (actThree) + inboundPeer2 + |> Result.deref + + Expect.equal actualActThreeResult expectedActThreeResult "" + + match inboundPeer3.NoiseState with + | Finished { + SK = sk + SN = sn + SCK = sck + RK = rk + RN = rn + RCK = rck + } -> + Expect.equal + sk + (hex.DecodeData( + "bb9020b8965f4df047e07f955f3c4b88418984aadc5cdb35096b9ea8fa5c3442" + ) + |> uint256) + "" + + Expect.equal sn 0UL "" + + Expect.equal + sck + ("919219dbb2920afa8db80f9a51787a840bcf111ed8d588caf9ab4be716e42b01" + |> hex.DecodeData + |> uint256) + "" + + Expect.equal + rk + ("969ab31b4d288cedf6218839b27a3e2140827047f2c0f01bf5c04435d43511a9" + |> hex.DecodeData + |> uint256) + "" + + Expect.equal rn 0UL "" + + Expect.equal + rck + ("919219dbb2920afa8db80f9a51787a840bcf111ed8d588caf9ab4be716e42b01" + |> hex.DecodeData + |> uint256) + "" + + inboundPeer <- inboundPeer3 + | _ -> failwith "Fail: inboundPeer3.NoiseState not Finished" + + let log = fun _s -> () + // eventX >> logger.info + let rec loop (i: int) localOutBound localInbound = + log(sprintf "%d th iteration ----\n\n" i) + log(sprintf "outbound is %A" localOutBound) + log(sprintf "inbound is %A" localInbound) + log(sprintf "----\n\n") + + if i > 1005 then + () + else + let msg = + [| + 0x68uy + 0x65uy + 0x6cuy + 0x6cuy + 0x6fuy + |] + + let res, newOutBound = + let instruction = + noise { return! encryptMessage msg } + + runP instruction localOutBound |> Result.deref + + Expect.equal (res.Length) (5 + 2 * 16 + 2) "" + log(sprintf "new outbound is %A" newOutBound) + let lengthHeader = res.[0 .. 2 + 16 - 1] + + let actualLengthR = + let instruction = + noise { + let! header = + decryptLengthHeader(lengthHeader) + + return header + } + + runP instruction localInbound + + Expect.isOk (Result.ToFSharpCoreResult actualLengthR) "" + + let actualLength, inbound2 = + actualLengthR |> Result.deref + + log(sprintf "new inbound is %A" inbound2) + let expectedLength = uint16 msg.Length + Expect.equal actualLength (expectedLength) "" + + let actualRR = + let instruction = + noise { + let! msg = decryptMessage(res.[2 + 16 ..]) + return msg + } + + runP instruction inbound2 + + let actual, inbound3 = actualRR |> Result.deref + Expect.equal (actual) (msg) "" + + if i = 0 then + let expected = + hex.DecodeData( + "cf2b30ddf0cf3f80e7c35a6e6730b59fe802473180f396d88a8fb0db8cbcf25d2f214cf9ea1d95" + ) + + Expect.equal res expected "" + + if i = 1 then + let expected = + hex.DecodeData( + "72887022101f0b6753e0c7de21657d35a4cb2a1f5cde2650528bbc8f837d0f0d7ad833b1a256a1" + ) + + Expect.equal res expected "" + + if i = 500 then + let expected = + hex.DecodeData( + "178cb9d7387190fa34db9c2d50027d21793c9bc2d40b1e14dcf30ebeeeb220f48364f7a4c68bf8" + ) + + Expect.equal res expected "" + + if i = 501 then + let expected = + hex.DecodeData( + "1b186c57d44eb6de4c057c49940d79bb838a145cb528d6e8fd26dbe50a60ca2c104b56b60e45bd" + ) + + Expect.equal res expected "" + + if i = 1000 then + let expected = + hex.DecodeData( + "4a2f3cc3b5e78ddb83dcb426d9863d9d9a723b0337c89dd0b005d89f8d3c05c52b76b29b740f09" + ) + + Expect.equal res expected "" + + if i = 1001 then + let expected = + hex.DecodeData( + "2ecd8c8a5629d0d02ab457a0fdd0f7b90a192cd46be5ecb6ca570bfc5e268338b1a16cf4ef2d36" + ) + + Expect.equal res expected "" + + loop (i + 1) newOutBound inbound3 + + loop 0 outboundPeer inboundPeer + ] diff --git a/tests/DotNetLightning.Core.Tests/PerCommitmentSecretStoreTests.fs b/tests/DotNetLightning.Core.Tests/PerCommitmentSecretStoreTests.fs index 5e14c8a26..7d07ac383 100644 --- a/tests/DotNetLightning.Core.Tests/PerCommitmentSecretStoreTests.fs +++ b/tests/DotNetLightning.Core.Tests/PerCommitmentSecretStoreTests.fs @@ -10,269 +10,313 @@ open ResultUtils.Portability [] let tests = - let insert (commitmentNumber: uint64) - (key: string) - (perCommitmentSecretStore: PerCommitmentSecretStore) - : Result = + let insert + (commitmentNumber: uint64) + (key: string) + (perCommitmentSecretStore: PerCommitmentSecretStore) + : Result = let hex = NBitcoin.DataEncoders.HexEncoder() - let commitmentNumber = CommitmentNumber <| UInt48.FromUInt64 commitmentNumber - let perCommitmentSecret = key |> hex.DecodeData |> fun h -> new Key(h) |> PerCommitmentSecret - perCommitmentSecretStore.InsertPerCommitmentSecret commitmentNumber perCommitmentSecret - let insertUnwrap (commitmentNumber: uint64) - (key: string) - (perCommitmentSecretStore: PerCommitmentSecretStore) - : PerCommitmentSecretStore = + let commitmentNumber = + CommitmentNumber <| UInt48.FromUInt64 commitmentNumber + + let perCommitmentSecret = + key + |> hex.DecodeData + |> fun h -> new Key(h) |> PerCommitmentSecret + + perCommitmentSecretStore.InsertPerCommitmentSecret + commitmentNumber + perCommitmentSecret + + let insertUnwrap + (commitmentNumber: uint64) + (key: string) + (perCommitmentSecretStore: PerCommitmentSecretStore) + : PerCommitmentSecretStore = Result.deref <| insert commitmentNumber key perCommitmentSecretStore - testList "Per commitment secret store tests" [ - testCase "insert secret correct sequence" <| fun _ -> - let _perCommitmentSecretStore: PerCommitmentSecretStore = - PerCommitmentSecretStore() - |> insertUnwrap - 281474976710655UL - "7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc" - |> insertUnwrap - 281474976710654UL - "c7518c8ae4660ed02894df8976fa1a3659c1a8b4b5bec0c4b872abeba4cb8964" - |> insertUnwrap - 281474976710653UL - "2273e227a5b7449b6e70f1fb4652864038b1cbf9cd7c043a7d6456b7fc275ad8" - |> insertUnwrap - 281474976710652UL - "27cddaa5624534cb6cb9d7da077cf2b22ab21e9b506fd4998a51d54502e99116" - |> insertUnwrap - 281474976710651UL - "c65716add7aa98ba7acb236352d665cab17345fe45b55fb879ff80e6bd0c41dd" - |> insertUnwrap - 281474976710650UL - "969660042a28f32d9be17344e09374b379962d03db1574df5a8a5a47e19ce3f2" - |> insertUnwrap - 281474976710649UL - "a5a64476122ca0925fb344bdc1854c1c0a59fc614298e50a33e331980a220f32" - |> insertUnwrap - 281474976710648UL - "05cde6323d949933f7f7b78776bcc1ea6d9b31447732e3802e1f7ac44b650e17" - () + testList + "Per commitment secret store tests" + [ + testCase "insert secret correct sequence" + <| fun _ -> + let _perCommitmentSecretStore: PerCommitmentSecretStore = + PerCommitmentSecretStore() + |> insertUnwrap + 281474976710655UL + "7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc" + |> insertUnwrap + 281474976710654UL + "c7518c8ae4660ed02894df8976fa1a3659c1a8b4b5bec0c4b872abeba4cb8964" + |> insertUnwrap + 281474976710653UL + "2273e227a5b7449b6e70f1fb4652864038b1cbf9cd7c043a7d6456b7fc275ad8" + |> insertUnwrap + 281474976710652UL + "27cddaa5624534cb6cb9d7da077cf2b22ab21e9b506fd4998a51d54502e99116" + |> insertUnwrap + 281474976710651UL + "c65716add7aa98ba7acb236352d665cab17345fe45b55fb879ff80e6bd0c41dd" + |> insertUnwrap + 281474976710650UL + "969660042a28f32d9be17344e09374b379962d03db1574df5a8a5a47e19ce3f2" + |> insertUnwrap + 281474976710649UL + "a5a64476122ca0925fb344bdc1854c1c0a59fc614298e50a33e331980a220f32" + |> insertUnwrap + 281474976710648UL + "05cde6323d949933f7f7b78776bcc1ea6d9b31447732e3802e1f7ac44b650e17" + + () + + testCase "insert secret 1 incorrect" + <| fun _ -> + let res = + PerCommitmentSecretStore() + |> insertUnwrap + 281474976710655UL + "02a40c85b6f28da08dfdbe0926c53fab2de6d28c10301f8f7c4073d5e42e3148" + |> insert + 281474976710654UL + "c7518c8ae4660ed02894df8976fa1a3659c1a8b4b5bec0c4b872abeba4cb8964" + + match res with + | Error(SecretMismatch(previous, current)) when + previous.Index().UInt64 = 281474976710655UL + && current.Index().UInt64 = 281474976710654UL + -> + () + | _ -> failwith <| sprintf "unexpected result: %A" res + + testCase "insert secret 2 incorrect" + <| fun _ -> + let res = + PerCommitmentSecretStore() + |> insertUnwrap + 281474976710655UL + "02a40c85b6f28da08dfdbe0926c53fab2de6d28c10301f8f7c4073d5e42e3148" + |> insertUnwrap + 281474976710654UL + "dddc3a8d14fddf2b68fa8c7fbad2748274937479dd0f8930d5ebb4ab6bd866a3" + |> insertUnwrap + 281474976710653UL + "2273e227a5b7449b6e70f1fb4652864038b1cbf9cd7c043a7d6456b7fc275ad8" + |> insert + 281474976710652UL + "27cddaa5624534cb6cb9d7da077cf2b22ab21e9b506fd4998a51d54502e99116" + + match res with + | Error(SecretMismatch(previous, current)) when + previous.Index().UInt64 = 281474976710654UL + && current.Index().UInt64 = 281474976710652UL + -> + () + | _ -> failwith <| sprintf "unexpected result: %A" res + + testCase "insert secret 3 incorrect" + <| fun _ -> + let res = + PerCommitmentSecretStore() + |> insertUnwrap + 281474976710655UL + "7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc" + |> insertUnwrap + 281474976710654UL + "c7518c8ae4660ed02894df8976fa1a3659c1a8b4b5bec0c4b872abeba4cb8964" + |> insertUnwrap + 281474976710653UL + "c51a18b13e8527e579ec56365482c62f180b7d5760b46e9477dae59e87ed423a" + |> insert + 281474976710652UL + "27cddaa5624534cb6cb9d7da077cf2b22ab21e9b506fd4998a51d54502e99116" + + match res with + | Error(SecretMismatch(previous, current)) when + previous.Index().UInt64 = 281474976710653UL + && current.Index().UInt64 = 281474976710652UL + -> + () + | _ -> failwith <| sprintf "unexpected result: %A" res + + testCase "insert secret 4 incorrect" + <| fun _ -> + let res = + PerCommitmentSecretStore() + |> insertUnwrap + 281474976710655UL + "02a40c85b6f28da08dfdbe0926c53fab2de6d28c10301f8f7c4073d5e42e3148" + |> insertUnwrap + 281474976710654UL + "dddc3a8d14fddf2b68fa8c7fbad2748274937479dd0f8930d5ebb4ab6bd866a3" + |> insertUnwrap + 281474976710653UL + "c51a18b13e8527e579ec56365482c62f180b7d5760b46e9477dae59e87ed423a" + |> insertUnwrap + 281474976710652UL + "ba65d7b0ef55a3ba300d4e87af29868f394f8f138d78a7011669c79b37b936f4" + |> insertUnwrap + 281474976710651UL + "c65716add7aa98ba7acb236352d665cab17345fe45b55fb879ff80e6bd0c41dd" + |> insertUnwrap + 281474976710650UL + "969660042a28f32d9be17344e09374b379962d03db1574df5a8a5a47e19ce3f2" + |> insertUnwrap + 281474976710649UL + "a5a64476122ca0925fb344bdc1854c1c0a59fc614298e50a33e331980a220f32" + |> insert + 281474976710648UL + "05cde6323d949933f7f7b78776bcc1ea6d9b31447732e3802e1f7ac44b650e17" + + match res with + | Error(SecretMismatch(previous, current)) when + (previous.Index().UInt64 = 281474976710654UL + || previous.Index().UInt64 = 281474976710653UL + || previous.Index().UInt64 = 281474976710652UL) + && current.Index().UInt64 = 281474976710648UL + -> + () + | _ -> failwith <| sprintf "unexpected result: %A" res - testCase "insert secret 1 incorrect" <| fun _ -> - let res = - PerCommitmentSecretStore() - |> insertUnwrap - 281474976710655UL - "02a40c85b6f28da08dfdbe0926c53fab2de6d28c10301f8f7c4073d5e42e3148" - |> insert - 281474976710654UL - "c7518c8ae4660ed02894df8976fa1a3659c1a8b4b5bec0c4b872abeba4cb8964" - match res with - | Error(SecretMismatch(previous, current)) - when previous.Index().UInt64 = 281474976710655UL && current.Index().UInt64 = 281474976710654UL - -> () - | _ -> failwith <| sprintf "unexpected result: %A" res + testCase "insert secret 5 incorrect" + <| fun _ -> + let res = + PerCommitmentSecretStore() + |> insertUnwrap + 281474976710655UL + "7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc" + |> insertUnwrap + 281474976710654UL + "c7518c8ae4660ed02894df8976fa1a3659c1a8b4b5bec0c4b872abeba4cb8964" + |> insertUnwrap + 281474976710653UL + "2273e227a5b7449b6e70f1fb4652864038b1cbf9cd7c043a7d6456b7fc275ad8" + |> insertUnwrap + 281474976710652UL + "27cddaa5624534cb6cb9d7da077cf2b22ab21e9b506fd4998a51d54502e99116" + |> insertUnwrap + 281474976710651UL + "631373ad5f9ef654bb3dade742d09504c567edd24320d2fcd68e3cc47e2ff6a6" + |> insert + 281474976710650UL + "969660042a28f32d9be17344e09374b379962d03db1574df5a8a5a47e19ce3f2" - testCase "insert secret 2 incorrect" <| fun _ -> - let res = - PerCommitmentSecretStore() - |> insertUnwrap - 281474976710655UL - "02a40c85b6f28da08dfdbe0926c53fab2de6d28c10301f8f7c4073d5e42e3148" - |> insertUnwrap - 281474976710654UL - "dddc3a8d14fddf2b68fa8c7fbad2748274937479dd0f8930d5ebb4ab6bd866a3" - |> insertUnwrap - 281474976710653UL - "2273e227a5b7449b6e70f1fb4652864038b1cbf9cd7c043a7d6456b7fc275ad8" - |> insert - 281474976710652UL - "27cddaa5624534cb6cb9d7da077cf2b22ab21e9b506fd4998a51d54502e99116" - match res with - | Error(SecretMismatch(previous, current)) - when previous.Index().UInt64 = 281474976710654UL && current.Index().UInt64 = 281474976710652UL - -> () - | _ -> failwith <| sprintf "unexpected result: %A" res + match res with + | Error(SecretMismatch(previous, current)) when + previous.Index().UInt64 = 281474976710651UL + && current.Index().UInt64 = 281474976710650UL + -> + () + | _ -> failwith <| sprintf "unexpected result: %A" res - testCase "insert secret 3 incorrect" <| fun _ -> - let res = - PerCommitmentSecretStore() - |> insertUnwrap - 281474976710655UL - "7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc" - |> insertUnwrap - 281474976710654UL - "c7518c8ae4660ed02894df8976fa1a3659c1a8b4b5bec0c4b872abeba4cb8964" - |> insertUnwrap - 281474976710653UL - "c51a18b13e8527e579ec56365482c62f180b7d5760b46e9477dae59e87ed423a" - |> insert - 281474976710652UL - "27cddaa5624534cb6cb9d7da077cf2b22ab21e9b506fd4998a51d54502e99116" - match res with - | Error(SecretMismatch(previous, current)) - when previous.Index().UInt64 = 281474976710653UL && current.Index().UInt64 = 281474976710652UL - -> () - | _ -> failwith <| sprintf "unexpected result: %A" res - - testCase "insert secret 4 incorrect" <| fun _ -> - let res = - PerCommitmentSecretStore() - |> insertUnwrap - 281474976710655UL - "02a40c85b6f28da08dfdbe0926c53fab2de6d28c10301f8f7c4073d5e42e3148" - |> insertUnwrap - 281474976710654UL - "dddc3a8d14fddf2b68fa8c7fbad2748274937479dd0f8930d5ebb4ab6bd866a3" - |> insertUnwrap - 281474976710653UL - "c51a18b13e8527e579ec56365482c62f180b7d5760b46e9477dae59e87ed423a" - |> insertUnwrap - 281474976710652UL - "ba65d7b0ef55a3ba300d4e87af29868f394f8f138d78a7011669c79b37b936f4" - |> insertUnwrap - 281474976710651UL - "c65716add7aa98ba7acb236352d665cab17345fe45b55fb879ff80e6bd0c41dd" - |> insertUnwrap - 281474976710650UL - "969660042a28f32d9be17344e09374b379962d03db1574df5a8a5a47e19ce3f2" - |> insertUnwrap - 281474976710649UL - "a5a64476122ca0925fb344bdc1854c1c0a59fc614298e50a33e331980a220f32" - |> insert - 281474976710648UL - "05cde6323d949933f7f7b78776bcc1ea6d9b31447732e3802e1f7ac44b650e17" - match res with - | Error(SecretMismatch(previous, current)) - when ( - previous.Index().UInt64 = 281474976710654UL || - previous.Index().UInt64 = 281474976710653UL || - previous.Index().UInt64 = 281474976710652UL - ) && current.Index().UInt64 = 281474976710648UL - -> () - | _ -> failwith <| sprintf "unexpected result: %A" res + testCase "insert secret 6 incorrect" + <| fun _ -> + let res = + PerCommitmentSecretStore() + |> insertUnwrap + 281474976710655UL + "7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc" + |> insertUnwrap + 281474976710654UL + "c7518c8ae4660ed02894df8976fa1a3659c1a8b4b5bec0c4b872abeba4cb8964" + |> insertUnwrap + 281474976710653UL + "2273e227a5b7449b6e70f1fb4652864038b1cbf9cd7c043a7d6456b7fc275ad8" + |> insertUnwrap + 281474976710652UL + "27cddaa5624534cb6cb9d7da077cf2b22ab21e9b506fd4998a51d54502e99116" + |> insertUnwrap + 281474976710651UL + "631373ad5f9ef654bb3dade742d09504c567edd24320d2fcd68e3cc47e2ff6a6" + |> insertUnwrap + 281474976710650UL + "b7e76a83668bde38b373970155c868a653304308f9896692f904a23731224bb1" + |> insertUnwrap + 281474976710649UL + "a5a64476122ca0925fb344bdc1854c1c0a59fc614298e50a33e331980a220f32" + |> insert + 281474976710648UL + "05cde6323d949933f7f7b78776bcc1ea6d9b31447732e3802e1f7ac44b650e17" - testCase "insert secret 5 incorrect" <| fun _ -> - let res = - PerCommitmentSecretStore() - |> insertUnwrap - 281474976710655UL - "7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc" - |> insertUnwrap - 281474976710654UL - "c7518c8ae4660ed02894df8976fa1a3659c1a8b4b5bec0c4b872abeba4cb8964" - |> insertUnwrap - 281474976710653UL - "2273e227a5b7449b6e70f1fb4652864038b1cbf9cd7c043a7d6456b7fc275ad8" - |> insertUnwrap - 281474976710652UL - "27cddaa5624534cb6cb9d7da077cf2b22ab21e9b506fd4998a51d54502e99116" - |> insertUnwrap - 281474976710651UL - "631373ad5f9ef654bb3dade742d09504c567edd24320d2fcd68e3cc47e2ff6a6" - |> insert - 281474976710650UL - "969660042a28f32d9be17344e09374b379962d03db1574df5a8a5a47e19ce3f2" - match res with - | Error(SecretMismatch(previous, current)) - when previous.Index().UInt64 = 281474976710651UL && current.Index().UInt64 = 281474976710650UL - -> () - | _ -> failwith <| sprintf "unexpected result: %A" res + match res with + | Error(SecretMismatch(previous, current)) when + previous.Index().UInt64 = 281474976710650UL + && current.Index().UInt64 = 281474976710648UL + -> + () + | _ -> failwith <| sprintf "unexpected result: %A" res - testCase "insert secret 6 incorrect" <| fun _ -> - let res = - PerCommitmentSecretStore() - |> insertUnwrap - 281474976710655UL - "7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc" - |> insertUnwrap - 281474976710654UL - "c7518c8ae4660ed02894df8976fa1a3659c1a8b4b5bec0c4b872abeba4cb8964" - |> insertUnwrap - 281474976710653UL - "2273e227a5b7449b6e70f1fb4652864038b1cbf9cd7c043a7d6456b7fc275ad8" - |> insertUnwrap - 281474976710652UL - "27cddaa5624534cb6cb9d7da077cf2b22ab21e9b506fd4998a51d54502e99116" - |> insertUnwrap - 281474976710651UL - "631373ad5f9ef654bb3dade742d09504c567edd24320d2fcd68e3cc47e2ff6a6" - |> insertUnwrap - 281474976710650UL - "b7e76a83668bde38b373970155c868a653304308f9896692f904a23731224bb1" - |> insertUnwrap - 281474976710649UL - "a5a64476122ca0925fb344bdc1854c1c0a59fc614298e50a33e331980a220f32" - |> insert - 281474976710648UL - "05cde6323d949933f7f7b78776bcc1ea6d9b31447732e3802e1f7ac44b650e17" - match res with - | Error(SecretMismatch(previous, current)) - when previous.Index().UInt64 = 281474976710650UL && current.Index().UInt64 = 281474976710648UL - -> () - | _ -> failwith <| sprintf "unexpected result: %A" res + testCase "insert secret 7 incorrect" + <| fun _ -> + let res = + PerCommitmentSecretStore() + |> insertUnwrap + 281474976710655UL + "7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc" + |> insertUnwrap + 281474976710654UL + "c7518c8ae4660ed02894df8976fa1a3659c1a8b4b5bec0c4b872abeba4cb8964" + |> insertUnwrap + 281474976710653UL + "2273e227a5b7449b6e70f1fb4652864038b1cbf9cd7c043a7d6456b7fc275ad8" + |> insertUnwrap + 281474976710652UL + "27cddaa5624534cb6cb9d7da077cf2b22ab21e9b506fd4998a51d54502e99116" + |> insertUnwrap + 281474976710651UL + "c65716add7aa98ba7acb236352d665cab17345fe45b55fb879ff80e6bd0c41dd" + |> insertUnwrap + 281474976710650UL + "969660042a28f32d9be17344e09374b379962d03db1574df5a8a5a47e19ce3f2" + |> insertUnwrap + 281474976710649UL + "e7971de736e01da8ed58b94c2fc216cb1dca9e326f3a96e7194fe8ea8af6c0a3" + |> insert + 281474976710648UL + "05cde6323d949933f7f7b78776bcc1ea6d9b31447732e3802e1f7ac44b650e17" - testCase "insert secret 7 incorrect" <| fun _ -> - let res = - PerCommitmentSecretStore() - |> insertUnwrap - 281474976710655UL - "7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc" - |> insertUnwrap - 281474976710654UL - "c7518c8ae4660ed02894df8976fa1a3659c1a8b4b5bec0c4b872abeba4cb8964" - |> insertUnwrap - 281474976710653UL - "2273e227a5b7449b6e70f1fb4652864038b1cbf9cd7c043a7d6456b7fc275ad8" - |> insertUnwrap - 281474976710652UL - "27cddaa5624534cb6cb9d7da077cf2b22ab21e9b506fd4998a51d54502e99116" - |> insertUnwrap - 281474976710651UL - "c65716add7aa98ba7acb236352d665cab17345fe45b55fb879ff80e6bd0c41dd" - |> insertUnwrap - 281474976710650UL - "969660042a28f32d9be17344e09374b379962d03db1574df5a8a5a47e19ce3f2" - |> insertUnwrap - 281474976710649UL - "e7971de736e01da8ed58b94c2fc216cb1dca9e326f3a96e7194fe8ea8af6c0a3" - |> insert - 281474976710648UL - "05cde6323d949933f7f7b78776bcc1ea6d9b31447732e3802e1f7ac44b650e17" - match res with - | Error(SecretMismatch(previous, current)) - when previous.Index().UInt64 = 281474976710649UL && current.Index().UInt64 = 281474976710648UL - -> () - | _ -> failwith <| sprintf "unexpected result: %A" res + match res with + | Error(SecretMismatch(previous, current)) when + previous.Index().UInt64 = 281474976710649UL + && current.Index().UInt64 = 281474976710648UL + -> + () + | _ -> failwith <| sprintf "unexpected result: %A" res - testCase "insert secret 8 incorrect" <| fun _ -> - let res = - PerCommitmentSecretStore() - |> insertUnwrap - 281474976710655UL - "7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc" - |> insertUnwrap - 281474976710654UL - "c7518c8ae4660ed02894df8976fa1a3659c1a8b4b5bec0c4b872abeba4cb8964" - |> insertUnwrap - 281474976710653UL - "2273e227a5b7449b6e70f1fb4652864038b1cbf9cd7c043a7d6456b7fc275ad8" - |> insertUnwrap - 281474976710652UL - "27cddaa5624534cb6cb9d7da077cf2b22ab21e9b506fd4998a51d54502e99116" - |> insertUnwrap - 281474976710651UL - "c65716add7aa98ba7acb236352d665cab17345fe45b55fb879ff80e6bd0c41dd" - |> insertUnwrap - 281474976710650UL - "969660042a28f32d9be17344e09374b379962d03db1574df5a8a5a47e19ce3f2" - |> insertUnwrap - 281474976710649UL - "a5a64476122ca0925fb344bdc1854c1c0a59fc614298e50a33e331980a220f32" - |> insert - 281474976710648UL - "a7efbc61aac46d34f77778bac22c8a20c6a46ca460addc49009bda875ec88fa4" - match res with - | Error(SecretMismatch(previous, current)) - when previous.Index().UInt64 = 281474976710649UL && current.Index().UInt64 = 281474976710648UL - -> () - | _ -> failwith <| sprintf "unexpected result: %A" res - ] + testCase "insert secret 8 incorrect" + <| fun _ -> + let res = + PerCommitmentSecretStore() + |> insertUnwrap + 281474976710655UL + "7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc" + |> insertUnwrap + 281474976710654UL + "c7518c8ae4660ed02894df8976fa1a3659c1a8b4b5bec0c4b872abeba4cb8964" + |> insertUnwrap + 281474976710653UL + "2273e227a5b7449b6e70f1fb4652864038b1cbf9cd7c043a7d6456b7fc275ad8" + |> insertUnwrap + 281474976710652UL + "27cddaa5624534cb6cb9d7da077cf2b22ab21e9b506fd4998a51d54502e99116" + |> insertUnwrap + 281474976710651UL + "c65716add7aa98ba7acb236352d665cab17345fe45b55fb879ff80e6bd0c41dd" + |> insertUnwrap + 281474976710650UL + "969660042a28f32d9be17344e09374b379962d03db1574df5a8a5a47e19ce3f2" + |> insertUnwrap + 281474976710649UL + "a5a64476122ca0925fb344bdc1854c1c0a59fc614298e50a33e331980a220f32" + |> insert + 281474976710648UL + "a7efbc61aac46d34f77778bac22c8a20c6a46ca460addc49009bda875ec88fa4" + match res with + | Error(SecretMismatch(previous, current)) when + previous.Index().UInt64 = 281474976710649UL + && current.Index().UInt64 = 281474976710648UL + -> + () + | _ -> failwith <| sprintf "unexpected result: %A" res + ] diff --git a/tests/DotNetLightning.Core.Tests/PrimitivesTests.fs b/tests/DotNetLightning.Core.Tests/PrimitivesTests.fs index 5f4b14f5a..be529faa4 100644 --- a/tests/DotNetLightning.Core.Tests/PrimitivesTests.fs +++ b/tests/DotNetLightning.Core.Tests/PrimitivesTests.fs @@ -5,25 +5,49 @@ open Expecto open DotNetLightning.Utils [] -let tests = - let feeRateFromFeeTest (fee: Money) (weight: uint64) (expected: FeeRatePerKw): unit = - Expect.equal (FeeRatePerKw.FromFee(fee, weight)) expected "fee rate mismatch" +let tests = + let feeRateFromFeeTest + (fee: Money) + (weight: uint64) + (expected: FeeRatePerKw) + : unit = + Expect.equal + (FeeRatePerKw.FromFee(fee, weight)) + expected + "fee rate mismatch" - testList "fee rate per kw tests" [ - testCase "feeRateFromFee test 0" <| fun _ -> - feeRateFromFeeTest (Money(1.0m, MoneyUnit.Satoshi)) 1000UL (FeeRatePerKw 1u) - testCase "feeRateFromFee test 1" <| fun _ -> - feeRateFromFeeTest (Money(10.0m, MoneyUnit.Satoshi)) 1000UL (FeeRatePerKw 10u) - testCase "feeRateFromFee test 2" <| fun _ -> - feeRateFromFeeTest (Money(10.0m, MoneyUnit.Satoshi)) 10000UL (FeeRatePerKw 1u) - ] + testList + "fee rate per kw tests" + [ + testCase "feeRateFromFee test 0" + <| fun _ -> + feeRateFromFeeTest + (Money(1.0m, MoneyUnit.Satoshi)) + 1000UL + (FeeRatePerKw 1u) + testCase "feeRateFromFee test 1" + <| fun _ -> + feeRateFromFeeTest + (Money(10.0m, MoneyUnit.Satoshi)) + 1000UL + (FeeRatePerKw 10u) + testCase "feeRateFromFee test 2" + <| fun _ -> + feeRateFromFeeTest + (Money(10.0m, MoneyUnit.Satoshi)) + 10000UL + (FeeRatePerKw 1u) + ] [] let primitiveTests = - testList "primitives" [ - testProperty "ShortChannelId serialization" <| fun (cId: ShortChannelId) -> - Expect.equal - (cId.ToUInt64() |> ShortChannelId.FromUInt64) - cId - "Short Channel Id must be same after (de)/serialization" - ] \ No newline at end of file + testList + "primitives" + [ + testProperty "ShortChannelId serialization" + <| fun (cId: ShortChannelId) -> + Expect.equal + (cId.ToUInt64() |> ShortChannelId.FromUInt64) + cId + "Short Channel Id must be same after (de)/serialization" + ] diff --git a/tests/DotNetLightning.Core.Tests/RouteCalculationTests.fs b/tests/DotNetLightning.Core.Tests/RouteCalculationTests.fs index 807a1e032..04030cae8 100644 --- a/tests/DotNetLightning.Core.Tests/RouteCalculationTests.fs +++ b/tests/DotNetLightning.Core.Tests/RouteCalculationTests.fs @@ -22,939 +22,3211 @@ let hex = Encoders.Hex let fsCheckConfig = { FsCheckConfig.defaultConfig with - arbitrary = [ typeof ] - maxTest = 2 - } + arbitrary = [ typeof ] + maxTest = 2 + } + let makeChannelAnnCore(shortChannelId: ShortChannelId, nodeIdA, nodeIdB) = - let (nodeId1, nodeId2) = if isNode1(nodeIdA, nodeIdB) then nodeIdA, nodeIdB else (nodeIdB, nodeIdA) - { UnsignedChannelAnnouncementMsg.ShortChannelId = shortChannelId - NodeId1 = nodeId1 - NodeId2 = nodeId2 - BitcoinKey1 = ((new Key()).PubKey |> ComparablePubKey) - BitcoinKey2 = ((new Key()).PubKey |> ComparablePubKey) - ChainHash = Network.RegTest.GenesisHash - Features = FeatureBits.Zero - ExcessData = [||]} + let (nodeId1, nodeId2) = + if isNode1(nodeIdA, nodeIdB) then + nodeIdA, nodeIdB + else + (nodeIdB, nodeIdA) + + { + UnsignedChannelAnnouncementMsg.ShortChannelId = shortChannelId + NodeId1 = nodeId1 + NodeId2 = nodeId2 + BitcoinKey1 = ((new Key()).PubKey |> ComparablePubKey) + BitcoinKey2 = ((new Key()).PubKey |> ComparablePubKey) + ChainHash = Network.RegTest.GenesisHash + Features = FeatureBits.Zero + ExcessData = [||] + } + let makeChannelAnn(shortChannelId: uint64, nodeIdA: NodeId, nodeIdB: NodeId) = - makeChannelAnnCore(ShortChannelId.FromUInt64(shortChannelId), nodeIdA, nodeIdB) - + makeChannelAnnCore( + ShortChannelId.FromUInt64(shortChannelId), + nodeIdA, + nodeIdB + ) + let pks = [ - "02999fa724ec3c244e4da52b4a91ad421dc96c9a810587849cd4b2469313519c73"; //a - "03f1cb1af20fe9ccda3ea128e27d7c39ee27375c8480f11a87c17197e97541ca6a"; //b - "0358e32d245ff5f5a3eb14c78c6f69c67cea7846bdf9aeeb7199e8f6fbb0306484"; //c - "029e059b6780f155f38e83601969919aae631ddf6faed58fe860c72225eb327d7c"; //d - "02f38f4e37142cc05df44683a83e22dea608cf4691492829ff4cf99888c5ec2d3a"; //e - "03fc5b91ce2d857f146fd9b986363374ffe04dc143d8bcd6d7664c8873c463cdfc"; //f - "03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f"; //g + "02999fa724ec3c244e4da52b4a91ad421dc96c9a810587849cd4b2469313519c73" //a + "03f1cb1af20fe9ccda3ea128e27d7c39ee27375c8480f11a87c17197e97541ca6a" //b + "0358e32d245ff5f5a3eb14c78c6f69c67cea7846bdf9aeeb7199e8f6fbb0306484" //c + "029e059b6780f155f38e83601969919aae631ddf6faed58fe860c72225eb327d7c" //d + "02f38f4e37142cc05df44683a83e22dea608cf4691492829ff4cf99888c5ec2d3a" //e + "03fc5b91ce2d857f146fd9b986363374ffe04dc143d8bcd6d7664c8873c463cdfc" //f + "03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f" //g ] - |> List.map (hex.DecodeData >> PubKey >> NodeId) -let a, b, c, d, e, f, g = pks.[0], pks.[1], pks.[2], pks.[3], pks.[4], pks.[5], pks.[6] + |> List.map(hex.DecodeData >> PubKey >> NodeId) -let hops2Ids (route: seq) = - route |> Seq.map(fun hop -> hop.LastUpdateValue.ShortChannelId.ToBytes() |> fun x -> NBitcoin.Utils.ToUInt64(x, false)) +let a, b, c, d, e, f, g = + pks.[0], pks.[1], pks.[2], pks.[3], pks.[4], pks.[5], pks.[6] -let hops2Nodes (route: seq) = +let hops2Ids(route: seq) = + route + |> Seq.map(fun hop -> + hop.LastUpdateValue.ShortChannelId.ToBytes() + |> fun x -> NBitcoin.Utils.ToUInt64(x, false) + ) + +let hops2Nodes(route: seq) = route |> Seq.map(fun hop -> (hop.NodeIdValue, hop.NextNodeIdValue)) -let hops2Edges (route: ChannelHop seq) = + +let hops2Edges(route: seq) = route |> Seq.map(fun h -> - { GraphLabel.Desc = - { ShortChannelId = h.LastUpdateValue.ShortChannelId - A = h.NodeIdValue - B = h.NodeIdValue } - Update = h.LastUpdateValue }) + { + GraphLabel.Desc = + { + ShortChannelId = h.LastUpdateValue.ShortChannelId + A = h.NodeIdValue + B = h.NodeIdValue + } + Update = h.LastUpdateValue + } + ) + [] -let tests = testList "Route Calculation" [ - let calculateRouteSimple routeParams = - let updates = [ - makeUpdate(1UL, a, b, LNMoney.MilliSatoshis(1L), 10u, None, None, BlockHeightOffset16.One |> Some) - makeUpdate(2UL, b, c, LNMoney.MilliSatoshis(1L), 10u, None, None, BlockHeightOffset16.One |> Some) - makeUpdate(3UL, c, d, LNMoney.MilliSatoshis(1L), 10u, None, None, BlockHeightOffset16.One |> Some) - makeUpdate(4UL, d, e, LNMoney.MilliSatoshis(1L), 10u, None, None, BlockHeightOffset16.One |> Some) - ] - - let g = DirectedLNGraph.Create().AddEdges(updates) - let route = - Routing.findRoute (g) (a) (e) DEFAULT_AMOUNT_MSAT 1 (Set.empty) (Set.empty) (Set.empty) routeParams (BlockHeight 400000u) - |> Result.deref - Expect.sequenceEqual (route |> hops2Ids) ([1UL; 2UL; 3UL; 4UL]) "" - testCase "Calculate simple route" <| fun _ -> - calculateRouteSimple DEFAULT_ROUTE_PARAMS - - testCase "Check fee against max pct properly" <| fun _ -> - // fee is acceptable if it is either - // - below our maximum fee base - // - below our maximum fraction of the paid amount - - // here we have a maximum fee base of 1 msat, and all our updates have a base fee of 10 msat - // so our fee will always be above the base fee, and we will always check that it is below our maximum percentage - // of the amount being paid - calculateRouteSimple { DEFAULT_ROUTE_PARAMS with MaxFeeBase = LNMoney.One } - - testCase "Calculate the shortest path (correct fees)" <| fun _ -> - let amount = LNMoney.MilliSatoshis(10000L) - let expectedCost = 10007L |> LNMoney.MilliSatoshis - let updates = [ - makeUpdate(1UL, a, b, LNMoney.One, 200u, Some(LNMoney.Zero), None, None) - makeUpdate(4UL, a, e, LNMoney.One, 200u, Some(LNMoney.Zero), None, None) - makeUpdate(2UL, b, c, LNMoney.One, 300u, Some(LNMoney.Zero), None, None) - makeUpdate(3UL, c, d, LNMoney.One, 400u, Some(LNMoney.Zero), None, None) - makeUpdate(5UL, e, f, LNMoney.One, 400u, Some(LNMoney.Zero), None, None) - makeUpdate(6UL, f, d, LNMoney.One, 100u, Some(LNMoney.Zero), None, None) - ] - let graph = DirectedLNGraph.Create().AddEdges(updates) - let route = - Routing.findRoute graph a d amount 1 (Set.empty) (Set.empty) (Set.empty) DEFAULT_ROUTE_PARAMS (BlockHeight(400000u)) - |> Result.deref - let totalCost = Graph.pathWeight(hops2Edges(route)) (amount) false BlockHeight.Zero None |> fun x -> x.Cost - Expect.sequenceEqual (hops2Ids(route)) [4UL; 5UL; 6UL] "" - Expect.equal totalCost expectedCost "" - - /// Now channel 5 could route the amount (10000) but not the amount + fees (10007) - let (desc, update) = makeUpdate(5UL, e, f, LNMoney.One, 400u, Some(LNMoney.Zero), Some(LNMoney.MilliSatoshis(10005L)), None) - let graph1 = graph.AddEdge(desc, update) - let route1 = - Routing.findRoute(graph1) a d amount 1 (Set.empty) (Set.empty) (Set.empty) DEFAULT_ROUTE_PARAMS (BlockHeight(400000u)) - |> Result.deref - Expect.sequenceEqual (hops2Ids(route1)) [1UL; 2UL; 3UL] "" - - testCase "calculate route considering the direct channel pays no fees" <| fun _ -> - let updates = [ - makeUpdate(1UL, a, b, LNMoney.MilliSatoshis(5L), 0u, None, None, None) - makeUpdate(2UL, a, d, LNMoney.MilliSatoshis(15L), 0u, None, None, None) - makeUpdate(3UL, b, c, LNMoney.MilliSatoshis(5L), 0u, None, None, None) - makeUpdate(4UL, c, d, LNMoney.MilliSatoshis(5L), 0u, None, None, None) - makeUpdate(5UL, d, e, LNMoney.MilliSatoshis(5L), 0u, None, None, None) - ] - let g = DirectedLNGraph.Create().AddEdges(updates) - let route = - Routing.findRoute(g) a e DEFAULT_AMOUNT_MSAT 1 (Set.empty) (Set.empty) (Set.empty) DEFAULT_ROUTE_PARAMS (BlockHeight(400000u)) - |> Result.deref - Expect.sequenceEqual (hops2Ids(route)) [2UL;5UL] "" - - testCase "Calculate simple route (add and remove edges)" <| fun _ -> - let updates = [ - makeUpdateSimple(1UL, a, b) - makeUpdateSimple(2UL, b, c) - makeUpdateSimple(3UL, c, d) - makeUpdateSimple(4UL, d, e) - ] - let g = DirectedLNGraph.Create().AddEdges(updates) - let route1 = - Routing.findRoute g a e DEFAULT_AMOUNT_MSAT 1 (Set.empty)(Set.empty) (Set.empty)DEFAULT_ROUTE_PARAMS (BlockHeight(400000u)) - |> Result.deref - - Expect.sequenceEqual (hops2Ids(route1)) [1UL; 2UL; 3UL; 4UL;] "" - - let graphWithRemovedEdge = g.RemoveEdge({ ChannelDesc.ShortChannelId = ShortChannelId.FromUInt64(3UL); A = c; B = d }) - - let route2 = - Routing.findRoute(graphWithRemovedEdge) a e DEFAULT_AMOUNT_MSAT 1 (Set.empty) (Set.empty)(Set.empty)DEFAULT_ROUTE_PARAMS (BlockHeight(400000u)) - Expect.isError (Result.ToFSharpCoreResult route2) "" - - testCase "calculate the shortest path (select direct channel)" <| fun _ -> - let updates = [ - makeUpdate(1UL, a, b, LNMoney.Zero, 0u, None, None, None) - makeUpdate(4UL, a, d, LNMoney.MilliSatoshis(50L), 0u, None, None, None) - makeUpdate(2UL, b, c, LNMoney.MilliSatoshis(0L), 0u, None, None, None) - makeUpdate(3UL, c, d, LNMoney.MilliSatoshis(0L), 0u, None, None, None) - ] - let graph = DirectedLNGraph.Create().AddEdges(updates) - let route = - Routing.findRoute(graph) a d DEFAULT_AMOUNT_MSAT 2 (Set.empty) (Set.empty) (Set.empty) DEFAULT_ROUTE_PARAMS (BlockHeight(400000u)) - |> Result.deref - Expect.sequenceEqual (hops2Ids(route)) [4UL] "" - - let (h, i) = ( - "03de6411928b3b0217b50b27b269aea8457f7b88797402fff3e86f2d28775af5d5" |> (hex.DecodeData >> PubKey >> NodeId), // H - "03ffda25c95266e33c06c8006bbcd3985932a79580dfb07d95855c332a0e13b9ef" |> (hex.DecodeData >> PubKey >> NodeId) // I target - ) - testCase "find a route using channels with hltcMaximumMsat close to the payment amount" <| fun _ -> - let updates = [ - makeUpdate(1UL, f, g, LNMoney.One, 0u, None, None, None) - makeUpdate(2UL, g, h, LNMoney.One, 0u, None, Some(DEFAULT_AMOUNT_MSAT + LNMoney.MilliSatoshis(50L)), None) - makeUpdate(3UL, h, i, LNMoney.One, 0u, None, None, None) - ] - let graph = DirectedLNGraph.Create().AddEdges(updates) - let route = - Routing.findRoute graph f i DEFAULT_AMOUNT_MSAT 1 (Set.empty)(Set.empty)(Set.empty) DEFAULT_ROUTE_PARAMS (BlockHeight(400000u)) - |> Result.deref - Expect.sequenceEqual (hops2Ids(route)) [1UL; 2UL; 3UL] "" - - testCase "find a route using channels with htlcMinimumMsat close to the payment amount" <| fun _ -> - let updates = [ - makeUpdate(1UL, f, g, LNMoney.One, 0u, None, None, None) - makeUpdate(2UL, g, h, LNMoney.One, 0u, Some(DEFAULT_AMOUNT_MSAT + LNMoney.MilliSatoshis(50L)), None, None) - makeUpdate(3UL, h, i, LNMoney.One, 0u, None, None, None) - ] - let graph = DirectedLNGraph.Create().AddEdges(updates) - let route = - Routing.findRoute graph f i DEFAULT_AMOUNT_MSAT 1 (Set.empty)(Set.empty)(Set.empty) DEFAULT_ROUTE_PARAMS (BlockHeight(400000u)) - Expect.isError (Result.ToFSharpCoreResult route) "" - - testCase "if there are multiple channels between the same node, select the cheapest" <| fun _ -> - let updates = [ - makeUpdate(1UL, f, g, LNMoney.Zero, 0u, None, None, None) - makeUpdate(2UL, g, h, LNMoney.MilliSatoshis(5L), 5u, None, None, None) // expensive g -> h channel - makeUpdate(6UL, g, h, LNMoney.MilliSatoshis(0L), 0u, None, None, None) // cheap g -> h channel - makeUpdate(3UL, h, i, LNMoney.MilliSatoshis(0L), 0u, None, None, None) - ] - let graph = DirectedLNGraph.Create().AddEdges(updates) - let route = - Routing.findRoute graph f i DEFAULT_AMOUNT_MSAT 1 (Set.empty)(Set.empty)(Set.empty)DEFAULT_ROUTE_PARAMS (BlockHeight(400000u)) - |> Result.deref - Expect.sequenceEqual(hops2Ids(route)) [1UL;6UL;3UL] "" - - testCase "Calculate longer but cheaper route" <| fun _ -> - let updates = [ - makeUpdateSimple(1UL, a, b) - makeUpdateSimple(2UL, b, c) - makeUpdateSimple(3UL, c, d) - makeUpdateSimple(4UL, d, e) - makeUpdate(5UL, b, e, LNMoney.MilliSatoshis(10L), 10u, None, None, None) - ] - let g = DirectedLNGraph.Create().AddEdges(updates) - let route = - Routing.findRoute g a e DEFAULT_AMOUNT_MSAT 1 (Set.empty) (Set.empty) (Set.empty) DEFAULT_ROUTE_PARAMS (BlockHeight(400000u)) - |> Result.deref - Expect.sequenceEqual (hops2Ids(route)) [1UL;2UL;3UL;4UL;] "" - testCase "no local channels" <| fun _ -> - let updates = [ - makeUpdateSimple(2UL, b, c) - makeUpdateSimple(4UL, d, e) - ] - - let g = DirectedLNGraph.Create().AddEdges(updates) - let route = Routing.findRoute(g) a e DEFAULT_AMOUNT_MSAT 1 (Set.empty)(Set.empty)(Set.empty) DEFAULT_ROUTE_PARAMS (BlockHeight(400000u)) - Expect.isError (Result.ToFSharpCoreResult route) "" - - testCase "route not found (source OR target node not connected)" <| fun _ -> - let updates = [ - makeUpdateSimple(2UL, b, c) - makeUpdateSimple(4UL, c, d) - ] - let g = DirectedLNGraph.Create().AddEdges(updates).AddVertex(a).AddVertex(e) - Expect.isError (Result.ToFSharpCoreResult (Routing.findRoute g a d DEFAULT_AMOUNT_MSAT 1 (Set.empty)(Set.empty)(Set.empty) DEFAULT_ROUTE_PARAMS (BlockHeight(400000u)))) "" - Expect.isError (Result.ToFSharpCoreResult (Routing.findRoute g b e DEFAULT_AMOUNT_MSAT 1 (Set.empty)(Set.empty)(Set.empty) DEFAULT_ROUTE_PARAMS (BlockHeight(400000u)))) "" - - testCase "route not found (amount too high OR too low)" <| fun _ -> - let highAmount = DEFAULT_AMOUNT_MSAT * 10 - let lowAmount = DEFAULT_AMOUNT_MSAT / 10 - let updatesHi = [ - makeUpdateSimple(1UL, a, b) - makeUpdate(2UL, b, c, LNMoney.Zero, 0u, None, Some(DEFAULT_AMOUNT_MSAT), None) - makeUpdateSimple(3UL, c, d) - ] - let updatesLow = [ - makeUpdateSimple(1UL, a, b) - makeUpdate(2UL, b, c, LNMoney.Zero, 0u, Some(DEFAULT_AMOUNT_MSAT), None, None) - makeUpdateSimple(3UL, c, d) - ] - - let gHigh = DirectedLNGraph.Create().AddEdges(updatesHi) - let gLow = DirectedLNGraph.Create().AddEdges(updatesLow) - - Expect.isError (Result.ToFSharpCoreResult (Routing.findRoute gHigh a d highAmount 1 (Set.empty)(Set.empty)(Set.empty) DEFAULT_ROUTE_PARAMS (BlockHeight(400000u)))) "" - Expect.isError (Result.ToFSharpCoreResult (Routing.findRoute gLow a d lowAmount 1 (Set.empty)(Set.empty)(Set.empty) DEFAULT_ROUTE_PARAMS (BlockHeight(400000u)))) "" - - testCase "route to self" <| fun _ -> - let updates = [ - makeUpdateSimple(1UL, a, b) - makeUpdateSimple(2UL, b, c) - makeUpdateSimple(3UL, c, d) - ] - - let g = DirectedLNGraph.Create().AddEdges(updates) - let route = - Routing.findRoute g a a DEFAULT_AMOUNT_MSAT 1 (Set.empty)(Set.empty)(Set.empty) DEFAULT_ROUTE_PARAMS (BlockHeight(400000u)) - Expect.isError (Result.ToFSharpCoreResult route) "" - - testCase "route to immediate neighbor" <| fun _ -> - let updates = [ - makeUpdateSimple(1UL, a, b) - makeUpdateSimple(2UL, b, c) - makeUpdateSimple(3UL, c, d) - makeUpdateSimple(4UL, d, e) - ] - - let g = DirectedLNGraph.Create().AddEdges(updates) - let route = - Routing.findRoute(g) a b DEFAULT_AMOUNT_MSAT 1 (Set.empty)(Set.empty)(Set.empty) DEFAULT_ROUTE_PARAMS (BlockHeight(400000u)) - |> Result.deref - Expect.sequenceEqual (hops2Ids(route)) [1UL] "" - - testCase "directed graph" <| fun _ -> - let updates = [ - makeUpdateSimple(1UL, a, b) - makeUpdateSimple(2UL, b, c) - makeUpdateSimple(3UL, c, d) - makeUpdateSimple(4UL, d, e) - ] - // a -> e works, e -> a fails - let g = DirectedLNGraph.Create().AddEdges(updates) - let route1 = - Routing.findRoute g a e DEFAULT_AMOUNT_MSAT 1 (Set.empty)(Set.empty)(Set.empty) DEFAULT_ROUTE_PARAMS (BlockHeight(400000u)) - |> Result.deref - - Expect.sequenceEqual (hops2Ids(route1)) [1UL; 2UL; 3UL; 4UL] "" - let route2 = - Routing.findRoute g e e DEFAULT_AMOUNT_MSAT 1 (Set.empty)(Set.empty)(Set.empty) DEFAULT_ROUTE_PARAMS (BlockHeight(400000u)) - Expect.isError (Result.ToFSharpCoreResult route2) "" - - testCase "calculate route and return metadata" <| fun _ -> - let uab = - { UnsignedChannelUpdateMsg.ChainHash = Network.RegTest.GenesisHash - ShortChannelId = ShortChannelId.FromUInt64(1UL) - Timestamp = 0u - MessageFlags = 0uy - ChannelFlags = 0uy - CLTVExpiryDelta = BlockHeightOffset16(1us) - HTLCMinimumMSat = LNMoney.MilliSatoshis(42L) - FeeBaseMSat = LNMoney.MilliSatoshis(2500L) - FeeProportionalMillionths = 140u - HTLCMaximumMSat = None } - let uba = - { UnsignedChannelUpdateMsg.ChainHash = Network.RegTest.GenesisHash - ShortChannelId = ShortChannelId.FromUInt64(1UL) - Timestamp = 1u - MessageFlags = 0uy - ChannelFlags = 1uy - CLTVExpiryDelta = BlockHeightOffset16(1us) - HTLCMinimumMSat = LNMoney.MilliSatoshis(43L) - FeeBaseMSat = LNMoney.MilliSatoshis(2501L) - FeeProportionalMillionths = 141u - HTLCMaximumMSat = None } - let ubc = - { UnsignedChannelUpdateMsg.ChainHash = Network.RegTest.GenesisHash - ShortChannelId = ShortChannelId.FromUInt64(2UL) - Timestamp = 1u - MessageFlags = 0uy - ChannelFlags = 0uy - CLTVExpiryDelta = BlockHeightOffset16(1us) - HTLCMinimumMSat = LNMoney.MilliSatoshis(44L) - FeeBaseMSat = LNMoney.MilliSatoshis(2502L) - FeeProportionalMillionths = 142u - HTLCMaximumMSat = None } - let ucb = - { UnsignedChannelUpdateMsg.ChainHash = Network.RegTest.GenesisHash - ShortChannelId = ShortChannelId.FromUInt64(2UL) - Timestamp = 1u - MessageFlags = 0uy - ChannelFlags = 1uy - CLTVExpiryDelta = BlockHeightOffset16(1us) - HTLCMinimumMSat = LNMoney.MilliSatoshis(45L) - FeeBaseMSat = LNMoney.MilliSatoshis(2503L) - FeeProportionalMillionths = 143u - HTLCMaximumMSat = None } - let ucd = - { UnsignedChannelUpdateMsg.ChainHash = Network.RegTest.GenesisHash - ShortChannelId = ShortChannelId.FromUInt64(3UL) - Timestamp = 1u - MessageFlags = 1uy - ChannelFlags = 0uy - CLTVExpiryDelta = BlockHeightOffset16(1us) - HTLCMinimumMSat = LNMoney.MilliSatoshis(46L) - FeeBaseMSat = LNMoney.MilliSatoshis(2504L) - FeeProportionalMillionths = 144u - HTLCMaximumMSat = Some(LNMoney.MilliSatoshis(500000000L)) } - let udc = - { UnsignedChannelUpdateMsg.ChainHash = Network.RegTest.GenesisHash - ShortChannelId = ShortChannelId.FromUInt64(3UL) - Timestamp = 1u - MessageFlags = 0uy - ChannelFlags = 1uy - CLTVExpiryDelta = BlockHeightOffset16(1us) - HTLCMinimumMSat = LNMoney.MilliSatoshis(47L) - FeeBaseMSat = LNMoney.MilliSatoshis(2505L) - FeeProportionalMillionths = 145u - HTLCMaximumMSat = None } - let ude = - { UnsignedChannelUpdateMsg.ChainHash = Network.RegTest.GenesisHash - ShortChannelId = ShortChannelId.FromUInt64(4UL) - Timestamp = 1u - MessageFlags = 0uy - ChannelFlags = 0uy - CLTVExpiryDelta = BlockHeightOffset16(1us) - HTLCMinimumMSat = LNMoney.MilliSatoshis(48L) - FeeBaseMSat = LNMoney.MilliSatoshis(2506L) - FeeProportionalMillionths = 146u - HTLCMaximumMSat = None } - let ued = - { UnsignedChannelUpdateMsg.ChainHash = Network.RegTest.GenesisHash - ShortChannelId = ShortChannelId.FromUInt64(4UL) - Timestamp = 1u - MessageFlags = 0uy - ChannelFlags = 1uy - CLTVExpiryDelta = BlockHeightOffset16(1us) - HTLCMinimumMSat = LNMoney.MilliSatoshis(49L) - FeeBaseMSat = LNMoney.MilliSatoshis(2507L) - FeeProportionalMillionths = 147u - HTLCMaximumMSat = None } - let updates = - Map.empty - |> Map.add ({ChannelDesc.ShortChannelId = ShortChannelId.FromUInt64(1UL); A = a; B = b}) uab - |> Map.add ({ChannelDesc.ShortChannelId = ShortChannelId.FromUInt64(1UL); A = b; B = a}) uba - |> Map.add ({ChannelDesc.ShortChannelId = ShortChannelId.FromUInt64(2UL); A = b; B = c}) ubc - |> Map.add ({ChannelDesc.ShortChannelId = ShortChannelId.FromUInt64(2UL); A = c; B = b}) ucb - |> Map.add ({ChannelDesc.ShortChannelId = ShortChannelId.FromUInt64(3UL); A = c; B = d}) ucd - |> Map.add ({ChannelDesc.ShortChannelId = ShortChannelId.FromUInt64(3UL); A = d; B = c}) udc - |> Map.add ({ChannelDesc.ShortChannelId = ShortChannelId.FromUInt64(4UL); A = d; B = e}) ude - |> Map.add ({ChannelDesc.ShortChannelId = ShortChannelId.FromUInt64(4UL); A = e; B = d}) ued - - let g = DirectedLNGraph.Create().AddEdges(updates |> Map.toSeq) - let hops = - Routing.findRoute g a e DEFAULT_AMOUNT_MSAT 1 (Set.empty)(Set.empty)(Set.empty) DEFAULT_ROUTE_PARAMS (BlockHeight(400000u)) - |> Result.deref - let e = [ - (ChannelHop.Create(a, b, uab)) - (ChannelHop.Create(b, c, ubc)) - (ChannelHop.Create(c, d, ucd)) - (ChannelHop.Create(d, e, ude)) - ] - Expect.sequenceEqual hops e "" - - testCase "convert extra hops to assisted channels" <| fun _ -> - let extraHop1 = { ExtraHop.NodeId = a - ShortChannelId = ShortChannelId.FromUInt64(1UL) - FeeBase = LNMoney.Satoshis(12L) - FeeProportionalMillionths = 10000u - CLTVExpiryDelta = BlockHeightOffset16(12us) } - let extraHop2 = { ExtraHop.NodeId = b - ShortChannelId = ShortChannelId.FromUInt64(2UL) - FeeBase = LNMoney.Satoshis(200L) - FeeProportionalMillionths = 0u - CLTVExpiryDelta = BlockHeightOffset16(22us) } - let extraHop3 = { ExtraHop.NodeId = c - ShortChannelId = ShortChannelId.FromUInt64(3UL) - FeeBase = LNMoney.Satoshis(150L) - FeeProportionalMillionths = 0u - CLTVExpiryDelta = BlockHeightOffset16(32us) } - let extraHop4 = { ExtraHop.NodeId = d - ShortChannelId = ShortChannelId.FromUInt64(4UL) - FeeBase = LNMoney.Satoshis(50L) - FeeProportionalMillionths = 0u - CLTVExpiryDelta = BlockHeightOffset16(42us) } - let extraHops = [ extraHop1; extraHop2; extraHop3; extraHop4 ] - let amount = LNMoney.Satoshis(900L) // below RoutingHeuristics.CAPACITY_CHANNEL_LOW - let acs = Routing.toAssistedChannels e amount extraHops |> Map.ofSeq - Expect.equal (acs.[extraHop4.ShortChannelId]) ({ AssistedChannel.ExtraHop = extraHop4; NextNodeId = e; HTLCMaximum = (LNMoney.Satoshis(1050L)) }) "" - Expect.equal (acs.[extraHop3.ShortChannelId]) ({ AssistedChannel.ExtraHop = extraHop3; NextNodeId = d; HTLCMaximum = (LNMoney.Satoshis(1200L)) }) "" - Expect.equal (acs.[extraHop2.ShortChannelId]) ({ AssistedChannel.ExtraHop = extraHop2; NextNodeId = c; HTLCMaximum = (LNMoney.Satoshis(1400L)) }) "" - Expect.equal (acs.[extraHop1.ShortChannelId]) ({ AssistedChannel.ExtraHop = extraHop1; NextNodeId = b; HTLCMaximum = (LNMoney.Satoshis(1426L)) }) "" - - testCase "blacklist routes" <| fun _ -> - let updates = [ - makeUpdateSimple(1UL, a, b) - makeUpdateSimple(2UL, b, c) - makeUpdateSimple(3UL, c, d) - makeUpdateSimple(4UL, d, e) - ] - let g = DirectedLNGraph.Create().AddEdges(updates) - let ignoredE = Set.singleton({ ShortChannelId = ShortChannelId.FromUInt64(3UL); A = c; B = d }) - let route1 = Routing.findRoute(g) a e DEFAULT_AMOUNT_MSAT 1 (Set.empty) (ignoredE) (Set.empty) DEFAULT_ROUTE_PARAMS (BlockHeight(400000u)) - Expect.isError (Result.ToFSharpCoreResult route1) "" - - // verify that we left the graph untouched - Expect.isTrue(g.ContainsEdge(makeUpdateSimple(3UL, c, d) |> fst)) "" - Expect.isTrue(g.ContainsVertex(c)) "" - Expect.isTrue(g.ContainsVertex(d)) "" - - // make sure we can find a route without the blacklist - let route2 = - Routing.findRoute g a e DEFAULT_AMOUNT_MSAT 1 (Set.empty) (Set.empty) (Set.empty) DEFAULT_ROUTE_PARAMS (BlockHeight(400000u)) - |> Result.deref - Expect.sequenceEqual(hops2Ids(route2)) [1UL; 2UL; 3UL; 4UL] "" - - testCase "route to a destination that is not in the graph (with assisted routes)" <| fun _ -> - let updates = [ - makeUpdate(1UL, a, b, LNMoney.MilliSatoshis(10L), 10u, None, None, None) - makeUpdate(2UL, b, c, LNMoney.MilliSatoshis(10L), 10u, None, None, None) - makeUpdate(3UL, c, d, LNMoney.MilliSatoshis(10L), 10u, None, None, None) - ] - let g = DirectedLNGraph.Create().AddEdges(updates) - let route = - Routing.findRoute(g) a e DEFAULT_AMOUNT_MSAT 1 (Set.empty)(Set.empty)(Set.empty) DEFAULT_ROUTE_PARAMS (BlockHeight(400000u)) - Expect.isError (Result.ToFSharpCoreResult route) "there should be no e node in the graph" - - // now we add the missing edge to reach the destination - let (extraDesc, extraUpdate) = makeUpdate(4UL, d, e, LNMoney.MilliSatoshis(5L), 5u, None, None, None) - let extraGraphEdges = Set.singleton({ GraphLabel.Desc =extraDesc; Update = extraUpdate }) - let route1 = - Routing.findRoute g a e DEFAULT_AMOUNT_MSAT 1 (extraGraphEdges) (Set.empty)(Set.empty) DEFAULT_ROUTE_PARAMS (BlockHeight(400000u)) - |> Result.deref - Expect.sequenceEqual(hops2Ids(route1)) [1UL; 2UL; 3UL; 4UL] "" - - testCase "Verify that extra hops takes precedence over known channels" <| fun _ -> - let updates = [ - makeUpdate(1UL, a, b, LNMoney.MilliSatoshis(10), 10u, None, None, None) - makeUpdate(2UL, b, c, LNMoney.MilliSatoshis(10), 10u, None, None, None) - makeUpdate(3UL, c, d, LNMoney.MilliSatoshis(10), 10u, None, None, None) - makeUpdate(4UL, d, e, LNMoney.MilliSatoshis(10), 10u, None, None, None) - ] - - let g = DirectedLNGraph.Create().AddEdges(updates) - let route1 = - Routing.findRoute g a e DEFAULT_AMOUNT_MSAT 1 (Set.empty) (Set.empty) (Set.empty) DEFAULT_ROUTE_PARAMS (BlockHeight(400000u)) - |> Result.deref - Expect.sequenceEqual (hops2Ids(route1)) [1UL;2UL; 3UL; 4UL] "" - Expect.equal ((route1 |> Seq.item 1).LastUpdateValue.FeeBaseMSat) (LNMoney.MilliSatoshis(10)) "" - - let (extraDesc, extraUpdate) = makeUpdate(2UL, b, c, LNMoney.MilliSatoshis(5), 5u, None, None, None) - let extraGraphEdges = Set.singleton({ GraphLabel.Desc = extraDesc; Update = extraUpdate }) - let route2 = - Routing.findRoute g a e DEFAULT_AMOUNT_MSAT 1 extraGraphEdges (Set.empty) (Set.empty) DEFAULT_ROUTE_PARAMS (BlockHeight(400000u)) - |> Result.deref - Expect.sequenceEqual (hops2Ids(route2)) [1UL; 2UL; 3UL; 4UL] "" - Expect.equal ((route2 |> Seq.item 1).LastUpdateValue.FeeBaseMSat) (LNMoney.MilliSatoshis(5)) "" - - testPropertyWithConfig fsCheckConfig "compute ignored channels" <| fun (f: NodeId, g:NodeId, h: NodeId, i: NodeId, j: NodeId) -> - let channels = - Map.empty - |> Map.add(ShortChannelId.FromUInt64(1UL)) (makeChannelAnn(1UL, a, b)) - |> Map.add(ShortChannelId.FromUInt64(2UL)) (makeChannelAnn(2UL, b, c)) - |> Map.add(ShortChannelId.FromUInt64(3UL)) (makeChannelAnn(3UL, c, d)) - |> Map.add(ShortChannelId.FromUInt64(4UL)) (makeChannelAnn(4UL, d, e)) - |> Map.add(ShortChannelId.FromUInt64(5UL)) (makeChannelAnn(5UL, f, g)) - |> Map.add(ShortChannelId.FromUInt64(6UL)) (makeChannelAnn(6UL, f, h)) - |> Map.add(ShortChannelId.FromUInt64(7UL)) (makeChannelAnn(7UL, h, i)) - |> Map.add(ShortChannelId.FromUInt64(8UL)) (makeChannelAnn(8UL, i, j)) - - let updates = [ - makeUpdate(1UL, a, b, LNMoney.MilliSatoshis(10), 10u, None, None, None) - makeUpdate(2UL, b, c, LNMoney.MilliSatoshis(10), 10u, None, None, None) - makeUpdate(2UL, c, b, LNMoney.MilliSatoshis(10), 10u, None, None, None) - makeUpdate(3UL, c, d, LNMoney.MilliSatoshis(10), 10u, None, None, None) - makeUpdate(4UL, d, e, LNMoney.MilliSatoshis(10), 10u, None, None, None) - makeUpdate(5UL, f, g, LNMoney.MilliSatoshis(10), 10u, None, None, None) - makeUpdate(6UL, f, h, LNMoney.MilliSatoshis(10), 10u, None, None, None) - makeUpdate(7UL, h, i, LNMoney.MilliSatoshis(10), 10u, None, None, None) - makeUpdate(8UL, i, j, LNMoney.MilliSatoshis(10), 10u, None, None, None) - ] - let publicChannels = - channels - |> Map.map(fun scid ann -> - let (_, update) = updates |> Seq.find(fun (upd, _) -> upd.ShortChannelId = scid) - let (maybeUpdate1, maybeUpdate2) = if (update.ChannelFlags &&& 1uy = 1uy) then (Some(update), None) else (None, Some(update)) - let pc = PublicChannel.Create(ann, TxId.Zero, Money.Satoshis(1000L), maybeUpdate1, maybeUpdate2) - (pc) - ) - - let ignored = - Routing.getIgnoredChannelDesc(publicChannels) (Set[c; j; (NodeId((new Key()).PubKey))]) - |> Set - Expect.isTrue(ignored.Contains({ ChannelDesc.ShortChannelId = ShortChannelId.FromUInt64(2UL); A = b; B = c })) "" - Expect.isTrue(ignored.Contains({ ChannelDesc.ShortChannelId = ShortChannelId.FromUInt64(2UL); A = c; B = b })) "" - Expect.isTrue(ignored.Contains({ ChannelDesc.ShortChannelId = ShortChannelId.FromUInt64(3UL); A = c; B = d })) "" - Expect.isTrue(ignored.Contains({ ChannelDesc.ShortChannelId = ShortChannelId.FromUInt64(8UL); A = i; B = j })) "" - - testCase "limit routes to 20 hops" <| fun () -> - let nodes = [ for _ in 0..22 -> (new Key()).PubKey |> NodeId ] - let updates = - Seq.zip (nodes |> List.rev |> List.skip 1 |> List.rev) (nodes |> Seq.skip 1) // (0, 1) :: (1, 2) :: ... - |> Seq.mapi (fun i (na, nb) -> makeUpdate((uint64 i), na, nb, LNMoney.MilliSatoshis(5), 0u, None, None, None) ) - - let g = DirectedLNGraph.Create().AddEdges(updates) - let r18 = - (Routing.findRoute g (nodes.[0]) nodes.[18] DEFAULT_AMOUNT_MSAT 1 (Set.empty) (Set.empty) (Set.empty) DEFAULT_ROUTE_PARAMS (BlockHeight(400000u))) - |> Result.deref - Expect.sequenceEqual (hops2Ids(r18)) [ for i in 0..17 -> (uint64 i) ] "" - let r19 = - (Routing.findRoute g (nodes.[0]) nodes.[19] DEFAULT_AMOUNT_MSAT 1 (Set.empty) (Set.empty) (Set.empty) DEFAULT_ROUTE_PARAMS (BlockHeight(400000u))) - |> Result.deref - Expect.sequenceEqual (hops2Ids(r19)) [ for i in 0..18 -> (uint64 i) ] "" - let r20 = - (Routing.findRoute g (nodes.[0]) nodes.[20] DEFAULT_AMOUNT_MSAT 1 (Set.empty) (Set.empty) (Set.empty) DEFAULT_ROUTE_PARAMS (BlockHeight(400000u))) - |> Result.deref - Expect.sequenceEqual (hops2Ids(r20)) [ for i in 0..19 -> (uint64 i) ] "" - let r21 = - (Routing.findRoute g (nodes.[0]) nodes.[21] DEFAULT_AMOUNT_MSAT 1 (Set.empty) (Set.empty) (Set.empty) DEFAULT_ROUTE_PARAMS (BlockHeight(400000u))) - Expect.isError (Result.ToFSharpCoreResult r21) "" - - testCase "ignore cheaper route when it has more than 20 hops" <| fun _ -> - let nodes = [ for _ in 0..50 -> (new Key()).PubKey |> NodeId ] - let updates = - List.zip (nodes |> List.rev |> List.skip 1 |> List.rev) (nodes |> List.skip 1) // (0, 1) :: (1, 2) :: ... - |> List.mapi (fun i (na, nb) -> makeUpdate((uint64 i), na, nb, LNMoney.One, 0u, None, None, None) ) - - // add expensive but shorter route - let updates2 = (makeUpdate(99UL, nodes.[2], nodes.[48], LNMoney.MilliSatoshis(1000L), 0u, None, None, None)) :: updates - let g = DirectedLNGraph.Create().AddEdges(updates2) - let route = - Routing.findRoute g nodes.[0] nodes.[49] DEFAULT_AMOUNT_MSAT 1 (Set.empty)(Set.empty)(Set.empty) DEFAULT_ROUTE_PARAMS (BlockHeight(400000u)) - |> Result.deref - Expect.sequenceEqual (hops2Ids(route)) [0UL; 1UL; 99UL; 48UL] "" - - testCase "ignore cheaper route when it has more than the requested CLTV limit" <| fun _ -> - let updates = [ - makeUpdate(1UL, a, b, LNMoney.One, 0u, Some(LNMoney.Zero), None, Some(BlockHeightOffset16(50us))) - makeUpdate(2UL, b, c, LNMoney.One, 0u, Some(LNMoney.Zero), None, Some(BlockHeightOffset16(50us))) - makeUpdate(3UL, c, d, LNMoney.One, 0u, Some(LNMoney.Zero), None, Some(BlockHeightOffset16(50us))) - makeUpdate(4UL, a, e, LNMoney.One, 0u, Some(LNMoney.Zero), None, Some(BlockHeightOffset16(9us))) - makeUpdate(5UL, e, f, LNMoney.MilliSatoshis(5), 0u, Some(LNMoney.Zero), None, Some(BlockHeightOffset16(9us))) - makeUpdate(6UL, f, d, LNMoney.MilliSatoshis(5), 0u, Some(LNMoney.Zero), None, Some(BlockHeightOffset16(9us))) - ] - let g = DirectedLNGraph.Create().AddEdges(updates) - let route = - Routing.findRoute (g) a d DEFAULT_AMOUNT_MSAT 1 (Set.empty) (Set.empty) (Set.empty) {DEFAULT_ROUTE_PARAMS with RouteMaxCLTV = (BlockHeightOffset16(28us))} (BlockHeight(400000u)) - |> Result.deref - - Expect.sequenceEqual (hops2Ids(route)) [4UL;5UL;6UL;] "" - - testCase "ignore cheaper route when it grows longer than the requested size" <| fun _ -> - let updates = [ - makeUpdate(1UL, a, b, LNMoney.One, 0u, Some(LNMoney.Zero), None, (Some (BlockHeightOffset16(9us)))) - makeUpdate(2UL, b, c, LNMoney.One, 0u, Some(LNMoney.Zero), None, (Some (BlockHeightOffset16(9us)))) - makeUpdate(3UL, c, d, LNMoney.One, 0u, Some(LNMoney.Zero), None, (Some (BlockHeightOffset16(9us)))) - makeUpdate(4UL, d, e, LNMoney.One, 0u, Some(LNMoney.Zero), None, (Some (BlockHeightOffset16(9us)))) - makeUpdate(5UL, e, f, LNMoney.One, 0u, Some(LNMoney.MilliSatoshis(5)), None, (Some (BlockHeightOffset16(9us)))) - makeUpdate(6UL, b, f, LNMoney.One, 0u, Some(LNMoney.MilliSatoshis(5)), None, (Some (BlockHeightOffset16(9us)))) - ] - let g = DirectedLNGraph.Create().AddEdges(updates) - let route = - Routing.findRoute g a f DEFAULT_AMOUNT_MSAT 1 (Set.empty)(Set.empty)(Set.empty) { DEFAULT_ROUTE_PARAMS with RouteMaxLength = 3} (BlockHeight(400000u)) - |> Result.deref - Expect.sequenceEqual (hops2Ids(route)) [1UL; 6UL] "" - - testCase "ignore loops" <| fun _ -> - let updates = [ - makeUpdate(1UL, a, b, LNMoney.MilliSatoshis(10), 10u, None, None, None) - makeUpdate(2UL, b, c, LNMoney.MilliSatoshis(10), 10u, None, None, None) - makeUpdate(3UL, c, a, LNMoney.MilliSatoshis(10), 10u, None, None, None) - makeUpdate(4UL, c, d, LNMoney.MilliSatoshis(10), 10u, None, None, None) - makeUpdate(5UL, d, e, LNMoney.MilliSatoshis(10), 10u, None, None, None) - ] - - let g = DirectedLNGraph.Create().AddEdges(updates) - let route1 = - Routing.findRoute g a e DEFAULT_AMOUNT_MSAT 1 (Set.empty)(Set.empty)(Set.empty) DEFAULT_ROUTE_PARAMS (BlockHeight(400000u)) - |> Result.deref - Expect.sequenceEqual (hops2Ids(route1)) [1UL;2UL; 4UL; 5UL;] "" - - testCase "ensure the route calculation terminates correctly when selecting 0-fees edges" <| fun _ -> - let updates = [ - makeUpdate(1UL, a, b, LNMoney.MilliSatoshis(10), 10u,None,None,None) - makeUpdate(2UL, b, c, LNMoney.MilliSatoshis(10), 10u,None,None,None) - makeUpdate(4UL, c, d, LNMoney.MilliSatoshis(10), 10u,None,None,None) - makeUpdateSimple(3UL, b, e) - makeUpdateSimple(6UL, e, f) - makeUpdateSimple(6UL, f, e) - makeUpdateSimple(5UL, e, d) - ] - let g = DirectedLNGraph.Create().AddEdges(updates) - let route1 = - Routing.findRoute g a d DEFAULT_AMOUNT_MSAT 1 (Set.empty)(Set.empty)(Set.empty) DEFAULT_ROUTE_PARAMS (BlockHeight(400000u)) - |> Result.deref - Expect.sequenceEqual (hops2Ids(route1)) [1UL; 3UL; 5UL;] "" - - // +---+ +---+ +---+ - // | A +-----+ | B +----------> | C | - // +-+-+ | +-+-+ +-+-+ - // ^ | ^ | - // | | | | - // | v----> + | | - // +-+-+ <-+-+ +-+-+ - // | D +----------> | E +----------> | F | - // +---+ +---+ +---+ - // - testCase "find the k-shortest paths in a graph, k = 4" <| fun _ -> - let updates = [ - makeUpdate(1UL, d, a, LNMoney.One, 0u, None, None, None) - makeUpdate(2UL, d, e, LNMoney.One, 0u, None, None, None) - makeUpdate(3UL, a, e, LNMoney.One, 0u, None, None, None) - makeUpdate(4UL, e, b, LNMoney.One, 0u, None, None, None) - makeUpdate(5UL, e, f, LNMoney.One, 0u, None, None, None) - makeUpdate(6UL, b, c, LNMoney.One, 0u, None, None, None) - makeUpdate(7UL, c, f, LNMoney.One, 0u, None, None, None) - ] - let g = DirectedLNGraph.Create().AddEdges(updates) - let fourShortestPaths = - Graph.yenKShortestPaths g d f DEFAULT_AMOUNT_MSAT (Set.empty)(Set.empty)(Set.empty) 4 None (BlockHeight.One) (fun _ -> true) - |> Seq.toList - Expect.equal (fourShortestPaths.Length) 4 (sprintf "found shortest paths were %A" fourShortestPaths) - let actuals = [ for i in 0..3 do fourShortestPaths.[i].Path |> Seq.map ChannelHop.FromGraphEdge |> hops2Ids ] - Expect.sequenceEqual actuals.[0] [2UL; 5UL] "" - Expect.sequenceEqual actuals.[1] [1UL; 3UL; 5UL] "" - Expect.sequenceEqual actuals.[2] [2UL; 4UL; 6UL; 7UL] "" - Expect.sequenceEqual actuals.[3] [1UL; 3UL; 4UL; 6UL; 7UL] "" - - testCase "find the k shortest path (wikipedia example)" <| fun _ -> - let updates = [ - makeUpdate(10UL, c, e, LNMoney.MilliSatoshis(2), 0u, None,None,None) - makeUpdate(20UL, c, d, LNMoney.MilliSatoshis(3), 0u, None,None,None) - makeUpdate(30UL, d, f, LNMoney.MilliSatoshis(4), 5u, None,None,None) // D -> F has a higher cost to distinguish from the 2nd cheapest route - makeUpdate(40UL, e, d, LNMoney.MilliSatoshis(1), 0u, None,None,None) - makeUpdate(50UL, e, f, LNMoney.MilliSatoshis(2), 0u, None,None,None) - makeUpdate(60UL, e, g, LNMoney.MilliSatoshis(3), 0u, None,None,None) - makeUpdate(70UL, f, g, LNMoney.MilliSatoshis(2), 0u, None,None,None) - - makeUpdate(80UL, f, h, LNMoney.MilliSatoshis(1), 0u, None,None,None) - makeUpdate(90UL, g, h, LNMoney.MilliSatoshis(2), 0u, None,None,None) - ] - - let graph = DirectedLNGraph.Create().AddEdges(updates) - let twoShortestPaths = - Graph.yenKShortestPaths graph c h DEFAULT_AMOUNT_MSAT Set.empty Set.empty Set.empty 2 None (BlockHeight(0u)) (fun _ -> true) - |> Seq.toList - - Expect.equal (twoShortestPaths.Length) 2 "" - let shortest = twoShortestPaths.[0] - Expect.sequenceEqual (shortest.Path |> Seq.map(ChannelHop.FromGraphEdge) |> hops2Ids) [10UL; 50UL; 80UL] "" - let secondShortest = twoShortestPaths.[1] - Expect.sequenceEqual (secondShortest.Path |> Seq.map(ChannelHop.FromGraphEdge) |> hops2Ids) [10UL; 60UL; 90UL] "" - - testCase "terminate looking for k-shortest path if there are no more alternative paths than k, must not consider routes going back on their steps" <| fun _ -> - // simple graph with only 2 possible paths from A to F - let updates = [ - makeUpdate(1UL, a, b, LNMoney.MilliSatoshis(1), 0u, None, None, None) - makeUpdate(1UL, b, a, LNMoney.MilliSatoshis(1), 0u, None, None, None) - makeUpdate(2UL, b, c, LNMoney.MilliSatoshis(1), 0u, None, None, None) - makeUpdate(2UL, c, b, LNMoney.MilliSatoshis(1), 0u, None, None, None) - makeUpdate(3UL, c, f, LNMoney.MilliSatoshis(1), 0u, None, None, None) - makeUpdate(3UL, f, c, LNMoney.MilliSatoshis(1), 0u, None, None, None) - makeUpdate(4UL, c, d, LNMoney.MilliSatoshis(1), 0u, None, None, None) - makeUpdate(4UL, d, c, LNMoney.MilliSatoshis(1), 0u, None, None, None) - makeUpdate(41UL, d, c, LNMoney.MilliSatoshis(1), 0u, None, None, None) // there is more than one D -> C channel - makeUpdate(5UL, d, e, LNMoney.MilliSatoshis(1), 0u, None, None, None) - makeUpdate(5UL, e, d, LNMoney.MilliSatoshis(1), 0u, None, None, None) - makeUpdate(6UL, e, f, LNMoney.MilliSatoshis(1), 0u, None, None, None) - makeUpdate(6UL, f, e, LNMoney.MilliSatoshis(1), 0u, None, None, None) - ] - let graph = DirectedLNGraph.Create().AddEdges(updates) - let foundPaths = - Graph.yenKShortestPaths graph a f DEFAULT_AMOUNT_MSAT (Set.empty)(Set.empty)(Set.empty) 3 None (BlockHeight 0u) (fun _ -> true) - |> Seq.toList - Expect.equal (foundPaths.Length) 2 "" - Expect.sequenceEqual (foundPaths.[0].Path |> Seq.map(ChannelHop.FromGraphEdge) |> hops2Ids) [1UL; 2UL; 3UL] "" - Expect.sequenceEqual (foundPaths.[1].Path |> Seq.map(ChannelHop.FromGraphEdge) |> hops2Ids) [1UL; 2UL; 4UL; 5UL; 6UL] "" - - testCase "select a random route below the requested fee" <| fun _ -> - let updates = [ - makeUpdate(1UL, a, b, LNMoney.MilliSatoshis(1), 0u, None, None, None) - makeUpdate(4UL, a, e, LNMoney.MilliSatoshis(1), 0u, None, None, None) - makeUpdate(2UL, b, c, LNMoney.MilliSatoshis(2), 0u, None, None, None) - makeUpdate(3UL, c, d, LNMoney.MilliSatoshis(3), 0u, None, None, None) - makeUpdate(5UL, e, f, LNMoney.MilliSatoshis(3), 0u, None, None, None) - makeUpdate(6UL, f, d, LNMoney.MilliSatoshis(3), 0u, None, None, None) - makeUpdate(7UL, e, c, LNMoney.MilliSatoshis(9), 0u, None, None, None) - ] - let graph = DirectedLNGraph.Create().AddEdges(updates) - let strictFeeParams = { DEFAULT_ROUTE_PARAMS with MaxFeeBase = LNMoney.MilliSatoshis(7); MaxFeePCT = 0. } - for _ in 0..10 do - let r = Routing.findRoute graph a d DEFAULT_AMOUNT_MSAT 3 (Set.empty) (Set.empty) (Set.empty) strictFeeParams (BlockHeight(400000u)) - Expect.isOk (Result.ToFSharpCoreResult r) "" - let someRoute = r |> Result.deref - let routeCost = - (Graph.pathWeight (hops2Edges(someRoute)) DEFAULT_AMOUNT_MSAT false (BlockHeight 0u) None).Cost - DEFAULT_AMOUNT_MSAT - let costMSat = routeCost.MilliSatoshi - Expect.isTrue(costMSat = 5L || costMSat = 6L) "" - testCase "Use weight ratios to when computing the edge weight" <| fun _ -> - let largeCap = LNMoney.MilliSatoshis(8000000000L) - let updates = [ - makeUpdate(1UL, a, b, LNMoney.MilliSatoshis(0), 0u, Some(LNMoney.Zero), None, Some (BlockHeightOffset16(13us))) - makeUpdate(4UL, a, e, LNMoney.MilliSatoshis(0), 0u, Some(LNMoney.Zero), None, Some (BlockHeightOffset16(12us))) - makeUpdate(2UL, b, c, LNMoney.MilliSatoshis(1), 0u, Some(LNMoney.Zero), None, Some (BlockHeightOffset16(500us))) - makeUpdate(3UL, c, d, LNMoney.MilliSatoshis(1), 0u, Some(LNMoney.Zero), None, Some (BlockHeightOffset16(500us))) - makeUpdate(5UL, e, f, LNMoney.MilliSatoshis(2), 0u, Some(LNMoney.Zero), None, Some (BlockHeightOffset16(9us))) - makeUpdate(6UL, f, d, LNMoney.MilliSatoshis(2), 0u, Some(LNMoney.Zero), None, Some (BlockHeightOffset16(9us))) - makeUpdate(7UL, e, c, LNMoney.MilliSatoshis(2), 0u, Some(LNMoney.Zero), Some(largeCap), Some (BlockHeightOffset16(12us))) - ] - let graph = DirectedLNGraph.Create().AddEdges(updates) - let r = - Routing.findRoute graph a d DEFAULT_AMOUNT_MSAT 0 (Set.empty) (Set.empty) (Set.empty) DEFAULT_ROUTE_PARAMS (BlockHeight(400000u)) - |> Result.deref - Expect.sequenceEqual (r |> hops2Nodes) [(a, b); (b, c); (c, d)] "" - let routeClTVOptimized = - let p = - let r = WeightRatios.TryCreate(1., 0., 0.) |> Result.deref - {DEFAULT_ROUTE_PARAMS with Ratios = Some(r)} - Routing.findRoute graph a d DEFAULT_AMOUNT_MSAT 0 (Set.empty) (Set.empty) (Set.empty) p (BlockHeight(400000u)) - |> Result.deref - Expect.sequenceEqual (routeClTVOptimized |> hops2Nodes) [(a,e); (e, f); (f, d)] "" - - let routeCapOptimized = - let p = - let r = WeightRatios.TryCreate(0., 0., 1.) |> Result.deref - {DEFAULT_ROUTE_PARAMS with Ratios = Some(r)} - Routing.findRoute graph a d DEFAULT_AMOUNT_MSAT 0 (Set.empty) (Set.empty) (Set.empty) p (BlockHeight(400000u)) - |> Result.deref - Expect.sequenceEqual (routeCapOptimized |> hops2Nodes) [(a,e); (e, c); (c, d)] "" - - testCase "Prefer going through an older channel if fees and CLTV are the same" <| fun _ -> - let currentBlockHeight = 554000u - let mu(schid, one, two) = - makeUpdate2(schid, one, two, LNMoney.MilliSatoshis(1), 0u, (Some LNMoney.Zero), None, (Some (BlockHeightOffset16(144us)))) - let updates = [ - mu((sprintf "%dx0x1" currentBlockHeight), a, b) - mu((sprintf "%dx0x4" currentBlockHeight), a, e) - mu((sprintf "%dx0x2" (currentBlockHeight - 3000u)), b, c) // younger channel - mu((sprintf "%dx0x3" (currentBlockHeight - 3000u)), c, d) - mu((sprintf "%dx0x5" currentBlockHeight), e, f) - mu((sprintf "%dx0x6" currentBlockHeight), f, d) - ] - let graph = DirectedLNGraph.Create().AddEdges(updates) - let routeScoreOptimized = - let wr = WeightRatios.TryCreate(0.33, 0.33, 0.33) |> Result.deref - let rp = { DEFAULT_ROUTE_PARAMS with Ratios = Some (wr) } - Routing.findRoute graph a d (DEFAULT_AMOUNT_MSAT / 2) 1 (Set.empty)(Set.empty)(Set.empty) rp (BlockHeight(currentBlockHeight)) - |> Result.deref - |> hops2Nodes - Expect.sequenceEqual routeScoreOptimized [(a,b); (b,c); (c,d)] "" - - testCase "prefer a route with a smaller total CLTV if fees and scores are the same" <| fun _ -> - let mu (schid, one, two, cltv) = - makeUpdate(schid, one, two, LNMoney.One, 0u, (Some LNMoney.Zero), None, (Some (BlockHeightOffset16 cltv))) - let updates = [ - mu(1UL, a, b, 12us) - mu(4UL, a, e, 12us) - mu(2UL, b, c, 10us) // smaller cltv - mu(3UL, c, d, 12us) - mu(5UL, e, f, 12us) - mu(6UL, f, d, 12us) - ] - let graph = DirectedLNGraph.Create().AddEdges(updates) - let routeScoreOptimized = - let wr = WeightRatios.TryCreate(0.33, 0.33, 0.33) |> Result.deref - let rp = { DEFAULT_ROUTE_PARAMS with Ratios = Some (wr) } - Routing.findRoute graph a d (DEFAULT_AMOUNT_MSAT / 2) 1 (Set.empty)(Set.empty)(Set.empty) rp (BlockHeight(400000u)) - |> Result.deref - |> hops2Nodes - Expect.sequenceEqual routeScoreOptimized [(a,b); (b,c); (c,d)] "" - - () - - testCase "avoid a route that breaks off the max CLTV" <| fun _ -> - let mu (schid, one, two, cltv) = - makeUpdate(schid, one, two, LNMoney.One, 0u, (Some LNMoney.Zero), None, (Some (BlockHeightOffset16 cltv))) - // A --> B --> C --> D is cheaper but has a total CLTV > 2016! - // A --> E --> F --> D is more expensive but has a total CLTV < 2016 - let updates = [ - mu(1UL, a, b, 144us) - mu(4UL, a, e, 144us) - mu(2UL, b, c, 1000us) - mu(3UL, c, d, 900us) - mu(5UL, e, f, 144us) - mu(6UL, f, d, 144us) +let tests = + testList + "Route Calculation" + [ + let calculateRouteSimple routeParams = + let updates = + [ + makeUpdate( + 1UL, + a, + b, + LNMoney.MilliSatoshis(1L), + 10u, + None, + None, + BlockHeightOffset16.One |> Some + ) + makeUpdate( + 2UL, + b, + c, + LNMoney.MilliSatoshis(1L), + 10u, + None, + None, + BlockHeightOffset16.One |> Some + ) + makeUpdate( + 3UL, + c, + d, + LNMoney.MilliSatoshis(1L), + 10u, + None, + None, + BlockHeightOffset16.One |> Some + ) + makeUpdate( + 4UL, + d, + e, + LNMoney.MilliSatoshis(1L), + 10u, + None, + None, + BlockHeightOffset16.One |> Some + ) + ] + + let g = DirectedLNGraph.Create().AddEdges(updates) + + let route = + Routing.findRoute + (g) + (a) + (e) + DEFAULT_AMOUNT_MSAT + 1 + (Set.empty) + (Set.empty) + (Set.empty) + routeParams + (BlockHeight 400000u) + |> Result.deref + + Expect.sequenceEqual + (route |> hops2Ids) + ([ 1UL; 2UL; 3UL; 4UL ]) + "" + + testCase "Calculate simple route" + <| fun _ -> calculateRouteSimple DEFAULT_ROUTE_PARAMS + + testCase "Check fee against max pct properly" + <| fun _ -> + // fee is acceptable if it is either + // - below our maximum fee base + // - below our maximum fraction of the paid amount + + // here we have a maximum fee base of 1 msat, and all our updates have a base fee of 10 msat + // so our fee will always be above the base fee, and we will always check that it is below our maximum percentage + // of the amount being paid + calculateRouteSimple + { DEFAULT_ROUTE_PARAMS with + MaxFeeBase = LNMoney.One + } + + testCase "Calculate the shortest path (correct fees)" + <| fun _ -> + let amount = LNMoney.MilliSatoshis(10000L) + let expectedCost = 10007L |> LNMoney.MilliSatoshis + + let updates = + [ + makeUpdate( + 1UL, + a, + b, + LNMoney.One, + 200u, + Some(LNMoney.Zero), + None, + None + ) + makeUpdate( + 4UL, + a, + e, + LNMoney.One, + 200u, + Some(LNMoney.Zero), + None, + None + ) + makeUpdate( + 2UL, + b, + c, + LNMoney.One, + 300u, + Some(LNMoney.Zero), + None, + None + ) + makeUpdate( + 3UL, + c, + d, + LNMoney.One, + 400u, + Some(LNMoney.Zero), + None, + None + ) + makeUpdate( + 5UL, + e, + f, + LNMoney.One, + 400u, + Some(LNMoney.Zero), + None, + None + ) + makeUpdate( + 6UL, + f, + d, + LNMoney.One, + 100u, + Some(LNMoney.Zero), + None, + None + ) + ] + + let graph = DirectedLNGraph.Create().AddEdges(updates) + + let route = + Routing.findRoute + graph + a + d + amount + 1 + (Set.empty) + (Set.empty) + (Set.empty) + DEFAULT_ROUTE_PARAMS + (BlockHeight(400000u)) + |> Result.deref + + let totalCost = + Graph.pathWeight + (hops2Edges(route)) + (amount) + false + BlockHeight.Zero + None + |> fun x -> x.Cost + + Expect.sequenceEqual (hops2Ids(route)) [ 4UL; 5UL; 6UL ] "" + Expect.equal totalCost expectedCost "" + + /// Now channel 5 could route the amount (10000) but not the amount + fees (10007) + let (desc, update) = + makeUpdate( + 5UL, + e, + f, + LNMoney.One, + 400u, + Some(LNMoney.Zero), + Some(LNMoney.MilliSatoshis(10005L)), + None + ) + + let graph1 = graph.AddEdge(desc, update) + + let route1 = + Routing.findRoute + (graph1) + a + d + amount + 1 + (Set.empty) + (Set.empty) + (Set.empty) + DEFAULT_ROUTE_PARAMS + (BlockHeight(400000u)) + |> Result.deref + + Expect.sequenceEqual (hops2Ids(route1)) [ 1UL; 2UL; 3UL ] "" + + testCase + "calculate route considering the direct channel pays no fees" + <| fun _ -> + let updates = + [ + makeUpdate( + 1UL, + a, + b, + LNMoney.MilliSatoshis(5L), + 0u, + None, + None, + None + ) + makeUpdate( + 2UL, + a, + d, + LNMoney.MilliSatoshis(15L), + 0u, + None, + None, + None + ) + makeUpdate( + 3UL, + b, + c, + LNMoney.MilliSatoshis(5L), + 0u, + None, + None, + None + ) + makeUpdate( + 4UL, + c, + d, + LNMoney.MilliSatoshis(5L), + 0u, + None, + None, + None + ) + makeUpdate( + 5UL, + d, + e, + LNMoney.MilliSatoshis(5L), + 0u, + None, + None, + None + ) + ] + + let g = DirectedLNGraph.Create().AddEdges(updates) + + let route = + Routing.findRoute + (g) + a + e + DEFAULT_AMOUNT_MSAT + 1 + (Set.empty) + (Set.empty) + (Set.empty) + DEFAULT_ROUTE_PARAMS + (BlockHeight(400000u)) + |> Result.deref + + Expect.sequenceEqual (hops2Ids(route)) [ 2UL; 5UL ] "" + + testCase "Calculate simple route (add and remove edges)" + <| fun _ -> + let updates = + [ + makeUpdateSimple(1UL, a, b) + makeUpdateSimple(2UL, b, c) + makeUpdateSimple(3UL, c, d) + makeUpdateSimple(4UL, d, e) + ] + + let g = DirectedLNGraph.Create().AddEdges(updates) + + let route1 = + Routing.findRoute + g + a + e + DEFAULT_AMOUNT_MSAT + 1 + (Set.empty) + (Set.empty) + (Set.empty) + DEFAULT_ROUTE_PARAMS + (BlockHeight(400000u)) + |> Result.deref + + Expect.sequenceEqual + (hops2Ids(route1)) + [ 1UL; 2UL; 3UL; 4UL ] + "" + + let graphWithRemovedEdge = + g.RemoveEdge( + { + ChannelDesc.ShortChannelId = + ShortChannelId.FromUInt64(3UL) + A = c + B = d + } + ) + + let route2 = + Routing.findRoute + (graphWithRemovedEdge) + a + e + DEFAULT_AMOUNT_MSAT + 1 + (Set.empty) + (Set.empty) + (Set.empty) + DEFAULT_ROUTE_PARAMS + (BlockHeight(400000u)) + + Expect.isError (Result.ToFSharpCoreResult route2) "" + + testCase "calculate the shortest path (select direct channel)" + <| fun _ -> + let updates = + [ + makeUpdate( + 1UL, + a, + b, + LNMoney.Zero, + 0u, + None, + None, + None + ) + makeUpdate( + 4UL, + a, + d, + LNMoney.MilliSatoshis(50L), + 0u, + None, + None, + None + ) + makeUpdate( + 2UL, + b, + c, + LNMoney.MilliSatoshis(0L), + 0u, + None, + None, + None + ) + makeUpdate( + 3UL, + c, + d, + LNMoney.MilliSatoshis(0L), + 0u, + None, + None, + None + ) + ] + + let graph = DirectedLNGraph.Create().AddEdges(updates) + + let route = + Routing.findRoute + (graph) + a + d + DEFAULT_AMOUNT_MSAT + 2 + (Set.empty) + (Set.empty) + (Set.empty) + DEFAULT_ROUTE_PARAMS + (BlockHeight(400000u)) + |> Result.deref + + Expect.sequenceEqual (hops2Ids(route)) [ 4UL ] "" + + let (h, i) = + ("03de6411928b3b0217b50b27b269aea8457f7b88797402fff3e86f2d28775af5d5" + |> (hex.DecodeData >> PubKey >> NodeId), // H + "03ffda25c95266e33c06c8006bbcd3985932a79580dfb07d95855c332a0e13b9ef" + |> (hex.DecodeData >> PubKey >> NodeId)) // I target + + testCase + "find a route using channels with hltcMaximumMsat close to the payment amount" + <| fun _ -> + let updates = + [ + makeUpdate(1UL, f, g, LNMoney.One, 0u, None, None, None) + makeUpdate( + 2UL, + g, + h, + LNMoney.One, + 0u, + None, + Some( + DEFAULT_AMOUNT_MSAT + LNMoney.MilliSatoshis(50L) + ), + None + ) + makeUpdate(3UL, h, i, LNMoney.One, 0u, None, None, None) + ] + + let graph = DirectedLNGraph.Create().AddEdges(updates) + + let route = + Routing.findRoute + graph + f + i + DEFAULT_AMOUNT_MSAT + 1 + (Set.empty) + (Set.empty) + (Set.empty) + DEFAULT_ROUTE_PARAMS + (BlockHeight(400000u)) + |> Result.deref + + Expect.sequenceEqual (hops2Ids(route)) [ 1UL; 2UL; 3UL ] "" + + testCase + "find a route using channels with htlcMinimumMsat close to the payment amount" + <| fun _ -> + let updates = + [ + makeUpdate(1UL, f, g, LNMoney.One, 0u, None, None, None) + makeUpdate( + 2UL, + g, + h, + LNMoney.One, + 0u, + Some( + DEFAULT_AMOUNT_MSAT + LNMoney.MilliSatoshis(50L) + ), + None, + None + ) + makeUpdate(3UL, h, i, LNMoney.One, 0u, None, None, None) + ] + + let graph = DirectedLNGraph.Create().AddEdges(updates) + + let route = + Routing.findRoute + graph + f + i + DEFAULT_AMOUNT_MSAT + 1 + (Set.empty) + (Set.empty) + (Set.empty) + DEFAULT_ROUTE_PARAMS + (BlockHeight(400000u)) + + Expect.isError (Result.ToFSharpCoreResult route) "" + + testCase + "if there are multiple channels between the same node, select the cheapest" + <| fun _ -> + let updates = + [ + makeUpdate( + 1UL, + f, + g, + LNMoney.Zero, + 0u, + None, + None, + None + ) + makeUpdate( + 2UL, + g, + h, + LNMoney.MilliSatoshis(5L), + 5u, + None, + None, + None + ) // expensive g -> h channel + makeUpdate( + 6UL, + g, + h, + LNMoney.MilliSatoshis(0L), + 0u, + None, + None, + None + ) // cheap g -> h channel + makeUpdate( + 3UL, + h, + i, + LNMoney.MilliSatoshis(0L), + 0u, + None, + None, + None + ) + ] + + let graph = DirectedLNGraph.Create().AddEdges(updates) + + let route = + Routing.findRoute + graph + f + i + DEFAULT_AMOUNT_MSAT + 1 + (Set.empty) + (Set.empty) + (Set.empty) + DEFAULT_ROUTE_PARAMS + (BlockHeight(400000u)) + |> Result.deref + + Expect.sequenceEqual (hops2Ids(route)) [ 1UL; 6UL; 3UL ] "" + + testCase "Calculate longer but cheaper route" + <| fun _ -> + let updates = + [ + makeUpdateSimple(1UL, a, b) + makeUpdateSimple(2UL, b, c) + makeUpdateSimple(3UL, c, d) + makeUpdateSimple(4UL, d, e) + makeUpdate( + 5UL, + b, + e, + LNMoney.MilliSatoshis(10L), + 10u, + None, + None, + None + ) + ] + + let g = DirectedLNGraph.Create().AddEdges(updates) + + let route = + Routing.findRoute + g + a + e + DEFAULT_AMOUNT_MSAT + 1 + (Set.empty) + (Set.empty) + (Set.empty) + DEFAULT_ROUTE_PARAMS + (BlockHeight(400000u)) + |> Result.deref + + Expect.sequenceEqual (hops2Ids(route)) [ 1UL; 2UL; 3UL; 4UL ] "" + + testCase "no local channels" + <| fun _ -> + let updates = + [ + makeUpdateSimple(2UL, b, c) + makeUpdateSimple(4UL, d, e) + ] + + let g = DirectedLNGraph.Create().AddEdges(updates) + + let route = + Routing.findRoute + (g) + a + e + DEFAULT_AMOUNT_MSAT + 1 + (Set.empty) + (Set.empty) + (Set.empty) + DEFAULT_ROUTE_PARAMS + (BlockHeight(400000u)) + + Expect.isError (Result.ToFSharpCoreResult route) "" + + testCase "route not found (source OR target node not connected)" + <| fun _ -> + let updates = + [ + makeUpdateSimple(2UL, b, c) + makeUpdateSimple(4UL, c, d) + ] + + let g = + DirectedLNGraph + .Create() + .AddEdges(updates) + .AddVertex(a) + .AddVertex(e) + + Expect.isError + (Result.ToFSharpCoreResult( + Routing.findRoute + g + a + d + DEFAULT_AMOUNT_MSAT + 1 + (Set.empty) + (Set.empty) + (Set.empty) + DEFAULT_ROUTE_PARAMS + (BlockHeight(400000u)) + )) + "" + + Expect.isError + (Result.ToFSharpCoreResult( + Routing.findRoute + g + b + e + DEFAULT_AMOUNT_MSAT + 1 + (Set.empty) + (Set.empty) + (Set.empty) + DEFAULT_ROUTE_PARAMS + (BlockHeight(400000u)) + )) + "" + + testCase "route not found (amount too high OR too low)" + <| fun _ -> + let highAmount = DEFAULT_AMOUNT_MSAT * 10 + let lowAmount = DEFAULT_AMOUNT_MSAT / 10 + + let updatesHi = + [ + makeUpdateSimple(1UL, a, b) + makeUpdate( + 2UL, + b, + c, + LNMoney.Zero, + 0u, + None, + Some(DEFAULT_AMOUNT_MSAT), + None + ) + makeUpdateSimple(3UL, c, d) + ] + + let updatesLow = + [ + makeUpdateSimple(1UL, a, b) + makeUpdate( + 2UL, + b, + c, + LNMoney.Zero, + 0u, + Some(DEFAULT_AMOUNT_MSAT), + None, + None + ) + makeUpdateSimple(3UL, c, d) + ] + + let gHigh = DirectedLNGraph.Create().AddEdges(updatesHi) + let gLow = DirectedLNGraph.Create().AddEdges(updatesLow) + + Expect.isError + (Result.ToFSharpCoreResult( + Routing.findRoute + gHigh + a + d + highAmount + 1 + (Set.empty) + (Set.empty) + (Set.empty) + DEFAULT_ROUTE_PARAMS + (BlockHeight(400000u)) + )) + "" + + Expect.isError + (Result.ToFSharpCoreResult( + Routing.findRoute + gLow + a + d + lowAmount + 1 + (Set.empty) + (Set.empty) + (Set.empty) + DEFAULT_ROUTE_PARAMS + (BlockHeight(400000u)) + )) + "" + + testCase "route to self" + <| fun _ -> + let updates = + [ + makeUpdateSimple(1UL, a, b) + makeUpdateSimple(2UL, b, c) + makeUpdateSimple(3UL, c, d) + ] + + let g = DirectedLNGraph.Create().AddEdges(updates) + + let route = + Routing.findRoute + g + a + a + DEFAULT_AMOUNT_MSAT + 1 + (Set.empty) + (Set.empty) + (Set.empty) + DEFAULT_ROUTE_PARAMS + (BlockHeight(400000u)) + + Expect.isError (Result.ToFSharpCoreResult route) "" + + testCase "route to immediate neighbor" + <| fun _ -> + let updates = + [ + makeUpdateSimple(1UL, a, b) + makeUpdateSimple(2UL, b, c) + makeUpdateSimple(3UL, c, d) + makeUpdateSimple(4UL, d, e) + ] + + let g = DirectedLNGraph.Create().AddEdges(updates) + + let route = + Routing.findRoute + (g) + a + b + DEFAULT_AMOUNT_MSAT + 1 + (Set.empty) + (Set.empty) + (Set.empty) + DEFAULT_ROUTE_PARAMS + (BlockHeight(400000u)) + |> Result.deref + + Expect.sequenceEqual (hops2Ids(route)) [ 1UL ] "" + + testCase "directed graph" + <| fun _ -> + let updates = + [ + makeUpdateSimple(1UL, a, b) + makeUpdateSimple(2UL, b, c) + makeUpdateSimple(3UL, c, d) + makeUpdateSimple(4UL, d, e) + ] + // a -> e works, e -> a fails + let g = DirectedLNGraph.Create().AddEdges(updates) + + let route1 = + Routing.findRoute + g + a + e + DEFAULT_AMOUNT_MSAT + 1 + (Set.empty) + (Set.empty) + (Set.empty) + DEFAULT_ROUTE_PARAMS + (BlockHeight(400000u)) + |> Result.deref + + Expect.sequenceEqual + (hops2Ids(route1)) + [ 1UL; 2UL; 3UL; 4UL ] + "" + + let route2 = + Routing.findRoute + g + e + e + DEFAULT_AMOUNT_MSAT + 1 + (Set.empty) + (Set.empty) + (Set.empty) + DEFAULT_ROUTE_PARAMS + (BlockHeight(400000u)) + + Expect.isError (Result.ToFSharpCoreResult route2) "" + + testCase "calculate route and return metadata" + <| fun _ -> + let uab = + { + UnsignedChannelUpdateMsg.ChainHash = + Network.RegTest.GenesisHash + ShortChannelId = ShortChannelId.FromUInt64(1UL) + Timestamp = 0u + MessageFlags = 0uy + ChannelFlags = 0uy + CLTVExpiryDelta = BlockHeightOffset16(1us) + HTLCMinimumMSat = LNMoney.MilliSatoshis(42L) + FeeBaseMSat = LNMoney.MilliSatoshis(2500L) + FeeProportionalMillionths = 140u + HTLCMaximumMSat = None + } + + let uba = + { + UnsignedChannelUpdateMsg.ChainHash = + Network.RegTest.GenesisHash + ShortChannelId = ShortChannelId.FromUInt64(1UL) + Timestamp = 1u + MessageFlags = 0uy + ChannelFlags = 1uy + CLTVExpiryDelta = BlockHeightOffset16(1us) + HTLCMinimumMSat = LNMoney.MilliSatoshis(43L) + FeeBaseMSat = LNMoney.MilliSatoshis(2501L) + FeeProportionalMillionths = 141u + HTLCMaximumMSat = None + } + + let ubc = + { + UnsignedChannelUpdateMsg.ChainHash = + Network.RegTest.GenesisHash + ShortChannelId = ShortChannelId.FromUInt64(2UL) + Timestamp = 1u + MessageFlags = 0uy + ChannelFlags = 0uy + CLTVExpiryDelta = BlockHeightOffset16(1us) + HTLCMinimumMSat = LNMoney.MilliSatoshis(44L) + FeeBaseMSat = LNMoney.MilliSatoshis(2502L) + FeeProportionalMillionths = 142u + HTLCMaximumMSat = None + } + + let ucb = + { + UnsignedChannelUpdateMsg.ChainHash = + Network.RegTest.GenesisHash + ShortChannelId = ShortChannelId.FromUInt64(2UL) + Timestamp = 1u + MessageFlags = 0uy + ChannelFlags = 1uy + CLTVExpiryDelta = BlockHeightOffset16(1us) + HTLCMinimumMSat = LNMoney.MilliSatoshis(45L) + FeeBaseMSat = LNMoney.MilliSatoshis(2503L) + FeeProportionalMillionths = 143u + HTLCMaximumMSat = None + } + + let ucd = + { + UnsignedChannelUpdateMsg.ChainHash = + Network.RegTest.GenesisHash + ShortChannelId = ShortChannelId.FromUInt64(3UL) + Timestamp = 1u + MessageFlags = 1uy + ChannelFlags = 0uy + CLTVExpiryDelta = BlockHeightOffset16(1us) + HTLCMinimumMSat = LNMoney.MilliSatoshis(46L) + FeeBaseMSat = LNMoney.MilliSatoshis(2504L) + FeeProportionalMillionths = 144u + HTLCMaximumMSat = + Some(LNMoney.MilliSatoshis(500000000L)) + } + + let udc = + { + UnsignedChannelUpdateMsg.ChainHash = + Network.RegTest.GenesisHash + ShortChannelId = ShortChannelId.FromUInt64(3UL) + Timestamp = 1u + MessageFlags = 0uy + ChannelFlags = 1uy + CLTVExpiryDelta = BlockHeightOffset16(1us) + HTLCMinimumMSat = LNMoney.MilliSatoshis(47L) + FeeBaseMSat = LNMoney.MilliSatoshis(2505L) + FeeProportionalMillionths = 145u + HTLCMaximumMSat = None + } + + let ude = + { + UnsignedChannelUpdateMsg.ChainHash = + Network.RegTest.GenesisHash + ShortChannelId = ShortChannelId.FromUInt64(4UL) + Timestamp = 1u + MessageFlags = 0uy + ChannelFlags = 0uy + CLTVExpiryDelta = BlockHeightOffset16(1us) + HTLCMinimumMSat = LNMoney.MilliSatoshis(48L) + FeeBaseMSat = LNMoney.MilliSatoshis(2506L) + FeeProportionalMillionths = 146u + HTLCMaximumMSat = None + } + + let ued = + { + UnsignedChannelUpdateMsg.ChainHash = + Network.RegTest.GenesisHash + ShortChannelId = ShortChannelId.FromUInt64(4UL) + Timestamp = 1u + MessageFlags = 0uy + ChannelFlags = 1uy + CLTVExpiryDelta = BlockHeightOffset16(1us) + HTLCMinimumMSat = LNMoney.MilliSatoshis(49L) + FeeBaseMSat = LNMoney.MilliSatoshis(2507L) + FeeProportionalMillionths = 147u + HTLCMaximumMSat = None + } + + let updates = + Map.empty + |> Map.add + ({ + ChannelDesc.ShortChannelId = + ShortChannelId.FromUInt64(1UL) + A = a + B = b + }) + uab + |> Map.add + ({ + ChannelDesc.ShortChannelId = + ShortChannelId.FromUInt64(1UL) + A = b + B = a + }) + uba + |> Map.add + ({ + ChannelDesc.ShortChannelId = + ShortChannelId.FromUInt64(2UL) + A = b + B = c + }) + ubc + |> Map.add + ({ + ChannelDesc.ShortChannelId = + ShortChannelId.FromUInt64(2UL) + A = c + B = b + }) + ucb + |> Map.add + ({ + ChannelDesc.ShortChannelId = + ShortChannelId.FromUInt64(3UL) + A = c + B = d + }) + ucd + |> Map.add + ({ + ChannelDesc.ShortChannelId = + ShortChannelId.FromUInt64(3UL) + A = d + B = c + }) + udc + |> Map.add + ({ + ChannelDesc.ShortChannelId = + ShortChannelId.FromUInt64(4UL) + A = d + B = e + }) + ude + |> Map.add + ({ + ChannelDesc.ShortChannelId = + ShortChannelId.FromUInt64(4UL) + A = e + B = d + }) + ued + + let g = + DirectedLNGraph + .Create() + .AddEdges(updates |> Map.toSeq) + + let hops = + Routing.findRoute + g + a + e + DEFAULT_AMOUNT_MSAT + 1 + (Set.empty) + (Set.empty) + (Set.empty) + DEFAULT_ROUTE_PARAMS + (BlockHeight(400000u)) + |> Result.deref + + let e = + [ + (ChannelHop.Create(a, b, uab)) + (ChannelHop.Create(b, c, ubc)) + (ChannelHop.Create(c, d, ucd)) + (ChannelHop.Create(d, e, ude)) + ] + + Expect.sequenceEqual hops e "" + + testCase "convert extra hops to assisted channels" + <| fun _ -> + let extraHop1 = + { + ExtraHop.NodeId = a + ShortChannelId = ShortChannelId.FromUInt64(1UL) + FeeBase = LNMoney.Satoshis(12L) + FeeProportionalMillionths = 10000u + CLTVExpiryDelta = BlockHeightOffset16(12us) + } + + let extraHop2 = + { + ExtraHop.NodeId = b + ShortChannelId = ShortChannelId.FromUInt64(2UL) + FeeBase = LNMoney.Satoshis(200L) + FeeProportionalMillionths = 0u + CLTVExpiryDelta = BlockHeightOffset16(22us) + } + + let extraHop3 = + { + ExtraHop.NodeId = c + ShortChannelId = ShortChannelId.FromUInt64(3UL) + FeeBase = LNMoney.Satoshis(150L) + FeeProportionalMillionths = 0u + CLTVExpiryDelta = BlockHeightOffset16(32us) + } + + let extraHop4 = + { + ExtraHop.NodeId = d + ShortChannelId = ShortChannelId.FromUInt64(4UL) + FeeBase = LNMoney.Satoshis(50L) + FeeProportionalMillionths = 0u + CLTVExpiryDelta = BlockHeightOffset16(42us) + } + + let extraHops = + [ + extraHop1 + extraHop2 + extraHop3 + extraHop4 + ] + + let amount = LNMoney.Satoshis(900L) // below RoutingHeuristics.CAPACITY_CHANNEL_LOW + + let acs = + Routing.toAssistedChannels e amount extraHops |> Map.ofSeq + + Expect.equal + (acs.[extraHop4.ShortChannelId]) + ({ + AssistedChannel.ExtraHop = extraHop4 + NextNodeId = e + HTLCMaximum = (LNMoney.Satoshis(1050L)) + }) + "" + + Expect.equal + (acs.[extraHop3.ShortChannelId]) + ({ + AssistedChannel.ExtraHop = extraHop3 + NextNodeId = d + HTLCMaximum = (LNMoney.Satoshis(1200L)) + }) + "" + + Expect.equal + (acs.[extraHop2.ShortChannelId]) + ({ + AssistedChannel.ExtraHop = extraHop2 + NextNodeId = c + HTLCMaximum = (LNMoney.Satoshis(1400L)) + }) + "" + + Expect.equal + (acs.[extraHop1.ShortChannelId]) + ({ + AssistedChannel.ExtraHop = extraHop1 + NextNodeId = b + HTLCMaximum = (LNMoney.Satoshis(1426L)) + }) + "" + + testCase "blacklist routes" + <| fun _ -> + let updates = + [ + makeUpdateSimple(1UL, a, b) + makeUpdateSimple(2UL, b, c) + makeUpdateSimple(3UL, c, d) + makeUpdateSimple(4UL, d, e) + ] + + let g = DirectedLNGraph.Create().AddEdges(updates) + + let ignoredE = + Set.singleton( + { + ShortChannelId = ShortChannelId.FromUInt64(3UL) + A = c + B = d + } + ) + + let route1 = + Routing.findRoute + (g) + a + e + DEFAULT_AMOUNT_MSAT + 1 + (Set.empty) + (ignoredE) + (Set.empty) + DEFAULT_ROUTE_PARAMS + (BlockHeight(400000u)) + + Expect.isError (Result.ToFSharpCoreResult route1) "" + + // verify that we left the graph untouched + Expect.isTrue + (g.ContainsEdge(makeUpdateSimple(3UL, c, d) |> fst)) + "" + + Expect.isTrue (g.ContainsVertex(c)) "" + Expect.isTrue (g.ContainsVertex(d)) "" + + // make sure we can find a route without the blacklist + let route2 = + Routing.findRoute + g + a + e + DEFAULT_AMOUNT_MSAT + 1 + (Set.empty) + (Set.empty) + (Set.empty) + DEFAULT_ROUTE_PARAMS + (BlockHeight(400000u)) + |> Result.deref + + Expect.sequenceEqual + (hops2Ids(route2)) + [ 1UL; 2UL; 3UL; 4UL ] + "" + + testCase + "route to a destination that is not in the graph (with assisted routes)" + <| fun _ -> + let updates = + [ + makeUpdate( + 1UL, + a, + b, + LNMoney.MilliSatoshis(10L), + 10u, + None, + None, + None + ) + makeUpdate( + 2UL, + b, + c, + LNMoney.MilliSatoshis(10L), + 10u, + None, + None, + None + ) + makeUpdate( + 3UL, + c, + d, + LNMoney.MilliSatoshis(10L), + 10u, + None, + None, + None + ) + ] + + let g = DirectedLNGraph.Create().AddEdges(updates) + + let route = + Routing.findRoute + (g) + a + e + DEFAULT_AMOUNT_MSAT + 1 + (Set.empty) + (Set.empty) + (Set.empty) + DEFAULT_ROUTE_PARAMS + (BlockHeight(400000u)) + + Expect.isError + (Result.ToFSharpCoreResult route) + "there should be no e node in the graph" + + // now we add the missing edge to reach the destination + let (extraDesc, extraUpdate) = + makeUpdate( + 4UL, + d, + e, + LNMoney.MilliSatoshis(5L), + 5u, + None, + None, + None + ) + + let extraGraphEdges = + Set.singleton( + { + GraphLabel.Desc = extraDesc + Update = extraUpdate + } + ) + + let route1 = + Routing.findRoute + g + a + e + DEFAULT_AMOUNT_MSAT + 1 + (extraGraphEdges) + (Set.empty) + (Set.empty) + DEFAULT_ROUTE_PARAMS + (BlockHeight(400000u)) + |> Result.deref + + Expect.sequenceEqual + (hops2Ids(route1)) + [ 1UL; 2UL; 3UL; 4UL ] + "" + + testCase + "Verify that extra hops takes precedence over known channels" + <| fun _ -> + let updates = + [ + makeUpdate( + 1UL, + a, + b, + LNMoney.MilliSatoshis(10), + 10u, + None, + None, + None + ) + makeUpdate( + 2UL, + b, + c, + LNMoney.MilliSatoshis(10), + 10u, + None, + None, + None + ) + makeUpdate( + 3UL, + c, + d, + LNMoney.MilliSatoshis(10), + 10u, + None, + None, + None + ) + makeUpdate( + 4UL, + d, + e, + LNMoney.MilliSatoshis(10), + 10u, + None, + None, + None + ) + ] + + let g = DirectedLNGraph.Create().AddEdges(updates) + + let route1 = + Routing.findRoute + g + a + e + DEFAULT_AMOUNT_MSAT + 1 + (Set.empty) + (Set.empty) + (Set.empty) + DEFAULT_ROUTE_PARAMS + (BlockHeight(400000u)) + |> Result.deref + + Expect.sequenceEqual + (hops2Ids(route1)) + [ 1UL; 2UL; 3UL; 4UL ] + "" + + Expect.equal + ((route1 |> Seq.item 1).LastUpdateValue.FeeBaseMSat) + (LNMoney.MilliSatoshis(10)) + "" + + let (extraDesc, extraUpdate) = + makeUpdate( + 2UL, + b, + c, + LNMoney.MilliSatoshis(5), + 5u, + None, + None, + None + ) + + let extraGraphEdges = + Set.singleton( + { + GraphLabel.Desc = extraDesc + Update = extraUpdate + } + ) + + let route2 = + Routing.findRoute + g + a + e + DEFAULT_AMOUNT_MSAT + 1 + extraGraphEdges + (Set.empty) + (Set.empty) + DEFAULT_ROUTE_PARAMS + (BlockHeight(400000u)) + |> Result.deref + + Expect.sequenceEqual + (hops2Ids(route2)) + [ 1UL; 2UL; 3UL; 4UL ] + "" + + Expect.equal + ((route2 |> Seq.item 1).LastUpdateValue.FeeBaseMSat) + (LNMoney.MilliSatoshis(5)) + "" + + testPropertyWithConfig fsCheckConfig "compute ignored channels" + <| fun (f: NodeId, g: NodeId, h: NodeId, i: NodeId, j: NodeId) -> + let channels = + Map.empty + |> Map.add + (ShortChannelId.FromUInt64(1UL)) + (makeChannelAnn(1UL, a, b)) + |> Map.add + (ShortChannelId.FromUInt64(2UL)) + (makeChannelAnn(2UL, b, c)) + |> Map.add + (ShortChannelId.FromUInt64(3UL)) + (makeChannelAnn(3UL, c, d)) + |> Map.add + (ShortChannelId.FromUInt64(4UL)) + (makeChannelAnn(4UL, d, e)) + |> Map.add + (ShortChannelId.FromUInt64(5UL)) + (makeChannelAnn(5UL, f, g)) + |> Map.add + (ShortChannelId.FromUInt64(6UL)) + (makeChannelAnn(6UL, f, h)) + |> Map.add + (ShortChannelId.FromUInt64(7UL)) + (makeChannelAnn(7UL, h, i)) + |> Map.add + (ShortChannelId.FromUInt64(8UL)) + (makeChannelAnn(8UL, i, j)) + + let updates = + [ + makeUpdate( + 1UL, + a, + b, + LNMoney.MilliSatoshis(10), + 10u, + None, + None, + None + ) + makeUpdate( + 2UL, + b, + c, + LNMoney.MilliSatoshis(10), + 10u, + None, + None, + None + ) + makeUpdate( + 2UL, + c, + b, + LNMoney.MilliSatoshis(10), + 10u, + None, + None, + None + ) + makeUpdate( + 3UL, + c, + d, + LNMoney.MilliSatoshis(10), + 10u, + None, + None, + None + ) + makeUpdate( + 4UL, + d, + e, + LNMoney.MilliSatoshis(10), + 10u, + None, + None, + None + ) + makeUpdate( + 5UL, + f, + g, + LNMoney.MilliSatoshis(10), + 10u, + None, + None, + None + ) + makeUpdate( + 6UL, + f, + h, + LNMoney.MilliSatoshis(10), + 10u, + None, + None, + None + ) + makeUpdate( + 7UL, + h, + i, + LNMoney.MilliSatoshis(10), + 10u, + None, + None, + None + ) + makeUpdate( + 8UL, + i, + j, + LNMoney.MilliSatoshis(10), + 10u, + None, + None, + None + ) + ] + + let publicChannels = + channels + |> Map.map(fun scid ann -> + let (_, update) = + updates + |> Seq.find(fun (upd, _) -> + upd.ShortChannelId = scid + ) + + let (maybeUpdate1, maybeUpdate2) = + if (update.ChannelFlags &&& 1uy = 1uy) then + (Some(update), None) + else + (None, Some(update)) + + let pc = + PublicChannel.Create( + ann, + TxId.Zero, + Money.Satoshis(1000L), + maybeUpdate1, + maybeUpdate2 + ) + + (pc) + ) + + let ignored = + Routing.getIgnoredChannelDesc + (publicChannels) + (Set [ c; j; (NodeId((new Key()).PubKey)) ]) + |> Set + + Expect.isTrue + (ignored.Contains( + { + ChannelDesc.ShortChannelId = + ShortChannelId.FromUInt64(2UL) + A = b + B = c + } + )) + "" + + Expect.isTrue + (ignored.Contains( + { + ChannelDesc.ShortChannelId = + ShortChannelId.FromUInt64(2UL) + A = c + B = b + } + )) + "" + + Expect.isTrue + (ignored.Contains( + { + ChannelDesc.ShortChannelId = + ShortChannelId.FromUInt64(3UL) + A = c + B = d + } + )) + "" + + Expect.isTrue + (ignored.Contains( + { + ChannelDesc.ShortChannelId = + ShortChannelId.FromUInt64(8UL) + A = i + B = j + } + )) + "" + + testCase "limit routes to 20 hops" + <| fun () -> + let nodes = + [ + for _ in 0..22 -> (new Key()).PubKey |> NodeId + ] + + let updates = + Seq.zip + (nodes |> List.rev |> List.skip 1 |> List.rev) + (nodes |> Seq.skip 1) // (0, 1) :: (1, 2) :: ... + |> Seq.mapi(fun i (na, nb) -> + makeUpdate( + (uint64 i), + na, + nb, + LNMoney.MilliSatoshis(5), + 0u, + None, + None, + None + ) + ) + + let g = DirectedLNGraph.Create().AddEdges(updates) + + let r18 = + (Routing.findRoute + g + (nodes.[0]) + nodes.[18] + DEFAULT_AMOUNT_MSAT + 1 + (Set.empty) + (Set.empty) + (Set.empty) + DEFAULT_ROUTE_PARAMS + (BlockHeight(400000u))) + |> Result.deref + + Expect.sequenceEqual + (hops2Ids(r18)) + [ for i in 0..17 -> (uint64 i) ] + "" + + let r19 = + (Routing.findRoute + g + (nodes.[0]) + nodes.[19] + DEFAULT_AMOUNT_MSAT + 1 + (Set.empty) + (Set.empty) + (Set.empty) + DEFAULT_ROUTE_PARAMS + (BlockHeight(400000u))) + |> Result.deref + + Expect.sequenceEqual + (hops2Ids(r19)) + [ for i in 0..18 -> (uint64 i) ] + "" + + let r20 = + (Routing.findRoute + g + (nodes.[0]) + nodes.[20] + DEFAULT_AMOUNT_MSAT + 1 + (Set.empty) + (Set.empty) + (Set.empty) + DEFAULT_ROUTE_PARAMS + (BlockHeight(400000u))) + |> Result.deref + + Expect.sequenceEqual + (hops2Ids(r20)) + [ for i in 0..19 -> (uint64 i) ] + "" + + let r21 = + (Routing.findRoute + g + (nodes.[0]) + nodes.[21] + DEFAULT_AMOUNT_MSAT + 1 + (Set.empty) + (Set.empty) + (Set.empty) + DEFAULT_ROUTE_PARAMS + (BlockHeight(400000u))) + + Expect.isError (Result.ToFSharpCoreResult r21) "" + + testCase "ignore cheaper route when it has more than 20 hops" + <| fun _ -> + let nodes = + [ + for _ in 0..50 -> (new Key()).PubKey |> NodeId + ] + + let updates = + List.zip + (nodes |> List.rev |> List.skip 1 |> List.rev) + (nodes |> List.skip 1) // (0, 1) :: (1, 2) :: ... + |> List.mapi(fun i (na, nb) -> + makeUpdate( + (uint64 i), + na, + nb, + LNMoney.One, + 0u, + None, + None, + None + ) + ) + + // add expensive but shorter route + let updates2 = + (makeUpdate( + 99UL, + nodes.[2], + nodes.[48], + LNMoney.MilliSatoshis(1000L), + 0u, + None, + None, + None + )) + :: updates + + let g = DirectedLNGraph.Create().AddEdges(updates2) + + let route = + Routing.findRoute + g + nodes.[0] + nodes.[49] + DEFAULT_AMOUNT_MSAT + 1 + (Set.empty) + (Set.empty) + (Set.empty) + DEFAULT_ROUTE_PARAMS + (BlockHeight(400000u)) + |> Result.deref + + Expect.sequenceEqual + (hops2Ids(route)) + [ 0UL; 1UL; 99UL; 48UL ] + "" + + testCase + "ignore cheaper route when it has more than the requested CLTV limit" + <| fun _ -> + let updates = + [ + makeUpdate( + 1UL, + a, + b, + LNMoney.One, + 0u, + Some(LNMoney.Zero), + None, + Some(BlockHeightOffset16(50us)) + ) + makeUpdate( + 2UL, + b, + c, + LNMoney.One, + 0u, + Some(LNMoney.Zero), + None, + Some(BlockHeightOffset16(50us)) + ) + makeUpdate( + 3UL, + c, + d, + LNMoney.One, + 0u, + Some(LNMoney.Zero), + None, + Some(BlockHeightOffset16(50us)) + ) + makeUpdate( + 4UL, + a, + e, + LNMoney.One, + 0u, + Some(LNMoney.Zero), + None, + Some(BlockHeightOffset16(9us)) + ) + makeUpdate( + 5UL, + e, + f, + LNMoney.MilliSatoshis(5), + 0u, + Some(LNMoney.Zero), + None, + Some(BlockHeightOffset16(9us)) + ) + makeUpdate( + 6UL, + f, + d, + LNMoney.MilliSatoshis(5), + 0u, + Some(LNMoney.Zero), + None, + Some(BlockHeightOffset16(9us)) + ) + ] + + let g = DirectedLNGraph.Create().AddEdges(updates) + + let route = + Routing.findRoute + (g) + a + d + DEFAULT_AMOUNT_MSAT + 1 + (Set.empty) + (Set.empty) + (Set.empty) + { DEFAULT_ROUTE_PARAMS with + RouteMaxCLTV = (BlockHeightOffset16(28us)) + } + (BlockHeight(400000u)) + |> Result.deref + + Expect.sequenceEqual (hops2Ids(route)) [ 4UL; 5UL; 6UL ] "" + + testCase + "ignore cheaper route when it grows longer than the requested size" + <| fun _ -> + let updates = + [ + makeUpdate( + 1UL, + a, + b, + LNMoney.One, + 0u, + Some(LNMoney.Zero), + None, + (Some(BlockHeightOffset16(9us))) + ) + makeUpdate( + 2UL, + b, + c, + LNMoney.One, + 0u, + Some(LNMoney.Zero), + None, + (Some(BlockHeightOffset16(9us))) + ) + makeUpdate( + 3UL, + c, + d, + LNMoney.One, + 0u, + Some(LNMoney.Zero), + None, + (Some(BlockHeightOffset16(9us))) + ) + makeUpdate( + 4UL, + d, + e, + LNMoney.One, + 0u, + Some(LNMoney.Zero), + None, + (Some(BlockHeightOffset16(9us))) + ) + makeUpdate( + 5UL, + e, + f, + LNMoney.One, + 0u, + Some(LNMoney.MilliSatoshis(5)), + None, + (Some(BlockHeightOffset16(9us))) + ) + makeUpdate( + 6UL, + b, + f, + LNMoney.One, + 0u, + Some(LNMoney.MilliSatoshis(5)), + None, + (Some(BlockHeightOffset16(9us))) + ) + ] + + let g = DirectedLNGraph.Create().AddEdges(updates) + + let route = + Routing.findRoute + g + a + f + DEFAULT_AMOUNT_MSAT + 1 + (Set.empty) + (Set.empty) + (Set.empty) + { DEFAULT_ROUTE_PARAMS with + RouteMaxLength = 3 + } + (BlockHeight(400000u)) + |> Result.deref + + Expect.sequenceEqual (hops2Ids(route)) [ 1UL; 6UL ] "" + + testCase "ignore loops" + <| fun _ -> + let updates = + [ + makeUpdate( + 1UL, + a, + b, + LNMoney.MilliSatoshis(10), + 10u, + None, + None, + None + ) + makeUpdate( + 2UL, + b, + c, + LNMoney.MilliSatoshis(10), + 10u, + None, + None, + None + ) + makeUpdate( + 3UL, + c, + a, + LNMoney.MilliSatoshis(10), + 10u, + None, + None, + None + ) + makeUpdate( + 4UL, + c, + d, + LNMoney.MilliSatoshis(10), + 10u, + None, + None, + None + ) + makeUpdate( + 5UL, + d, + e, + LNMoney.MilliSatoshis(10), + 10u, + None, + None, + None + ) + ] + + let g = DirectedLNGraph.Create().AddEdges(updates) + + let route1 = + Routing.findRoute + g + a + e + DEFAULT_AMOUNT_MSAT + 1 + (Set.empty) + (Set.empty) + (Set.empty) + DEFAULT_ROUTE_PARAMS + (BlockHeight(400000u)) + |> Result.deref + + Expect.sequenceEqual + (hops2Ids(route1)) + [ 1UL; 2UL; 4UL; 5UL ] + "" + + testCase + "ensure the route calculation terminates correctly when selecting 0-fees edges" + <| fun _ -> + let updates = + [ + makeUpdate( + 1UL, + a, + b, + LNMoney.MilliSatoshis(10), + 10u, + None, + None, + None + ) + makeUpdate( + 2UL, + b, + c, + LNMoney.MilliSatoshis(10), + 10u, + None, + None, + None + ) + makeUpdate( + 4UL, + c, + d, + LNMoney.MilliSatoshis(10), + 10u, + None, + None, + None + ) + makeUpdateSimple(3UL, b, e) + makeUpdateSimple(6UL, e, f) + makeUpdateSimple(6UL, f, e) + makeUpdateSimple(5UL, e, d) + ] + + let g = DirectedLNGraph.Create().AddEdges(updates) + + let route1 = + Routing.findRoute + g + a + d + DEFAULT_AMOUNT_MSAT + 1 + (Set.empty) + (Set.empty) + (Set.empty) + DEFAULT_ROUTE_PARAMS + (BlockHeight(400000u)) + |> Result.deref + + Expect.sequenceEqual (hops2Ids(route1)) [ 1UL; 3UL; 5UL ] "" + + // +---+ +---+ +---+ + // | A +-----+ | B +----------> | C | + // +-+-+ | +-+-+ +-+-+ + // ^ | ^ | + // | | | | + // | v----> + | | + // +-+-+ <-+-+ +-+-+ + // | D +----------> | E +----------> | F | + // +---+ +---+ +---+ + // + testCase "find the k-shortest paths in a graph, k = 4" + <| fun _ -> + let updates = + [ + makeUpdate(1UL, d, a, LNMoney.One, 0u, None, None, None) + makeUpdate(2UL, d, e, LNMoney.One, 0u, None, None, None) + makeUpdate(3UL, a, e, LNMoney.One, 0u, None, None, None) + makeUpdate(4UL, e, b, LNMoney.One, 0u, None, None, None) + makeUpdate(5UL, e, f, LNMoney.One, 0u, None, None, None) + makeUpdate(6UL, b, c, LNMoney.One, 0u, None, None, None) + makeUpdate(7UL, c, f, LNMoney.One, 0u, None, None, None) + ] + + let g = DirectedLNGraph.Create().AddEdges(updates) + + let fourShortestPaths = + Graph.yenKShortestPaths + g + d + f + DEFAULT_AMOUNT_MSAT + (Set.empty) + (Set.empty) + (Set.empty) + 4 + None + (BlockHeight.One) + (fun _ -> true) + |> Seq.toList + + Expect.equal + (fourShortestPaths.Length) + 4 + (sprintf "found shortest paths were %A" fourShortestPaths) + + let actuals = + [ + for i in 0..3 do + fourShortestPaths.[i].Path + |> Seq.map ChannelHop.FromGraphEdge + |> hops2Ids + ] + + Expect.sequenceEqual actuals.[0] [ 2UL; 5UL ] "" + Expect.sequenceEqual actuals.[1] [ 1UL; 3UL; 5UL ] "" + Expect.sequenceEqual actuals.[2] [ 2UL; 4UL; 6UL; 7UL ] "" + Expect.sequenceEqual actuals.[3] [ 1UL; 3UL; 4UL; 6UL; 7UL ] "" + + testCase "find the k shortest path (wikipedia example)" + <| fun _ -> + let updates = + [ + makeUpdate( + 10UL, + c, + e, + LNMoney.MilliSatoshis(2), + 0u, + None, + None, + None + ) + makeUpdate( + 20UL, + c, + d, + LNMoney.MilliSatoshis(3), + 0u, + None, + None, + None + ) + makeUpdate( + 30UL, + d, + f, + LNMoney.MilliSatoshis(4), + 5u, + None, + None, + None + ) // D -> F has a higher cost to distinguish from the 2nd cheapest route + makeUpdate( + 40UL, + e, + d, + LNMoney.MilliSatoshis(1), + 0u, + None, + None, + None + ) + makeUpdate( + 50UL, + e, + f, + LNMoney.MilliSatoshis(2), + 0u, + None, + None, + None + ) + makeUpdate( + 60UL, + e, + g, + LNMoney.MilliSatoshis(3), + 0u, + None, + None, + None + ) + makeUpdate( + 70UL, + f, + g, + LNMoney.MilliSatoshis(2), + 0u, + None, + None, + None + ) + + makeUpdate( + 80UL, + f, + h, + LNMoney.MilliSatoshis(1), + 0u, + None, + None, + None + ) + makeUpdate( + 90UL, + g, + h, + LNMoney.MilliSatoshis(2), + 0u, + None, + None, + None + ) + ] + + let graph = DirectedLNGraph.Create().AddEdges(updates) + + let twoShortestPaths = + Graph.yenKShortestPaths + graph + c + h + DEFAULT_AMOUNT_MSAT + Set.empty + Set.empty + Set.empty + 2 + None + (BlockHeight(0u)) + (fun _ -> true) + |> Seq.toList + + Expect.equal (twoShortestPaths.Length) 2 "" + let shortest = twoShortestPaths.[0] + + Expect.sequenceEqual + (shortest.Path + |> Seq.map(ChannelHop.FromGraphEdge) + |> hops2Ids) + [ 10UL; 50UL; 80UL ] + "" + + let secondShortest = twoShortestPaths.[1] + + Expect.sequenceEqual + (secondShortest.Path + |> Seq.map(ChannelHop.FromGraphEdge) + |> hops2Ids) + [ 10UL; 60UL; 90UL ] + "" + + testCase + "terminate looking for k-shortest path if there are no more alternative paths than k, must not consider routes going back on their steps" + <| fun _ -> + // simple graph with only 2 possible paths from A to F + let updates = + [ + makeUpdate( + 1UL, + a, + b, + LNMoney.MilliSatoshis(1), + 0u, + None, + None, + None + ) + makeUpdate( + 1UL, + b, + a, + LNMoney.MilliSatoshis(1), + 0u, + None, + None, + None + ) + makeUpdate( + 2UL, + b, + c, + LNMoney.MilliSatoshis(1), + 0u, + None, + None, + None + ) + makeUpdate( + 2UL, + c, + b, + LNMoney.MilliSatoshis(1), + 0u, + None, + None, + None + ) + makeUpdate( + 3UL, + c, + f, + LNMoney.MilliSatoshis(1), + 0u, + None, + None, + None + ) + makeUpdate( + 3UL, + f, + c, + LNMoney.MilliSatoshis(1), + 0u, + None, + None, + None + ) + makeUpdate( + 4UL, + c, + d, + LNMoney.MilliSatoshis(1), + 0u, + None, + None, + None + ) + makeUpdate( + 4UL, + d, + c, + LNMoney.MilliSatoshis(1), + 0u, + None, + None, + None + ) + makeUpdate( + 41UL, + d, + c, + LNMoney.MilliSatoshis(1), + 0u, + None, + None, + None + ) // there is more than one D -> C channel + makeUpdate( + 5UL, + d, + e, + LNMoney.MilliSatoshis(1), + 0u, + None, + None, + None + ) + makeUpdate( + 5UL, + e, + d, + LNMoney.MilliSatoshis(1), + 0u, + None, + None, + None + ) + makeUpdate( + 6UL, + e, + f, + LNMoney.MilliSatoshis(1), + 0u, + None, + None, + None + ) + makeUpdate( + 6UL, + f, + e, + LNMoney.MilliSatoshis(1), + 0u, + None, + None, + None + ) + ] + + let graph = DirectedLNGraph.Create().AddEdges(updates) + + let foundPaths = + Graph.yenKShortestPaths + graph + a + f + DEFAULT_AMOUNT_MSAT + (Set.empty) + (Set.empty) + (Set.empty) + 3 + None + (BlockHeight 0u) + (fun _ -> true) + |> Seq.toList + + Expect.equal (foundPaths.Length) 2 "" + + Expect.sequenceEqual + (foundPaths.[0].Path + |> Seq.map(ChannelHop.FromGraphEdge) + |> hops2Ids) + [ 1UL; 2UL; 3UL ] + "" + + Expect.sequenceEqual + (foundPaths.[1].Path + |> Seq.map(ChannelHop.FromGraphEdge) + |> hops2Ids) + [ 1UL; 2UL; 4UL; 5UL; 6UL ] + "" + + testCase "select a random route below the requested fee" + <| fun _ -> + let updates = + [ + makeUpdate( + 1UL, + a, + b, + LNMoney.MilliSatoshis(1), + 0u, + None, + None, + None + ) + makeUpdate( + 4UL, + a, + e, + LNMoney.MilliSatoshis(1), + 0u, + None, + None, + None + ) + makeUpdate( + 2UL, + b, + c, + LNMoney.MilliSatoshis(2), + 0u, + None, + None, + None + ) + makeUpdate( + 3UL, + c, + d, + LNMoney.MilliSatoshis(3), + 0u, + None, + None, + None + ) + makeUpdate( + 5UL, + e, + f, + LNMoney.MilliSatoshis(3), + 0u, + None, + None, + None + ) + makeUpdate( + 6UL, + f, + d, + LNMoney.MilliSatoshis(3), + 0u, + None, + None, + None + ) + makeUpdate( + 7UL, + e, + c, + LNMoney.MilliSatoshis(9), + 0u, + None, + None, + None + ) + ] + + let graph = DirectedLNGraph.Create().AddEdges(updates) + + let strictFeeParams = + { DEFAULT_ROUTE_PARAMS with + MaxFeeBase = LNMoney.MilliSatoshis(7) + MaxFeePCT = 0. + } + + for _ in 0..10 do + let r = + Routing.findRoute + graph + a + d + DEFAULT_AMOUNT_MSAT + 3 + (Set.empty) + (Set.empty) + (Set.empty) + strictFeeParams + (BlockHeight(400000u)) + + Expect.isOk (Result.ToFSharpCoreResult r) "" + let someRoute = r |> Result.deref + + let routeCost = + (Graph.pathWeight + (hops2Edges(someRoute)) + DEFAULT_AMOUNT_MSAT + false + (BlockHeight 0u) + None) + .Cost + - DEFAULT_AMOUNT_MSAT + + let costMSat = routeCost.MilliSatoshi + Expect.isTrue (costMSat = 5L || costMSat = 6L) "" + + testCase "Use weight ratios to when computing the edge weight" + <| fun _ -> + let largeCap = LNMoney.MilliSatoshis(8000000000L) + + let updates = + [ + makeUpdate( + 1UL, + a, + b, + LNMoney.MilliSatoshis(0), + 0u, + Some(LNMoney.Zero), + None, + Some(BlockHeightOffset16(13us)) + ) + makeUpdate( + 4UL, + a, + e, + LNMoney.MilliSatoshis(0), + 0u, + Some(LNMoney.Zero), + None, + Some(BlockHeightOffset16(12us)) + ) + makeUpdate( + 2UL, + b, + c, + LNMoney.MilliSatoshis(1), + 0u, + Some(LNMoney.Zero), + None, + Some(BlockHeightOffset16(500us)) + ) + makeUpdate( + 3UL, + c, + d, + LNMoney.MilliSatoshis(1), + 0u, + Some(LNMoney.Zero), + None, + Some(BlockHeightOffset16(500us)) + ) + makeUpdate( + 5UL, + e, + f, + LNMoney.MilliSatoshis(2), + 0u, + Some(LNMoney.Zero), + None, + Some(BlockHeightOffset16(9us)) + ) + makeUpdate( + 6UL, + f, + d, + LNMoney.MilliSatoshis(2), + 0u, + Some(LNMoney.Zero), + None, + Some(BlockHeightOffset16(9us)) + ) + makeUpdate( + 7UL, + e, + c, + LNMoney.MilliSatoshis(2), + 0u, + Some(LNMoney.Zero), + Some(largeCap), + Some(BlockHeightOffset16(12us)) + ) + ] + + let graph = DirectedLNGraph.Create().AddEdges(updates) + + let r = + Routing.findRoute + graph + a + d + DEFAULT_AMOUNT_MSAT + 0 + (Set.empty) + (Set.empty) + (Set.empty) + DEFAULT_ROUTE_PARAMS + (BlockHeight(400000u)) + |> Result.deref + + Expect.sequenceEqual + (r |> hops2Nodes) + [ (a, b); (b, c); (c, d) ] + "" + + let routeClTVOptimized = + let p = + let r = + WeightRatios.TryCreate(1., 0., 0.) |> Result.deref + + { DEFAULT_ROUTE_PARAMS with + Ratios = Some(r) + } + + Routing.findRoute + graph + a + d + DEFAULT_AMOUNT_MSAT + 0 + (Set.empty) + (Set.empty) + (Set.empty) + p + (BlockHeight(400000u)) + |> Result.deref + + Expect.sequenceEqual + (routeClTVOptimized |> hops2Nodes) + [ (a, e); (e, f); (f, d) ] + "" + + let routeCapOptimized = + let p = + let r = + WeightRatios.TryCreate(0., 0., 1.) |> Result.deref + + { DEFAULT_ROUTE_PARAMS with + Ratios = Some(r) + } + + Routing.findRoute + graph + a + d + DEFAULT_AMOUNT_MSAT + 0 + (Set.empty) + (Set.empty) + (Set.empty) + p + (BlockHeight(400000u)) + |> Result.deref + + Expect.sequenceEqual + (routeCapOptimized |> hops2Nodes) + [ (a, e); (e, c); (c, d) ] + "" + + testCase + "Prefer going through an older channel if fees and CLTV are the same" + <| fun _ -> + let currentBlockHeight = 554000u + + let mu(schid, one, two) = + makeUpdate2( + schid, + one, + two, + LNMoney.MilliSatoshis(1), + 0u, + (Some LNMoney.Zero), + None, + (Some(BlockHeightOffset16(144us))) + ) + + let updates = + [ + mu((sprintf "%ix0x1" currentBlockHeight), a, b) + mu((sprintf "%ix0x4" currentBlockHeight), a, e) + mu( + (sprintf "%ix0x2" (currentBlockHeight - 3000u)), + b, + c + ) // younger channel + mu( + (sprintf "%ix0x3" (currentBlockHeight - 3000u)), + c, + d + ) + mu((sprintf "%ix0x5" currentBlockHeight), e, f) + mu((sprintf "%ix0x6" currentBlockHeight), f, d) + ] + + let graph = DirectedLNGraph.Create().AddEdges(updates) + + let routeScoreOptimized = + let wr = + WeightRatios.TryCreate(0.33, 0.33, 0.33) |> Result.deref + + let rp = + { DEFAULT_ROUTE_PARAMS with + Ratios = Some(wr) + } + + Routing.findRoute + graph + a + d + (DEFAULT_AMOUNT_MSAT / 2) + 1 + (Set.empty) + (Set.empty) + (Set.empty) + rp + (BlockHeight(currentBlockHeight)) + |> Result.deref + |> hops2Nodes + + Expect.sequenceEqual + routeScoreOptimized + [ (a, b); (b, c); (c, d) ] + "" + + testCase + "prefer a route with a smaller total CLTV if fees and scores are the same" + <| fun _ -> + let mu(schid, one, two, cltv) = + makeUpdate( + schid, + one, + two, + LNMoney.One, + 0u, + (Some LNMoney.Zero), + None, + (Some(BlockHeightOffset16 cltv)) + ) + + let updates = + [ + mu(1UL, a, b, 12us) + mu(4UL, a, e, 12us) + mu(2UL, b, c, 10us) // smaller cltv + mu(3UL, c, d, 12us) + mu(5UL, e, f, 12us) + mu(6UL, f, d, 12us) + ] + + let graph = DirectedLNGraph.Create().AddEdges(updates) + + let routeScoreOptimized = + let wr = + WeightRatios.TryCreate(0.33, 0.33, 0.33) |> Result.deref + + let rp = + { DEFAULT_ROUTE_PARAMS with + Ratios = Some(wr) + } + + Routing.findRoute + graph + a + d + (DEFAULT_AMOUNT_MSAT / 2) + 1 + (Set.empty) + (Set.empty) + (Set.empty) + rp + (BlockHeight(400000u)) + |> Result.deref + |> hops2Nodes + + Expect.sequenceEqual + routeScoreOptimized + [ (a, b); (b, c); (c, d) ] + "" + + () + + testCase "avoid a route that breaks off the max CLTV" + <| fun _ -> + let mu(schid, one, two, cltv) = + makeUpdate( + schid, + one, + two, + LNMoney.One, + 0u, + (Some LNMoney.Zero), + None, + (Some(BlockHeightOffset16 cltv)) + ) + // A --> B --> C --> D is cheaper but has a total CLTV > 2016! + // A --> E --> F --> D is more expensive but has a total CLTV < 2016 + let updates = + [ + mu(1UL, a, b, 144us) + mu(4UL, a, e, 144us) + mu(2UL, b, c, 1000us) + mu(3UL, c, d, 900us) + mu(5UL, e, f, 144us) + mu(6UL, f, d, 144us) + ] + + let graph = DirectedLNGraph.Create().AddEdges(updates) + + let routeScoreOptimized = + let wr = + WeightRatios.TryCreate(0.33, 0.33, 0.33) |> Result.deref + + let rp = + { DEFAULT_ROUTE_PARAMS with + Ratios = Some(wr) + } + + Routing.findRoute + graph + a + d + (DEFAULT_AMOUNT_MSAT / 2) + 1 + (Set.empty) + (Set.empty) + (Set.empty) + rp + (BlockHeight(400000u)) + |> Result.deref + |> hops2Nodes + + Expect.sequenceEqual + routeScoreOptimized + [ (a, e); (e, f); (f, d) ] + "" + + testCase "cost function is monotonic" + <| fun _ -> + // This test have a channel (542280x2156x0) that according to heuristics is very convenient but actually useless to reach the target, + // then if the cost function is not monotonic the path-finding breaks because the result path contains a loop. + let updates = + let m = Map.empty + + let m = + let shortChannelId1 = + ShortChannelId.ParseUnsafe("565643x1216x0") + + let pk1 = + PubKey( + "03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f" + ) + |> NodeId + + let pk2 = + PubKey( + "024655b768ef40951b20053a5c4b951606d4d86085d51238f2c67c7dec29c792ca" + ) + |> NodeId + + let channelAnn = + makeChannelAnnCore(shortChannelId1, pk1, pk2) + + let unsignedChannelUpdate1 = + { + UnsignedChannelUpdateMsg.ChainHash = + uint256.Zero + ShortChannelId = shortChannelId1 + Timestamp = 0u + MessageFlags = 1uy + ChannelFlags = 0uy + CLTVExpiryDelta = BlockHeightOffset16(14us) + HTLCMinimumMSat = LNMoney.One + FeeBaseMSat = LNMoney.Satoshis(1L) + FeeProportionalMillionths = 10u + HTLCMaximumMSat = + Some(LNMoney.MilliSatoshis(4294967295L)) + } + + let unsignedChannelUpdate2 = + { + UnsignedChannelUpdateMsg.ChainHash = + uint256.Zero + ShortChannelId = shortChannelId1 + Timestamp = 0u + MessageFlags = 1uy + ChannelFlags = 1uy + CLTVExpiryDelta = BlockHeightOffset16(144us) + HTLCMinimumMSat = LNMoney.Zero + FeeBaseMSat = LNMoney.Satoshis(1L) + FeeProportionalMillionths = 100u + HTLCMaximumMSat = + Some(LNMoney.MilliSatoshis(15000000000L)) + } + + let pc1 = + PublicChannel.Create( + channelAnn, + TxId.Zero, + Money.Zero, + Some(unsignedChannelUpdate1), + Some(unsignedChannelUpdate2) + ) + + m |> Map.add (shortChannelId1) pc1 + + let m = + let shortChannelId2 = + ShortChannelId.ParseUnsafe("542280x2156x0") + + let pk1 = + PubKey( + "03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f" + ) + |> NodeId + + let pk2 = + PubKey( + "03cb7983dc247f9f81a0fa2dfa3ce1c255365f7279c8dd143e086ca333df10e278" + ) + |> NodeId + + let channelAnn = + makeChannelAnnCore(shortChannelId2, pk1, pk2) + + let unsignedChannelUpdate1 = + { + UnsignedChannelUpdateMsg.ChainHash = + uint256.Zero + ShortChannelId = shortChannelId2 + Timestamp = 0u + MessageFlags = 1uy + ChannelFlags = 0uy + CLTVExpiryDelta = BlockHeightOffset16(144us) + HTLCMinimumMSat = LNMoney.Satoshis(1) + FeeBaseMSat = LNMoney.Satoshis(1L) + FeeProportionalMillionths = 100u + HTLCMaximumMSat = + Some(LNMoney.MilliSatoshis(16777000000L)) + } + + let unsignedChannelUpdate2 = + { + UnsignedChannelUpdateMsg.ChainHash = + uint256.Zero + ShortChannelId = shortChannelId2 + Timestamp = 0u + MessageFlags = 1uy + ChannelFlags = 1uy + CLTVExpiryDelta = BlockHeightOffset16(144us) + HTLCMinimumMSat = LNMoney.One + FeeBaseMSat = LNMoney.Satoshis(667) + FeeProportionalMillionths = 1u + HTLCMaximumMSat = + Some(LNMoney.MilliSatoshis(16777000000L)) + } + + let pc2 = + PublicChannel.Create( + channelAnn, + TxId.Zero, + Money.Zero, + Some(unsignedChannelUpdate1), + Some(unsignedChannelUpdate2) + ) + + m |> Map.add (shortChannelId2) pc2 + + let m = + let shortChannelId3 = + ShortChannelId.ParseUnsafe("565779x2711x0") + + let pk1 = + PubKey( + "036d65409c41ab7380a43448f257809e7496b52bf92057c09c4f300cbd61c50d96" + ) + |> NodeId + + let pk2 = + PubKey( + "03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f" + ) + |> NodeId + + let channelAnn = + makeChannelAnnCore(shortChannelId3, pk1, pk2) + + let unsignedChannelUpdate1 = + { + UnsignedChannelUpdateMsg.ChainHash = + uint256.Zero + ShortChannelId = shortChannelId3 + Timestamp = 0u + MessageFlags = 1uy + ChannelFlags = 0uy + CLTVExpiryDelta = BlockHeightOffset16(144us) + HTLCMinimumMSat = LNMoney.One + FeeBaseMSat = LNMoney.Satoshis(1L) + FeeProportionalMillionths = 100u + HTLCMaximumMSat = + Some(LNMoney.MilliSatoshis(230000000L)) + } + + let unsignedChannelUpdate2 = + { + UnsignedChannelUpdateMsg.ChainHash = + uint256.Zero + ShortChannelId = shortChannelId3 + Timestamp = 0u + MessageFlags = 1uy + ChannelFlags = 3uy + CLTVExpiryDelta = BlockHeightOffset16(144us) + HTLCMinimumMSat = LNMoney.One + FeeBaseMSat = LNMoney.Satoshis(1) + FeeProportionalMillionths = 100u + HTLCMaximumMSat = + Some(LNMoney.MilliSatoshis(230000000L)) + } + + let pc3 = + PublicChannel.Create( + channelAnn, + TxId.Zero, + Money.Zero, + Some(unsignedChannelUpdate1), + Some(unsignedChannelUpdate2) + ) + + m |> Map.add (shortChannelId3) pc3 + + m + + let graph = DirectedLNGraph.MakeGraph(updates) + + let routeParams = + { + RouteParams.Randomize = false + MaxFeeBase = LNMoney.MilliSatoshis 21000 + MaxFeePCT = 0.03 + RouteMaxLength = 6 + RouteMaxCLTV = 1008us |> BlockHeightOffset16 + Ratios = + Some( + WeightRatios.TryCreate(0.15, 0.35, 0.5) + |> Result.deref + ) + } + + let thisNode = + PubKey( + "036d65409c41ab7380a43448f257809e7496b52bf92057c09c4f300cbd61c50d96" + ) + |> NodeId + + let targetNode = + PubKey( + "024655b768ef40951b20053a5c4b951606d4d86085d51238f2c67c7dec29c792ca" + ) + |> NodeId + + let amount = 351000 |> LNMoney.MilliSatoshis + + let route = + Routing.findRoute + graph + thisNode + targetNode + amount + 1 + (Set.empty) + (Set.empty) + (Set.empty) + routeParams + (BlockHeight(567634u)) + |> Result.deref + |> Seq.toList + + Expect.equal (route.Length) 2 "" + Expect.equal (route |> List.last).NextNodeIdValue targetNode "" ] - let graph = DirectedLNGraph.Create().AddEdges(updates) - let routeScoreOptimized = - let wr = WeightRatios.TryCreate(0.33, 0.33, 0.33) |> Result.deref - let rp = { DEFAULT_ROUTE_PARAMS with Ratios = Some (wr) } - Routing.findRoute graph a d (DEFAULT_AMOUNT_MSAT / 2) 1 (Set.empty)(Set.empty)(Set.empty) rp (BlockHeight(400000u)) - |> Result.deref - |> hops2Nodes - Expect.sequenceEqual routeScoreOptimized [(a,e); (e,f); (f,d)] "" - - testCase "cost function is monotonic" <| fun _ -> - // This test have a channel (542280x2156x0) that according to heuristics is very convenient but actually useless to reach the target, - // then if the cost function is not monotonic the path-finding breaks because the result path contains a loop. - let updates = - let m = Map.empty - let m = - let shortChannelId1 = ShortChannelId.ParseUnsafe("565643x1216x0") - let pk1 = PubKey("03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f") |> NodeId - let pk2 = PubKey("024655b768ef40951b20053a5c4b951606d4d86085d51238f2c67c7dec29c792ca") |> NodeId - let channelAnn = makeChannelAnnCore(shortChannelId1, pk1, pk2) - let unsignedChannelUpdate1 = - { UnsignedChannelUpdateMsg.ChainHash = uint256.Zero - ShortChannelId = shortChannelId1 - Timestamp = 0u - MessageFlags = 1uy - ChannelFlags = 0uy - CLTVExpiryDelta = BlockHeightOffset16(14us) - HTLCMinimumMSat = LNMoney.One - FeeBaseMSat = LNMoney.Satoshis(1L) - FeeProportionalMillionths = 10u - HTLCMaximumMSat = Some(LNMoney.MilliSatoshis(4294967295L)) } - let unsignedChannelUpdate2 = - { UnsignedChannelUpdateMsg.ChainHash = uint256.Zero - ShortChannelId = shortChannelId1 - Timestamp = 0u - MessageFlags = 1uy - ChannelFlags = 1uy - CLTVExpiryDelta = BlockHeightOffset16(144us) - HTLCMinimumMSat = LNMoney.Zero - FeeBaseMSat = LNMoney.Satoshis(1L) - FeeProportionalMillionths = 100u - HTLCMaximumMSat = Some(LNMoney.MilliSatoshis(15000000000L)) } - let pc1 = PublicChannel.Create(channelAnn,TxId.Zero, Money.Zero, Some(unsignedChannelUpdate1), Some(unsignedChannelUpdate2)) - m |> Map.add (shortChannelId1) pc1 - let m = - let shortChannelId2 = ShortChannelId.ParseUnsafe("542280x2156x0") - let pk1 = PubKey("03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f") |> NodeId - let pk2 = PubKey("03cb7983dc247f9f81a0fa2dfa3ce1c255365f7279c8dd143e086ca333df10e278") |> NodeId - let channelAnn = makeChannelAnnCore(shortChannelId2, pk1, pk2) - let unsignedChannelUpdate1 = - { UnsignedChannelUpdateMsg.ChainHash = uint256.Zero - ShortChannelId = shortChannelId2 - Timestamp = 0u - MessageFlags = 1uy - ChannelFlags = 0uy - CLTVExpiryDelta = BlockHeightOffset16(144us) - HTLCMinimumMSat = LNMoney.Satoshis(1) - FeeBaseMSat = LNMoney.Satoshis(1L) - FeeProportionalMillionths = 100u - HTLCMaximumMSat = Some(LNMoney.MilliSatoshis(16777000000L)) } - let unsignedChannelUpdate2 = - { UnsignedChannelUpdateMsg.ChainHash = uint256.Zero - ShortChannelId = shortChannelId2 - Timestamp = 0u - MessageFlags = 1uy - ChannelFlags = 1uy - CLTVExpiryDelta = BlockHeightOffset16(144us) - HTLCMinimumMSat = LNMoney.One - FeeBaseMSat = LNMoney.Satoshis(667) - FeeProportionalMillionths = 1u - HTLCMaximumMSat = Some(LNMoney.MilliSatoshis(16777000000L)) } - let pc2 = PublicChannel.Create(channelAnn,TxId.Zero, Money.Zero, Some(unsignedChannelUpdate1), Some(unsignedChannelUpdate2)) - m |> Map.add (shortChannelId2) pc2 - let m = - let shortChannelId3 = ShortChannelId.ParseUnsafe("565779x2711x0") - let pk1 = PubKey("036d65409c41ab7380a43448f257809e7496b52bf92057c09c4f300cbd61c50d96") |> NodeId - let pk2 = PubKey("03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f") |> NodeId - let channelAnn = makeChannelAnnCore(shortChannelId3, pk1, pk2) - let unsignedChannelUpdate1 = - { UnsignedChannelUpdateMsg.ChainHash = uint256.Zero - ShortChannelId = shortChannelId3 - Timestamp = 0u - MessageFlags = 1uy - ChannelFlags = 0uy - CLTVExpiryDelta = BlockHeightOffset16(144us) - HTLCMinimumMSat = LNMoney.One - FeeBaseMSat = LNMoney.Satoshis(1L) - FeeProportionalMillionths = 100u - HTLCMaximumMSat = Some(LNMoney.MilliSatoshis(230000000L)) } - let unsignedChannelUpdate2 = - { UnsignedChannelUpdateMsg.ChainHash = uint256.Zero - ShortChannelId = shortChannelId3 - Timestamp = 0u - MessageFlags = 1uy - ChannelFlags = 3uy - CLTVExpiryDelta = BlockHeightOffset16(144us) - HTLCMinimumMSat = LNMoney.One - FeeBaseMSat = LNMoney.Satoshis(1) - FeeProportionalMillionths = 100u - HTLCMaximumMSat = Some(LNMoney.MilliSatoshis(230000000L)) } - let pc3 = PublicChannel.Create(channelAnn,TxId.Zero, Money.Zero, Some(unsignedChannelUpdate1), Some(unsignedChannelUpdate2)) - m |> Map.add (shortChannelId3) pc3 - m - - let graph = DirectedLNGraph.MakeGraph(updates) - let routeParams = - { RouteParams.Randomize = false - MaxFeeBase = LNMoney.MilliSatoshis 21000 - MaxFeePCT = 0.03 - RouteMaxLength = 6 - RouteMaxCLTV = 1008us |> BlockHeightOffset16 - Ratios = Some(WeightRatios.TryCreate(0.15, 0.35, 0.5) |> Result.deref) } - let thisNode = PubKey("036d65409c41ab7380a43448f257809e7496b52bf92057c09c4f300cbd61c50d96") |> NodeId - let targetNode = PubKey("024655b768ef40951b20053a5c4b951606d4d86085d51238f2c67c7dec29c792ca") |> NodeId - let amount = 351000 |> LNMoney.MilliSatoshis - let route = - Routing.findRoute graph thisNode targetNode amount 1 (Set.empty)(Set.empty)(Set.empty) routeParams (BlockHeight(567634u)) - |> Result.deref - |> Seq.toList - - Expect.equal (route.Length) 2 "" - Expect.equal (route |> List.last).NextNodeIdValue targetNode "" -] diff --git a/tests/DotNetLightning.Core.Tests/Serialization.fs b/tests/DotNetLightning.Core.Tests/Serialization.fs index 57279a076..b680ba751 100644 --- a/tests/DotNetLightning.Core.Tests/Serialization.fs +++ b/tests/DotNetLightning.Core.Tests/Serialization.fs @@ -1,901 +1,2141 @@ -module Serialization - -open DotNetLightning.Utils -open DotNetLightning.Core.Utils.Extensions -open DotNetLightning.Serialization.Msgs -open DotNetLightning.Crypto - -open DotNetLightning.Serialization -open Expecto -open NBitcoin -open System -open System.Collections -open FsCheck - -open ResultUtils -open ResultUtils.Portability - -module SerializationTest = - - open Utils - /// helper for more clean error message - - let parseBitArray (str: string) = - match BitArray.TryParse str with - | Ok ba -> ba - | Error e -> failwith e - - let hex = NBitcoin.DataEncoders.HexEncoder() - let base64 = NBitcoin.DataEncoders.Base64Encoder() - let ascii = System.Text.ASCIIEncoding.ASCII - let signMessageWith (privKey: Key) (msgHash: string) = - let msgBytes = msgHash |> ascii.GetBytes - privKey.SignCompact(msgBytes |> uint256, false) |> fun d -> LNECDSASignature.FromBytesCompact(d, true) - let privKey1 = new Key(hex.DecodeData("0101010101010101010101010101010101010101010101010101010101010101")) - let privKey2 = new Key(hex.DecodeData("0202020202020202020202020202020202020202020202020202020202020202")) - let privKey3 = new Key(hex.DecodeData("0303030303030303030303030303030303030303030303030303030303030303")) - let privKey4 = new Key(hex.DecodeData("0404040404040404040404040404040404040404040404040404040404040404")) - let privKey5 = new Key(hex.DecodeData("0505050505050505050505050505050505050505050505050505050505050505")) - let privKey6 = new Key(hex.DecodeData("0606060606060606060606060606060606060606060606060606060606060606")) - let pubkey1 = privKey1.PubKey - let pubkey2 = privKey2.PubKey - let pubkey3 = privKey3.PubKey - let pubkey4 = privKey4.PubKey - let pubkey5 = privKey5.PubKey - let pubkey6 = privKey6.PubKey - - [] - let tests = - testList "Serialization unit tests" [ - testCase "node_announcement" <| fun _ -> - let sig1 = signMessageWith privKey1 "01010101010101010101010101010101" - let msg = { NodeAnnouncementMsg.Signature = sig1 - Contents = { UnsignedNodeAnnouncementMsg.NodeId = NodeId(PubKey("03f3c15dbc4d425a4f4c36162a9159bb83511fa920dba1cc2785c434ecaf094015")) - Features = FeatureBits.CreateUnsafe [|0uy|] - Timestamp = 1u - RGB = { Red = 217uy; Green = 228uy; Blue = 166uy } - Alias = uint256.Zero - Addresses = [|IPv4({ Addr=[|18uy; 94uy; 0uy; 118uy|]; Port = 7us })|] - ExcessAddressData = [|5uy; 121uy; 62uy; 96uy; 44uy; 34uy|] - ExcessData = [||] }} - Expect.equal (msg.Clone()) msg "" - testCase "short channel id test" <| fun _ -> - let shortChannelId = (ShortChannelId.FromUInt64(140737488420865UL)) - Expect.equal (shortChannelId.BlockHeight.Value) 128u "" - Expect.equal (shortChannelId.BlockIndex.Value) 1u "" - Expect.equal (shortChannelId.TxOutIndex.Value) 1us "" - () - ] - - [] - let testsRustLightningSerialization = - testList "SerializationTest ported from rust-lightning" [ - testCase "channel_reestablish no secret" <| fun _ -> - let cid = ChannelId (uint256([|4; 0; 0; 0; 0; 0; 0; 0; 5; 0; 0; 0; 0; 0; 0; 0; 6; 0; 0; 0; 0; 0; 0; 0; 7; 0; 0; 0; 0; 0; 0; 0|] |> Array.map((uint8)))) - let channelReestablishMsg = { - ChannelId = cid - NextCommitmentNumber = CommitmentNumber <| (UInt48.MaxValue - UInt48.FromUInt64 3UL) - NextRevocationNumber = CommitmentNumber <| (UInt48.MaxValue - UInt48.FromUInt64 4UL) - DataLossProtect = None - } - let actual = channelReestablishMsg.ToBytes() - let expected = - [|4; 0; 0; 0; 0; 0; 0; 0; 5; 0; 0; 0; 0; 0; 0; 0; 6; 0; 0; 0; 0; 0; 0; 0; 7; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 3; 0; 0; 0; 0; 0; 0; 0; 4|] - |> Array.map(uint8) - Expect.equal actual expected "channel_reestablish_no_secret failed" - - testCase "channel_reestablish with secret" <| fun _ -> - let channelReestablishMsg = { - ChannelId = ChannelId(uint256([|4; 0; 0; 0; 0; 0; 0; 0; 5; 0; 0; 0; 0; 0; 0; 0; 6; 0; 0; 0; 0; 0; 0; 0; 7; 0; 0; 0; 0; 0; 0; 0 |] |> Array.map(uint8))) - NextCommitmentNumber = CommitmentNumber <| (UInt48.MaxValue - UInt48.FromUInt64 3UL) - NextRevocationNumber = CommitmentNumber <| (UInt48.MaxValue - UInt48.FromUInt64 4UL) - DataLossProtect = OptionalField.Some <| { - YourLastPerCommitmentSecret = - Some <| PerCommitmentSecret.FromBytes - [| for _ in 0..(PerCommitmentSecret.BytesLength - 1) -> 9uy |] - MyCurrentPerCommitmentPoint = PerCommitmentPoint pubkey1 - } - } - let expected = [|4; 0; 0; 0; 0; 0; 0; 0; 5; 0; 0; 0; 0; 0; 0; 0; 6; 0; 0; 0; 0; 0; 0; 0; 7; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 3; 0; 0; 0; 0; 0; 0; 0; 4; 9; 9; 9; 9; 9; 9; 9; 9; 9; 9; 9; 9; 9; 9; 9; 9; 9; 9; 9; 9; 9; 9; 9; 9; 9; 9; 9; 9; 9; 9; 9; 9; 3; 27; 132; 197; 86; 123; 18; 100; 64; 153; 93; 62; 213; 170; 186; 5; 101; 215; 30; 24; 52; 96; 72; 25; 255; 156; 23; 245; 233; 213; 221; 7; 143 |] |> Array.map (byte) - Expect.equal (channelReestablishMsg.ToBytes()) expected "" - testCase "short_channel_id" <| fun _ -> - let actual = ShortChannelId.FromUInt64(2316138423780173UL) - let expected = [| 0uy; 8uy; 58uy; 132uy; 0uy; 0uy; 3uy; 77uy;|] - Expect.equal (actual.ToBytes()) expected "" - - testCase "announcement_signatures" <| fun _ -> - let sig1 = signMessageWith privKey1 "01010101010101010101010101010101" - let sig2 = signMessageWith privKey1 "02020202020202020202020202020202" - let actual = { - ChannelId = ChannelId(uint256([| 4; 0; 0; 0; 0; 0; 0; 0; 5; 0; 0; 0; 0; 0; 0; 0; 6; 0; 0; 0; 0; 0; 0; 0; 7; 0; 0; 0; 0; 0; 0; 0 |] |> Array.map(uint8))) - ShortChannelId = ShortChannelId.FromUInt64(2316138423780173UL) - NodeSignature = sig1 - BitcoinSignature = sig2 - } - let expected = hex.DecodeData("040000000000000005000000000000000600000000000000070000000000000000083a840000034dd977cb9b53d93a6ff64bb5f1e158b4094b66e798fb12911168a3ccdf80a83096340a6a95da0ae8d9f776528eecdbb747eb6b545495a4319ed5378e35b21e073acf9953cef4700860f5967838eba2bae89288ad188ebf8b20bf995c3ea53a26df1876d0a3a0e13172ba286a673140190c02ba9da60a2e43a745188c8a83c7f3ef") - Expect.equal (actual.ToBytes().Length) expected.Length "" - Expect.equal (actual.ToBytes()) (expected) "" - CheckArrayEqual (actual.ToBytes()) expected - - testCase "channel_announcement" <| fun _ -> - let channelAnnouncementTestCore (nonbitcoinChainHash: bool, excessData: bool) = - let sig1 = signMessageWith privKey1 "01010101010101010101010101010101" - let sig2 = signMessageWith privKey2 "01010101010101010101010101010101" - let sig3 = signMessageWith privKey3 "01010101010101010101010101010101" - let sig4 = signMessageWith privKey4 "01010101010101010101010101010101" - let mutable features = FeatureBits.CreateUnsafe [||] - - let unsignedChannelAnnoucement = { - Features = features - ChainHash = if (not nonbitcoinChainHash) then uint256(hex.DecodeData("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f")) else uint256(hex.DecodeData("000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943")) - ShortChannelId = ShortChannelId.FromUInt64(2316138423780173UL) - NodeId1 = NodeId(privKey1.PubKey) - NodeId2 = NodeId(privKey2.PubKey) - BitcoinKey1 = !> privKey3.PubKey - BitcoinKey2 = !> privKey4.PubKey - ExcessData = if excessData then ([| 10; 0; 0; 20; 0; 0; 30; 0; 0; 40 |] |> Array.map(byte)) else [||] - } - let channelAnnouncementMsg = { - NodeSignature1 = sig1 - NodeSignature2 = sig2 - BitcoinSignature1 = sig3 - BitcoinSignature2 = sig4 - Contents = unsignedChannelAnnoucement - } - - let mutable expected = hex.DecodeData("d977cb9b53d93a6ff64bb5f1e158b4094b66e798fb12911168a3ccdf80a83096340a6a95da0ae8d9f776528eecdbb747eb6b545495a4319ed5378e35b21e073a1735b6a427e80d5fe7cd90a2f4ee08dc9c27cda7c35a4172e5d85b12c49d4232537e98f9b1f3c5e6989a8b9644e90e8918127680dbd0d4043510840fc0f1e11a216c280b5395a2546e7e4b2663e04f811622f15a4f91e83aa2e92ba2a573c139142c54ae63072a1ec1ee7dc0c04bde5c847806172aa05c92c22ae8e308d1d2692b12cc195ce0a2d1bda6a88befa19fa07f51caa75ce83837f28965600b8aacab0855ffb0e741ec5f7c41421e9829a9d48611c8c831f71be5ea73e66594977ffd") - expected <- Array.append expected (hex.DecodeData("0000")) - if nonbitcoinChainHash then - expected <- Array.append expected (hex.DecodeData("000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943")) - else - expected <- Array.append expected (hex.DecodeData("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f")) - expected <- Array.append expected (hex.DecodeData("00083a840000034d031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f024d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d076602531fe6068134503d2723133227c867ac8fa6c83c537e9a44c3c5bdbdcb1fe33703462779ad4aad39514614751a71085f2f10e1c7a593e4e030efb5b8721ce55b0b")) - if excessData then - expected <- Array.append expected (hex.DecodeData("0a00001400001e000028")) - Expect.equal (channelAnnouncementMsg.ToBytes().[300..]) expected.[300..] "mismatch in postfix" - Expect.equal (channelAnnouncementMsg.ToBytes()) expected "" - channelAnnouncementTestCore (false, false) - channelAnnouncementTestCore (false, false) - channelAnnouncementTestCore (true, false) - channelAnnouncementTestCore (true, true) - channelAnnouncementTestCore (true, true) - channelAnnouncementTestCore (false, true) - channelAnnouncementTestCore (true, false) - channelAnnouncementTestCore (false, true) - testCase "node_announcement" <| fun _ -> - let nodeAnnouncementTestCore(ipv4: bool, ipv6: bool, onionv2: bool, onionv3: bool, excessAddressData: bool, excessData: bool) = - let sig1 = signMessageWith privKey1 "01010101010101010101010101010101" - let mutable features = FeatureBits.CreateUnsafe [||] - let mutable addresses = List.Empty - if ipv4 then - addresses <- addresses @ [NetAddress.IPv4 ({ IPv4Or6Data.Addr = [|255uy;254uy; 253uy; 252uy; |] - Port = 9735us }) - ] - if ipv6 then - addresses <- addresses @ - [NetAddress.IPv6 ({ IPv4Or6Data.Addr = [|255uy; - 254uy; - 253uy; - 252uy; - 251uy; - 250uy; - 249uy; - 248uy; - 247uy; - 246uy; - 245uy; - 244uy; - 243uy; - 242uy; - 241uy; - 240uy; |] - Port = 9735us })] - if onionv2 then - addresses <- addresses @ [NetAddress.OnionV2({ - Addr =[| 255uy - 254uy - 253uy - 252uy - 251uy - 250uy - 249uy - 248uy - 247uy - 246uy |] - Port = 9735us } )] - if onionv3 then - addresses <- addresses @ [NetAddress.OnionV3({ - ed25519PubKey = [| 255uy - 254uy - 253uy - 252uy - 251uy - 250uy - 249uy - 248uy - 247uy - 246uy - 245uy - 244uy - 243uy - 242uy - 241uy - 240uy - 239uy - 238uy - 237uy - 236uy - 235uy - 234uy - 233uy - 232uy - 231uy - 230uy - 229uy - 228uy - 227uy - 226uy - 225uy - 224uy|] - CheckSum = 32us - Version = 16uy - Port = 9735us - })] - let mutable addrLen = 0us - for addr in addresses do - addrLen <- addrLen + addr.Length + 1us - - let unsignedNodeAnnouncementMsg = { - Features = features - Timestamp = 20190119u - NodeId = NodeId(pubkey1) - RGB = {Blue = 32uy; Red = 32uy; Green = 32uy} - Alias = uint256([| for _ in 0..31 -> 16uy|]) - Addresses = addresses |> Array.ofList - ExcessAddressData = if excessAddressData then [|33uy - 108uy - 40uy - 11uy - 83uy - 149uy - 162uy - 84uy - 110uy - 126uy - 75uy - 38uy - 99uy - 224uy - 79uy - 129uy - 22uy - 34uy - 241uy - 90uy - 79uy - 146uy - 232uy - 58uy - 162uy - 233uy - 43uy - 162uy - 165uy - 115uy - 193uy - 57uy - 20uy - 44uy - 84uy - 174uy - 99uy - 7uy - 42uy - 30uy - 193uy - 238uy - 125uy - 192uy - 192uy - 75uy - 222uy - 92uy - 132uy - 120uy - 6uy - 23uy - 42uy - 160uy - 92uy - 146uy - 194uy - 42uy - 232uy - 227uy - 8uy - 209uy - 210uy - 105uy|] else [||] - ExcessData = if excessData then [|59uy - 18uy - 204uy - 25uy - 92uy - 224uy - 162uy - 209uy - 189uy - 166uy - 168uy - 139uy - 239uy - 161uy - 159uy - 160uy - 127uy - 81uy - 202uy - 167uy - 92uy - 232uy - 56uy - 55uy - 242uy - 137uy - 101uy - 96uy - 11uy - 138uy - 172uy - 171uy - 8uy - 85uy - 255uy - 176uy - 231uy - 65uy - 236uy - 95uy - 124uy - 65uy - 66uy - 30uy - 152uy - 41uy - 169uy - 212uy - 134uy - 17uy - 200uy - 200uy - 49uy - 247uy - 27uy - 229uy - 234uy - 115uy - 230uy - 101uy - 148uy - 151uy - 127uy - 253uy|] else [||] - } - addrLen <- addrLen + uint16 unsignedNodeAnnouncementMsg.ExcessAddressData.Length - let nodeAnnouncementMsg = { - NodeAnnouncementMsg.Signature = sig1 - Contents = unsignedNodeAnnouncementMsg - } - - let actual = nodeAnnouncementMsg.ToBytes() - let mutable expected = hex.DecodeData("d977cb9b53d93a6ff64bb5f1e158b4094b66e798fb12911168a3ccdf80a83096340a6a95da0ae8d9f776528eecdbb747eb6b545495a4319ed5378e35b21e073a") - expected <- Array.append expected (hex.DecodeData("0000")) - expected <- Array.append expected (hex.DecodeData("013413a7031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f2020201010101010101010101010101010101010101010101010101010101010101010")) - expected <- Array.append expected ([| byte(addrLen >>> 8); byte addrLen |]) - if ipv4 then - expected <- Array.append expected (hex.DecodeData("01fffefdfc2607")) - if ipv6 then - expected <- Array.append expected (hex.DecodeData("02fffefdfcfbfaf9f8f7f6f5f4f3f2f1f02607")) - if onionv2 then - expected <- Array.append expected (hex.DecodeData("03fffefdfcfbfaf9f8f7f62607")) - if onionv3 then - expected <- Array.append expected (hex.DecodeData("04fffefdfcfbfaf9f8f7f6f5f4f3f2f1f0efeeedecebeae9e8e7e6e5e4e3e2e1e00020102607")) - if excessAddressData then - expected <- Array.append expected (hex.DecodeData("216c280b5395a2546e7e4b2663e04f811622f15a4f92e83aa2e92ba2a573c139142c54ae63072a1ec1ee7dc0c04bde5c847806172aa05c92c22ae8e308d1d269")) - if excessData then - expected <- Array.append expected (hex.DecodeData("3b12cc195ce0a2d1bda6a88befa19fa07f51caa75ce83837f28965600b8aacab0855ffb0e741ec5f7c41421e9829a9d48611c8c831f71be5ea73e66594977ffd")) - // CheckArrayEqual actual expected - Expect.sequenceContainsOrder actual expected "" - Expect.equal actual expected "" - nodeAnnouncementTestCore(true, true, true, true, true, true) - nodeAnnouncementTestCore(false, false, false, false, false, false) - nodeAnnouncementTestCore(true, false, false, false, false, false) - nodeAnnouncementTestCore(false, true, false, false, false, false) - nodeAnnouncementTestCore(false, false, true, false, false, false) - nodeAnnouncementTestCore(false, false, false, true, false, false) - nodeAnnouncementTestCore(false, false, false, false, true, false) - nodeAnnouncementTestCore(true, false, true, false, true, false) - nodeAnnouncementTestCore(false, true, false, true, false, false) - testCase "channel_update msg" <| fun _ -> - let channelUpdateTestCore (nonBitcoinChainHash: bool, direction: bool, disable: bool, htlcMaximumMSat: bool) = - let sig1 = signMessageWith privKey1 "01010101010101010101010101010101" - let unsignedChannelUpdateMsg = { - UnsignedChannelUpdateMsg.ChainHash = if (not nonBitcoinChainHash) then uint256(hex.DecodeData("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f")) else uint256(hex.DecodeData("000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943")) - ShortChannelId = ShortChannelId.FromUInt64(2316138423780173UL) - Timestamp = 20190119u - MessageFlags = (if htlcMaximumMSat then 1uy else 0uy) - ChannelFlags = ((if direction && disable then (2uy) else if disable then (2uy) else if direction then 1uy else 0uy)) - CLTVExpiryDelta = !> 144us - HTLCMinimumMSat = LNMoney.MilliSatoshis(1000000L) - FeeBaseMSat = LNMoney.MilliSatoshis(10000L) - FeeProportionalMillionths = 20u - HTLCMaximumMSat = - if htlcMaximumMSat then - [| 0uy; 0uy; 0uy; 0uy; 59uy; 154uy; 202uy; 0uy |] - |> fun b -> NBitcoin.Utils.ToUInt64(b, false) - |> LNMoney.MilliSatoshis - |> Some - else - None - } - let channelUpdateMsg = { - ChannelUpdateMsg.Signature = sig1 - Contents = unsignedChannelUpdateMsg - } - let actual = channelUpdateMsg.ToBytes() - let mutable expected = hex.DecodeData("d977cb9b53d93a6ff64bb5f1e158b4094b66e798fb12911168a3ccdf80a83096340a6a95da0ae8d9f776528eecdbb747eb6b545495a4319ed5378e35b21e073a") - if nonBitcoinChainHash then - expected <- Array.append expected (hex.DecodeData("000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943")) - else - expected <- Array.append expected (hex.DecodeData("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f")) - expected <- Array.append expected (hex.DecodeData("00083a840000034d013413a7")) - if htlcMaximumMSat then - expected <- Array.append expected (hex.DecodeData("01")) - else - expected <- Array.append expected (hex.DecodeData("00")) - expected <- Array.append expected (hex.DecodeData("00")) - if direction then - expected.[expected.Length - 1] <- 1uy - if disable then - expected.[expected.Length - 1] <- expected.[expected.Length - 1] ||| 1uy <<< 1 - expected <- Array.append expected (hex.DecodeData("009000000000000f42400000271000000014")) - if htlcMaximumMSat then - expected <- Array.append expected (hex.DecodeData("000000003b9aca00")) - CheckArrayEqual actual expected - channelUpdateTestCore(false, false, false, false) - channelUpdateTestCore(true, false, false, false) - channelUpdateTestCore(false, true, false, false) - channelUpdateTestCore(false, false, true, false) - channelUpdateTestCore(false, false, false, true) - channelUpdateTestCore(true, true, true, true) - - testCase "open_channel" <| fun _ -> - let openChannelTestCore(nonBitcoinChainHash: bool, randomBit: bool, shutdown: bool) = - let openChannelMsg = { - Chainhash = if (not nonBitcoinChainHash) then uint256(hex.DecodeData("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f")) else uint256(hex.DecodeData("000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943")) - TemporaryChannelId = ChannelId(uint256([| for _ in 0..31 -> 2uy |])) - FundingSatoshis = Money.Satoshis(1311768467284833366UL) - PushMSat = LNMoney.MilliSatoshis(2536655962884945560L) - DustLimitSatoshis = Money.Satoshis(3608586615801332854UL) - MaxHTLCValueInFlightMsat = LNMoney.MilliSatoshis(8517154655701053848L) - ChannelReserveSatoshis = Money.Satoshis(8665828695742877976UL) - HTLCMinimumMsat = LNMoney.MilliSatoshis(2316138423780173UL) - FeeRatePerKw = FeeRatePerKw(821716u) - ToSelfDelay = BlockHeightOffset16(49340us) - MaxAcceptedHTLCs = 49340us - FundingPubKey = FundingPubKey pubkey1 - RevocationBasepoint = RevocationBasepoint pubkey2 - PaymentBasepoint = PaymentBasepoint pubkey3 - DelayedPaymentBasepoint = DelayedPaymentBasepoint pubkey4 - HTLCBasepoint = HtlcBasepoint pubkey5 - FirstPerCommitmentPoint = PerCommitmentPoint pubkey6 - ChannelFlags = { - AnnounceChannel = randomBit - } - TLVs = [| - OpenChannelTLV.UpfrontShutdownScript ( - if shutdown then - Some <| ShutdownScriptPubKey.FromPubKeyP2pkh pubkey1 - else - None - ) - |] - } - let actual = openChannelMsg.ToBytes() - let mutable expected = [||] - if nonBitcoinChainHash then - expected <- Array.append expected (hex.DecodeData("000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943")) - else - expected <- Array.append expected (hex.DecodeData("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f")) - expected <- Array.append expected (hex.DecodeData("02020202020202020202020202020202020202020202020202020202020202021234567890123456233403289122369832144668701144767633030896203198784335490624111800083a840000034d000c89d4c0bcc0bc031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f024d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d076602531fe6068134503d2723133227c867ac8fa6c83c537e9a44c3c5bdbdcb1fe33703462779ad4aad39514614751a71085f2f10e1c7a593e4e030efb5b8721ce55b0b0362c0a046dacce86ddd0343c6d3c7c79c2208ba0d9c9cf24a6d046d21d21f90f703f006a18d5653c4edf5391ff23a61f03ff83d237e880ee61187fa9f379a028e0a")) - if randomBit then - expected <- Array.append expected (hex.DecodeData("01")) - else - expected <- Array.append expected (hex.DecodeData("00")) - if shutdown then - expected <- Array.append expected (hex.DecodeData("001976a91479b000887626b294a914501a4cd226b58b23598388ac")) - else - expected <- Array.append expected (hex.DecodeData("0000")) - CheckArrayEqual actual expected - Expect.equal (openChannelMsg.Clone()) openChannelMsg "" - openChannelTestCore(false, false, false) - openChannelTestCore(true, false, false) - openChannelTestCore(false, true, false) - openChannelTestCore(false, false, true) - openChannelTestCore(true, true, true) - testCase "accept_channel" <| fun _ -> - let acceptChannelTestCore(shutdown: bool) = - let acceptChannelMsg = { - AcceptChannelMsg.TemporaryChannelId = ChannelId(uint256([| for _ in 0..31 -> 2uy|])) - DustLimitSatoshis = Money.Satoshis(1311768467284833366L) - MaxHTLCValueInFlightMsat = LNMoney.MilliSatoshis(2536655962884945560L) - ChannelReserveSatoshis = Money.Satoshis(3608586615801332854L) - HTLCMinimumMSat = LNMoney.MilliSatoshis(2316138423780173L) - MinimumDepth = 821716u |> BlockHeightOffset32 - ToSelfDelay = BlockHeightOffset16(49340us) - MaxAcceptedHTLCs = 49340us - FundingPubKey = FundingPubKey pubkey1 - RevocationBasepoint = RevocationBasepoint pubkey2 - PaymentBasepoint = PaymentBasepoint pubkey3 - DelayedPaymentBasepoint = DelayedPaymentBasepoint pubkey4 - HTLCBasepoint = HtlcBasepoint pubkey5 - FirstPerCommitmentPoint = PerCommitmentPoint pubkey6 - TLVs = [| - AcceptChannelTLV.UpfrontShutdownScript ( - if shutdown then - Some <| ShutdownScriptPubKey.FromPubKeyP2pkh pubkey1 - else - None - ) - |] - } - let actual = acceptChannelMsg.ToBytes() - let mutable expected = hex.DecodeData("020202020202020202020202020202020202020202020202020202020202020212345678901234562334032891223698321446687011447600083a840000034d000c89d4c0bcc0bc031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f024d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d076602531fe6068134503d2723133227c867ac8fa6c83c537e9a44c3c5bdbdcb1fe33703462779ad4aad39514614751a71085f2f10e1c7a593e4e030efb5b8721ce55b0b0362c0a046dacce86ddd0343c6d3c7c79c2208ba0d9c9cf24a6d046d21d21f90f703f006a18d5653c4edf5391ff23a61f03ff83d237e880ee61187fa9f379a028e0a") - if shutdown then - expected <- Array.append expected (hex.DecodeData("001976a91479b000887626b294a914501a4cd226b58b23598388ac")) - else - expected <- Array.append expected (hex.DecodeData("0000")) - CheckArrayEqual actual expected - acceptChannelTestCore(false) - acceptChannelTestCore(true) - testCase "funding_created" <| fun _ -> - let sig1 = signMessageWith privKey1 "01010101010101010101010101010101" - let txData = hex.DecodeData("c2d4449afa8d26140898dd54d3390b057ba2a5afcf03ba29d7dc0d8b9ffe966e") - Array.Reverse txData - let fundingCreatedMsg = { - FundingCreatedMsg.TemporaryChannelId = ChannelId(uint256[| for _ in 0..31 -> 2uy|]) - FundingTxId = TxId(uint256(txData, true)) - FundingOutputIndex = 255us |> TxOutIndex - Signature = sig1 - } - let actual = fundingCreatedMsg.ToBytes() - let expected = hex.DecodeData("02020202020202020202020202020202020202020202020202020202020202026e96fe9f8b0ddcd729ba03cfafa5a27b050b39d354dd980814268dfa9a44d4c200ffd977cb9b53d93a6ff64bb5f1e158b4094b66e798fb12911168a3ccdf80a83096340a6a95da0ae8d9f776528eecdbb747eb6b545495a4319ed5378e35b21e073a") - // Expect.equal (actual) expected "" - CheckArrayEqual actual expected - testCase "funding_signed" <| fun _ -> - let sig1 = signMessageWith privKey1 "01010101010101010101010101010101" - let fundingSignedMsg = { - FundingSignedMsg.ChannelId = ChannelId(uint256[| for _ in 0..31 -> 2uy|]) - Signature = sig1 - } - let expected = hex.DecodeData("0202020202020202020202020202020202020202020202020202020202020202d977cb9b53d93a6ff64bb5f1e158b4094b66e798fb12911168a3ccdf80a83096340a6a95da0ae8d9f776528eecdbb747eb6b545495a4319ed5378e35b21e073a") - CheckArrayEqual (fundingSignedMsg.ToBytes()) expected - () - testCase "funding_locked" <| fun _ -> - let fundingLockedMsg = { - FundingLockedMsg.ChannelId = ChannelId(uint256[| for _ in 0..31 -> 2uy|]) - NextPerCommitmentPoint = PerCommitmentPoint pubkey1 - } - let expected = hex.DecodeData("0202020202020202020202020202020202020202020202020202020202020202031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f") - CheckArrayEqual (fundingLockedMsg.ToBytes()) expected - testCase "shutdown" <| fun _ -> - let shutDownTestCore (scriptType: uint8) = - let script = Script("OP_TRUE") - let spk = - if (scriptType = 1uy) then - ShutdownScriptPubKey.FromPubKeyP2pkh pubkey1 - else if (scriptType = 2uy) then - ShutdownScriptPubKey.FromScriptP2sh script - else if (scriptType = 3uy) then - ShutdownScriptPubKey.FromPubKeyP2wpkh pubkey1 - else - ShutdownScriptPubKey.FromScriptP2wsh script - let shutdownMsg = { - ShutdownMsg.ChannelId = ChannelId(uint256[| for _ in 0..31 -> 2uy|]) - ScriptPubKey = spk - } - let mutable expected = hex.DecodeData("0202020202020202020202020202020202020202020202020202020202020202") - expected <- Array.append expected (if (scriptType = 1uy) then - hex.DecodeData("001976a91479b000887626b294a914501a4cd226b58b23598388ac") - else if (scriptType = 2uy) then - hex.DecodeData("0017a914da1745e9b549bd0bfa1a569971c77eba30cd5a4b87") - else if (scriptType = 3uy) then - hex.DecodeData("0016001479b000887626b294a914501a4cd226b58b235983") - else - hex.DecodeData("002200204ae81572f06e1b88fd5ced7a1a000945432e83e1551e6f721ee9c00b8cc33260")) - CheckArrayEqual (shutdownMsg.ToBytes()) expected - shutDownTestCore(1uy) - shutDownTestCore(2uy) - shutDownTestCore(3uy) - shutDownTestCore(4uy) - testCase "update_add_htlc" <| fun _ -> - let onionRoutingPacket = { - Version = 255uy - PublicKey = pubkey1.ToBytes() - HopData = [| for _ in 1..(20*65) -> 1uy |] - HMAC = uint256([| for _ in 0..31 -> 2uy |]) - } - let updateAddHtlcMsg = { - UpdateAddHTLCMsg.ChannelId = ChannelId(uint256([| for _ in 0..31 -> 2uy |])) - HTLCId = HTLCId(2316138423780173UL) - Amount = LNMoney.MilliSatoshis 3608586615801332854L - PaymentHash = PaymentHash(uint256[| for _ in 0..31 -> 1uy |]) - CLTVExpiry = 821716u |> BlockHeight - OnionRoutingPacket = onionRoutingPacket - } - let expected = hex.DecodeData("020202020202020202020202020202020202020202020202020202020202020200083a840000034d32144668701144760101010101010101010101010101010101010101010101010101010101010101000c89d4ff031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010202020202020202020202020202020202020202020202020202020202020202") - CheckArrayEqual (updateAddHtlcMsg.ToBytes()) expected - - testCase "update_fulfill_htlc" <| fun _ -> - let updateFulfillHTLCMsg = { - ChannelId = ChannelId(uint256([| for _ in 0..31 -> 2uy |])) - HTLCId = HTLCId(2316138423780173UL) - PaymentPreimage = PaymentPreimage.Create([| for _ in 0..(PaymentPreimage.LENGTH - 1) -> 1uy |]) - } - let expected = hex.DecodeData("020202020202020202020202020202020202020202020202020202020202020200083a840000034d0101010101010101010101010101010101010101010101010101010101010101") - CheckArrayEqual (updateFulfillHTLCMsg.ToBytes()) expected - testCase "update_fail_htlc" <| fun _ -> - let reason = { - Data = [| for _ in 0..31 -> 1uy |] - } - let updateFailHTLCMsg = { - ChannelId = ChannelId(uint256([| for _ in 0..31 -> 2uy |])) - HTLCId = HTLCId(2316138423780173UL) - Reason = reason - } - let expected = hex.DecodeData("020202020202020202020202020202020202020202020202020202020202020200083a840000034d00200101010101010101010101010101010101010101010101010101010101010101") - CheckArrayEqual (updateFailHTLCMsg.ToBytes()) expected - testCase "update_fail_malformed_htlc" <| fun _ -> - let updateFailMalformedHTLCMsg = { - ChannelId = ChannelId(uint256([| for _ in 0..31 -> 2uy |])) - HTLCId = HTLCId(2316138423780173UL) - Sha256OfOnion = uint256([| for _ in 0..31 -> 1uy |]) - FailureCode = OnionError.FailureCode(255us) - } - let expected = hex.DecodeData("020202020202020202020202020202020202020202020202020202020202020200083a840000034d010101010101010101010101010101010101010101010101010101010101010100ff") - Expect.equal (updateFailMalformedHTLCMsg.ToBytes()) expected "" - testCase "commitment_signed" <| fun _ -> - let testCommitmentSignedCore (htlcs: bool) = - let sig1 = signMessageWith privKey1 "01010101010101010101010101010101" - let sig2 = signMessageWith privKey2 "01010101010101010101010101010101" - let sig3 = signMessageWith privKey3 "01010101010101010101010101010101" - let sig4 = signMessageWith privKey4 "01010101010101010101010101010101" - let commitmentSignedMsg = { - ChannelId = ChannelId(uint256([| for _ in 0..31 -> 2uy |])) - Signature = sig1 - HTLCSignatures = if htlcs then [ sig2; sig3; sig4 ] else [] - } - let mutable expected = hex.DecodeData("0202020202020202020202020202020202020202020202020202020202020202d977cb9b53d93a6ff64bb5f1e158b4094b66e798fb12911168a3ccdf80a83096340a6a95da0ae8d9f776528eecdbb747eb6b545495a4319ed5378e35b21e073a") - if htlcs then - expected <- Array.append expected (hex.DecodeData("00031735b6a427e80d5fe7cd90a2f4ee08dc9c27cda7c35a4172e5d85b12c49d4232537e98f9b1f3c5e6989a8b9644e90e8918127680dbd0d4043510840fc0f1e11a216c280b5395a2546e7e4b2663e04f811622f15a4f91e83aa2e92ba2a573c139142c54ae63072a1ec1ee7dc0c04bde5c847806172aa05c92c22ae8e308d1d2692b12cc195ce0a2d1bda6a88befa19fa07f51caa75ce83837f28965600b8aacab0855ffb0e741ec5f7c41421e9829a9d48611c8c831f71be5ea73e66594977ffd")) - else - expected <- Array.append expected (hex.DecodeData("0000")) - CheckArrayEqual (commitmentSignedMsg.ToBytes()) expected - - testCommitmentSignedCore true - testCommitmentSignedCore false - testCase "revoke_and_ack" <| fun _ -> - let revokeAndACKMsg = { - ChannelId = ChannelId(uint256([| for _ in 0..31 -> 2uy |])) - PerCommitmentSecret = - PerCommitmentSecret.FromBytes - [| for _ in 0..(PaymentPreimage.LENGTH - 1) -> 1uy |] - NextPerCommitmentPoint = PerCommitmentPoint pubkey1 - } - let expected = hex.DecodeData("02020202020202020202020202020202020202020202020202020202020202020101010101010101010101010101010101010101010101010101010101010101031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f") - CheckArrayEqual (revokeAndACKMsg.ToBytes()) expected - testCase "update_fee" <| fun _ -> - let updateFeeMsg = { - ChannelId = ChannelId(uint256([| for _ in 0..31 -> 2uy |])) - FeeRatePerKw = FeeRatePerKw(20190119u) - } - let expected = hex.DecodeData("0202020202020202020202020202020202020202020202020202020202020202013413a7") - // not using CheckArrayEqual since it is smaller than 50 - Expect.equal (updateFeeMsg.ToBytes()) expected "" - - testCase "init" <| fun _ -> - let initTestCore (initialRoutingSync: bool) = - let flags = [||] - let globalFeatures = flags |> BitArray.FromBytes - let localFeatures = - if initialRoutingSync then "0b1000" |> BitArray.TryParse else BitArray.TryParse("") - |> function Ok ba -> ba | Error e -> failwith e - - let initMsg = { - Features = [| globalFeatures; localFeatures |] |> BitArray.Concat |> FeatureBits.CreateUnsafe - TLVStream = [||] - } - let mutable expected = [||] - expected <- Array.append expected (hex.DecodeData("0000")) - if initialRoutingSync then - expected <- Array.append expected (hex.DecodeData("000108")) - else - expected <- Array.append expected (hex.DecodeData("0000")) - Expect.equal (initMsg.ToBytes()) (expected) "" - initTestCore(false) - initTestCore(true) - - testCase "error" <| fun _ -> - let errorMsg = { - ChannelId = WhichChannel.SpecificChannel(ChannelId(uint256([| for _ in 0..31 -> 2uy |]))) - Data = ascii.GetBytes("rust-lightning") - } - let expected = hex.DecodeData("0202020202020202020202020202020202020202020202020202020202020202000e727573742d6c696768746e696e67") - Expect.equal (errorMsg.ToBytes()) (expected) "" - - testCase "ping" <| fun _ -> - let pingMsg = { - PongLen = 64us - BytesLen = 64us - } - let expected = hex.DecodeData("0040004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") - Expect.equal (pingMsg.ToBytes()) (expected) "" - testCase "pong" <| fun _ -> - let pongMsg = { - BytesLen = 64us - } - let expected = hex.DecodeData("004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") - Expect.equal (pongMsg.ToBytes()) (expected) "" - - ] - - [] - let testFeaturesSerialization = - testList "Features serialization" [ - testCase "initial_routing_sync" <| fun _ -> - Expect.isTrue (Feature.hasFeature(parseBitArray"0b00001000") (Feature.InitialRoutingSync) (Some Optional)) "" - Expect.isFalse(Feature.hasFeature(parseBitArray"0b00001000") (Feature.InitialRoutingSync) (Some Mandatory)) "" - - testCase "data_loss_protect" <| fun _ -> - Expect.isTrue (Feature.hasFeature(parseBitArray"0b00000001") (Feature.OptionDataLossProtect) (Some Mandatory)) "" - Expect.isTrue (Feature.hasFeature(parseBitArray"0b00000010") (Feature.OptionDataLossProtect) (Some Optional)) "" - - testCase "initial_routing_sync, data_loss_protect and option_upfront_shutdown_script features" <| fun _ -> - let features = parseBitArray("0000000000101010") - Expect.isTrue (Feature.areSupported(features)) "" - Expect.isTrue (Feature.hasFeature(features) (Feature.InitialRoutingSync) (None)) "" - Expect.isTrue (Feature.hasFeature(features) (Feature.OptionDataLossProtect) (None)) "" - Expect.isTrue (Feature.hasFeature(features) (Feature.OptionUpfrontShutdownScript) (None)) "" - - testCase "variable_length_onion feature" <| fun _ -> - Expect.isTrue (Feature.hasFeature("0b0000000100000000" |> parseBitArray) (Feature.VariableLengthOnion) (None)) "" - Expect.isTrue (Feature.hasFeature("0b0000000100000000" |> parseBitArray) (Feature.VariableLengthOnion) (Some(Mandatory))) "" - Expect.isTrue (Feature.hasFeature("0b0000001000000000" |> parseBitArray) (Feature.VariableLengthOnion) (None)) "" - Expect.isTrue (Feature.hasFeature("0b0000001000000000" |> parseBitArray) (Feature.VariableLengthOnion) (Some(Optional))) "" - () - - testProperty "BitArray serialization" <| fun (ba : NonNull) -> - let backAndForth = BitArray.FromBytes(ba.Get).ToByteArray() - let finalArray = Array.zeroCreate ba.Get.Length - Array.Copy(backAndForth, 0, finalArray, ba.Get.Length - backAndForth.Length, backAndForth.Length) - Expect.equal ba.Get finalArray "BitArray.ToByteArray does not invert and trim BitArray.FromBytes" - - testCase "FeatureBits to/from byte array preserves byte order, bit order and trims zero bytes" <| fun _ -> - let featuresOpt = - FeatureBits.TryCreate [| 0b00000000uy; 0b00100000uy; 0b10000010uy |] - - match featuresOpt with - | Error _err -> failwith "Should have been able to create features" - | Ok features -> - Expect.equal - features.ByteArray - [| 0b00100000uy; 0b10000010uy |] - "unexpected ByteArray value" - - testCase "features dependencies" <| fun _ -> - let testCases = - Map.empty - |> Map.add "" true - |> Map.add "00000000" true - |> Map.add "01011000" true - // gossip_queries_ex depend on gossip_queries - |> Map.add "0b000000000000010000000000" false - |> Map.add "0b000000000000100000000000" false - - |> Map.add "0b000000100100000100000000" true - |> Map.add "0b000000000000100010000000" true - // payment_secret depends on var_onion_optin - |> Map.add "0b000000000100000000000000" false - // event the feature is set by odd bit(optional), then deps must set flags (either optional/mandatory) - |> Map.add "0b000000001000000000000000" false - - |> Map.add "0b000000000100001000000000" true - // basic_mpp depends on payment_secret - |> Map.add "0b000000100000000000000000" false - |> Map.add "0b000000010000000000000000" false - - |> Map.add "0b000000101000000000000000" false - |> Map.add "0b000000011000000000000000" false - |> Map.add "0b000000011000001000000000" true - |> Map.add "0b000000100100000100000000" true - - testCases - |> Map.iter(fun testCase valid -> - let ba = testCase |> parseBitArray - let result = Feature.validateFeatureGraph (ba) - if valid then - Expect.isOk (Result.ToFSharpCoreResult result) (testCase) - else - Expect.isError (Result.ToFSharpCoreResult result) (testCase) - ) - - testCase "features compatibility (in int64)" <| fun _ -> - let testCases = - [ - 1L <<< Feature.OptionDataLossProtect.MandatoryBitPosition, true - 1L <<< Feature.OptionDataLossProtect.OptionalBitPosition, true - - 1L <<< Feature.InitialRoutingSync.OptionalBitPosition, true - - 1L <<< Feature.OptionUpfrontShutdownScript.MandatoryBitPosition, true - 1L <<< Feature.OptionUpfrontShutdownScript.OptionalBitPosition, true - - 1L <<< Feature.ChannelRangeQueries.MandatoryBitPosition, true - 1L <<< Feature.ChannelRangeQueries.OptionalBitPosition, true - - 1L <<< Feature.VariableLengthOnion.MandatoryBitPosition, true - 1L <<< Feature.VariableLengthOnion.OptionalBitPosition, true - - 1L <<< Feature.ChannelRangeQueriesExtended.MandatoryBitPosition, false - 1L <<< Feature.ChannelRangeQueriesExtended.OptionalBitPosition, true - - 1L <<< Feature.OptionStaticRemoteKey.MandatoryBitPosition, true - 1L <<< Feature.OptionStaticRemoteKey.OptionalBitPosition, true - - 1L <<< Feature.PaymentSecret.MandatoryBitPosition, true - 1L <<< Feature.PaymentSecret.OptionalBitPosition, true - - 1L <<< Feature.BasicMultiPartPayment.MandatoryBitPosition, false - 1L <<< Feature.BasicMultiPartPayment.OptionalBitPosition, true - - 1L <<< Feature.OptionSupportLargeChannel.MandatoryBitPosition, true - 1L <<< Feature.OptionSupportLargeChannel.OptionalBitPosition, true - ] - for (s, expected) in testCases do - let ba = BitArray.FromInt64(s) - Expect.equal(Feature.areSupported(ba)) expected (sprintf "%s" (ba.PrintBits())) - - testCase "features compatibility (in parsed string)" <| fun _ -> - let testCases = - Map.empty - |> Map.add " 00000000000000001011" true - // option_upfront_shutdown_script - |> Map.add " 00000000000000010000" true - // gossip_queries (mandatory), gossip_queries_ex (optional) - |> Map.add " 00000000100001000000" true - // gossip_queries_ex (mandatory) - |> Map.add " 00000000010001000000" false - // option_static_remote_key - |> Map.add " 00000001000000000000" true - // initial_routing_sync, payment_secret(mandatory) - |> Map.add " 00000100000000001000" true - // var_onion_secret(optional) payment_secret(optional) - |> Map.add " 00001000001000000000" true - // unknown optional feature bits - |> Map.add " 10000000000000000000" true - |> Map.add " 001000000000000000000000" true - // support_large_channel_option(mandatory) - |> Map.add " 000001000000000000000000" true - // those are useful for nonreg testing of the areSupported method (which needs to be updated with every new supported mandatory bit) - |> Map.add " 000100000000000000000000" false - |> Map.add " 010000000000000000000000" false - |> Map.add " 0001000000000000000000000000" false - |> Map.add " 0100000000000000000000000000" false - |> Map.add "00010000000000000000000000000000" false - |> Map.add "01000000000000000000000000000000" false - testCases - |> Map.iter(fun testCase expected -> - let fb = testCase |> parseBitArray - Expect.equal (Feature.areSupported(fb)) expected (sprintf "%A" (fb.PrintBits())) - ) - ] +module Serialization + +open DotNetLightning.Utils +open DotNetLightning.Core.Utils.Extensions +open DotNetLightning.Serialization.Msgs +open DotNetLightning.Crypto + +open DotNetLightning.Serialization +open Expecto +open NBitcoin +open System +open System.Collections +open FsCheck + +open ResultUtils +open ResultUtils.Portability + +module SerializationTest = + + open Utils + + /// helper for more clean error message + + let parseBitArray(str: string) = + match BitArray.TryParse str with + | Ok ba -> ba + | Error e -> failwith e + + let hex = NBitcoin.DataEncoders.HexEncoder() + let base64 = NBitcoin.DataEncoders.Base64Encoder() + let ascii = System.Text.ASCIIEncoding.ASCII + + let signMessageWith (privKey: Key) (msgHash: string) = + let msgBytes = msgHash |> ascii.GetBytes + + privKey.SignCompact(msgBytes |> uint256, false) + |> fun d -> LNECDSASignature.FromBytesCompact(d, true) + + let privKey1 = + new Key( + hex.DecodeData( + "0101010101010101010101010101010101010101010101010101010101010101" + ) + ) + + let privKey2 = + new Key( + hex.DecodeData( + "0202020202020202020202020202020202020202020202020202020202020202" + ) + ) + + let privKey3 = + new Key( + hex.DecodeData( + "0303030303030303030303030303030303030303030303030303030303030303" + ) + ) + + let privKey4 = + new Key( + hex.DecodeData( + "0404040404040404040404040404040404040404040404040404040404040404" + ) + ) + + let privKey5 = + new Key( + hex.DecodeData( + "0505050505050505050505050505050505050505050505050505050505050505" + ) + ) + + let privKey6 = + new Key( + hex.DecodeData( + "0606060606060606060606060606060606060606060606060606060606060606" + ) + ) + + let pubkey1 = privKey1.PubKey + let pubkey2 = privKey2.PubKey + let pubkey3 = privKey3.PubKey + let pubkey4 = privKey4.PubKey + let pubkey5 = privKey5.PubKey + let pubkey6 = privKey6.PubKey + + [] + let tests = + testList + "Serialization unit tests" + [ + testCase "node_announcement" + <| fun _ -> + let sig1 = + signMessageWith + privKey1 + "01010101010101010101010101010101" + + let msg = + { + NodeAnnouncementMsg.Signature = sig1 + Contents = + { + UnsignedNodeAnnouncementMsg.NodeId = + NodeId( + PubKey( + "03f3c15dbc4d425a4f4c36162a9159bb83511fa920dba1cc2785c434ecaf094015" + ) + ) + Features = + FeatureBits.CreateUnsafe [| 0uy |] + Timestamp = 1u + RGB = + { + Red = 217uy + Green = 228uy + Blue = 166uy + } + Alias = uint256.Zero + Addresses = + [| + IPv4( + { + Addr = + [| + 18uy + 94uy + 0uy + 118uy + |] + Port = 7us + } + ) + |] + ExcessAddressData = + [| 5uy; 121uy; 62uy; 96uy; 44uy; 34uy |] + ExcessData = [||] + } + } + + Expect.equal (msg.Clone()) msg "" + testCase "short channel id test" + <| fun _ -> + let shortChannelId = + (ShortChannelId.FromUInt64(140737488420865UL)) + + Expect.equal (shortChannelId.BlockHeight.Value) 128u "" + Expect.equal (shortChannelId.BlockIndex.Value) 1u "" + Expect.equal (shortChannelId.TxOutIndex.Value) 1us "" + () + ] + + [] + let testsRustLightningSerialization = + testList + "SerializationTest ported from rust-lightning" + [ + testCase "channel_reestablish no secret" + <| fun _ -> + let cid = + ChannelId( + uint256( + [| + 4 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 5 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 6 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 7 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + |] + |> Array.map((uint8)) + ) + ) + + let channelReestablishMsg = + { + ChannelId = cid + NextCommitmentNumber = + CommitmentNumber + <| (UInt48.MaxValue - UInt48.FromUInt64 3UL) + NextRevocationNumber = + CommitmentNumber + <| (UInt48.MaxValue - UInt48.FromUInt64 4UL) + DataLossProtect = None + } + + let actual = channelReestablishMsg.ToBytes() + + let expected = + [| + 4 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 5 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 6 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 7 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 3 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 4 + |] + |> Array.map(uint8) + + Expect.equal + actual + expected + "channel_reestablish_no_secret failed" + + testCase "channel_reestablish with secret" + <| fun _ -> + let channelReestablishMsg = + { + ChannelId = + ChannelId( + uint256( + [| + 4 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 5 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 6 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 7 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + |] + |> Array.map(uint8) + ) + ) + NextCommitmentNumber = + CommitmentNumber + <| (UInt48.MaxValue - UInt48.FromUInt64 3UL) + NextRevocationNumber = + CommitmentNumber + <| (UInt48.MaxValue - UInt48.FromUInt64 4UL) + DataLossProtect = + OptionalField.Some + <| { + YourLastPerCommitmentSecret = + Some + <| PerCommitmentSecret.FromBytes + [| + for _ in + 0 .. (PerCommitmentSecret.BytesLength + - 1) -> 9uy + |] + MyCurrentPerCommitmentPoint = + PerCommitmentPoint pubkey1 + } + } + + let expected = + [| + 4 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 5 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 6 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 7 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 3 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 4 + 9 + 9 + 9 + 9 + 9 + 9 + 9 + 9 + 9 + 9 + 9 + 9 + 9 + 9 + 9 + 9 + 9 + 9 + 9 + 9 + 9 + 9 + 9 + 9 + 9 + 9 + 9 + 9 + 9 + 9 + 9 + 9 + 3 + 27 + 132 + 197 + 86 + 123 + 18 + 100 + 64 + 153 + 93 + 62 + 213 + 170 + 186 + 5 + 101 + 215 + 30 + 24 + 52 + 96 + 72 + 25 + 255 + 156 + 23 + 245 + 233 + 213 + 221 + 7 + 143 + |] + |> Array.map(byte) + + Expect.equal (channelReestablishMsg.ToBytes()) expected "" + testCase "short_channel_id" + <| fun _ -> + let actual = ShortChannelId.FromUInt64(2316138423780173UL) + + let expected = + [| + 0uy + 8uy + 58uy + 132uy + 0uy + 0uy + 3uy + 77uy + |] + + Expect.equal (actual.ToBytes()) expected "" + + testCase "announcement_signatures" + <| fun _ -> + let sig1 = + signMessageWith + privKey1 + "01010101010101010101010101010101" + + let sig2 = + signMessageWith + privKey1 + "02020202020202020202020202020202" + + let actual = + { + ChannelId = + ChannelId( + uint256( + [| + 4 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 5 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 6 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 7 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + |] + |> Array.map(uint8) + ) + ) + ShortChannelId = + ShortChannelId.FromUInt64(2316138423780173UL) + NodeSignature = sig1 + BitcoinSignature = sig2 + } + + let expected = + hex.DecodeData( + "040000000000000005000000000000000600000000000000070000000000000000083a840000034dd977cb9b53d93a6ff64bb5f1e158b4094b66e798fb12911168a3ccdf80a83096340a6a95da0ae8d9f776528eecdbb747eb6b545495a4319ed5378e35b21e073acf9953cef4700860f5967838eba2bae89288ad188ebf8b20bf995c3ea53a26df1876d0a3a0e13172ba286a673140190c02ba9da60a2e43a745188c8a83c7f3ef" + ) + + Expect.equal (actual.ToBytes().Length) expected.Length "" + Expect.equal (actual.ToBytes()) (expected) "" + CheckArrayEqual (actual.ToBytes()) expected + + testCase "channel_announcement" + <| fun _ -> + let channelAnnouncementTestCore + ( + nonbitcoinChainHash: bool, + excessData: bool + ) = + let sig1 = + signMessageWith + privKey1 + "01010101010101010101010101010101" + + let sig2 = + signMessageWith + privKey2 + "01010101010101010101010101010101" + + let sig3 = + signMessageWith + privKey3 + "01010101010101010101010101010101" + + let sig4 = + signMessageWith + privKey4 + "01010101010101010101010101010101" + + let mutable features = FeatureBits.CreateUnsafe [||] + + let unsignedChannelAnnoucement = + { + Features = features + ChainHash = + if (not nonbitcoinChainHash) then + uint256( + hex.DecodeData( + "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f" + ) + ) + else + uint256( + hex.DecodeData( + "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943" + ) + ) + ShortChannelId = + ShortChannelId.FromUInt64( + 2316138423780173UL + ) + NodeId1 = NodeId(privKey1.PubKey) + NodeId2 = NodeId(privKey2.PubKey) + BitcoinKey1 = !>privKey3.PubKey + BitcoinKey2 = !>privKey4.PubKey + ExcessData = + if excessData then + ([| 10; 0; 0; 20; 0; 0; 30; 0; 0; 40 |] + |> Array.map(byte)) + else + [||] + } + + let channelAnnouncementMsg = + { + NodeSignature1 = sig1 + NodeSignature2 = sig2 + BitcoinSignature1 = sig3 + BitcoinSignature2 = sig4 + Contents = unsignedChannelAnnoucement + } + + let mutable expected = + hex.DecodeData( + "d977cb9b53d93a6ff64bb5f1e158b4094b66e798fb12911168a3ccdf80a83096340a6a95da0ae8d9f776528eecdbb747eb6b545495a4319ed5378e35b21e073a1735b6a427e80d5fe7cd90a2f4ee08dc9c27cda7c35a4172e5d85b12c49d4232537e98f9b1f3c5e6989a8b9644e90e8918127680dbd0d4043510840fc0f1e11a216c280b5395a2546e7e4b2663e04f811622f15a4f91e83aa2e92ba2a573c139142c54ae63072a1ec1ee7dc0c04bde5c847806172aa05c92c22ae8e308d1d2692b12cc195ce0a2d1bda6a88befa19fa07f51caa75ce83837f28965600b8aacab0855ffb0e741ec5f7c41421e9829a9d48611c8c831f71be5ea73e66594977ffd" + ) + + expected <- + Array.append expected (hex.DecodeData("0000")) + + if nonbitcoinChainHash then + expected <- + Array.append + expected + (hex.DecodeData( + "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943" + )) + else + expected <- + Array.append + expected + (hex.DecodeData( + "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f" + )) + + expected <- + Array.append + expected + (hex.DecodeData( + "00083a840000034d031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f024d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d076602531fe6068134503d2723133227c867ac8fa6c83c537e9a44c3c5bdbdcb1fe33703462779ad4aad39514614751a71085f2f10e1c7a593e4e030efb5b8721ce55b0b" + )) + + if excessData then + expected <- + Array.append + expected + (hex.DecodeData("0a00001400001e000028")) + + Expect.equal + (channelAnnouncementMsg.ToBytes().[300..]) + expected.[300..] + "mismatch in postfix" + + Expect.equal + (channelAnnouncementMsg.ToBytes()) + expected + "" + + channelAnnouncementTestCore(false, false) + channelAnnouncementTestCore(false, false) + channelAnnouncementTestCore(true, false) + channelAnnouncementTestCore(true, true) + channelAnnouncementTestCore(true, true) + channelAnnouncementTestCore(false, true) + channelAnnouncementTestCore(true, false) + channelAnnouncementTestCore(false, true) + testCase "node_announcement" + <| fun _ -> + let nodeAnnouncementTestCore + ( + ipv4: bool, + ipv6: bool, + onionv2: bool, + onionv3: bool, + excessAddressData: bool, + excessData: bool + ) = + let sig1 = + signMessageWith + privKey1 + "01010101010101010101010101010101" + + let mutable features = FeatureBits.CreateUnsafe [||] + let mutable addresses = List.Empty + + if ipv4 then + addresses <- + addresses + @ [ + NetAddress.IPv4( + { + IPv4Or6Data.Addr = + [| 255uy; 254uy; 253uy; 252uy |] + Port = 9735us + } + ) + ] + + if ipv6 then + addresses <- + addresses + @ [ + NetAddress.IPv6( + { + IPv4Or6Data.Addr = + [| + 255uy + 254uy + 253uy + 252uy + 251uy + 250uy + 249uy + 248uy + 247uy + 246uy + 245uy + 244uy + 243uy + 242uy + 241uy + 240uy + |] + Port = 9735us + } + ) + ] + + if onionv2 then + addresses <- + addresses + @ [ + NetAddress.OnionV2( + { + Addr = + [| + 255uy + 254uy + 253uy + 252uy + 251uy + 250uy + 249uy + 248uy + 247uy + 246uy + |] + Port = 9735us + } + ) + ] + + if onionv3 then + addresses <- + addresses + @ [ + NetAddress.OnionV3( + { + ed25519PubKey = + [| + 255uy + 254uy + 253uy + 252uy + 251uy + 250uy + 249uy + 248uy + 247uy + 246uy + 245uy + 244uy + 243uy + 242uy + 241uy + 240uy + 239uy + 238uy + 237uy + 236uy + 235uy + 234uy + 233uy + 232uy + 231uy + 230uy + 229uy + 228uy + 227uy + 226uy + 225uy + 224uy + |] + CheckSum = 32us + Version = 16uy + Port = 9735us + } + ) + ] + + let mutable addrLen = 0us + + for addr in addresses do + addrLen <- addrLen + addr.Length + 1us + + let unsignedNodeAnnouncementMsg = + { + Features = features + Timestamp = 20190119u + NodeId = NodeId(pubkey1) + RGB = + { + Blue = 32uy + Red = 32uy + Green = 32uy + } + Alias = uint256([| for _ in 0..31 -> 16uy |]) + Addresses = addresses |> Array.ofList + ExcessAddressData = + if excessAddressData then + [| + 33uy + 108uy + 40uy + 11uy + 83uy + 149uy + 162uy + 84uy + 110uy + 126uy + 75uy + 38uy + 99uy + 224uy + 79uy + 129uy + 22uy + 34uy + 241uy + 90uy + 79uy + 146uy + 232uy + 58uy + 162uy + 233uy + 43uy + 162uy + 165uy + 115uy + 193uy + 57uy + 20uy + 44uy + 84uy + 174uy + 99uy + 7uy + 42uy + 30uy + 193uy + 238uy + 125uy + 192uy + 192uy + 75uy + 222uy + 92uy + 132uy + 120uy + 6uy + 23uy + 42uy + 160uy + 92uy + 146uy + 194uy + 42uy + 232uy + 227uy + 8uy + 209uy + 210uy + 105uy + |] + else + [||] + ExcessData = + if excessData then + [| + 59uy + 18uy + 204uy + 25uy + 92uy + 224uy + 162uy + 209uy + 189uy + 166uy + 168uy + 139uy + 239uy + 161uy + 159uy + 160uy + 127uy + 81uy + 202uy + 167uy + 92uy + 232uy + 56uy + 55uy + 242uy + 137uy + 101uy + 96uy + 11uy + 138uy + 172uy + 171uy + 8uy + 85uy + 255uy + 176uy + 231uy + 65uy + 236uy + 95uy + 124uy + 65uy + 66uy + 30uy + 152uy + 41uy + 169uy + 212uy + 134uy + 17uy + 200uy + 200uy + 49uy + 247uy + 27uy + 229uy + 234uy + 115uy + 230uy + 101uy + 148uy + 151uy + 127uy + 253uy + |] + else + [||] + } + + addrLen <- + addrLen + + uint16 + unsignedNodeAnnouncementMsg.ExcessAddressData.Length + + let nodeAnnouncementMsg = + { + NodeAnnouncementMsg.Signature = sig1 + Contents = unsignedNodeAnnouncementMsg + } + + let actual = nodeAnnouncementMsg.ToBytes() + + let mutable expected = + hex.DecodeData( + "d977cb9b53d93a6ff64bb5f1e158b4094b66e798fb12911168a3ccdf80a83096340a6a95da0ae8d9f776528eecdbb747eb6b545495a4319ed5378e35b21e073a" + ) + + expected <- + Array.append expected (hex.DecodeData("0000")) + + expected <- + Array.append + expected + (hex.DecodeData( + "013413a7031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f2020201010101010101010101010101010101010101010101010101010101010101010" + )) + + expected <- + Array.append + expected + ([| byte(addrLen >>> 8); byte addrLen |]) + + if ipv4 then + expected <- + Array.append + expected + (hex.DecodeData("01fffefdfc2607")) + + if ipv6 then + expected <- + Array.append + expected + (hex.DecodeData( + "02fffefdfcfbfaf9f8f7f6f5f4f3f2f1f02607" + )) + + if onionv2 then + expected <- + Array.append + expected + (hex.DecodeData( + "03fffefdfcfbfaf9f8f7f62607" + )) + + if onionv3 then + expected <- + Array.append + expected + (hex.DecodeData( + "04fffefdfcfbfaf9f8f7f6f5f4f3f2f1f0efeeedecebeae9e8e7e6e5e4e3e2e1e00020102607" + )) + + if excessAddressData then + expected <- + Array.append + expected + (hex.DecodeData( + "216c280b5395a2546e7e4b2663e04f811622f15a4f92e83aa2e92ba2a573c139142c54ae63072a1ec1ee7dc0c04bde5c847806172aa05c92c22ae8e308d1d269" + )) + + if excessData then + expected <- + Array.append + expected + (hex.DecodeData( + "3b12cc195ce0a2d1bda6a88befa19fa07f51caa75ce83837f28965600b8aacab0855ffb0e741ec5f7c41421e9829a9d48611c8c831f71be5ea73e66594977ffd" + )) + // CheckArrayEqual actual expected + Expect.sequenceContainsOrder actual expected "" + Expect.equal actual expected "" + + nodeAnnouncementTestCore(true, true, true, true, true, true) + + nodeAnnouncementTestCore( + false, + false, + false, + false, + false, + false + ) + + nodeAnnouncementTestCore( + true, + false, + false, + false, + false, + false + ) + + nodeAnnouncementTestCore( + false, + true, + false, + false, + false, + false + ) + + nodeAnnouncementTestCore( + false, + false, + true, + false, + false, + false + ) + + nodeAnnouncementTestCore( + false, + false, + false, + true, + false, + false + ) + + nodeAnnouncementTestCore( + false, + false, + false, + false, + true, + false + ) + + nodeAnnouncementTestCore( + true, + false, + true, + false, + true, + false + ) + + nodeAnnouncementTestCore( + false, + true, + false, + true, + false, + false + ) + testCase "channel_update msg" + <| fun _ -> + let channelUpdateTestCore + ( + nonBitcoinChainHash: bool, + direction: bool, + disable: bool, + htlcMaximumMSat: bool + ) = + let sig1 = + signMessageWith + privKey1 + "01010101010101010101010101010101" + + let unsignedChannelUpdateMsg = + { + UnsignedChannelUpdateMsg.ChainHash = + if (not nonBitcoinChainHash) then + uint256( + hex.DecodeData( + "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f" + ) + ) + else + uint256( + hex.DecodeData( + "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943" + ) + ) + ShortChannelId = + ShortChannelId.FromUInt64( + 2316138423780173UL + ) + Timestamp = 20190119u + MessageFlags = + (if htlcMaximumMSat then + 1uy + else + 0uy) + ChannelFlags = + ((if direction && disable then + (2uy) + else if disable then + (2uy) + else if direction then + 1uy + else + 0uy)) + CLTVExpiryDelta = !> 144us + HTLCMinimumMSat = + LNMoney.MilliSatoshis(1000000L) + FeeBaseMSat = LNMoney.MilliSatoshis(10000L) + FeeProportionalMillionths = 20u + HTLCMaximumMSat = + if htlcMaximumMSat then + [| + 0uy + 0uy + 0uy + 0uy + 59uy + 154uy + 202uy + 0uy + |] + |> fun b -> + NBitcoin.Utils.ToUInt64(b, false) + |> LNMoney.MilliSatoshis + |> Some + else + None + } + + let channelUpdateMsg = + { + ChannelUpdateMsg.Signature = sig1 + Contents = unsignedChannelUpdateMsg + } + + let actual = channelUpdateMsg.ToBytes() + + let mutable expected = + hex.DecodeData( + "d977cb9b53d93a6ff64bb5f1e158b4094b66e798fb12911168a3ccdf80a83096340a6a95da0ae8d9f776528eecdbb747eb6b545495a4319ed5378e35b21e073a" + ) + + if nonBitcoinChainHash then + expected <- + Array.append + expected + (hex.DecodeData( + "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943" + )) + else + expected <- + Array.append + expected + (hex.DecodeData( + "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f" + )) + + expected <- + Array.append + expected + (hex.DecodeData("00083a840000034d013413a7")) + + if htlcMaximumMSat then + expected <- + Array.append expected (hex.DecodeData("01")) + else + expected <- + Array.append expected (hex.DecodeData("00")) + + expected <- Array.append expected (hex.DecodeData("00")) + + if direction then + expected.[expected.Length - 1] <- 1uy + + if disable then + expected.[expected.Length - 1] <- + expected.[expected.Length - 1] ||| 1uy <<< 1 + + expected <- + Array.append + expected + (hex.DecodeData( + "009000000000000f42400000271000000014" + )) + + if htlcMaximumMSat then + expected <- + Array.append + expected + (hex.DecodeData("000000003b9aca00")) + + CheckArrayEqual actual expected + + channelUpdateTestCore(false, false, false, false) + channelUpdateTestCore(true, false, false, false) + channelUpdateTestCore(false, true, false, false) + channelUpdateTestCore(false, false, true, false) + channelUpdateTestCore(false, false, false, true) + channelUpdateTestCore(true, true, true, true) + + testCase "open_channel" + <| fun _ -> + let openChannelTestCore + ( + nonBitcoinChainHash: bool, + randomBit: bool, + shutdown: bool + ) = + let openChannelMsg = + { + Chainhash = + if (not nonBitcoinChainHash) then + uint256( + hex.DecodeData( + "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f" + ) + ) + else + uint256( + hex.DecodeData( + "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943" + ) + ) + TemporaryChannelId = + ChannelId( + uint256([| for _ in 0..31 -> 2uy |]) + ) + FundingSatoshis = + Money.Satoshis(1311768467284833366UL) + PushMSat = + LNMoney.MilliSatoshis(2536655962884945560L) + DustLimitSatoshis = + Money.Satoshis(3608586615801332854UL) + MaxHTLCValueInFlightMsat = + LNMoney.MilliSatoshis(8517154655701053848L) + ChannelReserveSatoshis = + Money.Satoshis(8665828695742877976UL) + HTLCMinimumMsat = + LNMoney.MilliSatoshis(2316138423780173UL) + FeeRatePerKw = FeeRatePerKw(821716u) + ToSelfDelay = BlockHeightOffset16(49340us) + MaxAcceptedHTLCs = 49340us + FundingPubKey = FundingPubKey pubkey1 + RevocationBasepoint = + RevocationBasepoint pubkey2 + PaymentBasepoint = PaymentBasepoint pubkey3 + DelayedPaymentBasepoint = + DelayedPaymentBasepoint pubkey4 + HTLCBasepoint = HtlcBasepoint pubkey5 + FirstPerCommitmentPoint = + PerCommitmentPoint pubkey6 + ChannelFlags = + { + AnnounceChannel = randomBit + } + TLVs = + [| + OpenChannelTLV.UpfrontShutdownScript( + if shutdown then + Some + <| ShutdownScriptPubKey.FromPubKeyP2pkh + pubkey1 + else + None + ) + |] + } + + let actual = openChannelMsg.ToBytes() + let mutable expected = [||] + + if nonBitcoinChainHash then + expected <- + Array.append + expected + (hex.DecodeData( + "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943" + )) + else + expected <- + Array.append + expected + (hex.DecodeData( + "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f" + )) + + expected <- + Array.append + expected + (hex.DecodeData( + "02020202020202020202020202020202020202020202020202020202020202021234567890123456233403289122369832144668701144767633030896203198784335490624111800083a840000034d000c89d4c0bcc0bc031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f024d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d076602531fe6068134503d2723133227c867ac8fa6c83c537e9a44c3c5bdbdcb1fe33703462779ad4aad39514614751a71085f2f10e1c7a593e4e030efb5b8721ce55b0b0362c0a046dacce86ddd0343c6d3c7c79c2208ba0d9c9cf24a6d046d21d21f90f703f006a18d5653c4edf5391ff23a61f03ff83d237e880ee61187fa9f379a028e0a" + )) + + if randomBit then + expected <- + Array.append expected (hex.DecodeData("01")) + else + expected <- + Array.append expected (hex.DecodeData("00")) + + if shutdown then + expected <- + Array.append + expected + (hex.DecodeData( + "001976a91479b000887626b294a914501a4cd226b58b23598388ac" + )) + else + expected <- + Array.append expected (hex.DecodeData("0000")) + + CheckArrayEqual actual expected + Expect.equal (openChannelMsg.Clone()) openChannelMsg "" + + openChannelTestCore(false, false, false) + openChannelTestCore(true, false, false) + openChannelTestCore(false, true, false) + openChannelTestCore(false, false, true) + openChannelTestCore(true, true, true) + testCase "accept_channel" + <| fun _ -> + let acceptChannelTestCore(shutdown: bool) = + let acceptChannelMsg = + { + AcceptChannelMsg.TemporaryChannelId = + ChannelId( + uint256([| for _ in 0..31 -> 2uy |]) + ) + DustLimitSatoshis = + Money.Satoshis(1311768467284833366L) + MaxHTLCValueInFlightMsat = + LNMoney.MilliSatoshis(2536655962884945560L) + ChannelReserveSatoshis = + Money.Satoshis(3608586615801332854L) + HTLCMinimumMSat = + LNMoney.MilliSatoshis(2316138423780173L) + MinimumDepth = 821716u |> BlockHeightOffset32 + ToSelfDelay = BlockHeightOffset16(49340us) + MaxAcceptedHTLCs = 49340us + FundingPubKey = FundingPubKey pubkey1 + RevocationBasepoint = + RevocationBasepoint pubkey2 + PaymentBasepoint = PaymentBasepoint pubkey3 + DelayedPaymentBasepoint = + DelayedPaymentBasepoint pubkey4 + HTLCBasepoint = HtlcBasepoint pubkey5 + FirstPerCommitmentPoint = + PerCommitmentPoint pubkey6 + TLVs = + [| + AcceptChannelTLV.UpfrontShutdownScript( + if shutdown then + Some + <| ShutdownScriptPubKey.FromPubKeyP2pkh + pubkey1 + else + None + ) + |] + } + + let actual = acceptChannelMsg.ToBytes() + + let mutable expected = + hex.DecodeData( + "020202020202020202020202020202020202020202020202020202020202020212345678901234562334032891223698321446687011447600083a840000034d000c89d4c0bcc0bc031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f024d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d076602531fe6068134503d2723133227c867ac8fa6c83c537e9a44c3c5bdbdcb1fe33703462779ad4aad39514614751a71085f2f10e1c7a593e4e030efb5b8721ce55b0b0362c0a046dacce86ddd0343c6d3c7c79c2208ba0d9c9cf24a6d046d21d21f90f703f006a18d5653c4edf5391ff23a61f03ff83d237e880ee61187fa9f379a028e0a" + ) + + if shutdown then + expected <- + Array.append + expected + (hex.DecodeData( + "001976a91479b000887626b294a914501a4cd226b58b23598388ac" + )) + else + expected <- + Array.append expected (hex.DecodeData("0000")) + + CheckArrayEqual actual expected + + acceptChannelTestCore(false) + acceptChannelTestCore(true) + testCase "funding_created" + <| fun _ -> + let sig1 = + signMessageWith + privKey1 + "01010101010101010101010101010101" + + let txData = + hex.DecodeData( + "c2d4449afa8d26140898dd54d3390b057ba2a5afcf03ba29d7dc0d8b9ffe966e" + ) + + Array.Reverse txData + + let fundingCreatedMsg = + { + FundingCreatedMsg.TemporaryChannelId = + ChannelId(uint256 [| for _ in 0..31 -> 2uy |]) + FundingTxId = TxId(uint256(txData, true)) + FundingOutputIndex = 255us |> TxOutIndex + Signature = sig1 + } + + let actual = fundingCreatedMsg.ToBytes() + + let expected = + hex.DecodeData( + "02020202020202020202020202020202020202020202020202020202020202026e96fe9f8b0ddcd729ba03cfafa5a27b050b39d354dd980814268dfa9a44d4c200ffd977cb9b53d93a6ff64bb5f1e158b4094b66e798fb12911168a3ccdf80a83096340a6a95da0ae8d9f776528eecdbb747eb6b545495a4319ed5378e35b21e073a" + ) + // Expect.equal (actual) expected "" + CheckArrayEqual actual expected + testCase "funding_signed" + <| fun _ -> + let sig1 = + signMessageWith + privKey1 + "01010101010101010101010101010101" + + let fundingSignedMsg = + { + FundingSignedMsg.ChannelId = + ChannelId(uint256 [| for _ in 0..31 -> 2uy |]) + Signature = sig1 + } + + let expected = + hex.DecodeData( + "0202020202020202020202020202020202020202020202020202020202020202d977cb9b53d93a6ff64bb5f1e158b4094b66e798fb12911168a3ccdf80a83096340a6a95da0ae8d9f776528eecdbb747eb6b545495a4319ed5378e35b21e073a" + ) + + CheckArrayEqual (fundingSignedMsg.ToBytes()) expected + () + testCase "funding_locked" + <| fun _ -> + let fundingLockedMsg = + { + FundingLockedMsg.ChannelId = + ChannelId(uint256 [| for _ in 0..31 -> 2uy |]) + NextPerCommitmentPoint = PerCommitmentPoint pubkey1 + } + + let expected = + hex.DecodeData( + "0202020202020202020202020202020202020202020202020202020202020202031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f" + ) + + CheckArrayEqual (fundingLockedMsg.ToBytes()) expected + testCase "shutdown" + <| fun _ -> + let shutDownTestCore(scriptType: uint8) = + let script = Script("OP_TRUE") + + let spk = + if (scriptType = 1uy) then + ShutdownScriptPubKey.FromPubKeyP2pkh pubkey1 + else if (scriptType = 2uy) then + ShutdownScriptPubKey.FromScriptP2sh script + else if (scriptType = 3uy) then + ShutdownScriptPubKey.FromPubKeyP2wpkh pubkey1 + else + ShutdownScriptPubKey.FromScriptP2wsh script + + let shutdownMsg = + { + ShutdownMsg.ChannelId = + ChannelId( + uint256 [| for _ in 0..31 -> 2uy |] + ) + ScriptPubKey = spk + } + + let mutable expected = + hex.DecodeData( + "0202020202020202020202020202020202020202020202020202020202020202" + ) + + expected <- + Array.append + expected + (if (scriptType = 1uy) then + hex.DecodeData( + "001976a91479b000887626b294a914501a4cd226b58b23598388ac" + ) + else if (scriptType = 2uy) then + hex.DecodeData( + "0017a914da1745e9b549bd0bfa1a569971c77eba30cd5a4b87" + ) + else if (scriptType = 3uy) then + hex.DecodeData( + "0016001479b000887626b294a914501a4cd226b58b235983" + ) + else + hex.DecodeData( + "002200204ae81572f06e1b88fd5ced7a1a000945432e83e1551e6f721ee9c00b8cc33260" + )) + + CheckArrayEqual (shutdownMsg.ToBytes()) expected + + shutDownTestCore(1uy) + shutDownTestCore(2uy) + shutDownTestCore(3uy) + shutDownTestCore(4uy) + testCase "update_add_htlc" + <| fun _ -> + let onionRoutingPacket = + { + Version = 255uy + PublicKey = pubkey1.ToBytes() + HopData = [| for _ in 1 .. (20 * 65) -> 1uy |] + HMAC = uint256([| for _ in 0..31 -> 2uy |]) + } + + let updateAddHtlcMsg = + { + UpdateAddHTLCMsg.ChannelId = + ChannelId(uint256([| for _ in 0..31 -> 2uy |])) + HTLCId = HTLCId(2316138423780173UL) + Amount = LNMoney.MilliSatoshis 3608586615801332854L + PaymentHash = + PaymentHash(uint256 [| for _ in 0..31 -> 1uy |]) + CLTVExpiry = 821716u |> BlockHeight + OnionRoutingPacket = onionRoutingPacket + } + + let expected = + hex.DecodeData( + "020202020202020202020202020202020202020202020202020202020202020200083a840000034d32144668701144760101010101010101010101010101010101010101010101010101010101010101000c89d4ff031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010202020202020202020202020202020202020202020202020202020202020202" + ) + + CheckArrayEqual (updateAddHtlcMsg.ToBytes()) expected + + testCase "update_fulfill_htlc" + <| fun _ -> + let updateFulfillHTLCMsg = + { + ChannelId = + ChannelId(uint256([| for _ in 0..31 -> 2uy |])) + HTLCId = HTLCId(2316138423780173UL) + PaymentPreimage = + PaymentPreimage.Create( + [| + for _ in + 0 .. (PaymentPreimage.LENGTH - 1) -> + 1uy + |] + ) + } + + let expected = + hex.DecodeData( + "020202020202020202020202020202020202020202020202020202020202020200083a840000034d0101010101010101010101010101010101010101010101010101010101010101" + ) + + CheckArrayEqual (updateFulfillHTLCMsg.ToBytes()) expected + testCase "update_fail_htlc" + <| fun _ -> + let reason = + { + Data = [| for _ in 0..31 -> 1uy |] + } + + let updateFailHTLCMsg = + { + ChannelId = + ChannelId(uint256([| for _ in 0..31 -> 2uy |])) + HTLCId = HTLCId(2316138423780173UL) + Reason = reason + } + + let expected = + hex.DecodeData( + "020202020202020202020202020202020202020202020202020202020202020200083a840000034d00200101010101010101010101010101010101010101010101010101010101010101" + ) + + CheckArrayEqual (updateFailHTLCMsg.ToBytes()) expected + testCase "update_fail_malformed_htlc" + <| fun _ -> + let updateFailMalformedHTLCMsg = + { + ChannelId = + ChannelId(uint256([| for _ in 0..31 -> 2uy |])) + HTLCId = HTLCId(2316138423780173UL) + Sha256OfOnion = uint256([| for _ in 0..31 -> 1uy |]) + FailureCode = OnionError.FailureCode(255us) + } + + let expected = + hex.DecodeData( + "020202020202020202020202020202020202020202020202020202020202020200083a840000034d010101010101010101010101010101010101010101010101010101010101010100ff" + ) + + Expect.equal + (updateFailMalformedHTLCMsg.ToBytes()) + expected + "" + testCase "commitment_signed" + <| fun _ -> + let testCommitmentSignedCore(htlcs: bool) = + let sig1 = + signMessageWith + privKey1 + "01010101010101010101010101010101" + + let sig2 = + signMessageWith + privKey2 + "01010101010101010101010101010101" + + let sig3 = + signMessageWith + privKey3 + "01010101010101010101010101010101" + + let sig4 = + signMessageWith + privKey4 + "01010101010101010101010101010101" + + let commitmentSignedMsg = + { + ChannelId = + ChannelId( + uint256([| for _ in 0..31 -> 2uy |]) + ) + Signature = sig1 + HTLCSignatures = + if htlcs then + [ sig2; sig3; sig4 ] + else + [] + } + + let mutable expected = + hex.DecodeData( + "0202020202020202020202020202020202020202020202020202020202020202d977cb9b53d93a6ff64bb5f1e158b4094b66e798fb12911168a3ccdf80a83096340a6a95da0ae8d9f776528eecdbb747eb6b545495a4319ed5378e35b21e073a" + ) + + if htlcs then + expected <- + Array.append + expected + (hex.DecodeData( + "00031735b6a427e80d5fe7cd90a2f4ee08dc9c27cda7c35a4172e5d85b12c49d4232537e98f9b1f3c5e6989a8b9644e90e8918127680dbd0d4043510840fc0f1e11a216c280b5395a2546e7e4b2663e04f811622f15a4f91e83aa2e92ba2a573c139142c54ae63072a1ec1ee7dc0c04bde5c847806172aa05c92c22ae8e308d1d2692b12cc195ce0a2d1bda6a88befa19fa07f51caa75ce83837f28965600b8aacab0855ffb0e741ec5f7c41421e9829a9d48611c8c831f71be5ea73e66594977ffd" + )) + else + expected <- + Array.append expected (hex.DecodeData("0000")) + + CheckArrayEqual (commitmentSignedMsg.ToBytes()) expected + + testCommitmentSignedCore true + testCommitmentSignedCore false + testCase "revoke_and_ack" + <| fun _ -> + let revokeAndACKMsg = + { + ChannelId = + ChannelId(uint256([| for _ in 0..31 -> 2uy |])) + PerCommitmentSecret = + PerCommitmentSecret.FromBytes + [| + for _ in + 0 .. (PaymentPreimage.LENGTH - 1) -> + 1uy + |] + NextPerCommitmentPoint = PerCommitmentPoint pubkey1 + } + + let expected = + hex.DecodeData( + "02020202020202020202020202020202020202020202020202020202020202020101010101010101010101010101010101010101010101010101010101010101031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f" + ) + + CheckArrayEqual (revokeAndACKMsg.ToBytes()) expected + testCase "update_fee" + <| fun _ -> + let updateFeeMsg = + { + ChannelId = + ChannelId(uint256([| for _ in 0..31 -> 2uy |])) + FeeRatePerKw = FeeRatePerKw(20190119u) + } + + let expected = + hex.DecodeData( + "0202020202020202020202020202020202020202020202020202020202020202013413a7" + ) + // not using CheckArrayEqual since it is smaller than 50 + Expect.equal (updateFeeMsg.ToBytes()) expected "" + + testCase "init" + <| fun _ -> + let initTestCore(initialRoutingSync: bool) = + let flags = [||] + let globalFeatures = flags |> BitArray.FromBytes + + let localFeatures = + if initialRoutingSync then + "0b1000" |> BitArray.TryParse + else + BitArray.TryParse("") + |> function + | Ok ba -> ba + | Error e -> failwith e + + let initMsg = + { + Features = + [| globalFeatures; localFeatures |] + |> BitArray.Concat + |> FeatureBits.CreateUnsafe + TLVStream = [||] + } + + let mutable expected = [||] + + expected <- + Array.append expected (hex.DecodeData("0000")) + + if initialRoutingSync then + expected <- + Array.append expected (hex.DecodeData("000108")) + else + expected <- + Array.append expected (hex.DecodeData("0000")) + + Expect.equal (initMsg.ToBytes()) (expected) "" + + initTestCore(false) + initTestCore(true) + + testCase "error" + <| fun _ -> + let errorMsg = + { + ChannelId = + WhichChannel.SpecificChannel( + ChannelId( + uint256([| for _ in 0..31 -> 2uy |]) + ) + ) + Data = ascii.GetBytes("rust-lightning") + } + + let expected = + hex.DecodeData( + "0202020202020202020202020202020202020202020202020202020202020202000e727573742d6c696768746e696e67" + ) + + Expect.equal (errorMsg.ToBytes()) (expected) "" + + testCase "ping" + <| fun _ -> + let pingMsg = + { + PongLen = 64us + BytesLen = 64us + } + + let expected = + hex.DecodeData( + "0040004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + ) + + Expect.equal (pingMsg.ToBytes()) (expected) "" + testCase "pong" + <| fun _ -> + let pongMsg = + { + BytesLen = 64us + } + + let expected = + hex.DecodeData( + "004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + ) + + Expect.equal (pongMsg.ToBytes()) (expected) "" + + ] + + [] + let testFeaturesSerialization = + testList + "Features serialization" + [ + testCase "initial_routing_sync" + <| fun _ -> + Expect.isTrue + (Feature.hasFeature + (parseBitArray "0b00001000") + (Feature.InitialRoutingSync) + (Some Optional)) + "" + + Expect.isFalse + (Feature.hasFeature + (parseBitArray "0b00001000") + (Feature.InitialRoutingSync) + (Some Mandatory)) + "" + + testCase "data_loss_protect" + <| fun _ -> + Expect.isTrue + (Feature.hasFeature + (parseBitArray "0b00000001") + (Feature.OptionDataLossProtect) + (Some Mandatory)) + "" + + Expect.isTrue + (Feature.hasFeature + (parseBitArray "0b00000010") + (Feature.OptionDataLossProtect) + (Some Optional)) + "" + + testCase + "initial_routing_sync, data_loss_protect and option_upfront_shutdown_script features" + <| fun _ -> + let features = parseBitArray("0000000000101010") + Expect.isTrue (Feature.areSupported(features)) "" + + Expect.isTrue + (Feature.hasFeature + (features) + (Feature.InitialRoutingSync) + (None)) + "" + + Expect.isTrue + (Feature.hasFeature + (features) + (Feature.OptionDataLossProtect) + (None)) + "" + + Expect.isTrue + (Feature.hasFeature + (features) + (Feature.OptionUpfrontShutdownScript) + (None)) + "" + + testCase "variable_length_onion feature" + <| fun _ -> + Expect.isTrue + (Feature.hasFeature + ("0b0000000100000000" |> parseBitArray) + (Feature.VariableLengthOnion) + (None)) + "" + + Expect.isTrue + (Feature.hasFeature + ("0b0000000100000000" |> parseBitArray) + (Feature.VariableLengthOnion) + (Some(Mandatory))) + "" + + Expect.isTrue + (Feature.hasFeature + ("0b0000001000000000" |> parseBitArray) + (Feature.VariableLengthOnion) + (None)) + "" + + Expect.isTrue + (Feature.hasFeature + ("0b0000001000000000" |> parseBitArray) + (Feature.VariableLengthOnion) + (Some(Optional))) + "" + + () + + testProperty "BitArray serialization" + <| fun (ba: NonNull>) -> + let backAndForth = BitArray.FromBytes(ba.Get).ToByteArray() + let finalArray = Array.zeroCreate ba.Get.Length + + Array.Copy( + backAndForth, + 0, + finalArray, + ba.Get.Length - backAndForth.Length, + backAndForth.Length + ) + + Expect.equal + ba.Get + finalArray + "BitArray.ToByteArray does not invert and trim BitArray.FromBytes" + + testCase + "FeatureBits to/from byte array preserves byte order, bit order and trims zero bytes" + <| fun _ -> + let featuresOpt = + FeatureBits.TryCreate + [| + 0b00000000uy + 0b00100000uy + 0b10000010uy + |] + + match featuresOpt with + | Error _err -> + failwith "Should have been able to create features" + | Ok features -> + Expect.equal + features.ByteArray + [| 0b00100000uy; 0b10000010uy |] + "unexpected ByteArray value" + + testCase "features dependencies" + <| fun _ -> + let testCases = + Map.empty + |> Map.add "" true + |> Map.add "00000000" true + |> Map.add "01011000" true + // gossip_queries_ex depend on gossip_queries + |> Map.add "0b000000000000010000000000" false + |> Map.add "0b000000000000100000000000" false + + |> Map.add "0b000000100100000100000000" true + |> Map.add "0b000000000000100010000000" true + // payment_secret depends on var_onion_optin + |> Map.add "0b000000000100000000000000" false + // event the feature is set by odd bit(optional), then deps must set flags (either optional/mandatory) + |> Map.add "0b000000001000000000000000" false + + |> Map.add "0b000000000100001000000000" true + // basic_mpp depends on payment_secret + |> Map.add "0b000000100000000000000000" false + |> Map.add "0b000000010000000000000000" false + + |> Map.add "0b000000101000000000000000" false + |> Map.add "0b000000011000000000000000" false + |> Map.add "0b000000011000001000000000" true + |> Map.add "0b000000100100000100000000" true + + testCases + |> Map.iter(fun testCase valid -> + let ba = testCase |> parseBitArray + let result = Feature.validateFeatureGraph(ba) + + if valid then + Expect.isOk + (Result.ToFSharpCoreResult result) + (testCase) + else + Expect.isError + (Result.ToFSharpCoreResult result) + (testCase) + ) + + testCase "features compatibility (in int64)" + <| fun _ -> + let testCases = + [ + 1L + <<< Feature.OptionDataLossProtect.MandatoryBitPosition, + true + 1L + <<< Feature.OptionDataLossProtect.OptionalBitPosition, + true + + 1L + <<< Feature.InitialRoutingSync.OptionalBitPosition, + true + + 1L + <<< Feature.OptionUpfrontShutdownScript.MandatoryBitPosition, + true + 1L + <<< Feature.OptionUpfrontShutdownScript.OptionalBitPosition, + true + + 1L + <<< Feature.ChannelRangeQueries.MandatoryBitPosition, + true + 1L + <<< Feature.ChannelRangeQueries.OptionalBitPosition, + true + + 1L + <<< Feature.VariableLengthOnion.MandatoryBitPosition, + true + 1L + <<< Feature.VariableLengthOnion.OptionalBitPosition, + true + + 1L + <<< Feature.ChannelRangeQueriesExtended.MandatoryBitPosition, + false + 1L + <<< Feature.ChannelRangeQueriesExtended.OptionalBitPosition, + true + + 1L + <<< Feature.OptionStaticRemoteKey.MandatoryBitPosition, + true + 1L + <<< Feature.OptionStaticRemoteKey.OptionalBitPosition, + true + + 1L <<< Feature.PaymentSecret.MandatoryBitPosition, + true + 1L <<< Feature.PaymentSecret.OptionalBitPosition, + true + + 1L + <<< Feature.BasicMultiPartPayment.MandatoryBitPosition, + false + 1L + <<< Feature.BasicMultiPartPayment.OptionalBitPosition, + true + + 1L + <<< Feature.OptionSupportLargeChannel.MandatoryBitPosition, + true + 1L + <<< Feature.OptionSupportLargeChannel.OptionalBitPosition, + true + ] + + for (s, expected) in testCases do + let ba = BitArray.FromInt64(s) + + Expect.equal + (Feature.areSupported(ba)) + expected + (sprintf "%s" (ba.PrintBits())) + + testCase "features compatibility (in parsed string)" + <| fun _ -> + let testCases = + Map.empty + |> Map.add " 00000000000000001011" true + // option_upfront_shutdown_script + |> Map.add " 00000000000000010000" true + // gossip_queries (mandatory), gossip_queries_ex (optional) + |> Map.add " 00000000100001000000" true + // gossip_queries_ex (mandatory) + |> Map.add " 00000000010001000000" false + // option_static_remote_key + |> Map.add " 00000001000000000000" true + // initial_routing_sync, payment_secret(mandatory) + |> Map.add " 00000100000000001000" true + // var_onion_secret(optional) payment_secret(optional) + |> Map.add " 00001000001000000000" true + // unknown optional feature bits + |> Map.add " 10000000000000000000" true + |> Map.add " 001000000000000000000000" true + // support_large_channel_option(mandatory) + |> Map.add " 000001000000000000000000" true + // those are useful for nonreg testing of the areSupported method (which needs to be updated with every new supported mandatory bit) + |> Map.add " 000100000000000000000000" false + |> Map.add " 010000000000000000000000" false + |> Map.add " 0001000000000000000000000000" false + |> Map.add " 0100000000000000000000000000" false + |> Map.add "00010000000000000000000000000000" false + |> Map.add "01000000000000000000000000000000" false + + testCases + |> Map.iter(fun testCase expected -> + let fb = testCase |> parseBitArray + + Expect.equal + (Feature.areSupported(fb)) + expected + (sprintf "%A" (fb.PrintBits())) + ) + ] diff --git a/tests/DotNetLightning.Core.Tests/SerializationPropertyTests.fs b/tests/DotNetLightning.Core.Tests/SerializationPropertyTests.fs index 6507e2540..9f615a152 100644 --- a/tests/DotNetLightning.Core.Tests/SerializationPropertyTests.fs +++ b/tests/DotNetLightning.Core.Tests/SerializationPropertyTests.fs @@ -13,128 +13,172 @@ open ResultUtils.Portability let config = { FsCheckConfig.defaultConfig with - arbitrary = [ typeof; typeof ] - maxTest = 300 - } + arbitrary = + [ + typeof + typeof + ] + maxTest = 300 + } [] let testList1 = - testList "PrimitivesSerializationPropertyTests" [ - testPropertyWithConfig config "ecdsa signature" <| fun (signature: LNECDSASignature) -> - let actual = LNECDSASignature.FromBytesCompact(signature.ToBytesCompact(), false) - Expect.equal actual signature (sprintf "failed with actual: %A \n expected: %A" (actual.ToBytesCompact()) (signature.ToBytesCompact())) - ] + testList + "PrimitivesSerializationPropertyTests" + [ + testPropertyWithConfig config "ecdsa signature" + <| fun (signature: LNECDSASignature) -> + let actual = + LNECDSASignature.FromBytesCompact( + signature.ToBytesCompact(), + false + ) + + Expect.equal + actual + signature + (sprintf + "failed with actual: %A \n expected: %A" + (actual.ToBytesCompact()) + (signature.ToBytesCompact())) + ] [] let testList2 = - testList "SerializationPropertyTest" [ - testPropertyWithConfig config "init" <| fun (msg: InitMsg) -> - Expect.equal (msg.Clone()) (msg) "" - - testPropertyWithConfig config "ping" <| fun (msg: PingMsg) -> - Expect.equal (msg.Clone()) (msg) "" - - testPropertyWithConfig config "pong" <| fun (msg: PongMsg) -> - Expect.equal (msg.Clone()) (msg) "" - - testPropertyWithConfig config "open_channel" <| fun (msg: OpenChannelMsg) -> - Expect.equal (msg.Clone()) (msg) "" - - testPropertyWithConfig config "accept_channel" <| fun (msg: AcceptChannelMsg) -> - Expect.equal (msg.Clone()) (msg) "" - - testPropertyWithConfig config "funding_created" <| fun (msg: FundingCreatedMsg) -> - Expect.equal (msg.Clone()) (msg) "" - - testPropertyWithConfig config "funding_signed" <| fun (msg: FundingSignedMsg) -> - Expect.equal (msg.Clone()) (msg) "" - - testPropertyWithConfig config "funding_locked" <| fun (msg: FundingLockedMsg) -> - Expect.equal (msg.Clone()) (msg) "" - - testPropertyWithConfig config "shutdown" <| fun (msg: ShutdownMsg) -> - Expect.equal (msg.Clone()) (msg) "" - - testPropertyWithConfig config "closing_signed" <| fun (msg: ClosingSignedMsg) -> - Expect.equal (msg.Clone()) (msg) "" - - testPropertyWithConfig config "onion_packet" <| fun (msg: OnionPacket) -> - Expect.equal (msg.Clone()) (msg) "" - - testPropertyWithConfig config "update_add_htlc" <| fun (msg: UpdateAddHTLCMsg) -> - Expect.equal (msg.Clone()) (msg) "" - - testPropertyWithConfig config "update_fulfill_htlc" <| fun (msg: UpdateFulfillHTLCMsg) -> - Expect.equal (msg.Clone()) (msg) "" - - testPropertyWithConfig config "update_fail_htlc" <| fun (msg: UpdateFailHTLCMsg) -> - Expect.equal (msg.Clone()) (msg) "" - - testPropertyWithConfig config "update_fail_malformed_htlc" <| fun (msg: UpdateFailMalformedHTLCMsg) -> - Expect.equal (msg.Clone()) (msg) "" - - testPropertyWithConfig config "commitment_signed" <| fun (msg: CommitmentSignedMsg) -> - Expect.equal (msg.Clone()) (msg) "" - - testPropertyWithConfig config "revoke_and_ack" <| fun (msg: RevokeAndACKMsg) -> - Expect.equal (msg.Clone()) (msg) "" - - testPropertyWithConfig config "update_fee" <| fun (msg: UpdateFeeMsg) -> - Expect.equal (msg.Clone()) (msg) "" - - testPropertyWithConfig config "channel_reestablish" <| fun (msg: ChannelReestablishMsg) -> - Expect.equal (msg.Clone()) (msg) "" - - testPropertyWithConfig config "announcement_signatures" <| fun (msg: AnnouncementSignaturesMsg) -> - Expect.equal (msg.Clone()) (msg) "" - - testPropertyWithConfig config "node_announcement(unsigned contents)" <| fun (msg: UnsignedNodeAnnouncementMsg) -> - Expect.equal (msg.Clone()) (msg) "" - - testPropertyWithConfig config "node_announcement" <| fun (msg: NodeAnnouncementMsg) -> - Expect.equal (msg.Clone()) (msg) "" - - testPropertyWithConfig config "channel_announcement" <| fun (msg: ChannelAnnouncementMsg) -> - Expect.equal (msg.Clone()) (msg) "" - - testPropertyWithConfig config "channel_update" <| fun (msg: ChannelUpdateMsg) -> - Expect.equal (msg.Clone()) (msg) "" - - testPropertyWithConfig config "query_short_channel_ids" <| fun (msg: QueryShortChannelIdsMsg) -> - Expect.equal (msg.Clone()) (msg) "" - - testPropertyWithConfig config "reply_short_channel_ids" <| fun (msg: ReplyShortChannelIdsEndMsg) -> - Expect.equal (msg.Clone()) (msg) "" - - testPropertyWithConfig config "query_channel_range" <| fun (msg: QueryChannelRangeMsg) -> - Expect.equal (msg.Clone()) (msg) "" - - testPropertyWithConfig config "reply_channel_range" <| fun (msg: ReplyChannelRangeMsg) -> - Expect.equal (msg.Clone()) (msg) "" - - testPropertyWithConfig config "gossip_timestamp_filter" <| fun (msg: GossipTimestampFilterMsg) -> - Expect.equal (msg.Clone()) (msg) "" - - testPropertyWithConfig config "lightning p2p msg" <| fun (msg: ILightningMsg) -> - use ms = new MemoryStream() - use lws = new LightningWriterStream(ms) - ILightningSerializable.serializeWithFlags (lws) (msg) - let b = ms.ToArray() - use ms2 = new MemoryStream(b) - use lrs = new LightningReaderStream(ms2) - let actual = ILightningSerializable.deserializeWithFlag (lrs) - Expect.equal (actual) (msg) "" - - testPropertyWithConfig config "lightning p2p msg 2" <| fun (msg: ILightningMsg) -> - let actualR = LightningMsg.fromBytes(msg.ToBytes()) - match actualR with - | Ok x -> Expect.equal (msg) x - | Error ex -> - failwithf "failed to decode %A" ex - - testPropertyWithConfig config "onion payloads" <| fun (payload: OnionPayload) -> - let b = payload.ToBytes() - let f = OnionPayload.FromBytes(b) |> Result.deref - Expect.equal f payload "" - - ] + testList + "SerializationPropertyTest" + [ + testPropertyWithConfig config "init" + <| fun (msg: InitMsg) -> Expect.equal (msg.Clone()) (msg) "" + + testPropertyWithConfig config "ping" + <| fun (msg: PingMsg) -> Expect.equal (msg.Clone()) (msg) "" + + testPropertyWithConfig config "pong" + <| fun (msg: PongMsg) -> Expect.equal (msg.Clone()) (msg) "" + + testPropertyWithConfig config "open_channel" + <| fun (msg: OpenChannelMsg) -> Expect.equal (msg.Clone()) (msg) "" + + testPropertyWithConfig config "accept_channel" + <| fun (msg: AcceptChannelMsg) -> + Expect.equal (msg.Clone()) (msg) "" + + testPropertyWithConfig config "funding_created" + <| fun (msg: FundingCreatedMsg) -> + Expect.equal (msg.Clone()) (msg) "" + + testPropertyWithConfig config "funding_signed" + <| fun (msg: FundingSignedMsg) -> + Expect.equal (msg.Clone()) (msg) "" + + testPropertyWithConfig config "funding_locked" + <| fun (msg: FundingLockedMsg) -> + Expect.equal (msg.Clone()) (msg) "" + + testPropertyWithConfig config "shutdown" + <| fun (msg: ShutdownMsg) -> Expect.equal (msg.Clone()) (msg) "" + + testPropertyWithConfig config "closing_signed" + <| fun (msg: ClosingSignedMsg) -> + Expect.equal (msg.Clone()) (msg) "" + + testPropertyWithConfig config "onion_packet" + <| fun (msg: OnionPacket) -> Expect.equal (msg.Clone()) (msg) "" + + testPropertyWithConfig config "update_add_htlc" + <| fun (msg: UpdateAddHTLCMsg) -> + Expect.equal (msg.Clone()) (msg) "" + + testPropertyWithConfig config "update_fulfill_htlc" + <| fun (msg: UpdateFulfillHTLCMsg) -> + Expect.equal (msg.Clone()) (msg) "" + + testPropertyWithConfig config "update_fail_htlc" + <| fun (msg: UpdateFailHTLCMsg) -> + Expect.equal (msg.Clone()) (msg) "" + + testPropertyWithConfig config "update_fail_malformed_htlc" + <| fun (msg: UpdateFailMalformedHTLCMsg) -> + Expect.equal (msg.Clone()) (msg) "" + + testPropertyWithConfig config "commitment_signed" + <| fun (msg: CommitmentSignedMsg) -> + Expect.equal (msg.Clone()) (msg) "" + + testPropertyWithConfig config "revoke_and_ack" + <| fun (msg: RevokeAndACKMsg) -> Expect.equal (msg.Clone()) (msg) "" + + testPropertyWithConfig config "update_fee" + <| fun (msg: UpdateFeeMsg) -> Expect.equal (msg.Clone()) (msg) "" + + testPropertyWithConfig config "channel_reestablish" + <| fun (msg: ChannelReestablishMsg) -> + Expect.equal (msg.Clone()) (msg) "" + + testPropertyWithConfig config "announcement_signatures" + <| fun (msg: AnnouncementSignaturesMsg) -> + Expect.equal (msg.Clone()) (msg) "" + + testPropertyWithConfig config "node_announcement(unsigned contents)" + <| fun (msg: UnsignedNodeAnnouncementMsg) -> + Expect.equal (msg.Clone()) (msg) "" + + testPropertyWithConfig config "node_announcement" + <| fun (msg: NodeAnnouncementMsg) -> + Expect.equal (msg.Clone()) (msg) "" + + testPropertyWithConfig config "channel_announcement" + <| fun (msg: ChannelAnnouncementMsg) -> + Expect.equal (msg.Clone()) (msg) "" + + testPropertyWithConfig config "channel_update" + <| fun (msg: ChannelUpdateMsg) -> + Expect.equal (msg.Clone()) (msg) "" + + testPropertyWithConfig config "query_short_channel_ids" + <| fun (msg: QueryShortChannelIdsMsg) -> + Expect.equal (msg.Clone()) (msg) "" + + testPropertyWithConfig config "reply_short_channel_ids" + <| fun (msg: ReplyShortChannelIdsEndMsg) -> + Expect.equal (msg.Clone()) (msg) "" + + testPropertyWithConfig config "query_channel_range" + <| fun (msg: QueryChannelRangeMsg) -> + Expect.equal (msg.Clone()) (msg) "" + + testPropertyWithConfig config "reply_channel_range" + <| fun (msg: ReplyChannelRangeMsg) -> + Expect.equal (msg.Clone()) (msg) "" + + testPropertyWithConfig config "gossip_timestamp_filter" + <| fun (msg: GossipTimestampFilterMsg) -> + Expect.equal (msg.Clone()) (msg) "" + + testPropertyWithConfig config "lightning p2p msg" + <| fun (msg: ILightningMsg) -> + use ms = new MemoryStream() + use lws = new LightningWriterStream(ms) + ILightningSerializable.serializeWithFlags (lws) (msg) + let b = ms.ToArray() + use ms2 = new MemoryStream(b) + use lrs = new LightningReaderStream(ms2) + let actual = ILightningSerializable.deserializeWithFlag(lrs) + Expect.equal (actual) (msg) "" + + testPropertyWithConfig config "lightning p2p msg 2" + <| fun (msg: ILightningMsg) -> + let actualR = LightningMsg.fromBytes(msg.ToBytes()) + + match actualR with + | Ok x -> Expect.equal (msg) x + | Error ex -> failwithf "failed to decode %A" ex + + testPropertyWithConfig config "onion payloads" + <| fun (payload: OnionPayload) -> + let b = payload.ToBytes() + let f = OnionPayload.FromBytes(b) |> Result.deref + Expect.equal f payload "" + + ] diff --git a/tests/DotNetLightning.Core.Tests/SphinxTests.fs b/tests/DotNetLightning.Core.Tests/SphinxTests.fs index 663a319a2..e0d9c6f93 100644 --- a/tests/DotNetLightning.Core.Tests/SphinxTests.fs +++ b/tests/DotNetLightning.Core.Tests/SphinxTests.fs @@ -13,178 +13,469 @@ open DotNetLightning.Crypto.Sphinx let hex = NBitcoin.DataEncoders.HexEncoder() -let sessionKey = "4141414141414141414141414141414141414141414141414141414141414141" |> hex.DecodeData |> fun h -> new Key(h) -let privKeys = [ "4141414141414141414141414141414141414141414141414141414141414141" - "4242424242424242424242424242424242424242424242424242424242424242" - "4343434343434343434343434343434343434343434343434343434343434343" - "4444444444444444444444444444444444444444444444444444444444444444" - "4545454545454545454545454545454545454545454545454545454545454545" ] - |> List.map(hex.DecodeData >> fun h -> new Key(h)) +let sessionKey = + "4141414141414141414141414141414141414141414141414141414141414141" + |> hex.DecodeData + |> fun h -> new Key(h) + +let privKeys = + [ + "4141414141414141414141414141414141414141414141414141414141414141" + "4242424242424242424242424242424242424242424242424242424242424242" + "4343434343434343434343434343434343434343434343434343434343434343" + "4444444444444444444444444444444444444444444444444444444444444444" + "4545454545454545454545454545454545454545454545454545454545454545" + ] + |> List.map(hex.DecodeData >> fun h -> new Key(h)) + let pubKeys = privKeys |> List.map(fun k -> k.PubKey) -let expectedPubKeys = [ "02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619" - "0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c" - "027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007" - "032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991" - "02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145" ] - |> List.map(hex.DecodeData >> PubKey) +let expectedPubKeys = + [ + "02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619" + "0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c" + "027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007" + "032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991" + "02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145" + ] + |> List.map(hex.DecodeData >> PubKey) -let payloads = [ "000000000000000000000000000000000000000000000000000000000000000000" - "000101010101010101000000010000000100000000000000000000000000000000" - "000202020202020202000000020000000200000000000000000000000000000000" - "000303030303030303000000030000000300000000000000000000000000000000" - "000404040404040404000000040000000400000000000000000000000000000000" ] - |> List.map(hex.DecodeData) +let payloads = + [ + "000000000000000000000000000000000000000000000000000000000000000000" + "000101010101010101000000010000000100000000000000000000000000000000" + "000202020202020202000000020000000200000000000000000000000000000000" + "000303030303030303000000030000000300000000000000000000000000000000" + "000404040404040404000000040000000400000000000000000000000000000000" + ] + |> List.map(hex.DecodeData) -let associatedData = "4242424242424242424242424242424242424242424242424242424242424242" |> hex.DecodeData +let associatedData = + "4242424242424242424242424242424242424242424242424242424242424242" + |> hex.DecodeData let logger = Log.create "Sphinx tests" -let logCore = eventX >> logger.info -let log _level = logCore +let logCore = eventX >> logger.info + +let log _level = + logCore [] let bolt4Tests1 = - testList "bolt4 tests (legacy hop_data)" [ - testCase "pubkey is as expected" <| fun _ -> - Expect.equal (pubKeys) (expectedPubKeys) "" - - testCase "generate ephemeral keys and secrets" <| fun _ -> - let (ephKeys, sharedSecrets) = computeEphemeralPublicKeysAndSharedSecrets sessionKey pubKeys - let expectedEphKeys = [ "02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619" - "028f9438bfbf7feac2e108d677e3a82da596be706cc1cf342b75c7b7e22bf4e6e2" - "03bfd8225241ea71cd0843db7709f4c222f62ff2d4516fd38b39914ab6b83e0da0" - "031dde6926381289671300239ea8e57ffaf9bebd05b9a5b95beaf07af05cd43595" - "03a214ebd875aab6ddfd77f22c5e7311d7f77f17a169e599f157bbcdae8bf071f4" ] - |> List.map(hex.DecodeData >> PubKey) - let expectedSharedSecrets = [ "53eb63ea8a3fec3b3cd433b85cd62a4b145e1dda09391b348c4e1cd36a03ea66" - "a6519e98832a0b179f62123b3567c106db99ee37bef036e783263602f3488fae" - "3a6b412548762f0dbccce5c7ae7bb8147d1caf9b5471c34120b30bc9c04891cc" - "21e13c2d7cfe7e18836df50872466117a295783ab8aab0e7ecc8c725503ad02d" - "b5756b9b542727dbafc6765a49488b023a725d631af688fc031217e90770c328" ] - |> List.map(hex.DecodeData >> fun h -> new Key(h)) - Expect.equal ephKeys expectedEphKeys "" - Expect.equal sharedSecrets expectedSharedSecrets "" - - testCase "generate filler" <| fun _ -> - let (_, sharedSecrets) = computeEphemeralPublicKeysAndSharedSecrets sessionKey pubKeys - let filler = generateFiller "rho" sharedSecrets.[0..sharedSecrets.Length - 2] (PayloadLength + MacLength) (Some(20)) - let expectedFiller = "c6b008cf6414ed6e4c42c291eb505e9f22f5fe7d0ecdd15a833f4d016ac974d33adc6ea3293e20859e87ebfb937ba406abd025d14af692b12e9c9c2adbe307a679779259676211c071e614fdb386d1ff02db223a5b2fae03df68d321c7b29f7c7240edd3fa1b7cb6903f89dc01abf41b2eb0b49b6b8d73bb0774b58204c0d0e96d3cce45ad75406be0bc009e327b3e712a4bd178609c00b41da2daf8a4b0e1319f07a492ab4efb056f0f599f75e6dc7e0d10ce1cf59088ab6e873de377343880f7a24f0e36731a0b72092f8d5bc8cd346762e93b2bf203d00264e4bc136fc142de8f7b69154deb05854ea88e2d7506222c95ba1aab065c8a851391377d3406a35a9af3ac" |> hex.DecodeData - Expect.equal filler expectedFiller "" - - testCase "Create packet (reference test vector)" <| fun _ -> - let (onion, _ss) = - let p = PacketAndSecrets.Create (sessionKey, pubKeys, payloads, associatedData) - (p.Packet, p.SharedSecrets) - let expectedPacket = - "0002eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619e5f14350c2a76fc232b5e46d421e9615471ab9e0bc887beff8c95fdb878f7b3a716a996c7845c93d90e4ecbb9bde4ece2f69425c99e4bc820e44485455f135edc0d10f7d61ab590531cf08000179a333a347f8b4072f216400406bdf3bf038659793d4a1fd7b246979e3150a0a4cb052c9ec69acf0f48c3d39cd55675fe717cb7d80ce721caad69320c3a469a202f1e468c67eaf7a7cd8226d0fd32f7b48084dca885d56047694762b67021713ca673929c163ec36e04e40ca8e1c6d17569419d3039d9a1ec866abe044a9ad635778b961fc0776dc832b3a451bd5d35072d2269cf9b040f6b7a7dad84fb114ed413b1426cb96ceaf83825665ed5a1d002c1687f92465b49ed4c7f0218ff8c6c7dd7221d589c65b3b9aaa71a41484b122846c7c7b57e02e679ea8469b70e14fe4f70fee4d87b910cf144be6fe48eef24da475c0b0bcc6565ae82cd3f4e3b24c76eaa5616c6111343306ab35c1fe5ca4a77c0e314ed7dba39d6f1e0de791719c241a939cc493bea2bae1c1e932679ea94d29084278513c77b899cc98059d06a27d171b0dbdf6bee13ddc4fc17a0c4d2827d488436b57baa167544138ca2e64a11b43ac8a06cd0c2fba2d4d900ed2d9205305e2d7383cc98dacb078133de5f6fb6bed2ef26ba92cea28aafc3b9948dd9ae5559e8bd6920b8cea462aa445ca6a95e0e7ba52961b181c79e73bd581821df2b10173727a810c92b83b5ba4a0403eb710d2ca10689a35bec6c3a708e9e92f7d78ff3c5d9989574b00c6736f84c199256e76e19e78f0c98a9d580b4a658c84fc8f2096c2fbea8f5f8c59d0fdacb3be2802ef802abbecb3aba4acaac69a0e965abd8981e9896b1f6ef9d60f7a164b371af869fd0e48073742825e9434fc54da837e120266d53302954843538ea7c6c3dbfb4ff3b2fdbe244437f2a153ccf7bdb4c92aa08102d4f3cff2ae5ef86fab4653595e6a5837fa2f3e29f27a9cde5966843fb847a4a61f1e76c281fe8bb2b0a181d096100db5a1a5ce7a910238251a43ca556712eaadea167fb4d7d75825e440f3ecd782036d7574df8bceacb397abefc5f5254d2722215c53ff54af8299aaaad642c6d72a14d27882d9bbd539e1cc7a527526ba89b8c037ad09120e98ab042d3e8652b31ae0e478516bfaf88efca9f3676ffe99d2819dcaeb7610a626695f53117665d267d3f7abebd6bbd6733f645c72c389f03855bdf1e4b8075b516569b118233a0f0971d24b83113c0b096f5216a207ca99a7cddc81c130923fe3d91e7508c9ac5f2e914ff5dccab9e558566fa14efb34ac98d878580814b94b73acbfde9072f30b881f7f0fff42d4045d1ace6322d86a97d164aa84d93a60498065cc7c20e636f5862dc81531a88c60305a2e59a985be327a6902e4bed986dbf4a0b50c217af0ea7fdf9ab37f9ea1a1aaa72f54cf40154ea9b269f1a7c09f9f43245109431a175d50e2db0132337baa0ef97eed0fcf20489da36b79a1172faccc2f7ded7c60e00694282d93359c4682135642bc81f433574aa8ef0c97b4ade7ca372c5ffc23c7eddd839bab4e0f14d6df15c9dbeab176bec8b5701cf054eb3072f6dadc98f88819042bf10c407516ee58bce33fbe3b3d86a54255e577db4598e30a135361528c101683a5fcde7e8ba53f3456254be8f45fe3a56120ae96ea3773631fcb3873aa3abd91bcff00bd38bd43697a2e789e00da6077482e7b1b1a677b5afae4c54e6cbdf7377b694eb7d7a5b913476a5be923322d3de06060fd5e819635232a2cf4f0731da13b8546d1d6d4f8d75b9fce6c2341a71b0ea6f780df54bfdb0dd5cd9855179f602f9172307c7268724c3618e6817abd793adc214a0dc0bc616816632f27ea336fb56dfd" - |> hex.DecodeData - CheckArrayEqual (onion.ToBytes()) (expectedPacket) - let { Payload = payload0; NextPacket = nextPacket0; SharedSecret = _ss0 }: ParsedPacket = - Sphinx.parsePacket (privKeys.[0]) (associatedData) (onion.ToBytes()) - |> fun rr -> - Expect.isOk (Result.ToFSharpCoreResult rr) "" - Result.defaultWith (fun _ -> failwith "Unreachable") rr - let { Payload = payload1; NextPacket = nextPacket1; }: ParsedPacket = - Sphinx.parsePacket (privKeys.[1]) (associatedData) (nextPacket0.ToBytes()) - |> Result.defaultWith(fun _ -> failwith "Fail: bolt4 create packet ref test vector defaultClosure1") - let { Payload = payload2; NextPacket = nextPacket2; }: ParsedPacket = - Sphinx.parsePacket (privKeys.[2]) (associatedData) (nextPacket1.ToBytes()) - |> Result.defaultWith(fun _ -> failwith "Fail: bolt4 create packet ref test vector defaultClosure2") - let { Payload = payload3; NextPacket = nextPacket3; }: ParsedPacket = - Sphinx.parsePacket (privKeys.[3]) (associatedData) (nextPacket2.ToBytes()) - |> Result.defaultWith(fun _ -> failwith "Fail: bolt4 create packet ref test vector defaultClosure3") - let { Payload = payload4; NextPacket = nextPacket4; }: ParsedPacket = - Sphinx.parsePacket (privKeys.[4]) (associatedData) (nextPacket3.ToBytes()) - |> Result.defaultWith(fun _ -> failwith "Fail: bolt4 create packet ref test vector defaultClosure1") - - 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 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) - (p.Packet, p.SharedSecrets) - let { NextPacket = packet1; SharedSecret = ss0 }: ParsedPacket = - Sphinx.parsePacket (privKeys.[0]) (associatedData) (onion.ToBytes()) - |> fun r -> - Expect.isOk (Result.ToFSharpCoreResult r) "" - Result.defaultWith(fun _ -> failwith "Fail: bolt4 last node replies with err msg defaultClosure0") r - let { NextPacket = packet2; SharedSecret = ss1 }: ParsedPacket = - Sphinx.parsePacket (privKeys.[1]) (associatedData) (packet1.ToBytes()) - |> Result.defaultWith(fun _ -> failwith "Fail: bolt4 last node replies with err msg defaultClosure1") - let { NextPacket = packet3; SharedSecret = ss2 }: ParsedPacket = - Sphinx.parsePacket (privKeys.[2]) (associatedData) (packet2.ToBytes()) - |> Result.defaultWith(fun _ -> failwith "Fail: bolt4 last node replies with err msg defaultClosure2") - let { NextPacket = packet4; SharedSecret = ss3 }: ParsedPacket = - Sphinx.parsePacket (privKeys.[3]) (associatedData) (packet3.ToBytes()) - |> Result.defaultWith(fun _ -> failwith "Fail: bolt4 last node replies with err msg defaultClosure3") - let { NextPacket = packet5; SharedSecret = ss4 }: ParsedPacket = - Sphinx.parsePacket (privKeys.[4]) (associatedData) (packet4.ToBytes()) - |> Result.defaultWith(fun _ -> failwith "Fail: bolt4 last node replies with err msg defaultClosure4") - - Expect.isTrue (packet5.IsLastPacket) "" - let error = ErrorPacket.Create(ss4, { FailureMsg.Code = FailureCode (OnionError.TEMPORARY_NODE_FAILURE); Data = TemporaryNodeFailure }) - let _ = - let expected = "a5e6bd0c74cb347f10cce367f949098f2457d14c046fd8a22cb96efb30b0fdcda8cb9168b50f2fd45edd73c1b0c8b33002df376801ff58aaa94000bf8a86f92620f343baef38a580102395ae3abf9128d1047a0736ff9b83d456740ebbb4aeb3aa9737f18fb4afb4aa074fb26c4d702f42968888550a3bded8c05247e045b866baef0499f079fdaeef6538f31d44deafffdfd3afa2fb4ca9082b8f1c465371a9894dd8c243fb4847e004f5256b3e90e2edde4c9fb3082ddfe4d1e734cacd96ef0706bf63c9984e22dc98851bcccd1c3494351feb458c9c6af41c0044bea3c47552b1d992ae542b17a2d0bba1a096c78d169034ecb55b6e3a7263c26017f033031228833c1daefc0dedb8cf7c3e37c9c37ebfe42f3225c326e8bcfd338804c145b16e34e4" |> hex.DecodeData - Expect.equal(expected) error "" - - let error1 = Sphinx.forwardErrorPacket(error, ss3) - let _ = - let expected = "c49a1ce81680f78f5f2000cda36268de34a3f0a0662f55b4e837c83a8773c22aa081bab1616a0011585323930fa5b9fae0c85770a2279ff59ec427ad1bbff9001c0cd1497004bd2a0f68b50704cf6d6a4bf3c8b6a0833399a24b3456961ba00736785112594f65b6b2d44d9f5ea4e49b5e1ec2af978cbe31c67114440ac51a62081df0ed46d4a3df295da0b0fe25c0115019f03f15ec86fabb4c852f83449e812f141a9395b3f70b766ebbd4ec2fae2b6955bd8f32684c15abfe8fd3a6261e52650e8807a92158d9f1463261a925e4bfba44bd20b166d532f0017185c3a6ac7957adefe45559e3072c8dc35abeba835a8cb01a71a15c736911126f27d46a36168ca5ef7dccd4e2886212602b181463e0dd30185c96348f9743a02aca8ec27c0b90dca270" - |> hex.DecodeData - Expect.equal expected error1 "" - - let error2 = Sphinx.forwardErrorPacket(error1, ss2) - let _ = - let expected = "a5d3e8634cfe78b2307d87c6d90be6fe7855b4f2cc9b1dfb19e92e4b79103f61ff9ac25f412ddfb7466e74f81b3e545563cdd8f5524dae873de61d7bdfccd496af2584930d2b566b4f8d3881f8c043df92224f38cf094cfc09d92655989531524593ec6d6caec1863bdfaa79229b5020acc034cd6deeea1021c50586947b9b8e6faa83b81fbfa6133c0af5d6b07c017f7158fa94f0d206baf12dda6b68f785b773b360fd0497e16cc402d779c8d48d0fa6315536ef0660f3f4e1865f5b38ea49c7da4fd959de4e83ff3ab686f059a45c65ba2af4a6a79166aa0f496bf04d06987b6d2ea205bdb0d347718b9aeff5b61dfff344993a275b79717cd815b6ad4c0beb568c4ac9c36ff1c315ec1119a1993c4b61e6eaa0375e0aaf738ac691abd3263bf937e3" - |> hex.DecodeData - Expect.equal expected error2 "" - - let error3 = Sphinx.forwardErrorPacket (error2, ss1) - let _ = - let expected = "aac3200c4968f56b21f53e5e374e3a2383ad2b1b6501bbcc45abc31e59b26881b7dfadbb56ec8dae8857add94e6702fb4c3a4de22e2e669e1ed926b04447fc73034bb730f4932acd62727b75348a648a1128744657ca6a4e713b9b646c3ca66cac02cdab44dd3439890ef3aaf61708714f7375349b8da541b2548d452d84de7084bb95b3ac2345201d624d31f4d52078aa0fa05a88b4e20202bd2b86ac5b52919ea305a8949de95e935eed0319cf3cf19ebea61d76ba92532497fcdc9411d06bcd4275094d0a4a3c5d3a945e43305a5a9256e333e1f64dbca5fcd4e03a39b9012d197506e06f29339dfee3331995b21615337ae060233d39befea925cc262873e0530408e6990f1cbd233a150ef7b004ff6166c70c68d9f8c853c1abca640b8660db2921" - |> hex.DecodeData - Expect.equal expected error3 "" - - let error4 = Sphinx.forwardErrorPacket(error3, ss0) - let _ = - let expected = "9c5add3963fc7f6ed7f148623c84134b5647e1306419dbe2174e523fa9e2fbed3a06a19f899145610741c83ad40b7712aefaddec8c6baf7325d92ea4ca4d1df8bce517f7e54554608bf2bd8071a4f52a7a2f7ffbb1413edad81eeea5785aa9d990f2865dc23b4bc3c301a94eec4eabebca66be5cf638f693ec256aec514620cc28ee4a94bd9565bc4d4962b9d3641d4278fb319ed2b84de5b665f307a2db0f7fbb757366067d88c50f7e829138fde4f78d39b5b5802f1b92a8a820865af5cc79f9f30bc3f461c66af95d13e5e1f0381c184572a91dee1c849048a647a1158cf884064deddbf1b0b88dfe2f791428d0ba0f6fb2f04e14081f69165ae66d9297c118f0907705c9c4954a199bae0bb96fad763d690e7daa6cfda59ba7f2c8d11448b604d12d" - |> hex.DecodeData - Expect.equal expected error4 "" - let { OriginNode = pubkey; FailureMsg = failure } = - ErrorPacket.TryParse(error4, ss) - |> Result.defaultWith(fun _ -> failwith "Fail: bolt4 last node replies with err msg defaultClosure end") - Expect.equal (pubKeys.[4]) (pubkey.Value) "" - Expect.equal (TemporaryNodeFailure) (failure.Data) "" - () - - testCase "Intermediate node replies with an error message" <| fun _ -> - let { Packet = packet; SharedSecrets = ss } = - Sphinx.PacketAndSecrets.Create(sessionKey, pubKeys, payloads, associatedData) - 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") - let { NextPacket = packet2; SharedSecret = ss1 } = - Sphinx.parsePacket(privKeys.[1]) (associatedData) (packet1.ToBytes()) - |> Result.defaultWith(fun _ -> failwith "Fail: bolt4 intrm node replies with err msg defaultClosure1") - let { SharedSecret = ss2 } = - Sphinx.parsePacket(privKeys.[2]) (associatedData) (packet2.ToBytes()) - |> Result.defaultWith(fun _ -> failwith "Fail: bolt4 intrm node replies with err msg defaultClosure2") - - let error = ErrorPacket.Create(ss2, { Code = OnionError.FailureCode INVALID_REALM; Data = InvalidRealm }) - let error1 = forwardErrorPacket(error, ss1) - let error2 = forwardErrorPacket(error1, ss0) - let { OriginNode = pubkey; FailureMsg = failure } = - ErrorPacket.TryParse(error2, ss) - |> Result.defaultWith(fun _ -> failwith "Fail: bolt4 intrm node replies with err msg defaultClosure end") - Expect.equal (pubkey.Value) (pubKeys.[2]) "" - Expect.equal (InvalidRealm) (failure.Data) "" - () - ] - + testList + "bolt4 tests (legacy hop_data)" + [ + testCase "pubkey is as expected" + <| fun _ -> Expect.equal (pubKeys) (expectedPubKeys) "" + + testCase "generate ephemeral keys and secrets" + <| fun _ -> + let (ephKeys, sharedSecrets) = + computeEphemeralPublicKeysAndSharedSecrets + sessionKey + pubKeys + + let expectedEphKeys = + [ + "02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619" + "028f9438bfbf7feac2e108d677e3a82da596be706cc1cf342b75c7b7e22bf4e6e2" + "03bfd8225241ea71cd0843db7709f4c222f62ff2d4516fd38b39914ab6b83e0da0" + "031dde6926381289671300239ea8e57ffaf9bebd05b9a5b95beaf07af05cd43595" + "03a214ebd875aab6ddfd77f22c5e7311d7f77f17a169e599f157bbcdae8bf071f4" + ] + |> List.map(hex.DecodeData >> PubKey) + + let expectedSharedSecrets = + [ + "53eb63ea8a3fec3b3cd433b85cd62a4b145e1dda09391b348c4e1cd36a03ea66" + "a6519e98832a0b179f62123b3567c106db99ee37bef036e783263602f3488fae" + "3a6b412548762f0dbccce5c7ae7bb8147d1caf9b5471c34120b30bc9c04891cc" + "21e13c2d7cfe7e18836df50872466117a295783ab8aab0e7ecc8c725503ad02d" + "b5756b9b542727dbafc6765a49488b023a725d631af688fc031217e90770c328" + ] + |> List.map(hex.DecodeData >> fun h -> new Key(h)) + + Expect.equal ephKeys expectedEphKeys "" + Expect.equal sharedSecrets expectedSharedSecrets "" + + testCase "generate filler" + <| fun _ -> + let (_, sharedSecrets) = + computeEphemeralPublicKeysAndSharedSecrets + sessionKey + pubKeys + + let filler = + generateFiller + "rho" + sharedSecrets.[0 .. sharedSecrets.Length - 2] + (PayloadLength + MacLength) + (Some(20)) + + let expectedFiller = + "c6b008cf6414ed6e4c42c291eb505e9f22f5fe7d0ecdd15a833f4d016ac974d33adc6ea3293e20859e87ebfb937ba406abd025d14af692b12e9c9c2adbe307a679779259676211c071e614fdb386d1ff02db223a5b2fae03df68d321c7b29f7c7240edd3fa1b7cb6903f89dc01abf41b2eb0b49b6b8d73bb0774b58204c0d0e96d3cce45ad75406be0bc009e327b3e712a4bd178609c00b41da2daf8a4b0e1319f07a492ab4efb056f0f599f75e6dc7e0d10ce1cf59088ab6e873de377343880f7a24f0e36731a0b72092f8d5bc8cd346762e93b2bf203d00264e4bc136fc142de8f7b69154deb05854ea88e2d7506222c95ba1aab065c8a851391377d3406a35a9af3ac" + |> hex.DecodeData + + Expect.equal filler expectedFiller "" + + testCase "Create packet (reference test vector)" + <| fun _ -> + let (onion, _ss) = + let p = + PacketAndSecrets.Create( + sessionKey, + pubKeys, + payloads, + associatedData + ) + + (p.Packet, p.SharedSecrets) + + let expectedPacket = + "0002eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619e5f14350c2a76fc232b5e46d421e9615471ab9e0bc887beff8c95fdb878f7b3a716a996c7845c93d90e4ecbb9bde4ece2f69425c99e4bc820e44485455f135edc0d10f7d61ab590531cf08000179a333a347f8b4072f216400406bdf3bf038659793d4a1fd7b246979e3150a0a4cb052c9ec69acf0f48c3d39cd55675fe717cb7d80ce721caad69320c3a469a202f1e468c67eaf7a7cd8226d0fd32f7b48084dca885d56047694762b67021713ca673929c163ec36e04e40ca8e1c6d17569419d3039d9a1ec866abe044a9ad635778b961fc0776dc832b3a451bd5d35072d2269cf9b040f6b7a7dad84fb114ed413b1426cb96ceaf83825665ed5a1d002c1687f92465b49ed4c7f0218ff8c6c7dd7221d589c65b3b9aaa71a41484b122846c7c7b57e02e679ea8469b70e14fe4f70fee4d87b910cf144be6fe48eef24da475c0b0bcc6565ae82cd3f4e3b24c76eaa5616c6111343306ab35c1fe5ca4a77c0e314ed7dba39d6f1e0de791719c241a939cc493bea2bae1c1e932679ea94d29084278513c77b899cc98059d06a27d171b0dbdf6bee13ddc4fc17a0c4d2827d488436b57baa167544138ca2e64a11b43ac8a06cd0c2fba2d4d900ed2d9205305e2d7383cc98dacb078133de5f6fb6bed2ef26ba92cea28aafc3b9948dd9ae5559e8bd6920b8cea462aa445ca6a95e0e7ba52961b181c79e73bd581821df2b10173727a810c92b83b5ba4a0403eb710d2ca10689a35bec6c3a708e9e92f7d78ff3c5d9989574b00c6736f84c199256e76e19e78f0c98a9d580b4a658c84fc8f2096c2fbea8f5f8c59d0fdacb3be2802ef802abbecb3aba4acaac69a0e965abd8981e9896b1f6ef9d60f7a164b371af869fd0e48073742825e9434fc54da837e120266d53302954843538ea7c6c3dbfb4ff3b2fdbe244437f2a153ccf7bdb4c92aa08102d4f3cff2ae5ef86fab4653595e6a5837fa2f3e29f27a9cde5966843fb847a4a61f1e76c281fe8bb2b0a181d096100db5a1a5ce7a910238251a43ca556712eaadea167fb4d7d75825e440f3ecd782036d7574df8bceacb397abefc5f5254d2722215c53ff54af8299aaaad642c6d72a14d27882d9bbd539e1cc7a527526ba89b8c037ad09120e98ab042d3e8652b31ae0e478516bfaf88efca9f3676ffe99d2819dcaeb7610a626695f53117665d267d3f7abebd6bbd6733f645c72c389f03855bdf1e4b8075b516569b118233a0f0971d24b83113c0b096f5216a207ca99a7cddc81c130923fe3d91e7508c9ac5f2e914ff5dccab9e558566fa14efb34ac98d878580814b94b73acbfde9072f30b881f7f0fff42d4045d1ace6322d86a97d164aa84d93a60498065cc7c20e636f5862dc81531a88c60305a2e59a985be327a6902e4bed986dbf4a0b50c217af0ea7fdf9ab37f9ea1a1aaa72f54cf40154ea9b269f1a7c09f9f43245109431a175d50e2db0132337baa0ef97eed0fcf20489da36b79a1172faccc2f7ded7c60e00694282d93359c4682135642bc81f433574aa8ef0c97b4ade7ca372c5ffc23c7eddd839bab4e0f14d6df15c9dbeab176bec8b5701cf054eb3072f6dadc98f88819042bf10c407516ee58bce33fbe3b3d86a54255e577db4598e30a135361528c101683a5fcde7e8ba53f3456254be8f45fe3a56120ae96ea3773631fcb3873aa3abd91bcff00bd38bd43697a2e789e00da6077482e7b1b1a677b5afae4c54e6cbdf7377b694eb7d7a5b913476a5be923322d3de06060fd5e819635232a2cf4f0731da13b8546d1d6d4f8d75b9fce6c2341a71b0ea6f780df54bfdb0dd5cd9855179f602f9172307c7268724c3618e6817abd793adc214a0dc0bc616816632f27ea336fb56dfd" + |> hex.DecodeData + + CheckArrayEqual (onion.ToBytes()) (expectedPacket) + + let { + Payload = payload0 + NextPacket = nextPacket0 + SharedSecret = _ss0 + }: ParsedPacket = + Sphinx.parsePacket + (privKeys.[0]) + (associatedData) + (onion.ToBytes()) + |> fun rr -> + Expect.isOk (Result.ToFSharpCoreResult rr) "" + Result.defaultWith (fun _ -> failwith "Unreachable") rr + + let { + Payload = payload1 + NextPacket = nextPacket1 + }: ParsedPacket = + Sphinx.parsePacket + (privKeys.[1]) + (associatedData) + (nextPacket0.ToBytes()) + |> Result.defaultWith(fun _ -> + failwith + "Fail: bolt4 create packet ref test vector defaultClosure1" + ) + + let { + Payload = payload2 + NextPacket = nextPacket2 + }: ParsedPacket = + Sphinx.parsePacket + (privKeys.[2]) + (associatedData) + (nextPacket1.ToBytes()) + |> Result.defaultWith(fun _ -> + failwith + "Fail: bolt4 create packet ref test vector defaultClosure2" + ) + + let { + Payload = payload3 + NextPacket = nextPacket3 + }: ParsedPacket = + Sphinx.parsePacket + (privKeys.[3]) + (associatedData) + (nextPacket2.ToBytes()) + |> Result.defaultWith(fun _ -> + failwith + "Fail: bolt4 create packet ref test vector defaultClosure3" + ) + + let { + Payload = payload4 + NextPacket = nextPacket4 + }: ParsedPacket = + Sphinx.parsePacket + (privKeys.[4]) + (associatedData) + (nextPacket3.ToBytes()) + |> Result.defaultWith(fun _ -> + failwith + "Fail: bolt4 create packet ref test vector defaultClosure1" + ) + + 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 + 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 + ) + + (p.Packet, p.SharedSecrets) + + let { + NextPacket = packet1 + SharedSecret = ss0 + }: ParsedPacket = + Sphinx.parsePacket + (privKeys.[0]) + (associatedData) + (onion.ToBytes()) + |> fun r -> + Expect.isOk (Result.ToFSharpCoreResult r) "" + + Result.defaultWith + (fun _ -> + failwith + "Fail: bolt4 last node replies with err msg defaultClosure0" + ) + r + + let { + NextPacket = packet2 + SharedSecret = ss1 + }: ParsedPacket = + Sphinx.parsePacket + (privKeys.[1]) + (associatedData) + (packet1.ToBytes()) + |> Result.defaultWith(fun _ -> + failwith + "Fail: bolt4 last node replies with err msg defaultClosure1" + ) + + let { + NextPacket = packet3 + SharedSecret = ss2 + }: ParsedPacket = + Sphinx.parsePacket + (privKeys.[2]) + (associatedData) + (packet2.ToBytes()) + |> Result.defaultWith(fun _ -> + failwith + "Fail: bolt4 last node replies with err msg defaultClosure2" + ) + + let { + NextPacket = packet4 + SharedSecret = ss3 + }: ParsedPacket = + Sphinx.parsePacket + (privKeys.[3]) + (associatedData) + (packet3.ToBytes()) + |> Result.defaultWith(fun _ -> + failwith + "Fail: bolt4 last node replies with err msg defaultClosure3" + ) + + let { + NextPacket = packet5 + SharedSecret = ss4 + }: ParsedPacket = + Sphinx.parsePacket + (privKeys.[4]) + (associatedData) + (packet4.ToBytes()) + |> Result.defaultWith(fun _ -> + failwith + "Fail: bolt4 last node replies with err msg defaultClosure4" + ) + + Expect.isTrue (packet5.IsLastPacket) "" + + let error = + ErrorPacket.Create( + ss4, + { + FailureMsg.Code = + FailureCode(OnionError.TEMPORARY_NODE_FAILURE) + Data = TemporaryNodeFailure + } + ) + + let _ = + let expected = + "a5e6bd0c74cb347f10cce367f949098f2457d14c046fd8a22cb96efb30b0fdcda8cb9168b50f2fd45edd73c1b0c8b33002df376801ff58aaa94000bf8a86f92620f343baef38a580102395ae3abf9128d1047a0736ff9b83d456740ebbb4aeb3aa9737f18fb4afb4aa074fb26c4d702f42968888550a3bded8c05247e045b866baef0499f079fdaeef6538f31d44deafffdfd3afa2fb4ca9082b8f1c465371a9894dd8c243fb4847e004f5256b3e90e2edde4c9fb3082ddfe4d1e734cacd96ef0706bf63c9984e22dc98851bcccd1c3494351feb458c9c6af41c0044bea3c47552b1d992ae542b17a2d0bba1a096c78d169034ecb55b6e3a7263c26017f033031228833c1daefc0dedb8cf7c3e37c9c37ebfe42f3225c326e8bcfd338804c145b16e34e4" + |> hex.DecodeData + + Expect.equal (expected) error "" + + let error1 = Sphinx.forwardErrorPacket(error, ss3) + + let _ = + let expected = + "c49a1ce81680f78f5f2000cda36268de34a3f0a0662f55b4e837c83a8773c22aa081bab1616a0011585323930fa5b9fae0c85770a2279ff59ec427ad1bbff9001c0cd1497004bd2a0f68b50704cf6d6a4bf3c8b6a0833399a24b3456961ba00736785112594f65b6b2d44d9f5ea4e49b5e1ec2af978cbe31c67114440ac51a62081df0ed46d4a3df295da0b0fe25c0115019f03f15ec86fabb4c852f83449e812f141a9395b3f70b766ebbd4ec2fae2b6955bd8f32684c15abfe8fd3a6261e52650e8807a92158d9f1463261a925e4bfba44bd20b166d532f0017185c3a6ac7957adefe45559e3072c8dc35abeba835a8cb01a71a15c736911126f27d46a36168ca5ef7dccd4e2886212602b181463e0dd30185c96348f9743a02aca8ec27c0b90dca270" + |> hex.DecodeData + + Expect.equal expected error1 "" + + let error2 = Sphinx.forwardErrorPacket(error1, ss2) + + let _ = + let expected = + "a5d3e8634cfe78b2307d87c6d90be6fe7855b4f2cc9b1dfb19e92e4b79103f61ff9ac25f412ddfb7466e74f81b3e545563cdd8f5524dae873de61d7bdfccd496af2584930d2b566b4f8d3881f8c043df92224f38cf094cfc09d92655989531524593ec6d6caec1863bdfaa79229b5020acc034cd6deeea1021c50586947b9b8e6faa83b81fbfa6133c0af5d6b07c017f7158fa94f0d206baf12dda6b68f785b773b360fd0497e16cc402d779c8d48d0fa6315536ef0660f3f4e1865f5b38ea49c7da4fd959de4e83ff3ab686f059a45c65ba2af4a6a79166aa0f496bf04d06987b6d2ea205bdb0d347718b9aeff5b61dfff344993a275b79717cd815b6ad4c0beb568c4ac9c36ff1c315ec1119a1993c4b61e6eaa0375e0aaf738ac691abd3263bf937e3" + |> hex.DecodeData + + Expect.equal expected error2 "" + + let error3 = Sphinx.forwardErrorPacket(error2, ss1) + + let _ = + let expected = + "aac3200c4968f56b21f53e5e374e3a2383ad2b1b6501bbcc45abc31e59b26881b7dfadbb56ec8dae8857add94e6702fb4c3a4de22e2e669e1ed926b04447fc73034bb730f4932acd62727b75348a648a1128744657ca6a4e713b9b646c3ca66cac02cdab44dd3439890ef3aaf61708714f7375349b8da541b2548d452d84de7084bb95b3ac2345201d624d31f4d52078aa0fa05a88b4e20202bd2b86ac5b52919ea305a8949de95e935eed0319cf3cf19ebea61d76ba92532497fcdc9411d06bcd4275094d0a4a3c5d3a945e43305a5a9256e333e1f64dbca5fcd4e03a39b9012d197506e06f29339dfee3331995b21615337ae060233d39befea925cc262873e0530408e6990f1cbd233a150ef7b004ff6166c70c68d9f8c853c1abca640b8660db2921" + |> hex.DecodeData + + Expect.equal expected error3 "" + + let error4 = Sphinx.forwardErrorPacket(error3, ss0) + + let _ = + let expected = + "9c5add3963fc7f6ed7f148623c84134b5647e1306419dbe2174e523fa9e2fbed3a06a19f899145610741c83ad40b7712aefaddec8c6baf7325d92ea4ca4d1df8bce517f7e54554608bf2bd8071a4f52a7a2f7ffbb1413edad81eeea5785aa9d990f2865dc23b4bc3c301a94eec4eabebca66be5cf638f693ec256aec514620cc28ee4a94bd9565bc4d4962b9d3641d4278fb319ed2b84de5b665f307a2db0f7fbb757366067d88c50f7e829138fde4f78d39b5b5802f1b92a8a820865af5cc79f9f30bc3f461c66af95d13e5e1f0381c184572a91dee1c849048a647a1158cf884064deddbf1b0b88dfe2f791428d0ba0f6fb2f04e14081f69165ae66d9297c118f0907705c9c4954a199bae0bb96fad763d690e7daa6cfda59ba7f2c8d11448b604d12d" + |> hex.DecodeData + + Expect.equal expected error4 "" + + let { + OriginNode = pubkey + FailureMsg = failure + } = + ErrorPacket.TryParse(error4, ss) + |> Result.defaultWith(fun _ -> + failwith + "Fail: bolt4 last node replies with err msg defaultClosure end" + ) + + Expect.equal (pubKeys.[4]) (pubkey.Value) "" + Expect.equal (TemporaryNodeFailure) (failure.Data) "" + () + + testCase "Intermediate node replies with an error message" + <| fun _ -> + let { + Packet = packet + SharedSecrets = ss + } = + Sphinx.PacketAndSecrets.Create( + sessionKey, + pubKeys, + payloads, + associatedData + ) + + 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" + ) + + let { + NextPacket = packet2 + SharedSecret = ss1 + } = + Sphinx.parsePacket + (privKeys.[1]) + (associatedData) + (packet1.ToBytes()) + |> Result.defaultWith(fun _ -> + failwith + "Fail: bolt4 intrm node replies with err msg defaultClosure1" + ) + + let { + SharedSecret = ss2 + } = + Sphinx.parsePacket + (privKeys.[2]) + (associatedData) + (packet2.ToBytes()) + |> Result.defaultWith(fun _ -> + failwith + "Fail: bolt4 intrm node replies with err msg defaultClosure2" + ) + + let error = + ErrorPacket.Create( + ss2, + { + Code = OnionError.FailureCode INVALID_REALM + Data = InvalidRealm + } + ) + + let error1 = forwardErrorPacket(error, ss1) + let error2 = forwardErrorPacket(error1, ss0) + + let { + OriginNode = pubkey + FailureMsg = failure + } = + ErrorPacket.TryParse(error2, ss) + |> Result.defaultWith(fun _ -> + failwith + "Fail: bolt4 intrm node replies with err msg defaultClosure end" + ) + + Expect.equal (pubkey.Value) (pubKeys.[2]) "" + Expect.equal (InvalidRealm) (failure.Data) "" + () + ] diff --git a/tests/DotNetLightning.Core.Tests/TLVSerialize.fs b/tests/DotNetLightning.Core.Tests/TLVSerialize.fs index 2f783d651..7dc3c089a 100644 --- a/tests/DotNetLightning.Core.Tests/TLVSerialize.fs +++ b/tests/DotNetLightning.Core.Tests/TLVSerialize.fs @@ -11,65 +11,100 @@ open DotNetLightning.Serialization [] let bigSizeVarIntTests = let hex = NBitcoin.DataEncoders.HexEncoder() - let dataPath1 = Path.Join(AppDomain.CurrentDomain.BaseDirectory, "../../..", ("Data/bolt1-bigsize.json")) + + let dataPath1 = + Path.Join( + AppDomain.CurrentDomain.BaseDirectory, + "../../..", + ("Data/bolt1-bigsize.json") + ) + let testData1 = dataPath1 |> File.ReadAllText |> JsonDocument.Parse - testList "BigSize encoding tests" [ - let successTestCases = - testData1.RootElement.EnumerateArray() - |> Seq.choose(fun x -> match x.TryGetProperty("exp_error") with true, _ -> None | false, _ -> Some x) - for v in successTestCases do - yield testCase (v.GetProperty("name").GetString()) <| fun _ -> - use wms = new MemoryStream() - use writer = new LightningWriterStream(wms) - let value = v.GetProperty("value").GetUInt64() - let b = v.GetProperty("bytes").GetString() |> hex.DecodeData - let actualBytes = - writer.WriteBigSize(value) - wms.ToArray() - Expect.equal (actualBytes) b "failed to write" - - use rms = new MemoryStream(b) - use reader = new LightningReaderStream(rms) - let actualValue = reader.ReadBigSize() - Expect.equal actualValue value "failed to read" - - let failureTestCases = - testData1.RootElement.EnumerateArray() - |> Seq.choose(fun x -> match x.TryGetProperty("exp_error") with true, _ -> Some(x) | false, _ -> None) - - for v in failureTestCases do - yield testCase (v.GetProperty("name").GetString()) <| fun _ -> - use wms = new MemoryStream() - use writer = new LightningWriterStream(wms) - - let b = v.GetProperty("bytes").GetString() |> hex.DecodeData - use rms = new MemoryStream(b) - use reader = new LightningReaderStream(rms) - let isEOFError = v.GetProperty("exp_error").GetString().Contains("EOF") - if (isEOFError) then - Expect.throwsT - (fun _ -> reader.ReadBigSize() |> ignore) "should throw EOF exception" - else - Expect.throwsT - (fun _ -> reader.ReadBigSize() |> ignore) "should throw Format exception" - - - yield testProperty "Should encode-decode" <| fun (v: uint64) -> - use wms = new MemoryStream() - use writer = new LightningWriterStream(wms) - writer.WriteBigSize(v) - - use rms = new MemoryStream(wms.ToArray()) - use reader = new LightningReaderStream(rms) - let actual = reader.ReadBigSize() - Expect.equal actual v "" - ] - + testList + "BigSize encoding tests" + [ + let successTestCases = + testData1.RootElement.EnumerateArray() + |> Seq.choose(fun x -> + match x.TryGetProperty("exp_error") with + | true, _ -> None + | false, _ -> Some x + ) + + for v in successTestCases do + yield + testCase(v.GetProperty("name").GetString()) + <| fun _ -> + use wms = new MemoryStream() + use writer = new LightningWriterStream(wms) + let value = v.GetProperty("value").GetUInt64() + + let b = + v.GetProperty("bytes").GetString() |> hex.DecodeData + + let actualBytes = + writer.WriteBigSize(value) + wms.ToArray() + + Expect.equal (actualBytes) b "failed to write" + + use rms = new MemoryStream(b) + use reader = new LightningReaderStream(rms) + let actualValue = reader.ReadBigSize() + Expect.equal actualValue value "failed to read" + + let failureTestCases = + testData1.RootElement.EnumerateArray() + |> Seq.choose(fun x -> + match x.TryGetProperty("exp_error") with + | true, _ -> Some(x) + | false, _ -> None + ) + + for v in failureTestCases do + yield + testCase(v.GetProperty("name").GetString()) + <| fun _ -> + use wms = new MemoryStream() + use writer = new LightningWriterStream(wms) + + let b = + v.GetProperty("bytes").GetString() |> hex.DecodeData + + use rms = new MemoryStream(b) + use reader = new LightningReaderStream(rms) + + let isEOFError = + v + .GetProperty("exp_error") + .GetString() + .Contains("EOF") + + if isEOFError then + Expect.throwsT + (fun _ -> reader.ReadBigSize() |> ignore) + "should throw EOF exception" + else + Expect.throwsT + (fun _ -> reader.ReadBigSize() |> ignore) + "should throw Format exception" + + + yield + testProperty "Should encode-decode" + <| fun (v: uint64) -> + use wms = new MemoryStream() + use writer = new LightningWriterStream(wms) + writer.WriteBigSize(v) + + use rms = new MemoryStream(wms.ToArray()) + use reader = new LightningReaderStream(rms) + let actual = reader.ReadBigSize() + Expect.equal actual v "" + ] + let bolt4Tests2 = // let dataPath1 = Path.Join(AppDomain.CurrentDomain.BaseDirectory, "../../..", ("Data/bolt04/onion-test-multi-frame.json")) // let testData1 = dataPath1 |> File.ReadAllText |> JsonDocument.Parse - testList "bolt04 test vectors" [ - testCase "" <| fun _ -> - () - ] + testList "bolt04 test vectors" [ testCase "" <| fun _ -> () ] diff --git a/tests/DotNetLightning.Core.Tests/TransactionBolt3TestVectorTests.fs b/tests/DotNetLightning.Core.Tests/TransactionBolt3TestVectorTests.fs index 39097ede3..cdff90e40 100644 --- a/tests/DotNetLightning.Core.Tests/TransactionBolt3TestVectorTests.fs +++ b/tests/DotNetLightning.Core.Tests/TransactionBolt3TestVectorTests.fs @@ -23,507 +23,800 @@ let log = /// data formatted to json -let dataPath1 = Path.Join(AppDomain.CurrentDomain.BaseDirectory, "../../..", ("Data/bolt3-tx.json")) +let dataPath1 = + Path.Join( + AppDomain.CurrentDomain.BaseDirectory, + "../../..", + ("Data/bolt3-tx.json") + ) + let data1 = dataPath1 |> File.ReadAllText |> JsonDocument.Parse -let localPerCommitmentPoint = PerCommitmentPoint <| PubKey("025f7117a78150fe2ef97db7cfc83bd57b2e2c0d0dd25eaf467a4a1c2a45ce1486") -type LocalConfig = { - CommitTxNumber: CommitmentNumber - ToSelfDelay: BlockHeightOffset16 - DustLimit: Money - PaymentBasepointSecret: PaymentBasepointSecret - PaymentBasepoint: PaymentBasepoint - RevocationBasepointSecret: RevocationBasepointSecret - DelayedPaymentBasepointSecret: DelayedPaymentBasepointSecret - DelayedPaymentBasepoint: DelayedPaymentBasepoint - PerCommitmentPoint: PerCommitmentPoint - PaymentPrivKey: PaymentPrivKey - DelayedPaymentPrivKey: DelayedPaymentPrivKey - RevocationPubKey: RevocationPubKey - FeeRatePerKw: FeeRatePerKw - FundingPrivKey: FundingPrivKey -} -let getLocal(): LocalConfig = +let localPerCommitmentPoint = + PerCommitmentPoint + <| PubKey( + "025f7117a78150fe2ef97db7cfc83bd57b2e2c0d0dd25eaf467a4a1c2a45ce1486" + ) + +type LocalConfig = + { + CommitTxNumber: CommitmentNumber + ToSelfDelay: BlockHeightOffset16 + DustLimit: Money + PaymentBasepointSecret: PaymentBasepointSecret + PaymentBasepoint: PaymentBasepoint + RevocationBasepointSecret: RevocationBasepointSecret + DelayedPaymentBasepointSecret: DelayedPaymentBasepointSecret + DelayedPaymentBasepoint: DelayedPaymentBasepoint + PerCommitmentPoint: PerCommitmentPoint + PaymentPrivKey: PaymentPrivKey + DelayedPaymentPrivKey: DelayedPaymentPrivKey + RevocationPubKey: RevocationPubKey + FeeRatePerKw: FeeRatePerKw + FundingPrivKey: FundingPrivKey + } + +let getLocal() : LocalConfig = let paymentBasepointSecret = "1111111111111111111111111111111111111111111111111111111111111111" |> hex.DecodeData |> fun h -> new Key(h) |> PaymentBasepointSecret + let paymentBasepoint = paymentBasepointSecret.PaymentBasepoint() + let delayedPaymentBasepointSecret = "3333333333333333333333333333333333333333333333333333333333333333" |> hex.DecodeData |> fun h -> new Key(h) |> DelayedPaymentBasepointSecret + let revocationBasepointSecret = "2222222222222222222222222222222222222222222222222222222222222222" |> hex.DecodeData |> fun h -> new Key(h) |> RevocationBasepointSecret + let fundingPrivKey = - "30ff4956bbdd3222d44cc5e8a1261dab1e07957bdac5ae88fe3261ef321f3749" + "30ff4956bbdd3222d44cc5e8a1261dab1e07957bdac5ae88fe3261ef321f3749" |> hex.DecodeData |> fun h -> new Key(h) |> FundingPrivKey + { - CommitTxNumber = CommitmentNumber(UInt48.MaxValue - (UInt48.FromUInt64 42UL)) - ToSelfDelay = 144us |> BlockHeightOffset16 - DustLimit = Money.Satoshis(546L) - PaymentBasepointSecret = paymentBasepointSecret - PaymentBasepoint = paymentBasepoint - RevocationBasepointSecret = revocationBasepointSecret - DelayedPaymentBasepointSecret = delayedPaymentBasepointSecret - DelayedPaymentBasepoint = delayedPaymentBasepointSecret.DelayedPaymentBasepoint() - FundingPrivKey = fundingPrivKey - PerCommitmentPoint = localPerCommitmentPoint - PaymentPrivKey = localPerCommitmentPoint.DerivePaymentPrivKey paymentBasepointSecret - DelayedPaymentPrivKey = localPerCommitmentPoint.DeriveDelayedPaymentPrivKey delayedPaymentBasepointSecret - RevocationPubKey = RevocationPubKey <| PubKey("0212a140cd0c6539d07cd08dfe09984dec3251ea808b892efeac3ede9402bf2b19") - FeeRatePerKw = 15000u |> FeeRatePerKw + CommitTxNumber = + CommitmentNumber(UInt48.MaxValue - (UInt48.FromUInt64 42UL)) + ToSelfDelay = 144us |> BlockHeightOffset16 + DustLimit = Money.Satoshis(546L) + PaymentBasepointSecret = paymentBasepointSecret + PaymentBasepoint = paymentBasepoint + RevocationBasepointSecret = revocationBasepointSecret + DelayedPaymentBasepointSecret = delayedPaymentBasepointSecret + DelayedPaymentBasepoint = + delayedPaymentBasepointSecret.DelayedPaymentBasepoint() + FundingPrivKey = fundingPrivKey + PerCommitmentPoint = localPerCommitmentPoint + PaymentPrivKey = + localPerCommitmentPoint.DerivePaymentPrivKey paymentBasepointSecret + DelayedPaymentPrivKey = + localPerCommitmentPoint.DeriveDelayedPaymentPrivKey + delayedPaymentBasepointSecret + RevocationPubKey = + RevocationPubKey + <| PubKey( + "0212a140cd0c6539d07cd08dfe09984dec3251ea808b892efeac3ede9402bf2b19" + ) + FeeRatePerKw = 15000u |> FeeRatePerKw } -type RemoteConfig = { - CommitTxNumber: CommitmentNumber - ToSelfDelay: BlockHeightOffset16 - DustLimit: Money - PaymentBasepointSecret: PaymentBasepointSecret - PaymentBasepoint: PaymentBasepoint - RevocationBasepointSecret: RevocationBasepointSecret - RevocationBasepoint: RevocationBasepoint - FundingPrivKey: FundingPrivKey - PaymentPrivKey: PaymentPrivKey - PerCommitmentPoint: PerCommitmentPoint -} -let getRemote(): RemoteConfig = +type RemoteConfig = + { + CommitTxNumber: CommitmentNumber + ToSelfDelay: BlockHeightOffset16 + DustLimit: Money + PaymentBasepointSecret: PaymentBasepointSecret + PaymentBasepoint: PaymentBasepoint + RevocationBasepointSecret: RevocationBasepointSecret + RevocationBasepoint: RevocationBasepoint + FundingPrivKey: FundingPrivKey + PaymentPrivKey: PaymentPrivKey + PerCommitmentPoint: PerCommitmentPoint + } + +let getRemote() : RemoteConfig = let paymentBasepointSecret = "4444444444444444444444444444444444444444444444444444444444444444" |> hex.DecodeData |> fun h -> new Key(h) |> PaymentBasepointSecret + let paymentBasepoint = paymentBasepointSecret.PaymentBasepoint() + let revocationBasepointSecret = "2222222222222222222222222222222222222222222222222222222222222222" |> hex.DecodeData |> fun h -> new Key(h) |> RevocationBasepointSecret + let revocationBasepoint = revocationBasepointSecret.RevocationBasepoint() + let fundingPrivKey = "1552dfba4f6cf29a62a0af13c8d6981d36d0ef8d61ba10fb0fe90da7634d7e13" |> hex.DecodeData |> fun h -> new Key(h) |> FundingPrivKey + let perCommitmentPoint = - PerCommitmentPoint <| PubKey("022c76692fd70814a8d1ed9dedc833318afaaed8188db4d14727e2e99bc619d325") + PerCommitmentPoint + <| PubKey( + "022c76692fd70814a8d1ed9dedc833318afaaed8188db4d14727e2e99bc619d325" + ) + { - CommitTxNumber = CommitmentNumber(UInt48.MaxValue - (UInt48.FromUInt64 42UL)) - ToSelfDelay = 144us |> BlockHeightOffset16 - DustLimit = Money.Satoshis(546L) - PaymentBasepointSecret = paymentBasepointSecret - PaymentBasepoint = paymentBasepoint - RevocationBasepointSecret = revocationBasepointSecret - RevocationBasepoint = revocationBasepoint - FundingPrivKey = fundingPrivKey - PaymentPrivKey = localPerCommitmentPoint.DerivePaymentPrivKey paymentBasepointSecret - PerCommitmentPoint = perCommitmentPoint + CommitTxNumber = + CommitmentNumber(UInt48.MaxValue - (UInt48.FromUInt64 42UL)) + ToSelfDelay = 144us |> BlockHeightOffset16 + DustLimit = Money.Satoshis(546L) + PaymentBasepointSecret = paymentBasepointSecret + PaymentBasepoint = paymentBasepoint + RevocationBasepointSecret = revocationBasepointSecret + RevocationBasepoint = revocationBasepoint + FundingPrivKey = fundingPrivKey + PaymentPrivKey = + localPerCommitmentPoint.DerivePaymentPrivKey paymentBasepointSecret + PerCommitmentPoint = perCommitmentPoint } - + let n = Network.RegTest -let coinbaseTx = Transaction.Parse("01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff03510101ffffffff0100f2052a010000001976a9143ca33c2e4446f4a305f23c80df8ad1afdcf652f988ac00000000", n) -let fundingTx = Transaction.Parse("0200000001adbb20ea41a8423ea937e76e8151636bf6093b70eaff942930d20576600521fd000000006b48304502210090587b6201e166ad6af0227d3036a9454223d49a1f11839c1a362184340ef0240220577f7cd5cca78719405cbf1de7414ac027f0239ef6e214c90fcaab0454d84b3b012103535b32d5eb0a6ed0982a0479bbadc9868d9836f6ba94dd5a63be16d875069184ffffffff028096980000000000220020c015c4a6be010e21657068fc2e6a9d02b27ebe4d490a25846f7237f104d1a3cd20256d29010000001600143ca33c2e4446f4a305f23c80df8ad1afdcf652f900000000", n) +let coinbaseTx = + Transaction.Parse( + "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff03510101ffffffff0100f2052a010000001976a9143ca33c2e4446f4a305f23c80df8ad1afdcf652f988ac00000000", + n + ) + +let fundingTx = + Transaction.Parse( + "0200000001adbb20ea41a8423ea937e76e8151636bf6093b70eaff942930d20576600521fd000000006b48304502210090587b6201e166ad6af0227d3036a9454223d49a1f11839c1a362184340ef0240220577f7cd5cca78719405cbf1de7414ac027f0239ef6e214c90fcaab0454d84b3b012103535b32d5eb0a6ed0982a0479bbadc9868d9836f6ba94dd5a63be16d875069184ffffffff028096980000000000220020c015c4a6be010e21657068fc2e6a9d02b27ebe4d490a25846f7237f104d1a3cd20256d29010000001600143ca33c2e4446f4a305f23c80df8ad1afdcf652f900000000", + n + ) + let fundingAmount = fundingTx.Outputs.[0].Value log(sprintf "# funding-tx: %A" fundingTx) let local = getLocal() let remote = getRemote() + let fundingRedeem = Scripts.funding (local.FundingPrivKey.FundingPubKey()) (remote.FundingPrivKey.FundingPubKey()) let commitmentInputScriptCoin = - Coin(fundingTx.GetHash(), 0u, fundingAmount, fundingRedeem.WitHash.ScriptPubKey) + Coin( + fundingTx.GetHash(), + 0u, + fundingAmount, + fundingRedeem.WitHash.ScriptPubKey + ) |> fun c -> ScriptCoin(c, fundingRedeem) -log (sprintf "local payment basepoint is %A" local.PaymentBasepoint) -log (sprintf "remote payment basepoint is %A" remote.PaymentBasepoint) +log(sprintf "local payment basepoint is %A" local.PaymentBasepoint) +log(sprintf "remote payment basepoint is %A" remote.PaymentBasepoint) + let obscuredTxNumber = - let commitmentNumber = CommitmentNumber(UInt48.MaxValue - (UInt48.FromUInt64 42UL)) + let commitmentNumber = + CommitmentNumber(UInt48.MaxValue - (UInt48.FromUInt64 42UL)) + commitmentNumber.Obscure true local.PaymentBasepoint remote.PaymentBasepoint -Expect.equal obscuredTxNumber (0x2bb038521914UL ^^^ 42UL |> UInt48.FromUInt64 |> ObscuredCommitmentNumber) "" + +Expect.equal + obscuredTxNumber + (0x2bb038521914UL ^^^ 42UL |> UInt48.FromUInt64 |> ObscuredCommitmentNumber) + "" sprintf "local_payment_basepoint: %A " local.PaymentBasepoint |> log sprintf "remote_payment_basepoint: %A" remote.PaymentBasepoint |> log sprintf "local_funding_privkey: %A" local.FundingPrivKey |> log sprintf "local_funding_pubkey: %A" (local.FundingPrivKey.FundingPubKey()) |> log sprintf "remote_funding_privkey: %A" remote.FundingPrivKey |> log -sprintf "remote_funding_pubkey: %A" (remote.FundingPrivKey.FundingPubKey()) |> log + +sprintf "remote_funding_pubkey: %A" (remote.FundingPrivKey.FundingPubKey()) +|> log + sprintf "local_secretkey: %A" local.PaymentPrivKey |> log sprintf "localkey: %A" (local.PaymentPrivKey.PaymentPubKey()) |> log sprintf "remotekey: %A" remote.PaymentPrivKey |> log -sprintf "local_delayedkey: %A" (local.DelayedPaymentPrivKey.DelayedPaymentPubKey()) |> log + +sprintf + "local_delayedkey: %A" + (local.DelayedPaymentPrivKey.DelayedPaymentPubKey()) +|> log + sprintf "local_revocation_key: %A" local.RevocationPubKey |> log sprintf "# funding wscript = %A" fundingRedeem |> log -assert(fundingRedeem = Script.FromBytesUnsafe(hex.DecodeData "5221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae")) + +assert + (fundingRedeem = Script.FromBytesUnsafe( + hex.DecodeData + "5221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae" + )) let paymentPreImages = - let _s = ([ + let _s = + ([ ("0000000000000000000000000000000000000000000000000000000000000000") ("0101010101010101010101010101010101010101010101010101010101010101") ("0202020202020202020202020202020202020202020202020202020202020202") ("0303030303030303030303030303030303030303030303030303030303030303") ("0404040404040404040404040404040404040404040404040404040404040404") ]) + _s |> List.map(hex.DecodeData) |> List.map(PaymentPreimage.Create) - + type h = DirectedHTLC -log (sprintf "first payment hash is %A" paymentPreImages.[0].Hash) -let incomingHtlcs = [ - { - UpdateAddHTLCMsg.ChannelId = ChannelId.Zero; - HTLCId = HTLCId.Zero; - Amount = LNMoney.MilliSatoshis 1000000L - PaymentHash = paymentPreImages.[0].Hash - CLTVExpiry = 500u |> BlockHeight; - OnionRoutingPacket = OnionPacket.LastPacket - } - { - UpdateAddHTLCMsg.ChannelId = ChannelId.Zero; - HTLCId = HTLCId(1UL); - Amount = LNMoney.MilliSatoshis 2000000L - PaymentHash = paymentPreImages.[1].Hash - CLTVExpiry = 501u |> BlockHeight; - OnionRoutingPacket = OnionPacket.LastPacket - } - { - UpdateAddHTLCMsg.ChannelId = ChannelId.Zero; - HTLCId = HTLCId(4UL); - Amount = LNMoney.MilliSatoshis 4000000L - PaymentHash = paymentPreImages.[4].Hash - CLTVExpiry = 504u |> BlockHeight; - OnionRoutingPacket = OnionPacket.LastPacket - } -] -let outgoingHtlcs = [ - { - UpdateAddHTLCMsg.ChannelId = ChannelId.Zero; - HTLCId = HTLCId(2UL); - Amount = LNMoney.MilliSatoshis 2000000L - PaymentHash = paymentPreImages.[2].Hash - CLTVExpiry = 502u |> BlockHeight; - OnionRoutingPacket = OnionPacket.LastPacket - } - { - UpdateAddHTLCMsg.ChannelId = ChannelId.Zero; - HTLCId = HTLCId(3UL); - Amount = LNMoney.MilliSatoshis 3000000L - PaymentHash = paymentPreImages.[3].Hash - CLTVExpiry = 503u |> BlockHeight; - OnionRoutingPacket = OnionPacket.LastPacket - } -] +log(sprintf "first payment hash is %A" paymentPreImages.[0].Hash) + +let incomingHtlcs = + [ + { + UpdateAddHTLCMsg.ChannelId = ChannelId.Zero + HTLCId = HTLCId.Zero + Amount = LNMoney.MilliSatoshis 1000000L + PaymentHash = paymentPreImages.[0].Hash + CLTVExpiry = 500u |> BlockHeight + OnionRoutingPacket = OnionPacket.LastPacket + } + { + UpdateAddHTLCMsg.ChannelId = ChannelId.Zero + HTLCId = HTLCId(1UL) + Amount = LNMoney.MilliSatoshis 2000000L + PaymentHash = paymentPreImages.[1].Hash + CLTVExpiry = 501u |> BlockHeight + OnionRoutingPacket = OnionPacket.LastPacket + } + { + UpdateAddHTLCMsg.ChannelId = ChannelId.Zero + HTLCId = HTLCId(4UL) + Amount = LNMoney.MilliSatoshis 4000000L + PaymentHash = paymentPreImages.[4].Hash + CLTVExpiry = 504u |> BlockHeight + OnionRoutingPacket = OnionPacket.LastPacket + } + ] + +let outgoingHtlcs = + [ + { + UpdateAddHTLCMsg.ChannelId = ChannelId.Zero + HTLCId = HTLCId(2UL) + Amount = LNMoney.MilliSatoshis 2000000L + PaymentHash = paymentPreImages.[2].Hash + CLTVExpiry = 502u |> BlockHeight + OnionRoutingPacket = OnionPacket.LastPacket + } + { + UpdateAddHTLCMsg.ChannelId = ChannelId.Zero + HTLCId = HTLCId(3UL) + Amount = LNMoney.MilliSatoshis 3000000L + PaymentHash = paymentPreImages.[3].Hash + CLTVExpiry = 503u |> BlockHeight + OnionRoutingPacket = OnionPacket.LastPacket + } + ] + let incomingHtlcScripts = incomingHtlcs - |> List.map - (fun htlc -> - Scripts.htlcReceived - // FIXME: payment keys being used as htlc keys?? - (HtlcPubKey <| local.PaymentPrivKey.PaymentPubKey().RawPubKey()) - (HtlcPubKey <| remote.PaymentPrivKey.PaymentPubKey().RawPubKey()) - (local.RevocationPubKey) - (htlc.PaymentHash) - (htlc.CLTVExpiry.Value) - ) + |> List.map(fun htlc -> + Scripts.htlcReceived + // FIXME: payment keys being used as htlc keys?? + (HtlcPubKey <| local.PaymentPrivKey.PaymentPubKey().RawPubKey()) + (HtlcPubKey <| remote.PaymentPrivKey.PaymentPubKey().RawPubKey()) + (local.RevocationPubKey) + (htlc.PaymentHash) + (htlc.CLTVExpiry.Value) + ) + let outgoingHtlcScripts = outgoingHtlcs - |> List.map - (fun htlc -> - Scripts.htlcOffered - // FIXME: payment keys being used as htlc keys?? - (HtlcPubKey <| local.PaymentPrivKey.PaymentPubKey().RawPubKey()) - (HtlcPubKey <| remote.PaymentPrivKey.PaymentPubKey().RawPubKey()) - (local.RevocationPubKey) - (htlc.PaymentHash) - ) + |> List.map(fun htlc -> + Scripts.htlcOffered + // FIXME: payment keys being used as htlc keys?? + (HtlcPubKey <| local.PaymentPrivKey.PaymentPubKey().RawPubKey()) + (HtlcPubKey <| remote.PaymentPrivKey.PaymentPubKey().RawPubKey()) + (local.RevocationPubKey) + (htlc.PaymentHash) + ) -let run (spec: CommitmentSpec): (Transaction * _) = +let run(spec: CommitmentSpec) : (Transaction * _) = let local = getLocal() - log (sprintf "to_local_msat %A" spec.ToLocal) - log (sprintf "to_remote_msat %A" spec.ToRemote) - log (sprintf "local_feerate_per_kw %A" spec.FeeRatePerKw) - + log(sprintf "to_local_msat %A" spec.ToLocal) + log(sprintf "to_remote_msat %A" spec.ToRemote) + log(sprintf "local_feerate_per_kw %A" spec.FeeRatePerKw) + let commitTx = - let commitTx = Transactions.makeCommitTx - (commitmentInputScriptCoin) - (local.CommitTxNumber) - (local.PaymentBasepoint) - (remote.PaymentBasepoint) - (true) - (local.DustLimit) - (local.RevocationPubKey) - (local.ToSelfDelay) - (local.DelayedPaymentPrivKey.DelayedPaymentPubKey()) - (remote.PaymentPrivKey.PaymentPubKey()) - // FIXME: payment keys being used as htlc keys?? - (HtlcPubKey <| local.PaymentPrivKey.PaymentPubKey().RawPubKey()) - (HtlcPubKey <| remote.PaymentPrivKey.PaymentPubKey().RawPubKey()) - (spec) - (n) + let commitTx = + Transactions.makeCommitTx + (commitmentInputScriptCoin) + (local.CommitTxNumber) + (local.PaymentBasepoint) + (remote.PaymentBasepoint) + (true) + (local.DustLimit) + (local.RevocationPubKey) + (local.ToSelfDelay) + (local.DelayedPaymentPrivKey.DelayedPaymentPubKey()) + (remote.PaymentPrivKey.PaymentPubKey()) + // FIXME: payment keys being used as htlc keys?? + (HtlcPubKey <| local.PaymentPrivKey.PaymentPubKey().RawPubKey()) + (HtlcPubKey <| remote.PaymentPrivKey.PaymentPubKey().RawPubKey()) + (spec) + (n) // test vectors requires us to use RFC6974 - let localSig, tx2 = Transactions.signCore(commitTx, local.FundingPrivKey.RawKey(), false) - let remoteSig, tx3 = Transactions.signCore(tx2, remote.FundingPrivKey.RawKey(), false) - Transactions.checkSigAndAdd (tx3) (localSig) (local.FundingPrivKey.FundingPubKey().RawPubKey()) + let localSig, tx2 = + Transactions.signCore( + commitTx, + local.FundingPrivKey.RawKey(), + false + ) + + let remoteSig, tx3 = + Transactions.signCore(tx2, remote.FundingPrivKey.RawKey(), false) + + Transactions.checkSigAndAdd + (tx3) + (localSig) + (local.FundingPrivKey.FundingPubKey().RawPubKey()) >>= fun tx4 -> - Transactions.checkSigAndAdd (tx4) (remoteSig) (remote.FundingPrivKey.FundingPubKey().RawPubKey()) - |> function Ok e -> e | Error e -> failwithf "%A" e - let baseFee = Transactions.commitTxFee(local.DustLimit)(spec) - log (sprintf "base commitment transaction fee is %A" baseFee) - let actualFee = fundingAmount - match commitTx.Value.TryGetFee() with - | true, f -> f | false, _ -> failwith "fail: BOLT3 test, couldn't get fee" - log (sprintf "actual commitment tx fee is %A " actualFee) + Transactions.checkSigAndAdd + (tx4) + (remoteSig) + (remote.FundingPrivKey.FundingPubKey().RawPubKey()) + |> function + | Ok e -> e + | Error e -> failwithf "%A" e + + let baseFee = Transactions.commitTxFee (local.DustLimit) (spec) + log(sprintf "base commitment transaction fee is %A" baseFee) + + let actualFee = + fundingAmount + - match commitTx.Value.TryGetFee() with + | true, f -> f + | false, _ -> failwith "fail: BOLT3 test, couldn't get fee" + + log(sprintf "actual commitment tx fee is %A " actualFee) + commitTx.Value.GetGlobalTransaction().Outputs - |> List.ofSeq - |> List.iter(fun txOut -> match txOut.ScriptPubKey.Length with - | 22 -> log(sprintf "to-remote amount %A P2WPKH(%A)" (txOut.Value) (remote.PaymentPrivKey.PaymentPubKey())) - | 34 -> - let htlcScriptOpt = - Option.orElse - (incomingHtlcScripts |> List.tryFind(fun s -> s.WitHash.ScriptPubKey = txOut.ScriptPubKey)) - (outgoingHtlcScripts |> List.tryFind(fun s -> s.WitHash.ScriptPubKey = txOut.ScriptPubKey)) - match htlcScriptOpt with - | None -> - (sprintf "to-local amount %A. \n to-local wscript (%A)" - txOut.Value - (Scripts.toLocalDelayed(local.RevocationPubKey) - (local.ToSelfDelay) - (local.DelayedPaymentPrivKey.DelayedPaymentPubKey()))) - |> log - | Some htlcScript -> - (sprintf "to-local amount %A \n to-local wscript (%A)" txOut.Value htlcScript) - |> log - | x -> failwithf "unexpected scriptPubKey length %A" x) + |> List.ofSeq + |> List.iter(fun txOut -> + match txOut.ScriptPubKey.Length with + | 22 -> + log( + sprintf + "to-remote amount %A P2WPKH(%A)" + (txOut.Value) + (remote.PaymentPrivKey.PaymentPubKey()) + ) + | 34 -> + let htlcScriptOpt = + Option.orElse + (incomingHtlcScripts + |> List.tryFind(fun s -> + s.WitHash.ScriptPubKey = txOut.ScriptPubKey + )) + (outgoingHtlcScripts + |> List.tryFind(fun s -> + s.WitHash.ScriptPubKey = txOut.ScriptPubKey + )) + + match htlcScriptOpt with + | None -> + (sprintf + "to-local amount %A. \n to-local wscript (%A)" + txOut.Value + (Scripts.toLocalDelayed + (local.RevocationPubKey) + (local.ToSelfDelay) + (local.DelayedPaymentPrivKey.DelayedPaymentPubKey()))) + |> log + | Some htlcScript -> + (sprintf + "to-local amount %A \n to-local wscript (%A)" + txOut.Value + htlcScript) + |> log + | x -> failwithf "unexpected scriptPubKey length %A" x + ) + let actualCommitTxNumOpt = Transactions.getCommitTxNumber (commitTx.Value.GetGlobalTransaction()) (true) (local.PaymentBasepoint) (remote.PaymentBasepoint) + let expectedCommitTxNumber = local.CommitTxNumber Expect.equal actualCommitTxNumOpt.Value (expectedCommitTxNumber) "" commitTx.Value.Finalize() |> ignore Expect.isTrue (commitTx.Value.CanExtractTransaction()) "" sprintf "output commit_tx %A" commitTx.Value |> log + let (unsignedHTLCTimeoutTxs, unsignedHTLCSuccessTxs) = - Transactions.makeHTLCTxs(commitTx.Value.ExtractTransaction()) - local.DustLimit - local.RevocationPubKey - local.ToSelfDelay - (local.DelayedPaymentPrivKey.DelayedPaymentPubKey()) - // FIXME: payment keys being used as htlc keys?? - (HtlcPubKey <| local.PaymentPrivKey.PaymentPubKey().RawPubKey()) - (HtlcPubKey <| remote.PaymentPrivKey.PaymentPubKey().RawPubKey()) - spec - n - |> Result.defaultWith(fun _ -> failwith "fail(BOLT3 transactions): couldn't make HTLC transactions") + Transactions.makeHTLCTxs + (commitTx.Value.ExtractTransaction()) + local.DustLimit + local.RevocationPubKey + local.ToSelfDelay + (local.DelayedPaymentPrivKey.DelayedPaymentPubKey()) + // FIXME: payment keys being used as htlc keys?? + (HtlcPubKey <| local.PaymentPrivKey.PaymentPubKey().RawPubKey()) + (HtlcPubKey <| remote.PaymentPrivKey.PaymentPubKey().RawPubKey()) + spec + n + |> Result.defaultWith(fun _ -> + failwith "fail(BOLT3 transactions): couldn't make HTLC transactions" + ) + let htlcTxs = - Seq.append (unsignedHTLCSuccessTxs |> Seq.cast) (unsignedHTLCTimeoutTxs |> Seq.cast) + Seq.append + (unsignedHTLCSuccessTxs |> Seq.cast) + (unsignedHTLCTimeoutTxs |> Seq.cast) + sprintf "num htlcs: %A" htlcTxs |> log - let htlcTxs = htlcTxs - |> Seq.toList - |> List.sortBy(fun x -> x.Value.GetGlobalTransaction().Inputs.[0].PrevOut.N) + + let htlcTxs = + htlcTxs + |> Seq.toList + |> List.sortBy(fun x -> + x.Value.GetGlobalTransaction().Inputs.[0] + .PrevOut + .N + ) + let signedTxs = htlcTxs - |> List.map(fun htlcTx -> match htlcTx with - | :? HTLCSuccessTx as tx -> - let localSig, tx2 = Transactions.signCore(tx, local.PaymentPrivKey.RawKey(), false) - let remoteSig, _tx3 = Transactions.signCore(tx2, remote.PaymentPrivKey.RawKey(), false) - // just checking preimage is in global list - let paymentPreimage = (paymentPreImages |> List.find(fun p -> p.Hash = tx.PaymentHash)) - log (sprintf "Finalizing %A" tx) - match tx.Finalize(localSig, remoteSig, paymentPreimage) with - | Ok tx -> tx - | Error e -> failwithf "%A" e - | :? HTLCTimeoutTx as tx -> - let localSig, _ = Transactions.signCore(tx, local.PaymentPrivKey.RawKey(), false) - let remoteSig, _ = Transactions.signCore(tx, remote.PaymentPrivKey.RawKey(), false) - match tx.Finalize(localSig, remoteSig) with - | Ok tx -> tx - | Error e -> failwithf "%A" e - | _ -> failwith "unreachable") + |> List.map(fun htlcTx -> + match htlcTx with + | :? HTLCSuccessTx as tx -> + let localSig, tx2 = + Transactions.signCore( + tx, + local.PaymentPrivKey.RawKey(), + false + ) + + let remoteSig, _tx3 = + Transactions.signCore( + tx2, + remote.PaymentPrivKey.RawKey(), + false + ) + // just checking preimage is in global list + let paymentPreimage = + (paymentPreImages + |> List.find(fun p -> p.Hash = tx.PaymentHash)) + + log(sprintf "Finalizing %A" tx) + + match tx.Finalize(localSig, remoteSig, paymentPreimage) with + | Ok tx -> tx + | Error e -> failwithf "%A" e + | :? HTLCTimeoutTx as tx -> + let localSig, _ = + Transactions.signCore( + tx, + local.PaymentPrivKey.RawKey(), + false + ) + + let remoteSig, _ = + Transactions.signCore( + tx, + remote.PaymentPrivKey.RawKey(), + false + ) + + match tx.Finalize(localSig, remoteSig) with + | Ok tx -> tx + | Error e -> failwithf "%A" e + | _ -> failwith "unreachable" + ) + commitTx.Value.ExtractTransaction(), signedTxs -let testVectors = data1.RootElement.GetProperty("test_vectors").EnumerateArray() |> Seq.toArray +let testVectors = + data1 + .RootElement + .GetProperty("test_vectors") + .EnumerateArray() + |> Seq.toArray let incomingHtlcMap = incomingHtlcs |> List.map(fun htlc -> htlc.HTLCId, htlc) |> Map.ofList + let outgoingHtlcMap = outgoingHtlcs |> List.map(fun htlc -> htlc.HTLCId, htlc) |> Map.ofList -let runTest(testCase: JsonElement) (spec) (expectedOutputCount) = - log (sprintf "testing %A" (testCase.GetProperty("name").GetString())) - let commitTx, htlcTxs = run (spec) - Expect.equal(commitTx.Outputs.Count) (expectedOutputCount) "" - let expectedTx = testCase.GetProperty("output_commit_tx").GetString() +let runTest (testCase: JsonElement) spec expectedOutputCount = + log(sprintf "testing %A" (testCase.GetProperty("name").GetString())) + let commitTx, htlcTxs = run(spec) + Expect.equal (commitTx.Outputs.Count) (expectedOutputCount) "" + + let expectedTx = + testCase + .GetProperty("output_commit_tx") + .GetString() + Expect.equal (commitTx.ToHex()) (expectedTx) "" - let expectedHTLC = testCase.GetProperty("htlc_output_txs").EnumerateArray() + + let expectedHTLC = + testCase + .GetProperty("htlc_output_txs") + .EnumerateArray() + expectedHTLC - |> Seq.iteri( fun i htlc -> - Expect.equal (htlcTxs.[i].ToHex()) (htlc.GetProperty("value").GetString()) "" - ) + |> Seq.iteri(fun i htlc -> + Expect.equal + (htlcTxs.[i].ToHex()) + (htlc.GetProperty("value").GetString()) + "" + ) -let specBase = { - CommitmentSpec.IncomingHTLCs = incomingHtlcMap - CommitmentSpec.OutgoingHTLCs = outgoingHtlcMap - FeeRatePerKw = 15000u |> FeeRatePerKw - ToLocal = LNMoney.MilliSatoshis(6988000000L) - ToRemote = 3000000000L |> LNMoney.MilliSatoshis -} +let specBase = + { + CommitmentSpec.IncomingHTLCs = incomingHtlcMap + CommitmentSpec.OutgoingHTLCs = outgoingHtlcMap + FeeRatePerKw = 15000u |> FeeRatePerKw + ToLocal = LNMoney.MilliSatoshis(6988000000L) + ToRemote = 3000000000L |> LNMoney.MilliSatoshis + } [] let tests = - testList "Transaction test vectors" [ - testCase "simple commitment tx with no HTLCs" <| fun _ -> - let spec = { - CommitmentSpec.OutgoingHTLCs = Map.empty - CommitmentSpec.IncomingHTLCs = Map.empty - FeeRatePerKw = 15000u |> FeeRatePerKw - ToLocal = LNMoney.MilliSatoshis(7000000000L) - ToRemote = 3000000000L |> LNMoney.MilliSatoshis - } - let commitTx, _htlcTxs = run(spec) - let testCase = testVectors.[0] - Expect.equal(commitTx.Outputs.Count) (2) "" - let expectedTx = - testCase.GetProperty("output_commit_tx").GetString() - Expect.equal(commitTx.ToHex()) (expectedTx) "" - - testCase "commitment tx with all 5 htlcs untrimmed (minimum feerate)" <| fun _ -> - let testCase = testVectors.[1] - let spec = { - CommitmentSpec.IncomingHTLCs = incomingHtlcMap - CommitmentSpec.OutgoingHTLCs = outgoingHtlcMap - FeeRatePerKw = 0u |> FeeRatePerKw - ToLocal = 6988000000L |> LNMoney.MilliSatoshis - ToRemote = 3000000000L |> LNMoney.MilliSatoshis - } - runTest (testCase) (spec) (7) - - testCase "commitment tx with seven outputs untrimmed (maximum feerate)" <| fun _ -> - let testCase = testVectors.[2] - let spec = { - CommitmentSpec.IncomingHTLCs = incomingHtlcMap - CommitmentSpec.OutgoingHTLCs = outgoingHtlcMap - FeeRatePerKw = (454999UL / Constants.HTLC_SUCCESS_WEIGHT) |> uint32 |> FeeRatePerKw - ToLocal = 6988000000L |> LNMoney.MilliSatoshis - ToRemote = 3000000000L |> LNMoney.MilliSatoshis - } - runTest (testCase) (spec) (7) - - testCase "commitment tx with six outputs untrimmed (minimum feerate)" <| fun _ -> - let testCase = testVectors.[3] - let spec = { specBase with - FeeRatePerKw = (454999UL / Constants.HTLC_SUCCESS_WEIGHT) - |> ((+)1UL) |> uint32 |> FeeRatePerKw } - runTest (testCase) (spec) (6) - - testCase "commitment tx with six outputs untrimmed (maximum feerate)" <| fun _ -> - let testCase = testVectors.[4] - let spec = { specBase with - FeeRatePerKw = (1454999UL / Constants.HTLC_SUCCESS_WEIGHT) - |> uint32 |> FeeRatePerKw } - runTest (testCase) (spec) (6) - - testCase "commitment tx with five outputs untrimmed (minimum feerate)" <| fun _ -> - let testCase = testVectors.[5] - let spec = { specBase with - FeeRatePerKw = (1454999UL / Constants.HTLC_SUCCESS_WEIGHT) - |> ((+)1UL)|> uint32 |> FeeRatePerKw } - runTest (testCase) (spec) (5) - - testCase "commitment tx with five outputs untrimmed (maximum feerate)" <| fun _ -> - let testCase = testVectors.[6] - let spec = { specBase with - FeeRatePerKw = (1454999UL / Constants.HTLC_TIMEOUT_WEIGHT) - |> uint32 |> FeeRatePerKw } - runTest (testCase) (spec) (5) - - testCase "commitment tx with four outputs untrimmed (minimum feerate)" <| fun _ -> - let testCase = testVectors.[7] - let spec = { specBase with - FeeRatePerKw = (1454999UL / Constants.HTLC_TIMEOUT_WEIGHT) - |> ((+)1UL) |> uint32 |> FeeRatePerKw } - runTest (testCase) (spec) (4) - - testCase "commitment tx with four outputs untrimmed (maximum feerate)" <| fun _ -> - let testCase = testVectors.[8] - let spec = { specBase with - FeeRatePerKw = (2454999UL / Constants.HTLC_TIMEOUT_WEIGHT) - |> uint32 |> FeeRatePerKw } - runTest (testCase) (spec) (4) - - testCase "commitment tx with three outputs untrimmed (minimum feerate)" <| fun _ -> - let feeRate = 2454999UL / Constants.HTLC_TIMEOUT_WEIGHT - let spec = { - CommitmentSpec.IncomingHTLCs = incomingHtlcMap - CommitmentSpec.OutgoingHTLCs = outgoingHtlcMap - FeeRatePerKw = feeRate + 1UL |> uint32 |> FeeRatePerKw - ToLocal = 6988000000L |> LNMoney.MilliSatoshis - ToRemote = 3000000000L |> LNMoney.MilliSatoshis - } - let testCase = testVectors.[9] - runTest (testCase) (spec) (3) - - testCase "commitment tx with three outputs untrimmed (maximum feerate)" <| fun _ -> - let feeRate = 3454999UL / Constants.HTLC_SUCCESS_WEIGHT - let spec = { - CommitmentSpec.IncomingHTLCs = incomingHtlcMap - CommitmentSpec.OutgoingHTLCs = outgoingHtlcMap - FeeRatePerKw = feeRate |> uint32 |> FeeRatePerKw - ToLocal = 6988000000L |> LNMoney.MilliSatoshis - ToRemote = 3000000000L |> LNMoney.MilliSatoshis - } - let testCase = testVectors.[10] - runTest (testCase) (spec) (3) - - testCase "commitment tx with two outputs untrimmed (minimum feerate)" <| fun _ -> - let feeRate = 3454999UL / Constants.HTLC_SUCCESS_WEIGHT - let spec = { - CommitmentSpec.IncomingHTLCs = incomingHtlcMap - CommitmentSpec.OutgoingHTLCs = outgoingHtlcMap - FeeRatePerKw = feeRate + 1UL |> uint32 |> FeeRatePerKw - ToLocal = 6988000000L |> LNMoney.MilliSatoshis - ToRemote = 3000000000L |> LNMoney.MilliSatoshis - } - let testCase = testVectors.[11] - runTest (testCase) (spec) (2) - - testCase "commitment tx with two outputs untrimmed (maximum feerate)" <| fun _ -> - let spec = { - CommitmentSpec.IncomingHTLCs = incomingHtlcMap - CommitmentSpec.OutgoingHTLCs = outgoingHtlcMap - FeeRatePerKw = 9651180u |> FeeRatePerKw - ToLocal = 6988000000L |> LNMoney.MilliSatoshis - ToRemote = 3000000000L |> LNMoney.MilliSatoshis - } - let testCase = testVectors.[12] - runTest (testCase) (spec) (2) - - testCase "commitment tx with one output untrimmed (minimum feerate)" <| fun _ -> - let testCase = testVectors.[13] - let spec = { specBase with - FeeRatePerKw = (9651181u |> FeeRatePerKw) } - runTest (testCase) (spec) (1) - - testCase "commitment tx with fee greater than funder amount" <| fun _ -> - let spec = { - CommitmentSpec.IncomingHTLCs = incomingHtlcMap - CommitmentSpec.OutgoingHTLCs = outgoingHtlcMap - FeeRatePerKw = 9651936u |> FeeRatePerKw; - ToLocal = 6988000000L |> LNMoney.MilliSatoshis - ToRemote = 3000000000L |> LNMoney.MilliSatoshis - } - let testCase = testVectors.[14] - runTest(testCase) (spec) (1) - - ] + testList + "Transaction test vectors" + [ + testCase "simple commitment tx with no HTLCs" + <| fun _ -> + let spec = + { + CommitmentSpec.OutgoingHTLCs = Map.empty + CommitmentSpec.IncomingHTLCs = Map.empty + FeeRatePerKw = 15000u |> FeeRatePerKw + ToLocal = LNMoney.MilliSatoshis(7000000000L) + ToRemote = 3000000000L |> LNMoney.MilliSatoshis + } + + let commitTx, _htlcTxs = run(spec) + let testCase = testVectors.[0] + Expect.equal (commitTx.Outputs.Count) (2) "" + + let expectedTx = + testCase + .GetProperty("output_commit_tx") + .GetString() + + Expect.equal (commitTx.ToHex()) (expectedTx) "" + + testCase + "commitment tx with all 5 htlcs untrimmed (minimum feerate)" + <| fun _ -> + let testCase = testVectors.[1] + + let spec = + { + CommitmentSpec.IncomingHTLCs = incomingHtlcMap + CommitmentSpec.OutgoingHTLCs = outgoingHtlcMap + FeeRatePerKw = 0u |> FeeRatePerKw + ToLocal = 6988000000L |> LNMoney.MilliSatoshis + ToRemote = 3000000000L |> LNMoney.MilliSatoshis + } + + runTest (testCase) (spec) (7) + + testCase + "commitment tx with seven outputs untrimmed (maximum feerate)" + <| fun _ -> + let testCase = testVectors.[2] + + let spec = + { + CommitmentSpec.IncomingHTLCs = incomingHtlcMap + CommitmentSpec.OutgoingHTLCs = outgoingHtlcMap + FeeRatePerKw = + (454999UL / Constants.HTLC_SUCCESS_WEIGHT) + |> uint32 + |> FeeRatePerKw + ToLocal = 6988000000L |> LNMoney.MilliSatoshis + ToRemote = 3000000000L |> LNMoney.MilliSatoshis + } + + runTest (testCase) (spec) (7) + + testCase + "commitment tx with six outputs untrimmed (minimum feerate)" + <| fun _ -> + let testCase = testVectors.[3] + + let spec = + { specBase with + FeeRatePerKw = + (454999UL / Constants.HTLC_SUCCESS_WEIGHT) + |> ((+) 1UL) + |> uint32 + |> FeeRatePerKw + } + + runTest (testCase) (spec) (6) + + testCase + "commitment tx with six outputs untrimmed (maximum feerate)" + <| fun _ -> + let testCase = testVectors.[4] + + let spec = + { specBase with + FeeRatePerKw = + (1454999UL / Constants.HTLC_SUCCESS_WEIGHT) + |> uint32 + |> FeeRatePerKw + } + + runTest (testCase) (spec) (6) + + testCase + "commitment tx with five outputs untrimmed (minimum feerate)" + <| fun _ -> + let testCase = testVectors.[5] + + let spec = + { specBase with + FeeRatePerKw = + (1454999UL / Constants.HTLC_SUCCESS_WEIGHT) + |> ((+) 1UL) + |> uint32 + |> FeeRatePerKw + } + + runTest (testCase) (spec) (5) + + testCase + "commitment tx with five outputs untrimmed (maximum feerate)" + <| fun _ -> + let testCase = testVectors.[6] + + let spec = + { specBase with + FeeRatePerKw = + (1454999UL / Constants.HTLC_TIMEOUT_WEIGHT) + |> uint32 + |> FeeRatePerKw + } + + runTest (testCase) (spec) (5) + + testCase + "commitment tx with four outputs untrimmed (minimum feerate)" + <| fun _ -> + let testCase = testVectors.[7] + + let spec = + { specBase with + FeeRatePerKw = + (1454999UL / Constants.HTLC_TIMEOUT_WEIGHT) + |> ((+) 1UL) + |> uint32 + |> FeeRatePerKw + } + + runTest (testCase) (spec) (4) + + testCase + "commitment tx with four outputs untrimmed (maximum feerate)" + <| fun _ -> + let testCase = testVectors.[8] + + let spec = + { specBase with + FeeRatePerKw = + (2454999UL / Constants.HTLC_TIMEOUT_WEIGHT) + |> uint32 + |> FeeRatePerKw + } + + runTest (testCase) (spec) (4) + + testCase + "commitment tx with three outputs untrimmed (minimum feerate)" + <| fun _ -> + let feeRate = 2454999UL / Constants.HTLC_TIMEOUT_WEIGHT + + let spec = + { + CommitmentSpec.IncomingHTLCs = incomingHtlcMap + CommitmentSpec.OutgoingHTLCs = outgoingHtlcMap + FeeRatePerKw = feeRate + 1UL |> uint32 |> FeeRatePerKw + ToLocal = 6988000000L |> LNMoney.MilliSatoshis + ToRemote = 3000000000L |> LNMoney.MilliSatoshis + } + + let testCase = testVectors.[9] + runTest (testCase) (spec) (3) + + testCase + "commitment tx with three outputs untrimmed (maximum feerate)" + <| fun _ -> + let feeRate = 3454999UL / Constants.HTLC_SUCCESS_WEIGHT + + let spec = + { + CommitmentSpec.IncomingHTLCs = incomingHtlcMap + CommitmentSpec.OutgoingHTLCs = outgoingHtlcMap + FeeRatePerKw = feeRate |> uint32 |> FeeRatePerKw + ToLocal = 6988000000L |> LNMoney.MilliSatoshis + ToRemote = 3000000000L |> LNMoney.MilliSatoshis + } + + let testCase = testVectors.[10] + runTest (testCase) (spec) (3) + + testCase + "commitment tx with two outputs untrimmed (minimum feerate)" + <| fun _ -> + let feeRate = 3454999UL / Constants.HTLC_SUCCESS_WEIGHT + + let spec = + { + CommitmentSpec.IncomingHTLCs = incomingHtlcMap + CommitmentSpec.OutgoingHTLCs = outgoingHtlcMap + FeeRatePerKw = feeRate + 1UL |> uint32 |> FeeRatePerKw + ToLocal = 6988000000L |> LNMoney.MilliSatoshis + ToRemote = 3000000000L |> LNMoney.MilliSatoshis + } + + let testCase = testVectors.[11] + runTest (testCase) (spec) (2) + + testCase + "commitment tx with two outputs untrimmed (maximum feerate)" + <| fun _ -> + let spec = + { + CommitmentSpec.IncomingHTLCs = incomingHtlcMap + CommitmentSpec.OutgoingHTLCs = outgoingHtlcMap + FeeRatePerKw = 9651180u |> FeeRatePerKw + ToLocal = 6988000000L |> LNMoney.MilliSatoshis + ToRemote = 3000000000L |> LNMoney.MilliSatoshis + } + + let testCase = testVectors.[12] + runTest (testCase) (spec) (2) + + testCase "commitment tx with one output untrimmed (minimum feerate)" + <| fun _ -> + let testCase = testVectors.[13] + + let spec = + { specBase with + FeeRatePerKw = (9651181u |> FeeRatePerKw) + } + + runTest (testCase) (spec) (1) + + testCase "commitment tx with fee greater than funder amount" + <| fun _ -> + let spec = + { + CommitmentSpec.IncomingHTLCs = incomingHtlcMap + CommitmentSpec.OutgoingHTLCs = outgoingHtlcMap + FeeRatePerKw = 9651936u |> FeeRatePerKw + ToLocal = 6988000000L |> LNMoney.MilliSatoshis + ToRemote = 3000000000L |> LNMoney.MilliSatoshis + } + + let testCase = testVectors.[14] + runTest (testCase) (spec) (1) + + ] diff --git a/tests/DotNetLightning.Core.Tests/TransactionTests.fs b/tests/DotNetLightning.Core.Tests/TransactionTests.fs index 5da51e9e9..e5008d10e 100644 --- a/tests/DotNetLightning.Core.Tests/TransactionTests.fs +++ b/tests/DotNetLightning.Core.Tests/TransactionTests.fs @@ -16,282 +16,392 @@ open NBitcoin let n = Network.RegTest [] -let testList = testList "transaction tests" [ - testCase "check fund recovery from local/remote commitment txs" <| fun _ -> - let rand = Random() - - let localNodeMasterPrivKey = - let extKey = ExtKey() - NodeMasterPrivKey extKey - let localChannelPrivKeys = localNodeMasterPrivKey.ChannelPrivKeys (rand.Next(1, 100)) - let localChannelPubKeys = localChannelPrivKeys.ToChannelPubKeys() - let localDestPrivKey = new Key() - let localDestPubKey = localDestPrivKey.PubKey - - let remoteNodeMasterPrivKey = - let extKey = ExtKey() - NodeMasterPrivKey extKey - let remoteChannelPrivKeys = remoteNodeMasterPrivKey.ChannelPrivKeys (rand.Next(1, 100)) - let remoteChannelPubKeys = remoteChannelPrivKeys.ToChannelPubKeys() - - let fundingAmount = 10_000_000L |> Money.Satoshis - let fundingScriptPubKey = - Scripts.funding - localChannelPubKeys.FundingPubKey - remoteChannelPubKeys.FundingPubKey - let fundingDestination = fundingScriptPubKey.WitHash :> IDestination - let fundingTxId = NBitcoin.RandomUtils.GetUInt256() - let fundingOutputIndex = uint32(rand.Next(0, 10)) - let fundingCoin = Coin(fundingTxId, fundingOutputIndex, fundingAmount, fundingDestination.ScriptPubKey) - let fundingScriptCoin = ScriptCoin(fundingCoin, fundingScriptPubKey) - - let commitmentNumber = - let uint48 = rand.Next(1, 100) |> uint64 |> UInt48.FromUInt64 - CommitmentNumber (UInt48.MaxValue - uint48) - let perCommitmentSecret = localChannelPrivKeys.CommitmentSeed.DerivePerCommitmentSecret commitmentNumber - let perCommitmentPoint = perCommitmentSecret.PerCommitmentPoint() - let localCommitmentPubKeys = perCommitmentPoint.DeriveCommitmentPubKeys localChannelPubKeys - let remoteCommitmentPubKeys = perCommitmentPoint.DeriveCommitmentPubKeys remoteChannelPubKeys - - let localParams : LocalParams = { - DustLimitSatoshis = 546L |> Money.Satoshis - MaxHTLCValueInFlightMSat = 10_000_000L |> LNMoney - ChannelReserveSatoshis = 1000L |> Money.Satoshis - HTLCMinimumMSat = 1000L |> LNMoney - ToSelfDelay = 144us |> BlockHeightOffset16 - MaxAcceptedHTLCs = 1000us - Features = FeatureBits.Zero - } - - let remoteLocalParam : LocalParams = { - DustLimitSatoshis = 546L |> Money.Satoshis - MaxHTLCValueInFlightMSat = 10_000_000L |> LNMoney - ChannelReserveSatoshis = 1000L |> Money.Satoshis - HTLCMinimumMSat = 1000L |> LNMoney - ToSelfDelay = 144us |> BlockHeightOffset16 - MaxAcceptedHTLCs = 1000us - Features = FeatureBits.Zero - } - - let remoteParam : RemoteParams = { - DustLimitSatoshis = 546L |> Money.Satoshis - MaxHTLCValueInFlightMSat = 10_000_000L |> LNMoney - ChannelReserveSatoshis = 1000L |> Money.Satoshis - HTLCMinimumMSat = 1000L |> LNMoney - ToSelfDelay = 144us |> BlockHeightOffset16 - MaxAcceptedHTLCs = 1000us - Features = FeatureBits.Zero - } - - let feeRate = FeeRatePerKw (rand.Next(0, 300) |> uint32) - let localAmount = 2_000_000_000L |> LNMoney - let remoteAmount = LNMoney.Satoshis(fundingAmount.Satoshi) - localAmount - let commitmentSpec = { - IncomingHTLCs = Map.empty - OutgoingHTLCs = Map.empty - FeeRatePerKw = feeRate - ToLocal = localAmount - ToRemote = remoteAmount - } - - let staticLocalChannelConfig : StaticChannelConfig = - { - FundingScriptCoin = fundingScriptCoin - AnnounceChannel = false - RemoteNodeId = remoteNodeMasterPrivKey.NodeId() - Network = Network.RegTest - IsFunder = true - FundingTxMinimumDepth = BlockHeightOffset32 6u - LocalStaticShutdownScriptPubKey = None - RemoteStaticShutdownScriptPubKey = None - LocalParams = localParams - RemoteParams = remoteParam - RemoteChannelPubKeys = remoteChannelPubKeys - } - - let unsignedCommitmentTx = - makeCommitTx - fundingScriptCoin - commitmentNumber - localChannelPubKeys.PaymentBasepoint - remoteChannelPubKeys.PaymentBasepoint - true - localParams.DustLimitSatoshis - remoteCommitmentPubKeys.RevocationPubKey - localParams.ToSelfDelay - localCommitmentPubKeys.DelayedPaymentPubKey - remoteCommitmentPubKeys.PaymentPubKey - localCommitmentPubKeys.HtlcPubKey - remoteCommitmentPubKeys.HtlcPubKey - commitmentSpec - Network.RegTest - let commitmentTx = - unsignedCommitmentTx.Value - .SignWithKeys(localChannelPrivKeys.FundingPrivKey.RawKey(), remoteChannelPrivKeys.FundingPrivKey.RawKey()) - .Finalize() - .ExtractTransaction() - - let transactionBuilder = - (ClosingHelpers.LocalClose.ClaimCommitTxOutputs - commitmentTx - staticLocalChannelConfig - localChannelPrivKeys).MainOutput - |> Result.deref - - let recoveryTransaction = - transactionBuilder - .SendAll(localDestPubKey) - .BuildTransaction(true) - let inputs = recoveryTransaction.Inputs - Expect.equal inputs.Count 1 "wrong number of inputs" - let input = inputs.[0] - Expect.equal input.Sequence.Value (uint32 localParams.ToSelfDelay.Value) "wrong sequence nuber" - Expect.equal input.PrevOut.Hash (commitmentTx.GetHash()) "wrong prevout hash" - let expectedAmount = - let fullAmount = commitmentSpec.ToLocal.ToMoney() - let fee = commitmentTx.GetFee [| fundingScriptCoin :> ICoin |] - fullAmount - fee - let actualAmount = - commitmentTx.Outputs.[input.PrevOut.N].Value - Expect.equal actualAmount expectedAmount "wrong prevout amount" - - let remoteDestPrivKey = new Key() - let remoteDestPubKey = remoteDestPrivKey.PubKey - let remoteRemotePerCommitmentSecrets = - let rec addKeys (remoteRemotePerCommitmentSecrets: PerCommitmentSecretStore) - (currentCommitmentNumber: CommitmentNumber) - : PerCommitmentSecretStore = - if currentCommitmentNumber = commitmentNumber then - remoteRemotePerCommitmentSecrets - else - let currentPerCommitmentSecret = - localChannelPrivKeys.CommitmentSeed.DerivePerCommitmentSecret - currentCommitmentNumber - let nextLocalPerCommitmentSecretsRes = - remoteRemotePerCommitmentSecrets.InsertPerCommitmentSecret - currentCommitmentNumber - currentPerCommitmentSecret +let testList = + testList + "transaction tests" + [ + testCase "check fund recovery from local/remote commitment txs" + <| fun _ -> + let rand = Random() + + let localNodeMasterPrivKey = + let extKey = ExtKey() + NodeMasterPrivKey extKey + + let localChannelPrivKeys = + localNodeMasterPrivKey.ChannelPrivKeys(rand.Next(1, 100)) + + let localChannelPubKeys = + localChannelPrivKeys.ToChannelPubKeys() + + let localDestPrivKey = new Key() + let localDestPubKey = localDestPrivKey.PubKey + + let remoteNodeMasterPrivKey = + let extKey = ExtKey() + NodeMasterPrivKey extKey + + let remoteChannelPrivKeys = + remoteNodeMasterPrivKey.ChannelPrivKeys(rand.Next(1, 100)) + + let remoteChannelPubKeys = + remoteChannelPrivKeys.ToChannelPubKeys() + + let fundingAmount = 10_000_000L |> Money.Satoshis + + let fundingScriptPubKey = + Scripts.funding + localChannelPubKeys.FundingPubKey + remoteChannelPubKeys.FundingPubKey + + let fundingDestination = + fundingScriptPubKey.WitHash :> IDestination + + let fundingTxId = NBitcoin.RandomUtils.GetUInt256() + let fundingOutputIndex = uint32(rand.Next(0, 10)) + + let fundingCoin = + Coin( + fundingTxId, + fundingOutputIndex, + fundingAmount, + fundingDestination.ScriptPubKey + ) + + let fundingScriptCoin = + ScriptCoin(fundingCoin, fundingScriptPubKey) + + let commitmentNumber = + let uint48 = + rand.Next(1, 100) |> uint64 |> UInt48.FromUInt64 + + CommitmentNumber(UInt48.MaxValue - uint48) + + let perCommitmentSecret = + localChannelPrivKeys.CommitmentSeed.DerivePerCommitmentSecret + commitmentNumber + + let perCommitmentPoint = + perCommitmentSecret.PerCommitmentPoint() + + let localCommitmentPubKeys = + perCommitmentPoint.DeriveCommitmentPubKeys + localChannelPubKeys + + let remoteCommitmentPubKeys = + perCommitmentPoint.DeriveCommitmentPubKeys + remoteChannelPubKeys + + let localParams: LocalParams = + { + DustLimitSatoshis = 546L |> Money.Satoshis + MaxHTLCValueInFlightMSat = 10_000_000L |> LNMoney + ChannelReserveSatoshis = 1000L |> Money.Satoshis + HTLCMinimumMSat = 1000L |> LNMoney + ToSelfDelay = 144us |> BlockHeightOffset16 + MaxAcceptedHTLCs = 1000us + Features = FeatureBits.Zero + } + + let remoteLocalParam: LocalParams = + { + DustLimitSatoshis = 546L |> Money.Satoshis + MaxHTLCValueInFlightMSat = 10_000_000L |> LNMoney + ChannelReserveSatoshis = 1000L |> Money.Satoshis + HTLCMinimumMSat = 1000L |> LNMoney + ToSelfDelay = 144us |> BlockHeightOffset16 + MaxAcceptedHTLCs = 1000us + Features = FeatureBits.Zero + } + + let remoteParam: RemoteParams = + { + DustLimitSatoshis = 546L |> Money.Satoshis + MaxHTLCValueInFlightMSat = 10_000_000L |> LNMoney + ChannelReserveSatoshis = 1000L |> Money.Satoshis + HTLCMinimumMSat = 1000L |> LNMoney + ToSelfDelay = 144us |> BlockHeightOffset16 + MaxAcceptedHTLCs = 1000us + Features = FeatureBits.Zero + } + + let feeRate = FeeRatePerKw(rand.Next(0, 300) |> uint32) + let localAmount = 2_000_000_000L |> LNMoney + + let remoteAmount = + LNMoney.Satoshis(fundingAmount.Satoshi) - localAmount + + let commitmentSpec = + { + IncomingHTLCs = Map.empty + OutgoingHTLCs = Map.empty + FeeRatePerKw = feeRate + ToLocal = localAmount + ToRemote = remoteAmount + } + + let staticLocalChannelConfig: StaticChannelConfig = + { + FundingScriptCoin = fundingScriptCoin + AnnounceChannel = false + RemoteNodeId = remoteNodeMasterPrivKey.NodeId() + Network = Network.RegTest + IsFunder = true + FundingTxMinimumDepth = BlockHeightOffset32 6u + LocalStaticShutdownScriptPubKey = None + RemoteStaticShutdownScriptPubKey = None + LocalParams = localParams + RemoteParams = remoteParam + RemoteChannelPubKeys = remoteChannelPubKeys + } + + let unsignedCommitmentTx = + makeCommitTx + fundingScriptCoin + commitmentNumber + localChannelPubKeys.PaymentBasepoint + remoteChannelPubKeys.PaymentBasepoint + true + localParams.DustLimitSatoshis + remoteCommitmentPubKeys.RevocationPubKey + localParams.ToSelfDelay + localCommitmentPubKeys.DelayedPaymentPubKey + remoteCommitmentPubKeys.PaymentPubKey + localCommitmentPubKeys.HtlcPubKey + remoteCommitmentPubKeys.HtlcPubKey + commitmentSpec + Network.RegTest + + let commitmentTx = + unsignedCommitmentTx + .Value + .SignWithKeys( + localChannelPrivKeys.FundingPrivKey.RawKey(), + remoteChannelPrivKeys.FundingPrivKey.RawKey() + ) + .Finalize() + .ExtractTransaction() + + let transactionBuilder = + (ClosingHelpers.LocalClose.ClaimCommitTxOutputs + commitmentTx + staticLocalChannelConfig + localChannelPrivKeys) + .MainOutput + |> Result.deref + + let recoveryTransaction = + transactionBuilder + .SendAll(localDestPubKey) + .BuildTransaction(true) + + let inputs = recoveryTransaction.Inputs + Expect.equal inputs.Count 1 "wrong number of inputs" + let input = inputs.[0] + + Expect.equal + input.Sequence.Value + (uint32 localParams.ToSelfDelay.Value) + "wrong sequence nuber" + + Expect.equal + input.PrevOut.Hash + (commitmentTx.GetHash()) + "wrong prevout hash" + + let expectedAmount = + let fullAmount = commitmentSpec.ToLocal.ToMoney() + + let fee = + commitmentTx.GetFee [| fundingScriptCoin :> ICoin |] + + fullAmount - fee + + let actualAmount = commitmentTx.Outputs.[input.PrevOut.N].Value + Expect.equal actualAmount expectedAmount "wrong prevout amount" + + let remoteDestPrivKey = new Key() + let remoteDestPubKey = remoteDestPrivKey.PubKey + + let remoteRemotePerCommitmentSecrets = + let rec addKeys + (remoteRemotePerCommitmentSecrets: PerCommitmentSecretStore) + (currentCommitmentNumber: CommitmentNumber) + : PerCommitmentSecretStore = + if currentCommitmentNumber = commitmentNumber then + remoteRemotePerCommitmentSecrets + else + let currentPerCommitmentSecret = + localChannelPrivKeys.CommitmentSeed.DerivePerCommitmentSecret + currentCommitmentNumber + + let nextLocalPerCommitmentSecretsRes = + remoteRemotePerCommitmentSecrets.InsertPerCommitmentSecret + currentCommitmentNumber + currentPerCommitmentSecret + + addKeys + (Result.deref nextLocalPerCommitmentSecretsRes) + (currentCommitmentNumber.NextCommitment()) + addKeys - (Result.deref nextLocalPerCommitmentSecretsRes) - (currentCommitmentNumber.NextCommitment()) - addKeys (PerCommitmentSecretStore()) CommitmentNumber.FirstCommitment - - let remoteRemoteCommit = { - Index = commitmentNumber - Spec = commitmentSpec - TxId = TxId <| commitmentTx.GetHash() - RemotePerCommitmentPoint = perCommitmentPoint - } - let remoteRemoteParams = { - DustLimitSatoshis = localParams.DustLimitSatoshis - MaxHTLCValueInFlightMSat = localParams.MaxHTLCValueInFlightMSat - ChannelReserveSatoshis = localParams.ChannelReserveSatoshis - HTLCMinimumMSat = localParams.HTLCMinimumMSat - ToSelfDelay = localParams.ToSelfDelay - MaxAcceptedHTLCs = localParams.MaxAcceptedHTLCs - Features = localParams.Features - } - - let staticRemoteChannelConfig : StaticChannelConfig = - { - FundingScriptCoin = fundingScriptCoin - AnnounceChannel = false - RemoteNodeId = localNodeMasterPrivKey.NodeId() - Network = Network.RegTest - IsFunder = false - FundingTxMinimumDepth = BlockHeightOffset32 6u - LocalStaticShutdownScriptPubKey = None - RemoteStaticShutdownScriptPubKey = None - LocalParams = remoteLocalParam - RemoteParams = remoteRemoteParams - RemoteChannelPubKeys = localChannelPubKeys - } - - let remoteCommitmentSpec = { - IncomingHTLCs = Map.empty - OutgoingHTLCs = Map.empty - FeeRatePerKw = feeRate - ToLocal = remoteAmount - ToRemote = localAmount - } - let remoteLocalCommit : LocalCommit = - { - Index = commitmentNumber - Spec = remoteCommitmentSpec - PublishableTxs = { - CommitTx = FinalizedTx commitmentTx - HTLCTxs = List.Empty - } - PendingHTLCSuccessTxs = List.Empty - } - - let remoteSavedChannelState : SavedChannelState = { - StaticChannelConfig = staticRemoteChannelConfig - RemotePerCommitmentSecrets = remoteRemotePerCommitmentSecrets - ShortChannelId = None - LocalCommit = remoteLocalCommit - RemoteCommit = remoteRemoteCommit - LocalChanges = LocalChanges.Zero - RemoteChanges = RemoteChanges.Zero - } - - let transactionBuilder = - (ClosingHelpers.RemoteClose.ClaimCommitTxOutputs - commitmentTx - remoteSavedChannelState.StaticChannelConfig - remoteChannelPrivKeys - remoteRemoteCommit).MainOutput - |> Result.deref - - let recoveryTransaction = - transactionBuilder - .SendAll(remoteDestPubKey) - .BuildTransaction(true) - let inputs = recoveryTransaction.Inputs - Expect.equal inputs.Count 1 "wrong number of inputs" - let input = inputs.[0] - Expect.equal input.PrevOut.Hash (commitmentTx.GetHash()) "wrong prevout hash" - let expectedAmount = commitmentSpec.ToRemote.ToMoney() - let actualAmount = - commitmentTx.Outputs.[input.PrevOut.N].Value - Expect.equal actualAmount expectedAmount "wrong prevout amount" - - let transactionBuilder = - ClosingHelpers.RevokedClose.createPenaltyTx - remoteChannelPrivKeys - remoteSavedChannelState.StaticChannelConfig - remoteRemoteCommit - perCommitmentSecret - |> Result.deref - let penaltyTransaction = - transactionBuilder - .SendAll(remoteDestPubKey) - .BuildTransaction(true) - let inputs = penaltyTransaction.Inputs - Expect.equal inputs.Count 2 "wrong number of inputs" - Expect.equal inputs.[0].PrevOut.Hash (commitmentTx.GetHash()) "wrong prevout hash on input 0" - Expect.equal inputs.[1].PrevOut.Hash (commitmentTx.GetHash()) "wrong prevout hash on input 1" - - let expectedAmountFromToLocal = - let localAmount = commitmentSpec.ToLocal.ToMoney() - let fee = commitmentTx.GetFee [| fundingScriptCoin :> ICoin |] - localAmount - fee - let expectedAmountFromToRemote = - commitmentSpec.ToRemote.ToMoney() - - let actualAmount0 = - commitmentTx.Outputs.[inputs.[0].PrevOut.N].Value - let actualAmount1 = - commitmentTx.Outputs.[inputs.[1].PrevOut.N].Value - - if actualAmount0 = expectedAmountFromToLocal then - Expect.equal actualAmount1 expectedAmountFromToRemote "wrong prevout amount for to_remote" - elif actualAmount0 = expectedAmountFromToRemote then - Expect.equal actualAmount1 expectedAmountFromToLocal "wrong prevout amount for to_local" - else - failwith "amount of input 0 does not match either expected amount" - - (* + (PerCommitmentSecretStore()) + CommitmentNumber.FirstCommitment + + let remoteRemoteCommit = + { + Index = commitmentNumber + Spec = commitmentSpec + TxId = TxId <| commitmentTx.GetHash() + RemotePerCommitmentPoint = perCommitmentPoint + } + + let remoteRemoteParams = + { + DustLimitSatoshis = localParams.DustLimitSatoshis + MaxHTLCValueInFlightMSat = + localParams.MaxHTLCValueInFlightMSat + ChannelReserveSatoshis = + localParams.ChannelReserveSatoshis + HTLCMinimumMSat = localParams.HTLCMinimumMSat + ToSelfDelay = localParams.ToSelfDelay + MaxAcceptedHTLCs = localParams.MaxAcceptedHTLCs + Features = localParams.Features + } + + let staticRemoteChannelConfig: StaticChannelConfig = + { + FundingScriptCoin = fundingScriptCoin + AnnounceChannel = false + RemoteNodeId = localNodeMasterPrivKey.NodeId() + Network = Network.RegTest + IsFunder = false + FundingTxMinimumDepth = BlockHeightOffset32 6u + LocalStaticShutdownScriptPubKey = None + RemoteStaticShutdownScriptPubKey = None + LocalParams = remoteLocalParam + RemoteParams = remoteRemoteParams + RemoteChannelPubKeys = localChannelPubKeys + } + + let remoteCommitmentSpec = + { + IncomingHTLCs = Map.empty + OutgoingHTLCs = Map.empty + FeeRatePerKw = feeRate + ToLocal = remoteAmount + ToRemote = localAmount + } + + let remoteLocalCommit: LocalCommit = + { + Index = commitmentNumber + Spec = remoteCommitmentSpec + PublishableTxs = + { + CommitTx = FinalizedTx commitmentTx + HTLCTxs = List.Empty + } + PendingHTLCSuccessTxs = List.Empty + } + + let remoteSavedChannelState: SavedChannelState = + { + StaticChannelConfig = staticRemoteChannelConfig + RemotePerCommitmentSecrets = + remoteRemotePerCommitmentSecrets + ShortChannelId = None + LocalCommit = remoteLocalCommit + RemoteCommit = remoteRemoteCommit + LocalChanges = LocalChanges.Zero + RemoteChanges = RemoteChanges.Zero + } + + let transactionBuilder = + (ClosingHelpers.RemoteClose.ClaimCommitTxOutputs + commitmentTx + remoteSavedChannelState.StaticChannelConfig + remoteChannelPrivKeys + remoteRemoteCommit) + .MainOutput + |> Result.deref + + let recoveryTransaction = + transactionBuilder + .SendAll(remoteDestPubKey) + .BuildTransaction(true) + + let inputs = recoveryTransaction.Inputs + Expect.equal inputs.Count 1 "wrong number of inputs" + let input = inputs.[0] + + Expect.equal + input.PrevOut.Hash + (commitmentTx.GetHash()) + "wrong prevout hash" + + let expectedAmount = commitmentSpec.ToRemote.ToMoney() + let actualAmount = commitmentTx.Outputs.[input.PrevOut.N].Value + Expect.equal actualAmount expectedAmount "wrong prevout amount" + + let transactionBuilder = + ClosingHelpers.RevokedClose.createPenaltyTx + remoteChannelPrivKeys + remoteSavedChannelState.StaticChannelConfig + remoteRemoteCommit + perCommitmentSecret + |> Result.deref + + let penaltyTransaction = + transactionBuilder + .SendAll(remoteDestPubKey) + .BuildTransaction(true) + + let inputs = penaltyTransaction.Inputs + Expect.equal inputs.Count 2 "wrong number of inputs" + + Expect.equal + inputs.[0].PrevOut.Hash + (commitmentTx.GetHash()) + "wrong prevout hash on input 0" + + Expect.equal + inputs.[1].PrevOut.Hash + (commitmentTx.GetHash()) + "wrong prevout hash on input 1" + + let expectedAmountFromToLocal = + let localAmount = commitmentSpec.ToLocal.ToMoney() + + let fee = + commitmentTx.GetFee [| fundingScriptCoin :> ICoin |] + + localAmount - fee + + let expectedAmountFromToRemote = + commitmentSpec.ToRemote.ToMoney() + + let actualAmount0 = + commitmentTx.Outputs.[inputs.[0].PrevOut.N].Value + + let actualAmount1 = + commitmentTx.Outputs.[inputs.[1].PrevOut.N].Value + + if actualAmount0 = expectedAmountFromToLocal then + Expect.equal + actualAmount1 + expectedAmountFromToRemote + "wrong prevout amount for to_remote" + elif actualAmount0 = expectedAmountFromToRemote then + Expect.equal + actualAmount1 + expectedAmountFromToLocal + "wrong prevout amount for to_local" + else + failwith + "amount of input 0 does not match either expected amount" + + (* testCase "check pre-computed transaction weights" <| fun _ -> let localPaymentPriv = [| for _ in 0..31 -> 0xdduy |] |> fun b -> new Key(b) let finalSpk = @@ -299,7 +409,7 @@ let testList = testList "transaction tests" [ s.PubKey.WitHash let localDustLimit = 546L |> Money.Satoshis let feeRatePerKw = 1000u |> FeeRatePerKw - + let _ = let pubkeyScript = localPaymentPriv.PubKey.WitHash.ScriptPubKey let commitTx = @@ -326,499 +436,682 @@ let testList = testList "transaction tests" [ tx.GetVirtualSize() |> uint64 Expect.equal(Constants.CLAIM_P2WPKH_OUTPUT_WEIGHT) (weight) "" () - + () *) - testCase "check spend handling from local/remote commitment txs" <| fun _ -> - let rand = Random() - - let localNodeMasterPrivKey = - let extKey = ExtKey() - NodeMasterPrivKey extKey - let localChannelPrivKeys = localNodeMasterPrivKey.ChannelPrivKeys (rand.Next(1, 100)) - let localChannelPubKeys = localChannelPrivKeys.ToChannelPubKeys() - - let remoteNodeMasterPrivKey = - let extKey = ExtKey() - NodeMasterPrivKey extKey - let remoteChannelPrivKeys = remoteNodeMasterPrivKey.ChannelPrivKeys (rand.Next(1, 100)) - let remoteChannelPubKeys = remoteChannelPrivKeys.ToChannelPubKeys() - - let feeRate = FeeRatePerKw (rand.Next(0, 300) |> uint32) - let fundingAmount = 10_000_000L |> Money.Satoshis - let localAmount = 2_000_000_000L |> LNMoney - let remoteAmount = LNMoney.Satoshis fundingAmount.Satoshi - localAmount - let localCommitmentSpec = { - IncomingHTLCs = Map.empty - OutgoingHTLCs = Map.empty - FeeRatePerKw = feeRate - ToLocal = localAmount - ToRemote = remoteAmount - } - let remoteCommitmentSpec = { - IncomingHTLCs = Map.empty - OutgoingHTLCs = Map.empty - FeeRatePerKw = feeRate - ToLocal = remoteAmount - ToRemote = localAmount - } - - let fundingScriptCoin = - let fundingScriptPubKey = - Scripts.funding - localChannelPubKeys.FundingPubKey - remoteChannelPubKeys.FundingPubKey - let fundingDestination = fundingScriptPubKey.WitHash :> IDestination - let fundingTxId = NBitcoin.RandomUtils.GetUInt256() - let fundingOutputIndex = uint32(rand.Next(0, 10)) - let fundingCoin = Coin(fundingTxId, fundingOutputIndex, fundingAmount, fundingDestination.ScriptPubKey) - ScriptCoin(fundingCoin, fundingScriptPubKey) - - let commitmentNumber = - let uint48 = rand.Next(2, 100) |> uint64 |> UInt48.FromUInt64 - CommitmentNumber (UInt48.MaxValue - uint48) - - let perCommitmentPoint = - let perCommitmentSecret = localChannelPrivKeys.CommitmentSeed.DerivePerCommitmentSecret commitmentNumber - perCommitmentSecret.PerCommitmentPoint() - - let localParams : LocalParams = { - DustLimitSatoshis = 546L |> Money.Satoshis - MaxHTLCValueInFlightMSat = 10_000_000L |> LNMoney - ChannelReserveSatoshis = 1000L |> Money.Satoshis - HTLCMinimumMSat = 1000L |> LNMoney - ToSelfDelay = 144us |> BlockHeightOffset16 - MaxAcceptedHTLCs = 1000us - Features = FeatureBits.Zero - } - - let remoteLocalParam : LocalParams = { - DustLimitSatoshis = 546L |> Money.Satoshis - MaxHTLCValueInFlightMSat = 10_000_000L |> LNMoney - ChannelReserveSatoshis = 1000L |> Money.Satoshis - HTLCMinimumMSat = 1000L |> LNMoney - ToSelfDelay = 144us |> BlockHeightOffset16 - MaxAcceptedHTLCs = 1000us - Features = FeatureBits.Zero - } - - let remoteParams : RemoteParams = { - DustLimitSatoshis = 546L |> Money.Satoshis - MaxHTLCValueInFlightMSat = 10_000_000L |> LNMoney - ChannelReserveSatoshis = 1000L |> Money.Satoshis - HTLCMinimumMSat = 1000L |> LNMoney - ToSelfDelay = 144us |> BlockHeightOffset16 - MaxAcceptedHTLCs = 1000us - Features = FeatureBits.Zero - } - - let createLocalCommitmentTransaction (commitmentNumber: CommitmentNumber) = - let perCommitmentPoint = - let perCommitmentSecret = localChannelPrivKeys.CommitmentSeed.DerivePerCommitmentSecret commitmentNumber - perCommitmentSecret.PerCommitmentPoint() - let localCommitmentPubKeys = perCommitmentPoint.DeriveCommitmentPubKeys localChannelPubKeys - let remoteCommitmentPubKeys = perCommitmentPoint.DeriveCommitmentPubKeys remoteChannelPubKeys - - let unsignedCommitmentTx = - makeCommitTx - fundingScriptCoin - commitmentNumber - localChannelPubKeys.PaymentBasepoint - remoteChannelPubKeys.PaymentBasepoint - true - localParams.DustLimitSatoshis - remoteCommitmentPubKeys.RevocationPubKey - remoteParams.ToSelfDelay - localCommitmentPubKeys.DelayedPaymentPubKey - remoteCommitmentPubKeys.PaymentPubKey - localCommitmentPubKeys.HtlcPubKey - remoteCommitmentPubKeys.HtlcPubKey - localCommitmentSpec - Network.RegTest - - unsignedCommitmentTx.Value - .SignWithKeys(localChannelPrivKeys.FundingPrivKey.RawKey(), remoteChannelPrivKeys.FundingPrivKey.RawKey()) - .Finalize() - .ExtractTransaction() - - let localCommitmentTx = - createLocalCommitmentTransaction commitmentNumber - let revokedLocalCommitmentTx = - createLocalCommitmentTransaction CommitmentNumber.FirstCommitment - - let remoteCommitmentTx = - let remotePerCommitmentPoint = - let remotePerCommitmentSecret = remoteChannelPrivKeys.CommitmentSeed.DerivePerCommitmentSecret commitmentNumber - remotePerCommitmentSecret.PerCommitmentPoint() - let remoteRemoteCommitmentPubKeys = remotePerCommitmentPoint.DeriveCommitmentPubKeys localChannelPubKeys - let remoteLocalCommitmentPubKeys = remotePerCommitmentPoint.DeriveCommitmentPubKeys remoteChannelPubKeys - - let unsignedCommitmentTx = - makeCommitTx - fundingScriptCoin - commitmentNumber - remoteChannelPubKeys.PaymentBasepoint - localChannelPubKeys.PaymentBasepoint - false - remoteParams.DustLimitSatoshis - remoteRemoteCommitmentPubKeys.RevocationPubKey - localParams.ToSelfDelay - remoteLocalCommitmentPubKeys.DelayedPaymentPubKey - remoteRemoteCommitmentPubKeys.PaymentPubKey - remoteLocalCommitmentPubKeys.HtlcPubKey - remoteRemoteCommitmentPubKeys.HtlcPubKey - remoteCommitmentSpec - Network.RegTest - - unsignedCommitmentTx.Value - .SignWithKeys(localChannelPrivKeys.FundingPrivKey.RawKey(), remoteChannelPrivKeys.FundingPrivKey.RawKey()) - .Finalize() - .ExtractTransaction() - - let remoteRemotePerCommitmentSecrets = - let rec addKeys (remoteRemotePerCommitmentSecrets: PerCommitmentSecretStore) - (currentCommitmentNumber: CommitmentNumber) - : PerCommitmentSecretStore = - if currentCommitmentNumber = commitmentNumber then - remoteRemotePerCommitmentSecrets - else - let currentPerCommitmentSecret = + testCase "check spend handling from local/remote commitment txs" + <| fun _ -> + let rand = Random() + + let localNodeMasterPrivKey = + let extKey = ExtKey() + NodeMasterPrivKey extKey + + let localChannelPrivKeys = + localNodeMasterPrivKey.ChannelPrivKeys(rand.Next(1, 100)) + + let localChannelPubKeys = + localChannelPrivKeys.ToChannelPubKeys() + + let remoteNodeMasterPrivKey = + let extKey = ExtKey() + NodeMasterPrivKey extKey + + let remoteChannelPrivKeys = + remoteNodeMasterPrivKey.ChannelPrivKeys(rand.Next(1, 100)) + + let remoteChannelPubKeys = + remoteChannelPrivKeys.ToChannelPubKeys() + + let feeRate = FeeRatePerKw(rand.Next(0, 300) |> uint32) + let fundingAmount = 10_000_000L |> Money.Satoshis + let localAmount = 2_000_000_000L |> LNMoney + + let remoteAmount = + LNMoney.Satoshis fundingAmount.Satoshi - localAmount + + let localCommitmentSpec = + { + IncomingHTLCs = Map.empty + OutgoingHTLCs = Map.empty + FeeRatePerKw = feeRate + ToLocal = localAmount + ToRemote = remoteAmount + } + + let remoteCommitmentSpec = + { + IncomingHTLCs = Map.empty + OutgoingHTLCs = Map.empty + FeeRatePerKw = feeRate + ToLocal = remoteAmount + ToRemote = localAmount + } + + let fundingScriptCoin = + let fundingScriptPubKey = + Scripts.funding + localChannelPubKeys.FundingPubKey + remoteChannelPubKeys.FundingPubKey + + let fundingDestination = + fundingScriptPubKey.WitHash :> IDestination + + let fundingTxId = NBitcoin.RandomUtils.GetUInt256() + let fundingOutputIndex = uint32(rand.Next(0, 10)) + + let fundingCoin = + Coin( + fundingTxId, + fundingOutputIndex, + fundingAmount, + fundingDestination.ScriptPubKey + ) + + ScriptCoin(fundingCoin, fundingScriptPubKey) + + let commitmentNumber = + let uint48 = + rand.Next(2, 100) |> uint64 |> UInt48.FromUInt64 + + CommitmentNumber(UInt48.MaxValue - uint48) + + let perCommitmentPoint = + let perCommitmentSecret = localChannelPrivKeys.CommitmentSeed.DerivePerCommitmentSecret - currentCommitmentNumber - let nextLocalPerCommitmentSecretsRes = - remoteRemotePerCommitmentSecrets.InsertPerCommitmentSecret - currentCommitmentNumber - currentPerCommitmentSecret + commitmentNumber + + perCommitmentSecret.PerCommitmentPoint() + + let localParams: LocalParams = + { + DustLimitSatoshis = 546L |> Money.Satoshis + MaxHTLCValueInFlightMSat = 10_000_000L |> LNMoney + ChannelReserveSatoshis = 1000L |> Money.Satoshis + HTLCMinimumMSat = 1000L |> LNMoney + ToSelfDelay = 144us |> BlockHeightOffset16 + MaxAcceptedHTLCs = 1000us + Features = FeatureBits.Zero + } + + let remoteLocalParam: LocalParams = + { + DustLimitSatoshis = 546L |> Money.Satoshis + MaxHTLCValueInFlightMSat = 10_000_000L |> LNMoney + ChannelReserveSatoshis = 1000L |> Money.Satoshis + HTLCMinimumMSat = 1000L |> LNMoney + ToSelfDelay = 144us |> BlockHeightOffset16 + MaxAcceptedHTLCs = 1000us + Features = FeatureBits.Zero + } + + let remoteParams: RemoteParams = + { + DustLimitSatoshis = 546L |> Money.Satoshis + MaxHTLCValueInFlightMSat = 10_000_000L |> LNMoney + ChannelReserveSatoshis = 1000L |> Money.Satoshis + HTLCMinimumMSat = 1000L |> LNMoney + ToSelfDelay = 144us |> BlockHeightOffset16 + MaxAcceptedHTLCs = 1000us + Features = FeatureBits.Zero + } + + let createLocalCommitmentTransaction + (commitmentNumber: CommitmentNumber) + = + let perCommitmentPoint = + let perCommitmentSecret = + localChannelPrivKeys.CommitmentSeed.DerivePerCommitmentSecret + commitmentNumber + + perCommitmentSecret.PerCommitmentPoint() + + let localCommitmentPubKeys = + perCommitmentPoint.DeriveCommitmentPubKeys + localChannelPubKeys + + let remoteCommitmentPubKeys = + perCommitmentPoint.DeriveCommitmentPubKeys + remoteChannelPubKeys + + let unsignedCommitmentTx = + makeCommitTx + fundingScriptCoin + commitmentNumber + localChannelPubKeys.PaymentBasepoint + remoteChannelPubKeys.PaymentBasepoint + true + localParams.DustLimitSatoshis + remoteCommitmentPubKeys.RevocationPubKey + remoteParams.ToSelfDelay + localCommitmentPubKeys.DelayedPaymentPubKey + remoteCommitmentPubKeys.PaymentPubKey + localCommitmentPubKeys.HtlcPubKey + remoteCommitmentPubKeys.HtlcPubKey + localCommitmentSpec + Network.RegTest + + unsignedCommitmentTx + .Value + .SignWithKeys( + localChannelPrivKeys.FundingPrivKey.RawKey(), + remoteChannelPrivKeys.FundingPrivKey.RawKey() + ) + .Finalize() + .ExtractTransaction() + + let localCommitmentTx = + createLocalCommitmentTransaction commitmentNumber + + let revokedLocalCommitmentTx = + createLocalCommitmentTransaction + CommitmentNumber.FirstCommitment + + let remoteCommitmentTx = + let remotePerCommitmentPoint = + let remotePerCommitmentSecret = + remoteChannelPrivKeys.CommitmentSeed.DerivePerCommitmentSecret + commitmentNumber + + remotePerCommitmentSecret.PerCommitmentPoint() + + let remoteRemoteCommitmentPubKeys = + remotePerCommitmentPoint.DeriveCommitmentPubKeys + localChannelPubKeys + + let remoteLocalCommitmentPubKeys = + remotePerCommitmentPoint.DeriveCommitmentPubKeys + remoteChannelPubKeys + + let unsignedCommitmentTx = + makeCommitTx + fundingScriptCoin + commitmentNumber + remoteChannelPubKeys.PaymentBasepoint + localChannelPubKeys.PaymentBasepoint + false + remoteParams.DustLimitSatoshis + remoteRemoteCommitmentPubKeys.RevocationPubKey + localParams.ToSelfDelay + remoteLocalCommitmentPubKeys.DelayedPaymentPubKey + remoteRemoteCommitmentPubKeys.PaymentPubKey + remoteLocalCommitmentPubKeys.HtlcPubKey + remoteRemoteCommitmentPubKeys.HtlcPubKey + remoteCommitmentSpec + Network.RegTest + + unsignedCommitmentTx + .Value + .SignWithKeys( + localChannelPrivKeys.FundingPrivKey.RawKey(), + remoteChannelPrivKeys.FundingPrivKey.RawKey() + ) + .Finalize() + .ExtractTransaction() + + let remoteRemotePerCommitmentSecrets = + let rec addKeys + (remoteRemotePerCommitmentSecrets: PerCommitmentSecretStore) + (currentCommitmentNumber: CommitmentNumber) + : PerCommitmentSecretStore = + if currentCommitmentNumber = commitmentNumber then + remoteRemotePerCommitmentSecrets + else + let currentPerCommitmentSecret = + localChannelPrivKeys.CommitmentSeed.DerivePerCommitmentSecret + currentCommitmentNumber + + let nextLocalPerCommitmentSecretsRes = + remoteRemotePerCommitmentSecrets.InsertPerCommitmentSecret + currentCommitmentNumber + currentPerCommitmentSecret + + addKeys + (Result.deref nextLocalPerCommitmentSecretsRes) + (currentCommitmentNumber.NextCommitment()) + addKeys - (Result.deref nextLocalPerCommitmentSecretsRes) - (currentCommitmentNumber.NextCommitment()) - addKeys (PerCommitmentSecretStore()) CommitmentNumber.FirstCommitment - - let remoteRemoteCommit = { - Index = commitmentNumber - Spec = localCommitmentSpec - TxId = TxId <| localCommitmentTx.GetHash() - RemotePerCommitmentPoint = perCommitmentPoint - } - - let remoteRemoteParams = { - DustLimitSatoshis = localParams.DustLimitSatoshis - MaxHTLCValueInFlightMSat = localParams.MaxHTLCValueInFlightMSat - ChannelReserveSatoshis = localParams.ChannelReserveSatoshis - HTLCMinimumMSat = localParams.HTLCMinimumMSat - ToSelfDelay = localParams.ToSelfDelay - MaxAcceptedHTLCs = localParams.MaxAcceptedHTLCs - Features = localParams.Features - } - - let staticRemoteChannelConfig : StaticChannelConfig = - { - FundingScriptCoin = fundingScriptCoin - AnnounceChannel = false - RemoteNodeId = localNodeMasterPrivKey.NodeId() - Network = Network.RegTest - IsFunder = false - FundingTxMinimumDepth = BlockHeightOffset32 6u - LocalStaticShutdownScriptPubKey = None - RemoteStaticShutdownScriptPubKey = None - LocalParams = remoteLocalParam - RemoteParams = remoteRemoteParams - RemoteChannelPubKeys = localChannelPubKeys - } - - let remoteLocalCommit : LocalCommit = - { - Index = commitmentNumber - Spec = remoteCommitmentSpec - PublishableTxs = { - CommitTx = FinalizedTx remoteCommitmentTx - HTLCTxs = List.Empty - } - PendingHTLCSuccessTxs = List.Empty - } - - let remoteSavedChannelState : SavedChannelState = { - StaticChannelConfig = staticRemoteChannelConfig - RemotePerCommitmentSecrets = remoteRemotePerCommitmentSecrets - ShortChannelId = None - LocalCommit = remoteLocalCommit - RemoteCommit = remoteRemoteCommit - LocalChanges = LocalChanges.Zero - RemoteChanges = RemoteChanges.Zero - } - - let validateClosingResult (expectedAmount: Money) (errorMsg: string) (result: ClosingHelpers.ClosingResult) = - let dest = - use remoteDestPrivKey = new Key() - remoteDestPrivKey.PubKey.WitHash.ScriptPubKey - let actualAmount = - (Result.deref result.MainOutput) - .SendAll(dest) - .SendFees(Money.Zero) - .BuildTransaction(true) - .Outputs - |> Seq.pick (fun output -> if output.ScriptPubKey = dest then Some output.Value else None) - Expect.equal actualAmount expectedAmount errorMsg - - ClosingHelpers.HandleFundingTxSpent - remoteSavedChannelState - None - remoteChannelPrivKeys - localCommitmentTx - |> validateClosingResult (remoteAmount.ToMoney()) "wrong to_remote spending tx" - - ClosingHelpers.HandleFundingTxSpent - remoteSavedChannelState - None - remoteChannelPrivKeys - remoteCommitmentTx - |> validateClosingResult (remoteAmount.ToMoney()) "wrong to_local spending tx" - - let expectedAmountFromToLocal = - let localAmount = localCommitmentSpec.ToLocal.ToMoney() - let fee = revokedLocalCommitmentTx.GetFee [| fundingScriptCoin :> ICoin |] - localAmount - fee - let expectedAmountFromToRemote = - localCommitmentSpec.ToRemote.ToMoney() - - ClosingHelpers.HandleFundingTxSpent - remoteSavedChannelState - None - remoteChannelPrivKeys - revokedLocalCommitmentTx - |> validateClosingResult (expectedAmountFromToRemote + expectedAmountFromToLocal) "wrong penalty tx" - - // HandleFundingTxSpent cannot handle old local commitments - // instead it sends them to RevokedClose for handling, - // this causes the RevokedClose find the remote perCommitmentSecret - // for the commitment number but because that perCommitmentSecret - // actually belongs to the remote party commitment tx (with the same commitment number) - // it can't reproduce the outputs so it returns the UnknownClosingTx error - testCase "HandleFundingTxSpent should return UnknownClosingTx error for old local commitment txs" <| fun _ -> - let rand = Random() - - let localNodeMasterPrivKey = - let extKey = ExtKey() - NodeMasterPrivKey extKey - let localChannelPrivKeys = localNodeMasterPrivKey.ChannelPrivKeys (rand.Next(1, 100)) - let localChannelPubKeys = localChannelPrivKeys.ToChannelPubKeys() - - let remoteNodeMasterPrivKey = - let extKey = ExtKey() - NodeMasterPrivKey extKey - let remoteChannelPrivKeys = remoteNodeMasterPrivKey.ChannelPrivKeys (rand.Next(1, 100)) - let remoteChannelPubKeys = remoteChannelPrivKeys.ToChannelPubKeys() - - let fundingAmount = 10_000_000L |> Money.Satoshis - - let fundingScriptCoin = - let fundingScriptPubKey = - Scripts.funding - localChannelPubKeys.FundingPubKey - remoteChannelPubKeys.FundingPubKey - let fundingDestination = fundingScriptPubKey.WitHash :> IDestination - let fundingTxId = NBitcoin.RandomUtils.GetUInt256() - let fundingOutputIndex = uint32(rand.Next(0, 10)) - let fundingCoin = Coin(fundingTxId, fundingOutputIndex, fundingAmount, fundingDestination.ScriptPubKey) - ScriptCoin(fundingCoin, fundingScriptPubKey) - - let commitmentNumber = - let uint48 = rand.Next(2, 100) |> uint64 |> UInt48.FromUInt64 - CommitmentNumber (UInt48.MaxValue - uint48) - let perCommitmentSecret = localChannelPrivKeys.CommitmentSeed.DerivePerCommitmentSecret commitmentNumber - let perCommitmentPoint = perCommitmentSecret.PerCommitmentPoint() - let localCommitmentPubKeys = perCommitmentPoint.DeriveCommitmentPubKeys localChannelPubKeys - let remoteCommitmentPubKeys = perCommitmentPoint.DeriveCommitmentPubKeys remoteChannelPubKeys - - let localParams : LocalParams = { - DustLimitSatoshis = 546L |> Money.Satoshis - MaxHTLCValueInFlightMSat = 10_000_000L |> LNMoney - ChannelReserveSatoshis = 1000L |> Money.Satoshis - HTLCMinimumMSat = 1000L |> LNMoney - ToSelfDelay = 144us |> BlockHeightOffset16 - MaxAcceptedHTLCs = 1000us - Features = FeatureBits.Zero - } - - let remoteLocalParam : LocalParams = { - DustLimitSatoshis = 546L |> Money.Satoshis - MaxHTLCValueInFlightMSat = 10_000_000L |> LNMoney - ChannelReserveSatoshis = 1000L |> Money.Satoshis - HTLCMinimumMSat = 1000L |> LNMoney - ToSelfDelay = 144us |> BlockHeightOffset16 - MaxAcceptedHTLCs = 1000us - Features = FeatureBits.Zero - } - - let remoteParam : RemoteParams = { - DustLimitSatoshis = 546L |> Money.Satoshis - MaxHTLCValueInFlightMSat = 10_000_000L |> LNMoney - ChannelReserveSatoshis = 1000L |> Money.Satoshis - HTLCMinimumMSat = 1000L |> LNMoney - ToSelfDelay = 144us |> BlockHeightOffset16 - MaxAcceptedHTLCs = 1000us - Features = FeatureBits.Zero - } - - let feeRate = FeeRatePerKw (rand.Next(0, 300) |> uint32) - let localAmount = 2_000_000_000L |> LNMoney - let remoteAmount = LNMoney.Satoshis(fundingAmount.Satoshi) - localAmount - let localCommitmentSpec = { - IncomingHTLCs = Map.empty - OutgoingHTLCs = Map.empty - FeeRatePerKw = feeRate - ToLocal = localAmount - ToRemote = remoteAmount - } - - let remoteCommitmentSpec = { - IncomingHTLCs = Map.empty - OutgoingHTLCs = Map.empty - FeeRatePerKw = feeRate - ToLocal = remoteAmount - ToRemote = localAmount - } - - let localCommitmentTx = - let unsignedCommitmentTx = - makeCommitTx - fundingScriptCoin - commitmentNumber - localChannelPubKeys.PaymentBasepoint - remoteChannelPubKeys.PaymentBasepoint - true - localParams.DustLimitSatoshis - remoteCommitmentPubKeys.RevocationPubKey - remoteParam.ToSelfDelay - localCommitmentPubKeys.DelayedPaymentPubKey - remoteCommitmentPubKeys.PaymentPubKey - localCommitmentPubKeys.HtlcPubKey - remoteCommitmentPubKeys.HtlcPubKey - localCommitmentSpec - Network.RegTest - - unsignedCommitmentTx.Value - .SignWithKeys(localChannelPrivKeys.FundingPrivKey.RawKey(), remoteChannelPrivKeys.FundingPrivKey.RawKey()) - .Finalize() - .ExtractTransaction() - - let createRemoteCommitmentTransaction (commitmentNumber: CommitmentNumber) = - let perCommitmentSecret = remoteChannelPrivKeys.CommitmentSeed.DerivePerCommitmentSecret commitmentNumber - let perCommitmentPoint = perCommitmentSecret.PerCommitmentPoint() - let localCommitmentPubKeys = perCommitmentPoint.DeriveCommitmentPubKeys localChannelPubKeys - let remoteCommitmentPubKeys = perCommitmentPoint.DeriveCommitmentPubKeys remoteChannelPubKeys - - let unsignedCommitmentTx = - makeCommitTx - fundingScriptCoin - commitmentNumber - remoteChannelPubKeys.PaymentBasepoint - localChannelPubKeys.PaymentBasepoint - false - remoteParam.DustLimitSatoshis - remoteCommitmentPubKeys.RevocationPubKey - localParams.ToSelfDelay - localCommitmentPubKeys.DelayedPaymentPubKey - remoteCommitmentPubKeys.PaymentPubKey - localCommitmentPubKeys.HtlcPubKey - remoteCommitmentPubKeys.HtlcPubKey - remoteCommitmentSpec - Network.RegTest - - unsignedCommitmentTx.Value - .SignWithKeys(localChannelPrivKeys.FundingPrivKey.RawKey(), remoteChannelPrivKeys.FundingPrivKey.RawKey()) - .Finalize() - .ExtractTransaction() - - let revokedRemoteCommitmentTx = - createRemoteCommitmentTransaction CommitmentNumber.FirstCommitment - let remoteCommitmentTx = - createRemoteCommitmentTransaction commitmentNumber - - let remoteRemotePerCommitmentSecrets = - let rec addKeys (remoteRemotePerCommitmentSecrets: PerCommitmentSecretStore) - (currentCommitmentNumber: CommitmentNumber) - : PerCommitmentSecretStore = - if currentCommitmentNumber = commitmentNumber then - remoteRemotePerCommitmentSecrets - else - let currentPerCommitmentSecret = - localChannelPrivKeys.CommitmentSeed.DerivePerCommitmentSecret - currentCommitmentNumber - let nextLocalPerCommitmentSecretsRes = - remoteRemotePerCommitmentSecrets.InsertPerCommitmentSecret - currentCommitmentNumber - currentPerCommitmentSecret + (PerCommitmentSecretStore()) + CommitmentNumber.FirstCommitment + + let remoteRemoteCommit = + { + Index = commitmentNumber + Spec = localCommitmentSpec + TxId = TxId <| localCommitmentTx.GetHash() + RemotePerCommitmentPoint = perCommitmentPoint + } + + let remoteRemoteParams = + { + DustLimitSatoshis = localParams.DustLimitSatoshis + MaxHTLCValueInFlightMSat = + localParams.MaxHTLCValueInFlightMSat + ChannelReserveSatoshis = + localParams.ChannelReserveSatoshis + HTLCMinimumMSat = localParams.HTLCMinimumMSat + ToSelfDelay = localParams.ToSelfDelay + MaxAcceptedHTLCs = localParams.MaxAcceptedHTLCs + Features = localParams.Features + } + + let staticRemoteChannelConfig: StaticChannelConfig = + { + FundingScriptCoin = fundingScriptCoin + AnnounceChannel = false + RemoteNodeId = localNodeMasterPrivKey.NodeId() + Network = Network.RegTest + IsFunder = false + FundingTxMinimumDepth = BlockHeightOffset32 6u + LocalStaticShutdownScriptPubKey = None + RemoteStaticShutdownScriptPubKey = None + LocalParams = remoteLocalParam + RemoteParams = remoteRemoteParams + RemoteChannelPubKeys = localChannelPubKeys + } + + let remoteLocalCommit: LocalCommit = + { + Index = commitmentNumber + Spec = remoteCommitmentSpec + PublishableTxs = + { + CommitTx = FinalizedTx remoteCommitmentTx + HTLCTxs = List.Empty + } + PendingHTLCSuccessTxs = List.Empty + } + + let remoteSavedChannelState: SavedChannelState = + { + StaticChannelConfig = staticRemoteChannelConfig + RemotePerCommitmentSecrets = + remoteRemotePerCommitmentSecrets + ShortChannelId = None + LocalCommit = remoteLocalCommit + RemoteCommit = remoteRemoteCommit + LocalChanges = LocalChanges.Zero + RemoteChanges = RemoteChanges.Zero + } + + let validateClosingResult + (expectedAmount: Money) + (errorMsg: string) + (result: ClosingHelpers.ClosingResult) + = + let dest = + use remoteDestPrivKey = new Key() + remoteDestPrivKey.PubKey.WitHash.ScriptPubKey + + let actualAmount = + (Result.deref result.MainOutput).SendAll( + dest + ) + .SendFees( + Money.Zero + ) + .BuildTransaction( + true + ) + .Outputs + |> Seq.pick(fun output -> + if output.ScriptPubKey = dest then + Some output.Value + else + None + ) + + Expect.equal actualAmount expectedAmount errorMsg + + ClosingHelpers.HandleFundingTxSpent + remoteSavedChannelState + None + remoteChannelPrivKeys + localCommitmentTx + |> validateClosingResult + (remoteAmount.ToMoney()) + "wrong to_remote spending tx" + + ClosingHelpers.HandleFundingTxSpent + remoteSavedChannelState + None + remoteChannelPrivKeys + remoteCommitmentTx + |> validateClosingResult + (remoteAmount.ToMoney()) + "wrong to_local spending tx" + + let expectedAmountFromToLocal = + let localAmount = localCommitmentSpec.ToLocal.ToMoney() + + let fee = + revokedLocalCommitmentTx.GetFee + [| fundingScriptCoin :> ICoin |] + + localAmount - fee + + let expectedAmountFromToRemote = + localCommitmentSpec.ToRemote.ToMoney() + + ClosingHelpers.HandleFundingTxSpent + remoteSavedChannelState + None + remoteChannelPrivKeys + revokedLocalCommitmentTx + |> validateClosingResult + (expectedAmountFromToRemote + expectedAmountFromToLocal) + "wrong penalty tx" + + // HandleFundingTxSpent cannot handle old local commitments + // instead it sends them to RevokedClose for handling, + // this causes the RevokedClose find the remote perCommitmentSecret + // for the commitment number but because that perCommitmentSecret + // actually belongs to the remote party commitment tx (with the same commitment number) + // it can't reproduce the outputs so it returns the UnknownClosingTx error + testCase + "HandleFundingTxSpent should return UnknownClosingTx error for old local commitment txs" + <| fun _ -> + let rand = Random() + + let localNodeMasterPrivKey = + let extKey = ExtKey() + NodeMasterPrivKey extKey + + let localChannelPrivKeys = + localNodeMasterPrivKey.ChannelPrivKeys(rand.Next(1, 100)) + + let localChannelPubKeys = + localChannelPrivKeys.ToChannelPubKeys() + + let remoteNodeMasterPrivKey = + let extKey = ExtKey() + NodeMasterPrivKey extKey + + let remoteChannelPrivKeys = + remoteNodeMasterPrivKey.ChannelPrivKeys(rand.Next(1, 100)) + + let remoteChannelPubKeys = + remoteChannelPrivKeys.ToChannelPubKeys() + + let fundingAmount = 10_000_000L |> Money.Satoshis + + let fundingScriptCoin = + let fundingScriptPubKey = + Scripts.funding + localChannelPubKeys.FundingPubKey + remoteChannelPubKeys.FundingPubKey + + let fundingDestination = + fundingScriptPubKey.WitHash :> IDestination + + let fundingTxId = NBitcoin.RandomUtils.GetUInt256() + let fundingOutputIndex = uint32(rand.Next(0, 10)) + + let fundingCoin = + Coin( + fundingTxId, + fundingOutputIndex, + fundingAmount, + fundingDestination.ScriptPubKey + ) + + ScriptCoin(fundingCoin, fundingScriptPubKey) + + let commitmentNumber = + let uint48 = + rand.Next(2, 100) |> uint64 |> UInt48.FromUInt64 + + CommitmentNumber(UInt48.MaxValue - uint48) + + let perCommitmentSecret = + localChannelPrivKeys.CommitmentSeed.DerivePerCommitmentSecret + commitmentNumber + + let perCommitmentPoint = + perCommitmentSecret.PerCommitmentPoint() + + let localCommitmentPubKeys = + perCommitmentPoint.DeriveCommitmentPubKeys + localChannelPubKeys + + let remoteCommitmentPubKeys = + perCommitmentPoint.DeriveCommitmentPubKeys + remoteChannelPubKeys + + let localParams: LocalParams = + { + DustLimitSatoshis = 546L |> Money.Satoshis + MaxHTLCValueInFlightMSat = 10_000_000L |> LNMoney + ChannelReserveSatoshis = 1000L |> Money.Satoshis + HTLCMinimumMSat = 1000L |> LNMoney + ToSelfDelay = 144us |> BlockHeightOffset16 + MaxAcceptedHTLCs = 1000us + Features = FeatureBits.Zero + } + + let remoteLocalParam: LocalParams = + { + DustLimitSatoshis = 546L |> Money.Satoshis + MaxHTLCValueInFlightMSat = 10_000_000L |> LNMoney + ChannelReserveSatoshis = 1000L |> Money.Satoshis + HTLCMinimumMSat = 1000L |> LNMoney + ToSelfDelay = 144us |> BlockHeightOffset16 + MaxAcceptedHTLCs = 1000us + Features = FeatureBits.Zero + } + + let remoteParam: RemoteParams = + { + DustLimitSatoshis = 546L |> Money.Satoshis + MaxHTLCValueInFlightMSat = 10_000_000L |> LNMoney + ChannelReserveSatoshis = 1000L |> Money.Satoshis + HTLCMinimumMSat = 1000L |> LNMoney + ToSelfDelay = 144us |> BlockHeightOffset16 + MaxAcceptedHTLCs = 1000us + Features = FeatureBits.Zero + } + + let feeRate = FeeRatePerKw(rand.Next(0, 300) |> uint32) + let localAmount = 2_000_000_000L |> LNMoney + + let remoteAmount = + LNMoney.Satoshis(fundingAmount.Satoshi) - localAmount + + let localCommitmentSpec = + { + IncomingHTLCs = Map.empty + OutgoingHTLCs = Map.empty + FeeRatePerKw = feeRate + ToLocal = localAmount + ToRemote = remoteAmount + } + + let remoteCommitmentSpec = + { + IncomingHTLCs = Map.empty + OutgoingHTLCs = Map.empty + FeeRatePerKw = feeRate + ToLocal = remoteAmount + ToRemote = localAmount + } + + let localCommitmentTx = + let unsignedCommitmentTx = + makeCommitTx + fundingScriptCoin + commitmentNumber + localChannelPubKeys.PaymentBasepoint + remoteChannelPubKeys.PaymentBasepoint + true + localParams.DustLimitSatoshis + remoteCommitmentPubKeys.RevocationPubKey + remoteParam.ToSelfDelay + localCommitmentPubKeys.DelayedPaymentPubKey + remoteCommitmentPubKeys.PaymentPubKey + localCommitmentPubKeys.HtlcPubKey + remoteCommitmentPubKeys.HtlcPubKey + localCommitmentSpec + Network.RegTest + + unsignedCommitmentTx + .Value + .SignWithKeys( + localChannelPrivKeys.FundingPrivKey.RawKey(), + remoteChannelPrivKeys.FundingPrivKey.RawKey() + ) + .Finalize() + .ExtractTransaction() + + let createRemoteCommitmentTransaction + (commitmentNumber: CommitmentNumber) + = + let perCommitmentSecret = + remoteChannelPrivKeys.CommitmentSeed.DerivePerCommitmentSecret + commitmentNumber + + let perCommitmentPoint = + perCommitmentSecret.PerCommitmentPoint() + + let localCommitmentPubKeys = + perCommitmentPoint.DeriveCommitmentPubKeys + localChannelPubKeys + + let remoteCommitmentPubKeys = + perCommitmentPoint.DeriveCommitmentPubKeys + remoteChannelPubKeys + + let unsignedCommitmentTx = + makeCommitTx + fundingScriptCoin + commitmentNumber + remoteChannelPubKeys.PaymentBasepoint + localChannelPubKeys.PaymentBasepoint + false + remoteParam.DustLimitSatoshis + remoteCommitmentPubKeys.RevocationPubKey + localParams.ToSelfDelay + localCommitmentPubKeys.DelayedPaymentPubKey + remoteCommitmentPubKeys.PaymentPubKey + localCommitmentPubKeys.HtlcPubKey + remoteCommitmentPubKeys.HtlcPubKey + remoteCommitmentSpec + Network.RegTest + + unsignedCommitmentTx + .Value + .SignWithKeys( + localChannelPrivKeys.FundingPrivKey.RawKey(), + remoteChannelPrivKeys.FundingPrivKey.RawKey() + ) + .Finalize() + .ExtractTransaction() + + let revokedRemoteCommitmentTx = + createRemoteCommitmentTransaction + CommitmentNumber.FirstCommitment + + let remoteCommitmentTx = + createRemoteCommitmentTransaction commitmentNumber + + let remoteRemotePerCommitmentSecrets = + let rec addKeys + (remoteRemotePerCommitmentSecrets: PerCommitmentSecretStore) + (currentCommitmentNumber: CommitmentNumber) + : PerCommitmentSecretStore = + if currentCommitmentNumber = commitmentNumber then + remoteRemotePerCommitmentSecrets + else + let currentPerCommitmentSecret = + localChannelPrivKeys.CommitmentSeed.DerivePerCommitmentSecret + currentCommitmentNumber + + let nextLocalPerCommitmentSecretsRes = + remoteRemotePerCommitmentSecrets.InsertPerCommitmentSecret + currentCommitmentNumber + currentPerCommitmentSecret + + addKeys + (Result.deref nextLocalPerCommitmentSecretsRes) + (currentCommitmentNumber.NextCommitment()) + addKeys - (Result.deref nextLocalPerCommitmentSecretsRes) - (currentCommitmentNumber.NextCommitment()) - addKeys (PerCommitmentSecretStore()) CommitmentNumber.FirstCommitment - - let remoteRemoteCommit = { - Index = commitmentNumber - Spec = localCommitmentSpec - TxId = TxId <| localCommitmentTx.GetHash() - RemotePerCommitmentPoint = perCommitmentPoint - } - let remoteRemoteParams = { - DustLimitSatoshis = localParams.DustLimitSatoshis - MaxHTLCValueInFlightMSat = localParams.MaxHTLCValueInFlightMSat - ChannelReserveSatoshis = localParams.ChannelReserveSatoshis - HTLCMinimumMSat = localParams.HTLCMinimumMSat - ToSelfDelay = localParams.ToSelfDelay - MaxAcceptedHTLCs = localParams.MaxAcceptedHTLCs - Features = localParams.Features - } - - let staticRemoteChannelConfig : StaticChannelConfig = - { - FundingScriptCoin = fundingScriptCoin - AnnounceChannel = false - RemoteNodeId = localNodeMasterPrivKey.NodeId() - Network = Network.RegTest - IsFunder = false - FundingTxMinimumDepth = BlockHeightOffset32 6u - LocalStaticShutdownScriptPubKey = None - RemoteStaticShutdownScriptPubKey = None - LocalParams = remoteLocalParam - RemoteParams = remoteRemoteParams - RemoteChannelPubKeys = localChannelPubKeys - } - - let remoteLocalCommit : LocalCommit = - { - Index = commitmentNumber - Spec = remoteCommitmentSpec - PublishableTxs = { - CommitTx = FinalizedTx remoteCommitmentTx - HTLCTxs = List.Empty - } - PendingHTLCSuccessTxs = List.Empty - } - - let remoteSavedChannelState : SavedChannelState = { - StaticChannelConfig = staticRemoteChannelConfig - RemotePerCommitmentSecrets = remoteRemotePerCommitmentSecrets - ShortChannelId = None - LocalCommit = remoteLocalCommit - RemoteCommit = remoteRemoteCommit - LocalChanges = LocalChanges.Zero - RemoteChanges = RemoteChanges.Zero - } - - let remoteSpendingOldLocalCommitmentRes = - ClosingHelpers.HandleFundingTxSpent - remoteSavedChannelState - None - remoteChannelPrivKeys - revokedRemoteCommitmentTx - - let handleFundingTxSpentErrorIsUnknownClosingTx = - match remoteSpendingOldLocalCommitmentRes.MainOutput with - | Error ClosingHelpers.OutputClaimError.UnknownClosingTx -> - true - | _ -> - false - - Expect.isTrue handleFundingTxSpentErrorIsUnknownClosingTx "HandleFundingTxSpent didn't return UnknownClosingTx for an old local commitment tx" -] + (PerCommitmentSecretStore()) + CommitmentNumber.FirstCommitment + + let remoteRemoteCommit = + { + Index = commitmentNumber + Spec = localCommitmentSpec + TxId = TxId <| localCommitmentTx.GetHash() + RemotePerCommitmentPoint = perCommitmentPoint + } + + let remoteRemoteParams = + { + DustLimitSatoshis = localParams.DustLimitSatoshis + MaxHTLCValueInFlightMSat = + localParams.MaxHTLCValueInFlightMSat + ChannelReserveSatoshis = + localParams.ChannelReserveSatoshis + HTLCMinimumMSat = localParams.HTLCMinimumMSat + ToSelfDelay = localParams.ToSelfDelay + MaxAcceptedHTLCs = localParams.MaxAcceptedHTLCs + Features = localParams.Features + } + + let staticRemoteChannelConfig: StaticChannelConfig = + { + FundingScriptCoin = fundingScriptCoin + AnnounceChannel = false + RemoteNodeId = localNodeMasterPrivKey.NodeId() + Network = Network.RegTest + IsFunder = false + FundingTxMinimumDepth = BlockHeightOffset32 6u + LocalStaticShutdownScriptPubKey = None + RemoteStaticShutdownScriptPubKey = None + LocalParams = remoteLocalParam + RemoteParams = remoteRemoteParams + RemoteChannelPubKeys = localChannelPubKeys + } + + let remoteLocalCommit: LocalCommit = + { + Index = commitmentNumber + Spec = remoteCommitmentSpec + PublishableTxs = + { + CommitTx = FinalizedTx remoteCommitmentTx + HTLCTxs = List.Empty + } + PendingHTLCSuccessTxs = List.Empty + } + + let remoteSavedChannelState: SavedChannelState = + { + StaticChannelConfig = staticRemoteChannelConfig + RemotePerCommitmentSecrets = + remoteRemotePerCommitmentSecrets + ShortChannelId = None + LocalCommit = remoteLocalCommit + RemoteCommit = remoteRemoteCommit + LocalChanges = LocalChanges.Zero + RemoteChanges = RemoteChanges.Zero + } + + let remoteSpendingOldLocalCommitmentRes = + ClosingHelpers.HandleFundingTxSpent + remoteSavedChannelState + None + remoteChannelPrivKeys + revokedRemoteCommitmentTx + + let handleFundingTxSpentErrorIsUnknownClosingTx = + match remoteSpendingOldLocalCommitmentRes.MainOutput with + | Error ClosingHelpers.OutputClaimError.UnknownClosingTx -> + true + | _ -> false + + Expect.isTrue + handleFundingTxSpentErrorIsUnknownClosingTx + "HandleFundingTxSpent didn't return UnknownClosingTx for an old local commitment tx" + ] diff --git a/tests/DotNetLightning.Core.Tests/TxOutLexicographicCompareTests.fs b/tests/DotNetLightning.Core.Tests/TxOutLexicographicCompareTests.fs index 7c5c4a2e9..fb56b96ee 100644 --- a/tests/DotNetLightning.Core.Tests/TxOutLexicographicCompareTests.fs +++ b/tests/DotNetLightning.Core.Tests/TxOutLexicographicCompareTests.fs @@ -6,14 +6,17 @@ open DotNetLightning.Utils [] let tests = - let test (amount0: int) - (amount1: int) - (script0: array) - (script1: array) - (expected: int) = - let txOut0 = TxOut(Money (int64 amount0), Script script0) - let txOut1 = TxOut(Money (int64 amount1), Script script1) + let test + (amount0: int) + (amount1: int) + (script0: array) + (script1: array) + (expected: int) + = + let txOut0 = TxOut(Money(int64 amount0), Script script0) + let txOut1 = TxOut(Money(int64 amount1), Script script1) let result = TxOut.LexicographicCompare txOut0 txOut1 + Expect.equal result expected @@ -23,64 +26,65 @@ let tests = amount0 script0 amount1 - script1 - ) + script1) - testList "TxOutLexicographicCompare tests" [ - testCase "smaller amount, shorter pubkey, lower bytes values" <| fun _ -> - test 1 2 [| 0uy |] [| 1uy; 1uy |] -1 - testCase "smaller amount, shorter pubkey, equal bytes values" <| fun _ -> - test 1 2 [| 0uy |] [| 0uy; 0uy |] -1 - testCase "smaller amount, shorter pubkey, greater bytes values" <| fun _ -> - test 1 2 [| 1uy |] [| 0uy; 0uy |] -1 - testCase "smaller amount, equal lengths, lower bytes values" <| fun _ -> - test 1 2 [| 0uy |] [| 1uy |] -1 - testCase "smaller amount, equal lengths, equal bytes values" <| fun _ -> - test 1 2 [| 0uy |] [| 0uy |] -1 - testCase "smaller amount, equal lengths, greater bytes values" <| fun _ -> - test 1 2 [| 1uy |] [| 0uy |] -1 - testCase "smaller amount, longer pubkey, lower bytes values" <| fun _ -> - test 1 2 [| 0uy; 0uy |] [| 1uy |] -1 - testCase "smaller amount, longer pubkey, equal bytes values" <| fun _ -> - test 1 2 [| 0uy; 0uy |] [| 0uy |] -1 - testCase "smaller amount, longer pubkey, greater bytes values" <| fun _ -> - test 1 2 [| 1uy; 1uy |] [| 0uy |] -1 + testList + "TxOutLexicographicCompare tests" + [ + testCase "smaller amount, shorter pubkey, lower bytes values" + <| fun _ -> test 1 2 [| 0uy |] [| 1uy; 1uy |] -1 + testCase "smaller amount, shorter pubkey, equal bytes values" + <| fun _ -> test 1 2 [| 0uy |] [| 0uy; 0uy |] -1 + testCase "smaller amount, shorter pubkey, greater bytes values" + <| fun _ -> test 1 2 [| 1uy |] [| 0uy; 0uy |] -1 + testCase "smaller amount, equal lengths, lower bytes values" + <| fun _ -> test 1 2 [| 0uy |] [| 1uy |] -1 + testCase "smaller amount, equal lengths, equal bytes values" + <| fun _ -> test 1 2 [| 0uy |] [| 0uy |] -1 + testCase "smaller amount, equal lengths, greater bytes values" + <| fun _ -> test 1 2 [| 1uy |] [| 0uy |] -1 + testCase "smaller amount, longer pubkey, lower bytes values" + <| fun _ -> test 1 2 [| 0uy; 0uy |] [| 1uy |] -1 + testCase "smaller amount, longer pubkey, equal bytes values" + <| fun _ -> test 1 2 [| 0uy; 0uy |] [| 0uy |] -1 + testCase "smaller amount, longer pubkey, greater bytes values" + <| fun _ -> test 1 2 [| 1uy; 1uy |] [| 0uy |] -1 - testCase "same amount, shorter pubkey, lower bytes values" <| fun _ -> - test 1 1 [| 0uy |] [| 1uy; 1uy |] -1 - testCase "same amount, shorter pubkey, equal bytes values" <| fun _ -> - test 1 1 [| 0uy |] [| 0uy; 0uy |] -1 - testCase "same amount, shorter pubkey, greater bytes values" <| fun _ -> - test 1 1 [| 1uy |] [| 0uy; 0uy |] 1 - testCase "same amount, equal lengths, lower bytes values" <| fun _ -> - test 1 1 [| 0uy |] [| 1uy |] -1 - testCase "same amount, equal lengths, equal bytes values" <| fun _ -> - test 1 1 [| 0uy |] [| 0uy |] 0 - testCase "same amount, equal lengths, greater bytes values" <| fun _ -> - test 1 1 [| 1uy |] [| 0uy |] 1 - testCase "same amount, longer pubkey, lower bytes values" <| fun _ -> - test 1 1 [| 0uy; 0uy |] [| 1uy |] -1 - testCase "same amount, longer pubkey, equal bytes values" <| fun _ -> - test 1 1 [| 0uy; 0uy |] [| 0uy |] 1 - testCase "same amount, longer pubkey, greater bytes values" <| fun _ -> - test 1 1 [| 1uy; 1uy |] [| 0uy |] 1 + testCase "same amount, shorter pubkey, lower bytes values" + <| fun _ -> test 1 1 [| 0uy |] [| 1uy; 1uy |] -1 + testCase "same amount, shorter pubkey, equal bytes values" + <| fun _ -> test 1 1 [| 0uy |] [| 0uy; 0uy |] -1 + testCase "same amount, shorter pubkey, greater bytes values" + <| fun _ -> test 1 1 [| 1uy |] [| 0uy; 0uy |] 1 + testCase "same amount, equal lengths, lower bytes values" + <| fun _ -> test 1 1 [| 0uy |] [| 1uy |] -1 + testCase "same amount, equal lengths, equal bytes values" + <| fun _ -> test 1 1 [| 0uy |] [| 0uy |] 0 + testCase "same amount, equal lengths, greater bytes values" + <| fun _ -> test 1 1 [| 1uy |] [| 0uy |] 1 + testCase "same amount, longer pubkey, lower bytes values" + <| fun _ -> test 1 1 [| 0uy; 0uy |] [| 1uy |] -1 + testCase "same amount, longer pubkey, equal bytes values" + <| fun _ -> test 1 1 [| 0uy; 0uy |] [| 0uy |] 1 + testCase "same amount, longer pubkey, greater bytes values" + <| fun _ -> test 1 1 [| 1uy; 1uy |] [| 0uy |] 1 - testCase "greater amount, shorter pubkey, lower bytes values" <| fun _ -> - test 2 1 [| 0uy |] [| 1uy; 1uy |] 1 - testCase "greater amount, shorter pubkey, equal bytes values" <| fun _ -> - test 2 1 [| 0uy |] [| 0uy; 0uy |] 1 - testCase "greater amount, shorter pubkey, greater bytes values" <| fun _ -> - test 2 1 [| 1uy |] [| 0uy; 0uy |] 1 - testCase "greater amount, equal lengths, lower bytes values" <| fun _ -> - test 2 1 [| 0uy |] [| 1uy |] 1 - testCase "greater amount, equal lengths, equal bytes values" <| fun _ -> - test 2 1 [| 0uy |] [| 0uy |] 1 - testCase "greater amount, equal lengths, greater bytes values" <| fun _ -> - test 2 1 [| 1uy |] [| 0uy |] 1 - testCase "greater amount, longer pubkey, lower bytes values" <| fun _ -> - test 2 1 [| 0uy; 0uy |] [| 1uy |] 1 - testCase "greater amount, longer pubkey, equal bytes values" <| fun _ -> - test 2 1 [| 0uy; 0uy |] [| 0uy |] 1 - testCase "greater amount, longer pubkey, greater bytes values" <| fun _ -> - test 2 1 [| 1uy; 1uy |] [| 0uy |] 1 - ] + testCase "greater amount, shorter pubkey, lower bytes values" + <| fun _ -> test 2 1 [| 0uy |] [| 1uy; 1uy |] 1 + testCase "greater amount, shorter pubkey, equal bytes values" + <| fun _ -> test 2 1 [| 0uy |] [| 0uy; 0uy |] 1 + testCase "greater amount, shorter pubkey, greater bytes values" + <| fun _ -> test 2 1 [| 1uy |] [| 0uy; 0uy |] 1 + testCase "greater amount, equal lengths, lower bytes values" + <| fun _ -> test 2 1 [| 0uy |] [| 1uy |] 1 + testCase "greater amount, equal lengths, equal bytes values" + <| fun _ -> test 2 1 [| 0uy |] [| 0uy |] 1 + testCase "greater amount, equal lengths, greater bytes values" + <| fun _ -> test 2 1 [| 1uy |] [| 0uy |] 1 + testCase "greater amount, longer pubkey, lower bytes values" + <| fun _ -> test 2 1 [| 0uy; 0uy |] [| 1uy |] 1 + testCase "greater amount, longer pubkey, equal bytes values" + <| fun _ -> test 2 1 [| 0uy; 0uy |] [| 0uy |] 1 + testCase "greater amount, longer pubkey, greater bytes values" + <| fun _ -> test 2 1 [| 1uy; 1uy |] [| 0uy |] 1 + ] diff --git a/tests/DotNetLightning.Core.Tests/Utils/Utils.fs b/tests/DotNetLightning.Core.Tests/Utils/Utils.fs index d4f1d5a79..e6a6f7835 100644 --- a/tests/DotNetLightning.Core.Tests/Utils/Utils.fs +++ b/tests/DotNetLightning.Core.Tests/Utils/Utils.fs @@ -4,18 +4,42 @@ module Utils open System open Expecto -let CheckArrayEqual (actual: 'a array) (expected: 'a array) = +let CheckArrayEqual (actual: array<'a>) (expected: array<'a>) = let mutable index = 0 + try - for offset in seq { for x in 1..Int32.MaxValue do if x % 50 = 0 then yield x} do + for offset in + seq { + for x in 1 .. Int32.MaxValue do + if x % 50 = 0 then + yield x + } do index <- offset - Expect.equal actual.[offset..(offset + 50)] expected.[offset..(offset + 50)] (sprintf "failed in %d ~ %d" offset (offset + 50)) + + Expect.equal + actual.[offset .. (offset + 50)] + expected.[offset .. (offset + 50)] + (sprintf "failed in %i ~ %i" offset (offset + 50)) with - | :? IndexOutOfRangeException -> + | :? IndexOutOfRangeException -> try - Expect.equal actual.[(actual.Length - 50)..(actual.Length - 1)] expected.[(actual.Length - 50)..(actual.Length - 1)] (sprintf "failed in last 50 of actual: %d (expected length was %d)" (actual.Length) (expected.Length)) + Expect.equal + actual.[(actual.Length - 50) .. (actual.Length - 1)] + expected.[(actual.Length - 50) .. (actual.Length - 1)] + (sprintf + "failed in last 50 of actual: %i (expected length was %i)" + (actual.Length) + (expected.Length)) + Expect.equal actual expected "" with | :? IndexOutOfRangeException -> - Expect.equal actual.[(expected.Length - 50)..(expected.Length - 1)] expected.[(expected.Length - 50)..(expected.Length - 1)] (sprintf "failed in last 50 of expected: %d (actual length was %d)" (expected.Length) (actual.Length)) - Expect.equal actual expected "" \ No newline at end of file + Expect.equal + actual.[(expected.Length - 50) .. (expected.Length - 1)] + expected.[(expected.Length - 50) .. (expected.Length - 1)] + (sprintf + "failed in last 50 of expected: %i (actual length was %i)" + (expected.Length) + (actual.Length)) + + Expect.equal actual expected ""