Skip to content

HIP-150: value multiplier tickets and the multiplier on burned sessions - #483

Open
michaeldjeffrey wants to merge 3 commits into
masterfrom
mj/hip-150
Open

HIP-150: value multiplier tickets and the multiplier on burned sessions#483
michaeldjeffrey wants to merge 3 commits into
masterfrom
mj/hip-150

Conversation

@michaeldjeffrey

Copy link
Copy Markdown
Contributor

HIP-150 Decision 1 grants a hotspot a multiplier on the data credits derived from its rewardable bytes, raising both what a payer burns for its data and what its deployer earns, in the same proportion.

Adds to poc_mobile:

  • value_multiplier_ticket_req_v1 :: the grant, signed by an authorized issuing key
  • value_multiplier_ticket_resp_v1
  • value_multiplier_ticket_ingest_report_v1 :: ingest writes to s3
  • verified_value_multiplier_ticket_report_v1 :: the verifier's outcome, recording rejections as well as grants so both are auditable
  • verified_value_multiplier_ticket_status
  • rpc submit_value_multiplier_ticket

The multiplier is a helium.Decimal rather than a scaled integer. A message field is nullable, so an absent multiplier is absent rather than a reserved value of a scalar — there is no sentinel to misread.

The accepted range is deliberately not stated here. It is policy the oracles enforce and can change without a schema change, so the wire format does not encode it; the verifier emits a verified report for a rejected multiplier, keeping the rejection publicly auditable rather than dropping it at ingest.

valid_data_transfer_session gains the same type. num_dcs stays the post-multiplier figure — what was actually burned, and what the reward path already reads — so this field is what makes the pre-multiplier value recoverable. Absent means no multiplier was in force or the record predates HIP-150; readers resolve absent to 1, which needs no sentinel and keeps old records decoding correctly.

network_key_role gains value_multiplier. Kept separate from mobile_carrier so that holding a carrier key does not confer the ability to grant a hotspot a reward multiplier.

HIP-150 Decision 1 grants a hotspot a multiplier on the data credits
derived from its rewardable bytes, raising both what a payer burns for its
data and what its deployer earns, in the same proportion.

Adds to poc_mobile:

  value_multiplier_ticket_req_v1            the grant, signed by an
                                            authorized issuing key
  value_multiplier_ticket_resp_v1
  value_multiplier_ticket_ingest_report_v1  what ingest writes to s3
  verified_value_multiplier_ticket_report_v1  the verifier's outcome,
                                            recording rejections as well as
                                            grants so both are auditable
  verified_value_multiplier_ticket_status
  rpc submit_value_multiplier_ticket

The multiplier is a helium.Decimal rather than a scaled integer. A message
field is nullable, so an absent multiplier is absent rather than a
reserved value of a scalar — there is no sentinel to misread.

The accepted range is deliberately not stated here. It is policy the
oracles enforce and can change without a schema change, so the wire format
does not encode it; the verifier emits a verified report for a rejected
multiplier, keeping the rejection publicly auditable rather than dropping
it at ingest.

valid_data_transfer_session gains the same type. num_dcs stays the
post-multiplier figure — what was actually burned, and what the reward
path already reads — so this field is what makes the pre-multiplier value
recoverable. Absent means no multiplier was in force or the record
predates HIP-150; readers resolve absent to 1, which needs no sentinel and
keeps old records decoding correctly.

network_key_role gains value_multiplier. Kept separate from
mobile_carrier so that holding a carrier key does not confer the ability
to grant a hotspot a reward multiplier.
Rename: value_multiplier -> data_transfer_multiplier throughout. "Value
multiplier" does not say what is multiplied; the multiplier applies to the
data credits derived from a hotspot's rewardable bytes, and the new name
says so. Covers the ticket messages, the verified report and its status
enum, the rpc, and the network_key_role variant.

Reserve zero in the status enum as `unspecified` rather than `valid`.
Proto3 cannot distinguish an absent field from one explicitly set to zero,
so with `valid = 0` a producer that forgets to set the status, a bug that
clears it, or a partially-constructed message all decode as a grant. The
sibling verified-report enums in this file put `valid` at zero and fail
open the same way; that is not followed here, and the comment says why so
it is not "fixed" back later. Matches the newer enums in this file
(carrier_id_v2_unspecified, ban_reason_unknown).

Add `invalid_timestamp` for a ticket dated in the future, or old enough
that honouring it would mean acting on a replayed or long-delayed grant. A
signature stays valid forever, so without a freshness window a captured
ticket is replayable indefinitely.
The verified report is written only by our own packet verifier and read as
an audit artifact; it never arrives from an untrusted party. Reserving zero
was defending against a bug in a single producer we control, at the cost of
an impossible state every consumer has to match on and a divergence from
the twelve sibling verified-report enums in this file.

The attacker-facing message is data_transfer_multiplier_ticket_req_v1, and
it has no status field — what protects it is the signature, the allow-list
and the freshness window, none of which this variant contributed to.

`valid` returns to zero, matching every other verified-report enum here.
invalid_timestamp is unchanged and renumbers with the rest; nothing consumes
these values yet, so the renumber is free.
michaeldjeffrey added a commit to helium/oracles that referenced this pull request Aug 26, 2026
* HIP-150: accept data transfer multiplier tickets in ingest

Adds submit_data_transfer_multiplier_ticket. A ticket grants one hotspot a
multiplier on the data credits derived from its rewardable bytes.

Ingest verifies the signer and the timestamp, then persists the ticket
verbatim. It does not parse the multiplier — the packet verifier decides
whether one is acceptable and records that verdict in a verified report, so
a rejection stays as auditable as a grant.

Notes:

- Ticket signers get their own allow-list and is_ticket_signer() check,
  rather than a NetworkKeyRole variant: that enum belongs to the
  decommissioned mobile-config. A carrier key must not be able to grant a
  multiplier.
- The list may be empty and defaults to empty, since the release ships
  before any ticket can be issued. Empty rejects every ticket and warns at
  startup.
- Tickets older than data_transfer_multiplier_ticket_max_age (default 10
  minutes) or dated in the future are refused. Signatures never expire, so
  without this a captured ticket is replayable forever.
- valid_data_transfer_session gained a multiplier field with the proto
  bump; set to None here, so behaviour is unchanged.

Cargo.toml patches helium-proto to proto's mj/hip-150 (helium/proto#483).
REVERT BEFORE MERGING mj/hip-150 TO main.

* HIP-150: tolerate client clock drift on multiplier tickets

A client does not share a clock with ingest, so a ticket signed at what the
client believes is "now" can arrive stamped slightly ahead of us. Those were
refused as post-dated, which is a confusing failure for an honest client
with a drifting clock.

Tickets up to MAX_CLOCK_DRIFT (1 minute) in the future are now treated as
current. Beyond that they are still refused: post-dating must not buy an
attacker a longer replay window than an honest client gets, and a ticket
inside the allowance still ages out of the freshness window at the same
rate, it just starts a minute earlier.

The allowance is a shared constant in file-store-oracles rather than a
setting in each service. The packet verifier checks freshness too, and
measures a ticket's age against the timestamp ingest stamped on it — so if
ingest tolerated drift the verifier did not, every ticket ingest accepted
from a fast client would be refused downstream. One value, not two that can
be configured apart.
michaeldjeffrey added a commit to helium/oracles that referenced this pull request Aug 28, 2026
* HIP-150: accept data transfer multiplier tickets in ingest

Adds submit_data_transfer_multiplier_ticket. A ticket grants one hotspot a
multiplier on the data credits derived from its rewardable bytes.

Ingest verifies the signer and the timestamp, then persists the ticket
verbatim. It does not parse the multiplier — the packet verifier decides
whether one is acceptable and records that verdict in a verified report, so
a rejection stays as auditable as a grant.

Notes:

- Ticket signers get their own allow-list and is_ticket_signer() check,
  rather than a NetworkKeyRole variant: that enum belongs to the
  decommissioned mobile-config. A carrier key must not be able to grant a
  multiplier.
- The list may be empty and defaults to empty, since the release ships
  before any ticket can be issued. Empty rejects every ticket and warns at
  startup.
- Tickets older than data_transfer_multiplier_ticket_max_age (default 10
  minutes) or dated in the future are refused. Signatures never expire, so
  without this a captured ticket is replayable forever.
- valid_data_transfer_session gained a multiplier field with the proto
  bump; set to None here, so behaviour is unchanged.

Cargo.toml patches helium-proto to proto's mj/hip-150 (helium/proto#483).
REVERT BEFORE MERGING mj/hip-150 TO main.

* HIP-150: tolerate client clock drift on multiplier tickets

A client does not share a clock with ingest, so a ticket signed at what the
client believes is "now" can arrive stamped slightly ahead of us. Those were
refused as post-dated, which is a confusing failure for an honest client
with a drifting clock.

Tickets up to MAX_CLOCK_DRIFT (1 minute) in the future are now treated as
current. Beyond that they are still refused: post-dating must not buy an
attacker a longer replay window than an honest client gets, and a ticket
inside the allowance still ages out of the freshness window at the same
rate, it just starts a minute earlier.

The allowance is a shared constant in file-store-oracles rather than a
setting in each service. The packet verifier checks freshness too, and
measures a ticket's age against the timestamp ingest stamped on it — so if
ingest tolerated drift the verifier did not, every ticket ingest accepted
from a fast client would be refused downstream. One value, not two that can
be configured apart.
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.

3 participants