tailscale/tailscale · error

incorrect len for NodePublic (%d != %d)

Error message

incorrect len for NodePublic (%d != %d)

What it means

NodePublic.UnmarshalBinary accepts the binary prefix but the remaining payload length differs from NodePublicRawLen (the two %d values show got vs want). The blob is truncated, padded, or a different key size, so it cannot be copied into the fixed 32-byte field.

Source

Thrown at types/key/node.go:355

	return parseHex(k.k[:], mem.B(b), mem.S(nodePublicHexPrefix))
}

// MarshalBinary implements encoding.BinaryMarshaler.
func (k NodePublic) MarshalBinary() (data []byte, err error) {
	b := make([]byte, len(nodePublicBinaryPrefix)+NodePublicRawLen)
	copy(b[:len(nodePublicBinaryPrefix)], nodePublicBinaryPrefix)
	copy(b[len(nodePublicBinaryPrefix):], k.k[:])
	return b, nil
}

// UnmarshalBinary implements encoding.BinaryUnmarshaler.
func (k *NodePublic) UnmarshalBinary(in []byte) error {
	data := mem.B(in)
	if !mem.HasPrefix(data, mem.S(nodePublicBinaryPrefix)) {
		return fmt.Errorf("missing/incorrect type prefix %s", nodePublicBinaryPrefix)
	}
	if want, got := len(nodePublicBinaryPrefix)+NodePublicRawLen, data.Len(); want != got {
		return fmt.Errorf("incorrect len for NodePublic (%d != %d)", got, want)
	}

	data.SliceFrom(len(nodePublicBinaryPrefix)).Copy(k.k[:])
	return nil
}

// WireGuardGoString prints k in the same format used by wireguard-go.
func (k NodePublic) WireGuardGoString() string {
	// This implementation deliberately matches the overly complicated
	// implementation in wireguard-go.
	b64 := func(input byte) byte {
		return input + 'A' + byte(((25-int(input))>>8)&6) - byte(((51-int(input))>>8)&75) - byte(((61-int(input))>>8)&15) + byte(((62-int(input))>>8)&3)
	}
	b := []byte("peer(____…____)")
	const first = len("peer(")
	const second = len("peer(____…")
	b[first+0] = b64((k.k[0] >> 2) & 63)
	b[first+1] = b64(((k.k[0] << 4) | (k.k[1] >> 4)) & 63)

View on GitHub (pinned to 6e0912f979)

Solutions

  1. Re-serialize the key with MarshalBinary so the payload is exactly the fixed raw length
  2. Verify storage/transport did not truncate the blob (check lengths before calling UnmarshalBinary)
  3. Treat mismatched lengths as corrupt data: discard and re-fetch the trusted key material
Defensive patterns

Strategy: validation

When it happens

Trigger: Thrown at types/key/node.go:355 when the library encounters an invalid state.

Common situations: See trigger scenarios.


AI-assisted analysis of tailscale/tailscale@6e0912f979 (2026-08-18). Data as JSON: /api/errors/957ed3e08074747f. Report an issue: GitHub.