├── .github └── workflows │ ├── generated-pr.yml │ ├── go-check.yml │ ├── go-test.yml │ ├── release-check.yml │ ├── releaser.yml │ ├── stale.yml │ └── tagpush.yml ├── LICENSE ├── README.md ├── codecov.yml ├── go.mod ├── go.sum ├── pb ├── gen.go ├── record.pb.go └── record.proto ├── pubkey.go ├── record.go ├── util.go ├── validator.go ├── validator_test.go └── version.json /.github/workflows/generated-pr.yml: -------------------------------------------------------------------------------- 1 | name: Close Generated PRs 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' 6 | workflow_dispatch: 7 | 8 | permissions: 9 | issues: write 10 | pull-requests: write 11 | 12 | jobs: 13 | stale: 14 | uses: ipdxco/unified-github-workflows/.github/workflows/reusable-generated-pr.yml@v1 15 | -------------------------------------------------------------------------------- /.github/workflows/go-check.yml: -------------------------------------------------------------------------------- 1 | name: Go Checks 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: ["master"] 7 | workflow_dispatch: 8 | 9 | permissions: 10 | contents: read 11 | 12 | concurrency: 13 | group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event_name == 'push' && github.sha || github.ref }} 14 | cancel-in-progress: true 15 | 16 | jobs: 17 | go-check: 18 | uses: ipdxco/unified-github-workflows/.github/workflows/go-check.yml@v1.0 19 | -------------------------------------------------------------------------------- /.github/workflows/go-test.yml: -------------------------------------------------------------------------------- 1 | name: Go Test 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: ["master"] 7 | workflow_dispatch: 8 | 9 | permissions: 10 | contents: read 11 | 12 | concurrency: 13 | group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event_name == 'push' && github.sha || github.ref }} 14 | cancel-in-progress: true 15 | 16 | jobs: 17 | go-test: 18 | uses: ipdxco/unified-github-workflows/.github/workflows/go-test.yml@v1.0 19 | secrets: 20 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 21 | -------------------------------------------------------------------------------- /.github/workflows/release-check.yml: -------------------------------------------------------------------------------- 1 | name: Release Checker 2 | 3 | on: 4 | pull_request_target: 5 | paths: [ 'version.json' ] 6 | types: [ opened, synchronize, reopened, labeled, unlabeled ] 7 | workflow_dispatch: 8 | 9 | permissions: 10 | contents: write 11 | pull-requests: write 12 | 13 | concurrency: 14 | group: ${{ github.workflow }}-${{ github.ref }} 15 | cancel-in-progress: true 16 | 17 | jobs: 18 | release-check: 19 | uses: ipdxco/unified-github-workflows/.github/workflows/release-check.yml@v1.0 20 | -------------------------------------------------------------------------------- /.github/workflows/releaser.yml: -------------------------------------------------------------------------------- 1 | name: Releaser 2 | 3 | on: 4 | push: 5 | paths: [ 'version.json' ] 6 | workflow_dispatch: 7 | 8 | permissions: 9 | contents: write 10 | 11 | concurrency: 12 | group: ${{ github.workflow }}-${{ github.sha }} 13 | cancel-in-progress: true 14 | 15 | jobs: 16 | releaser: 17 | uses: ipdxco/unified-github-workflows/.github/workflows/releaser.yml@v1.0 18 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: Close Stale Issues 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' 6 | workflow_dispatch: 7 | 8 | permissions: 9 | issues: write 10 | pull-requests: write 11 | 12 | jobs: 13 | stale: 14 | uses: ipdxco/unified-github-workflows/.github/workflows/reusable-stale-issue.yml@v1 15 | -------------------------------------------------------------------------------- /.github/workflows/tagpush.yml: -------------------------------------------------------------------------------- 1 | name: Tag Push Checker 2 | 3 | on: 4 | push: 5 | tags: 6 | - v* 7 | 8 | permissions: 9 | contents: read 10 | issues: write 11 | 12 | concurrency: 13 | group: ${{ github.workflow }}-${{ github.ref }} 14 | cancel-in-progress: true 15 | 16 | jobs: 17 | releaser: 18 | uses: ipdxco/unified-github-workflows/.github/workflows/tagpush.yml@v1.0 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 libp2p 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-libp2p-record 2 | 3 | [![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](https://protocol.ai) 4 | [![](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](https://libp2p.io/) 5 | [![](https://img.shields.io/badge/freenode-%23libp2p-yellow.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23libp2p) 6 | [![Discourse posts](https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg)](https://discuss.libp2p.io) 7 | 8 | > signed records for use with routing systems 9 | 10 | ## Documentation 11 | 12 | See https://godoc.org/github.com/libp2p/go-libp2p-record. 13 | 14 | ## Testing 15 | 16 | This package has some tests that rely on generating weak RSA keys (for speed). 17 | In order to successfully run the tests, one must set the environment variable, 18 | `LIBP2P_ALLOW_WEAK_RSA_KEYS` to any non-empty value, such as `1`. 19 | 20 | ## Contribute 21 | 22 | Feel free to join in. All welcome. Open an [issue](https://github.com/ipfs/go-key/issues)! 23 | 24 | This repository falls under the libp2p [Code of Conduct](https://github.com/libp2p/community/blob/master/code-of-conduct.md). 25 | 26 | ### Want to hack on libp2p? 27 | 28 | [![](https://cdn.rawgit.com/libp2p/community/master/img/contribute.gif)](https://github.com/libp2p/community/blob/master/CONTRIBUTE.md) 29 | 30 | ## License 31 | 32 | MIT 33 | 34 | --- 35 | 36 | The last gx published version of this module was: 4.1.15: QmbeHtaBy9nZsW4cHRcvgVY4CnDhXudE2Dr6qDxS7yg9rX 37 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | range: "50...100" 3 | comment: off 4 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/libp2p/go-libp2p-record 2 | 3 | require ( 4 | github.com/ipfs/go-test v0.0.4 5 | github.com/libp2p/go-libp2p v0.38.2 6 | github.com/multiformats/go-multihash v0.2.3 7 | google.golang.org/protobuf v1.36.3 8 | ) 9 | 10 | require ( 11 | github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect 12 | github.com/ipfs/go-block-format v0.2.0 // indirect 13 | github.com/ipfs/go-cid v0.5.0 // indirect 14 | github.com/ipfs/go-ipfs-util v0.0.3 // indirect 15 | github.com/klauspost/cpuid/v2 v2.2.9 // indirect 16 | github.com/libp2p/go-buffer-pool v0.1.0 // indirect 17 | github.com/minio/sha256-simd v1.0.1 // indirect 18 | github.com/mr-tron/base58 v1.2.0 // indirect 19 | github.com/multiformats/go-base32 v0.1.0 // indirect 20 | github.com/multiformats/go-base36 v0.2.0 // indirect 21 | github.com/multiformats/go-multiaddr v0.14.0 // indirect 22 | github.com/multiformats/go-multibase v0.2.0 // indirect 23 | github.com/multiformats/go-multicodec v0.9.0 // indirect 24 | github.com/multiformats/go-varint v0.0.7 // indirect 25 | github.com/spaolacci/murmur3 v1.1.0 // indirect 26 | golang.org/x/crypto v0.32.0 // indirect 27 | golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect 28 | golang.org/x/sys v0.29.0 // indirect 29 | lukechampine.com/blake3 v1.3.0 // indirect 30 | ) 31 | 32 | go 1.23 33 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= 4 | github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= 5 | github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg= 6 | github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= 7 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 8 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 9 | github.com/ipfs/go-block-format v0.2.0 h1:ZqrkxBA2ICbDRbK8KJs/u0O3dlp6gmAuuXUJNiW1Ycs= 10 | github.com/ipfs/go-block-format v0.2.0/go.mod h1:+jpL11nFx5A/SPpsoBn6Bzkra/zaArfSmsknbPMYgzM= 11 | github.com/ipfs/go-cid v0.5.0 h1:goEKKhaGm0ul11IHA7I6p1GmKz8kEYniqFopaB5Otwg= 12 | github.com/ipfs/go-cid v0.5.0/go.mod h1:0L7vmeNXpQpUS9vt+yEARkJ8rOg43DF3iPgn4GIN0mk= 13 | github.com/ipfs/go-ipfs-util v0.0.3 h1:2RFdGez6bu2ZlZdI+rWfIdbQb1KudQp3VGwPtdNCmE0= 14 | github.com/ipfs/go-ipfs-util v0.0.3/go.mod h1:LHzG1a0Ig4G+iZ26UUOMjHd+lfM84LZCrn17xAKWBvs= 15 | github.com/ipfs/go-test v0.0.4 h1:DKT66T6GBB6PsDFLoO56QZPrOmzJkqU1FZH5C9ySkew= 16 | github.com/ipfs/go-test v0.0.4/go.mod h1:qhIM1EluEfElKKM6fnWxGn822/z9knUGM1+I/OAQNKI= 17 | github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY= 18 | github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8= 19 | github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= 20 | github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= 21 | github.com/libp2p/go-libp2p v0.38.2 h1:9SZQDOCi82A25An4kx30lEtr6kGTxrtoaDkbs5xrK5k= 22 | github.com/libp2p/go-libp2p v0.38.2/go.mod h1:QWV4zGL3O9nXKdHirIC59DoRcZ446dfkjbOJ55NEWFo= 23 | github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= 24 | github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= 25 | github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= 26 | github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= 27 | github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE= 28 | github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI= 29 | github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0= 30 | github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4= 31 | github.com/multiformats/go-multiaddr v0.14.0 h1:bfrHrJhrRuh/NXH5mCnemjpbGjzRw/b+tJFOD41g2tU= 32 | github.com/multiformats/go-multiaddr v0.14.0/go.mod h1:6EkVAxtznq2yC3QT5CM1UTAwG0GTP3EWAIcjHuzQ+r4= 33 | github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g= 34 | github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk= 35 | github.com/multiformats/go-multicodec v0.9.0 h1:pb/dlPnzee/Sxv/j4PmkDRxCOi3hXTz3IbPKOXWJkmg= 36 | github.com/multiformats/go-multicodec v0.9.0/go.mod h1:L3QTQvMIaVBkXOXXtVmYE+LI16i14xuaojr/H7Ai54k= 37 | github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U= 38 | github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM= 39 | github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8= 40 | github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU= 41 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 42 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 43 | github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= 44 | github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 45 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 46 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 47 | golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= 48 | golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= 49 | golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA= 50 | golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU= 51 | golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= 52 | golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 53 | google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU= 54 | google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 55 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 56 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 57 | lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE= 58 | lukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= 59 | -------------------------------------------------------------------------------- /pb/gen.go: -------------------------------------------------------------------------------- 1 | // These commands work around namespace conflicts that occur when multiple 2 | // repositories depend on .proto files with generic filenames. Due to the way 3 | // protobuf registers files (e.g., foo.proto or pb/foo.proto), naming 4 | // collisions can occur when the same filename is used across different 5 | // packages. 6 | // 7 | // The only way to generate a *.pb.go file that includes the full package path 8 | // (e.g., github.com/org/repo/pb/foo.proto) is to place the .proto file in a 9 | // directory structure that mirrors its package path. 10 | // 11 | // References: 12 | // - https://protobuf.dev/reference/go/faq#namespace-conflict 13 | // - https://github.com/golang/protobuf/issues/1122#issuecomment-2045945265 14 | // 15 | //go:generate mkdir -p github.com/libp2p/go-libp2p-record/pb 16 | //go:generate ln -f record.proto github.com/libp2p/go-libp2p-record/pb/ 17 | //go:generate protoc --go_out=. github.com/libp2p/go-libp2p-record/pb/record.proto 18 | //go:generate mv -f github.com/libp2p/go-libp2p-record/pb/record.pb.go . 19 | //go:generate rm -rf github.com 20 | 21 | package pb 22 | -------------------------------------------------------------------------------- /pb/record.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go v1.36.3 4 | // protoc v5.29.2 5 | // source: github.com/libp2p/go-libp2p-record/pb/record.proto 6 | 7 | package pb 8 | 9 | import ( 10 | reflect "reflect" 11 | sync "sync" 12 | 13 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 14 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 15 | ) 16 | 17 | const ( 18 | // Verify that this generated code is sufficiently up-to-date. 19 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 20 | // Verify that runtime/protoimpl is sufficiently up-to-date. 21 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 22 | ) 23 | 24 | // Record represents a dht record that contains a value 25 | // for a key value pair 26 | type Record struct { 27 | state protoimpl.MessageState `protogen:"open.v1"` 28 | // The key that references this record 29 | Key []byte `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` 30 | // The actual value this record is storing 31 | Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` 32 | // Time the record was received, set by receiver 33 | TimeReceived string `protobuf:"bytes,5,opt,name=timeReceived,proto3" json:"timeReceived,omitempty"` 34 | unknownFields protoimpl.UnknownFields 35 | sizeCache protoimpl.SizeCache 36 | } 37 | 38 | func (x *Record) Reset() { 39 | *x = Record{} 40 | mi := &file_github_com_libp2p_go_libp2p_record_pb_record_proto_msgTypes[0] 41 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 42 | ms.StoreMessageInfo(mi) 43 | } 44 | 45 | func (x *Record) String() string { 46 | return protoimpl.X.MessageStringOf(x) 47 | } 48 | 49 | func (*Record) ProtoMessage() {} 50 | 51 | func (x *Record) ProtoReflect() protoreflect.Message { 52 | mi := &file_github_com_libp2p_go_libp2p_record_pb_record_proto_msgTypes[0] 53 | if x != nil { 54 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 55 | if ms.LoadMessageInfo() == nil { 56 | ms.StoreMessageInfo(mi) 57 | } 58 | return ms 59 | } 60 | return mi.MessageOf(x) 61 | } 62 | 63 | // Deprecated: Use Record.ProtoReflect.Descriptor instead. 64 | func (*Record) Descriptor() ([]byte, []int) { 65 | return file_github_com_libp2p_go_libp2p_record_pb_record_proto_rawDescGZIP(), []int{0} 66 | } 67 | 68 | func (x *Record) GetKey() []byte { 69 | if x != nil { 70 | return x.Key 71 | } 72 | return nil 73 | } 74 | 75 | func (x *Record) GetValue() []byte { 76 | if x != nil { 77 | return x.Value 78 | } 79 | return nil 80 | } 81 | 82 | func (x *Record) GetTimeReceived() string { 83 | if x != nil { 84 | return x.TimeReceived 85 | } 86 | return "" 87 | } 88 | 89 | var File_github_com_libp2p_go_libp2p_record_pb_record_proto protoreflect.FileDescriptor 90 | 91 | var file_github_com_libp2p_go_libp2p_record_pb_record_proto_rawDesc = []byte{ 92 | 0x0a, 0x32, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x62, 93 | 0x70, 0x32, 0x70, 0x2f, 0x67, 0x6f, 0x2d, 0x6c, 0x69, 0x62, 0x70, 0x32, 0x70, 0x2d, 0x72, 0x65, 94 | 0x63, 0x6f, 0x72, 0x64, 0x2f, 0x70, 0x62, 0x2f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x2e, 0x70, 95 | 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x09, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x2e, 0x70, 0x62, 0x22, 96 | 0x54, 0x0a, 0x06, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 97 | 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 98 | 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 99 | 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x74, 0x69, 0x6d, 0x65, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 100 | 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x74, 0x69, 0x6d, 0x65, 0x52, 0x65, 0x63, 101 | 0x65, 0x69, 0x76, 0x65, 0x64, 0x42, 0x27, 0x5a, 0x25, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 102 | 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x62, 0x70, 0x32, 0x70, 0x2f, 0x67, 0x6f, 0x2d, 0x6c, 0x69, 103 | 0x62, 0x70, 0x32, 0x70, 0x2d, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x2f, 0x70, 0x62, 0x62, 0x06, 104 | 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 105 | } 106 | 107 | var ( 108 | file_github_com_libp2p_go_libp2p_record_pb_record_proto_rawDescOnce sync.Once 109 | file_github_com_libp2p_go_libp2p_record_pb_record_proto_rawDescData = file_github_com_libp2p_go_libp2p_record_pb_record_proto_rawDesc 110 | ) 111 | 112 | func file_github_com_libp2p_go_libp2p_record_pb_record_proto_rawDescGZIP() []byte { 113 | file_github_com_libp2p_go_libp2p_record_pb_record_proto_rawDescOnce.Do(func() { 114 | file_github_com_libp2p_go_libp2p_record_pb_record_proto_rawDescData = protoimpl.X.CompressGZIP(file_github_com_libp2p_go_libp2p_record_pb_record_proto_rawDescData) 115 | }) 116 | return file_github_com_libp2p_go_libp2p_record_pb_record_proto_rawDescData 117 | } 118 | 119 | var file_github_com_libp2p_go_libp2p_record_pb_record_proto_msgTypes = make([]protoimpl.MessageInfo, 1) 120 | var file_github_com_libp2p_go_libp2p_record_pb_record_proto_goTypes = []any{ 121 | (*Record)(nil), // 0: record.pb.Record 122 | } 123 | var file_github_com_libp2p_go_libp2p_record_pb_record_proto_depIdxs = []int32{ 124 | 0, // [0:0] is the sub-list for method output_type 125 | 0, // [0:0] is the sub-list for method input_type 126 | 0, // [0:0] is the sub-list for extension type_name 127 | 0, // [0:0] is the sub-list for extension extendee 128 | 0, // [0:0] is the sub-list for field type_name 129 | } 130 | 131 | func init() { file_github_com_libp2p_go_libp2p_record_pb_record_proto_init() } 132 | func file_github_com_libp2p_go_libp2p_record_pb_record_proto_init() { 133 | if File_github_com_libp2p_go_libp2p_record_pb_record_proto != nil { 134 | return 135 | } 136 | type x struct{} 137 | out := protoimpl.TypeBuilder{ 138 | File: protoimpl.DescBuilder{ 139 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 140 | RawDescriptor: file_github_com_libp2p_go_libp2p_record_pb_record_proto_rawDesc, 141 | NumEnums: 0, 142 | NumMessages: 1, 143 | NumExtensions: 0, 144 | NumServices: 0, 145 | }, 146 | GoTypes: file_github_com_libp2p_go_libp2p_record_pb_record_proto_goTypes, 147 | DependencyIndexes: file_github_com_libp2p_go_libp2p_record_pb_record_proto_depIdxs, 148 | MessageInfos: file_github_com_libp2p_go_libp2p_record_pb_record_proto_msgTypes, 149 | }.Build() 150 | File_github_com_libp2p_go_libp2p_record_pb_record_proto = out.File 151 | file_github_com_libp2p_go_libp2p_record_pb_record_proto_rawDesc = nil 152 | file_github_com_libp2p_go_libp2p_record_pb_record_proto_goTypes = nil 153 | file_github_com_libp2p_go_libp2p_record_pb_record_proto_depIdxs = nil 154 | } 155 | -------------------------------------------------------------------------------- /pb/record.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package record.pb; 3 | 4 | option go_package = "github.com/libp2p/go-libp2p-record/pb"; 5 | 6 | // Record represents a dht record that contains a value 7 | // for a key value pair 8 | message Record { 9 | // The key that references this record 10 | bytes key = 1; 11 | 12 | // The actual value this record is storing 13 | bytes value = 2; 14 | 15 | // Note: These fields were removed from the Record message 16 | // hash of the authors public key 17 | // optional string author = 3; 18 | // A PKI signature for the key+value+author 19 | // optional bytes signature = 4; 20 | 21 | // Time the record was received, set by receiver 22 | string timeReceived = 5; 23 | } 24 | -------------------------------------------------------------------------------- /pubkey.go: -------------------------------------------------------------------------------- 1 | package record 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | 8 | "github.com/libp2p/go-libp2p/core/crypto" 9 | "github.com/libp2p/go-libp2p/core/peer" 10 | mh "github.com/multiformats/go-multihash" 11 | ) 12 | 13 | // PublicKeyValidator is a Validator that validates public keys. 14 | type PublicKeyValidator struct{} 15 | 16 | // Validate conforms to the Validator interface. 17 | // 18 | // It verifies that the passed in record value is the PublicKey that matches the 19 | // passed in key. 20 | func (pkv PublicKeyValidator) Validate(key string, value []byte) error { 21 | ns, key, err := SplitKey(key) 22 | if err != nil { 23 | return err 24 | } 25 | if ns != "pk" { 26 | return errors.New("namespace not 'pk'") 27 | } 28 | 29 | keyhash := []byte(key) 30 | if _, err := mh.Cast(keyhash); err != nil { 31 | return fmt.Errorf("key did not contain valid multihash: %s", err) 32 | } 33 | 34 | pk, err := crypto.UnmarshalPublicKey(value) 35 | if err != nil { 36 | return err 37 | } 38 | id, err := peer.IDFromPublicKey(pk) 39 | if err != nil { 40 | return err 41 | } 42 | if !bytes.Equal(keyhash, []byte(id)) { 43 | return errors.New("public key does not match storage key") 44 | } 45 | return nil 46 | } 47 | 48 | // Select conforms to the Validator interface. 49 | // 50 | // It always returns 0 as all public keys are equivalently valid. 51 | func (pkv PublicKeyValidator) Select(k string, vals [][]byte) (int, error) { 52 | return 0, nil 53 | } 54 | 55 | var _ Validator = PublicKeyValidator{} 56 | -------------------------------------------------------------------------------- /record.go: -------------------------------------------------------------------------------- 1 | package record 2 | 3 | import ( 4 | "github.com/libp2p/go-libp2p-record/pb" 5 | ) 6 | 7 | // MakePutRecord creates a dht record for the given key/value pair 8 | func MakePutRecord(key string, value []byte) *pb.Record { 9 | record := new(pb.Record) 10 | record.Key = []byte(key) 11 | record.Value = value 12 | return record 13 | } 14 | -------------------------------------------------------------------------------- /util.go: -------------------------------------------------------------------------------- 1 | package record 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | // SplitKey takes a key in the form `/$namespace/$path` and splits it into 8 | // `$namespace` and `$path`. 9 | func SplitKey(key string) (string, string, error) { 10 | if len(key) == 0 || key[0] != '/' { 11 | return "", "", ErrInvalidRecordType 12 | } 13 | 14 | key = key[1:] 15 | 16 | i := strings.IndexByte(key, '/') 17 | if i <= 0 { 18 | return "", "", ErrInvalidRecordType 19 | } 20 | 21 | return key[:i], key[i+1:], nil 22 | } 23 | -------------------------------------------------------------------------------- /validator.go: -------------------------------------------------------------------------------- 1 | package record 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | ) 7 | 8 | // ErrInvalidRecordType is returned if a DHTRecord keys prefix 9 | // is not found in the Validator map of the DHT. 10 | var ErrInvalidRecordType = errors.New("invalid record keytype") 11 | 12 | // ErrBetterRecord is returned by a subsystem when it fails because it found a 13 | // better record. 14 | type ErrBetterRecord struct { 15 | // Key is the key associated with the record. 16 | Key string 17 | // Value is the best value that was found, according to the record's 18 | // validator. 19 | Value []byte 20 | } 21 | 22 | func (e *ErrBetterRecord) Error() string { 23 | return fmt.Sprintf("found better value for %q", e.Key) 24 | } 25 | 26 | // Validator is an interface that should be implemented by record validators. 27 | type Validator interface { 28 | // Validate validates the given record, returning an error if it's 29 | // invalid (e.g., expired, signed by the wrong key, etc.). 30 | Validate(key string, value []byte) error 31 | 32 | // Select selects the best record from the set of records (e.g., the 33 | // newest). 34 | // 35 | // Decisions made by select should be stable. 36 | Select(key string, values [][]byte) (int, error) 37 | } 38 | 39 | // NamespacedValidator is a validator that delegates to sub-validators by 40 | // namespace. 41 | type NamespacedValidator map[string]Validator 42 | 43 | // ValidatorByKey looks up the validator responsible for validating the given 44 | // key. 45 | func (v NamespacedValidator) ValidatorByKey(key string) Validator { 46 | ns, _, err := SplitKey(key) 47 | if err != nil { 48 | return nil 49 | } 50 | return v[ns] 51 | } 52 | 53 | // Validate conforms to the Validator interface. 54 | func (v NamespacedValidator) Validate(key string, value []byte) error { 55 | vi := v.ValidatorByKey(key) 56 | if vi == nil { 57 | return ErrInvalidRecordType 58 | } 59 | return vi.Validate(key, value) 60 | } 61 | 62 | // Select conforms to the Validator interface. 63 | func (v NamespacedValidator) Select(key string, values [][]byte) (int, error) { 64 | if len(values) == 0 { 65 | return 0, errors.New("can't select from no values") 66 | } 67 | vi := v.ValidatorByKey(key) 68 | if vi == nil { 69 | return 0, ErrInvalidRecordType 70 | } 71 | return vi.Select(key, values) 72 | } 73 | 74 | var _ Validator = NamespacedValidator{} 75 | -------------------------------------------------------------------------------- /validator_test.go: -------------------------------------------------------------------------------- 1 | package record 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "github.com/ipfs/go-test/random" 8 | ci "github.com/libp2p/go-libp2p/core/crypto" 9 | "github.com/libp2p/go-libp2p/core/peer" 10 | "github.com/libp2p/go-libp2p/core/test" 11 | mh "github.com/multiformats/go-multihash" 12 | ) 13 | 14 | var badPaths = []string{ 15 | "foo/bar/baz", 16 | "//foo/bar/baz", 17 | "/ns", 18 | "ns", 19 | "ns/", 20 | "", 21 | "//", 22 | "/", 23 | "////", 24 | } 25 | 26 | func TestSplitPath(t *testing.T) { 27 | ns, key, err := SplitKey("/foo/bar/baz") 28 | if err != nil { 29 | t.Fatal(err) 30 | } 31 | if ns != "foo" { 32 | t.Errorf("wrong namespace: %s", ns) 33 | } 34 | if key != "bar/baz" { 35 | t.Errorf("wrong key: %s", key) 36 | } 37 | 38 | ns, key, err = SplitKey("/foo/bar") 39 | if err != nil { 40 | t.Fatal(err) 41 | } 42 | if ns != "foo" { 43 | t.Errorf("wrong namespace: %s", ns) 44 | } 45 | if key != "bar" { 46 | t.Errorf("wrong key: %s", key) 47 | } 48 | 49 | for _, badP := range badPaths { 50 | _, _, err := SplitKey(badP) 51 | if err == nil { 52 | t.Errorf("expected error for bad path: %s", badP) 53 | } 54 | } 55 | } 56 | 57 | func TestBadRecords(t *testing.T) { 58 | v := NamespacedValidator{ 59 | "pk": PublicKeyValidator{}, 60 | } 61 | 62 | sr := random.NewSeededRand(15) // generate deterministic keypair 63 | _, pubk, err := ci.GenerateKeyPairWithReader(ci.RSA, 2048, sr) 64 | if err != nil { 65 | t.Fatal(err) 66 | } 67 | 68 | pkb, err := ci.MarshalPublicKey(pubk) 69 | if err != nil { 70 | t.Fatal(err) 71 | } 72 | 73 | for _, badP := range badPaths { 74 | if v.Validate(badP, pkb) == nil { 75 | t.Errorf("expected error for path: %s", badP) 76 | } 77 | } 78 | 79 | // Test missing namespace 80 | if v.Validate("/missing/ns", pkb) == nil { 81 | t.Error("expected error for missing namespace 'missing'") 82 | } 83 | 84 | // Test valid namespace 85 | pkh, err := mh.Sum(pkb, mh.SHA2_256, -1) 86 | if err != nil { 87 | t.Fatal(err) 88 | } 89 | k := "/pk/" + string(pkh) 90 | err = v.Validate(k, pkb) 91 | if err != nil { 92 | t.Fatal(err) 93 | } 94 | } 95 | 96 | func TestValidatePublicKey(t *testing.T) { 97 | var pkv PublicKeyValidator 98 | 99 | sr := random.NewSeededRand(16) // generate deterministic keypair 100 | _, pubk, err := ci.GenerateKeyPairWithReader(ci.RSA, 2048, sr) 101 | if err != nil { 102 | t.Fatal(err) 103 | } 104 | pkb, err := ci.MarshalPublicKey(pubk) 105 | if err != nil { 106 | t.Fatal(err) 107 | } 108 | 109 | pkb2, err := ci.MarshalPublicKey(pubk) 110 | if err != nil { 111 | t.Fatal(err) 112 | } 113 | 114 | pkh, err := mh.Sum(pkb2, mh.SHA2_256, -1) 115 | if err != nil { 116 | t.Fatal(err) 117 | } 118 | k := "/pk/" + string(pkh) 119 | 120 | // Good public key should pass 121 | if err := pkv.Validate(k, pkb); err != nil { 122 | t.Fatal(err) 123 | } 124 | 125 | // Bad key format should fail 126 | badf := "/aa/" + string(pkh) 127 | if err := pkv.Validate(badf, pkb); err == nil { 128 | t.Fatal("Failed to detect bad prefix") 129 | } 130 | 131 | // Bad key hash should fail 132 | badk := "/pk/" + strings.Repeat("A", len(pkh)) 133 | if err := pkv.Validate(badk, pkb); err == nil { 134 | t.Fatal("Failed to detect bad public key hash") 135 | } 136 | 137 | // Bad public key should fail 138 | pkb[0] = 'A' 139 | if err := pkv.Validate(k, pkb); err == nil { 140 | t.Fatal("Failed to detect bad public key data") 141 | } 142 | } 143 | 144 | func TestValidateEd25519PublicKey(t *testing.T) { 145 | var pkv PublicKeyValidator 146 | 147 | _, pk, err := test.RandTestKeyPair(ci.Ed25519, 0) 148 | if err != nil { 149 | t.Fatal(err) 150 | } 151 | 152 | id, err := peer.IDFromPublicKey(pk) 153 | if err != nil { 154 | t.Fatal(err) 155 | } 156 | 157 | pkb, err := ci.MarshalPublicKey(pk) 158 | if err != nil { 159 | t.Fatal(err) 160 | } 161 | 162 | k := "/pk/" + string(id) 163 | 164 | // Good public key should pass 165 | if err := pkv.Validate(k, pkb); err != nil { 166 | t.Fatal(err) 167 | } 168 | } 169 | 170 | func TestBestRecord(t *testing.T) { 171 | sel := NamespacedValidator{ 172 | "pk": PublicKeyValidator{}, 173 | } 174 | 175 | i, err := sel.Select("/pk/thing", [][]byte{[]byte("first"), []byte("second")}) 176 | if err != nil { 177 | t.Fatal(err) 178 | } 179 | if i != 0 { 180 | t.Error("expected to select first record") 181 | } 182 | 183 | _, err = sel.Select("/pk/thing", nil) 184 | if err == nil { 185 | t.Fatal("expected error for no records") 186 | } 187 | 188 | _, err = sel.Select("/other/thing", [][]byte{[]byte("first"), []byte("second")}) 189 | if err == nil { 190 | t.Fatal("expected error for unregistered ns") 191 | } 192 | 193 | _, err = sel.Select("bad", [][]byte{[]byte("first"), []byte("second")}) 194 | if err == nil { 195 | t.Fatal("expected error for bad key") 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /version.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "v0.3.1" 3 | } 4 | --------------------------------------------------------------------------------