mirror of
https://github.com/greenart7c3/Amber.git
synced 2026-10-05 19:08:23 +00:00
Use base64 v3 wire but log the decoded plaintext
Per the nip44v3 NIP-46 draft, v3 plaintext travels Base64-encoded on the wire: AmberUtils now base64-decodes on encrypt and encodes on decrypt. The EncryptedDataKind stores the readable plaintext in `text` and the Base64 wire value in `result`, so history/display read the decoded plaintext (via nip44v3Plaintext) while responses keep the wire value. The ContentProvider auto-accept path has no EncryptedDataKind, so it decodes the wire value once for its history entry.
This commit is contained in:
@@ -28,6 +28,14 @@ class SignerProvider : ContentProvider() {
|
||||
|
||||
private fun rejectedCursor(): Cursor = MatrixCursor(arrayOf("rejected")).also { it.addRow(arrayOf("true")) }
|
||||
|
||||
// Decodes the Base64 v3 wire value to readable plaintext for history.
|
||||
@OptIn(kotlin.io.encoding.ExperimentalEncodingApi::class)
|
||||
private fun nip44v3Plaintext(wireValue: String): String = try {
|
||||
kotlin.io.encoding.Base64.decode(wireValue).toString(Charsets.UTF_8)
|
||||
} catch (_: Exception) {
|
||||
wireValue
|
||||
}
|
||||
|
||||
override fun delete(
|
||||
uri: Uri,
|
||||
selection: String?,
|
||||
@@ -395,6 +403,13 @@ class SignerProvider : ContentProvider() {
|
||||
"Could not decrypt the message"
|
||||
}
|
||||
|
||||
// For v3 the wire value is Base64; this auto-accept path has no
|
||||
// EncryptedDataKind, so decode it once for the readable log.
|
||||
val historyContent = if (isV3) {
|
||||
nip44v3Plaintext(if (!isEncrypt) finalResult else content)
|
||||
} else {
|
||||
if (!isEncrypt) finalResult else content
|
||||
}
|
||||
scope.launch {
|
||||
historyDatabase.dao().addHistory(
|
||||
listOf(
|
||||
@@ -405,7 +420,7 @@ class SignerProvider : ContentProvider() {
|
||||
v3Kind,
|
||||
TimeUtils.now(),
|
||||
true,
|
||||
content = if (!isEncrypt) finalResult else content,
|
||||
content = historyContent,
|
||||
),
|
||||
),
|
||||
account.npub,
|
||||
|
||||
@@ -31,6 +31,13 @@ fun SignerType.toPermissionTypeString(encryptedData: EncryptedDataKind?): String
|
||||
else -> this.toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* The readable NIP-44 v3 plaintext. For v3 the data kind stores the real
|
||||
* plaintext in `text` and the Base64 wire value in `result`, so history/display
|
||||
* read `text` and never touch the wire encoding.
|
||||
*/
|
||||
fun EncryptedDataKind?.nip44v3Plaintext(): String = (this as? ClearTextEncryptedDataKind)?.text ?: this?.result ?: ""
|
||||
|
||||
val encryptDecryptSignerTypes = setOf(
|
||||
SignerType.NIP04_ENCRYPT,
|
||||
SignerType.NIP44_ENCRYPT,
|
||||
|
||||
@@ -25,11 +25,13 @@ import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
|
||||
object AmberUtils {
|
||||
/**
|
||||
* Like v2, NIP-44 v3 plaintext is handled as a UTF-8 string here: encrypt
|
||||
* takes the plaintext and returns the ciphertext; decrypt returns the
|
||||
* plaintext. [nip44v3Kind] and [nip44v3Scope] are required for the V3
|
||||
* SignerType variants (kind comes from the request; scope defaults to empty).
|
||||
* Produces the wire value for a request. Per the nip44v3 NIP-46 draft, v3
|
||||
* plaintext travels Base64-encoded: encrypt receives base64(plaintext) and
|
||||
* returns the ciphertext; decrypt returns base64(plaintext). The readable
|
||||
* plaintext for logs/display comes from the EncryptedDataKind instead (see
|
||||
* [nip44v3Plaintext]). [nip44v3Kind]/[nip44v3Scope] are required for V3.
|
||||
*/
|
||||
@OptIn(kotlin.io.encoding.ExperimentalEncodingApi::class)
|
||||
suspend fun encryptOrDecryptData(
|
||||
data: String,
|
||||
type: SignerType,
|
||||
@@ -52,11 +54,11 @@ object AmberUtils {
|
||||
}
|
||||
SignerType.NIP44_V3_ENCRYPT -> {
|
||||
requireNotNull(nip44v3Kind) { "kind is required for NIP44_V3_ENCRYPT" }
|
||||
account.nip44v3Encrypt(data.toByteArray(Charsets.UTF_8), pubKey, nip44v3Kind, nip44v3Scope)
|
||||
account.nip44v3Encrypt(kotlin.io.encoding.Base64.decode(data), pubKey, nip44v3Kind, nip44v3Scope)
|
||||
}
|
||||
SignerType.NIP44_V3_DECRYPT -> {
|
||||
requireNotNull(nip44v3Kind) { "kind is required for NIP44_V3_DECRYPT" }
|
||||
account.nip44v3Decrypt(data, pubKey, nip44v3Kind, nip44v3Scope).toString(Charsets.UTF_8)
|
||||
kotlin.io.encoding.Base64.encode(account.nip44v3Decrypt(data, pubKey, nip44v3Kind, nip44v3Scope))
|
||||
}
|
||||
else -> {
|
||||
account.nip44Decrypt(data, pubKey)
|
||||
|
||||
@@ -16,6 +16,7 @@ import com.greenart7c3.nostrsigner.models.AmberBunkerRequest
|
||||
import com.greenart7c3.nostrsigner.models.EncryptionType
|
||||
import com.greenart7c3.nostrsigner.models.Permission
|
||||
import com.greenart7c3.nostrsigner.models.SignerType
|
||||
import com.greenart7c3.nostrsigner.models.nip44v3Plaintext
|
||||
import com.greenart7c3.nostrsigner.relays.AmberListenerSingleton
|
||||
import com.greenart7c3.nostrsigner.service.model.AmberEvent
|
||||
import com.greenart7c3.nostrsigner.ui.RememberType
|
||||
@@ -430,9 +431,13 @@ object BunkerRequestUtils {
|
||||
SignerType.SIGN_EVENT,
|
||||
SignerType.NIP04_DECRYPT,
|
||||
SignerType.NIP44_DECRYPT,
|
||||
SignerType.NIP44_V3_DECRYPT,
|
||||
SignerType.DECRYPT_ZAP_EVENT,
|
||||
-> response
|
||||
// v3 wire values are Base64; log the readable plaintext
|
||||
// already decoded into encryptedData.
|
||||
SignerType.NIP44_V3_ENCRYPT,
|
||||
SignerType.NIP44_V3_DECRYPT,
|
||||
-> bunkerRequest.encryptedData.nip44v3Plaintext()
|
||||
else -> getDataFromBunker(bunkerRequest.request)
|
||||
},
|
||||
),
|
||||
|
||||
@@ -785,9 +785,11 @@ class EventNotificationConsumer(private val applicationContext: Context) {
|
||||
* Builds the preview [EncryptedDataKind] for a NIP-44 v3 bunker request,
|
||||
* mirroring v2: encrypt stores the plaintext as `text` and the ciphertext
|
||||
* as `result`; decrypt stores the ciphertext as `text` and the decrypted
|
||||
* plaintext as `result`. Returns null if it can't be produced (the request
|
||||
* is auto-rejected elsewhere in that case).
|
||||
* plaintext as `result`. The Base64 wire payload is decoded here once so
|
||||
* history/display can use the readable plaintext. Returns null if it can't
|
||||
* be produced (the request is auto-rejected elsewhere in that case).
|
||||
*/
|
||||
@OptIn(kotlin.io.encoding.ExperimentalEncodingApi::class)
|
||||
private fun nip44v3EncryptedDataKind(
|
||||
bunkerRequest: BunkerRequest,
|
||||
acc: Account,
|
||||
@@ -797,12 +799,14 @@ class EventNotificationConsumer(private val applicationContext: Context) {
|
||||
val data = BunkerRequestUtils.getDataFromBunker(bunkerRequest)
|
||||
val pubKey = bunkerRequest.params.firstOrNull() ?: return null
|
||||
return try {
|
||||
// text = readable plaintext (for display/history); result = wire value.
|
||||
if (bunkerRequest.method == "nip44v3_encrypt") {
|
||||
val ciphertext = acc.nip44v3Encrypt(data.toByteArray(Charsets.UTF_8), pubKey, kind, scope)
|
||||
ClearTextEncryptedDataKind(data, ciphertext)
|
||||
val plainBytes = kotlin.io.encoding.Base64.decode(data)
|
||||
val ciphertext = acc.nip44v3Encrypt(plainBytes, pubKey, kind, scope)
|
||||
ClearTextEncryptedDataKind(plainBytes.toString(Charsets.UTF_8), ciphertext)
|
||||
} else {
|
||||
val plaintext = acc.nip44v3Decrypt(data, pubKey, kind, scope).toString(Charsets.UTF_8)
|
||||
ClearTextEncryptedDataKind(data, plaintext)
|
||||
val plainBytes = acc.nip44v3Decrypt(data, pubKey, kind, scope)
|
||||
ClearTextEncryptedDataKind(plainBytes.toString(Charsets.UTF_8), kotlin.io.encoding.Base64.encode(plainBytes))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
if (e is kotlinx.coroutines.CancellationException) throw e
|
||||
|
||||
@@ -35,6 +35,7 @@ import com.greenart7c3.nostrsigner.models.ReturnType
|
||||
import com.greenart7c3.nostrsigner.models.SignerType
|
||||
import com.greenart7c3.nostrsigner.models.TagArrayEncryptedDataKind
|
||||
import com.greenart7c3.nostrsigner.models.containsNip
|
||||
import com.greenart7c3.nostrsigner.models.nip44v3Plaintext
|
||||
import com.greenart7c3.nostrsigner.service.model.AmberEvent
|
||||
import com.greenart7c3.nostrsigner.ui.RememberType
|
||||
import com.greenart7c3.nostrsigner.ui.components.DecryptTypeScope
|
||||
@@ -545,6 +546,7 @@ object IntentUtils {
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(kotlin.io.encoding.ExperimentalEncodingApi::class)
|
||||
private suspend fun getEncryptedDataKind(
|
||||
type: SignerType,
|
||||
result: String,
|
||||
@@ -555,6 +557,25 @@ object IntentUtils {
|
||||
PrivateZapEncryptedDataKind(result)
|
||||
}
|
||||
|
||||
// v3 wire values are Base64; decode once so display/history can read the
|
||||
// plaintext from `text`, while `result` keeps the wire value.
|
||||
SignerType.NIP44_V3_ENCRYPT -> {
|
||||
val plaintext = try {
|
||||
kotlin.io.encoding.Base64.decode(data).toString(Charsets.UTF_8)
|
||||
} catch (_: Exception) {
|
||||
data
|
||||
}
|
||||
ClearTextEncryptedDataKind(plaintext, result)
|
||||
}
|
||||
SignerType.NIP44_V3_DECRYPT -> {
|
||||
val plaintext = try {
|
||||
kotlin.io.encoding.Base64.decode(result).toString(Charsets.UTF_8)
|
||||
} catch (_: Exception) {
|
||||
result
|
||||
}
|
||||
ClearTextEncryptedDataKind(plaintext, result)
|
||||
}
|
||||
|
||||
else -> {
|
||||
if (type.name.contains("ENCRYPT")) {
|
||||
if (data.startsWith("{")) {
|
||||
@@ -854,10 +875,15 @@ object IntentUtils {
|
||||
SignerType.SIGN_EVENT -> event
|
||||
SignerType.NIP04_DECRYPT,
|
||||
SignerType.NIP44_DECRYPT,
|
||||
SignerType.NIP44_V3_DECRYPT,
|
||||
SignerType.DECRYPT_ZAP_EVENT,
|
||||
-> value
|
||||
|
||||
// v3 wire values are Base64; log the readable
|
||||
// plaintext already decoded into encryptedData.
|
||||
SignerType.NIP44_V3_ENCRYPT,
|
||||
SignerType.NIP44_V3_DECRYPT,
|
||||
-> intentData.encryptedData.nip44v3Plaintext()
|
||||
|
||||
else -> intentData.data
|
||||
},
|
||||
),
|
||||
|
||||
+3
-3
@@ -839,9 +839,9 @@ fun BunkerSingleEventHomeScreen(
|
||||
encryptedData = bunkerRequest.encryptedData,
|
||||
shouldRunOnAccept = acceptOrReject,
|
||||
onAccept = { rememberType, scope ->
|
||||
// encryptedData.result holds the ciphertext (encrypt) or
|
||||
// decrypted plaintext (decrypt), computed when the request
|
||||
// arrived — same as the v2 path.
|
||||
// encryptedData.result holds the Base64 wire value (ciphertext
|
||||
// for encrypt, base64 plaintext for decrypt), computed when the
|
||||
// request arrived — same shape as the v2 path.
|
||||
val result = bunkerRequest.encryptedData?.result ?: ""
|
||||
// SPECIFIC ⇒ kind-scoped grant; ALL ⇒ broad grant (kind=null).
|
||||
val grantedKind = if (scope == DecryptTypeScope.SPECIFIC) v3Kind else null
|
||||
|
||||
@@ -27,10 +27,10 @@ import androidx.compose.ui.unit.sp
|
||||
import com.greenart7c3.nostrsigner.Amber
|
||||
import com.greenart7c3.nostrsigner.R
|
||||
import com.greenart7c3.nostrsigner.models.Account
|
||||
import com.greenart7c3.nostrsigner.models.ClearTextEncryptedDataKind
|
||||
import com.greenart7c3.nostrsigner.models.EncryptedDataKind
|
||||
import com.greenart7c3.nostrsigner.models.Permission
|
||||
import com.greenart7c3.nostrsigner.models.SignerType
|
||||
import com.greenart7c3.nostrsigner.models.nip44v3Plaintext
|
||||
import com.greenart7c3.nostrsigner.ui.RememberType
|
||||
|
||||
/**
|
||||
@@ -61,7 +61,7 @@ fun Nip44v3ApprovalData(
|
||||
|
||||
val isEncrypt = type == SignerType.NIP44_V3_ENCRYPT
|
||||
val messageRes = if (isEncrypt) R.string.nip44_v3_wants_to_encrypt else R.string.nip44_v3_wants_to_decrypt
|
||||
val displayContent = nip44v3DisplayContent(encryptedData, isEncrypt)
|
||||
val displayContent = encryptedData.nip44v3Plaintext()
|
||||
|
||||
Column(modifier) {
|
||||
if (isBunker) {
|
||||
@@ -181,13 +181,3 @@ fun Nip44v3ContextBox(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The plaintext to show: encrypt stores it as `text`, decrypt as `result`
|
||||
* (populated when the request was parsed) — same shape as the v2 screen.
|
||||
*/
|
||||
private fun nip44v3DisplayContent(encryptedData: EncryptedDataKind?, isEncrypt: Boolean): String = if (isEncrypt) {
|
||||
(encryptedData as? ClearTextEncryptedDataKind)?.text ?: encryptedData?.result ?: ""
|
||||
} else {
|
||||
encryptedData?.result ?: ""
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user