mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-02-22 14:22:37 +01:00
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:
parent
b40afeaa08
commit
f86557c3e4
11 changed files with 206 additions and 573 deletions
|
@ -9,9 +9,8 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/boltdb/bolt"
|
"github.com/boltdb/bolt"
|
||||||
"github.com/lightningnetwork/lnd/elkrem"
|
"github.com/lightningnetwork/lnd/shachain"
|
||||||
"github.com/roasbeef/btcd/btcec"
|
"github.com/roasbeef/btcd/btcec"
|
||||||
"github.com/roasbeef/btcd/chaincfg/chainhash"
|
|
||||||
"github.com/roasbeef/btcd/wire"
|
"github.com/roasbeef/btcd/wire"
|
||||||
"github.com/roasbeef/btcutil"
|
"github.com/roasbeef/btcutil"
|
||||||
)
|
)
|
||||||
|
@ -89,9 +88,9 @@ var (
|
||||||
// and finally 2-of-2 multisig redeem script.
|
// and finally 2-of-2 multisig redeem script.
|
||||||
fundingTxnKey = []byte("fsk")
|
fundingTxnKey = []byte("fsk")
|
||||||
|
|
||||||
// elkremStateKey stores their current revocation hash, and our elkrem
|
// preimageStateKey stores their current revocation hash, our
|
||||||
// sender, and their elkrem receiver.
|
// preimage producer and their preimage store.
|
||||||
elkremStateKey = []byte("esk")
|
preimageStateKey = []byte("esk")
|
||||||
|
|
||||||
// deliveryScriptsKey stores the scripts for the final delivery in the
|
// deliveryScriptsKey stores the scripts for the final delivery in the
|
||||||
// case of a cooperative closure.
|
// 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.
|
// aren't yet able to verify that it's actually in the hash chain.
|
||||||
TheirCurrentRevocation *btcec.PublicKey
|
TheirCurrentRevocation *btcec.PublicKey
|
||||||
TheirCurrentRevocationHash [32]byte
|
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
|
// OurDeliveryScript is the script to be used to pay to us in
|
||||||
// cooperative closes.
|
// cooperative closes.
|
||||||
|
@ -480,15 +488,16 @@ func (c *OpenChannel) AppendToRevocationLog(delta *ChannelDelta) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Persist the latest elkrem state to disk as the remote peer
|
// Persist the latest preimage state to disk as the remote peer
|
||||||
// has just added to our local elkrem receiver, and given us a
|
// has just added to our local preimage store, and
|
||||||
// new pending revocation key.
|
// given us a new pending revocation key.
|
||||||
if err := putChanElkremState(nodeChanBucket, c); err != nil {
|
if err := putChanPreimageState(nodeChanBucket, c); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// With the current elkrem state updated, append a new log
|
// With the current preimage producer/store state updated,
|
||||||
// entry recording this the delta of this state transition.
|
// append a new log entry recording this the delta of this state
|
||||||
|
// transition.
|
||||||
// TODO(roasbeef): could make the deltas relative, would save
|
// TODO(roasbeef): could make the deltas relative, would save
|
||||||
// space, but then tradeoff for more disk-seeks to recover the
|
// space, but then tradeoff for more disk-seeks to recover the
|
||||||
// full state.
|
// full state.
|
||||||
|
@ -737,7 +746,7 @@ func putOpenChannel(openChanBucket *bolt.Bucket, nodeChanBucket *bolt.Bucket,
|
||||||
if err := putChanFundingInfo(nodeChanBucket, channel); err != nil {
|
if err := putChanFundingInfo(nodeChanBucket, channel); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := putChanElkremState(nodeChanBucket, channel); err != nil {
|
if err := putChanPreimageState(nodeChanBucket, channel); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := putChanDeliveryScripts(nodeChanBucket, channel); err != nil {
|
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 {
|
if err = fetchChanFundingInfo(nodeChanBucket, channel); err != nil {
|
||||||
return nil, fmt.Errorf("unable to read funding info: %v", err)
|
return nil, fmt.Errorf("unable to read funding info: %v", err)
|
||||||
}
|
}
|
||||||
if err = fetchChanElkremState(nodeChanBucket, channel); err != nil {
|
if err = fetchChanPreimageState(nodeChanBucket, channel); err != nil {
|
||||||
return nil, fmt.Errorf("uable to read elkrem state: %v", err)
|
return nil, err
|
||||||
}
|
}
|
||||||
if err = fetchChanDeliveryScripts(nodeChanBucket, channel); err != nil {
|
if err = fetchChanDeliveryScripts(nodeChanBucket, channel); err != nil {
|
||||||
return nil, fmt.Errorf("unable to read delivery scripts: %v", err)
|
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 {
|
if err := deleteChanFundingInfo(nodeChanBucket, channelID); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := deleteChanElkremState(nodeChanBucket, channelID); err != nil {
|
if err := deleteChanPreimageState(nodeChanBucket, channelID); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := deleteChanDeliveryScripts(nodeChanBucket, channelID); err != nil {
|
if err := deleteChanDeliveryScripts(nodeChanBucket, channelID); err != nil {
|
||||||
|
@ -1459,16 +1468,7 @@ func fetchChanFundingInfo(nodeChanBucket *bolt.Bucket, channel *OpenChannel) err
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func putChanElkremState(nodeChanBucket *bolt.Bucket, channel *OpenChannel) error {
|
func putChanPreimageState(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())
|
|
||||||
|
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
|
|
||||||
revKey := channel.TheirCurrentRevocation.SerializeCompressed()
|
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
|
// TODO(roasbeef): shouldn't be storing on disk, should re-derive as
|
||||||
// needed
|
// needed
|
||||||
senderBytes := channel.LocalElkrem.ToBytes()
|
data, err := channel.RevocationProducer.ToBytes()
|
||||||
if err := wire.WriteVarBytes(&b, 0, senderBytes); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
reciverBytes, err := channel.RemoteElkrem.ToBytes()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1499,28 +1502,36 @@ func putChanElkremState(nodeChanBucket *bolt.Bucket, channel *OpenChannel) error
|
||||||
return err
|
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 {
|
func deleteChanPreimageState(nodeChanBucket *bolt.Bucket, chanID []byte) error {
|
||||||
elkremKey := make([]byte, len(elkremStateKey)+len(chanID))
|
preimageKey := make([]byte, len(preimageStateKey)+len(chanID))
|
||||||
copy(elkremKey[:3], elkremStateKey)
|
copy(preimageKey[:3], preimageStateKey)
|
||||||
copy(elkremKey[3:], chanID)
|
copy(preimageKey[3:], chanID)
|
||||||
return nodeChanBucket.Delete(elkremKey)
|
return nodeChanBucket.Delete(preimageKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchChanElkremState(nodeChanBucket *bolt.Bucket, channel *OpenChannel) error {
|
func fetchChanPreimageState(nodeChanBucket *bolt.Bucket, channel *OpenChannel) error {
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
if err := writeOutpoint(&b, channel.ChanID); err != nil {
|
if err := writeOutpoint(&b, channel.ChanID); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
elkremKey := make([]byte, len(elkremStateKey)+b.Len())
|
preimageKey := make([]byte, len(preimageStateKey)+b.Len())
|
||||||
copy(elkremKey[:3], elkremStateKey)
|
copy(preimageKey[:3], preimageStateKey)
|
||||||
copy(elkremKey[3:], b.Bytes())
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -1529,32 +1540,30 @@ func fetchChanElkremState(nodeChanBucket *bolt.Bucket, channel *OpenChannel) err
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := elkremStateBytes.Read(channel.TheirCurrentRevocationHash[:]); err != nil {
|
if _, err := reader.Read(channel.TheirCurrentRevocationHash[:]); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(roasbeef): should be rederiving on fly, or encrypting on disk.
|
// 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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
elkremRoot, err := chainhash.NewHash(senderBytes)
|
channel.RevocationProducer, err = shachain.NewRevocationProducerFromBytes(producerBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
channel.LocalElkrem = elkrem.NewElkremSender(*elkremRoot)
|
|
||||||
|
|
||||||
reciverBytes, err := wire.ReadVarBytes(elkremStateBytes, 0, 1000, "")
|
storeBytes, err := wire.ReadVarBytes(reader, 0, 1000, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
remoteE, err := elkrem.ElkremReceiverFromBytes(reciverBytes)
|
channel.RevocationStore, err = shachain.NewRevocationStoreFromBytes(storeBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
channel.RemoteElkrem = remoteE
|
|
||||||
|
|
||||||
_, err = io.ReadFull(elkremStateBytes, channel.StateHintObsfucator[:])
|
_, err = io.ReadFull(reader, channel.StateHintObsfucator[:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/davecgh/go-spew/spew"
|
"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/btcec"
|
||||||
"github.com/roasbeef/btcd/chaincfg"
|
"github.com/roasbeef/btcd/chaincfg"
|
||||||
"github.com/roasbeef/btcd/chaincfg/chainhash"
|
"github.com/roasbeef/btcd/chaincfg/chainhash"
|
||||||
|
@ -117,17 +117,16 @@ func createTestChannelState(cdb *DB) (*OpenChannel, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simulate 1000 channel updates via progression of the elkrem
|
// Simulate 1000 channel updates.
|
||||||
// revocation trees.
|
producer := shachain.NewRevocationProducer((*chainhash.Hash)(&key))
|
||||||
sender := elkrem.NewElkremSender(key)
|
store := shachain.NewRevocationStore()
|
||||||
receiver := &elkrem.ElkremReceiver{}
|
|
||||||
for i := 0; i < 1000; i++ {
|
for i := 0; i < 1000; i++ {
|
||||||
preImage, err := sender.AtIndex(uint64(i))
|
preImage, err := producer.AtIndex(uint64(i))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if receiver.AddNext(preImage); err != nil {
|
if store.Store(preImage); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -150,8 +149,8 @@ func createTestChannelState(cdb *DB) (*OpenChannel, error) {
|
||||||
TheirBalance: btcutil.Amount(9000),
|
TheirBalance: btcutil.Amount(9000),
|
||||||
OurCommitTx: testTx,
|
OurCommitTx: testTx,
|
||||||
OurCommitSig: bytes.Repeat([]byte{1}, 71),
|
OurCommitSig: bytes.Repeat([]byte{1}, 71),
|
||||||
LocalElkrem: sender,
|
RevocationProducer: producer,
|
||||||
RemoteElkrem: receiver,
|
RevocationStore: store,
|
||||||
StateHintObsfucator: obsfucator,
|
StateHintObsfucator: obsfucator,
|
||||||
FundingOutpoint: testOutpoint,
|
FundingOutpoint: testOutpoint,
|
||||||
OurMultiSigKey: privKey.PubKey(),
|
OurMultiSigKey: privKey.PubKey(),
|
||||||
|
@ -207,34 +206,34 @@ func TestOpenChannelPutGetDelete(t *testing.T) {
|
||||||
// The decoded channel state should be identical to what we stored
|
// The decoded channel state should be identical to what we stored
|
||||||
// above.
|
// above.
|
||||||
if !state.IdentityPub.IsEqual(newState.IdentityPub) {
|
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) {
|
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 {
|
if state.MinFeePerKb != newState.MinFeePerKb {
|
||||||
t.Fatalf("fee/kb doesn't match")
|
t.Fatal("fee/kb doesn't match")
|
||||||
}
|
}
|
||||||
if state.TheirDustLimit != newState.TheirDustLimit {
|
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 {
|
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 {
|
if state.IsInitiator != newState.IsInitiator {
|
||||||
t.Fatalf("initiator status doesn't match")
|
t.Fatal("initiator status doesn't match")
|
||||||
}
|
}
|
||||||
if state.ChanType != newState.ChanType {
|
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(),
|
if !bytes.Equal(state.OurCommitKey.SerializeCompressed(),
|
||||||
newState.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(),
|
if !bytes.Equal(state.TheirCommitKey.SerializeCompressed(),
|
||||||
newState.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 {
|
if state.Capacity != newState.Capacity {
|
||||||
|
@ -242,49 +241,49 @@ func TestOpenChannelPutGetDelete(t *testing.T) {
|
||||||
newState.Capacity)
|
newState.Capacity)
|
||||||
}
|
}
|
||||||
if state.OurBalance != newState.OurBalance {
|
if state.OurBalance != newState.OurBalance {
|
||||||
t.Fatalf("our balance doesn't match")
|
t.Fatal("our balance doesn't match")
|
||||||
}
|
}
|
||||||
if state.TheirBalance != newState.TheirBalance {
|
if state.TheirBalance != newState.TheirBalance {
|
||||||
t.Fatalf("their balance doesn't match")
|
t.Fatal("their balance doesn't match")
|
||||||
}
|
}
|
||||||
|
|
||||||
var b1, b2 bytes.Buffer
|
var b1, b2 bytes.Buffer
|
||||||
if err := state.OurCommitTx.Serialize(&b1); err != nil {
|
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 {
|
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()) {
|
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) {
|
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?
|
// TODO(roasbeef): replace with a single equal?
|
||||||
if !reflect.DeepEqual(state.FundingOutpoint, newState.FundingOutpoint) {
|
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(),
|
if !bytes.Equal(state.OurMultiSigKey.SerializeCompressed(),
|
||||||
newState.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(),
|
if !bytes.Equal(state.TheirMultiSigKey.SerializeCompressed(),
|
||||||
newState.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) {
|
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.
|
// The local and remote delivery scripts should be identical.
|
||||||
if !bytes.Equal(state.OurDeliveryScript, newState.OurDeliveryScript) {
|
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) {
|
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 {
|
if state.NumUpdates != newState.NumUpdates {
|
||||||
|
@ -304,33 +303,45 @@ func TestOpenChannelPutGetDelete(t *testing.T) {
|
||||||
state.TotalSatoshisSent, newState.TotalSatoshisSent)
|
state.TotalSatoshisSent, newState.TotalSatoshisSent)
|
||||||
}
|
}
|
||||||
if state.TotalSatoshisReceived != newState.TotalSatoshisReceived {
|
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() {
|
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.
|
// The local and remote producers should be identical.
|
||||||
if !bytes.Equal(state.LocalElkrem.ToBytes(), newState.LocalElkrem.ToBytes()) {
|
oldProducer, err := state.RevocationProducer.ToBytes()
|
||||||
t.Fatalf("local elkrems don't match")
|
|
||||||
}
|
|
||||||
oldRemoteElkrem, err := state.RemoteElkrem.ToBytes()
|
|
||||||
if err != nil {
|
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 {
|
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) {
|
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[:]) {
|
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]) {
|
if !reflect.DeepEqual(state.Htlcs[0], newState.Htlcs[0]) {
|
||||||
t.Fatalf("htlcs don't match: %v vs %v", spew.Sdump(state.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[:],
|
if !bytes.Equal(state.StateHintObsfucator[:],
|
||||||
newState.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
|
// 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.
|
// should yield no results.
|
||||||
openChans, err = cdb.FetchAllChannels()
|
openChans, err = cdb.FetchAllChannels()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to fetch all open chans")
|
t.Fatal("unable to fetch all open chans")
|
||||||
}
|
}
|
||||||
if len(openChans) != 0 {
|
if len(openChans) != 0 {
|
||||||
t.Fatalf("all channels not deleted, found %v", len(openChans))
|
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
|
// The two deltas (the original vs the on-disk version) should
|
||||||
// identical, and all HTLC data should properly be retained.
|
// identical, and all HTLC data should properly be retained.
|
||||||
if delta.LocalBalance != diskDelta.LocalBalance {
|
if delta.LocalBalance != diskDelta.LocalBalance {
|
||||||
t.Fatalf("local balances don't match")
|
t.Fatal("local balances don't match")
|
||||||
}
|
}
|
||||||
if delta.RemoteBalance != diskDelta.RemoteBalance {
|
if delta.RemoteBalance != diskDelta.RemoteBalance {
|
||||||
t.Fatalf("remote balances don't match")
|
t.Fatal("remote balances don't match")
|
||||||
}
|
}
|
||||||
if delta.UpdateNum != diskDelta.UpdateNum {
|
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++ {
|
for i := 0; i < len(delta.Htlcs); i++ {
|
||||||
originalHTLC := delta.Htlcs[i]
|
originalHTLC := delta.Htlcs[i]
|
||||||
|
@ -546,7 +557,7 @@ func TestChannelStateTransition(t *testing.T) {
|
||||||
}
|
}
|
||||||
if !bytes.Equal(updatedChannel[0].TheirCurrentRevocationHash[:],
|
if !bytes.Equal(updatedChannel[0].TheirCurrentRevocationHash[:],
|
||||||
newRevocation) {
|
newRevocation) {
|
||||||
t.Fatalf("revocation state wasn't synced!")
|
t.Fatal("revocation state wasn't synced!")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now attempt to delete the channel from the database.
|
// Now attempt to delete the channel from the database.
|
||||||
|
@ -569,6 +580,6 @@ func TestChannelStateTransition(t *testing.T) {
|
||||||
// revocation log has been deleted.
|
// revocation log has been deleted.
|
||||||
_, err = updatedChannel[0].FindPreviousState(uint64(delta.UpdateNum))
|
_, err = updatedChannel[0].FindPreviousState(uint64(delta.UpdateNum))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatalf("revocation log search should've failed")
|
t.Fatal("revocation log search should've failed")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
151
elkrem/elkrem.go
151
elkrem/elkrem.go
|
@ -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<<h) { // left
|
|
||||||
sha = LeftSha(sha)
|
|
||||||
i = i - (1 << h) // left descent reduces index by 2**h
|
|
||||||
} else { // right
|
|
||||||
sha = RightSha(sha)
|
|
||||||
i-- // right descent reduces index by 1
|
|
||||||
}
|
|
||||||
if h == 0 { // avoid underflowing h
|
|
||||||
break
|
|
||||||
}
|
|
||||||
h-- // either descent reduces height by 1
|
|
||||||
}
|
|
||||||
if w != i { // somehow couldn't / didn't end up where we wanted to go
|
|
||||||
return sha, fmt.Errorf("can't generate index %d from %d", w, i)
|
|
||||||
}
|
|
||||||
return sha, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creates an Elkrem Sender from a root hash.
|
|
||||||
func NewElkremSender(r chainhash.Hash) *ElkremSender {
|
|
||||||
var e ElkremSender
|
|
||||||
e.root = &r
|
|
||||||
return &e
|
|
||||||
}
|
|
||||||
|
|
||||||
// AtIndex skips to the requested index
|
|
||||||
// should never error; remove error..?
|
|
||||||
func (e *ElkremSender) AtIndex(w uint64) (*chainhash.Hash, error) {
|
|
||||||
out, err := descend(w, maxIndex, maxHeight, *e.root)
|
|
||||||
return &out, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddNext inserts the next hash in the tree. Returns an error if
|
|
||||||
// the incoming hash doesn't fit.
|
|
||||||
func (e *ElkremReceiver) AddNext(sha *chainhash.Hash) error {
|
|
||||||
// note: careful about atomicity / disk writes here
|
|
||||||
var n ElkremNode
|
|
||||||
n.sha = sha
|
|
||||||
t := len(e.s) - 1 // top of stack
|
|
||||||
if t >= 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
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
130
elkrem/serdes.go
130
elkrem/serdes.go
|
@ -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[:]
|
|
||||||
}
|
|
|
@ -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")
|
|
||||||
// }
|
|
||||||
//}
|
|
|
@ -734,10 +734,10 @@ func newBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64,
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// With the state number broadcast known, we can now derive the proper
|
// With the state number broadcast known, we can now derive/restore the
|
||||||
// leaf from our revocation tree necessary to sweep the remote party's
|
// proper revocation preimage necessary to sweep the remote party's
|
||||||
// output.
|
// output.
|
||||||
revocationPreimage, err := chanState.RemoteElkrem.AtIndex(stateNum)
|
revocationPreimage, err := chanState.RevocationStore.LookUp(stateNum)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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
|
// derive the key+hash needed to construct the new commitment view and
|
||||||
// state.
|
// state.
|
||||||
nextHeight := lc.currentHeight + 1
|
nextHeight := lc.currentHeight + 1
|
||||||
revocation, err := lc.channelState.LocalElkrem.AtIndex(nextHeight)
|
revocation, err := lc.channelState.RevocationProducer.AtIndex(nextHeight)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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
|
// Now that we've accept a new state transition, we send the remote
|
||||||
// party the revocation for our current commitment state.
|
// party the revocation for our current commitment state.
|
||||||
revocationMsg := &lnwire.RevokeAndAck{}
|
revocationMsg := &lnwire.RevokeAndAck{}
|
||||||
currentRevocation, err := lc.channelState.LocalElkrem.AtIndex(lc.currentHeight)
|
currentRevocation, err := lc.channelState.RevocationProducer.AtIndex(lc.currentHeight)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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
|
// Along with this revocation, we'll also send an additional extension
|
||||||
// to our revocation window to the remote party.
|
// to our revocation window to the remote party.
|
||||||
lc.revocationWindowEdge++
|
lc.revocationWindowEdge++
|
||||||
revocationEdge, err := lc.channelState.LocalElkrem.AtIndex(lc.revocationWindowEdge)
|
revocationEdge, err := lc.channelState.RevocationProducer.AtIndex(lc.revocationWindowEdge)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -1655,11 +1655,10 @@ func (lc *LightningChannel) ReceiveRevocation(revMsg *lnwire.RevokeAndAck) ([]*P
|
||||||
currentRevocationKey := lc.channelState.TheirCurrentRevocation
|
currentRevocationKey := lc.channelState.TheirCurrentRevocation
|
||||||
pendingRevocation := chainhash.Hash(revMsg.Revocation)
|
pendingRevocation := chainhash.Hash(revMsg.Revocation)
|
||||||
|
|
||||||
// Ensure the new preimage fits in properly within the elkrem receiver
|
// Ensure that the new pre-image can be placed in preimage store.
|
||||||
// tree. If this fails, then all other checks are skipped.
|
|
||||||
// TODO(rosbeef): abstract into func
|
// TODO(rosbeef): abstract into func
|
||||||
remoteElkrem := lc.channelState.RemoteElkrem
|
store := lc.channelState.RevocationStore
|
||||||
if err := remoteElkrem.AddNext(&pendingRevocation); err != nil {
|
if err := store.Store(&pendingRevocation); err != nil {
|
||||||
return nil, err
|
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
|
// At this point, the revocation has been accepted, and we've rotated
|
||||||
// the current revocation key+hash for the remote party. Therefore we
|
// the current revocation key+hash for the remote party. Therefore we
|
||||||
// sync now to ensure the elkrem receiver state is consistent with the
|
// sync now to ensure the revocation producer state is consistent with
|
||||||
// current commitment height.
|
// the current commitment height.
|
||||||
tail := lc.remoteCommitChain.tail()
|
tail := lc.remoteCommitChain.tail()
|
||||||
delta, err := tail.toChannelDelta()
|
delta, err := tail.toChannelDelta()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1778,7 +1777,7 @@ func (lc *LightningChannel) ExtendRevocationWindow() (*lnwire.RevokeAndAck, erro
|
||||||
revMsg.ChannelPoint = *lc.channelState.ChanID
|
revMsg.ChannelPoint = *lc.channelState.ChanID
|
||||||
|
|
||||||
nextHeight := lc.revocationWindowEdge + 1
|
nextHeight := lc.revocationWindowEdge + 1
|
||||||
revocation, err := lc.channelState.LocalElkrem.AtIndex(nextHeight)
|
revocation, err := lc.channelState.RevocationProducer.AtIndex(nextHeight)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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
|
// Re-derive the original pkScript for out to-self output within the
|
||||||
// commitment transaction. We'll need this for the created sign
|
// commitment transaction. We'll need this for the created sign
|
||||||
// descriptor.
|
// descriptor.
|
||||||
elkrem := lc.channelState.LocalElkrem
|
producer := lc.channelState.RevocationProducer
|
||||||
unusedRevocation, err := elkrem.AtIndex(lc.currentHeight)
|
unusedRevocation, err := producer.AtIndex(lc.currentHeight)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,8 +11,8 @@ import (
|
||||||
"github.com/go-errors/errors"
|
"github.com/go-errors/errors"
|
||||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||||
"github.com/lightningnetwork/lnd/channeldb"
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
"github.com/lightningnetwork/lnd/elkrem"
|
|
||||||
"github.com/lightningnetwork/lnd/lnwire"
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
|
"github.com/lightningnetwork/lnd/shachain"
|
||||||
"github.com/roasbeef/btcd/blockchain"
|
"github.com/roasbeef/btcd/blockchain"
|
||||||
"github.com/roasbeef/btcd/btcec"
|
"github.com/roasbeef/btcd/btcec"
|
||||||
"github.com/roasbeef/btcd/chaincfg/chainhash"
|
"github.com/roasbeef/btcd/chaincfg/chainhash"
|
||||||
|
@ -202,15 +202,17 @@ func createTestChannels(revocationWindow int) (*LightningChannel, *LightningChan
|
||||||
}
|
}
|
||||||
fundingTxIn := wire.NewTxIn(prevOut, nil, nil)
|
fundingTxIn := wire.NewTxIn(prevOut, nil, nil)
|
||||||
|
|
||||||
bobElkrem := elkrem.NewElkremSender(deriveElkremRoot(bobKeyPriv, bobKeyPub, aliceKeyPub))
|
bobRoot := deriveRevocationRoot(bobKeyPriv, bobKeyPub, aliceKeyPub)
|
||||||
bobFirstRevoke, err := bobElkrem.AtIndex(0)
|
bobPreimageProducer := shachain.NewRevocationProducer(bobRoot)
|
||||||
|
bobFirstRevoke, err := bobPreimageProducer.AtIndex(0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
bobRevokeKey := DeriveRevocationPubkey(aliceKeyPub, bobFirstRevoke[:])
|
bobRevokeKey := DeriveRevocationPubkey(aliceKeyPub, bobFirstRevoke[:])
|
||||||
|
|
||||||
aliceElkrem := elkrem.NewElkremSender(deriveElkremRoot(aliceKeyPriv, aliceKeyPub, bobKeyPub))
|
aliceRoot := deriveRevocationRoot(aliceKeyPriv, aliceKeyPub, bobKeyPub)
|
||||||
aliceFirstRevoke, err := aliceElkrem.AtIndex(0)
|
alicePreimageProducer := shachain.NewRevocationProducer(aliceRoot)
|
||||||
|
aliceFirstRevoke, err := alicePreimageProducer.AtIndex(0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
|
@ -262,8 +264,8 @@ func createTestChannels(revocationWindow int) (*LightningChannel, *LightningChan
|
||||||
LocalCsvDelay: csvTimeoutAlice,
|
LocalCsvDelay: csvTimeoutAlice,
|
||||||
RemoteCsvDelay: csvTimeoutBob,
|
RemoteCsvDelay: csvTimeoutBob,
|
||||||
TheirCurrentRevocation: bobRevokeKey,
|
TheirCurrentRevocation: bobRevokeKey,
|
||||||
LocalElkrem: aliceElkrem,
|
RevocationProducer: alicePreimageProducer,
|
||||||
RemoteElkrem: &elkrem.ElkremReceiver{},
|
RevocationStore: shachain.NewRevocationStore(),
|
||||||
TheirDustLimit: bobDustLimit,
|
TheirDustLimit: bobDustLimit,
|
||||||
OurDustLimit: aliceDustLimit,
|
OurDustLimit: aliceDustLimit,
|
||||||
Db: dbAlice,
|
Db: dbAlice,
|
||||||
|
@ -288,8 +290,8 @@ func createTestChannels(revocationWindow int) (*LightningChannel, *LightningChan
|
||||||
LocalCsvDelay: csvTimeoutBob,
|
LocalCsvDelay: csvTimeoutBob,
|
||||||
RemoteCsvDelay: csvTimeoutAlice,
|
RemoteCsvDelay: csvTimeoutAlice,
|
||||||
TheirCurrentRevocation: aliceRevokeKey,
|
TheirCurrentRevocation: aliceRevokeKey,
|
||||||
LocalElkrem: bobElkrem,
|
RevocationProducer: bobPreimageProducer,
|
||||||
RemoteElkrem: &elkrem.ElkremReceiver{},
|
RevocationStore: shachain.NewRevocationStore(),
|
||||||
TheirDustLimit: aliceDustLimit,
|
TheirDustLimit: aliceDustLimit,
|
||||||
OurDustLimit: bobDustLimit,
|
OurDustLimit: bobDustLimit,
|
||||||
Db: dbBob,
|
Db: dbBob,
|
||||||
|
@ -598,9 +600,10 @@ func TestSimpleAddSettleWorkflow(t *testing.T) {
|
||||||
// transaction with some degree of error corresponds to the actual size.
|
// transaction with some degree of error corresponds to the actual size.
|
||||||
func TestCheckCommitTxSize(t *testing.T) {
|
func TestCheckCommitTxSize(t *testing.T) {
|
||||||
checkSize := func(channel *LightningChannel, count int) {
|
checkSize := func(channel *LightningChannel, count int) {
|
||||||
// Due to variable size of the signatures (71-73) we may have
|
// Due to variable size of the signatures (70-73) in
|
||||||
// an estimation error.
|
// witness script actual size of commitment transaction might
|
||||||
BaseCommitmentTxSizeEstimationError := 4
|
// be lower on 6 weight.
|
||||||
|
BaseCommitmentTxSizeEstimationError := 6
|
||||||
|
|
||||||
commitTx, err := channel.getSignedCommitTx()
|
commitTx, err := channel.getSignedCommitTx()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -752,31 +752,31 @@ func DeriveRevocationPrivKey(commitPrivKey *btcec.PrivateKey,
|
||||||
return privRevoke
|
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
|
// 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
|
// instantiated with sha-256. The secret data used is our multi-sig private
|
||||||
// key, with the salt being the remote node's public key.
|
// key, with the salt being the remote node's public key.
|
||||||
//
|
//
|
||||||
// [1]: https://eprint.iacr.org/2010/264.pdf
|
// [1]: https://eprint.iacr.org/2010/264.pdf
|
||||||
// [2]: https://tools.ietf.org/html/rfc5869
|
// [2]: https://tools.ietf.org/html/rfc5869
|
||||||
func deriveElkremRoot(elkremDerivationRoot *btcec.PrivateKey,
|
func deriveRevocationRoot(derivationRoot *btcec.PrivateKey,
|
||||||
localMultiSigKey *btcec.PublicKey,
|
localMultiSigKey *btcec.PublicKey,
|
||||||
remoteMultiSigKey *btcec.PublicKey) chainhash.Hash {
|
remoteMultiSigKey *btcec.PublicKey) *chainhash.Hash {
|
||||||
|
|
||||||
secret := elkremDerivationRoot.Serialize()
|
secret := derivationRoot.Serialize()
|
||||||
salt := localMultiSigKey.SerializeCompressed()
|
salt := localMultiSigKey.SerializeCompressed()
|
||||||
info := remoteMultiSigKey.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
|
// 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.
|
// be draining the HKDF past its available entropy horizon.
|
||||||
// TODO(roasbeef): revisit...
|
// TODO(roasbeef): revisit...
|
||||||
var elkremRoot chainhash.Hash
|
var root chainhash.Hash
|
||||||
rootReader.Read(elkremRoot[:])
|
seedReader.Read(root[:])
|
||||||
|
|
||||||
return elkremRoot
|
return &root
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetStateNumHint encodes the current state number within the passed
|
// SetStateNumHint encodes the current state number within the passed
|
||||||
|
|
|
@ -85,14 +85,14 @@ const (
|
||||||
// - Marker: 1 byte
|
// - Marker: 1 byte
|
||||||
WitnessHeaderSize = 1 + 1
|
WitnessHeaderSize = 1 + 1
|
||||||
|
|
||||||
// CommitmentTransaction: 125 bytes
|
// CommitmentTransaction: 125 43 * num-htlc-outputs bytes
|
||||||
// - Version: 4 bytes
|
// - Version: 4 bytes
|
||||||
// - WitnessHeader <---- part of the witness data
|
// - WitnessHeader <---- part of the witness data
|
||||||
// - CountTxIn: 1 byte
|
// - CountTxIn: 1 byte
|
||||||
// - TxIn:
|
// - TxIn: 41 bytes
|
||||||
// FundingInput
|
// FundingInput
|
||||||
// - CountTxOut: 1 byte
|
// - CountTxOut: 1 byte
|
||||||
// - TxOut:
|
// - TxOut: 74 + 43 * num-htlc-outputs bytes
|
||||||
// OutputPayingToThem,
|
// OutputPayingToThem,
|
||||||
// OutputPayingToUs,
|
// OutputPayingToUs,
|
||||||
// ....HTLCOutputs...
|
// ....HTLCOutputs...
|
||||||
|
|
|
@ -11,10 +11,10 @@ import (
|
||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||||
"github.com/lightningnetwork/lnd/channeldb"
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
"github.com/lightningnetwork/lnd/elkrem"
|
|
||||||
"github.com/roasbeef/btcd/chaincfg"
|
"github.com/roasbeef/btcd/chaincfg"
|
||||||
"github.com/roasbeef/btcutil/hdkeychain"
|
"github.com/roasbeef/btcutil/hdkeychain"
|
||||||
|
|
||||||
|
"github.com/lightningnetwork/lnd/shachain"
|
||||||
"github.com/roasbeef/btcd/btcec"
|
"github.com/roasbeef/btcd/btcec"
|
||||||
"github.com/roasbeef/btcd/txscript"
|
"github.com/roasbeef/btcd/txscript"
|
||||||
"github.com/roasbeef/btcd/wire"
|
"github.com/roasbeef/btcd/wire"
|
||||||
|
@ -27,9 +27,9 @@ const (
|
||||||
// outside word.
|
// outside word.
|
||||||
msgBufferSize = 100
|
msgBufferSize = 100
|
||||||
|
|
||||||
// elkremRootIndex is the top level HD key index from which secrets
|
// revocationRootIndex is the top level HD key index from which secrets
|
||||||
// used to generate elkrem roots should be derived from.
|
// used to generate producer roots should be derived from.
|
||||||
elkremRootIndex = hdkeychain.HardenedKeyStart + 1
|
revocationRootIndex = hdkeychain.HardenedKeyStart + 1
|
||||||
|
|
||||||
// identityKeyIndex is the top level HD key index which is used to
|
// identityKeyIndex is the top level HD key index which is used to
|
||||||
// generate/rotate identity keys.
|
// generate/rotate identity keys.
|
||||||
|
@ -42,7 +42,6 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
||||||
// Namespace bucket keys.
|
// Namespace bucket keys.
|
||||||
lightningNamespaceKey = []byte("ln-wallet")
|
lightningNamespaceKey = []byte("ln-wallet")
|
||||||
waddrmgrNamespaceKey = []byte("waddrmgr")
|
waddrmgrNamespaceKey = []byte("waddrmgr")
|
||||||
|
@ -757,11 +756,11 @@ func (l *LightningWallet) handleContributionMsg(req *addContributionMsg) {
|
||||||
// Initialize an empty sha-chain for them, tracking the current pending
|
// 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
|
// revocation hash (we don't yet know the preimage so we can't add it
|
||||||
// to the chain).
|
// to the chain).
|
||||||
e := &elkrem.ElkremReceiver{}
|
s := shachain.NewRevocationStore()
|
||||||
pendingReservation.partialState.RemoteElkrem = e
|
pendingReservation.partialState.RevocationStore = s
|
||||||
pendingReservation.partialState.TheirCurrentRevocation = theirContribution.RevocationKey
|
pendingReservation.partialState.TheirCurrentRevocation = theirContribution.RevocationKey
|
||||||
|
|
||||||
masterElkremRoot, err := l.deriveMasterElkremRoot()
|
masterElkremRoot, err := l.deriveMasterRevocationRoot()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
req.err <- err
|
req.err <- err
|
||||||
return
|
return
|
||||||
|
@ -769,12 +768,11 @@ func (l *LightningWallet) handleContributionMsg(req *addContributionMsg) {
|
||||||
|
|
||||||
// Now that we have their commitment key, we can create the revocation
|
// Now that we have their commitment key, we can create the revocation
|
||||||
// key for the first version of our commitment transaction. To do so,
|
// 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
|
// we'll first create our root, then produce the first pre-image.
|
||||||
// from it.
|
root := deriveRevocationRoot(masterElkremRoot, ourKey, theirKey)
|
||||||
elkremRoot := deriveElkremRoot(masterElkremRoot, ourKey, theirKey)
|
producer := shachain.NewRevocationProducer(root)
|
||||||
elkremSender := elkrem.NewElkremSender(elkremRoot)
|
pendingReservation.partialState.RevocationProducer = producer
|
||||||
pendingReservation.partialState.LocalElkrem = elkremSender
|
firstPreimage, err := producer.AtIndex(0)
|
||||||
firstPreimage, err := elkremSender.AtIndex(0)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
req.err <- err
|
req.err <- err
|
||||||
return
|
return
|
||||||
|
@ -812,7 +810,7 @@ func (l *LightningWallet) handleContributionMsg(req *addContributionMsg) {
|
||||||
// TODO(roasbeef): define obsfucator scheme for dual funder
|
// TODO(roasbeef): define obsfucator scheme for dual funder
|
||||||
var stateObsfucator [StateHintSize]byte
|
var stateObsfucator [StateHintSize]byte
|
||||||
if pendingReservation.partialState.IsInitiator {
|
if pendingReservation.partialState.IsInitiator {
|
||||||
stateObsfucator, err = deriveStateHintObsfucator(elkremSender)
|
stateObsfucator, err = deriveStateHintObfuscator(producer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
req.err <- err
|
req.err <- err
|
||||||
return
|
return
|
||||||
|
@ -902,7 +900,7 @@ func (l *LightningWallet) handleSingleContribution(req *addSingleContributionMsg
|
||||||
}
|
}
|
||||||
pendingReservation.partialState.FundingWitnessScript = witnessScript
|
pendingReservation.partialState.FundingWitnessScript = witnessScript
|
||||||
|
|
||||||
masterElkremRoot, err := l.deriveMasterElkremRoot()
|
masterElkremRoot, err := l.deriveMasterRevocationRoot()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
req.err <- err
|
req.err <- err
|
||||||
return
|
return
|
||||||
|
@ -910,22 +908,22 @@ func (l *LightningWallet) handleSingleContribution(req *addSingleContributionMsg
|
||||||
|
|
||||||
// Now that we know their commitment key, we can create the revocation
|
// Now that we know their commitment key, we can create the revocation
|
||||||
// key for our version of the initial commitment transaction.
|
// key for our version of the initial commitment transaction.
|
||||||
elkremRoot := deriveElkremRoot(masterElkremRoot, ourKey, theirKey)
|
root := deriveRevocationRoot(masterElkremRoot, ourKey, theirKey)
|
||||||
elkremSender := elkrem.NewElkremSender(elkremRoot)
|
producer := shachain.NewRevocationProducer(root)
|
||||||
firstPreimage, err := elkremSender.AtIndex(0)
|
firstPreimage, err := producer.AtIndex(0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
req.err <- err
|
req.err <- err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
pendingReservation.partialState.LocalElkrem = elkremSender
|
pendingReservation.partialState.RevocationProducer = producer
|
||||||
theirCommitKey := theirContribution.CommitKey
|
theirCommitKey := theirContribution.CommitKey
|
||||||
ourRevokeKey := DeriveRevocationPubkey(theirCommitKey, firstPreimage[:])
|
ourRevokeKey := DeriveRevocationPubkey(theirCommitKey, firstPreimage[:])
|
||||||
|
|
||||||
// Initialize an empty sha-chain for them, tracking the current pending
|
// 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
|
// revocation hash (we don't yet know the preimage so we can't add it
|
||||||
// to the chain).
|
// to the chain).
|
||||||
remoteElkrem := &elkrem.ElkremReceiver{}
|
remotePreimageStore := shachain.NewRevocationStore()
|
||||||
pendingReservation.partialState.RemoteElkrem = remoteElkrem
|
pendingReservation.partialState.RevocationStore = remotePreimageStore
|
||||||
|
|
||||||
// Record the counterpaty's remaining contributions to the channel,
|
// Record the counterpaty's remaining contributions to the channel,
|
||||||
// converting their delivery address into a public key script.
|
// converting their delivery address into a public key script.
|
||||||
|
@ -1379,11 +1377,11 @@ func (l *LightningWallet) selectCoinsAndChange(feeRate uint64, amt btcutil.Amoun
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// deriveMasterElkremRoot derives the private key which serves as the master
|
// deriveMasterRevocationRoot derives the private key which serves as the master
|
||||||
// elkrem root. This master secret is used as the secret input to a HKDF to
|
// producer root. This master secret is used as the secret input to a HKDF to
|
||||||
// generate elkrem secrets based on random, but public data.
|
// generate revocation secrets based on random, but public data.
|
||||||
func (l *LightningWallet) deriveMasterElkremRoot() (*btcec.PrivateKey, error) {
|
func (l *LightningWallet) deriveMasterRevocationRoot() (*btcec.PrivateKey, error) {
|
||||||
masterElkremRoot, err := l.rootKey.Child(elkremRootIndex)
|
masterElkremRoot, err := l.rootKey.Child(revocationRootIndex)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -1391,34 +1389,34 @@ func (l *LightningWallet) deriveMasterElkremRoot() (*btcec.PrivateKey, error) {
|
||||||
return masterElkremRoot.ECPrivKey()
|
return masterElkremRoot.ECPrivKey()
|
||||||
}
|
}
|
||||||
|
|
||||||
// deriveStateHintObsfucator derives the bytes to be used for obsfucatating the
|
// deriveStateHintObfuscator derives the bytes to be used for obfuscating the
|
||||||
// state hints from the elkerem root to be used for a new channel. The
|
// state hints from the root to be used for a new channel. The
|
||||||
// obsfucator is generated by performing an additional sha256 hash of the first
|
// obfuscator 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
|
// child derived from the revocation root. The leading 4 bytes are used for the
|
||||||
// obsfucator.
|
// obfuscator.
|
||||||
func deriveStateHintObsfucator(elkremRoot *elkrem.ElkremSender) ([StateHintSize]byte, error) {
|
func deriveStateHintObfuscator(producer shachain.Producer) ([StateHintSize]byte, error) {
|
||||||
var obsfucator [StateHintSize]byte
|
var obfuscator [StateHintSize]byte
|
||||||
|
|
||||||
firstChild, err := elkremRoot.AtIndex(0)
|
firstChild, err := producer.AtIndex(0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return obsfucator, err
|
return obfuscator, err
|
||||||
}
|
}
|
||||||
|
|
||||||
grandChild := fastsha256.Sum256(firstChild[:])
|
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
|
// initStateHints properly sets the obsfucated state hints on both commitment
|
||||||
// transactions using the passed obsfucator.
|
// transactions using the passed obsfucator.
|
||||||
func initStateHints(commit1, commit2 *wire.MsgTx,
|
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
|
return err
|
||||||
}
|
}
|
||||||
if err := SetStateNumHint(commit2, 0, obsfucator); err != nil {
|
if err := SetStateNumHint(commit2, 0, obfuscator); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1426,7 +1424,7 @@ func initStateHints(commit1, commit2 *wire.MsgTx,
|
||||||
}
|
}
|
||||||
|
|
||||||
// selectInputs selects a slice of inputs necessary to meet the specified
|
// 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
|
// 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
|
// selected coins are returned in order for the caller to properly handle
|
||||||
// change+fees.
|
// change+fees.
|
||||||
|
|
Loading…
Add table
Reference in a new issue