diff options
| author | Adam Soltys <[email protected]> | 2026-10-06 23:26:23 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-10-06 23:26:23 -0700 |
| commit | 9b3a440f9e56054d23bf24b9c0b2010c92e98d68 (patch) | |
| tree | e1eb6495242c5887e4919fa4c21ad4e824f6235d /src/marmot.js | |
| parent | d657d79bc95062a8fc5e128e795e23bcefd0bf04 (diff) | |
| download | coinosv3-9b3a440f9e56054d23bf24b9c0b2010c92e98d68.tar.gz coinosv3-9b3a440f9e56054d23bf24b9c0b2010c92e98d68.zip | |
White Noise groups reach every device of an account
An invitation adds all of a person's fresh KeyPackages (one per slot, at
most ten) instead of one, and where our account is an admin a device that
finds a sibling's KeyPackage not yet in a group adds it, so the Welcome
reaches the other device through our own inbox. These are the same-account
rules Marmot's multi-device direction marks as valid today; the device
group and pairing it plans are not here. Verified against MDK (wn): a group
with two coinos leaves of one npub plus a wn member, all three reading and
writing (tools/marmot-multidevice-test.js). marmotClient takes an
injectable store for tests.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01YRC6aKhwAud231ot9FPmhi
Diffstat (limited to 'src/marmot.js')
| -rw-r--r-- | src/marmot.js | 15 |
1 files changed, 11 insertions, 4 deletions
diff --git a/src/marmot.js b/src/marmot.js index 7dd55d6..f6153a0 100644 --- a/src/marmot.js +++ b/src/marmot.js @@ -382,6 +382,7 @@ export function readKeyPackageEvent(ev, at = now()) { return { id: ev.id, pubkey: ev.pubkey, slot: d, ref: i, created_at: ev.created_at, lastResort, keyPackage: kp.keyPackage, + sigKey: bytesToHex(kp.leaf.signatureKey), // which device: the leaf it would occupy components: idList.dec(comp(kp.leaf.extensions, COMP.APP_COMPONENTS)), extensions: caps.extensions, proposals: caps.proposals, client: ev.tags.find((t) => t[0] === "client")?.[1] || "", @@ -389,17 +390,23 @@ export function readKeyPackageEvent(ev, at = now()) { } catch { return null; } } -// One candidate per (author, slot) — the newest, lower id on a tie — then -// single-use packages before last-resort ones, fresher first, lower ref last. -export function pickKeyPackage(candidates) { +// One candidate per (author, slot) — the newest, lower id on a tie: a slot +// is one installation, so this is one package per device. Ordered single-use +// before last-resort, fresher first, lower ref last; at most `max` of them +// (Marmot's multi-device direction caps an account at ten leaves). +export const MAX_DEVICES = 10; +export function pickKeyPackages(candidates, max = MAX_DEVICES) { const slots = new Map(); for (const c of candidates) { const k = c.pubkey + c.slot, cur = slots.get(k); if (!cur || c.created_at > cur.created_at || (c.created_at === cur.created_at && c.id < cur.id)) slots.set(k, c); } return [...slots.values()].sort((a, b) => - (a.lastResort - b.lastResort) || (b.created_at - a.created_at) || (a.ref < b.ref ? -1 : 1))[0] || null; + (a.lastResort - b.lastResort) || (b.created_at - a.created_at) || (a.ref < b.ref ? -1 : 1)).slice(0, max); } +export const pickKeyPackage = (candidates) => pickKeyPackages(candidates, 1)[0] || null; +// the signature keys of every leaf in a state: which devices are already in +export const leafKeys = (state) => mls.members(state).map((m) => bytesToHex(m.signatureKey)); // Can this KeyPackage join a group in this state? export function fits(state, cand) { |
