Merge main: relay hotfixes and the docs pages
Conflicts were comment wording only, from the WordPress purge. Kept this
branch's prose: main's side carries the rename's artifacts ('the the
previous platform'), and the code either side registers is identical.
Verified after resolving — one api index, no wp-json index, the
federation refusal path, HomeRelays, and FN_ACCOUNTS_URL all present.
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
package io.flamenet.e2e
|
||||
|
||||
/**
|
||||
* Global account identity — the address a person keeps when a relay does not.
|
||||
*
|
||||
* A relay's `user_id` is a row number it assigned itself; the account service's
|
||||
* pseudonym is HMAC'd per product so two services cannot correlate their users.
|
||||
* Neither survives a trip to a second relay. This does, because it is a public
|
||||
* key the client derives and no server issues.
|
||||
*
|
||||
* Implements `engine/src/fnid.js`, which is normative.
|
||||
*
|
||||
* ⚠️ This engine can *encode* an address but cannot yet *derive* one: the
|
||||
* account key comes from PBKDF2 over the passphrase under
|
||||
* `flamenet/account/identity/v1`, and this module has no KDF at all — the JS
|
||||
* and Swift engines each have one and Kotlin does not. Until that exists, an
|
||||
* Android client can verify and display an address it is given and cannot
|
||||
* produce its own.
|
||||
*/
|
||||
object FNID {
|
||||
|
||||
/**
|
||||
* Crockford base32: no padding, and no `I`, `L`, `O` or `U`, so the
|
||||
* alphabet holds no pair a person can confuse reading an address aloud or
|
||||
* copying it off a screen. Matches the recovery codes Files already issues
|
||||
* rather than introducing a second encoding.
|
||||
*/
|
||||
const val ALPHABET = "0123456789abcdefghjkmnpqrstvwxyz"
|
||||
|
||||
/** 32 raw bytes of an Ed25519 public key encode to 52 characters. */
|
||||
const val ENCODED_LENGTH = 52
|
||||
|
||||
fun encode(bytes: ByteArray): String {
|
||||
val out = StringBuilder()
|
||||
var buffer = 0
|
||||
var bits = 0
|
||||
for (b in bytes) {
|
||||
buffer = (buffer shl 8) or (b.toInt() and 0xFF)
|
||||
bits += 8
|
||||
while (bits >= 5) {
|
||||
bits -= 5
|
||||
out.append(ALPHABET[(buffer shr bits) and 31])
|
||||
}
|
||||
}
|
||||
if (bits > 0) out.append(ALPHABET[(buffer shl (5 - bits)) and 31])
|
||||
return out.toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* The address for an account key, prefixed so a loose comparison cannot
|
||||
* mistake some other 52-character string for one of these.
|
||||
*/
|
||||
fun address(accountPublicKey: ByteArray): String = "fnid:" + encode(accountPublicKey)
|
||||
|
||||
/**
|
||||
* What a client signs to claim an address at a relay.
|
||||
*
|
||||
* Binds the audience (so a claim cannot be replayed at another relay), the
|
||||
* local user id (so it cannot be lifted onto another account there) and the
|
||||
* key itself (so the signature cannot be transplanted). Concatenated rather
|
||||
* than encoded, because three implementations must reproduce these bytes
|
||||
* exactly and an encoder may respace between releases.
|
||||
*/
|
||||
fun claimCanonical(audience: String, userId: Long, accountKeyB64: String): ByteArray =
|
||||
"fnacct-v1|$audience|$userId|$accountKeyB64".toByteArray(Charsets.UTF_8)
|
||||
}
|
||||
Reference in New Issue
Block a user