channeldb+lnwallet: replace elkrem with shachain

In this commit the initial implementation of revocation hash
generation 'elkrem' was replaced with 'shachain' Rusty Russel
implementation which currently enshrined in the spec. This alghoritm has
the same asymptotic characteristics but has more complex scheme
to determine wish hash we can drop and what needs to be stored
in order to be able to achive full compression.
This commit is contained in:
Andrey Samokhvalov authored and Olaoluwa Osuntokun committed 2017-02-22 16:49:29 -08:00
1 parent b40afeaa08
commit f86557c3e4
11 files changed
+206 -573

No files matched your search

+14 -15
View File
@@ -734,10 +734,10 @@ func newBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64,
return nil, err
}
// With the state number broadcast known, we can now derive the proper
// leaf from our revocation tree necessary to sweep the remote party's
// With the state number broadcast known, we can now derive/restore the
// proper revocation preimage necessary to sweep the remote party's
// output.
revocationPreimage, err := chanState.RemoteElkrem.AtIndex(stateNum)
revocationPreimage, err := chanState.RevocationStore.LookUp(stateNum)
if err != nil {
return nil, err
}
@@ -1482,7 +1482,7 @@ func (lc *LightningChannel) ReceiveNewCommitment(rawSig []byte) error {
// derive the key+hash needed to construct the new commitment view and
// state.
nextHeight := lc.currentHeight + 1
revocation, err := lc.channelState.LocalElkrem.AtIndex(nextHeight)
revocation, err := lc.channelState.RevocationProducer.AtIndex(nextHeight)
if err != nil {
return err
}
@@ -1577,7 +1577,7 @@ func (lc *LightningChannel) RevokeCurrentCommitment() (*lnwire.RevokeAndAck, err
// Now that we've accept a new state transition, we send the remote
// party the revocation for our current commitment state.
revocationMsg := &lnwire.RevokeAndAck{}
currentRevocation, err := lc.channelState.LocalElkrem.AtIndex(lc.currentHeight)
currentRevocation, err := lc.channelState.RevocationProducer.AtIndex(lc.currentHeight)
if err != nil {
return nil, err
}
@@ -1586,7 +1586,7 @@ func (lc *LightningChannel) RevokeCurrentCommitment() (*lnwire.RevokeAndAck, err
// Along with this revocation, we'll also send an additional extension
// to our revocation window to the remote party.
lc.revocationWindowEdge++
revocationEdge, err := lc.channelState.LocalElkrem.AtIndex(lc.revocationWindowEdge)
revocationEdge, err := lc.channelState.RevocationProducer.AtIndex(lc.revocationWindowEdge)
if err != nil {
return nil, err
}
@@ -1655,11 +1655,10 @@ func (lc *LightningChannel) ReceiveRevocation(revMsg *lnwire.RevokeAndAck) ([]*P
currentRevocationKey := lc.channelState.TheirCurrentRevocation
pendingRevocation := chainhash.Hash(revMsg.Revocation)
// Ensure the new preimage fits in properly within the elkrem receiver
// tree. If this fails, then all other checks are skipped.
// Ensure that the new pre-image can be placed in preimage store.
// TODO(rosbeef): abstract into func
remoteElkrem := lc.channelState.RemoteElkrem
if err := remoteElkrem.AddNext(&pendingRevocation); err != nil {
store := lc.channelState.RevocationStore
if err := store.Store(&pendingRevocation); err != nil {
return nil, err
}
@@ -1698,8 +1697,8 @@ func (lc *LightningChannel) ReceiveRevocation(revMsg *lnwire.RevokeAndAck) ([]*P
// At this point, the revocation has been accepted, and we've rotated
// the current revocation key+hash for the remote party. Therefore we
// sync now to ensure the elkrem receiver state is consistent with the
// current commitment height.
// sync now to ensure the revocation producer state is consistent with
// the current commitment height.
tail := lc.remoteCommitChain.tail()
delta, err := tail.toChannelDelta()
if err != nil {
@@ -1778,7 +1777,7 @@ func (lc *LightningChannel) ExtendRevocationWindow() (*lnwire.RevokeAndAck, erro
revMsg.ChannelPoint = *lc.channelState.ChanID
nextHeight := lc.revocationWindowEdge + 1
revocation, err := lc.channelState.LocalElkrem.AtIndex(nextHeight)
revocation, err := lc.channelState.RevocationProducer.AtIndex(nextHeight)
if err != nil {
return nil, err
}
@@ -2147,8 +2146,8 @@ func (lc *LightningChannel) ForceClose() (*ForceCloseSummary, error) {
// Re-derive the original pkScript for out to-self output within the
// commitment transaction. We'll need this for the created sign
// descriptor.
elkrem := lc.channelState.LocalElkrem
unusedRevocation, err := elkrem.AtIndex(lc.currentHeight)
producer := lc.channelState.RevocationProducer
unusedRevocation, err := producer.AtIndex(lc.currentHeight)
if err != nil {
return nil, err
}
+15 -12
View File
@@ -11,8 +11,8 @@ import (
"github.com/go-errors/errors"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/elkrem"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/shachain"
"github.com/roasbeef/btcd/blockchain"
"github.com/roasbeef/btcd/btcec"
"github.com/roasbeef/btcd/chaincfg/chainhash"
@@ -202,15 +202,17 @@ func createTestChannels(revocationWindow int) (*LightningChannel, *LightningChan
}
fundingTxIn := wire.NewTxIn(prevOut, nil, nil)
bobElkrem := elkrem.NewElkremSender(deriveElkremRoot(bobKeyPriv, bobKeyPub, aliceKeyPub))
bobFirstRevoke, err := bobElkrem.AtIndex(0)
bobRoot := deriveRevocationRoot(bobKeyPriv, bobKeyPub, aliceKeyPub)
bobPreimageProducer := shachain.NewRevocationProducer(bobRoot)
bobFirstRevoke, err := bobPreimageProducer.AtIndex(0)
if err != nil {
return nil, nil, nil, err
}
bobRevokeKey := DeriveRevocationPubkey(aliceKeyPub, bobFirstRevoke[:])
aliceElkrem := elkrem.NewElkremSender(deriveElkremRoot(aliceKeyPriv, aliceKeyPub, bobKeyPub))
aliceFirstRevoke, err := aliceElkrem.AtIndex(0)
aliceRoot := deriveRevocationRoot(aliceKeyPriv, aliceKeyPub, bobKeyPub)
alicePreimageProducer := shachain.NewRevocationProducer(aliceRoot)
aliceFirstRevoke, err := alicePreimageProducer.AtIndex(0)
if err != nil {
return nil, nil, nil, err
}
@@ -262,8 +264,8 @@ func createTestChannels(revocationWindow int) (*LightningChannel, *LightningChan
LocalCsvDelay: csvTimeoutAlice,
RemoteCsvDelay: csvTimeoutBob,
TheirCurrentRevocation: bobRevokeKey,
LocalElkrem: aliceElkrem,
RemoteElkrem: &elkrem.ElkremReceiver{},
RevocationProducer: alicePreimageProducer,
RevocationStore: shachain.NewRevocationStore(),
TheirDustLimit: bobDustLimit,
OurDustLimit: aliceDustLimit,
Db: dbAlice,
@@ -288,8 +290,8 @@ func createTestChannels(revocationWindow int) (*LightningChannel, *LightningChan
LocalCsvDelay: csvTimeoutBob,
RemoteCsvDelay: csvTimeoutAlice,
TheirCurrentRevocation: aliceRevokeKey,
LocalElkrem: bobElkrem,
RemoteElkrem: &elkrem.ElkremReceiver{},
RevocationProducer: bobPreimageProducer,
RevocationStore: shachain.NewRevocationStore(),
TheirDustLimit: aliceDustLimit,
OurDustLimit: bobDustLimit,
Db: dbBob,
@@ -598,9 +600,10 @@ func TestSimpleAddSettleWorkflow(t *testing.T) {
// transaction with some degree of error corresponds to the actual size.
func TestCheckCommitTxSize(t *testing.T) {
checkSize := func(channel *LightningChannel, count int) {
// Due to variable size of the signatures (71-73) we may have
// an estimation error.
BaseCommitmentTxSizeEstimationError := 4
// Due to variable size of the signatures (70-73) in
// witness script actual size of commitment transaction might
// be lower on 6 weight.
BaseCommitmentTxSizeEstimationError := 6
commitTx, err := channel.getSignedCommitTx()
if err != nil {
+9 -9
View File
@@ -752,31 +752,31 @@ func DeriveRevocationPrivKey(commitPrivKey *btcec.PrivateKey,
return privRevoke
}
// deriveElkremRoot derives an elkrem root unique to a channel given the
// deriveRevocationRoot derives an root unique to a channel given the
// private key for our public key in the 2-of-2 multi-sig, and the remote
// node's multi-sig public key. The root is derived using the HKDF[1][2]
// node's multi-sig public key. The seed is derived using the HKDF[1][2]
// instantiated with sha-256. The secret data used is our multi-sig private
// key, with the salt being the remote node's public key.
//
// [1]: https://eprint.iacr.org/2010/264.pdf
// [2]: https://tools.ietf.org/html/rfc5869
func deriveElkremRoot(elkremDerivationRoot *btcec.PrivateKey,
func deriveRevocationRoot(derivationRoot *btcec.PrivateKey,
localMultiSigKey *btcec.PublicKey,
remoteMultiSigKey *btcec.PublicKey) chainhash.Hash {
remoteMultiSigKey *btcec.PublicKey) *chainhash.Hash {
secret := elkremDerivationRoot.Serialize()
secret := derivationRoot.Serialize()
salt := localMultiSigKey.SerializeCompressed()
info := remoteMultiSigKey.SerializeCompressed()
rootReader := hkdf.New(sha256.New, secret, salt, info)
seedReader := hkdf.New(sha256.New, secret, salt, info)
// It's safe to ignore the error her as we know for sure that we won't
// be draining the HKDF past its available entropy horizon.
// TODO(roasbeef): revisit...
var elkremRoot chainhash.Hash
rootReader.Read(elkremRoot[:])
var root chainhash.Hash
seedReader.Read(root[:])
return elkremRoot
return &root
}
// SetStateNumHint encodes the current state number within the passed
+3 -3
View File
@@ -85,14 +85,14 @@ const (
// - Marker: 1 byte
WitnessHeaderSize = 1 + 1
// CommitmentTransaction: 125 bytes
// CommitmentTransaction: 125 43 * num-htlc-outputs bytes
// - Version: 4 bytes
// - WitnessHeader <---- part of the witness data
// - CountTxIn: 1 byte
// - TxIn:
// - TxIn: 41 bytes
// FundingInput
// - CountTxOut: 1 byte
// - TxOut:
// - TxOut: 74 + 43 * num-htlc-outputs bytes
// OutputPayingToThem,
// OutputPayingToUs,
// ....HTLCOutputs...
+40 -42
View File
@@ -11,10 +11,10 @@ import (
"github.com/davecgh/go-spew/spew"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/elkrem"
"github.com/roasbeef/btcd/chaincfg"
"github.com/roasbeef/btcutil/hdkeychain"
"github.com/lightningnetwork/lnd/shachain"
"github.com/roasbeef/btcd/btcec"
"github.com/roasbeef/btcd/txscript"
"github.com/roasbeef/btcd/wire"
@@ -27,9 +27,9 @@ const (
// outside word.
msgBufferSize = 100
// elkremRootIndex is the top level HD key index from which secrets
// used to generate elkrem roots should be derived from.
elkremRootIndex = hdkeychain.HardenedKeyStart + 1
// revocationRootIndex is the top level HD key index from which secrets
// used to generate producer roots should be derived from.
revocationRootIndex = hdkeychain.HardenedKeyStart + 1
// identityKeyIndex is the top level HD key index which is used to
// generate/rotate identity keys.
@@ -42,7 +42,6 @@ const (
)
var (
// Namespace bucket keys.
lightningNamespaceKey = []byte("ln-wallet")
waddrmgrNamespaceKey = []byte("waddrmgr")
@@ -757,11 +756,11 @@ func (l *LightningWallet) handleContributionMsg(req *addContributionMsg) {
// Initialize an empty sha-chain for them, tracking the current pending
// revocation hash (we don't yet know the preimage so we can't add it
// to the chain).
e := &elkrem.ElkremReceiver{}
pendingReservation.partialState.RemoteElkrem = e
s := shachain.NewRevocationStore()
pendingReservation.partialState.RevocationStore = s
pendingReservation.partialState.TheirCurrentRevocation = theirContribution.RevocationKey
masterElkremRoot, err := l.deriveMasterElkremRoot()
masterElkremRoot, err := l.deriveMasterRevocationRoot()
if err != nil {
req.err <- err
return
@@ -769,12 +768,11 @@ func (l *LightningWallet) handleContributionMsg(req *addContributionMsg) {
// Now that we have their commitment key, we can create the revocation
// key for the first version of our commitment transaction. To do so,
// we'll first create our elkrem root, then grab the first pre-iamge
// from it.
elkremRoot := deriveElkremRoot(masterElkremRoot, ourKey, theirKey)
elkremSender := elkrem.NewElkremSender(elkremRoot)
pendingReservation.partialState.LocalElkrem = elkremSender
firstPreimage, err := elkremSender.AtIndex(0)
// we'll first create our root, then produce the first pre-image.
root := deriveRevocationRoot(masterElkremRoot, ourKey, theirKey)
producer := shachain.NewRevocationProducer(root)
pendingReservation.partialState.RevocationProducer = producer
firstPreimage, err := producer.AtIndex(0)
if err != nil {
req.err <- err
return
@@ -812,7 +810,7 @@ func (l *LightningWallet) handleContributionMsg(req *addContributionMsg) {
// TODO(roasbeef): define obsfucator scheme for dual funder
var stateObsfucator [StateHintSize]byte
if pendingReservation.partialState.IsInitiator {
stateObsfucator, err = deriveStateHintObsfucator(elkremSender)
stateObsfucator, err = deriveStateHintObfuscator(producer)
if err != nil {
req.err <- err
return
@@ -902,7 +900,7 @@ func (l *LightningWallet) handleSingleContribution(req *addSingleContributionMsg
}
pendingReservation.partialState.FundingWitnessScript = witnessScript
masterElkremRoot, err := l.deriveMasterElkremRoot()
masterElkremRoot, err := l.deriveMasterRevocationRoot()
if err != nil {
req.err <- err
return
@@ -910,22 +908,22 @@ func (l *LightningWallet) handleSingleContribution(req *addSingleContributionMsg
// Now that we know their commitment key, we can create the revocation
// key for our version of the initial commitment transaction.
elkremRoot := deriveElkremRoot(masterElkremRoot, ourKey, theirKey)
elkremSender := elkrem.NewElkremSender(elkremRoot)
firstPreimage, err := elkremSender.AtIndex(0)
root := deriveRevocationRoot(masterElkremRoot, ourKey, theirKey)
producer := shachain.NewRevocationProducer(root)
firstPreimage, err := producer.AtIndex(0)
if err != nil {
req.err <- err
return
}
pendingReservation.partialState.LocalElkrem = elkremSender
pendingReservation.partialState.RevocationProducer = producer
theirCommitKey := theirContribution.CommitKey
ourRevokeKey := DeriveRevocationPubkey(theirCommitKey, firstPreimage[:])
// Initialize an empty sha-chain for them, tracking the current pending
// revocation hash (we don't yet know the preimage so we can't add it
// to the chain).
remoteElkrem := &elkrem.ElkremReceiver{}
pendingReservation.partialState.RemoteElkrem = remoteElkrem
remotePreimageStore := shachain.NewRevocationStore()
pendingReservation.partialState.RevocationStore = remotePreimageStore
// Record the counterpaty's remaining contributions to the channel,
// converting their delivery address into a public key script.
@@ -1379,11 +1377,11 @@ func (l *LightningWallet) selectCoinsAndChange(feeRate uint64, amt btcutil.Amoun
return nil
}
// deriveMasterElkremRoot derives the private key which serves as the master
// elkrem root. This master secret is used as the secret input to a HKDF to
// generate elkrem secrets based on random, but public data.
func (l *LightningWallet) deriveMasterElkremRoot() (*btcec.PrivateKey, error) {
masterElkremRoot, err := l.rootKey.Child(elkremRootIndex)
// deriveMasterRevocationRoot derives the private key which serves as the master
// producer root. This master secret is used as the secret input to a HKDF to
// generate revocation secrets based on random, but public data.
func (l *LightningWallet) deriveMasterRevocationRoot() (*btcec.PrivateKey, error) {
masterElkremRoot, err := l.rootKey.Child(revocationRootIndex)
if err != nil {
return nil, err
}
@@ -1391,34 +1389,34 @@ func (l *LightningWallet) deriveMasterElkremRoot() (*btcec.PrivateKey, error) {
return masterElkremRoot.ECPrivKey()
}
// deriveStateHintObsfucator derives the bytes to be used for obsfucatating the
// state hints from the elkerem root to be used for a new channel. The
// obsfucator is generated by performing an additional sha256 hash of the first
// child derived from the elkrem root. The leading 4 bytes are used for the
// obsfucator.
func deriveStateHintObsfucator(elkremRoot *elkrem.ElkremSender) ([StateHintSize]byte, error) {
var obsfucator [StateHintSize]byte
// deriveStateHintObfuscator derives the bytes to be used for obfuscating the
// state hints from the root to be used for a new channel. The
// obfuscator is generated by performing an additional sha256 hash of the first
// child derived from the revocation root. The leading 4 bytes are used for the
// obfuscator.
func deriveStateHintObfuscator(producer shachain.Producer) ([StateHintSize]byte, error) {
var obfuscator [StateHintSize]byte
firstChild, err := elkremRoot.AtIndex(0)
firstChild, err := producer.AtIndex(0)
if err != nil {
return obsfucator, err
return obfuscator, err
}
grandChild := fastsha256.Sum256(firstChild[:])
copy(obsfucator[:], grandChild[:])
copy(obfuscator[:], grandChild[:])
return obsfucator, nil
return obfuscator, nil
}
// initStateHints properly sets the obsfucated state hints on both commitment
// transactions using the passed obsfucator.
func initStateHints(commit1, commit2 *wire.MsgTx,
obsfucator [StateHintSize]byte) error {
obfuscator [StateHintSize]byte) error {
if err := SetStateNumHint(commit1, 0, obsfucator); err != nil {
if err := SetStateNumHint(commit1, 0, obfuscator); err != nil {
return err
}
if err := SetStateNumHint(commit2, 0, obsfucator); err != nil {
if err := SetStateNumHint(commit2, 0, obfuscator); err != nil {
return err
}
@@ -1426,7 +1424,7 @@ func initStateHints(commit1, commit2 *wire.MsgTx,
}
// selectInputs selects a slice of inputs necessary to meet the specified
// selection amount. If input selection is unable to suceed to to insuffcient
// selection amount. If input selection is unable to succeed to to insufficient
// funds, a non-nil error is returned. Additionally, the total amount of the
// selected coins are returned in order for the caller to properly handle
// change+fees.