Skip to content

Commit

Permalink
Merge pull request #116 from libp2p/gui/genrandomkey
Browse files Browse the repository at this point in the history
GenRandomKey function
  • Loading branch information
guillaumemichel authored Mar 14, 2023
2 parents 539f2e5 + eedec94 commit 182a8e0
Show file tree
Hide file tree
Showing 2 changed files with 115 additions and 1 deletion.
36 changes: 35 additions & 1 deletion table_refresh.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,45 @@ func (rt *RoutingTable) GenRandPeerID(targetCpl uint) (peer.ID, error) {

// Convert to a known peer ID.
key := keyPrefixMap[targetPrefix]
id := [34]byte{mh.SHA2_256, 32}
id := [32 + 2]byte{mh.SHA2_256, 32}
binary.BigEndian.PutUint32(id[2:], key)
return peer.ID(id[:]), nil
}

// GenRandomKey generates a random key matching a provided Common Prefix Length (Cpl)
// wrt. the local identity. The returned key matches the targetCpl first bits of the
// local key, the following bit is the inverse of the local key's bit at position
// targetCpl+1 and the remaining bits are randomly generated.
func (rt *RoutingTable) GenRandomKey(targetCpl uint) (ID, error) {
if int(targetCpl+1) >= len(rt.local)*8 {
return nil, fmt.Errorf("cannot generate peer ID for Cpl greater than key length")
}
partialOffset := targetCpl / 8

// output contains the first partialOffset bytes of the local key
// and the remaining bytes are random
output := make([]byte, len(rt.local))
copy(output, rt.local[:partialOffset])
_, err := rand.Read(output[partialOffset:])
if err != nil {
return nil, err
}

remainingBits := 8 - targetCpl%8
orig := rt.local[partialOffset]

origMask := ^uint8(0) << remainingBits
randMask := ^origMask >> 1
flippedBitOffset := remainingBits - 1
flippedBitMask := uint8(1) << flippedBitOffset

// restore the remainingBits Most Significant Bits of orig
// and flip the flippedBitOffset-th bit of orig
output[partialOffset] = orig&origMask | (orig & flippedBitMask) ^ flippedBitMask | output[partialOffset]&randMask

return ID(output), nil
}

// ResetCplRefreshedAtForID resets the refresh time for the Cpl of the given ID.
func (rt *RoutingTable) ResetCplRefreshedAtForID(id ID, newTime time.Time) {
cpl := CommonPrefixLen(id, rt.local)
Expand Down
80 changes: 80 additions & 0 deletions table_refresh_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,86 @@ func TestGenRandPeerID(t *testing.T) {
}
}

func TestGenRandomKey(t *testing.T) {
// test can be run in parallel
t.Parallel()

// run multiple occurences to make sure the test wasn't just lucky
for i := 0; i < 100; i++ {
// generate routing table with random local peer ID
local := test.RandPeerIDFatal(t)
m := pstore.NewMetrics()
rt, err := NewRoutingTable(1, ConvertPeerID(local), time.Hour, m, NoOpThreshold, nil)
require.NoError(t, err)

// GenRandomKey fails for cpl >= 256
_, err = rt.GenRandomKey(256)
require.Error(t, err)
_, err = rt.GenRandomKey(300)
require.Error(t, err)

// bitwise comparison legend:
// O for same bit, X for different bit, ? for don't care

// we compare the returned generated key with the local key
// for CPL = X, the first X bits should be the same, bit X+1 should be
// different, and the rest should be random / don't care

// cpl = 0 should return a different first bit
// X??????? ???...
key0, err := rt.GenRandomKey(0)
require.NoError(t, err)
// most significant bit should be different
require.NotEqual(t, key0[0]>>7, rt.local[0]>>7)

// cpl = 1 should return a different second bit
// OX?????? ???...
key1, err := rt.GenRandomKey(1)
require.NoError(t, err)
// MSB should be equal, as cpl = 1
require.Equal(t, key1[0]>>7, rt.local[0]>>7)
// 2nd MSB should be different
require.NotEqual(t, (key1[0]<<1)>>6, (rt.local[0]<<1)>>6)

// cpl = 2 should return a different third bit
// OOX????? ???...
key2, err := rt.GenRandomKey(2)
require.NoError(t, err)
// 2 MSB should be equal, as cpl = 2
require.Equal(t, key2[0]>>6, rt.local[0]>>6)
// 3rd MSB should be different
require.NotEqual(t, (key2[0]<<2)>>5, (rt.local[0]<<2)>>5)

// cpl = 7 should return a different eighth bit
// OOOOOOOX ???...
key7, err := rt.GenRandomKey(7)
require.NoError(t, err)
// 7 MSB should be equal, as cpl = 7
require.Equal(t, key7[0]>>1, rt.local[0]>>1)
// 8th MSB should be different
require.NotEqual(t, key7[0]<<7, rt.local[0]<<7)

// cpl = 8 should return a different ninth bit
// OOOOOOOO X???...
key8, err := rt.GenRandomKey(8)
require.NoError(t, err)
// 8 MSB should be equal, as cpl = 8
require.Equal(t, key8[0], rt.local[0])
// 9th MSB should be different
require.NotEqual(t, key8[1]>>7, rt.local[1]>>7)

// cpl = 53 should return a different 54th bit
// OOOOOOOO OOOOOOOO OOOOOOOO OOOOOOOO OOOOOOOO OOOOOOOO OOOOOX?? ???...
key53, err := rt.GenRandomKey(53)
require.NoError(t, err)
// 53 MSB should be equal, as cpl = 53
require.Equal(t, key53[:6], rt.local[:6])
require.Equal(t, key53[6]>>3, rt.local[6]>>3)
// 54th MSB should be different
require.NotEqual(t, (key53[6]<<5)>>7, (rt.local[6]<<5)>>7)
}
}

func TestRefreshAndGetTrackedCpls(t *testing.T) {
t.Parallel()

Expand Down

0 comments on commit 182a8e0

Please sign in to comment.