Skip to content

Add wg (WireGuard) protocol support#108

Open
goodboy wants to merge 1 commit intomultiformats:masterfrom
baudco:wg_support
Open

Add wg (WireGuard) protocol support#108
goodboy wants to merge 1 commit intomultiformats:masterfrom
baudco:wg_support

Conversation

@goodboy
Copy link
Copy Markdown

@goodboy goodboy commented Apr 15, 2026

Add wg (WireGuard) protocol support

Closes #107.

WireGuard has no entry in the upstream multiaddr
protocols.csv
or the multicodec table yet,
but there's clear demand for declaring WireGuard tunnel endpoints
using multiaddr notation — particularly for overlay-network use cases
where distributed processes communicate over wg interfaces.

This patch adds a draft wg protocol so that multiaddrs like
/ip4/1.2.3.4/udp/51820/wg/<b64-pubkey> can be parsed, validated,
and round-tripped through the existing codec machinery.


Src of research

The following provide ctx on the upstream spec landscape and the
multicodec addition process,


Summary of changes

(8af8da0) Add wg protocol with draft code P_WG = 0x01C7 (unassigned slot between noise 0x01C6 and shs 0x01C8
in the multicodec table).

  • New multiaddr/codecs/wg.py codec: fixed 256-bit (32-byte)
    Curve25519 public key using standard base64 — matching wg(8)
    tooling conventions.
  • Protocol(P_WG, "wg", "wg") entry registered in the default
    PROTOCOLS list.
  • 8 codec unit tests: roundtrip, zero-key, invalid base64,
    wrong-length string/bytes, validate(), registry lookup, full
    Multiaddr integration.
  • 5 multiaddr string test cases (3 invalid, 2 valid) covering IPv4
    and IPv6 stacks.

Scopes changed

  • multiaddr.protocols
    • P_WG = 0x01C7 constant added alongside existing protocol codes.
    • Protocol(P_WG, "wg", "wg") appended after noise in
      PROTOCOLS.
  • multiaddr.codecs.wg (new)
    • Codec class: SIZE=256, IS_PATH=False.
    • to_bytes(): base64-decode + 32-byte length check.
    • to_string(): 32-byte length check + base64-encode.
    • validate(): byte-length assertion.
  • tests.test_protocols
    • wg codec import and 8 new test fns.
  • tests.test_multiaddr
    • 3 invalid + 2 valid parametrized multiaddr strings.

TODOs before landing

  • Submit a draft multicodec addition PR upstream to reserve
    wg,multiaddr,0x01c7,draft in table.csv per the addition
    process
    — this patch uses the code speculatively until
    that lands.

Reviewers

cc @acul71 @pacrob


(this pr content was generated in some part by claude-code)

Introduce a new `wg` multiaddr protocol using draft
code `0x01C7` (unassigned slot between `noise`
0x01C6 and `shs` 0x01C8 in the multicodec table).

Deats,
- `multiaddr/codecs/wg.py`: fixed 256-bit codec
  for Curve25519 public keys using standard base64
  (matching `wg(8)` tooling conventions)
- `P_WG = 0x01C7` constant and `Protocol` entry
  registered in the default `PROTOCOLS` list
- codec unit tests: roundtrip, validation, error
  paths, registry lookup, full `Multiaddr`
  integration
- valid/invalid multiaddr string test cases for
  both IPv4 and IPv6 stacks (e.g.
  `/ip4/1.2.3.4/udp/51820/wg/<b64-pubkey>`)

Further, the `0x01C7` allocation is not yet in the
upstream multicodec table; an addition PR per
https://github.com/multiformats/multicodec?tab=readme-ov-file#adding-new-multicodecs-to-the-table
is needed to make it official.

Prompt-IO: ai/prompt-io/claude/20260415T002453Z_0b6493a_prompt_io.md

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
Copilot AI review requested due to automatic review settings April 15, 2026 00:42
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds initial WireGuard (wg) protocol support to py-multiaddr by introducing a new protocol code (0x01C7) and a corresponding fixed-size codec for 32-byte Curve25519 public keys.

Changes:

  • Register wg as a new protocol (P_WG = 0x01C7) in the default protocol registry.
  • Add multiaddr/codecs/wg.py implementing base64 encode/decode for 32-byte public keys.
  • Extend test suite with wg codec/unit/integration cases and multiaddr valid/invalid string fixtures.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
multiaddr/protocols.py Adds P_WG constant and registers the wg protocol in PROTOCOLS.
multiaddr/codecs/wg.py New codec for WireGuard public keys (32 bytes, base64).
tests/test_protocols.py Adds wg codec tests and a Multiaddr integration test.
tests/test_multiaddr.py Adds wg to invalid/valid multiaddr string parameter sets.
ai/prompt-io/claude/README.md Documents prompt logging policy for Claude-assisted changes.
ai/prompt-io/claude/20260415T002453Z_0b6493a_prompt_io*.md Stores the prompt I/O log for this change.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread multiaddr/codecs/wg.py
Comment on lines +33 to +56
def to_bytes(self, proto: Any, string: str) -> bytes:
try:
raw = base64.b64decode(string, validate=True)
except Exception as exc:
raise ValueError(
f"invalid base64 WireGuard public key: {exc}"
) from exc

if len(raw) != WG_KEY_LENGTH:
raise ValueError(
f"WireGuard public key must be {WG_KEY_LENGTH} bytes, "
f"got {len(raw)}"
)
return raw

def to_string(self, proto: Any, buf: bytes) -> str:
if len(buf) != WG_KEY_LENGTH:
raise BinaryParseError(
f"WireGuard public key must be {WG_KEY_LENGTH} bytes, "
f"got {len(buf)}",
buf,
"wg",
)
return base64.b64encode(buf).decode("ascii")
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wg uses standard base64, which can include the '/' character. Because multiaddr strings are '/'-delimited and _from_string splits on '/', any key containing '/' cannot be represented or parsed, and bytes_to_string() may emit an invalid/ambiguous multiaddr. Encode reserved characters in to_string (at least '/') and decode them in to_bytes before base64 decoding (or switch to a URL-safe encoding); add a roundtrip test that includes a key whose base64 contains '/'.

Copilot uses AI. Check for mistakes.
Comment thread tests/test_protocols.py
Comment on lines +631 to +637
# A valid 32-byte Curve25519 public key (random)
VALID_WG_KEY_BYTES = os.urandom(32)
VALID_WG_KEY_STRING = base64.b64encode(VALID_WG_KEY_BYTES).decode("ascii")

# A well-known test key (all zeros)
ZERO_WG_KEY_BYTES = b"\x00" * 32
ZERO_WG_KEY_STRING = base64.b64encode(ZERO_WG_KEY_BYTES).decode("ascii")
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tests build VALID_WG_KEY_STRING from os.urandom(32) at import time. Since standard base64 may contain '/', this can make /.../wg/<key> unparsable and cause nondeterministic failures. Use a deterministic test key (and ideally one that exercises the chosen escaping/encoding strategy) instead of randomness.

Copilot uses AI. Check for mistakes.
Comment thread tests/test_multiaddr.py
"/ip4/127.0.0.1/tcp/127/webrtc-direct",
"/ip4/127.0.0.1/tcp/127/webrtc",
"/ip4/1.2.3.4/udp/51820/wg/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
"/ip6/::1/udp/51820/wg/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The added wg valid cases only cover base64 strings without reserved multiaddr delimiters. Once wg supports escaping (or uses URL-safe encoding), add a valid case that includes a key whose encoded form would contain '/' to ensure string parsing and str(ma) roundtrip are reliable.

Suggested change
"/ip6/::1/udp/51820/wg/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
"/ip6/::1/udp/51820/wg/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
"/ip6/::1/udp/51820/wg/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA%2F=",

Copilot uses AI. Check for mistakes.
@goodboy
Copy link
Copy Markdown
Author

goodboy commented Apr 15, 2026

I'm going to wait for real human reviewers before addressing the copilot review if that's ok with y'all 🙏🏼

@acul71
Copy link
Copy Markdown
Contributor

acul71 commented Apr 15, 2026

Hello @goodboy thanks for this PR.

Here's a review and some thoughts about this PR.

AI PR Review: #108 — Add wg (WireGuard) protocol support

Review date: 2026-04-15
Reviewer: AI (per downloads/py-multiaddr-pr-review-prompt.md)
Branch checked out: wg_support (gh pr checkout 108)


1. Summary of changes

  • Type: Feature — draft WireGuard (wg) multiaddr protocol and codec.
  • Affected areas:
    • multiaddr/protocols.pyP_WG = 0x01C7, Protocol(P_WG, "wg", "wg").
    • multiaddr/codecs/wg.py — new codec: 32-byte Curve25519 public key ↔ standard base64.
    • tests/test_protocols.py, tests/test_multiaddr.py — codec, integration, and string fixture tests.
    • ai/prompt-io/claude/* — prompt I/O logging artifacts and README (not part of the library API).
  • Linked issue: #107 — Add wireguard support (feature request for prototype / tunnel-style usage).
  • Breaking changes: None intended; adds a new protocol. However, the string representation as implemented is not safe for arbitrary real WireGuard keys (see Critical issues).

2. Branch sync status and merge conflicts

  • git rev-list --left-right --count origin/master...HEAD: 0 1
    • Behind origin/master: 0 commits
    • Ahead of origin/master: 1 commit
  • Merge probe: git merge --no-commit --no-ff origin/master completed cleanly; merge aborted afterward.
✅ No merge conflicts detected. The PR branch can be merged cleanly into origin/master.

3. Strengths

  • Clear alignment with the request in #107: optional overlay endpoint notation for WireGuard.
  • Codec is small, uses CodecBase, validates 32-byte length on decode/encode, and matches common wg(8) base64 tooling for the isolated encode/decode path.
  • Tests cover invalid base64, wrong lengths, validate(), registry lookup, and several invalid multiaddr strings.
  • PR description documents that 0x01C7 is draft until multicodec table.csv is updated — appropriate transparency for a speculative codepoint.
  • make typecheck and make docs-ci pass on the reviewed branch.

4. Issues found

Critical

  • File: multiaddr/multiaddr.py
    Line(s): 348
    Issue: String multiaddrs are tokenized with a naive split("/"). Standard base64 can contain /. Any wg key whose base64 includes / is split into multiple path segments, so the value passed to the codec is truncated and parsing fails or mis-parses. The same problem exists in multiaddr/transforms.py (string_iter, parts = ...split("/")).
    Suggestion: Pick an encoding that cannot contain / in the textual multiaddr form (see section 9 — Ecosystem-aligned encoding). Do not rely on extending the Python parser to rejoin path segments: other implementations (go-multiaddr, js-multiaddr, rust-multiaddr, etc.) tokenize with split("/") as well; a Python-only join would still produce strings that those stacks cannot parse, or would fork the string format. Match precedents such as multibase/base64url for /certhash rather than raw standard base64.
    Evidence: On this review run, pytest failed test_wg_integration when os.urandom(32) produced a key whose base64 included /, e.g. address
    /ip4/1.2.3.4/udp/51820/wg/nZ4P7A+S3//KHH7sQOYZIhbCpGiA8tykGfTAodal/lw=
    was split so the codec saw only nZ4P7A+S3 as the value.

  • File: tests/test_protocols.py
    Line(s): 631–633, 705–708
    Issue: VALID_WG_KEY_* is derived from os.urandom(32) at import time, which makes test_wg_integration nondeterministic and currently red whenever the encoded key contains /.
    Suggestion: Use a fixed test vector (or a deterministic construction that is known to include / in standard base64 once the encoding strategy is fixed). Add an explicit round-trip test for the delimiter case.

Major

  • File: (missing) newsfragments/
    Line(s): n/a
    Issue: No newsfragments/<NUMBER>.feature.rst (or appropriate type). Per repo policy described in the review prompt, this blocks merge readiness for user-visible features. The PR closes Add wireguard support (with key 'wg'?) #107, so 107.feature.rst is the natural choice if that convention is followed.
    Suggestion: Add a ReST fragment describing the draft wg protocol and its limitations until multicodec allocation lands.

  • File: ai/prompt-io/claude/* (3 new files)
    Line(s): n/a
    Issue: The PR adds NLNet-style prompt logging under ai/. This may be valuable to the author’s org but is unusual for multiformats/* and increases review surface (policy, licensing tone, repo hygiene). Maintainers should decide whether this belongs in the upstream repo or in a fork/private log.
    Suggestion: Confirm with pacrob / acul71 / manusheel before merge; consider dropping from the upstream PR if not a documented project convention.

  • File: multiaddr/codecs/wg.py
    Line(s): 33–39
    Issue: except Exception as exc is broad; acceptable for a thin wrapper but slightly obscures expected failure modes (binascii.Error vs ValueError).
    Suggestion: Catch binascii.Error and ValueError explicitly if you want clearer errors and typing-friendly patterns (optional cleanup).

Minor

  • File: multiaddr/codecs/wg.py
    Line(s): whole file
    Issue: ruff format wants to reformat this file; make lint fails until it is formatted.
    Suggestion: Run ruff format multiaddr/codecs/wg.py (or pre-commit run ruff-format --all-files) and commit.

  • File: README.md / docs/examples.rst (if applicable)
    Line(s): n/a
    Issue: No user-facing example of /.../wg/<key> yet.
    Suggestion: After fixing the string encoding, add a short example and note draft status / multicodec follow-up.


5. Security review

Risk Impact Mitigation
Parsing treats part of a public key as separate path components Medium (failed connections, confusing errors; not memory corruption) Fix encoding so / cannot appear inside the wg value (see section 9); add regression tests.
Broad except Exception around base64 decode Low Narrow exceptions; avoids accidentally swallowing KeyboardInterrupt in odd call paths (unlikely here).
Error strings include decoder details Low Acceptable; avoid logging full multiaddr with live keys in application code (call-site concern).

No DNS/resolver/subprocess/file-system changes in the core feature; scope is local validation and serialization.


6. Documentation and examples

  • Module docstring in multiaddr/codecs/wg.py is helpful and links to multicodec/multiaddr processes.
  • Sphinx make docs-ci succeeds; autodoc picks up the new module.
  • End-user README / examples do not yet document wg; consider adding after the string encoding is fixed.

7. Newsfragment requirement

  • Missing: No newsfragments/107.feature.rst (or 108.feature.rst if the project uses PR numbers when no issue exists — here Add wireguard support (with key 'wg'?) #107 exists and is closed by the PR).
  • Action: Add a fragment with ReST body, trailing newline, and type feature.

8. Tests and validation

Check Result Log
make lint Failedruff format reformats multiaddr/codecs/wg.py downloads/AI-PR-REVIEWS/108/lint_output.log
make typecheck Passed downloads/AI-PR-REVIEWS/108/typecheck_output.log
make test Failedtests/test_protocols.py::test_wg_integration (base64 contained /) downloads/AI-PR-REVIEWS/108/test_output.log
make docs-ci Passed downloads/AI-PR-REVIEWS/108/docs_output.log

Note: Logs were captured on the PR branch as merged from GitHub (wg_support); working tree restored after ruff format auto-changes.


9. Recommendations for improvement

  1. Fix delimiter safety for the textual multiaddr form (this is both a correctness and CI stability issue). Align with whatever go-multiaddr / js-multiaddr or the eventual IETF/multiformats spec chooses, if any precedent exists.
  2. Replace import-time os.urandom in tests with fixed vectors; add a test that requires a / in standard base64 once you support it safely.
  3. Add newsfragments/107.feature.rst.
  4. Run ruff format and re-run make lint.
  5. Resolve whether ai/prompt-io/ should ship in multiformats/py-multiaddr.

Ecosystem-aligned encoding (refined recommendation)

  1. Document explicitly that textual wg values MUST use an encoding that cannot contain / in the value (for example multibase consistent with other binary components such as /certhash, or base64url with stated padding rules). Prefer aligning padding and multibase code with go-multiaddr / existing multiaddr spec text rather than inventing a one-off variant.

  2. Codec behavior (input vs output):

    • What wg(8) gives you: Tools print the Curve25519 public key as standard base64, whose alphabet includes / (and +). That is fine as opaque text, but / must not appear inside a single multiaddr path component, because implementations split the address string on /.
    • Convenience on input: It is still useful to let users paste exactly what WireGuard printed. That can be supported via a narrow, documented path (e.g. a small helper like from_wg_public_key(...), or a documented rule that applies only when building a Multiaddr from raw wg output). Decode standard base64 there, then store 32 raw bytes internally as today.
    • Canonical on output: When serializing to the multiaddr string (str(ma) / to_string), the wg segment should always use the safe, specified encoding from item 1 above (e.g. base64url or multibase—never raw standard base64). So: same key bytes everywhere, but the only form allowed inside a printed multiaddr is the normalized one. That way str(ma) round-trips under split("/") and matches what Go, JS, Rust, etc. can parse if they follow the same rule.
  3. Tests: Use fixed vectors (no import-time os.urandom). Include at least one key whose standard base64 contains /, proving the stored/transmitted multiaddr form does not embed raw / inside the wg component and that parse + str() round-trip works.


10. Questions for the author

  1. Is there an emerging multiformats convention for wg (name, codepoint, encoding) beyond this draft, or should py-multiaddr track a specific reference implementation?
  2. For textual multiaddrs, do you prefer base64url, percent-encoding, or hex for interoperability with other stacks?
  3. Should the prompt I/O files remain in the upstream repo, or live elsewhere per maintainer preference?
  4. Has a multicodec table PR been opened for 0x01C7 / wg?

11. Overall assessment

Dimension Rating
Quality Needs work — core idea is sound; string form and tests are not yet reliable.
Security impact Low (no classic memory/sandbox issues; logic/parsing bug with operational impact).
Merge readiness Needs fixes — failing tests, lint/format, missing newsfragment, and critical string-encoding design gap.
Confidence High — failure reproduced locally; root cause is clear from parser structure and failing trace.

Bottom line: The PR addresses #107 in spirit, but standard base64 in a /-delimited string is incompatible with how py-multiaddr parses strings today. Until that is resolved, real WireGuard keys cannot be represented reliably in textual multiaddr form, and CI can fail randomly. GitHub Copilot’s review on the same point is directionally correct.


Appendix: relevant code references

String parsing uses / splitting:

        parts_list = addr.strip("/").split("/")

Tests use nondeterministic key material at import time:

# A valid 32-byte Curve25519 public key (random)
VALID_WG_KEY_BYTES = os.urandom(32)
VALID_WG_KEY_STRING = base64.b64encode(VALID_WG_KEY_BYTES).decode("ascii")
def test_wg_integration():
    ma = Multiaddr(f"/ip4/1.2.3.4/udp/51820/wg/{VALID_WG_KEY_STRING}")
    assert str(ma) == f"/ip4/1.2.3.4/udp/51820/wg/{VALID_WG_KEY_STRING}"
    assert ma.value_for_protocol(protocols.P_WG) == VALID_WG_KEY_STRING

Addendum: libp2p, WireGuard, and ecosystem projects

Context for PR #108 / issue #107: why a wg component in multiaddrs might matter even though WireGuard is not a first-class libp2p transport.

Core libp2p vs WireGuard as a transport

There is no official, actively maintained “WireGuard transport” wired into the core libp2p stack the way TCP, QUIC, or WebRTC are. Libp2p and WireGuard still meet often in real deployments: libp2p excels at peer discovery, NAT traversal, and relay semantics; WireGuard excels at kernel-fast, modern crypto tunnels for the data plane.

Noise (shared cryptographic lineage)

Libp2p’s default encrypted transports (e.g. the Noise-based handshake in go-libp2p) and WireGuard both draw from the Noise Protocol Framework—the same design space (modern DH, AEAD, simple handshake patterns). They are not the same wire protocol: patterns, framing, and integration differ. The point is only that “libp2p crypto” and “WireGuard crypto” are cousins, not that libp2p runs inside a WG tunnel by default.

Projects that combine libp2p-style networking with WireGuard (or similar goals)

These are illustrative community / product efforts; listing them does not imply endorsement or that py-multiaddr must track any one of them.

Project Role of libp2p / P2P Role of WireGuard (or analogue)
wgmesh (example community implementation) Peer discovery and TLS-style peer control traffic WireGuard for the actual mesh / tunnel data plane
Webmesh Zero-config mesh; uses libp2p circuit relays (and WebRTC / ICE) to negotiate connectivity Manages a WireGuard mesh across OSes
Hyprspace Lightweight VPN over IPFS + libp2p (DHT, hole-punching) Not WireGuard under the hood; WG-inspired; addresses “everyone behind NAT” scenarios libp2p is good at

Several other repos use the name wgmesh; treat the table as archetypes (control plane vs data plane) rather than a single canonical package.

Historical note: go-wireguard in the libp2p org

The archived/experimental repo github.com/libp2p/go-wireguard was an early Go helper around WireGuard interfaces and packet queues (~2016). It was not a maintained, plug-in libp2p transport comparable to today’s stack, and it has not seen active development in years. It is useful mainly as historical context for “WireGuard has been on the radar near libp2p for a long time.”

Tie-in to this review

Overlays that use libp2p (or multiaddr-bearing tooling) for signaling and WireGuard for tunnels benefit from a stable, interoperable way to pin a peer’s WireGuard public key into an address or descriptor. That is the practical motivation for a draft /wg/<key> component—provided the textual encoding cannot break /-delimited parsing (see section 9).

@goodboy
Copy link
Copy Markdown
Author

goodboy commented Apr 16, 2026

Ok so reading this all, I feel like i'm concluding this isn't a totally terrible idea and presuming we resolve the /-in-encoding-discrepancy (obvious issue) this protocol is something addable to the codec and spec longer run yah?

@acul71 ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add wireguard support (with key 'wg'?)

3 participants