- server: relay storing signed objects (PUT/GET), per-IP rate limiting, per-subject quota (1000), one-response-per-request, pagination, /v1/healthz /v1/readyz /v1/metrics - verify: signature-verifying trust evaluator; every object is checked via env.Verify(), approvals via VerifyApprovalResponse, revocations via VerifyRevocationOf; k-of-n approval quorum - docs: TRUST-MODEL.md and API.md describing issuer-anchored signatures and the endpoint/status-code contract - tests: server, verify, and ratelimit packages
3.7 KiB
Trust address format
A trust address is the textual form of an Ed25519 public key. It is the only identifier the protocol has for an identity.
trust1qqak5faue6m2gttz5w5dq2n0p4ek2vs4wuw7ysax8tqy3gvtt8dzj0yfahr
\___/\__________________________________________________/\_____/
hrp payload (53 chars) checksum
Encoding
hrp = "trust"
payload = version(1 byte) || ed25519 public key(32 bytes)
address = bech32m(hrp, convertbits(payload, 8 -> 5, pad = true))
- Version is currently
0x00, denoting a raw 32-byte Ed25519 public key. Because bech32 regroups the payload into 5-bit units, a leading zero byte renders asq, which is why every current address reads astrust1q.... - Length is exactly 65 characters for version 0.
- Case is lowercase only. bech32 permits an all-uppercase form, but two spellings of one address would violate INV-8, so uppercase input is refused.
Why bech32m rather than bech32
bech32m (BIP-350) differs from the original bech32 (BIP-173) only in the checksum constant. The original has a known weakness when the final data character can vary in a length-extending way, which is why BIP-350 exists. A trust payload is preceded by a version byte that is intended to change over time, so bech32m is the correct choice.
Decoding enforces this exactly. The bech32 constant is recognised and rejected with a distinct error, so a downgrade to the weaker checksum or a confusion with a foreign address family is not accepted silently.
Validation performed on decode
Parsing an address is strict, and every rule removes either an alternative spelling or an unusable key:
| Check | Rejected because |
|---|---|
| non-empty, length <= 65 | bounds work done on untrusted input |
| lowercase only | two spellings of one address (INV-8) |
| bech32m checksum verifies | corruption must never decode to another identity |
| checksum constant is bech32m | downgrade or foreign address family |
hrp is trust |
address belongs to another protocol |
| payload is exactly 33 bytes | malformed |
version is 0x00 |
unknown protocol version |
| 5 to 8 bit regrouping leaves no non-zero padding | alternative spelling |
| key is a canonical curve point | non-canonical encodings alias one point |
| key is not of small order | universal signature forgery, see below |
Public key validation
The last two rows are a cryptographic requirement, not a formatting one.
crypto/ed25519.Verify performs no checks on the public key, as RFC 8032
specifies. When the attacker chooses the key this is exploitable: the order-1
point (0100...00) has a signature that verifies against every message.
Anyone who registered such a key would own an identity whose signatures are
valid on any claim or approval that anyone cares to construct.
Every key is therefore checked to be a canonically encoded point that is not annihilated by multiplication by the cofactor. Honest keys always pass; the degenerate ones can never become an address, and therefore can never become an identity.
Properties relied on elsewhere
- Bijective. Every valid key has exactly one address; every valid address yields exactly one key. Verified by fuzzing in both directions.
- Self-validating. A non-zero
Addressvalue has already passed every check above, so later code never has to re-validate. - Corruption-detecting. Every single-character substitution and every transposition in the data part is rejected. A mistyped address fails to parse rather than resolving to a different identity.
- Fail-closed zero value. The zero
Addressdenotes no identity, does not compare equal to itself, and stringifies to the empty string.