Launch readiness: import utility

This commit is contained in:
Cassandra Heart 2023-08-20 22:50:38 -05:00
parent ffab09ae6b
commit 529f434393
No known key found for this signature in database
GPG Key ID: 6352152859385958
668 changed files with 471702 additions and 363 deletions

View File

@ -56,18 +56,18 @@ func (t *Bitmask) SetScoreParams(p *BitmaskScoreParams) error {
result := make(chan error, 1)
update := func() {
gs, ok := t.p.rt.(*BlossomSubRouter)
bs, ok := t.p.rt.(*BlossomSubRouter)
if !ok {
result <- fmt.Errorf("pubsub router is not BlossomSub")
return
}
if gs.score == nil {
if bs.score == nil {
result <- fmt.Errorf("peer scoring is not enabled in router")
return
}
err := gs.score.SetBitmaskScoreParams(t.bitmask, p)
err := bs.score.SetBitmaskScoreParams(t.bitmask, p)
result <- err
}

File diff suppressed because it is too large Load Diff

View File

@ -38,9 +38,9 @@ func TestBlossomSubCustomProtocols(t *testing.T) {
defer cancel()
hosts := getNetHosts(t, ctx, 3)
gsubs := getBlossomSubs(ctx, hosts[:2], WithBlossomSubProtocols(protos, features))
bsubs := getBlossomSubs(ctx, hosts[:2], WithBlossomSubProtocols(protos, features))
fsub := getPubsub(ctx, hosts[2])
psubs := append(gsubs, fsub)
psubs := append(bsubs, fsub)
connectAll(t, hosts)
@ -58,33 +58,33 @@ func TestBlossomSubCustomProtocols(t *testing.T) {
// wait for heartbeats to build mesh
time.Sleep(time.Second * 2)
// check the meshes of the gsubs, the BlossomSub meshes should include each other but not the
// check the meshes of the bsubs, the BlossomSub meshes should include each other but not the
// floddsub peer
gsubs[0].eval <- func() {
gs := gsubs[0].rt.(*BlossomSubRouter)
bsubs[0].eval <- func() {
bs := bsubs[0].rt.(*BlossomSubRouter)
_, ok := gs.mesh[string(bitmask)][hosts[1].ID()]
_, ok := bs.mesh[string(bitmask)][hosts[1].ID()]
if !ok {
t.Fatal("expected gs0 to have gs1 in its mesh")
t.Fatal("expected bs0 to have bs1 in its mesh")
}
_, ok = gs.mesh[string(bitmask)][hosts[2].ID()]
_, ok = bs.mesh[string(bitmask)][hosts[2].ID()]
if ok {
t.Fatal("expected gs0 to not have fs in its mesh")
t.Fatal("expected bs0 to not have fs in its mesh")
}
}
gsubs[1].eval <- func() {
gs := gsubs[1].rt.(*BlossomSubRouter)
bsubs[1].eval <- func() {
bs := bsubs[1].rt.(*BlossomSubRouter)
_, ok := gs.mesh[string(bitmask)][hosts[0].ID()]
_, ok := bs.mesh[string(bitmask)][hosts[0].ID()]
if !ok {
t.Fatal("expected gs1 to have gs0 in its mesh")
t.Fatal("expected bs1 to have bs0 in its mesh")
}
_, ok = gs.mesh[string(bitmask)][hosts[2].ID()]
_, ok = bs.mesh[string(bitmask)][hosts[2].ID()]
if ok {
t.Fatal("expected gs1 to not have fs in its mesh")
t.Fatal("expected bs1 to not have fs in its mesh")
}
}

View File

@ -81,7 +81,7 @@ func TestBlossomSubAttackSpamIWANT(t *testing.T) {
}
}()
newMockGS(ctx, t, attacker, func(writeMsg func(*pb.RPC), irpc *pb.RPC) {
newMockBS(ctx, t, attacker, func(writeMsg func(*pb.RPC), irpc *pb.RPC) {
// When the legit host connects it will send us its subscriptions
for _, sub := range irpc.GetSubscriptions() {
if sub.GetSubscribe() {
@ -185,7 +185,7 @@ func TestBlossomSubAttackSpamIHAVE(t *testing.T) {
iWantCount += i
}
newMockGS(ctx, t, attacker, func(writeMsg func(*pb.RPC), irpc *pb.RPC) {
newMockBS(ctx, t, attacker, func(writeMsg func(*pb.RPC), irpc *pb.RPC) {
// When the legit host connects it will send us its subscriptions
for _, sub := range irpc.GetSubscriptions() {
if sub.GetSubscribe() {
@ -319,7 +319,7 @@ func TestBlossomSubAttackGRAFTNonExistentBitmask(t *testing.T) {
}
}
newMockGS(ctx, t, attacker, func(writeMsg func(*pb.RPC), irpc *pb.RPC) {
newMockBS(ctx, t, attacker, func(writeMsg func(*pb.RPC), irpc *pb.RPC) {
// When the legit host connects it will send us its subscriptions
for _, sub := range irpc.GetSubscriptions() {
if sub.GetSubscribe() {
@ -419,7 +419,7 @@ func TestBlossomSubAttackGRAFTDuringBackoff(t *testing.T) {
pruneCount += i
}
newMockGS(ctx, t, attacker, func(writeMsg func(*pb.RPC), irpc *pb.RPC) {
newMockBS(ctx, t, attacker, func(writeMsg func(*pb.RPC), irpc *pb.RPC) {
// When the legit host connects it will send us its subscriptions
for _, sub := range irpc.GetSubscriptions() {
if sub.GetSubscribe() {
@ -599,11 +599,11 @@ func TestBlossomSubAttackGRAFTDuringBackoff(t *testing.T) {
<-ctx.Done()
}
type gsAttackInvalidMsgTracer struct {
type bsAttackInvalidMsgTracer struct {
rejectCount int
}
func (t *gsAttackInvalidMsgTracer) Trace(evt *pb.TraceEvent) {
func (t *bsAttackInvalidMsgTracer) Trace(evt *pb.TraceEvent) {
// fmt.Printf(" %s %s\n", evt.Type, evt)
if evt.GetType() == pb.TraceEvent_REJECT_MESSAGE {
t.rejectCount++
@ -660,7 +660,7 @@ func TestBlossomSubAttackInvalidMessageSpam(t *testing.T) {
}
// Set up BlossomSub on the legit host
tracer := &gsAttackInvalidMsgTracer{}
tracer := &bsAttackInvalidMsgTracer{}
ps, err := NewBlossomSub(ctx, legit,
WithEventTracer(tracer),
WithPeerScore(params, thresholds),
@ -692,7 +692,7 @@ func TestBlossomSubAttackInvalidMessageSpam(t *testing.T) {
pruneCount += i
}
newMockGS(ctx, t, attacker, func(writeMsg func(*pb.RPC), irpc *pb.RPC) {
newMockBS(ctx, t, attacker, func(writeMsg func(*pb.RPC), irpc *pb.RPC) {
// When the legit host connects it will send us its subscriptions
for _, sub := range irpc.GetSubscriptions() {
if sub.GetSubscribe() {
@ -762,9 +762,9 @@ func TestBlossomSubAttackInvalidMessageSpam(t *testing.T) {
<-ctx.Done()
}
type mockGSOnRead func(writeMsg func(*pb.RPC), irpc *pb.RPC)
type MockBSOnRead func(writeMsg func(*pb.RPC), irpc *pb.RPC)
func newMockGS(ctx context.Context, t *testing.T, attacker host.Host, onReadMsg mockGSOnRead) {
func newMockBS(ctx context.Context, t *testing.T, attacker host.Host, onReadMsg MockBSOnRead) {
// Listen on the BlossomSub protocol
const BlossomSubID = protocol.ID("/meshsub/1.0.0")
const maxMessageSize = 1024 * 1024

View File

@ -35,13 +35,13 @@ func newGossipTracer() *gossipTracer {
}
}
func (gt *gossipTracer) Start(gs *BlossomSubRouter) {
func (gt *gossipTracer) Start(bs *BlossomSubRouter) {
if gt == nil {
return
}
gt.idGen = gs.p.idGen
gt.followUpTime = gs.params.IWantFollowupTime
gt.idGen = bs.p.idGen
gt.followUpTime = bs.params.IWantFollowupTime
}
// track a promise to deliver a message from a list of msgIDs we are requesting

2
go-libp2p/.codecov.yml Normal file
View File

@ -0,0 +1,2 @@
github_checks:
annotations: false

View File

@ -0,0 +1,8 @@
# Git Hooks
This directory contains useful Git hooks for working with go-libp2p.
Install them by running
```bash
git config core.hooksPath .githooks
```

13
go-libp2p/.githooks/pre-commit Executable file
View File

@ -0,0 +1,13 @@
#!/bin/bash
pushd ./test-plans > /dev/null
go mod tidy
if [[ -n $(git diff --name-only -- "go.mod" "go.sum") ]]; then
echo "go.mod / go.sum in test-plans not tidied"
errored=true
fi
popd > /dev/null
if [ "$errored" = true ]; then
exit 1
fi

View File

@ -0,0 +1,20 @@
---
name: 'Bug Report'
about: 'Report a bug in go-libp2p.'
labels: bug
---
<!-- This is where you get to tell us what went wrong. When doing so, please make sure to include *all* relevant information.
Please try to include:
* What you were doing when you experienced the bug.
* Any error messages you saw, *where* you saw them, and what you believe may have caused them (if you have any ideas).
* When possible, steps to reliably produce the bug.
-->
<details>
<summary>Version Information</summary>
<pre>
<!-- Insert the output of `go list -m all` HERE -->
</pre>
</details>

13
go-libp2p/.github/ISSUE_TEMPLATE/doc.md vendored Normal file
View File

@ -0,0 +1,13 @@
---
name: 'Documentation Issue'
about: 'Report missing/erroneous documentation, propose new documentation, report broken links, etc.'
labels: documentation
---
#### Location
<!-- In the case of missing/erroneous documentation, where is the error? If possible, a link/url would be great! -->
#### Description
<!-- Describe the documentation issue. -->

View File

@ -0,0 +1,11 @@
---
name: 'Enhancement'
about: 'Suggest an improvement to an existing go-libp2p feature.'
labels: enhancement
---
<!--
Note: If you'd like to suggest an idea related to libp2p but not specifically related to the Go implementation, please file an issue at https://github.com/libp2p/specs instead. Even better, create a new topic on the forums (https://discuss.libp2p.io).
When requesting an _enhancement_, please be sure to include your motivation and try to be as specific as possible.
-->

View File

@ -0,0 +1,15 @@
---
name: 'Feature'
about: 'Suggest a new feature in go-libp2p.'
labels: feature
---
<!--
Note: If you'd like to suggest an idea related to libp2p but not specifically related to the Go implementation, please file an issue at https://github.com/libp2p/specs instead. Even better, create a new topic on the forums (https://discuss.libp2p.io).
When requesting a _feature_, please be sure to include:
* Your motivation. Why do you need the feature?
* How the feature should work.
Please try to be as specific and concrete as possible.
-->

View File

@ -0,0 +1,9 @@
---
name: 'Question/Support'
about: 'Ask a question about go-libp2p or request support.'
labels: question, invalid
---
This bug tracker is only for actionable bug reports and feature requests. Please direct any questions to https://discuss.libp2p.io or to our Matrix (#libp2p:matrix.org) or IRC (#libp2p on freenode) channels.
If you don't get an immediate response, please keep trying.

View File

@ -0,0 +1,34 @@
---
name: 'Libp2p Release'
about: 'Start a new libp2p release.'
---
## 🗺 What's left for release
<List of items with remaining PRs and/or Issues to be considered for this release>
## 🔦 Highlights
< top highlights for this release notes >
## Changelog
< changelog generated by scripts/mkreleaselog >
## ✅ Release Checklist
- [ ] **Stage 0 - Finishing Touches**
- [ ] Go through relevant libp2p repos looking for unreleased changes that should make it into the release. If you find any, cut releases.
- [ ] Run `go get -u ./...` to see if there are any out-of-date deps that look important. If there are, bubble them. Try to avoid _directly_ updating indirect deps in go-libp2p's `go.mod` when possible.
- [ ] **Stage 1 - Release**
- [ ] Publish the release through the GitHub UI, adding the release notes. Some users rely on this to receive notifications of new releases.
- [ ] Announce the release on the [discuss.libp2p.io](https://discuss.libp2p.io).
- [ ] **Stage 2 - Update Upstream**
- [ ] Update the examples to the final release
- [ ] Update the upstream dependencies to the final release and create PRs.
- [ ] [filecoin-project/lotus](https://github.com/filecoin-project/lotus)
- [ ] [go-libp2p-kad-dht](https://github.com/libp2p/go-libp2p-kad-dht/)
- [ ] [go-libp2p-pubsub](https://github.com/libp2p/go-libp2p-pubsub) (In case of breaking changes.)
- [ ] [ipfs/kubo](https://github.com/ipfs/kubo)
- [ ] Add new release to interop tester in [test-plans](https://github.com/libp2p/test-plans/)
- [ ] Make required changes to the release process.

View File

@ -0,0 +1,11 @@
runs:
using: "composite"
steps:
- name: Install Protoc
uses: trail-of-forks/setup-protoc@a97892a429d98fae78d26f40334ab7eb616d08b9 # include https://github.com/arduino/setup-protoc/pull/58
with:
version: '21.12'
repo-token: ${{ github.token }}
- name: Install Protobuf compiler
shell: bash
run: go install google.golang.org/protobuf/cmd/protoc-gen-go

View File

@ -0,0 +1,11 @@
runs:
using: "composite"
steps:
- name: increase the UDP receive buffer size # see https://github.com/lucas-clemente/quic-go/wiki/UDP-Receive-Buffer-Size
shell: bash
run: sysctl -w net.core.rmem_max=2500000
if: ${{ matrix.os == 'ubuntu' }}
- name: Run nocover tests. These are tests that require the coverage analysis to be off # See https://github.com/protocol/.github/issues/460
shell: bash
# This matches only tests with "NoCover" in their test name to avoid running all tests again.
run: go test -tags nocover -run NoCover -v ./...

View File

@ -0,0 +1,11 @@
# File managed by web3-bot. DO NOT EDIT.
# See https://github.com/protocol/.github/ for details.
name: Automerge
on: [ pull_request ]
jobs:
automerge:
uses: protocol/.github/.github/workflows/automerge.yml@master
with:
job: 'automerge'

View File

@ -0,0 +1,3 @@
{
"gogenerate": true
}

View File

@ -0,0 +1,67 @@
# File managed by web3-bot. DO NOT EDIT.
# See https://github.com/protocol/.github/ for details.
on: [push, pull_request]
name: Go Checks
jobs:
unit:
runs-on: ubuntu-latest
name: All
steps:
- uses: actions/checkout@v3
with:
submodules: recursive
- id: config
uses: protocol/.github/.github/actions/read-config@master
- uses: actions/setup-go@v3
with:
go-version: 1.20.x
- name: Run repo-specific setup
uses: ./.github/actions/go-check-setup
if: hashFiles('./.github/actions/go-check-setup') != ''
- name: Install staticcheck
run: go install honnef.co/go/tools/cmd/staticcheck@4970552d932f48b71485287748246cf3237cebdf # 2023.1 (v0.4.0)
- name: Check that go.mod is tidy
uses: protocol/multiple-go-modules@v1.2
with:
run: |
go mod tidy
if [[ -n $(git ls-files --other --exclude-standard --directory -- go.sum) ]]; then
echo "go.sum was added by go mod tidy"
exit 1
fi
git diff --exit-code -- go.sum go.mod
- name: gofmt
if: success() || failure() # run this step even if the previous one failed
run: |
out=$(gofmt -s -l .)
if [[ -n "$out" ]]; then
echo $out | awk '{print "::error file=" $0 ",line=0,col=0::File is not gofmt-ed."}'
exit 1
fi
- name: go vet
if: success() || failure() # run this step even if the previous one failed
uses: protocol/multiple-go-modules@v1.2
with:
run: go vet ./...
- name: staticcheck
if: success() || failure() # run this step even if the previous one failed
uses: protocol/multiple-go-modules@v1.2
with:
run: |
set -o pipefail
staticcheck ./... | sed -e 's@\(.*\)\.go@./\1.go@g'
- name: go generate
uses: protocol/multiple-go-modules@v1.2
if: (success() || failure()) && fromJSON(steps.config.outputs.json).gogenerate == true
with:
run: |
git clean -fd # make sure there aren't untracked files / directories
go generate -x ./...
# check if go generate modified or added any files
if ! $(git add . && git diff-index HEAD --exit-code --quiet); then
echo "go generated caused changes to the repository:"
git status --short
exit 1
fi

76
go-libp2p/.github/workflows/go-test.yml vendored Normal file
View File

@ -0,0 +1,76 @@
# File managed by web3-bot. DO NOT EDIT.
# See https://github.com/protocol/.github/ for details.
on: [push, pull_request]
name: Go Test
jobs:
unit:
strategy:
fail-fast: false
matrix:
os: [ "ubuntu", "windows", "macos" ]
go: ["1.19.x","1.20.x"]
env:
COVERAGES: ""
runs-on: ${{ fromJSON(vars[format('UCI_GO_TEST_RUNNER_{0}', matrix.os)] || format('"{0}-latest"', matrix.os)) }}
name: ${{ matrix.os }} (go ${{ matrix.go }})
steps:
- uses: actions/checkout@v3
with:
submodules: recursive
- id: config
uses: protocol/.github/.github/actions/read-config@master
- uses: actions/setup-go@v3
with:
go-version: ${{ matrix.go }}
- name: Go information
run: |
go version
go env
- name: Use msys2 on windows
if: matrix.os == 'windows'
shell: bash
# The executable for msys2 is also called bash.cmd
# https://github.com/actions/virtual-environments/blob/main/images/win/Windows2019-Readme.md#shells
# If we prepend its location to the PATH
# subsequent 'shell: bash' steps will use msys2 instead of gitbash
run: echo "C:/msys64/usr/bin" >> $GITHUB_PATH
- name: Run repo-specific setup
uses: ./.github/actions/go-test-setup
if: hashFiles('./.github/actions/go-test-setup') != ''
- name: Run tests
if: contains(fromJSON(steps.config.outputs.json).skipOSes, matrix.os) == false
uses: protocol/multiple-go-modules@v1.2
with:
# Use -coverpkg=./..., so that we include cross-package coverage.
# If package ./A imports ./B, and ./A's tests also cover ./B,
# this means ./B's coverage will be significantly higher than 0%.
run: go test -v -shuffle=on -coverprofile=module-coverage.txt -coverpkg=./... ./...
- name: Run tests (32 bit)
# can't run 32 bit tests on OSX.
if: matrix.os != 'macos' &&
fromJSON(steps.config.outputs.json).skip32bit != true &&
contains(fromJSON(steps.config.outputs.json).skipOSes, matrix.os) == false
uses: protocol/multiple-go-modules@v1.2
env:
GOARCH: 386
with:
run: |
export "PATH=$PATH_386:$PATH"
go test -v -shuffle=on ./...
- name: Run tests with race detector
# speed things up. Windows and OSX VMs are slow
if: matrix.os == 'ubuntu' &&
contains(fromJSON(steps.config.outputs.json).skipOSes, matrix.os) == false
uses: protocol/multiple-go-modules@v1.2
with:
run: go test -v -race ./...
- name: Collect coverage files
shell: bash
run: echo "COVERAGES=$(find . -type f -name 'module-coverage.txt' | tr -s '\n' ',' | sed 's/,$//')" >> $GITHUB_ENV
- name: Upload coverage to Codecov
uses: codecov/codecov-action@d9f34f8cd5cb3b3eb79b3e4b5dae3a16df499a70 # v3.1.1
with:
files: '${{ env.COVERAGES }}'
env_vars: OS=${{ matrix.os }}, GO=${{ matrix.go }}

View File

@ -0,0 +1,22 @@
name: Interoperability Testing
on:
pull_request:
push:
branches:
- "master"
jobs:
run-multidim-interop:
name: Run multidimensional interoperability tests
runs-on: ${{ fromJSON(vars['INTEROP_TEST_RUNNER_UBUNTU'] || '"ubuntu-22.04"') }}
steps:
- uses: actions/checkout@v3
- name: Build image
run: docker build -t go-libp2p-head -f test-plans/PingDockerfile .
- uses: libp2p/test-plans/.github/actions/run-interop-ping-test@master
with:
test-filter: go-libp2p-head
extra-versions: ${{ github.workspace }}/test-plans/ping-version.json
s3-cache-bucket: libp2p-by-tf-aws-bootstrap
s3-access-key-id: ${{ vars.TEST_PLANS_BUILD_CACHE_KEY_ID }}
s3-secret-access-key: ${{ secrets.TEST_PLANS_BUILD_CACHE_KEY }}

View File

@ -0,0 +1,17 @@
name: Markdown Link Checking
on:
pull_request:
push:
branches:
- "master"
jobs:
check-links:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: gaurav-nelson/github-action-markdown-link-check@v1
with:
use-quiet-mode: 'yes' # show only broken links
use-verbose-mode: 'yes'
config-file: .github/workflows/markdown-links-config.json # for removing any false positives

View File

@ -0,0 +1,22 @@
{
"ignorePatterns": [
{
"pattern": "^http://localhost"
},
{
"pattern": "^https://twitter.com/"
},
{
"pattern": "^https://opensource.org/"
}
],
"aliveStatusCodes": [200],
"httpHeaders": [
{
"urls": ["https://docs.github.com/"],
"headers": {
"Accept-Encoding": "*"
}
}
]
}

View File

@ -0,0 +1,13 @@
# File managed by web3-bot. DO NOT EDIT.
# See https://github.com/protocol/.github/ for details.
name: Release Checker
on:
pull_request_target:
paths: [ 'version.json' ]
jobs:
release-check:
uses: protocol/.github/.github/workflows/release-check.yml@master
with:
go-version: 1.20.x

View File

@ -0,0 +1,11 @@
# File managed by web3-bot. DO NOT EDIT.
# See https://github.com/protocol/.github/ for details.
name: Releaser
on:
push:
paths: [ 'version.json' ]
jobs:
releaser:
uses: protocol/.github/.github/workflows/releaser.yml@master

13
go-libp2p/.github/workflows/stale.yml vendored Normal file
View File

@ -0,0 +1,13 @@
name: Close and mark stale issue
on:
schedule:
- cron: '0 0 * * *'
permissions:
issues: write
pull-requests: write
jobs:
stale:
uses: pl-strflt/.github/.github/workflows/reusable-stale-issue.yml@v0.3

12
go-libp2p/.github/workflows/tagpush.yml vendored Normal file
View File

@ -0,0 +1,12 @@
# File managed by web3-bot. DO NOT EDIT.
# See https://github.com/protocol/.github/ for details.
name: Tag Push Checker
on:
push:
tags:
- v*
jobs:
releaser:
uses: protocol/.github/.github/workflows/tagpush.yml@master

View File

@ -0,0 +1,59 @@
on:
pull_request:
branches:
- master
push:
branches:
- master
name: Upstream Test
jobs:
unit:
strategy:
fail-fast: false
matrix:
os:
- 'ubuntu'
go:
# - '1.15.x'
- '1.16.x'
upstream:
# - 'libp2p/go-libp2p-pubsub' flaky
# - 'ipfs/go-bitswap' flaky
# - 'libp2p/go-libp2p-kad-dht'
- 'libp2p/go-libp2p-daemon'
runs-on: ${{ matrix.os }}-latest
name: ${{ matrix.upstream }} unit tests (${{ matrix.os }}, Go ${{ matrix.go }})
steps:
- uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go }}
- name: Go information
run: |
go version
go env
- uses: actions/checkout@v2
with:
path: 'libp2p'
- uses: actions/checkout@v2
with:
repository: ${{ matrix.upstream }}
path: upstream
- name: Patch in new go-libp2p
working-directory: upstream
run: |
go mod edit -replace "github.com/libp2p/go-libp2p=../libp2p"
go mod tidy
- name: Run tests
working-directory: upstream
run: go test -v ./...
- name: Run tests (32 bit)
working-directory: upstream
if: ${{ matrix.os != 'macos' }} # can't run 32 bit tests on OSX.
env:
GOARCH: 386
run: go test -v ./...
- name: Run tests with race detector
working-directory: upstream
if: ${{ matrix.os == 'ubuntu' }} # speed things up. Windows and OSX VMs are slow
run: go test -v -race ./...

6
go-libp2p/.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
*.swp
.idea
*.qlog
*.sqlog
*.qlog.zst
*.sqlog.zst

293
go-libp2p/CHANGELOG.md Normal file
View File

@ -0,0 +1,293 @@
# Table Of Contents <!-- omit in toc -->
- [v0.28.0](#v0280)
- [v0.27.0](#v0270)
- [v0.26.4](#v0264)
- [v0.26.3](#v0263)
- [v0.26.2](#v0262)
- [v0.26.1](#v0261)
- [v0.26.0](#v0260)
- [v0.25.1](#v0251)
- [v0.25.0](#v0250)
# [v0.28.0](https://github.com/libp2p/go-libp2p/releases/tag/v0.28.0)
## 🔦 Highlights <!-- omit in toc -->
### Smart Dialing <!-- omit in toc -->
This release introduces smart dialing logic. Currently, libp2p dials all addresses of a remote peer in parallel, and
aborts all outstanding dials as soon as the first one succeeds.
Dialing many addresses in parallel creates a lot of churn on the client side, and unnecessary load on the network and
on the server side, and is heavily discouraged by the networking community (see [RFC 8305](https://www.rfc-editor.org/rfc/rfc8305) for example).
When connecting to a peer we first determine the order to dial its addresses. This ranking logic considers a number of corner cases
described in detail in the documentation of the swarm package (`swarm.DefaultDialRanker`).
At a high level, this is what happens:
* If a peer offers a WebTransport and a QUIC address (on the same IP:port), the QUIC address is preferred.
* If a peer has a QUIC and a TCP address, the QUIC address is dialed first. Only if the connection attempt doesn't succeed within 250ms, a TCP connection is started.
Our measurements on the IPFS network show that for >90% of established libp2p connections, the first connection attempt succeeds,
leading a dramatic decrease in the number of aborted connection attempts.
We also added new metrics to the swarm Grafana dashboard, showing:
* The number of connection attempts it took to establish a connection
* The delay introduced by the ranking logic
This feature should be safe to enable for nodes running in data centers and for most nodes in home networks.
However, there are some (mostly home and corporate networks) that block all UDP traffic. If enabled, the current implementation
of the smart dialing logic will lead to a regression, since it preferes QUIC addresses over TCP addresses. Nodes would still be
able to connect, but connection establishment of the TCP connection would be delayed by 250ms.
In a future release (see #1605 for details), we will introduce a feature called blackhole detection. By observing the outcome of
QUIC connection attempts, we can determine if UDP traffic is blocked (namely, if all QUIC connection attempts fail), and stop
dialing QUIC in this case altogether. Once this detection logic is in place, smart dialing will be enabled by default.
### More Metrics! <!-- omit in toc -->
Since the last release, we've added metrics for:
* [Holepunching](https://github.com/libp2p/go-libp2p/pull/2246)
* Smart Dialing (see above)
### WebTransport <!-- omit in toc -->
* [#2251](https://github.com/libp2p/go-libp2p/pull/2251): Infer public WebTransport address from `quic-v1` addresses if both transports are using the same port for both quic-v1 and WebTransport addresses.
* [#2271](https://github.com/libp2p/go-libp2p/pull/2271): Only add certificate hashes to WebTransport mulitaddress if listening on WebTransport
## Housekeeping updates <!-- omit in toc -->
* Identify
* [#2303](https://github.com/libp2p/go-libp2p/pull/2303): Don't send default protocol version
* Prevent polluting PeerStore with local addrs
* [#2325](https://github.com/libp2p/go-libp2p/pull/2325): Don't save signed peer records
* [#2300](https://github.com/libp2p/go-libp2p/pull/2300): Filter received addresses based on the node's remote address
* WebSocket
* [#2280](https://github.com/libp2p/go-libp2p/pull/2280): Reverted back to the Gorilla library for WebSocket
* NAT
* [#2248](https://github.com/libp2p/go-libp2p/pull/2248): Move NAT mapping logic out of the host
## 🐞 Bugfixes <!-- omit in toc -->
* Identify
* [Reject signed peer records on peer ID mismatch](https://github.com/libp2p/go-libp2p/commit/8d771355b41297623e05b04a865d029a2522a074)
* [#2299](https://github.com/libp2p/go-libp2p/pull/2299): Avoid spuriously pushing updates
* Swarm
* [#2322](https://github.com/libp2p/go-libp2p/pull/2322): Dedup addresses to dial
* [#2284](https://github.com/libp2p/go-libp2p/pull/2284): Change maps with multiaddress keys to use strings
* QUIC
* [#2262](https://github.com/libp2p/go-libp2p/pull/2262): Prioritize listen connections for reuse
* [#2276](https://github.com/libp2p/go-libp2p/pull/2276): Don't panic when quic-go's accept call errors
* [#2263](https://github.com/libp2p/go-libp2p/pull/2263): Fix race condition when generating random holepunch packet
**Full Changelog**: https://github.com/libp2p/go-libp2p/compare/v0.27.0...v0.28.0
# [v0.27.0](https://github.com/libp2p/go-libp2p/releases/tag/v0.27.0)
### Breaking Changes <!-- omit in toc -->
* The `LocalPrivateKey` method was removed from the `network.Conn` interface. [#2144](https://github.com/libp2p/go-libp2p/pull/2144)
## 🔦 Highlights <!-- omit in toc -->
### Additional metrics <!-- omit in toc -->
Since the last release, we've added metrics for:
* [Relay Service](https://github.com/libp2p/go-libp2p/pull/2154): RequestStatus, RequestCounts, RejectionReasons for Reservation and Connection Requests,
ConnectionDuration, BytesTransferred, Relay Service Status.
* [Autorelay](https://github.com/libp2p/go-libp2p/pull/2185): relay finder status, reservation request outcomes, current reservations, candidate circuit v2 support, current candidates, relay addresses updated, num relay address, and scheduled work times
## 🐞 Bugfixes <!-- omit in toc -->
* autonat: don't change status on dial request refused [2225](https://github.com/libp2p/go-libp2p/pull/2225)
* relaysvc: fix flaky TestReachabilityChangeEvent [2215](https://github.com/libp2p/go-libp2p/pull/2215)
* basichost: prevent duplicate dials [2196](https://github.com/libp2p/go-libp2p/pull/2196)
* websocket: don't set a WSS multiaddr for accepted unencrypted conns [2199](https://github.com/libp2p/go-libp2p/pull/2199)
* identify: Fix IdentifyWait when Connected events happen out of order [2173](https://github.com/libp2p/go-libp2p/pull/2173)
* circuitv2: cleanup relay service properly [2164](https://github.com/libp2p/go-libp2p/pull/2164)
**Full Changelog**: https://github.com/libp2p/go-libp2p/compare/v0.26.4...v0.27.0
# [v0.26.4](https://github.com/libp2p/go-libp2p/releases/tag/v0.26.4)
This patch release fixes a busy-looping happening inside AutoRelay on private nodes, see [2208](https://github.com/libp2p/go-libp2p/pull/2208).
**Full Changelog**: https://github.com/libp2p/go-libp2p/compare/v0.26.0...v0.26.4
# [v0.26.3](https://github.com/libp2p/go-libp2p/releases/tag/v0.26.3)
* rcmgr: fix JSON marshalling of ResourceManagerStat peer map [2156](https://github.com/libp2p/go-libp2p/pull/2156)
* websocket: Don't limit message sizes in the websocket reader [2193](https://github.com/libp2p/go-libp2p/pull/2193)
**Full Changelog**: https://github.com/libp2p/go-libp2p/compare/v0.26.0...v0.26.3
# [v0.26.2](https://github.com/libp2p/go-libp2p/releases/tag/v0.26.2)
This patch release fixes two bugs:
* A panic in WebTransport: https://github.com/quic-go/webtransport-go/releases/tag/v0.5.2
* Incorrect accounting of accepted connections in the swarm metrics: [#2147](https://github.com/libp2p/go-libp2p/pull/2147)
**Full Changelog**: https://github.com/libp2p/go-libp2p/compare/v0.26.0...v0.26.2
# v0.26.1
This version was retracted due to errors when publishing the release.
# [v0.26.0](https://github.com/libp2p/go-libp2p/releases/tag/v0.26.0)
## 🔦 Highlights <!-- omit in toc -->
### Circuit Relay Changes <!-- omit in toc -->
#### [Removed Circuit Relay v1](https://github.com/libp2p/go-libp2p/pull/2107) <!-- omit in toc -->
We've decided to remove support for Circuit Relay v1 in this release. v1 Relays have been retired a few months ago. Notably, running the Relay v1 protocol was expensive and resulted in only a small number of nodes in the network. Users had to either manually configure these nodes as static relays, or discover them from the DHT.
Furthermore, rust-libp2p [has dropped support](https://github.com/libp2p/rust-libp2p/pull/2549) and js-libp2p [is dropping support](https://github.com/libp2p/js-libp2p/pull/1533) for Relay v1.
Support for Relay v2 was first added in [late 2021 in v0.16.0](https://github.com/libp2p/go-libp2p/releases/tag/v0.16.0). With Circuit Relay v2 it became cheap to run (limited) relays. Public nodes also started the relay service by default. There's now a massive number of Relay v2 nodes on the IPFS network, and they don't advertise their service to the DHT any more. Because there's now so many of these nodes, connecting to just a small number of nodes (e.g. by joining the DHT), a node is statistically guaranteed to connect to some relays.
#### [Unlimited Relay v2](https://github.com/libp2p/go-libp2p/pull/2125) <!-- omit in toc -->
In conjunction with removing relay v1, we also added an option to Circuit Relay v2 to disable limits.
This done by enabling `WithInfiniteLimits`. When enabled this allows for users to have a drop in replacement for Relay v1 with Relay v2.
### Additional metrics <!-- omit in toc -->
Since the last release, we've added additional metrics to different components.
Metrics were added to:
* [AutoNat](https://github.com/libp2p/go-libp2p/pull/2086): Current Reachability Status and Confidence, Client and Server DialResponses, Server DialRejections. The dashboard is [available here](https://github.com/libp2p/go-libp2p/blob/master/dashboards/autonat/autonat.json).
* Swarm:
- [Early Muxer Selection](https://github.com/libp2p/go-libp2p/pull/2119): Added early_muxer label indicating whether a connection was established using early muxer selection.
- [IP Version](https://github.com/libp2p/go-libp2p/pull/2114): Added ip_version label to connection metrics
* Identify:
- Metrics for Identify, IdentifyPush, PushesTriggered (https://github.com/libp2p/go-libp2p/pull/2069)
- Address Count, Protocol Count, Connection IDPush Support (https://github.com/libp2p/go-libp2p/pull/2126)
We also migrated the metric dashboards to a top-level [dashboards](https://github.com/libp2p/go-libp2p/tree/master/dashboards) directory.
## 🐞 Bugfixes <!-- omit in toc -->
### AutoNat <!-- omit in toc -->
* [Fixed a bug](https://github.com/libp2p/go-libp2p/issues/2091) where AutoNat would emit events when the observed address has changed even though the node reachability hadn't changed.
### Relay Manager <!-- omit in toc -->
* [Fixed a bug](https://github.com/libp2p/go-libp2p/pull/2093) where the Relay Manager started a new relay even though the previous reachability was `Public` or if a relay already existed.
### [Stop sending detailed error messages on closing QUIC connections](https://github.com/libp2p/go-libp2p/pull/2112) <!-- omit in toc -->
Users reported seeing confusing error messages and could not determine the root cause or if the error was from a local or remote peer:
```{12D... Application error 0x0: conn-27571160: system: cannot reserve inbound connection: resource limit exceeded}```
This error occurred when a connection had been made with a remote peer but the remote peer dropped the connection (due to it exceeding limits).
This was actually an `Application error` emitted by `quic-go` and it was a bug in go-libp2p that we sent the whole message.
For now, we decided to stop sending this confusing error message. In the future, we will report such errors via [error codes](https://github.com/libp2p/specs/issues/479).
**Full Changelog**: https://github.com/libp2p/go-libp2p/compare/v0.25.1...v0.26.0
# [v0.25.1](https://github.com/libp2p/go-libp2p/releases/tag/v0.25.1)
Fix some test-utils used by https://github.com/libp2p/go-libp2p-kad-dht
* mocknet: Start host in mocknet by @MarcoPolo in https://github.com/libp2p/go-libp2p/pull/2078
* chore: update go-multistream by @MarcoPolo in https://github.com/libp2p/go-libp2p/pull/2081
**Full Changelog**: https://github.com/libp2p/go-libp2p/compare/v0.25.0...v0.25.1
# [v0.25.0](https://github.com/libp2p/go-libp2p/releases/tag/v0.25.0)
## 🔦 Highlights <!-- omit in toc -->
### Metrics <!-- omit in toc -->
We've started instrumenting the entire stack. In this release, we're adding metrics for:
* the swarm: tracking incoming and outgoing connections, transports, security protocols and stream multiplexers in use: (https://github.com/libp2p/go-libp2p/blob/master/dashboards/swarm/swarm.json)
* the event bus: tracking how different events are propagated through the stack and to external consumers (https://github.com/libp2p/go-libp2p/blob/master/dashboards/eventbus/eventbus.json)
Our metrics effort is still ongoing, see https://github.com/libp2p/go-libp2p/issues/1356 for progress. We'll add metrics and dashboards for more libp2p components in a future release.
### Switching to Google's official Protobuf compiler <!-- omit in toc -->
So far, we were using GoGo Protobuf to compile our Protobuf definitions to Go code. However, this library was deprecated in October last year: https://twitter.com/awalterschulze/status/1584553056100057088. We [benchmarked](https://github.com/libp2p/go-libp2p/issues/1976#issuecomment-1371527732) serialization and deserialization, and found that it's (only) 20% slower than GoGo. Since the vast majority of go-libp2p's CPU time is spent in code paths other than Protobuf handling, switching to the official compiler seemed like a worthwhile tradeoff.
### Removal of OpenSSL <!-- omit in toc -->
Before this release, go-libp2p had an option to use OpenSSL bindings for certain cryptographic primitives, mostly to speed up the generation of signatures and their verification. When building go-libp2p using `go build`, we'd use the standard library crypto packages. OpenSSL was only used when passing in a build tag: `go build -tags openssl`.
Maintaining our own fork of the long unmaintained [go-openssl package](https://github.com/libp2p/go-openssl) has proven to place a larger than expected maintenance burden on the libp2p stewards, and when we recently discovered a range of new bugs ([this](https://github.com/libp2p/go-openssl/issues/38) and [this](https://github.com/libp2p/go-libp2p/issues/1892) and [this](https://github.com/libp2p/go-libp2p/issues/1951)), we decided to re-evaluate if this code path is really worth it. The results surprised us, it turns out that:
* The Go standard library is faster than OpenSSL for all key types that are not RSA.
* Verifying RSA signatures is as fast as Ed25519 signatures using the Go standard library, and even faster in OpenSSL.
* Generating RSA signatures is painfully slow, both using Go standard library crypto and using OpenSSL (but even slower using Go standard library).
Now the good news is, that if your node is not using an RSA key, it will never create any RSA signatures (it might need to verify them though, when it connects to a node that uses RSA keys). If you're concerned about CPU performance, it's a good idea to avoid RSA keys (the same applies to bandwidth, RSA keys are huge!). Even for nodes using RSA keys, it turns out that generating the signatures is not a significant part of their CPU load, as verified by profiling one of Kubo's bootstrap nodes.
We therefore concluded that it's safe to drop this code path altogether, and thereby reduce our maintenance burden.
### New Resource Manager types <!-- omit in toc -->
* Introduces a new type `LimitVal` which can explicitly specify "use default", "unlimited", "block all", as well as any positive number. The zero value of `LimitVal` (the value when you create the object in Go) is "Use default".
* The JSON marshalling of this is straightforward.
* Introduces a new `ResourceLimits` type which uses `LimitVal` instead of ints so it can encode the above for the resources.
* Changes `LimitConfig` to `PartialLimitConfig` and uses `ResourceLimits`. This along with the marshalling changes means you can now marshal the fact that some resource limit is set to block all.
* Because the default is to use the defaults, this avoids the footgun of initializing the resource manager with 0 limits (that would block everything).
In general, you can go from a resource config with defaults to a concrete one with `.Build()`. e.g. `ResourceLimits.Build() => BaseLimit`, `PartialLimitConfig.Build() => ConcreteLimitConfig`, `LimitVal.Build() => int`. See PR #2000 for more details.
If you're using the defaults for the resource manager, there should be no changes needed.
### Other Breaking Changes <!-- omit in toc -->
We've cleaned up our API to consistently use `protocol.ID` for libp2p and application protocols. Specifically, this means that the peer store now uses `protocol.ID`s, and the host's `SetStreamHandler` as well.
## What's Changed <!-- omit in toc -->
* chore: use generic LRU cache by @muXxer in https://github.com/libp2p/go-libp2p/pull/1980
* core/crypto: drop all OpenSSL code paths by @marten-seemann in https://github.com/libp2p/go-libp2p/pull/1953
* add WebTransport to the list of default transports by @marten-seemann in https://github.com/libp2p/go-libp2p/pull/1915
* identify: remove old code targeting Go 1.17 by @marten-seemann in https://github.com/libp2p/go-libp2p/pull/1964
* core: remove introspection package by @marten-seemann in https://github.com/libp2p/go-libp2p/pull/1978
* identify: remove support for Identify Delta by @marten-seemann in https://github.com/libp2p/go-libp2p/pull/1975
* roadmap: remove optimizations of the TCP-based handshake by @marten-seemann in https://github.com/libp2p/go-libp2p/pull/1959
* circuitv2: correctly set the transport in the ConnectionState by @marten-seemann in https://github.com/libp2p/go-libp2p/pull/1972
* switch to Google's Protobuf library, make protobufs compile with go generate by @marten-seemann in https://github.com/libp2p/go-libp2p/pull/1979
* ci: run go generate as part of the go-check workflow by @marten-seemann in https://github.com/libp2p/go-libp2p/pull/1986
* ci: use GitHub token to install protoc by @marten-seemann in https://github.com/libp2p/go-libp2p/pull/1996
* feat: add some users to the readme by @p-shahi in https://github.com/libp2p/go-libp2p/pull/1981
* CI: Fast multidimensional Interop tests by @MarcoPolo in https://github.com/libp2p/go-libp2p/pull/1991
* Fix: Ignore zero values when marshalling Limits. by @ajnavarro in https://github.com/libp2p/go-libp2p/pull/1998
* feat: add ci flakiness score to readme by @MarcoPolo in https://github.com/libp2p/go-libp2p/pull/2002
* peerstore: make it possible to use an empty peer ID by @marten-seemann in https://github.com/libp2p/go-libp2p/pull/2006
* feat: rcmgr: Export resource manager errors by @MarcoPolo in https://github.com/libp2p/go-libp2p/pull/2008
* feat: ci test-plans: Parse test timeout parameter for interop test by @MarcoPolo in https://github.com/libp2p/go-libp2p/pull/2014
* Clean addresses with peer id before adding to addrbook by @sukunrt in https://github.com/libp2p/go-libp2p/pull/2007
* Expose muxer ids by @aschmahmann in https://github.com/libp2p/go-libp2p/pull/2012
* swarm: add a basic metrics tracer by @marten-seemann in https://github.com/libp2p/go-libp2p/pull/1973
* consistently use protocol.ID instead of strings by @sukunrt in https://github.com/libp2p/go-libp2p/pull/2004
* swarm metrics: fix datasource for dashboard by @MarcoPolo in https://github.com/libp2p/go-libp2p/pull/2024
* chore: remove textual roadmap in favor for Starmap by @p-shahi in https://github.com/libp2p/go-libp2p/pull/2036
* rcmgr: *: Always close connscope by @MarcoPolo in https://github.com/libp2p/go-libp2p/pull/2037
* chore: remove license files from the eventbus package by @marten-seemann in https://github.com/libp2p/go-libp2p/pull/2042
* Migrate to test-plan composite action by @thomaseizinger in https://github.com/libp2p/go-libp2p/pull/2039
* use quic-go and webtransport-go from quic-go organization by @marten-seemann in https://github.com/libp2p/go-libp2p/pull/2040
* holepunch: fix flaky test by not removing holepunch protocol handler by @marten-seemann in https://github.com/libp2p/go-libp2p/pull/1948
* quic / webtransport: extend test to test dialing a draft-29 and a v1 by @marten-seemann in https://github.com/libp2p/go-libp2p/pull/1957
* p2p/test: add test for EvtLocalAddressesUpdated event by @marten-seemann in https://github.com/libp2p/go-libp2p/pull/2016
* quic, tcp: only register Prometheus counters when metrics are enabled by @marten-seemann in https://github.com/libp2p/go-libp2p/pull/1971
* p2p/test: fix flaky notification test by @marten-seemann in https://github.com/libp2p/go-libp2p/pull/2051
* quic: disable sending of Version Negotiation packets by @marten-seemann in https://github.com/libp2p/go-libp2p/pull/2015
* eventbus: add metrics by @sukunrt in https://github.com/libp2p/go-libp2p/pull/2038
* metrics: use a single slice pool for all metrics tracer by @marten-seemann in https://github.com/libp2p/go-libp2p/pull/2054
* webtransport: tidy up some test output by @MarcoPolo in https://github.com/libp2p/go-libp2p/pull/2053
* set names for eventbus event subscriptions by @marten-seemann in https://github.com/libp2p/go-libp2p/pull/2057
* autorelay: Split libp2p.EnableAutoRelay into 2 functions by @sukunrt in https://github.com/libp2p/go-libp2p/pull/2022
* rcmgr: Use prometheus SDK for rcmgr metrics by @MarcoPolo in https://github.com/libp2p/go-libp2p/pull/2044
* websocket: Replace gorilla websocket transport with nhooyr websocket transport by @MarcoPolo in https://github.com/libp2p/go-libp2p/pull/1982
* rcmgr: add libp2p prefix to all metrics by @marten-seemann in https://github.com/libp2p/go-libp2p/pull/2063
* chore: git-ignore various flavors of qlog files by @marten-seemann in https://github.com/libp2p/go-libp2p/pull/2064
* interop: Update interop test to match spec by @MarcoPolo in https://github.com/libp2p/go-libp2p/pull/2049
* chore: update webtransport-go to v0.5.1 by @marten-seemann in https://github.com/libp2p/go-libp2p/pull/2072
* identify: refactor sending of Identify pushes by @marten-seemann in https://github.com/libp2p/go-libp2p/pull/1984
* feat!: rcmgr: Change LimitConfig to use LimitVal type by @MarcoPolo in https://github.com/libp2p/go-libp2p/pull/2000
* p2p/test/quic: use contexts with a timeout for Connect calls by @marten-seemann in https://github.com/libp2p/go-libp2p/pull/2070
* identify: add some basic metrics by @marten-seemann in https://github.com/libp2p/go-libp2p/pull/2069
* chore: Release v0.25.0 by @MarcoPolo in https://github.com/libp2p/go-libp2p/pull/2077
## New Contributors <!-- omit in toc -->
* @muXxer made their first contribution in https://github.com/libp2p/go-libp2p/pull/1980
* @ajnavarro made their first contribution in https://github.com/libp2p/go-libp2p/pull/1998
* @sukunrt made their first contribution in https://github.com/libp2p/go-libp2p/pull/2007
* @thomaseizinger made their first contribution in https://github.com/libp2p/go-libp2p/pull/2039
**Full Changelog**: https://github.com/libp2p/go-libp2p/compare/v0.24.2...v0.25.0

21
go-libp2p/LICENSE Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014 Juan Batiz-Benet
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

100
go-libp2p/README.md Normal file
View File

@ -0,0 +1,100 @@
<h1 align="center">
<a href="libp2p.io"><img width="250" src="https://github.com/libp2p/libp2p/blob/master/logo/black-bg-2.png?raw=true" alt="libp2p hex logo" /></a>
</h1>
<h3 align="center">The Go implementation of the libp2p Networking Stack.</h3>
<p align="center">
<a href="http://protocol.ai"><img src="https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square" /></a>
<a href="http://libp2p.io/"><img src="https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square" /></a>
<a href="https://pkg.go.dev/github.com/libp2p/go-libp2p"><img src="https://pkg.go.dev/badge/github.com/libp2p/go-libp2p.svg" alt="Go Reference"></a>
<a href="https://discuss.libp2p.io"><img src="https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg"/></a>
<a href="https://marcopolo.github.io/FlakyTests/"><img src="https://marcopolo.github.io/FlakyTests/current-score.svg"/></a>
</p>
# Table of Contents <!-- omit in toc -->
- [Background](#background)
- [Roadmap](#roadmap)
- [Usage](#usage)
- [Examples](#examples)
- [Contribute](#contribute)
- [Supported Go Versions](#supported-go-versions)
- [Notable Users](#notable-users)
# Background
[libp2p](https://github.com/libp2p/specs) is a networking stack and library modularized out of [The IPFS Project](https://github.com/ipfs/ipfs), and bundled separately for other tools to use.
>
libp2p is the product of a long, and arduous quest of understanding -- a deep dive into the internet's network stack, and plentiful peer-to-peer protocols from the past. Building large-scale peer-to-peer systems has been complex and difficult in the last 15 years, and libp2p is a way to fix that. It is a "network stack" -- a protocol suite -- that cleanly separates concerns, and enables sophisticated applications to only use the protocols they absolutely need, without giving up interoperability and upgradeability. libp2p grew out of IPFS, but it is built so that lots of people can use it, for lots of different projects.
To learn more, check out the following resources:
- [**Our documentation**](https://docs.libp2p.io)
- [**Our community discussion forum**](https://discuss.libp2p.io)
- [**The libp2p Specification**](https://github.com/libp2p/specs)
- [**js-libp2p implementation**](https://github.com/libp2p/js-libp2p)
- [**rust-libp2p implementation**](https://github.com/libp2p/rust-libp2p)
# Roadmap
Our roadmap for go-libp2p can be found here: https://github.com/libp2p/go-libp2p/blob/master/ROADMAP.md
This document represents current projects the go-libp2p team is focused on and provides an estimation of completion targets. It is a complementary roadmap to the overarching libp2p project roadmap: https://github.com/libp2p/specs/blob/master/ROADMAP.md
# Usage
This repository (`go-libp2p`) serves as the entrypoint to the universe of packages that compose the Go implementation of the libp2p stack.
You can start using go-libp2p in your Go application simply by adding imports from our repos, e.g.:
```go
import "github.com/libp2p/go-libp2p"
```
## Examples
Examples can be found in the [examples folder](examples).
# Contribute
go-libp2p is MIT-licensed open source software. We welcome contributions big and small! Take a look at the [community contributing notes](https://github.com/ipfs/community/blob/master/CONTRIBUTING.md). Please make sure to check the [issues](https://github.com/libp2p/go-libp2p/issues). Search the closed ones before reporting things, and help us with the open ones.
Guidelines:
- read the [libp2p spec](https://github.com/libp2p/specs)
- ask questions or talk about things in our [discussion forums](https://discuss.libp2p.io), or open an [issue](https://github.com/libp2p/go-libp2p/issues) for bug reports, or #libp2p-implementers on [Filecoin slack](https://filecoin.io/slack).
- ensure you are able to contribute (no legal issues please -- we use the DCO)
- get in touch with @libp2p/go-libp2p-maintainers about how best to contribute
- have fun!
There's a few things you can do right now to help out:
- Go through the modules below and **check out existing issues**. This would be especially useful for modules in active development. Some knowledge of IPFS/libp2p may be required, as well as the infrastructure behind it - for instance, you may need to read up on p2p and more complex operations like muxing to be able to help technically.
- **Perform code reviews**.
- **Add tests**. There can never be enough tests.
## Supported Go Versions
We test against and support the two most recent major releases of Go. This is
informed by Go's own [security policy](https://go.dev/security).
# Notable Users
Some notable users of go-libp2p are:
- [Kubo](https://github.com/ipfs/kubo) - The original Go implementation of IPFS
- [Lotus](https://github.com/filecoin-project/lotus) - An implementation of the Filecoin protocol
- [Drand](https://github.com/drand/drand) - A distributed random beacon daemon
- [Prysm](https://github.com/prysmaticlabs/prysm) - An Ethereum Beacon Chain consensus client built by [Prysmatic Labs](https://prysmaticlabs.com/)
- [Berty](https://github.com/berty/berty) - An open, secure, offline-first, peer-to-peer and zero trust messaging app.
- [Wasp](https://github.com/iotaledger/wasp) - A node that runs IOTA Smart Contracts built by the [IOTA Foundation](https://www.iota.org/)
- [Mina](https://github.com/minaprotocol/mina) - A lightweight, constant-sized blockchain that runs zero-knowledge smart contracts
- [Polygon Edge](https://github.com/0xPolygon/polygon-edge) - A modular, extensible framework for building Ethereum compatible networks
- [Celestia Node](https://github.com/celestiaorg/celestia-node) - The Go implementation of Celestia's data availability nodes
- [Status go](https://github.com/status-im/status-go) - Status bindings for go-ethereum, built by [Status.im](https://status.im/)
- [Flow](https://github.com/onflow/flow-go) - A blockchain built to support games, apps, and digital assets built by [Dapper Labs](https://www.dapperlabs.com/)
- [Swarm Bee](https://github.com/ethersphere/bee) - A client for connecting to the [Swarm network](https://www.ethswarm.org/)
- [Elrond Go](https://github.com/multiversx/mx-chain-go) - The Go implementation of the the Elrond network protocol
- [Sonr](https://github.com/sonr-io/sonr) - A platform to integrate DID Documents, WebAuthn, and IPFS and manage digital identity and assets.
- [EdgeVPN](https://github.com/mudler/edgevpn) - A decentralized, immutable, portable VPN and reverse proxy over p2p.
- [Kairos](https://github.com/kairos-io/kairos) - A Kubernetes-focused, Cloud Native Linux meta-distribution.
- [Oasis Core](https://github.com/oasisprotocol/oasis-core) - The consensus and runtime layers of the [Oasis protocol](https://oasisprotocol.org/).
Please open a pull request if you want your project (min. 250 GitHub stars) to be added here.

5
go-libp2p/ROADMAP.md Normal file
View File

@ -0,0 +1,5 @@
# go-libp2p roadmap Q422/Q123
Please see our roadmap in [Starmap](https://starmap.site/roadmap/github.com/libp2p/go-libp2p/issues/1806#simple)
Please add any feedback or questions in: https://github.com/libp2p/go-libp2p/issues/1806

20
go-libp2p/SECURITY.md Normal file
View File

@ -0,0 +1,20 @@
# Security Policy
go-libp2p is still in development. This means that there may be problems in our protocols,
or there may be mistakes in our implementations.
We take security vulnerabilities very seriously. If you discover a security issue,
please bring it to our attention right away!
## Reporting a Vulnerability
If you find a vulnerability that may affect live deployments -- for example, by exposing
a remote execution exploit -- please [**report privately**](https://github.com/libp2p/go-libp2p/security/advisories/new).
Please **DO NOT file a public issue**.
If the issue is an implementation weakness that cannot be immediately exploited or
something not yet deployed, just discuss it openly.
If you need assistance, please reach out to [security@libp2p.io](mailto:security@libp2p.io).
## Reporting a non security bug
For non-security bugs, please simply file a GitHub [issue](https://github.com/libp2p/go-libp2p/issues/new).

483
go-libp2p/config/config.go Normal file
View File

@ -0,0 +1,483 @@
package config
import (
"crypto/rand"
"errors"
"fmt"
"time"
"github.com/libp2p/go-libp2p/core/connmgr"
"github.com/libp2p/go-libp2p/core/crypto"
"github.com/libp2p/go-libp2p/core/event"
"github.com/libp2p/go-libp2p/core/host"
"github.com/libp2p/go-libp2p/core/metrics"
"github.com/libp2p/go-libp2p/core/network"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-libp2p/core/peerstore"
"github.com/libp2p/go-libp2p/core/pnet"
"github.com/libp2p/go-libp2p/core/protocol"
"github.com/libp2p/go-libp2p/core/routing"
"github.com/libp2p/go-libp2p/core/sec"
"github.com/libp2p/go-libp2p/core/sec/insecure"
"github.com/libp2p/go-libp2p/core/transport"
"github.com/libp2p/go-libp2p/p2p/host/autonat"
"github.com/libp2p/go-libp2p/p2p/host/autorelay"
bhost "github.com/libp2p/go-libp2p/p2p/host/basic"
blankhost "github.com/libp2p/go-libp2p/p2p/host/blank"
"github.com/libp2p/go-libp2p/p2p/host/eventbus"
"github.com/libp2p/go-libp2p/p2p/host/peerstore/pstoremem"
rcmgr "github.com/libp2p/go-libp2p/p2p/host/resource-manager"
routed "github.com/libp2p/go-libp2p/p2p/host/routed"
"github.com/libp2p/go-libp2p/p2p/net/swarm"
tptu "github.com/libp2p/go-libp2p/p2p/net/upgrader"
circuitv2 "github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/client"
relayv2 "github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/relay"
"github.com/libp2p/go-libp2p/p2p/protocol/holepunch"
"github.com/libp2p/go-libp2p/p2p/transport/quicreuse"
"github.com/prometheus/client_golang/prometheus"
ma "github.com/multiformats/go-multiaddr"
madns "github.com/multiformats/go-multiaddr-dns"
"go.uber.org/fx"
"go.uber.org/fx/fxevent"
)
// AddrsFactory is a function that takes a set of multiaddrs we're listening on and
// returns the set of multiaddrs we should advertise to the network.
type AddrsFactory = bhost.AddrsFactory
// NATManagerC is a NATManager constructor.
type NATManagerC func(network.Network) bhost.NATManager
type RoutingC func(host.Host) (routing.PeerRouting, error)
// AutoNATConfig defines the AutoNAT behavior for the libp2p host.
type AutoNATConfig struct {
ForceReachability *network.Reachability
EnableService bool
ThrottleGlobalLimit int
ThrottlePeerLimit int
ThrottleInterval time.Duration
}
type Security struct {
ID protocol.ID
Constructor interface{}
}
// Config describes a set of settings for a libp2p node
//
// This is *not* a stable interface. Use the options defined in the root
// package.
type Config struct {
// UserAgent is the identifier this node will send to other peers when
// identifying itself, e.g. via the identify protocol.
//
// Set it via the UserAgent option function.
UserAgent string
// ProtocolVersion is the protocol version that identifies the family
// of protocols used by the peer in the Identify protocol. It is set
// using the [ProtocolVersion] option.
ProtocolVersion string
PeerKey crypto.PrivKey
QUICReuse []fx.Option
Transports []fx.Option
Muxers []tptu.StreamMuxer
SecurityTransports []Security
Insecure bool
PSK pnet.PSK
DialTimeout time.Duration
RelayCustom bool
Relay bool // should the relay transport be used
EnableRelayService bool // should we run a circuitv2 relay (if publicly reachable)
RelayServiceOpts []relayv2.Option
ListenAddrs []ma.Multiaddr
AddrsFactory bhost.AddrsFactory
ConnectionGater connmgr.ConnectionGater
ConnManager connmgr.ConnManager
ResourceManager network.ResourceManager
NATManager NATManagerC
Peerstore peerstore.Peerstore
Reporter metrics.Reporter
MultiaddrResolver *madns.Resolver
DisablePing bool
Routing RoutingC
EnableAutoRelay bool
AutoRelayOpts []autorelay.Option
AutoNATConfig
EnableHolePunching bool
HolePunchingOptions []holepunch.Option
DisableMetrics bool
PrometheusRegisterer prometheus.Registerer
DialRanker network.DialRanker
SwarmOpts []swarm.Option
}
func (cfg *Config) makeSwarm(eventBus event.Bus, enableMetrics bool) (*swarm.Swarm, error) {
if cfg.Peerstore == nil {
return nil, fmt.Errorf("no peerstore specified")
}
// Check this early. Prevents us from even *starting* without verifying this.
if pnet.ForcePrivateNetwork && len(cfg.PSK) == 0 {
log.Error("tried to create a libp2p node with no Private" +
" Network Protector but usage of Private Networks" +
" is forced by the environment")
// Note: This is *also* checked the upgrader itself, so it'll be
// enforced even *if* you don't use the libp2p constructor.
return nil, pnet.ErrNotInPrivateNetwork
}
if cfg.PeerKey == nil {
return nil, fmt.Errorf("no peer key specified")
}
// Obtain Peer ID from public key
pid, err := peer.IDFromPublicKey(cfg.PeerKey.GetPublic())
if err != nil {
return nil, err
}
if err := cfg.Peerstore.AddPrivKey(pid, cfg.PeerKey); err != nil {
return nil, err
}
if err := cfg.Peerstore.AddPubKey(pid, cfg.PeerKey.GetPublic()); err != nil {
return nil, err
}
opts := cfg.SwarmOpts
if cfg.Reporter != nil {
opts = append(opts, swarm.WithMetrics(cfg.Reporter))
}
if cfg.ConnectionGater != nil {
opts = append(opts, swarm.WithConnectionGater(cfg.ConnectionGater))
}
if cfg.DialTimeout != 0 {
opts = append(opts, swarm.WithDialTimeout(cfg.DialTimeout))
}
if cfg.ResourceManager != nil {
opts = append(opts, swarm.WithResourceManager(cfg.ResourceManager))
}
if cfg.MultiaddrResolver != nil {
opts = append(opts, swarm.WithMultiaddrResolver(cfg.MultiaddrResolver))
}
if cfg.DialRanker != nil {
opts = append(opts, swarm.WithDialRanker(cfg.DialRanker))
}
if enableMetrics {
opts = append(opts,
swarm.WithMetricsTracer(swarm.NewMetricsTracer(swarm.WithRegisterer(cfg.PrometheusRegisterer))))
}
// TODO: Make the swarm implementation configurable.
return swarm.NewSwarm(pid, cfg.Peerstore, eventBus, opts...)
}
func (cfg *Config) addTransports(h host.Host) error {
swrm, ok := h.Network().(transport.TransportNetwork)
if !ok {
// Should probably skip this if no transports.
return fmt.Errorf("swarm does not support transports")
}
fxopts := []fx.Option{
fx.WithLogger(func() fxevent.Logger { return getFXLogger() }),
fx.Provide(fx.Annotate(tptu.New, fx.ParamTags(`name:"security"`))),
fx.Supply(cfg.Muxers),
fx.Supply(h.ID()),
fx.Provide(func() host.Host { return h }),
fx.Provide(func() crypto.PrivKey { return h.Peerstore().PrivKey(h.ID()) }),
fx.Provide(func() connmgr.ConnectionGater { return cfg.ConnectionGater }),
fx.Provide(func() pnet.PSK { return cfg.PSK }),
fx.Provide(func() network.ResourceManager { return cfg.ResourceManager }),
fx.Provide(func() *madns.Resolver { return cfg.MultiaddrResolver }),
}
fxopts = append(fxopts, cfg.Transports...)
if cfg.Insecure {
fxopts = append(fxopts,
fx.Provide(
fx.Annotate(
func(id peer.ID, priv crypto.PrivKey) []sec.SecureTransport {
return []sec.SecureTransport{insecure.NewWithIdentity(insecure.ID, id, priv)}
},
fx.ResultTags(`name:"security"`),
),
),
)
} else {
// fx groups are unordered, but we need to preserve the order of the security transports
// First of all, we construct the security transports that are needed,
// and save them to a group call security_unordered.
for _, s := range cfg.SecurityTransports {
fxName := fmt.Sprintf(`name:"security_%s"`, s.ID)
fxopts = append(fxopts, fx.Supply(fx.Annotate(s.ID, fx.ResultTags(fxName))))
fxopts = append(fxopts,
fx.Provide(fx.Annotate(
s.Constructor,
fx.ParamTags(fxName),
fx.As(new(sec.SecureTransport)),
fx.ResultTags(`group:"security_unordered"`),
)),
)
}
// Then we consume the group security_unordered, and order them by the user's preference.
fxopts = append(fxopts, fx.Provide(
fx.Annotate(
func(secs []sec.SecureTransport) ([]sec.SecureTransport, error) {
if len(secs) != len(cfg.SecurityTransports) {
return nil, errors.New("inconsistent length for security transports")
}
t := make([]sec.SecureTransport, 0, len(secs))
for _, s := range cfg.SecurityTransports {
for _, st := range secs {
if s.ID != st.ID() {
continue
}
t = append(t, st)
}
}
return t, nil
},
fx.ParamTags(`group:"security_unordered"`),
fx.ResultTags(`name:"security"`),
)))
}
fxopts = append(fxopts, fx.Provide(PrivKeyToStatelessResetKey))
if cfg.QUICReuse != nil {
fxopts = append(fxopts, cfg.QUICReuse...)
} else {
fxopts = append(fxopts, fx.Provide(quicreuse.NewConnManager)) // TODO: close the ConnManager when shutting down the node
}
fxopts = append(fxopts, fx.Invoke(
fx.Annotate(
func(tpts []transport.Transport) error {
for _, t := range tpts {
if err := swrm.AddTransport(t); err != nil {
return err
}
}
return nil
},
fx.ParamTags(`group:"transport"`),
)),
)
if cfg.Relay {
fxopts = append(fxopts, fx.Invoke(circuitv2.AddTransport))
}
app := fx.New(fxopts...)
if err := app.Err(); err != nil {
h.Close()
return err
}
return nil
}
// NewNode constructs a new libp2p Host from the Config.
//
// This function consumes the config. Do not reuse it (really!).
func (cfg *Config) NewNode() (host.Host, error) {
eventBus := eventbus.NewBus(eventbus.WithMetricsTracer(eventbus.NewMetricsTracer(eventbus.WithRegisterer(cfg.PrometheusRegisterer))))
swrm, err := cfg.makeSwarm(eventBus, !cfg.DisableMetrics)
if err != nil {
return nil, err
}
if !cfg.DisableMetrics {
rcmgr.MustRegisterWith(cfg.PrometheusRegisterer)
}
h, err := bhost.NewHost(swrm, &bhost.HostOpts{
EventBus: eventBus,
ConnManager: cfg.ConnManager,
AddrsFactory: cfg.AddrsFactory,
NATManager: cfg.NATManager,
EnablePing: !cfg.DisablePing,
UserAgent: cfg.UserAgent,
ProtocolVersion: cfg.ProtocolVersion,
EnableHolePunching: cfg.EnableHolePunching,
HolePunchingOptions: cfg.HolePunchingOptions,
EnableRelayService: cfg.EnableRelayService,
RelayServiceOpts: cfg.RelayServiceOpts,
EnableMetrics: !cfg.DisableMetrics,
PrometheusRegisterer: cfg.PrometheusRegisterer,
})
if err != nil {
swrm.Close()
return nil, err
}
if cfg.Relay {
// If we've enabled the relay, we should filter out relay
// addresses by default.
//
// TODO: We shouldn't be doing this here.
oldFactory := h.AddrsFactory
h.AddrsFactory = func(addrs []ma.Multiaddr) []ma.Multiaddr {
return oldFactory(autorelay.Filter(addrs))
}
}
if err := cfg.addTransports(h); err != nil {
h.Close()
return nil, err
}
// TODO: This method succeeds if listening on one address succeeds. We
// should probably fail if listening on *any* addr fails.
if err := h.Network().Listen(cfg.ListenAddrs...); err != nil {
h.Close()
return nil, err
}
// Configure routing and autorelay
var router routing.PeerRouting
if cfg.Routing != nil {
router, err = cfg.Routing(h)
if err != nil {
h.Close()
return nil, err
}
}
// Note: h.AddrsFactory may be changed by relayFinder, but non-relay version is
// used by AutoNAT below.
var ar *autorelay.AutoRelay
addrF := h.AddrsFactory
if cfg.EnableAutoRelay {
if !cfg.Relay {
h.Close()
return nil, fmt.Errorf("cannot enable autorelay; relay is not enabled")
}
if !cfg.DisableMetrics {
mt := autorelay.WithMetricsTracer(
autorelay.NewMetricsTracer(autorelay.WithRegisterer(cfg.PrometheusRegisterer)))
mtOpts := []autorelay.Option{mt}
cfg.AutoRelayOpts = append(mtOpts, cfg.AutoRelayOpts...)
}
ar, err = autorelay.NewAutoRelay(h, cfg.AutoRelayOpts...)
if err != nil {
return nil, err
}
}
autonatOpts := []autonat.Option{
autonat.UsingAddresses(func() []ma.Multiaddr {
return addrF(h.AllAddrs())
}),
}
if !cfg.DisableMetrics {
autonatOpts = append(autonatOpts,
autonat.WithMetricsTracer(
autonat.NewMetricsTracer(autonat.WithRegisterer(cfg.PrometheusRegisterer))))
}
if cfg.AutoNATConfig.ThrottleInterval != 0 {
autonatOpts = append(autonatOpts,
autonat.WithThrottling(cfg.AutoNATConfig.ThrottleGlobalLimit, cfg.AutoNATConfig.ThrottleInterval),
autonat.WithPeerThrottling(cfg.AutoNATConfig.ThrottlePeerLimit))
}
if cfg.AutoNATConfig.EnableService {
autonatPrivKey, _, err := crypto.GenerateEd25519Key(rand.Reader)
if err != nil {
return nil, err
}
ps, err := pstoremem.NewPeerstore()
if err != nil {
return nil, err
}
// Pull out the pieces of the config that we _actually_ care about.
// Specifically, don't set up things like autorelay, listeners,
// identify, etc.
autoNatCfg := Config{
Transports: cfg.Transports,
Muxers: cfg.Muxers,
SecurityTransports: cfg.SecurityTransports,
Insecure: cfg.Insecure,
PSK: cfg.PSK,
ConnectionGater: cfg.ConnectionGater,
Reporter: cfg.Reporter,
PeerKey: autonatPrivKey,
Peerstore: ps,
DialRanker: swarm.NoDelayDialRanker,
}
dialer, err := autoNatCfg.makeSwarm(eventbus.NewBus(), false)
if err != nil {
h.Close()
return nil, err
}
dialerHost := blankhost.NewBlankHost(dialer)
if err := autoNatCfg.addTransports(dialerHost); err != nil {
dialerHost.Close()
h.Close()
return nil, err
}
// NOTE: We're dropping the blank host here but that's fine. It
// doesn't really _do_ anything and doesn't even need to be
// closed (as long as we close the underlying network).
autonatOpts = append(autonatOpts, autonat.EnableService(dialerHost.Network()))
}
if cfg.AutoNATConfig.ForceReachability != nil {
autonatOpts = append(autonatOpts, autonat.WithReachability(*cfg.AutoNATConfig.ForceReachability))
}
autonat, err := autonat.New(h, autonatOpts...)
if err != nil {
h.Close()
return nil, fmt.Errorf("cannot enable autorelay; autonat failed to start: %v", err)
}
h.SetAutoNat(autonat)
// start the host background tasks
h.Start()
var ho host.Host
ho = h
if router != nil {
ho = routed.Wrap(h, router)
}
if ar != nil {
arh := autorelay.NewAutoRelayHost(ho, ar)
arh.Start()
ho = arh
}
return ho, nil
}
// Option is a libp2p config option that can be given to the libp2p constructor
// (`libp2p.New`).
type Option func(cfg *Config) error
// Apply applies the given options to the config, returning the first error
// encountered (if any).
func (cfg *Config) Apply(opts ...Option) error {
for _, opt := range opts {
if opt == nil {
continue
}
if err := opt(cfg); err != nil {
return err
}
}
return nil
}

View File

@ -0,0 +1,23 @@
package config
import (
"testing"
)
func TestNilOption(t *testing.T) {
var cfg Config
optsRun := 0
opt := func(c *Config) error {
optsRun++
return nil
}
if err := cfg.Apply(nil); err != nil {
t.Fatal(err)
}
if err := cfg.Apply(opt, nil, nil, opt, opt, nil); err != nil {
t.Fatal(err)
}
if optsRun != 3 {
t.Fatalf("expected to have handled 3 options, handled %d", optsRun)
}
}

28
go-libp2p/config/log.go Normal file
View File

@ -0,0 +1,28 @@
package config
import (
"strings"
"sync"
logging "github.com/ipfs/go-log/v2"
"go.uber.org/fx/fxevent"
)
var log = logging.Logger("p2p-config")
var (
fxLogger fxevent.Logger
logInitOnce sync.Once
)
type fxLogWriter struct{}
func (l *fxLogWriter) Write(b []byte) (int, error) {
log.Debug(strings.TrimSuffix(string(b), "\n"))
return len(b), nil
}
func getFXLogger() fxevent.Logger {
logInitOnce.Do(func() { fxLogger = &fxevent.ConsoleLogger{W: &fxLogWriter{}} })
return fxLogger
}

View File

@ -0,0 +1,27 @@
package config
import (
"crypto/sha256"
"io"
"golang.org/x/crypto/hkdf"
"github.com/libp2p/go-libp2p/core/crypto"
"github.com/quic-go/quic-go"
)
const statelessResetKeyInfo = "libp2p quic stateless reset key"
func PrivKeyToStatelessResetKey(key crypto.PrivKey) (quic.StatelessResetKey, error) {
var statelessResetKey quic.StatelessResetKey
keyBytes, err := key.Raw()
if err != nil {
return statelessResetKey, err
}
keyReader := hkdf.New(sha256.New, keyBytes, nil, []byte(statelessResetKeyInfo))
if _, err := io.ReadFull(keyReader, statelessResetKey[:]); err != nil {
return statelessResetKey, err
}
return statelessResetKey, nil
}

51
go-libp2p/core/alias.go Normal file
View File

@ -0,0 +1,51 @@
// Package core provides convenient access to foundational, central go-libp2p primitives via type aliases.
package core
import (
"github.com/libp2p/go-libp2p/core/host"
"github.com/libp2p/go-libp2p/core/network"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-libp2p/core/protocol"
"github.com/multiformats/go-multiaddr"
)
// Multiaddr aliases the Multiaddr type from github.com/multiformats/go-multiaddr.
//
// Refer to the docs on that type for more info.
type Multiaddr = multiaddr.Multiaddr
// PeerID aliases peer.ID.
//
// Refer to the docs on that type for more info.
type PeerID = peer.ID
// ProtocolID aliases protocol.ID.
//
// Refer to the docs on that type for more info.
type ProtocolID = protocol.ID
// PeerAddrInfo aliases peer.AddrInfo.
//
// Refer to the docs on that type for more info.
type PeerAddrInfo = peer.AddrInfo
// Host aliases host.Host.
//
// Refer to the docs on that type for more info.
type Host = host.Host
// Network aliases network.Network.
//
// Refer to the docs on that type for more info.
type Network = network.Network
// Conn aliases network.Conn.
//
// Refer to the docs on that type for more info.
type Conn = network.Conn
// Stream aliases network.Stream.
//
// Refer to the docs on that type for more info.
type Stream = network.Stream

View File

@ -0,0 +1,57 @@
package canonicallog
import (
"fmt"
"math/rand"
"net"
"strings"
"github.com/libp2p/go-libp2p/core/peer"
logging "github.com/ipfs/go-log/v2"
"github.com/multiformats/go-multiaddr"
manet "github.com/multiformats/go-multiaddr/net"
)
var log = logging.WithSkip(logging.Logger("canonical-log"), 1)
// LogMisbehavingPeer is the canonical way to log a misbehaving peer.
// Protocols should use this to identify a misbehaving peer to allow the end
// user to easily identify these nodes across protocols and libp2p.
func LogMisbehavingPeer(p peer.ID, peerAddr multiaddr.Multiaddr, component string, err error, msg string) {
log.Warnf("CANONICAL_MISBEHAVING_PEER: peer=%s addr=%s component=%s err=%q msg=%q", p, peerAddr.String(), component, err, msg)
}
// LogMisbehavingPeerNetAddr is the canonical way to log a misbehaving peer.
// Protocols should use this to identify a misbehaving peer to allow the end
// user to easily identify these nodes across protocols and libp2p.
func LogMisbehavingPeerNetAddr(p peer.ID, peerAddr net.Addr, component string, originalErr error, msg string) {
ma, err := manet.FromNetAddr(peerAddr)
if err != nil {
log.Warnf("CANONICAL_MISBEHAVING_PEER: peer=%s net_addr=%s component=%s err=%q msg=%q", p, peerAddr.String(), component, originalErr, msg)
return
}
LogMisbehavingPeer(p, ma, component, originalErr, msg)
}
// LogPeerStatus logs any useful information about a peer. It takes in a sample
// rate and will only log one in every sampleRate messages (randomly). This is
// useful in surfacing events that are normal in isolation, but may be abnormal
// in large quantities. For example, a successful connection from an IP address
// is normal. 10,000 connections from that same IP address is not normal. libp2p
// itself does nothing besides emitting this log. Hook this up to another tool
// like fail2ban to action on the log.
func LogPeerStatus(sampleRate int, p peer.ID, peerAddr multiaddr.Multiaddr, keyVals ...string) {
if rand.Intn(sampleRate) == 0 {
keyValsStr := strings.Builder{}
for i, kOrV := range keyVals {
if i%2 == 0 {
fmt.Fprintf(&keyValsStr, " %v=", kOrV)
} else {
fmt.Fprintf(&keyValsStr, "%q", kOrV)
}
}
log.Infof("CANONICAL_PEER_STATUS: peer=%s addr=%s sample_rate=%v%s", p, peerAddr.String(), sampleRate, keyValsStr.String())
}
}

View File

@ -0,0 +1,26 @@
package canonicallog
import (
"fmt"
"net"
"testing"
"github.com/libp2p/go-libp2p/core/test"
logging "github.com/ipfs/go-log/v2"
"github.com/multiformats/go-multiaddr"
)
func TestLogs(t *testing.T) {
err := logging.SetLogLevel("canonical-log", "info")
if err != nil {
t.Fatal(err)
}
LogMisbehavingPeer(test.RandPeerIDFatal(t), multiaddr.StringCast("/ip4/1.2.3.4"), "somecomponent", fmt.Errorf("something"), "hi")
netAddr := &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 80}
LogMisbehavingPeerNetAddr(test.RandPeerIDFatal(t), netAddr, "somecomponent", fmt.Errorf("something"), "hello \"world\"")
LogPeerStatus(1, test.RandPeerIDFatal(t), multiaddr.StringCast("/ip4/1.2.3.4"), "extra", "info")
}

View File

@ -0,0 +1,109 @@
package connmgr
import (
"io"
"time"
"github.com/libp2p/go-libp2p/core/peer"
)
// Decayer is implemented by connection managers supporting decaying tags. A
// decaying tag is one whose value automatically decays over time.
//
// The actual application of the decay behaviour is encapsulated in a
// user-provided decaying function (DecayFn). The function is called on every
// tick (determined by the interval parameter), and returns either the new value
// of the tag, or whether it should be erased altogether.
//
// We do not set values on a decaying tag. Rather, we "bump" decaying tags by a
// delta. This calls the BumpFn with the old value and the delta, to determine
// the new value.
//
// Such a pluggable design affords a great deal of flexibility and versatility.
// Behaviours that are straightforward to implement include:
//
// - Decay a tag by -1, or by half its current value, on every tick.
// - Every time a value is bumped, sum it to its current value.
// - Exponentially boost a score with every bump.
// - Sum the incoming score, but keep it within min, max bounds.
//
// Commonly used DecayFns and BumpFns are provided in this package.
type Decayer interface {
io.Closer
// RegisterDecayingTag creates and registers a new decaying tag, if and only
// if a tag with the supplied name doesn't exist yet. Otherwise, an error is
// returned.
//
// The caller provides the interval at which the tag is refreshed, as well
// as the decay function and the bump function. Refer to godocs on DecayFn
// and BumpFn for more info.
RegisterDecayingTag(name string, interval time.Duration, decayFn DecayFn, bumpFn BumpFn) (DecayingTag, error)
}
// DecayFn applies a decay to the peer's score. The implementation must call
// DecayFn at the interval supplied when registering the tag.
//
// It receives a copy of the decaying value, and returns the score after
// applying the decay, as well as a flag to signal if the tag should be erased.
type DecayFn func(value DecayingValue) (after int, rm bool)
// BumpFn applies a delta onto an existing score, and returns the new score.
//
// Non-trivial bump functions include exponential boosting, moving averages,
// ceilings, etc.
type BumpFn func(value DecayingValue, delta int) (after int)
// DecayingTag represents a decaying tag. The tag is a long-lived general
// object, used to operate on tag values for peers.
type DecayingTag interface {
// Name returns the name of the tag.
Name() string
// Interval is the effective interval at which this tag will tick. Upon
// registration, the desired interval may be overwritten depending on the
// decayer's resolution, and this method allows you to obtain the effective
// interval.
Interval() time.Duration
// Bump applies a delta to a tag value, calling its bump function. The bump
// will be applied asynchronously, and a non-nil error indicates a fault
// when queuing.
Bump(peer peer.ID, delta int) error
// Remove removes a decaying tag from a peer. The removal will be applied
// asynchronously, and a non-nil error indicates a fault when queuing.
Remove(peer peer.ID) error
// Close closes a decaying tag. The Decayer will stop tracking this tag,
// and the state of all peers in the Connection Manager holding this tag
// will be updated.
//
// The deletion is performed asynchronously.
//
// Once deleted, a tag should not be used, and further calls to Bump/Remove
// will error.
//
// Duplicate calls to Remove will not return errors, but a failure to queue
// the first actual removal, will (e.g. when the system is backlogged).
Close() error
}
// DecayingValue represents a value for a decaying tag.
type DecayingValue struct {
// Tag points to the tag this value belongs to.
Tag DecayingTag
// Peer is the peer ID to whom this value is associated.
Peer peer.ID
// Added is the timestamp when this value was added for the first time for
// a tag and a peer.
Added time.Time
// LastVisit is the timestamp of the last visit.
LastVisit time.Time
// Value is the current value of the tag.
Value int
}

View File

@ -0,0 +1,89 @@
package connmgr
import (
ma "github.com/multiformats/go-multiaddr"
"github.com/libp2p/go-libp2p/core/control"
"github.com/libp2p/go-libp2p/core/network"
"github.com/libp2p/go-libp2p/core/peer"
)
// ConnectionGater can be implemented by a type that supports active
// inbound or outbound connection gating.
//
// ConnectionGaters are active, whereas ConnManagers tend to be passive.
//
// A ConnectionGater will be consulted during different states in the lifecycle
// of a connection being established/upgraded. Specific functions will be called
// throughout the process, to allow you to intercept the connection at that stage.
//
// InterceptPeerDial is called on an imminent outbound peer dial request, prior
// to the addresses of that peer being available/resolved. Blocking connections
// at this stage is typical for blacklisting scenarios.
//
// InterceptAddrDial is called on an imminent outbound dial to a peer on a
// particular address. Blocking connections at this stage is typical for
// address filtering.
//
// InterceptAccept is called as soon as a transport listener receives an
// inbound connection request, before any upgrade takes place. Transports who
// accept already secure and/or multiplexed connections (e.g. possibly QUIC)
// MUST call this method regardless, for correctness/consistency.
//
// InterceptSecured is called for both inbound and outbound connections,
// after a security handshake has taken place and we've authenticated the peer.
//
// InterceptUpgraded is called for inbound and outbound connections, after
// libp2p has finished upgrading the connection entirely to a secure,
// multiplexed channel.
//
// This interface can be used to implement *strict/active* connection management
// policies, such as hard limiting of connections once a maximum count has been
// reached, maintaining a peer blacklist, or limiting connections by transport
// quotas.
//
// EXPERIMENTAL: a DISCONNECT protocol/message will be supported in the future.
// This allows gaters and other components to communicate the intention behind
// a connection closure, to curtail potential reconnection attempts.
//
// For now, InterceptUpgraded can return a non-zero DisconnectReason when
// blocking a connection, but this interface is likely to change in the future
// as we solidify this feature. The reason why only this method can handle
// DisconnectReasons is that we require stream multiplexing capability to open a
// control protocol stream to transmit the message.
type ConnectionGater interface {
// InterceptPeerDial tests whether we're permitted to Dial the specified peer.
//
// This is called by the network.Network implementation when dialling a peer.
InterceptPeerDial(p peer.ID) (allow bool)
// InterceptAddrDial tests whether we're permitted to dial the specified
// multiaddr for the given peer.
//
// This is called by the network.Network implementation after it has
// resolved the peer's addrs, and prior to dialling each.
InterceptAddrDial(peer.ID, ma.Multiaddr) (allow bool)
// InterceptAccept tests whether an incipient inbound connection is allowed.
//
// This is called by the upgrader, or by the transport directly (e.g. QUIC,
// Bluetooth), straight after it has accepted a connection from its socket.
InterceptAccept(network.ConnMultiaddrs) (allow bool)
// InterceptSecured tests whether a given connection, now authenticated,
// is allowed.
//
// This is called by the upgrader, after it has performed the security
// handshake, and before it negotiates the muxer, or by the directly by the
// transport, at the exact same checkpoint.
InterceptSecured(network.Direction, peer.ID, network.ConnMultiaddrs) (allow bool)
// InterceptUpgraded tests whether a fully capable connection is allowed.
//
// At this point, the connection a multiplexer has been selected.
// When rejecting a connection, the gater can return a DisconnectReason.
// Refer to the godoc on the ConnectionGater type for more information.
//
// NOTE: the go-libp2p implementation currently IGNORES the disconnect reason.
InterceptUpgraded(network.Conn) (allow bool, reason control.DisconnectReason)
}

View File

@ -0,0 +1,91 @@
// Package connmgr provides connection tracking and management interfaces for libp2p.
//
// The ConnManager interface exported from this package allows libp2p to enforce an
// upper bound on the total number of open connections. To avoid service disruptions,
// connections can be tagged with metadata and optionally "protected" to ensure that
// essential connections are not arbitrarily cut.
package connmgr
import (
"context"
"time"
"github.com/libp2p/go-libp2p/core/network"
"github.com/libp2p/go-libp2p/core/peer"
)
// SupportsDecay evaluates if the provided ConnManager supports decay, and if
// so, it returns the Decayer object. Refer to godocs on Decayer for more info.
func SupportsDecay(mgr ConnManager) (Decayer, bool) {
d, ok := mgr.(Decayer)
return d, ok
}
// ConnManager tracks connections to peers, and allows consumers to associate
// metadata with each peer.
//
// It enables connections to be trimmed based on implementation-defined
// heuristics. The ConnManager allows libp2p to enforce an upper bound on the
// total number of open connections.
//
// ConnManagers supporting decaying tags implement Decayer. Use the
// SupportsDecay function to safely cast an instance to Decayer, if supported.
type ConnManager interface {
// TagPeer tags a peer with a string, associating a weight with the tag.
TagPeer(peer.ID, string, int)
// Untag removes the tagged value from the peer.
UntagPeer(p peer.ID, tag string)
// UpsertTag updates an existing tag or inserts a new one.
//
// The connection manager calls the upsert function supplying the current
// value of the tag (or zero if inexistent). The return value is used as
// the new value of the tag.
UpsertTag(p peer.ID, tag string, upsert func(int) int)
// GetTagInfo returns the metadata associated with the peer,
// or nil if no metadata has been recorded for the peer.
GetTagInfo(p peer.ID) *TagInfo
// TrimOpenConns terminates open connections based on an implementation-defined
// heuristic.
TrimOpenConns(ctx context.Context)
// Notifee returns an implementation that can be called back to inform of
// opened and closed connections.
Notifee() network.Notifiee
// Protect protects a peer from having its connection(s) pruned.
//
// Tagging allows different parts of the system to manage protections without interfering with one another.
//
// Calls to Protect() with the same tag are idempotent. They are not refcounted, so after multiple calls
// to Protect() with the same tag, a single Unprotect() call bearing the same tag will revoke the protection.
Protect(id peer.ID, tag string)
// Unprotect removes a protection that may have been placed on a peer, under the specified tag.
//
// The return value indicates whether the peer continues to be protected after this call, by way of a different tag.
// See notes on Protect() for more info.
Unprotect(id peer.ID, tag string) (protected bool)
// IsProtected returns true if the peer is protected for some tag; if the tag is the empty string
// then it will return true if the peer is protected for any tag
IsProtected(id peer.ID, tag string) (protected bool)
// Close closes the connection manager and stops background processes.
Close() error
}
// TagInfo stores metadata associated with a peer.
type TagInfo struct {
FirstSeen time.Time
Value int
// Tags maps tag ids to the numerical values.
Tags map[string]int
// Conns maps connection ids (such as remote multiaddr) to their creation time.
Conns map[string]time.Time
}

View File

@ -0,0 +1,24 @@
package connmgr
import (
"context"
"github.com/libp2p/go-libp2p/core/network"
"github.com/libp2p/go-libp2p/core/peer"
)
// NullConnMgr is a ConnMgr that provides no functionality.
type NullConnMgr struct{}
var _ ConnManager = (*NullConnMgr)(nil)
func (NullConnMgr) TagPeer(peer.ID, string, int) {}
func (NullConnMgr) UntagPeer(peer.ID, string) {}
func (NullConnMgr) UpsertTag(peer.ID, string, func(int) int) {}
func (NullConnMgr) GetTagInfo(peer.ID) *TagInfo { return &TagInfo{} }
func (NullConnMgr) TrimOpenConns(ctx context.Context) {}
func (NullConnMgr) Notifee() network.Notifiee { return network.GlobalNoopNotifiee }
func (NullConnMgr) Protect(peer.ID, string) {}
func (NullConnMgr) Unprotect(peer.ID, string) bool { return false }
func (NullConnMgr) IsProtected(peer.ID, string) bool { return false }
func (NullConnMgr) Close() error { return nil }

View File

@ -0,0 +1,67 @@
package connmgr
import (
"math"
"time"
)
// DecayNone applies no decay.
func DecayNone() DecayFn {
return func(value DecayingValue) (_ int, rm bool) {
return value.Value, false
}
}
// DecayFixed subtracts from by the provided minuend, and deletes the tag when
// first reaching 0 or negative.
func DecayFixed(minuend int) DecayFn {
return func(value DecayingValue) (_ int, rm bool) {
v := value.Value - minuend
return v, v <= 0
}
}
// DecayLinear applies a fractional coefficient to the value of the current tag,
// rounding down via math.Floor. It erases the tag when the result is zero.
func DecayLinear(coef float64) DecayFn {
return func(value DecayingValue) (after int, rm bool) {
v := math.Floor(float64(value.Value) * coef)
return int(v), v <= 0
}
}
// DecayExpireWhenInactive expires a tag after a certain period of no bumps.
func DecayExpireWhenInactive(after time.Duration) DecayFn {
return func(value DecayingValue) (_ int, rm bool) {
rm = time.Until(value.LastVisit) >= after
return 0, rm
}
}
// BumpSumUnbounded adds the incoming value to the peer's score.
func BumpSumUnbounded() BumpFn {
return func(value DecayingValue, delta int) (after int) {
return value.Value + delta
}
}
// BumpSumBounded keeps summing the incoming score, keeping it within a
// [min, max] range.
func BumpSumBounded(min, max int) BumpFn {
return func(value DecayingValue, delta int) (after int) {
v := value.Value + delta
if v >= max {
return max
} else if v <= min {
return min
}
return v
}
}
// BumpOverwrite replaces the current value of the tag with the incoming one.
func BumpOverwrite() BumpFn {
return func(value DecayingValue, delta int) (after int) {
return delta
}
}

View File

@ -0,0 +1,9 @@
package control
// DisconnectReason communicates the reason why a connection is being closed.
//
// A zero value stands for "no reason" / NA.
//
// This is an EXPERIMENTAL type. It will change in the future. Refer to the
// connmgr.ConnectionGater godoc for more info.
type DisconnectReason int

View File

@ -0,0 +1,84 @@
package crypto
import "testing"
func BenchmarkSignRSA1B(b *testing.B) { RunBenchmarkSignRSA(b, 1) }
func BenchmarkSignRSA10B(b *testing.B) { RunBenchmarkSignRSA(b, 10) }
func BenchmarkSignRSA100B(b *testing.B) { RunBenchmarkSignRSA(b, 100) }
func BenchmarkSignRSA1000B(b *testing.B) { RunBenchmarkSignRSA(b, 1000) }
func BenchmarkSignRSA10000B(b *testing.B) { RunBenchmarkSignRSA(b, 10000) }
func BenchmarkSignRSA100000B(b *testing.B) { RunBenchmarkSignRSA(b, 100000) }
func BenchmarkVerifyRSA1B(b *testing.B) { RunBenchmarkVerifyRSA(b, 1) }
func BenchmarkVerifyRSA10B(b *testing.B) { RunBenchmarkVerifyRSA(b, 10) }
func BenchmarkVerifyRSA100B(b *testing.B) { RunBenchmarkVerifyRSA(b, 100) }
func BenchmarkVerifyRSA1000B(b *testing.B) { RunBenchmarkVerifyRSA(b, 1000) }
func BenchmarkVerifyRSA10000B(b *testing.B) { RunBenchmarkVerifyRSA(b, 10000) }
func BenchmarkVerifyRSA100000B(b *testing.B) { RunBenchmarkVerifyRSA(b, 100000) }
func BenchmarkSignEd255191B(b *testing.B) { RunBenchmarkSignEd25519(b, 1) }
func BenchmarkSignEd2551910B(b *testing.B) { RunBenchmarkSignEd25519(b, 10) }
func BenchmarkSignEd25519100B(b *testing.B) { RunBenchmarkSignEd25519(b, 100) }
func BenchmarkSignEd255191000B(b *testing.B) { RunBenchmarkSignEd25519(b, 1000) }
func BenchmarkSignEd2551910000B(b *testing.B) { RunBenchmarkSignEd25519(b, 10000) }
func BenchmarkSignEd25519100000B(b *testing.B) { RunBenchmarkSignEd25519(b, 100000) }
func BenchmarkVerifyEd255191B(b *testing.B) { RunBenchmarkVerifyEd25519(b, 1) }
func BenchmarkVerifyEd2551910B(b *testing.B) { RunBenchmarkVerifyEd25519(b, 10) }
func BenchmarkVerifyEd25519100B(b *testing.B) { RunBenchmarkVerifyEd25519(b, 100) }
func BenchmarkVerifyEd255191000B(b *testing.B) { RunBenchmarkVerifyEd25519(b, 1000) }
func BenchmarkVerifyEd2551910000B(b *testing.B) { RunBenchmarkVerifyEd25519(b, 10000) }
func BenchmarkVerifyEd25519100000B(b *testing.B) { RunBenchmarkVerifyEd25519(b, 100000) }
func RunBenchmarkSignRSA(b *testing.B, numBytes int) {
runBenchmarkSign(b, numBytes, RSA)
}
func RunBenchmarkSignEd25519(b *testing.B, numBytes int) {
runBenchmarkSign(b, numBytes, Ed25519)
}
func runBenchmarkSign(b *testing.B, numBytes int, t int) {
secret, _, err := GenerateKeyPair(t, 2048)
if err != nil {
b.Fatal(err)
}
someData := make([]byte, numBytes)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := secret.Sign(someData)
if err != nil {
b.Fatal(err)
}
}
}
func RunBenchmarkVerifyRSA(b *testing.B, numBytes int) {
runBenchmarkVerify(b, numBytes, RSA)
}
func RunBenchmarkVerifyEd25519(b *testing.B, numBytes int) {
runBenchmarkVerify(b, numBytes, Ed25519)
}
func runBenchmarkVerify(b *testing.B, numBytes int, t int) {
secret, public, err := GenerateKeyPair(t, 2048)
if err != nil {
b.Fatal(err)
}
someData := make([]byte, numBytes)
signature, err := secret.Sign(someData)
if err != nil {
b.Fatal(err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
valid, err := public.Verify(someData, signature)
if err != nil {
b.Fatal(err)
}
if !valid {
b.Fatal("signature should be valid")
}
}
}

View File

@ -0,0 +1,187 @@
package crypto
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"encoding/asn1"
"errors"
"io"
"math/big"
pb "github.com/libp2p/go-libp2p/core/crypto/pb"
"github.com/libp2p/go-libp2p/core/internal/catch"
"github.com/minio/sha256-simd"
)
// ECDSAPrivateKey is an implementation of an ECDSA private key
type ECDSAPrivateKey struct {
priv *ecdsa.PrivateKey
}
// ECDSAPublicKey is an implementation of an ECDSA public key
type ECDSAPublicKey struct {
pub *ecdsa.PublicKey
}
// ECDSASig holds the r and s values of an ECDSA signature
type ECDSASig struct {
R, S *big.Int
}
var (
// ErrNotECDSAPubKey is returned when the public key passed is not an ecdsa public key
ErrNotECDSAPubKey = errors.New("not an ecdsa public key")
// ErrNilSig is returned when the signature is nil
ErrNilSig = errors.New("sig is nil")
// ErrNilPrivateKey is returned when a nil private key is provided
ErrNilPrivateKey = errors.New("private key is nil")
// ErrNilPublicKey is returned when a nil public key is provided
ErrNilPublicKey = errors.New("public key is nil")
// ECDSACurve is the default ecdsa curve used
ECDSACurve = elliptic.P256()
)
// GenerateECDSAKeyPair generates a new ecdsa private and public key
func GenerateECDSAKeyPair(src io.Reader) (PrivKey, PubKey, error) {
return GenerateECDSAKeyPairWithCurve(ECDSACurve, src)
}
// GenerateECDSAKeyPairWithCurve generates a new ecdsa private and public key with a specified curve
func GenerateECDSAKeyPairWithCurve(curve elliptic.Curve, src io.Reader) (PrivKey, PubKey, error) {
priv, err := ecdsa.GenerateKey(curve, src)
if err != nil {
return nil, nil, err
}
return &ECDSAPrivateKey{priv}, &ECDSAPublicKey{&priv.PublicKey}, nil
}
// ECDSAKeyPairFromKey generates a new ecdsa private and public key from an input private key
func ECDSAKeyPairFromKey(priv *ecdsa.PrivateKey) (PrivKey, PubKey, error) {
if priv == nil {
return nil, nil, ErrNilPrivateKey
}
return &ECDSAPrivateKey{priv}, &ECDSAPublicKey{&priv.PublicKey}, nil
}
// ECDSAPublicKeyFromPubKey generates a new ecdsa public key from an input public key
func ECDSAPublicKeyFromPubKey(pub ecdsa.PublicKey) (PubKey, error) {
return &ECDSAPublicKey{pub: &pub}, nil
}
// MarshalECDSAPrivateKey returns x509 bytes from a private key
func MarshalECDSAPrivateKey(ePriv ECDSAPrivateKey) (res []byte, err error) {
defer func() { catch.HandlePanic(recover(), &err, "ECDSA private-key marshal") }()
return x509.MarshalECPrivateKey(ePriv.priv)
}
// MarshalECDSAPublicKey returns x509 bytes from a public key
func MarshalECDSAPublicKey(ePub ECDSAPublicKey) (res []byte, err error) {
defer func() { catch.HandlePanic(recover(), &err, "ECDSA public-key marshal") }()
return x509.MarshalPKIXPublicKey(ePub.pub)
}
// UnmarshalECDSAPrivateKey returns a private key from x509 bytes
func UnmarshalECDSAPrivateKey(data []byte) (res PrivKey, err error) {
defer func() { catch.HandlePanic(recover(), &err, "ECDSA private-key unmarshal") }()
priv, err := x509.ParseECPrivateKey(data)
if err != nil {
return nil, err
}
return &ECDSAPrivateKey{priv}, nil
}
// UnmarshalECDSAPublicKey returns the public key from x509 bytes
func UnmarshalECDSAPublicKey(data []byte) (key PubKey, err error) {
defer func() { catch.HandlePanic(recover(), &err, "ECDSA public-key unmarshal") }()
pubIfc, err := x509.ParsePKIXPublicKey(data)
if err != nil {
return nil, err
}
pub, ok := pubIfc.(*ecdsa.PublicKey)
if !ok {
return nil, ErrNotECDSAPubKey
}
return &ECDSAPublicKey{pub}, nil
}
// Type returns the key type
func (ePriv *ECDSAPrivateKey) Type() pb.KeyType {
return pb.KeyType_ECDSA
}
// Raw returns x509 bytes from a private key
func (ePriv *ECDSAPrivateKey) Raw() (res []byte, err error) {
defer func() { catch.HandlePanic(recover(), &err, "ECDSA private-key marshal") }()
return x509.MarshalECPrivateKey(ePriv.priv)
}
// Equals compares two private keys
func (ePriv *ECDSAPrivateKey) Equals(o Key) bool {
return basicEquals(ePriv, o)
}
// Sign returns the signature of the input data
func (ePriv *ECDSAPrivateKey) Sign(data []byte) (sig []byte, err error) {
defer func() { catch.HandlePanic(recover(), &err, "ECDSA signing") }()
hash := sha256.Sum256(data)
r, s, err := ecdsa.Sign(rand.Reader, ePriv.priv, hash[:])
if err != nil {
return nil, err
}
return asn1.Marshal(ECDSASig{
R: r,
S: s,
})
}
// GetPublic returns a public key
func (ePriv *ECDSAPrivateKey) GetPublic() PubKey {
return &ECDSAPublicKey{&ePriv.priv.PublicKey}
}
// Type returns the key type
func (ePub *ECDSAPublicKey) Type() pb.KeyType {
return pb.KeyType_ECDSA
}
// Raw returns x509 bytes from a public key
func (ePub *ECDSAPublicKey) Raw() ([]byte, error) {
return x509.MarshalPKIXPublicKey(ePub.pub)
}
// Equals compares to public keys
func (ePub *ECDSAPublicKey) Equals(o Key) bool {
return basicEquals(ePub, o)
}
// Verify compares data to a signature
func (ePub *ECDSAPublicKey) Verify(data, sigBytes []byte) (success bool, err error) {
defer func() {
catch.HandlePanic(recover(), &err, "ECDSA signature verification")
// Just to be extra paranoid.
if err != nil {
success = false
}
}()
sig := new(ECDSASig)
if _, err := asn1.Unmarshal(sigBytes, sig); err != nil {
return false, err
}
hash := sha256.Sum256(data)
return ecdsa.Verify(ePub.pub, hash[:], sig.R, sig.S), nil
}

View File

@ -0,0 +1,142 @@
package crypto
import (
"crypto/ecdsa"
"crypto/rand"
"testing"
)
func TestECDSABasicSignAndVerify(t *testing.T) {
priv, pub, err := GenerateECDSAKeyPair(rand.Reader)
if err != nil {
t.Fatal(err)
}
data := []byte("hello! and welcome to some awesome crypto primitives")
sig, err := priv.Sign(data)
if err != nil {
t.Fatal(err)
}
ok, err := pub.Verify(data, sig)
if err != nil {
t.Fatal(err)
}
if !ok {
t.Fatal("signature didnt match")
}
// change data
data[0] = ^data[0]
ok, err = pub.Verify(data, sig)
if err != nil {
t.Fatal(err)
}
if ok {
t.Fatal("signature matched and shouldn't")
}
}
func TestECDSASignZero(t *testing.T) {
priv, pub, err := GenerateECDSAKeyPair(rand.Reader)
if err != nil {
t.Fatal(err)
}
data := make([]byte, 0)
sig, err := priv.Sign(data)
if err != nil {
t.Fatal(err)
}
ok, err := pub.Verify(data, sig)
if err != nil {
t.Fatal(err)
}
if !ok {
t.Fatal("signature didn't match")
}
}
func TestECDSAMarshalLoop(t *testing.T) {
priv, pub, err := GenerateECDSAKeyPair(rand.Reader)
if err != nil {
t.Fatal(err)
}
privB, err := MarshalPrivateKey(priv)
if err != nil {
t.Fatal(err)
}
privNew, err := UnmarshalPrivateKey(privB)
if err != nil {
t.Fatal(err)
}
if !priv.Equals(privNew) || !privNew.Equals(priv) {
t.Fatal("keys are not equal")
}
pubB, err := MarshalPublicKey(pub)
if err != nil {
t.Fatal(err)
}
pubNew, err := UnmarshalPublicKey(pubB)
if err != nil {
t.Fatal(err)
}
if !pub.Equals(pubNew) || !pubNew.Equals(pub) {
t.Fatal("keys are not equal")
}
}
func TestECDSAPublicKeyFromPubKey(t *testing.T) {
ecdsaPrivK, err := ecdsa.GenerateKey(ECDSACurve, rand.Reader)
if err != nil {
t.Fatal(err)
}
privK, _, err := ECDSAKeyPairFromKey(ecdsaPrivK)
if err != nil {
t.Fatal(err)
}
data := []byte("Hello world!")
signature, err := privK.Sign(data)
if err != nil {
t.Fatal(err)
}
pubKey, err := ECDSAPublicKeyFromPubKey(ecdsaPrivK.PublicKey)
if err != nil {
t.Fatal(err)
}
ok, err := pubKey.Verify(data, signature)
if err != nil {
t.Fatal(err)
}
if !ok {
t.Fatal("signature didn't match")
}
pubB, err := MarshalPublicKey(pubKey)
if err != nil {
t.Fatal(err)
}
pubNew, err := UnmarshalPublicKey(pubB)
if err != nil {
t.Fatal(err)
}
if !pubKey.Equals(pubNew) || !pubNew.Equals(pubKey) {
t.Fatal("keys are not equal")
}
}

View File

@ -0,0 +1,156 @@
package crypto
import (
"bytes"
"crypto/ed25519"
"crypto/subtle"
"errors"
"fmt"
"io"
pb "github.com/libp2p/go-libp2p/core/crypto/pb"
"github.com/libp2p/go-libp2p/core/internal/catch"
)
// Ed25519PrivateKey is an ed25519 private key.
type Ed25519PrivateKey struct {
k ed25519.PrivateKey
}
// Ed25519PublicKey is an ed25519 public key.
type Ed25519PublicKey struct {
k ed25519.PublicKey
}
// GenerateEd25519Key generates a new ed25519 private and public key pair.
func GenerateEd25519Key(src io.Reader) (PrivKey, PubKey, error) {
pub, priv, err := ed25519.GenerateKey(src)
if err != nil {
return nil, nil, err
}
return &Ed25519PrivateKey{
k: priv,
},
&Ed25519PublicKey{
k: pub,
},
nil
}
// Type of the private key (Ed25519).
func (k *Ed25519PrivateKey) Type() pb.KeyType {
return pb.KeyType_Ed25519
}
// Raw private key bytes.
func (k *Ed25519PrivateKey) Raw() ([]byte, error) {
// The Ed25519 private key contains two 32-bytes curve points, the private
// key and the public key.
// It makes it more efficient to get the public key without re-computing an
// elliptic curve multiplication.
buf := make([]byte, len(k.k))
copy(buf, k.k)
return buf, nil
}
func (k *Ed25519PrivateKey) pubKeyBytes() []byte {
return k.k[ed25519.PrivateKeySize-ed25519.PublicKeySize:]
}
// Equals compares two ed25519 private keys.
func (k *Ed25519PrivateKey) Equals(o Key) bool {
edk, ok := o.(*Ed25519PrivateKey)
if !ok {
return basicEquals(k, o)
}
return subtle.ConstantTimeCompare(k.k, edk.k) == 1
}
// GetPublic returns an ed25519 public key from a private key.
func (k *Ed25519PrivateKey) GetPublic() PubKey {
return &Ed25519PublicKey{k: k.pubKeyBytes()}
}
// Sign returns a signature from an input message.
func (k *Ed25519PrivateKey) Sign(msg []byte) (res []byte, err error) {
defer func() { catch.HandlePanic(recover(), &err, "ed15519 signing") }()
return ed25519.Sign(k.k, msg), nil
}
// Type of the public key (Ed25519).
func (k *Ed25519PublicKey) Type() pb.KeyType {
return pb.KeyType_Ed25519
}
// Raw public key bytes.
func (k *Ed25519PublicKey) Raw() ([]byte, error) {
return k.k, nil
}
// Equals compares two ed25519 public keys.
func (k *Ed25519PublicKey) Equals(o Key) bool {
edk, ok := o.(*Ed25519PublicKey)
if !ok {
return basicEquals(k, o)
}
return bytes.Equal(k.k, edk.k)
}
// Verify checks a signature against the input data.
func (k *Ed25519PublicKey) Verify(data []byte, sig []byte) (success bool, err error) {
defer func() {
catch.HandlePanic(recover(), &err, "ed15519 signature verification")
// To be safe.
if err != nil {
success = false
}
}()
return ed25519.Verify(k.k, data, sig), nil
}
// UnmarshalEd25519PublicKey returns a public key from input bytes.
func UnmarshalEd25519PublicKey(data []byte) (PubKey, error) {
if len(data) != 32 {
return nil, errors.New("expect ed25519 public key data size to be 32")
}
return &Ed25519PublicKey{
k: ed25519.PublicKey(data),
}, nil
}
// UnmarshalEd25519PrivateKey returns a private key from input bytes.
func UnmarshalEd25519PrivateKey(data []byte) (PrivKey, error) {
switch len(data) {
case ed25519.PrivateKeySize + ed25519.PublicKeySize:
// Remove the redundant public key. See issue #36.
redundantPk := data[ed25519.PrivateKeySize:]
pk := data[ed25519.PrivateKeySize-ed25519.PublicKeySize : ed25519.PrivateKeySize]
if subtle.ConstantTimeCompare(pk, redundantPk) == 0 {
return nil, errors.New("expected redundant ed25519 public key to be redundant")
}
// No point in storing the extra data.
newKey := make([]byte, ed25519.PrivateKeySize)
copy(newKey, data[:ed25519.PrivateKeySize])
data = newKey
case ed25519.PrivateKeySize:
default:
return nil, fmt.Errorf(
"expected ed25519 data size to be %d or %d, got %d",
ed25519.PrivateKeySize,
ed25519.PrivateKeySize+ed25519.PublicKeySize,
len(data),
)
}
return &Ed25519PrivateKey{
k: ed25519.PrivateKey(data),
}, nil
}

View File

@ -0,0 +1,214 @@
package crypto
import (
"crypto/ed25519"
"crypto/rand"
"testing"
"github.com/libp2p/go-libp2p/core/crypto/pb"
"google.golang.org/protobuf/proto"
)
func TestBasicSignAndVerify(t *testing.T) {
priv, pub, err := GenerateEd25519Key(rand.Reader)
if err != nil {
t.Fatal(err)
}
data := []byte("hello! and welcome to some awesome crypto primitives")
sig, err := priv.Sign(data)
if err != nil {
t.Fatal(err)
}
ok, err := pub.Verify(data, sig)
if err != nil {
t.Fatal(err)
}
if !ok {
t.Fatal("signature didn't match")
}
// change data
data[0] = ^data[0]
ok, err = pub.Verify(data, sig)
if err != nil {
t.Fatal(err)
}
if ok {
t.Fatal("signature matched and shouldn't")
}
}
func TestSignZero(t *testing.T) {
priv, pub, err := GenerateEd25519Key(rand.Reader)
if err != nil {
t.Fatal(err)
}
data := make([]byte, 0)
sig, err := priv.Sign(data)
if err != nil {
t.Fatal(err)
}
ok, err := pub.Verify(data, sig)
if err != nil {
t.Fatal(err)
}
if !ok {
t.Fatal("signature didn't match")
}
}
func TestMarshalLoop(t *testing.T) {
priv, pub, err := GenerateEd25519Key(rand.Reader)
if err != nil {
t.Fatal(err)
}
t.Run("PrivateKey", func(t *testing.T) {
for name, f := range map[string]func() ([]byte, error){
"Marshal": func() ([]byte, error) {
return MarshalPrivateKey(priv)
},
"Redundant": func() ([]byte, error) {
// See issue #36.
// Ed25519 private keys used to contain the public key twice.
// For backwards-compatibility, we need to continue supporting
// that scenario.
data, err := priv.Raw()
if err != nil {
t.Fatal(err)
}
data = append(data, data[len(data)-ed25519.PublicKeySize:]...)
return proto.Marshal(&pb.PrivateKey{
Type: priv.Type().Enum(),
Data: data,
})
},
} {
t.Run(name, func(t *testing.T) {
bts, err := f()
if err != nil {
t.Fatal(err)
}
privNew, err := UnmarshalPrivateKey(bts)
if err != nil {
t.Fatal(err)
}
if !priv.Equals(privNew) || !privNew.Equals(priv) {
t.Fatal("keys are not equal")
}
msg := []byte("My child, my sister,\nThink of the rapture\nOf living together there!")
signed, err := privNew.Sign(msg)
if err != nil {
t.Fatal(err)
}
ok, err := privNew.GetPublic().Verify(msg, signed)
if err != nil {
t.Fatal(err)
}
if !ok {
t.Fatal("signature didn't match")
}
})
}
})
t.Run("PublicKey", func(t *testing.T) {
for name, f := range map[string]func() ([]byte, error){
"Marshal": func() ([]byte, error) {
return MarshalPublicKey(pub)
},
} {
t.Run(name, func(t *testing.T) {
bts, err := f()
if err != nil {
t.Fatal(err)
}
pubNew, err := UnmarshalPublicKey(bts)
if err != nil {
t.Fatal(err)
}
if !pub.Equals(pubNew) || !pubNew.Equals(pub) {
t.Fatal("keys are not equal")
}
})
}
})
}
func TestUnmarshalErrors(t *testing.T) {
t.Run("PublicKey", func(t *testing.T) {
t.Run("Invalid data length", func(t *testing.T) {
data, err := proto.Marshal(&pb.PublicKey{
Type: pb.KeyType_Ed25519.Enum(),
Data: []byte{42},
})
if err != nil {
t.Fatal(err)
}
if _, err := UnmarshalPublicKey(data); err == nil {
t.Fatal("expected an error")
}
})
})
t.Run("PrivateKey", func(t *testing.T) {
t.Run("Redundant public key mismatch", func(t *testing.T) {
priv, _, err := GenerateEd25519Key(rand.Reader)
if err != nil {
t.Fatal(err)
}
data, err := priv.Raw()
if err != nil {
t.Fatal(err)
}
// Append the private key instead of the public key.
data = append(data, data[:ed25519.PublicKeySize]...)
b, err := proto.Marshal(&pb.PrivateKey{
Type: priv.Type().Enum(),
Data: data,
})
if err != nil {
t.Fatal(err)
}
_, err = UnmarshalPrivateKey(b)
if err == nil {
t.Fatal("expected an error")
}
if err.Error() != "expected redundant ed25519 public key to be redundant" {
t.Fatalf("invalid error received: %s", err.Error())
}
})
t.Run("Invalid data length", func(t *testing.T) {
data, err := proto.Marshal(&pb.PrivateKey{
Type: pb.KeyType_Ed25519.Enum(),
Data: []byte{42},
})
if err != nil {
t.Fatal(err)
}
_, err = UnmarshalPrivateKey(data)
if err == nil {
t.Fatal("expected an error")
}
})
})
}

View File

@ -0,0 +1,157 @@
package crypto
import (
"bytes"
"crypto/subtle"
"errors"
"fmt"
"io"
pb "github.com/libp2p/go-libp2p/core/crypto/pb"
"github.com/libp2p/go-libp2p/core/internal/catch"
"github.com/cloudflare/circl/sign/ed448"
)
// Ed448PrivateKey is an ed448 private key.
type Ed448PrivateKey struct {
k ed448.PrivateKey
}
// Ed448PublicKey is an ed448 public key.
type Ed448PublicKey struct {
k ed448.PublicKey
}
// GenerateEd448Key generates a new ed448 private and public key pair.
func GenerateEd448Key(src io.Reader) (PrivKey, PubKey, error) {
pub, priv, err := ed448.GenerateKey(src)
if err != nil {
return nil, nil, err
}
return &Ed448PrivateKey{
k: priv,
},
&Ed448PublicKey{
k: pub,
},
nil
}
// Type of the private key (Ed448).
func (k *Ed448PrivateKey) Type() pb.KeyType {
return pb.KeyType_Ed448
}
// Raw private key bytes.
func (k *Ed448PrivateKey) Raw() ([]byte, error) {
// The Ed448 private key contains two 57-bytes curve points, the private
// key and the public key.
// It makes it more efficient to get the public key without re-computing an
// elliptic curve multiplication.
buf := make([]byte, len(k.k))
copy(buf, k.k)
return buf, nil
}
func (k *Ed448PrivateKey) pubKeyBytes() []byte {
return k.k[ed448.PrivateKeySize-ed448.PublicKeySize:]
}
// Equals compares two ed448 private keys.
func (k *Ed448PrivateKey) Equals(o Key) bool {
edk, ok := o.(*Ed448PrivateKey)
if !ok {
return basicEquals(k, o)
}
return subtle.ConstantTimeCompare(k.k, edk.k) == 1
}
// GetPublic returns an ed448 public key from a private key.
func (k *Ed448PrivateKey) GetPublic() PubKey {
return &Ed448PublicKey{k: k.pubKeyBytes()}
}
// Sign returns a signature from an input message.
func (k *Ed448PrivateKey) Sign(msg []byte) (res []byte, err error) {
defer func() { catch.HandlePanic(recover(), &err, "ed448 signing") }()
return ed448.Sign(k.k, msg, ""), nil
}
// Type of the public key (Ed448).
func (k *Ed448PublicKey) Type() pb.KeyType {
return pb.KeyType_Ed448
}
// Raw public key bytes.
func (k *Ed448PublicKey) Raw() ([]byte, error) {
return k.k, nil
}
// Equals compares two ed448 public keys.
func (k *Ed448PublicKey) Equals(o Key) bool {
edk, ok := o.(*Ed448PublicKey)
if !ok {
return basicEquals(k, o)
}
return bytes.Equal(k.k, edk.k)
}
// Verify checks a signature against the input data.
func (k *Ed448PublicKey) Verify(data []byte, sig []byte) (success bool, err error) {
defer func() {
catch.HandlePanic(recover(), &err, "ed448 signature verification")
// To be safe.
if err != nil {
success = false
}
}()
return ed448.Verify(k.k, data, sig, ""), nil
}
// UnmarshalEd448PublicKey returns a public key from input bytes.
func UnmarshalEd448PublicKey(data []byte) (PubKey, error) {
if len(data) != 57 {
return nil, errors.New("expect ed448 public key data size to be 57")
}
return &Ed448PublicKey{
k: ed448.PublicKey(data),
}, nil
}
// UnmarshalEd448PrivateKey returns a private key from input bytes.
func UnmarshalEd448PrivateKey(data []byte) (PrivKey, error) {
switch len(data) {
case ed448.PrivateKeySize + ed448.PublicKeySize:
// Remove the redundant public key. See issue #36.
redundantPk := data[ed448.PrivateKeySize:]
pk := data[ed448.PrivateKeySize-ed448.PublicKeySize : ed448.PrivateKeySize]
if subtle.ConstantTimeCompare(pk, redundantPk) == 0 {
return nil, errors.New("expected redundant ed448 public key to be redundant")
}
// No point in storing the extra data.
newKey := make([]byte, ed448.PrivateKeySize)
copy(newKey, data[:ed448.PrivateKeySize])
data = newKey
case ed448.PrivateKeySize:
default:
return nil, fmt.Errorf(
"expected ed448 data size to be %d or %d, got %d",
ed448.PrivateKeySize,
ed448.PrivateKeySize+ed448.PublicKeySize,
len(data),
)
}
return &Ed448PrivateKey{
k: ed448.PrivateKey(data),
}, nil
}

View File

@ -0,0 +1,210 @@
package crypto
import (
"crypto/rand"
"testing"
"github.com/cloudflare/circl/sign/ed448"
"github.com/libp2p/go-libp2p/core/crypto/pb"
"google.golang.org/protobuf/proto"
)
func TestEd448BasicSignAndVerify(t *testing.T) {
priv, pub, err := GenerateEd25519Key(rand.Reader)
if err != nil {
t.Fatal(err)
}
data := []byte("hello! and welcome to some awesome crypto primitives")
sig, err := priv.Sign(data)
if err != nil {
t.Fatal(err)
}
ok, err := pub.Verify(data, sig)
if err != nil {
t.Fatal(err)
}
if !ok {
t.Fatal("signature didn't match")
}
// change data
data[0] = ^data[0]
ok, err = pub.Verify(data, sig)
if err != nil {
t.Fatal(err)
}
if ok {
t.Fatal("signature matched and shouldn't")
}
}
func TestEd448SignZero(t *testing.T) {
priv, pub, err := GenerateEd25519Key(rand.Reader)
if err != nil {
t.Fatal(err)
}
data := make([]byte, 0)
sig, err := priv.Sign(data)
if err != nil {
t.Fatal(err)
}
ok, err := pub.Verify(data, sig)
if err != nil {
t.Fatal(err)
}
if !ok {
t.Fatal("signature didn't match")
}
}
func TestEd448MarshalLoop(t *testing.T) {
priv, pub, err := GenerateEd448Key(rand.Reader)
if err != nil {
t.Fatal(err)
}
t.Run("PrivateKey", func(t *testing.T) {
for name, f := range map[string]func() ([]byte, error){
"Marshal": func() ([]byte, error) {
return MarshalPrivateKey(priv)
},
"Redundant": func() ([]byte, error) {
data, err := priv.Raw()
if err != nil {
t.Fatal(err)
}
data = append(data, data[len(data)-ed448.PublicKeySize:]...)
return proto.Marshal(&pb.PrivateKey{
Type: priv.Type().Enum(),
Data: data,
})
},
} {
t.Run(name, func(t *testing.T) {
bts, err := f()
if err != nil {
t.Fatal(err)
}
privNew, err := UnmarshalPrivateKey(bts)
if err != nil {
t.Fatal(err)
}
if !priv.Equals(privNew) || !privNew.Equals(priv) {
t.Fatal("keys are not equal")
}
msg := []byte("My child, my sister,\nThink of the rapture\nOf living together there!")
signed, err := privNew.Sign(msg)
if err != nil {
t.Fatal(err)
}
ok, err := privNew.GetPublic().Verify(msg, signed)
if err != nil {
t.Fatal(err)
}
if !ok {
t.Fatal("signature didn't match")
}
})
}
})
t.Run("PublicKey", func(t *testing.T) {
for name, f := range map[string]func() ([]byte, error){
"Marshal": func() ([]byte, error) {
return MarshalPublicKey(pub)
},
} {
t.Run(name, func(t *testing.T) {
bts, err := f()
if err != nil {
t.Fatal(err)
}
pubNew, err := UnmarshalPublicKey(bts)
if err != nil {
t.Fatal(err)
}
if !pub.Equals(pubNew) || !pubNew.Equals(pub) {
t.Fatal("keys are not equal")
}
})
}
})
}
func TestEd448UnmarshalErrors(t *testing.T) {
t.Run("PublicKey", func(t *testing.T) {
t.Run("Invalid data length", func(t *testing.T) {
data, err := proto.Marshal(&pb.PublicKey{
Type: pb.KeyType_Ed448.Enum(),
Data: []byte{74},
})
if err != nil {
t.Fatal(err)
}
if _, err := UnmarshalPublicKey(data); err == nil {
t.Fatal("expected an error")
}
})
})
t.Run("PrivateKey", func(t *testing.T) {
t.Run("Redundant public key mismatch", func(t *testing.T) {
priv, _, err := GenerateEd448Key(rand.Reader)
if err != nil {
t.Fatal(err)
}
data, err := priv.Raw()
if err != nil {
t.Fatal(err)
}
// Append the private key instead of the public key.
data = append(data, data[:ed448.PublicKeySize]...)
b, err := proto.Marshal(&pb.PrivateKey{
Type: priv.Type().Enum(),
Data: data,
})
if err != nil {
t.Fatal(err)
}
_, err = UnmarshalPrivateKey(b)
if err == nil {
t.Fatal("expected an error")
}
if err.Error() != "expected redundant ed448 public key to be redundant" {
t.Fatalf("invalid error received: %s", err.Error())
}
})
t.Run("Invalid data length", func(t *testing.T) {
data, err := proto.Marshal(&pb.PrivateKey{
Type: pb.KeyType_Ed448.Enum(),
Data: []byte{74},
})
if err != nil {
t.Fatal(err)
}
_, err = UnmarshalPrivateKey(data)
if err == nil {
t.Fatal("expected an error")
}
})
})
}

View File

@ -0,0 +1,132 @@
package crypto_test
import (
"bytes"
"crypto/rand"
"fmt"
"io"
"os"
"testing"
"github.com/libp2p/go-libp2p/core/crypto"
crypto_pb "github.com/libp2p/go-libp2p/core/crypto/pb"
)
var message = []byte("Libp2p is the _best_!")
type testCase struct {
keyType crypto_pb.KeyType
gen func(i io.Reader) (crypto.PrivKey, crypto.PubKey, error)
sigDeterministic bool
}
var keyTypes = []testCase{
{
keyType: crypto_pb.KeyType_ECDSA,
gen: crypto.GenerateECDSAKeyPair,
},
{
keyType: crypto_pb.KeyType_Secp256k1,
sigDeterministic: true,
gen: crypto.GenerateSecp256k1Key,
},
{
keyType: crypto_pb.KeyType_RSA,
sigDeterministic: true,
gen: func(i io.Reader) (crypto.PrivKey, crypto.PubKey, error) {
return crypto.GenerateRSAKeyPair(2048, i)
},
},
}
func fname(kt crypto_pb.KeyType, ext string) string {
return fmt.Sprintf("test_data/%d.%s", kt, ext)
}
func TestFixtures(t *testing.T) {
for _, tc := range keyTypes {
t.Run(tc.keyType.String(), func(t *testing.T) {
pubBytes, err := os.ReadFile(fname(tc.keyType, "pub"))
if err != nil {
t.Fatal(err)
}
privBytes, err := os.ReadFile(fname(tc.keyType, "priv"))
if err != nil {
t.Fatal(err)
}
sigBytes, err := os.ReadFile(fname(tc.keyType, "sig"))
if err != nil {
t.Fatal(err)
}
pub, err := crypto.UnmarshalPublicKey(pubBytes)
if err != nil {
t.Fatal(err)
}
pubBytes2, err := crypto.MarshalPublicKey(pub)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(pubBytes2, pubBytes) {
t.Fatal("encoding round-trip failed")
}
priv, err := crypto.UnmarshalPrivateKey(privBytes)
if err != nil {
t.Fatal(err)
}
privBytes2, err := crypto.MarshalPrivateKey(priv)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(privBytes2, privBytes) {
t.Fatal("encoding round-trip failed")
}
ok, err := pub.Verify(message, sigBytes)
if !ok || err != nil {
t.Fatal("failed to validate signature with public key")
}
if tc.sigDeterministic {
sigBytes2, err := priv.Sign(message)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(sigBytes2, sigBytes) {
t.Fatal("signature not deterministic")
}
}
})
}
}
func init() {
// set to true to re-generate test data
if false {
generate()
panic("generated")
}
}
// generate re-generates test data
func generate() {
for _, tc := range keyTypes {
priv, pub, err := tc.gen(rand.Reader)
if err != nil {
panic(err)
}
pubb, err := crypto.MarshalPublicKey(pub)
if err != nil {
panic(err)
}
privb, err := crypto.MarshalPrivateKey(priv)
if err != nil {
panic(err)
}
sig, err := priv.Sign(message)
if err != nil {
panic(err)
}
os.WriteFile(fname(tc.keyType, "pub"), pubb, 0666)
os.WriteFile(fname(tc.keyType, "priv"), privb, 0666)
os.WriteFile(fname(tc.keyType, "sig"), sig, 0666)
}
}

View File

@ -0,0 +1,298 @@
// Package crypto implements various cryptographic utilities used by libp2p.
// This includes a Public and Private key interface and key implementations
// for supported key algorithms.
package crypto
import (
"crypto/elliptic"
"crypto/rand"
"crypto/subtle"
"encoding/base64"
"errors"
"fmt"
"io"
"github.com/libp2p/go-libp2p/core/crypto/pb"
"google.golang.org/protobuf/proto"
)
//go:generate protoc --go_out=. --go_opt=Mpb/crypto.proto=./pb pb/crypto.proto
const (
// RSA is an enum for the supported RSA key type
RSA = iota
// Ed25519 is an enum for the supported Ed25519 key type
Ed25519
// Secp256k1 is an enum for the supported Secp256k1 key type
Secp256k1
// ECDSA is an enum for the supported ECDSA key type
ECDSA
// Ed448 is an enum for the supported Ed448 key type
Ed448
)
var (
// ErrBadKeyType is returned when a key is not supported
ErrBadKeyType = errors.New("invalid or unsupported key type")
// KeyTypes is a list of supported keys
KeyTypes = []int{
RSA,
Ed25519,
Secp256k1,
ECDSA,
Ed448,
}
)
// PubKeyUnmarshaller is a func that creates a PubKey from a given slice of bytes
type PubKeyUnmarshaller func(data []byte) (PubKey, error)
// PrivKeyUnmarshaller is a func that creates a PrivKey from a given slice of bytes
type PrivKeyUnmarshaller func(data []byte) (PrivKey, error)
// PubKeyUnmarshallers is a map of unmarshallers by key type
var PubKeyUnmarshallers = map[pb.KeyType]PubKeyUnmarshaller{
pb.KeyType_RSA: UnmarshalRsaPublicKey,
pb.KeyType_Ed25519: UnmarshalEd25519PublicKey,
pb.KeyType_Secp256k1: UnmarshalSecp256k1PublicKey,
pb.KeyType_ECDSA: UnmarshalECDSAPublicKey,
pb.KeyType_Ed448: UnmarshalEd448PublicKey,
}
// PrivKeyUnmarshallers is a map of unmarshallers by key type
var PrivKeyUnmarshallers = map[pb.KeyType]PrivKeyUnmarshaller{
pb.KeyType_RSA: UnmarshalRsaPrivateKey,
pb.KeyType_Ed25519: UnmarshalEd25519PrivateKey,
pb.KeyType_Secp256k1: UnmarshalSecp256k1PrivateKey,
pb.KeyType_ECDSA: UnmarshalECDSAPrivateKey,
pb.KeyType_Ed448: UnmarshalEd448PrivateKey,
}
// Key represents a crypto key that can be compared to another key
type Key interface {
// Equals checks whether two PubKeys are the same
Equals(Key) bool
// Raw returns the raw bytes of the key (not wrapped in the
// libp2p-crypto protobuf).
//
// This function is the inverse of {Priv,Pub}KeyUnmarshaler.
Raw() ([]byte, error)
// Type returns the protobuf key type.
Type() pb.KeyType
}
// PrivKey represents a private key that can be used to generate a public key and sign data
type PrivKey interface {
Key
// Cryptographically sign the given bytes
Sign([]byte) ([]byte, error)
// Return a public key paired with this private key
GetPublic() PubKey
}
// PubKey is a public key that can be used to verify data signed with the corresponding private key
type PubKey interface {
Key
// Verify that 'sig' is the signed hash of 'data'
Verify(data []byte, sig []byte) (bool, error)
}
// GenSharedKey generates the shared key from a given private key
type GenSharedKey func([]byte) ([]byte, error)
// GenerateKeyPair generates a private and public key
func GenerateKeyPair(typ, bits int) (PrivKey, PubKey, error) {
return GenerateKeyPairWithReader(typ, bits, rand.Reader)
}
// GenerateKeyPairWithReader returns a keypair of the given type and bit-size
func GenerateKeyPairWithReader(typ, bits int, src io.Reader) (PrivKey, PubKey, error) {
switch typ {
case RSA:
return GenerateRSAKeyPair(bits, src)
case Ed25519:
return GenerateEd25519Key(src)
case Secp256k1:
return GenerateSecp256k1Key(src)
case ECDSA:
return GenerateECDSAKeyPair(src)
case Ed448:
return GenerateEd448Key(src)
default:
return nil, nil, ErrBadKeyType
}
}
// GenerateEKeyPair returns an ephemeral public key and returns a function that will compute
// the shared secret key. Used in the identify module.
//
// Focuses only on ECDH now, but can be made more general in the future.
func GenerateEKeyPair(curveName string) ([]byte, GenSharedKey, error) {
var curve elliptic.Curve
switch curveName {
case "P-256":
curve = elliptic.P256()
case "P-384":
curve = elliptic.P384()
case "P-521":
curve = elliptic.P521()
default:
return nil, nil, fmt.Errorf("unknown curve name")
}
priv, x, y, err := elliptic.GenerateKey(curve, rand.Reader)
if err != nil {
return nil, nil, err
}
pubKey := elliptic.Marshal(curve, x, y)
done := func(theirPub []byte) ([]byte, error) {
// Verify and unpack node's public key.
x, y := elliptic.Unmarshal(curve, theirPub)
if x == nil {
return nil, fmt.Errorf("malformed public key: %d %v", len(theirPub), theirPub)
}
if !curve.IsOnCurve(x, y) {
return nil, errors.New("invalid public key")
}
// Generate shared secret.
secret, _ := curve.ScalarMult(x, y, priv)
return secret.Bytes(), nil
}
return pubKey, done, nil
}
// UnmarshalPublicKey converts a protobuf serialized public key into its
// representative object
func UnmarshalPublicKey(data []byte) (PubKey, error) {
pmes := new(pb.PublicKey)
err := proto.Unmarshal(data, pmes)
if err != nil {
return nil, err
}
return PublicKeyFromProto(pmes)
}
// PublicKeyFromProto converts an unserialized protobuf PublicKey message
// into its representative object.
func PublicKeyFromProto(pmes *pb.PublicKey) (PubKey, error) {
um, ok := PubKeyUnmarshallers[pmes.GetType()]
if !ok {
return nil, ErrBadKeyType
}
data := pmes.GetData()
pk, err := um(data)
if err != nil {
return nil, err
}
switch tpk := pk.(type) {
case *RsaPublicKey:
tpk.cached, _ = proto.Marshal(pmes)
}
return pk, nil
}
// MarshalPublicKey converts a public key object into a protobuf serialized
// public key
func MarshalPublicKey(k PubKey) ([]byte, error) {
pbmes, err := PublicKeyToProto(k)
if err != nil {
return nil, err
}
return proto.Marshal(pbmes)
}
// PublicKeyToProto converts a public key object into an unserialized
// protobuf PublicKey message.
func PublicKeyToProto(k PubKey) (*pb.PublicKey, error) {
data, err := k.Raw()
if err != nil {
return nil, err
}
return &pb.PublicKey{
Type: k.Type().Enum(),
Data: data,
}, nil
}
// UnmarshalPrivateKey converts a protobuf serialized private key into its
// representative object
func UnmarshalPrivateKey(data []byte) (PrivKey, error) {
pmes := new(pb.PrivateKey)
err := proto.Unmarshal(data, pmes)
if err != nil {
return nil, err
}
um, ok := PrivKeyUnmarshallers[pmes.GetType()]
if !ok {
return nil, ErrBadKeyType
}
return um(pmes.GetData())
}
// MarshalPrivateKey converts a key object into its protobuf serialized form.
func MarshalPrivateKey(k PrivKey) ([]byte, error) {
data, err := k.Raw()
if err != nil {
return nil, err
}
return proto.Marshal(&pb.PrivateKey{
Type: k.Type().Enum(),
Data: data,
})
}
// ConfigDecodeKey decodes from b64 (for config file) to a byte array that can be unmarshalled.
func ConfigDecodeKey(b string) ([]byte, error) {
return base64.StdEncoding.DecodeString(b)
}
// ConfigEncodeKey encodes a marshalled key to b64 (for config file).
func ConfigEncodeKey(b []byte) string {
return base64.StdEncoding.EncodeToString(b)
}
// KeyEqual checks whether two Keys are equivalent (have identical byte representations).
func KeyEqual(k1, k2 Key) bool {
if k1 == k2 {
return true
}
return k1.Equals(k2)
}
func basicEquals(k1, k2 Key) bool {
if k1.Type() != k2.Type() {
return false
}
a, err := k1.Raw()
if err != nil {
return false
}
b, err := k2.Raw()
if err != nil {
return false
}
return subtle.ConstantTimeCompare(a, b) == 1
}

View File

@ -0,0 +1,321 @@
package crypto_test
import (
"bytes"
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"fmt"
"reflect"
"testing"
"github.com/cloudflare/circl/sign/ed448"
. "github.com/libp2p/go-libp2p/core/crypto"
pb "github.com/libp2p/go-libp2p/core/crypto/pb"
"github.com/libp2p/go-libp2p/core/test"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
secp256k1ecdsa "github.com/decred/dcrd/dcrec/secp256k1/v4/ecdsa"
"github.com/minio/sha256-simd"
)
func TestKeys(t *testing.T) {
for _, typ := range KeyTypes {
testKeyType(typ, t)
}
}
func TestKeyPairFromKey(t *testing.T) {
var (
data = []byte(`hello world`)
hashed = sha256.Sum256(data)
)
privk, err := secp256k1.GeneratePrivateKey()
if err != nil {
t.Fatalf("err generating btcec priv key:\n%v", err)
}
sigK := secp256k1ecdsa.Sign(privk, hashed[:])
eKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
t.Fatalf("err generating ecdsa priv key:\n%v", err)
}
sigE, err := eKey.Sign(rand.Reader, hashed[:], crypto.SHA256)
if err != nil {
t.Fatalf("err generating ecdsa sig:\n%v", err)
}
rKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
t.Fatalf("err generating rsa priv key:\n%v", err)
}
sigR, err := rKey.Sign(rand.Reader, hashed[:], crypto.SHA256)
if err != nil {
t.Fatalf("err generating rsa sig:\n%v", err)
}
_, edKey, err := ed25519.GenerateKey(rand.Reader)
sigEd := ed25519.Sign(edKey, data[:])
if err != nil {
t.Fatalf("err generating ed25519 sig:\n%v", err)
}
_, ed448Key, err := ed448.GenerateKey(rand.Reader)
sigEd448 := ed448.Sign(ed448Key, data[:], "")
if err != nil {
t.Fatalf("err generating ed448 sig:\n%v", err)
}
for i, tt := range []struct {
in crypto.PrivateKey
typ pb.KeyType
sig []byte
}{
{
eKey,
ECDSA,
sigE,
},
{
privk,
Secp256k1,
sigK.Serialize(),
},
{
rKey,
RSA,
sigR,
},
{
&edKey,
Ed25519,
sigEd,
},
{
&ed448Key,
Ed448,
sigEd448,
},
} {
t.Run(fmt.Sprintf("%v", i), func(t *testing.T) {
priv, pub, err := KeyPairFromStdKey(tt.in)
if err != nil {
t.Fatal(err)
}
if priv == nil || pub == nil {
t.Errorf("received nil private key or public key: %v, %v", priv, pub)
}
if priv == nil || priv.Type() != tt.typ {
t.Errorf("want %v; got %v", tt.typ, priv.Type())
}
v, err := pub.Verify(data[:], tt.sig)
if err != nil {
t.Error(err)
}
if !v {
t.Error("signature was not verified")
}
stdPub, err := PubKeyToStdKey(pub)
if stdPub == nil {
t.Errorf("err getting std public key from key: %v", err)
}
var stdPubBytes []byte
switch p := stdPub.(type) {
case *Secp256k1PublicKey:
stdPubBytes, err = p.Raw()
case ed25519.PublicKey:
stdPubBytes = []byte(p)
case ed448.PublicKey:
stdPubBytes = []byte(p)
default:
stdPubBytes, err = x509.MarshalPKIXPublicKey(stdPub)
}
if err != nil {
t.Errorf("Error while marshaling %v key: %v", reflect.TypeOf(stdPub), err)
}
pubBytes, err := pub.Raw()
if err != nil {
t.Errorf("err getting raw bytes for %v key: %v", reflect.TypeOf(pub), err)
}
if !bytes.Equal(stdPubBytes, pubBytes) {
t.Errorf("err roundtripping %v key", reflect.TypeOf(pub))
}
stdPriv, err := PrivKeyToStdKey(priv)
if stdPub == nil {
t.Errorf("err getting std private key from key: %v", err)
}
var stdPrivBytes []byte
switch p := stdPriv.(type) {
case *Secp256k1PrivateKey:
stdPrivBytes, err = p.Raw()
case *ecdsa.PrivateKey:
stdPrivBytes, err = x509.MarshalECPrivateKey(p)
case *ed25519.PrivateKey:
stdPrivBytes = *p
case *ed448.PrivateKey:
stdPrivBytes = *p
case *rsa.PrivateKey:
stdPrivBytes = x509.MarshalPKCS1PrivateKey(p)
}
if err != nil {
t.Errorf("err marshaling %v key: %v", reflect.TypeOf(stdPriv), err)
}
privBytes, err := priv.Raw()
if err != nil {
t.Errorf("err getting raw bytes for %v key: %v", reflect.TypeOf(priv), err)
}
if !bytes.Equal(stdPrivBytes, privBytes) {
t.Errorf("err roundtripping %v key", reflect.TypeOf(priv))
}
})
}
}
func testKeyType(typ int, t *testing.T) {
bits := 512
if typ == RSA {
bits = 2048
}
sk, pk, err := test.RandTestKeyPair(typ, bits)
if err != nil {
t.Fatal(err)
}
testKeySignature(t, sk)
testKeyEncoding(t, sk)
testKeyEquals(t, sk)
testKeyEquals(t, pk)
}
func testKeySignature(t *testing.T, sk PrivKey) {
pk := sk.GetPublic()
text := make([]byte, 16)
if _, err := rand.Read(text); err != nil {
t.Fatal(err)
}
sig, err := sk.Sign(text)
if err != nil {
t.Fatal(err)
}
valid, err := pk.Verify(text, sig)
if err != nil {
t.Fatal(err)
}
if !valid {
t.Fatal("Invalid signature.")
}
}
func testKeyEncoding(t *testing.T, sk PrivKey) {
skbm, err := MarshalPrivateKey(sk)
if err != nil {
t.Fatal(err)
}
sk2, err := UnmarshalPrivateKey(skbm)
if err != nil {
t.Fatal(err)
}
if !sk.Equals(sk2) {
t.Error("Unmarshaled private key didn't match original.\n")
}
skbm2, err := MarshalPrivateKey(sk2)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(skbm, skbm2) {
t.Error("skb -> marshal -> unmarshal -> skb failed.\n", skbm, "\n", skbm2)
}
pk := sk.GetPublic()
pkbm, err := MarshalPublicKey(pk)
if err != nil {
t.Fatal(err)
}
pk2, err := UnmarshalPublicKey(pkbm)
if err != nil {
t.Fatal(err)
}
if !pk.Equals(pk2) {
t.Error("Unmarshaled public key didn't match original.\n")
}
pkbm2, err := MarshalPublicKey(pk)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(pkbm, pkbm2) {
t.Error("skb -> marshal -> unmarshal -> skb failed.\n", pkbm, "\n", pkbm2)
}
}
func testKeyEquals(t *testing.T, k Key) {
// kb, err := k.Raw()
// if err != nil {
// t.Fatal(err)
// }
if !KeyEqual(k, k) {
t.Fatal("Key not equal to itself.")
}
// bad test, relies on deep internals..
// if !KeyEqual(k, testkey(kb)) {
// t.Fatal("Key not equal to key with same bytes.")
// }
sk, pk, err := test.RandTestKeyPair(RSA, 2048)
if err != nil {
t.Fatal(err)
}
if KeyEqual(k, sk) {
t.Fatal("Keys should not equal.")
}
if KeyEqual(k, pk) {
t.Fatal("Keys should not equal.")
}
}
func TestUnknownCurveErrors(t *testing.T) {
_, _, err := GenerateEKeyPair("P-256")
if err != nil {
t.Fatal(err)
}
_, _, err = GenerateEKeyPair("error-please")
if err == nil {
t.Fatal("expected invalid key type to error")
}
}

View File

@ -0,0 +1,88 @@
package crypto
import (
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rsa"
"github.com/cloudflare/circl/sign/ed448"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
)
// KeyPairFromStdKey wraps standard library (and secp256k1) private keys in libp2p/go-libp2p/core/crypto keys
func KeyPairFromStdKey(priv crypto.PrivateKey) (PrivKey, PubKey, error) {
if priv == nil {
return nil, nil, ErrNilPrivateKey
}
switch p := priv.(type) {
case *rsa.PrivateKey:
return &RsaPrivateKey{*p}, &RsaPublicKey{k: p.PublicKey}, nil
case *ecdsa.PrivateKey:
return &ECDSAPrivateKey{p}, &ECDSAPublicKey{&p.PublicKey}, nil
case *ed25519.PrivateKey:
pubIfc := p.Public()
pub, _ := pubIfc.(ed25519.PublicKey)
return &Ed25519PrivateKey{*p}, &Ed25519PublicKey{pub}, nil
case *secp256k1.PrivateKey:
sPriv := Secp256k1PrivateKey(*p)
sPub := Secp256k1PublicKey(*p.PubKey())
return &sPriv, &sPub, nil
case *ed448.PrivateKey:
pubIfc := p.Public()
pub, _ := pubIfc.(ed448.PublicKey)
return &Ed448PrivateKey{*p}, &Ed448PublicKey{pub}, nil
default:
return nil, nil, ErrBadKeyType
}
}
// PrivKeyToStdKey converts libp2p/go-libp2p/core/crypto private keys to standard library (and secp256k1) private keys
func PrivKeyToStdKey(priv PrivKey) (crypto.PrivateKey, error) {
if priv == nil {
return nil, ErrNilPrivateKey
}
switch p := priv.(type) {
case *RsaPrivateKey:
return &p.sk, nil
case *ECDSAPrivateKey:
return p.priv, nil
case *Ed25519PrivateKey:
return &p.k, nil
case *Secp256k1PrivateKey:
return p, nil
case *Ed448PrivateKey:
return &p.k, nil
default:
return nil, ErrBadKeyType
}
}
// PubKeyToStdKey converts libp2p/go-libp2p/core/crypto private keys to standard library (and secp256k1) public keys
func PubKeyToStdKey(pub PubKey) (crypto.PublicKey, error) {
if pub == nil {
return nil, ErrNilPublicKey
}
switch p := pub.(type) {
case *RsaPublicKey:
return &p.k, nil
case *ECDSAPublicKey:
return p.pub, nil
case *Ed25519PublicKey:
return p.k, nil
case *Secp256k1PublicKey:
return p, nil
case *Ed448PublicKey:
return p.k, nil
default:
return nil, ErrBadKeyType
}
}

View File

@ -0,0 +1,301 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.30.0
// protoc v3.21.12
// source: pb/crypto.proto
package pb
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type KeyType int32
const (
KeyType_RSA KeyType = 0
KeyType_Ed25519 KeyType = 1
KeyType_Secp256k1 KeyType = 2
KeyType_ECDSA KeyType = 3
KeyType_Ed448 KeyType = 4
)
// Enum value maps for KeyType.
var (
KeyType_name = map[int32]string{
0: "RSA",
1: "Ed25519",
2: "Secp256k1",
3: "ECDSA",
4: "Ed448",
}
KeyType_value = map[string]int32{
"RSA": 0,
"Ed25519": 1,
"Secp256k1": 2,
"ECDSA": 3,
"Ed448": 4,
}
)
func (x KeyType) Enum() *KeyType {
p := new(KeyType)
*p = x
return p
}
func (x KeyType) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (KeyType) Descriptor() protoreflect.EnumDescriptor {
return file_pb_crypto_proto_enumTypes[0].Descriptor()
}
func (KeyType) Type() protoreflect.EnumType {
return &file_pb_crypto_proto_enumTypes[0]
}
func (x KeyType) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Do not use.
func (x *KeyType) UnmarshalJSON(b []byte) error {
num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b)
if err != nil {
return err
}
*x = KeyType(num)
return nil
}
// Deprecated: Use KeyType.Descriptor instead.
func (KeyType) EnumDescriptor() ([]byte, []int) {
return file_pb_crypto_proto_rawDescGZIP(), []int{0}
}
type PublicKey struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Type *KeyType `protobuf:"varint,1,req,name=Type,enum=crypto.pb.KeyType" json:"Type,omitempty"`
Data []byte `protobuf:"bytes,2,req,name=Data" json:"Data,omitempty"`
}
func (x *PublicKey) Reset() {
*x = PublicKey{}
if protoimpl.UnsafeEnabled {
mi := &file_pb_crypto_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *PublicKey) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*PublicKey) ProtoMessage() {}
func (x *PublicKey) ProtoReflect() protoreflect.Message {
mi := &file_pb_crypto_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use PublicKey.ProtoReflect.Descriptor instead.
func (*PublicKey) Descriptor() ([]byte, []int) {
return file_pb_crypto_proto_rawDescGZIP(), []int{0}
}
func (x *PublicKey) GetType() KeyType {
if x != nil && x.Type != nil {
return *x.Type
}
return KeyType_RSA
}
func (x *PublicKey) GetData() []byte {
if x != nil {
return x.Data
}
return nil
}
type PrivateKey struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Type *KeyType `protobuf:"varint,1,req,name=Type,enum=crypto.pb.KeyType" json:"Type,omitempty"`
Data []byte `protobuf:"bytes,2,req,name=Data" json:"Data,omitempty"`
}
func (x *PrivateKey) Reset() {
*x = PrivateKey{}
if protoimpl.UnsafeEnabled {
mi := &file_pb_crypto_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *PrivateKey) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*PrivateKey) ProtoMessage() {}
func (x *PrivateKey) ProtoReflect() protoreflect.Message {
mi := &file_pb_crypto_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use PrivateKey.ProtoReflect.Descriptor instead.
func (*PrivateKey) Descriptor() ([]byte, []int) {
return file_pb_crypto_proto_rawDescGZIP(), []int{1}
}
func (x *PrivateKey) GetType() KeyType {
if x != nil && x.Type != nil {
return *x.Type
}
return KeyType_RSA
}
func (x *PrivateKey) GetData() []byte {
if x != nil {
return x.Data
}
return nil
}
var File_pb_crypto_proto protoreflect.FileDescriptor
var file_pb_crypto_proto_rawDesc = []byte{
0x0a, 0x0f, 0x70, 0x62, 0x2f, 0x63, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x12, 0x09, 0x63, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x2e, 0x70, 0x62, 0x22, 0x47, 0x0a, 0x09,
0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x26, 0x0a, 0x04, 0x54, 0x79, 0x70,
0x65, 0x18, 0x01, 0x20, 0x02, 0x28, 0x0e, 0x32, 0x12, 0x2e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x6f,
0x2e, 0x70, 0x62, 0x2e, 0x4b, 0x65, 0x79, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x54, 0x79, 0x70,
0x65, 0x12, 0x12, 0x0a, 0x04, 0x44, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x02, 0x28, 0x0c, 0x52,
0x04, 0x44, 0x61, 0x74, 0x61, 0x22, 0x48, 0x0a, 0x0a, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65,
0x4b, 0x65, 0x79, 0x12, 0x26, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x02, 0x28,
0x0e, 0x32, 0x12, 0x2e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x2e, 0x70, 0x62, 0x2e, 0x4b, 0x65,
0x79, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x44,
0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x02, 0x28, 0x0c, 0x52, 0x04, 0x44, 0x61, 0x74, 0x61, 0x2a,
0x44, 0x0a, 0x07, 0x4b, 0x65, 0x79, 0x54, 0x79, 0x70, 0x65, 0x12, 0x07, 0x0a, 0x03, 0x52, 0x53,
0x41, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x45, 0x64, 0x32, 0x35, 0x35, 0x31, 0x39, 0x10, 0x01,
0x12, 0x0d, 0x0a, 0x09, 0x53, 0x65, 0x63, 0x70, 0x32, 0x35, 0x36, 0x6b, 0x31, 0x10, 0x02, 0x12,
0x09, 0x0a, 0x05, 0x45, 0x43, 0x44, 0x53, 0x41, 0x10, 0x03, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x64,
0x34, 0x34, 0x38, 0x10, 0x04, 0x42, 0x2c, 0x5a, 0x2a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e,
0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x62, 0x70, 0x32, 0x70, 0x2f, 0x67, 0x6f, 0x2d, 0x6c, 0x69,
0x62, 0x70, 0x32, 0x70, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x63, 0x72, 0x79, 0x70, 0x74, 0x6f,
0x2f, 0x70, 0x62,
}
var (
file_pb_crypto_proto_rawDescOnce sync.Once
file_pb_crypto_proto_rawDescData = file_pb_crypto_proto_rawDesc
)
func file_pb_crypto_proto_rawDescGZIP() []byte {
file_pb_crypto_proto_rawDescOnce.Do(func() {
file_pb_crypto_proto_rawDescData = protoimpl.X.CompressGZIP(file_pb_crypto_proto_rawDescData)
})
return file_pb_crypto_proto_rawDescData
}
var file_pb_crypto_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
var file_pb_crypto_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_pb_crypto_proto_goTypes = []interface{}{
(KeyType)(0), // 0: crypto.pb.KeyType
(*PublicKey)(nil), // 1: crypto.pb.PublicKey
(*PrivateKey)(nil), // 2: crypto.pb.PrivateKey
}
var file_pb_crypto_proto_depIdxs = []int32{
0, // 0: crypto.pb.PublicKey.Type:type_name -> crypto.pb.KeyType
0, // 1: crypto.pb.PrivateKey.Type:type_name -> crypto.pb.KeyType
2, // [2:2] is the sub-list for method output_type
2, // [2:2] is the sub-list for method input_type
2, // [2:2] is the sub-list for extension type_name
2, // [2:2] is the sub-list for extension extendee
0, // [0:2] is the sub-list for field type_name
}
func init() { file_pb_crypto_proto_init() }
func file_pb_crypto_proto_init() {
if File_pb_crypto_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_pb_crypto_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*PublicKey); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_pb_crypto_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*PrivateKey); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_pb_crypto_proto_rawDesc,
NumEnums: 1,
NumMessages: 2,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_pb_crypto_proto_goTypes,
DependencyIndexes: file_pb_crypto_proto_depIdxs,
EnumInfos: file_pb_crypto_proto_enumTypes,
MessageInfos: file_pb_crypto_proto_msgTypes,
}.Build()
File_pb_crypto_proto = out.File
file_pb_crypto_proto_rawDesc = nil
file_pb_crypto_proto_goTypes = nil
file_pb_crypto_proto_depIdxs = nil
}

View File

@ -0,0 +1,23 @@
syntax = "proto2";
package crypto.pb;
option go_package = "github.com/libp2p/go-libp2p/core/crypto/pb";
enum KeyType {
RSA = 0;
Ed25519 = 1;
Secp256k1 = 2;
ECDSA = 3;
Ed448 = 4;
}
message PublicKey {
required KeyType Type = 1;
required bytes Data = 2;
}
message PrivateKey {
required KeyType Type = 1;
required bytes Data = 2;
}

View File

@ -0,0 +1,28 @@
package crypto
import (
"fmt"
"os"
)
// WeakRsaKeyEnv is an environment variable which, when set, lowers the
// minimum required bits of RSA keys to 512. This should be used exclusively in
// test situations.
const WeakRsaKeyEnv = "LIBP2P_ALLOW_WEAK_RSA_KEYS"
var MinRsaKeyBits = 2048
var maxRsaKeyBits = 8192
// ErrRsaKeyTooSmall is returned when trying to generate or parse an RSA key
// that's smaller than MinRsaKeyBits bits. In test
var ErrRsaKeyTooSmall error
var ErrRsaKeyTooBig error = fmt.Errorf("rsa keys must be <= %d bits", maxRsaKeyBits)
func init() {
if _, ok := os.LookupEnv(WeakRsaKeyEnv); ok {
MinRsaKeyBits = 512
}
ErrRsaKeyTooSmall = fmt.Errorf("rsa keys must be >= %d bits to be useful", MinRsaKeyBits)
}

View File

@ -0,0 +1,155 @@
package crypto
import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"errors"
"io"
pb "github.com/libp2p/go-libp2p/core/crypto/pb"
"github.com/libp2p/go-libp2p/core/internal/catch"
"github.com/minio/sha256-simd"
)
// RsaPrivateKey is a rsa private key
type RsaPrivateKey struct {
sk rsa.PrivateKey
}
// RsaPublicKey is a rsa public key
type RsaPublicKey struct {
k rsa.PublicKey
cached []byte
}
// GenerateRSAKeyPair generates a new rsa private and public key
func GenerateRSAKeyPair(bits int, src io.Reader) (PrivKey, PubKey, error) {
if bits < MinRsaKeyBits {
return nil, nil, ErrRsaKeyTooSmall
}
if bits > maxRsaKeyBits {
return nil, nil, ErrRsaKeyTooBig
}
priv, err := rsa.GenerateKey(src, bits)
if err != nil {
return nil, nil, err
}
pk := priv.PublicKey
return &RsaPrivateKey{sk: *priv}, &RsaPublicKey{k: pk}, nil
}
// Verify compares a signature against input data
func (pk *RsaPublicKey) Verify(data, sig []byte) (success bool, err error) {
defer func() {
catch.HandlePanic(recover(), &err, "RSA signature verification")
// To be safe
if err != nil {
success = false
}
}()
hashed := sha256.Sum256(data)
err = rsa.VerifyPKCS1v15(&pk.k, crypto.SHA256, hashed[:], sig)
if err != nil {
return false, err
}
return true, nil
}
func (pk *RsaPublicKey) Type() pb.KeyType {
return pb.KeyType_RSA
}
func (pk *RsaPublicKey) Raw() (res []byte, err error) {
defer func() { catch.HandlePanic(recover(), &err, "RSA public-key marshaling") }()
return x509.MarshalPKIXPublicKey(&pk.k)
}
// Equals checks whether this key is equal to another
func (pk *RsaPublicKey) Equals(k Key) bool {
// make sure this is a rsa public key
other, ok := (k).(*RsaPublicKey)
if !ok {
return basicEquals(pk, k)
}
return pk.k.N.Cmp(other.k.N) == 0 && pk.k.E == other.k.E
}
// Sign returns a signature of the input data
func (sk *RsaPrivateKey) Sign(message []byte) (sig []byte, err error) {
defer func() { catch.HandlePanic(recover(), &err, "RSA signing") }()
hashed := sha256.Sum256(message)
return rsa.SignPKCS1v15(rand.Reader, &sk.sk, crypto.SHA256, hashed[:])
}
// GetPublic returns a public key
func (sk *RsaPrivateKey) GetPublic() PubKey {
return &RsaPublicKey{k: sk.sk.PublicKey}
}
func (sk *RsaPrivateKey) Type() pb.KeyType {
return pb.KeyType_RSA
}
func (sk *RsaPrivateKey) Raw() (res []byte, err error) {
defer func() { catch.HandlePanic(recover(), &err, "RSA private-key marshaling") }()
b := x509.MarshalPKCS1PrivateKey(&sk.sk)
return b, nil
}
// Equals checks whether this key is equal to another
func (sk *RsaPrivateKey) Equals(k Key) bool {
// make sure this is a rsa public key
other, ok := (k).(*RsaPrivateKey)
if !ok {
return basicEquals(sk, k)
}
a := sk.sk
b := other.sk
// Don't care about constant time. We're only comparing the public half.
return a.PublicKey.N.Cmp(b.PublicKey.N) == 0 && a.PublicKey.E == b.PublicKey.E
}
// UnmarshalRsaPrivateKey returns a private key from the input x509 bytes
func UnmarshalRsaPrivateKey(b []byte) (key PrivKey, err error) {
defer func() { catch.HandlePanic(recover(), &err, "RSA private-key unmarshaling") }()
sk, err := x509.ParsePKCS1PrivateKey(b)
if err != nil {
return nil, err
}
if sk.N.BitLen() < MinRsaKeyBits {
return nil, ErrRsaKeyTooSmall
}
if sk.N.BitLen() > maxRsaKeyBits {
return nil, ErrRsaKeyTooBig
}
return &RsaPrivateKey{sk: *sk}, nil
}
// UnmarshalRsaPublicKey returns a public key from the input x509 bytes
func UnmarshalRsaPublicKey(b []byte) (key PubKey, err error) {
defer func() { catch.HandlePanic(recover(), &err, "RSA public-key unmarshaling") }()
pub, err := x509.ParsePKIXPublicKey(b)
if err != nil {
return nil, err
}
pk, ok := pub.(*rsa.PublicKey)
if !ok {
return nil, errors.New("not actually an rsa public key")
}
if pk.N.BitLen() < MinRsaKeyBits {
return nil, ErrRsaKeyTooSmall
}
if pk.N.BitLen() > maxRsaKeyBits {
return nil, ErrRsaKeyTooBig
}
return &RsaPublicKey{k: *pk}, nil
}

View File

@ -0,0 +1,162 @@
package crypto
import (
"crypto/rand"
"testing"
)
func TestRSABasicSignAndVerify(t *testing.T) {
priv, pub, err := GenerateRSAKeyPair(2048, rand.Reader)
if err != nil {
t.Fatal(err)
}
data := []byte("hello! and welcome to some awesome crypto primitives")
sig, err := priv.Sign(data)
if err != nil {
t.Fatal(err)
}
ok, err := pub.Verify(data, sig)
if err != nil {
t.Fatal(err)
}
if !ok {
t.Fatal("signature didnt match")
}
// change data
data[0] = ^data[0]
ok, err = pub.Verify(data, sig)
if err == nil {
t.Fatal("should have produced a verification error")
}
if ok {
t.Fatal("signature matched and shouldn't")
}
}
func TestRSASmallKey(t *testing.T) {
_, _, err := GenerateRSAKeyPair(MinRsaKeyBits/2, rand.Reader)
if err != ErrRsaKeyTooSmall {
t.Fatal("should have refused to create small RSA key")
}
MinRsaKeyBits /= 2
badPriv, badPub, err := GenerateRSAKeyPair(MinRsaKeyBits, rand.Reader)
if err != nil {
t.Fatalf("should have succeeded, got: %s", err)
}
pubBytes, err := MarshalPublicKey(badPub)
if err != nil {
t.Fatal(err)
}
privBytes, err := MarshalPrivateKey(badPriv)
if err != nil {
t.Fatal(err)
}
MinRsaKeyBits *= 2
_, err = UnmarshalPublicKey(pubBytes)
if err != ErrRsaKeyTooSmall {
t.Fatal("should have refused to unmarshal a weak key")
}
_, err = UnmarshalPrivateKey(privBytes)
if err != ErrRsaKeyTooSmall {
t.Fatal("should have refused to unmarshal a weak key")
}
}
func TestRSABigKeyFailsToGenerate(t *testing.T) {
_, _, err := GenerateRSAKeyPair(maxRsaKeyBits*2, rand.Reader)
if err != ErrRsaKeyTooBig {
t.Fatal("should have refused to create too big RSA key")
}
}
func TestRSABigKey(t *testing.T) {
// Make the global limit smaller for this test to run faster.
// Note we also change the limit below, but this is different
origSize := maxRsaKeyBits
maxRsaKeyBits = 2048
defer func() { maxRsaKeyBits = origSize }() //
maxRsaKeyBits *= 2
badPriv, badPub, err := GenerateRSAKeyPair(maxRsaKeyBits, rand.Reader)
if err != nil {
t.Fatalf("should have succeeded, got: %s", err)
}
pubBytes, err := MarshalPublicKey(badPub)
if err != nil {
t.Fatal(err)
}
privBytes, err := MarshalPrivateKey(badPriv)
if err != nil {
t.Fatal(err)
}
maxRsaKeyBits /= 2
_, err = UnmarshalPublicKey(pubBytes)
if err != ErrRsaKeyTooBig {
t.Fatal("should have refused to unmarshal a too big key")
}
_, err = UnmarshalPrivateKey(privBytes)
if err != ErrRsaKeyTooBig {
t.Fatal("should have refused to unmarshal a too big key")
}
}
func TestRSASignZero(t *testing.T) {
priv, pub, err := GenerateRSAKeyPair(2048, rand.Reader)
if err != nil {
t.Fatal(err)
}
data := make([]byte, 0)
sig, err := priv.Sign(data)
if err != nil {
t.Fatal(err)
}
ok, err := pub.Verify(data, sig)
if err != nil {
t.Fatal(err)
}
if !ok {
t.Fatal("signature didn't match")
}
}
func TestRSAMarshalLoop(t *testing.T) {
priv, pub, err := GenerateRSAKeyPair(2048, rand.Reader)
if err != nil {
t.Fatal(err)
}
privB, err := MarshalPrivateKey(priv)
if err != nil {
t.Fatal(err)
}
privNew, err := UnmarshalPrivateKey(privB)
if err != nil {
t.Fatal(err)
}
if !priv.Equals(privNew) || !privNew.Equals(priv) {
t.Fatal("keys are not equal")
}
pubB, err := MarshalPublicKey(pub)
if err != nil {
t.Fatal(err)
}
pubNew, err := UnmarshalPublicKey(pubB)
if err != nil {
t.Fatal(err)
}
if !pub.Equals(pubNew) || !pubNew.Equals(pub) {
t.Fatal("keys are not equal")
}
}

View File

@ -0,0 +1,127 @@
package crypto
import (
"fmt"
"io"
pb "github.com/libp2p/go-libp2p/core/crypto/pb"
"github.com/libp2p/go-libp2p/core/internal/catch"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/decred/dcrd/dcrec/secp256k1/v4/ecdsa"
"github.com/minio/sha256-simd"
)
// Secp256k1PrivateKey is a Secp256k1 private key
type Secp256k1PrivateKey secp256k1.PrivateKey
// Secp256k1PublicKey is a Secp256k1 public key
type Secp256k1PublicKey secp256k1.PublicKey
// GenerateSecp256k1Key generates a new Secp256k1 private and public key pair
func GenerateSecp256k1Key(src io.Reader) (PrivKey, PubKey, error) {
privk, err := secp256k1.GeneratePrivateKey()
if err != nil {
return nil, nil, err
}
k := (*Secp256k1PrivateKey)(privk)
return k, k.GetPublic(), nil
}
// UnmarshalSecp256k1PrivateKey returns a private key from bytes
func UnmarshalSecp256k1PrivateKey(data []byte) (k PrivKey, err error) {
if len(data) != secp256k1.PrivKeyBytesLen {
return nil, fmt.Errorf("expected secp256k1 data size to be %d", secp256k1.PrivKeyBytesLen)
}
defer func() { catch.HandlePanic(recover(), &err, "secp256k1 private-key unmarshal") }()
privk := secp256k1.PrivKeyFromBytes(data)
return (*Secp256k1PrivateKey)(privk), nil
}
// UnmarshalSecp256k1PublicKey returns a public key from bytes
func UnmarshalSecp256k1PublicKey(data []byte) (_k PubKey, err error) {
defer func() { catch.HandlePanic(recover(), &err, "secp256k1 public-key unmarshal") }()
k, err := secp256k1.ParsePubKey(data)
if err != nil {
return nil, err
}
return (*Secp256k1PublicKey)(k), nil
}
// Type returns the private key type
func (k *Secp256k1PrivateKey) Type() pb.KeyType {
return pb.KeyType_Secp256k1
}
// Raw returns the bytes of the key
func (k *Secp256k1PrivateKey) Raw() ([]byte, error) {
return (*secp256k1.PrivateKey)(k).Serialize(), nil
}
// Equals compares two private keys
func (k *Secp256k1PrivateKey) Equals(o Key) bool {
sk, ok := o.(*Secp256k1PrivateKey)
if !ok {
return basicEquals(k, o)
}
return k.GetPublic().Equals(sk.GetPublic())
}
// Sign returns a signature from input data
func (k *Secp256k1PrivateKey) Sign(data []byte) (_sig []byte, err error) {
defer func() { catch.HandlePanic(recover(), &err, "secp256k1 signing") }()
key := (*secp256k1.PrivateKey)(k)
hash := sha256.Sum256(data)
sig := ecdsa.Sign(key, hash[:])
return sig.Serialize(), nil
}
// GetPublic returns a public key
func (k *Secp256k1PrivateKey) GetPublic() PubKey {
return (*Secp256k1PublicKey)((*secp256k1.PrivateKey)(k).PubKey())
}
// Type returns the public key type
func (k *Secp256k1PublicKey) Type() pb.KeyType {
return pb.KeyType_Secp256k1
}
// Raw returns the bytes of the key
func (k *Secp256k1PublicKey) Raw() (res []byte, err error) {
defer func() { catch.HandlePanic(recover(), &err, "secp256k1 public key marshaling") }()
return (*secp256k1.PublicKey)(k).SerializeCompressed(), nil
}
// Equals compares two public keys
func (k *Secp256k1PublicKey) Equals(o Key) bool {
sk, ok := o.(*Secp256k1PublicKey)
if !ok {
return basicEquals(k, o)
}
return (*secp256k1.PublicKey)(k).IsEqual((*secp256k1.PublicKey)(sk))
}
// Verify compares a signature against the input data
func (k *Secp256k1PublicKey) Verify(data []byte, sigStr []byte) (success bool, err error) {
defer func() {
catch.HandlePanic(recover(), &err, "secp256k1 signature verification")
// To be extra safe.
if err != nil {
success = false
}
}()
sig, err := ecdsa.ParseDERSignature(sigStr)
if err != nil {
return false, err
}
hash := sha256.Sum256(data)
return sig.Verify(hash[:], (*secp256k1.PublicKey)(k)), nil
}

View File

@ -0,0 +1,96 @@
package crypto
import (
"crypto/rand"
"testing"
)
func TestSecp256k1BasicSignAndVerify(t *testing.T) {
priv, pub, err := GenerateSecp256k1Key(rand.Reader)
if err != nil {
t.Fatal(err)
}
data := []byte("hello! and welcome to some awesome crypto primitives")
sig, err := priv.Sign(data)
if err != nil {
t.Fatal(err)
}
ok, err := pub.Verify(data, sig)
if err != nil {
t.Fatal(err)
}
if !ok {
t.Fatal("signature didnt match")
}
// change data
data[0] = ^data[0]
ok, err = pub.Verify(data, sig)
if err != nil {
t.Fatal(err)
}
if ok {
t.Fatal("signature matched and shouldn't")
}
}
func TestSecp256k1SignZero(t *testing.T) {
priv, pub, err := GenerateSecp256k1Key(rand.Reader)
if err != nil {
t.Fatal(err)
}
data := make([]byte, 0)
sig, err := priv.Sign(data)
if err != nil {
t.Fatal(err)
}
ok, err := pub.Verify(data, sig)
if err != nil {
t.Fatal(err)
}
if !ok {
t.Fatal("signature didn't match")
}
}
func TestSecp256k1MarshalLoop(t *testing.T) {
priv, pub, err := GenerateSecp256k1Key(rand.Reader)
if err != nil {
t.Fatal(err)
}
privB, err := MarshalPrivateKey(priv)
if err != nil {
t.Fatal(err)
}
privNew, err := UnmarshalPrivateKey(privB)
if err != nil {
t.Fatal(err)
}
if !priv.Equals(privNew) || !privNew.Equals(priv) {
t.Fatal("keys are not equal")
}
pubB, err := MarshalPublicKey(pub)
if err != nil {
t.Fatal(err)
}
pubNew, err := UnmarshalPublicKey(pubB)
if err != nil {
t.Fatal(err)
}
if !pub.Equals(pubNew) || !pubNew.Equals(pub) {
t.Fatal("keys are not equal")
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1 @@
 1A½`jPLDò4”ØóNò[¹µ-ªÐX¾ƒ¶àF±X

View File

@ -0,0 +1 @@
!5@­„*ψ¤5Q©Mƒƒ¥U©&Pk<50>ωS<CF89>磡³ΦΆ

View File

@ -0,0 +1 @@
0D 1§Ö3ÂóäZŤCuú¨Ü›˘@ĹőłĘŇČţLó<4C>ň IęŤ!źE†<EFBFBD>ŇGuŐCꏲ<EFBFBD>pCGű5I<@;ÂÂY˛ťž

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,27 @@
// Package discovery provides service advertisement and peer discovery interfaces for libp2p.
package discovery
import (
"context"
"time"
"github.com/libp2p/go-libp2p/core/peer"
)
// Advertiser is an interface for advertising services
type Advertiser interface {
// Advertise advertises a service
Advertise(ctx context.Context, ns string, opts ...Option) (time.Duration, error)
}
// Discoverer is an interface for peer discovery
type Discoverer interface {
// FindPeers discovers peers providing a service
FindPeers(ctx context.Context, ns string, opts ...Option) (<-chan peer.AddrInfo, error)
}
// Discovery is an interface that combines service advertisement and peer discovery
type Discovery interface {
Advertiser
Discoverer
}

View File

@ -0,0 +1,41 @@
package discovery
import "time"
// DiscoveryOpt is a single discovery option.
type Option func(opts *Options) error
// DiscoveryOpts is a set of discovery options.
type Options struct {
Ttl time.Duration
Limit int
// Other (implementation-specific) options
Other map[interface{}]interface{}
}
// Apply applies the given options to this DiscoveryOpts
func (opts *Options) Apply(options ...Option) error {
for _, o := range options {
if err := o(opts); err != nil {
return err
}
}
return nil
}
// TTL is an option that provides a hint for the duration of an advertisement
func TTL(ttl time.Duration) Option {
return func(opts *Options) error {
opts.Ttl = ttl
return nil
}
}
// Limit is an option that provides an upper bound on the peer count for discovery
func Limit(limit int) Option {
return func(opts *Options) error {
opts.Limit = limit
return nil
}
}

View File

@ -0,0 +1,83 @@
package event
import (
"github.com/libp2p/go-libp2p/core/record"
ma "github.com/multiformats/go-multiaddr"
)
// AddrAction represents an action taken on one of a Host's listen addresses.
// It is used to add context to address change events in EvtLocalAddressesUpdated.
type AddrAction int
const (
// Unknown means that the event producer was unable to determine why the address
// is in the current state.
Unknown AddrAction = iota
// Added means that the address is new and was not present prior to the event.
Added
// Maintained means that the address was not altered between the current and
// previous states.
Maintained
// Removed means that the address was removed from the Host.
Removed
)
// UpdatedAddress is used in the EvtLocalAddressesUpdated event to convey
// address change information.
type UpdatedAddress struct {
// Address contains the address that was updated.
Address ma.Multiaddr
// Action indicates what action was taken on the address during the
// event. May be Unknown if the event producer cannot produce diffs.
Action AddrAction
}
// EvtLocalAddressesUpdated should be emitted when the set of listen addresses for
// the local host changes. This may happen for a number of reasons. For example,
// we may have opened a new relay connection, established a new NAT mapping via
// UPnP, or been informed of our observed address by another peer.
//
// EvtLocalAddressesUpdated contains a snapshot of the current listen addresses,
// and may also contain a diff between the current state and the previous state.
// If the event producer is capable of creating a diff, the Diffs field will be
// true, and event consumers can inspect the Action field of each UpdatedAddress
// to see how each address was modified.
//
// For example, the Action will tell you whether an address in
// the Current list was Added by the event producer, or was Maintained without
// changes. Addresses that were removed from the Host will have the AddrAction
// of Removed, and will be in the Removed list.
//
// If the event producer is not capable or producing diffs, the Diffs field will
// be false, the Removed list will always be empty, and the Action for each
// UpdatedAddress in the Current list will be Unknown.
//
// In addition to the above, EvtLocalAddressesUpdated also contains the updated peer.PeerRecord
// for the Current set of listen addresses, wrapped in a record.Envelope and signed by the Host's private key.
// This record can be shared with other peers to inform them of what we believe are our diallable addresses
// a secure and authenticated way.
type EvtLocalAddressesUpdated struct {
// Diffs indicates whether this event contains a diff of the Host's previous
// address set.
Diffs bool
// Current contains all current listen addresses for the Host.
// If Diffs == true, the Action field of each UpdatedAddress will tell
// you whether an address was Added, or was Maintained from the previous
// state.
Current []UpdatedAddress
// Removed contains addresses that were removed from the Host.
// This field is only set when Diffs == true.
Removed []UpdatedAddress
// SignedPeerRecord contains our own updated peer.PeerRecord, listing the addresses enumerated in Current.
// wrapped in a record.Envelope and signed by the Host's private key.
SignedPeerRecord *record.Envelope
}

100
go-libp2p/core/event/bus.go Normal file
View File

@ -0,0 +1,100 @@
package event
import (
"io"
"reflect"
)
// SubscriptionOpt represents a subscriber option. Use the options exposed by the implementation of choice.
type SubscriptionOpt = func(interface{}) error
// EmitterOpt represents an emitter option. Use the options exposed by the implementation of choice.
type EmitterOpt = func(interface{}) error
// CancelFunc closes a subscriber.
type CancelFunc = func()
// wildcardSubscriptionType is a virtual type to represent wildcard
// subscriptions.
type wildcardSubscriptionType interface{}
// WildcardSubscription is the type to subscribe to receive all events
// emitted in the eventbus.
var WildcardSubscription = new(wildcardSubscriptionType)
// Emitter represents an actor that emits events onto the eventbus.
type Emitter interface {
io.Closer
// Emit emits an event onto the eventbus. If any channel subscribed to the topic is blocked,
// calls to Emit will block.
//
// Calling this function with wrong event type will cause a panic.
Emit(evt interface{}) error
}
// Subscription represents a subscription to one or multiple event types.
type Subscription interface {
io.Closer
// Out returns the channel from which to consume events.
Out() <-chan interface{}
// Name returns the name for the subscription
Name() string
}
// Bus is an interface for a type-based event delivery system.
type Bus interface {
// Subscribe creates a new Subscription.
//
// eventType can be either a pointer to a single event type, or a slice of pointers to
// subscribe to multiple event types at once, under a single subscription (and channel).
//
// Failing to drain the channel may cause publishers to block.
//
// If you want to subscribe to ALL events emitted in the bus, use
// `WildcardSubscription` as the `eventType`:
//
// eventbus.Subscribe(WildcardSubscription)
//
// Simple example
//
// sub, err := eventbus.Subscribe(new(EventType))
// defer sub.Close()
// for e := range sub.Out() {
// event := e.(EventType) // guaranteed safe
// [...]
// }
//
// Multi-type example
//
// sub, err := eventbus.Subscribe([]interface{}{new(EventA), new(EventB)})
// defer sub.Close()
// for e := range sub.Out() {
// select e.(type):
// case EventA:
// [...]
// case EventB:
// [...]
// }
// }
Subscribe(eventType interface{}, opts ...SubscriptionOpt) (Subscription, error)
// Emitter creates a new event emitter.
//
// eventType accepts typed nil pointers, and uses the type information for wiring purposes.
//
// Example:
// em, err := eventbus.Emitter(new(EventT))
// defer em.Close() // MUST call this after being done with the emitter
// em.Emit(EventT{})
Emitter(eventType interface{}, opts ...EmitterOpt) (Emitter, error)
// GetAllEventTypes returns all the event types that this bus knows about
// (having emitters and subscribers). It omits the WildcardSubscription.
//
// The caller is guaranteed that this function will only return value types;
// no pointer types will be returned.
GetAllEventTypes() []reflect.Type
}

View File

@ -0,0 +1,21 @@
package event
// RawJSON is a type that contains a raw JSON string.
type RawJSON string
// GenericDHTEvent is a type that encapsulates an actual DHT event by carrying
// its raw JSON.
//
// Context: the DHT event system is rather bespoke and a bit messy at the time,
// so until we unify/clean that up, this event bridges the gap. It should only
// be consumed for informational purposes.
//
// EXPERIMENTAL: this will likely be removed if/when the DHT event types are
// hoisted to core, and the DHT event system is reconciled with the eventbus.
type GenericDHTEvent struct {
// Type is the type of the DHT event that occurred.
Type string
// Raw is the raw JSON representation of the event payload.
Raw RawJSON
}

View File

@ -0,0 +1,11 @@
// Package event contains the abstractions for a local event bus, along with the standard events
// that libp2p subsystems may emit.
//
// Source code is arranged as follows:
// - doc.go: this file.
// - bus.go: abstractions for the event bus.
// - rest: event structs, sensibly categorised in files by entity, and following this naming convention:
// Evt[Entity (noun)][Event (verb past tense / gerund)]
// The past tense is used to convey that something happened, whereas the gerund form of the verb (-ing)
// expresses that a process is in progress. Examples: EvtConnEstablishing, EvtConnEstablished.
package event

View File

@ -0,0 +1,17 @@
package event
import "github.com/libp2p/go-libp2p/core/peer"
// EvtPeerIdentificationCompleted is emitted when the initial identification round for a peer is completed.
type EvtPeerIdentificationCompleted struct {
// Peer is the ID of the peer whose identification succeeded.
Peer peer.ID
}
// EvtPeerIdentificationFailed is emitted when the initial identification round for a peer failed.
type EvtPeerIdentificationFailed struct {
// Peer is the ID of the peer whose identification failed.
Peer peer.ID
// Reason is the reason why identification failed.
Reason error
}

View File

@ -0,0 +1,18 @@
package event
import "github.com/libp2p/go-libp2p/core/network"
// EvtNATDeviceTypeChanged is an event struct to be emitted when the type of the NAT device changes for a Transport Protocol.
//
// Note: This event is meaningful ONLY if the AutoNAT Reachability is Private.
// Consumers of this event should ALSO consume the `EvtLocalReachabilityChanged` event and interpret
// this event ONLY if the Reachability on the `EvtLocalReachabilityChanged` is Private.
type EvtNATDeviceTypeChanged struct {
// TransportProtocol is the Transport Protocol for which the NAT Device Type has been determined.
TransportProtocol network.NATTransportProtocol
// NatDeviceType indicates the type of the NAT Device for the Transport Protocol.
// Currently, it can be either a `Cone NAT` or a `Symmetric NAT`. Please see the detailed documentation
// on `network.NATDeviceType` enumerations for a better understanding of what these types mean and
// how they impact Connectivity and Hole Punching.
NatDeviceType network.NATDeviceType
}

View File

@ -0,0 +1,55 @@
package event
import (
"github.com/libp2p/go-libp2p/core/network"
"github.com/libp2p/go-libp2p/core/peer"
)
// EvtPeerConnectednessChanged should be emitted every time the "connectedness" to a
// given peer changes. Specifically, this event is emitted in the following
// cases:
//
// - Connectedness = Connected: Every time we transition from having no
// connections to a peer to having at least one connection to the peer.
// - Connectedness = NotConnected: Every time we transition from having at least
// one connection to a peer to having no connections to the peer.
//
// Additional connectedness states may be added in the future. This list should
// not be considered exhaustive.
//
// Take note:
//
// - It's possible to have _multiple_ connections to a given peer.
// - Both libp2p and networks are asynchronous.
//
// This means that all the following situations are possible:
//
// A connection is cut and is re-established:
//
// - Peer A observes a transition from Connected -> NotConnected -> Connected
// - Peer B observes a transition from Connected -> NotConnected -> Connected
//
// Explanation: Both peers observe the connection die. This is the "nice" case.
//
// A connection is cut and is re-established.
//
// - Peer A observes a transition from Connected -> NotConnected -> Connected.
// - Peer B observes no transition.
//
// Explanation: Peer A re-establishes the dead connection. Peer B observes the
// new connection form before it observes the old connection die.
//
// A connection is cut:
//
// - Peer A observes no transition.
// - Peer B observes no transition.
//
// Explanation: There were two connections and one was cut. This connection
// might have been in active use but neither peer will observe a change in
// "connectedness". Peers should always make sure to retry network requests.
type EvtPeerConnectednessChanged struct {
// Peer is the remote peer whose connectedness has changed.
Peer peer.ID
// Connectedness is the new connectedness state.
Connectedness network.Connectedness
}

View File

@ -0,0 +1,26 @@
package event
import (
peer "github.com/libp2p/go-libp2p/core/peer"
protocol "github.com/libp2p/go-libp2p/core/protocol"
)
// EvtPeerProtocolsUpdated should be emitted when a peer we're connected to adds or removes protocols from their stack.
type EvtPeerProtocolsUpdated struct {
// Peer is the peer whose protocols were updated.
Peer peer.ID
// Added enumerates the protocols that were added by this peer.
Added []protocol.ID
// Removed enumerates the protocols that were removed by this peer.
Removed []protocol.ID
}
// EvtLocalProtocolsUpdated should be emitted when stream handlers are attached or detached from the local host.
// For handlers attached with a matcher predicate (host.SetStreamHandlerMatch()), only the protocol ID will be
// included in this event.
type EvtLocalProtocolsUpdated struct {
// Added enumerates the protocols that were added locally.
Added []protocol.ID
// Removed enumerates the protocols that were removed locally.
Removed []protocol.ID
}

View File

@ -0,0 +1,13 @@
package event
import (
"github.com/libp2p/go-libp2p/core/network"
)
// EvtLocalReachabilityChanged is an event struct to be emitted when the local's
// node reachability changes state.
//
// This event is usually emitted by the AutoNAT subsystem.
type EvtLocalReachabilityChanged struct {
Reachability network.Reachability
}

View File

@ -0,0 +1,11 @@
package host
import "github.com/libp2p/go-libp2p/core/peer"
// InfoFromHost returns a peer.AddrInfo struct with the Host's ID and all of its Addrs.
func InfoFromHost(h Host) *peer.AddrInfo {
return &peer.AddrInfo{
ID: h.ID(),
Addrs: h.Addrs(),
}
}

View File

@ -0,0 +1,75 @@
// Package host provides the core Host interface for libp2p.
//
// Host represents a single libp2p node in a peer-to-peer network.
package host
import (
"context"
"github.com/libp2p/go-libp2p/core/connmgr"
"github.com/libp2p/go-libp2p/core/event"
"github.com/libp2p/go-libp2p/core/network"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-libp2p/core/peerstore"
"github.com/libp2p/go-libp2p/core/protocol"
ma "github.com/multiformats/go-multiaddr"
)
// Host is an object participating in a p2p network, which
// implements protocols or provides services. It handles
// requests like a Server, and issues requests like a Client.
// It is called Host because it is both Server and Client (and Peer
// may be confusing).
type Host interface {
// ID returns the (local) peer.ID associated with this Host
ID() peer.ID
// Peerstore returns the Host's repository of Peer Addresses and Keys.
Peerstore() peerstore.Peerstore
// Returns the listen addresses of the Host
Addrs() []ma.Multiaddr
// Networks returns the Network interface of the Host
Network() network.Network
// Mux returns the Mux multiplexing incoming streams to protocol handlers
Mux() protocol.Switch
// Connect ensures there is a connection between this host and the peer with
// given peer.ID. Connect will absorb the addresses in pi into its internal
// peerstore. If there is not an active connection, Connect will issue a
// h.Network.Dial, and block until a connection is open, or an error is
// returned. // TODO: Relay + NAT.
Connect(ctx context.Context, pi peer.AddrInfo) error
// SetStreamHandler sets the protocol handler on the Host's Mux.
// This is equivalent to:
// host.Mux().SetHandler(proto, handler)
// (Thread-safe)
SetStreamHandler(pid protocol.ID, handler network.StreamHandler)
// SetStreamHandlerMatch sets the protocol handler on the Host's Mux
// using a matching function for protocol selection.
SetStreamHandlerMatch(protocol.ID, func(protocol.ID) bool, network.StreamHandler)
// RemoveStreamHandler removes a handler on the mux that was set by
// SetStreamHandler
RemoveStreamHandler(pid protocol.ID)
// NewStream opens a new stream to given peer p, and writes a p2p/protocol
// header with given ProtocolID. If there is no connection to p, attempts
// to create one. If ProtocolID is "", writes no header.
// (Thread-safe)
NewStream(ctx context.Context, p peer.ID, pids ...protocol.ID) (network.Stream, error)
// Close shuts down the host, its Network, and services.
Close() error
// ConnManager returns this hosts connection manager
ConnManager() connmgr.ConnManager
// EventBus returns the hosts eventbus
EventBus() event.Bus
}

View File

@ -0,0 +1,18 @@
package catch
import (
"fmt"
"io"
"os"
"runtime/debug"
)
var panicWriter io.Writer = os.Stderr
// HandlePanic handles and logs panics.
func HandlePanic(rerr interface{}, err *error, where string) {
if rerr != nil {
fmt.Fprintf(panicWriter, "caught panic: %s\n%s\n", rerr, debug.Stack())
*err = fmt.Errorf("panic in %s: %s", where, rerr)
}
}

View File

@ -0,0 +1,28 @@
package catch
import (
"bytes"
"testing"
"github.com/stretchr/testify/require"
)
func TestCatch(t *testing.T) {
buf := new(bytes.Buffer)
oldPanicWriter := panicWriter
t.Cleanup(func() { panicWriter = oldPanicWriter })
panicWriter = buf
panicAndCatch := func() (err error) {
defer func() { HandlePanic(recover(), &err, "somewhere") }()
panic("here")
}
err := panicAndCatch()
require.Error(t, err)
require.Contains(t, err.Error(), "panic in somewhere: here")
require.Contains(t, buf.String(), "caught panic: here")
}

View File

@ -0,0 +1,176 @@
// Package metrics provides metrics collection and reporting interfaces for libp2p.
package metrics
import (
"time"
"github.com/libp2p/go-flow-metrics"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-libp2p/core/protocol"
)
// BandwidthCounter tracks incoming and outgoing data transferred by the local peer.
// Metrics are available for total bandwidth across all peers / protocols, as well
// as segmented by remote peer ID and protocol ID.
type BandwidthCounter struct {
totalIn flow.Meter
totalOut flow.Meter
protocolIn flow.MeterRegistry
protocolOut flow.MeterRegistry
peerIn flow.MeterRegistry
peerOut flow.MeterRegistry
}
// NewBandwidthCounter creates a new BandwidthCounter.
func NewBandwidthCounter() *BandwidthCounter {
return new(BandwidthCounter)
}
// LogSentMessage records the size of an outgoing message
// without associating the bandwidth to a specific peer or protocol.
func (bwc *BandwidthCounter) LogSentMessage(size int64) {
bwc.totalOut.Mark(uint64(size))
}
// LogRecvMessage records the size of an incoming message
// without associating the bandwidth to a specific peer or protocol.
func (bwc *BandwidthCounter) LogRecvMessage(size int64) {
bwc.totalIn.Mark(uint64(size))
}
// LogSentMessageStream records the size of an outgoing message over a single logical stream.
// Bandwidth is associated with the given protocol.ID and peer.ID.
func (bwc *BandwidthCounter) LogSentMessageStream(size int64, proto protocol.ID, p peer.ID) {
bwc.protocolOut.Get(string(proto)).Mark(uint64(size))
bwc.peerOut.Get(string(p)).Mark(uint64(size))
}
// LogRecvMessageStream records the size of an incoming message over a single logical stream.
// Bandwidth is associated with the given protocol.ID and peer.ID.
func (bwc *BandwidthCounter) LogRecvMessageStream(size int64, proto protocol.ID, p peer.ID) {
bwc.protocolIn.Get(string(proto)).Mark(uint64(size))
bwc.peerIn.Get(string(p)).Mark(uint64(size))
}
// GetBandwidthForPeer returns a Stats struct with bandwidth metrics associated with the given peer.ID.
// The metrics returned include all traffic sent / received for the peer, regardless of protocol.
func (bwc *BandwidthCounter) GetBandwidthForPeer(p peer.ID) (out Stats) {
inSnap := bwc.peerIn.Get(string(p)).Snapshot()
outSnap := bwc.peerOut.Get(string(p)).Snapshot()
return Stats{
TotalIn: int64(inSnap.Total),
TotalOut: int64(outSnap.Total),
RateIn: inSnap.Rate,
RateOut: outSnap.Rate,
}
}
// GetBandwidthForProtocol returns a Stats struct with bandwidth metrics associated with the given protocol.ID.
// The metrics returned include all traffic sent / received for the protocol, regardless of which peers were
// involved.
func (bwc *BandwidthCounter) GetBandwidthForProtocol(proto protocol.ID) (out Stats) {
inSnap := bwc.protocolIn.Get(string(proto)).Snapshot()
outSnap := bwc.protocolOut.Get(string(proto)).Snapshot()
return Stats{
TotalIn: int64(inSnap.Total),
TotalOut: int64(outSnap.Total),
RateIn: inSnap.Rate,
RateOut: outSnap.Rate,
}
}
// GetBandwidthTotals returns a Stats struct with bandwidth metrics for all data sent / received by the
// local peer, regardless of protocol or remote peer IDs.
func (bwc *BandwidthCounter) GetBandwidthTotals() (out Stats) {
inSnap := bwc.totalIn.Snapshot()
outSnap := bwc.totalOut.Snapshot()
return Stats{
TotalIn: int64(inSnap.Total),
TotalOut: int64(outSnap.Total),
RateIn: inSnap.Rate,
RateOut: outSnap.Rate,
}
}
// GetBandwidthByPeer returns a map of all remembered peers and the bandwidth
// metrics with respect to each. This method may be very expensive.
func (bwc *BandwidthCounter) GetBandwidthByPeer() map[peer.ID]Stats {
peers := make(map[peer.ID]Stats)
bwc.peerIn.ForEach(func(p string, meter *flow.Meter) {
id := peer.ID(p)
snap := meter.Snapshot()
stat := peers[id]
stat.TotalIn = int64(snap.Total)
stat.RateIn = snap.Rate
peers[id] = stat
})
bwc.peerOut.ForEach(func(p string, meter *flow.Meter) {
id := peer.ID(p)
snap := meter.Snapshot()
stat := peers[id]
stat.TotalOut = int64(snap.Total)
stat.RateOut = snap.Rate
peers[id] = stat
})
return peers
}
// GetBandwidthByProtocol returns a map of all remembered protocols and
// the bandwidth metrics with respect to each. This method may be moderately
// expensive.
func (bwc *BandwidthCounter) GetBandwidthByProtocol() map[protocol.ID]Stats {
protocols := make(map[protocol.ID]Stats)
bwc.protocolIn.ForEach(func(p string, meter *flow.Meter) {
id := protocol.ID(p)
snap := meter.Snapshot()
stat := protocols[id]
stat.TotalIn = int64(snap.Total)
stat.RateIn = snap.Rate
protocols[id] = stat
})
bwc.protocolOut.ForEach(func(p string, meter *flow.Meter) {
id := protocol.ID(p)
snap := meter.Snapshot()
stat := protocols[id]
stat.TotalOut = int64(snap.Total)
stat.RateOut = snap.Rate
protocols[id] = stat
})
return protocols
}
// Reset clears all stats.
func (bwc *BandwidthCounter) Reset() {
bwc.totalIn.Reset()
bwc.totalOut.Reset()
bwc.protocolIn.Clear()
bwc.protocolOut.Clear()
bwc.peerIn.Clear()
bwc.peerOut.Clear()
}
// TrimIdle trims all timers idle since the given time.
func (bwc *BandwidthCounter) TrimIdle(since time.Time) {
bwc.peerIn.TrimIdle(since)
bwc.peerOut.TrimIdle(since)
bwc.protocolIn.TrimIdle(since)
bwc.protocolOut.TrimIdle(since)
}

View File

@ -0,0 +1,170 @@
package metrics
import (
"fmt"
"sync"
"testing"
"time"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-libp2p/core/protocol"
"github.com/libp2p/go-flow-metrics"
"github.com/benbjohnson/clock"
"github.com/stretchr/testify/require"
)
var cl = clock.NewMock()
func init() {
flow.SetClock(cl)
}
func BenchmarkBandwidthCounter(b *testing.B) {
b.StopTimer()
b.ResetTimer()
for i := 0; i < b.N; i++ {
bwc := NewBandwidthCounter()
round(bwc, b)
}
}
func round(bwc *BandwidthCounter, b *testing.B) {
start := make(chan struct{})
var wg sync.WaitGroup
wg.Add(10000)
for i := 0; i < 1000; i++ {
p := peer.ID(fmt.Sprintf("peer-%d", i))
for j := 0; j < 10; j++ {
proto := protocol.ID(fmt.Sprintf("bitswap-%d", j))
go func() {
defer wg.Done()
<-start
for i := 0; i < 1000; i++ {
bwc.LogSentMessage(100)
bwc.LogSentMessageStream(100, proto, p)
time.Sleep(1 * time.Millisecond)
}
}()
}
}
b.StartTimer()
close(start)
wg.Wait()
b.StopTimer()
}
func TestBandwidthCounter(t *testing.T) {
bwc := NewBandwidthCounter()
for i := 0; i < 40; i++ {
for i := 0; i < 100; i++ {
p := peer.ID(fmt.Sprintf("peer-%d", i))
for j := 0; j < 2; j++ {
proto := protocol.ID(fmt.Sprintf("proto-%d", j))
// make sure the bandwidth counters are active
bwc.LogSentMessage(100)
bwc.LogRecvMessage(50)
bwc.LogSentMessageStream(100, proto, p)
bwc.LogRecvMessageStream(50, proto, p)
// <-start
}
}
cl.Add(100 * time.Millisecond)
}
assertProtocols := func(check func(Stats)) {
byProtocol := bwc.GetBandwidthByProtocol()
require.Len(t, byProtocol, 2, "expected 2 protocols")
for i := 0; i < 2; i++ {
p := protocol.ID(fmt.Sprintf("proto-%d", i))
for _, stats := range [...]Stats{bwc.GetBandwidthForProtocol(p), byProtocol[p]} {
check(stats)
}
}
}
assertPeers := func(check func(Stats)) {
byPeer := bwc.GetBandwidthByPeer()
require.Len(t, byPeer, 100, "expected 100 peers")
for i := 0; i < 100; i++ {
p := peer.ID(fmt.Sprintf("peer-%d", i))
for _, stats := range [...]Stats{bwc.GetBandwidthForPeer(p), byPeer[p]} {
check(stats)
}
}
}
assertPeers(func(stats Stats) {
require.Equal(t, int64(8000), stats.TotalOut)
require.Equal(t, int64(4000), stats.TotalIn)
})
assertProtocols(func(stats Stats) {
require.Equal(t, int64(400000), stats.TotalOut)
require.Equal(t, int64(200000), stats.TotalIn)
})
stats := bwc.GetBandwidthTotals()
require.Equal(t, int64(800000), stats.TotalOut)
require.Equal(t, int64(400000), stats.TotalIn)
}
func TestResetBandwidthCounter(t *testing.T) {
bwc := NewBandwidthCounter()
p := peer.ID("peer-0")
proto := protocol.ID("proto-0")
// We don't calculate bandwidth till we've been active for a second.
bwc.LogSentMessage(42)
bwc.LogRecvMessage(24)
bwc.LogSentMessageStream(100, proto, p)
bwc.LogRecvMessageStream(50, proto, p)
time.Sleep(200 * time.Millisecond) // make sure the meters are registered with the sweeper
cl.Add(time.Second)
bwc.LogSentMessage(42)
bwc.LogRecvMessage(24)
bwc.LogSentMessageStream(100, proto, p)
bwc.LogRecvMessageStream(50, proto, p)
cl.Add(time.Second)
{
stats := bwc.GetBandwidthTotals()
require.Equal(t, int64(84), stats.TotalOut)
require.Equal(t, int64(48), stats.TotalIn)
}
{
stats := bwc.GetBandwidthByProtocol()
require.Len(t, stats, 1)
stat := stats[proto]
require.Equal(t, float64(100), stat.RateOut)
require.Equal(t, float64(50), stat.RateIn)
}
{
stats := bwc.GetBandwidthByPeer()
require.Len(t, stats, 1)
stat := stats[p]
require.Equal(t, float64(100), stat.RateOut)
require.Equal(t, float64(50), stat.RateIn)
}
bwc.Reset()
{
stats := bwc.GetBandwidthTotals()
require.Zero(t, stats.TotalOut)
require.Zero(t, stats.TotalIn)
require.Empty(t, bwc.GetBandwidthByProtocol(), "expected 0 protocols")
require.Empty(t, bwc.GetBandwidthByPeer(), "expected 0 peers")
}
}

View File

@ -0,0 +1,31 @@
// Package metrics provides metrics collection and reporting interfaces for libp2p.
package metrics
import (
"github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-libp2p/core/protocol"
)
// Stats represents a point-in-time snapshot of bandwidth metrics.
//
// The TotalIn and TotalOut fields record cumulative bytes sent / received.
// The RateIn and RateOut fields record bytes sent / received per second.
type Stats struct {
TotalIn int64
TotalOut int64
RateIn float64
RateOut float64
}
// Reporter provides methods for logging and retrieving metrics.
type Reporter interface {
LogSentMessage(int64)
LogRecvMessage(int64)
LogSentMessageStream(int64, protocol.ID, peer.ID)
LogRecvMessageStream(int64, protocol.ID, peer.ID)
GetBandwidthForPeer(peer.ID) Stats
GetBandwidthForProtocol(protocol.ID) Stats
GetBandwidthTotals() Stats
GetBandwidthByPeer() map[peer.ID]Stats
GetBandwidthByProtocol() map[protocol.ID]Stats
}

View File

@ -0,0 +1,93 @@
package network
import (
"context"
"io"
ic "github.com/libp2p/go-libp2p/core/crypto"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-libp2p/core/protocol"
ma "github.com/multiformats/go-multiaddr"
)
// Conn is a connection to a remote peer. It multiplexes streams.
// Usually there is no need to use a Conn directly, but it may
// be useful to get information about the peer on the other side:
//
// stream.Conn().RemotePeer()
type Conn interface {
io.Closer
ConnSecurity
ConnMultiaddrs
ConnStat
ConnScoper
// ID returns an identifier that uniquely identifies this Conn within this
// host, during this run. Connection IDs may repeat across restarts.
ID() string
// NewStream constructs a new Stream over this conn.
NewStream(context.Context) (Stream, error)
// GetStreams returns all open streams over this conn.
GetStreams() []Stream
// IsClosed returns whether a connection is fully closed, so it can
// be garbage collected.
IsClosed() bool
}
// ConnectionState holds information about the connection.
type ConnectionState struct {
// The stream multiplexer used on this connection (if any). For example: /yamux/1.0.0
StreamMultiplexer protocol.ID
// The security protocol used on this connection (if any). For example: /tls/1.0.0
Security protocol.ID
// the transport used on this connection. For example: tcp
Transport string
// indicates whether StreamMultiplexer was selected using inlined muxer negotiation
UsedEarlyMuxerNegotiation bool
}
// ConnSecurity is the interface that one can mix into a connection interface to
// give it the security methods.
type ConnSecurity interface {
// LocalPeer returns our peer ID
LocalPeer() peer.ID
// RemotePeer returns the peer ID of the remote peer.
RemotePeer() peer.ID
// RemotePublicKey returns the public key of the remote peer.
RemotePublicKey() ic.PubKey
// ConnState returns information about the connection state.
ConnState() ConnectionState
}
// ConnMultiaddrs is an interface mixin for connection types that provide multiaddr
// addresses for the endpoints.
type ConnMultiaddrs interface {
// LocalMultiaddr returns the local Multiaddr associated
// with this connection
LocalMultiaddr() ma.Multiaddr
// RemoteMultiaddr returns the remote Multiaddr associated
// with this connection
RemoteMultiaddr() ma.Multiaddr
}
// ConnStat is an interface mixin for connection types that provide connection statistics.
type ConnStat interface {
// Stat stores metadata pertaining to this conn.
Stat() ConnStats
}
// ConnScoper is the interface that one can mix into a connection interface to give it a resource
// management scope
type ConnScoper interface {
// Scope returns the user view of this connection's resource scope
Scope() ConnScope
}

View File

@ -0,0 +1,110 @@
package network
import (
"context"
"time"
)
// DialPeerTimeout is the default timeout for a single call to `DialPeer`. When
// there are multiple concurrent calls to `DialPeer`, this timeout will apply to
// each independently.
var DialPeerTimeout = 60 * time.Second
type noDialCtxKey struct{}
type dialPeerTimeoutCtxKey struct{}
type forceDirectDialCtxKey struct{}
type useTransientCtxKey struct{}
type simConnectCtxKey struct{ isClient bool }
var noDial = noDialCtxKey{}
var forceDirectDial = forceDirectDialCtxKey{}
var useTransient = useTransientCtxKey{}
var simConnectIsServer = simConnectCtxKey{}
var simConnectIsClient = simConnectCtxKey{isClient: true}
// EXPERIMENTAL
// WithForceDirectDial constructs a new context with an option that instructs the network
// to attempt to force a direct connection to a peer via a dial even if a proxied connection to it already exists.
func WithForceDirectDial(ctx context.Context, reason string) context.Context {
return context.WithValue(ctx, forceDirectDial, reason)
}
// EXPERIMENTAL
// GetForceDirectDial returns true if the force direct dial option is set in the context.
func GetForceDirectDial(ctx context.Context) (forceDirect bool, reason string) {
v := ctx.Value(forceDirectDial)
if v != nil {
return true, v.(string)
}
return false, ""
}
// WithSimultaneousConnect constructs a new context with an option that instructs the transport
// to apply hole punching logic where applicable.
// EXPERIMENTAL
func WithSimultaneousConnect(ctx context.Context, isClient bool, reason string) context.Context {
if isClient {
return context.WithValue(ctx, simConnectIsClient, reason)
}
return context.WithValue(ctx, simConnectIsServer, reason)
}
// GetSimultaneousConnect returns true if the simultaneous connect option is set in the context.
// EXPERIMENTAL
func GetSimultaneousConnect(ctx context.Context) (simconnect bool, isClient bool, reason string) {
if v := ctx.Value(simConnectIsClient); v != nil {
return true, true, v.(string)
}
if v := ctx.Value(simConnectIsServer); v != nil {
return true, false, v.(string)
}
return false, false, ""
}
// WithNoDial constructs a new context with an option that instructs the network
// to not attempt a new dial when opening a stream.
func WithNoDial(ctx context.Context, reason string) context.Context {
return context.WithValue(ctx, noDial, reason)
}
// GetNoDial returns true if the no dial option is set in the context.
func GetNoDial(ctx context.Context) (nodial bool, reason string) {
v := ctx.Value(noDial)
if v != nil {
return true, v.(string)
}
return false, ""
}
// GetDialPeerTimeout returns the current DialPeer timeout (or the default).
func GetDialPeerTimeout(ctx context.Context) time.Duration {
if to, ok := ctx.Value(dialPeerTimeoutCtxKey{}).(time.Duration); ok {
return to
}
return DialPeerTimeout
}
// WithDialPeerTimeout returns a new context with the DialPeer timeout applied.
//
// This timeout overrides the default DialPeerTimeout and applies per-dial
// independently.
func WithDialPeerTimeout(ctx context.Context, timeout time.Duration) context.Context {
return context.WithValue(ctx, dialPeerTimeoutCtxKey{}, timeout)
}
// WithUseTransient constructs a new context with an option that instructs the network
// that it is acceptable to use a transient connection when opening a new stream.
func WithUseTransient(ctx context.Context, reason string) context.Context {
return context.WithValue(ctx, useTransient, reason)
}
// GetUseTransient returns true if the use transient option is set in the context.
func GetUseTransient(ctx context.Context) (usetransient bool, reason string) {
v := ctx.Value(useTransient)
if v != nil {
return true, v.(string)
}
return false, ""
}

View File

@ -0,0 +1,59 @@
package network
import (
"context"
"testing"
"time"
"github.com/stretchr/testify/require"
)
func TestDefaultTimeout(t *testing.T) {
ctx := context.Background()
dur := GetDialPeerTimeout(ctx)
if dur != DialPeerTimeout {
t.Fatal("expected default peer timeout")
}
}
func TestNonDefaultTimeout(t *testing.T) {
customTimeout := time.Duration(1)
ctx := context.WithValue(
context.Background(),
dialPeerTimeoutCtxKey{},
customTimeout,
)
dur := GetDialPeerTimeout(ctx)
if dur != customTimeout {
t.Fatal("peer timeout doesn't match set timeout")
}
}
func TestSettingTimeout(t *testing.T) {
customTimeout := time.Duration(1)
ctx := WithDialPeerTimeout(
context.Background(),
customTimeout,
)
dur := GetDialPeerTimeout(ctx)
if dur != customTimeout {
t.Fatal("peer timeout doesn't match set timeout")
}
}
func TestSimultaneousConnect(t *testing.T) {
t.Run("for the server", func(t *testing.T) {
serverCtx := WithSimultaneousConnect(context.Background(), false, "foobar")
ok, isClient, reason := GetSimultaneousConnect(serverCtx)
require.True(t, ok)
require.False(t, isClient)
require.Equal(t, reason, "foobar")
})
t.Run("for the client", func(t *testing.T) {
serverCtx := WithSimultaneousConnect(context.Background(), true, "foo")
ok, isClient, reason := GetSimultaneousConnect(serverCtx)
require.True(t, ok)
require.True(t, isClient)
require.Equal(t, reason, "foo")
})
}

View File

@ -0,0 +1,33 @@
package network
import (
"errors"
"net"
)
type temporaryError string
func (e temporaryError) Error() string { return string(e) }
func (e temporaryError) Temporary() bool { return true }
func (e temporaryError) Timeout() bool { return false }
var _ net.Error = temporaryError("")
// ErrNoRemoteAddrs is returned when there are no addresses associated with a peer during a dial.
var ErrNoRemoteAddrs = errors.New("no remote addresses")
// ErrNoConn is returned when attempting to open a stream to a peer with the NoDial
// option and no usable connection is available.
var ErrNoConn = errors.New("no usable connection to peer")
// ErrTransientConn is returned when attempting to open a stream to a peer with only a transient
// connection, without specifying the UseTransient option.
var ErrTransientConn = errors.New("transient connection to peer")
// ErrResourceLimitExceeded is returned when attempting to perform an operation that would
// exceed system resource limits.
var ErrResourceLimitExceeded = temporaryError("resource limit exceeded")
// ErrResourceScopeClosed is returned when attempting to reserve resources in a closed resource
// scope.
var ErrResourceScopeClosed = errors.New("resource scope closed")

View File

@ -0,0 +1,131 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/libp2p/go-libp2p/core/network (interfaces: ConnManagementScope)
// Package mocknetwork is a generated GoMock package.
package mocknetwork
import (
reflect "reflect"
gomock "github.com/golang/mock/gomock"
network "github.com/libp2p/go-libp2p/core/network"
peer "github.com/libp2p/go-libp2p/core/peer"
)
// MockConnManagementScope is a mock of ConnManagementScope interface.
type MockConnManagementScope struct {
ctrl *gomock.Controller
recorder *MockConnManagementScopeMockRecorder
}
// MockConnManagementScopeMockRecorder is the mock recorder for MockConnManagementScope.
type MockConnManagementScopeMockRecorder struct {
mock *MockConnManagementScope
}
// NewMockConnManagementScope creates a new mock instance.
func NewMockConnManagementScope(ctrl *gomock.Controller) *MockConnManagementScope {
mock := &MockConnManagementScope{ctrl: ctrl}
mock.recorder = &MockConnManagementScopeMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockConnManagementScope) EXPECT() *MockConnManagementScopeMockRecorder {
return m.recorder
}
// BeginSpan mocks base method.
func (m *MockConnManagementScope) BeginSpan() (network.ResourceScopeSpan, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "BeginSpan")
ret0, _ := ret[0].(network.ResourceScopeSpan)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// BeginSpan indicates an expected call of BeginSpan.
func (mr *MockConnManagementScopeMockRecorder) BeginSpan() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BeginSpan", reflect.TypeOf((*MockConnManagementScope)(nil).BeginSpan))
}
// Done mocks base method.
func (m *MockConnManagementScope) Done() {
m.ctrl.T.Helper()
m.ctrl.Call(m, "Done")
}
// Done indicates an expected call of Done.
func (mr *MockConnManagementScopeMockRecorder) Done() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Done", reflect.TypeOf((*MockConnManagementScope)(nil).Done))
}
// PeerScope mocks base method.
func (m *MockConnManagementScope) PeerScope() network.PeerScope {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "PeerScope")
ret0, _ := ret[0].(network.PeerScope)
return ret0
}
// PeerScope indicates an expected call of PeerScope.
func (mr *MockConnManagementScopeMockRecorder) PeerScope() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PeerScope", reflect.TypeOf((*MockConnManagementScope)(nil).PeerScope))
}
// ReleaseMemory mocks base method.
func (m *MockConnManagementScope) ReleaseMemory(arg0 int) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "ReleaseMemory", arg0)
}
// ReleaseMemory indicates an expected call of ReleaseMemory.
func (mr *MockConnManagementScopeMockRecorder) ReleaseMemory(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReleaseMemory", reflect.TypeOf((*MockConnManagementScope)(nil).ReleaseMemory), arg0)
}
// ReserveMemory mocks base method.
func (m *MockConnManagementScope) ReserveMemory(arg0 int, arg1 byte) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ReserveMemory", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// ReserveMemory indicates an expected call of ReserveMemory.
func (mr *MockConnManagementScopeMockRecorder) ReserveMemory(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReserveMemory", reflect.TypeOf((*MockConnManagementScope)(nil).ReserveMemory), arg0, arg1)
}
// SetPeer mocks base method.
func (m *MockConnManagementScope) SetPeer(arg0 peer.ID) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetPeer", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// SetPeer indicates an expected call of SetPeer.
func (mr *MockConnManagementScopeMockRecorder) SetPeer(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetPeer", reflect.TypeOf((*MockConnManagementScope)(nil).SetPeer), arg0)
}
// Stat mocks base method.
func (m *MockConnManagementScope) Stat() network.ScopeStat {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Stat")
ret0, _ := ret[0].(network.ScopeStat)
return ret0
}
// Stat indicates an expected call of Stat.
func (mr *MockConnManagementScopeMockRecorder) Stat() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Stat", reflect.TypeOf((*MockConnManagementScope)(nil).Stat))
}

View File

@ -0,0 +1,105 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/libp2p/go-libp2p/core/network (interfaces: PeerScope)
// Package mocknetwork is a generated GoMock package.
package mocknetwork
import (
reflect "reflect"
gomock "github.com/golang/mock/gomock"
network "github.com/libp2p/go-libp2p/core/network"
peer "github.com/libp2p/go-libp2p/core/peer"
)
// MockPeerScope is a mock of PeerScope interface.
type MockPeerScope struct {
ctrl *gomock.Controller
recorder *MockPeerScopeMockRecorder
}
// MockPeerScopeMockRecorder is the mock recorder for MockPeerScope.
type MockPeerScopeMockRecorder struct {
mock *MockPeerScope
}
// NewMockPeerScope creates a new mock instance.
func NewMockPeerScope(ctrl *gomock.Controller) *MockPeerScope {
mock := &MockPeerScope{ctrl: ctrl}
mock.recorder = &MockPeerScopeMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockPeerScope) EXPECT() *MockPeerScopeMockRecorder {
return m.recorder
}
// BeginSpan mocks base method.
func (m *MockPeerScope) BeginSpan() (network.ResourceScopeSpan, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "BeginSpan")
ret0, _ := ret[0].(network.ResourceScopeSpan)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// BeginSpan indicates an expected call of BeginSpan.
func (mr *MockPeerScopeMockRecorder) BeginSpan() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BeginSpan", reflect.TypeOf((*MockPeerScope)(nil).BeginSpan))
}
// Peer mocks base method.
func (m *MockPeerScope) Peer() peer.ID {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Peer")
ret0, _ := ret[0].(peer.ID)
return ret0
}
// Peer indicates an expected call of Peer.
func (mr *MockPeerScopeMockRecorder) Peer() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Peer", reflect.TypeOf((*MockPeerScope)(nil).Peer))
}
// ReleaseMemory mocks base method.
func (m *MockPeerScope) ReleaseMemory(arg0 int) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "ReleaseMemory", arg0)
}
// ReleaseMemory indicates an expected call of ReleaseMemory.
func (mr *MockPeerScopeMockRecorder) ReleaseMemory(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReleaseMemory", reflect.TypeOf((*MockPeerScope)(nil).ReleaseMemory), arg0)
}
// ReserveMemory mocks base method.
func (m *MockPeerScope) ReserveMemory(arg0 int, arg1 byte) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ReserveMemory", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// ReserveMemory indicates an expected call of ReserveMemory.
func (mr *MockPeerScopeMockRecorder) ReserveMemory(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReserveMemory", reflect.TypeOf((*MockPeerScope)(nil).ReserveMemory), arg0, arg1)
}
// Stat mocks base method.
func (m *MockPeerScope) Stat() network.ScopeStat {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Stat")
ret0, _ := ret[0].(network.ScopeStat)
return ret0
}
// Stat indicates an expected call of Stat.
func (mr *MockPeerScopeMockRecorder) Stat() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Stat", reflect.TypeOf((*MockPeerScope)(nil).Stat))
}

View File

@ -0,0 +1,105 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/libp2p/go-libp2p/core/network (interfaces: ProtocolScope)
// Package mocknetwork is a generated GoMock package.
package mocknetwork
import (
reflect "reflect"
gomock "github.com/golang/mock/gomock"
network "github.com/libp2p/go-libp2p/core/network"
protocol "github.com/libp2p/go-libp2p/core/protocol"
)
// MockProtocolScope is a mock of ProtocolScope interface.
type MockProtocolScope struct {
ctrl *gomock.Controller
recorder *MockProtocolScopeMockRecorder
}
// MockProtocolScopeMockRecorder is the mock recorder for MockProtocolScope.
type MockProtocolScopeMockRecorder struct {
mock *MockProtocolScope
}
// NewMockProtocolScope creates a new mock instance.
func NewMockProtocolScope(ctrl *gomock.Controller) *MockProtocolScope {
mock := &MockProtocolScope{ctrl: ctrl}
mock.recorder = &MockProtocolScopeMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockProtocolScope) EXPECT() *MockProtocolScopeMockRecorder {
return m.recorder
}
// BeginSpan mocks base method.
func (m *MockProtocolScope) BeginSpan() (network.ResourceScopeSpan, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "BeginSpan")
ret0, _ := ret[0].(network.ResourceScopeSpan)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// BeginSpan indicates an expected call of BeginSpan.
func (mr *MockProtocolScopeMockRecorder) BeginSpan() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BeginSpan", reflect.TypeOf((*MockProtocolScope)(nil).BeginSpan))
}
// Protocol mocks base method.
func (m *MockProtocolScope) Protocol() protocol.ID {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Protocol")
ret0, _ := ret[0].(protocol.ID)
return ret0
}
// Protocol indicates an expected call of Protocol.
func (mr *MockProtocolScopeMockRecorder) Protocol() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Protocol", reflect.TypeOf((*MockProtocolScope)(nil).Protocol))
}
// ReleaseMemory mocks base method.
func (m *MockProtocolScope) ReleaseMemory(arg0 int) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "ReleaseMemory", arg0)
}
// ReleaseMemory indicates an expected call of ReleaseMemory.
func (mr *MockProtocolScopeMockRecorder) ReleaseMemory(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReleaseMemory", reflect.TypeOf((*MockProtocolScope)(nil).ReleaseMemory), arg0)
}
// ReserveMemory mocks base method.
func (m *MockProtocolScope) ReserveMemory(arg0 int, arg1 byte) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ReserveMemory", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// ReserveMemory indicates an expected call of ReserveMemory.
func (mr *MockProtocolScopeMockRecorder) ReserveMemory(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReserveMemory", reflect.TypeOf((*MockProtocolScope)(nil).ReserveMemory), arg0, arg1)
}
// Stat mocks base method.
func (m *MockProtocolScope) Stat() network.ScopeStat {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Stat")
ret0, _ := ret[0].(network.ScopeStat)
return ret0
}
// Stat indicates an expected call of Stat.
func (mr *MockProtocolScopeMockRecorder) Stat() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Stat", reflect.TypeOf((*MockProtocolScope)(nil).Stat))
}

View File

@ -0,0 +1,152 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/libp2p/go-libp2p/core/network (interfaces: ResourceManager)
// Package mocknetwork is a generated GoMock package.
package mocknetwork
import (
reflect "reflect"
gomock "github.com/golang/mock/gomock"
network "github.com/libp2p/go-libp2p/core/network"
peer "github.com/libp2p/go-libp2p/core/peer"
protocol "github.com/libp2p/go-libp2p/core/protocol"
multiaddr "github.com/multiformats/go-multiaddr"
)
// MockResourceManager is a mock of ResourceManager interface.
type MockResourceManager struct {
ctrl *gomock.Controller
recorder *MockResourceManagerMockRecorder
}
// MockResourceManagerMockRecorder is the mock recorder for MockResourceManager.
type MockResourceManagerMockRecorder struct {
mock *MockResourceManager
}
// NewMockResourceManager creates a new mock instance.
func NewMockResourceManager(ctrl *gomock.Controller) *MockResourceManager {
mock := &MockResourceManager{ctrl: ctrl}
mock.recorder = &MockResourceManagerMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockResourceManager) EXPECT() *MockResourceManagerMockRecorder {
return m.recorder
}
// Close mocks base method.
func (m *MockResourceManager) Close() error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Close")
ret0, _ := ret[0].(error)
return ret0
}
// Close indicates an expected call of Close.
func (mr *MockResourceManagerMockRecorder) Close() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockResourceManager)(nil).Close))
}
// OpenConnection mocks base method.
func (m *MockResourceManager) OpenConnection(arg0 network.Direction, arg1 bool, arg2 multiaddr.Multiaddr) (network.ConnManagementScope, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "OpenConnection", arg0, arg1, arg2)
ret0, _ := ret[0].(network.ConnManagementScope)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// OpenConnection indicates an expected call of OpenConnection.
func (mr *MockResourceManagerMockRecorder) OpenConnection(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OpenConnection", reflect.TypeOf((*MockResourceManager)(nil).OpenConnection), arg0, arg1, arg2)
}
// OpenStream mocks base method.
func (m *MockResourceManager) OpenStream(arg0 peer.ID, arg1 network.Direction) (network.StreamManagementScope, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "OpenStream", arg0, arg1)
ret0, _ := ret[0].(network.StreamManagementScope)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// OpenStream indicates an expected call of OpenStream.
func (mr *MockResourceManagerMockRecorder) OpenStream(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OpenStream", reflect.TypeOf((*MockResourceManager)(nil).OpenStream), arg0, arg1)
}
// ViewPeer mocks base method.
func (m *MockResourceManager) ViewPeer(arg0 peer.ID, arg1 func(network.PeerScope) error) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ViewPeer", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// ViewPeer indicates an expected call of ViewPeer.
func (mr *MockResourceManagerMockRecorder) ViewPeer(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ViewPeer", reflect.TypeOf((*MockResourceManager)(nil).ViewPeer), arg0, arg1)
}
// ViewProtocol mocks base method.
func (m *MockResourceManager) ViewProtocol(arg0 protocol.ID, arg1 func(network.ProtocolScope) error) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ViewProtocol", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// ViewProtocol indicates an expected call of ViewProtocol.
func (mr *MockResourceManagerMockRecorder) ViewProtocol(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ViewProtocol", reflect.TypeOf((*MockResourceManager)(nil).ViewProtocol), arg0, arg1)
}
// ViewService mocks base method.
func (m *MockResourceManager) ViewService(arg0 string, arg1 func(network.ServiceScope) error) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ViewService", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// ViewService indicates an expected call of ViewService.
func (mr *MockResourceManagerMockRecorder) ViewService(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ViewService", reflect.TypeOf((*MockResourceManager)(nil).ViewService), arg0, arg1)
}
// ViewSystem mocks base method.
func (m *MockResourceManager) ViewSystem(arg0 func(network.ResourceScope) error) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ViewSystem", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// ViewSystem indicates an expected call of ViewSystem.
func (mr *MockResourceManagerMockRecorder) ViewSystem(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ViewSystem", reflect.TypeOf((*MockResourceManager)(nil).ViewSystem), arg0)
}
// ViewTransient mocks base method.
func (m *MockResourceManager) ViewTransient(arg0 func(network.ResourceScope) error) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ViewTransient", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// ViewTransient indicates an expected call of ViewTransient.
func (mr *MockResourceManagerMockRecorder) ViewTransient(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ViewTransient", reflect.TypeOf((*MockResourceManager)(nil).ViewTransient), arg0)
}

Some files were not shown because too many files have changed in this diff Show More