From 974723f7163132c0aa81ac97a183646b512a55fc Mon Sep 17 00:00:00 2001 From: Andrew Cann Date: Tue, 2 Jun 2020 18:44:16 +0800 Subject: [PATCH] Add and switch to new types for revocation Add the UInt48, RevocationKey, CommitmentPubKey, CommitmentSeed and RevocationSet types. A RevocationKey is a secret key that can be used to revoke a transaction. It's shared in the revoke_and_ack message and stored in the user's wallet in a RevocationSet. A CommitmentPubKey is the public key of a RevocationKey, also known as a "per commitment point". A CommitmentSeed is the master revocation key for a side of a channel and can be used to derive all the revocation keys for that side of that channel. A RevocationSet stores a set of revocation keys in a compact form specified in BOLT #3 which allows earlier keys to be derived from later keys. --- .../Chain/KeysInterface.fs | 6 +- src/DotNetLightning.Core/Channel/Channel.fs | 81 ++--- .../Channel/ChannelTypes.fs | 4 +- .../Channel/ChannelUtils.fs | 20 -- .../Channel/ChannelValidation.fs | 20 +- .../Channel/Commitments.fs | 20 +- .../Channel/CommitmentsModule.fs | 100 ++++--- .../Crypto/RevocationSet.fs | 73 +++++ .../DotNetLightning.Core.fsproj | 9 +- .../Serialize/LightningStream.fs | 8 + .../Serialize/Msgs/Msgs.fs | 44 +-- .../Transactions/Transactions.fs | 59 ++-- src/DotNetLightning.Core/Utils/ChannelId.fs | 9 + src/DotNetLightning.Core/Utils/Extensions.fs | 18 ++ .../Utils/NBitcoinExtensions.fs | 9 + src/DotNetLightning.Core/Utils/Primitives.fs | 166 ++++++++++- src/DotNetLightning.Core/Utils/TxId.fs | 9 + src/DotNetLightning.Core/Utils/UInt48.fs | 86 ++++++ .../ActorManagers/ChannelManager.fs | 1 + .../Interfaces/IChainWatcher.fs | 1 + .../Services/BitcoinRPCPollingChainWatcher.fs | 1 + .../Services/BroadCaster.fs | 2 + .../Services/ChainWatcher.fs | 1 + .../DotNetLightning.Core.Tests.fsproj | 1 + .../Generators/Msgs.fs | 18 +- .../Generators/Primitives.fs | 19 +- .../KeyRepositoryTests.fs | 4 +- .../RevocationSetTests.fs | 276 ++++++++++++++++++ .../Serialization.fs | 22 +- .../TransactionBolt3TestVectorTests.fs | 27 +- 30 files changed, 889 insertions(+), 225 deletions(-) delete mode 100644 src/DotNetLightning.Core/Channel/ChannelUtils.fs create mode 100644 src/DotNetLightning.Core/Crypto/RevocationSet.fs create mode 100644 src/DotNetLightning.Core/Utils/ChannelId.fs create mode 100644 src/DotNetLightning.Core/Utils/TxId.fs create mode 100644 src/DotNetLightning.Core/Utils/UInt48.fs create mode 100644 tests/DotNetLightning.Core.Tests/RevocationSetTests.fs diff --git a/src/DotNetLightning.Core/Chain/KeysInterface.fs b/src/DotNetLightning.Core/Chain/KeysInterface.fs index 97a14f392..2e87ebb7b 100644 --- a/src/DotNetLightning.Core/Chain/KeysInterface.fs +++ b/src/DotNetLightning.Core/Chain/KeysInterface.fs @@ -54,7 +54,7 @@ type ChannelKeys = { PaymentBaseKey: Key DelayedPaymentBaseKey: Key HTLCBaseKey: Key - CommitmentSeed: uint256 + CommitmentSeed: CommitmentSeed } with @@ -65,7 +65,6 @@ type ChannelKeys = { PaymentBasePubKey = this.PaymentBaseKey.PubKey DelayedPaymentBasePubKey = this.DelayedPaymentBaseKey.PubKey HTLCBasePubKey = this.HTLCBaseKey.PubKey - CommitmentSeed = this.CommitmentSeed } /// In usual operation we should not hold secrets on memory. So only hold pubkey @@ -75,7 +74,6 @@ and ChannelPubKeys = { PaymentBasePubKey: PubKey DelayedPaymentBasePubKey: PubKey HTLCBasePubKey: PubKey - CommitmentSeed: uint256 } @@ -108,7 +106,7 @@ type DefaultKeyRepository(nodeSecret: ExtKey, channelIndex: int) = let destinationKey = channelMasterKey.Derive(1, true).PrivateKey let shutdownKey = channelMasterKey.Derive(2, true).PrivateKey - let commitmentSeed = channelMasterKey.Derive(3, true).PrivateKey.ToBytes() |> uint256 + let commitmentSeed = channelMasterKey.Derive(3, true).PrivateKey |> CommitmentSeed let fundingKey = channelMasterKey.Derive(4, true).PrivateKey let fundingPubKey = fundingKey.PubKey diff --git a/src/DotNetLightning.Core/Channel/Channel.fs b/src/DotNetLightning.Core/Channel/Channel.fs index 24baaa2cd..978149bca 100644 --- a/src/DotNetLightning.Core/Channel/Channel.fs +++ b/src/DotNetLightning.Core/Channel/Channel.fs @@ -99,25 +99,34 @@ module Channel = [ MutualClosePerformed nextData ] |> Ok - let claimCurrentLocalCommitTxOutputs (_keyRepo: IKeysRepository, channelPubKeys: ChannelPubKeys, commitments: Commitments, commitTx: CommitTx) = + let claimCurrentLocalCommitTxOutputs (keyRepo: IKeysRepository, channelPubKeys: ChannelPubKeys, commitments: Commitments, commitTx: CommitTx) = result { + let chanPrivateKeys = keyRepo.GetChannelKeys commitments.LocalParams.IsFunder + let commitmentSeed = chanPrivateKeys.CommitmentSeed do! check (commitments.LocalCommit.PublishableTxs.CommitTx.Value.GetTxId()) (=) (commitTx.Value.GetTxId()) "txid mismatch. provided txid (%A) does not match current local commit tx (%A)" - let _localPerCommitmentPoint = ChannelUtils.buildCommitmentPoint (channelPubKeys.CommitmentSeed, commitments.LocalCommit.Index) + let _localPerCommitmentPoint = + commitmentSeed.DeriveCommitmentPubKey commitments.LocalCommit.Index let _localRevocationPubKey = Generators.revocationPubKey failwith "TODO" } - let makeChannelReestablish (data: Data.IHasCommitments): Result = - let commitmentSeed = data.Commitments.LocalParams.ChannelPubKeys.CommitmentSeed + let makeChannelReestablish (keyRepo: IKeysRepository) + (data: Data.IHasCommitments) + : Result = + let chanPrivateKeys = keyRepo.GetChannelKeys data.Commitments.LocalParams.IsFunder + let commitmentSeed = chanPrivateKeys.CommitmentSeed let ourChannelReestablish = { ChannelId = data.ChannelId NextLocalCommitmentNumber = 1UL NextRemoteCommitmentNumber = 0UL - DataLossProtect = OptionalField.Some({ - YourLastPerCommitmentSecret = PaymentPreimage.Create([|for _ in 0..31 -> 0uy|]) - MyCurrentPerCommitmentPoint = ChannelUtils.buildCommitmentPoint(commitmentSeed, 0UL) - }) + DataLossProtect = OptionalField.Some <| { + YourLastPerCommitmentSecret = + RevocationKey.FromBytes [|for _ in 0..31 -> 0uy|] + MyCurrentPerCommitmentPoint = + let revocationKey = commitmentSeed.DeriveRevocationKey CommitmentNumber.LastCommitment + revocationKey.CommitmentPubKey + } } [ WeSentChannelReestablish ourChannelReestablish ] |> Ok @@ -143,7 +152,7 @@ module Channel = PaymentBasepoint = inputInitFunder.ChannelKeys.PaymentBaseKey.PubKey DelayedPaymentBasepoint = inputInitFunder.ChannelKeys.DelayedPaymentBaseKey.PubKey HTLCBasepoint = inputInitFunder.ChannelKeys.HTLCBaseKey.PubKey - FirstPerCommitmentPoint = ChannelUtils.buildCommitmentPoint(inputInitFunder.ChannelKeys.CommitmentSeed, 0UL) + FirstPerCommitmentPoint = inputInitFunder.ChannelKeys.CommitmentSeed.DeriveCommitmentPubKey CommitmentNumber.FirstCommitment ChannelFlags = inputInitFunder.ChannelFlags ShutdownScriptPubKey = cs.Config.ChannelOptions.ShutdownScriptPubKey } @@ -177,7 +186,7 @@ module Channel = state.LastSent.FeeRatePerKw outIndex (fundingTx.Value.GetHash() |> TxId) - (ChannelUtils.buildCommitmentPoint(commitmentSeed, 0UL)) + (commitmentSeed.DeriveCommitmentPubKey CommitmentNumber.FirstCommitment) msg.FirstPerCommitmentPoint cs.Secp256k1Context cs.Network @@ -194,7 +203,7 @@ module Channel = Data.WaitForFundingSignedData.FundingTx = fundingTx Data.WaitForFundingSignedData.LocalSpec = commitmentSpec LocalCommitTx = localCommitTx - RemoteCommit = { RemoteCommit.Index = 0UL; + RemoteCommit = { RemoteCommit.Index = CommitmentNumber.FirstCommitment Spec = remoteSpec TxId = remoteCommitTx.Value.GetGlobalTransaction().GetTxId() RemotePerCommitmentPoint = msg.FirstPerCommitmentPoint } @@ -221,7 +230,7 @@ module Channel = state.LastSent.FundingTxId state.LastSent.FundingOutputIndex amount - LocalCommit = { Index = 0UL; + LocalCommit = { Index = CommitmentNumber.FirstCommitment; Spec = state.LocalSpec; PublishableTxs = { PublishableTxs.CommitTx = finalizedLocalCommitTx HTLCTxs = [] } @@ -233,8 +242,8 @@ module Channel = RemoteNextHTLCId = HTLCId.Zero OriginChannels = Map.empty // we will receive their next per-commitment point in the next msg, so we temporarily put a random byte array - RemoteNextCommitInfo = DataEncoders.HexEncoder() .DecodeData("0101010101010101010101010101010101010101010101010101010101010101") |> Key |> fun k -> k.PubKey |> RemoteNextCommitInfo.Revoked - RemotePerCommitmentSecrets = ShaChain.Zero + RemoteNextCommitInfo = DataEncoders.HexEncoder().DecodeData("0101010101010101010101010101010101010101010101010101010101010101") |> Key |> fun k -> k.PubKey |> CommitmentPubKey |> RemoteNextCommitInfo.Revoked + RemotePerCommitmentSecrets = RevocationSet() ChannelId = msg.ChannelId } let nextState = { WaitForFundingConfirmedData.Commitments = commitments @@ -249,15 +258,15 @@ module Channel = [ NewInboundChannelStarted({ InitFundee = inputInitFundee }) ] |> Ok | WaitForFundingConfirmed state, CreateChannelReestablish -> - makeChannelReestablish state + makeChannelReestablish cs.KeysRepository state | ChannelState.Normal state, CreateChannelReestablish -> - makeChannelReestablish state + makeChannelReestablish cs.KeysRepository state | WaitForOpenChannel state, ApplyOpenChannel msg -> result { do! Validation.checkOpenChannelMsgAcceptable (cs.FeeEstimator) (cs.Config) msg let localParams = state.InitFundee.LocalParams let channelKeys = state.InitFundee.ChannelKeys - let localCommitmentSecret = ChannelUtils.buildCommitmentSecret (channelKeys.CommitmentSeed, 0UL) + let localCommitmentPubKey = channelKeys.CommitmentSeed.DeriveCommitmentPubKey CommitmentNumber.FirstCommitment let acceptChannelMsg: AcceptChannelMsg = { TemporaryChannelId = msg.TemporaryChannelId DustLimitSatoshis = localParams.DustLimitSatoshis @@ -272,7 +281,7 @@ module Channel = PaymentBasepoint = channelKeys.PaymentBaseKey.PubKey DelayedPaymentBasepoint = channelKeys.DelayedPaymentBaseKey.PubKey HTLCBasepoint = channelKeys.HTLCBaseKey.PubKey - FirstPerCommitmentPoint = localCommitmentSecret.PubKey + FirstPerCommitmentPoint = localCommitmentPubKey ShutdownScriptPubKey = cs.Config.ChannelOptions.ShutdownScriptPubKey } let remoteParams = RemoteParams.FromOpenChannel cs.RemoteNodeId state.InitFundee.RemoteInit msg cs.Config.ChannelHandshakeConfig @@ -313,12 +322,12 @@ module Channel = RemoteParams = state.RemoteParams ChannelFlags = state.ChannelFlags FundingScriptCoin = ChannelHelpers.getFundingScriptCoin state.LocalParams.ChannelPubKeys state.RemoteParams.FundingPubKey msg.FundingTxId msg.FundingOutputIndex state.FundingSatoshis - LocalCommit = { LocalCommit.Index = 0UL; + LocalCommit = { LocalCommit.Index = CommitmentNumber.FirstCommitment Spec = localSpec PublishableTxs = { PublishableTxs.CommitTx = finalizedCommitTx; HTLCTxs = [] } PendingHTLCSuccessTxs = [] } - RemoteCommit = { RemoteCommit.Index = 0UL; + RemoteCommit = { RemoteCommit.Index = CommitmentNumber.FirstCommitment Spec = remoteSpec TxId = remoteCommitTx.Value.GetGlobalTransaction().GetTxId() RemotePerCommitmentPoint = state.RemoteFirstPerCommitmentPoint } @@ -327,8 +336,8 @@ module Channel = LocalNextHTLCId = HTLCId.Zero RemoteNextHTLCId = HTLCId.Zero OriginChannels = Map.empty - RemoteNextCommitInfo = DataEncoders.HexEncoder() .DecodeData("0101010101010101010101010101010101010101010101010101010101010101") |> Key |> fun k -> k.PubKey |> RemoteNextCommitInfo.Revoked - RemotePerCommitmentSecrets = ShaChain.Zero + RemoteNextCommitInfo = DataEncoders.HexEncoder().DecodeData("0101010101010101010101010101010101010101010101010101010101010101") |> Key |> fun k -> k.PubKey |> CommitmentPubKey |> RemoteNextCommitInfo.Revoked + RemotePerCommitmentSecrets = RevocationSet() ChannelId = channelId } let nextState = { WaitForFundingConfirmedData.Commitments = commitments Deferred = None @@ -343,8 +352,9 @@ module Channel = if state.Commitments.RemoteParams.MinimumDepth > depth then [] |> Ok else + let chanPrivateKeys = cs.KeysRepository.GetChannelKeys state.Commitments.LocalParams.IsFunder let nextPerCommitmentPoint = - ChannelUtils.buildCommitmentPoint (state.Commitments.LocalParams.ChannelPubKeys.CommitmentSeed, 1UL) + chanPrivateKeys.CommitmentSeed.DeriveCommitmentPubKey CommitmentNumber.FirstCommitment.NextCommitment let msgToSend: FundingLockedMsg = { ChannelId = state.Commitments.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 @@ -485,22 +495,27 @@ module Channel = | ChannelState.Normal state, ApplyRevokeAndACK msg -> let cm = state.Commitments match cm.RemoteNextCommitInfo with - | RemoteNextCommitInfo.Waiting _ when (msg.PerCommitmentSecret.ToPubKey() <> cm.RemoteCommit.RemotePerCommitmentPoint) -> + | RemoteNextCommitInfo.Waiting _ when (msg.PerCommitmentSecret.CommitmentPubKey <> cm.RemoteCommit.RemotePerCommitmentPoint) -> let errorMsg = sprintf "Invalid revoke_and_ack %A; must be %A" msg.PerCommitmentSecret cm.RemoteCommit.RemotePerCommitmentPoint invalidRevokeAndACK msg errorMsg | RemoteNextCommitInfo.Revoked _ -> let errorMsg = sprintf "Unexpected revocation" invalidRevokeAndACK msg errorMsg | RemoteNextCommitInfo.Waiting({ NextRemoteCommit = theirNextCommit }) -> - let commitments1 = { cm with LocalChanges = { cm.LocalChanges with Signed = []; ACKed = cm.LocalChanges.ACKed @ cm.LocalChanges.Signed } - RemoteChanges = { cm.RemoteChanges with Signed = [] } - RemoteCommit = theirNextCommit - RemoteNextCommitInfo = RemoteNextCommitInfo.Revoked(msg.NextPerCommitmentPoint) - RemotePerCommitmentSecrets = cm.RemotePerCommitmentSecrets.AddHash (msg.PerCommitmentSecret.ToByteArray(), 0xffffffffffffUL - cm.RemoteCommit.Index) } - let result = [ WeAcceptedRevokeAndACK(commitments1) ] - result |> Ok - failwith "needs update" - + let remotePerCommitmentSecretsOpt = + cm.RemotePerCommitmentSecrets.InsertRevocationKey + cm.RemoteCommit.Index + msg.PerCommitmentSecret + match remotePerCommitmentSecretsOpt with + | Error err -> invalidRevokeAndACK msg err.Message + | Ok remotePerCommitmentSecrets -> + let commitments1 = { cm with LocalChanges = { cm.LocalChanges with Signed = []; ACKed = cm.LocalChanges.ACKed @ cm.LocalChanges.Signed } + RemoteChanges = { cm.RemoteChanges with Signed = [] } + RemoteCommit = theirNextCommit + RemoteNextCommitInfo = RemoteNextCommitInfo.Revoked msg.NextPerCommitmentPoint + RemotePerCommitmentSecrets = remotePerCommitmentSecrets } + let result = Ok [ WeAcceptedRevokeAndACK(commitments1) ] + failwith "needs update" | ChannelState.Normal state, ChannelCommand.Close cmd -> let localSPK = cmd.ScriptPubKey |> Option.defaultValue (state.Commitments.LocalParams.DefaultFinalScriptPubKey) diff --git a/src/DotNetLightning.Core/Channel/ChannelTypes.fs b/src/DotNetLightning.Core/Channel/ChannelTypes.fs index e77f1b0bd..db389291d 100644 --- a/src/DotNetLightning.Core/Channel/ChannelTypes.fs +++ b/src/DotNetLightning.Core/Channel/ChannelTypes.fs @@ -76,7 +76,7 @@ module Data = FundingSatoshis: Money PushMsat: LNMoney InitialFeeRatePerKw: FeeRatePerKw - RemoteFirstPerCommitmentPoint: PubKey + RemoteFirstPerCommitmentPoint: CommitmentPubKey LastSent: OpenChannelMsg } with interface IChannelStateData @@ -88,7 +88,7 @@ module Data = FundingSatoshis: Money PushMSat: LNMoney InitialFeeRatePerKw: FeeRatePerKw - RemoteFirstPerCommitmentPoint: PubKey + RemoteFirstPerCommitmentPoint: CommitmentPubKey ChannelFlags: uint8 LastSent: AcceptChannelMsg } diff --git a/src/DotNetLightning.Core/Channel/ChannelUtils.fs b/src/DotNetLightning.Core/Channel/ChannelUtils.fs deleted file mode 100644 index d95f47721..000000000 --- a/src/DotNetLightning.Core/Channel/ChannelUtils.fs +++ /dev/null @@ -1,20 +0,0 @@ -namespace DotNetLightning.Channel - -open NBitcoin -open NBitcoin.Crypto - -module ChannelUtils = - - /// Various functions for key derivation and tx creation for use within channels. Primarily used in Channel - let buildCommitmentSecret (commitmentSeed: uint256, index: uint64): Key = - let mutable res = commitmentSeed.ToBytes() - for i in 0..47 do - let bitpos = 47 - i - if index &&& (1UL <<< bitpos) = (1UL <<< bitpos) then - res.[bitpos / 8] <- (res.[bitpos / 8] ^^^ (1uy <<< (7 &&& bitpos))) - res <- Hashes.SHA256(res) - res |> Key - - let buildCommitmentPoint(seed: uint256, index: uint64) = - buildCommitmentSecret(seed, index) |> fun k -> k.PubKey - diff --git a/src/DotNetLightning.Core/Channel/ChannelValidation.fs b/src/DotNetLightning.Core/Channel/ChannelValidation.fs index e98b2de6e..4626db3a2 100644 --- a/src/DotNetLightning.Core/Channel/ChannelValidation.fs +++ b/src/DotNetLightning.Core/Channel/ChannelValidation.fs @@ -73,8 +73,8 @@ module internal ChannelHelpers = (initialFeeRatePerKw: FeeRatePerKw) (fundingOutputIndex: TxOutIndex) (fundingTxId: TxId) - (localPerCommitmentPoint: PubKey) - (remotePerCommitmentPoint: PubKey) + (localPerCommitmentPoint: CommitmentPubKey) + (remotePerCommitmentPoint: CommitmentPubKey) (secpContext: ISecp256k1) (n: Network): Result = let toLocal = if (localParams.IsFunder) then fundingSatoshis.ToLNMoney() - pushMSat else pushMSat @@ -95,12 +95,12 @@ module internal ChannelHelpers = fundingTxId fundingOutputIndex fundingSatoshis - let revPubKeyForLocal = Generators.revocationPubKey secpContext remoteParams.RevocationBasePoint localPerCommitmentPoint - let delayedPubKeyForLocal = Generators.derivePubKey secpContext localParams.ChannelPubKeys.DelayedPaymentBasePubKey localPerCommitmentPoint - let paymentPubKeyForLocal = Generators.derivePubKey secpContext remoteParams.PaymentBasePoint localPerCommitmentPoint + let revPubKeyForLocal = Generators.revocationPubKey secpContext remoteParams.RevocationBasePoint localPerCommitmentPoint.PubKey + let delayedPubKeyForLocal = Generators.derivePubKey secpContext localParams.ChannelPubKeys.DelayedPaymentBasePubKey localPerCommitmentPoint.PubKey + let paymentPubKeyForLocal = Generators.derivePubKey secpContext remoteParams.PaymentBasePoint localPerCommitmentPoint.PubKey let localCommitTx = Transactions.makeCommitTx scriptCoin - 0UL + CommitmentNumber.FirstCommitment localParams.ChannelPubKeys.PaymentBasePubKey remoteParams.PaymentBasePoint localParams.IsFunder @@ -113,12 +113,12 @@ module internal ChannelHelpers = (remoteParams.HTLCBasePoint) localSpec n - let revPubKeyForRemote = Generators.revocationPubKey secpContext localParams.ChannelPubKeys.RevocationBasePubKey remotePerCommitmentPoint - let delayedPubKeyForRemote = Generators.derivePubKey secpContext remoteParams.DelayedPaymentBasePoint remotePerCommitmentPoint - let paymentPubKeyForRemote = Generators.derivePubKey secpContext localParams.ChannelPubKeys.PaymentBasePubKey remotePerCommitmentPoint + let revPubKeyForRemote = Generators.revocationPubKey secpContext localParams.ChannelPubKeys.RevocationBasePubKey remotePerCommitmentPoint.PubKey + let delayedPubKeyForRemote = Generators.derivePubKey secpContext remoteParams.DelayedPaymentBasePoint remotePerCommitmentPoint.PubKey + let paymentPubKeyForRemote = Generators.derivePubKey secpContext localParams.ChannelPubKeys.PaymentBasePubKey remotePerCommitmentPoint.PubKey let remoteCommitTx = Transactions.makeCommitTx scriptCoin - 0UL + CommitmentNumber.FirstCommitment remoteParams.PaymentBasePoint localParams.ChannelPubKeys.PaymentBasePubKey (not localParams.IsFunder) diff --git a/src/DotNetLightning.Core/Channel/Commitments.fs b/src/DotNetLightning.Core/Channel/Commitments.fs index 1f26ebfbd..da1109687 100644 --- a/src/DotNetLightning.Core/Channel/Commitments.fs +++ b/src/DotNetLightning.Core/Channel/Commitments.fs @@ -60,23 +60,23 @@ type PublishableTxs = { } type LocalCommit = { - Index: uint64 + Index: CommitmentNumber Spec: CommitmentSpec PublishableTxs: PublishableTxs /// These are not redeemable on-chain until we get a corresponding preimage. PendingHTLCSuccessTxs: HTLCSuccessTx list } type RemoteCommit = { - Index: uint64 + Index: CommitmentNumber Spec: CommitmentSpec TxId: TxId - RemotePerCommitmentPoint: PubKey + RemotePerCommitmentPoint: CommitmentPubKey } type WaitingForRevocation = { NextRemoteCommit: RemoteCommit Sent: CommitmentSignedMsg - SentAfterLocalCommitmentIndex: uint64 + SentAfterLocalCommitmentIndex: CommitmentNumber ReSignASAP: bool } with @@ -90,7 +90,7 @@ type WaitingForRevocation = { type RemoteNextCommitInfo = | Waiting of WaitingForRevocation - | Revoked of PubKey + | Revoked of CommitmentPubKey with static member Waiting_: Prism = (fun remoteNextCommitInfo -> @@ -102,15 +102,15 @@ type RemoteNextCommitInfo = | Waiting _ -> Waiting waitingForRevocation | Revoked _ -> remoteNextCommitInfo) - static member Revoked_: Prism = + static member Revoked_: Prism = (fun remoteNextCommitInfo -> match remoteNextCommitInfo with | Waiting _ -> None - | Revoked pubKey -> Some pubKey), - (fun pubKey remoteNextCommitInfo -> + | Revoked commitmentPubKey -> Some commitmentPubKey), + (fun commitmentPubKey remoteNextCommitInfo -> match remoteNextCommitInfo with | Waiting _ -> remoteNextCommitInfo - | Revoked pubKey -> Revoked pubKey) + | Revoked commitmentPubKecommitmentPubKey -> Revoked commitmentPubKey) type Commitments = { LocalParams: LocalParams @@ -125,7 +125,7 @@ type Commitments = { RemoteNextHTLCId: HTLCId OriginChannels: Map RemoteNextCommitInfo: RemoteNextCommitInfo - RemotePerCommitmentSecrets: ShaChain + RemotePerCommitmentSecrets: RevocationSet ChannelId: ChannelId } with diff --git a/src/DotNetLightning.Core/Channel/CommitmentsModule.fs b/src/DotNetLightning.Core/Channel/CommitmentsModule.fs index d6f3adc00..2673742fd 100644 --- a/src/DotNetLightning.Core/Channel/CommitmentsModule.fs +++ b/src/DotNetLightning.Core/Channel/CommitmentsModule.fs @@ -23,25 +23,25 @@ module internal Commitments = let makeRemoteTxs (ctx: ISecp256k1) - (channelKeys: ChannelKeys) - (commitTxNumber: uint64) + (commitTxNumber: CommitmentNumber) (localParams: LocalParams) (remoteParams: RemoteParams) (commitmentInput: ScriptCoin) - (remotePerCommitmentPoint: PubKey) + (remotePerCommitmentPoint: CommitmentPubKey) (spec) (n) = - let pkGen = Generators.derivePubKey ctx remotePerCommitmentPoint - let localPaymentPK = pkGen (channelKeys.PaymentBaseKey.PubKey) - let localHTLCPK = pkGen channelKeys.HTLCBaseKey.PubKey - let remotePaymentPK = pkGen (remoteParams.PaymentBasePoint) - let remoteDelayedPaymentPK = pkGen (remoteParams.DelayedPaymentBasePoint) - let remoteHTLCPK = pkGen (remoteParams.HTLCBasePoint) - let remoteRevocationPK = pkGen (channelKeys.RevocationBaseKey.PubKey) + let channelKeys = localParams.ChannelPubKeys + let pkGen = Generators.derivePubKey ctx remotePerCommitmentPoint.PubKey + let localPaymentPK = pkGen channelKeys.PaymentBasePubKey + let localHTLCPK = pkGen channelKeys.HTLCBasePubKey + let remotePaymentPK = pkGen remoteParams.PaymentBasePoint + let remoteDelayedPaymentPK = pkGen remoteParams.DelayedPaymentBasePoint + let remoteHTLCPK = pkGen remoteParams.HTLCBasePoint + let remoteRevocationPK = pkGen channelKeys.RevocationBasePubKey let commitTx = Transactions.makeCommitTx commitmentInput commitTxNumber remoteParams.PaymentBasePoint - channelKeys.PaymentBaseKey.PubKey + channelKeys.PaymentBasePubKey (not localParams.IsFunder) (remoteParams.DustLimitSatoshis) (remoteRevocationPK) @@ -68,15 +68,15 @@ module internal Commitments = let makeLocalTXs (ctx: ISecp256k1) - (channelKeys: ChannelPubKeys) - (commitTxNumber: uint64) + (commitTxNumber: CommitmentNumber) (localParams: LocalParams) (remoteParams: RemoteParams) (commitmentInput: ScriptCoin) - (localPerCommitmentPoint: PubKey) + (localPerCommitmentPoint: CommitmentPubKey) (spec: CommitmentSpec) n: Result<(CommitTx * HTLCTimeoutTx list * HTLCSuccessTx list), _> = - let pkGen = Generators.derivePubKey ctx localPerCommitmentPoint + let channelKeys = localParams.ChannelPubKeys + let pkGen = Generators.derivePubKey ctx localPerCommitmentPoint.PubKey let localPaymentPK = pkGen channelKeys.PaymentBasePubKey let localDelayedPaymentPK = pkGen channelKeys.DelayedPaymentBasePubKey let localHTLCPK = pkGen channelKeys.HTLCBasePubKey @@ -277,11 +277,9 @@ module internal Commitments = result { // remote commitment will include all local changes + remote acked changes let! spec = cm.RemoteCommit.Spec.Reduce(cm.RemoteChanges.ACKed, cm.LocalChanges.Proposed) |> expectTransactionError - let localKeys = keyRepo.GetChannelKeys(not cm.LocalParams.IsFunder) let! (remoteCommitTx, htlcTimeoutTxs, htlcSuccessTxs) = Helpers.makeRemoteTxs (ctx) - (localKeys) - (cm.RemoteCommit.Index + 1UL) + cm.RemoteCommit.Index.NextCommitment (cm.LocalParams) (cm.RemoteParams) (cm.FundingScriptCoin) @@ -292,7 +290,7 @@ module internal Commitments = let htlcSigs = sortedHTLCTXs |> List.map( - (fun htlc -> keyRepo.GenerateKeyFromBasePointAndSign(htlc.Value, cm.LocalParams.ChannelPubKeys.HTLCBasePubKey, remoteNextPerCommitmentPoint)) + (fun htlc -> keyRepo.GenerateKeyFromBasePointAndSign(htlc.Value, cm.LocalParams.ChannelPubKeys.HTLCBasePubKey, remoteNextPerCommitmentPoint.PubKey)) >> fst >> (fun txSig -> txSig.Signature) ) @@ -300,17 +298,19 @@ module internal Commitments = Signature = !> signature.Signature HTLCSignatures = htlcSigs |> List.map (!>) } let nextCommitments = - let nextRemoteCommitInfo = { WaitingForRevocation.NextRemoteCommit = - { cm.RemoteCommit - with - Index = cm.RemoteCommit.Index + 1UL; - Spec = spec; - RemotePerCommitmentPoint = remoteNextPerCommitmentPoint - TxId = remoteCommitTx.GetTxId() } - Sent = msg - SentAfterLocalCommitmentIndex = cm.LocalCommit.Index - ReSignASAP = false } - + let nextRemoteCommitInfo = { + WaitingForRevocation.NextRemoteCommit = { + cm.RemoteCommit + with + Index = cm.RemoteCommit.Index.NextCommitment + Spec = spec + RemotePerCommitmentPoint = remoteNextPerCommitmentPoint + TxId = remoteCommitTx.GetTxId() + } + Sent = msg + SentAfterLocalCommitmentIndex = cm.LocalCommit.Index + ReSignASAP = false + } { cm with RemoteNextCommitInfo = RemoteNextCommitInfo.Waiting(nextRemoteCommitInfo) LocalChanges = { cm.LocalChanges with Proposed = []; Signed = cm.LocalChanges.Proposed } RemoteChanges = { cm.RemoteChanges with ACKed = []; Signed = cm.RemoteChanges.ACKed } } @@ -328,13 +328,15 @@ module internal Commitments = if cm.RemoteHasChanges() |> not then ReceivedCommitmentSignedWhenWeHaveNoPendingChanges |> Error else + let chanPrivateKeys = keyRepo.GetChannelKeys cm.LocalParams.IsFunder + let commitmentSeed = chanPrivateKeys.CommitmentSeed let chanKeys = cm.LocalParams.ChannelPubKeys - let nextI = cm.LocalCommit.Index + 1UL + let nextI = cm.LocalCommit.Index.NextCommitment result { let! spec = cm.LocalCommit.Spec.Reduce(cm.LocalChanges.ACKed, cm.RemoteChanges.Proposed) |> expectTransactionError - let localPerCommitmentPoint = ChannelUtils.buildCommitmentPoint (chanKeys.CommitmentSeed, nextI) + let localPerCommitmentPoint = commitmentSeed.DeriveCommitmentPubKey nextI let! (localCommitTx, htlcTimeoutTxs, htlcSuccessTxs) = - Helpers.makeLocalTXs (ctx) chanKeys (nextI) (cm.LocalParams) (cm.RemoteParams) (cm.FundingScriptCoin) (localPerCommitmentPoint) spec n + Helpers.makeLocalTXs (ctx) (nextI) (cm.LocalParams) (cm.RemoteParams) (cm.FundingScriptCoin) (localPerCommitmentPoint) spec n |> expectTransactionErrors let signature, signedCommitTx = keyRepo.GetSignatureFor (localCommitTx.Value, chanKeys.FundingPubKey) @@ -350,10 +352,13 @@ module internal Commitments = do! checkSignatureCountMismatch sortedHTLCTXs msg let _localHTLCSigs, sortedHTLCTXs = - let localHtlcSigsAndHTLCTxs = sortedHTLCTXs |> List.map(fun htlc -> keyRepo.GenerateKeyFromBasePointAndSign(htlc.Value, cm.LocalParams.ChannelPubKeys.HTLCBasePubKey, localPerCommitmentPoint)) + let localHtlcSigsAndHTLCTxs = + sortedHTLCTXs |> List.map(fun htlc -> + keyRepo.GenerateKeyFromBasePointAndSign(htlc.Value, cm.LocalParams.ChannelPubKeys.HTLCBasePubKey, localPerCommitmentPoint.PubKey) + ) localHtlcSigsAndHTLCTxs |> List.map(fst), localHtlcSigsAndHTLCTxs |> List.map(snd) |> Seq.cast |> List.ofSeq - let remoteHTLCPubKey = Generators.derivePubKey ctx (cm.RemoteParams.HTLCBasePoint) (localPerCommitmentPoint) + let remoteHTLCPubKey = Generators.derivePubKey ctx (cm.RemoteParams.HTLCBasePoint) localPerCommitmentPoint.PubKey let checkHTLCSig (htlc: IHTLCTx, remoteECDSASig: LNECDSASignature): Result<_, _> = let remoteS = TransactionSignature(remoteECDSASig.Value, SigHash.All) @@ -372,19 +377,32 @@ module internal Commitments = |> List.map(checkHTLCSig) |> List.sequenceResultA |> expectTransactionErrors - let successTxs = txList |> List.choose(fun o -> match o with | :? HTLCSuccessTx as tx -> Some tx | _ -> None) - let finalizedTxs = txList |> List.choose(fun o -> match o with | :? FinalizedTx as tx -> Some tx | _ -> None) + let successTxs = + txList |> List.choose(fun o -> + match o with + | :? HTLCSuccessTx as tx -> Some tx + | _ -> None + ) + let finalizedTxs = + txList |> List.choose(fun o -> + match o with + | :? FinalizedTx as tx -> Some tx + | _ -> None + ) let localPerCommitmentSecret = - ChannelUtils.buildCommitmentSecret(cm.LocalParams.ChannelPubKeys.CommitmentSeed, cm.LocalCommit.Index) + chanPrivateKeys.CommitmentSeed.DeriveRevocationKey cm.LocalCommit.Index let localNextPerCommitmentPoint = - ChannelUtils.buildCommitmentPoint(cm.LocalParams.ChannelPubKeys.CommitmentSeed, cm.LocalCommit.Index + 2UL) + let revocationKey = + chanPrivateKeys.CommitmentSeed.DeriveRevocationKey + cm.LocalCommit.Index.NextCommitment.NextCommitment + revocationKey.CommitmentPubKey let nextMsg = { RevokeAndACKMsg.ChannelId = cm.ChannelId - PerCommitmentSecret = localPerCommitmentSecret.ToBytes() |> PaymentPreimage.Create + PerCommitmentSecret = localPerCommitmentSecret NextPerCommitmentPoint = localNextPerCommitmentPoint } let nextCommitments = - let localCommit1 = { LocalCommit.Index = cm.LocalCommit.Index + 1UL + let localCommit1 = { LocalCommit.Index = cm.LocalCommit.Index.NextCommitment Spec = spec PublishableTxs = { PublishableTxs.CommitTx = finalizedCommitTx HTLCTxs = finalizedTxs } diff --git a/src/DotNetLightning.Core/Crypto/RevocationSet.fs b/src/DotNetLightning.Core/Crypto/RevocationSet.fs new file mode 100644 index 000000000..cf71b9db9 --- /dev/null +++ b/src/DotNetLightning.Core/Crypto/RevocationSet.fs @@ -0,0 +1,73 @@ +namespace DotNetLightning.Crypto + +open NBitcoin +open NBitcoin.Crypto +open DotNetLightning.Utils + +type InsertRevocationKeyError = + | UnexpectedCommitmentNumber of got: CommitmentNumber * expected: CommitmentNumber + | KeyMismatch 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()) + | KeyMismatch(previousCommitmentNumber, newCommitmentNumber) -> + sprintf + "Revocation key for commitment %s derives a key for commitment %s which does \ + not match the recorded key" + (newCommitmentNumber.ToString()) + (previousCommitmentNumber.ToString()) + +type RevocationSet private (keys: list) = + new() = RevocationSet(List.empty) + + member private this.Keys = keys + + member this.NextCommitmentNumber: CommitmentNumber = + if this.Keys.IsEmpty then + CommitmentNumber.FirstCommitment + else + let prevCommitmentNumber, _ = this.Keys.Head + prevCommitmentNumber.NextCommitment + + member this.InsertRevocationKey (commitmentNumber: CommitmentNumber) + (revocationKey: RevocationKey) + : Result = + let nextCommitmentNumber = this.NextCommitmentNumber + if commitmentNumber <> nextCommitmentNumber then + Error <| UnexpectedCommitmentNumber (commitmentNumber, nextCommitmentNumber) + else + let rec fold (keys: list) + : Result = + if keys.IsEmpty then + let res = [commitmentNumber, revocationKey] + Ok <| RevocationSet res + else + let storedCommitmentNumber, storedRevocationKey = keys.Head + match revocationKey.DeriveChild commitmentNumber storedCommitmentNumber with + | Some derivedRevocationKey -> + if derivedRevocationKey <> storedRevocationKey then + Error <| KeyMismatch (storedCommitmentNumber, commitmentNumber) + else + fold keys.Tail + | None -> + let res = (commitmentNumber, revocationKey) :: keys + Ok <| RevocationSet res + fold this.Keys + + member this.GetRevocationKey (commitmentNumber: CommitmentNumber) + : Option = + let rec fold (keys: list) = + if keys.IsEmpty then + None + else + let storedCommitmentNumber, storedRevocationKey = keys.Head + match storedRevocationKey.DeriveChild storedCommitmentNumber commitmentNumber with + | Some revocationKey -> Some revocationKey + | None -> fold keys.Tail + fold this.Keys + diff --git a/src/DotNetLightning.Core/DotNetLightning.Core.fsproj b/src/DotNetLightning.Core/DotNetLightning.Core.fsproj index a4b75c443..9cb5650ce 100644 --- a/src/DotNetLightning.Core/DotNetLightning.Core.fsproj +++ b/src/DotNetLightning.Core/DotNetLightning.Core.fsproj @@ -26,10 +26,13 @@ - - + + + + + @@ -47,6 +50,7 @@ + @@ -60,7 +64,6 @@ - diff --git a/src/DotNetLightning.Core/Serialize/LightningStream.fs b/src/DotNetLightning.Core/Serialize/LightningStream.fs index 0af468094..e9b095b11 100644 --- a/src/DotNetLightning.Core/Serialize/LightningStream.fs +++ b/src/DotNetLightning.Core/Serialize/LightningStream.fs @@ -328,6 +328,14 @@ type LightningReaderStream(inner: Stream) = else raise (FormatException("Invalid Pubkey encoding")) + member this.ReadRevocationKey() = + let bytes = this.ReadBytes RevocationKey.BytesLength + RevocationKey.FromBytes bytes + + member this.ReadCommitmentPubKey() = + let bytes = this.ReadBytes CommitmentPubKey.BytesLength + CommitmentPubKey.FromBytes bytes + member this.ReadECDSACompact() = let data = this.ReadBytes(64) LNECDSASignature.FromBytesCompact(data) diff --git a/src/DotNetLightning.Core/Serialize/Msgs/Msgs.fs b/src/DotNetLightning.Core/Serialize/Msgs/Msgs.fs index 051629dde..b56cfd7d5 100644 --- a/src/DotNetLightning.Core/Serialize/Msgs/Msgs.fs +++ b/src/DotNetLightning.Core/Serialize/Msgs/Msgs.fs @@ -507,7 +507,7 @@ type OpenChannelMsg = { mutable PaymentBasepoint: PubKey mutable DelayedPaymentBasepoint: PubKey mutable HTLCBasepoint: PubKey - mutable FirstPerCommitmentPoint: PubKey + mutable FirstPerCommitmentPoint: CommitmentPubKey mutable ChannelFlags: uint8 mutable ShutdownScriptPubKey: OptionalField