Generates keypackages with 64 chars
This commit is contained in:
+48
-9
@@ -62,6 +62,13 @@ class KeyPackageRotationManager(
|
||||
private val activeBundles = mutableMapOf<String, KeyPackageBundle>()
|
||||
private val pendingRotations = mutableSetOf<String>()
|
||||
|
||||
/**
|
||||
* Maps logical slot names (e.g., "primary") to their persisted random d-tag values.
|
||||
* Per MIP-00, each slot gets a cryptographically random 64-char hex d-tag on first
|
||||
* use, which is then reused for all rotations of that slot.
|
||||
*/
|
||||
private val namedSlotDTags = mutableMapOf<String, String>()
|
||||
|
||||
/**
|
||||
* Maps the Nostr event id (kind:30443) of a published KeyPackage to the
|
||||
* d-tag slot whose bundle backs it. The welcome event references its
|
||||
@@ -136,10 +143,13 @@ class KeyPackageRotationManager(
|
||||
pendingRotations.addAll(decoded.pending)
|
||||
eventIdToSlot.clear()
|
||||
eventIdToSlot.putAll(decoded.eventIdToSlot)
|
||||
namedSlotDTags.clear()
|
||||
namedSlotDTags.putAll(decoded.namedSlotDTags)
|
||||
}
|
||||
Log.d("KeyPackageRotationManager") {
|
||||
"Restored ${decoded.bundles.size} active KeyPackage bundle(s), " +
|
||||
"${decoded.pending.size} pending rotation, ${decoded.eventIdToSlot.size} eventId mapping(s)"
|
||||
"${decoded.pending.size} pending rotation, ${decoded.eventIdToSlot.size} eventId mapping(s), " +
|
||||
"${decoded.namedSlotDTags.size} named slot d-tag(s)"
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.w("KeyPackageRotationManager", "Failed to decode persisted KeyPackages: ${e.message}")
|
||||
@@ -150,6 +160,7 @@ class KeyPackageRotationManager(
|
||||
val bundles: Map<String, KeyPackageBundle>,
|
||||
val pending: Set<String>,
|
||||
val eventIdToSlot: Map<String, String>,
|
||||
val namedSlotDTags: Map<String, String>,
|
||||
)
|
||||
|
||||
/**
|
||||
@@ -180,6 +191,12 @@ class KeyPackageRotationManager(
|
||||
writer.putOpaque2(eventId.encodeToByteArray())
|
||||
writer.putOpaque2(slot.encodeToByteArray())
|
||||
}
|
||||
// named slot → actual random d-tag (added in v3)
|
||||
writer.putUint32(namedSlotDTags.size.toLong())
|
||||
for ((name, dTag) in namedSlotDTags) {
|
||||
writer.putOpaque2(name.encodeToByteArray())
|
||||
writer.putOpaque2(dTag.encodeToByteArray())
|
||||
}
|
||||
return writer.toByteArray()
|
||||
}
|
||||
|
||||
@@ -213,15 +230,22 @@ class KeyPackageRotationManager(
|
||||
pending.add(reader.readOpaque2().decodeToString())
|
||||
}
|
||||
val eventIdMap = mutableMapOf<String, String>()
|
||||
val numEventIds = reader.readUint32().toInt()
|
||||
repeat(numEventIds) {
|
||||
val eventId = reader.readOpaque2().decodeToString()
|
||||
val slot = reader.readOpaque2().decodeToString()
|
||||
eventIdMap[eventId] = slot
|
||||
}
|
||||
val namedSlots = mutableMapOf<String, String>()
|
||||
if (reader.hasRemaining) {
|
||||
val numEventIds = reader.readUint32().toInt()
|
||||
repeat(numEventIds) {
|
||||
val eventId = reader.readOpaque2().decodeToString()
|
||||
val slot = reader.readOpaque2().decodeToString()
|
||||
eventIdMap[eventId] = slot
|
||||
val numNamed = reader.readUint32().toInt()
|
||||
repeat(numNamed) {
|
||||
val name = reader.readOpaque2().decodeToString()
|
||||
val dTag = reader.readOpaque2().decodeToString()
|
||||
namedSlots[name] = dTag
|
||||
}
|
||||
}
|
||||
return Snapshot(bundles, pending, eventIdMap)
|
||||
return Snapshot(bundles, pending, eventIdMap, namedSlots)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -238,11 +262,25 @@ class KeyPackageRotationManager(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the actual d-tag (64-char random hex) for a logical slot name, generating
|
||||
* and persisting a fresh random value on first call for that name.
|
||||
*
|
||||
* Per MIP-00: "Clients SHOULD generate a cryptographically random 32-byte hex string
|
||||
* as the d tag value when first publishing a KeyPackage."
|
||||
*/
|
||||
suspend fun getOrCreateSlotDTag(logicalSlotName: String): String =
|
||||
mutex.withLock {
|
||||
namedSlotDTags.getOrPut(logicalSlotName) {
|
||||
KeyPackageUtils.generateRandomDTag().also { persistUnlocked() }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a new KeyPackage and its associated private bundle.
|
||||
*
|
||||
* @param identity the user's identity bytes (typically 32-byte Nostr pubkey)
|
||||
* @param dTagSlot the d-tag slot for addressable replacement
|
||||
* @param dTagSlot the actual d-tag value (64-char hex) for addressable replacement
|
||||
* @return a [KeyPackageBundle] containing the KeyPackage and all private keys
|
||||
*/
|
||||
suspend fun generateKeyPackage(
|
||||
@@ -480,7 +518,8 @@ class KeyPackageRotationManager(
|
||||
* On-disk snapshot format version for [KeyPackageBundleStore].
|
||||
* v1: bundles + pendingRotations
|
||||
* v2: + eventIdToSlot map (so welcome lookup by Nostr event id works)
|
||||
* v3: + namedSlotDTags map (per MIP-00, d-tags are random 64-char hex, persisted here)
|
||||
*/
|
||||
private const val SNAPSHOT_VERSION = 2
|
||||
private const val SNAPSHOT_VERSION = 3
|
||||
}
|
||||
}
|
||||
|
||||
+15
-3
@@ -21,8 +21,10 @@
|
||||
package com.vitorpamplona.quartz.marmot.mip00KeyPackages
|
||||
|
||||
import com.vitorpamplona.quartz.marmot.mip00KeyPackages.tags.EncodingTag
|
||||
import com.vitorpamplona.quartz.marmot.mls.crypto.MlsCryptoProvider
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.EventTemplate
|
||||
|
||||
@@ -41,12 +43,22 @@ object KeyPackageUtils {
|
||||
/** Current addressable KeyPackage kind */
|
||||
const val CURRENT_KIND = KeyPackageEvent.KIND // 30443
|
||||
|
||||
/** Default d-tag slot for primary KeyPackage */
|
||||
const val PRIMARY_SLOT = "0"
|
||||
|
||||
/** Maximum number of d-tag slots a user should maintain */
|
||||
const val MAX_SLOTS = 10
|
||||
|
||||
/**
|
||||
* Logical name for the primary KeyPackage slot.
|
||||
* This is NOT the actual d-tag value — the actual d-tag is a randomly
|
||||
* generated 64-char hex string stored persistently in [KeyPackageRotationManager].
|
||||
*/
|
||||
const val PRIMARY_SLOT = "primary"
|
||||
|
||||
/**
|
||||
* Generates a cryptographically random d-tag value per MIP-00:
|
||||
* a 32-byte (64 hex char) random identifier for a KeyPackage slot.
|
||||
*/
|
||||
fun generateRandomDTag(): String = MlsCryptoProvider.randomBytes(32).toHexKey()
|
||||
|
||||
/**
|
||||
* Selects the best KeyPackage from a set of candidates for a given user.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user