Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deterministic channel keypath #1026

Closed
wants to merge 49 commits into from
Closed

Conversation

araspitzu
Copy link
Contributor

This PR introduces deterministic keypath construction for the derivation of the keys used in the channel operations, the construction is made in such a way that we guarantee we never reuse the same keypath in order to prevent reusing keys across channels.The schema can be summarized as follow:

Funder scenario:

keyPath = m/47'/2'/SHA256(funding_tx.input[0].outpoint)/0

Fundee scenario:

fundingPubkey.keyPath = m/47'/2'/SHA256(blockchain_height || counter)/2
keyPath = m/47'/2'/SHA256(funding_tx.output[channel_output_index].scriptPubkey)/1

The fundee scenario involves using 2 different keypaths, one to derive the funding pubkey and another to derive all the other points required to construct accept_channel message. The usage of 2 different keypaths is required to compute the scriptPubkey of the funding transaction which is later used to construct the second keypath which is then used to derive all the other points for the channel.
As side effect of this PR all the static channel data, as in the data derived from the channel points exchanged in the open/accept messages, can now be derived only from the seed.

Credits: @armelinw

@araspitzu araspitzu force-pushed the deterministic_channel_keypath branch from 3d60a90 to b041d94 Compare June 6, 2019 14:33
@ACINQ ACINQ deleted a comment from codecov-io Jun 11, 2019
@ACINQ ACINQ deleted a comment from codecov-io Jun 12, 2019
(version match {
case CommitmentV0 => ("channelPath" | keyPathCodec.xmap[Either[KeyPath, KeyPathFundee]](Left(_), {
case Left(kp) => kp
case Right(_) => throw new IllegalArgumentException(s"Found unexpected value for version=$version")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will not work for "fundee" channels, but it should be possible to use the V0 key path we read from the database

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this has been fixed in 847eb06

@@ -588,7 +588,7 @@ object Peer {
def makeChannelParams(nodeParams: NodeParams, defaultFinalScriptPubKey: ByteVector, isFunder: Boolean, fundingSatoshis: Long, channelKeyPath: DeterministicWallet.KeyPath): LocalParams = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If unused it should be removed ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 5ded959

}
}


Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is possible to infer the counter value from the data we read from the database (check all channels fro a given peer, then get their block heights from the short channel id) ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The alternative is to have an atomic counter stored in volatile memory, however while this is a more elegant solution it opens up to new attacker scenarios.

def deterministicCommitmentSecret(localParams: LocalParams, index: Long): Crypto.Scalar

def deterministicCommitmentPoint(localParams: LocalParams, index: Long): Crypto.Point

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we keep the old names instead ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes these changes have been reverted

@araspitzu araspitzu marked this pull request as ready for review June 14, 2019 15:21
# Conflicts:
#	eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala
# Conflicts:
#	eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala
#	eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala
#	eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala
#	eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala
#	eclair-core/src/main/scala/fr/acinq/eclair/crypto/LocalKeyManager.scala
#	eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala
#	eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala
#	eclair-core/src/test/scala/fr/acinq/eclair/crypto/LocalKeyManagerSpec.scala
@araspitzu araspitzu force-pushed the deterministic_channel_keypath branch from 5183bc9 to 922072f Compare June 18, 2019 09:02
@ACINQ ACINQ deleted a comment from codecov-io Jun 25, 2019
araspitzu added 2 commits July 4, 2019 11:01
# Conflicts:
#	eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala
#	eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala
#	eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingConfirmedStateSpec.scala
@araspitzu araspitzu force-pushed the deterministic_channel_keypath branch from c12c706 to bdcd0ab Compare July 4, 2019 10:21
@codecov-io
Copy link

codecov-io commented Jul 4, 2019

Codecov Report

Merging #1026 into master will increase coverage by 0.1%.
The diff coverage is 90.9%.

@@            Coverage Diff            @@
##           master    #1026     +/-   ##
=========================================
+ Coverage   82.47%   82.58%   +0.1%     
=========================================
  Files          99       99             
  Lines        7594     7682     +88     
  Branches      293      292      -1     
=========================================
+ Hits         6263     6344     +81     
- Misses       1331     1338      +7
Impacted Files Coverage Δ
...air/blockchain/electrum/ElectrumEclairWallet.scala 0% <0%> (ø) ⬆️
...a/fr/acinq/eclair/db/sqlite/SqliteChannelsDb.scala 96.92% <100%> (+1.08%) ⬆️
...n/scala/fr/acinq/eclair/channel/ChannelTypes.scala 100% <100%> (ø) ⬆️
...scala/fr/acinq/eclair/crypto/LocalKeyManager.scala 91.3% <100%> (+1.83%) ⬆️
...ain/scala/fr/acinq/eclair/wire/ChannelCodecs.scala 100% <100%> (ø) ⬆️
...c/main/scala/fr/acinq/eclair/channel/Helpers.scala 94.44% <100%> (+0.01%) ⬆️
...nq/eclair/blockchain/electrum/ElectrumWallet.scala 80.66% <100%> (+0.14%) ⬆️
...-core/src/main/scala/fr/acinq/eclair/io/Peer.scala 73.94% <100%> (-0.99%) ⬇️
...a/fr/acinq/eclair/wire/LightningMessageTypes.scala 100% <100%> (ø) ⬆️
...in/scala/fr/acinq/eclair/channel/Commitments.scala 86.92% <100%> (ø) ⬆️
... and 7 more

@araspitzu
Copy link
Contributor Author

araspitzu commented Jul 4, 2019

Summary of the changes

Key path generation

Eclair uses BIP32 key paths to derive all the various keys needed for channel operation, instead of generating a new random keypath when negotiating a channel we now use an external source of entropy to feed randomness in the path construction. The entropy is taken from the funding input's outpoint and in the case we're fundee we use a combination of block height and an incrementing counter to ensure we never reuse keys, the full construction is highlighted below:

Funder scenario:

keyPath = m/47'/2'/SHA256(funding_tx.input[0].outpoint)/0'

Fundee scenario:

fundingPubkey.keyPath = m/47'/2'/SHA256(blockchain_height || counter)/2'
keyPath = m/47'/2'/SHA256(funding_tx.output[channel_output_index].scriptPubkey)/1'

Channel creation changes

Funder scenario : in order to send an open_channel we need to compute the basepoints for the channel operation and indeed we need the key path to do so. This means that before starting the negotiation of the channel opening we must ask the wallet to provide an input for the channel funding, we will use the input outpoint to construct the channel key path. Such particular flow modified the Channel FSM to have some new intermediate states, namely where we ask the wallet to create a funding transaction WAIT_FOR_FUNDING_INTERNAL_CREATED and where we ask the wallet to sign it WAIT_FOR_FUNDING_SIGNED_INTERNAL. Note that after we ask the wallet to make a funding transaction we strip the signatures from it and we later query the wallet again to add new signatures, the wallet will sign the updated transaction with the script containing the funding pubkey from the remote peer (we have the remote funding pubkey only after we receive accept_channel).
Fundee scenario: this scenario uses 2 different keypaths to operate the channel, one for the funding pubkey and one for all the other points. This is necessary because we want to use the scriptPubkey from the funding transaction to construct the keypath but to do so we need to derive out funding pubkey first, in order to do that we use a combination of block height and a counter. This scenario doesn't alter the channel opening flow.

Reference: funder construction and fundee construction

Codecs and Commitments upgrades

The usage of 2 different kinds of keypaths (one for funder scenario, two for fundee) forced to upgrade the LocalParams class to store an Either[KeyPath, KeyPathFundee], we also need to upgrade the codecs to store these new objects and we must do it in a backward compatible manner.
The codec has been modified to support commitment versioning and depending on the version we'll fetch the correct key paths.

Database layer upgrades

There is no db migration needed here but we add a new table to channel db, block_key_counter table. This is used to emulate an atomic auto incrementing counter used for the fundee key path construction, the alternative implementation using the AtomicLong has been ditched because of security concerns.

Testing

The PR has been manual tested against eclair master, there are also new codec tests and backward compatibility test for fundee scenario in DATA_NORMAL: here. To ensure previous version with yet-to-open channels can safely upgrade there are some test where we assert we can do FSM transition like "WAIT_FOR_FUNDING_CONFIRMED -> WAIT_FOR_FUNDING_LOCKED", test here and here.

# Conflicts:
#	eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala
#	eclair-core/src/test/scala/fr/acinq/eclair/db/ChannelStateSpec.scala
#	eclair-core/src/test/scala/fr/acinq/eclair/wire/ChannelCodecsSpec.scala
# Conflicts:
#	eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala
#	eclair-core/src/test/scala/fr/acinq/eclair/wire/ChannelCodecsSpec.scala
@araspitzu
Copy link
Contributor Author

Replaced by #1086

@araspitzu araspitzu closed this Jul 22, 2019
@araspitzu araspitzu deleted the deterministic_channel_keypath branch July 22, 2019 13:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants