From 5b833274d46b50a519d6b4e532a7b44f05447c64 Mon Sep 17 00:00:00 2001 From: Alexander Medvedev Date: Tue, 18 Aug 2026 15:46:30 +0200 Subject: [PATCH] fix: pumpkin-plugin-utils --- Cargo.lock | 4 - crates/pumpkin-plugin-api/src/lib.rs | 8 +- crates/pumpkin-plugin-utils/Cargo.toml | 6 +- crates/pumpkin-plugin-utils/src/crypto.rs | 149 ---------- crates/pumpkin-plugin-utils/src/lib.rs | 84 +++--- crates/pumpkin-plugin-utils/src/license.rs | 105 ++------ crates/pumpkin-plugin-utils/src/models.rs | 46 ++-- crates/pumpkin-plugin-utils/src/resolver.rs | 81 ------ crates/pumpkin-plugin-utils/src/wasm.rs | 254 ------------------ .../tests/licensing_test.rs | 117 ++------ crates/pumpkin-plugin-wit | 2 +- .../src/plugin/loader/wasm/wasm_host/mod.rs | 27 +- .../src/plugin/loader/wasm/wasm_host/state.rs | 3 + .../loader/wasm/wasm_host/wit/v0_1/context.rs | 9 +- .../wasm/wasm_host/wit/v0_1/marketplace.rs | 9 - .../loader/wasm/wasm_host/wit/v0_1/mod.rs | 1 - 16 files changed, 128 insertions(+), 777 deletions(-) delete mode 100644 crates/pumpkin-plugin-utils/src/crypto.rs delete mode 100644 crates/pumpkin-plugin-utils/src/resolver.rs delete mode 100644 crates/pumpkin-plugin-utils/src/wasm.rs delete mode 100644 crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/marketplace.rs diff --git a/Cargo.lock b/Cargo.lock index 6164dc32f..0fdfa68b0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3553,8 +3553,6 @@ dependencies = [ name = "pumpkin-plugin-utils" version = "0.1.0" dependencies = [ - "ed25519-dalek", - "hex", "pumpkin-plugin-api", "serde", "serde_json", @@ -3562,8 +3560,6 @@ dependencies = [ "thiserror 2.0.20", "tracing", "ureq", - "wasm-encoder 0.256.0", - "wasmparser 0.256.0", ] [[package]] diff --git a/crates/pumpkin-plugin-api/src/lib.rs b/crates/pumpkin-plugin-api/src/lib.rs index e834ec197..ab266914d 100644 --- a/crates/pumpkin-plugin-api/src/lib.rs +++ b/crates/pumpkin-plugin-api/src/lib.rs @@ -106,14 +106,14 @@ pub mod command { pub use wit::pumpkin::plugin::{ advancement as advancement_wit, bedrock_packets, block_entity, boss_bar, command as command_wit, common, - context::{Context, Server}, + context::{self, Context, MarketplaceMetadata, Server}, damage_types as damage_types_wit, data_components, display as display_wit, enchantments as enchantments_wit, entity, entity_types::EntityType, event::{self as events_wit, EventType}, - gui, i18n, ipc, item_stack, java_dialogs, java_packets, marketplace, particles, permission, - player, recipe as recipe_wit, scoreboard, screens as screens_wit, server, - statistics as statistics_wit, text, uuid, world, + gui, i18n, ipc, item_stack, java_dialogs, java_packets, particles, permission, player, + recipe as recipe_wit, scoreboard, screens as screens_wit, server, statistics as statistics_wit, + text, uuid, world, }; // Convenience re-exports of commonly-used plugin types so plugin authors can diff --git a/crates/pumpkin-plugin-utils/Cargo.toml b/crates/pumpkin-plugin-utils/Cargo.toml index e7063bbec..a5ce2e140 100644 --- a/crates/pumpkin-plugin-utils/Cargo.toml +++ b/crates/pumpkin-plugin-utils/Cargo.toml @@ -3,21 +3,17 @@ name = "pumpkin-plugin-utils" version = "0.1.0" edition.workspace = true license = "MIT OR Apache-2.0" -description = "Utilities for Pumpkin plugins: licensing, signature verification, and update checks." +description = "Utilities for Pumpkin plugins: licensing, lease caching, and update checks." [dependencies] pumpkin-plugin-api = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } -ed25519-dalek = { workspace = true } -hex = { workspace = true } -wasmparser = { workspace = true } tracing = { workspace = true } thiserror = { workspace = true } ureq = { workspace = true, features = ["json"] } [dev-dependencies] -wasm-encoder = "0.256" tempfile = { workspace = true } [lints] diff --git a/crates/pumpkin-plugin-utils/src/crypto.rs b/crates/pumpkin-plugin-utils/src/crypto.rs deleted file mode 100644 index 798fe55c8..000000000 --- a/crates/pumpkin-plugin-utils/src/crypto.rs +++ /dev/null @@ -1,149 +0,0 @@ -//! Cryptographic signature verification utilities. - -use crate::models::WasmSignatureEnvelope; -use ed25519_dalek::{Signature, Verifier, VerifyingKey}; -use thiserror::Error; - -/// Cryptographic verification errors. -#[derive(Debug, Error)] -pub enum CryptoError { - /// Public key format is invalid hex or not 32 bytes. - #[error("Invalid public key hex format: {0}")] - InvalidPublicKeyHex(String), - /// Public key is not a valid Ed25519 point. - #[error("Invalid Ed25519 public key: {0}")] - InvalidVerifyingKey(String), - /// Signature hex format is invalid. - #[error("Invalid signature hex format: {0}")] - InvalidSignatureHex(String), - /// Signature bytes are malformed. - #[error("Invalid Ed25519 signature bytes: {0}")] - InvalidSignatureBytes(String), - /// Cryptographic verification failed (signature does not match data). - #[error("Cryptographic signature verification failed: {0}")] - VerificationFailed(String), -} - -/// Verifies an Ed25519 signature over `clean_wasm + metadata_raw` against a public key. -/// -/// If `expected_public_key_hex` is non-empty, the public key inside the envelope must match -/// the expected key (preventing substitution attacks). -/// -/// # Errors -/// -/// Returns `CryptoError` if parsing fails or if the signature does not verify. -pub fn verify_signature( - clean_wasm: &[u8], - metadata_raw: &[u8], - signature_envelope: &WasmSignatureEnvelope, - expected_public_key_hex: &str, -) -> Result<(), CryptoError> { - // Determine the public key to verify with - let pub_key_hex = if !expected_public_key_hex.is_empty() { - if !signature_envelope.public_key_hex.is_empty() - && !signature_envelope - .public_key_hex - .eq_ignore_ascii_case(expected_public_key_hex) - { - return Err(CryptoError::InvalidPublicKeyHex(format!( - "Signature envelope public key ({}) does not match expected public key ({})", - signature_envelope.public_key_hex, expected_public_key_hex - ))); - } - expected_public_key_hex - } else if !signature_envelope.public_key_hex.is_empty() { - &signature_envelope.public_key_hex - } else { - return Err(CryptoError::InvalidPublicKeyHex( - "No public key available for signature verification".to_string(), - )); - }; - - let pub_key_bytes = - hex::decode(pub_key_hex).map_err(|e| CryptoError::InvalidPublicKeyHex(e.to_string()))?; - - let verifying_key = VerifyingKey::try_from(pub_key_bytes.as_slice()) - .map_err(|e| CryptoError::InvalidVerifyingKey(e.to_string()))?; - - let sig_bytes = hex::decode(&signature_envelope.signature_hex) - .map_err(|e| CryptoError::InvalidSignatureHex(e.to_string()))?; - - let signature = Signature::from_slice(&sig_bytes) - .map_err(|e| CryptoError::InvalidSignatureBytes(e.to_string()))?; - - // Build the sign payload: clean WASM binary + metadata JSON - let mut sign_payload = Vec::with_capacity(clean_wasm.len() + metadata_raw.len()); - sign_payload.extend_from_slice(clean_wasm); - sign_payload.extend_from_slice(metadata_raw); - - verifying_key - .verify(&sign_payload, &signature) - .map_err(|e| CryptoError::VerificationFailed(e.to_string()))?; - - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::*; - use ed25519_dalek::Signer; - - #[test] - fn signature_verification_success() { - let seed = [42u8; 32]; - let signing_key = ed25519_dalek::SigningKey::from_bytes(&seed); - let verifying_key = signing_key.verifying_key(); - let pub_key_hex = hex::encode(verifying_key.to_bytes()); - - let clean_wasm = b"\0asm\x01\0\0\0"; - let meta_raw = br#"{"marketplace_url":"https://market.pumpkinmc.org","plugin_id":1,"plugin_name":"test","version":"0.1.0","dev_id":1,"dev_name":"dev","is_paid":true,"user_id":100,"license_key":"KEY-123","issued_at":"2026-08-17"}"#; - - let mut payload = Vec::new(); - payload.extend_from_slice(clean_wasm); - payload.extend_from_slice(meta_raw); - - let sig = signing_key.sign(&payload); - let env = WasmSignatureEnvelope { - version: 1, - algorithm: "Ed25519".to_string(), - public_key_hex: pub_key_hex.clone(), - signature_hex: hex::encode(sig.to_bytes()), - }; - - let res = verify_signature(clean_wasm, meta_raw, &env, &pub_key_hex); - assert!(res.is_ok()); - } - - #[test] - fn signature_verification_tamper_fails() { - let seed = [42u8; 32]; - let signing_key = ed25519_dalek::SigningKey::from_bytes(&seed); - let verifying_key = signing_key.verifying_key(); - let pub_key_hex = hex::encode(verifying_key.to_bytes()); - - let clean_wasm = b"\0asm\x01\0\0\0"; - let meta_raw = br#"{"marketplace_url":"https://market.pumpkinmc.org","plugin_id":1,"plugin_name":"test","version":"0.1.0","dev_id":1,"dev_name":"dev","is_paid":true,"user_id":100,"license_key":"KEY-123","issued_at":"2026-08-17"}"#; - - let mut payload = Vec::new(); - payload.extend_from_slice(clean_wasm); - payload.extend_from_slice(meta_raw); - - let sig = signing_key.sign(&payload); - let env = WasmSignatureEnvelope { - version: 1, - algorithm: "Ed25519".to_string(), - public_key_hex: pub_key_hex.clone(), - signature_hex: hex::encode(sig.to_bytes()), - }; - - // Tamper with wasm bytes - let tampered_wasm = b"\0asm\x01\0\0\x01"; - let res = verify_signature(tampered_wasm, meta_raw, &env, &pub_key_hex); - assert!(res.is_err()); - - // Tamper with metadata bytes (e.g. altered user_id) - let tampered_meta = br#"{"marketplace_url":"https://market.pumpkinmc.org","plugin_id":1,"plugin_name":"test","version":"0.1.0","dev_id":1,"dev_name":"dev","is_paid":true,"user_id":999,"license_key":"KEY-123","issued_at":"2026-08-17"}"#; - let res2 = verify_signature(clean_wasm, tampered_meta, &env, &pub_key_hex); - assert!(res2.is_err()); - } -} diff --git a/crates/pumpkin-plugin-utils/src/lib.rs b/crates/pumpkin-plugin-utils/src/lib.rs index 5202bf255..e1bc48171 100644 --- a/crates/pumpkin-plugin-utils/src/lib.rs +++ b/crates/pumpkin-plugin-utils/src/lib.rs @@ -1,12 +1,10 @@ //! # Pumpkin Plugin Utilities (`pumpkin-plugin-utils`) //! //! A fast, secure, and developer-friendly utility crate for Pumpkin server plugins, providing: -//! - **Offline Ed25519 Signature Verification**: Validates plugin integrity and marketplace metadata on startup in `< 1ms`. -//! - **Automatic Metadata Caching**: Call `init(context)` once on load; metadata is verified and cached globally for all subsequent operations. +//! - **Automatic Metadata Caching**: Call `init(context)` once on load; marketplace metadata is retrieved from host WIT and cached globally. //! - **Zero-Argument Updates & Online Licensing**: Check licenses and updates against official Pumpkin Marketplace endpoints without manual arguments. //! - **Online License Checks**: Verify active licenses with `https://market.pumpkinmc.org/api/v1/rest/check-license`. //! - **License Checks & Grace Periods**: Local lease management (`license_lease.json`) to prevent outages during marketplace downtime. -//! - **Dynamic Public Key Resolution**: Resolves keys via host WIT import, local cache, or HTTPS fallback without hardcoding keys. //! //! # Quick Start //! @@ -20,7 +18,7 @@ //! fn new() -> Self { MyPlugin } //! //! fn on_load(&self, context: &Context) -> Result<(), String> { -//! // 1. Initialize plugin-utils (verifies signature & caches metadata globally) +//! // 1. Initialize plugin-utils (retrieves verified marketplace metadata from host) //! let metadata = pumpkin_plugin_utils::init(context) //! .map_err(|e| format!("Plugin initialization failed: {e}"))?; //! @@ -59,32 +57,21 @@ )] #![cfg_attr(test, allow(clippy::unwrap_used, clippy::expect_used, clippy::panic))] -/// Cryptographic signature verification. -pub mod crypto; /// HTTP client helpers for marketplace interaction. pub mod http; /// License checking, validation, and lease management. pub mod license; -/// Data models for metadata, signatures, licenses, and updates. +/// Data models for metadata, licenses, and updates. pub mod models; -/// Dynamic marketplace public key resolution. -pub mod resolver; /// Non-blocking update checks against marketplace endpoints. pub mod updater; -/// WASM binary inspection and custom section extraction. -pub mod wasm; -pub use crypto::verify_signature; pub use license::{LicenseChecker, LicenseError}; pub use models::{ CheckLicenseResponse, CheckUpdateResponse, DEFAULT_MARKETPLACE_URL, LicenseLease, - LicenseStatus, MarketplacePublicKeyResponse, PumpkinMetadata, WasmSignatureEnvelope, + LicenseStatus, PumpkinMetadata, }; -pub use resolver::PublicKeyResolver; pub use updater::{UpdateChecker, UpdateError}; -pub use wasm::{ - ExtractedSections, WasmError, extract_sections, find_self_wasm, strip_pumpkin_sections, -}; use std::{ path::{Path, PathBuf}, @@ -98,57 +85,50 @@ static GLOBAL_DATA_FOLDER: OnceLock = OnceLock::new(); /// Initializes `pumpkin-plugin-utils` using the plugin's runtime `Context`. /// -/// Automatically locates the plugin WASM binary, verifies its cryptographic Ed25519 signature -/// against the marketplace public key, and caches the verified metadata globally. +/// Retrieves verified marketplace metadata provided by the host if the plugin is signed, +/// and caches it globally. /// /// # Errors /// -/// Returns `LicenseError` if signature verification, section extraction, or public key resolution fails. +/// Returns `LicenseError::UnsignedPlugin` if the plugin is not signed or marketplace metadata is missing. pub fn init( context: &pumpkin_plugin_api::Context, ) -> Result<&'static PumpkinMetadata, LicenseError> { let data_folder = PathBuf::from(context.get_data_folder()); - init_with_folder(data_folder) + + #[cfg(target_arch = "wasm32")] + { + if let Some(market_meta) = context.get_marketplace_metadata() { + let meta: PumpkinMetadata = market_meta.into(); + return init_with_metadata(meta, data_folder); + } + Err(LicenseError::UnsignedPlugin) + } + + #[cfg(not(target_arch = "wasm32"))] + { + let _ = data_folder; + GLOBAL_METADATA.get().ok_or(LicenseError::NotInitialized) + } } -/// Initializes `pumpkin-plugin-utils` with a specific data folder path. +/// Initializes `pumpkin-plugin-utils` with explicit metadata (useful for tests or custom initialization). /// /// # Errors /// -/// Returns `LicenseError` if signature verification or section extraction fails. -pub fn init_with_folder( +/// Returns `LicenseError::NotInitialized` if caching fails. +pub fn init_with_metadata( + metadata: PumpkinMetadata, data_folder: impl AsRef, ) -> Result<&'static PumpkinMetadata, LicenseError> { let folder = data_folder.as_ref().to_path_buf(); - let checker = LicenseChecker::new(&folder); - let metadata = checker.verify_self_offline()?; - let _ = GLOBAL_DATA_FOLDER.set(folder); let _ = GLOBAL_METADATA.set(metadata); GLOBAL_METADATA.get().ok_or(LicenseError::NotInitialized) } -/// Initializes `pumpkin-plugin-utils` with raw WASM bytes directly (useful for tests or embedded bytes). -/// -/// # Errors -/// -/// Returns `LicenseError` if signature verification fails. -pub fn init_with_bytes( - wasm_bytes: &[u8], - data_folder: impl AsRef, -) -> Result<&'static PumpkinMetadata, LicenseError> { - let folder = data_folder.as_ref().to_path_buf(); - let checker = LicenseChecker::new(&folder); - let metadata = checker.verify_offline(wasm_bytes)?; - - let _ = GLOBAL_DATA_FOLDER.set(folder); - let _ = GLOBAL_METADATA.set(metadata); - - GLOBAL_METADATA.get().ok_or(LicenseError::NotInitialized) -} - -/// Returns a reference to the globally cached, verified metadata if `init` has been called. +/// Returns a reference to the globally cached metadata if `init` has been called. #[must_use] pub fn get_metadata() -> Option<&'static PumpkinMetadata> { GLOBAL_METADATA.get() @@ -197,16 +177,16 @@ pub fn check_for_updates() -> Result { UpdateChecker::new().check_for_updates(&meta.plugin_name, &meta.version, &meta.marketplace_url) } -/// Evaluates the complete offline license status (offline check + lease cache + grace period) +/// Evaluates the complete offline license status (metadata + lease cache + grace period) /// using the globally cached plugin data. #[must_use] pub fn evaluate_license(grace_period_days: u32) -> LicenseStatus { let Some(folder) = get_data_folder() else { return LicenseStatus::Invalid("pumpkin_plugin_utils has not been initialized".to_string()); }; + let Some(meta) = get_metadata() else { + return LicenseStatus::Invalid("pumpkin_plugin_utils has not been initialized".to_string()); + }; let checker = LicenseChecker::new(folder); - match wasm::find_self_wasm(folder) { - Ok(bytes) => checker.evaluate_license(&bytes, grace_period_days), - Err(e) => LicenseStatus::Invalid(e.to_string()), - } + checker.evaluate_license(meta, grace_period_days) } diff --git a/crates/pumpkin-plugin-utils/src/license.rs b/crates/pumpkin-plugin-utils/src/license.rs index 880c6680b..32c01133c 100644 --- a/crates/pumpkin-plugin-utils/src/license.rs +++ b/crates/pumpkin-plugin-utils/src/license.rs @@ -1,11 +1,8 @@ -//! License validation, offline verification, and online leasing. +//! License validation, leasing, and offline grace periods. use crate::{ - crypto::{CryptoError, verify_signature}, http::{HttpClient, HttpError}, models::{CheckLicenseResponse, LicenseLease, LicenseStatus, PumpkinMetadata}, - resolver::{PublicKeyResolver, ResolveError}, - wasm::{WasmError, extract_sections, find_self_wasm}, }; use std::{ path::{Path, PathBuf}, @@ -17,15 +14,6 @@ use tracing::{debug, info}; /// License checking and verification errors. #[derive(Debug, Error)] pub enum LicenseError { - /// WASM extraction error. - #[error("WASM inspection failed: {0}")] - Wasm(#[from] WasmError), - /// Cryptographic signature verification error. - #[error("Cryptographic verification failed: {0}")] - Crypto(#[from] CryptoError), - /// Public key resolution error. - #[error("Public key resolution failed: {0}")] - Resolver(#[from] ResolveError), /// HTTP communication error with marketplace. #[error("Marketplace HTTP error: {0}")] Http(#[from] HttpError), @@ -44,6 +32,9 @@ pub enum LicenseError { /// JSON serialization error. #[error("JSON serialization error: {0}")] Json(#[from] serde_json::Error), + /// Plugin is unsigned or missing marketplace metadata. + #[error("Plugin is unsigned or missing marketplace metadata")] + UnsignedPlugin, /// Plugin has not been initialized. #[error( "Plugin-utils has not been initialized (call pumpkin_plugin_utils::init(context) first)" @@ -51,10 +42,9 @@ pub enum LicenseError { NotInitialized, } -/// Manages fast offline signature checks and background online license validation. +/// Manages license checks, cached leases, and offline grace periods. pub struct LicenseChecker { data_folder: PathBuf, - resolver: PublicKeyResolver, http_client: HttpClient, } @@ -65,7 +55,6 @@ impl LicenseChecker { let folder = data_folder.as_ref().to_path_buf(); Self { data_folder: folder, - resolver: PublicKeyResolver::new(), http_client: HttpClient::default(), } } @@ -109,72 +98,22 @@ impl LicenseChecker { .as_secs() } - /// Performs an instant, offline cryptographic verification of the given WASM bytes. - /// - /// # Errors - /// - /// Returns `LicenseError` if parsing, public key resolution, or signature verification fails. - pub fn verify_offline(&self, wasm_bytes: &[u8]) -> Result { - // 1. Extract custom sections and clean WASM binary - let extracted = extract_sections(wasm_bytes)?; - - // 2. Validate metadata fields - if extracted.metadata.is_paid && extracted.metadata.license_key.is_none() { - return Err(LicenseError::MetadataMismatch( - "Paid plugin metadata missing license_key".to_string(), - )); + /// Evaluates the complete license status (metadata + lease cache + grace period). + #[must_use] + pub fn evaluate_license( + &self, + metadata: &PumpkinMetadata, + grace_period_days: u32, + ) -> LicenseStatus { + // Free/Open-Source plugins are always valid + if !metadata.is_paid { + return LicenseStatus::Valid(metadata.clone()); } - // 3. Resolve public key (Host WIT -> Local Cache -> HTTPS fetch) - let public_key = self - .resolver - .resolve_public_key(&extracted.metadata.marketplace_url)?; - - // 4. Verify Ed25519 signature over clean_wasm + metadata_raw - verify_signature( - &extracted.clean_wasm, - &extracted.metadata_raw, - &extracted.signature_envelope, - &public_key, - )?; - - debug!( - "Successfully verified offline signature for plugin '{}' (User ID: {})", - extracted.metadata.plugin_name, extracted.metadata.user_id - ); - - Ok(extracted.metadata) - } - - /// Locates the plugin's WASM file on disk and verifies its signature offline. - /// - /// # Errors - /// - /// Returns `LicenseError` if reading the file or signature verification fails. - pub fn verify_self_offline(&self) -> Result { - let bytes = find_self_wasm(&self.data_folder)?; - self.verify_offline(&bytes) - } - - /// Evaluates the complete license status (offline check + lease cache + grace period). - #[must_use] - pub fn evaluate_license(&self, wasm_bytes: &[u8], grace_period_days: u32) -> LicenseStatus { - let metadata = match self.verify_offline(wasm_bytes) { - Ok(m) => m, - Err(e) => { - if matches!( - e, - LicenseError::Wasm(WasmError::MissingMetadata | WasmError::MissingSignature) - ) { - return LicenseStatus::Unsigned; - } - return LicenseStatus::Invalid(e.to_string()); - } - }; - - // Free/Open-Source plugins with valid signatures are always valid - if !metadata.is_paid { - return LicenseStatus::Valid(metadata); + if metadata.license_key.is_none() { + return LicenseStatus::Invalid( + "Paid plugin metadata is missing a license_key".to_string(), + ); } // For paid plugins, inspect cached lease @@ -186,7 +125,7 @@ impl LicenseChecker { && lease.status == "valid" && now <= lease.expires_timestamp { - return LicenseStatus::Valid(metadata); + return LicenseStatus::Valid(metadata.clone()); } // Check if within grace period @@ -196,7 +135,7 @@ impl LicenseChecker { (lease.last_verified_timestamp + grace_seconds).saturating_sub(now); let days_remaining = (seconds_left / 86400).max(1) as u32; return LicenseStatus::GracePeriod { - metadata, + metadata: metadata.clone(), days_remaining, reason: "Operating in offline grace period with previous valid lease" .to_string(), @@ -205,7 +144,7 @@ impl LicenseChecker { } // If no cached lease exists yet (first run) and offline, allow initial valid state - LicenseStatus::Valid(metadata) + LicenseStatus::Valid(metadata.clone()) } /// Checks the license online against the marketplace REST API: diff --git a/crates/pumpkin-plugin-utils/src/models.rs b/crates/pumpkin-plugin-utils/src/models.rs index c4bfe073b..976d7a253 100644 --- a/crates/pumpkin-plugin-utils/src/models.rs +++ b/crates/pumpkin-plugin-utils/src/models.rs @@ -2,13 +2,6 @@ use serde::{Deserialize, Serialize}; -/// Custom section names used in Pumpkin WASM plugin binaries. -pub const PUMPKIN_METADATA_SECTION: &str = "pumpkin.metadata"; -/// Custom section name for the W3C/Pumpkin Ed25519 signature. -pub const WASM_SIGNATURE_SECTION: &str = "wasm_signature"; -/// Legacy section name for signature backwards-compatibility. -pub const LEGACY_SIGNATURE_SECTION: &str = "pumpkin.signature"; - /// Default Pumpkin Marketplace URL. pub const DEFAULT_MARKETPLACE_URL: &str = "https://market.pumpkinmc.org"; @@ -37,27 +30,31 @@ pub struct PumpkinMetadata { pub issued_at: String, } -/// Standard W3C Wasm-Sign signature envelope structure. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub struct WasmSignatureEnvelope { - /// Signature envelope schema version. - pub version: u8, - /// Signature algorithm (e.g. "Ed25519"). - pub algorithm: String, - /// Hex-encoded public key of the signer. - pub public_key_hex: String, - /// Hex-encoded signature bytes. - pub signature_hex: String, +impl From for PumpkinMetadata { + fn from(m: pumpkin_plugin_api::MarketplaceMetadata) -> Self { + Self { + marketplace_url: m.marketplace_url, + plugin_id: m.plugin_id, + plugin_name: m.plugin_name, + version: m.version, + dev_id: m.dev_id, + dev_name: m.dev_name, + is_paid: m.is_paid, + user_id: m.user_id, + license_key: m.license_key, + issued_at: m.issued_at, + } + } } /// Result of evaluating a plugin's license. #[derive(Debug, Clone, PartialEq, Eq)] pub enum LicenseStatus { - /// The license is completely valid and verified. + /// The license is completely valid. Valid(PumpkinMetadata), /// Operating in an offline grace period with valid cached lease. GracePeriod { - /// The verified metadata. + /// The metadata. metadata: PumpkinMetadata, /// Remaining days in the grace period. days_remaining: u32, @@ -102,12 +99,3 @@ pub struct CheckUpdateResponse { /// The latest stable version string, if one exists. pub latest_version: Option, } - -/// Response returned by the marketplace `/api/v1/rest/public-key` endpoint. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub struct MarketplacePublicKeyResponse { - /// Cryptographic algorithm (e.g. "Ed25519"). - pub algorithm: String, - /// Hex-encoded public key. - pub public_key_hex: String, -} diff --git a/crates/pumpkin-plugin-utils/src/resolver.rs b/crates/pumpkin-plugin-utils/src/resolver.rs deleted file mode 100644 index bf31bc0e0..000000000 --- a/crates/pumpkin-plugin-utils/src/resolver.rs +++ /dev/null @@ -1,81 +0,0 @@ -//! Dynamic public key resolution for marketplace signature verification. - -use crate::http::HttpClient; -use thiserror::Error; -use tracing::debug; - -/// Public key resolution errors. -#[derive(Debug, Error)] -pub enum ResolveError { - /// Public key format is invalid. - #[error("Retrieved public key is empty or invalid: {0}")] - InvalidKey(String), -} - -/// Resolves the Pumpkin Marketplace public key dynamically. -/// -/// Follows a 2-tier live resolution strategy without stale on-disk caching: -/// 1. Host WIT interface (`marketplace::get_public_key()`). -/// 2. Live remote marketplace REST endpoint (`/api/v1/rest/public-key`). -pub struct PublicKeyResolver { - http_client: HttpClient, -} - -impl Default for PublicKeyResolver { - fn default() -> Self { - Self::new() - } -} - -impl PublicKeyResolver { - /// Creates a new public key resolver. - #[must_use] - pub fn new() -> Self { - Self { - http_client: HttpClient::default(), - } - } - - /// Resolves the marketplace public key dynamically without on-disk caching. - /// - /// # Errors - /// - /// Returns `ResolveError` if the retrieved key is malformed. - pub fn resolve_public_key(&self, marketplace_url: &str) -> Result { - // 1. Try host WIT import if running inside WASM runtime - #[cfg(target_arch = "wasm32")] - if let Some(host_key) = pumpkin_plugin_api::marketplace::get_public_key() { - let key = host_key.trim().trim_matches('"').to_string(); - if !key.is_empty() { - debug!("Resolved marketplace public key from host WIT context"); - return Ok(key); - } - } - - // 2. Live HTTPS fetch from marketplace - let url = format!( - "{}/api/v1/rest/public-key", - marketplace_url.trim_end_matches('/') - ); - debug!( - "Fetching live marketplace public key via HTTPS from {}", - url - ); - if let Ok(body) = self.http_client.get(&url) { - let key = if let Ok(resp) = - serde_json::from_str::(&body) - { - resp.public_key_hex - } else { - body.trim().trim_matches('"').to_string() - }; - - if !key.is_empty() { - return Ok(key); - } - } - - // Fallback to empty string (lets signature verification verify against envelope key) - Ok(String::new()) - } -} diff --git a/crates/pumpkin-plugin-utils/src/wasm.rs b/crates/pumpkin-plugin-utils/src/wasm.rs deleted file mode 100644 index c353929ca..000000000 --- a/crates/pumpkin-plugin-utils/src/wasm.rs +++ /dev/null @@ -1,254 +0,0 @@ -//! WASM binary inspection and custom section extraction. - -use crate::models::{ - LEGACY_SIGNATURE_SECTION, PUMPKIN_METADATA_SECTION, PumpkinMetadata, WASM_SIGNATURE_SECTION, - WasmSignatureEnvelope, -}; -use thiserror::Error; -use wasmparser::{Parser, Payload}; - -/// Errors encountered while parsing or inspecting WASM binaries. -#[derive(Debug, Error)] -pub enum WasmError { - /// Error reading the WASM file from disk. - #[error("Failed to read plugin WASM file from disk: {0}")] - Io(#[from] std::io::Error), - /// Error parsing the WASM structure. - #[error("Failed to parse WASM binary: {0}")] - Parser(String), - /// Metadata custom section is missing. - #[error("Missing 'pumpkin.metadata' custom section in WASM binary")] - MissingMetadata, - /// Signature custom section is missing. - #[error("Missing 'wasm_signature' custom section in WASM binary")] - MissingSignature, - /// Failed to deserialize metadata JSON. - #[error("Failed to deserialize Pumpkin metadata JSON: {0}")] - InvalidMetadataJson(#[from] serde_json::Error), -} - -/// Decodes an unsigned LEB128 integer from a byte slice. -fn read_leb128(bytes: &[u8]) -> Option<(u64, usize)> { - let mut result: u64 = 0; - let mut shift = 0; - for (i, &byte) in bytes.iter().enumerate() { - if shift >= 64 { - return None; - } - result |= u64::from(byte & 0x7f) << shift; - if (byte & 0x80) == 0 { - return Some((result, i + 1)); - } - shift += 7; - } - None -} - -/// Strips `pumpkin.metadata`, `wasm_signature`, and legacy signature sections from WASM bytes. -/// -/// Returns the "clean" WASM payload used for signature calculation and verification. -/// -/// # Errors -/// -/// Returns a `WasmError` if the WASM binary is invalid. -pub fn strip_pumpkin_sections(wasm_bytes: &[u8]) -> Result, WasmError> { - let mut last_valid_end = wasm_bytes.len(); - let parser = Parser::new(0); - - for payload in parser.parse_all(wasm_bytes) { - match payload { - Ok(Payload::Version { ref range, .. }) => { - last_valid_end = range.end; - } - Ok(Payload::CustomSection(cs)) => { - if cs.name() == PUMPKIN_METADATA_SECTION - || cs.name() == WASM_SIGNATURE_SECTION - || cs.name() == LEGACY_SIGNATURE_SECTION - { - // Section will be omitted - } else { - last_valid_end = cs.range().end; - } - } - Ok(p) => { - if let Some((_, range)) = p.as_section() { - last_valid_end = range.end; - } - } - Err(_) => { - // Stop parsing on trailing appended sections - break; - } - } - } - - Ok(wasm_bytes[..last_valid_end].to_vec()) -} - -/// Extracted custom sections and clean WASM payload. -#[derive(Debug, Clone)] -pub struct ExtractedSections { - /// Parsed pumpkin metadata. - pub metadata: PumpkinMetadata, - /// Raw metadata JSON bytes (used for signature verification). - pub metadata_raw: Vec, - /// Parsed signature envelope. - pub signature_envelope: WasmSignatureEnvelope, - /// Clean WASM bytes without metadata and signature custom sections. - pub clean_wasm: Vec, -} - -/// Extracts metadata and signature custom sections from a WASM binary. -/// -/// # Errors -/// -/// Returns a `WasmError` if sections are missing or malformed. -pub fn extract_sections(wasm_bytes: &[u8]) -> Result { - let clean_wasm = strip_pumpkin_sections(wasm_bytes)?; - - let mut metadata_raw: Option> = None; - let mut signature_raw: Option> = None; - - let parser = Parser::new(0); - for payload in parser.parse_all(wasm_bytes) { - if let Ok(Payload::CustomSection(cs)) = payload { - if cs.name() == PUMPKIN_METADATA_SECTION { - metadata_raw = Some(cs.data().to_vec()); - } else if cs.name() == WASM_SIGNATURE_SECTION || cs.name() == LEGACY_SIGNATURE_SECTION { - signature_raw = Some(cs.data().to_vec()); - } - } - } - - // Check trailing sections if not found in primary pass (e.g. appended custom sections) - if metadata_raw.is_none() || signature_raw.is_none() { - if clean_wasm.len() < wasm_bytes.len() { - let trailing = &wasm_bytes[clean_wasm.len()..]; - let mut cursor = 0; - while cursor < trailing.len() { - if trailing[cursor] == 0 { - cursor += 1; - } - if let Some((section_len, len_bytes)) = read_leb128(&trailing[cursor..]) { - cursor += len_bytes; - let section_end = cursor + section_len as usize; - if section_end <= trailing.len() { - let section_data = &trailing[cursor..section_end]; - if let Some((name_len, name_len_bytes)) = read_leb128(section_data) { - let name_start = name_len_bytes; - let name_end = name_start + name_len as usize; - if name_end <= section_data.len() - && let Ok(name) = - std::str::from_utf8(§ion_data[name_start..name_end]) - { - let payload_data = §ion_data[name_end..]; - if name == PUMPKIN_METADATA_SECTION { - metadata_raw = Some(payload_data.to_vec()); - } else if name == WASM_SIGNATURE_SECTION - || name == LEGACY_SIGNATURE_SECTION - { - signature_raw = Some(payload_data.to_vec()); - } - } - } - } - cursor = section_end; - continue; - } - break; - } - } - } - - let meta_bytes = metadata_raw.ok_or(WasmError::MissingMetadata)?; - let sig_bytes = signature_raw.ok_or(WasmError::MissingSignature)?; - - let metadata: PumpkinMetadata = serde_json::from_slice(&meta_bytes)?; - - let signature_envelope: WasmSignatureEnvelope = - if let Ok(envelope) = serde_json::from_slice::(&sig_bytes) { - envelope - } else { - // Legacy raw signature fallback - WasmSignatureEnvelope { - version: 1, - algorithm: "Ed25519".to_string(), - public_key_hex: String::new(), - signature_hex: hex::encode(&sig_bytes), - } - }; - - Ok(ExtractedSections { - metadata, - metadata_raw: meta_bytes, - signature_envelope, - clean_wasm, - }) -} - -/// Automatically locates and reads the plugin's own WASM file from the filesystem. -/// -/// Discovers candidates by: -/// 1. Checking data folder path naming hint (e.g. `plugins/data/` -> `plugins/.wasm`). -/// 2. Scanning the `plugins/` directory for `.wasm` files containing valid pumpkin custom sections. -/// 3. Scanning current working directory for `.wasm` files. -/// -/// # Errors -/// -/// Returns `WasmError::Io` if no valid WASM binary can be found or read. -pub fn find_self_wasm(data_folder: &std::path::Path) -> Result, WasmError> { - // 1. Check data folder hint - if let Some(folder_name) = data_folder.file_name().and_then(|n| n.to_str()) { - if folder_name != "data" && !folder_name.is_empty() { - let candidate_paths = [ - format!("plugins/{folder_name}.wasm"), - format!("plugins/{folder_name}.cwasm"), - format!("plugins/{folder_name}"), - format!("{folder_name}.wasm"), - ]; - for path in &candidate_paths { - if let Ok(bytes) = std::fs::read(path) { - return Ok(bytes); - } - } - } - } - - // 2. Scan `plugins/` directory - if let Ok(entries) = std::fs::read_dir("plugins") { - for entry in entries.flatten() { - let path = entry.path(); - if let Some(ext) = path.extension().and_then(|e| e.to_str()) { - if ext == "wasm" || ext == "cwasm" { - if let Ok(bytes) = std::fs::read(&path) { - // Check if this WASM file contains pumpkin metadata - if extract_sections(&bytes).is_ok() { - return Ok(bytes); - } - } - } - } - } - } - - // 3. Scan current directory - if let Ok(entries) = std::fs::read_dir(".") { - for entry in entries.flatten() { - let path = entry.path(); - if let Some(ext) = path.extension().and_then(|e| e.to_str()) { - if ext == "wasm" || ext == "cwasm" { - if let Ok(bytes) = std::fs::read(&path) { - if extract_sections(&bytes).is_ok() { - return Ok(bytes); - } - } - } - } - } - } - - Err(WasmError::Io(std::io::Error::new( - std::io::ErrorKind::NotFound, - "Could not locate plugin WASM binary with pumpkin metadata in plugins directory", - ))) -} diff --git a/crates/pumpkin-plugin-utils/tests/licensing_test.rs b/crates/pumpkin-plugin-utils/tests/licensing_test.rs index 6644b9254..e7c6d5c18 100644 --- a/crates/pumpkin-plugin-utils/tests/licensing_test.rs +++ b/crates/pumpkin-plugin-utils/tests/licensing_test.rs @@ -5,101 +5,18 @@ clippy::uninlined_format_args )] -use ed25519_dalek::Signer; use pumpkin_plugin_utils::{ - CheckLicenseResponse, LicenseChecker, LicenseLease, LicenseStatus, PumpkinMetadata, - WasmSignatureEnvelope, extract_sections, get_metadata, init_with_bytes, metadata, - strip_pumpkin_sections, verify_signature, + CheckLicenseResponse, CheckUpdateResponse, LicenseChecker, LicenseLease, LicenseStatus, + PumpkinMetadata, get_metadata, init_with_metadata, metadata, }; use std::time::{SystemTime, UNIX_EPOCH}; use tempfile::tempdir; -use wasm_encoder::{CustomSection, Encode, Module}; - -/// Helper to build a signed WASM module with `pumpkin.metadata` and `wasm_signature`. -fn create_signed_wasm( - metadata: &PumpkinMetadata, - signing_key: &ed25519_dalek::SigningKey, -) -> (Vec, String) { - let module = Module::new(); - let clean_wasm = module.finish(); - - let meta_json = serde_json::to_vec(metadata).unwrap(); - - let mut sign_payload = Vec::new(); - sign_payload.extend_from_slice(&clean_wasm); - sign_payload.extend_from_slice(&meta_json); - - let signature = signing_key.sign(&sign_payload); - let pub_key_hex = hex::encode(signing_key.verifying_key().to_bytes()); - - let envelope = WasmSignatureEnvelope { - version: 1, - algorithm: "Ed25519".to_string(), - public_key_hex: pub_key_hex.clone(), - signature_hex: hex::encode(signature.to_bytes()), - }; - let sig_json = serde_json::to_vec(&envelope).unwrap(); - - let mut out = clean_wasm; - let meta_section = CustomSection { - name: "pumpkin.metadata".into(), - data: meta_json.as_slice().into(), - }; - let sig_section = CustomSection { - name: "wasm_signature".into(), - data: sig_json.as_slice().into(), - }; - - meta_section.encode(&mut out); - sig_section.encode(&mut out); - - (out, pub_key_hex) -} - -#[test] -fn wasm_custom_section_extraction_and_stripping() { - let seed = [7u8; 32]; - let signing_key = ed25519_dalek::SigningKey::from_bytes(&seed); - - let metadata = PumpkinMetadata { - marketplace_url: "http://127.0.0.1:0".to_string(), - plugin_id: 42, - plugin_name: "super-shop".to_string(), - version: "1.0.0".to_string(), - dev_id: 10, - dev_name: "alex".to_string(), - is_paid: true, - user_id: 101, - license_key: Some("LIC-9999".to_string()), - issued_at: "2026-08-17T08:00:00Z".to_string(), - }; - - let (wasm_bytes, pub_key_hex) = create_signed_wasm(&metadata, &signing_key); - - let extracted = extract_sections(&wasm_bytes).expect("Should extract custom sections"); - assert_eq!(extracted.metadata, metadata); - assert_eq!(extracted.signature_envelope.public_key_hex, pub_key_hex); - - let stripped = strip_pumpkin_sections(&wasm_bytes).expect("Should strip sections"); - assert_eq!(stripped, extracted.clean_wasm); - - let res = verify_signature( - &extracted.clean_wasm, - &extracted.metadata_raw, - &extracted.signature_envelope, - &pub_key_hex, - ); - assert!(res.is_ok()); -} #[test] fn license_checker_offline_and_grace_period() { let dir = tempdir().unwrap(); let data_folder = dir.path(); - let seed = [9u8; 32]; - let signing_key = ed25519_dalek::SigningKey::from_bytes(&seed); - let metadata = PumpkinMetadata { marketplace_url: "http://127.0.0.1:0".to_string(), plugin_id: 123, @@ -113,15 +30,9 @@ fn license_checker_offline_and_grace_period() { issued_at: "2026-08-17T08:00:00Z".to_string(), }; - let (wasm_bytes, _pub_key_hex) = create_signed_wasm(&metadata, &signing_key); - let checker = LicenseChecker::new(data_folder); - // 1. Verify offline directly - let verified_meta = checker.verify_offline(&wasm_bytes).unwrap(); - assert_eq!(verified_meta.user_id, 500); - - // 2. Evaluate with expired lease -> should enter GracePeriod + // 1. Evaluate with expired lease -> should enter GracePeriod let now = SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap() @@ -136,7 +47,7 @@ fn license_checker_offline_and_grace_period() { }; checker.write_cached_lease(&lease).unwrap(); - let status = checker.evaluate_license(&wasm_bytes, 7); + let status = checker.evaluate_license(&metadata, 7); match status { LicenseStatus::GracePeriod { metadata: m, @@ -149,7 +60,7 @@ fn license_checker_offline_and_grace_period() { other => panic!("Expected GracePeriod, got {other:?}"), } - // 3. Evaluate with active lease -> should be Valid + // 2. Evaluate with active lease -> should be Valid let active_lease = LicenseLease { plugin_name: "anti-grief".to_string(), license_key: Some("KEY-12345".to_string()), @@ -159,7 +70,7 @@ fn license_checker_offline_and_grace_period() { }; checker.write_cached_lease(&active_lease).unwrap(); - let status = checker.evaluate_license(&wasm_bytes, 7); + let status = checker.evaluate_license(&metadata, 7); match status { LicenseStatus::Valid(m) => assert_eq!(m.user_id, 500), other => panic!("Expected Valid, got {other:?}"), @@ -171,9 +82,6 @@ fn global_init_and_metadata_access() { let dir = tempdir().unwrap(); let data_folder = dir.path(); - let seed = [11u8; 32]; - let signing_key = ed25519_dalek::SigningKey::from_bytes(&seed); - let original_meta = PumpkinMetadata { marketplace_url: "http://127.0.0.1:0".to_string(), plugin_id: 777, @@ -187,10 +95,8 @@ fn global_init_and_metadata_access() { issued_at: "2026-08-17T08:00:00Z".to_string(), }; - let (wasm_bytes, _pub_key_hex) = create_signed_wasm(&original_meta, &signing_key); - - // 1. Initialize globally with bytes - let verified = init_with_bytes(&wasm_bytes, data_folder).expect("Init should succeed"); + // 1. Initialize globally with metadata + let verified = init_with_metadata(original_meta, data_folder).expect("Init should succeed"); assert_eq!(verified.plugin_name, "economy-core"); assert_eq!(verified.version, "3.2.1"); assert_eq!(verified.user_id, 8888); @@ -204,4 +110,11 @@ fn global_init_and_metadata_access() { serde_json::from_str::(r#"{"valid":true,"status":"valid"}"#).unwrap(); assert!(check_resp.valid); assert_eq!(check_resp.status, "valid"); + + let update_resp = serde_json::from_str::( + r#"{"update_available":true,"latest_version":"4.0.0"}"#, + ) + .unwrap(); + assert!(update_resp.update_available); + assert_eq!(update_resp.latest_version.as_deref(), Some("4.0.0")); } diff --git a/crates/pumpkin-plugin-wit b/crates/pumpkin-plugin-wit index 78b1b8b90..8339cbf6e 160000 --- a/crates/pumpkin-plugin-wit +++ b/crates/pumpkin-plugin-wit @@ -1 +1 @@ -Subproject commit 78b1b8b9005d41d6ceb6e8788fb21840128cf29c +Subproject commit 8339cbf6ec8bd945f9ec740a222dfe3a2fc092ce diff --git a/crates/pumpkin/src/plugin/loader/wasm/wasm_host/mod.rs b/crates/pumpkin/src/plugin/loader/wasm/wasm_host/mod.rs index 152d600f7..33a9db058 100644 --- a/crates/pumpkin/src/plugin/loader/wasm/wasm_host/mod.rs +++ b/crates/pumpkin/src/plugin/loader/wasm/wasm_host/mod.rs @@ -92,7 +92,26 @@ impl PluginRuntime { ) -> Result<(Arc, PluginMetadata), PluginInitError> { let wasm_bytes = std::fs::read(&path).map_err(PluginInitError::FileReadFailed)?; - signature::verify_wasm_plugin(&wasm_bytes, &path.as_ref().to_string_lossy()); + let verification = + signature::verify_wasm_plugin(&wasm_bytes, &path.as_ref().to_string_lossy()); + let marketplace_metadata = if verification.is_signed && verification.is_valid { + verification.metadata.map(|m| { + wit::v0_1::pumpkin::plugin::context::MarketplaceMetadata { + marketplace_url: m.marketplace_url, + plugin_id: m.plugin_id, + plugin_name: m.plugin_name, + version: m.version, + dev_id: m.dev_id, + dev_name: m.dev_name, + is_paid: m.is_paid, + user_id: m.user_id, + license_key: m.license_key, + issued_at: m.issued_at, + } + }) + } else { + None + }; let wasm_bytes = signature::strip_pumpkin_sections(&wasm_bytes).unwrap_or(wasm_bytes); @@ -111,7 +130,11 @@ impl PluginRuntime { }; let wasm_plugin = Arc::new(wasm_plugin); - wasm_plugin.store.lock().await.data_mut().plugin = Some(Arc::downgrade(&wasm_plugin)); + { + let mut store = wasm_plugin.store.lock().await; + store.data_mut().plugin = Some(Arc::downgrade(&wasm_plugin)); + store.data_mut().marketplace_metadata = marketplace_metadata; + }; Ok((wasm_plugin, metadata)) } } diff --git a/crates/pumpkin/src/plugin/loader/wasm/wasm_host/state.rs b/crates/pumpkin/src/plugin/loader/wasm/wasm_host/state.rs index 244b6be47..a76ea7f26 100644 --- a/crates/pumpkin/src/plugin/loader/wasm/wasm_host/state.rs +++ b/crates/pumpkin/src/plugin/loader/wasm/wasm_host/state.rs @@ -114,6 +114,8 @@ pub struct PluginHostState { pub server: Option>, pub permissions: Vec, pub name: Option, + pub marketplace_metadata: + Option, } impl Default for PluginHostState { @@ -139,6 +141,7 @@ impl PluginHostState { server: None, permissions: Vec::new(), name: None, + marketplace_metadata: None, } } diff --git a/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/context.rs b/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/context.rs index 01af84aef..5b99f71b7 100644 --- a/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/context.rs +++ b/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/context.rs @@ -10,7 +10,7 @@ use crate::plugin::loader::wasm::wasm_host::{ self, plugin::{ command::Command, - context::Context, + context::{Context, MarketplaceMetadata}, event::{EventPriority, EventType}, permission::{Permission, PermissionDefault, PermissionLevel}, server::Server, @@ -1982,6 +1982,13 @@ impl pumpkin::plugin::context::HostContext for PluginHostState { .map_err(|_| wasmtime::Error::msg("failed to add server resource")) } + async fn get_marketplace_metadata( + &mut self, + _context: Resource, + ) -> wasmtime::Result> { + Ok(self.marketplace_metadata.clone()) + } + async fn drop(&mut self, rep: Resource) -> wasmtime::Result<()> { let _ = self .resource_table diff --git a/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/marketplace.rs b/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/marketplace.rs deleted file mode 100644 index 4a983de7a..000000000 --- a/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/marketplace.rs +++ /dev/null @@ -1,9 +0,0 @@ -use crate::plugin::loader::wasm::wasm_host::{ - signature, state::PluginHostState, wit::v0_1::pumpkin, -}; - -impl pumpkin::plugin::marketplace::Host for PluginHostState { - async fn get_public_key(&mut self) -> wasmtime::Result> { - Ok(signature::fetch_market_public_key().ok()) - } -} diff --git a/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/mod.rs b/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/mod.rs index 451f9ed94..d88abe0bb 100644 --- a/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/mod.rs +++ b/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/mod.rs @@ -26,7 +26,6 @@ pub mod ipc; pub mod item_stack; pub mod java_dialogs; pub mod logging; -pub mod marketplace; pub mod permission; pub mod player; pub mod recipe;