Add inbound stamp cost option

Adds an Inbound Stamp Cost setting so senders must attach an LXMF
proof of work stamp to message you, deterring automated spam. A
separate Require valid stamps setting controls enforcement: when off
the cost is advertised but unstamped messages are still delivered,
when on the router drops them. Both apply live without a restart.
This commit is contained in:
Jascha 2026-08-24 13:06:56 -07:00
parent df5aea94ea
commit e41ebbdebb
2 changed files with 49 additions and 0 deletions

View file

@ -126,8 +126,13 @@ class ReticulumMeshChat:
self.local_lxmf_destination = self.message_router.register_delivery_identity(
identity=self.identity,
display_name=self.config.display_name.get(),
stamp_cost=self.config.lxmf_inbound_stamp_cost.get(),
)
# drop inbound messages without a valid stamp if enforcement is enabled
if self.config.lxmf_enforce_inbound_stamp_cost.get():
self.message_router.enforce_stamps()
# set a callback for when an lxmf message is received
self.message_router.register_delivery_callback(self.on_lxmf_delivery)
@ -2133,6 +2138,20 @@ class ReticulumMeshChat:
value = bool(data["show_suggested_community_interfaces"])
self.config.show_suggested_community_interfaces.set(value)
if "lxmf_inbound_stamp_cost" in data:
value = int(data["lxmf_inbound_stamp_cost"])
if 0 <= value <= 254:
self.config.lxmf_inbound_stamp_cost.set(value)
self.message_router.set_inbound_stamp_cost(self.local_lxmf_destination.hash, value)
if "lxmf_enforce_inbound_stamp_cost" in data:
value = bool(data["lxmf_enforce_inbound_stamp_cost"])
self.config.lxmf_enforce_inbound_stamp_cost.set(value)
if value:
self.message_router.enforce_stamps()
else:
self.message_router.ignore_stamps()
if "lxmf_preferred_propagation_node_destination_hash" in data:
# update config value
@ -2388,6 +2407,8 @@ class ReticulumMeshChat:
"allow_auto_resending_failed_messages_with_attachments": self.config.allow_auto_resending_failed_messages_with_attachments.get(),
"auto_send_failed_messages_to_propagation_node": self.config.auto_send_failed_messages_to_propagation_node.get(),
"show_suggested_community_interfaces": self.config.show_suggested_community_interfaces.get(),
"lxmf_inbound_stamp_cost": self.config.lxmf_inbound_stamp_cost.get(),
"lxmf_enforce_inbound_stamp_cost": self.config.lxmf_enforce_inbound_stamp_cost.get(),
"lxmf_local_propagation_node_enabled": self.config.lxmf_local_propagation_node_enabled.get(),
"lxmf_local_propagation_node_address_hash": self.message_router.propagation_destination.hexhash,
"lxmf_preferred_propagation_node_destination_hash": self.config.lxmf_preferred_propagation_node_destination_hash.get(),
@ -3410,6 +3431,8 @@ class Config:
auto_send_failed_messages_to_propagation_node = BoolConfig("auto_send_failed_messages_to_propagation_node", False)
show_suggested_community_interfaces = BoolConfig("show_suggested_community_interfaces", True)
lxmf_delivery_transfer_limit_in_bytes = IntConfig("lxmf_delivery_transfer_limit_in_bytes", 1000 * 1000 * 10) # 10MB
lxmf_inbound_stamp_cost = IntConfig("lxmf_inbound_stamp_cost", 0) # 0 = disabled, valid range 1-254
lxmf_enforce_inbound_stamp_cost = BoolConfig("lxmf_enforce_inbound_stamp_cost", False)
lxmf_preferred_propagation_node_destination_hash = StringConfig("lxmf_preferred_propagation_node_destination_hash", None)
lxmf_preferred_propagation_node_auto_sync_interval_seconds = IntConfig("lxmf_preferred_propagation_node_auto_sync_interval_seconds", 0)
lxmf_preferred_propagation_node_last_synced_at = IntConfig("lxmf_preferred_propagation_node_last_synced_at", None)

View file

@ -91,6 +91,22 @@
<div class="text-sm text-gray-700 dark:text-gray-300">When enabled, messages that fail to send will be sent to the configured propagation node.</div>
</div>
<div class="p-2">
<label class="text-sm font-medium text-gray-900 dark:text-gray-100">Inbound Stamp Cost</label>
<input v-model="config.lxmf_inbound_stamp_cost" @change="onLxmfInboundStampCostChange" type="number" min="0" max="254" class="bg-gray-50 dark:bg-zinc-700 border border-gray-300 dark:border-zinc-600 text-gray-900 dark:text-gray-100 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 dark:focus:ring-blue-600 dark:focus:border-blue-600 block w-full p-2.5 mt-1">
<div class="text-sm text-gray-700 dark:text-gray-300">Senders must attach a proof of work stamp of this difficulty to message you, which deters automated spam. 0 disables it. Higher values take senders longer to compute. Peers learn your cost from your next announce.</div>
</div>
<div class="p-2">
<div class="flex items-start">
<div class="flex items-center h-5">
<input v-model="config.lxmf_enforce_inbound_stamp_cost" @change="onLxmfEnforceInboundStampCostChange" type="checkbox" class="w-4 h-4 border border-gray-300 dark:border-zinc-600 rounded bg-gray-50 dark:bg-zinc-700 focus:ring-3 focus:ring-blue-300 dark:focus:ring-blue-600">
</div>
<label class="ml-2 text-sm font-medium text-gray-900 dark:text-gray-100">Require valid stamps</label>
</div>
<div class="text-sm text-gray-700 dark:text-gray-300">When enabled, messages without a valid stamp will be dropped. When disabled, your stamp cost is advertised but unstamped messages are still delivered.</div>
</div>
</div>
</div>
@ -242,6 +258,16 @@ export default {
"allow_auto_resending_failed_messages_with_attachments": this.config.allow_auto_resending_failed_messages_with_attachments,
});
},
async onLxmfInboundStampCostChange() {
await this.updateConfig({
"lxmf_inbound_stamp_cost": this.config.lxmf_inbound_stamp_cost,
});
},
async onLxmfEnforceInboundStampCostChange() {
await this.updateConfig({
"lxmf_enforce_inbound_stamp_cost": this.config.lxmf_enforce_inbound_stamp_cost,
});
},
async onAutoSendFailedMessagesToPropagationNodeChange() {
await this.updateConfig({
"auto_send_failed_messages_to_propagation_node": this.config.auto_send_failed_messages_to_propagation_node,