Skip to content

Commit b7bda82

Browse files
JOHNJOHN
authored andcommitted
feat: add provider identity binding for proposals
1 parent b44bd52 commit b7bda82

File tree

1 file changed

+23
-5
lines changed

1 file changed

+23
-5
lines changed

app/src/main/java/to/bitkit/paykit/services/DirectoryService.kt

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -557,7 +557,7 @@ removeNoiseEndpoint(transport)
557557
val proposalPath = "$proposalsPath$proposalId"
558558
val envelopeBytes = pubkyStorage.retrieve(proposalPath, adapter, peerPubkey)
559559
val envelopeJson = envelopeBytes?.let { String(it) }
560-
decryptAndParseSubscriptionProposal(proposalId, envelopeJson, myPubkey)
560+
decryptAndParseSubscriptionProposal(proposalId, envelopeJson, myPubkey, peerPubkey)
561561
} catch (e: Exception) {
562562
Logger.error("Failed to parse proposal $proposalId", e, context = TAG)
563563
null
@@ -592,7 +592,8 @@ removeNoiseEndpoint(transport)
592592
val proposalPath = "$proposalsPath$proposalId"
593593
val envelopeBytes = pubkyStorage.retrieve(proposalPath, adapter, ownerPubkey)
594594
val envelopeJson = envelopeBytes?.let { String(it) }
595-
decryptAndParseSubscriptionProposal(proposalId, envelopeJson, ownerPubkey)
595+
// NOTE: This deprecated method cannot verify provider binding since we don't know who we're polling
596+
decryptAndParseSubscriptionProposal(proposalId, envelopeJson, ownerPubkey, expectedProviderPubkey = null)
596597
} catch (e: Exception) {
597598
Logger.error("Failed to parse proposal $proposalId", e, context = TAG)
598599
null
@@ -688,7 +689,7 @@ removeNoiseEndpoint(transport)
688689
return try {
689690
val envelopeBytes = pubkyStorage.retrieve(proposalPath, adapter, providerPubkey)
690691
val envelopeJson = envelopeBytes?.let { String(it) }
691-
decryptAndParseSubscriptionProposal(proposalId, envelopeJson, subscriberPubkey)
692+
decryptAndParseSubscriptionProposal(proposalId, envelopeJson, subscriberPubkey, providerPubkey)
692693
} catch (e: Exception) {
693694
Logger.error("Failed to fetch subscription proposal $proposalId", e, context = TAG)
694695
null
@@ -786,12 +787,14 @@ removeNoiseEndpoint(transport)
786787
* @param proposalId The proposal ID
787788
* @param envelopeJson The JSON string of the sealed blob (or null)
788789
* @param subscriberPubkey Our pubkey (used for canonical AAD computation)
789-
* @return The parsed proposal or null if decryption/parsing fails
790+
* @param expectedProviderPubkey If provided, verifies that provider_pubkey in the proposal matches this value
791+
* @return The parsed proposal or null if decryption/parsing/validation fails
790792
*/
791793
private fun decryptAndParseSubscriptionProposal(
792794
proposalId: String,
793795
envelopeJson: String?,
794796
subscriberPubkey: String,
797+
expectedProviderPubkey: String? = null,
795798
): to.bitkit.paykit.workers.DiscoveredSubscriptionProposal? {
796799
if (envelopeJson.isNullOrBlank()) return null
797800

@@ -816,9 +819,24 @@ removeNoiseEndpoint(transport)
816819
val plaintextJson = String(plaintextBytes)
817820
val obj = org.json.JSONObject(plaintextJson)
818821

822+
val providerPubkey = obj.optString("provider_pubkey", "")
823+
824+
// SECURITY: Verify provider identity binding
825+
if (expectedProviderPubkey != null && providerPubkey.isNotEmpty()) {
826+
val normalizedExpected = PaykitV0Protocol.normalizePubkeyZ32(expectedProviderPubkey)
827+
val normalizedActual = PaykitV0Protocol.normalizePubkeyZ32(providerPubkey)
828+
if (normalizedExpected != normalizedActual) {
829+
Logger.error(
830+
"Provider identity mismatch for proposal $proposalId: expected $normalizedExpected, got $normalizedActual",
831+
context = TAG,
832+
)
833+
return null
834+
}
835+
}
836+
819837
to.bitkit.paykit.workers.DiscoveredSubscriptionProposal(
820838
subscriptionId = proposalId,
821-
providerPubkey = obj.optString("provider_pubkey", ""),
839+
providerPubkey = providerPubkey,
822840
amountSats = obj.optLong("amount_sats", 0),
823841
description = if (obj.has("description")) obj.getString("description") else null,
824842
frequency = obj.optString("frequency", "monthly"),

0 commit comments

Comments
 (0)