Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

rules+session_rpc: use existing privacy mapper for obfuscating rules of linked sessions #637

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions firewall/privacy_mapper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -555,6 +555,12 @@ func (m *mockPrivacyMapDB) RealToPseudo(real string) (string, error) {
return p, nil
}

func (m *mockPrivacyMapDB) FetchAllPairs() (*firewalldb.PrivacyMapPairs,
error) {

return firewalldb.NewPrivacyMapPairs(m.r2p), nil
}

var _ firewalldb.PrivacyMapDB = (*mockPrivacyMapDB)(nil)

// TestRandBetween tests random number generation for numbers in an interval.
Expand Down
123 changes: 123 additions & 0 deletions firewalldb/privacy_mapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"math/big"
"strconv"
"strings"
"sync"

"github.com/lightninglabs/lightning-terminal/session"
"go.etcd.io/bbolt"
Expand Down Expand Up @@ -78,6 +79,10 @@ type PrivacyMapTx interface {
// RealToPseudo returns the pseudo value associated with the given real
// value. If no such pair is found, then ErrNoSuchKeyFound is returned.
RealToPseudo(real string) (string, error)

// FetchAllPairs loads and returns the real-to-pseudo pairs in the form
// of a PrivacyMapPairs struct.
FetchAllPairs() (*PrivacyMapPairs, error)
}

// privacyMapDB is an implementation of PrivacyMapDB.
Expand Down Expand Up @@ -169,6 +174,8 @@ type privacyMapTx struct {
}

// NewPair inserts a new real-pseudo pair into the db.
//
// NOTE: this is part of the PrivacyMapTx interface.
func (p *privacyMapTx) NewPair(real, pseudo string) error {
privacyBucket, err := getBucket(p.boltTx, privacyBucketKey)
if err != nil {
Expand All @@ -194,6 +201,16 @@ func (p *privacyMapTx) NewPair(real, pseudo string) error {
return err
}

if len(realToPseudoBucket.Get([]byte(real))) != 0 {
return fmt.Errorf("an entry already exists for real "+
"value: %x", real)
}

if len(pseudoToRealBucket.Get([]byte(pseudo))) != 0 {
return fmt.Errorf("an entry already exists for pseudo "+
"value: %x", pseudo)
}

err = realToPseudoBucket.Put([]byte(real), []byte(pseudo))
if err != nil {
return err
Expand All @@ -204,6 +221,8 @@ func (p *privacyMapTx) NewPair(real, pseudo string) error {

// PseudoToReal will check the db to see if the given pseudo key exists. If
// it does then the real value is returned, else an error is returned.
//
// NOTE: this is part of the PrivacyMapTx interface.
func (p *privacyMapTx) PseudoToReal(pseudo string) (string, error) {
privacyBucket, err := getBucket(p.boltTx, privacyBucketKey)
if err != nil {
Expand All @@ -230,6 +249,8 @@ func (p *privacyMapTx) PseudoToReal(pseudo string) (string, error) {

// RealToPseudo will check the db to see if the given real key exists. If
// it does then the pseudo value is returned, else an error is returned.
//
// NOTE: this is part of the PrivacyMapTx interface.
func (p *privacyMapTx) RealToPseudo(real string) (string, error) {
privacyBucket, err := getBucket(p.boltTx, privacyBucketKey)
if err != nil {
Expand All @@ -254,6 +275,38 @@ func (p *privacyMapTx) RealToPseudo(real string) (string, error) {
return string(pseudo), nil
}

// FetchAllPairs loads and returns the real-to-pseudo pairs.
//
// NOTE: this is part of the PrivacyMapTx interface.
func (p *privacyMapTx) FetchAllPairs() (*PrivacyMapPairs, error) {
privacyBucket, err := getBucket(p.boltTx, privacyBucketKey)
if err != nil {
return nil, err
}

sessBucket := privacyBucket.Bucket(p.groupID[:])
if sessBucket == nil {
return nil, ErrNoSuchKeyFound
}

realToPseudoBucket := sessBucket.Bucket(realToPseudoKey)
if realToPseudoBucket == nil {
return nil, ErrNoSuchKeyFound
}

pairs := make(map[string]string)
err = realToPseudoBucket.ForEach(func(r, p []byte) error {
pairs[string(r)] = string(p)

return nil
})
if err != nil {
return nil, err
}

return NewPrivacyMapPairs(pairs), nil
}

func HideString(tx PrivacyMapTx, real string) (string, error) {
pseudo, err := tx.RealToPseudo(real)
if err != nil && err != ErrNoSuchKeyFound {
Expand Down Expand Up @@ -460,3 +513,73 @@ func decodeChannelPoint(cp string) (string, uint32, error) {

return parts[0], uint32(index), nil
}

// PrivacyMapReader is an interface that gives read access to a privacy map
// DB.
type PrivacyMapReader interface {
// GetPseudo returns the associated pseudo value for a given real value.
// If no such real value exists in the DB, then false is returned.
GetPseudo(real string) (string, bool)
}

// PrivacyMapPairs is an in memory implementation of the PrivacyMapReader.
type PrivacyMapPairs struct {
// pairs is a map from real to psuedo strings.
pairs map[string]string

mu sync.Mutex
}

// NewPrivacyMapPairs constructs a new PrivacyMapPairs struct. It may be
// initialised with either a nil map or a pre-defined real-to-pseudo strings
// map.
func NewPrivacyMapPairs(m map[string]string) *PrivacyMapPairs {
if m != nil {
return &PrivacyMapPairs{
pairs: m,
}
}

return &PrivacyMapPairs{
pairs: make(map[string]string),
}
}

// GetPseudo returns the associated pseudo value for a given real value. If no
// such real value exists in the DB, then false is returned.
//
// NOTE: this is part of the PrivacyMapReader interface.
func (p *PrivacyMapPairs) GetPseudo(real string) (string, bool) {
p.mu.Lock()
defer p.mu.Unlock()

pseudo, ok := p.pairs[real]

return pseudo, ok
}

// Add adds the passed set of real-to-pseudo pairs to the PrivacyMapPairs
// structure. It will throw an error if the new pairs conflict with any of the
// existing pairs.
ViktorTigerstrom marked this conversation as resolved.
Show resolved Hide resolved
func (p *PrivacyMapPairs) Add(pairs map[string]string) error {
p.mu.Lock()
defer p.mu.Unlock()

// Do a first pass to ensure that none of the new entries conflict with
// the existing entries. We do this so that we don't mutate the set of
// pairs before we know that the new set is valid.
for realStr, pseudoStr := range pairs {
ps, ok := p.pairs[realStr]
if ok && ps != pseudoStr {
return fmt.Errorf("cannot replace existing pseudo "+
"entry for real value: %s", realStr)
}
}

// In our second pass, we can add the new pairs to our set.
for realStr, pseudoStr := range pairs {
p.pairs[realStr] = pseudoStr
}

return nil
}
107 changes: 107 additions & 0 deletions firewalldb/privacy_mapper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@ func TestPrivacyMapStorage(t *testing.T) {
require.NoError(t, err)
require.Equal(t, "real", real)

pairs, err := tx.FetchAllPairs()
require.NoError(t, err)

require.EqualValues(t, pairs.pairs, map[string]string{
"real": "pseudo",
})

return nil
})

Expand All @@ -59,6 +66,106 @@ func TestPrivacyMapStorage(t *testing.T) {
require.NoError(t, err)
require.Equal(t, "real 2", real)

pairs, err := tx.FetchAllPairs()
require.NoError(t, err)

require.EqualValues(t, pairs.pairs, map[string]string{
"real 2": "pseudo 2",
})

return nil
})

pdb3 := db.PrivacyDB([4]byte{3, 3, 3, 3})

_ = pdb3.Update(func(tx PrivacyMapTx) error {
// Add a new pair.
err = tx.NewPair("real 1", "pseudo 1")
require.NoError(t, err)

// Try to add a new pair that has the same real value as the
// first pair. This should fail.
err = tx.NewPair("real 1", "pseudo 2")
require.ErrorContains(t, err, "an entry already exists for "+
"real value")

// Try to add a new pair that has the same pseudo value as the
// first pair. This should fail.
err = tx.NewPair("real 2", "pseudo 1")
require.ErrorContains(t, err, "an entry already exists for "+
"pseudo value")

// Add a few more pairs.
err = tx.NewPair("real 2", "pseudo 2")
require.NoError(t, err)

err = tx.NewPair("real 3", "pseudo 3")
require.NoError(t, err)

err = tx.NewPair("real 4", "pseudo 4")
require.NoError(t, err)

// Check that FetchAllPairs correctly returns all the pairs.
pairs, err := tx.FetchAllPairs()
require.NoError(t, err)

require.EqualValues(t, pairs.pairs, map[string]string{
"real 1": "pseudo 1",
"real 2": "pseudo 2",
"real 3": "pseudo 3",
"real 4": "pseudo 4",
})

// Do a few tests to ensure that the PrivacyMapPairs struct
// returned from FetchAllPairs also works as expected.
pseudo, ok := pairs.GetPseudo("real 1")
require.True(t, ok)
require.Equal(t, "pseudo 1", pseudo)

// Fetch a real value that is not present.
_, ok = pairs.GetPseudo("real 5")
require.False(t, ok)

// Try to add a conflicting pair.
err = pairs.Add(map[string]string{"real 2": "pseudo 10"})
require.ErrorContains(t, err, "cannot replace existing "+
"pseudo entry for real value")

// Add a new pair.
err = pairs.Add(map[string]string{"real 5": "pseudo 5"})
require.NoError(t, err)

pseudo, ok = pairs.GetPseudo("real 5")
require.True(t, ok)
require.Equal(t, "pseudo 5", pseudo)
ViktorTigerstrom marked this conversation as resolved.
Show resolved Hide resolved

// Finally, also test adding multiple new pairs with some
// overlapping with previously added pairs.
err = pairs.Add(map[string]string{
// Add some pairs that already exist.
"real 1": "pseudo 1",
"real 3": "pseudo 3",
// Add some new pairs.
"real 6": "pseudo 6",
"real 7": "pseudo 7",
})
require.NoError(t, err)

// Verify that all the expected pairs can be found.
for r, p := range map[string]string{
"real 1": "pseudo 1",
"real 2": "pseudo 2",
"real 3": "pseudo 3",
"real 4": "pseudo 4",
"real 5": "pseudo 5",
"real 6": "pseudo 6",
"real 7": "pseudo 7",
} {
pseudo, ok = pairs.GetPseudo(r)
require.True(t, ok)
require.Equal(t, p, pseudo)
}

return nil
})
}
Expand Down
4 changes: 3 additions & 1 deletion rules/chan_policy_bounds.go
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,8 @@ func (f *ChanPolicyBounds) PseudoToReal(_ firewalldb.PrivacyMapDB) (Values,
// that should be persisted. This is a no-op for the ChanPolicyBounds rule.
//
// NOTE: this is part of the Values interface.
func (f *ChanPolicyBounds) RealToPseudo() (Values, map[string]string, error) {
func (f *ChanPolicyBounds) RealToPseudo(_ firewalldb.PrivacyMapReader) (Values,
map[string]string, error) {

return f, nil, nil
}
11 changes: 8 additions & 3 deletions rules/channel_restrictions.go
Original file line number Diff line number Diff line change
Expand Up @@ -360,17 +360,22 @@ func (c *ChannelRestrict) PseudoToReal(db firewalldb.PrivacyMapDB) (Values,
}, nil
}

// RealToPseudo converts all the channel IDs into pseudo IDs.
// RealToPseudo converts all the real channel IDs into pseudo IDs. It returns a
// map of any new real to pseudo strings that should be persisted that it did
// not find in the given PrivacyMapReader.
//
// NOTE: this is part of the Values interface.
func (c *ChannelRestrict) RealToPseudo() (Values, map[string]string, error) {
func (c *ChannelRestrict) RealToPseudo(db firewalldb.PrivacyMapReader) (Values,
map[string]string, error) {

pseudoIDs := make([]uint64, len(c.DenyList))
privMapPairs := make(map[string]string)
for i, c := range c.DenyList {
// TODO(elle): check that this channel actually exists

chanID := firewalldb.Uint64ToStr(c)
if pseudo, ok := privMapPairs[chanID]; ok {
pseudo, ok := pseudoFromReal(db, privMapPairs, chanID)
if ok {
p, err := firewalldb.StrToUint64(pseudo)
if err != nil {
return nil, nil, err
Expand Down
Loading