From f86557c3e4706bc7044fa25620b5cc25d579e56e Mon Sep 17 00:00:00 2001 From: Andrey Samokhvalov Date: Wed, 14 Dec 2016 17:01:48 +0300 Subject: [PATCH] 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. --- channeldb/channel.go | 117 +++++++++++++++-------------- channeldb/channel_test.go | 113 +++++++++++++++------------- elkrem/elkrem.go | 151 -------------------------------------- elkrem/elkrem_test.go | 57 -------------- elkrem/serdes.go | 130 -------------------------------- elkrem/serdes_test.go | 49 ------------- lnwallet/channel.go | 29 ++++---- lnwallet/channel_test.go | 27 ++++--- lnwallet/script_utils.go | 18 ++--- lnwallet/size.go | 6 +- lnwallet/wallet.go | 82 ++++++++++----------- 11 files changed, 206 insertions(+), 573 deletions(-) delete mode 100644 elkrem/elkrem.go delete mode 100644 elkrem/elkrem_test.go delete mode 100644 elkrem/serdes.go delete mode 100644 elkrem/serdes_test.go diff --git a/channeldb/channel.go b/channeldb/channel.go index 9a52b494..b513c48a 100644 --- a/channeldb/channel.go +++ b/channeldb/channel.go @@ -9,9 +9,8 @@ import ( "time" "github.com/boltdb/bolt" - "github.com/lightningnetwork/lnd/elkrem" + "github.com/lightningnetwork/lnd/shachain" "github.com/roasbeef/btcd/btcec" - "github.com/roasbeef/btcd/chaincfg/chainhash" "github.com/roasbeef/btcd/wire" "github.com/roasbeef/btcutil" ) @@ -89,9 +88,9 @@ var ( // and finally 2-of-2 multisig redeem script. fundingTxnKey = []byte("fsk") - // elkremStateKey stores their current revocation hash, and our elkrem - // sender, and their elkrem receiver. - elkremStateKey = []byte("esk") + // preimageStateKey stores their current revocation hash, our + // preimage producer and their preimage store. + preimageStateKey = []byte("esk") // deliveryScriptsKey stores the scripts for the final delivery in the // case of a cooperative closure. @@ -225,8 +224,17 @@ type OpenChannel struct { // aren't yet able to verify that it's actually in the hash chain. TheirCurrentRevocation *btcec.PublicKey TheirCurrentRevocationHash [32]byte - LocalElkrem *elkrem.ElkremSender - RemoteElkrem *elkrem.ElkremReceiver + + // RevocationProducer is used to generate the revocation in such a way + // that remote side might store it efficiently and have the ability to + // restore the revocation by index if needed. Current implementation of + // secret producer is shachain producer. + RevocationProducer shachain.Producer + + // RevocationStore is used to efficiently store the revocations for + // previous channels states sent to us by remote side. Current + // implementation of secret store is shachain store. + RevocationStore shachain.Store // OurDeliveryScript is the script to be used to pay to us in // cooperative closes. @@ -480,15 +488,16 @@ func (c *OpenChannel) AppendToRevocationLog(delta *ChannelDelta) error { return err } - // Persist the latest elkrem state to disk as the remote peer - // has just added to our local elkrem receiver, and given us a - // new pending revocation key. - if err := putChanElkremState(nodeChanBucket, c); err != nil { + // Persist the latest preimage state to disk as the remote peer + // has just added to our local preimage store, and + // given us a new pending revocation key. + if err := putChanPreimageState(nodeChanBucket, c); err != nil { return err } - // With the current elkrem state updated, append a new log - // entry recording this the delta of this state transition. + // With the current preimage producer/store state updated, + // append a new log entry recording this the delta of this state + // transition. // TODO(roasbeef): could make the deltas relative, would save // space, but then tradeoff for more disk-seeks to recover the // full state. @@ -737,7 +746,7 @@ func putOpenChannel(openChanBucket *bolt.Bucket, nodeChanBucket *bolt.Bucket, if err := putChanFundingInfo(nodeChanBucket, channel); err != nil { return err } - if err := putChanElkremState(nodeChanBucket, channel); err != nil { + if err := putChanPreimageState(nodeChanBucket, channel); err != nil { return err } if err := putChanDeliveryScripts(nodeChanBucket, channel); err != nil { @@ -774,8 +783,8 @@ func fetchOpenChannel(openChanBucket *bolt.Bucket, nodeChanBucket *bolt.Bucket, if err = fetchChanFundingInfo(nodeChanBucket, channel); err != nil { return nil, fmt.Errorf("unable to read funding info: %v", err) } - if err = fetchChanElkremState(nodeChanBucket, channel); err != nil { - return nil, fmt.Errorf("uable to read elkrem state: %v", err) + if err = fetchChanPreimageState(nodeChanBucket, channel); err != nil { + return nil, err } if err = fetchChanDeliveryScripts(nodeChanBucket, channel); err != nil { return nil, fmt.Errorf("unable to read delivery scripts: %v", err) @@ -849,7 +858,7 @@ func deleteOpenChannel(openChanBucket *bolt.Bucket, nodeChanBucket *bolt.Bucket, if err := deleteChanFundingInfo(nodeChanBucket, channelID); err != nil { return err } - if err := deleteChanElkremState(nodeChanBucket, channelID); err != nil { + if err := deleteChanPreimageState(nodeChanBucket, channelID); err != nil { return err } if err := deleteChanDeliveryScripts(nodeChanBucket, channelID); err != nil { @@ -1459,16 +1468,7 @@ func fetchChanFundingInfo(nodeChanBucket *bolt.Bucket, channel *OpenChannel) err return nil } -func putChanElkremState(nodeChanBucket *bolt.Bucket, channel *OpenChannel) error { - var bc bytes.Buffer - if err := writeOutpoint(&bc, channel.ChanID); err != nil { - return err - } - - elkremKey := make([]byte, len(elkremStateKey)+bc.Len()) - copy(elkremKey[:3], elkremStateKey) - copy(elkremKey[3:], bc.Bytes()) - +func putChanPreimageState(nodeChanBucket *bolt.Bucket, channel *OpenChannel) error { var b bytes.Buffer revKey := channel.TheirCurrentRevocation.SerializeCompressed() @@ -1482,16 +1482,19 @@ func putChanElkremState(nodeChanBucket *bolt.Bucket, channel *OpenChannel) error // TODO(roasbeef): shouldn't be storing on disk, should re-derive as // needed - senderBytes := channel.LocalElkrem.ToBytes() - if err := wire.WriteVarBytes(&b, 0, senderBytes); err != nil { - return err - } - - reciverBytes, err := channel.RemoteElkrem.ToBytes() + data, err := channel.RevocationProducer.ToBytes() if err != nil { return err } - if err := wire.WriteVarBytes(&b, 0, reciverBytes); err != nil { + if err := wire.WriteVarBytes(&b, 0, data); err != nil { + return err + } + + data, err = channel.RevocationStore.ToBytes() + if err != nil { + return err + } + if err := wire.WriteVarBytes(&b, 0, data); err != nil { return err } @@ -1499,28 +1502,36 @@ func putChanElkremState(nodeChanBucket *bolt.Bucket, channel *OpenChannel) error return err } - return nodeChanBucket.Put(elkremKey, b.Bytes()) + var bc bytes.Buffer + if err := writeOutpoint(&bc, channel.ChanID); err != nil { + return err + } + + preimageKey := make([]byte, len(preimageStateKey)+bc.Len()) + copy(preimageKey[:3], preimageStateKey) + copy(preimageKey[3:], bc.Bytes()) + return nodeChanBucket.Put(preimageKey, b.Bytes()) } -func deleteChanElkremState(nodeChanBucket *bolt.Bucket, chanID []byte) error { - elkremKey := make([]byte, len(elkremStateKey)+len(chanID)) - copy(elkremKey[:3], elkremStateKey) - copy(elkremKey[3:], chanID) - return nodeChanBucket.Delete(elkremKey) +func deleteChanPreimageState(nodeChanBucket *bolt.Bucket, chanID []byte) error { + preimageKey := make([]byte, len(preimageStateKey)+len(chanID)) + copy(preimageKey[:3], preimageStateKey) + copy(preimageKey[3:], chanID) + return nodeChanBucket.Delete(preimageKey) } -func fetchChanElkremState(nodeChanBucket *bolt.Bucket, channel *OpenChannel) error { +func fetchChanPreimageState(nodeChanBucket *bolt.Bucket, channel *OpenChannel) error { var b bytes.Buffer if err := writeOutpoint(&b, channel.ChanID); err != nil { return err } - elkremKey := make([]byte, len(elkremStateKey)+b.Len()) - copy(elkremKey[:3], elkremStateKey) - copy(elkremKey[3:], b.Bytes()) + preimageKey := make([]byte, len(preimageStateKey)+b.Len()) + copy(preimageKey[:3], preimageStateKey) + copy(preimageKey[3:], b.Bytes()) - elkremStateBytes := bytes.NewReader(nodeChanBucket.Get(elkremKey)) + reader := bytes.NewReader(nodeChanBucket.Get(preimageKey)) - revKeyBytes, err := wire.ReadVarBytes(elkremStateBytes, 0, 1000, "") + revKeyBytes, err := wire.ReadVarBytes(reader, 0, 1000, "") if err != nil { return err } @@ -1529,32 +1540,30 @@ func fetchChanElkremState(nodeChanBucket *bolt.Bucket, channel *OpenChannel) err return err } - if _, err := elkremStateBytes.Read(channel.TheirCurrentRevocationHash[:]); err != nil { + if _, err := reader.Read(channel.TheirCurrentRevocationHash[:]); err != nil { return err } // TODO(roasbeef): should be rederiving on fly, or encrypting on disk. - senderBytes, err := wire.ReadVarBytes(elkremStateBytes, 0, 1000, "") + producerBytes, err := wire.ReadVarBytes(reader, 0, 1000, "") if err != nil { return err } - elkremRoot, err := chainhash.NewHash(senderBytes) + channel.RevocationProducer, err = shachain.NewRevocationProducerFromBytes(producerBytes) if err != nil { return err } - channel.LocalElkrem = elkrem.NewElkremSender(*elkremRoot) - reciverBytes, err := wire.ReadVarBytes(elkremStateBytes, 0, 1000, "") + storeBytes, err := wire.ReadVarBytes(reader, 0, 1000, "") if err != nil { return err } - remoteE, err := elkrem.ElkremReceiverFromBytes(reciverBytes) + channel.RevocationStore, err = shachain.NewRevocationStoreFromBytes(storeBytes) if err != nil { return err } - channel.RemoteElkrem = remoteE - _, err = io.ReadFull(elkremStateBytes, channel.StateHintObsfucator[:]) + _, err = io.ReadFull(reader, channel.StateHintObsfucator[:]) if err != nil { return err } diff --git a/channeldb/channel_test.go b/channeldb/channel_test.go index 1baf1abb..166ca7e5 100644 --- a/channeldb/channel_test.go +++ b/channeldb/channel_test.go @@ -9,7 +9,7 @@ import ( "time" "github.com/davecgh/go-spew/spew" - "github.com/lightningnetwork/lnd/elkrem" + "github.com/lightningnetwork/lnd/shachain" "github.com/roasbeef/btcd/btcec" "github.com/roasbeef/btcd/chaincfg" "github.com/roasbeef/btcd/chaincfg/chainhash" @@ -117,17 +117,16 @@ func createTestChannelState(cdb *DB) (*OpenChannel, error) { return nil, err } - // Simulate 1000 channel updates via progression of the elkrem - // revocation trees. - sender := elkrem.NewElkremSender(key) - receiver := &elkrem.ElkremReceiver{} + // Simulate 1000 channel updates. + producer := shachain.NewRevocationProducer((*chainhash.Hash)(&key)) + store := shachain.NewRevocationStore() for i := 0; i < 1000; i++ { - preImage, err := sender.AtIndex(uint64(i)) + preImage, err := producer.AtIndex(uint64(i)) if err != nil { return nil, err } - if receiver.AddNext(preImage); err != nil { + if store.Store(preImage); err != nil { return nil, err } } @@ -150,8 +149,8 @@ func createTestChannelState(cdb *DB) (*OpenChannel, error) { TheirBalance: btcutil.Amount(9000), OurCommitTx: testTx, OurCommitSig: bytes.Repeat([]byte{1}, 71), - LocalElkrem: sender, - RemoteElkrem: receiver, + RevocationProducer: producer, + RevocationStore: store, StateHintObsfucator: obsfucator, FundingOutpoint: testOutpoint, OurMultiSigKey: privKey.PubKey(), @@ -207,34 +206,34 @@ func TestOpenChannelPutGetDelete(t *testing.T) { // The decoded channel state should be identical to what we stored // above. if !state.IdentityPub.IsEqual(newState.IdentityPub) { - t.Fatalf("their id doesn't match") + t.Fatal("their id doesn't match") } if !reflect.DeepEqual(state.ChanID, newState.ChanID) { - t.Fatalf("chan id's don't match") + t.Fatal("chan id's don't match") } if state.MinFeePerKb != newState.MinFeePerKb { - t.Fatalf("fee/kb doesn't match") + t.Fatal("fee/kb doesn't match") } if state.TheirDustLimit != newState.TheirDustLimit { - t.Fatalf("their dust limit doesn't match") + t.Fatal("their dust limit doesn't match") } if state.OurDustLimit != newState.OurDustLimit { - t.Fatalf("our dust limit doesn't match") + t.Fatal("our dust limit doesn't match") } if state.IsInitiator != newState.IsInitiator { - t.Fatalf("initiator status doesn't match") + t.Fatal("initiator status doesn't match") } if state.ChanType != newState.ChanType { - t.Fatalf("channel type doesn't match") + t.Fatal("channel type doesn't match") } if !bytes.Equal(state.OurCommitKey.SerializeCompressed(), newState.OurCommitKey.SerializeCompressed()) { - t.Fatalf("our commit key doesn't match") + t.Fatal("our commit key doesn't match") } if !bytes.Equal(state.TheirCommitKey.SerializeCompressed(), newState.TheirCommitKey.SerializeCompressed()) { - t.Fatalf("their commit key doesn't match") + t.Fatal("their commit key doesn't match") } if state.Capacity != newState.Capacity { @@ -242,49 +241,49 @@ func TestOpenChannelPutGetDelete(t *testing.T) { newState.Capacity) } if state.OurBalance != newState.OurBalance { - t.Fatalf("our balance doesn't match") + t.Fatal("our balance doesn't match") } if state.TheirBalance != newState.TheirBalance { - t.Fatalf("their balance doesn't match") + t.Fatal("their balance doesn't match") } var b1, b2 bytes.Buffer if err := state.OurCommitTx.Serialize(&b1); err != nil { - t.Fatalf("unable to serialize transaction") + t.Fatal("unable to serialize transaction") } if err := newState.OurCommitTx.Serialize(&b2); err != nil { - t.Fatalf("unable to serialize transaction") + t.Fatal("unable to serialize transaction") } if !bytes.Equal(b1.Bytes(), b2.Bytes()) { - t.Fatalf("ourCommitTx doesn't match") + t.Fatal("ourCommitTx doesn't match") } if !bytes.Equal(newState.OurCommitSig, state.OurCommitSig) { - t.Fatalf("commit sigs don't match") + t.Fatal("commit sigs don't match") } // TODO(roasbeef): replace with a single equal? if !reflect.DeepEqual(state.FundingOutpoint, newState.FundingOutpoint) { - t.Fatalf("funding outpoint doesn't match") + t.Fatal("funding outpoint doesn't match") } if !bytes.Equal(state.OurMultiSigKey.SerializeCompressed(), newState.OurMultiSigKey.SerializeCompressed()) { - t.Fatalf("our multisig key doesn't match") + t.Fatal("our multisig key doesn't match") } if !bytes.Equal(state.TheirMultiSigKey.SerializeCompressed(), newState.TheirMultiSigKey.SerializeCompressed()) { - t.Fatalf("their multisig key doesn't match") + t.Fatal("their multisig key doesn't match") } if !bytes.Equal(state.FundingWitnessScript, newState.FundingWitnessScript) { - t.Fatalf("redeem script doesn't match") + t.Fatal("redeem script doesn't match") } // The local and remote delivery scripts should be identical. if !bytes.Equal(state.OurDeliveryScript, newState.OurDeliveryScript) { - t.Fatalf("our delivery address doesn't match") + t.Fatal("our delivery address doesn't match") } if !bytes.Equal(state.TheirDeliveryScript, newState.TheirDeliveryScript) { - t.Fatalf("their delivery address doesn't match") + t.Fatal("their delivery address doesn't match") } if state.NumUpdates != newState.NumUpdates { @@ -304,33 +303,45 @@ func TestOpenChannelPutGetDelete(t *testing.T) { state.TotalSatoshisSent, newState.TotalSatoshisSent) } if state.TotalSatoshisReceived != newState.TotalSatoshisReceived { - t.Fatalf("satoshis received doesn't match") + t.Fatal("satoshis received doesn't match") } if state.CreationTime.Unix() != newState.CreationTime.Unix() { - t.Fatalf("creation time doesn't match") + t.Fatal("creation time doesn't match") } - // The local and remote elkrems should be identical. - if !bytes.Equal(state.LocalElkrem.ToBytes(), newState.LocalElkrem.ToBytes()) { - t.Fatalf("local elkrems don't match") - } - oldRemoteElkrem, err := state.RemoteElkrem.ToBytes() + // The local and remote producers should be identical. + oldProducer, err := state.RevocationProducer.ToBytes() if err != nil { - t.Fatalf("unable to serialize old remote elkrem: %v", err) + t.Fatalf("can't convert old revocation producer to bytes: %v", + err) } - newRemoteElkrem, err := newState.RemoteElkrem.ToBytes() + + newProducer, err := newState.RevocationProducer.ToBytes() if err != nil { - t.Fatalf("unable to serialize new remote elkrem: %v", err) + t.Fatalf("can't convert new revocation producer to bytes: %v", + err) } - if !bytes.Equal(oldRemoteElkrem, newRemoteElkrem) { - t.Fatalf("remote elkrems don't match") + + if !bytes.Equal(oldProducer, newProducer) { + t.Fatal("local producer don't match") + } + oldStore, err := state.RevocationStore.ToBytes() + if err != nil { + t.Fatalf("unable to serialize old remote store: %v", err) + } + newStore, err := newState.RevocationStore.ToBytes() + if err != nil { + t.Fatalf("unable to serialize new remote store: %v", err) + } + if !bytes.Equal(oldStore, newStore) { + t.Fatal("remote store don't match") } if !newState.TheirCurrentRevocation.IsEqual(state.TheirCurrentRevocation) { - t.Fatalf("revocation keys don't match") + t.Fatal("revocation keys don't match") } if !bytes.Equal(newState.TheirCurrentRevocationHash[:], state.TheirCurrentRevocationHash[:]) { - t.Fatalf("revocation hashes don't match") + t.Fatal("revocation hashes don't match") } if !reflect.DeepEqual(state.Htlcs[0], newState.Htlcs[0]) { t.Fatalf("htlcs don't match: %v vs %v", spew.Sdump(state.Htlcs[0]), @@ -338,7 +349,7 @@ func TestOpenChannelPutGetDelete(t *testing.T) { } if !bytes.Equal(state.StateHintObsfucator[:], newState.StateHintObsfucator[:]) { - t.Fatalf("obsfuctators don't match") + t.Fatal("obsfuctators don't match") } // Finally to wrap up the test, delete the state of the channel within @@ -363,7 +374,7 @@ func TestOpenChannelPutGetDelete(t *testing.T) { // should yield no results. openChans, err = cdb.FetchAllChannels() if err != nil { - t.Fatalf("unable to fetch all open chans") + t.Fatal("unable to fetch all open chans") } if len(openChans) != 0 { t.Fatalf("all channels not deleted, found %v", len(openChans)) @@ -494,13 +505,13 @@ func TestChannelStateTransition(t *testing.T) { // The two deltas (the original vs the on-disk version) should // identical, and all HTLC data should properly be retained. if delta.LocalBalance != diskDelta.LocalBalance { - t.Fatalf("local balances don't match") + t.Fatal("local balances don't match") } if delta.RemoteBalance != diskDelta.RemoteBalance { - t.Fatalf("remote balances don't match") + t.Fatal("remote balances don't match") } if delta.UpdateNum != diskDelta.UpdateNum { - t.Fatalf("update number doesn't match") + t.Fatal("update number doesn't match") } for i := 0; i < len(delta.Htlcs); i++ { originalHTLC := delta.Htlcs[i] @@ -546,7 +557,7 @@ func TestChannelStateTransition(t *testing.T) { } if !bytes.Equal(updatedChannel[0].TheirCurrentRevocationHash[:], newRevocation) { - t.Fatalf("revocation state wasn't synced!") + t.Fatal("revocation state wasn't synced!") } // Now attempt to delete the channel from the database. @@ -569,6 +580,6 @@ func TestChannelStateTransition(t *testing.T) { // revocation log has been deleted. _, err = updatedChannel[0].FindPreviousState(uint64(delta.UpdateNum)) if err == nil { - t.Fatalf("revocation log search should've failed") + t.Fatal("revocation log search should've failed") } } diff --git a/elkrem/elkrem.go b/elkrem/elkrem.go deleted file mode 100644 index 53b20da7..00000000 --- a/elkrem/elkrem.go +++ /dev/null @@ -1,151 +0,0 @@ -package elkrem - -import ( - "fmt" - - "github.com/roasbeef/btcd/chaincfg/chainhash" -) - -/* elkrem is a simpler alternative to the 64 dimensional sha-chain. -it's basically a reverse merkle tree. If we want to provide 2**64 possible -hashes, this requires a worst case computation of 63 hashes for the -sender, and worst-case storage of 64 hashes for the receiver. - -The operations are left hash L() and right hash R(), which are -hash(parent) and hash(parent, 1) respectively. (concatenate one byte) - -Here is a shorter example of a tree with 8 leaves and 15 total nodes. - -The sender first computes the bottom left leaf 0b0000. This is -L(L(L(L(root)))). The receiver stores leaf 0. - -Next the sender computes 0b0001. R(L(L(L(root)))). Receiver stores. -Next sender computes 0b1000 (8). L(L(L(root))). Receiver stores this, and -discards leaves 0b0000 and 0b0001, as they have the parent node 8. - -For total hashes (2**h)-1 requires a tree of height h. - -Sender: -as state, must store 1 hash (root) and that's all -generate any index, compute at most h hashes. - -Receiver: -as state, must store at most h+1 hashes and the index of each hash (h*(h+1)) bits -to compute a previous index, compute at most h hashes. -*/ -const maxIndex = uint64(281474976710654) // 2^48 - 2 -const maxHeight = uint8(47) - -// You can calculate h from i but I can't figure out how without taking -// O(i) ops. Feels like there should be a clever O(h) way. 1 byte, whatever. -type ElkremNode struct { - h uint8 // height of this node - i uint64 // index (i'th node) - sha *chainhash.Hash // hash -} -type ElkremSender struct { - root *chainhash.Hash // root hash of the tree -} -type ElkremReceiver struct { - s []ElkremNode // store of received hashes -} - -func LeftSha(in chainhash.Hash) chainhash.Hash { - return chainhash.DoubleHashH(in[:]) // left is sha(sha(in)) -} -func RightSha(in chainhash.Hash) chainhash.Hash { - return chainhash.DoubleHashH(append(in[:], 0x01)) // sha(sha(in, 1)) -} - -// iterative descent of sub-tree. w = hash number you want. i = input index -// h = height of input index. sha = input hash -func descend(w, i uint64, h uint8, sha chainhash.Hash) (chainhash.Hash, error) { - for w < i { - if w <= i-(1<= 0 { // if this is not the first hash (>= because we -1'd) - n.i = e.s[t].i + 1 // incoming index is tip of stack index + 1 - } - if t > 0 && e.s[t-1].h == e.s[t].h { // top 2 elements are equal height - // next node must be parent; verify and remove children - n.h = e.s[t].h + 1 // assign height - l := LeftSha(*sha) // calc l child - r := RightSha(*sha) // calc r child - if !e.s[t-1].sha.IsEqual(&l) { // test l child - return fmt.Errorf("left child doesn't match, expect %s got %s", - e.s[t-1].sha.String(), l.String()) - } - if !e.s[t].sha.IsEqual(&r) { // test r child - return fmt.Errorf("right child doesn't match, expect %s got %s", - e.s[t].sha.String(), r.String()) - } - e.s = e.s[:len(e.s)-2] // l and r children OK, remove them - } // if that didn't happen, height defaults to 0 - e.s = append(e.s, n) // append new node to stack - return nil -} - -// AtIndex returns the w'th hash in the receiver. -func (e *ElkremReceiver) AtIndex(w uint64) (*chainhash.Hash, error) { - if e == nil || e.s == nil { - return nil, fmt.Errorf("nil elkrem receiver") - } - var out ElkremNode // node we will eventually return - for _, n := range e.s { // go through stack - if w <= n.i { // found one bigger than or equal to what we want - out = n - break - } - } - if out.sha == nil { // didn't find anything - return nil, fmt.Errorf("receiver has max %d, less than requested %d", - e.s[len(e.s)-1].i, w) - } - sha, err := descend(w, out.i, out.h, *out.sha) - return &sha, err -} - -// UpTo tells you what the receiver can go up to. -func (e *ElkremReceiver) UpTo() uint64 { - if len(e.s) < 1 { - return 0 - } - return e.s[len(e.s)-1].i -} diff --git a/elkrem/elkrem_test.go b/elkrem/elkrem_test.go deleted file mode 100644 index ca6f522c..00000000 --- a/elkrem/elkrem_test.go +++ /dev/null @@ -1,57 +0,0 @@ -package elkrem - -import ( - "testing" - - "github.com/roasbeef/btcd/chaincfg/chainhash" -) - -// TestElkremBig tries 10K hashes -func TestElkremBig(t *testing.T) { - var rcv ElkremReceiver - - sndr := NewElkremSender(chainhash.DoubleHashH([]byte("elktest"))) - - for n := uint64(0); n < 10000; n++ { - sha, err := sndr.AtIndex(n) - if err != nil { - t.Fatal(err) - } - - if err = rcv.AddNext(sha); err != nil { - t.Fatal(err) - } - } - - ReceiverSerdesTest(t, &rcv) - - for n := uint64(0); n < 10000; n += 500 { - if _, err := rcv.AtIndex(n); err != nil { - t.Fatal(err) - } - } -} - -// TestElkremLess tries 10K hashes -func TestElkremLess(t *testing.T) { - var rcv ElkremReceiver - - sndr := NewElkremSender(chainhash.DoubleHashH([]byte("elktest2"))) - - for n := uint64(0); n < 5000; n++ { - sha, err := sndr.AtIndex(n) - if err != nil { - t.Fatal(err) - } - - if err = rcv.AddNext(sha); err != nil { - t.Fatal(err) - } - } - - for n := uint64(0); n < 5000; n += 500 { - if _, err := rcv.AtIndex(n); err != nil { - t.Fatal(err) - } - } -} diff --git a/elkrem/serdes.go b/elkrem/serdes.go deleted file mode 100644 index 4db43905..00000000 --- a/elkrem/serdes.go +++ /dev/null @@ -1,130 +0,0 @@ -package elkrem - -import ( - "bytes" - "encoding/binary" - "fmt" - - "github.com/roasbeef/btcd/chaincfg/chainhash" -) - -/* Serialization and Deserialization methods for the Elkrem structs. -Senders turn into 41 byte long slices. Receivers are variable length, -with 41 bytes for each stored hash, up to a maximum of 48. Receivers are -prepended with the total number of hashes, so the total max size is 1969 bytes. -*/ - -// ToBytes turns the Elkrem Receiver into a bunch of bytes in a slice. -// first the number of nodes (1 byte), then a series of 41 byte long -// serialized nodes, which are 1 byte height, 8 byte index, 32 byte hash. -func (e *ElkremReceiver) ToBytes() ([]byte, error) { - numOfNodes := uint8(len(e.s)) - // 0 element receiver also OK. Just an empty slice. - if numOfNodes == 0 { - return nil, nil - } - if numOfNodes > maxHeight+1 { - return nil, fmt.Errorf("Broken ElkremReceiver has %d nodes, max 64", - len(e.s)) - } - var buf bytes.Buffer // create buffer - - // write number of nodes (1 byte) - err := binary.Write(&buf, binary.BigEndian, numOfNodes) - if err != nil { - return nil, err - } - for _, node := range e.s { - // write 1 byte height - err = binary.Write(&buf, binary.BigEndian, node.h) - if err != nil { - return nil, err - } - // write 8 byte index - err = binary.Write(&buf, binary.BigEndian, node.i) - if err != nil { - return nil, err - } - if node.sha == nil { - return nil, fmt.Errorf("node %d has nil hash", node.i) - } - // write 32 byte sha hash - n, err := buf.Write(node.sha[:]) - if err != nil { - return nil, err - } - if n != 32 { // make sure that was 32 bytes - return nil, fmt.Errorf("%d byte hash, expect 32", n) - } - } - if buf.Len() != (int(numOfNodes)*41)+1 { - return nil, fmt.Errorf("Somehow made wrong size buf, got %d expect %d", - buf.Len(), (numOfNodes*41)+1) - } - return buf.Bytes(), nil -} - -func ElkremReceiverFromBytes(b []byte) (*ElkremReceiver, error) { - var e ElkremReceiver - if len(b) == 0 { // empty receiver, which is OK - return &e, nil - } - buf := bytes.NewBuffer(b) - // read 1 byte number of nodes stored in receiver - numOfNodes, err := buf.ReadByte() - if err != nil { - return nil, err - } - if numOfNodes < 1 || numOfNodes > maxHeight+1 { - return nil, fmt.Errorf("Read invalid number of nodes: %d", numOfNodes) - } - if buf.Len() != (int(numOfNodes) * 41) { - return nil, fmt.Errorf("Remaining buf wrong size, expect %d got %d", - (numOfNodes * 41), buf.Len()) - } - - e.s = make([]ElkremNode, numOfNodes) - - for j, _ := range e.s { - e.s[j].sha = new(chainhash.Hash) - // read 1 byte height - err := binary.Read(buf, binary.BigEndian, &e.s[j].h) - if err != nil { - return nil, err - } - // read 8 byte index - err = binary.Read(buf, binary.BigEndian, &e.s[j].i) - if err != nil { - return nil, err - } - // read 32 byte sha hash - err = e.s[j].sha.SetBytes(buf.Next(32)) - if err != nil { - return nil, err - } - // sanity check. Note that this doesn't check that index and height - // match. Could add that but it's slow. - if e.s[j].h > maxHeight { // check for super high nodes - return nil, fmt.Errorf("Read invalid node height %d", e.s[j].h) - } - if e.s[j].i > maxIndex { // check for index higher than height allows - return nil, fmt.Errorf("Node claims index %d; %d max at height %d", - e.s[j].i, maxIndex, e.s[j].h) - } - - if j > 0 { // check that node heights are descending - if e.s[j-1].h < e.s[j].h { - return nil, fmt.Errorf("Node heights out of order") - } - } - } - return &e, nil -} - -// ToBytes returns the root of the elkrem sender tree as a byte slice. This -// function is in place to allow one to export the root of the tree. However, -// node that if one uses a deterministic procedure to generate the root, then -// serialization isn't necessary as it can simply be re-derived on the fly. -func (e *ElkremSender) ToBytes() []byte { - return e.root[:] -} diff --git a/elkrem/serdes_test.go b/elkrem/serdes_test.go deleted file mode 100644 index 4bd8e3a5..00000000 --- a/elkrem/serdes_test.go +++ /dev/null @@ -1,49 +0,0 @@ -package elkrem - -import ( - "bytes" - "testing" -) - -func ReceiverSerdesTest(t *testing.T, rcv *ElkremReceiver) { - b, err := rcv.ToBytes() - if err != nil { - t.Fatal(err) - } - - rcv2, err := ElkremReceiverFromBytes(b) - if err != nil { - t.Fatal(err) - } - - b2, err := rcv2.ToBytes() - if err != nil { - t.Fatal(err) - } - - if !bytes.Equal(b, b2) { - t.Fatalf("First and second serializations different") - } -} - -//func SenderSerdesTest(t *testing.T, sndr *ElkremSender) { -// b, err := sndr.ToBytes() -// if err != nil { -// t.Fatal(err) -// } -// t.Logf("Serialized sender; %d bytes, hex:\n%x\n", len(b), b) - -// *sndr, err = ElkremSenderFromBytes(b) -// if err != nil { -// t.Fatal(err) -// } - -// b2, err := sndr.ToBytes() -// if err != nil { -// t.Fatal(err) -// } - -// if !bytes.Equal(b, b2) { -// t.Fatalf("First and second serializations different") -// } -//} diff --git a/lnwallet/channel.go b/lnwallet/channel.go index dfeb5066..21b6a41b 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -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 } diff --git a/lnwallet/channel_test.go b/lnwallet/channel_test.go index 82f546c3..eca2b7c7 100644 --- a/lnwallet/channel_test.go +++ b/lnwallet/channel_test.go @@ -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 { diff --git a/lnwallet/script_utils.go b/lnwallet/script_utils.go index a687ee0d..0bb14fd9 100644 --- a/lnwallet/script_utils.go +++ b/lnwallet/script_utils.go @@ -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 diff --git a/lnwallet/size.go b/lnwallet/size.go index edd12533..c7aa78d9 100644 --- a/lnwallet/size.go +++ b/lnwallet/size.go @@ -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... diff --git a/lnwallet/wallet.go b/lnwallet/wallet.go index bc9bdcb6..b3f9fe36 100644 --- a/lnwallet/wallet.go +++ b/lnwallet/wallet.go @@ -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.