Reputation test vectors
vectors/reputation_v1.json holds the test vectors of the
reputation attestation. Every implementation of it —
mostro-core, both clients and lnp2pBot — tests against this file, so they accept and
refuse exactly the same events.
The file is generated by tools/reputation-vectors, which builds the events with the
nostr crate alone, independently of mostro-core. Keys are derived from fixed labels and
signatures are BIP-340 with no auxiliary randomness, so rerunning it reproduces the file
byte for byte:
cargo run --manifest-path tools/reputation-vectors/Cargo.toml > src/vectors/reputation_v1.json
The secret keys are in the file. They are test keys and nothing else.
Layout
| Key | Contents |
|---|---|
context | The destination every attestation check runs as: the clock (now), the 300-second clock_skew, the 7-day max_lifetime, the identity the request proves (proven_identity), the trust_list as named entries with their keys, and the destination's own_issuer_key, which its trust list also holds |
secret_keys | The secret key behind every public key in the file |
attestation.valid | An attestation to accept, as an object (event) and in the serialisation it travels in (json), with its id and the fields a parser extracts (expect). expect.issuer_name is the trust-list entry its key belongs to |
attestation.accepted | More attestations to accept: each skew and lifetime boundary exactly at its limit, reviews and rating at their floors, and an unknown tag |
attestation.invalid | Attestations to refuse, each with the cant-do reason the destination answers with |
rating_rounding | An internal average and the rating an issuer writes for it |
rebind | A rebind authorisation to accept and several to refuse, with their own rebind.context: the clock (now) and clock_skew, the issuer whose key is rebind.context.issuer_key, the account's bound identity rebind.context.bound_identity, and the destination of the export request the authorisation comes with, rebind.context.destination |
merge | A user row before an import, after it, and after the import is reversed |
Using the attestation vectors
Run the redemption checks on each event with
the values of context:
attestation.validand everyattestation.acceptedentry pass;- every
attestation.invalidentry fails with itsreason.
Most invalid entries break a rule of the event itself and fail before any context is
read. Three are well formed and fail only against the destination's context:
identity_mismatch (step 5), untrusted_issuer and own_issuer_key (step 3). A parser
that leaves those checks to its caller, as mostro-core does, accepts them and its
caller's tests refuse them.
Where an entry breaks two rules at once the order of the checks decides the reason; each entry here breaks exactly one.
Using the rebind vectors
Run the rebind checks on each event with the
values of rebind.context, not of the root context: rebind.valid passes, and every
rebind.invalid entry fails with invalid_reputation_rebind. Two of those are well
formed and fail only against that context: other_destination, whose p is not the
request's destination, and signed_by_other_identity, whose author is not the bound
identity.
Using the rounding vectors
rating is clamp(round(average × 100), 100, 500) / 100 in double precision, rounded
half away from zero and written with two decimals. The entries include products that
are exact halves (412.5, 400.5), so an implementation that rounds half to even
fails, and one that is not (4.895 × 100 = 489.49999999999994), so one that rounds the
decimal string instead of the double fails too.
Using the merge vectors
Apply the import to before and compare with after, then reverse it and compare with
reverted. Floating-point columns compare within merge.tolerance; every other column
compares exactly. The rating the import carries is the decimal string parsed as a
double. no_reputation covers a row with no rating at all, where the import also sets
min_rating, max_rating and last_rating, and the reversal clears them.