Pumpkin-MC/Pumpkin · critical · VineError

Failed to verify Ed25519 signature

Error message

Failed to verify Ed25519 signature

What it means

VineError::InvalidSignature is returned when the Ed25519 signature over the Vine forwarding payload does not verify against the configured public key (vine.rs:165-167). This means the response was not signed by the holder of the matching key or the payload was altered in transit. The server rejects the login to prevent spoofed player identities/IPs.

Solutions

  1. Confirm the backend's public_key matches the proxy's current signing key and re-copy it after any key rotation
  2. If using secret-based derivation, verify both sides use the identical secret string and the same derivation scheme
  3. Ensure only the proxy can reach the backend port so third parties cannot inject forged responses
  4. Update both proxy and server together when upgrading, so signing formats stay in sync

Example fix

// before
public_key = "aaaaaaaa..." # stale key from old proxy install
// after
public_key = "1fc8f9e2a4b7..." # current public key shown by the proxy
Defensive patterns

Strategy: try-catch

Validate before calling

// sanity-check pairing at startup: derive the verifying key and compare
// against a key the proxy reports
let vk = get_verifying_key(&config)?;
println!("expect proxy public key: {}", hex::encode(vk.to_bytes()));

Try / catch

match receive_vine_plugin_response(port, &config, response, challenge) {
    Err(VineError::InvalidSignature) => {
        tracing::warn!("Vine signature check failed: is the proxy's public key up to date?");
        disconnect(DisconnectReason::BadForwardingSignature);
    }
    result => result?,
}

Prevention

When it happens

Trigger: verifying_key.verify(payload, &signature) fails in receive_vine_plugin_response: the proxy signed with a different key than the server configured, the payload bytes were modified, or a non-proxy client forged the packet.

Common situations: Key rotation happened on the proxy but the backend still has the old public key; the proxy and backend use different secrets that derive different keys (e.g. different secret strings, or one side uses hex seed vs. raw-string SHA-256 derivation); a man-in-the-middle or direct-connect client injected a fake response.

Related errors


AI-assisted analysis of Pumpkin-MC/Pumpkin@8d4639e25a (2026-09-09). Data as JSON: /api/errors/702deda04d9ddd4f. Report an issue: GitHub.

Appendix: source

Thrown at crates/pumpkin/src/net/proxy/vine.rs:34

use tracing::debug;

use crate::net::{GameProfile, java::pending::PendingConnection};

pub const VINE_PLAYER_INFO_CHANNEL: &str = "vine:player_info";
pub const VINE_FORWARDING_VERSION: i32 = 1;
pub const MAX_TIMESTAMP_DRIFT_SECS: i64 = 30;

#[derive(Error, Debug)]
pub enum VineError {
    #[error("No response data received")]
    NoData,
    #[error("Vine response data too short (minimum 89 bytes)")]
    DataTooShort,
    #[error("No public key or secret configured for Vine proxy")]
    MissingKeyConfig,
    #[error("Invalid Ed25519 public key")]
    InvalidPublicKey,
    #[error("Failed to verify Ed25519 signature")]
    InvalidSignature,
    #[error("Failed to read forward version")]
    FailedReadForwardVersion,
    #[error("Unsupported forwarding version {0}. Expected {1}")]
    UnsupportedForwardVersion(i32, i32),
    #[error("Vine timestamp expired or desynchronized: skew of {0}s exceeds limit of {1}s")]
    TimestampExpired(i64, i64),
    #[error("Vine challenge nonce mismatch")]
    ChallengeMismatch,
    #[error("Missing expected challenge from pending connection")]
    MissingChallenge,
    #[error("Failed to read address")]
    FailedReadAddress,
    #[error("Failed to parse address")]
    FailedParseAddress,
    #[error("Failed to read game profile name")]
    FailedReadProfileName,
    #[error("Failed to read game profile UUID")]

View on GitHub (pinned to 8d4639e25a)