diff --git a/p2p/security/noise/integration_test.go b/p2p/security/noise/integration_test.go index f43a28355d..87b43e0de4 100644 --- a/p2p/security/noise/integration_test.go +++ b/p2p/security/noise/integration_test.go @@ -5,7 +5,7 @@ import ( "context" "crypto/rand" "fmt" - libp2p "github.com/libp2p/go-libp2p" + "github.com/libp2p/go-libp2p" "github.com/libp2p/go-libp2p-core/crypto" "github.com/libp2p/go-libp2p-core/host" net "github.com/libp2p/go-libp2p-core/network" @@ -39,13 +39,10 @@ func makeNode(t *testing.T, seed int64, port int, kp *Keypair) (host.Host, error t.Fatal(err) } - pid, err := peer.IDFromPrivateKey(priv) + tpt, err := New(priv, NoiseKeyPair(kp)) if err != nil { t.Fatal(err) } - - tpt := NewTransport(pid, priv, false, kp) - ip := "0.0.0.0" addr, err := ma.NewMultiaddr(fmt.Sprintf("/ip4/%s/tcp/%d", ip, port)) if err != nil { @@ -69,12 +66,11 @@ func makeNodePipes(t *testing.T, seed int64, port int, rpid peer.ID, rpubkey [32 t.Fatal(err) } - pid, err := peer.IDFromPrivateKey(priv) + tpt, err := New(priv, UseNoisePipes, NoiseKeyPair(kp)) if err != nil { t.Fatal(err) } - tpt := NewTransport(pid, priv, true, kp) tpt.NoiseStaticKeyCache = NewKeyCache() tpt.NoiseStaticKeyCache.Store(rpid, rpubkey) @@ -256,6 +252,20 @@ func TestLibp2pIntegration_XXFallback(t *testing.T) { time.Sleep(time.Second) } +func TestConstrucingWithMaker(t *testing.T) { + kp := GenerateKeypair() + + ctx := context.Background() + h, err := libp2p.New(ctx, + libp2p.Security(ID, + Maker(NoiseKeyPair(kp), UseNoisePipes))) + + if err != nil { + t.Fatalf("unable to create libp2p host with Maker: %v", err) + } + _ = h.Close() +} + func handleStream(stream net.Stream) { defer func() { if err := stream.Close(); err != nil { diff --git a/p2p/security/noise/options.go b/p2p/security/noise/options.go new file mode 100644 index 0000000000..e4a0c155a9 --- /dev/null +++ b/p2p/security/noise/options.go @@ -0,0 +1,44 @@ +package noise + +// UseNoisePipes configures the Noise transport to use the Noise Pipes pattern. +// Noise Pipes attempts to use the more efficient IK handshake pattern when +// dialing a remote peer, if that peer's static Noise key is known. If this +// is unsuccessful, the transport will fallback to using the default XX pattern. +// +// Note that the fallback does not add any additional round-trips vs. simply +// using XX in the first place, however there is a slight processing overhead +// due to the initial decryption attempt of the IK message. +func UseNoisePipes(cfg *config) { + cfg.NoisePipesSupport = true +} + +// NoiseKeyPair configures the Noise transport to use the given Noise static +// keypair. This is distinct from the libp2p Host's identity keypair and is +// used only for Noise. If this option is not provided, a new Noise static +// keypair will be generated when the transport is initialized. +// +// This option is most useful when Noise Pipes is enabled, as longer static +// key lifetimes may lead to more successful IK handshake attempts. +// +// If you do use this option with a key that's been saved to disk, you must +// take care to store the key securely! +func NoiseKeyPair(kp *Keypair) Option { + return func(cfg *config) { + cfg.NoiseKeypair = kp + } +} + +type config struct { + NoiseKeypair *Keypair + NoisePipesSupport bool +} + +type Option func(cfg *config) + +func (cfg *config) applyOptions(opts ...Option) { + for _, opt := range opts { + if opt != nil { + opt(cfg) + } + } +} diff --git a/p2p/security/noise/transport.go b/p2p/security/noise/transport.go index 0a97e546bc..38656aa471 100644 --- a/p2p/security/noise/transport.go +++ b/p2p/security/noise/transport.go @@ -42,26 +42,82 @@ type Transport struct { NoiseKeypair *Keypair } -// NewTransport creates a new noise transport and can be configured to use noise pipes and a given -// noise ed25519 keypair -func NewTransport(localID peer.ID, privkey crypto.PrivKey, noisePipesSupport bool, kp *Keypair) *Transport { +type transportConstructor func(crypto.PrivKey) (*Transport, error) + +// Maker returns a function that will construct a new Noise transport +// using the given Options. The returned function may be provided as a libp2p.Security +// option when configuring a libp2p Host using libp2p.New, and is compatible with the +// "reflection magic" that libp2p.New uses to inject the private identity key: +// +// host := libp2p.New( +// libp2p.Security(noise.ID, noise.Maker())) +// +// The transport can be configured by passing in Options. +// +// To enable the Noise Pipes pattern (which can be more efficient when reconnecting +// to a known peer), pass in the UseNoisePipes Option: +// +// Maker(UseNoisePipes) +// +// To use a specific Noise keypair, pass in the NoiseKeyPair(kp) option, where +// kp is a noise.Keypair struct. This is most useful when using Noise Pipes, whose +// efficiency gains rely on the static Noise key being known in advance. Persisting +// the Noise keypair across process restarts makes it more likely that other peers +// will be able to use the more efficient IK handshake pattern. +// +// Maker(UseNoisePipes, NoiseKeypair(keypairLoadedFromDisk)) +func Maker(options ...Option) transportConstructor { + return func(privKey crypto.PrivKey) (*Transport, error) { + return New(privKey, options...) + } +} + +// New creates a new Noise transport using the given private key as its +// libp2p identity key. This function may be used when you want a transport +// instance and know the libp2p Host's identity key before the Host is initialized. +// When configuring a go-libp2p Host using libp2p.New, it's simpler to use +// Maker instead, which will receive the identity key when the Host +// is initialized. +// +// New supports all the same Options as noise.Maker. +// +// To configure a go-libp2p Host to use the newly created transport, pass it into +// libp2p.New wrapped in a libp2p.Security Option. You will also need to +// make sure to set the libp2p.Identity option so that the Host uses the same +// identity key: +// +// privkey := loadPrivateKeyFromSomewhere() +// noiseTpt := noise.New(privkey) +// host := libp2p.New( +// libp2p.Identity(privkey), +// libp2p.Security(noise.ID, noiseTpt)) +func New(privkey crypto.PrivKey, options ...Option) (*Transport, error) { + localID, err := peer.IDFromPrivateKey(privkey) + if err != nil { + return nil, err + } + + cfg := config{} + cfg.applyOptions(options...) + + kp := cfg.NoiseKeypair if kp == nil { kp = GenerateKeypair() } // the static key cache is only useful if Noise Pipes is enabled var keyCache *KeyCache - if noisePipesSupport { + if cfg.NoisePipesSupport { keyCache = NewKeyCache() } return &Transport{ LocalID: localID, PrivateKey: privkey, - NoisePipesSupport: noisePipesSupport, + NoisePipesSupport: cfg.NoisePipesSupport, NoiseKeypair: kp, NoiseStaticKeyCache: keyCache, - } + }, nil } // SecureInbound runs noise handshake as the responder