mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-10-11 03:24:12 +02:00
Implements NIP-44 extension for bigger payloads https://github.com/nostr-protocol/nips/pull/1907
This commit is contained in:
@@ -143,12 +143,14 @@ class Nip44v2Test {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun invalidMessageLengths() {
|
fun extendedMessageLengths() {
|
||||||
for (v in vectors.v2?.invalid?.encryptMsgLengths!!) {
|
for (v in vectors.v2?.invalid?.encryptMsgLengths!!) {
|
||||||
val key = RandomInstance.bytes(32)
|
val key = RandomInstance.bytes(32)
|
||||||
try {
|
try {
|
||||||
nip44v2.encrypt("a".repeat(v), key)
|
val input = "a".repeat(v)
|
||||||
fail("Should Throw for $v")
|
val result = nip44v2.encrypt(input, key)
|
||||||
|
val decrypted = nip44v2.decrypt(result, key)
|
||||||
|
assertEquals(input, decrypted)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
assertNotNull(e)
|
assertNotNull(e)
|
||||||
}
|
}
|
||||||
|
@@ -510,6 +510,22 @@
|
|||||||
"repeat": 16383,
|
"repeat": 16383,
|
||||||
"plaintext_sha256": "a249558d161b77297bc0cb311dde7d77190f6571b25c7e4429cd19044634a61f",
|
"plaintext_sha256": "a249558d161b77297bc0cb311dde7d77190f6571b25c7e4429cd19044634a61f",
|
||||||
"payload_sha256": "b3348422471da1f3c59d79acfe2fe103f3cd24488109e5b18734cdb5953afd15"
|
"payload_sha256": "b3348422471da1f3c59d79acfe2fe103f3cd24488109e5b18734cdb5953afd15"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"conversation_key": "56adbe3720339363ab9c3b8526ffce9fd77600927488bfc4b59f7a68ffe5eae0",
|
||||||
|
"nonce": "ad68da81833c2a8ff609c3d2c0335fd44fe5954f85bb580c6a8d467aa9fc5dd0",
|
||||||
|
"pattern": "!",
|
||||||
|
"repeat": 65536,
|
||||||
|
"plaintext_sha256": "b007fe445ff5b583c095c4688c75d8afef66d4c93eb6aeebcea0942e670e1cd3",
|
||||||
|
"payload_sha256": "f816ffbcb053a0669488992e2d7c57c2d950d3d4af6e19a16297f3e565a4103f"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"conversation_key": "56adbe3720339363ab9c3b8526ffce9fd77600927488bfc4b59f7a68ffe5eae0",
|
||||||
|
"nonce": "ad68da81833c2a8ff609c3d2c0335fd44fe5954f85bb580c6a8d467aa9fc5dd0",
|
||||||
|
"pattern": "a",
|
||||||
|
"repeat": 20000000,
|
||||||
|
"plaintext_sha256": "aded0ea9b4d06589b13d00bab483faf479d61ed5de21f1760aa7018a28e330e5",
|
||||||
|
"payload_sha256": "9e683311894d52e48a825837883c539263c7787c7fe024e1590d96776a31684b"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@@ -26,8 +26,6 @@ import com.vitorpamplona.quartz.utils.LibSodiumInstance
|
|||||||
import com.vitorpamplona.quartz.utils.RandomInstance
|
import com.vitorpamplona.quartz.utils.RandomInstance
|
||||||
import com.vitorpamplona.quartz.utils.Secp256k1Instance
|
import com.vitorpamplona.quartz.utils.Secp256k1Instance
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
import java.nio.ByteBuffer
|
|
||||||
import java.nio.ByteOrder
|
|
||||||
import java.util.Base64
|
import java.util.Base64
|
||||||
import kotlin.math.floor
|
import kotlin.math.floor
|
||||||
import kotlin.math.log2
|
import kotlin.math.log2
|
||||||
@@ -42,6 +40,9 @@ class Nip44v2 {
|
|||||||
private val minPlaintextSize: Int = 0x0001 // 1b msg => padded to 32b
|
private val minPlaintextSize: Int = 0x0001 // 1b msg => padded to 32b
|
||||||
private val maxPlaintextSize: Int = 0xffff // 65535 (64kb-1) => padded to 64kb
|
private val maxPlaintextSize: Int = 0xffff // 65535 (64kb-1) => padded to 64kb
|
||||||
|
|
||||||
|
private val extMinPlaintextSize: Int = 0x00000001 // 1b msg => padded to 32b
|
||||||
|
private val extMaxPlaintextSize: Long = 0xffffffff // 4294967294 => padded
|
||||||
|
|
||||||
fun clearCache() {
|
fun clearCache() {
|
||||||
sharedKeyCache.clearCache()
|
sharedKeyCache.clearCache()
|
||||||
}
|
}
|
||||||
@@ -151,42 +152,49 @@ class Nip44v2 {
|
|||||||
|
|
||||||
check(unpaddedLen > 0) { "Message is empty ($unpaddedLen): $plaintext" }
|
check(unpaddedLen > 0) { "Message is empty ($unpaddedLen): $plaintext" }
|
||||||
|
|
||||||
check(unpaddedLen <= maxPlaintextSize) { "Message is too long ($unpaddedLen): $plaintext" }
|
|
||||||
|
|
||||||
val prefix =
|
val prefix =
|
||||||
ByteBuffer
|
if (unpaddedLen <= maxPlaintextSize) {
|
||||||
.allocate(2)
|
// 2 bytes in big endian
|
||||||
.order(ByteOrder.BIG_ENDIAN)
|
intTo2BytesBigEndian(unpaddedLen)
|
||||||
.putShort(unpaddedLen.toShort())
|
} else if (unpaddedLen <= extMaxPlaintextSize) {
|
||||||
.array()
|
// Extension to allow > 65KB payloads
|
||||||
val suffix = ByteArray(calcPaddedLen(unpaddedLen) - unpaddedLen)
|
// 2+4 bytes in big endian
|
||||||
return ByteBuffer.wrap(prefix + unpadded + suffix).array()
|
byteArrayOf(0, 0) + intTo4BytesBigEndian(unpaddedLen)
|
||||||
|
} else {
|
||||||
|
throw IllegalArgumentException("Message is too long ($unpaddedLen): $plaintext")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun bytesToInt(
|
val suffix = ByteArray(calcPaddedLen(unpaddedLen) - unpaddedLen)
|
||||||
byte1: Byte,
|
return prefix + unpadded + suffix
|
||||||
byte2: Byte,
|
|
||||||
bigEndian: Boolean,
|
|
||||||
): Int =
|
|
||||||
if (bigEndian) {
|
|
||||||
(byte1.toInt() and 0xFF shl 8 or (byte2.toInt() and 0xFF))
|
|
||||||
} else {
|
|
||||||
(byte2.toInt() and 0xFF shl 8 or (byte1.toInt() and 0xFF))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun unpad(padded: ByteArray): String {
|
fun unpad(padded: ByteArray): String {
|
||||||
val unpaddedLen: Int = bytesToInt(padded[0], padded[1], true)
|
val unpaddedLenPreExt: Int = bytesToIntBigEndian(padded[0], padded[1])
|
||||||
val unpadded = padded.sliceArray(2 until 2 + unpaddedLen)
|
|
||||||
|
|
||||||
check(
|
return if (unpaddedLenPreExt == 0) {
|
||||||
unpaddedLen in minPlaintextSize..maxPlaintextSize &&
|
// NIP-44 extension to handle bigger than 65K payloads
|
||||||
unpadded.size == unpaddedLen &&
|
val unpaddedLenExt: Int = bytesToIntBigEndian(padded[2], padded[3], padded[4], padded[5])
|
||||||
padded.size == 2 + calcPaddedLen(unpaddedLen),
|
|
||||||
) {
|
check(unpaddedLenExt in extMinPlaintextSize..extMaxPlaintextSize) {
|
||||||
"invalid padding ${unpadded.size} != $unpaddedLen"
|
"Invalid size $unpaddedLenExt not between $extMinPlaintextSize and $extMaxPlaintextSize"
|
||||||
}
|
}
|
||||||
|
|
||||||
return unpadded.decodeToString()
|
check(padded.size == 6 + calcPaddedLen(unpaddedLenExt)) {
|
||||||
|
"Invalid padding ${calcPaddedLen(unpaddedLenExt)} != $unpaddedLenExt"
|
||||||
|
}
|
||||||
|
|
||||||
|
String(padded, 6, unpaddedLenExt)
|
||||||
|
} else {
|
||||||
|
check(unpaddedLenPreExt in minPlaintextSize..maxPlaintextSize) {
|
||||||
|
"Invalid size $unpaddedLenPreExt not between $minPlaintextSize and $maxPlaintextSize"
|
||||||
|
}
|
||||||
|
|
||||||
|
check(padded.size == 2 + calcPaddedLen(unpaddedLenPreExt)) {
|
||||||
|
"Invalid padding ${calcPaddedLen(unpaddedLenPreExt)} != $unpaddedLenPreExt"
|
||||||
|
}
|
||||||
|
|
||||||
|
String(padded, 2, unpaddedLenPreExt)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun hmacAad(
|
fun hmacAad(
|
||||||
@@ -264,4 +272,41 @@ class Nip44v2 {
|
|||||||
byteArrayOf(V.toByte()) + nonce + ciphertext + mac,
|
byteArrayOf(V.toByte()) + nonce + ciphertext + mac,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -----------------------------------
|
||||||
|
// FASTER METHODS THAN BUFFER WRAPPING
|
||||||
|
// -----------------------------------
|
||||||
|
private fun bytesToIntBigEndian(
|
||||||
|
byte1: Byte,
|
||||||
|
byte2: Byte,
|
||||||
|
): Int = (byte1.toInt() and 0xFF shl 8 or (byte2.toInt() and 0xFF))
|
||||||
|
|
||||||
|
private fun bytesToIntBigEndian(
|
||||||
|
byte1: Byte,
|
||||||
|
byte2: Byte,
|
||||||
|
byte3: Byte,
|
||||||
|
byte4: Byte,
|
||||||
|
): Int {
|
||||||
|
val result =
|
||||||
|
((byte1.toLong() and 0xFF) shl 24) or
|
||||||
|
((byte2.toLong() and 0xFF) shl 16) or
|
||||||
|
((byte3.toLong() and 0xFF) shl 8) or
|
||||||
|
(byte4.toLong() and 0xFF)
|
||||||
|
|
||||||
|
check(result <= Int.MAX_VALUE) {
|
||||||
|
"JVM cannot handle more than 2GB payloads. Current length: $result"
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun intTo2BytesBigEndian(value: Int): ByteArray = byteArrayOf((value shr 8).toByte(), (value and 0xFF).toByte())
|
||||||
|
|
||||||
|
private fun intTo4BytesBigEndian(value: Int): ByteArray =
|
||||||
|
byteArrayOf(
|
||||||
|
(value shr 24).toByte(),
|
||||||
|
(value shr 16).toByte(),
|
||||||
|
(value shr 8).toByte(),
|
||||||
|
(value and 0xFF).toByte(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user