├── .gitignore ├── LICENSE ├── README.md ├── bits.go ├── bits_test.go ├── cmd └── kad │ └── main.go ├── go.mod ├── go.sum ├── id.go ├── id_test.go ├── packet.go ├── protocol.go ├── table.go └── table_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Kenta Iwasaki 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kademlia 2 | 3 | S/Kademlia in Go. Heavy WIP. -------------------------------------------------------------------------------- /bits.go: -------------------------------------------------------------------------------- 1 | package kademlia 2 | 3 | import ( 4 | "bytes" 5 | "github.com/lithdew/bytesutil" 6 | "math/bits" 7 | "sort" 8 | ) 9 | 10 | func leadingZeros(buf []byte) (count int) { 11 | for i := range buf { 12 | b := buf[i] 13 | if b != 0 { 14 | return i*8 + bits.LeadingZeros8(b) 15 | } 16 | } 17 | return len(buf) * 8 18 | } 19 | 20 | func xor(dst, a, b []byte) []byte { 21 | s := len(a) 22 | if sb := len(b); sb < s { 23 | s = sb 24 | } 25 | dst = bytesutil.ExtendSlice(dst, s) 26 | for i := 0; i < s; i++ { 27 | dst[i] = a[i] ^ b[i] 28 | } 29 | return dst 30 | } 31 | 32 | func SortIDs(pub PublicKey, ids []ID) []ID { 33 | dst := func(idx int) []byte { return xor(nil, ids[idx].Pub[:], pub[:]) } 34 | cmp := func(i, j int) bool { return bytes.Compare(dst(i), dst(j)) == -1 } 35 | sort.Slice(ids, cmp) 36 | return ids 37 | } 38 | -------------------------------------------------------------------------------- /bits_test.go: -------------------------------------------------------------------------------- 1 | package kademlia 2 | 3 | import ( 4 | "github.com/stretchr/testify/require" 5 | "testing" 6 | ) 7 | 8 | func TestLeadingZeros(t *testing.T) { 9 | tests := []struct { 10 | payload []byte 11 | expected int 12 | }{ 13 | {payload: []byte{0, 0, 0, 2}, expected: 30}, 14 | {payload: []byte{0, 0, 1, 0}, expected: 23}, 15 | } 16 | 17 | for _, test := range tests { 18 | require.EqualValues(t, test.expected, leadingZeros(test.payload)) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /cmd/kad/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/lithdew/kademlia" 5 | "github.com/lithdew/reliable" 6 | "log" 7 | "math" 8 | "net" 9 | "time" 10 | ) 11 | 12 | func check(err error) { 13 | if err != nil { 14 | log.Panic(err) 15 | } 16 | } 17 | 18 | func readLoop(pc net.PacketConn, c *reliable.Conn) { 19 | var ( 20 | n int 21 | addr net.Addr 22 | err error 23 | ) 24 | 25 | buf := make([]byte, math.MaxUint16+1) 26 | for { 27 | n, addr, err = pc.ReadFrom(buf) 28 | if err != nil { 29 | break 30 | } 31 | header, buf, err := reliable.UnmarshalPacketHeader(buf[:n]) 32 | if err == nil { 33 | err = c.Read(header, buf) 34 | } 35 | if err != nil { 36 | log.Printf("%s->%s: error occured (err=%q)", pc.LocalAddr(), addr, err) 37 | } 38 | } 39 | } 40 | 41 | func main() { 42 | log.SetFlags(0) 43 | 44 | pa, err := kademlia.NewProtocol() 45 | check(err) 46 | 47 | pb, err := kademlia.NewProtocol() 48 | check(err) 49 | 50 | ca, err := net.ListenPacket("udp", "127.0.0.1:0") 51 | check(err) 52 | 53 | cb, err := net.ListenPacket("udp", "127.0.0.1:0") 54 | check(err) 55 | 56 | pha := func(addr net.Addr, seq uint16, buf []byte) { 57 | check(pa.Read(buf, addr)) 58 | } 59 | 60 | phb := func(addr net.Addr, seq uint16, buf []byte) { 61 | check(pb.Read(buf, addr)) 62 | } 63 | 64 | a := reliable.NewConn(ca, cb.LocalAddr(), reliable.WithPacketHandler(pha)) 65 | b := reliable.NewConn(cb, ca.LocalAddr(), reliable.WithPacketHandler(phb)) 66 | 67 | go readLoop(ca, a) 68 | go readLoop(cb, b) 69 | 70 | go a.Run() 71 | go b.Run() 72 | 73 | log.Printf("A (addr=%q) (pub=%q) (priv=%q)", ca.LocalAddr(), pa.PublicKey(), pa.PrivateKey().Seed()) 74 | log.Printf("B (addr=%q) (pub=%q) (priv=%q)", cb.LocalAddr(), pb.PublicKey(), pb.PrivateKey().Seed()) 75 | 76 | defer func() { 77 | check(ca.SetDeadline(time.Now().Add(1 * time.Millisecond))) 78 | check(cb.SetDeadline(time.Now().Add(1 * time.Millisecond))) 79 | 80 | a.Close() 81 | b.Close() 82 | 83 | check(ca.Close()) 84 | check(cb.Close()) 85 | }() 86 | 87 | //pkt := kademlia.HandshakeRequest{ 88 | // Node: pa.PublicKey(), 89 | // Signature: pa.PrivateKey().Sign(append(kademlia.ZeroPublicKey[:], pa.hm...)), 90 | //} 91 | 92 | //check(a.WriteReliablePacket(pkt.AppendTo(nil))) 93 | 94 | time.Sleep(100 * time.Millisecond) 95 | } 96 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/lithdew/kademlia 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/davecgh/go-spew v1.1.1 7 | github.com/lithdew/bytesutil v0.0.0-20200409052507-d98389230a59 8 | github.com/lithdew/reliable v0.0.0-20200506103725-7df64908b057 9 | github.com/oasislabs/ed25519 v0.0.0-20200302143042-29f6767a7c3e 10 | github.com/stretchr/testify v1.5.1 11 | golang.org/x/crypto v0.0.0-20191119213627-4f8c1d86b1ba 12 | ) 13 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 5 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 6 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 7 | github.com/lithdew/bytesutil v0.0.0-20200409052507-d98389230a59 h1:CQpoOQecHxhvgOU/ijue/yWuShZYDtNpI9bsD4Dkzrk= 8 | github.com/lithdew/bytesutil v0.0.0-20200409052507-d98389230a59/go.mod h1:89JlULMIJ/+YWzAp5aHXgAD2d02S2mY+a+PMgXDtoNs= 9 | github.com/lithdew/reliable v0.0.0-20200506103725-7df64908b057 h1:CBhKVPym/7ZzY7ascOG93XSTlJuqrmIU/Hd0UDHC1TA= 10 | github.com/lithdew/reliable v0.0.0-20200506103725-7df64908b057/go.mod h1:b9iSDHZ4DaCGpwhQdKsH0u61UancBXJMe0r8SCPKEEA= 11 | github.com/lithdew/seq v0.0.0-20200504083424-74d5d8117a05 h1:j1UtG8NYCupA5xUwQ/vrTf/zjuNlZ0D1n7UtM8LhS58= 12 | github.com/lithdew/seq v0.0.0-20200504083424-74d5d8117a05/go.mod h1:4vVgbfmYc+ZIh0dy99HRrM6knnAtQXNI8MOx+1pUYso= 13 | github.com/oasislabs/ed25519 v0.0.0-20200302143042-29f6767a7c3e h1:85L+lUTJHx4O7UP9y/65XV8iq7oaA2Uqe5WiUSB8XE4= 14 | github.com/oasislabs/ed25519 v0.0.0-20200302143042-29f6767a7c3e/go.mod h1:xIpCyrK2ouGA4QBGbiNbkoONrvJ00u9P3QOkXSOAC0c= 15 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 16 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 17 | github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= 18 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 19 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 20 | github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= 21 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 22 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 23 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 24 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 25 | go.uber.org/goleak v1.0.0/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= 26 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 27 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 28 | golang.org/x/crypto v0.0.0-20191119213627-4f8c1d86b1ba h1:9bFeDpN3gTqNanMVqNcoR/pJQuP5uroC3t1D7eXozTE= 29 | golang.org/x/crypto v0.0.0-20191119213627-4f8c1d86b1ba/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 30 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 31 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 32 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 33 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 34 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 35 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 36 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 37 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 38 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 39 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 40 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 41 | golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= 42 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 43 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 44 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 45 | golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 46 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 47 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 48 | golang.org/x/tools v0.0.0-20200501005904-d351ea090f9b/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 49 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 50 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 51 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 52 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 53 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 54 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= 55 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 56 | -------------------------------------------------------------------------------- /id.go: -------------------------------------------------------------------------------- 1 | package kademlia 2 | 3 | import ( 4 | "crypto" 5 | "encoding/hex" 6 | "errors" 7 | "fmt" 8 | "github.com/lithdew/bytesutil" 9 | "github.com/oasislabs/ed25519" 10 | "golang.org/x/crypto/blake2b" 11 | "io" 12 | "net" 13 | "unsafe" 14 | ) 15 | 16 | const ( 17 | SizePublicKey = ed25519.PublicKeySize 18 | SizePrivateKey = ed25519.PrivateKeySize 19 | SizeSeed = ed25519.SeedSize 20 | SizeSignature = ed25519.SignatureSize 21 | 22 | SizeNodeID = 32 23 | SizeX = SizeNodeID 24 | 25 | MinSizeID = SizePublicKey + 1 + net.IPv4len + 2 26 | MaxSizeID = SizePublicKey + 1 + net.IPv6len + 2 27 | ) 28 | 29 | type ( 30 | PublicKey [SizePublicKey]byte 31 | PrivateKey [SizePrivateKey]byte 32 | Seed [SizeSeed]byte 33 | Signature [SizeSignature]byte 34 | 35 | NodeID [SizeNodeID]byte 36 | X [SizeX]byte 37 | ) 38 | 39 | var ( 40 | ZeroPublicKey PublicKey 41 | ZeroPrivateKey PrivateKey 42 | ZeroSeed Seed 43 | ZeroSignature Signature 44 | 45 | ZeroNodeID NodeID 46 | ZeroX X 47 | 48 | ZeroID ID 49 | ) 50 | 51 | type ID struct { 52 | Pub PublicKey `json:"public_key"` 53 | Host net.IP `json:"host"` 54 | Port uint16 `json:"port"` 55 | } 56 | 57 | func (h ID) Validate() error { 58 | if len(h.Host) != net.IPv4len && len(h.Host) != net.IPv6len { 59 | return fmt.Errorf("node host is not valid ipv4 or ipv6: host ip is %d byte(s)", len(h.Host)) 60 | } 61 | if h.Port == 0 { 62 | return errors.New("node port cannot be 0") 63 | } 64 | return nil 65 | } 66 | 67 | func (id ID) AppendTo(dst []byte) []byte { 68 | dst = append(dst, id.Pub[:]...) 69 | if len(id.Host) == 0 || len(id.Host) == net.IPv4len { 70 | dst = append(dst, 0) 71 | } else { 72 | dst = append(dst, 1) 73 | } 74 | if len(id.Host) == 0 { 75 | id.Host = make(net.IP, net.IPv4len) 76 | } 77 | dst = append(dst, id.Host...) 78 | dst = bytesutil.AppendUint16BE(dst, id.Port) 79 | return dst 80 | } 81 | 82 | func UnmarshalID(buf []byte) (ID, []byte, error) { 83 | var id ID 84 | if len(buf) < MinSizeID { 85 | return id, buf, io.ErrUnexpectedEOF 86 | } 87 | id.Pub, buf = *(*PublicKey)(unsafe.Pointer(&((buf[:SizePublicKey])[0]))), buf[SizePublicKey:] 88 | ipv4, buf := buf[0] == 0, buf[1:] 89 | if ipv4 && len(buf) >= net.IPv4len+2 { 90 | id.Host = make([]byte, net.IPv4len) 91 | copy(id.Host, buf[:net.IPv4len]) 92 | buf = buf[net.IPv4len:] 93 | } else if !ipv4 && len(buf) >= net.IPv6len+2 { 94 | id.Host = make([]byte, net.IPv6len) 95 | copy(id.Host, buf[:net.IPv6len]) 96 | buf = buf[net.IPv6len:] 97 | } else { 98 | return id, buf, fmt.Errorf("host is malformed: %w", io.ErrUnexpectedEOF) 99 | } 100 | id.Port, buf = bytesutil.Uint16BE(buf[:2]), buf[2:] 101 | return id, buf, nil 102 | } 103 | 104 | // GeneratePuzzleKeys takes O(2^c1). 105 | func GeneratePuzzleKeys(r io.Reader, c1 int) (pub PublicKey, priv PrivateKey, err error) { 106 | for { 107 | pub, priv, err = GenerateKeys(r) 108 | if err != nil { 109 | return pub, priv, fmt.Errorf("failed to generate keys in static puzzle: %w", err) 110 | } 111 | if pub.NodeID().Valid(c1) { 112 | break 113 | } 114 | } 115 | return pub, priv, err 116 | } 117 | 118 | func GenerateKeys(r io.Reader) (publicKey PublicKey, privateKey PrivateKey, err error) { 119 | pub, priv, err := ed25519.GenerateKey(r) 120 | if err != nil { 121 | return publicKey, privateKey, err 122 | } 123 | publicKey = *(*PublicKey)(unsafe.Pointer(&pub[0])) 124 | privateKey = *(*PrivateKey)(unsafe.Pointer(&priv[0])) 125 | return publicKey, privateKey, err 126 | } 127 | 128 | func (p PrivateKey) String() string { return hex.EncodeToString(p[:]) } 129 | func (p PrivateKey) Zero() bool { return p == ZeroPrivateKey } 130 | func (p PrivateKey) Seed() Seed { return *(*Seed)(unsafe.Pointer(&((p[:SizeSeed])[0]))) } 131 | func (p PrivateKey) Public() PublicKey { return *(*PublicKey)(unsafe.Pointer(&((p[SizeSeed:])[0]))) } 132 | 133 | func (p PrivateKey) Sign(buf []byte) Signature { 134 | signature, _ := (ed25519.PrivateKey)(p[:]).Sign(nil, buf, crypto.Hash(0)) 135 | return *(*Signature)(unsafe.Pointer(&signature[0])) 136 | } 137 | 138 | func (s Seed) String() string { return hex.EncodeToString(s[:]) } 139 | func (s Seed) Zero() bool { return s == ZeroSeed } 140 | 141 | func (s Signature) String() string { return hex.EncodeToString(s[:]) } 142 | func (s Signature) Zero() bool { return s == ZeroSignature } 143 | 144 | func (s Signature) Verify(pub PublicKey, msg []byte) bool { 145 | return !s.Zero() && ed25519.Verify(pub[:], msg, s[:]) 146 | } 147 | 148 | func (p PublicKey) String() string { return hex.EncodeToString(p[:]) } 149 | func (p PublicKey) Zero() bool { return p == ZeroPublicKey } 150 | func (p PublicKey) NodeID() NodeID { return blake2b.Sum256(p[:]) } 151 | 152 | func (p PublicKey) Verify(msg []byte, s Signature) bool { 153 | return !s.Zero() && ed25519.Verify(p[:], msg, s[:]) 154 | } 155 | 156 | func (id NodeID) String() string { return hex.EncodeToString(id[:]) } 157 | func (id NodeID) Zero() bool { return id == ZeroNodeID } 158 | func (id NodeID) Valid(c1 int) bool { p := blake2b.Sum256(id[:]); return leadingZeros(p[:]) >= c1 } 159 | 160 | // GenerateX takes O(2^c2). 161 | func (id NodeID) GenerateX(r io.Reader, c2 int) (x X, err error) { 162 | for { 163 | _, err = io.ReadFull(r, x[:]) 164 | if err != nil { 165 | return x, fmt.Errorf("failed to generate 'x' in dynamic puzzle: %w", err) 166 | } 167 | if x.Valid(id, c2) { 168 | break 169 | } 170 | } 171 | return x, err 172 | } 173 | 174 | func (x X) String() string { return hex.EncodeToString(x[:]) } 175 | func (x X) Zero() bool { return x == ZeroX } 176 | func (x X) Valid(id NodeID, c2 int) bool { return leadingZeros(xor(nil, id[:], x[:])) >= c2 } 177 | -------------------------------------------------------------------------------- /id_test.go: -------------------------------------------------------------------------------- 1 | package kademlia 2 | 3 | import ( 4 | "github.com/davecgh/go-spew/spew" 5 | "github.com/stretchr/testify/assert" 6 | "github.com/stretchr/testify/require" 7 | "math/rand" 8 | "net" 9 | "testing" 10 | "testing/quick" 11 | ) 12 | 13 | func TestGeneratePuzzleKeys(t *testing.T) { 14 | pub, priv, err := GeneratePuzzleKeys(nil, 10) 15 | require.NoError(t, err) 16 | spew.Dump(pub, priv) 17 | } 18 | 19 | func TestHandshakePacket(t *testing.T) { 20 | var buf []byte 21 | 22 | f := func(expected HandshakeRequest) bool { 23 | actual, buf, err := UnmarshalHandshakeRequest(expected.AppendTo(buf[:0])) 24 | return assert.EqualValues(t, expected, actual) && assert.Len(t, buf, 0) && assert.NoError(t, err) 25 | } 26 | 27 | require.NoError(t, quick.Check(f, nil)) 28 | } 29 | 30 | func TestID(t *testing.T) { 31 | var buf []byte 32 | 33 | f := func(expected ID) bool { 34 | if rand.Intn(1) == 0 { 35 | expected.Host = make([]byte, net.IPv4len) 36 | } else { 37 | expected.Host = make([]byte, net.IPv6len) 38 | } 39 | actual, _, err := UnmarshalID(expected.AppendTo(buf[:0])) 40 | return assert.EqualValues(t, expected, actual) && assert.NoError(t, err) 41 | } 42 | 43 | require.NoError(t, quick.Check(f, nil)) 44 | } 45 | -------------------------------------------------------------------------------- /packet.go: -------------------------------------------------------------------------------- 1 | package kademlia 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "unsafe" 7 | ) 8 | 9 | const ( 10 | SizeHandshakeRequest = 2*SizePublicKey + SizeSignature 11 | SizeFindNodeRequest = SizePublicKey 12 | ) 13 | 14 | type HandshakeRequest struct { 15 | PublicKey PublicKey `json:"public_key"` 16 | SessionKey PublicKey `json:"session_key"` 17 | Signature Signature `json:"signature"` 18 | } 19 | 20 | func (p HandshakeRequest) AppendTo(dst []byte) []byte { 21 | dst = append(dst, p.PublicKey[:]...) 22 | dst = append(dst, p.SessionKey[:]...) 23 | dst = append(dst, p.Signature[:]...) 24 | return dst 25 | } 26 | 27 | func UnmarshalHandshakeRequest(buf []byte) (HandshakeRequest, []byte, error) { 28 | var packet HandshakeRequest 29 | if len(buf) < SizeHandshakeRequest { 30 | return packet, buf, io.ErrUnexpectedEOF 31 | } 32 | packet.PublicKey, buf = *(*PublicKey)(unsafe.Pointer(&((buf[:SizePublicKey])[0]))), 33 | buf[SizePublicKey:] 34 | packet.SessionKey, buf = *(*PublicKey)(unsafe.Pointer(&((buf[:SizePublicKey])[0]))), 35 | buf[SizePublicKey:] 36 | packet.Signature, buf = *(*Signature)(unsafe.Pointer(&((buf[:SizeSignature])[0]))), 37 | buf[SizeSignature:] 38 | return packet, buf, nil 39 | } 40 | 41 | type FindNodeRequest struct { 42 | Target PublicKey `json:"public_key"` 43 | } 44 | 45 | func (r FindNodeRequest) AppendTo(dst []byte) []byte { return append(dst, r.Target[:]...) } 46 | 47 | func UnmarshalFindNodeRequest(buf []byte) (FindNodeRequest, []byte, error) { 48 | var packet FindNodeRequest 49 | if len(buf) < SizeFindNodeRequest { 50 | return packet, buf, io.ErrUnexpectedEOF 51 | } 52 | packet.Target, buf = *(*PublicKey)(unsafe.Pointer(&((buf[:SizePublicKey])[0]))), 53 | buf[SizePublicKey:] 54 | return packet, buf, nil 55 | } 56 | 57 | type FindNodeResponse struct { 58 | Closest []ID `json:"closest"` 59 | } 60 | 61 | func (r FindNodeResponse) AppendTo(dst []byte) []byte { 62 | dst = append(dst, byte(len(r.Closest))) 63 | for _, id := range r.Closest { 64 | dst = id.AppendTo(dst) 65 | } 66 | return dst 67 | } 68 | 69 | func UnmarshalFindNodeResponse(buf []byte) (FindNodeResponse, []byte, error) { 70 | var packet FindNodeResponse 71 | if len(buf) < 1 { 72 | return packet, buf, io.ErrUnexpectedEOF 73 | } 74 | 75 | packet.Closest, buf = make([]ID, 0, buf[0]), buf[1:] 76 | 77 | var ( 78 | id ID 79 | err error 80 | ) 81 | 82 | for i := 0; i < cap(packet.Closest); i++ { 83 | id, buf, err = UnmarshalID(buf) 84 | if err != nil { 85 | return packet, buf, fmt.Errorf("failed to decode id: %w", err) 86 | } 87 | packet.Closest = append(packet.Closest, id) 88 | } 89 | 90 | return packet, buf, nil 91 | } 92 | -------------------------------------------------------------------------------- /protocol.go: -------------------------------------------------------------------------------- 1 | package kademlia 2 | 3 | import ( 4 | "fmt" 5 | "github.com/davecgh/go-spew/spew" 6 | "net" 7 | ) 8 | 9 | const ( 10 | DefaultC1 = 10 11 | DefaultC2 = 10 12 | ) 13 | 14 | type Protocol struct { 15 | c1 int // s/kademlia static puzzle c1 16 | c2 int // s/kademlia dynamic puzzle c2 17 | 18 | pub PublicKey // node public key 19 | priv PrivateKey // node private key 20 | 21 | id ID // node id 22 | table *Table // node routing table 23 | 24 | setup bool // have we already handshaked with this peer? 25 | } 26 | 27 | func (p Protocol) PublicKey() PublicKey { return p.pub } 28 | func (p Protocol) PrivateKey() PrivateKey { return p.priv } 29 | 30 | func NewProtocol() (*Protocol, error) { 31 | p := &Protocol{} 32 | if p.c1 == 0 { 33 | p.c1 = DefaultC1 34 | } 35 | if p.c2 == 0 { 36 | p.c2 = DefaultC2 37 | } 38 | if !p.priv.Zero() && p.pub.Zero() { 39 | p.pub = p.priv.Public() 40 | } 41 | if p.priv.Zero() { 42 | pub, priv, err := GeneratePuzzleKeys(nil, p.c1) 43 | if err != nil { 44 | return nil, err 45 | } 46 | p.pub = pub 47 | p.priv = priv 48 | } 49 | p.id = ID{Pub: p.pub} 50 | p.table = NewTable(p.pub) 51 | return p, nil 52 | } 53 | 54 | func (p *Protocol) Read(buf []byte, addr net.Addr) error { 55 | if !p.setup { 56 | packet, _, err := UnmarshalHandshakeRequest(buf) 57 | if err != nil { 58 | return err 59 | } 60 | return p.Handshake(packet, addr) 61 | } 62 | 63 | return nil 64 | } 65 | 66 | func (p *Protocol) Handshake(packet HandshakeRequest, addr net.Addr) error { 67 | spew.Dump(packet) 68 | if !packet.Signature.Verify(packet.PublicKey, packet.SessionKey[:]) { 69 | return fmt.Errorf("%s: invalid signature on handshake packet", addr) 70 | } 71 | p.setup = true 72 | return nil 73 | } 74 | -------------------------------------------------------------------------------- /table.go: -------------------------------------------------------------------------------- 1 | package kademlia 2 | 3 | import "container/list" 4 | 5 | const ( 6 | SizeTable = SizePublicKey * 8 7 | DefaultBucketSize = 16 8 | ) 9 | 10 | type Table struct { 11 | pub PublicKey 12 | cap int // capacity of peer id bucket 13 | len int // number of peer ids stored 14 | 15 | buckets [SizeTable]*list.List // peer id buckets 16 | } 17 | 18 | func NewTable(id PublicKey) *Table { 19 | t := &Table{pub: id} 20 | for i := range t.buckets { 21 | t.buckets[i] = list.New() 22 | } 23 | if t.cap == 0 { 24 | t.cap = DefaultBucketSize 25 | } 26 | return t 27 | } 28 | 29 | func (t Table) bucketIndex(pub PublicKey) int { 30 | if pub == t.pub { 31 | return 0 32 | } 33 | return leadingZeros(xor(nil, pub[:], t.pub[:])) 34 | } 35 | 36 | type UpdateResult int 37 | 38 | const ( 39 | UpdateNew UpdateResult = iota 40 | UpdateOk 41 | UpdateFull 42 | UpdateFail 43 | ) 44 | 45 | // O(bucket_size) complexity. 46 | func (t *Table) Update(id ID) UpdateResult { 47 | if t.pub == id.Pub { 48 | return UpdateFail 49 | } 50 | bucket := t.buckets[t.bucketIndex(id.Pub)] 51 | for e := bucket.Front(); e != nil; e = e.Next() { 52 | if e.Value.(ID).Pub == id.Pub { 53 | bucket.MoveToFront(e) 54 | return UpdateOk 55 | } 56 | } 57 | if bucket.Len() < t.cap { 58 | bucket.PushFront(id) 59 | t.len++ 60 | return UpdateNew 61 | } 62 | return UpdateFull 63 | } 64 | 65 | // O(bucket_size) complexity. 66 | func (t *Table) Delete(pub PublicKey) bool { 67 | bucket := t.buckets[t.bucketIndex(pub)] 68 | for e := bucket.Front(); e != nil; e = e.Next() { 69 | if e.Value.(ID).Pub == pub { 70 | bucket.Remove(e) 71 | t.len-- 72 | return true 73 | } 74 | } 75 | return false 76 | } 77 | 78 | // Len returns the number of entries in this table. 79 | func (t Table) Len() int { 80 | return t.len 81 | } 82 | 83 | // Has returns true if this table has pub. 84 | func (t Table) Has(pub PublicKey) bool { 85 | bucket := t.buckets[t.bucketIndex(pub)] 86 | for e := bucket.Front(); e != nil; e = e.Next() { 87 | if e.Value.(ID).Pub == pub { 88 | return true 89 | } 90 | } 91 | return false 92 | } 93 | 94 | // O(min(k, bucket_size * num_buckets)) complexity. 95 | func (t Table) ClosestTo(pub PublicKey, k int) []ID { 96 | if k > t.len { 97 | k = t.len 98 | } 99 | 100 | closest := make([]ID, 0, k) 101 | 102 | fill := func(i int) { 103 | for e := t.buckets[i].Front(); e != nil; e = e.Next() { 104 | if id := e.Value.(ID); id.Pub != pub { 105 | closest = append(closest, id) 106 | } 107 | } 108 | } 109 | 110 | m := t.bucketIndex(pub) 111 | 112 | fill(m) 113 | 114 | for i := 1; len(closest) < k && (m-i >= 0 || m+i < len(t.buckets)); i++ { 115 | if m-i >= 0 { 116 | fill(m - i) 117 | } 118 | if m+i < len(t.buckets) { 119 | fill(m + i) 120 | } 121 | } 122 | 123 | closest = SortIDs(t.pub, closest) 124 | 125 | if len(closest) > k { 126 | closest = closest[:k] 127 | } 128 | 129 | return closest 130 | } 131 | -------------------------------------------------------------------------------- /table_test.go: -------------------------------------------------------------------------------- 1 | package kademlia 2 | 3 | import ( 4 | "github.com/stretchr/testify/require" 5 | "testing" 6 | ) 7 | 8 | func genKeys(t testing.TB) (PublicKey, PrivateKey) { 9 | t.Helper() 10 | pub, sec, err := GenerateKeys(nil) 11 | require.NoError(t, err) 12 | return pub, sec 13 | } 14 | 15 | func genPub(t testing.TB) PublicKey { 16 | t.Helper() 17 | pub, _ := genKeys(t) 18 | return pub 19 | } 20 | 21 | func genBucketPub(t testing.TB, table *Table, idx int) PublicKey { 22 | t.Helper() 23 | for { 24 | pub := genPub(t) 25 | if table.bucketIndex(pub) != idx { 26 | continue 27 | } 28 | return pub 29 | } 30 | } 31 | 32 | func TestTable(t *testing.T) { 33 | table := NewTable(genPub(t)) 34 | 35 | for bucket := 0; bucket < 2; bucket++ { 36 | ids := make([]ID, 0, table.cap) 37 | for i := 0; i < table.cap; i++ { 38 | id := ID{Pub: genBucketPub(t, table, bucket)} 39 | ids = append(ids, id) 40 | 41 | require.EqualValues(t, UpdateNew, table.Update(id)) 42 | } 43 | 44 | require.EqualValues(t, table.cap, table.buckets[bucket].Len()) 45 | for _, id := range ids { 46 | require.True(t, table.Has(id.Pub)) 47 | } 48 | 49 | require.ElementsMatch(t, ids, table.ClosestTo(table.pub, table.cap)) 50 | 51 | for _, id := range ids { 52 | require.True(t, table.Delete(id.Pub)) 53 | } 54 | require.EqualValues(t, 0, table.buckets[bucket].Len()) 55 | } 56 | } 57 | --------------------------------------------------------------------------------