feat: include all group members in p-tags of inner ephemeral events

All signaling event types in group calls (answer, hangup, reject,
renegotiate) now include p-tags for every group member, matching the
pattern already used by CallOfferEvent. This allows signing the inner
event once and gift-wrapping it separately for each recipient.

- Add Set<HexKey> build() overloads to CallAnswerEvent, CallHangupEvent,
  CallRejectEvent, and CallRenegotiateEvent
- Add createGroupCallAnswer, createGroupRenegotiate factory methods
- Fix createGroupHangup to sign once instead of per-peer
- Update createGroupReject to accept full member set
- Update CallManager to use group methods when in group call
- Document group call p-tag convention in NIP-AC spec
- Add tests for all group build overloads

https://claude.ai/code/session_013h2E7spwHDgSjunqumsYgp
This commit is contained in:
Claude
2026-04-03 04:53:17 +00:00
parent 2fa045370d
commit b2584e6705
8 changed files with 268 additions and 31 deletions
@@ -249,6 +249,72 @@ class CallEventsTest {
assertEquals((2000L + CallOfferEvent.EXPIRATION_SECONDS).toString(), expirationTag?.get(1))
}
@Test
fun groupCallAnswerBuildIncludesAllPTags() {
val members = setOf("alice", "bob", "carol")
val template =
CallAnswerEvent.build(
sdpAnswer = "answer-sdp",
memberPubKeys = members,
callId = "group-call-1",
)
val pTagValues =
template.tags
.filter { it[0] == "p" }
.map { it[1] }
.toSet()
assertEquals(members, pTagValues)
}
@Test
fun groupCallHangupBuildIncludesAllPTags() {
val members = setOf("alice", "bob", "carol")
val template =
CallHangupEvent.build(
memberPubKeys = members,
callId = "group-call-1",
)
val pTagValues =
template.tags
.filter { it[0] == "p" }
.map { it[1] }
.toSet()
assertEquals(members, pTagValues)
}
@Test
fun groupCallRejectBuildIncludesAllPTags() {
val members = setOf("alice", "bob", "carol")
val template =
CallRejectEvent.build(
memberPubKeys = members,
callId = "group-call-1",
)
val pTagValues =
template.tags
.filter { it[0] == "p" }
.map { it[1] }
.toSet()
assertEquals(members, pTagValues)
}
@Test
fun groupCallRenegotiateBuildIncludesAllPTags() {
val members = setOf("alice", "bob", "carol")
val template =
CallRenegotiateEvent.build(
sdpOffer = "new-sdp-offer",
memberPubKeys = members,
callId = "group-call-1",
)
val pTagValues =
template.tags
.filter { it[0] == "p" }
.map { it[1] }
.toSet()
assertEquals(members, pTagValues)
}
@Test
fun singleCalleeOfferIsNotGroupCall() {
val template =